initial commit

This commit is contained in:
TopchetoEU 2023-08-05 18:37:18 +03:00
commit 2858d685ad
No known key found for this signature in database
GPG Key ID: 24E57B2E9C61AD19
151 changed files with 13448 additions and 0 deletions

9
.gitattributes vendored Normal file
View File

@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.vscode
.gradle
out
build
bin
/*.js

21
LICENESE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 TopchetoEU
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

34
README.md Normal file
View File

@ -0,0 +1,34 @@
# JScript
**NOTE: This had nothing to do with Microsoft's dialect of EcmaScript**
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
## Example
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
```java
var engine = new PolyfillEngine(new File("."));
var in = new BufferedReader(new InputStreamReader(System.in));
engine.start();
while (true) {
try {
var raw = in.readLine();
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
Values.printValue(engine.context(), res);
System.out.println();
}
catch (EngineException e) {
try {
System.out.println("Uncaught " + e.toString(engine.context()));
}
catch (InterruptedException _e) { return; }
}
catch (IOException | InterruptedException e) { return; }
}
```

144
files.txt Normal file
View File

@ -0,0 +1,144 @@
src/me/topchetoeu/jscript/compilation/AssignableStatement.java
src/me/topchetoeu/jscript/compilation/CompileOptions.java
src/me/topchetoeu/jscript/compilation/CompoundStatement.java
src/me/topchetoeu/jscript/compilation/control/BreakStatement.java
src/me/topchetoeu/jscript/compilation/control/ContinueStatement.java
src/me/topchetoeu/jscript/compilation/control/DebugStatement.java
src/me/topchetoeu/jscript/compilation/control/DeleteStatement.java
src/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java
src/me/topchetoeu/jscript/compilation/control/ForInStatement.java
src/me/topchetoeu/jscript/compilation/control/ForStatement.java
src/me/topchetoeu/jscript/compilation/control/IfStatement.java
src/me/topchetoeu/jscript/compilation/control/ReturnStatement.java
src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java
src/me/topchetoeu/jscript/compilation/control/ThrowStatement.java
src/me/topchetoeu/jscript/compilation/control/TryStatement.java
src/me/topchetoeu/jscript/compilation/control/WhileStatement.java
src/me/topchetoeu/jscript/compilation/DiscardStatement.java
src/me/topchetoeu/jscript/compilation/Instruction.java
src/me/topchetoeu/jscript/compilation/Statement.java
src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java
src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java
src/me/topchetoeu/jscript/compilation/values/CallStatement.java
src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java
src/me/topchetoeu/jscript/compilation/values/CommaStatement.java
src/me/topchetoeu/jscript/compilation/values/ConstantStatement.java
src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java
src/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java
src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java
src/me/topchetoeu/jscript/compilation/values/IndexStatement.java
src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java
src/me/topchetoeu/jscript/compilation/values/NewStatement.java
src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java
src/me/topchetoeu/jscript/compilation/values/OperationStatement.java
src/me/topchetoeu/jscript/compilation/values/RegexStatement.java
src/me/topchetoeu/jscript/compilation/values/TernaryStatement.java
src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java
src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java
src/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java
src/me/topchetoeu/jscript/compilation/values/VariableStatement.java
src/me/topchetoeu/jscript/compilation/values/VoidStatement.java
src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java
src/me/topchetoeu/jscript/engine/frame/CodeFrame.java
src/me/topchetoeu/jscript/engine/frame/ConvertHint.java
src/me/topchetoeu/jscript/engine/frame/InstructionResult.java
src/me/topchetoeu/jscript/engine/frame/Runners.java
src/me/topchetoeu/jscript/engine/BreakpointData.java
src/me/topchetoeu/jscript/engine/CallContext.java
src/me/topchetoeu/jscript/engine/debug/DebugServer.java
src/me/topchetoeu/jscript/engine/debug/DebugState.java
src/me/topchetoeu/jscript/engine/debug/handlers/DebuggerHandles.java
src/me/topchetoeu/jscript/engine/debug/Http.java
src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
src/me/topchetoeu/jscript/engine/debug/V8Error.java
src/me/topchetoeu/jscript/engine/debug/V8Event.java
src/me/topchetoeu/jscript/engine/debug/V8Message.java
src/me/topchetoeu/jscript/engine/debug/V8Result.java
src/me/topchetoeu/jscript/engine/debug/WebSocket.java
src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
src/me/topchetoeu/jscript/engine/DebugCommand.java
src/me/topchetoeu/jscript/engine/Engine.java
src/me/topchetoeu/jscript/engine/modules/FileModuleProvider.java
src/me/topchetoeu/jscript/engine/modules/Module.java
src/me/topchetoeu/jscript/engine/modules/ModuleManager.java
src/me/topchetoeu/jscript/engine/modules/ModuleProvider.java
src/me/topchetoeu/jscript/engine/scope/GlobalScope.java
src/me/topchetoeu/jscript/engine/scope/LocalScope.java
src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java
src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java
src/me/topchetoeu/jscript/engine/scope/ValueVariable.java
src/me/topchetoeu/jscript/engine/scope/Variable.java
src/me/topchetoeu/jscript/engine/values/ArrayValue.java
src/me/topchetoeu/jscript/engine/values/CodeFunction.java
src/me/topchetoeu/jscript/engine/values/FunctionValue.java
src/me/topchetoeu/jscript/engine/values/NativeFunction.java
src/me/topchetoeu/jscript/engine/values/NativeWrapper.java
src/me/topchetoeu/jscript/engine/values/ObjectValue.java
src/me/topchetoeu/jscript/engine/values/SignalValue.java
src/me/topchetoeu/jscript/engine/values/Symbol.java
src/me/topchetoeu/jscript/engine/values/Values.java
src/me/topchetoeu/jscript/events/Awaitable.java
src/me/topchetoeu/jscript/events/DataNotifier.java
src/me/topchetoeu/jscript/events/Event.java
src/me/topchetoeu/jscript/events/FinishedException.java
src/me/topchetoeu/jscript/events/Handle.java
src/me/topchetoeu/jscript/events/Notifier.java
src/me/topchetoeu/jscript/events/Observable.java
src/me/topchetoeu/jscript/events/Observer.java
src/me/topchetoeu/jscript/events/Pipe.java
src/me/topchetoeu/jscript/events/WarmObservable.java
src/me/topchetoeu/jscript/exceptions/EngineException.java
src/me/topchetoeu/jscript/exceptions/SyntaxException.java
src/me/topchetoeu/jscript/filesystem/File.java
src/me/topchetoeu/jscript/filesystem/Filesystem.java
src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java
src/me/topchetoeu/jscript/filesystem/MemoryFile.java
src/me/topchetoeu/jscript/filesystem/Permissions.java
src/me/topchetoeu/jscript/filesystem/PermissionsProvider.java
src/me/topchetoeu/jscript/filesystem/PhysicalFile.java
src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java
src/me/topchetoeu/jscript/interop/Native.java
src/me/topchetoeu/jscript/interop/NativeGetter.java
src/me/topchetoeu/jscript/interop/NativeSetter.java
src/me/topchetoeu/jscript/interop/NativeTypeRegister.java
src/me/topchetoeu/jscript/interop/Overload.java
src/me/topchetoeu/jscript/interop/OverloadFunction.java
src/me/topchetoeu/jscript/json/JSON.java
src/me/topchetoeu/jscript/json/JSONElement.java
src/me/topchetoeu/jscript/json/JSONList.java
src/me/topchetoeu/jscript/json/JSONMap.java
src/me/topchetoeu/jscript/Location.java
src/me/topchetoeu/jscript/Main.java
src/me/topchetoeu/jscript/MessageReceiver.java
src/me/topchetoeu/jscript/parsing/Operator.java
src/me/topchetoeu/jscript/parsing/ParseRes.java
src/me/topchetoeu/jscript/parsing/Parsing.java
src/me/topchetoeu/jscript/parsing/RawToken.java
src/me/topchetoeu/jscript/parsing/TestRes.java
src/me/topchetoeu/jscript/parsing/Token.java
src/me/topchetoeu/jscript/parsing/TokenType.java
src/me/topchetoeu/jscript/polyfills/Date.java
src/me/topchetoeu/jscript/polyfills/Internals.java
src/me/topchetoeu/jscript/polyfills/JSON.java
src/me/topchetoeu/jscript/polyfills/Map.java
src/me/topchetoeu/jscript/polyfills/Math.java
src/me/topchetoeu/jscript/polyfills/PolyfillEngine.java
src/me/topchetoeu/jscript/polyfills/Promise.java
src/me/topchetoeu/jscript/polyfills/RegExp.java
src/me/topchetoeu/jscript/polyfills/Set.java
src/me/topchetoeu/jscript/polyfills/TypescriptEngine.java
lib/core.ts
lib/iterators.ts
lib/map.ts
lib/promise.ts
lib/regex.ts
lib/require.ts
lib/set.ts
lib/values/array.ts
lib/values/boolean.ts
lib/values/errors.ts
lib/values/function.ts
lib/values/number.ts
lib/values/object.ts
lib/values/string.ts
lib/values/symbol.ts

193
lib/core.ts Normal file
View File

@ -0,0 +1,193 @@
type PropertyDescriptor<T, ThisT> = {
value: any;
writable?: boolean;
enumerable?: boolean;
configurable?: boolean;
} | {
get?(this: ThisT): T;
set(this: ThisT, val: T): void;
enumerable?: boolean;
configurable?: boolean;
} | {
get(this: ThisT): T;
set?(this: ThisT, val: T): void;
enumerable?: boolean;
configurable?: boolean;
};
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type Record<KeyT extends string | number | symbol, ValT> = { [x in KeyT]: ValT }
interface IArguments {
[i: number]: any;
length: number;
}
interface MathObject {
readonly E: number;
readonly PI: number;
readonly SQRT2: number;
readonly SQRT1_2: number;
readonly LN2: number;
readonly LN10: number;
readonly LOG2E: number;
readonly LOG10E: number;
asin(x: number): number;
acos(x: number): number;
atan(x: number): number;
atan2(y: number, x: number): number;
asinh(x: number): number;
acosh(x: number): number;
atanh(x: number): number;
sin(x: number): number;
cos(x: number): number;
tan(x: number): number;
sinh(x: number): number;
cosh(x: number): number;
tanh(x: number): number;
sqrt(x: number): number;
cbrt(x: number): number;
hypot(...vals: number[]): number;
imul(a: number, b: number): number;
exp(x: number): number;
expm1(x: number): number;
pow(x: number, y: number): number;
log(x: number): number;
log10(x: number): number;
log1p(x: number): number;
log2(x: number): number;
ceil(x: number): number;
floor(x: number): number;
round(x: number): number;
fround(x: number): number;
trunc(x: number): number;
abs(x: number): number;
max(...vals: number[]): number;
min(...vals: number[]): number;
sign(x: number): number;
random(): number;
clz32(x: number): number;
}
//@ts-ignore
declare const arguments: IArguments;
declare const Math: MathObject;
declare const NaN: number;
declare const Infinity: number;
declare var setTimeout: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
declare var setInterval: <T extends any[]>(handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number;
declare var clearTimeout: (id: number) => void;
declare var clearInterval: (id: number) => void;
/** @internal */
declare var internals: any;
/** @internal */
declare function run(file: string, pollute?: boolean): void;
/** @internal */
type ReplaceThis<T, ThisT> = T extends ((...args: infer ArgsT) => infer RetT) ?
((this: ThisT, ...args: ArgsT) => RetT) :
T;
/** @internal */
declare var setProps: <
TargetT extends object,
DescT extends { [x in Exclude<keyof TargetT, 'constructor'> ]?: ReplaceThis<TargetT[x], TargetT> }
>(target: TargetT, desc: DescT) => void;
/** @internal */
declare var setConstr: <ConstrT, T extends { constructor: ConstrT }>(target: T, constr: ConstrT) => void;
declare function log(...vals: any[]): void;
/** @internal */
declare var lgt: typeof globalThis, gt: typeof globalThis;
declare function assert(condition: () => unknown, message?: string): boolean;
gt.assert = (cond, msg) => {
try {
if (!cond()) throw 'condition not satisfied';
log('Passed ' + msg);
return true;
}
catch (e) {
log('Failed ' + msg + ' because of: ' + e);
return false;
}
}
try {
lgt.setProps = (target, desc) => {
var props = internals.keys(desc, false);
for (var i = 0; i < props.length; i++) {
var key = props[i];
internals.defineField(
target, key, (desc as any)[key],
true, // writable
false, // enumerable
true // configurable
);
}
}
lgt.setConstr = (target, constr) => {
internals.defineField(
target, 'constructor', constr,
true, // writable
false, // enumerable
true // configurable
);
}
run('values/object.js');
run('values/symbol.js');
run('values/function.js');
run('values/errors.js');
run('values/string.js');
run('values/number.js');
run('values/boolean.js');
run('values/array.js');
internals.special(Object, Function, Error, Array);
gt.setTimeout = (func, delay, ...args) => {
if (typeof func !== 'function') throw new TypeError("func must be a function.");
delay = (delay ?? 0) - 0;
return internals.setTimeout(() => func(...args), delay)
};
gt.setInterval = (func, delay, ...args) => {
if (typeof func !== 'function') throw new TypeError("func must be a function.");
delay = (delay ?? 0) - 0;
return internals.setInterval(() => func(...args), delay)
};
gt.clearTimeout = (id) => {
id = id | 0;
internals.clearTimeout(id);
};
gt.clearInterval = (id) => {
id = id | 0;
internals.clearInterval(id);
};
run('iterators.js');
run('promise.js');
run('map.js', true);
run('set.js', true);
run('regex.js');
run('require.js');
log('Loaded polyfills!');
}
catch (e: any) {
var err = 'Uncaught error while loading polyfills: ';
if (typeof Error !== 'undefined' && e instanceof Error && e.toString !== {}.toString) err += e;
else if ('message' in e) {
if ('name' in e) err += e.name + ": " + e.message;
else err += 'Error: ' + e.message;
}
else err += e;
}

216
lib/iterators.ts Normal file
View File

@ -0,0 +1,216 @@
interface SymbolConstructor {
readonly iterator: unique symbol;
readonly asyncIterator: unique symbol;
}
type IteratorYieldResult<TReturn> =
{ done?: false; } &
(TReturn extends undefined ? { value?: undefined; } : { value: TReturn; });
type IteratorReturnResult<TReturn> =
{ done: true } &
(TReturn extends undefined ? { value?: undefined; } : { value: TReturn; });
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface Iterator<T, TReturn = any, TNext = undefined> {
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>;
[Symbol.iterator](): Generator<T, TReturn, TNext>;
}
interface GeneratorFunction {
/**
* Creates a new Generator object.
* @param args A list of arguments the function accepts.
*/
new (...args: any[]): Generator;
/**
* Creates a new Generator object.
* @param args A list of arguments the function accepts.
*/
(...args: any[]): Generator;
/**
* The length of the arguments.
*/
readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: Generator;
}
interface GeneratorFunctionConstructor {
/**
* Creates a new Generator function.
* @param args A list of arguments the function accepts.
*/
new (...args: string[]): GeneratorFunction;
/**
* Creates a new Generator function.
* @param args A list of arguments the function accepts.
*/
(...args: string[]): GeneratorFunction;
/**
* The length of the arguments.
*/
readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string;
/**
* A reference to the prototype.
*/
}
interface AsyncIterator<T, TReturn = any, TNext = undefined> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
return?(value?: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw?(e?: any): Promise<IteratorResult<T, TReturn>>;
}
interface AsyncIterable<T> {
[Symbol.asyncIterator](): AsyncIterator<T>;
}
interface AsyncIterableIterator<T> extends AsyncIterator<T> {
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
}
interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
return(value: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw(e: any): Promise<IteratorResult<T, TReturn>>;
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
}
interface AsyncGeneratorFunction {
/**
* Creates a new AsyncGenerator object.
* @param args A list of arguments the function accepts.
*/
new (...args: any[]): AsyncGenerator;
/**
* Creates a new AsyncGenerator object.
* @param args A list of arguments the function accepts.
*/
(...args: any[]): AsyncGenerator;
/**
* The length of the arguments.
*/
readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: AsyncGenerator;
}
interface AsyncGeneratorFunctionConstructor {
/**
* Creates a new AsyncGenerator function.
* @param args A list of arguments the function accepts.
*/
new (...args: string[]): AsyncGeneratorFunction;
/**
* Creates a new AsyncGenerator function.
* @param args A list of arguments the function accepts.
*/
(...args: string[]): AsyncGeneratorFunction;
/**
* The length of the arguments.
*/
readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: AsyncGeneratorFunction;
}
interface Array<T> extends IterableIterator<T> {
entries(): IterableIterator<[number, T]>;
values(): IterableIterator<T>;
keys(): IterableIterator<number>;
}
setProps(Symbol, {
iterator: Symbol("Symbol.iterator") as any,
asyncIterator: Symbol("Symbol.asyncIterator") as any,
});
setProps(Array.prototype, {
[Symbol.iterator]: function() {
return this.values();
},
values() {
var i = 0;
return {
next: () => {
while (i < this.length) {
if (i++ in this) return { done: false, value: this[i - 1] };
}
return { done: true, value: undefined };
},
[Symbol.iterator]() { return this; }
};
},
keys() {
var i = 0;
return {
next: () => {
while (i < this.length) {
if (i++ in this) return { done: false, value: i - 1 };
}
return { done: true, value: undefined };
},
[Symbol.iterator]() { return this; }
};
},
entries() {
var i = 0;
return {
next: () => {
while (i < this.length) {
if (i++ in this) return { done: false, value: [i - 1, this[i - 1]] };
}
return { done: true, value: undefined };
},
[Symbol.iterator]() { return this; }
};
},
});

44
lib/map.ts Normal file
View File

@ -0,0 +1,44 @@
declare class Map<KeyT, ValueT> {
public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>;
public clear(): void;
public delete(key: KeyT): boolean;
public entries(): IterableIterator<[KeyT, ValueT]>;
public keys(): IterableIterator<KeyT>;
public values(): IterableIterator<ValueT>;
public get(key: KeyT): ValueT;
public set(key: KeyT, val: ValueT): this;
public has(key: KeyT): boolean;
public get size(): number;
public forEach(func: (key: KeyT, val: ValueT, map: Map<KeyT, ValueT>) => void, thisArg?: any): void;
public constructor();
}
Map.prototype[Symbol.iterator] = function() {
return this.entries();
};
var entries = Map.prototype.entries;
var keys = Map.prototype.keys;
var values = Map.prototype.values;
Map.prototype.entries = function() {
var it = entries.call(this);
it[Symbol.iterator] = () => it;
return it;
};
Map.prototype.keys = function() {
var it = keys.call(this);
it[Symbol.iterator] = () => it;
return it;
};
Map.prototype.values = function() {
var it = values.call(this);
it[Symbol.iterator] = () => it;
return it;
};

43
lib/promise.ts Normal file
View File

@ -0,0 +1,43 @@
type PromiseFulfillFunc<T> = (val: T) => void;
type PromiseThenFunc<T, NextT> = (val: T) => NextT;
type PromiseRejectFunc = (err: unknown) => void;
type PromiseFunc<T> = (resolve: PromiseFulfillFunc<T>, reject: PromiseRejectFunc) => void;
type PromiseResult<T> ={ type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; }
interface Thenable<T> {
then<NextT>(this: Promise<T>, onFulfilled: PromiseThenFunc<T, NextT>, onRejected?: PromiseRejectFunc): Promise<Awaited<NextT>>;
then(this: Promise<T>, onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise<T>;
}
// wippidy-wine, this code is now mine :D
type Awaited<T> =
T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable
T;
interface PromiseConstructor {
prototype: Promise<any>;
new <T>(func: PromiseFunc<T>): Promise<Awaited<T>>;
resolve<T>(val: T): Promise<Awaited<T>>;
reject(val: any): Promise<never>;
any<T>(promises: (Promise<T>|T)[]): Promise<T>;
race<T>(promises: (Promise<T>|T)[]): Promise<T>;
all<T extends any[]>(promises: T): Promise<{ [Key in keyof T]: Awaited<T[Key]> }>;
allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>;
}
interface Promise<T> extends Thenable<T> {
constructor: PromiseConstructor;
catch(func: PromiseRejectFunc): Promise<T>;
finally(func: () => void): Promise<T>;
}
declare var Promise: PromiseConstructor;
(Promise.prototype as any)[Symbol.typeName] = 'Promise';

211
lib/regex.ts Normal file
View File

@ -0,0 +1,211 @@
interface RegExpResultIndices extends Array<[number, number]> {
groups?: { [name: string]: [number, number]; };
}
interface RegExpResult extends Array<string> {
groups?: { [name: string]: string; };
index: number;
input: string;
indices?: RegExpResultIndices;
escape(raw: string, flags: string): RegExp;
}
interface SymbolConstructor {
readonly match: unique symbol;
readonly matchAll: unique symbol;
readonly split: unique symbol;
readonly replace: unique symbol;
readonly search: unique symbol;
}
type ReplaceFunc = (match: string, ...args: any[]) => string;
interface Matcher {
[Symbol.match](target: string): RegExpResult | string[] | null;
[Symbol.matchAll](target: string): IterableIterator<RegExpResult>;
}
interface Splitter {
[Symbol.split](target: string, limit?: number, sensible?: boolean): string[];
}
interface Replacer {
[Symbol.replace](target: string, replacement: string): string;
}
interface Searcher {
[Symbol.search](target: string, reverse?: boolean, start?: number): number;
}
declare class RegExp implements Matcher, Splitter, Replacer, Searcher {
static escape(raw: any, flags?: string): RegExp;
prototype: RegExp;
exec(val: string): RegExpResult | null;
test(val: string): boolean;
toString(): string;
[Symbol.match](target: string): RegExpResult | string[] | null;
[Symbol.matchAll](target: string): IterableIterator<RegExpResult>;
[Symbol.split](target: string, limit?: number, sensible?: boolean): string[];
[Symbol.replace](target: string, replacement: string | ReplaceFunc): string;
[Symbol.search](target: string, reverse?: boolean, start?: number): number;
readonly dotAll: boolean;
readonly global: boolean;
readonly hasIndices: boolean;
readonly ignoreCase: boolean;
readonly multiline: boolean;
readonly sticky: boolean;
readonly unicode: boolean;
readonly source: string;
readonly flags: string;
lastIndex: number;
constructor(pattern?: string, flags?: string);
constructor(pattern?: RegExp, flags?: string);
}
(Symbol as any).replace = Symbol('Symbol.replace');
(Symbol as any).match = Symbol('Symbol.match');
(Symbol as any).matchAll = Symbol('Symbol.matchAll');
(Symbol as any).split = Symbol('Symbol.split');
(Symbol as any).search = Symbol('Symbol.search');
setProps(RegExp.prototype, {
[Symbol.typeName]: 'RegExp',
test(val) {
return !!this.exec(val);
},
toString() {
return '/' + this.source + '/' + this.flags;
},
[Symbol.match](target) {
if (this.global) {
const res: string[] = [];
let val;
while (val = this.exec(target)) {
res.push(val[0]);
}
this.lastIndex = 0;
return res;
}
else {
const res = this.exec(target);
if (!this.sticky) this.lastIndex = 0;
return res;
}
},
[Symbol.matchAll](target) {
let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
return {
next: (): IteratorResult<RegExpResult, undefined> => {
const val = pattern?.exec(target);
if (val === null || val === undefined) {
pattern = undefined;
return { done: true };
}
else return { value: val };
},
[Symbol.iterator]() { return this; }
}
},
[Symbol.split](target, limit, sensible) {
const pattern = new this.constructor(this, this.flags + "g") as RegExp;
let match: RegExpResult | null;
let lastEnd = 0;
const res: string[] = [];
while ((match = pattern.exec(target)) !== null) {
let added: string[] = [];
if (match.index >= target.length) break;
if (match[0].length === 0) {
added = [ target.substring(lastEnd, pattern.lastIndex), ];
if (pattern.lastIndex < target.length) added.push(...match.slice(1));
}
else if (match.index - lastEnd > 0) {
added = [ target.substring(lastEnd, match.index), ...match.slice(1) ];
}
else {
for (let i = 1; i < match.length; i++) {
res[res.length - match.length + i] = match[i];
}
}
if (sensible) {
if (limit !== undefined && res.length + added.length >= limit) break;
else res.push(...added);
}
else {
for (let i = 0; i < added.length; i++) {
if (limit !== undefined && res.length >= limit) return res;
else res.push(added[i]);
}
}
lastEnd = pattern.lastIndex;
}
if (lastEnd < target.length) {
res.push(target.substring(lastEnd));
}
return res;
},
[Symbol.replace](target, replacement) {
const pattern = new this.constructor(this, this.flags + "d") as RegExp;
let match: RegExpResult | null;
let lastEnd = 0;
const res: string[] = [];
// log(pattern.toString());
while ((match = pattern.exec(target)) !== null) {
const indices = match.indices![0];
res.push(target.substring(lastEnd, indices[0]));
if (replacement instanceof Function) {
res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target));
}
else {
res.push(replacement);
}
lastEnd = indices[1];
if (!pattern.global) break;
}
if (lastEnd < target.length) {
res.push(target.substring(lastEnd));
}
return res.join('');
},
[Symbol.search](target, reverse, start) {
const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
if (!reverse) {
pattern.lastIndex = (start as any) | 0;
const res = pattern.exec(target);
if (res) return res.index;
else return -1;
}
else {
start ??= target.length;
start |= 0;
let res: RegExpResult | null = null;
while (true) {
const tmp = pattern.exec(target);
if (tmp === null || tmp.index > start) break;
res = tmp;
}
if (res && res.index <= start) return res.index;
else return -1;
}
},
});

15
lib/require.ts Normal file
View File

@ -0,0 +1,15 @@
type RequireFunc = (path: string) => any;
interface Module {
exports: any;
name: string;
}
declare var require: RequireFunc;
declare var exports: any;
declare var module: Module;
gt.require = function(path: string) {
if (typeof path !== 'string') path = path + '';
return internals.require(path);
};

45
lib/set.ts Normal file
View File

@ -0,0 +1,45 @@
declare class Set<T> {
public [Symbol.iterator](): IterableIterator<T>;
public entries(): IterableIterator<[T, T]>;
public keys(): IterableIterator<T>;
public values(): IterableIterator<T>;
public clear(): void;
public add(val: T): this;
public delete(val: T): boolean;
public has(key: T): boolean;
public get size(): number;
public forEach(func: (key: T, set: Set<T>) => void, thisArg?: any): void;
public constructor();
}
Set.prototype[Symbol.iterator] = function() {
return this.values();
};
(() => {
var entries = Set.prototype.entries;
var keys = Set.prototype.keys;
var values = Set.prototype.values;
Set.prototype.entries = function() {
var it = entries.call(this);
it[Symbol.iterator] = () => it;
return it;
};
Set.prototype.keys = function() {
var it = keys.call(this);
it[Symbol.iterator] = () => it;
return it;
};
Set.prototype.values = function() {
var it = values.call(this);
it[Symbol.iterator] = () => it;
return it;
};
})();

361
lib/values/array.ts Normal file
View File

@ -0,0 +1,361 @@
// god this is awful
type FlatArray<Arr, Depth extends number> = {
"done": Arr,
"recur": Arr extends Array<infer InnerArr>
? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
: Arr
}[Depth extends -1 ? "done" : "recur"];
interface Array<T> {
[i: number]: T;
constructor: ArrayConstructor;
length: number;
toString(): string;
// toLocaleString(): string;
join(separator?: string): string;
fill(val: T, start?: number, end?: number): T[];
pop(): T | undefined;
push(...items: T[]): number;
concat(...items: (T | T[])[]): T[];
concat(...items: (T | T[])[]): T[];
join(separator?: string): string;
reverse(): T[];
shift(): T | undefined;
slice(start?: number, end?: number): T[];
sort(compareFn?: (a: T, b: T) => number): this;
splice(start: number, deleteCount?: number | undefined, ...items: T[]): T[];
unshift(...items: T[]): number;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
includes(el: any, start?: number): boolean;
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
find(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[];
findIndex(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): number;
findLast(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[];
findLastIndex(predicate: (value: T, index: number, array: T[]) => boolean, thisArg?: any): number;
flat<D extends number = 1>(depth?: D): FlatArray<T, D>;
flatMap(func: (val: T, i: number, arr: T[]) => T | T[], thisAarg?: any): FlatArray<T[], 1>;
sort(func?: (a: T, b: T) => number): this;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
}
interface ArrayConstructor {
new <T>(arrayLength?: number): T[];
new <T>(...items: T[]): T[];
<T>(arrayLength?: number): T[];
<T>(...items: T[]): T[];
isArray(arg: any): arg is any[];
prototype: Array<any>;
}
declare var Array: ArrayConstructor;
gt.Array = function(len?: number) {
var res = [];
if (typeof len === 'number' && arguments.length === 1) {
if (len < 0) throw 'Invalid array length.';
res.length = len;
}
else {
for (var i = 0; i < arguments.length; i++) {
res[i] = arguments[i];
}
}
return res;
} as ArrayConstructor;
Array.prototype = ([] as any).__proto__ as Array<any>;
setConstr(Array.prototype, Array);
function wrapI(max: number, i: number) {
i |= 0;
if (i < 0) i = max + i;
return i;
}
function clampI(max: number, i: number) {
if (i < 0) i = 0;
if (i > max) i = max;
return i;
}
lgt.wrapI = wrapI;
lgt.clampI = clampI;
(Array.prototype as any)[Symbol.typeName] = "Array";
setProps(Array.prototype, {
concat() {
var res = [] as any[];
res.push.apply(res, this);
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (arg instanceof Array) {
res.push.apply(res, arg);
}
else {
res.push(arg);
}
}
return res;
},
every(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument not a function.");
func = func.bind(thisArg);
for (var i = 0; i < this.length; i++) {
if (!func(this[i], i, this)) return false;
}
return true;
},
some(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument not a function.");
func = func.bind(thisArg);
for (var i = 0; i < this.length; i++) {
if (func(this[i], i, this)) return true;
}
return false;
},
fill(val, start, end) {
if (arguments.length < 3) end = this.length;
if (arguments.length < 2) start = 0;
start = clampI(this.length, wrapI(this.length + 1, start ?? 0));
end = clampI(this.length, wrapI(this.length + 1, end ?? this.length));
for (; start < end; start++) {
this[start] = val;
}
return this;
},
filter(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
var res = [];
for (var i = 0; i < this.length; i++) {
if (i in this && func.call(thisArg, this[i], i, this)) res.push(this[i]);
}
return res;
},
find(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = 0; i < this.length; i++) {
if (i in this && func.call(thisArg, this[i], i, this)) return this[i];
}
return undefined;
},
findIndex(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = 0; i < this.length; i++) {
if (i in this && func.call(thisArg, this[i], i, this)) return i;
}
return -1;
},
findLast(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = this.length - 1; i >= 0; i--) {
if (i in this && func.call(thisArg, this[i], i, this)) return this[i];
}
return undefined;
},
findLastIndex(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
for (var i = this.length - 1; i >= 0; i--) {
if (i in this && func.call(thisArg, this[i], i, this)) return i;
}
return -1;
},
flat(depth) {
var res = [] as any[];
var buff = [];
res.push(...this);
for (var i = 0; i < (depth ?? 1); i++) {
var anyArrays = false;
for (var el of res) {
if (el instanceof Array) {
buff.push(...el);
anyArrays = true;
}
else buff.push(el);
}
res = buff;
buff = [];
if (!anyArrays) break;
}
return res;
},
flatMap(func, th) {
return this.map(func, th).flat();
},
forEach(func, thisArg) {
for (var i = 0; i < this.length; i++) {
if (i in this) func.call(thisArg, this[i], i, this);
}
},
map(func, thisArg) {
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
var res = [];
for (var i = 0; i < this.length; i++) {
if (i in this) res[i] = func.call(thisArg, this[i], i, this);
}
return res;
},
pop() {
if (this.length === 0) return undefined;
var val = this[this.length - 1];
this.length--;
return val;
},
push() {
for (var i = 0; i < arguments.length; i++) {
this[this.length] = arguments[i];
}
return arguments.length;
},
shift() {
if (this.length === 0) return undefined;
var res = this[0];
for (var i = 0; i < this.length - 1; i++) {
this[i] = this[i + 1];
}
this.length--;
return res;
},
unshift() {
for (var i = this.length - 1; i >= 0; i--) {
this[i + arguments.length] = this[i];
}
for (var i = 0; i < arguments.length; i++) {
this[i] = arguments[i];
}
return arguments.length;
},
slice(start, end) {
start = clampI(this.length, wrapI(this.length + 1, start ?? 0));
end = clampI(this.length, wrapI(this.length + 1, end ?? this.length));
var res: any[] = [];
var n = end - start;
if (n <= 0) return res;
for (var i = 0; i < n; i++) {
res[i] = this[start + i];
}
return res;
},
toString() {
let res = '';
for (let i = 0; i < this.length; i++) {
if (i > 0) res += ',';
if (i in this && this[i] !== undefined && this[i] !== null) res += this[i];
}
return res;
},
indexOf(el, start) {
start = start! | 0;
for (var i = Math.max(0, start); i < this.length; i++) {
if (i in this && this[i] == el) return i;
}
return -1;
},
includes(el, start) {
return this.indexOf(el, start) >= 0;
},
join(val = ',') {
let res = '', first = true;
for (let i = 0; i < this.length; i++) {
if (!(i in this)) continue;
if (!first) res += val;
first = false;
res += this[i];
}
return res;
},
sort(func) {
func ??= (a, b) => {
const _a = a + '';
const _b = b + '';
if (_a > _b) return 1;
if (_a < _b) return -1;
return 0;
};
if (typeof func !== 'function') throw new TypeError('Expected func to be undefined or a function.');
internals.sort(this, func);
return this;
},
splice(start, deleteCount, ...items) {
start = clampI(this.length, wrapI(this.length, start ?? 0));
deleteCount = (deleteCount ?? Infinity | 0);
if (start + deleteCount >= this.length) deleteCount = this.length - start;
const res = this.slice(start, start + deleteCount);
const moveN = items.length - deleteCount;
const len = this.length;
if (moveN < 0) {
for (let i = start - moveN; i < len; i++) {
this[i + moveN] = this[i];
}
}
else if (moveN > 0) {
for (let i = len - 1; i >= start; i--) {
this[i + moveN] = this[i];
}
}
for (let i = 0; i < items.length; i++) {
this[i + start] = items[i];
}
this.length = len + moveN;
return res;
}
});
setProps(Array, {
isArray(val: any) { return (val instanceof Array); }
});

22
lib/values/boolean.ts Normal file
View File

@ -0,0 +1,22 @@
interface Boolean {
valueOf(): boolean;
constructor: BooleanConstructor;
}
interface BooleanConstructor {
(val: any): boolean;
new (val: any): Boolean;
prototype: Boolean;
}
declare var Boolean: BooleanConstructor;
gt.Boolean = function (this: Boolean | undefined, arg) {
var val;
if (arguments.length === 0) val = false;
else val = !!arg;
if (this === undefined || this === null) return val;
else (this as any).value = val;
} as BooleanConstructor;
Boolean.prototype = (false as any).__proto__ as Boolean;
setConstr(Boolean.prototype, Boolean);

89
lib/values/errors.ts Normal file
View File

@ -0,0 +1,89 @@
interface Error {
constructor: ErrorConstructor;
name: string;
message: string;
stack: string[];
}
interface ErrorConstructor {
(msg?: any): Error;
new (msg?: any): Error;
prototype: Error;
}
interface TypeErrorConstructor extends ErrorConstructor {
(msg?: any): TypeError;
new (msg?: any): TypeError;
prototype: Error;
}
interface TypeError extends Error {
constructor: TypeErrorConstructor;
name: 'TypeError';
}
interface RangeErrorConstructor extends ErrorConstructor {
(msg?: any): RangeError;
new (msg?: any): RangeError;
prototype: Error;
}
interface RangeError extends Error {
constructor: RangeErrorConstructor;
name: 'RangeError';
}
interface SyntaxErrorConstructor extends ErrorConstructor {
(msg?: any): RangeError;
new (msg?: any): RangeError;
prototype: Error;
}
interface SyntaxError extends Error {
constructor: SyntaxErrorConstructor;
name: 'SyntaxError';
}
declare var Error: ErrorConstructor;
declare var RangeError: RangeErrorConstructor;
declare var TypeError: TypeErrorConstructor;
declare var SyntaxError: SyntaxErrorConstructor;
gt.Error = function Error(msg: string) {
if (msg === undefined) msg = '';
else msg += '';
return Object.setPrototypeOf({
message: msg,
stack: [] as string[],
}, Error.prototype);
} as ErrorConstructor;
Error.prototype = internals.err ?? {};
Error.prototype.name = 'Error';
setConstr(Error.prototype, Error);
Error.prototype.toString = function() {
if (!(this instanceof Error)) return '';
if (this.message === '') return this.name;
else return this.name + ': ' + this.message;
};
function makeError<T extends ErrorConstructor>(name: string, proto: any): T {
var err = function (msg: string) {
var res = new Error(msg);
(res as any).__proto__ = err.prototype;
return res;
} as T;
err.prototype = proto;
err.prototype.name = name;
setConstr(err.prototype, err as ErrorConstructor);
(err.prototype as any).__proto__ = Error.prototype;
(err as any).__proto__ = Error;
internals.special(err);
return err;
}
gt.RangeError = makeError('RangeError', internals.range ?? {});
gt.TypeError = makeError('TypeError', internals.type ?? {});
gt.SyntaxError = makeError('SyntaxError', internals.syntax ?? {});

78
lib/values/function.ts Normal file
View File

@ -0,0 +1,78 @@
interface Function {
apply(this: Function, thisArg: any, argArray?: any): any;
call(this: Function, thisArg: any, ...argArray: any[]): any;
bind(this: Function, thisArg: any, ...argArray: any[]): Function;
toString(): string;
prototype: any;
constructor: FunctionConstructor;
readonly length: number;
name: string;
}
interface FunctionConstructor extends Function {
(...args: string[]): (...args: any[]) => any;
new (...args: string[]): (...args: any[]) => any;
prototype: Function;
}
interface CallableFunction extends Function {
(...args: any[]): any;
apply<ThisArg, Args extends any[], RetT>(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, argArray?: Args): RetT;
call<ThisArg, Args extends any[], RetT>(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, ...argArray: Args): RetT;
bind<ThisArg, Args extends any[], Rest extends any[], RetT>(this: (this: ThisArg, ...args: [ ...Args, ...Rest ]) => RetT, thisArg: ThisArg, ...argArray: Args): (this: void, ...args: Rest) => RetT;
}
interface NewableFunction extends Function {
new(...args: any[]): any;
apply<Args extends any[], RetT>(this: new (...args: Args) => RetT, thisArg: any, argArray?: Args): RetT;
call<Args extends any[], RetT>(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): RetT;
bind<Args extends any[], RetT>(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): new (...args: Args) => RetT;
}
declare var Function: FunctionConstructor;
gt.Function = function() {
throw 'Using the constructor Function() is forbidden.';
} as unknown as FunctionConstructor;
Function.prototype = (Function as any).__proto__ as Function;
setConstr(Function.prototype, Function);
setProps(Function.prototype, {
apply(thisArg, args) {
if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.';
var len = args.length - 0;
var newArgs: any[] = [];
while (len >= 0) {
len--;
newArgs[len] = args[len];
}
return internals.apply(this, thisArg, newArgs);
},
call(thisArg, ...args) {
return this.apply(thisArg, args);
},
bind(thisArg, ...args) {
var func = this;
var res = function() {
var resArgs = [];
for (var i = 0; i < args.length; i++) {
resArgs[i] = args[i];
}
for (var i = 0; i < arguments.length; i++) {
resArgs[i + args.length] = arguments[i];
}
return func.apply(thisArg, resArgs);
};
res.name = "<bound> " + func.name;
return res;
},
toString() {
return 'function (...) { ... }';
},
});

46
lib/values/number.ts Normal file
View File

@ -0,0 +1,46 @@
interface Number {
toString(): string;
valueOf(): number;
constructor: NumberConstructor;
}
interface NumberConstructor {
(val: any): number;
new (val: any): Number;
prototype: Number;
parseInt(val: unknown): number;
parseFloat(val: unknown): number;
}
declare var Number: NumberConstructor;
declare const parseInt: typeof Number.parseInt;
declare const parseFloat: typeof Number.parseFloat;
gt.Number = function(this: Number | undefined, arg: any) {
var val;
if (arguments.length === 0) val = 0;
else val = arg - 0;
if (this === undefined || this === null) return val;
else (this as any).value = val;
} as NumberConstructor;
Number.prototype = (0 as any).__proto__ as Number;
setConstr(Number.prototype, Number);
setProps(Number.prototype, {
valueOf() {
if (typeof this === 'number') return this;
else return (this as any).value;
},
toString() {
if (typeof this === 'number') return this + '';
else return (this as any).value + '';
}
});
setProps(Number, {
parseInt(val) { return Math.trunc(Number.parseFloat(val)); },
parseFloat(val) { return internals.parseFloat(val); },
});
(gt as any).parseInt = Number.parseInt;
(gt as any).parseFloat = Number.parseFloat;

234
lib/values/object.ts Normal file
View File

@ -0,0 +1,234 @@
interface Object {
constructor: NewableFunction;
[Symbol.typeName]: string;
valueOf(): this;
toString(): string;
hasOwnProperty(key: any): boolean;
}
interface ObjectConstructor extends Function {
(arg: string): String;
(arg: number): Number;
(arg: boolean): Boolean;
(arg?: undefined | null): {};
<T extends object>(arg: T): T;
new (arg: string): String;
new (arg: number): Number;
new (arg: boolean): Boolean;
new (arg?: undefined | null): {};
new <T extends object>(arg: T): T;
prototype: Object;
assign<T extends object>(target: T, ...src: object[]): T;
create<T extends object>(proto: T, props?: { [key: string]: PropertyDescriptor<any, T> }): T;
keys<T extends object>(obj: T, onlyString?: true): (keyof T)[];
keys<T extends object>(obj: T, onlyString: false): any[];
entries<T extends object>(obj: T, onlyString?: true): [keyof T, T[keyof T]][];
entries<T extends object>(obj: T, onlyString: false): [any, any][];
values<T extends object>(obj: T, onlyString?: true): (T[keyof T])[];
values<T extends object>(obj: T, onlyString: false): any[];
fromEntries(entries: Iterable<[any, any]>): object;
defineProperty<T, ThisT extends object>(obj: ThisT, key: any, desc: PropertyDescriptor<T, ThisT>): ThisT;
defineProperties<ThisT extends object>(obj: ThisT, desc: { [key: string]: PropertyDescriptor<any, ThisT> }): ThisT;
getOwnPropertyNames<T extends object>(obj: T): (keyof T)[];
getOwnPropertySymbols<T extends object>(obj: T): (keyof T)[];
hasOwn<T extends object, KeyT>(obj: T, key: KeyT): boolean;
getOwnPropertyDescriptor<T extends object, KeyT extends keyof T>(obj: T, key: KeyT): PropertyDescriptor<T[KeyT], T>;
getOwnPropertyDescriptors<T extends object>(obj: T): { [x in keyof T]: PropertyDescriptor<T[x], T> };
getPrototypeOf(obj: any): object | null;
setPrototypeOf<T>(obj: T, proto: object | null): T;
preventExtensions<T extends object>(obj: T): T;
seal<T extends object>(obj: T): T;
freeze<T extends object>(obj: T): T;
isExtensible(obj: object): boolean;
isSealed(obj: object): boolean;
isFrozen(obj: object): boolean;
}
declare var Object: ObjectConstructor;
gt.Object = function(arg: any) {
if (arg === undefined || arg === null) return {};
else if (typeof arg === 'boolean') return new Boolean(arg);
else if (typeof arg === 'number') return new Number(arg);
else if (typeof arg === 'string') return new String(arg);
return arg;
} as ObjectConstructor;
Object.prototype = ({} as any).__proto__ as Object;
setConstr(Object.prototype, Object as any);
function throwNotObject(obj: any, name: string) {
if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') {
throw new TypeError(`Object.${name} may only be used for objects.`);
}
}
function check(obj: any) {
return typeof obj === 'object' && obj !== null || typeof obj === 'function';
}
setProps(Object, {
assign: function(dst, ...src) {
throwNotObject(dst, 'assign');
for (let i = 0; i < src.length; i++) {
const obj = src[i];
throwNotObject(obj, 'assign');
for (const key of Object.keys(obj)) {
(dst as any)[key] = (obj as any)[key];
}
}
return dst;
},
create(obj, props) {
props ??= {};
return Object.defineProperties({ __proto__: obj }, props as any) as any;
},
defineProperty(obj, key, attrib) {
throwNotObject(obj, 'defineProperty');
if (typeof attrib !== 'object') throw new TypeError('Expected attributes to be an object.');
if ('value' in attrib) {
if ('get' in attrib || 'set' in attrib) throw new TypeError('Cannot specify a value and accessors for a property.');
if (!internals.defineField(
obj, key,
attrib.value,
!!attrib.writable,
!!attrib.enumerable,
!!attrib.configurable
)) throw new TypeError('Can\'t define property \'' + key + '\'.');
}
else {
if (typeof attrib.get !== 'function' && attrib.get !== undefined) throw new TypeError('Get accessor must be a function.');
if (typeof attrib.set !== 'function' && attrib.set !== undefined) throw new TypeError('Set accessor must be a function.');
if (!internals.defineProp(
obj, key,
attrib.get,
attrib.set,
!!attrib.enumerable,
!!attrib.configurable
)) throw new TypeError('Can\'t define property \'' + key + '\'.');
}
return obj;
},
defineProperties(obj, attrib) {
throwNotObject(obj, 'defineProperties');
if (typeof attrib !== 'object' && typeof attrib !== 'function') throw 'Expected second argument to be an object.';
for (var key in attrib) {
Object.defineProperty(obj, key, attrib[key]);
}
return obj;
},
keys(obj, onlyString) {
onlyString = !!(onlyString ?? true);
return internals.keys(obj, onlyString);
},
entries(obj, onlyString) {
return Object.keys(obj, onlyString).map(v => [ v, (obj as any)[v] ]);
},
values(obj, onlyString) {
return Object.keys(obj, onlyString).map(v => (obj as any)[v]);
},
getOwnPropertyDescriptor(obj, key) {
return internals.ownProp(obj, key);
},
getOwnPropertyDescriptors(obj) {
return Object.fromEntries([
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
].map(v => [ v, Object.getOwnPropertyDescriptor(obj, v) ])) as any;
},
getOwnPropertyNames(obj) {
return internals.ownPropKeys(obj, false);
},
getOwnPropertySymbols(obj) {
return internals.ownPropKeys(obj, true);
},
hasOwn(obj, key) {
if (Object.getOwnPropertyNames(obj).includes(key)) return true;
if (Object.getOwnPropertySymbols(obj).includes(key)) return true;
return false;
},
getPrototypeOf(obj) {
return obj.__proto__;
},
setPrototypeOf(obj, proto) {
(obj as any).__proto__ = proto;
return obj;
},
fromEntries(iterable) {
const res = {} as any;
for (const el of iterable) {
res[el[0]] = el[1];
}
return res;
},
preventExtensions(obj) {
throwNotObject(obj, 'preventExtensions');
internals.preventExtensions(obj);
return obj;
},
seal(obj) {
throwNotObject(obj, 'seal');
internals.seal(obj);
return obj;
},
freeze(obj) {
throwNotObject(obj, 'freeze');
internals.freeze(obj);
return obj;
},
isExtensible(obj) {
if (!check(obj)) return false;
return internals.extensible(obj);
},
isSealed(obj) {
if (!check(obj)) return true;
if (Object.isExtensible(obj)) return false;
return Object.getOwnPropertyNames(obj).every(v => !Object.getOwnPropertyDescriptor(obj, v).configurable);
},
isFrozen(obj) {
if (!check(obj)) return true;
if (Object.isExtensible(obj)) return false;
return Object.getOwnPropertyNames(obj).every(v => {
var prop = Object.getOwnPropertyDescriptor(obj, v);
if ('writable' in prop && prop.writable) return false;
return !prop.configurable;
});
}
});
setProps(Object.prototype, {
valueOf() {
return this;
},
toString() {
return '[object ' + (this[Symbol.typeName] ?? 'Unknown') + ']';
},
hasOwnProperty(key) {
return Object.hasOwn(this, key);
},
});

261
lib/values/string.ts Normal file
View File

@ -0,0 +1,261 @@
interface Replacer {
[Symbol.replace](target: string, val: string | ((match: string, ...args: any[]) => string)): string;
}
interface String {
[i: number]: string;
toString(): string;
valueOf(): string;
charAt(pos: number): string;
charCodeAt(pos: number): number;
substring(start?: number, end?: number): string;
slice(start?: number, end?: number): string;
substr(start?: number, length?: number): string;
startsWith(str: string, pos?: number): string;
endsWith(str: string, pos?: number): string;
replace(pattern: string | Replacer, val: string): string;
replaceAll(pattern: string | Replacer, val: string): string;
match(pattern: string | Matcher): RegExpResult | string[] | null;
matchAll(pattern: string | Matcher): IterableIterator<RegExpResult>;
split(pattern: string | Splitter, limit?: number, sensible?: boolean): string;
concat(...others: string[]): string;
indexOf(term: string | Searcher, start?: number): number;
lastIndexOf(term: string | Searcher, start?: number): number;
toLowerCase(): string;
toUpperCase(): string;
trim(): string;
includes(term: string, start?: number): boolean;
length: number;
constructor: StringConstructor;
}
interface StringConstructor {
(val: any): string;
new (val: any): String;
fromCharCode(val: number): string;
prototype: String;
}
declare var String: StringConstructor;
gt.String = function(this: String | undefined, arg: any) {
var val;
if (arguments.length === 0) val = '';
else val = arg + '';
if (this === undefined || this === null) return val;
else (this as any).value = val;
} as StringConstructor;
String.prototype = ('' as any).__proto__ as String;
setConstr(String.prototype, String);
setProps(String.prototype, {
toString() {
if (typeof this === 'string') return this;
else return (this as any).value;
},
valueOf() {
if (typeof this === 'string') return this;
else return (this as any).value;
},
substring(start, end) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.substring(start, end);
else throw new Error('This function may be used only with primitive or object strings.');
}
start = start ?? 0 | 0;
end = (end ?? this.length) | 0;
return internals.substring(this, start, end);
},
substr(start, length) {
start = start ?? 0 | 0;
if (start >= this.length) start = this.length - 1;
if (start < 0) start = 0;
length = (length ?? this.length - start) | 0;
return this.substring(start, length + start);
},
toLowerCase() {
return internals.toLower(this + '');
},
toUpperCase() {
return internals.toUpper(this + '');
},
charAt(pos) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.charAt(pos);
else throw new Error('This function may be used only with primitive or object strings.');
}
pos = pos | 0;
if (pos < 0 || pos >= this.length) return '';
return this[pos];
},
charCodeAt(pos) {
var res = this.charAt(pos);
if (res === '') return NaN;
else return internals.toCharCode(res);
},
startsWith(term, pos) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.startsWith(term, pos);
else throw new Error('This function may be used only with primitive or object strings.');
}
pos = pos! | 0;
return internals.startsWith(this, term + '', pos);
},
endsWith(term, pos) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.endsWith(term, pos);
else throw new Error('This function may be used only with primitive or object strings.');
}
pos = (pos ?? this.length) | 0;
return internals.endsWith(this, term + '', pos);
},
indexOf(term: any, start) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.indexOf(term, start);
else throw new Error('This function may be used only with primitive or object strings.');
}
if (typeof term[Symbol.search] !== 'function') term = RegExp.escape(term);
return term[Symbol.search](this, false, start);
},
lastIndexOf(term: any, start) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.indexOf(term, start);
else throw new Error('This function may be used only with primitive or object strings.');
}
if (typeof term[Symbol.search] !== 'function') term = RegExp.escape(term);
return term[Symbol.search](this, true, start);
},
includes(term, start) {
return this.indexOf(term, start) >= 0;
},
replace(pattern: any, val) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.replace(pattern, val);
else throw new Error('This function may be used only with primitive or object strings.');
}
if (typeof pattern[Symbol.replace] !== 'function') pattern = RegExp.escape(pattern);
return pattern[Symbol.replace](this, val);
},
replaceAll(pattern: any, val) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.replace(pattern, val);
else throw new Error('This function may be used only with primitive or object strings.');
}
if (typeof pattern[Symbol.replace] !== 'function') pattern = RegExp.escape(pattern, "g");
if (pattern instanceof RegExp && !pattern.global) pattern = new pattern.constructor(pattern.source, pattern.flags + "g");
return pattern[Symbol.replace](this, val);
},
match(pattern: any) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.match(pattern);
else throw new Error('This function may be used only with primitive or object strings.');
}
if (typeof pattern[Symbol.match] !== 'function') pattern = RegExp.escape(pattern);
return pattern[Symbol.match](this);
},
matchAll(pattern: any) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.matchAll(pattern);
else throw new Error('This function may be used only with primitive or object strings.');
}
if (typeof pattern[Symbol.match] !== 'function') pattern = RegExp.escape(pattern, "g");
if (pattern instanceof RegExp && !pattern.global) pattern = new pattern.constructor(pattern.source, pattern.flags + "g");
return pattern[Symbol.match](this);
},
split(pattern: any, lim, sensible) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.split(pattern, lim, sensible);
else throw new Error('This function may be used only with primitive or object strings.');
}
if (typeof pattern[Symbol.split] !== 'function') pattern = RegExp.escape(pattern, "g");
return pattern[Symbol.split](this, lim, sensible);
},
slice(start, end) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.slice(start, end);
else throw new Error('This function may be used only with primitive or object strings.');
}
start = wrapI(this.length, start ?? 0 | 0);
end = wrapI(this.length, end ?? this.length | 0);
if (start > end) return '';
return this.substring(start, end);
},
concat(...args) {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.concat(...args);
else throw new Error('This function may be used only with primitive or object strings.');
}
var res = this;
for (var arg of args) res += arg;
return res;
},
trim() {
return this
.replace(/^\s+/g, '')
.replace(/\s+$/g, '');
}
});
setProps(String, {
fromCharCode(val) {
return internals.fromCharCode(val | 0);
},
})
Object.defineProperty(String.prototype, 'length', {
get() {
if (typeof this !== 'string') {
if (this instanceof String) return (this as any).value.length;
else throw new Error('This function may be used only with primitive or object strings.');
}
return internals.strlen(this);
},
configurable: true,
enumerable: false,
});

38
lib/values/symbol.ts Normal file
View File

@ -0,0 +1,38 @@
interface Symbol {
valueOf(): symbol;
constructor: SymbolConstructor;
}
interface SymbolConstructor {
(val?: any): symbol;
prototype: Symbol;
for(key: string): symbol;
keyFor(sym: symbol): string;
readonly typeName: unique symbol;
}
declare var Symbol: SymbolConstructor;
gt.Symbol = function(this: any, val?: string) {
if (this !== undefined && this !== null) throw new TypeError("Symbol may not be called with 'new'.");
if (typeof val !== 'string' && val !== undefined) throw new TypeError('val must be a string or undefined.');
return internals.symbol(val, true);
} as SymbolConstructor;
Symbol.prototype = internals.symbolProto;
setConstr(Symbol.prototype, Symbol);
(Symbol as any).typeName = Symbol("Symbol.name");
setProps(Symbol, {
for(key) {
if (typeof key !== 'string' && key !== undefined) throw new TypeError('key must be a string or undefined.');
return internals.symbol(key, false);
},
keyFor(sym) {
if (typeof sym !== 'symbol') throw new TypeError('sym must be a symbol.');
return internals.symStr(sym);
},
typeName: Symbol('Symbol.name') as any,
});
Object.defineProperty(Object.prototype, Symbol.typeName, { value: 'Object' });
Object.defineProperty(gt, Symbol.typeName, { value: 'Window' });

View File

@ -0,0 +1,63 @@
package me.topchetoeu.jscript;
public class Location {
public static final Location INTERNAL = new Location(0, 0, "<internal>");
private int line;
private int start;
private String filename;
public int line() { return line; }
public int start() { return start; }
public String filename() { return filename; }
@Override
public String toString() {
return filename + ":" + line + ":" + start;
}
public Location add(int n, boolean clone) {
if (clone) return new Location(line, start + n, filename);
this.start += n;
return this;
}
public Location add(int n) {
return add(n, false);
}
public Location nextLine() {
line++;
start = 0;
return this;
}
public Location clone() {
return new Location(line, start, filename);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + line;
result = prime * result + start;
result = prime * result + ((filename == null) ? 0 : filename.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Location other = (Location) obj;
if (line != other.line) return false;
if (start != other.start) return false;
if (filename == null && other.filename != null) return false;
else if (!filename.equals(other.filename)) return false;
return true;
}
public Location(int line, int start, String filename) {
this.line = line;
this.start = start;
this.filename = filename;
}
}

View File

@ -0,0 +1,104 @@
package me.topchetoeu.jscript;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.polyfills.PolyfillEngine;
public class Main {
static Thread task;
static Engine engine;
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) {
try {
Values.printValue(engine.context(), data);
}
catch (InterruptedException e) { }
System.out.println();
}
public void error(RuntimeException err) {
try {
if (err instanceof EngineException) {
System.out.println("Uncaught " + ((EngineException)err).toString(engine.context()));
}
else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
}
else if (err.getCause() instanceof InterruptedException) return;
else {
System.out.println("Internal error ocurred:");
err.printStackTrace();
}
}
catch (EngineException ex) {
System.out.println("Uncaught [error while converting to string]");
}
catch (InterruptedException ex) {
return;
}
}
};
public static void main(String args[]) {
var in = new BufferedReader(new InputStreamReader(System.in));
engine = new PolyfillEngine(new File("."));
var scope = engine.global().globalChild();
var exited = new boolean[1];
scope.define("exit", ctx -> {
exited[0] = true;
task.interrupt();
throw new InterruptedException();
});
task = engine.start();
var reader = new Thread(() -> {
try {
while (true) {
try {
var raw = in.readLine();
if (raw == null) break;
engine.pushMsg(false, scope, Map.of(), "<stdio>", raw, null).toObservable().once(valuePrinter);
}
catch (EngineException e) {
try {
System.out.println("Uncaught " + e.toString(engine.context()));
}
catch (EngineException ex) {
System.out.println("Uncaught [error while converting to string]");
}
}
}
}
catch (IOException e) {
e.printStackTrace();
return;
}
catch (SyntaxException ex) {
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
if (exited[0]) return;
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
catch (InterruptedException e) { return; }
if (exited[0]) return;
});
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
}
}

View File

@ -0,0 +1,6 @@
package me.topchetoeu.jscript;
public interface MessageReceiver {
void sendMessage(String msg);
void sendError(String msg);
}

View File

@ -0,0 +1,12 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.Type;
public abstract class AssignableStatement extends Statement {
public abstract Statement toAssign(Statement val, Type operation);
protected AssignableStatement(Location loc) {
super(loc);
}
}

View File

@ -0,0 +1,11 @@
package me.topchetoeu.jscript.compilation;
public class CompileOptions {
public final boolean emitBpMap;
public final boolean emitVarNames;
public CompileOptions(boolean emitBpMap, boolean emitVarNames) {
this.emitBpMap = emitBpMap;
this.emitVarNames = emitVarNames;
}
}

View File

@ -0,0 +1,77 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.control.ContinueStatement;
import me.topchetoeu.jscript.compilation.control.ReturnStatement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement {
public final Statement[] statements;
@Override
public boolean pollutesStack() {
for (var stm : statements) {
if (stm instanceof FunctionStatement) continue;
return true;
}
return false;
}
@Override
public void declare(ScopeRecord varsScope) {
for (var stm : statements) {
stm.declare(varsScope);
}
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
for (var stm : statements) {
if (stm instanceof FunctionStatement) {
int start = target.size();
((FunctionStatement)stm).compile(target, scope, null, true);
target.get(start).setDebug(true);
target.add(Instruction.discard());
}
}
for (var i = 0; i < statements.length; i++) {
var stm = statements[i];
if (stm instanceof FunctionStatement) continue;
if (i != statements.length - 1) stm.compileNoPollution(target, scope, true);
else stm.compileWithPollution(target, scope);
}
}
@Override
public Statement optimize() {
var res = new ArrayList<Statement>();
for (var i = 0; i < statements.length; i++) {
var stm = statements[i].optimize();
if (i < statements.length - 1 && stm.pure()) continue;
res.add(stm);
if (
stm instanceof ContinueStatement ||
stm instanceof ReturnStatement ||
stm instanceof ThrowStatement ||
stm instanceof ContinueStatement
) break;
}
if (res.size() == 1) return res.get(0);
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
}
public CompoundStatement(Location loc, Statement... statements) {
super(loc);
this.statements = statements;
}
}

View File

@ -0,0 +1,33 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DiscardStatement extends Statement {
public final Statement value;
@Override
public boolean pollutesStack() { return false; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (value == null) return;
value.compile(target, scope);
if (value.pollutesStack()) target.add(Instruction.discard());
}
@Override
public Statement optimize() {
if (value == null) return this;
var val = value.optimize();
if (val.pure()) return new ConstantStatement(loc(), null);
else return new DiscardStatement(loc(), val);
}
public DiscardStatement(Location loc, Statement val) {
super(loc);
this.value = val;
}
}

View File

@ -0,0 +1,285 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class Instruction {
public static enum Type {
RETURN,
SIGNAL,
THROW,
THROW_SYNTAX,
DELETE,
TRY,
NOP,
CALL,
CALL_NEW,
JMP_IF,
JMP_IFN,
JMP,
LOAD_VALUE,
LOAD_VAR,
LOAD_MEMBER,
LOAD_VAL_MEMBER,
LOAD_GLOB,
LOAD_FUNC,
LOAD_ARR,
LOAD_OBJ,
STORE_SELF_FUNC,
LOAD_REGEX,
DUP,
STORE_VAR,
STORE_MEMBER,
DISCARD,
MAKE_VAR,
DEF_PROP,
KEYS,
TYPEOF,
INSTANCEOF(true),
IN(true),
MULTIPLY(true),
DIVIDE(true),
MODULO(true),
ADD(true),
SUBTRACT(true),
USHIFT_RIGHT(true),
SHIFT_RIGHT(true),
SHIFT_LEFT(true),
GREATER(true),
LESS(true),
GREATER_EQUALS(true),
LESS_EQUALS(true),
LOOSE_EQUALS(true),
LOOSE_NOT_EQUALS(true),
EQUALS(true),
NOT_EQUALS(true),
AND(true),
OR(true),
XOR(true),
NEG(true),
POS(true),
NOT(true),
INVERSE(true);
final boolean isOperation;
private Type(boolean isOperation) {
this.isOperation = isOperation;
}
private Type() {
this(false);
}
}
public final Type type;
public final Object[] params;
public Location location;
public boolean debugged;
public Instruction locate(Location loc) {
this.location = loc;
return this;
}
public Instruction setDebug(boolean debug) {
debugged = debug;
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(int i) {
if (i >= params.length || i < 0) return null;
return (T)params[i];
}
public boolean match(Object ...args) {
if (args.length != params.length) return false;
for (int i = 0; i < args.length; i++) {
var a = params[i];
var b = args[i];
if (a == null || b == null) {
if (!(a == null && b == null)) return false;
}
if (!a.equals(b)) return false;
}
return true;
}
public boolean is(int i, Object arg) {
if (params.length <= i) return false;
return params[i].equals(arg);
}
private Instruction(Location location, Type type, Object... params) {
this.location = location;
this.type = type;
this.params = params;
}
public static Instruction tryInstr(boolean hasCatch, boolean hasFinally) {
return new Instruction(null, Type.TRY, hasCatch, hasFinally);
}
public static Instruction throwInstr() {
return new Instruction(null, Type.THROW);
}
public static Instruction throwSyntax(SyntaxException err) {
return new Instruction(null, Type.THROW_SYNTAX, err.getMessage());
}
public static Instruction delete() {
return new Instruction(null, Type.DELETE);
}
public static Instruction ret() {
return new Instruction(null, Type.RETURN);
}
public static Instruction debug() {
return new Instruction(null, Type.NOP, "debug");
}
public static Instruction debugVarNames(String[] names) {
var args = new Object[names.length + 1];
args[0] = "dbg_vars";
System.arraycopy(names, 0, args, 1, names.length);
return new Instruction(null, Type.NOP, args);
}
/**
* ATTENTION: Usage outside of try/catch is broken af
*/
public static Instruction signal(String name) {
return new Instruction(null, Type.SIGNAL, name);
}
public static Instruction nop(Object ...params) {
for (var param : params) {
if (param instanceof String) continue;
if (param instanceof Boolean) continue;
if (param instanceof Double) continue;
if (param instanceof Integer) continue;
if (param == null) continue;
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls.");
}
return new Instruction(null, Type.NOP, params);
}
public static Instruction call(int argn) {
return new Instruction(null, Type.CALL, argn);
}
public static Instruction callNew(int argn) {
return new Instruction(null, Type.CALL_NEW, argn);
}
public static Instruction jmp(int offset) {
return new Instruction(null, Type.JMP, offset);
}
public static Instruction jmpIf(int offset) {
return new Instruction(null, Type.JMP_IF, offset);
}
public static Instruction jmpIfNot(int offset) {
return new Instruction(null, Type.JMP_IFN, offset);
}
public static Instruction loadValue(Object val) {
return new Instruction(null, Type.LOAD_VALUE, val);
}
public static Instruction makeVar(String name) {
return new Instruction(null, Type.MAKE_VAR, name);
}
public static Instruction loadVar(Object i) {
return new Instruction(null, Type.LOAD_VAR, i);
}
public static Instruction loadGlob() {
return new Instruction(null, Type.LOAD_GLOB);
}
public static Instruction loadMember() {
return new Instruction(null, Type.LOAD_MEMBER);
}
public static Instruction loadMember(Object key) {
if (key instanceof Number) key = ((Number)key).doubleValue();
return new Instruction(null, Type.LOAD_VAL_MEMBER, key);
}
public static Instruction loadRegex(String pattern, String flags) {
return new Instruction(null, Type.LOAD_REGEX, pattern, flags);
}
public static Instruction loadFunc(int instrN, int varN, int len, int[] captures) {
var args = new Object[3 + captures.length];
args[0] = instrN;
args[1] = varN;
args[2] = len;
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
return new Instruction(null, Type.LOAD_FUNC, args);
}
public static Instruction loadObj() {
return new Instruction(null, Type.LOAD_OBJ);
}
public static Instruction loadArr(int count) {
return new Instruction(null, Type.LOAD_ARR, count);
}
public static Instruction dup(int count) {
return new Instruction(null, Type.DUP, count, 0);
}
public static Instruction dup(int count, int offset) {
return new Instruction(null, Type.DUP, count, offset);
}
public static Instruction storeSelfFunc(int i) {
return new Instruction(null, Type.STORE_SELF_FUNC, i);
}
public static Instruction storeVar(Object i) {
return new Instruction(null, Type.STORE_VAR, i, false);
}
public static Instruction storeVar(Object i, boolean keep) {
return new Instruction(null, Type.STORE_VAR, i, keep);
}
public static Instruction storeMember() {
return new Instruction(null, Type.STORE_MEMBER, false);
}
public static Instruction storeMember(boolean keep) {
return new Instruction(null, Type.STORE_MEMBER, keep);
}
public static Instruction discard() {
return new Instruction(null, Type.DISCARD);
}
public static Instruction typeof() {
return new Instruction(null, Type.TYPEOF);
}
public static Instruction typeof(String varName) {
return new Instruction(null, Type.TYPEOF, varName);
}
public static Instruction keys() {
return new Instruction(null, Type.KEYS);
}
public static Instruction defProp() {
return new Instruction(null, Type.DEF_PROP);
}
public static Instruction operation(Type op) {
if (!op.isOperation) throw new IllegalArgumentException("The instruction type %s is not an operation.".formatted(op));
return new Instruction(null, op);
}
@Override
public String toString() {
var res = type.toString();
for (int i = 0; i < params.length; i++) {
res += " " + params[i];
}
return res;
}
}

View File

@ -0,0 +1,42 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public abstract class Statement {
private Location _loc;
public abstract boolean pollutesStack();
public boolean pure() { return false; }
public abstract void compile(List<Instruction> target, ScopeRecord scope);
public void declare(ScopeRecord varsScope) { }
public Statement optimize() { return this; }
public void compileNoPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
int start = target.size();
compile(target, scope);
if (debug && target.size() != start) target.get(start).setDebug(true);
if (pollutesStack()) target.add(Instruction.discard().locate(loc()));
}
public void compileWithPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
int start = target.size();
compile(target, scope);
if (debug && target.size() != start) target.get(start).setDebug(true);
if (!pollutesStack()) target.add(Instruction.loadValue(null).locate(loc()));
}
public void compileNoPollution(List<Instruction> target, ScopeRecord scope) {
compileNoPollution(target, scope, false);
}
public void compileWithPollution(List<Instruction> target, ScopeRecord scope) {
compileWithPollution(target, scope, false);
}
public Location loc() { return _loc; }
public void setLoc(Location loc) { _loc = loc; }
protected Statement(Location loc) {
this._loc = loc;
}
}

View File

@ -0,0 +1,44 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableDeclareStatement extends Statement {
public static record Pair(String name, Statement value) {}
public final List<Pair> values;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord varsScope) {
for (var key : values) {
varsScope.define(key.name());
}
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
for (var entry : values) {
if (entry.name() == null) continue;
var key = scope.getKey(entry.name());
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
if (entry.value() instanceof FunctionStatement) {
((FunctionStatement)entry.value()).compile(target, scope, entry.name(), false);
target.add(Instruction.storeVar(key).locate(loc()));
}
else if (entry.value() != null) {
entry.value().compileWithPollution(target, scope);
target.add(Instruction.storeVar(key).locate(loc()));
}
}
}
public VariableDeclareStatement(Location loc, List<Pair> values) {
super(loc);
this.values = values;
}
}

View File

@ -0,0 +1,25 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class BreakStatement extends Statement {
public final String label;
@Override
public boolean pollutesStack() { return false; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.nop("break", label).locate(loc()));
}
public BreakStatement(Location loc, String label) {
super(loc);
this.label = label;
}
}

View File

@ -0,0 +1,25 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ContinueStatement extends Statement {
public final String label;
@Override
public boolean pollutesStack() { return false; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.nop("cont", label).locate(loc()));
}
public ContinueStatement(Location loc, String label) {
super(loc);
this.label = label;
}
}

View File

@ -0,0 +1,22 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DebugStatement extends Statement {
@Override
public boolean pollutesStack() { return false; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.debug().locate(loc()));
}
public DebugStatement(Location loc) {
super(loc);
}
}

View File

@ -0,0 +1,29 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class DeleteStatement extends Statement {
public final Statement key;
public final Statement value;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
value.compile(target, scope);
key.compile(target, scope);
target.add(Instruction.delete().locate(loc()));
}
public DeleteStatement(Location loc, Statement key, Statement value) {
super(loc);
this.key = key;
this.value = value;
}
}

View File

@ -0,0 +1,82 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class DoWhileStatement extends Statement {
public final Statement condition, body;
public final String label;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord globScope) {
body.declare(globScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (condition instanceof ConstantStatement) {
int start = target.size();
body.compileNoPollution(target, scope);
int end = target.size();
if (Values.toBoolean(((ConstantStatement)condition).value)) {
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
}
else {
target.add(Instruction.jmp(start - end).locate(loc()));
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
}
return;
}
int start = target.size();
body.compileNoPollution(target, scope, true);
int mid = target.size();
condition.compileWithPollution(target, scope);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
target.add(Instruction.jmpIf(start - end).locate(loc()));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
if (b instanceof CompoundStatement) {
var comp = (CompoundStatement)b;
if (comp.statements.length > 0) {
var last = comp.statements[comp.statements.length - 1];
if (last instanceof ContinueStatement) comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
if (last instanceof BreakStatement) {
comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
return new CompoundStatement(loc());
}
}
}
else if (b instanceof ContinueStatement) {
b = new CompoundStatement(loc());
}
else if (b instanceof BreakStatement) return new CompoundStatement(loc());
if (b.pure()) return new DoWhileStatement(loc(), label, cond, new CompoundStatement(loc()));
else return new DoWhileStatement(loc(), label, cond, b);
}
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
}

View File

@ -0,0 +1,81 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ForInStatement extends Statement {
public final String varName;
public final boolean isDeclaration;
public final Statement varValue, object, body;
public final String label;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord globScope) {
body.declare(globScope);
if (isDeclaration) globScope.define(varName);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
var key = scope.getKey(varName);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
if (varValue != null) {
varValue.compileWithPollution(target, scope);
target.add(Instruction.storeVar(scope.getKey(varName)));
}
object.compileWithPollution(target, scope);
target.add(Instruction.keys());
int start = target.size();
target.add(Instruction.dup(1));
target.add(Instruction.loadMember("length"));
target.add(Instruction.loadValue(0));
target.add(Instruction.operation(Type.LESS_EQUALS));
int mid = target.size();
target.add(Instruction.nop());
target.add(Instruction.dup(2));
target.add(Instruction.loadMember("length"));
target.add(Instruction.loadValue(1));
target.add(Instruction.operation(Type.SUBTRACT));
target.add(Instruction.dup(1, 2));
target.add(Instruction.loadValue("length"));
target.add(Instruction.dup(1, 2));
target.add(Instruction.storeMember());
target.add(Instruction.loadMember());
target.add(Instruction.storeVar(key));
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
body.compileNoPollution(target, scope, true);
int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc()));
target.add(Instruction.discard().locate(loc()));
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc()));
}
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
super(loc);
this.label = label;
this.isDeclaration = isDecl;
this.varName = varName;
this.varValue = varValue;
this.object = object;
this.body = body;
}
}

View File

@ -0,0 +1,97 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class ForStatement extends Statement {
public final Statement declaration, assignment, condition, body;
public final String label;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord globScope) {
declaration.declare(globScope);
body.declare(globScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
declaration.compile(target, scope);
if (condition instanceof ConstantStatement) {
if (Values.toBoolean(((ConstantStatement)condition).value)) {
int start = target.size();
body.compileNoPollution(target, scope);
int mid = target.size();
assignment.compileNoPollution(target, scope, true);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
target.add(Instruction.jmp(start - target.size()).locate(loc()));
return;
}
}
int start = target.size();
condition.compileWithPollution(target, scope);
int mid = target.size();
target.add(Instruction.nop());
body.compileNoPollution(target, scope);
int beforeAssign = target.size();
assignment.compile(target, scope);
int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
target.add(Instruction.jmp(start - end).locate(loc()));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
}
@Override
public Statement optimize() {
var decl = declaration.optimize();
var asgn = assignment.optimize();
var cond = condition.optimize();
var b = body.optimize();
if (asgn.pure()) {
if (decl.pure()) return new WhileStatement(loc(), label, cond, b).optimize();
else return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, b)
).optimize();
}
else if (b instanceof ContinueStatement) return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, new CompoundStatement(loc(), b, asgn))
);
else if (b instanceof BreakStatement) return decl;
if (b.pure()) return new ForStatement(loc(), label, decl, cond, asgn, new CompoundStatement(null));
else return new ForStatement(loc(), label, decl, cond, asgn, b);
}
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
super(loc);
this.label = label;
this.declaration = declaration;
this.condition = condition;
this.assignment = assignment;
this.body = body;
}
public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
return new CompoundStatement(loc,
declaration,
new WhileStatement(loc, label, condition, new CompoundStatement(loc,
body,
increment
))
);
}
}

View File

@ -0,0 +1,80 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.DiscardStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class IfStatement extends Statement {
public final Statement condition, body, elseBody;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord globScope) {
body.declare(globScope);
if (elseBody != null) elseBody.declare(globScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (condition instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)condition).value)) {
if (elseBody != null) elseBody.compileNoPollution(target, scope, true);
}
else {
body.compileNoPollution(target, scope, true);
}
return;
}
condition.compileWithPollution(target, scope);
if (elseBody == null) {
int i = target.size();
target.add(Instruction.nop());
body.compileNoPollution(target, scope, true);
int endI = target.size();
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc()));
}
else {
int start = target.size();
target.add(Instruction.nop());
body.compileNoPollution(target, scope, true);
target.add(Instruction.nop());
int mid = target.size();
elseBody.compileNoPollution(target, scope, true);
int end = target.size();
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc()));
}
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
var e = elseBody == null ? null : elseBody.optimize();
if (b.pure()) b = new CompoundStatement(null);
if (e != null && e.pure()) e = null;
if (b.pure() && e == null) return new DiscardStatement(loc(), cond).optimize();
else return new IfStatement(loc(), cond, b, e);
}
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
super(loc);
this.condition = condition;
this.body = body;
this.elseBody = elseBody;
}
}

View File

@ -0,0 +1,27 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ReturnStatement extends Statement {
public final Statement value;
@Override
public boolean pollutesStack() { return false; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
else value.compileWithPollution(target, scope);
target.add(Instruction.ret().locate(loc()));
}
public ReturnStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@ -0,0 +1,81 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.HashMap;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class SwitchStatement extends Statement {
public static record SwitchCase(Statement value, int statementI) {}
@Override
public boolean pollutesStack() { return false; }
public final Statement value;
public final SwitchCase[] cases;
public final Statement[] body;
public final int defaultI;
@Override
public void declare(ScopeRecord varsScope) {
for (var stm : body) stm.declare(varsScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
var caseMap = new HashMap<Integer, Integer>();
var stmIndexMap = new HashMap<Integer, Integer>();
value.compile(target, scope);
for (var ccase : cases) {
target.add(Instruction.dup(1).locate(loc()));
ccase.value.compileWithPollution(target, scope);
target.add(Instruction.operation(Type.EQUALS).locate(loc()));
caseMap.put(target.size(), ccase.statementI);
target.add(Instruction.nop());
}
int start = target.size();
target.add(Instruction.nop());
for (var stm : body) {
stmIndexMap.put(stmIndexMap.size(), target.size());
stm.compileNoPollution(target, scope, true);
}
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc());
for (int i = start; i < target.size(); i++) {
var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
target.set(i, Instruction.jmp(target.size() - i).locate(instr.location));
}
if (instr.type == Type.NOP && instr.is(0, "try_break") && instr.get(1) == null) {
target.set(i, Instruction.signal("jmp_" + (target.size() - (Integer)instr.get(2))).locate(instr.location));
}
}
for (var el : caseMap.entrySet()) {
var loc = target.get(el.getKey()).location;
var i = stmIndexMap.get(el.getValue());
if (i == null) i = target.size();
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true));
}
target.add(Instruction.discard().locate(loc()));
}
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
super(loc);
this.value = value;
this.defaultI = defaultI;
this.cases = cases;
this.body = body;
}
}

View File

@ -0,0 +1,26 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ThrowStatement extends Statement {
public final Statement value;
@Override
public boolean pollutesStack() { return false; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
value.compileWithPollution(target, scope);
target.add(Instruction.throwInstr().locate(loc()));
}
public ThrowStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@ -0,0 +1,87 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class TryStatement extends Statement {
public final Statement tryBody;
public final Statement catchBody;
public final Statement finallyBody;
public final String name;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord globScope) {
tryBody.declare(globScope);
if (catchBody != null) catchBody.declare(globScope);
if (finallyBody != null) finallyBody.declare(globScope);
}
private void compileBody(List<Instruction> target, ScopeRecord scope, Statement body, String arg) {
var subscope = scope.child();
int start = target.size();
target.add(Instruction.nop());
subscope.define("this");
var argsVar = subscope.define("<catchargs>");
if (arg != null) {
target.add(Instruction.loadVar(argsVar));
target.add(Instruction.loadMember(0));
target.add(Instruction.storeVar(subscope.define(arg)));
}
int bodyStart = target.size();
body.compile(target, subscope);
target.add(Instruction.signal("no_return"));
target.get(bodyStart).locate(body.loc());
target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), 0, subscope.getCaptures()));
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
int start = target.size();
compileBody(target, scope, tryBody, null);
if (catchBody != null) {
compileBody(target, scope, catchBody, name);
}
if (finallyBody != null) {
compileBody(target, scope, finallyBody, null);
}
for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) {
var instr = target.get(i);
if (instr.is(0, "break")) {
target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location));
}
else if (instr.is(0, "cont")) {
target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location));
}
}
}
target.add(Instruction.tryInstr(catchBody != null, finallyBody != null).locate(loc()));
}
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
super(loc);
this.tryBody = tryBody;
this.catchBody = catchBody;
this.finallyBody = finallyBody;
this.name = name;
}
}

View File

@ -0,0 +1,104 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.DiscardStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class WhileStatement extends Statement {
public final Statement condition, body;
public final String label;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord globScope) {
body.declare(globScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (condition instanceof ConstantStatement) {
if (Values.toBoolean(((ConstantStatement)condition).value)) {
int start = target.size();
body.compileNoPollution(target, scope);
int end = target.size();
replaceBreaks(target, label, start, end, start, end + 1);
target.add(Instruction.jmp(start - target.size()).locate(loc()));
return;
}
}
int start = target.size();
condition.compileWithPollution(target, scope);
int mid = target.size();
target.add(Instruction.nop());
body.compileNoPollution(target, scope);
int end = target.size();
replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc()));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
if (b instanceof ContinueStatement) {
b = new CompoundStatement(loc());
}
else if (b instanceof BreakStatement) return new DiscardStatement(loc(), cond).optimize();
if (b.pure()) return new WhileStatement(loc(), label, cond, new CompoundStatement(null));
else return new WhileStatement(loc(), label, cond, b);
}
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
super(loc);
this.label = label;
this.condition = condition;
this.body = body;
}
public static void replaceBreaks(List<Instruction> target, String label, int start, int end, int continuePoint, int breakPoint) {
for (int i = start; i < end; i++) {
var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.jmp(continuePoint - i));
target.get(i).location = instr.location;
}
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.jmp(breakPoint - i));
target.get(i).location = instr.location;
}
if (instr.type == Type.NOP && instr.is(0, "try_cont") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.signal("jmp_" + (continuePoint - (Integer)instr.get(2))));
target.get(i).location = instr.location;
}
if (instr.type == Type.NOP && instr.is(0, "try_break") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.signal("jmp_" + (breakPoint - (Integer)instr.get(2))));
target.get(i).location = instr.location;
}
}
}
// public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
// return new CompoundStatement(loc,
// declaration,
// new WhileStatement(loc, label, condition, new CompoundStatement(loc,
// body,
// increment
// ))
// );
// }
}

View File

@ -0,0 +1,37 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ArrayStatement extends Statement {
public final Statement[] statements;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadArr(statements.length).locate(loc()));
var i = 0;
for (var el : statements) {
if (el != null) {
target.add(Instruction.dup(1).locate(loc()));
target.add(Instruction.loadValue(i).locate(loc()));
el.compileWithPollution(target, scope);
target.add(Instruction.storeMember().locate(loc()));
}
i++;
}
}
public ArrayStatement(Location loc, Statement[] statements) {
super(loc);
this.statements = statements;
}
}

View File

@ -0,0 +1,44 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CallStatement extends Statement {
public final Statement func;
public final Statement[] args;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, scope, true);
}
else {
target.add(Instruction.loadValue(null).locate(loc()));
func.compileWithPollution(target, scope);
}
for (var arg : args) {
arg.compileWithPollution(target, scope);
}
target.add(Instruction.call(args.length).locate(loc()).setDebug(true));
}
public CallStatement(Location loc, Statement func, Statement... args) {
super(loc);
this.func = func;
this.args = args;
}
public CallStatement(Location loc, Statement obj, Object key, Statement... args) {
super(loc);
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
this.args = args;
}
}

View File

@ -0,0 +1,35 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ChangeStatement extends Statement {
public final AssignableStatement value;
public final double addAmount;
public final boolean postfix;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Type.SUBTRACT).compileWithPollution(target, scope);
if (postfix) {
target.add(Instruction.loadValue(addAmount).locate(loc()));
target.add(Instruction.operation(Type.SUBTRACT).locate(loc()));
}
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
super(loc);
this.value = value;
this.addAmount = addAmount;
this.postfix = postfix;
}
}

View File

@ -0,0 +1,38 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CommaStatement extends Statement {
public final Statement first;
public final Statement second;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
first.compileNoPollution(target, scope);
second.compileWithPollution(target, scope);
}
@Override
public Statement optimize() {
var f = first.optimize();
var s = second.optimize();
if (f.pure()) return s;
else return new CommaStatement(loc(), f, s);
}
public CommaStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

@ -0,0 +1,27 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ConstantStatement extends Statement {
public final Object value;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadValue(value).locate(loc()));
}
public ConstantStatement(Location loc, Object val) {
super(loc);
this.value = val;
}
}

View File

@ -0,0 +1,110 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class FunctionStatement extends Statement {
public final CompoundStatement body;
public final String name;
public final String[] args;
@Override
public boolean pure() { return name == null; }
@Override
public boolean pollutesStack() { return true; }
@Override
public void declare(ScopeRecord scope) {
if (name != null) scope.define(name);
}
public static void checkBreakAndCont(List<Instruction> target, int start) {
for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) {
if (target.get(i).is(0, "break") || target.get(i).is(0, "try_break")) {
throw new SyntaxException(target.get(i).location, "Break was placed outside a loop.");
}
if (target.get(i).is(0, "cont") || target.get(i).is(0, "try_cont")) {
throw new SyntaxException(target.get(i).location, "Continue was placed outside a loop.");
}
}
}
}
public void compile(List<Instruction> target, ScopeRecord scope, String name, boolean isStatement) {
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])){
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.")));
return;
}
}
}
var subscope = scope.child();
int start = target.size();
target.add(Instruction.nop());
subscope.define("this");
var argsVar = subscope.define("arguments");
if (args.length > 0) {
target.add(Instruction.loadVar(argsVar).locate(loc()));
if (args.length != 1) target.add(Instruction.dup(args.length - 1).locate(loc()));
for (var i = 0; i < args.length; i++) {
target.add(Instruction.loadMember(i).locate(loc()));
target.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
}
}
if (!isStatement && this.name != null) {
target.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
}
body.declare(subscope);
target.add(Instruction.debugVarNames(subscope.locals()));
body.compile(target, subscope);
checkBreakAndCont(target, start);
if (!(body instanceof CompoundStatement)) target.add(Instruction.ret().locate(loc()));
target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
if (name == null) name = this.name;
if (name != null) {
target.add(Instruction.dup(1).locate(loc()));
target.add(Instruction.loadValue("name").locate(loc()));
target.add(Instruction.loadValue(name).locate(loc()));
target.add(Instruction.storeMember().locate(loc()));
}
if (this.name != null && isStatement) {
var key = scope.getKey(this.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
target.add(Instruction.storeVar(scope.getKey(this.name), true).locate(loc()));
}
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
compile(target, scope, null, false);
}
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
super(loc);
this.name = name;
this.args = args;
this.body = body;
}
}

View File

@ -0,0 +1,24 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class GlobalThisStatement extends Statement {
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadGlob().locate(loc()));
}
public GlobalThisStatement(Location loc) {
super(loc);
}
}

View File

@ -0,0 +1,51 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class IndexAssignStatement extends Statement {
public final Statement object;
public final Statement index;
public final Statement value;
public final Type operation;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
int start = 0;
if (operation != null) {
object.compileWithPollution(target, scope);
index.compileWithPollution(target, scope);
target.add(Instruction.dup(1, 1).locate(loc()));
target.add(Instruction.dup(1, 1).locate(loc()));
target.add(Instruction.loadMember().locate(loc()));
value.compileWithPollution(target, scope);
target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeMember(true).locate(loc()));
}
else {
object.compileWithPollution(target, scope);
index.compileWithPollution(target, scope);
value.compileWithPollution(target, scope);
target.add(Instruction.storeMember(true).locate(loc()));
}
target.get(start).setDebug(true);
}
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Type operation) {
super(loc);
this.object = object;
this.index = index;
this.value = value;
this.operation = operation;
}
}

View File

@ -0,0 +1,53 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class IndexStatement extends AssignableStatement {
public final Statement object;
public final Statement index;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public Statement toAssign(Statement val, Type operation) {
return new IndexAssignStatement(loc(), object, index, val, operation);
}
public void compile(List<Instruction> target, ScopeRecord scope, boolean dupObj) {
int start = 0;
object.compileWithPollution(target, scope);
if (dupObj) target.add(Instruction.dup(1).locate(loc()));
if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
return;
}
index.compileWithPollution(target, scope);
target.add(Instruction.loadMember().locate(loc()));
target.get(start).setDebug(true);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
compile(target, scope, false);
}
public IndexStatement(Location loc, Statement object, Statement index) {
super(loc);
this.object = object;
this.index = index;
}
public IndexStatement(Location loc, Statement object, Object index) {
super(loc);
this.object = object;
this.index = new ConstantStatement(loc, index);
}
}

View File

@ -0,0 +1,45 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class LazyAndStatement extends Statement {
public final Statement first, second;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() {
return first.pure() && second.pure();
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) {
first.compileWithPollution(target, scope);
}
else second.compileWithPollution(target, scope);
return;
}
first.compileWithPollution(target, scope);
target.add(Instruction.dup(1).locate(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));
second.compileWithPollution(target, scope);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
}
public LazyAndStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

@ -0,0 +1,45 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class LazyOrStatement extends Statement {
public final Statement first, second;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() {
return first.pure() && second.pure();
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) {
second.compileWithPollution(target, scope);
}
else first.compileWithPollution(target, scope);
return;
}
first.compileWithPollution(target, scope);
target.add(Instruction.dup(1).locate(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));
second.compileWithPollution(target, scope);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
}
public LazyOrStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

View File

@ -0,0 +1,32 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class NewStatement extends Statement {
public final Statement func;
public final Statement[] args;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
func.compileWithPollution(target, scope);
for (var arg : args) {
arg.compileWithPollution(target, scope);
}
target.add(Instruction.callNew(args.length).locate(loc()).setDebug(true));
}
public NewStatement(Location loc, Statement func, Statement... args) {
super(loc);
this.func = func;
this.args = args;
}
}

View File

@ -0,0 +1,57 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ObjectStatement extends Statement {
public final Map<Object, Statement> map;
public final Map<Object, FunctionStatement> getters;
public final Map<Object, FunctionStatement> setters;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadObj().locate(loc()));
if (!map.isEmpty()) target.add(Instruction.dup(map.size()).locate(loc()));
for (var el : map.entrySet()) {
target.add(Instruction.loadValue(el.getKey()).locate(loc()));
var val = el.getValue();
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);
else val.compileWithPollution(target, scope);
target.add(Instruction.storeMember().locate(loc()));
}
var keys = new ArrayList<Object>();
keys.addAll(getters.keySet());
keys.addAll(setters.keySet());
for (var key : keys) {
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc()));
else target.add(Instruction.loadValue((Double)key).locate(loc()));
if (getters.containsKey(key)) getters.get(key).compileWithPollution(target, scope);
else target.add(Instruction.loadValue(null).locate(loc()));
if (setters.containsKey(key)) setters.get(key).compileWithPollution(target, scope);
else target.add(Instruction.loadValue(null).locate(loc()));
target.add(Instruction.defProp().locate(loc()));
}
}
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
super(loc);
this.map = Map.copyOf(map);
this.getters = Map.copyOf(getters);
this.setters = Map.copyOf(setters);
}
}

View File

@ -0,0 +1,104 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class OperationStatement extends Statement {
public final Statement[] args;
public final Instruction.Type operation;
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
for (var arg : args) {
arg.compileWithPollution(target, scope);
}
target.add(Instruction.operation(operation).locate(loc()));
}
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() {
for (var arg : args) {
if (!arg.pure()) return false;
}
return true;
}
@Override
public Statement optimize() {
var args = new Statement[this.args.length];
var allConst = true;
for (var i = 0; i < this.args.length; i++) {
args[i] = this.args[i].optimize();
if (!(args[i] instanceof ConstantStatement)) allConst = false;
}
if (allConst) {
var vals = new Object[this.args.length];
for (var i = 0; i < args.length; i++) {
vals[i] = ((ConstantStatement)args[i]).value;
}
try {
var ctx = new CallContext(null);
switch (operation) {
case ADD: return new ConstantStatement(loc(), Values.add(ctx, vals[0], vals[1]));
case SUBTRACT: return new ConstantStatement(loc(), Values.subtract(ctx, vals[0], vals[1]));
case DIVIDE: return new ConstantStatement(loc(), Values.divide(ctx, vals[0], vals[1]));
case MULTIPLY: return new ConstantStatement(loc(), Values.multiply(ctx, vals[0], vals[1]));
case MODULO: return new ConstantStatement(loc(), Values.modulo(ctx, vals[0], vals[1]));
case AND: return new ConstantStatement(loc(), Values.and(ctx, vals[0], vals[1]));
case OR: return new ConstantStatement(loc(), Values.or(ctx, vals[0], vals[1]));
case XOR: return new ConstantStatement(loc(), Values.xor(ctx, vals[0], vals[1]));
case EQUALS: return new ConstantStatement(loc(), Values.strictEquals(vals[0], vals[1]));
case NOT_EQUALS: return new ConstantStatement(loc(), !Values.strictEquals(vals[0], vals[1]));
case LOOSE_EQUALS: return new ConstantStatement(loc(), Values.looseEqual(ctx, vals[0], vals[1]));
case LOOSE_NOT_EQUALS: return new ConstantStatement(loc(), !Values.looseEqual(ctx, vals[0], vals[1]));
case GREATER: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) < 0);
case GREATER_EQUALS: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) <= 0);
case LESS: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) > 0);
case LESS_EQUALS: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) >= 0);
case INVERSE: return new ConstantStatement(loc(), Values.bitwiseNot(ctx, vals[0]));
case NOT: return new ConstantStatement(loc(), Values.not(vals[0]));
case POS: return new ConstantStatement(loc(), Values.toNumber(ctx, vals[0]));
case NEG: return new ConstantStatement(loc(), Values.negative(ctx, vals[0]));
case SHIFT_LEFT: return new ConstantStatement(loc(), Values.shiftLeft(ctx, vals[0], vals[1]));
case SHIFT_RIGHT: return new ConstantStatement(loc(), Values.shiftRight(ctx, vals[0], vals[1]));
case USHIFT_RIGHT: return new ConstantStatement(loc(), Values.unsignedShiftRight(ctx, vals[0], vals[1]));
default: break;
}
}
catch (EngineException e) {
return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value));
}
catch (InterruptedException e) { return null; }
}
return new OperationStatement(loc(), operation, args);
}
public OperationStatement(Location loc, Instruction.Type operation, Statement... args) {
super(loc);
this.operation = operation;
this.args = args;
}
}

View File

@ -0,0 +1,28 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class RegexStatement extends Statement {
public final String pattern, flags;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadRegex(pattern, flags).locate(loc()));
}
public RegexStatement(Location loc, String pattern, String flags) {
super(loc);
this.pattern = pattern;
this.flags = flags;
}
}

View File

@ -0,0 +1,58 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
public class TernaryStatement extends Statement {
public final Statement condition;
public final Statement first;
public final Statement second;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (condition instanceof ConstantStatement) {
if (!Values.toBoolean(((ConstantStatement)condition).value)) {
second.compileWithPollution(target, scope);
}
else first.compileWithPollution(target, scope);
return;
}
condition.compileWithPollution(target, scope);
int start = target.size();
target.add(Instruction.nop());
first.compileWithPollution(target, scope);
int mid = target.size();
target.add(Instruction.nop());
second.compileWithPollution(target, scope);
int end = target.size();
target.set(start, Instruction.jmpIfNot(mid - start + 1).locate(loc()));
target.set(mid, Instruction.jmp(end - mid).locate(loc()));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var f = first.optimize();
var s = second.optimize();
return new TernaryStatement(loc(), cond, f, s);
}
public TernaryStatement(Location loc, Statement condition, Statement first, Statement second) {
super(loc);
this.condition = condition;
this.first = first;
this.second = second;
}
}

View File

@ -0,0 +1,55 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Symbol;
public class TypeofStatement extends Statement {
public final Statement value;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (value instanceof VariableStatement) {
var i = scope.getKey(((VariableStatement)value).name);
if (i instanceof String) {
target.add(Instruction.typeof((String)i));
return;
}
}
value.compileWithPollution(target, scope);
target.add(Instruction.typeof().locate(loc()));
}
@Override
public Statement optimize() {
var val = value.optimize();
if (val instanceof ConstantStatement) {
var cnst = (ConstantStatement)val;
if (cnst.value == null) return new ConstantStatement(loc(), "undefined");
if (cnst.value instanceof Number) return new ConstantStatement(loc(), "number");
if (cnst.value instanceof Boolean) return new ConstantStatement(loc(), "boolean");
if (cnst.value instanceof String) return new ConstantStatement(loc(), "string");
if (cnst.value instanceof Symbol) return new ConstantStatement(loc(), "symbol");
if (cnst.value instanceof FunctionValue) return new ConstantStatement(loc(), "function");
return new ConstantStatement(loc(), "object");
}
return new TypeofStatement(loc(), val);
}
public TypeofStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@ -0,0 +1,42 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableAssignStatement extends Statement {
public final String name;
public final Statement value;
public final Type operation;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
var i = scope.getKey(name);
if (operation != null) {
target.add(Instruction.loadVar(i).locate(loc()));
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compileWithPollution(target, scope);
target.add(Instruction.operation(operation).locate(loc()));
}
else {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compileWithPollution(target, scope);
}
target.add(Instruction.storeVar(i, true).locate(loc()));
}
public VariableAssignStatement(Location loc, String name, Statement val, Type operation) {
super(loc);
this.name = name;
this.value = val;
this.operation = operation;
}
}

View File

@ -0,0 +1,27 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableIndexStatement extends Statement {
public final int index;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadVar(index).locate(loc()));
}
public VariableIndexStatement(Location loc, int i) {
super(loc);
this.index = i;
}
}

View File

@ -0,0 +1,35 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableStatement extends AssignableStatement {
public final String name;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return true; }
@Override
public Statement toAssign(Statement val, Type operation) {
return new VariableAssignStatement(loc(), name, val, operation);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
var i = scope.getKey(name);
target.add(Instruction.loadVar(i).locate(loc()));
}
public VariableStatement(Location loc, String name) {
super(loc);
this.name = name;
}
}

View File

@ -0,0 +1,34 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.compilation.Instruction;
public class VoidStatement extends Statement {
public final Statement value;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (value != null) value.compileNoPollution(target, scope);
target.add(Instruction.loadValue(null).locate(loc()));
}
@Override
public Statement optimize() {
if (value == null) return this;
var val = value.optimize();
if (val.pure()) return new ConstantStatement(loc(), null);
else return new VoidStatement(loc(), val);
}
public VoidStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@ -0,0 +1,5 @@
package me.topchetoeu.jscript.engine;
import me.topchetoeu.jscript.Location;
public record BreakpointData(Location loc, CallContext ctx) { }

View File

@ -0,0 +1,58 @@
package me.topchetoeu.jscript.engine;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
public class CallContext {
public static final class DataKey<T> {}
public final Engine engine;
private final Map<DataKey<?>, Object> data = new Hashtable<>();
public Engine engine() { return engine; }
public Map<DataKey<?>, Object> data() { return Collections.unmodifiableMap(data); }
public CallContext copy() {
return new CallContext(engine).mergeData(data);
}
public CallContext mergeData(Map<DataKey<?>, Object> objs) {
data.putAll(objs);
return this;
}
public <T> CallContext setData(DataKey<T> key, T val) {
data.put(key, val);
return this;
}
@SuppressWarnings("unchecked")
public <T> T addData(DataKey<T> key, T val) {
if (data.containsKey(key)) return (T)data.get(key);
else {
data.put(key, val);
return val;
}
}
public boolean hasData(DataKey<?> key) { return data.containsKey(key); }
public <T> T getData(DataKey<T> key) {
return getData(key, null);
}
@SuppressWarnings("unchecked")
public <T> T getData(DataKey<T> key, T defaultVal) {
if (!hasData(key)) return defaultVal;
else return (T)data.get(key);
}
public CallContext changeData(DataKey<Integer> key, int n, int start) {
return setData(key, getData(key, start) + n);
}
public CallContext changeData(DataKey<Integer> key, int n) {
return changeData(key, n, 0);
}
public CallContext changeData(DataKey<Integer> key) {
return changeData(key, 1, 0);
}
public CallContext(Engine engine) {
this.engine = engine;
}
}

View File

@ -0,0 +1,8 @@
package me.topchetoeu.jscript.engine;
public enum DebugCommand {
NORMAL,
STEP_OVER,
STEP_OUT,
STEP_INTO,
}

View File

@ -0,0 +1,195 @@
package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingDeque;
import me.topchetoeu.jscript.engine.CallContext.DataKey;
import me.topchetoeu.jscript.engine.debug.DebugState;
import me.topchetoeu.jscript.engine.modules.ModuleManager;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.NativeTypeRegister;
import me.topchetoeu.jscript.parsing.Parsing;
public class Engine {
private static record RawFunction(GlobalScope scope, String filename, String raw) { }
private static class Task {
public final Object func;
public final Object thisArg;
public final Object[] args;
public final Map<DataKey<?>, Object> data;
public final DataNotifier<Object> notifier = new DataNotifier<>();
public Task(Object func, Map<DataKey<?>, Object> data, Object thisArg, Object[] args) {
this.func = func;
this.data = data;
this.thisArg = thisArg;
this.args = args;
}
}
public static final DataKey<DebugState> DEBUG_STATE_KEY = new DataKey<>();
private static int nextId = 0;
private Map<DataKey<?>, Object> callCtxVals = new HashMap<>();
private GlobalScope global = new GlobalScope();
private ObjectValue arrayProto = new ObjectValue();
private ObjectValue boolProto = new ObjectValue();
private ObjectValue funcProto = new ObjectValue();
private ObjectValue numProto = new ObjectValue();
private ObjectValue objProto = new ObjectValue(PlaceholderProto.NONE);
private ObjectValue strProto = new ObjectValue();
private ObjectValue symProto = new ObjectValue();
private ObjectValue errProto = new ObjectValue();
private ObjectValue syntaxErrProto = new ObjectValue(PlaceholderProto.ERROR);
private ObjectValue typeErrProto = new ObjectValue(PlaceholderProto.ERROR);
private ObjectValue rangeErrProto = new ObjectValue(PlaceholderProto.ERROR);
private NativeTypeRegister typeRegister;
private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId;
public final DebugState debugState = new DebugState();
public ObjectValue arrayProto() { return arrayProto; }
public ObjectValue booleanProto() { return boolProto; }
public ObjectValue functionProto() { return funcProto; }
public ObjectValue numberProto() { return numProto; }
public ObjectValue objectProto() { return objProto; }
public ObjectValue stringProto() { return strProto; }
public ObjectValue symbolProto() { return symProto; }
public ObjectValue errorProto() { return errProto; }
public ObjectValue syntaxErrorProto() { return syntaxErrProto; }
public ObjectValue typeErrorProto() { return typeErrProto; }
public ObjectValue rangeErrorProto() { return rangeErrProto; }
public GlobalScope global() { return global; }
public NativeTypeRegister typeRegister() { return typeRegister; }
public void copyFrom(Engine other) {
global = other.global;
typeRegister = other.typeRegister;
arrayProto = other.arrayProto;
boolProto = other.boolProto;
funcProto = other.funcProto;
numProto = other.numProto;
objProto = other.objProto;
strProto = other.strProto;
symProto = other.symProto;
errProto = other.errProto;
syntaxErrProto = other.syntaxErrProto;
typeErrProto = other.typeErrProto;
rangeErrProto = other.rangeErrProto;
}
private void runTask(Task task) throws InterruptedException {
try {
FunctionValue func;
if (task.func instanceof FunctionValue) func = (FunctionValue)task.func;
else {
var raw = (RawFunction)task.func;
func = compile(raw.scope, raw.filename, raw.raw);
}
task.notifier.next(func.call(context().mergeData(task.data), task.thisArg, task.args));
}
catch (InterruptedException e) {
task.notifier.error(new RuntimeException(e));
throw e;
}
catch (EngineException e) {
task.notifier.error(e);
}
catch (RuntimeException e) {
task.notifier.error(e);
e.printStackTrace();
}
}
private void run() {
while (true) {
try {
runTask(macroTasks.take());
while (!microTasks.isEmpty()) {
runTask(microTasks.take());
}
}
catch (InterruptedException e) {
for (var msg : macroTasks) {
msg.notifier.error(new RuntimeException(e));
}
break;
}
}
}
public void exposeClass(String name, Class<?> clazz) {
global.define(name, true, typeRegister.getConstr(clazz));
}
public void exposeNamespace(String name, Class<?> clazz) {
global.define(name, true, NativeTypeRegister.makeNamespace(clazz));
}
public Thread start() {
if (this.thread == null) {
this.thread = new Thread(this::run, "JavaScript Runner #" + id);
this.thread.start();
}
return this.thread;
}
public void stop() {
thread.interrupt();
thread = null;
}
public boolean inExecThread() {
return Thread.currentThread() == thread;
}
public boolean isRunning() {
return this.thread != null;
}
public Object makeRegex(String pattern, String flags) {
throw EngineException.ofError("Regular expressions not supported.");
}
public ModuleManager modules() {
return null;
}
public ObjectValue getPrototype(Class<?> clazz) {
return typeRegister.getProto(clazz);
}
public CallContext context() { return new CallContext(this).mergeData(callCtxVals); }
public Awaitable<Object> pushMsg(boolean micro, FunctionValue func, Map<DataKey<?>, Object> data, Object thisArg, Object... args) {
var msg = new Task(func, data, thisArg, args);
if (micro) microTasks.addLast(msg);
else macroTasks.addLast(msg);
return msg.notifier;
}
public Awaitable<Object> pushMsg(boolean micro, GlobalScope scope, Map<DataKey<?>, Object> data, String filename, String raw, Object thisArg, Object... args) {
var msg = new Task(new RawFunction(scope, filename, raw), data, thisArg, args);
if (micro) microTasks.addLast(msg);
else macroTasks.addLast(msg);
return msg.notifier;
}
public CodeFunction compile(GlobalScope scope, String filename, String raw) throws InterruptedException {
return Parsing.compile(scope, filename, raw);
}
public Engine(NativeTypeRegister register) {
this.typeRegister = register;
this.callCtxVals.put(DEBUG_STATE_KEY, debugState);
}
public Engine() {
this(new NativeTypeRegister());
}
}

View File

@ -0,0 +1,154 @@
package me.topchetoeu.jscript.engine.debug;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.util.Base64;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.engine.debug.handlers.DebuggerHandles;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class DebugServer {
public static String browserDisplayName = "jscript";
public static String targetName = "target";
public final Engine engine;
private static void send(Socket socket, String val) throws IOException {
Http.writeResponse(socket.getOutputStream(), 200, "OK", "application/json", val.getBytes());
}
// SILENCE JAVA
private MessageDigest getDigestInstance() {
try {
return MessageDigest.getInstance("sha1");
}
catch (Throwable a) { return null; }
}
private static Thread runAsync(Runnable func, String name) {
var res = new Thread(func);
res.setName(name);
res.start();
return res;
}
private void handle(WebSocket ws) throws InterruptedException, IOException {
WebSocketMessage raw;
while ((raw = ws.receive()) != null) {
if (raw.type != Type.Text) {
ws.send(new V8Error("Expected a text message."));
continue;
}
V8Message msg;
try {
msg = new V8Message(raw.textData());
}
catch (SyntaxException e) {
ws.send(new V8Error(e.getMessage()));
return;
}
switch (msg.name) {
case "Debugger.enable": DebuggerHandles.enable(msg, engine, ws); continue;
case "Debugger.disable": DebuggerHandles.disable(msg, engine, ws); continue;
case "Debugger.stepInto": DebuggerHandles.stepInto(msg, engine, ws); continue;
}
}
}
private void onWsConnect(HttpRequest req, Socket socket) throws IOException {
var key = req.headers().get("sec-websocket-key");
if (key == null) {
Http.writeResponse(
socket.getOutputStream(), 426, "Upgrade Required", "text/txt",
"Expected a WS upgrade".getBytes()
);
return;
}
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
));
Http.writeCode(socket.getOutputStream(), 101, "Switching Protocols");
Http.writeHeader(socket.getOutputStream(), "Connection", "Upgrade");
Http.writeHeader(socket.getOutputStream(), "Sec-WebSocket-Accept", resKey);
Http.writeLastHeader(socket.getOutputStream(), "Upgrade", "WebSocket");
var ws = new WebSocket(socket);
runAsync(() -> {
try {
handle(ws);
}
catch (InterruptedException e) { return; }
catch (IOException e) { e.printStackTrace(); }
finally { ws.close(); }
}, "Debug Server Message Reader");
runAsync(() -> {
try {
handle(ws);
}
catch (InterruptedException e) { return; }
catch (IOException e) { e.printStackTrace(); }
finally { ws.close(); }
}, "Debug Server Event Writer");
}
public void open(InetSocketAddress address) throws IOException {
ServerSocket server = new ServerSocket();
server.bind(address);
try {
while (true) {
var socket = server.accept();
var req = Http.readRequest(socket.getInputStream());
switch (req.path()) {
case "/json/version":
send(socket, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}");
break;
case "/json/list":
case "/json":
var addr = "ws://" + address.getHostString() + ":" + address.getPort() + "/devtools/page/" + targetName;
send(socket, "{\"id\":\"" + browserDisplayName + "\",\"webSocketDebuggerUrl\":\"" + addr + "\"}");
break;
case "/json/new":
case "/json/activate":
case "/json/protocol":
case "/json/close":
case "/devtools/inspector.html":
Http.writeResponse(
socket.getOutputStream(),
501, "Not Implemented", "text/txt",
"This feature isn't (and won't be) implemented.".getBytes()
);
break;
default:
if (req.path().equals("/devtools/page/" + targetName)) onWsConnect(req, socket);
else {
Http.writeResponse(
socket.getOutputStream(),
404, "Not Found", "text/txt",
"Not found :/".getBytes()
);
}
break;
}
}
}
finally { server.close(); }
}
public DebugServer(Engine engine) {
this.engine = engine;
}
}

View File

@ -0,0 +1,52 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.BreakpointData;
import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.events.Event;
public class DebugState {
private boolean paused = false;
public final HashSet<Location> breakpoints = new HashSet<>();
public final List<CodeFrame> frames = new ArrayList<>();
public final Map<String, String> sources = new HashMap<>();
public final Event<BreakpointData> breakpointNotifier = new Event<>();
public final Event<DebugCommand> commandNotifier = new Event<>();
public final Event<String> sourceAdded = new Event<>();
public DebugState pushFrame(CodeFrame frame) {
frames.add(frame);
return this;
}
public DebugState popFrame() {
if (frames.size() > 0) frames.remove(frames.size() - 1);
return this;
}
public DebugCommand pause(BreakpointData data) throws InterruptedException {
paused = true;
breakpointNotifier.next(data);
return commandNotifier.toAwaitable().await();
}
public void resume(DebugCommand command) {
paused = false;
commandNotifier.next(command);
}
// public void addSource()?
public boolean paused() { return paused; }
public boolean isBreakpoint(Location loc) {
return breakpoints.contains(loc);
}
}

View File

@ -0,0 +1,65 @@
package me.topchetoeu.jscript.engine.debug;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.IllegalFormatException;
// We dont need no http library
public class Http {
public static void writeCode(OutputStream str, int code, String name) throws IOException {
str.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes());
}
public static void writeHeader(OutputStream str, String name, String value) throws IOException {
str.write((name + ": " + value + "\r\n").getBytes());
}
public static void writeLastHeader(OutputStream str, String name, String value) throws IOException {
str.write((name + ": " + value + "\r\n").getBytes());
writeHeadersEnd(str);
}
public static void writeHeadersEnd(OutputStream str) throws IOException {
str.write("\n".getBytes());
}
public static void writeResponse(OutputStream str, int code, String name, String type, byte[] data) throws IOException {
writeCode(str, code, name);
writeHeader(str, "Content-Type", type);
writeLastHeader(str, "Content-Length", data.length + "");
str.write(data);
str.close();
}
public static HttpRequest readRequest(InputStream str) throws IOException {
var lines = new BufferedReader(new InputStreamReader(str));
var line = lines.readLine();
var i1 = line.indexOf(" ");
var i2 = line.lastIndexOf(" ");
var method = line.substring(0, i1).trim().toUpperCase();
var path = line.substring(i1 + 1, i2).trim();
var headers = new HashMap<String, String>();
while (!(line = lines.readLine()).isEmpty()) {
var i = line.indexOf(":");
if (i < 0) continue;
var name = line.substring(0, i).trim().toLowerCase();
var value = line.substring(i + 1).trim();
if (name.length() == 0) continue;
headers.put(name, value);
}
if (headers.containsKey("content-length")) {
try {
var i = Integer.parseInt(headers.get("content-length"));
str.skip(i);
}
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
}
return new HttpRequest(method, path, headers);
}
}

View File

@ -0,0 +1,6 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.Map;
public record HttpRequest(String method, String path, Map<String, String> headers) {}

View File

@ -0,0 +1,19 @@
package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Error {
public final String message;
public V8Error(String message) {
this.message = message;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap().set("error", new JSONMap()
.set("message", message)
));
}
}

View File

@ -0,0 +1,22 @@
package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Event {
public final String name;
public final JSONMap params;
public V8Event(String name, JSONMap params) {
this.name = name;
this.params = params;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
);
}
}

View File

@ -0,0 +1,50 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.Map;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Message {
public final String name;
public final int id;
public final JSONMap params;
public V8Message(String name, int id, Map<String, JSONElement> params) {
this.name = name;
this.params = new JSONMap(params);
this.id = id;
}
public V8Result respond(JSONMap result) {
return new V8Result(id, result);
}
public V8Result respond() {
return new V8Result(id, new JSONMap());
}
public V8Message(JSONMap raw) {
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
this.name = raw.string("method");
this.id = (int)raw.number("id");
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
}
public V8Message(String raw) {
this(JSON.parse("json", raw).map());
}
public JSONMap toMap() {
var res = new JSONMap();
return res;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
.set("id", id)
);
}
}

View File

@ -0,0 +1,22 @@
package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Result {
public final int id;
public final JSONMap result;
public V8Result(int id, JSONMap result) {
this.id = id;
this.result = result;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("id", id)
.set("result", result)
);
}
}

View File

@ -0,0 +1,185 @@
package me.topchetoeu.jscript.engine.debug;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
public class WebSocket implements AutoCloseable {
public long maxLength = 2000000;
private Socket socket;
private boolean closed = false;
private OutputStream out() throws IOException {
return socket.getOutputStream();
}
private InputStream in() throws IOException {
return socket.getInputStream();
}
private long readLen(int byteLen) throws IOException {
long res = 0;
if (byteLen == 126) {
res |= in().read() << 8;
res |= in().read();
return res;
}
else if (byteLen == 127) {
res |= in().read() << 56;
res |= in().read() << 48;
res |= in().read() << 40;
res |= in().read() << 32;
res |= in().read() << 24;
res |= in().read() << 16;
res |= in().read() << 8;
res |= in().read();
return res;
}
else return byteLen;
}
private byte[] readMask(boolean has) throws IOException {
if (has) {
return new byte[] {
(byte)in().read(),
(byte)in().read(),
(byte)in().read(),
(byte)in().read()
};
}
else return new byte[4];
}
private void writeLength(long len) throws IOException {
if (len < 126) {
out().write((int)len);
}
else if (len < 0xFFFF) {
out().write(126);
out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF);
}
else {
out().write(127);
out().write((int)(len >> 56) & 0xFF);
out().write((int)(len >> 48) & 0xFF);
out().write((int)(len >> 40) & 0xFF);
out().write((int)(len >> 32) & 0xFF);
out().write((int)(len >> 24) & 0xFF);
out().write((int)(len >> 16) & 0xFF);
out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF);
}
}
private synchronized void write(int type, byte[] data) throws IOException {
out().write(type | 0x80);
writeLength(data.length);
for (int i = 0; i < data.length; i++) {
out().write(data[i]);
}
}
public void send(String data) throws IOException {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.getBytes());
}
public void send(byte[] data) throws IOException {
if (closed) throw new IllegalStateException("Object is closed.");
write(2, data);
}
public void send(WebSocketMessage msg) throws IOException {
if (msg.type == Type.Binary) send(msg.binaryData());
else send(msg.textData());
}
public void send(Object data) throws IOException {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes());
}
public void close(String reason) {
if (socket != null) {
try { write(8, reason.getBytes()); } catch (IOException e) { /* ¯\_(ツ)_/¯ */ }
try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
}
socket = null;
closed = true;
}
public void close() {
close("");
}
private WebSocketMessage fail(String reason) {
System.out.println("WebSocket Error: " + reason);
close(reason);
return null;
}
private byte[] readData() throws IOException {
var maskLen = in().read();
var hasMask = (maskLen & 0x80) != 0;
var len = (int)readLen(maskLen & 0x7F);
var mask = readMask(hasMask);
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
else {
var buff = new byte[len];
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
else {
for (int i = 0; i < len; i++) {
buff[i] ^= mask[(int)(i % 4)];
}
return buff;
}
}
return null;
}
public WebSocketMessage receive() throws InterruptedException {
try {
var data = new ByteArrayOutputStream();
var type = 0;
while (socket != null && !closed) {
var finId = in().read();
var fin = (finId & 0x80) != 0;
int id = finId & 0x0F;
if (id == 0x8) { close(); return null; }
if (id >= 0x8) {
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
if (id == 0x9) write(0xA, data.toByteArray());
continue;
}
if (type == 0) type = id;
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
var buff = readData();
if (buff == null) break;
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
data.write(buff);
if (!fin) continue;
var raw = data.toByteArray();
if (type == 1) return new WebSocketMessage(new String(raw));
else return new WebSocketMessage(raw);
}
}
catch (IOException e) { close(); }
return null;
}
public WebSocket(Socket socket) {
this.socket = socket;
}
}

View File

@ -0,0 +1,29 @@
package me.topchetoeu.jscript.engine.debug;
public class WebSocketMessage {
public static enum Type {
Text,
Binary,
}
public final Type type;
private final Object data;
public final String textData() {
if (type != Type.Text) throw new IllegalStateException("Message is not text.");
return (String)data;
}
public final byte[] binaryData() {
if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
return (byte[])data;
}
public WebSocketMessage(String data) {
this.type = Type.Text;
this.data = data;
}
public WebSocketMessage(byte[] data) {
this.type = Type.Binary;
this.data = data;
}
}

View File

@ -0,0 +1,29 @@
package me.topchetoeu.jscript.engine.debug.handlers;
import java.io.IOException;
import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.debug.V8Error;
import me.topchetoeu.jscript.engine.debug.V8Message;
import me.topchetoeu.jscript.engine.debug.WebSocket;
import me.topchetoeu.jscript.json.JSONMap;
public class DebuggerHandles {
public static void enable(V8Message msg, Engine engine, WebSocket ws) throws IOException {
if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine."));
else ws.send(msg.respond(new JSONMap().set("debuggerId", 1)));
}
public static void disable(V8Message msg, Engine engine, WebSocket ws) throws IOException {
if (engine.debugState == null) ws.send(msg.respond());
else ws.send(new V8Error("Debugger may not be disabled."));
}
public static void stepInto(V8Message msg, Engine engine, WebSocket ws) throws IOException {
if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine."));
else if (!engine.debugState.paused()) ws.send(new V8Error("Debugger is not paused."));
else {
engine.debugState.resume(DebugCommand.STEP_INTO);
ws.send(msg.respond());
}
}
}

View File

@ -0,0 +1,177 @@
package me.topchetoeu.jscript.engine.frame;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.BreakpointData;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.CallContext.DataKey;
import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class CodeFrame {
public static final DataKey<Integer> STACK_N_KEY = new DataKey<>();
public static final DataKey<Integer> MAX_STACK_KEY = new DataKey<>();
public static final DataKey<Boolean> STOP_AT_START_KEY = new DataKey<>();
public static final DataKey<Boolean> STEPPING_TROUGH_KEY = new DataKey<>();
public final LocalScope scope;
public final Object thisArg;
public final Object[] args;
public final List<Object> stack = new ArrayList<>();
public final CodeFunction function;
public int codePtr = 0;
private DebugCommand debugCmd = null;
private Location prevLoc = null;
public Object peek() {
return peek(0);
}
public Object peek(int offset) {
if (stack.size() <= offset) return null;
else return stack.get(stack.size() - 1 - offset);
}
public Object pop() {
if (stack.size() == 0) return null;
else return stack.remove(stack.size() - 1);
}
public void push(Object val) {
stack.add(stack.size(), Values.normalize(val));
}
private void cleanup(CallContext ctx) {
stack.clear();
codePtr = 0;
debugCmd = null;
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugState != null) debugState.popFrame();
ctx.changeData(STACK_N_KEY, -1);
}
public Object next(CallContext ctx) throws InterruptedException {
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugCmd == null) {
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 1000))
throw EngineException.ofRange("Stack overflow!");
ctx.changeData(STACK_N_KEY);
if (ctx.getData(STOP_AT_START_KEY, false)) debugCmd = DebugCommand.STEP_OVER;
else debugCmd = DebugCommand.NORMAL;
if (debugState != null) debugState.pushFrame(this);
}
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
var instr = function.body[codePtr];
var loc = instr.location;
if (loc != null) prevLoc = loc;
if (debugState != null && loc != null) {
if (
instr.type == Type.NOP && instr.match("debug") || debugState.breakpoints.contains(loc) || (
ctx.getData(STEPPING_TROUGH_KEY, false) &&
(debugCmd == DebugCommand.STEP_INTO || debugCmd == DebugCommand.STEP_OVER)
)
) {
ctx.setData(STEPPING_TROUGH_KEY, true);
debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
debugCmd = debugState.commandNotifier.toAwaitable().await();
if (debugCmd == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
}
}
try {
var res = Runners.exec(debugCmd, instr, this, ctx);
if (res != Runners.NO_RETURN) cleanup(ctx);
return res;
}
catch (EngineException e) {
cleanup(ctx);
throw e.add(function.name, prevLoc);
}
catch (RuntimeException e) {
cleanup(ctx);
throw e;
}
}
public Object run(CallContext ctx) throws InterruptedException {
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
DebugCommand command = ctx.getData(STOP_AT_START_KEY, false) ? DebugCommand.STEP_OVER : DebugCommand.NORMAL;
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 200)) throw EngineException.ofRange("Stack overflow!");
ctx.changeData(STACK_N_KEY);
if (debugState != null) debugState.pushFrame(this);
Location loc = null;
try {
while (codePtr >= 0 && codePtr < function.body.length) {
var _loc = function.body[codePtr].location;
if (_loc != null) loc = _loc;
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
var instr = function.body[codePtr];
if (debugState != null && loc != null) {
if (
instr.type == Type.NOP && instr.match("debug") ||
(
(command == DebugCommand.STEP_INTO || command == DebugCommand.STEP_OVER) &&
ctx.getData(STEPPING_TROUGH_KEY, false)
) ||
debugState.breakpoints.contains(loc)
) {
ctx.setData(STEPPING_TROUGH_KEY, true);
debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
command = debugState.commandNotifier.toAwaitable().await();
if (command == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
}
}
try {
var res = Runners.exec(command, instr, this, ctx);
if (res != Runners.NO_RETURN) return res;
}
catch (EngineException e) {
throw e.add(function.name, instr.location);
}
}
return null;
}
// catch (StackOverflowError e) {
// e.printStackTrace();
// throw EngineException.ofRange("Stack overflow!").add(function.name, loc);
// }
finally {
ctx.changeData(STACK_N_KEY, -1);
}
}
public CodeFrame(Object thisArg, Object[] args, CodeFunction func) {
this.args = args.clone();
this.scope = new LocalScope(func.localsN, func.captures);
this.scope.get(0).set(null, thisArg);
var argsObj = new ArrayValue();
for (var i = 0; i < args.length; i++) {
argsObj.set(i, args[i]);
}
this.scope.get(1).value = argsObj;
this.thisArg = thisArg;
this.function = func;
}
}

View File

@ -0,0 +1,6 @@
package me.topchetoeu.jscript.engine.frame;
public enum ConvertHint {
TOSTRING,
VALUEOF,
}

View File

@ -0,0 +1,9 @@
package me.topchetoeu.jscript.engine.frame;
public class InstructionResult {
public final Object value;
public InstructionResult(Object value) {
this.value = value;
}
}

View File

@ -0,0 +1,605 @@
package me.topchetoeu.jscript.engine.frame;
import java.util.Collections;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.SignalValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class Runners {
public static final Object NO_RETURN = new Object();
public static Object execReturn(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.codePtr++;
return frame.pop();
}
public static Object execSignal(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.codePtr++;
return new SignalValue(instr.get(0));
}
public static Object execThrow(Instruction instr, CodeFrame frame, CallContext ctx) {
throw new EngineException(frame.pop());
}
public static Object execThrowSyntax(Instruction instr, CodeFrame frame, CallContext ctx) {
throw EngineException.ofSyntax((String)instr.get(0));
}
private static Object call(DebugCommand state, CallContext ctx, Object func, Object thisArg, Object... args) throws InterruptedException {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state == DebugCommand.STEP_INTO);
return Values.call(ctx, func, thisArg, args);
}
public static Object execCall(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
int n = instr.get(0);
var callArgs = new Object[n];
for (var i = n - 1; i >= 0; i--) callArgs[i] = frame.pop();
var func = frame.pop();
var thisArg = frame.pop();
frame.push(call(state, ctx, func, thisArg, callArgs));
frame.codePtr++;
return NO_RETURN;
}
public static Object execCallNew(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
int n = instr.get(0);
var callArgs = new Object[n];
for (var i = n - 1; i >= 0; i--) callArgs[i] = frame.pop();
var funcObj = frame.pop();
if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
frame.push(call(state, ctx, funcObj, null, callArgs));
}
else {
var proto = Values.getMember(ctx, funcObj, "prototype");
var obj = new ObjectValue();
obj.setPrototype(ctx, proto);
call(state, ctx, funcObj, obj, callArgs);
frame.push(obj);
}
frame.codePtr++;
return NO_RETURN;
}
public static Object execMakeVar(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var name = (String)instr.get(0);
frame.function.globals.define(name);
frame.codePtr++;
return NO_RETURN;
}
public static Object execDefProp(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var setter = frame.pop();
var getter = frame.pop();
var name = frame.pop();
var obj = frame.pop();
if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined.");
if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined.");
if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object.");
Values.object(obj).defineProperty(name, Values.function(getter), Values.function(setter), false, false);
frame.push(obj);
frame.codePtr++;
return NO_RETURN;
}
public static Object execInstanceof(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var type = frame.pop();
var obj = frame.pop();
if (!Values.isPrimitive(type)) {
var proto = Values.getMember(ctx, type, "prototype");
frame.push(Values.isInstanceOf(ctx, obj, proto));
}
else {
frame.push(false);
}
frame.codePtr++;
return NO_RETURN;
}
public static Object execKeys(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var val = frame.pop();
var arr = new ObjectValue();
var i = 0;
var members = Values.getMembers(ctx, val, false, false);
Collections.reverse(members);
for (var el : members) {
if (el instanceof Symbol) continue;
arr.defineProperty(i++, el);
}
arr.defineProperty("length", i);
frame.push(arr);
frame.codePtr++;
return NO_RETURN;
}
public static Object execTry(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var finallyFunc = (boolean)instr.get(1) ? frame.pop() : null;
var catchFunc = (boolean)instr.get(0) ? frame.pop() : null;
var func = frame.pop();
if (
!Values.isFunction(func) ||
catchFunc != null && !Values.isFunction(catchFunc) ||
finallyFunc != null && !Values.isFunction(finallyFunc)
) throw EngineException.ofType("TRY instruction can be applied only upon functions.");
Object res = new SignalValue("no_return");
EngineException exception = null;
Values.function(func).name = frame.function.name + "::try";
if (catchFunc != null) Values.function(catchFunc).name = frame.function.name + "::catch";
if (finallyFunc != null) Values.function(finallyFunc).name = frame.function.name + "::finally";
try {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
res = Values.call(ctx, func, frame.thisArg);
}
catch (EngineException e) {
exception = e.setCause(exception);
}
if (exception != null && catchFunc != null) {
var exc = exception;
exception = null;
try {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
var _res = Values.call(ctx, catchFunc, frame.thisArg, exc);
if (!SignalValue.isSignal(_res, "no_return")) res = _res;
}
catch (EngineException e) {
exception = e.setCause(exc);
}
}
if (finallyFunc != null) {
try {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
var _res = Values.call(ctx, finallyFunc, frame.thisArg);
if (!SignalValue.isSignal(_res, "no_return")) {
res = _res;
exception = null;
}
}
catch (EngineException e) {
exception = e.setCause(exception);
}
}
if (exception != null) throw exception;
if (SignalValue.isSignal(res, "no_return")) {
frame.codePtr++;
return NO_RETURN;
}
else if (SignalValue.isSignal(res, "jmp_*")) {
frame.codePtr += Integer.parseInt(((SignalValue)res).data.substring(4));
return NO_RETURN;
}
else return res;
}
public static Object execDup(Instruction instr, CodeFrame frame, CallContext ctx) {
var val = frame.peek(instr.get(1));
for (int i = 0; i < (int)instr.get(0); i++) {
frame.push(val);
}
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadUndefined(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.push(null);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadValue(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.push(instr.get(0));
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadVar(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var i = instr.get(0);
if (i instanceof String) frame.push(frame.function.globals.get(ctx, (String)i));
else frame.push(frame.scope.get((int)i).get(ctx));
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadObj(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.push(new ObjectValue());
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadGlob(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.push(frame.function.globals.obj);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadArr(Instruction instr, CodeFrame frame, CallContext ctx) {
var res = new ArrayValue();
res.setSize(instr.get(0));
frame.push(res);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadFunc(Instruction instr, CodeFrame frame, CallContext ctx) {
int n = (Integer)instr.get(0);
int localsN = (Integer)instr.get(1);
int len = (Integer)instr.get(2);
var captures = new ValueVariable[instr.params.length - 3];
for (var i = 3; i < instr.params.length; i++) {
captures[i - 3] = frame.scope.get(instr.get(i));
}
var start = frame.codePtr + 1;
var end = start + n - 1;
var body = new Instruction[end - start];
System.arraycopy(frame.function.body, start, body, 0, end - start);
var func = new CodeFunction("", localsN, len, frame.function.globals, captures, body);
frame.push(func);
frame.codePtr += n;
return NO_RETURN;
}
public static Object execLoadMember(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var key = frame.pop();
var obj = frame.pop();
try {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state == DebugCommand.STEP_INTO);
frame.push(Values.getMember(ctx, obj, key));
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return NO_RETURN;
}
public static Object execLoadKeyMember(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(instr.get(0));
return execLoadMember(state, instr, frame, ctx);
}
public static Object execLoadRegEx(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(ctx.engine().makeRegex(instr.get(0), instr.get(1)));
frame.codePtr++;
return NO_RETURN;
}
public static Object execDiscard(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.pop();
frame.codePtr++;
return NO_RETURN;
}
public static Object execStoreMember(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var val = frame.pop();
var key = frame.pop();
var obj = frame.pop();
ctx.setData(CodeFrame.STOP_AT_START_KEY, state == DebugCommand.STEP_INTO);
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
if ((boolean)instr.get(0)) frame.push(val);
frame.codePtr++;
return NO_RETURN;
}
public static Object execStoreVar(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0);
if (i instanceof String) frame.function.globals.set(ctx, (String)i, val);
else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++;
return NO_RETURN;
}
public static Object execStoreSelfFunc(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
frame.codePtr++;
return NO_RETURN;
}
public static Object execJmp(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.codePtr += (int)instr.get(0);
return NO_RETURN;
}
public static Object execJmpIf(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.codePtr += Values.toBoolean(frame.pop()) ? (int)instr.get(0) : 1;
return NO_RETURN;
}
public static Object execJmpIfNot(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.codePtr += Values.not(frame.pop()) ? (int)instr.get(0) : 1;
return NO_RETURN;
}
public static Object execIn(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var obj = frame.pop();
var index = frame.pop();
frame.push(Values.hasMember(ctx, obj, index, false));
frame.codePtr++;
return NO_RETURN;
}
public static Object execTypeof(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
String name = instr.get(0);
Object obj;
if (name != null) {
if (frame.function.globals.has(ctx, name)) {
obj = frame.function.globals.get(ctx, name);
}
else obj = null;
}
else obj = frame.pop();
frame.push(Values.type(obj));
frame.codePtr++;
return NO_RETURN;
}
public static Object execNop(Instruction instr, CodeFrame frame, CallContext ctx) {
if (instr.is(0, "dbg_names")) {
var names = new String[instr.params.length - 1];
for (var i = 0; i < instr.params.length - 1; i++) {
if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters.");
names[i] = (String)instr.params[i + 1];
}
frame.scope.setNames(names);
}
frame.codePtr++;
return NO_RETURN;
}
public static Object execDelete(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var key = frame.pop();
var val = frame.pop();
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
frame.push(true);
frame.codePtr++;
return NO_RETURN;
}
public static Object execAdd(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.add(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execSubtract(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.subtract(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execMultiply(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.multiply(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execDivide(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.divide(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execModulo(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.modulo(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execAnd(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.and(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execOr(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.or(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execXor(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.xor(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execLeftShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.shiftLeft(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execRightShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.shiftRight(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execUnsignedRightShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.unsignedShiftRight(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execNot(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.push(Values.not(frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execNeg(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(Values.negative(ctx, frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execPos(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(Values.toNumber(ctx, frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execInverse(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(Values.bitwiseNot(ctx, frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execGreaterThan(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) > 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLessThan(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) < 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execGreaterThanEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) >= 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLessThanEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) <= 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLooseEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.looseEqual(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execLooseNotEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(!Values.looseEqual(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.strictEquals(a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execNotEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(!Values.strictEquals(a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object exec(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
switch (instr.type) {
case NOP: return execNop(instr, frame, ctx);
case RETURN: return execReturn(instr, frame, ctx);
case SIGNAL: return execSignal(instr, frame, ctx);
case THROW: return execThrow(instr, frame, ctx);
case THROW_SYNTAX: return execThrowSyntax(instr, frame, ctx);
case CALL: return execCall(state, instr, frame, ctx);
case CALL_NEW: return execCallNew(state, instr, frame, ctx);
case TRY: return execTry(state, instr, frame, ctx);
case DUP: return execDup(instr, frame, ctx);
case LOAD_VALUE: return execLoadValue(instr, frame, ctx);
case LOAD_VAR: return execLoadVar(instr, frame, ctx);
case LOAD_OBJ: return execLoadObj(instr, frame, ctx);
case LOAD_ARR: return execLoadArr(instr, frame, ctx);
case LOAD_FUNC: return execLoadFunc(instr, frame, ctx);
case LOAD_MEMBER: return execLoadMember(state, instr, frame, ctx);
case LOAD_VAL_MEMBER: return execLoadKeyMember(state, instr, frame, ctx);
case LOAD_REGEX: return execLoadRegEx(instr, frame, ctx);
case LOAD_GLOB: return execLoadGlob(instr, frame, ctx);
case DISCARD: return execDiscard(instr, frame, ctx);
case STORE_MEMBER: return execStoreMember(state, instr, frame, ctx);
case STORE_VAR: return execStoreVar(instr, frame, ctx);
case STORE_SELF_FUNC: return execStoreSelfFunc(instr, frame, ctx);
case MAKE_VAR: return execMakeVar(instr, frame, ctx);
case IN: return execIn(instr, frame, ctx);
case KEYS: return execKeys(instr, frame, ctx);
case DEF_PROP: return execDefProp(instr, frame, ctx);
case TYPEOF: return execTypeof(instr, frame, ctx);
case DELETE: return execDelete(instr, frame, ctx);
case INSTANCEOF: return execInstanceof(instr, frame, ctx);
case JMP: return execJmp(instr, frame, ctx);
case JMP_IF: return execJmpIf(instr, frame, ctx);
case JMP_IFN: return execJmpIfNot(instr, frame, ctx);
case ADD: return execAdd(instr, frame, ctx);
case SUBTRACT: return execSubtract(instr, frame, ctx);
case MULTIPLY: return execMultiply(instr, frame, ctx);
case DIVIDE: return execDivide(instr, frame, ctx);
case MODULO: return execModulo(instr, frame, ctx);
case AND: return execAnd(instr, frame, ctx);
case OR: return execOr(instr, frame, ctx);
case XOR: return execXor(instr, frame, ctx);
case SHIFT_LEFT: return execLeftShift(instr, frame, ctx);
case SHIFT_RIGHT: return execRightShift(instr, frame, ctx);
case USHIFT_RIGHT: return execUnsignedRightShift(instr, frame, ctx);
case NOT: return execNot(instr, frame, ctx);
case NEG: return execNeg(instr, frame, ctx);
case POS: return execPos(instr, frame, ctx);
case INVERSE: return execInverse(instr, frame, ctx);
case GREATER: return execGreaterThan(instr, frame, ctx);
case GREATER_EQUALS: return execGreaterThanEquals(instr, frame, ctx);
case LESS: return execLessThan(instr, frame, ctx);
case LESS_EQUALS: return execLessThanEquals(instr, frame, ctx);
case LOOSE_EQUALS: return execLooseEquals(instr, frame, ctx);
case LOOSE_NOT_EQUALS: return execLooseNotEquals(instr, frame, ctx);
case EQUALS: return execEquals(instr, frame, ctx);
case NOT_EQUALS: return execNotEquals(instr, frame, ctx);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
}
}
}

View File

@ -0,0 +1,50 @@
package me.topchetoeu.jscript.engine.modules;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import me.topchetoeu.jscript.polyfills.PolyfillEngine;
public class FileModuleProvider implements ModuleProvider {
public File root;
public final boolean allowOutside;
private boolean checkInside(Path modFile) {
return modFile.toAbsolutePath().startsWith(root.toPath().toAbsolutePath());
}
@Override
public Module getModule(File cwd, String name) {
var realName = getRealName(cwd, name);
if (realName == null) return null;
var path = Path.of(realName + ".js").normalize();
try {
var res = PolyfillEngine.streamToString(new FileInputStream(path.toFile()));
return new Module(realName, path.toString(), res);
}
catch (IOException e) {
return null;
}
}
@Override
public String getRealName(File cwd, String name) {
var path = Path.of(".", Path.of(cwd.toString(), name).normalize().toString());
var fileName = path.getFileName().toString();
if (fileName == null) return null;
if (!fileName.equals("index") && path.toFile().isDirectory()) return getRealName(cwd, name + "/index");
path = Path.of(path.toString() + ".js");
if (!allowOutside && !checkInside(path)) return null;
if (!path.toFile().isFile() || !path.toFile().canRead()) return null;
var res = path.toString().replace('\\', '/');
var i = res.lastIndexOf('.');
return res.substring(0, i);
}
public FileModuleProvider(File root, boolean allowOutside) {
this.root = root.toPath().normalize().toFile();
this.allowOutside = allowOutside;
}
}

View File

@ -0,0 +1,57 @@
package me.topchetoeu.jscript.engine.modules;
import java.io.File;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.CallContext.DataKey;
import me.topchetoeu.jscript.engine.scope.Variable;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter;
public class Module {
public class ExportsVariable implements Variable {
@Override
public boolean readonly() { return false; }
@Override
public Object get(CallContext ctx) { return exports; }
@Override
public void set(CallContext ctx, Object val) { exports = val; }
}
public static DataKey<Module> KEY = new DataKey<>();
public final String filename;
public final String source;
public final String name;
private Object exports = new ObjectValue();
private boolean executing = false;
@NativeGetter("name")
public String name() { return name; }
@NativeGetter("exports")
public Object exports() { return exports; }
@NativeSetter("exports")
public void setExports(Object val) { exports = val; }
public void execute(CallContext ctx) throws InterruptedException {
if (executing) return;
executing = true;
var scope = ctx.engine().scope().globalChild();
scope.define("module", true, this);
scope.define("exports", new ExportsVariable());
var parent = new File(filename).getParentFile();
if (parent == null) parent = new File(".");
ctx.engine().compile(scope, filename, source).call(ctx.copy().setData(KEY, this), null);
executing = false;
}
public Module(String name, String filename, String source) {
this.name = name;
this.filename = filename;
this.source = source;
}
}

View File

@ -0,0 +1,80 @@
package me.topchetoeu.jscript.engine.modules;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import me.topchetoeu.jscript.engine.CallContext;
public class ModuleManager {
private final List<ModuleProvider> providers = new ArrayList<>();
private final HashMap<String, Module> cache = new HashMap<>();
public final FileModuleProvider files;
public void addProvider(ModuleProvider provider) {
this.providers.add(provider);
}
public boolean isCached(File cwd, String name) {
name = name.replace("\\", "/");
// Absolute paths are forbidden
if (name.startsWith("/")) return false;
// Look for files if we have a relative path
if (name.startsWith("../") || name.startsWith("./")) {
var realName = files.getRealName(cwd, name);
if (cache.containsKey(realName)) return true;
else return false;
}
for (var provider : providers) {
var realName = provider.getRealName(cwd, name);
if (realName == null) continue;
if (cache.containsKey(realName)) return true;
}
return false;
}
public Module tryLoad(CallContext ctx, String name) throws InterruptedException {
name = name.replace('\\', '/');
var pcwd = Path.of(".");
if (ctx.hasData(Module.KEY)) {
pcwd = Path.of(((Module)ctx.getData(Module.KEY)).filename).getParent();
if (pcwd == null) pcwd = Path.of(".");
}
var cwd = pcwd.toFile();
if (name.startsWith("/")) return null;
if (name.startsWith("../") || name.startsWith("./")) {
var realName = files.getRealName(cwd, name);
if (realName == null) return null;
if (cache.containsKey(realName)) return cache.get(realName);
var mod = files.getModule(cwd, name);
// cache.put(mod.name(), mod);
mod.execute(ctx);
return mod;
}
for (var provider : providers) {
var realName = provider.getRealName(cwd, name);
if (realName == null) continue;
if (cache.containsKey(realName)) return cache.get(realName);
var mod = provider.getModule(cwd, name);
// cache.put(mod.name(), mod);
mod.execute(ctx);
return mod;
}
return null;
}
public ModuleManager(File root) {
files = new FileModuleProvider(root, false);
}
}

View File

@ -0,0 +1,9 @@
package me.topchetoeu.jscript.engine.modules;
import java.io.File;
public interface ModuleProvider {
Module getModule(File cwd, String name);
String getRealName(File cwd, String name);
default boolean hasModule(File cwd, String name) { return getRealName(cwd, name) != null; }
}

View File

@ -0,0 +1,83 @@
package me.topchetoeu.jscript.engine.scope;
import java.util.HashSet;
import java.util.Set;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException;
public class GlobalScope implements ScopeRecord {
public final ObjectValue obj;
public boolean has(CallContext ctx, String name) throws InterruptedException {
return obj.hasMember(ctx, name, false);
}
public Object getKey(String name) {
return name;
}
public GlobalScope globalChild() {
return new GlobalScope(this);
}
public LocalScopeRecord child() {
return new LocalScopeRecord(this);
}
public Object define(String name) {
try {
if (obj.hasMember(null, name, true)) return name;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return name;
}
obj.defineProperty(name, null);
return name;
}
public void define(String name, Variable val) {
obj.defineProperty(name,
new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)),
new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }),
true, true
);
}
public void define(String name, boolean readonly, Object val) {
obj.defineProperty(name, val, readonly, true, true);
}
public void define(String... names) {
for (var n : names) define(n);
}
public void define(boolean readonly, FunctionValue val) {
define(val.name, readonly, val);
}
public Object get(CallContext ctx, String name) throws InterruptedException {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return obj.getMember(ctx, name);
}
public void set(CallContext ctx, String name, Object val) throws InterruptedException {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
}
public Set<String> keys() {
var res = new HashSet<String>();
for (var key : keys()) {
if (key instanceof String) res.add((String)key);
}
return res;
}
public GlobalScope() {
this.obj = new ObjectValue();
}
public GlobalScope(GlobalScope parent) {
this.obj = new ObjectValue();
this.obj.setPrototype(null, parent.obj);
}
}

View File

@ -0,0 +1,58 @@
package me.topchetoeu.jscript.engine.scope;
public class LocalScope {
private String[] names;
private LocalScope parent;
public final ValueVariable[] captures;
public final ValueVariable[] locals;
public ValueVariable get(int i) {
if (i >= 0) return locals[i];
else return captures[~i];
}
public String[] getNames() {
var res = new String[locals.length];
for (var i = 0; i < locals.length; i++) {
if (names == null || i >= names.length) res[i] = "local_" + i;
else res[i] = names[i];
}
return res;
}
public void setNames(String[] names) {
this.names = names;
}
public int size() {
return captures.length + locals.length;
}
public GlobalScope toGlobal(GlobalScope global) {
GlobalScope res;
if (parent == null) res = new GlobalScope(global);
else res = new GlobalScope(parent.toGlobal(global));
var names = getNames();
for (var i = 0; i < names.length; i++) {
res.define(names[i], locals[i]);
}
return res;
}
public LocalScope(int n, ValueVariable[] captures) {
locals = new ValueVariable[n];
this.captures = captures;
for (int i = 0; i < n; i++) {
locals[i] = new ValueVariable(false, null);
}
}
public LocalScope(int n, ValueVariable[] captures, LocalScope parent) {
this(n, captures);
this.parent = parent;
}
}

View File

@ -0,0 +1,79 @@
package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList;
import me.topchetoeu.jscript.engine.CallContext;
public class LocalScopeRecord implements ScopeRecord {
public final LocalScopeRecord parent;
public final GlobalScope global;
private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>();
public String[] locals() {
return locals.toArray(String[]::new);
}
public LocalScopeRecord child() {
return new LocalScopeRecord(this, global);
}
public int localsCount() {
return locals.size();
}
public int capturesCount() {
return captures.size();
}
public int[] getCaptures() {
var buff = new int[captures.size()];
var i = 0;
for (var name : captures) {
var index = parent.getKey(name);
if (index instanceof Integer) buff[i++] = (int)index;
}
var res = new int[i];
System.arraycopy(buff, 0, res, 0, i);
return res;
}
public Object getKey(String name) {
var capI = captures.indexOf(name);
var locI = locals.indexOf(name);
if (locI >= 0) return locI;
if (capI >= 0) return ~capI;
if (parent != null) {
var res = parent.getKey(name);
if (res != null && res instanceof Integer) {
captures.add(name);
return -captures.size();
}
}
return name;
}
public boolean has(CallContext ctx, String name) throws InterruptedException {
return
global.has(ctx, name) ||
locals.contains(name) ||
parent != null && parent.has(ctx, name);
}
public Object define(String name) {
if (locals.contains(name)) return locals.indexOf(name);
locals.add(name);
return locals.size() - 1;
}
public LocalScopeRecord(GlobalScope global) {
this.parent = null;
this.global = global;
}
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
this.parent = parent;
this.global = global;
}
}

View File

@ -0,0 +1,7 @@
package me.topchetoeu.jscript.engine.scope;
public interface ScopeRecord {
public Object getKey(String name);
public Object define(String name);
public LocalScopeRecord child();
}

View File

@ -0,0 +1,28 @@
package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.values.Values;
public class ValueVariable implements Variable {
public boolean readonly;
public Object value;
@Override
public boolean readonly() { return readonly; }
@Override
public Object get(CallContext ctx) {
return value;
}
@Override
public void set(CallContext ctx, Object val) {
if (readonly) return;
this.value = Values.normalize(val);
}
public ValueVariable(boolean readonly, Object val) {
this.readonly = readonly;
this.value = val;
}
}

View File

@ -0,0 +1,9 @@
package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.CallContext;
public interface Variable {
Object get(CallContext ctx) throws InterruptedException;
default boolean readonly() { return true; }
default void set(CallContext ctx, Object val) throws InterruptedException { }
}

View File

@ -0,0 +1,160 @@
package me.topchetoeu.jscript.engine.values;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import me.topchetoeu.jscript.engine.CallContext;
public class ArrayValue extends ObjectValue {
private static final Object EMPTY = new Object();
private final ArrayList<Object> values = new ArrayList<>();
public int size() { return values.size(); }
public boolean setSize(int val) {
if (val < 0) return false;
while (size() > val) {
values.remove(values.size() - 1);
}
while (size() < val) {
values.add(EMPTY);
}
return true;
}
public Object get(int i) {
if (i < 0 || i >= values.size()) return null;
var res = values.get(i);
if (res == EMPTY) return null;
else return res;
}
public void set(int i, Object val) {
if (i < 0) return;
while (values.size() <= i) {
values.add(EMPTY);
}
values.set(i, Values.normalize(val));
}
public boolean has(int i) {
return i >= 0 && i < values.size() && values.get(i) != EMPTY;
}
public void remove(int i) {
if (i < 0 || i >= values.size()) return;
values.set(i, EMPTY);
}
public void shrink(int n) {
if (n > values.size()) values.clear();
else {
for (int i = 0; i < n && values.size() > 0; i++) {
values.remove(values.size() - 1);
}
}
}
public void sort(Comparator<Object> comparator) {
values.sort((a, b) -> {
var _a = 0;
var _b = 0;
if (a == null) _a = 1;
if (a == EMPTY) _a = 2;
if (b == null) _b = 1;
if (b == EMPTY) _b = 2;
if (Integer.compare(_a, _b) != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
public Object[] toArray() {
Object[] res = new Object[values.size()];
for (var i = 0; i < values.size(); i++) {
if (values.get(i) == EMPTY) res[i] = null;
else res[i] = values.get(i);
}
return res;
}
@Override
protected Object getField(CallContext ctx, Object key) throws InterruptedException {
if (key.equals("length")) return values.size();
if (key instanceof Number) {
var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) {
return get((int)i);
}
}
return super.getField(ctx, key);
}
@Override
protected boolean setField(CallContext ctx, Object key, Object val) throws InterruptedException {
if (key.equals("length")) {
return setSize((int)Values.toNumber(ctx, val));
}
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
set((int)i, val);
return true;
}
}
return super.setField(ctx, key, val);
}
@Override
protected boolean hasField(CallContext ctx, Object key) throws InterruptedException {
if (key.equals("length")) return true;
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
return has((int)i);
}
}
return super.hasField(ctx, key);
}
@Override
protected void deleteField(CallContext ctx, Object key) throws InterruptedException {
if (key instanceof Number) {
var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) {
remove((int)i);
return;
}
}
super.deleteField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i);
}
if (includeNonEnumerable) res.add("length");
return res;
}
public ArrayValue() {
super(PlaceholderProto.ARRAY);
nonEnumerableSet.add("length");
nonConfigurableSet.add("length");
}
public ArrayValue(Object ...values) {
this();
for (var i = 0; i < values.length; i++) this.values.add(values[i]);
}
public static ArrayValue of(Collection<Object> values) {
return new ArrayValue(values.toArray(Object[]::new));
}
}

View File

@ -0,0 +1,50 @@
package me.topchetoeu.jscript.engine.values;
import java.util.LinkedHashMap;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class CodeFunction extends FunctionValue {
public final int localsN;
public final int length;
public final Instruction[] body;
public final LinkedHashMap<Location, Integer> breakableLocToIndex = new LinkedHashMap<>();
public final LinkedHashMap<Integer, Location> breakableIndexToLoc = new LinkedHashMap<>();
public final ValueVariable[] captures;
public final GlobalScope globals;
public Location loc() {
for (var instr : body) {
if (instr.location != null) return instr.location;
}
return null;
}
@Override
public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException {
return new CodeFrame(thisArg, args, this).run(ctx);
}
public CodeFunction(String name, int localsN, int length, GlobalScope globals, ValueVariable[] captures, Instruction[] body) {
super(name, length);
this.captures = captures;
this.globals = globals;
this.localsN = localsN;
this.length = length;
this.body = body;
for (var i = 0; i < body.length; i++) {
if (body[i].type == Type.LOAD_FUNC) i += (int)body[i].get(0);
if (body[i].debugged && body[i].location != null) {
breakableLocToIndex.put(body[i].location, i);
breakableIndexToLoc.put(i, body[i].location);
}
}
}
}

View File

@ -0,0 +1,70 @@
package me.topchetoeu.jscript.engine.values;
import java.util.List;
import me.topchetoeu.jscript.engine.CallContext;
public abstract class FunctionValue extends ObjectValue {
public String name = "";
public boolean special = false;
public int length;
@Override
public String toString() {
return "function(...) { ... }";
}
public abstract Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException;
public Object call(CallContext ctx) throws InterruptedException {
return call(ctx, null);
}
@Override
protected Object getField(CallContext ctx, Object key) throws InterruptedException {
if (key.equals("name")) return name;
if (key.equals("length")) return length;
return super.getField(ctx, key);
}
@Override
protected boolean setField(CallContext ctx, Object key, Object val) throws InterruptedException {
if (key.equals("name")) name = Values.toString(ctx, val);
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
else return super.setField(ctx, key, val);
return true;
}
@Override
protected boolean hasField(CallContext ctx, Object key) throws InterruptedException {
if (key.equals("name")) return true;
if (key.equals("length")) return true;
return super.hasField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
if (includeNonEnumerable) {
res.add("name");
res.add("length");
}
return res;
}
public FunctionValue(String name, int length) {
super(PlaceholderProto.FUNCTION);
if (name == null) name = "";
this.length = length;
this.name = name;
nonConfigurableSet.add("name");
nonEnumerableSet.add("name");
nonWritableSet.add("length");
nonConfigurableSet.add("length");
nonEnumerableSet.add("length");
var proto = new ObjectValue();
proto.defineProperty("constructor", this, true, false, false);
this.defineProperty("prototype", proto, true, false, false);
}
}

View File

@ -0,0 +1,21 @@
package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.engine.CallContext;
public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner {
Object run(CallContext ctx, Object thisArg, Object[] values) throws InterruptedException;
}
public final NativeFunctionRunner action;
@Override
public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException {
return action.run(ctx, thisArg, args);
}
public NativeFunction(String name, NativeFunctionRunner action) {
super(name, 0);
this.action = action;
}
}

View File

@ -0,0 +1,19 @@
package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.engine.CallContext;
public class NativeWrapper extends ObjectValue {
private static final Object NATIVE_PROTO = new Object();
public final Object wrapped;
@Override
public ObjectValue getPrototype(CallContext ctx) throws InterruptedException {
if (prototype == NATIVE_PROTO) return ctx.engine.getPrototype(wrapped.getClass());
else return super.getPrototype(ctx);
}
public NativeWrapper(Object wrapped) {
this.wrapped = wrapped;
prototype = NATIVE_PROTO;
}
}

View File

@ -0,0 +1,331 @@
package me.topchetoeu.jscript.engine.values;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.engine.CallContext;
public class ObjectValue {
public static enum PlaceholderProto {
NONE,
OBJECT,
ARRAY,
FUNCTION,
ERROR,
SYNTAX_ERROR,
TYPE_ERROR,
RANGE_ERROR,
}
public static enum State {
NORMAL,
NO_EXTENSIONS,
SEALED,
FROZEN,
}
public static record Property(FunctionValue getter, FunctionValue setter) {}
private static final Object OBJ_PROTO = new Object();
private static final Object ARR_PROTO = new Object();
private static final Object FUNC_PROTO = new Object();
private static final Object ERR_PROTO = new Object();
private static final Object SYNTAX_ERR_PROTO = new Object();
private static final Object TYPE_ERR_PROTO = new Object();
private static final Object RANGE_ERR_PROTO = new Object();
protected Object prototype;
public State state = State.NORMAL;
public HashMap<Object, Object> values = new HashMap<>();
public HashMap<Object, Property> properties = new HashMap<>();
public HashSet<Object> nonWritableSet = new HashSet<>();
public HashSet<Object> nonConfigurableSet = new HashSet<>();
public HashSet<Object> nonEnumerableSet = new HashSet<>();
public final boolean memberWritable(Object key) {
if (state == State.FROZEN) return false;
return !nonWritableSet.contains(key);
}
public final boolean memberConfigurable(Object key) {
if (state == State.SEALED || state == State.FROZEN) return false;
return !nonConfigurableSet.contains(key);
}
public final boolean memberEnumerable(Object key) {
return !nonEnumerableSet.contains(key);
}
public final boolean extensible() {
return state == State.NORMAL;
}
public final void preventExtensions() {
if (state == State.NORMAL) state = State.NO_EXTENSIONS;
}
public final void seal() {
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
}
public final void freeze() {
state = State.FROZEN;
}
public final boolean defineProperty(Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
key = Values.normalize(key); val = Values.normalize(val);
boolean reconfigured =
writable != memberWritable(key) ||
configurable != memberConfigurable(key) ||
enumerable != memberEnumerable(key);
if (!reconfigured) {
if (!memberWritable(key)) {
var a = values.get(key);
var b = val;
if (a == null || b == null) return a == null && b == null;
return a == b || a.equals(b);
}
values.put(key, val);
return true;
}
if (
properties.containsKey(key) &&
values.get(key) == val &&
!reconfigured
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key))
return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!writable) nonWritableSet.add(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
values.put(key, val);
return true;
}
public final boolean defineProperty(Object key, Object val) {
return defineProperty(Values.normalize(key), Values.normalize(val), true, true, true);
}
public final boolean defineProperty(Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
key = Values.normalize(key);
if (
properties.containsKey(key) &&
properties.get(key).getter == getter &&
properties.get(key).setter == setter &&
!configurable == nonConfigurableSet.contains(key) &&
!enumerable == nonEnumerableSet.contains(key)
) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) return false;
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
properties.remove(key);
values.remove(key);
if (!configurable) nonConfigurableSet.add(key);
if (!enumerable) nonEnumerableSet.add(key);
properties.put(key, new Property(getter, setter));
return true;
}
public ObjectValue getPrototype(CallContext ctx) throws InterruptedException {
try {
if (prototype == OBJ_PROTO) return ctx.engine().objectProto();
if (prototype == ARR_PROTO) return ctx.engine().arrayProto();
if (prototype == FUNC_PROTO) return ctx.engine().functionProto();
if (prototype == ERR_PROTO) return ctx.engine().errorProto();
if (prototype == RANGE_ERR_PROTO) return ctx.engine().rangeErrorProto();
if (prototype == SYNTAX_ERR_PROTO) return ctx.engine().syntaxErrorProto();
if (prototype == TYPE_ERR_PROTO) return ctx.engine().typeErrorProto();
}
catch (NullPointerException e) {
return null;
}
return (ObjectValue)prototype;
}
public final boolean setPrototype(CallContext ctx, Object val) {
val = Values.normalize(val);
if (!extensible()) return false;
if (val == null || val == Values.NULL) prototype = null;
else if (Values.isObject(val)) {
var obj = Values.object(val);
if (ctx != null && ctx.engine() != null) {
if (obj == ctx.engine().objectProto()) prototype = OBJ_PROTO;
else if (obj == ctx.engine().arrayProto()) prototype = ARR_PROTO;
else if (obj == ctx.engine().functionProto()) prototype = FUNC_PROTO;
else if (obj == ctx.engine().errorProto()) prototype = ERR_PROTO;
else if (obj == ctx.engine().syntaxErrorProto()) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.engine().typeErrorProto()) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.engine().rangeErrorProto()) prototype = RANGE_ERR_PROTO;
else prototype = obj;
}
else prototype = obj;
return true;
}
return false;
}
public final boolean setPrototype(PlaceholderProto val) {
if (!extensible()) return false;
switch (val) {
case OBJECT: prototype = OBJ_PROTO; break;
case FUNCTION: prototype = FUNC_PROTO; break;
case ARRAY: prototype = ARR_PROTO; break;
case ERROR: prototype = ERR_PROTO; break;
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
case NONE: prototype = null; break;
}
return true;
}
protected Property getProperty(CallContext ctx, Object key) throws InterruptedException {
if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(ctx);
if (proto != null) return proto.getProperty(ctx, key);
else return null;
}
protected Object getField(CallContext ctx, Object key) throws InterruptedException {
if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(ctx);
if (proto != null) return proto.getField(ctx, key);
else return null;
}
protected boolean setField(CallContext ctx, Object key, Object val) throws InterruptedException {
values.put(key, val);
return true;
}
protected void deleteField(CallContext ctx, Object key) throws InterruptedException {
values.remove(key);
}
protected boolean hasField(CallContext ctx, Object key) throws InterruptedException {
return values.containsKey(key);
}
public final Object getMember(CallContext ctx, Object key, Object thisArg) throws InterruptedException {
key = Values.normalize(key);
if (key.equals("__proto__")) {
var res = getPrototype(ctx);
return res == null ? Values.NULL : res;
}
var prop = getProperty(ctx, key);
if (prop != null) {
if (prop.getter == null) return null;
else return prop.getter.call(ctx, Values.normalize(thisArg));
}
else return getField(ctx, key);
}
public final Object getMember(CallContext ctx, Object key) throws InterruptedException {
return getMember(ctx, key, this);
}
public final boolean setMember(CallContext ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException {
key = Values.normalize(key); val = Values.normalize(val);
var prop = getProperty(ctx, key);
if (prop != null) {
if (prop.setter == null) return false;
prop.setter.call(ctx, Values.normalize(thisArg), val);
return true;
}
else if (onlyProps) return false;
else if (!extensible() && !values.containsKey(key)) return false;
else if (key == null) {
values.put(key, val);
return true;
}
else if (key.equals("__proto__")) return setPrototype(ctx, val);
else if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val);
}
public final boolean setMember(CallContext ctx, Object key, Object val, boolean onlyProps) throws InterruptedException {
return setMember(ctx, Values.normalize(key), Values.normalize(val), this, onlyProps);
}
public final boolean hasMember(CallContext ctx, Object key, boolean own) throws InterruptedException {
key = Values.normalize(key);
if (key != null && key.equals("__proto__")) return true;
if (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true;
if (own) return false;
return prototype != null && getPrototype(ctx).hasMember(ctx, key, own);
}
public final boolean deleteMember(CallContext ctx, Object key) throws InterruptedException {
key = Values.normalize(key);
if (!memberConfigurable(key)) return false;
properties.remove(key);
nonWritableSet.remove(key);
nonEnumerableSet.remove(key);
deleteField(ctx, key);
return true;
}
public final ObjectValue getMemberDescriptor(CallContext ctx, Object key) throws InterruptedException {
key = Values.normalize(key);
var prop = properties.get(key);
var res = new ObjectValue();
res.defineProperty("configurable", memberConfigurable(key));
res.defineProperty("enumerable", memberEnumerable(key));
if (prop != null) {
res.defineProperty("get", prop.getter);
res.defineProperty("set", prop.setter);
}
else if (hasField(ctx, key)) {
res.defineProperty("value", values.get(key));
res.defineProperty("writable", memberWritable(key));
}
else return null;
return res;
}
public List<Object> keys(boolean includeNonEnumerable) {
var res = new ArrayList<Object>();
for (var key : values.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
for (var key : properties.keySet()) {
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
res.add(key);
}
return res;
}
public ObjectValue(Map<?, ?> values) {
this(PlaceholderProto.OBJECT);
for (var el : values.entrySet()) {
defineProperty(el.getKey(), el.getValue());
}
}
public ObjectValue(PlaceholderProto proto) {
nonConfigurableSet.add("__proto__");
nonEnumerableSet.add("__proto__");
setPrototype(proto);
}
public ObjectValue() {
this(PlaceholderProto.OBJECT);
}
}

View File

@ -0,0 +1,17 @@
package me.topchetoeu.jscript.engine.values;
public final class SignalValue {
public final String data;
public SignalValue(String data) {
this.data = data;
}
public static boolean isSignal(Object signal, String value) {
if (!(signal instanceof SignalValue)) return false;
var val = ((SignalValue)signal).data;
if (value.endsWith("*")) return val.startsWith(value.substring(0, value.length() - 1));
else return val.equals(value);
}
}

Some files were not shown because too many files have changed in this diff Show More