diff --git a/src/assets/favicon.png b/src/assets/debugger/favicon.png similarity index 100% rename from src/assets/favicon.png rename to src/assets/debugger/favicon.png diff --git a/src/assets/index.html b/src/assets/debugger/index.html similarity index 100% rename from src/assets/index.html rename to src/assets/debugger/index.html diff --git a/src/assets/protocol.json b/src/assets/debugger/protocol.json similarity index 100% rename from src/assets/protocol.json rename to src/assets/debugger/protocol.json diff --git a/src/me/topchetoeu/jscript/js/bootstrap.js b/src/assets/js/bootstrap.js similarity index 77% rename from src/me/topchetoeu/jscript/js/bootstrap.js rename to src/assets/js/bootstrap.js index 6736a28..74f854d 100644 --- a/src/me/topchetoeu/jscript/js/bootstrap.js +++ b/src/assets/js/bootstrap.js @@ -1,8 +1,8 @@ -(function (_arguments) { - var ts = _arguments[0]; +(function (ts, env, libs) { var src = '', version = 0; - var lib = _arguments[2].concat([ - 'declare const exit: never; declare const go: any;', + var lib = libs.concat([ + 'declare function exit(): never;', + 'declare function go(): any;', 'declare function getTsDeclarations(): string[];' ]).join(''); var libSnapshot = ts.ScriptSnapshot.fromString(lib); @@ -21,6 +21,7 @@ forceConsistentCasingInFileNames: true, experimentalDecorators: true, strict: true, + sourceMap: true, }; var reg = ts.createDocumentRegistry(); @@ -55,6 +56,8 @@ service.getEmitOutput("/lib.d.ts"); log("Loaded libraries!"); + var oldCompile = env.compile; + function compile(code, filename, env) { src = code; version++; @@ -82,21 +85,20 @@ throw new SyntaxError(diagnostics.join("\n")); } - var result = emit.outputFiles[0].text; - var declaration = emit.outputFiles[1].text; - + var map = JSON.parse(emit.outputFiles[0].text); + var result = emit.outputFiles[1].text; + var declaration = emit.outputFiles[2].text; + + var compiled = oldCompile(result, filename, env); return { - source: result, - runner: function(func) { - return function() { - var val = func.apply(this, arguments); - if (declaration !== '') { - declSnapshots.push(ts.ScriptSnapshot.fromString(declaration)); - } - return val; - } - } + function: function () { + var val = compiled.function.apply(this, arguments); + if (declaration !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration)); + return val; + }, + breakpoints: compiled.breakpoints, + mapChain: compiled.mapChain.concat(map.mappings), }; } @@ -107,5 +109,5 @@ } } - apply(_arguments[1]); -})(arguments); + apply(env); +})(arguments[0], arguments[1], arguments[2]); diff --git a/src/me/topchetoeu/jscript/js/lib.d.ts b/src/assets/js/lib.d.ts similarity index 94% rename from src/me/topchetoeu/jscript/js/lib.d.ts rename to src/assets/js/lib.d.ts index a128ca4..b843aba 100644 --- a/src/me/topchetoeu/jscript/js/lib.d.ts +++ b/src/assets/js/lib.d.ts @@ -1,618 +1,618 @@ -type PropertyDescriptor = { - 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 extends U ? never : T; -type Extract = T extends U ? T : never; -type Record = { [x in KeyT]: ValT } -type ReplaceFunc = (match: string, ...args: any[]) => string; - -type PromiseFulfillFunc = (val: T) => void; -type PromiseThenFunc = (val: T) => NextT; -type PromiseRejectFunc = (err: unknown) => void; -type PromiseFunc = (resolve: PromiseFulfillFunc, reject: PromiseRejectFunc) => void; - -type PromiseResult ={ type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; } - -// wippidy-wine, this code is now mine :D -type Awaited = - 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 : // recursively unwrap the value - never : // the argument to `then` was not callable - T; - -type IteratorYieldResult = - { done?: false; } & - (TReturn extends undefined ? { value?: undefined; } : { value: TReturn; }); - -type IteratorReturnResult = - { done: true } & - (TReturn extends undefined ? { value?: undefined; } : { value: TReturn; }); - -type IteratorResult = IteratorYieldResult | IteratorReturnResult; - -interface Thenable { - then(onFulfilled: PromiseThenFunc, onRejected?: PromiseRejectFunc): Promise>; - then(onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise; -} - -interface RegExpResultIndices extends Array<[number, number]> { - groups?: { [name: string]: [number, number]; }; -} -interface RegExpResult extends Array { - groups?: { [name: string]: string; }; - index: number; - input: string; - indices?: RegExpResultIndices; - escape(raw: string, flags: string): RegExp; -} - -interface Matcher { - [Symbol.match](target: string): RegExpResult | string[] | null; - [Symbol.matchAll](target: string): IterableIterator; -} -interface Splitter { - [Symbol.split](target: string, limit?: number, sensible?: boolean): string[]; -} -interface Replacer { - [Symbol.replace](target: string, replacement: string | ReplaceFunc): string; -} -interface Searcher { - [Symbol.search](target: string, reverse?: boolean, start?: number): number; -} - -type FlatArray = { - "done": Arr, - "recur": Arr extends Array - ? FlatArray - : Arr -}[Depth extends -1 ? "done" : "recur"]; - -interface IArguments { - [i: number]: any; - length: number; -} - -interface Iterator { - next(...args: [] | [TNext]): IteratorResult; - return?(value?: TReturn): IteratorResult; - throw?(e?: any): IteratorResult; -} -interface Iterable { - [Symbol.iterator](): Iterator; -} -interface IterableIterator extends Iterator { - [Symbol.iterator](): IterableIterator; -} - -interface AsyncIterator { - next(...args: [] | [TNext]): Promise>; - return?(value?: TReturn | Thenable): Promise>; - throw?(e?: any): Promise>; -} -interface AsyncIterable { - [Symbol.asyncIterator](): AsyncIterator; -} -interface AsyncIterableIterator extends AsyncIterator { - [Symbol.asyncIterator](): AsyncIterableIterator; -} - -interface Generator extends Iterator { - [Symbol.iterator](): Generator; - return(value: TReturn): IteratorResult; - throw(e: any): IteratorResult; -} -interface GeneratorFunction { - new (...args: any[]): Generator; - (...args: any[]): Generator; - readonly length: number; - readonly name: string; - readonly prototype: Generator; -} - -interface AsyncGenerator extends AsyncIterator { - return(value: TReturn | Thenable): Promise>; - throw(e: any): Promise>; - [Symbol.asyncIterator](): AsyncGenerator; -} -interface AsyncGeneratorFunction { - new (...args: any[]): AsyncGenerator; - (...args: any[]): AsyncGenerator; - readonly length: number; - readonly name: string; - readonly prototype: AsyncGenerator; -} - - -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; -} - -interface Array extends IterableIterator { - [i: number]: T; - - 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(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(depth?: D): FlatArray; - flatMap(func: (val: T, i: number, arr: T[]) => T | T[], thisAarg?: any): FlatArray; - 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(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(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; - - entries(): IterableIterator<[number, T]>; - values(): IterableIterator; - keys(): IterableIterator; -} -interface ArrayConstructor { - new (arrayLength?: number): T[]; - new (...items: T[]): T[]; - (arrayLength?: number): T[]; - (...items: T[]): T[]; - isArray(arg: any): arg is any[]; - prototype: Array; -} - -interface Boolean { - toString(): string; - valueOf(): boolean; -} -interface BooleanConstructor { - (val: any): boolean; - new (val: any): Boolean; - prototype: Boolean; -} - -interface Error { - name: string; - message: string; - stack: string[]; - toString(): 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 { - name: 'TypeError'; -} - -interface RangeErrorConstructor extends ErrorConstructor { - (msg?: any): RangeError; - new (msg?: any): RangeError; - prototype: Error; -} -interface RangeError extends Error { - name: 'RangeError'; -} - -interface SyntaxErrorConstructor extends ErrorConstructor { - (msg?: any): RangeError; - new (msg?: any): RangeError; - prototype: Error; -} -interface SyntaxError extends Error { - name: 'SyntaxError'; -} - -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; - readonly length: number; - name: string; -} -interface CallableFunction extends Function { - (...args: any[]): any; - apply(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, argArray?: Args): RetT; - call(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, ...argArray: Args): RetT; - bind(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(this: new (...args: Args) => RetT, thisArg: any, argArray?: Args): RetT; - call(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): RetT; - bind(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): new (...args: Args) => RetT; -} -interface FunctionConstructor extends Function { - (...args: string[]): (...args: any[]) => any; - new (...args: string[]): (...args: any[]) => any; - prototype: Function; - async( - func: (await: (val: T) => Awaited) => (...args: ArgsT) => RetT - ): (...args: ArgsT) => Promise; - asyncGenerator( - func: (await: (val: T) => Awaited, _yield: (val: T) => void) => (...args: ArgsT) => RetT - ): (...args: ArgsT) => AsyncGenerator; - generator( - func: (_yield: (val: T) => TNext) => (...args: ArgsT) => RetT - ): (...args: ArgsT) => Generator; -} - -interface Number { - toString(): string; - valueOf(): number; -} -interface NumberConstructor { - (val: any): number; - new (val: any): Number; - prototype: Number; - parseInt(val: unknown): number; - parseFloat(val: unknown): number; -} - -interface Object { - constructor: NewableFunction; - [Symbol.typeName]: string; - - valueOf(): this; - toString(): string; - hasOwnProperty(key: any): boolean; -} -interface ObjectConstructor { - (arg: string): String; - (arg: number): Number; - (arg: boolean): Boolean; - (arg?: undefined | null): {}; - (arg: T): T; - - new (arg: string): String; - new (arg: number): Number; - new (arg: boolean): Boolean; - new (arg?: undefined | null): {}; - new (arg: T): T; - - prototype: Object; - - assign(target: T, ...src: object[]): T; - create(proto: T, props?: { [key: string]: PropertyDescriptor }): T; - - keys(obj: T, onlyString?: true): (keyof T)[]; - keys(obj: T, onlyString: false): any[]; - entries(obj: T, onlyString?: true): [keyof T, T[keyof T]][]; - entries(obj: T, onlyString: false): [any, any][]; - values(obj: T, onlyString?: true): (T[keyof T])[]; - values(obj: T, onlyString: false): any[]; - - fromEntries(entries: Iterable<[any, any]>): object; - - defineProperty(obj: ThisT, key: any, desc: PropertyDescriptor): ThisT; - defineProperties(obj: ThisT, desc: { [key: string]: PropertyDescriptor }): ThisT; - - getOwnPropertyNames(obj: T): (keyof T)[]; - getOwnPropertySymbols(obj: T): (keyof T)[]; - hasOwn(obj: T, key: KeyT): boolean; - - getOwnPropertyDescriptor(obj: T, key: KeyT): PropertyDescriptor; - getOwnPropertyDescriptors(obj: T): { [x in keyof T]: PropertyDescriptor }; - - getPrototypeOf(obj: any): object | null; - setPrototypeOf(obj: T, proto: object | null): T; - - preventExtensions(obj: T): T; - seal(obj: T): T; - freeze(obj: T): T; - - isExtensible(obj: object): boolean; - isSealed(obj: object): boolean; - isFrozen(obj: object): boolean; -} - -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 | ReplaceFunc): string; - replaceAll(pattern: string | Replacer, val: string | ReplaceFunc): string; - - match(pattern: string | Matcher): RegExpResult | string[] | null; - matchAll(pattern: string | Matcher): IterableIterator; - - 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; -} -interface StringConstructor { - (val: any): string; - new (val: any): String; - - fromCharCode(val: number): string; - - prototype: String; -} - -interface Symbol { - valueOf(): symbol; -} -interface SymbolConstructor { - (val?: any): symbol; - new(...args: any[]): never; - prototype: Symbol; - for(key: string): symbol; - keyFor(sym: symbol): string; - - readonly typeName: unique symbol; - readonly match: unique symbol; - readonly matchAll: unique symbol; - readonly split: unique symbol; - readonly replace: unique symbol; - readonly search: unique symbol; - readonly iterator: unique symbol; - readonly asyncIterator: unique symbol; -} - -interface Promise extends Thenable { - catch(func: PromiseRejectFunc): Promise; - finally(func: () => void): Promise; -} -interface PromiseConstructor { - prototype: Promise; - - new (func: PromiseFunc): Promise>; - resolve(val: T): Promise>; - reject(val: any): Promise; - - isAwaitable(val: unknown): val is Thenable; - any(promises: T[]): Promise>; - race(promises: (Promise|T)[]): Promise; - all(promises: T): Promise<{ [Key in keyof T]: Awaited }>; - allSettled(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult>}]>; -} - -interface FileStat { - type: 'file' | 'folder'; - mode: 'r' | 'rw'; -} -interface File { - readonly pointer: Promise; - readonly length: Promise; - readonly mode: Promise<'' | 'r' | 'rw'>; - - read(n: number): Promise; - write(buff: number[]): Promise; - close(): Promise; - setPointer(val: number): Promise; -} -interface Filesystem { - open(path: string, mode: 'r' | 'rw'): Promise; - ls(path: string): AsyncIterableIterator; - mkdir(path: string): Promise; - mkfile(path: string): Promise; - rm(path: string, recursive?: boolean): Promise; - stat(path: string): Promise; - exists(path: string): Promise; -} - -interface Encoding { - encode(val: string): number[]; - decode(val: number[]): string; -} - -declare var String: StringConstructor; -//@ts-ignore -declare const arguments: IArguments; -declare var NaN: number; -declare var Infinity: number; - -declare var setTimeout: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number; -declare var setInterval: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number; - -declare var clearTimeout: (id: number) => void; -declare var clearInterval: (id: number) => void; - -declare var parseInt: typeof Number.parseInt; -declare var parseFloat: typeof Number.parseFloat; - -declare function log(...vals: any[]): void; - -declare var Array: ArrayConstructor; -declare var Boolean: BooleanConstructor; -declare var Promise: PromiseConstructor; -declare var Function: FunctionConstructor; -declare var Number: NumberConstructor; -declare var Object: ObjectConstructor; -declare var Symbol: SymbolConstructor; -declare var Promise: PromiseConstructor; -declare var Math: MathObject; -declare var Encoding: Encoding; -declare var Filesystem: Filesystem; - -declare var Error: ErrorConstructor; -declare var RangeError: RangeErrorConstructor; -declare var TypeError: TypeErrorConstructor; -declare var SyntaxError: SyntaxErrorConstructor; - -declare class Map { - public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>; - - public clear(): void; - public delete(key: KeyT): boolean; - - public entries(): IterableIterator<[KeyT, ValueT]>; - public keys(): IterableIterator; - public values(): IterableIterator; - - 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) => void, thisArg?: any): void; - - public constructor(); -} -declare class Set { - public [Symbol.iterator](): IterableIterator; - - public entries(): IterableIterator<[T, T]>; - public keys(): IterableIterator; - public values(): IterableIterator; - - 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) => void, thisArg?: any): void; - - public constructor(); -} - -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; - [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); -} +type PropertyDescriptor = { + 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 extends U ? never : T; +type Extract = T extends U ? T : never; +type Record = { [x in KeyT]: ValT } +type ReplaceFunc = (match: string, ...args: any[]) => string; + +type PromiseResult = { type: 'fulfilled'; value: T; } | { type: 'rejected'; reason: any; } + +// wippidy-wine, this code is now mine :D +type Awaited = + 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 : // recursively unwrap the value + never : // the argument to `then` was not callable + T; + +type IteratorYieldResult = + { done?: false; } & + (TReturn extends undefined ? { value?: undefined; } : { value: TReturn; }); + +type IteratorReturnResult = + { done: true } & + (TReturn extends undefined ? { value?: undefined; } : { value: TReturn; }); + +type IteratorResult = IteratorYieldResult | IteratorReturnResult; + +interface Thenable { + then(onFulfilled?: (val: T) => NextT, onRejected?: (err: any) => NextT): Promise>; +} + +interface RegExpResultIndices extends Array<[number, number]> { + groups?: { [name: string]: [number, number]; }; +} +interface RegExpResult extends Array { + groups?: { [name: string]: string; }; + index: number; + input: string; + indices?: RegExpResultIndices; + escape(raw: string, flags: string): RegExp; +} + +interface Matcher { + [Symbol.match](target: string): RegExpResult | string[] | null; + [Symbol.matchAll](target: string): IterableIterator; +} +interface Splitter { + [Symbol.split](target: string, limit?: number, sensible?: boolean): string[]; +} +interface Replacer { + [Symbol.replace](target: string, replacement: string | ReplaceFunc): string; +} +interface Searcher { + [Symbol.search](target: string, reverse?: boolean, start?: number): number; +} + +type FlatArray = { + "done": Arr, + "recur": Arr extends Array + ? FlatArray + : Arr +}[Depth extends -1 ? "done" : "recur"]; + +interface IArguments { + [i: number]: any; + length: number; +} + +interface Iterator { + next(...args: [] | [TNext]): IteratorResult; + return?(value?: TReturn): IteratorResult; + throw?(e?: any): IteratorResult; +} +interface Iterable { + [Symbol.iterator](): Iterator; +} +interface IterableIterator extends Iterator { + [Symbol.iterator](): IterableIterator; +} + +interface AsyncIterator { + next(...args: [] | [TNext]): Promise>; + return?(value?: TReturn | Thenable): Promise>; + throw?(e?: any): Promise>; +} +interface AsyncIterable { + [Symbol.asyncIterator](): AsyncIterator; +} +interface AsyncIterableIterator extends AsyncIterator { + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +interface Generator extends Iterator { + [Symbol.iterator](): Generator; + return(value: TReturn): IteratorResult; + throw(e: any): IteratorResult; +} +interface GeneratorFunction { + new (...args: any[]): Generator; + (...args: any[]): Generator; + readonly length: number; + readonly name: string; + readonly prototype: Generator; +} + +interface AsyncGenerator extends AsyncIterator { + return(value: TReturn | Thenable): Promise>; + throw(e: any): Promise>; + [Symbol.asyncIterator](): AsyncGenerator; +} +interface AsyncGeneratorFunction { + new (...args: any[]): AsyncGenerator; + (...args: any[]): AsyncGenerator; + readonly length: number; + readonly name: string; + readonly prototype: AsyncGenerator; +} + + +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; +} + +interface Array extends IterableIterator { + [i: number]: T; + + 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(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(depth?: D): FlatArray; + flatMap(func: (val: T, i: number, arr: T[]) => T | T[], thisAarg?: any): FlatArray; + 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(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(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; + + entries(): IterableIterator<[number, T]>; + values(): IterableIterator; + keys(): IterableIterator; +} +interface ArrayConstructor { + new (arrayLength?: number): T[]; + new (...items: T[]): T[]; + (arrayLength?: number): T[]; + (...items: T[]): T[]; + isArray(arg: any): arg is any[]; + prototype: Array; +} + +interface Boolean { + toString(): string; + valueOf(): boolean; +} +interface BooleanConstructor { + (val: any): boolean; + new (val: any): Boolean; + prototype: Boolean; +} + +interface Error { + name: string; + message: string; + stack: string[]; + toString(): 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 { + name: 'TypeError'; +} + +interface RangeErrorConstructor extends ErrorConstructor { + (msg?: any): RangeError; + new (msg?: any): RangeError; + prototype: Error; +} +interface RangeError extends Error { + name: 'RangeError'; +} + +interface SyntaxErrorConstructor extends ErrorConstructor { + (msg?: any): RangeError; + new (msg?: any): RangeError; + prototype: Error; +} +interface SyntaxError extends Error { + name: 'SyntaxError'; +} + +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; + readonly length: number; + name: string; +} +interface CallableFunction extends Function { + (...args: any[]): any; + apply(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, argArray?: Args): RetT; + call(this: (this: ThisArg, ...args: Args) => RetT, thisArg: ThisArg, ...argArray: Args): RetT; + bind(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(this: new (...args: Args) => RetT, thisArg: any, argArray?: Args): RetT; + call(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): RetT; + bind(this: new (...args: Args) => RetT, thisArg: any, ...argArray: Args): new (...args: Args) => RetT; +} +interface FunctionConstructor extends Function { + (...args: string[]): (...args: any[]) => any; + new (...args: string[]): (...args: any[]) => any; + prototype: Function; + async( + func: (await: (val: T) => Awaited) => (...args: ArgsT) => RetT + ): (...args: ArgsT) => Promise; + asyncGenerator( + func: (await: (val: T) => Awaited, _yield: (val: T) => void) => (...args: ArgsT) => RetT + ): (...args: ArgsT) => AsyncGenerator; + generator( + func: (_yield: (val: T) => TNext) => (...args: ArgsT) => RetT + ): (...args: ArgsT) => Generator; +} + +interface Number { + toString(): string; + valueOf(): number; +} +interface NumberConstructor { + (val: any): number; + new (val: any): Number; + prototype: Number; + parseInt(val: unknown): number; + parseFloat(val: unknown): number; +} + +interface Object { + constructor: NewableFunction; + [Symbol.typeName]: string; + + valueOf(): this; + toString(): string; + hasOwnProperty(key: any): boolean; +} +interface ObjectConstructor { + (arg: string): String; + (arg: number): Number; + (arg: boolean): Boolean; + (arg?: undefined | null): {}; + (arg: T): T; + + new (arg: string): String; + new (arg: number): Number; + new (arg: boolean): Boolean; + new (arg?: undefined | null): {}; + new (arg: T): T; + + prototype: Object; + + assign(target: T, ...src: object[]): T; + create(proto: T, props?: { [key: string]: PropertyDescriptor }): T; + + keys(obj: T, onlyString?: true): (keyof T)[]; + keys(obj: T, onlyString: false): any[]; + entries(obj: T, onlyString?: true): [keyof T, T[keyof T]][]; + entries(obj: T, onlyString: false): [any, any][]; + values(obj: T, onlyString?: true): (T[keyof T])[]; + values(obj: T, onlyString: false): any[]; + + fromEntries(entries: Iterable<[any, any]>): object; + + defineProperty(obj: ThisT, key: any, desc: PropertyDescriptor): ThisT; + defineProperties(obj: ThisT, desc: { [key: string]: PropertyDescriptor }): ThisT; + + getOwnPropertyNames(obj: T): (keyof T)[]; + getOwnPropertySymbols(obj: T): (keyof T)[]; + hasOwn(obj: T, key: KeyT): boolean; + + getOwnPropertyDescriptor(obj: T, key: KeyT): PropertyDescriptor; + getOwnPropertyDescriptors(obj: T): { [x in keyof T]: PropertyDescriptor }; + + getPrototypeOf(obj: any): object | null; + setPrototypeOf(obj: T, proto: object | null): T; + + preventExtensions(obj: T): T; + seal(obj: T): T; + freeze(obj: T): T; + + isExtensible(obj: object): boolean; + isSealed(obj: object): boolean; + isFrozen(obj: object): boolean; +} + +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 | ReplaceFunc): string; + replaceAll(pattern: string | Replacer, val: string | ReplaceFunc): string; + + match(pattern: string | Matcher): RegExpResult | string[] | null; + matchAll(pattern: string | Matcher): IterableIterator; + + 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; +} +interface StringConstructor { + (val: any): string; + new (val: any): String; + + fromCharCode(val: number): string; + + prototype: String; +} + +interface Symbol { + valueOf(): symbol; +} +interface SymbolConstructor { + (val?: any): symbol; + new(...args: any[]): never; + prototype: Symbol; + for(key: string): symbol; + keyFor(sym: symbol): string; + + readonly typeName: unique symbol; + readonly match: unique symbol; + readonly matchAll: unique symbol; + readonly split: unique symbol; + readonly replace: unique symbol; + readonly search: unique symbol; + readonly iterator: unique symbol; + readonly asyncIterator: unique symbol; +} + + +interface Promise extends Thenable { + catch(func: (err: unknown) => ResT): Promise; + finally(func: () => void): Promise; + constructor: PromiseConstructor; +} +interface PromiseConstructorLike { + new (func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Thenable>; +} +interface PromiseConstructor extends PromiseConstructorLike { + prototype: Promise; + + new (func: (res: (val: T) => void, rej: (err: unknown) => void) => void): Promise>; + resolve(val: T): Promise>; + reject(val: any): Promise; + + isAwaitable(val: unknown): val is Thenable; + any(promises: T[]): Promise>; + race(promises: (Promise|T)[]): Promise; + all(promises: T): Promise<{ [Key in keyof T]: Awaited }>; + allSettled(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult>}]>; +} + +interface FileStat { + type: 'file' | 'folder'; + mode: 'r' | 'rw'; +} +interface File { + readonly pointer: Promise; + readonly length: Promise; + readonly mode: Promise<'' | 'r' | 'rw'>; + + read(n: number): Promise; + write(buff: number[]): Promise; + close(): Promise; + setPointer(val: number): Promise; +} +interface Filesystem { + open(path: string, mode: 'r' | 'rw'): Promise; + ls(path: string): AsyncIterableIterator; + mkdir(path: string): Promise; + mkfile(path: string): Promise; + rm(path: string, recursive?: boolean): Promise; + stat(path: string): Promise; + exists(path: string): Promise; +} + +interface Encoding { + encode(val: string): number[]; + decode(val: number[]): string; +} + +declare var String: StringConstructor; +//@ts-ignore +declare const arguments: IArguments; +declare var NaN: number; +declare var Infinity: number; + +declare var setTimeout: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number; +declare var setInterval: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number; + +declare var clearTimeout: (id: number) => void; +declare var clearInterval: (id: number) => void; + +declare var parseInt: typeof Number.parseInt; +declare var parseFloat: typeof Number.parseFloat; + +declare function log(...vals: any[]): void; + +declare var Array: ArrayConstructor; +declare var Boolean: BooleanConstructor; +declare var Promise: PromiseConstructor; +declare var Function: FunctionConstructor; +declare var Number: NumberConstructor; +declare var Object: ObjectConstructor; +declare var Symbol: SymbolConstructor; +declare var Promise: PromiseConstructor; +declare var Math: MathObject; +declare var Encoding: Encoding; +declare var Filesystem: Filesystem; + +declare var Error: ErrorConstructor; +declare var RangeError: RangeErrorConstructor; +declare var TypeError: TypeErrorConstructor; +declare var SyntaxError: SyntaxErrorConstructor; +declare var self: typeof globalThis; + +declare class Map { + public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]>; + + public clear(): void; + public delete(key: KeyT): boolean; + + public entries(): IterableIterator<[KeyT, ValueT]>; + public keys(): IterableIterator; + public values(): IterableIterator; + + 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) => void, thisArg?: any): void; + + public constructor(); +} +declare class Set { + public [Symbol.iterator](): IterableIterator; + + public entries(): IterableIterator<[T, T]>; + public keys(): IterableIterator; + public values(): IterableIterator; + + 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) => void, thisArg?: any): void; + + public constructor(); +} + +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; + [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); +} diff --git a/src/me/topchetoeu/jscript/js/ts.js b/src/assets/js/ts.js similarity index 100% rename from src/me/topchetoeu/jscript/js/ts.js rename to src/assets/js/ts.js diff --git a/src/me/topchetoeu/jscript/filesystem/Buffer.java b/src/me/topchetoeu/jscript/Buffer.java similarity index 75% rename from src/me/topchetoeu/jscript/filesystem/Buffer.java rename to src/me/topchetoeu/jscript/Buffer.java index 3a77157..9fd6b8b 100644 --- a/src/me/topchetoeu/jscript/filesystem/Buffer.java +++ b/src/me/topchetoeu/jscript/Buffer.java @@ -1,41 +1,53 @@ -package me.topchetoeu.jscript.filesystem; - -public class Buffer { - private byte[] data; - private int length; - - public void write(int i, byte[] val) { - if (i + val.length > data.length) { - var newCap = i + val.length + 1; - if (newCap < data.length * 2) newCap = data.length * 2; - - var tmp = new byte[newCap]; - System.arraycopy(this.data, 0, tmp, 0, length); - this.data = tmp; - } - - System.arraycopy(val, 0, data, i, val.length); - if (i + val.length > length) length = i + val.length; - } - public int read(int i, byte[] buff) { - int n = buff.length; - if (i + n > length) n = length - i; - System.arraycopy(data, i, buff, 0, n); - return n; - } - - public byte[] data() { - var res = new byte[length]; - System.arraycopy(this.data, 0, res, 0, length); - return res; - } - public int length() { - return length; - } - - public Buffer(byte[] data) { - this.data = new byte[data.length]; - this.length = data.length; - System.arraycopy(data, 0, this.data, 0, data.length); - } -} +package me.topchetoeu.jscript; + +public class Buffer { + private byte[] data; + private int length; + + public void write(int i, byte[] val) { + if (i + val.length > data.length) { + var newCap = i + val.length + 1; + if (newCap < data.length * 2) newCap = data.length * 2; + + var tmp = new byte[newCap]; + System.arraycopy(this.data, 0, tmp, 0, length); + this.data = tmp; + } + + System.arraycopy(val, 0, data, i, val.length); + if (i + val.length > length) length = i + val.length; + } + public int read(int i, byte[] buff) { + int n = buff.length; + if (i + n > length) n = length - i; + System.arraycopy(data, i, buff, 0, n); + return n; + } + + public void append(byte b) { + write(length, new byte[] { b }); + } + + public byte[] data() { + var res = new byte[length]; + System.arraycopy(this.data, 0, res, 0, length); + return res; + } + public int length() { + return length; + } + + public Buffer(byte[] data) { + this.data = new byte[data.length]; + this.length = data.length; + System.arraycopy(data, 0, this.data, 0, data.length); + } + public Buffer(int capacity) { + this.data = new byte[capacity]; + this.length = 0; + } + public Buffer() { + this.data = new byte[128]; + this.length = 0; + } +} diff --git a/src/me/topchetoeu/jscript/Location.java b/src/me/topchetoeu/jscript/Location.java index 19f0ef2..98d0e16 100644 --- a/src/me/topchetoeu/jscript/Location.java +++ b/src/me/topchetoeu/jscript/Location.java @@ -71,4 +71,23 @@ public class Location implements Comparable { this.start = start; this.filename = filename; } + + public static Location parse(String raw) { + int i0 = -1, i1 = -1; + for (var i = raw.length() - 1; i >= 0; i--) { + if (raw.charAt(i) == ':') { + if (i1 == -1) i1 = i; + else if (i0 == -1) { + i0 = i; + break; + } + } + } + + return new Location( + Integer.parseInt(raw.substring(i0 + 1, i1)), + Integer.parseInt(raw.substring(i1 + 1)), + Filename.parse(raw.substring(0, i0)) + ); + } } diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 452660a..2143316 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -1,165 +1,170 @@ -package me.topchetoeu.jscript; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.file.Files; -import java.nio.file.Path; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Engine; -import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.debug.DebugServer; -import me.topchetoeu.jscript.engine.debug.SimpleDebugger; -import me.topchetoeu.jscript.engine.values.ArrayValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.events.Observer; -import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.exceptions.InterruptException; -import me.topchetoeu.jscript.exceptions.SyntaxException; -import me.topchetoeu.jscript.filesystem.MemoryFilesystem; -import me.topchetoeu.jscript.filesystem.Mode; -import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; -import me.topchetoeu.jscript.lib.Internals; - -public class Main { - public static class Printer implements Observer { - public void next(Object data) { - Values.printValue(null, data); - System.out.println(); - } - - public void error(RuntimeException err) { - Values.printError(err, null); - } - - public void finish() { - engineTask.interrupt(); - } - } - - static Thread engineTask, debugTask; - static Engine engine = new Engine(true); - static DebugServer debugServer = new DebugServer(); - static Environment environment = new Environment(null, null, null); - - static int j = 0; - static boolean exited = false; - static String[] args; - - private static void reader() { - try { - for (var arg : args) { - try { - if (arg.equals("--ts")) initTypescript(); - else { - var file = Path.of(arg); - var raw = Files.readString(file); - var res = engine.pushMsg( - false, new Context(engine, environment), - Filename.fromFile(file.toFile()), - raw, null - ).await(); - Values.printValue(null, res); - System.out.println(); - } - } - catch (EngineException e) { Values.printError(e, null); } - } - for (var i = 0; ; i++) { - try { - var raw = Reading.read(); - - if (raw == null) break; - var res = engine.pushMsg( - false, new Context(engine, environment), - new Filename("jscript", "repl/" + i + ".js"), - raw, null - ).await(); - Values.printValue(null, res); - System.out.println(); - } - catch (EngineException e) { Values.printError(e, null); } - catch (SyntaxException e) { Values.printError(e, null); } - } - } - catch (IOException e) { - System.out.println(e.toString()); - exited = true; - } - catch (RuntimeException ex) { - if (!exited) { - System.out.println("Internal error ocurred:"); - ex.printStackTrace(); - } - } - if (exited) { - debugTask.interrupt(); - engineTask.interrupt(); - } - } - - private static void initEnv() { - environment = Internals.apply(environment); - - environment.global.define("exit", _ctx -> { - exited = true; - throw new InterruptException(); - }); - environment.global.define("go", _ctx -> { - try { - var f = Path.of("do.js"); - var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); - return func.call(_ctx); - } - catch (IOException e) { - throw new EngineException("Couldn't open do.js"); - } - }); - - environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); - environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath())); - } - private static void initEngine() { - debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); - engineTask = engine.start(); - debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); - } - private static void initTypescript() { - try { - var tsEnv = Internals.apply(new Environment(null, null, null)); - var bsEnv = Internals.apply(new Environment(null, null, null)); - - engine.pushMsg( - false, new Context(engine, tsEnv), - new Filename("jscript", "ts.js"), - Reading.resourceToString("js/ts.js"), null - ).await(); - System.out.println("Loaded typescript!"); - - var ctx = new Context(engine, bsEnv); - - engine.pushMsg( - false, ctx, - new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, - tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) - ).await(); - } - catch (EngineException e) { - Values.printError(e, "(while initializing TS)"); - } - } - - public static void main(String args[]) { - System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); - - Main.args = args; - var reader = new Thread(Main::reader); - - initEnv(); - initEngine(); - - reader.setDaemon(true); - reader.setName("STD Reader"); - reader.start(); - } -} +package me.topchetoeu.jscript; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.debug.DebugServer; +import me.topchetoeu.jscript.engine.debug.SimpleDebugger; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.NativeFunction; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.events.Observer; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.InterruptException; +import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.filesystem.MemoryFilesystem; +import me.topchetoeu.jscript.filesystem.Mode; +import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; +import me.topchetoeu.jscript.lib.Internals; + +public class Main { + public static class Printer implements Observer { + public void next(Object data) { + Values.printValue(null, data); + System.out.println(); + } + + public void error(RuntimeException err) { + Values.printError(err, null); + } + + public void finish() { + engineTask.interrupt(); + } + } + + static Thread engineTask, debugTask; + static Engine engine = new Engine(true); + static DebugServer debugServer = new DebugServer(); + static Environment environment = new Environment(null, null, null); + + static int j = 0; + static boolean exited = false; + static String[] args; + + private static void reader() { + try { + for (var arg : args) { + try { + if (arg.equals("--ts")) initTypescript(); + else { + var file = Path.of(arg); + var raw = Files.readString(file); + var res = engine.pushMsg( + false, new Context(engine, environment), + Filename.fromFile(file.toFile()), + raw, null + ).await(); + Values.printValue(null, res); + System.out.println(); + } + } + catch (EngineException e) { Values.printError(e, null); } + } + for (var i = 0; ; i++) { + try { + var raw = Reading.read(); + + if (raw == null) break; + var res = engine.pushMsg( + false, new Context(engine, environment), + new Filename("jscript", "repl/" + i + ".js"), + raw, null + ).await(); + Values.printValue(null, res); + System.out.println(); + } + catch (EngineException e) { Values.printError(e, null); } + catch (SyntaxException e) { Values.printError(e, null); } + } + } + catch (IOException e) { + System.out.println(e.toString()); + exited = true; + } + catch (RuntimeException ex) { + if (!exited) { + System.out.println("Internal error ocurred:"); + ex.printStackTrace(); + } + } + if (exited) { + debugTask.interrupt(); + engineTask.interrupt(); + } + } + + private static void initEnv() { + environment = Internals.apply(environment); + + environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> { + exited = true; + throw new InterruptException(); + })); + environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> { + try { + var f = Path.of("do.js"); + var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); + return func.call(_ctx); + } + catch (IOException e) { + throw new EngineException("Couldn't open do.js"); + } + })); + + environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); + environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath())); + } + private static void initEngine() { + debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); + engineTask = engine.start(); + debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); + } + private static void initTypescript() { + try { + var tsEnv = Internals.apply(new Environment(null, null, null)); + tsEnv.stackVisible = false; + tsEnv.global.define(null, "module", false, new ObjectValue()); + var bsEnv = Internals.apply(new Environment(null, null, null)); + bsEnv.stackVisible = false; + + engine.pushMsg( + false, new Context(engine, tsEnv), + new Filename("jscript", "ts.js"), + Reading.resourceToString("js/ts.js"), null + ).await(); + System.out.println("Loaded typescript!"); + + var ctx = new Context(engine, bsEnv); + + engine.pushMsg( + false, ctx, + new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, + tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) + ).await(); + } + catch (EngineException e) { + Values.printError(e, "(while initializing TS)"); + } + } + + public static void main(String args[]) { + System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); + + Main.args = args; + var reader = new Thread(Main::reader); + + initEnv(); + initEngine(); + + reader.setDaemon(true); + reader.setName("STD Reader"); + reader.start(); + } +} diff --git a/src/me/topchetoeu/jscript/Reading.java b/src/me/topchetoeu/jscript/Reading.java index 6d797c8..c076f53 100644 --- a/src/me/topchetoeu/jscript/Reading.java +++ b/src/me/topchetoeu/jscript/Reading.java @@ -15,22 +15,13 @@ public class Reading { } public static String streamToString(InputStream in) { - try { - StringBuilder out = new StringBuilder(); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - for(var line = br.readLine(); line != null; line = br.readLine()) { - out.append(line).append('\n'); - } - - br.close(); - return out.toString(); - } + try { return new String(in.readAllBytes()); } catch (Throwable e) { throw new UncheckedException(e); } } + public static InputStream resourceToStream(String name) { + return Reading.class.getResourceAsStream("/assets/" + name); + } public static String resourceToString(String name) { - var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); - if (str == null) return null; - return streamToString(str); + return streamToString(resourceToStream(name)); } } diff --git a/src/me/topchetoeu/jscript/compilation/CalculateResult.java b/src/me/topchetoeu/jscript/compilation/CalculateResult.java new file mode 100644 index 0000000..08b2d47 --- /dev/null +++ b/src/me/topchetoeu/jscript/compilation/CalculateResult.java @@ -0,0 +1,21 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.engine.values.Values; + +public final class CalculateResult { + public final boolean exists; + public final Object value; + + public final boolean isTruthy() { + return exists && Values.toBoolean(value); + } + + public CalculateResult(Object value) { + this.exists = true; + this.value = value; + } + public CalculateResult() { + this.exists = false; + this.value = null; + } +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/compilation/CompileTarget.java b/src/me/topchetoeu/jscript/compilation/CompileTarget.java index 1484d19..da3166b 100644 --- a/src/me/topchetoeu/jscript/compilation/CompileTarget.java +++ b/src/me/topchetoeu/jscript/compilation/CompileTarget.java @@ -1,38 +1,66 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.Map; -import java.util.TreeSet; -import java.util.Vector; - -import me.topchetoeu.jscript.Location; - -public class CompileTarget { - public final Vector target = new Vector<>(); - public final Map functions; - public final TreeSet breakpoints; - - public Instruction add(Instruction instr) { - target.add(instr); - return instr; - } - public Instruction set(int i, Instruction instr) { - return target.set(i, instr); - } - public void setDebug(int i) { - breakpoints.add(target.get(i).location); - } - public void setDebug() { - setDebug(target.size() - 1); - } - public Instruction get(int i) { - return target.get(i); - } - public int size() { return target.size(); } - - public Instruction[] array() { return target.toArray(Instruction[]::new); } - - public CompileTarget(Map functions, TreeSet breakpoints) { - this.functions = functions; - this.breakpoints = breakpoints; - } -} +package me.topchetoeu.jscript.compilation; + +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; +import java.util.Vector; + +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.values.CodeFunction; + +public class CompileTarget { + public final Vector target = new Vector<>(); + public final Map functions; + public final TreeSet breakpoints; + private final HashMap bpToInstr = new HashMap<>(); + + public Instruction add(Instruction instr) { + target.add(instr); + return instr; + } + public Instruction set(int i, Instruction instr) { + return target.set(i, instr); + } + public void setDebug(int i, BreakpointType type) { + var instr = target.get(i); + instr.breakpoint = type; + + if (type == BreakpointType.NONE) { + breakpoints.remove(target.get(i).location); + bpToInstr.remove(instr.location, instr); + } + else { + breakpoints.add(target.get(i).location); + + var old = bpToInstr.put(instr.location, instr); + if (old != null) old.breakpoint = BreakpointType.NONE; + } + } + public void setDebug(BreakpointType type) { + setDebug(target.size() - 1, type); + } + public Instruction get(int i) { + return target.get(i); + } + public int size() { return target.size(); } + public Location lastLoc(Location fallback) { + if (target.size() == 0) return fallback; + else return target.get(target.size() - 1).location; + } + + public Instruction[] array() { return target.toArray(Instruction[]::new); } + + public FunctionBody body() { + return functions.get(0l); + } + public CodeFunction func(Environment env) { + return new CodeFunction(env, "", body()); + } + + public CompileTarget(Map functions, TreeSet breakpoints) { + this.functions = functions; + this.breakpoints = breakpoints; + } +} diff --git a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java index 1e25de7..010ece3 100644 --- a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -1,66 +1,54 @@ package me.topchetoeu.jscript.compilation; +import java.util.List; import java.util.Vector; 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.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.values.FunctionStatement; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class CompoundStatement extends Statement { public final Statement[] statements; + public final boolean separateFuncs; public Location end; + @Override public boolean pure() { + for (var stm : statements) { + if (!stm.pure()) return false; + } + + return true; + } + @Override public void declare(ScopeRecord varsScope) { - for (var stm : statements) { - stm.declare(varsScope); - } + for (var stm : statements) stm.declare(varsScope); } @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - for (var stm : statements) { - if (stm instanceof FunctionStatement) { - ((FunctionStatement)stm).compile(target, scope, null, true); - target.add(Instruction.discard()); + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) { + List statements = new Vector(); + if (separateFuncs) for (var stm : this.statements) { + if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) { + stm.compile(target, scope, false); } + else statements.add(stm); + } + else statements = List.of(this.statements); + + var polluted = false; + + for (var i = 0; i < statements.size(); i++) { + var stm = statements.get(i); + + if (i != statements.size() - 1) stm.compile(target, scope, false, BreakpointType.STEP_OVER); + else stm.compile(target, scope, polluted = pollute, BreakpointType.STEP_OVER); } - for (var i = 0; i < statements.length; i++) { - var stm = statements[i]; - - if (stm instanceof FunctionStatement) continue; - if (i != statements.length - 1) stm.compileWithDebug(target, scope, false); - else stm.compileWithDebug(target, scope, pollute); + if (!polluted && pollute) { + target.add(Instruction.loadValue(loc(), null)); } - - if (end != null) { - target.add(Instruction.nop().locate(end)); - target.setDebug(); - } - } - - @Override - public Statement optimize() { - var res = new Vector(statements.length); - - 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 setEnd(Location loc) { @@ -68,8 +56,9 @@ public class CompoundStatement extends Statement { return this; } - public CompoundStatement(Location loc, Statement ...statements) { + public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) { super(loc); + this.separateFuncs = separateFuncs; this.statements = statements; } } diff --git a/src/me/topchetoeu/jscript/compilation/FunctionBody.java b/src/me/topchetoeu/jscript/compilation/FunctionBody.java index 718eeec..7f78066 100644 --- a/src/me/topchetoeu/jscript/compilation/FunctionBody.java +++ b/src/me/topchetoeu/jscript/compilation/FunctionBody.java @@ -3,13 +3,25 @@ package me.topchetoeu.jscript.compilation; public class FunctionBody { public final Instruction[] instructions; public final String[] captureNames, localNames; + public final int localsN, argsN; - public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) { + public FunctionBody(int localsN, int argsN, Instruction[] instructions, String[] captureNames, String[] localNames) { + this.argsN = argsN; + this.localsN = localsN; this.instructions = instructions; this.captureNames = captureNames; this.localNames = localNames; } - public FunctionBody(Instruction[] instructions) { + public FunctionBody(int localsN, int argsN, Instruction[] instructions) { + this.argsN = argsN; + this.localsN = localsN; + this.instructions = instructions; + this.captureNames = new String[0]; + this.localNames = new String[0]; + } + public FunctionBody(Instruction... instructions) { + this.argsN = 0; + this.localsN = 2; this.instructions = instructions; this.captureNames = new String[0]; this.localNames = new String[0]; diff --git a/src/me/topchetoeu/jscript/compilation/Instruction.java b/src/me/topchetoeu/jscript/compilation/Instruction.java index c1dae6e..e589f4f 100644 --- a/src/me/topchetoeu/jscript/compilation/Instruction.java +++ b/src/me/topchetoeu/jscript/compilation/Instruction.java @@ -10,7 +10,8 @@ public class Instruction { THROW, THROW_SYNTAX, DELETE, - TRY, + TRY_START, + TRY_END, NOP, CALL, @@ -33,7 +34,6 @@ public class Instruction { LOAD_REGEX, DUP, - MOVE, STORE_VAR, STORE_MEMBER, @@ -45,51 +45,30 @@ public class Instruction { TYPEOF, OPERATION; - // TYPEOF, - // INSTANCEOF(true), - // IN(true), + } + public static enum BreakpointType { + NONE, + STEP_OVER, + STEP_IN; - // 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 boolean shouldStepIn() { + return this != NONE; + } + public boolean shouldStepOver() { + return this == STEP_OVER; + } } public final Type type; public final Object[] params; public Location location; + public BreakpointType breakpoint = BreakpointType.NONE; + + public Instruction setDbgData(Instruction other) { + this.location = other.location; + this.breakpoint = other.breakpoint; + return this; + } public Instruction locate(Location loc) { this.location = loc; @@ -129,26 +108,32 @@ public class Instruction { this.params = params; } - public static Instruction tryInstr(int n, int catchN, int finallyN) { - return new Instruction(null, Type.TRY, n, catchN, finallyN); + public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) { + return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end); } - public static Instruction throwInstr() { - return new Instruction(null, Type.THROW); + public static Instruction tryEnd(Location loc) { + return new Instruction(loc, Type.TRY_END); } - public static Instruction throwSyntax(SyntaxException err) { - return new Instruction(null, Type.THROW_SYNTAX, err.getMessage()); + public static Instruction throwInstr(Location loc) { + return new Instruction(loc, Type.THROW); } - public static Instruction delete() { - return new Instruction(null, Type.DELETE); + public static Instruction throwSyntax(Location loc, SyntaxException err) { + return new Instruction(loc, Type.THROW_SYNTAX, err.getMessage()); } - public static Instruction ret() { - return new Instruction(null, Type.RETURN); + public static Instruction throwSyntax(Location loc, String err) { + return new Instruction(loc, Type.THROW_SYNTAX, err); } - public static Instruction debug() { - return new Instruction(null, Type.NOP, "debug"); + public static Instruction delete(Location loc) { + return new Instruction(loc, Type.DELETE); + } + public static Instruction ret(Location loc) { + return new Instruction(loc, Type.RETURN); + } + public static Instruction debug(Location loc) { + return new Instruction(loc, Type.NOP, "debug"); } - public static Instruction nop(Object ...params) { + public static Instruction nop(Location loc, Object ...params) { for (var param : params) { if (param instanceof String) continue; if (param instanceof Boolean) continue; @@ -158,109 +143,104 @@ public class Instruction { throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls."); } - return new Instruction(null, Type.NOP, params); + return new Instruction(loc, Type.NOP, params); } - public static Instruction call(int argn) { - return new Instruction(null, Type.CALL, argn); + public static Instruction call(Location loc, int argn) { + return new Instruction(loc, Type.CALL, argn); } - public static Instruction callNew(int argn) { - return new Instruction(null, Type.CALL_NEW, argn); + public static Instruction callNew(Location loc, int argn) { + return new Instruction(loc, Type.CALL_NEW, argn); } - public static Instruction jmp(int offset) { - return new Instruction(null, Type.JMP, offset); + public static Instruction jmp(Location loc, int offset) { + return new Instruction(loc, Type.JMP, offset); } - public static Instruction jmpIf(int offset) { - return new Instruction(null, Type.JMP_IF, offset); + public static Instruction jmpIf(Location loc, int offset) { + return new Instruction(loc, Type.JMP_IF, offset); } - public static Instruction jmpIfNot(int offset) { - return new Instruction(null, Type.JMP_IFN, offset); + public static Instruction jmpIfNot(Location loc, int offset) { + return new Instruction(loc, Type.JMP_IFN, offset); } - public static Instruction loadValue(Object val) { - return new Instruction(null, Type.LOAD_VALUE, val); + public static Instruction loadValue(Location loc, Object val) { + return new Instruction(loc, Type.LOAD_VALUE, val); } - public static Instruction makeVar(String name) { - return new Instruction(null, Type.MAKE_VAR, name); + public static Instruction makeVar(Location loc, String name) { + return new Instruction(loc, Type.MAKE_VAR, name); } - public static Instruction loadVar(Object i) { - return new Instruction(null, Type.LOAD_VAR, i); + public static Instruction loadVar(Location loc, Object i) { + return new Instruction(loc, Type.LOAD_VAR, i); } - public static Instruction loadGlob() { - return new Instruction(null, Type.LOAD_GLOB); + public static Instruction loadGlob(Location loc) { + return new Instruction(loc, Type.LOAD_GLOB); } - public static Instruction loadMember() { - return new Instruction(null, Type.LOAD_MEMBER); + public static Instruction loadMember(Location loc) { + return new Instruction(loc, Type.LOAD_MEMBER); } - public static Instruction loadMember(Object key) { + public static Instruction loadMember(Location loc, Object key) { if (key instanceof Number) key = ((Number)key).doubleValue(); - return new Instruction(null, Type.LOAD_VAL_MEMBER, key); + return new Instruction(loc, 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 loadRegex(Location loc, String pattern, String flags) { + return new Instruction(loc, Type.LOAD_REGEX, pattern, flags); } - public static Instruction loadFunc(long id, int varN, int len, int[] captures) { - var args = new Object[3 + captures.length]; + public static Instruction loadFunc(Location loc, long id, int[] captures) { + var args = new Object[1 + captures.length]; args[0] = id; - 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); + for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i]; + return new Instruction(loc, Type.LOAD_FUNC, args); } - public static Instruction loadObj() { - return new Instruction(null, Type.LOAD_OBJ); + public static Instruction loadObj(Location loc) { + return new Instruction(loc, Type.LOAD_OBJ); } - public static Instruction loadArr(int count) { - return new Instruction(null, Type.LOAD_ARR, count); + public static Instruction loadArr(Location loc, int count) { + return new Instruction(loc, Type.LOAD_ARR, count); } - public static Instruction dup() { - return new Instruction(null, Type.DUP, 0, 1); + public static Instruction dup(Location loc) { + return new Instruction(loc, Type.DUP, 1); } - public static Instruction dup(int count, int offset) { - return new Instruction(null, Type.DUP, offset, count); - } - public static Instruction move(int count, int offset) { - return new Instruction(null, Type.MOVE, offset, count); + public static Instruction dup(Location loc, int count) { + return new Instruction(loc, Type.DUP, count); } - public static Instruction storeSelfFunc(int i) { - return new Instruction(null, Type.STORE_SELF_FUNC, i); + public static Instruction storeSelfFunc(Location loc, int i) { + return new Instruction(loc, Type.STORE_SELF_FUNC, i); } - public static Instruction storeVar(Object i) { - return new Instruction(null, Type.STORE_VAR, i, false); + public static Instruction storeVar(Location loc, Object i) { + return new Instruction(loc, 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 storeVar(Location loc, Object i, boolean keep) { + return new Instruction(loc, Type.STORE_VAR, i, keep); } - public static Instruction storeMember() { - return new Instruction(null, Type.STORE_MEMBER, false); + public static Instruction storeMember(Location loc) { + return new Instruction(loc, Type.STORE_MEMBER, false); } - public static Instruction storeMember(boolean keep) { - return new Instruction(null, Type.STORE_MEMBER, keep); + public static Instruction storeMember(Location loc, boolean keep) { + return new Instruction(loc, Type.STORE_MEMBER, keep); } - public static Instruction discard() { - return new Instruction(null, Type.DISCARD); + public static Instruction discard(Location loc) { + return new Instruction(loc, Type.DISCARD); } - public static Instruction typeof() { - return new Instruction(null, Type.TYPEOF); + public static Instruction typeof(Location loc) { + return new Instruction(loc, Type.TYPEOF); } - public static Instruction typeof(Object varName) { - return new Instruction(null, Type.TYPEOF, varName); + public static Instruction typeof(Location loc, Object varName) { + return new Instruction(loc, Type.TYPEOF, varName); } - public static Instruction keys(boolean forInFormat) { - return new Instruction(null, Type.KEYS, forInFormat); + public static Instruction keys(Location loc, boolean forInFormat) { + return new Instruction(loc, Type.KEYS, forInFormat); } - public static Instruction defProp() { - return new Instruction(null, Type.DEF_PROP); + public static Instruction defProp(Location loc) { + return new Instruction(loc, Type.DEF_PROP); } - public static Instruction operation(Operation op) { - return new Instruction(null, Type.OPERATION, op); + public static Instruction operation(Location loc, Operation op) { + return new Instruction(loc, Type.OPERATION, op); } @Override diff --git a/src/me/topchetoeu/jscript/compilation/Statement.java b/src/me/topchetoeu/jscript/compilation/Statement.java index 663ed12..3a6de82 100644 --- a/src/me/topchetoeu/jscript/compilation/Statement.java +++ b/src/me/topchetoeu/jscript/compilation/Statement.java @@ -1,20 +1,26 @@ package me.topchetoeu.jscript.compilation; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public abstract class Statement { private Location _loc; public boolean pure() { return false; } - public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute); public void declare(ScopeRecord varsScope) { } - public Statement optimize() { return this; } - public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) { + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) { int start = target.size(); compile(target, scope, pollute); - if (target.size() != start) target.setDebug(start); + + if (target.size() != start) { + target.get(start).locate(loc()); + target.setDebug(start, type); + } + } + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { + compile(target, scope, pollute, BreakpointType.NONE); } public Location loc() { return _loc; } diff --git a/src/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java b/src/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java new file mode 100644 index 0000000..400f5de --- /dev/null +++ b/src/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java @@ -0,0 +1,18 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.engine.scope.ScopeRecord; +import me.topchetoeu.jscript.exceptions.SyntaxException; + +public class ThrowSyntaxStatement extends Statement { + public final String name; + + @Override + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { + target.add(Instruction.throwSyntax(loc(), name)); + } + + public ThrowSyntaxStatement(SyntaxException e) { + super(e.loc); + this.name = e.msg; + } +} diff --git a/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java b/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java index c51de30..4c3b731 100644 --- a/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java +++ b/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation; import java.util.List; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.values.FunctionStatement; import me.topchetoeu.jscript.engine.scope.ScopeRecord; @@ -32,23 +33,16 @@ public class VariableDeclareStatement extends Statement { for (var entry : values) { if (entry.name == null) continue; var key = scope.getKey(entry.name); - int start = target.size(); - if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location)); + if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key)); - if (entry.value instanceof FunctionStatement) { - ((FunctionStatement)entry.value).compile(target, scope, entry.name, false); - target.add(Instruction.storeVar(key).locate(entry.location)); + if (entry.value != null) { + FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER); + target.add(Instruction.storeVar(entry.location, key)); } - else if (entry.value != null) { - entry.value.compile(target, scope, true); - target.add(Instruction.storeVar(key).locate(entry.location)); - } - - if (target.size() != start) target.setDebug(start); } - if (pollute) target.add(Instruction.loadValue(null).locate(loc())); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public VariableDeclareStatement(Location loc, List values) { diff --git a/src/me/topchetoeu/jscript/compilation/control/BreakStatement.java b/src/me/topchetoeu/jscript/compilation/control/BreakStatement.java index 88be1ab..c9a9f4a 100644 --- a/src/me/topchetoeu/jscript/compilation/control/BreakStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/BreakStatement.java @@ -11,8 +11,8 @@ public class BreakStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - target.add(Instruction.nop("break", label).locate(loc())); - if (pollute) target.add(Instruction.loadValue(null).locate(loc())); + target.add(Instruction.nop(loc(), "break", label)); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public BreakStatement(Location loc, String label) { diff --git a/src/me/topchetoeu/jscript/compilation/control/ContinueStatement.java b/src/me/topchetoeu/jscript/compilation/control/ContinueStatement.java index 6828502..87b6452 100644 --- a/src/me/topchetoeu/jscript/compilation/control/ContinueStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/ContinueStatement.java @@ -11,8 +11,8 @@ public class ContinueStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - target.add(Instruction.nop("cont", label).locate(loc())); - if (pollute) target.add(Instruction.loadValue(null).locate(loc())); + target.add(Instruction.nop(loc(), "cont", label)); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public ContinueStatement(Location loc, String label) { diff --git a/src/me/topchetoeu/jscript/compilation/control/DebugStatement.java b/src/me/topchetoeu/jscript/compilation/control/DebugStatement.java index f98a434..bf610eb 100644 --- a/src/me/topchetoeu/jscript/compilation/control/DebugStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/DebugStatement.java @@ -9,8 +9,8 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class DebugStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - target.add(Instruction.debug().locate(loc())); - if (pollute) target.add(Instruction.loadValue(null).locate(loc())); + target.add(Instruction.debug(loc())); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public DebugStatement(Location loc) { diff --git a/src/me/topchetoeu/jscript/compilation/control/DeleteStatement.java b/src/me/topchetoeu/jscript/compilation/control/DeleteStatement.java index 526ff74..894b4ab 100644 --- a/src/me/topchetoeu/jscript/compilation/control/DeleteStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/DeleteStatement.java @@ -15,8 +15,8 @@ public class DeleteStatement extends Statement { value.compile(target, scope, true); key.compile(target, scope, true); - target.add(Instruction.delete().locate(loc())); - if (!pollute) target.add(Instruction.discard().locate(loc())); + target.add(Instruction.delete(loc())); + if (pollute) target.add(Instruction.loadValue(loc(), true)); } public DeleteStatement(Location loc, Statement key, Statement value) { diff --git a/src/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java b/src/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java index 41f9dfe..b3e7a68 100644 --- a/src/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java @@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; -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.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.engine.scope.ScopeRecord; -import me.topchetoeu.jscript.engine.values.Values; public class DoWhileStatement extends Statement { public final Statement condition, body; @@ -20,54 +18,14 @@ public class DoWhileStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (condition instanceof ConstantStatement) { - int start = target.size(); - body.compile(target, scope, false); - 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); - } - if (pollute) target.add(Instruction.loadValue(null).locate(loc())); - return; - } - int start = target.size(); - body.compileWithDebug(target, scope, false); + body.compile(target, scope, false, BreakpointType.STEP_OVER); int mid = target.size(); - condition.compile(target, scope, true); + condition.compile(target, scope, true, BreakpointType.STEP_OVER); 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); + target.add(Instruction.jmpIf(loc(), start - end)); } public DoWhileStatement(Location loc, String label, Statement condition, Statement body) { diff --git a/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java b/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java index f5d04d8..ae40175 100644 --- a/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java @@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; @@ -25,39 +26,38 @@ public class ForInStatement extends Statement { var key = scope.getKey(varName); int first = target.size(); - if (key instanceof String) target.add(Instruction.makeVar((String)key)); + if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key)); if (varValue != null) { varValue.compile(target, scope, true); - target.add(Instruction.storeVar(scope.getKey(varName))); + target.add(Instruction.storeVar(loc(), scope.getKey(varName))); } - object.compileWithDebug(target, scope, true); - target.add(Instruction.keys(true)); + object.compile(target, scope, true, BreakpointType.STEP_OVER); + target.add(Instruction.keys(loc(), true)); int start = target.size(); - target.add(Instruction.dup()); - target.add(Instruction.loadValue(null)); - target.add(Instruction.operation(Operation.EQUALS)); + target.add(Instruction.dup(loc())); + target.add(Instruction.loadValue(loc(), null)); + target.add(Instruction.operation(loc(), Operation.EQUALS)); int mid = target.size(); - target.add(Instruction.nop()); + target.add(Instruction.nop(loc())); - target.add(Instruction.loadMember("value").locate(varLocation)); - target.setDebug(); - target.add(Instruction.storeVar(key)); + target.add(Instruction.loadMember(varLocation, "value")); + target.add(Instruction.storeVar(object.loc(), key)); + target.setDebug(BreakpointType.STEP_OVER); - body.compileWithDebug(target, scope, false); + body.compile(target, scope, false, BreakpointType.STEP_OVER); int end = target.size(); WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); - target.add(Instruction.jmp(start - end)); - target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(end - mid + 1)); - if (pollute) target.add(Instruction.loadValue(null)); + target.add(Instruction.jmp(loc(), start - end)); + target.add(Instruction.discard(loc())); + target.set(mid, Instruction.jmpIf(loc(), end - mid + 1)); + if (pollute) target.add(Instruction.loadValue(loc(), null)); target.get(first).locate(loc()); - target.setDebug(first); } public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { diff --git a/src/me/topchetoeu/jscript/compilation/control/ForStatement.java b/src/me/topchetoeu/jscript/compilation/control/ForStatement.java index 726e05e..1385fb1 100644 --- a/src/me/topchetoeu/jscript/compilation/control/ForStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/ForStatement.java @@ -2,12 +2,10 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileTarget; -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; @@ -20,58 +18,22 @@ public class ForStatement extends Statement { } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - declaration.compile(target, scope, false); - - if (condition instanceof ConstantStatement) { - if (Values.toBoolean(((ConstantStatement)condition).value)) { - int start = target.size(); - body.compile(target, scope, false); - int mid = target.size(); - assignment.compileWithDebug(target, scope, false); - int end = target.size(); - WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1); - target.add(Instruction.jmp(start - target.size()).locate(loc())); - if (pollute) target.add(Instruction.loadValue(null).locate(loc())); - } - return; - } + declaration.compile(target, scope, false, BreakpointType.STEP_OVER); int start = target.size(); - condition.compile(target, scope, true); + condition.compile(target, scope, true, BreakpointType.STEP_OVER); int mid = target.size(); - target.add(Instruction.nop()); - body.compile(target, scope, false); + target.add(Instruction.nop(null)); + body.compile(target, scope, false, BreakpointType.STEP_OVER); int beforeAssign = target.size(); - assignment.compileWithDebug(target, scope, false); + assignment.compile(target, scope, false, BreakpointType.STEP_OVER); 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())); - if (pollute) target.add(Instruction.loadValue(null).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); + target.add(Instruction.jmp(loc(), start - end)); + target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1)); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) { @@ -82,14 +44,4 @@ public class ForStatement extends Statement { 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 - )) - ); - } } diff --git a/src/me/topchetoeu/jscript/compilation/control/IfStatement.java b/src/me/topchetoeu/jscript/compilation/control/IfStatement.java index 96c94ac..bba4a58 100644 --- a/src/me/topchetoeu/jscript/compilation/control/IfStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/IfStatement.java @@ -2,13 +2,10 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; -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.compilation.Instruction.BreakpointType; 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; @@ -19,53 +16,31 @@ public class IfStatement extends Statement { if (elseBody != null) elseBody.declare(globScope); } - @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (condition instanceof ConstantStatement) { - if (Values.not(((ConstantStatement)condition).value)) { - if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute); - } - else { - body.compileWithDebug(target, scope, pollute); - } - - return; - } - - condition.compile(target, scope, true); + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) { + condition.compile(target, scope, true, breakpoint); if (elseBody == null) { int i = target.size(); - target.add(Instruction.nop()); - body.compileWithDebug(target, scope, pollute); + target.add(Instruction.nop(null)); + body.compile(target, scope, pollute, breakpoint); int endI = target.size(); - target.set(i, Instruction.jmpIfNot(endI - i).locate(loc())); + target.set(i, Instruction.jmpIfNot(loc(), endI - i)); } else { int start = target.size(); - target.add(Instruction.nop()); - body.compileWithDebug(target, scope, pollute); - target.add(Instruction.nop()); + target.add(Instruction.nop(null)); + body.compile(target, scope, pollute, breakpoint); + target.add(Instruction.nop(null)); int mid = target.size(); - elseBody.compileWithDebug(target, scope, pollute); + elseBody.compile(target, scope, pollute, breakpoint); int end = target.size(); - target.set(start, Instruction.jmpIfNot(mid - start).locate(loc())); - target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc())); + target.set(start, Instruction.jmpIfNot(loc(), mid - start)); + target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1)); } } - - @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); + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { + compile(target, scope, pollute, BreakpointType.STEP_IN); } public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) { diff --git a/src/me/topchetoeu/jscript/compilation/control/ReturnStatement.java b/src/me/topchetoeu/jscript/compilation/control/ReturnStatement.java index 896f33c..1ec792f 100644 --- a/src/me/topchetoeu/jscript/compilation/control/ReturnStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/ReturnStatement.java @@ -11,9 +11,9 @@ public class ReturnStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (value == null) target.add(Instruction.loadValue(null).locate(loc())); + if (value == null) target.add(Instruction.loadValue(loc(), null)); else value.compile(target, scope, true); - target.add(Instruction.ret().locate(loc())); + target.add(Instruction.ret(loc())); } public ReturnStatement(Location loc, Statement value) { diff --git a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index 9efc9e7..dc5d5a9 100644 --- a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -6,6 +6,7 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; @@ -36,44 +37,42 @@ public class SwitchStatement extends Statement { var caseToStatement = new HashMap(); var statementToIndex = new HashMap(); - value.compile(target, scope, true); + value.compile(target, scope, true, BreakpointType.STEP_OVER); for (var ccase : cases) { - target.add(Instruction.dup().locate(loc())); + target.add(Instruction.dup(loc())); ccase.value.compile(target, scope, true); - target.add(Instruction.operation(Operation.EQUALS).locate(loc())); + target.add(Instruction.operation(loc(), Operation.EQUALS)); caseToStatement.put(target.size(), ccase.statementI); - target.add(Instruction.nop().locate(ccase.value.loc())); + target.add(Instruction.nop(null)); } int start = target.size(); - target.add(Instruction.nop()); + target.add(Instruction.nop(null)); for (var stm : body) { statementToIndex.put(statementToIndex.size(), target.size()); - stm.compileWithDebug(target, scope, false); + stm.compile(target, scope, false, BreakpointType.STEP_OVER); } int end = target.size(); - target.add(Instruction.discard().locate(loc())); - if (pollute) target.add(Instruction.loadValue(null)); + target.add(Instruction.discard(loc())); + if (pollute) target.add(Instruction.loadValue(loc(), null)); - if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start).locate(loc())); - else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)).locate(loc()); + if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start)); + else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start)); for (int i = start; i < end; i++) { var instr = target.get(i); if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { - target.set(i, Instruction.jmp(end - i).locate(instr.location)); + target.set(i, Instruction.jmp(loc(), end - i).locate(instr.location)); } } for (var el : caseToStatement.entrySet()) { - var loc = target.get(el.getKey()).location; var i = statementToIndex.get(el.getValue()); if (i == null) i = end; - target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc)); - target.setDebug(el.getKey()); + target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey())); } } diff --git a/src/me/topchetoeu/jscript/compilation/control/ThrowStatement.java b/src/me/topchetoeu/jscript/compilation/control/ThrowStatement.java index cf424ad..5aa69ff 100644 --- a/src/me/topchetoeu/jscript/compilation/control/ThrowStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/ThrowStatement.java @@ -12,7 +12,7 @@ public class ThrowStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { value.compile(target, scope, true); - target.add(Instruction.throwInstr().locate(loc())); + target.add(Instruction.throwInstr(loc())); } public ThrowStatement(Location loc, Statement value) { diff --git a/src/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/me/topchetoeu/jscript/compilation/control/TryStatement.java index 1855f1b..f906d9e 100644 --- a/src/me/topchetoeu/jscript/compilation/control/TryStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/TryStatement.java @@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.LocalScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord; @@ -22,31 +23,32 @@ public class TryStatement extends Statement { } @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - target.add(Instruction.nop()); + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) { + target.add(Instruction.nop(null)); - int start = target.size(), tryN, catchN = -1, finN = -1; + int start = target.size(), catchStart = -1, finallyStart = -1; tryBody.compile(target, scope, false); - tryN = target.size() - start; + target.add(Instruction.tryEnd(loc())); if (catchBody != null) { - int tmp = target.size(); + catchStart = target.size() - start; var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope; local.define(name, true); catchBody.compile(target, scope, false); local.undefine(); - catchN = target.size() - tmp; + target.add(Instruction.tryEnd(loc())); } if (finallyBody != null) { - int tmp = target.size(); + finallyStart = target.size() - start; finallyBody.compile(target, scope, false); - finN = target.size() - tmp; + target.add(Instruction.tryEnd(loc())); } - target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc())); - if (pollute) target.add(Instruction.loadValue(null).locate(loc())); + target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start)); + target.setDebug(start - 1, BreakpointType.STEP_OVER); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) { diff --git a/src/me/topchetoeu/jscript/compilation/control/WhileStatement.java b/src/me/topchetoeu/jscript/compilation/control/WhileStatement.java index 68a9e1b..fb474ce 100644 --- a/src/me/topchetoeu/jscript/compilation/control/WhileStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/WhileStatement.java @@ -3,13 +3,10 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.CompileTarget; -import me.topchetoeu.jscript.compilation.CompoundStatement; -import me.topchetoeu.jscript.compilation.DiscardStatement; import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; 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; @@ -21,43 +18,19 @@ public class WhileStatement extends Statement { } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (condition instanceof ConstantStatement) { - if (Values.toBoolean(((ConstantStatement)condition).value)) { - int start = target.size(); - body.compile(target, scope, false); - 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.compile(target, scope, true); int mid = target.size(); - target.add(Instruction.nop()); - body.compile(target, scope, false); + target.add(Instruction.nop(null)); + body.compile(target, scope, false, BreakpointType.STEP_OVER); 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())); - if (pollute) target.add(Instruction.loadValue(null).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); + target.add(Instruction.jmp(loc(), start - end)); + target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1)); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public WhileStatement(Location loc, String label, Statement condition, Statement body) { @@ -71,23 +44,11 @@ public class WhileStatement extends Statement { 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; + target.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i))); } 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; + target.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i))); } } } - - // 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 - // )) - // ); - // } } diff --git a/src/me/topchetoeu/jscript/compilation/control/ArrayStatement.java b/src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java similarity index 51% rename from src/me/topchetoeu/jscript/compilation/control/ArrayStatement.java rename to src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java index e819baa..a7249b0 100644 --- a/src/me/topchetoeu/jscript/compilation/control/ArrayStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java @@ -1,35 +1,41 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.CompileTarget; -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 pure() { return true; } - - @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - target.add(Instruction.loadArr(statements.length).locate(loc())); - var i = 0; - for (var el : statements) { - if (el != null) { - target.add(Instruction.dup().locate(loc())); - target.add(Instruction.loadValue(i).locate(loc())); - el.compile(target, scope, true); - target.add(Instruction.storeMember().locate(loc())); - } - i++; - } - if (!pollute) target.add(Instruction.discard().locate(loc())); - } - - public ArrayStatement(Location loc, Statement[] statements) { - super(loc); - this.statements = statements; - } -} +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.CompileTarget; +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 pure() { + for (var stm : statements) { + if (!stm.pure()) return false; + } + + return true; + } + + @Override + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { + target.add(Instruction.loadArr(loc(), statements.length)); + + for (var i = 0; i < statements.length; i++) { + var el = statements[i]; + if (el != null) { + target.add(Instruction.dup(loc())); + target.add(Instruction.loadValue(loc(), i)); + el.compile(target, scope, true); + target.add(Instruction.storeMember(loc())); + } + } + + if (!pollute) target.add(Instruction.discard(loc())); + } + + public ArrayStatement(Location loc, Statement[] statements) { + super(loc); + this.statements = statements; + } +} diff --git a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java index a14be7d..2ccacb0 100644 --- a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java @@ -4,36 +4,47 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class CallStatement extends Statement { public final Statement func; public final Statement[] args; + public final boolean isNew; @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (func instanceof IndexStatement) { + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) { + if (isNew) func.compile(target, scope, true); + else if (func instanceof IndexStatement) { ((IndexStatement)func).compile(target, scope, true, true); } else { - target.add(Instruction.loadValue(null).locate(loc())); + target.add(Instruction.loadValue(loc(), null)); func.compile(target, scope, true); } for (var arg : args) arg.compile(target, scope, true); - target.add(Instruction.call(args.length).locate(loc())); - target.setDebug(); - if (!pollute) target.add(Instruction.discard().locate(loc())); + if (isNew) target.add(Instruction.callNew(loc(), args.length)); + else target.add(Instruction.call(loc(), args.length)); + target.setDebug(type); + + if (!pollute) target.add(Instruction.discard(loc())); + } + @Override + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { + compile(target, scope, pollute, BreakpointType.STEP_IN); } - public CallStatement(Location loc, Statement func, Statement ...args) { + public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) { super(loc); + this.isNew = isNew; this.func = func; this.args = args; } - public CallStatement(Location loc, Statement obj, Object key, Statement ...args) { + public CallStatement(Location loc, boolean isNew, Statement obj, Object key, Statement ...args) { super(loc); + this.isNew = isNew; this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key)); this.args = args; } diff --git a/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java b/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java index 9f8db27..f6705f7 100644 --- a/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java @@ -16,10 +16,10 @@ public class ChangeStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true); - if (!pollute) target.add(Instruction.discard().locate(loc())); + if (!pollute) target.add(Instruction.discard(loc())); else if (postfix) { - target.add(Instruction.loadValue(addAmount)); - target.add(Instruction.operation(Operation.SUBTRACT)); + target.add(Instruction.loadValue(loc(), addAmount)); + target.add(Instruction.operation(loc(), Operation.SUBTRACT)); } } diff --git a/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java b/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java deleted file mode 100644 index 6bbf9c6..0000000 --- a/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import java.util.Vector; - -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.CompileTarget; -import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.engine.scope.ScopeRecord; - -public class CommaStatement extends Statement { - public final Statement[] values; - - @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - for (var i = 0; i < values.length; i++) { - values[i].compileWithDebug(target, scope, i == values.length - 1 && pollute); - } - } - - @Override - public Statement optimize() { - var res = new Vector(values.length); - - for (var i = 0; i < values.length; i++) { - var stm = values[i].optimize(); - if (i < values.length - 1 && stm.pure()) continue; - res.add(stm); - } - - if (res.size() == 1) return res.get(0); - else return new CommaStatement(loc(), res.toArray(Statement[]::new)); - } - - public CommaStatement(Location loc, Statement ...args) { - super(loc); - this.values = args; - } -} diff --git a/src/me/topchetoeu/jscript/compilation/values/ConstantStatement.java b/src/me/topchetoeu/jscript/compilation/values/ConstantStatement.java index f0458ae..9d5454e 100644 --- a/src/me/topchetoeu/jscript/compilation/values/ConstantStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ConstantStatement.java @@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class ConstantStatement extends Statement { public final Object value; - @Override - public boolean pure() { return true; } + @Override public boolean pure() { return true; } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (pollute) target.add(Instruction.loadValue(value).locate(loc())); + if (pollute) target.add(Instruction.loadValue(loc(), value)); } public ConstantStatement(Location loc, Object val) { diff --git a/src/me/topchetoeu/jscript/compilation/DiscardStatement.java b/src/me/topchetoeu/jscript/compilation/values/DiscardStatement.java similarity index 52% rename from src/me/topchetoeu/jscript/compilation/DiscardStatement.java rename to src/me/topchetoeu/jscript/compilation/values/DiscardStatement.java index 6e54b4c..a16cc9a 100644 --- a/src/me/topchetoeu/jscript/compilation/DiscardStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/DiscardStatement.java @@ -1,23 +1,20 @@ -package me.topchetoeu.jscript.compilation; +package me.topchetoeu.jscript.compilation.values; import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.values.ConstantStatement; +import me.topchetoeu.jscript.compilation.CompileTarget; +import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class DiscardStatement extends Statement { public final Statement value; + @Override public boolean pure() { return value.pure(); } + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { value.compile(target, scope, false); - - } - @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); + if (pollute) target.add(Instruction.loadValue(loc(), null)); } public DiscardStatement(Location loc, Statement val) { diff --git a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java index 52c292a..06d1540 100644 --- a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -8,23 +8,25 @@ import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; 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 varName; public final String[] args; + public final boolean statement; + public final Location end; private static Random rand = new Random(); - @Override - public boolean pure() { return name == null; } + @Override public boolean pure() { return varName == null && statement; } @Override public void declare(ScopeRecord scope) { - if (name != null) scope.define(name); + if (varName != null && statement) scope.define(varName); } public static void checkBreakAndCont(CompileTarget target, int start) { @@ -40,73 +42,98 @@ public class FunctionStatement extends Statement { } } - public void compile(CompileTarget target, ScopeRecord scope, String name, boolean isStatement) { + private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) { 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; + if (args[i].equals(args[j])) { + throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."); } } } - var subscope = scope.child(); - int start = target.size(); - var funcTarget = new CompileTarget(target.functions, target.breakpoints); + var id = rand.nextLong(); + var subscope = scope.child(); + var subtarget = new CompileTarget(target.functions, target.breakpoints); subscope.define("this"); var argsVar = subscope.define("arguments"); if (args.length > 0) { for (var i = 0; i < args.length; i++) { - funcTarget.add(Instruction.loadVar(argsVar).locate(loc())); - funcTarget.add(Instruction.loadMember(i).locate(loc())); - funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc())); + subtarget.add(Instruction.loadVar(loc(), argsVar)); + subtarget.add(Instruction.loadMember(loc(), i)); + subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i]))); } } - if (!isStatement && this.name != null) { - funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name))); + if (!statement && this.varName != null) { + subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName))); + subtarget.setDebug(bp); } body.declare(subscope); - body.compile(funcTarget, subscope, false); - funcTarget.add(Instruction.ret().locate(loc())); - checkBreakAndCont(funcTarget, start); + body.compile(subtarget, subscope, false); + subtarget.add(Instruction.ret(end)); + checkBreakAndCont(subtarget, 0); - var id = rand.nextLong(); + if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures())); + target.functions.put(id, new FunctionBody( + subscope.localsCount(), args.length, + subtarget.array(), subscope.captures(), subscope.locals() + )); - target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc())); - target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals())); - - if (name == null) name = this.name; - - if (name != null) { - target.add(Instruction.dup().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), false).locate(loc())); - } - - } - @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - compile(target, scope, null, false); - if (!pollute) target.add(Instruction.discard().locate(loc())); + return id; } - public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) { + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) { + if (this.varName != null) name = this.varName; + + var hasVar = this.varName != null && statement; + var hasName = name != null; + + compileBody(target, scope, pollute || hasVar || hasName, bp); + + if (hasName) { + if (pollute || hasVar) target.add(Instruction.dup(loc())); + target.add(Instruction.loadValue(loc(), "name")); + target.add(Instruction.loadValue(loc(), name)); + target.add(Instruction.storeMember(loc())); + } + + if (hasVar) { + var key = scope.getKey(this.varName); + + if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key)); + target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false)); + } + } + public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name) { + compile(target, scope, pollute, name, BreakpointType.NONE); + } + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bp) { + compile(target, scope, pollute, (String)null, bp); + } + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { + compile(target, scope, pollute, (String)null, BreakpointType.NONE); + } + + public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) { super(loc); - this.name = name; + + this.end = end; + this.varName = varName; + this.statement = statement; this.args = args; this.body = body; } + + public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name) { + if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name); + else stm.compile(target, scope, pollute); + } + public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) { + if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp); + else stm.compile(target, scope, pollute, bp); + } } diff --git a/src/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java b/src/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java index 354d09d..ba60194 100644 --- a/src/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java @@ -7,12 +7,11 @@ import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class GlobalThisStatement extends Statement { - @Override - public boolean pure() { return true; } + @Override public boolean pure() { return true; } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (pollute) target.add(Instruction.loadGlob().locate(loc())); + if (pollute) target.add(Instruction.loadGlob(loc())); } public GlobalThisStatement(Location loc) { diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java index 2cf8b00..77cb68c 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java @@ -4,6 +4,7 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; @@ -18,22 +19,22 @@ public class IndexAssignStatement extends Statement { if (operation != null) { object.compile(target, scope, true); index.compile(target, scope, true); - target.add(Instruction.dup(2, 0).locate(loc())); + target.add(Instruction.dup(loc(), 2)); - target.add(Instruction.loadMember().locate(loc())); + target.add(Instruction.loadMember(loc())); value.compile(target, scope, true); - target.add(Instruction.operation(operation).locate(loc())); + target.add(Instruction.operation(loc(), operation)); - target.add(Instruction.storeMember(pollute).locate(loc())); - target.setDebug(); + target.add(Instruction.storeMember(loc(), pollute)); + target.setDebug(BreakpointType.STEP_IN); } else { object.compile(target, scope, true); index.compile(target, scope, true); value.compile(target, scope, true); - target.add(Instruction.storeMember(pollute).locate(loc())); - target.setDebug(); + target.add(Instruction.storeMember(loc(), pollute)); + target.setDebug(BreakpointType.STEP_IN); } } diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java index b821cd0..f2b7c42 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -5,6 +5,7 @@ import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.Instruction.BreakpointType; import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; @@ -12,26 +13,23 @@ public class IndexStatement extends AssignableStatement { public final Statement object; public final Statement index; - @Override - public boolean pure() { return true; } - @Override public Statement toAssign(Statement val, Operation operation) { return new IndexAssignStatement(loc(), object, index, val, operation); } public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { object.compile(target, scope, true); - if (dupObj) target.add(Instruction.dup().locate(loc())); + if (dupObj) target.add(Instruction.dup(loc())); if (index instanceof ConstantStatement) { - target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); - target.setDebug(); + target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value)); + target.setDebug(BreakpointType.STEP_IN); return; } index.compile(target, scope, true); - target.add(Instruction.loadMember().locate(loc())); - target.setDebug(); - if (!pollute) target.add(Instruction.discard().locate(loc())); + target.add(Instruction.loadMember(loc())); + target.setDebug(BreakpointType.STEP_IN); + if (!pollute) target.add(Instruction.discard(loc())); } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { diff --git a/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java b/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java index fea8de5..f71f841 100644 --- a/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java @@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values; public class LazyAndStatement extends Statement { public final Statement first, second; - @Override - public boolean pure() { - return first.pure() && second.pure(); - } + @Override public boolean pure() { return first.pure() && second.pure(); } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { @@ -26,12 +23,12 @@ public class LazyAndStatement extends Statement { } first.compile(target, scope, true); - if (pollute) target.add(Instruction.dup().locate(loc())); + if (pollute) target.add(Instruction.dup(loc())); int start = target.size(); - target.add(Instruction.nop()); - if (pollute) target.add(Instruction.discard().locate(loc())); + target.add(Instruction.nop(null)); + if (pollute) target.add(Instruction.discard(loc())); second.compile(target, scope, pollute); - target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc())); + target.set(start, Instruction.jmpIfNot(loc(), target.size() - start)); } public LazyAndStatement(Location loc, Statement first, Statement second) { diff --git a/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java b/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java index 3e896dd..22ddd5b 100644 --- a/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java @@ -10,10 +10,7 @@ import me.topchetoeu.jscript.engine.values.Values; public class LazyOrStatement extends Statement { public final Statement first, second; - @Override - public boolean pure() { - return first.pure() && second.pure(); - } + @Override public boolean pure() { return first.pure() && second.pure(); } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { @@ -26,12 +23,12 @@ public class LazyOrStatement extends Statement { } first.compile(target, scope, true); - if (pollute) target.add(Instruction.dup().locate(loc())); + if (pollute) target.add(Instruction.dup(loc())); int start = target.size(); - target.add(Instruction.nop()); - if (pollute) target.add(Instruction.discard().locate(loc())); + target.add(Instruction.nop(null)); + if (pollute) target.add(Instruction.discard(loc())); second.compile(target, scope, pollute); - target.set(start, Instruction.jmpIf(target.size() - start).locate(loc())); + target.set(start, Instruction.jmpIf(loc(), target.size() - start)); } public LazyOrStatement(Location loc, Statement first, Statement second) { diff --git a/src/me/topchetoeu/jscript/compilation/values/NewStatement.java b/src/me/topchetoeu/jscript/compilation/values/NewStatement.java deleted file mode 100644 index d60ef3a..0000000 --- a/src/me/topchetoeu/jscript/compilation/values/NewStatement.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.CompileTarget; -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 void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - func.compile(target, scope, true); - - for (var arg : args) arg.compile(target, scope, true); - - target.add(Instruction.callNew(args.length).locate(loc())); - target.setDebug(); - } - - public NewStatement(Location loc, Statement func, Statement ...args) { - super(loc); - this.func = func; - this.args = args; - } -} diff --git a/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java b/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java index abcec8d..5e95ff0 100644 --- a/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java @@ -14,17 +14,24 @@ public class ObjectStatement extends Statement { public final Map getters; public final Map setters; + @Override public boolean pure() { + for (var el : map.values()) { + if (!el.pure()) return false; + } + + return true; + } + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - target.add(Instruction.loadObj().locate(loc())); + target.add(Instruction.loadObj(loc())); for (var el : map.entrySet()) { - target.add(Instruction.dup().locate(loc())); - target.add(Instruction.loadValue(el.getKey()).locate(loc())); + target.add(Instruction.dup(loc())); + target.add(Instruction.loadValue(loc(), el.getKey())); var val = el.getValue(); - if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false); - else val.compile(target, scope, true); - target.add(Instruction.storeMember().locate(loc())); + FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString()); + target.add(Instruction.storeMember(loc())); } var keys = new ArrayList(); @@ -32,19 +39,19 @@ public class ObjectStatement extends Statement { 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 (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key)); + else target.add(Instruction.loadValue(loc(), (Double)key)); if (getters.containsKey(key)) getters.get(key).compile(target, scope, true); - else target.add(Instruction.loadValue(null).locate(loc())); + else target.add(Instruction.loadValue(loc(), null)); if (setters.containsKey(key)) setters.get(key).compile(target, scope, true); - else target.add(Instruction.loadValue(null).locate(loc())); + else target.add(Instruction.loadValue(loc(), null)); - target.add(Instruction.defProp().locate(loc())); + target.add(Instruction.defProp(loc())); } - if (!pollute) target.add(Instruction.discard().locate(loc())); + if (!pollute) target.add(Instruction.discard(loc())); } public ObjectStatement(Location loc, Map map, Map getters, Map setters) { diff --git a/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java b/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java index 97479bd..7ab59a0 100644 --- a/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java @@ -4,57 +4,29 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.control.ThrowStatement; import me.topchetoeu.jscript.engine.Operation; 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 Operation operation; + @Override public boolean pure() { + for (var el : args) { + if (!el.pure()) return false; + } + + return true; + } + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { for (var arg : args) { arg.compile(target, scope, true); } - if (pollute) target.add(Instruction.operation(operation).locate(loc())); - else target.add(Instruction.discard().locate(loc())); - } - - @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 { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); } - catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); } - } - - return new OperationStatement(loc(), operation, args); - + if (pollute) target.add(Instruction.operation(loc(), operation)); + else target.add(Instruction.discard(loc())); } public OperationStatement(Location loc, Operation operation, Statement ...args) { diff --git a/src/me/topchetoeu/jscript/compilation/values/RegexStatement.java b/src/me/topchetoeu/jscript/compilation/values/RegexStatement.java index 26f83b4..aaec539 100644 --- a/src/me/topchetoeu/jscript/compilation/values/RegexStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/RegexStatement.java @@ -9,13 +9,13 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class RegexStatement extends Statement { public final String pattern, flags; - @Override - public boolean pure() { return true; } + // Not really pure, since a function is called, but can be ignored. + @Override public boolean pure() { return true; } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - target.add(Instruction.loadRegex(pattern, flags).locate(loc())); - if (!pollute) target.add(Instruction.discard().locate(loc())); + target.add(Instruction.loadRegex(loc(), pattern, flags)); + if (!pollute) target.add(Instruction.discard(loc())); } public RegexStatement(Location loc, String pattern, String flags) { diff --git a/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java b/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java index 41eb2a1..37a8ce1 100644 --- a/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java @@ -4,44 +4,26 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.control.ArrayStatement; import me.topchetoeu.jscript.engine.scope.ScopeRecord; -import me.topchetoeu.jscript.engine.values.Values; public class TypeofStatement extends Statement { public final Statement value; - @Override - public boolean pure() { return true; } + // Not really pure, since a variable from the global scope could be accessed, + // which could lead to code execution, that would get omitted + @Override public boolean pure() { return true; } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { if (value instanceof VariableStatement) { var i = scope.getKey(((VariableStatement)value).name); if (i instanceof String) { - target.add(Instruction.typeof((String)i).locate(loc())); + target.add(Instruction.typeof(loc(), (String)i)); return; } } value.compile(target, scope, pollute); - target.add(Instruction.typeof().locate(loc())); - } - - @Override - public Statement optimize() { - var val = value.optimize(); - - if (val instanceof ConstantStatement) { - return new ConstantStatement(loc(), Values.type(((ConstantStatement)val).value)); - } - else if ( - val instanceof ObjectStatement || - val instanceof ArrayStatement || - val instanceof GlobalThisStatement - ) return new ConstantStatement(loc(), "object"); - else if(val instanceof FunctionStatement) return new ConstantStatement(loc(), "function"); - - return new TypeofStatement(loc(), val); + target.add(Instruction.typeof(loc())); } public TypeofStatement(Location loc, Statement value) { diff --git a/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java b/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java index 28f46e9..84be851 100644 --- a/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java @@ -12,20 +12,20 @@ public class VariableAssignStatement extends Statement { public final Statement value; public final Operation operation; + @Override public boolean pure() { return false; } + @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { 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.compile(target, scope, true); - target.add(Instruction.operation(operation).locate(loc())); - target.add(Instruction.storeVar(i, pollute).locate(loc())); + target.add(Instruction.loadVar(loc(), i)); + FunctionStatement.compileWithName(value, target, scope, true, name); + target.add(Instruction.operation(loc(), operation)); + target.add(Instruction.storeVar(loc(), i, pollute)); } else { - if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); - else value.compile(target, scope, true); - target.add(Instruction.storeVar(i, pollute).locate(loc())); + FunctionStatement.compileWithName(value, target, scope, true, name); + target.add(Instruction.storeVar(loc(), i, pollute)); } } diff --git a/src/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java b/src/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java index a916407..f0350a1 100644 --- a/src/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java @@ -9,12 +9,11 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class VariableIndexStatement extends Statement { public final int index; - @Override - public boolean pure() { return true; } + @Override public boolean pure() { return true; } @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (pollute) target.add(Instruction.loadVar(index).locate(loc())); + if (pollute) target.add(Instruction.loadVar(loc(), index)); } public VariableIndexStatement(Location loc, int i) { diff --git a/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java b/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java index 947d029..3f4402f 100644 --- a/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java @@ -11,8 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class VariableStatement extends AssignableStatement { public final String name; - @Override - public boolean pure() { return true; } + @Override public boolean pure() { return false; } @Override public Statement toAssign(Statement val, Operation operation) { @@ -22,8 +21,8 @@ public class VariableStatement extends AssignableStatement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { var i = scope.getKey(name); - target.add(Instruction.loadVar(i).locate(loc())); - if (!pollute) target.add(Instruction.discard().locate(loc())); + target.add(Instruction.loadVar(loc(), i)); + if (!pollute) target.add(Instruction.discard(loc())); } public VariableStatement(Location loc, String name) { diff --git a/src/me/topchetoeu/jscript/compilation/values/VoidStatement.java b/src/me/topchetoeu/jscript/compilation/values/VoidStatement.java deleted file mode 100644 index fbf0b63..0000000 --- a/src/me/topchetoeu/jscript/compilation/values/VoidStatement.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.engine.scope.ScopeRecord; -import me.topchetoeu.jscript.compilation.CompileTarget; -import me.topchetoeu.jscript.compilation.Instruction; - -public class VoidStatement extends Statement { - public final Statement value; - - @Override - public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - if (value != null) value.compile(target, scope, false); - if (pollute) 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; - } -} diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index c8246a5..354d49c 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -1,111 +1,125 @@ -package me.topchetoeu.jscript.engine; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Stack; -import java.util.TreeSet; - -import me.topchetoeu.jscript.Filename; -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.parsing.Parsing; - -public class Context { - private final Stack env = new Stack<>(); - private final ArrayList frames = new ArrayList<>(); - public final Engine engine; - - public Environment environment() { - return env.empty() ? null : env.peek(); - } - - public Context pushEnv(Environment env) { - this.env.push(env); - return this; - } - public void popEnv() { - if (!env.empty()) this.env.pop(); - } - - public FunctionValue compile(Filename filename, String raw) { - var env = environment(); - var transpiled = env.compile.call(this, null, raw, filename.toString(), env); - String source = null; - FunctionValue runner = null; - - if (transpiled instanceof ObjectValue) { - source = Values.toString(this, Values.getMember(this, transpiled, "source")); - var _runner = Values.getMember(this, transpiled, "runner"); - if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner; - } - else source = Values.toString(this, transpiled); - - var breakpoints = new TreeSet(); - FunctionValue res = Parsing.compile(Engine.functions, breakpoints, env, filename, source); - engine.onSource(filename, source, breakpoints); - - if (runner != null) res = (FunctionValue)runner.call(this, null, res); - - return res; - } - - - public void pushFrame(CodeFrame frame) { - frames.add(frame); - if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); - pushEnv(frame.function.environment); - } - public boolean popFrame(CodeFrame frame) { - if (frames.size() == 0) return false; - if (frames.get(frames.size() - 1) != frame) return false; - frames.remove(frames.size() - 1); - popEnv(); - engine.onFramePop(this, frame); - return true; - } - public CodeFrame peekFrame() { - if (frames.size() == 0) return null; - return frames.get(frames.size() - 1); - } - - public List frames() { - return Collections.unmodifiableList(frames); - } - public List stackTrace() { - var res = new ArrayList(); - - for (var i = frames.size() - 1; i >= 0; i--) { - var el = frames.get(i); - var name = el.function.name; - Location loc = null; - - for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location; - if (loc == null) loc = el.function.loc(); - - var trace = ""; - - if (loc != null) trace += "at " + loc.toString() + " "; - if (name != null && !name.equals("")) trace += "in " + name + " "; - - trace = trace.trim(); - - if (!trace.equals("")) res.add(trace); - } - - return res; - } - - public Context(Engine engine) { - this.engine = engine; - } - public Context(Engine engine, Environment env) { - this(engine); - this.pushEnv(env); - - } -} +package me.topchetoeu.jscript.engine; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Stack; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.mapping.SourceMap; + +public class Context { + private final Stack env = new Stack<>(); + private final ArrayList frames = new ArrayList<>(); + public final Engine engine; + + public Environment environment() { + return env.empty() ? null : env.peek(); + } + + public Context pushEnv(Environment env) { + this.env.push(env); + return this; + } + public void popEnv() { + if (!env.empty()) this.env.pop(); + } + + public FunctionValue compile(Filename filename, String raw) { + var env = environment(); + var result = env.compile.call(this, null, raw, filename.toString(), env); + + var function = (FunctionValue)Values.getMember(this, result, "function"); + if (!engine.debugging) return function; + + var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray(); + var breakpoints = new TreeSet<>( + Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray()) + .map(v -> Location.parse(Values.toString(this, v))) + .collect(Collectors.toList()) + ); + var maps = new SourceMap[rawMapChain.length]; + + for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i])); + + var map = SourceMap.chain(maps); + + if (map != null) { + var newBreakpoints = new TreeSet(); + for (var bp : breakpoints) { + bp = map.toCompiled(bp); + if (bp != null) newBreakpoints.add(bp); + } + breakpoints = newBreakpoints; + } + + engine.onSource(filename, raw, breakpoints, map); + + return function; + } + + + public void pushFrame(CodeFrame frame) { + frames.add(frame); + if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); + pushEnv(frame.function.environment); + engine.onFramePush(this, frame); + } + public boolean popFrame(CodeFrame frame) { + if (frames.size() == 0) return false; + if (frames.get(frames.size() - 1) != frame) return false; + frames.remove(frames.size() - 1); + popEnv(); + engine.onFramePop(this, frame); + return true; + } + public CodeFrame peekFrame() { + if (frames.size() == 0) return null; + return frames.get(frames.size() - 1); + } + + public List frames() { + return Collections.unmodifiableList(frames); + } + public List stackTrace() { + var res = new ArrayList(); + + for (var i = frames.size() - 1; i >= 0; i--) { + var el = frames.get(i); + var name = el.function.name; + Location loc = null; + + for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location; + if (loc == null) loc = el.function.loc(); + + var trace = ""; + + if (loc != null) trace += "at " + loc.toString() + " "; + if (name != null && !name.equals("")) trace += "in " + name + " "; + + trace = trace.trim(); + + if (!trace.equals("")) res.add(trace); + } + + return res; + } + + public Context(Engine engine) { + this.engine = engine; + } + public Context(Engine engine, Environment env) { + this(engine); + this.pushEnv(env); + + } +} diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 5dde3da..773ec9f 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -15,6 +15,7 @@ import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; +import me.topchetoeu.jscript.mapping.SourceMap; public class Engine implements DebugController { private class UncompiledFunction extends FunctionValue { @@ -66,22 +67,36 @@ public class Engine implements DebugController { private final HashMap sources = new HashMap<>(); private final HashMap> bpts = new HashMap<>(); + private final HashMap maps = new HashMap<>(); + + public Location mapToCompiled(Location location) { + var map = maps.get(location.filename()); + if (map == null) return location; + return map.toCompiled(location); + } + public Location mapToOriginal(Location location) { + var map = maps.get(location.filename()); + if (map == null) return location; + return map.toOriginal(location); + } private DebugController debugger; private Thread thread; private PriorityBlockingQueue tasks = new PriorityBlockingQueue<>(); - public boolean attachDebugger(DebugController debugger) { + public synchronized boolean attachDebugger(DebugController debugger) { if (!debugging || this.debugger != null) return false; - for (var source : sources.entrySet()) { - debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey())); - } + for (var source : sources.entrySet()) debugger.onSource( + source.getKey(), source.getValue(), + bpts.get(source.getKey()), + maps.get(source.getKey()) + ); this.debugger = debugger; return true; } - public boolean detachDebugger() { + public synchronized boolean detachDebugger() { if (!debugging || this.debugger == null) return false; this.debugger = null; return true; @@ -122,7 +137,7 @@ public class Engine implements DebugController { public boolean inExecThread() { return Thread.currentThread() == thread; } - public boolean isRunning() { + public synchronized boolean isRunning() { return this.thread != null; } @@ -135,6 +150,10 @@ public class Engine implements DebugController { return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); } + @Override + public void onFramePush(Context ctx, CodeFrame frame) { + if (debugging && debugger != null) debugger.onFramePush(ctx, frame); + } @Override public void onFramePop(Context ctx, CodeFrame frame) { if (debugging && debugger != null) debugger.onFramePop(ctx, frame); } @@ -142,11 +161,12 @@ public class Engine implements DebugController { if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); else return false; } - @Override public void onSource(Filename filename, String source, TreeSet breakpoints) { + @Override public void onSource(Filename filename, String source, TreeSet breakpoints, SourceMap map) { if (!debugging) return; - if (debugger != null) debugger.onSource(filename, source, breakpoints); + if (debugger != null) debugger.onSource(filename, source, breakpoints, map); sources.put(filename, source); bpts.put(filename, breakpoints); + maps.put(filename, map); } public Engine(boolean debugging) { diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 49d67c5..7fb7714 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -1,100 +1,131 @@ -package me.topchetoeu.jscript.engine; - -import java.util.HashMap; - -import me.topchetoeu.jscript.engine.scope.GlobalScope; -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.engine.values.Symbol; -import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.filesystem.RootFilesystem; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeSetter; -import me.topchetoeu.jscript.interop.NativeWrapperProvider; -import me.topchetoeu.jscript.permissions.Permission; -import me.topchetoeu.jscript.permissions.PermissionsProvider; - -public class Environment implements PermissionsProvider { - private HashMap prototypes = new HashMap<>(); - - public final Data data = new Data(); - public static final HashMap symbols = new HashMap<>(); - - public GlobalScope global; - public WrappersProvider wrappers; - public PermissionsProvider permissions = null; - public final RootFilesystem filesystem = new RootFilesystem(this); - - private static int nextId = 0; - - @Native public int id = ++nextId; - - @Native public FunctionValue compile; - @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); - }); - - public Environment addData(Data data) { - this.data.addAll(data); - return this; - } - - @Native public ObjectValue proto(String name) { - return prototypes.get(name); - } - @Native public void setProto(String name, ObjectValue val) { - prototypes.put(name, val); - } - - @Native public Symbol symbol(String name) { - if (symbols.containsKey(name)) return symbols.get(name); - else { - var res = new Symbol(name); - symbols.put(name, res); - return res; - } - } - - @NativeGetter("global") public ObjectValue getGlobal() { - return global.obj; - } - @NativeSetter("global") public void setGlobal(ObjectValue val) { - global = new GlobalScope(val); - } - - @Native public Environment fork() { - var res = new Environment(compile, null, global); - res.wrappers = wrappers.fork(res); - res.regexConstructor = regexConstructor; - res.prototypes = new HashMap<>(prototypes); - return res; - } - @Native public Environment child() { - var res = fork(); - res.global = res.global.globalChild(); - return res; - } - - @Override public boolean hasPermission(Permission perm, char delim) { - return permissions == null || permissions.hasPermission(perm, delim); - } - @Override public boolean hasPermission(Permission perm) { - return permissions == null || permissions.hasPermission(perm); - } - - public Context context(Engine engine) { - return new Context(engine).pushEnv(this); - } - - public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { - if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]); - if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); - if (global == null) global = new GlobalScope(); - - this.wrappers = nativeConverter; - this.compile = compile; - this.global = global; - } -} +package me.topchetoeu.jscript.engine; + +import java.util.HashMap; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.scope.GlobalScope; +import me.topchetoeu.jscript.engine.values.ArrayValue; +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.engine.values.Symbol; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.filesystem.RootFilesystem; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeSetter; +import me.topchetoeu.jscript.interop.NativeWrapperProvider; +import me.topchetoeu.jscript.parsing.Parsing; +import me.topchetoeu.jscript.permissions.Permission; +import me.topchetoeu.jscript.permissions.PermissionsProvider; + +public class Environment implements PermissionsProvider { + private HashMap prototypes = new HashMap<>(); + + public final Data data = new Data(); + public static final HashMap symbols = new HashMap<>(); + + public GlobalScope global; + public WrappersProvider wrappers; + public PermissionsProvider permissions = null; + public final RootFilesystem filesystem = new RootFilesystem(this); + + private static int nextId = 0; + + @Native public boolean stackVisible = true; + @Native public int id = ++nextId; + + @Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> { + var source = Values.toString(ctx, args[0]); + var filename = Values.toString(ctx, args[1]); + var isDebug = Values.toBoolean(args[2]); + + var env = Values.wrapper(args[2], Environment.class); + var res = new ObjectValue(); + + var target = Parsing.compile(env, Filename.parse(filename), source); + Engine.functions.putAll(target.functions); + Engine.functions.remove(0l); + + res.defineProperty(ctx, "function", target.func(env)); + res.defineProperty(ctx, "mapChain", new ArrayValue()); + + + if (isDebug) { + res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))); + } + + return res; + }); + @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { + throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); + }); + + public Environment addData(Data data) { + this.data.addAll(data); + return this; + } + + @Native public ObjectValue proto(String name) { + return prototypes.get(name); + } + @Native public void setProto(String name, ObjectValue val) { + prototypes.put(name, val); + } + + @Native public Symbol symbol(String name) { + return getSymbol(name); + } + + @NativeGetter("global") public ObjectValue getGlobal() { + return global.obj; + } + @NativeSetter("global") public void setGlobal(ObjectValue val) { + global = new GlobalScope(val); + } + + @Native public Environment fork() { + var res = new Environment(compile, null, global); + res.wrappers = wrappers.fork(res); + res.regexConstructor = regexConstructor; + res.prototypes = new HashMap<>(prototypes); + return res; + } + @Native public Environment child() { + var res = fork(); + res.global = res.global.globalChild(); + return res; + } + + @Override public boolean hasPermission(Permission perm, char delim) { + return permissions == null || permissions.hasPermission(perm, delim); + } + @Override public boolean hasPermission(Permission perm) { + return permissions == null || permissions.hasPermission(perm); + } + + public Context context(Engine engine) { + return new Context(engine).pushEnv(this); + } + + public static Symbol getSymbol(String name) { + if (symbols.containsKey(name)) return symbols.get(name); + else { + var res = new Symbol(name); + symbols.put(name, res); + return res; + } + } + + public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { + if (compile != null) this.compile = compile; + if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); + if (global == null) global = new GlobalScope(); + + this.wrappers = nativeConverter; + this.global = global; + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugController.java b/src/me/topchetoeu/jscript/engine/debug/DebugController.java index 0b04c0f..de4e4a1 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugController.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugController.java @@ -1,40 +1,51 @@ -package me.topchetoeu.jscript.engine.debug; - -import java.util.TreeSet; - -import me.topchetoeu.jscript.Filename; -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.Instruction; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.exceptions.EngineException; - -public interface DebugController { - /** - * Called when a script has been loaded - * @param breakpoints - */ - void onSource(Filename filename, String source, TreeSet breakpoints); - - /** - * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. - * This function might pause in order to await debugging commands. - * @param ctx The context of execution - * @param frame The frame in which execution is occuring - * @param instruction The instruction which was or will be executed - * @param loc The most recent location the code frame has been at - * @param returnVal The return value of the instruction, Runners.NO_RETURN if none - * @param error The error that the instruction threw, null if none - * @param caught Whether or not the error has been caught - * @return Whether or not the frame should restart - */ - boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); - - /** - * Called immediatly after a frame has been popped out of the frame stack. - * This function might pause in order to await debugging commands. - * @param ctx The context of execution - * @param frame The code frame which was popped out - */ - void onFramePop(Context ctx, CodeFrame frame); -} +package me.topchetoeu.jscript.engine.debug; + +import java.util.TreeSet; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.mapping.SourceMap; + +public interface DebugController { + /** + * Called when a script has been loaded + * @param filename The name of the source + * @param source The name of the source + * @param breakpoints A set of all the breakpointable locations in this source + * @param map The source map associated with this file. null if this source map isn't mapped + */ + void onSource(Filename filename, String source, TreeSet breakpoints, SourceMap map); + + /** + * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. + * This function might pause in order to await debugging commands. + * @param ctx The context of execution + * @param frame The frame in which execution is occuring + * @param instruction The instruction which was or will be executed + * @param loc The most recent location the code frame has been at + * @param returnVal The return value of the instruction, Runners.NO_RETURN if none + * @param error The error that the instruction threw, null if none + * @param caught Whether or not the error has been caught + * @return Whether or not the frame should restart + */ + boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); + + /** + * Called immediatly before a frame has been pushed on the frame stack. + * This function might pause in order to await debugging commands. + * @param ctx The context of execution + * @param frame The code frame which was pushed + */ + void onFramePush(Context ctx, CodeFrame frame); + /** + * Called immediatly after a frame has been popped out of the frame stack. + * This function might pause in order to await debugging commands. + * @param ctx The context of execution + * @param frame The code frame which was popped out + */ + void onFramePop(Context ctx, CodeFrame frame); +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java index fbcc838..371217b 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java @@ -1,35 +1,34 @@ -package me.topchetoeu.jscript.engine.debug; - -public interface DebugHandler { - void enable(V8Message msg); - void disable(V8Message msg); - - void setBreakpoint(V8Message msg); - void setBreakpointByUrl(V8Message msg); - void removeBreakpoint(V8Message msg); - void continueToLocation(V8Message msg); - - void getScriptSource(V8Message msg); - void getPossibleBreakpoints(V8Message msg); - - void resume(V8Message msg); - void pause(V8Message msg); - - void stepInto(V8Message msg); - void stepOut(V8Message msg); - void stepOver(V8Message msg); - - void setPauseOnExceptions(V8Message msg); - - void evaluateOnCallFrame(V8Message msg); - - void getProperties(V8Message msg); - void releaseObjectGroup(V8Message msg); - void releaseObject(V8Message msg); - /** - * This method might not execute the actual code for well-known requests - */ - void callFunctionOn(V8Message msg); - - void runtimeEnable(V8Message msg); -} +package me.topchetoeu.jscript.engine.debug; + +public interface DebugHandler { + void enable(V8Message msg); + void disable(V8Message msg); + + void setBreakpointByUrl(V8Message msg); + void removeBreakpoint(V8Message msg); + void continueToLocation(V8Message msg); + + void getScriptSource(V8Message msg); + void getPossibleBreakpoints(V8Message msg); + + void resume(V8Message msg); + void pause(V8Message msg); + + void stepInto(V8Message msg); + void stepOut(V8Message msg); + void stepOver(V8Message msg); + + void setPauseOnExceptions(V8Message msg); + + void evaluateOnCallFrame(V8Message msg); + + void getProperties(V8Message msg); + void releaseObjectGroup(V8Message msg); + void releaseObject(V8Message msg); + /** + * This method might not execute the actual code for well-known requests + */ + void callFunctionOn(V8Message msg); + + void runtimeEnable(V8Message msg); +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index 3154ab6..001c9cb 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -1,246 +1,245 @@ -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 java.util.HashMap; - -import me.topchetoeu.jscript.Metadata; -import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; -import me.topchetoeu.jscript.events.Notifier; -import me.topchetoeu.jscript.exceptions.SyntaxException; -import me.topchetoeu.jscript.exceptions.UncheckedException; -import me.topchetoeu.jscript.exceptions.UncheckedIOException; -import me.topchetoeu.jscript.json.JSON; -import me.topchetoeu.jscript.json.JSONList; -import me.topchetoeu.jscript.json.JSONMap; - -public class DebugServer { - public static String browserDisplayName = Metadata.name() + "/" + Metadata.version(); - - public final HashMap targets = new HashMap<>(); - - private final byte[] favicon, index, protocol; - private final Notifier connNotifier = new Notifier(); - - private static void send(HttpRequest req, String val) throws IOException { - req.writeResponse(200, "OK", "application/json", val.getBytes()); - } - - // SILENCE JAVA - private MessageDigest getDigestInstance() { - try { - return MessageDigest.getInstance("sha1"); - } - catch (Throwable e) { throw new UncheckedException(e); } - - } - - 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, Debugger debugger) { - WebSocketMessage raw; - - debugger.connect(); - - 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; - } - - try { - switch (msg.name) { - case "Debugger.enable": - connNotifier.next(); - debugger.enable(msg); continue; - case "Debugger.disable": debugger.disable(msg); continue; - - case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue; - case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue; - case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue; - case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue; - - case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue; - case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue; - - case "Debugger.resume": debugger.resume(msg); continue; - case "Debugger.pause": debugger.pause(msg); continue; - - case "Debugger.stepInto": debugger.stepInto(msg); continue; - case "Debugger.stepOut": debugger.stepOut(msg); continue; - case "Debugger.stepOver": debugger.stepOver(msg); continue; - - case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue; - case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue; - - case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue; - case "Runtime.releaseObject": debugger.releaseObject(msg); continue; - case "Runtime.getProperties": debugger.getProperties(msg); continue; - case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue; - // case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue; - case "Runtime.enable": debugger.runtimeEnable(msg); continue; - } - - if ( - msg.name.startsWith("DOM.") || - msg.name.startsWith("DOMDebugger.") || - msg.name.startsWith("Emulation.") || - msg.name.startsWith("Input.") || - msg.name.startsWith("Network.") || - msg.name.startsWith("Page.") - ) ws.send(new V8Error("This isn't a browser...")); - else ws.send(new V8Error("This API is not supported yet.")); - } - catch (Throwable e) { - e.printStackTrace(); - throw new UncheckedException(e); - } - } - - debugger.disconnect(); - } - private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) { - var key = req.headers.get("sec-websocket-key"); - - if (key == null) { - req.writeResponse( - 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() - )); - - req.writeCode(101, "Switching Protocols"); - req.writeHeader("Connection", "Upgrade"); - req.writeHeader("Sec-WebSocket-Accept", resKey); - req.writeLastHeader("Upgrade", "WebSocket"); - - var ws = new WebSocket(socket); - var debugger = debuggerProvider.getDebugger(ws, req); - - if (debugger == null) { - ws.close(); - return; - } - - runAsync(() -> { - try { handle(ws, debugger); } - catch (RuntimeException e) { - ws.send(new V8Error(e.getMessage())); - } - finally { ws.close(); debugger.disconnect(); } - }, "Debug Handler"); - } - - public void awaitConnection() { - connNotifier.await(); - } - - public void run(InetSocketAddress address) { - try { - ServerSocket server = new ServerSocket(); - server.bind(address); - - try { - while (true) { - var socket = server.accept(); - var req = HttpRequest.read(socket); - - if (req == null) continue; - - switch (req.path) { - case "/json/version": - send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}"); - break; - case "/json/list": - case "/json": { - var res = new JSONList(); - - for (var el : targets.entrySet()) { - res.add(new JSONMap() - .set("description", "JScript debugger") - .set("favicon", "/favicon.ico") - .set("id", el.getKey()) - .set("type", "node") - .set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey()) - ); - } - send(req, JSON.stringify(res)); - break; - } - case "/json/protocol": - req.writeResponse(200, "OK", "application/json", protocol); - break; - case "/json/new": - case "/json/activate": - case "/json/close": - case "/devtools/inspector.html": - req.writeResponse( - 501, "Not Implemented", "text/txt", - "This feature isn't (and probably won't be) implemented.".getBytes() - ); - break; - case "/": - case "/index.html": - req.writeResponse(200, "OK", "text/html", index); - break; - case "/favicon.ico": - req.writeResponse(200, "OK", "image/png", favicon); - break; - default: - if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) { - onWsConnect(req, socket, targets.get(req.path.substring(1))); - } - break; - } - } - } - finally { server.close(); } - } - catch (IOException e) { throw new UncheckedIOException(e); } - } - - public Thread start(InetSocketAddress address, boolean daemon) { - var res = new Thread(() -> run(address), "Debug Server"); - res.setDaemon(daemon); - res.start(); - return res; - } - - public DebugServer() { - try { - this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes(); - this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); - var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); - this.index = index - .replace("${NAME}", Metadata.name()) - .replace("${VERSION}", Metadata.version()) - .replace("${AUTHOR}", Metadata.author()) - .getBytes(); - } - catch (IOException e) { throw new UncheckedIOException(e); } - } -} +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 java.util.HashMap; + +import me.topchetoeu.jscript.Metadata; +import me.topchetoeu.jscript.Reading; +import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; +import me.topchetoeu.jscript.events.Notifier; +import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.exceptions.UncheckedException; +import me.topchetoeu.jscript.exceptions.UncheckedIOException; +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONList; +import me.topchetoeu.jscript.json.JSONMap; + +public class DebugServer { + public static String browserDisplayName = Metadata.name() + "/" + Metadata.version(); + + public final HashMap targets = new HashMap<>(); + + private final byte[] favicon, index, protocol; + private final Notifier connNotifier = new Notifier(); + + private static void send(HttpRequest req, String val) throws IOException { + req.writeResponse(200, "OK", "application/json", val.getBytes()); + } + + // SILENCE JAVA + private MessageDigest getDigestInstance() { + try { + return MessageDigest.getInstance("sha1"); + } + catch (Throwable e) { throw new UncheckedException(e); } + + } + + 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, Debugger debugger) { + WebSocketMessage raw; + + debugger.connect(); + + 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; + } + + try { + switch (msg.name) { + case "Debugger.enable": + connNotifier.next(); + debugger.enable(msg); continue; + case "Debugger.disable": debugger.disable(msg); continue; + + case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue; + case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue; + case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue; + + case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue; + case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue; + + case "Debugger.resume": debugger.resume(msg); continue; + case "Debugger.pause": debugger.pause(msg); continue; + + case "Debugger.stepInto": debugger.stepInto(msg); continue; + case "Debugger.stepOut": debugger.stepOut(msg); continue; + case "Debugger.stepOver": debugger.stepOver(msg); continue; + + case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue; + case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue; + + case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue; + case "Runtime.releaseObject": debugger.releaseObject(msg); continue; + case "Runtime.getProperties": debugger.getProperties(msg); continue; + case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue; + // case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue; + case "Runtime.enable": debugger.runtimeEnable(msg); continue; + } + + if ( + msg.name.startsWith("DOM.") || + msg.name.startsWith("DOMDebugger.") || + msg.name.startsWith("Emulation.") || + msg.name.startsWith("Input.") || + msg.name.startsWith("Network.") || + msg.name.startsWith("Page.") + ) ws.send(new V8Error("This isn't a browser...")); + else ws.send(new V8Error("This API is not supported yet.")); + } + catch (Throwable e) { + e.printStackTrace(); + throw new UncheckedException(e); + } + } + + debugger.disconnect(); + } + private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) { + var key = req.headers.get("sec-websocket-key"); + + if (key == null) { + req.writeResponse( + 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() + )); + + req.writeCode(101, "Switching Protocols"); + req.writeHeader("Connection", "Upgrade"); + req.writeHeader("Sec-WebSocket-Accept", resKey); + req.writeLastHeader("Upgrade", "WebSocket"); + + var ws = new WebSocket(socket); + var debugger = debuggerProvider.getDebugger(ws, req); + + if (debugger == null) { + ws.close(); + return; + } + + runAsync(() -> { + try { handle(ws, debugger); } + catch (RuntimeException e) { + ws.send(new V8Error(e.getMessage())); + } + finally { ws.close(); debugger.disconnect(); } + }, "Debug Handler"); + } + + public void awaitConnection() { + connNotifier.await(); + } + + public void run(InetSocketAddress address) { + try { + ServerSocket server = new ServerSocket(); + server.bind(address); + + try { + while (true) { + var socket = server.accept(); + var req = HttpRequest.read(socket); + + if (req == null) continue; + + switch (req.path) { + case "/json/version": + send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}"); + break; + case "/json/list": + case "/json": { + var res = new JSONList(); + + for (var el : targets.entrySet()) { + res.add(new JSONMap() + .set("description", "JScript debugger") + .set("favicon", "/favicon.ico") + .set("id", el.getKey()) + .set("type", "node") + .set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey()) + ); + } + send(req, JSON.stringify(res)); + break; + } + case "/json/protocol": + req.writeResponse(200, "OK", "application/json", protocol); + break; + case "/json/new": + case "/json/activate": + case "/json/close": + case "/devtools/inspector.html": + req.writeResponse( + 501, "Not Implemented", "text/txt", + "This feature isn't (and probably won't be) implemented.".getBytes() + ); + break; + case "/": + case "/index.html": + req.writeResponse(200, "OK", "text/html", index); + break; + case "/favicon.ico": + req.writeResponse(200, "OK", "image/png", favicon); + break; + default: + if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) { + onWsConnect(req, socket, targets.get(req.path.substring(1))); + } + break; + } + } + } + finally { server.close(); } + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + public Thread start(InetSocketAddress address, boolean daemon) { + var res = new Thread(() -> run(address), "Debug Server"); + res.setDaemon(daemon); + res.start(); + return res; + } + + public DebugServer() { + try { + this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes(); + this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes(); + this.index = Reading.resourceToString("debugger/index.html") + .replace("${NAME}", Metadata.name()) + .replace("${VERSION}", Metadata.version()) + .replace("${AUTHOR}", Metadata.author()) + .getBytes(); + } + catch (IOException e) { throw new UncheckedIOException(e); } + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index eaae038..5143ad7 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -1,813 +1,970 @@ -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.Random; -import java.util.TreeSet; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import me.topchetoeu.jscript.Filename; -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.Instruction; -import me.topchetoeu.jscript.compilation.Instruction.Type; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Engine; -import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.engine.frame.Runners; -import me.topchetoeu.jscript.engine.scope.GlobalScope; -import me.topchetoeu.jscript.engine.values.ArrayValue; -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.Symbol; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.events.Notifier; -import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.json.JSON; -import me.topchetoeu.jscript.json.JSONElement; -import me.topchetoeu.jscript.json.JSONList; -import me.topchetoeu.jscript.json.JSONMap; - -// very simple indeed -public class SimpleDebugger implements Debugger { - public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}"; - public static final String VSCODE_STRINGIFY_PROPS = "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}"; - public static final String VSCODE_SYMBOL_REQUEST = "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}"; - public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}"; - public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s breakpoints; - - public Source(int id, Filename filename, String source, TreeSet breakpoints) { - this.id = id; - this.filename = filename; - this.source = source; - this.breakpoints = breakpoints; - } - } - private static class Breakpoint { - public final int id; - public final Location location; - public final String condition; - - public Breakpoint(int id, Location location, String condition) { - this.id = id; - this.location = location; - this.condition = condition; - } - } - private static class BreakpointCandidate { - public final int id; - public final String condition; - public final Pattern pattern; - public final int line, start; - public final HashSet resolvedBreakpoints = new HashSet<>(); - - public BreakpointCandidate(int id, Pattern pattern, int line, int start, String condition) { - this.id = id; - this.pattern = pattern; - this.line = line; - this.start = start; - if (condition != null && condition.trim().equals("")) condition = null; - this.condition = condition; - } - } - private class Frame { - public CodeFrame frame; - public CodeFunction func; - public int id; - public ObjectValue local, capture, global, valstack; - public JSONMap serialized; - public Location location; - public boolean debugData = false; - - public void updateLoc(Location loc) { - serialized.set("location", serializeLocation(loc)); - this.location = loc; - } - - public Frame(Context ctx, CodeFrame frame, int id) { - this.frame = frame; - this.func = frame.function; - this.id = id; - this.location = frame.function.loc(); - - this.global = frame.function.environment.global.obj; - this.local = frame.getLocalScope(ctx, true); - this.capture = frame.getCaptureScope(ctx, true); - this.local.setPrototype(ctx, capture); - this.capture.setPrototype(ctx, global); - this.valstack = frame.getValStackScope(ctx); - - if (location != null) { - debugData = true; - this.serialized = new JSONMap() - .set("callFrameId", id + "") - .set("functionName", func.name) - .set("location", serializeLocation(location)) - .set("scopeChain", new JSONList() - .add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local))) - .add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture))) - .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global))) - .add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack))) - ); - } - } - } - - private static class RunResult { - public final Context ctx; - public final Object result; - public final EngineException error; - - public RunResult(Context ctx, Object result, EngineException error) { - this.ctx = ctx; - this.result = result; - this.error = error; - } - } - - public boolean enabled = true; - public CatchType execptionType = CatchType.ALL; - public State state = State.RESUMED; - - public final WebSocket ws; - public final Engine target; - - private ObjectValue emptyObject = new ObjectValue(); - - private HashMap idToBptCand = new HashMap<>(); - - private HashMap idToBreakpoint = new HashMap<>(); - private HashMap locToBreakpoint = new HashMap<>(); - private HashSet tmpBreakpts = new HashSet<>(); - - private HashMap filenameToId = new HashMap<>(); - private HashMap idToSource = new HashMap<>(); - private ArrayList pendingSources = new ArrayList<>(); - - private HashMap idToFrame = new HashMap<>(); - private HashMap codeFrameToFrame = new HashMap<>(); - - private HashMap idToObject = new HashMap<>(); - private HashMap objectToId = new HashMap<>(); - private HashMap objectToCtx = new HashMap<>(); - private HashMap> objectGroups = new HashMap<>(); - - private Notifier updateNotifier = new Notifier(); - - private int nextId = new Random().nextInt() & 0x7FFFFFFF; - private Location prevLocation = null; - private Frame stepOutFrame = null, currFrame = null; - - private int nextId() { - return nextId++ ^ 1630022591 /* big prime */; - } - - private void updateFrames(Context ctx) { - var frame = ctx.peekFrame(); - if (frame == null) return; - - if (!codeFrameToFrame.containsKey(frame)) { - var id = nextId(); - var fr = new Frame(ctx, frame, id); - - idToFrame.put(id, fr); - codeFrameToFrame.put(frame, fr); - } - - currFrame = codeFrameToFrame.get(frame); - } - private JSONList serializeFrames(Context ctx) { - var res = new JSONList(); - var frames = ctx.frames(); - - for (var i = frames.size() - 1; i >= 0; i--) { - res.add(codeFrameToFrame.get(frames.get(i)).serialized); - } - - return res; - } - - private Location correctLocation(Source source, Location loc) { - var set = source.breakpoints; - - if (set.contains(loc)) return loc; - - var tail = set.tailSet(loc); - if (tail.isEmpty()) return null; - - return tail.first(); - } - private Location deserializeLocation(JSONElement el, boolean correct) { - if (!el.isMap()) throw new RuntimeException("Expected location to be a map."); - var id = Integer.parseInt(el.map().string("scriptId")); - var line = (int)el.map().number("lineNumber") + 1; - var column = (int)el.map().number("columnNumber") + 1; - - if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id)); - - var res = new Location(line, column, idToSource.get(id).filename); - if (correct) res = correctLocation(idToSource.get(id), res); - return res; - } - private JSONMap serializeLocation(Location loc) { - var source = filenameToId.get(loc.filename()); - return new JSONMap() - .set("scriptId", source + "") - .set("lineNumber", loc.line() - 1) - .set("columnNumber", loc.start() - 1); - } - - private Integer objectId(Context ctx, ObjectValue obj) { - if (objectToId.containsKey(obj)) return objectToId.get(obj); - else { - int id = nextId(); - objectToId.put(obj, id); - if (ctx != null) objectToCtx.put(obj, ctx); - idToObject.put(id, obj); - return id; - } - } - private JSONMap serializeObj(Context ctx, Object val) { - val = Values.normalize(null, val); - - if (val == Values.NULL) { - return new JSONMap() - .set("type", "object") - .set("subtype", "null") - .setNull("value") - .set("description", "null"); - } - - if (val instanceof ObjectValue) { - var obj = (ObjectValue)val; - var id = objectId(ctx, obj); - var type = "object"; - String subtype = null; - String className = null; - - if (obj instanceof FunctionValue) type = "function"; - if (obj instanceof ArrayValue) subtype = "array"; - - try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); } - catch (Exception e) { } - - var res = new JSONMap() - .set("type", type) - .set("objectId", id + ""); - - if (subtype != null) res.set("subtype", subtype); - if (className != null) { - res.set("className", className); - res.set("description", "{ " + className + " }"); - } - - return res; - } - - if (val == null) return new JSONMap().set("type", "undefined"); - if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val); - if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val); - if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString()); - if (val instanceof Number) { - var num = (double)(Number)val; - var res = new JSONMap().set("type", "number"); - - if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity"); - else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity"); - else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0"); - else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0"); - else if (Double.isNaN(num)) res.set("unserializableValue", "NaN"); - else res.set("value", num); - - return res; - } - - throw new IllegalArgumentException("Unexpected JS object."); - } - private void setObjectGroup(String name, Object val) { - if (val instanceof ObjectValue) { - var obj = (ObjectValue)val; - - if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj); - else objectGroups.put(name, new ArrayList<>(List.of(obj))); - } - } - private void releaseGroup(String name) { - var objs = objectGroups.remove(name); - - if (objs != null) { - for (var obj : objs) { - var id = objectToId.remove(obj); - if (id != null) idToObject.remove(id); - } - } - } - private Object deserializeArgument(JSONMap val) { - if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))); - else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) { - case "NaN": return Double.NaN; - case "-Infinity": return Double.NEGATIVE_INFINITY; - case "Infinity": return Double.POSITIVE_INFINITY; - case "-0": return -0.; - } - return JSON.toJs(val.get("value")); - } - - private JSONMap serializeException(Context ctx, EngineException err) { - String text = null; - - try { - text = Values.toString(ctx, err.value); - } - catch (EngineException e) { - text = "[error while stringifying]"; - } - - var res = new JSONMap() - .set("exceptionId", nextId()) - .set("exception", serializeObj(ctx, err.value)) - .set("exception", serializeObj(ctx, err.value)) - .set("text", text); - - return res; - } - - private void resume(State state) { - this.state = state; - ws.send(new V8Event("Debugger.resumed", new JSONMap())); - updateNotifier.next(); - } - private void pauseDebug(Context ctx, Breakpoint bp) { - state = State.PAUSED_NORMAL; - var map = new JSONMap() - .set("callFrames", serializeFrames(ctx)) - .set("reason", "debugCommand"); - - if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + "")); - ws.send(new V8Event("Debugger.paused", map)); - } - private void pauseException(Context ctx) { - state = State.PAUSED_EXCEPTION; - var map = new JSONMap() - .set("callFrames", serializeFrames(ctx)) - .set("reason", "exception"); - - ws.send(new V8Event("Debugger.paused", map)); - } - - private void sendSource(Source src) { - ws.send(new V8Event("Debugger.scriptParsed", new JSONMap() - .set("scriptId", src.id + "") - .set("hash", src.source.hashCode()) - .set("url", src.filename + "") - )); - } - - private void addBreakpoint(Breakpoint bpt) { - idToBreakpoint.put(bpt.id, bpt); - locToBreakpoint.put(bpt.location, bpt); - - ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap() - .set("breakpointId", bpt.id) - .set("location", serializeLocation(bpt.location)) - )); - } - - private RunResult run(Frame codeFrame, String code) { - var engine = new Engine(false); - var env = codeFrame.func.environment.fork(); - - ObjectValue global = env.global.obj, - capture = codeFrame.frame.getCaptureScope(null, enabled), - local = codeFrame.frame.getLocalScope(null, enabled); - - capture.setPrototype(null, global); - local.setPrototype(null, capture); - env.global = new GlobalScope(local); - - var ctx = new Context(engine).pushEnv(env); - var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); - - engine.run(true); - - try { return new RunResult(ctx, awaiter.await(), null); } - catch (EngineException e) { return new RunResult(ctx, null, e); } - } - - @Override public void enable(V8Message msg) { - enabled = true; - ws.send(msg.respond()); - - for (var el : pendingSources) sendSource(el); - pendingSources.clear(); - - updateNotifier.next(); - } - @Override public void disable(V8Message msg) { - enabled = false; - ws.send(msg.respond()); - updateNotifier.next(); - } - - @Override public void getScriptSource(V8Message msg) { - int id = Integer.parseInt(msg.params.string("scriptId")); - ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); - } - @Override public void getPossibleBreakpoints(V8Message msg) { - var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId"))); - var start = deserializeLocation(msg.params.get("start"), false); - var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null; - - var res = new JSONList(); - - for (var loc : src.breakpoints.tailSet(start, true)) { - if (end != null && loc.compareTo(end) > 0) break; - res.add(serializeLocation(loc)); - } - - ws.send(msg.respond(new JSONMap().set("locations", res))); - } - - @Override public void pause(V8Message msg) { - } - - @Override public void resume(V8Message msg) { - resume(State.RESUMED); - ws.send(msg.respond(new JSONMap())); - } - - @Override public void setBreakpoint(V8Message msg) { - // int id = nextId(); - // var loc = deserializeLocation(msg.params.get("location"), true); - // var bpt = new Breakpoint(id, loc, null); - // breakpoints.put(loc, bpt); - // idToBrpt.put(id, bpt); - // ws.send(msg.respond(new JSONMap() - // .set("breakpointId", id) - // .set("actualLocation", serializeLocation(loc)) - // )); - } - @Override public void setBreakpointByUrl(V8Message msg) { - var line = (int)msg.params.number("lineNumber") + 1; - var col = (int)msg.params.number("columnNumber", 0) + 1; - var cond = msg.params.string("condition", "").trim(); - - if (cond.equals("")) cond = null; - if (cond != null) cond = "(" + cond + ")"; - - Pattern regex; - - if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url"))); - else regex = Pattern.compile(msg.params.string("urlRegex")); - - var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond); - idToBptCand.put(bpcd.id, bpcd); - - var locs = new JSONList(); - - for (var src : idToSource.values()) { - if (regex.matcher(src.filename.toString()).matches()) { - var loc = correctLocation(src, new Location(line, col, src.filename)); - if (loc == null) continue; - var bp = new Breakpoint(nextId(), loc, bpcd.condition); - - bpcd.resolvedBreakpoints.add(bp); - locs.add(serializeLocation(loc)); - addBreakpoint(bp); - } - } - - ws.send(msg.respond(new JSONMap() - .set("breakpointId", bpcd.id + "") - .set("locations", locs) - )); - } - @Override public void removeBreakpoint(V8Message msg) { - var id = Integer.parseInt(msg.params.string("breakpointId")); - - if (idToBptCand.containsKey(id)) { - var bpcd = idToBptCand.get(id); - for (var bp : bpcd.resolvedBreakpoints) { - idToBreakpoint.remove(bp.id); - locToBreakpoint.remove(bp.location); - } - idToBptCand.remove(id); - } - else if (idToBreakpoint.containsKey(id)) { - var bp = idToBreakpoint.remove(id); - locToBreakpoint.remove(bp.location); - } - ws.send(msg.respond()); - } - @Override public void continueToLocation(V8Message msg) { - var loc = deserializeLocation(msg.params.get("location"), true); - - tmpBreakpts.add(loc); - - resume(State.RESUMED); - ws.send(msg.respond()); - } - - @Override public void setPauseOnExceptions(V8Message msg) { - ws.send(new V8Error("i dont wanna to")); - } - - @Override public void stepInto(V8Message msg) { - if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); - else { - prevLocation = currFrame.location; - stepOutFrame = currFrame; - resume(State.STEPPING_IN); - ws.send(msg.respond()); - } - } - @Override public void stepOut(V8Message msg) { - if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); - else { - prevLocation = currFrame.location; - stepOutFrame = currFrame; - resume(State.STEPPING_OUT); - ws.send(msg.respond()); - } - } - @Override public void stepOver(V8Message msg) { - if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); - else { - prevLocation = currFrame.location; - stepOutFrame = currFrame; - resume(State.STEPPING_OVER); - ws.send(msg.respond()); - } - } - - @Override public void evaluateOnCallFrame(V8Message msg) { - var cfId = Integer.parseInt(msg.params.string("callFrameId")); - var expr = msg.params.string("expression"); - var group = msg.params.string("objectGroup", null); - - var cf = idToFrame.get(cfId); - var res = run(cf, expr); - - if (group != null) setObjectGroup(group, res.result); - - if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeObj(res.ctx, res.result)))); - - ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result)))); - } - - @Override public void releaseObjectGroup(V8Message msg) { - var group = msg.params.string("objectGroup"); - releaseGroup(group); - ws.send(msg.respond()); - } - @Override - public void releaseObject(V8Message msg) { - var id = Integer.parseInt(msg.params.string("objectId")); - var obj = idToObject.remove(id); - if (obj != null) objectToId.remove(obj); - ws.send(msg.respond()); - } - @Override public void getProperties(V8Message msg) { - var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); - - var res = new JSONList(); - var ctx = objectToCtx.get(obj); - - if (obj != emptyObject) { - for (var key : obj.keys(true)) { - var propDesc = new JSONMap(); - - if (obj.properties.containsKey(key)) { - var prop = obj.properties.get(key); - - propDesc.set("name", Values.toString(ctx, key)); - if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); - if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); - propDesc.set("enumerable", obj.memberEnumerable(key)); - propDesc.set("configurable", obj.memberConfigurable(key)); - propDesc.set("isOwn", true); - res.add(propDesc); - } - else { - propDesc.set("name", Values.toString(ctx, key)); - propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); - propDesc.set("writable", obj.memberWritable(key)); - propDesc.set("enumerable", obj.memberEnumerable(key)); - propDesc.set("configurable", obj.memberConfigurable(key)); - propDesc.set("isOwn", true); - res.add(propDesc); - } - } - - var proto = obj.getPrototype(ctx); - - var protoDesc = new JSONMap(); - protoDesc.set("name", "__proto__"); - protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto)); - protoDesc.set("writable", true); - protoDesc.set("enumerable", false); - protoDesc.set("configurable", false); - protoDesc.set("isOwn", true); - res.add(protoDesc); - } - - ws.send(msg.respond(new JSONMap().set("result", res))); - } - @Override public void callFunctionOn(V8Message msg) { - var src = msg.params.string("functionDeclaration"); - var args = msg.params - .list("arguments", new JSONList()) - .stream() - .map(v -> v.map()) - .map(this::deserializeArgument) - .collect(Collectors.toList()); - - var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); - var ctx = objectToCtx.get(thisArg); - - while (true) { - var start = src.lastIndexOf("//# sourceURL="); - if (start < 0) break; - var end = src.indexOf("\n", start); - if (end < 0) src = src.substring(0, start); - else src = src.substring(0, start) + src.substring(end + 1); - } - - try { - switch (src) { - case CHROME_GET_PROP_FUNC: { - var path = JSON.parse(null, (String)args.get(0)).list(); - Object res = thisArg; - for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el)); - ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); - return; - } - case VSCODE_STRINGIFY_VAL: - case VSCODE_STRINGIFY_PROPS: - case VSCODE_SHALLOW_COPY: - case VSCODE_SYMBOL_REQUEST: - ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject)))); - break; - case VSCODE_FLATTEN_ARRAY: - ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg)))); - break; - case VSCODE_CALL: { - var func = (FunctionValue)(args.size() < 1 ? null : args.get(0)); - ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg))))); - break; - } - default: - ws.send(new V8Error("Please use well-known functions with callFunctionOn")); - break; - // default: { - // var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx); - // if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args); - // ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); - // } - } - } - catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); } - } - - @Override - public void runtimeEnable(V8Message msg) { - ws.send(msg.respond()); - } - - @Override public void onSource(Filename filename, String source, TreeSet locations) { - int id = nextId(); - var src = new Source(id, filename, source, locations); - - idToSource.put(id, src); - filenameToId.put(filename, id); - - for (var bpcd : idToBptCand.values()) { - if (!bpcd.pattern.matcher(filename.toString()).matches()) continue; - var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename)); - var bp = new Breakpoint(nextId(), loc, bpcd.condition); - if (loc == null) continue; - bpcd.resolvedBreakpoints.add(bp); - addBreakpoint(bp); - } - - if (!enabled) pendingSources.add(src); - else sendSource(src); - } - @Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) { - if (!enabled) return false; - - updateFrames(ctx); - var frame = codeFrameToFrame.get(cf); - - if (!frame.debugData) return false; - - if (instruction.location != null) frame.updateLoc(instruction.location); - var loc = frame.location; - var isBreakpointable = loc != null && ( - idToSource.get(filenameToId.get(loc.filename())).breakpoints.contains(loc) || - returnVal != Runners.NO_RETURN - ); - - // TODO: FIXXXX - // if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null; - - if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { - pauseException(ctx); - } - else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { - var bp = locToBreakpoint.get(loc); - var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result); - if (ok) pauseDebug(ctx, locToBreakpoint.get(loc)); - } - else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); - else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null); - - while (enabled) { - switch (state) { - case PAUSED_EXCEPTION: - case PAUSED_NORMAL: break; - - case STEPPING_OUT: - case RESUMED: return false; - case STEPPING_IN: - if (!prevLocation.equals(loc)) { - if (isBreakpointable) pauseDebug(ctx, null); - else if (returnVal != Runners.NO_RETURN) pauseDebug(ctx, null); - else return false; - } - else return false; - break; - case STEPPING_OVER: - if (stepOutFrame.frame == frame.frame) { - if (isBreakpointable && ( - !loc.filename().equals(prevLocation.filename()) || - loc.line() != prevLocation.line() - )) pauseDebug(ctx, null); - else return false; - } - else return false; - break; - } - updateNotifier.await(); - } - - return false; - } - @Override public void onFramePop(Context ctx, CodeFrame frame) { - updateFrames(ctx); - - try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } - catch (NullPointerException e) { } - - if (ctx.frames().size() == 0) resume(State.RESUMED); - else if (stepOutFrame != null && stepOutFrame.frame == frame && - (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) - ) { - // if (state == State.STEPPING_OVER) state = State.STEPPING_IN; - // else { - pauseDebug(ctx, null); - updateNotifier.await(); - // } - } - } - - @Override public void connect() { - if (!target.attachDebugger(this)) { - ws.send(new V8Error("A debugger is already attached to this engine.")); - } - } - @Override public void disconnect() { - target.detachDebugger(); - enabled = false; - updateNotifier.next(); - } - - public SimpleDebugger(WebSocket ws, Engine target) { - this.ws = ws; - this.target = target; - } -} +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.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.frame.Runners; +import me.topchetoeu.jscript.engine.scope.GlobalScope; +import me.topchetoeu.jscript.engine.values.ArrayValue; +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.Symbol; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.events.Notifier; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONElement; +import me.topchetoeu.jscript.json.JSONList; +import me.topchetoeu.jscript.json.JSONMap; +import me.topchetoeu.jscript.mapping.SourceMap; +import me.topchetoeu.jscript.parsing.Parsing; + +// very simple indeed +public class SimpleDebugger implements Debugger { + public static final Set VSCODE_EMPTY = Set.of( + "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}", + "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}", + "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}", + "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}" + ); + public static final Set VSCODE_SELF = Set.of( + "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s{if(p!==\"function\")return n;if(l===\"constructor\")return\"class\";let m=String(f);return m.startsWith(\"class \")||m.includes(\"[native code]\")&&/^[A-Z]/.test(l)?\"class\":r?\"function\":\"method\"\n},o=l=>{switch(typeof l){case\"number\":case\"boolean\":return`${l}`;case\"object\":return l===null?\"null\":l.constructor.name||\"object\";case\"function\":return`fn(${new Array(l.length).fill(\"?\").join(\", \")})`;default:return typeof l}},s=[],a=new Set,u=\"~\",c=t===void 0?this:t;for(;c!=null;c=c.__proto__){u+=\"~\";let l=Object.getOwnPropertyNames(c).filter(p=>p.startsWith(e)&&!p.match(/^\\d+$/));for(let p of l){if(a.has(p))continue;a.add(p);let f=Object.getOwnPropertyDescriptor(c,p),m=n,h;try{let H=c[p];m=i(p,typeof f?.value,H),h=o(H)}catch{}s.push({label:p,sortText:u+p.replace(/^_+/,H=>\"{\".repeat(H.length)),type:m,detail:h})}r=!1}return{result:s,isArray:this instanceof Array}}"; + + private static enum State { + RESUMED, + STEPPING_IN, + STEPPING_OUT, + STEPPING_OVER, + PAUSED_NORMAL, + PAUSED_EXCEPTION, + } + private static enum CatchType { + NONE, + UNCAUGHT, + ALL, + } + private static class Source { + public final int id; + public final Filename filename; + public final String source; + public final TreeSet breakpoints; + + public Source(int id, Filename filename, String source, TreeSet breakpoints) { + this.id = id; + this.filename = filename; + this.source = source; + this.breakpoints = breakpoints; + } + } + private static class Breakpoint { + public final int id; + public final Location location; + public final String condition; + + public Breakpoint(int id, Location location, String condition) { + this.id = id; + this.location = location; + this.condition = condition; + } + } + private static class BreakpointCandidate { + public final int id; + public final String condition; + public final Pattern pattern; + public final int line, start; + public final HashSet resolvedBreakpoints = new HashSet<>(); + + public BreakpointCandidate(int id, Pattern pattern, int line, int start, String condition) { + this.id = id; + this.pattern = pattern; + this.line = line; + this.start = start; + if (condition != null && condition.trim().equals("")) condition = null; + this.condition = condition; + } + } + private class Frame { + public CodeFrame frame; + public CodeFunction func; + public int id; + public ObjectValue local, capture, global, valstack; + public JSONMap serialized; + public Location location; + public boolean debugData = false; + + public void updateLoc(Location loc) { + if (loc == null) return; + this.location = loc; + } + + public Frame(Context ctx, CodeFrame frame, int id) { + this.frame = frame; + this.func = frame.function; + this.id = id; + + this.global = frame.function.environment.global.obj; + this.local = frame.getLocalScope(ctx, true); + this.capture = frame.getCaptureScope(ctx, true); + this.local.setPrototype(ctx, capture); + this.capture.setPrototype(ctx, global); + this.valstack = frame.getValStackScope(ctx); + debugData = true; + + this.serialized = new JSONMap() + .set("callFrameId", id + "") + .set("functionName", func.name) + .set("scopeChain", new JSONList() + .add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local))) + .add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture))) + .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global))) + .add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack))) + ); + } + } + private class ObjRef { + public final ObjectValue obj; + public final Context ctx; + public final HashSet heldGroups = new HashSet<>(); + public boolean held = true; + + public boolean shouldRelease() { + return !held && heldGroups.size() == 0; + } + + public ObjRef(Context ctx, ObjectValue obj) { + this.ctx = ctx; + this.obj = obj; + } + } + + private static class RunResult { + public final Context ctx; + public final Object result; + public final EngineException error; + + public RunResult(Context ctx, Object result, EngineException error) { + this.ctx = ctx; + this.result = result; + this.error = error; + } + } + + public boolean enabled = true; + public CatchType execptionType = CatchType.NONE; + public State state = State.RESUMED; + + public final WebSocket ws; + public final Engine target; + + private ObjectValue emptyObject = new ObjectValue(); + + private HashMap idToBptCand = new HashMap<>(); + + private HashMap idToBreakpoint = new HashMap<>(); + private HashMap locToBreakpoint = new HashMap<>(); + private HashSet tmpBreakpts = new HashSet<>(); + + private HashMap filenameToId = new HashMap<>(); + private HashMap idToSource = new HashMap<>(); + private ArrayList pendingSources = new ArrayList<>(); + + private HashMap idToFrame = new HashMap<>(); + private HashMap codeFrameToFrame = new HashMap<>(); + + private HashMap idToObject = new HashMap<>(); + private HashMap objectToId = new HashMap<>(); + private HashMap> objectGroups = new HashMap<>(); + + private Notifier updateNotifier = new Notifier(); + private boolean pendingPause = false; + + private int nextId = 0; + private Frame stepOutFrame = null, currFrame = null; + private int stepOutPtr = 0; + + private boolean compare(String src, String target) { + if (src.length() != target.length()) return false; + var diff = 0; + var all = 0; + + for (var i = 0; i < src.length(); i++) { + var a = src.charAt(i); + var b = target.charAt(i); + var letter = Parsing.isLetter(a) && Parsing.isLetter(b); + + if (a != b) { + if (letter) diff++; + else return false; + } + + if (letter) all++; + } + + return diff / (float)all < .5f; + } + private boolean compare(String src, Set target) { + for (var el : target) { + if (compare(src, el)) return true; + } + return false; + } + + private int nextId() { + return nextId++; + } + + private synchronized void updateFrames(Context ctx) { + var frame = ctx.peekFrame(); + if (frame == null) return; + + if (!codeFrameToFrame.containsKey(frame)) { + var id = nextId(); + var fr = new Frame(ctx, frame, id); + + idToFrame.put(id, fr); + codeFrameToFrame.put(frame, fr); + } + + currFrame = codeFrameToFrame.get(frame); + } + private JSONList serializeFrames(Context ctx) { + var res = new JSONList(); + var frames = ctx.frames(); + + for (var i = frames.size() - 1; i >= 0; i--) { + var frame = codeFrameToFrame.get(frames.get(i)); + if (frame.location == null) continue; + frame.serialized.set("location", serializeLocation(frame.location)); + if (frame.location != null) res.add(frame.serialized); + } + + return res; + } + + private Location correctLocation(Source source, Location loc) { + var set = source.breakpoints; + + if (set.contains(loc)) return loc; + + var tail = set.tailSet(loc); + if (tail.isEmpty()) return null; + + return tail.first(); + } + private Location deserializeLocation(JSONElement el, boolean correct) { + if (!el.isMap()) throw new RuntimeException("Expected location to be a map."); + var id = Integer.parseInt(el.map().string("scriptId")); + var line = (int)el.map().number("lineNumber") + 1; + var column = (int)el.map().number("columnNumber") + 1; + + if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id)); + + var res = new Location(line, column, idToSource.get(id).filename); + if (correct) res = correctLocation(idToSource.get(id), res); + return res; + } + private JSONMap serializeLocation(Location loc) { + var source = filenameToId.get(loc.filename()); + return new JSONMap() + .set("scriptId", source + "") + .set("lineNumber", loc.line() - 1) + .set("columnNumber", loc.start() - 1); + } + + private Integer objectId(Context ctx, ObjectValue obj) { + if (objectToId.containsKey(obj)) return objectToId.get(obj); + else { + int id = nextId(); + var ref = new ObjRef(ctx, obj); + objectToId.put(obj, id); + idToObject.put(id, ref); + return id; + } + } + private JSONMap serializeObj(Context ctx, Object val, boolean byValue) { + val = Values.normalize(null, val); + + if (val == Values.NULL) { + return new JSONMap() + .set("type", "object") + .set("subtype", "null") + .setNull("value") + .set("description", "null"); + } + + if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + var id = objectId(ctx, obj); + var type = "object"; + String subtype = null; + String className = null; + + if (obj instanceof FunctionValue) type = "function"; + if (obj instanceof ArrayValue) subtype = "array"; + + try { className = Values.toString(ctx, Values.getMemberPath(ctx, obj, "constructor", "name")); } + catch (Exception e) { } + + var res = new JSONMap() + .set("type", type) + .set("objectId", id + ""); + + if (subtype != null) res.set("subtype", subtype); + if (className != null) { + res.set("className", className); + } + + if (obj instanceof ArrayValue) res.set("description", "Array(" + ((ArrayValue)obj).size() + ")"); + else if (obj instanceof FunctionValue) res.set("description", obj.toString()); + else { + var defaultToString = false; + + try { + defaultToString = + Values.getMember(ctx, obj, "toString") == + Values.getMember(ctx, ctx.environment().proto("object"), "toString"); + } + catch (Exception e) { } + + try { res.set("description", className + (defaultToString ? "" : " { " + Values.toString(ctx, obj) + " }")); } + catch (EngineException e) { res.set("description", className); } + } + + + if (byValue) try { res.put("value", JSON.fromJs(ctx, obj)); } + catch (Exception e) { } + + return res; + } + + if (val == null) return new JSONMap().set("type", "undefined"); + if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val); + if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val); + if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString()); + if (val instanceof Number) { + var num = (double)(Number)val; + var res = new JSONMap().set("type", "number"); + + if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity"); + else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity"); + else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0"); + else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0"); + else if (Double.isNaN(num)) res.set("unserializableValue", "NaN"); + else res.set("value", num); + + return res; + } + + throw new IllegalArgumentException("Unexpected JS object."); + } + private JSONMap serializeObj(Context ctx, Object val) { + return serializeObj(ctx, val, false); + } + private void addObjectGroup(String name, Object val) { + if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + var id = objectToId.getOrDefault(obj, -1); + if (id < 0) return; + + var ref = idToObject.get(id); + + if (objectGroups.containsKey(name)) objectGroups.get(name).add(ref); + else objectGroups.put(name, new ArrayList<>(List.of(ref))); + + ref.heldGroups.add(name); + } + } + private void releaseGroup(String name) { + var objs = objectGroups.remove(name); + + if (objs != null) for (var obj : objs) { + if (obj.heldGroups.remove(name) && obj.shouldRelease()) { + var id = objectToId.remove(obj.obj); + if (id != null) idToObject.remove(id); + } + } + } + private Object deserializeArgument(JSONMap val) { + if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))).obj; + else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) { + case "NaN": return Double.NaN; + case "-Infinity": return Double.NEGATIVE_INFINITY; + case "Infinity": return Double.POSITIVE_INFINITY; + case "-0": return -0.; + } + var res = val.get("value"); + if (res == null) return null; + else return JSON.toJs(res); + } + + private JSONMap serializeException(Context ctx, EngineException err) { + String text = null; + + try { + text = Values.toString(ctx, err.value); + } + catch (EngineException e) { + text = "[error while stringifying]"; + } + + var res = new JSONMap() + .set("exceptionId", nextId()) + .set("exception", serializeObj(ctx, err.value)) + .set("text", text); + + return res; + } + + private void resume(State state) { + this.state = state; + ws.send(new V8Event("Debugger.resumed", new JSONMap())); + updateNotifier.next(); + } + private void pauseDebug(Context ctx, Breakpoint bp) { + state = State.PAUSED_NORMAL; + var map = new JSONMap() + .set("callFrames", serializeFrames(ctx)) + .set("reason", "debugCommand"); + + if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + "")); + ws.send(new V8Event("Debugger.paused", map)); + } + private void pauseException(Context ctx) { + state = State.PAUSED_EXCEPTION; + var map = new JSONMap() + .set("callFrames", serializeFrames(ctx)) + .set("reason", "exception"); + + ws.send(new V8Event("Debugger.paused", map)); + } + + private void sendSource(Source src) { + ws.send(new V8Event("Debugger.scriptParsed", new JSONMap() + .set("scriptId", src.id + "") + .set("hash", src.source.hashCode()) + .set("url", src.filename + "") + )); + } + + private void addBreakpoint(Breakpoint bpt) { + idToBreakpoint.put(bpt.id, bpt); + locToBreakpoint.put(bpt.location, bpt); + + ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap() + .set("breakpointId", bpt.id) + .set("location", serializeLocation(bpt.location)) + )); + } + + private RunResult run(Frame codeFrame, String code) { + if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!")); + var engine = new Engine(false); + var env = codeFrame.func.environment.fork(); + + env.global = new GlobalScope(codeFrame.local); + + var ctx = new Context(engine).pushEnv(env); + var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); + + engine.run(true); + + try { return new RunResult(ctx, awaiter.await(), null); } + catch (EngineException e) { return new RunResult(ctx, null, e); } + } + + private ObjectValue vscodeAutoSuggest(Context ctx, Object target, String query, boolean variable) { + var res = new ArrayValue(); + var passed = new HashSet(); + var tildas = "~"; + if (target == null) target = ctx.environment().getGlobal(); + + for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) { + for (var el : Values.getMembers(ctx, proto, true, true)) { + var strKey = Values.toString(ctx, el); + if (passed.contains(strKey)) continue; + passed.add(strKey); + + var val = Values.getMember(ctx, Values.getMemberDescriptor(ctx, proto, el), "value"); + var desc = new ObjectValue(); + var sortText = ""; + if (strKey.startsWith(query)) sortText += "0@"; + else if (strKey.toLowerCase().startsWith(query.toLowerCase())) sortText += "1@"; + else if (strKey.contains(query)) sortText += "2@"; + else if (strKey.toLowerCase().contains(query.toLowerCase())) sortText += "3@"; + else sortText += "4@"; + sortText += tildas + strKey; + + desc.defineProperty(ctx, "label", strKey); + desc.defineProperty(ctx, "sortText", sortText); + + if (val instanceof FunctionValue) { + if (strKey.equals("constructor")) desc.defineProperty(ctx, "type", "name"); + else desc.defineProperty(ctx, "type", variable ? "function" : "method"); + } + else desc.defineProperty(ctx, "type", variable ? "variable" : "property"); + + switch (Values.type(val)) { + case "number": + case "boolean": + desc.defineProperty(ctx, "detail", Values.toString(ctx, val)); + break; + case "object": + if (val == Values.NULL) desc.defineProperty(ctx, "detail", "null"); + else try { + desc.defineProperty(ctx, "detail", Values.getMemberPath(ctx, target, "constructor", "name")); + } + catch (IllegalArgumentException e) { + desc.defineProperty(ctx, "detail", "object"); + } + break; + case "function": { + var type = "fn("; + for (var i = 0; i < ((FunctionValue)val).length; i++) { + if (i != 0) type += ","; + type += "?"; + } + type += ")"; + desc.defineProperty(ctx, "detail", type); + break; + } + default: + desc.defineProperty(ctx, "type", Values.type(val)); + break; + } + + res.set(ctx, res.size(), desc); + } + + tildas += "~"; + variable = true; + } + + var resObj = new ObjectValue(); + resObj.defineProperty(ctx, "result", res); + resObj.defineProperty(ctx, "isArray", target instanceof ArrayValue); + return resObj; + } + + @Override public synchronized void enable(V8Message msg) { + enabled = true; + ws.send(msg.respond()); + + for (var el : pendingSources) sendSource(el); + pendingSources.clear(); + + updateNotifier.next(); + } + @Override public synchronized void disable(V8Message msg) { + enabled = false; + ws.send(msg.respond()); + updateNotifier.next(); + } + + @Override public synchronized void getScriptSource(V8Message msg) { + int id = Integer.parseInt(msg.params.string("scriptId")); + ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); + } + @Override public synchronized void getPossibleBreakpoints(V8Message msg) { + var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId"))); + var start = deserializeLocation(msg.params.get("start"), false); + var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null; + + var res = new JSONList(); + + for (var loc : src.breakpoints.tailSet(start, true)) { + if (end != null && loc.compareTo(end) > 0) break; + res.add(serializeLocation(loc)); + } + + ws.send(msg.respond(new JSONMap().set("locations", res))); + } + + @Override public synchronized void pause(V8Message msg) { + pendingPause = true; + ws.send(msg.respond()); + } + @Override public synchronized void resume(V8Message msg) { + resume(State.RESUMED); + ws.send(msg.respond(new JSONMap())); + } + + @Override public synchronized void setBreakpointByUrl(V8Message msg) { + var line = (int)msg.params.number("lineNumber") + 1; + var col = (int)msg.params.number("columnNumber", 0) + 1; + var cond = msg.params.string("condition", "").trim(); + + if (cond.equals("")) cond = null; + if (cond != null) cond = "(" + cond + ")"; + + Pattern regex; + + if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url"))); + else regex = Pattern.compile(msg.params.string("urlRegex")); + + var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond); + idToBptCand.put(bpcd.id, bpcd); + + var locs = new JSONList(); + + for (var src : idToSource.values()) { + if (regex.matcher(src.filename.toString()).matches()) { + var loc = correctLocation(src, new Location(line, col, src.filename)); + if (loc == null) continue; + var bp = new Breakpoint(nextId(), loc, bpcd.condition); + + bpcd.resolvedBreakpoints.add(bp); + locs.add(serializeLocation(loc)); + addBreakpoint(bp); + } + } + + ws.send(msg.respond(new JSONMap() + .set("breakpointId", bpcd.id + "") + .set("locations", locs) + )); + } + @Override public synchronized void removeBreakpoint(V8Message msg) { + var id = Integer.parseInt(msg.params.string("breakpointId")); + + if (idToBptCand.containsKey(id)) { + var bpcd = idToBptCand.get(id); + for (var bp : bpcd.resolvedBreakpoints) { + idToBreakpoint.remove(bp.id); + locToBreakpoint.remove(bp.location); + } + idToBptCand.remove(id); + } + else if (idToBreakpoint.containsKey(id)) { + var bp = idToBreakpoint.remove(id); + locToBreakpoint.remove(bp.location); + } + ws.send(msg.respond()); + } + @Override public synchronized void continueToLocation(V8Message msg) { + var loc = deserializeLocation(msg.params.get("location"), true); + + tmpBreakpts.add(loc); + + resume(State.RESUMED); + ws.send(msg.respond()); + } + + @Override public synchronized void setPauseOnExceptions(V8Message msg) { + switch (msg.params.string("state")) { + case "none": execptionType = CatchType.NONE; break; + case "all": execptionType = CatchType.ALL; break; + case "uncaught": execptionType = CatchType.UNCAUGHT; break; + default: + ws.send(new V8Error("Invalid exception pause type.")); + return; + } + + ws.send(msg.respond()); + } + + @Override public synchronized void stepInto(V8Message msg) { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + stepOutFrame = currFrame; + stepOutPtr = currFrame.frame.codePtr; + resume(State.STEPPING_IN); + ws.send(msg.respond()); + } + } + @Override public synchronized void stepOut(V8Message msg) { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + stepOutFrame = currFrame; + stepOutPtr = currFrame.frame.codePtr; + resume(State.STEPPING_OUT); + ws.send(msg.respond()); + } + } + @Override public synchronized void stepOver(V8Message msg) { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + stepOutFrame = currFrame; + stepOutPtr = currFrame.frame.codePtr; + resume(State.STEPPING_OVER); + ws.send(msg.respond()); + } + } + + @Override public synchronized void evaluateOnCallFrame(V8Message msg) { + var cfId = Integer.parseInt(msg.params.string("callFrameId")); + var expr = msg.params.string("expression"); + var group = msg.params.string("objectGroup", null); + + var cf = idToFrame.get(cfId); + var res = run(cf, expr); + + if (group != null) addObjectGroup(group, res.result); + + if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(res.ctx, res.error)))); + else ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result)))); + } + + @Override public synchronized void releaseObjectGroup(V8Message msg) { + var group = msg.params.string("objectGroup"); + releaseGroup(group); + ws.send(msg.respond()); + } + @Override public synchronized void releaseObject(V8Message msg) { + var id = Integer.parseInt(msg.params.string("objectId")); + var ref = idToObject.get(id); + ref.held = false; + + if (ref.shouldRelease()) { + objectToId.remove(ref.obj); + idToObject.remove(id); + } + + ws.send(msg.respond()); + } + @Override public synchronized void getProperties(V8Message msg) { + var ref = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var obj = ref.obj; + + var res = new JSONList(); + var ctx = ref.ctx; + + if (obj != emptyObject && obj != null) { + for (var key : obj.keys(true)) { + var propDesc = new JSONMap(); + + if (obj.properties.containsKey(key)) { + var prop = obj.properties.get(key); + + propDesc.set("name", Values.toString(ctx, key)); + if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); + if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", true); + res.add(propDesc); + } + else { + propDesc.set("name", Values.toString(ctx, key)); + propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); + propDesc.set("writable", obj.memberWritable(key)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", true); + res.add(propDesc); + } + } + + var proto = obj.getPrototype(ctx); + + var protoDesc = new JSONMap(); + protoDesc.set("name", "__proto__"); + protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto)); + protoDesc.set("writable", true); + protoDesc.set("enumerable", false); + protoDesc.set("configurable", false); + protoDesc.set("isOwn", true); + res.add(protoDesc); + } + + ws.send(msg.respond(new JSONMap().set("result", res))); + } + @Override public synchronized void callFunctionOn(V8Message msg) { + var src = msg.params.string("functionDeclaration"); + var args = msg.params + .list("arguments", new JSONList()) + .stream() + .map(v -> v.map()) + .map(this::deserializeArgument) + .collect(Collectors.toList()); + var byValue = msg.params.bool("returnByValue", false); + + var thisArgRef = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var thisArg = thisArgRef.obj; + var ctx = thisArgRef.ctx; + + while (true) { + var start = src.lastIndexOf("//# sourceURL="); + if (start < 0) break; + var end = src.indexOf("\n", start); + if (end < 0) src = src.substring(0, start); + else src = src.substring(0, start) + src.substring(end + 1); + } + + try { + Object res = null; + if (compare(src, VSCODE_EMPTY)) res = emptyObject; + else if (compare(src, VSCODE_SELF)) res = thisArg; + else if (compare(src, CHROME_GET_PROP_FUNC)) { + res = thisArg; + for (var el : JSON.parse(null, (String)args.get(0)).list()) res = Values.getMember(ctx, res, JSON.toJs(el)); + } + else if (compare(src, VSCODE_CALL)) { + var func = (FunctionValue)(args.size() < 1 ? null : args.get(0)); + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg))))); + } + else if (compare(src, VSCODE_AUTOCOMPLETE)) { + var target = args.get(0); + if (target == null) target = thisArg; + res = vscodeAutoSuggest(ctx, target, Values.toString(ctx, args.get(1)), Values.toBoolean(args.get(2))); + } + else { + ws.send(new V8Error("Please use well-known functions with callFunctionOn")); + return; + } + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res, byValue)))); + } + catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); } + } + + @Override public synchronized void runtimeEnable(V8Message msg) { + ws.send(msg.respond()); + } + + @Override public void onSource(Filename filename, String source, TreeSet locations, SourceMap map) { + int id = nextId(); + var src = new Source(id, filename, source, locations); + + idToSource.put(id, src); + filenameToId.put(filename, id); + + for (var bpcd : idToBptCand.values()) { + if (!bpcd.pattern.matcher(filename.toString()).matches()) continue; + var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename)); + var bp = new Breakpoint(nextId(), loc, bpcd.condition); + if (loc == null) continue; + bpcd.resolvedBreakpoints.add(bp); + addBreakpoint(bp); + } + + if (!enabled) pendingSources.add(src); + else sendSource(src); + } + @Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + if (!enabled) return false; + + boolean isBreakpointable; + Location loc; + Frame frame; + + synchronized (this) { + frame = codeFrameToFrame.get(cf); + + if (!frame.debugData) return false; + + if (instruction.location != null) frame.updateLoc(ctx.engine.mapToCompiled(instruction.location)); + loc = frame.location; + isBreakpointable = loc != null && (instruction.breakpoint.shouldStepIn()); + + if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { + pauseException(ctx); + } + else if (loc != null && (state == State.STEPPING_IN || state == State.STEPPING_OVER) && returnVal != Runners.NO_RETURN && stepOutFrame == frame) { + pauseDebug(ctx, null); + } + else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { + var bp = locToBreakpoint.get(loc); + var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result); + if (ok) pauseDebug(ctx, locToBreakpoint.get(loc)); + } + else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); + else if (isBreakpointable && pendingPause) { + pauseDebug(ctx, null); + pendingPause = false; + } + else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null); + } + + + while (enabled) { + synchronized (this) { + switch (state) { + case PAUSED_EXCEPTION: + case PAUSED_NORMAL: break; + + case STEPPING_OUT: + case RESUMED: return false; + + case STEPPING_IN: + case STEPPING_OVER: + if (stepOutFrame.frame == frame.frame) { + if (returnVal != Runners.NO_RETURN || error != null) { + state = State.STEPPING_OUT; + continue; + } + else if (stepOutPtr != frame.frame.codePtr) { + if (state == State.STEPPING_IN && instruction.breakpoint.shouldStepIn()) { + pauseDebug(ctx, null); + break; + } + else if (state == State.STEPPING_OVER && instruction.breakpoint.shouldStepOver()) { + pauseDebug(ctx, null); + break; + } + } + } + return false; + } + } + updateNotifier.await(); + } + + return false; + } + @Override public void onFramePush(Context ctx, CodeFrame frame) { + var prevFrame = currFrame; + updateFrames(ctx); + + if (stepOutFrame != null && stepOutFrame.frame == prevFrame.frame && state == State.STEPPING_IN) { + stepOutFrame = currFrame; + } + } + @Override public void onFramePop(Context ctx, CodeFrame frame) { + updateFrames(ctx); + + try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } + catch (NullPointerException e) { } + + if (ctx.frames().size() == 0) { + if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED); + } + else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) { + state = State.STEPPING_IN; + stepOutFrame = currFrame; + } + } + + @Override public synchronized void connect() { + if (!target.attachDebugger(this)) { + ws.send(new V8Error("A debugger is already attached to this engine.")); + } + } + @Override public synchronized void disconnect() { + target.detachDebugger(); + enabled = false; + updateNotifier.next(); + } + + public SimpleDebugger(WebSocket ws, Engine target) { + this.ws = ws; + this.target = target; + } +} diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 7ea3a4d..5b3e701 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -17,32 +17,74 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; public class CodeFrame { + public static enum TryState { + TRY, + CATCH, + FINALLY, + } + public static class TryCtx { - public static final int STATE_TRY = 0; - public static final int STATE_CATCH = 1; - public static final int STATE_FINALLY_THREW = 2; - public static final int STATE_FINALLY_RETURNED = 3; - public static final int STATE_FINALLY_JUMPED = 4; + public final int start, end, catchStart, finallyStart; + public final int restoreStackPtr; + public final TryState state; + public final EngineException error; + public final PendingResult result; - public final boolean hasCatch, hasFinally; - public final int tryStart, catchStart, finallyStart, end; - public int state; - public Object retVal; - public EngineException err; - public int jumpPtr; + public boolean hasCatch() { return catchStart >= 0; } + public boolean hasFinally() { return finallyStart >= 0; } - public TryCtx(int tryStart, int tryN, int catchN, int finallyN) { - hasCatch = catchN >= 0; - hasFinally = finallyN >= 0; + public boolean inBounds(int ptr) { + return ptr >= start && ptr < end; + } - if (catchN < 0) catchN = 0; - if (finallyN < 0) finallyN = 0; + public TryCtx _catch(EngineException e) { + if (error != null) e.setCause(error); + return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart); + } + public TryCtx _finally(PendingResult res) { + return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1); + } - this.tryStart = tryStart; - this.catchStart = tryStart + tryN; - this.finallyStart = catchStart + catchN; - this.end = finallyStart + finallyN; - this.jumpPtr = end; + public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) { + this.catchStart = catchStart; + this.finallyStart = finallyStart; + this.restoreStackPtr = stackPtr; + this.result = res == null ? PendingResult.ofNone() : res; + this.state = state; + this.start = start; + this.end = end; + this.error = err; + } + } + + private static class PendingResult { + public final boolean isReturn, isJump, isThrow; + public final Object value; + public final EngineException error; + public final int ptr; + public final Instruction instruction; + + private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) { + this.instruction = instr; + this.isReturn = isReturn; + this.isJump = isJump; + this.isThrow = isThrow; + this.value = value; + this.error = error; + this.ptr = ptr; + } + + public static PendingResult ofNone() { + return new PendingResult(null, false, false, false, null, null, 0); + } + public static PendingResult ofReturn(Object value, Instruction instr) { + return new PendingResult(instr, true, false, false, value, null, 0); + } + public static PendingResult ofThrow(EngineException error, Instruction instr) { + return new PendingResult(instr, false, false, true, null, error, 0); + } + public static PendingResult ofJump(int codePtr, Instruction instr) { + return new PendingResult(instr, false, true, false, null, null, codePtr); } } @@ -51,11 +93,10 @@ public class CodeFrame { public final Object[] args; public final Stack tryStack = new Stack<>(); public final CodeFunction function; - public Object[] stack = new Object[32]; public int stackPtr = 0; public int codePtr = 0; - public boolean jumpFlag = false; + public boolean jumpFlag = false, popTryFlag = false; private Location prevLoc = null; public ObjectValue getLocalScope(Context ctx, boolean props) { @@ -105,9 +146,9 @@ public class CodeFrame { }; } - public void addTry(int n, int catchN, int finallyN) { - var res = new TryCtx(codePtr + 1, n, catchN, finallyN); - if (!tryStack.empty()) res.err = tryStack.peek().err; + public void addTry(int start, int end, int catchStart, int finallyStart) { + var err = tryStack.empty() ? null : tryStack.peek().error; + var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart); tryStack.add(res); } @@ -145,10 +186,6 @@ public class CodeFrame { stack[stackPtr++] = Values.normalize(ctx, val); } - private void setCause(Context ctx, EngineException err, EngineException cause) { - err.setCause(cause); - } - public Object next(Context ctx, Object value, Object returnValue, EngineException error) { if (value != Runners.NO_RETURN) push(ctx, value); @@ -161,16 +198,17 @@ public class CodeFrame { if (instr == null) returnValue = null; else { + // System.out.println(instr + "@" + instr.location); ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); if (instr.location != null) prevLoc = instr.location; try { - this.jumpFlag = false; + this.jumpFlag = this.popTryFlag = false; returnValue = Runners.exec(ctx, instr, this); } catch (EngineException e) { - error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine); + error = e.add(ctx, function.name, prevLoc); } } } @@ -179,109 +217,80 @@ public class CodeFrame { while (!tryStack.empty()) { var tryCtx = tryStack.peek(); - var newState = -1; + TryCtx newCtx = null; - switch (tryCtx.state) { - case TryCtx.STATE_TRY: - if (error != null) { - if (tryCtx.hasCatch) { - tryCtx.err = error; - newState = TryCtx.STATE_CATCH; - } - else if (tryCtx.hasFinally) { - tryCtx.err = error; - newState = TryCtx.STATE_FINALLY_THREW; - } - break; - } - else if (returnValue != Runners.NO_RETURN) { - if (tryCtx.hasFinally) { - tryCtx.retVal = returnValue; - newState = TryCtx.STATE_FINALLY_RETURNED; - } - break; - } - else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) return Runners.NO_RETURN; - - if (tryCtx.hasFinally) { - if (jumpFlag) tryCtx.jumpPtr = codePtr; - else tryCtx.jumpPtr = tryCtx.end; - newState = TryCtx.STATE_FINALLY_JUMPED; - } - else codePtr = tryCtx.end; - break; - case TryCtx.STATE_CATCH: - if (error != null) { - setCause(ctx, error, tryCtx.err); - if (tryCtx.hasFinally) { - tryCtx.err = error; - newState = TryCtx.STATE_FINALLY_THREW; - } - break; - } - else if (returnValue != Runners.NO_RETURN) { - if (tryCtx.hasFinally) { - tryCtx.retVal = returnValue; - newState = TryCtx.STATE_FINALLY_RETURNED; - } - break; - } - else if (codePtr >= tryCtx.catchStart && codePtr < tryCtx.finallyStart) return Runners.NO_RETURN; - - if (tryCtx.hasFinally) { - if (jumpFlag) tryCtx.jumpPtr = codePtr; - else tryCtx.jumpPtr = tryCtx.end; - newState = TryCtx.STATE_FINALLY_JUMPED; - } - else codePtr = tryCtx.end; - break; - case TryCtx.STATE_FINALLY_THREW: - if (error != null) setCause(ctx, error, tryCtx.err); - else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err; - else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN; - break; - case TryCtx.STATE_FINALLY_RETURNED: - if (error != null) setCause(ctx, error, tryCtx.err); - if (returnValue == Runners.NO_RETURN) { - if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal; - else return Runners.NO_RETURN; - } - break; - case TryCtx.STATE_FINALLY_JUMPED: - if (error != null) setCause(ctx, error, tryCtx.err); - else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) { - if (!jumpFlag) codePtr = tryCtx.jumpPtr; - else codePtr = tryCtx.end; - } - else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN; - break; + if (error != null) { + if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error); + else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr)); } - - if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); - - if (newState == -1) { - var err = tryCtx.err; - tryStack.pop(); - if (!tryStack.isEmpty()) tryStack.peek().err = err; - continue; + else if (returnValue != Runners.NO_RETURN) { + if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr)); } + else if (jumpFlag && !tryCtx.inBounds(codePtr)) { + if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr)); + } + else if (!this.popTryFlag) newCtx = tryCtx; - tryCtx.state = newState; - switch (newState) { - case TryCtx.STATE_CATCH: - scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); - codePtr = tryCtx.catchStart; - ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true); - break; - default: + if (newCtx != null) { + if (newCtx != tryCtx) { + switch (newCtx.state) { + case CATCH: + if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value)); + codePtr = tryCtx.catchStart; + stackPtr = tryCtx.restoreStackPtr; + break; + case FINALLY: + if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + codePtr = tryCtx.finallyStart; + stackPtr = tryCtx.restoreStackPtr; + default: + } + + tryStack.pop(); + tryStack.push(newCtx); + } + error = null; + returnValue = Runners.NO_RETURN; + break; + } + else { + if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + + if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) { codePtr = tryCtx.finallyStart; + stackPtr = tryCtx.restoreStackPtr; + tryStack.pop(); + tryStack.push(tryCtx._finally(null)); + break; + } + else { + tryStack.pop(); + codePtr = tryCtx.end; + if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction; + if (tryCtx.result.isJump) { + codePtr = tryCtx.result.ptr; + jumpFlag = true; + } + if (tryCtx.result.isReturn) returnValue = tryCtx.result.value; + if (tryCtx.result.isThrow) { + error = tryCtx.result.error; + } + if (error != null) error.setCause(tryCtx.error); + continue; + } } - - return Runners.NO_RETURN; } if (error != null) { - ctx.engine.onInstruction(ctx, this, instr, null, error, false); + var caught = false; + + for (var frame : ctx.frames()) { + for (var tryCtx : frame.tryStack) { + if (tryCtx.state == TryState.TRY) caught = true; + } + } + + ctx.engine.onInstruction(ctx, this, instr, null, error, caught); throw error; } if (returnValue != Runners.NO_RETURN) { diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index fc76a4e..f2564fb 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -97,50 +97,37 @@ public class Runners { obj.defineProperty(ctx, "value", el); frame.push(ctx, obj); } - // var arr = new ObjectValue(); - // var members = Values.getMembers(ctx, val, false, false); - // Collections.reverse(members); - // for (var el : members) { - // if (el instanceof Symbol) continue; - // arr.defineProperty(ctx, i++, el); - // } - - // arr.defineProperty(ctx, "length", i); - - // frame.push(ctx, arr); frame.codePtr++; return NO_RETURN; } - public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) { - frame.addTry(instr.get(0), instr.get(1), instr.get(2)); + public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) { + int start = frame.codePtr + 1; + int catchStart = (int)instr.get(0); + int finallyStart = (int)instr.get(1); + if (finallyStart >= 0) finallyStart += start; + if (catchStart >= 0) catchStart += start; + int end = (int)instr.get(2) + start; + frame.addTry(start, end, catchStart, finallyStart); frame.codePtr++; return NO_RETURN; } + public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) { + frame.popTryFlag = true; + return NO_RETURN; + } public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) { - int offset = instr.get(0), count = instr.get(1); + int count = instr.get(0); for (var i = 0; i < count; i++) { - frame.push(ctx, frame.peek(offset + count - 1)); + frame.push(ctx, frame.peek(count - 1)); } frame.codePtr++; return NO_RETURN; } - public static Object execMove(Context ctx, Instruction instr, CodeFrame frame) { - int offset = instr.get(0), count = instr.get(1); - - var tmp = frame.take(offset); - var res = frame.take(count); - - for (var i = 0; i < offset; i++) frame.push(ctx, tmp[i]); - for (var i = 0; i < count; i++) frame.push(ctx, res[i]); - - frame.codePtr++; - return NO_RETURN; - } public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, null); frame.codePtr++; @@ -179,16 +166,13 @@ public class Runners { } public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) { long id = (Long)instr.get(0); - int localsN = (Integer)instr.get(1); - int len = (Integer)instr.get(2); - var captures = new ValueVariable[instr.params.length - 3]; + var captures = new ValueVariable[instr.params.length - 1]; - for (var i = 3; i < instr.params.length; i++) { - captures[i - 3] = frame.scope.get(instr.get(i)); + for (var i = 1; i < instr.params.length; i++) { + captures[i - 1] = frame.scope.get(instr.get(i)); } - var body = Engine.functions.get(id); - var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body); + var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures); frame.push(ctx, func); @@ -306,7 +290,6 @@ public class Runners { var val = frame.pop(); if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); - frame.push(ctx, true); frame.codePtr++; return NO_RETURN; } @@ -330,10 +313,10 @@ public class Runners { case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame); case CALL: return execCall(ctx, instr, frame); case CALL_NEW: return execCallNew(ctx, instr, frame); - case TRY: return execTry(ctx, instr, frame); + case TRY_START: return execTryStart(ctx, instr, frame); + case TRY_END: return execTryEnd(ctx, instr, frame); case DUP: return execDup(ctx, instr, frame); - case MOVE: return execMove(ctx, instr, frame); case LOAD_VALUE: return execLoadValue(ctx, instr, frame); case LOAD_VAR: return execLoadVar(ctx, instr, frame); case LOAD_OBJ: return execLoadObj(ctx, instr, frame); diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index 62027d8..f1ab188 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -12,9 +12,6 @@ import me.topchetoeu.jscript.exceptions.EngineException; public class GlobalScope implements ScopeRecord { public final ObjectValue obj; - @Override - public GlobalScope parent() { return null; } - public boolean has(Context ctx, String name) { return obj.hasMember(ctx, name, false); } @@ -28,7 +25,7 @@ public class GlobalScope implements ScopeRecord { return new GlobalScope(obj); } public LocalScopeRecord child() { - return new LocalScopeRecord(this); + return new LocalScopeRecord(); } public Object define(String name) { diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java index d196716..d1c65e4 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java @@ -2,11 +2,8 @@ package me.topchetoeu.jscript.engine.scope; import java.util.ArrayList; -import me.topchetoeu.jscript.engine.Context; - public class LocalScopeRecord implements ScopeRecord { public final LocalScopeRecord parent; - public final GlobalScope global; private final ArrayList captures = new ArrayList<>(); private final ArrayList locals = new ArrayList<>(); @@ -18,11 +15,8 @@ public class LocalScopeRecord implements ScopeRecord { return locals.toArray(String[]::new); } - @Override - public LocalScopeRecord parent() { return parent; } - public LocalScopeRecord child() { - return new LocalScopeRecord(this, global); + return new LocalScopeRecord(this); } public int localsCount() { @@ -62,12 +56,6 @@ public class LocalScopeRecord implements ScopeRecord { return name; } - public boolean has(Context ctx, String name) { - return - global.has(ctx, name) || - locals.contains(name) || - parent != null && parent.has(ctx, name); - } public Object define(String name, boolean force) { if (!force && locals.contains(name)) return locals.indexOf(name); locals.add(name); @@ -80,12 +68,10 @@ public class LocalScopeRecord implements ScopeRecord { locals.remove(locals.size() - 1); } - public LocalScopeRecord(GlobalScope global) { + public LocalScopeRecord() { this.parent = null; - this.global = global; } - public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) { + public LocalScopeRecord(LocalScopeRecord parent) { this.parent = parent; - this.global = global; } } diff --git a/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java index fa76859..fe4d5d3 100644 --- a/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java @@ -3,6 +3,5 @@ package me.topchetoeu.jscript.engine.scope; public interface ScopeRecord { public Object getKey(String name); public Object define(String name); - public ScopeRecord parent(); public LocalScopeRecord child(); } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index adc14d4..313dbc3 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -11,7 +11,6 @@ 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 String[] captureNames, localNames; public final ValueVariable[] captures; @@ -46,14 +45,13 @@ public class CodeFunction extends FunctionValue { } } - public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) { - super(name, length); + public CodeFunction(Environment environment, String name, FunctionBody body, ValueVariable... captures) { + super(name, body.argsN); this.captures = captures; this.captureNames = body.captureNames; this.localNames = body.localNames; this.environment = environment; - this.localsN = localsN; - this.length = length; + this.localsN = body.localsN; this.body = body.instructions; } } diff --git a/src/me/topchetoeu/jscript/engine/values/ScopeValue.java b/src/me/topchetoeu/jscript/engine/values/ScopeValue.java index 768e3c2..95c3583 100644 --- a/src/me/topchetoeu/jscript/engine/values/ScopeValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ScopeValue.java @@ -1,51 +1,54 @@ -package me.topchetoeu.jscript.engine.values; - -import java.util.HashMap; -import java.util.List; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.scope.ValueVariable; - -public class ScopeValue extends ObjectValue { - public final ValueVariable[] variables; - public final HashMap names = new HashMap<>(); - - @Override - protected Object getField(Context ctx, Object key) { - if (names.containsKey(key)) return variables[names.get(key)].get(ctx); - return super.getField(ctx, key); - } - @Override - protected boolean setField(Context ctx, Object key, Object val) { - if (names.containsKey(key)) { - variables[names.get(key)].set(ctx, val); - return true; - } - - return super.setField(ctx, key, val); - } - @Override - protected void deleteField(Context ctx, Object key) { - if (names.containsKey(key)) return; - super.deleteField(ctx, key); - } - @Override - protected boolean hasField(Context ctx, Object key) { - if (names.containsKey(key)) return true; - return super.hasField(ctx, key); - } - @Override - public List keys(boolean includeNonEnumerable) { - var res = super.keys(includeNonEnumerable); - res.addAll(names.keySet()); - return res; - } - - public ScopeValue(ValueVariable[] variables, String[] names) { - this.variables = variables; - for (var i = 0; i < names.length && i < variables.length; i++) { - this.names.put(names[i], i); - this.nonConfigurableSet.add(names[i]); - } - } -} +package me.topchetoeu.jscript.engine.values; + +import java.util.HashMap; +import java.util.List; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.scope.ValueVariable; + +public class ScopeValue extends ObjectValue { + public final ValueVariable[] variables; + public final HashMap names = new HashMap<>(); + + @Override + protected Object getField(Context ctx, Object key) { + if (names.containsKey(key)) return variables[names.get(key)].get(ctx); + return super.getField(ctx, key); + } + @Override + protected boolean setField(Context ctx, Object key, Object val) { + if (names.containsKey(key)) { + variables[names.get(key)].set(ctx, val); + return true; + } + + var proto = getPrototype(ctx); + if (proto != null && proto.hasField(ctx, key) && proto.setField(ctx, key, val)) return true; + + return super.setField(ctx, key, val); + } + @Override + protected void deleteField(Context ctx, Object key) { + if (names.containsKey(key)) return; + super.deleteField(ctx, key); + } + @Override + protected boolean hasField(Context ctx, Object key) { + if (names.containsKey(key)) return true; + return super.hasField(ctx, key); + } + @Override + public List keys(boolean includeNonEnumerable) { + var res = super.keys(includeNonEnumerable); + res.addAll(names.keySet()); + return res; + } + + public ScopeValue(ValueVariable[] variables, String[] names) { + this.variables = variables; + for (var i = 0; i < names.length && i < variables.length; i++) { + this.names.put(names[i], i); + this.nonConfigurableSet.add(names[i]); + } + } +} diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index b4cc5a4..83646e6 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -70,8 +70,7 @@ public class Values { @SuppressWarnings("unchecked") public static T wrapper(Object val, Class clazz) { - if (!isWrapper(val)) return null; - + if (!isWrapper(val)) val = new NativeWrapper(val); var res = (NativeWrapper)val; if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped; else return null; diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index e63169f..cb82bb1 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -12,19 +12,48 @@ import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; public class EngineException extends RuntimeException { + public static class StackElement { + public final Location location; + public final String function; + public final Context ctx; + + public boolean visible() { + return ctx == null || ctx.environment() == null || ctx.environment().stackVisible; + } + public String toString() { + var res = ""; + var loc = location; + + if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc); + + if (loc != null) res += "at " + loc.toString() + " "; + if (function != null && !function.equals("")) res += "in " + function + " "; + + return res.trim(); + } + + public StackElement(Context ctx, Location location, String function) { + if (function != null) function = function.trim(); + if (function.equals("")) function = null; + + if (ctx == null) this.ctx = null; + else this.ctx = new Context(ctx.engine).pushEnv(ctx.environment()); + this.location = location; + this.function = function; + } + } + public final Object value; public EngineException cause; public Environment env = null; public Engine engine = null; - public final List stackTrace = new ArrayList<>(); + public final List stackTrace = new ArrayList<>(); - public EngineException add(String name, Location location) { - var res = ""; - - if (location != null) res += "at " + location.toString() + " "; - if (name != null && !name.equals("")) res += "in " + name + " "; - - this.stackTrace.add(res.trim()); + public EngineException add(Context ctx, String name, Location location) { + var el = new StackElement(ctx, location, name); + if (el.function == null && el.location == null) return this; + setCtx(ctx.environment(), ctx.engine); + stackTrace.add(el); return this; } public EngineException setCause(EngineException cause) { @@ -46,7 +75,7 @@ public class EngineException extends RuntimeException { ss.append("[Error while stringifying]\n"); } for (var line : stackTrace) { - ss.append(" ").append(line).append('\n'); + if (line.visible()) ss.append(" ").append(line.toString()).append("\n"); } if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n'); ss.deleteCharAt(ss.length() - 1); @@ -74,7 +103,7 @@ public class EngineException extends RuntimeException { return new EngineException(err(null, msg, PlaceholderProto.ERROR)); } public static EngineException ofSyntax(SyntaxException e) { - return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc); + return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, null, e.loc); } public static EngineException ofSyntax(String msg) { return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); diff --git a/src/me/topchetoeu/jscript/filesystem/File.java b/src/me/topchetoeu/jscript/filesystem/File.java index 487b5dc..6e7f24a 100644 --- a/src/me/topchetoeu/jscript/filesystem/File.java +++ b/src/me/topchetoeu/jscript/filesystem/File.java @@ -1,39 +1,41 @@ -package me.topchetoeu.jscript.filesystem; - -public interface File { - int read(byte[] buff); - void write(byte[] buff); - long getPtr(); - void setPtr(long offset, int pos); - void close(); - Mode mode(); - - default String readToString() { - setPtr(0, 2); - long len = getPtr(); - if (len < 0) return null; - - setPtr(0, 0); - - byte[] res = new byte[(int)len]; - read(res); - - return new String(res); - } - default String readLine() { - var res = new Buffer(new byte[0]); - var buff = new byte[1]; - - while (true) { - if (read(buff) == 0) { - if (res.length() == 0) return null; - else break; - } - - if (buff[0] == '\n') break; - - res.write(res.length(), buff); - } - return new String(res.data()); - } +package me.topchetoeu.jscript.filesystem; + +import me.topchetoeu.jscript.Buffer; + +public interface File { + int read(byte[] buff); + void write(byte[] buff); + long getPtr(); + void setPtr(long offset, int pos); + void close(); + Mode mode(); + + default String readToString() { + setPtr(0, 2); + long len = getPtr(); + if (len < 0) return null; + + setPtr(0, 0); + + byte[] res = new byte[(int)len]; + read(res); + + return new String(res); + } + default String readLine() { + var res = new Buffer(); + var buff = new byte[1]; + + while (true) { + if (read(buff) == 0) { + if (res.length() == 0) return null; + else break; + } + + if (buff[0] == '\n') break; + + res.write(res.length(), buff); + } + return new String(res.data()); + } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/MemoryFile.java b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java index 0ca8425..bea8a0b 100644 --- a/src/me/topchetoeu/jscript/filesystem/MemoryFile.java +++ b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java @@ -1,66 +1,67 @@ -package me.topchetoeu.jscript.filesystem; - -import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; - -public class MemoryFile implements File { - private int ptr; - private Mode mode; - private Buffer data; - private String filename; - - public Buffer data() { return data; } - - @Override - public int read(byte[] buff) { - if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); - var res = data.read(ptr, buff); - ptr += res; - return res; - } - @Override - public void write(byte[] buff) { - if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); - - data.write(ptr, buff); - ptr += buff.length; - } - - @Override - public long getPtr() { - if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); - return ptr; - } - @Override - public void setPtr(long offset, int pos) { - if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); - - if (pos == 0) ptr = (int)offset; - else if (pos == 1) ptr += (int)offset; - else if (pos == 2) ptr = data.length() - (int)offset; - } - - @Override - public void close() { - mode = Mode.NONE; - ptr = 0; - } - @Override - public Mode mode() { - if (data == null) return Mode.NONE; - return mode; - } - - public MemoryFile(String filename, Buffer buff, Mode mode) { - this.filename = filename; - this.data = buff; - this.mode = mode; - } - - public static MemoryFile fromFileList(String filename, java.io.File[] list) { - var res = new StringBuilder(); - - for (var el : list) res.append(el.getName()).append('\n'); - - return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ); - } -} +package me.topchetoeu.jscript.filesystem; + +import me.topchetoeu.jscript.Buffer; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + +public class MemoryFile implements File { + private int ptr; + private Mode mode; + private Buffer data; + private String filename; + + public Buffer data() { return data; } + + @Override + public int read(byte[] buff) { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + var res = data.read(ptr, buff); + ptr += res; + return res; + } + @Override + public void write(byte[] buff) { + if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); + + data.write(ptr, buff); + ptr += buff.length; + } + + @Override + public long getPtr() { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + return ptr; + } + @Override + public void setPtr(long offset, int pos) { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + + if (pos == 0) ptr = (int)offset; + else if (pos == 1) ptr += (int)offset; + else if (pos == 2) ptr = data.length() - (int)offset; + } + + @Override + public void close() { + mode = Mode.NONE; + ptr = 0; + } + @Override + public Mode mode() { + if (data == null) return Mode.NONE; + return mode; + } + + public MemoryFile(String filename, Buffer buff, Mode mode) { + this.filename = filename; + this.data = buff; + this.mode = mode; + } + + public static MemoryFile fromFileList(String filename, java.io.File[] list) { + var res = new StringBuilder(); + + for (var el : list) res.append(el.getName()).append('\n'); + + return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ); + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java b/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java index 7db85db..a54fcfe 100644 --- a/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java @@ -1,89 +1,90 @@ -package me.topchetoeu.jscript.filesystem; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.HashSet; - -import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; - -public class MemoryFilesystem implements Filesystem { - public final Mode mode; - private HashMap files = new HashMap<>(); - private HashSet folders = new HashSet<>(); - - private Path getPath(String name) { - return Path.of("/" + name.replace("\\", "/")).normalize(); - } - - @Override - public void create(String path, EntryType type) { - var _path = getPath(path); - - switch (type) { - case FILE: - if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); - if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); - if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); - files.put(_path, new Buffer(new byte[0])); - break; - case FOLDER: - if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); - if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); - folders.add(_path); - break; - default: - case NONE: - if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST); - } - } - - @Override - public File open(String path, Mode perms) { - var _path = getPath(path); - var pcount = _path.getNameCount(); - - if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms); - else if (folders.contains(_path)) { - var res = new StringBuilder(); - for (var folder : folders) { - if (pcount + 1 != folder.getNameCount()) continue; - if (!folder.startsWith(_path)) continue; - res.append(folder.toFile().getName()).append('\n'); - } - for (var file : files.keySet()) { - if (pcount + 1 != file.getNameCount()) continue; - if (!file.startsWith(_path)) continue; - res.append(file.toFile().getName()).append('\n'); - } - return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)); - } - else throw new FilesystemException(path, FSCode.DOESNT_EXIST); - } - - @Override - public FileStat stat(String path) { - var _path = getPath(path); - - if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE); - else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER); - else throw new FilesystemException(path, FSCode.DOESNT_EXIST); - } - - public MemoryFilesystem put(String path, byte[] data) { - var _path = getPath(path); - var _curr = "/"; - - for (var seg : _path) { - create(_curr, EntryType.FOLDER); - _curr += seg + "/"; - } - - files.put(_path, new Buffer(data)); - return this; - } - - public MemoryFilesystem(Mode mode) { - this.mode = mode; - folders.add(Path.of("/")); - } -} +package me.topchetoeu.jscript.filesystem; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; + +import me.topchetoeu.jscript.Buffer; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + +public class MemoryFilesystem implements Filesystem { + public final Mode mode; + private HashMap files = new HashMap<>(); + private HashSet folders = new HashSet<>(); + + private Path getPath(String name) { + return Path.of("/" + name.replace("\\", "/")).normalize(); + } + + @Override + public void create(String path, EntryType type) { + var _path = getPath(path); + + switch (type) { + case FILE: + if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + files.put(_path, new Buffer()); + break; + case FOLDER: + if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + folders.add(_path); + break; + default: + case NONE: + if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + } + + @Override + public File open(String path, Mode perms) { + var _path = getPath(path); + var pcount = _path.getNameCount(); + + if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms); + else if (folders.contains(_path)) { + var res = new StringBuilder(); + for (var folder : folders) { + if (pcount + 1 != folder.getNameCount()) continue; + if (!folder.startsWith(_path)) continue; + res.append(folder.toFile().getName()).append('\n'); + } + for (var file : files.keySet()) { + if (pcount + 1 != file.getNameCount()) continue; + if (!file.startsWith(_path)) continue; + res.append(file.toFile().getName()).append('\n'); + } + return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)); + } + else throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + + @Override + public FileStat stat(String path) { + var _path = getPath(path); + + if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE); + else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER); + else throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + + public MemoryFilesystem put(String path, byte[] data) { + var _path = getPath(path); + var _curr = "/"; + + for (var seg : _path) { + create(_curr, EntryType.FOLDER); + _curr += seg + "/"; + } + + files.put(_path, new Buffer(data)); + return this; + } + + public MemoryFilesystem(Mode mode) { + this.mode = mode; + folders.add(Path.of("/")); + } +} diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 6edc93f..f3045b7 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -165,7 +165,7 @@ public class NativeWrapperProvider implements WrappersProvider { } if (((OverloadFunction)func).overloads.size() == 0) { - func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); + func = new NativeFunction(getName(clazz), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); } applyMethods(ctx, false, func, clazz); diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 9edd2a5..ac6bcfd 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -83,11 +83,11 @@ public class OverloadFunction extends FunctionValue { catch (InvocationTargetException e) { var loc = Location.INTERNAL; if (e.getTargetException() instanceof EngineException) { - throw ((EngineException)e.getTargetException()).add(name, loc); + throw ((EngineException)e.getTargetException()).add(ctx, name, loc); } else if (e.getTargetException() instanceof NullPointerException) { e.printStackTrace(); - throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc); + throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, loc); } else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) { throw new InterruptException(); @@ -100,11 +100,11 @@ public class OverloadFunction extends FunctionValue { err.defineProperty(ctx, "message", target.getMessage()); err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass)); - throw new EngineException(err).add(name, loc); + throw new EngineException(err).add(ctx, name, loc); } } catch (ReflectiveOperationException e) { - throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL); + throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL); } } diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 457a885..fcf6355 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -37,21 +37,6 @@ public class JSON { if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue()); if (val instanceof String) return JSONElement.string((String)val); if (val == Values.NULL) return JSONElement.NULL; - if (val instanceof ObjectValue) { - if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); - prev.add(val); - - var res = new JSONMap(); - - for (var el : ((ObjectValue)val).keys(false)) { - var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev); - if (jsonEl == null) continue; - if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); - } - - prev.remove(val); - return JSONElement.of(res); - } if (val instanceof ArrayValue) { if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); prev.add(val); @@ -67,6 +52,21 @@ public class JSON { prev.remove(val); return JSONElement.of(res); } + if (val instanceof ObjectValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONMap(); + + for (var el : ((ObjectValue)val).keys(false)) { + var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev); + if (jsonEl == null) continue; + if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } if (val == null) return null; return null; } diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index d9473a5..5a199a1 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -1,164 +1,221 @@ -package me.topchetoeu.jscript.lib; - -import java.io.IOException; -import java.util.HashMap; - -import me.topchetoeu.jscript.Reading; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.DataKey; -import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.scope.GlobalScope; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; - -public class Internals { - private static final DataKey> THREADS = new DataKey<>(); - private static final DataKey I = new DataKey<>(); - - - @Native public static Object log(Context ctx, Object ...args) { - for (var arg : args) { - Values.printValue(ctx, arg); - System.out.print(" "); - } - System.out.println(); - - if (args.length == 0) return null; - else return args[0]; - } - @Native public static String readline(Context ctx) { - try { - return Reading.read(); - } - catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - @Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { - var thread = new Thread(() -> { - var ms = (long)delay; - var ns = (int)((delay - ms) * 10000000); - - try { - Thread.sleep(ms, ns); - } - catch (InterruptedException e) { return; } - - ctx.engine.pushMsg(false, ctx, func, null, args); - }); - thread.start(); - - int i = ctx.environment().data.increase(I, 1, 0); - var threads = ctx.environment().data.get(THREADS, new HashMap<>()); - threads.put(++i, thread); - return i; - } - @Native public static int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { - var thread = new Thread(() -> { - var ms = (long)delay; - var ns = (int)((delay - ms) * 10000000); - - while (true) { - try { - Thread.sleep(ms, ns); - } - catch (InterruptedException e) { return; } - - ctx.engine.pushMsg(false, ctx, func, null, args); - } - }); - thread.start(); - - int i = ctx.environment().data.increase(I, 1, 0); - var threads = ctx.environment().data.get(THREADS, new HashMap<>()); - threads.put(++i, thread); - return i; - } - - @Native public static void clearTimeout(Context ctx, int i) { - var threads = ctx.environment().data.get(THREADS, new HashMap<>()); - - var thread = threads.remove(i); - if (thread != null) thread.interrupt(); - } - @Native public static void clearInterval(Context ctx, int i) { - clearTimeout(ctx, i); - } - - @Native public static double parseInt(Context ctx, String val) { - return NumberLib.parseInt(ctx, val); - } - @Native public static double parseFloat(Context ctx, String val) { - return NumberLib.parseFloat(ctx, val); - } - - @Native public static boolean isNaN(Context ctx, double val) { - return NumberLib.isNaN(ctx, val); - } - @Native public static boolean isFinite(Context ctx, double val) { - return NumberLib.isFinite(ctx, val); - } - @Native public static boolean isInfinite(Context ctx, double val) { - return NumberLib.isInfinite(ctx, val); - } - - @NativeGetter public static double NaN(Context ctx) { - return Double.NaN; - } - @NativeGetter public static double Infinity(Context ctx) { - return Double.POSITIVE_INFINITY; - } - - public static Environment apply(Environment env) { - var wp = env.wrappers; - var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class)); - - glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); - glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class)); - glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class)); - glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class)); - - glob.define(null, "Date", false, wp.getConstr(DateLib.class)); - glob.define(null, "Object", false, wp.getConstr(ObjectLib.class)); - glob.define(null, "Function", false, wp.getConstr(FunctionLib.class)); - glob.define(null, "Array", false, wp.getConstr(ArrayLib.class)); - - glob.define(null, "Boolean", false, wp.getConstr(BooleanLib.class)); - glob.define(null, "Number", false, wp.getConstr(NumberLib.class)); - glob.define(null, "String", false, wp.getConstr(StringLib.class)); - glob.define(null, "Symbol", false, wp.getConstr(SymbolLib.class)); - - glob.define(null, "Promise", false, wp.getConstr(PromiseLib.class)); - glob.define(null, "RegExp", false, wp.getConstr(RegExpLib.class)); - glob.define(null, "Map", false, wp.getConstr(MapLib.class)); - glob.define(null, "Set", false, wp.getConstr(SetLib.class)); - - glob.define(null, "Error", false, wp.getConstr(ErrorLib.class)); - glob.define(null, "SyntaxError", false, wp.getConstr(SyntaxErrorLib.class)); - glob.define(null, "TypeError", false, wp.getConstr(TypeErrorLib.class)); - glob.define(null, "RangeError", false, wp.getConstr(RangeErrorLib.class)); - - env.setProto("object", wp.getProto(ObjectLib.class)); - env.setProto("function", wp.getProto(FunctionLib.class)); - env.setProto("array", wp.getProto(ArrayLib.class)); - - env.setProto("bool", wp.getProto(BooleanLib.class)); - env.setProto("number", wp.getProto(NumberLib.class)); - env.setProto("string", wp.getProto(StringLib.class)); - env.setProto("symbol", wp.getProto(SymbolLib.class)); - - env.setProto("error", wp.getProto(ErrorLib.class)); - env.setProto("syntaxErr", wp.getProto(SyntaxErrorLib.class)); - env.setProto("typeErr", wp.getProto(TypeErrorLib.class)); - env.setProto("rangeErr", wp.getProto(RangeErrorLib.class)); - - wp.getProto(ObjectLib.class).setPrototype(null, null); - env.regexConstructor = wp.getConstr(RegExpLib.class); - - return env; - } -} +package me.topchetoeu.jscript.lib; + +import java.io.IOException; +import java.util.HashMap; + +import me.topchetoeu.jscript.Buffer; +import me.topchetoeu.jscript.Reading; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.DataKey; +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.scope.GlobalScope; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.parsing.Parsing; + +public class Internals { + private static final DataKey> THREADS = new DataKey<>(); + private static final DataKey I = new DataKey<>(); + + + @Native public static Object log(Context ctx, Object ...args) { + for (var arg : args) { + Values.printValue(ctx, arg); + System.out.print(" "); + } + System.out.println(); + + if (args.length == 0) return null; + else return args[0]; + } + @Native public static String readline(Context ctx) { + try { + return Reading.read(); + } + catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + @Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { + var thread = new Thread(() -> { + var ms = (long)delay; + var ns = (int)((delay - ms) * 10000000); + + try { + Thread.sleep(ms, ns); + } + catch (InterruptedException e) { return; } + + ctx.engine.pushMsg(false, ctx, func, null, args); + }); + thread.start(); + + int i = ctx.environment().data.increase(I, 1, 0); + var threads = ctx.environment().data.get(THREADS, new HashMap<>()); + threads.put(++i, thread); + return i; + } + @Native public static int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { + var thread = new Thread(() -> { + var ms = (long)delay; + var ns = (int)((delay - ms) * 10000000); + + while (true) { + try { + Thread.sleep(ms, ns); + } + catch (InterruptedException e) { return; } + + ctx.engine.pushMsg(false, ctx, func, null, args); + } + }); + thread.start(); + + int i = ctx.environment().data.increase(I, 1, 0); + var threads = ctx.environment().data.get(THREADS, new HashMap<>()); + threads.put(++i, thread); + return i; + } + + @Native public static void clearTimeout(Context ctx, int i) { + var threads = ctx.environment().data.get(THREADS, new HashMap<>()); + + var thread = threads.remove(i); + if (thread != null) thread.interrupt(); + } + @Native public static void clearInterval(Context ctx, int i) { + clearTimeout(ctx, i); + } + + @Native public static double parseInt(Context ctx, String val) { + return NumberLib.parseInt(ctx, val); + } + @Native public static double parseFloat(Context ctx, String val) { + return NumberLib.parseFloat(ctx, val); + } + + @Native public static boolean isNaN(Context ctx, double val) { + return NumberLib.isNaN(ctx, val); + } + @Native public static boolean isFinite(Context ctx, double val) { + return NumberLib.isFinite(ctx, val); + } + @Native public static boolean isInfinite(Context ctx, double val) { + return NumberLib.isInfinite(ctx, val); + } + + @NativeGetter public static double NaN(Context ctx) { + return Double.NaN; + } + @NativeGetter public static double Infinity(Context ctx) { + return Double.POSITIVE_INFINITY; + } + private static final String HEX = "0123456789ABCDEF"; + + private static String encodeUriAny(String str, String keepAlphabet) { + if (str == null) str = "undefined"; + + var bytes = str.getBytes(); + var sb = new StringBuilder(bytes.length); + + for (byte c : bytes) { + if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c); + else { + sb.append('%'); + sb.append(HEX.charAt(c / 16)); + sb.append(HEX.charAt(c % 16)); + } + } + + return sb.toString(); + } + private static String decodeUriAny(String str, String keepAlphabet) { + if (str == null) str = "undefined"; + + var res = new Buffer(); + var bytes = str.getBytes(); + + for (var i = 0; i < bytes.length; i++) { + var c = bytes[i]; + if (c == '%') { + if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed."); + var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]); + if (!Parsing.isAny((char)b, keepAlphabet)) { + i += 2; + res.append((byte)b); + continue; + } + } + res.append(c); + } + + return new String(res.data()); + } + + @Native public static String encodeURIComponent(String str) { + return encodeUriAny(str, ".-_!~*'()"); + } + @Native public static String decodeURIComponent(String str) { + return decodeUriAny(str, ""); + } + @Native public static String encodeURI(String str) { + return encodeUriAny(str, ";,/?:@&=+$#.-_!~*'()"); + } + @Native public static String decodeURI(String str) { + return decodeUriAny(str, ",/?:@&=+$#."); + } + + public static Environment apply(Environment env) { + var wp = env.wrappers; + var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class)); + + glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); + glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class)); + glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class)); + glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class)); + + glob.define(false, wp.getConstr(DateLib.class)); + glob.define(false, wp.getConstr(ObjectLib.class)); + glob.define(false, wp.getConstr(FunctionLib.class)); + glob.define(false, wp.getConstr(ArrayLib.class)); + + glob.define(false, wp.getConstr(BooleanLib.class)); + glob.define(false, wp.getConstr(NumberLib.class)); + glob.define(false, wp.getConstr(StringLib.class)); + glob.define(false, wp.getConstr(SymbolLib.class)); + + glob.define(false, wp.getConstr(PromiseLib.class)); + glob.define(false, wp.getConstr(RegExpLib.class)); + glob.define(false, wp.getConstr(MapLib.class)); + glob.define(false, wp.getConstr(SetLib.class)); + + glob.define(false, wp.getConstr(ErrorLib.class)); + glob.define(false, wp.getConstr(SyntaxErrorLib.class)); + glob.define(false, wp.getConstr(TypeErrorLib.class)); + glob.define(false, wp.getConstr(RangeErrorLib.class)); + + env.setProto("object", wp.getProto(ObjectLib.class)); + env.setProto("function", wp.getProto(FunctionLib.class)); + env.setProto("array", wp.getProto(ArrayLib.class)); + + env.setProto("bool", wp.getProto(BooleanLib.class)); + env.setProto("number", wp.getProto(NumberLib.class)); + env.setProto("string", wp.getProto(StringLib.class)); + env.setProto("symbol", wp.getProto(SymbolLib.class)); + + env.setProto("error", wp.getProto(ErrorLib.class)); + env.setProto("syntaxErr", wp.getProto(SyntaxErrorLib.class)); + env.setProto("typeErr", wp.getProto(TypeErrorLib.class)); + env.setProto("rangeErr", wp.getProto(RangeErrorLib.class)); + + wp.getProto(ObjectLib.class).setPrototype(null, null); + env.regexConstructor = wp.getConstr(RegExpLib.class); + + return env; + } +} diff --git a/src/me/topchetoeu/jscript/lib/MapLib.java b/src/me/topchetoeu/jscript/lib/MapLib.java index f7762c5..c731dd5 100644 --- a/src/me/topchetoeu/jscript/lib/MapLib.java +++ b/src/me/topchetoeu/jscript/lib/MapLib.java @@ -1,78 +1,78 @@ -package me.topchetoeu.jscript.lib; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.stream.Collectors; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.ArrayValue; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; - -@Native("Map") public class MapLib { - private LinkedHashMap map = new LinkedHashMap<>(); - - @Native("@@Symbol.typeName") public final String name = "Map"; - @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { - return this.entries(ctx); - } - - @Native public void clear() { - map.clear(); - } - @Native public boolean delete(Object key) { - if (map.containsKey(key)) { - map.remove(key); - return true; - } - return false; - } - - @Native public ObjectValue entries(Context ctx) { - var res = map.entrySet().stream().map(v -> { - return new ArrayValue(ctx, v.getKey(), v.getValue()); - }).collect(Collectors.toList()); - return Values.toJSIterator(ctx, res.iterator()); - } - @Native public ObjectValue keys(Context ctx) { - var res = new ArrayList<>(map.keySet()); - return Values.toJSIterator(ctx, res.iterator()); - } - @Native public ObjectValue values(Context ctx) { - var res = new ArrayList<>(map.values()); - return Values.toJSIterator(ctx, res.iterator()); - } - - @Native public Object get(Object key) { - return map.get(key); - } - @Native public MapLib set(Object key, Object val) { - map.put(key, val); - return this; - } - @Native public boolean has(Object key) { - return map.containsKey(key); - } - - @NativeGetter public int size() { - return map.size(); - } - - @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { - var keys = new ArrayList<>(map.keySet()); - - for (var el : keys) func.call(ctx, thisArg, map.get(el), el,this); - } - - @Native public MapLib(Context ctx, Object iterable) { - for (var el : Values.fromJSIterator(ctx, iterable)) { - try { - set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); - } - catch (IllegalArgumentException e) { } - } - } -} +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; + +@Native("Map") public class MapLib { + private LinkedHashMap map = new LinkedHashMap<>(); + + @Native("@@Symbol.typeName") public final String name = "Map"; + @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { + return this.entries(ctx); + } + + @Native public void clear() { + map.clear(); + } + @Native public boolean delete(Object key) { + if (map.containsKey(key)) { + map.remove(key); + return true; + } + return false; + } + + @Native public ObjectValue entries(Context ctx) { + return ArrayValue.of(ctx, map + .entrySet() + .stream() + .map(v -> new ArrayValue(ctx, v.getKey(), v.getValue())) + .collect(Collectors.toList()) + ); + } + @Native public ObjectValue keys(Context ctx) { + return ArrayValue.of(ctx, map.keySet()); + } + @Native public ObjectValue values(Context ctx) { + return ArrayValue.of(ctx, map.values()); + } + + @Native public Object get(Object key) { + return map.get(key); + } + @Native public MapLib set(Object key, Object val) { + map.put(key, val); + return this; + } + @Native public boolean has(Object key) { + return map.containsKey(key); + } + + @NativeGetter public int size() { + return map.size(); + } + + @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { + var keys = new ArrayList<>(map.keySet()); + + for (var el : keys) func.call(ctx, thisArg, map.get(el), el,this); + } + + @Native public MapLib(Context ctx, Object iterable) { + for (var el : Values.fromJSIterator(ctx, iterable)) { + try { + set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); + } + catch (IllegalArgumentException e) { } + } + } +} diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 4489041..7ffb708 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -1,366 +1,367 @@ -package me.topchetoeu.jscript.lib; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.ArrayValue; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.NativeFunction; -import me.topchetoeu.jscript.engine.values.NativeWrapper; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.Native; - -@Native("Promise") public class PromiseLib { - public static interface PromiseRunner { - Object run(); - } - private static class Handle { - public final Context ctx; - public final FunctionValue fulfilled; - public final FunctionValue rejected; - - public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) { - this.ctx = ctx; - this.fulfilled = fulfilled; - this.rejected = rejected; - } - } - - @Native("resolve") - public static PromiseLib ofResolved(Context ctx, Object val) { - var res = new PromiseLib(); - res.fulfill(ctx, val); - return res; - } - @Native("reject") - public static PromiseLib ofRejected(Context ctx, Object val) { - var res = new PromiseLib(); - res.reject(ctx, val); - return res; - } - - @Native public static PromiseLib any(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - - var errors = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - var index = i; - var val = promises.get(i); - then(ctx, val, - new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), - new NativeFunction(null, (e, th, args) -> { - errors.set(ctx, index, args[0]); - n[0]--; - if (n[0] <= 0) res.reject(e, errors); - return null; - }) - ); - } - - return res; - } - @Native public static PromiseLib race(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var res = new PromiseLib(); - - for (var i = 0; i < promises.size(); i++) { - var val = promises.get(i); - then(ctx, val, - new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), - new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) - ); - } - - return res; - } - @Native public static PromiseLib all(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - - var result = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - var index = i; - var val = promises.get(i); - then(ctx, val, - new NativeFunction(null, (e, th, args) -> { - result.set(ctx, index, args[0]); - n[0]--; - if (n[0] <= 0) res.fulfill(e, result); - return null; - }), - new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) - ); - } - - if (n[0] <= 0) res.fulfill(ctx, result); - - return res; - } - @Native public static PromiseLib allSettled(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - - var result = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - var index = i; - var val = promises.get(i); - then(ctx, val, - new NativeFunction(null, (e, th, args) -> { - result.set(ctx, index, new ObjectValue(ctx, Map.of( - "status", "fulfilled", - "value", args[0] - ))); - n[0]--; - if (n[0] <= 0) res.fulfill(e, result); - return null; - }), - new NativeFunction(null, (e, th, args) -> { - result.set(ctx, index, new ObjectValue(ctx, Map.of( - "status", "rejected", - "reason", args[0] - ))); - n[0]--; - if (n[0] <= 0) res.fulfill(e, result); - return null; - }) - ); - } - - if (n[0] <= 0) res.fulfill(ctx, result); - - return res; - } - - /** - * Thread safe - you can call this from anywhere - * HOWEVER, it's strongly recommended to use this only in javascript - */ - @Native(thisArg=true) public static Object then(Context ctx, Object thisArg, Object _onFulfill, Object _onReject) { - var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null; - var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null; - - var res = new PromiseLib(); - - var fulfill = onFulfill == null ? new NativeFunction((_ctx, _thisArg, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill; - var reject = onReject == null ? new NativeFunction((_ctx, _thisArg, _args) -> { - throw new EngineException(_args.length > 0 ? _args[0] : null); - }) : (FunctionValue)onReject; - - if (thisArg instanceof NativeWrapper && ((NativeWrapper)thisArg).wrapped instanceof PromiseLib) { - thisArg = ((NativeWrapper)thisArg).wrapped; - } - - var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> { - try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); } - catch (EngineException err) { res.reject(ctx, err.value); } - return null; - }); - var rejectHandle = new NativeFunction(null, (_ctx, th, a) -> { - try { res.fulfill(ctx, reject.call(ctx, null, a[0])); } - catch (EngineException err) { res.reject(ctx, err.value); } - return null; - }); - - if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle); - else { - Object next; - try { next = Values.getMember(ctx, thisArg, "then"); } - catch (IllegalArgumentException e) { next = null; } - - try { - if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle); - else res.fulfill(ctx, fulfill.call(ctx, null, thisArg)); - } - catch (EngineException err) { - res.reject(ctx, fulfill.call(ctx, null, err.value)); - } - } - - return res; - } - /** - * Thread safe - you can call this from anywhere - * HOWEVER, it's strongly recommended to use this only in javascript - */ - @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) { - return then(ctx, thisArg, null, _onReject); - } - /** - * Thread safe - you can call this from anywhere - * HOWEVER, it's strongly recommended to use this only in javascript - */ - @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) { - return then(ctx, thisArg, - new NativeFunction(null, (e, th, _args) -> { - if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); - return _args.length > 0 ? _args[0] : null; - }), - new NativeFunction(null, (e, th, _args) -> { - if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); - throw new EngineException(_args.length > 0 ? _args[0] : null); - }) - ); - } - - private List handles = new ArrayList<>(); - - private static final int STATE_PENDING = 0; - private static final int STATE_FULFILLED = 1; - private static final int STATE_REJECTED = 2; - - private int state = STATE_PENDING; - private boolean handled = false; - private Object val; - - public synchronized void fulfill(Context ctx, Object val) { - if (this.state != STATE_PENDING) return; - - if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, - new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }), - new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) - ); - else { - Object then; - try { then = Values.getMember(ctx, val, "then"); } - catch (IllegalArgumentException e) { then = null; } - - try { - if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, - new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }), - new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) - ); - else { - this.val = val; - this.state = STATE_FULFILLED; - - ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { - for (var handle : handles) { - handle.fulfilled.call(handle.ctx, null, val); - } - handles = null; - return null; - }), null); - } - } - catch (EngineException err) { - this.reject(ctx, err.value); - } - } - } - public synchronized void reject(Context ctx, Object val) { - if (this.state != STATE_PENDING) return; - - if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, - new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }), - new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) - ); - else { - Object then; - try { then = Values.getMember(ctx, val, "then"); } - catch (IllegalArgumentException e) { then = null; } - - try { - if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, - new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }), - new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) - ); - else { - this.val = val; - this.state = STATE_REJECTED; - - ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { - for (var handle : handles) handle.rejected.call(handle.ctx, null, val); - if (!handled) { - Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); - } - handles = null; - return null; - }), null); - } - } - catch (EngineException err) { - this.reject(ctx, err.value); - } - } - } - - private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { - if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val); - else if (state == STATE_REJECTED) { - ctx.engine.pushMsg(true, ctx, reject, null, val); - handled = true; - } - else handles.add(new Handle(ctx, fulfill, reject)); - } - - @Override @Native public String toString() { - if (state == STATE_PENDING) return "Promise (pending)"; - else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; - else return "Promise (rejected)"; - } - - /** - * NOT THREAD SAFE - must be called from the engine executor thread - */ - @Native public PromiseLib(Context ctx, FunctionValue func) { - if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); - try { - func.call( - ctx, null, - new NativeFunction(null, (e, th, args) -> { - fulfill(e, args.length > 0 ? args[0] : null); - return null; - }), - new NativeFunction(null, (e, th, args) -> { - reject(e, args.length > 0 ? args[0] : null); - return null; - }) - ); - } - catch (EngineException e) { - reject(ctx, e.value); - } - } - - private PromiseLib(int state, Object val) { - this.state = state; - this.val = val; - } - public PromiseLib() { - this(STATE_PENDING, null); - } - - public static PromiseLib await(Context ctx, PromiseRunner runner) { - var res = new PromiseLib(); - - new Thread(() -> { - try { - res.fulfill(ctx, runner.run()); - } - catch (EngineException e) { - res.reject(ctx, e.value); - } - }, "Promisifier").start(); - - return res; - } -} +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.NativeFunction; +import me.topchetoeu.jscript.engine.values.NativeWrapper; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; + +@Native("Promise") public class PromiseLib { + public static interface PromiseRunner { + Object run(); + } + private static class Handle { + public final Context ctx; + public final FunctionValue fulfilled; + public final FunctionValue rejected; + + public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) { + this.ctx = ctx; + this.fulfilled = fulfilled; + this.rejected = rejected; + } + } + + @Native("resolve") + public static PromiseLib ofResolved(Context ctx, Object val) { + var res = new PromiseLib(); + res.fulfill(ctx, val); + return res; + } + @Native("reject") + public static PromiseLib ofRejected(Context ctx, Object val) { + var res = new PromiseLib(); + res.reject(ctx, val); + return res; + } + + @Native public static PromiseLib any(Context ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + + var errors = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + then(ctx, val, + new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), + new NativeFunction(null, (e, th, args) -> { + errors.set(ctx, index, args[0]); + n[0]--; + if (n[0] <= 0) res.reject(e, errors); + return null; + }) + ); + } + + return res; + } + @Native public static PromiseLib race(Context ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); + var res = new PromiseLib(); + + for (var i = 0; i < promises.size(); i++) { + var val = promises.get(i); + then(ctx, val, + new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), + new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) + ); + } + + return res; + } + @Native public static PromiseLib all(Context ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + then(ctx, val, + new NativeFunction(null, (e, th, args) -> { + result.set(ctx, index, args[0]); + n[0]--; + if (n[0] <= 0) res.fulfill(e, result); + return null; + }), + new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) + ); + } + + if (n[0] <= 0) res.fulfill(ctx, result); + + return res; + } + @Native public static PromiseLib allSettled(Context ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + then(ctx, val, + new NativeFunction(null, (e, th, args) -> { + result.set(ctx, index, new ObjectValue(ctx, Map.of( + "status", "fulfilled", + "value", args[0] + ))); + n[0]--; + if (n[0] <= 0) res.fulfill(e, result); + return null; + }), + new NativeFunction(null, (e, th, args) -> { + result.set(ctx, index, new ObjectValue(ctx, Map.of( + "status", "rejected", + "reason", args[0] + ))); + n[0]--; + if (n[0] <= 0) res.fulfill(e, result); + return null; + }) + ); + } + + if (n[0] <= 0) res.fulfill(ctx, result); + + return res; + } + + /** + * Thread safe - you can call this from anywhere + * HOWEVER, it's strongly recommended to use this only in javascript + */ + @Native(thisArg=true) public static Object then(Context ctx, Object _thisArg, Object _onFulfill, Object _onReject) { + var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null; + var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null; + + var res = new PromiseLib(); + + var fulfill = onFulfill == null ? new NativeFunction((_ctx, _0, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill; + var reject = onReject == null ? new NativeFunction((_ctx, _0, _args) -> { + throw new EngineException(_args.length > 0 ? _args[0] : null); + }) : (FunctionValue)onReject; + + var thisArg = _thisArg instanceof NativeWrapper && ((NativeWrapper)_thisArg).wrapped instanceof PromiseLib ? + ((NativeWrapper)_thisArg).wrapped : + _thisArg; + + var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> { + try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); } + catch (EngineException err) { res.reject(ctx, err.value); } + return null; + }); + var rejectHandle = new NativeFunction(null, (_ctx, th, a) -> { + try { res.fulfill(ctx, reject.call(ctx, null, a[0])); } + catch (EngineException err) { res.reject(ctx, err.value); } + if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handled = true; + return null; + }); + + if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle); + else { + Object next; + try { next = Values.getMember(ctx, thisArg, "then"); } + catch (IllegalArgumentException e) { next = null; } + + try { + if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle); + else res.fulfill(ctx, fulfill.call(ctx, null, thisArg)); + } + catch (EngineException err) { + res.reject(ctx, fulfill.call(ctx, null, err.value)); + } + } + + return res; + } + /** + * Thread safe - you can call this from anywhere + * HOWEVER, it's strongly recommended to use this only in javascript + */ + @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) { + return then(ctx, thisArg, null, _onReject); + } + /** + * Thread safe - you can call this from anywhere + * HOWEVER, it's strongly recommended to use this only in javascript + */ + @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) { + return then(ctx, thisArg, + new NativeFunction(null, (e, th, _args) -> { + if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); + return _args.length > 0 ? _args[0] : null; + }), + new NativeFunction(null, (e, th, _args) -> { + if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); + throw new EngineException(_args.length > 0 ? _args[0] : null); + }) + ); + } + + private List handles = new ArrayList<>(); + + private static final int STATE_PENDING = 0; + private static final int STATE_FULFILLED = 1; + private static final int STATE_REJECTED = 2; + + private int state = STATE_PENDING; + private boolean handled = false; + private Object val; + + public synchronized void fulfill(Context ctx, Object val) { + if (this.state != STATE_PENDING) return; + + if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, + new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }), + new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) + ); + else { + Object then; + try { then = Values.getMember(ctx, val, "then"); } + catch (IllegalArgumentException e) { then = null; } + + try { + if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, + new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }), + new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) + ); + else { + this.val = val; + this.state = STATE_FULFILLED; + + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + for (var handle : handles) { + handle.fulfilled.call(handle.ctx, null, val); + } + handles = null; + return null; + }), null); + } + } + catch (EngineException err) { + this.reject(ctx, err.value); + } + } + } + public synchronized void reject(Context ctx, Object val) { + if (this.state != STATE_PENDING) return; + + if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, + new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }), + new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) + ); + else { + Object then; + try { then = Values.getMember(ctx, val, "then"); } + catch (IllegalArgumentException e) { then = null; } + + try { + if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, + new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }), + new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) + ); + else { + this.val = val; + this.state = STATE_REJECTED; + + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + for (var handle : handles) handle.rejected.call(handle.ctx, null, val); + if (!handled) { + Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); + } + handles = null; + return null; + }), null); + } + } + catch (EngineException err) { + this.reject(ctx, err.value); + } + } + } + + private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { + if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val); + else if (state == STATE_REJECTED) { + ctx.engine.pushMsg(true, ctx, reject, null, val); + handled = true; + } + else handles.add(new Handle(ctx, fulfill, reject)); + } + + @Override @Native public String toString() { + if (state == STATE_PENDING) return "Promise (pending)"; + else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; + else return "Promise (rejected)"; + } + + /** + * NOT THREAD SAFE - must be called from the engine executor thread + */ + @Native public PromiseLib(Context ctx, FunctionValue func) { + if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); + try { + func.call( + ctx, null, + new NativeFunction(null, (e, th, args) -> { + fulfill(e, args.length > 0 ? args[0] : null); + return null; + }), + new NativeFunction(null, (e, th, args) -> { + reject(e, args.length > 0 ? args[0] : null); + return null; + }) + ); + } + catch (EngineException e) { + reject(ctx, e.value); + } + } + + private PromiseLib(int state, Object val) { + this.state = state; + this.val = val; + } + public PromiseLib() { + this(STATE_PENDING, null); + } + + public static PromiseLib await(Context ctx, PromiseRunner runner) { + var res = new PromiseLib(); + + new Thread(() -> { + try { + res.fulfill(ctx, runner.run()); + } + catch (EngineException e) { + res.reject(ctx, e.value); + } + }, "Promisifier").start(); + + return res; + } +} diff --git a/src/me/topchetoeu/jscript/lib/SetLib.java b/src/me/topchetoeu/jscript/lib/SetLib.java index 90ab717..27856dc 100644 --- a/src/me/topchetoeu/jscript/lib/SetLib.java +++ b/src/me/topchetoeu/jscript/lib/SetLib.java @@ -1,63 +1,60 @@ -package me.topchetoeu.jscript.lib; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.stream.Collectors; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.ArrayValue; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; - -@Native("Set") public class SetLib { - private LinkedHashSet set = new LinkedHashSet<>(); - - @Native("@@Symbol.typeName") public final String name = "Set"; - @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { - return this.values(ctx); - } - - @Native public ObjectValue entries(Context ctx) { - var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); - return Values.toJSIterator(ctx, res.iterator()); - } - @Native public ObjectValue keys(Context ctx) { - var res = new ArrayList<>(set); - return Values.toJSIterator(ctx, res.iterator()); - } - @Native public ObjectValue values(Context ctx) { - var res = new ArrayList<>(set); - return Values.toJSIterator(ctx, res.iterator()); - } - - @Native public Object add(Object key) { - return set.add(key); - } - @Native public boolean delete(Object key) { - return set.remove(key); - } - @Native public boolean has(Object key) { - return set.contains(key); - } - - @Native public void clear() { - set.clear(); - } - - @NativeGetter public int size() { - return set.size(); - } - - @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { - var keys = new ArrayList<>(set); - - for (var el : keys) func.call(ctx, thisArg, el, el, this); - } - - @Native public SetLib(Context ctx, Object iterable) { - for (var el : Values.fromJSIterator(ctx, iterable)) add(el); - } -} +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; + +@Native("Set") public class SetLib { + private LinkedHashSet set = new LinkedHashSet<>(); + + @Native("@@Symbol.typeName") public final String name = "Set"; + @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { + return this.values(ctx); + } + + @Native public ObjectValue entries(Context ctx) { + return ArrayValue.of(ctx, set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList())); + } + @Native public ObjectValue keys(Context ctx) { + return ArrayValue.of(ctx, set); + } + @Native public ObjectValue values(Context ctx) { + return ArrayValue.of(ctx, set); + } + + @Native public Object add(Object key) { + return set.add(key); + } + @Native public boolean delete(Object key) { + return set.remove(key); + } + @Native public boolean has(Object key) { + return set.contains(key); + } + + @Native public void clear() { + set.clear(); + } + + @NativeGetter public int size() { + return set.size(); + } + + @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { + var keys = new ArrayList<>(set); + + for (var el : keys) func.call(ctx, thisArg, el, el, this); + } + + @Native public SetLib(Context ctx, Object iterable) { + for (var el : Values.fromJSIterator(ctx, iterable)) add(el); + } +} diff --git a/src/me/topchetoeu/jscript/mapping/SourceMap.java b/src/me/topchetoeu/jscript/mapping/SourceMap.java new file mode 100644 index 0000000..2c2220b --- /dev/null +++ b/src/me/topchetoeu/jscript/mapping/SourceMap.java @@ -0,0 +1,108 @@ +package me.topchetoeu.jscript.mapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.json.JSON; + +public class SourceMap { + private final TreeMap origToComp = new TreeMap<>(); + private final TreeMap compToOrig = new TreeMap<>(); + + public Location toCompiled(Location loc) { return convert(loc, origToComp); } + public Location toOriginal(Location loc) { return convert(loc, compToOrig); } + + private void add(long orig, long comp) { + var a = origToComp.remove(orig); + var b = compToOrig.remove(comp); + + if (b != null) origToComp.remove(b); + if (a != null) compToOrig.remove(a); + + origToComp.put(orig, comp); + compToOrig.put(comp, orig); + } + + public SourceMap apply(SourceMap map) { + var res = new SourceMap(); + + for (var el : new ArrayList<>(origToComp.entrySet())) { + var mapped = convert(el.getValue(), map.origToComp); + add(el.getKey(), mapped); + } + for (var el : new ArrayList<>(compToOrig.entrySet())) { + var mapped = convert(el.getKey(), map.compToOrig); + add(el.getValue(), mapped); + } + + return res; + } + + public SourceMap clone() { + var res = new SourceMap(); + res.origToComp.putAll(this.origToComp); + res.compToOrig.putAll(this.compToOrig); + return res; + } + + public static SourceMap parse(String raw) { + var mapping = VLQ.decodeMapping(raw); + var res = new SourceMap(); + + var compRow = 0l; + var compCol = 0l; + + for (var origRow = 0; origRow < mapping.length; origRow++) { + var origCol = 0; + + for (var rawSeg : mapping[origRow]) { + if (rawSeg.length > 1 && rawSeg[1] != 0) throw new IllegalArgumentException("Source mapping is to more than one files."); + origCol += rawSeg.length > 0 ? rawSeg[0] : 0; + compRow += rawSeg.length > 2 ? rawSeg[2] : 0; + compCol += rawSeg.length > 3 ? rawSeg[3] : 0; + + var compPacked = ((long)compRow << 32) | compCol; + var origPacked = ((long)origRow << 32) | origCol; + + res.add(origPacked, compPacked); + } + } + + return res; + } + public static List getSources(String raw) { + var json = JSON.parse(null, raw).map(); + return json + .list("sourcesContent") + .stream() + .map(v -> v.string()) + .collect(Collectors.toList()); + } + + public static SourceMap chain(SourceMap ...maps) { + if (maps.length == 0) return null; + var res = maps[0]; + + for (var i = 1; i < maps.length; i++) res = res.apply(maps[i]); + + return res; + } + + private static Long convert(long packed, TreeMap map) { + if (map.containsKey(packed)) return map.get(packed); + var key = map.floorKey(packed); + if (key == null) return null; + else return map.get(key); + } + + private static Location convert(Location loc, TreeMap map) { + var packed = ((loc.line() - 1l) << 32) | (loc.start() - 1); + var resPacked = convert(packed, map); + + if (resPacked == null) return null; + else return new Location((int)(resPacked >> 32) + 1, (int)(resPacked & 0xFFFF) + 1, loc.filename()); + } +} diff --git a/src/me/topchetoeu/jscript/mapping/VLQ.java b/src/me/topchetoeu/jscript/mapping/VLQ.java new file mode 100644 index 0000000..e9b68db --- /dev/null +++ b/src/me/topchetoeu/jscript/mapping/VLQ.java @@ -0,0 +1,95 @@ +package me.topchetoeu.jscript.mapping; + +import java.util.ArrayList; +import java.util.List; + +public class VLQ { + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + private static long[] toArray(List list) { + var arr = new long[list.size()]; + for (var i = 0; i < list.size(); i++) arr[i] = list.get(i); + return arr; + } + + public static String encode(long... arr) { + var raw = new StringBuilder(); + + for (var data : arr) { + var b = data < 0 ? 1 : 0; + data = Math.abs(data); + b |= (int)(data & 0b1111) << 1; + data >>= 4; + b |= data > 0 ? 0x20 : 0;; + raw.append(ALPHABET.charAt(b)); + + while (data > 0) { + b = (int)(data & 0b11111); + data >>= 5; + b |= data > 0 ? 0x20 : 0; + raw.append(ALPHABET.charAt(b)); + } + } + + return raw.toString(); + } + public static long[] decode(String val) { + if (val.length() == 0) return new long[0]; + + var list = new ArrayList(); + + for (var i = 0; i < val.length();) { + var sign = 1; + var curr = ALPHABET.indexOf(val.charAt(i++)); + var cont = (curr & 0x20) == 0x20; + if ((curr & 1) == 1) sign = -1; + long res = (curr & 0b11110) >> 1; + var n = 4; + + for (; i < val.length() && cont;) { + curr = ALPHABET.indexOf(val.charAt(i++)); + cont = (curr & 0x20) == 0x20; + res |= (curr & 0b11111) << n; + n += 5; + if (!cont) break; + } + + list.add(res * sign); + } + + return toArray(list); + } + + public static String encodeMapping(long[][][] arr) { + var res = new StringBuilder(); + var semicolon = false; + + for (var line : arr) { + var comma = false; + + if (semicolon) res.append(";"); + semicolon = true; + + for (var el : line) { + if (comma) res.append(","); + comma = true; + res.append(encode(el)); + } + } + + return res.toString(); + } + public static long[][][] decodeMapping(String val) { + var lines = new ArrayList(); + + for (var line : val.split(";", -1)) { + var elements = new ArrayList(); + for (var el : line.split(",", -1)) { + elements.add(decode(el)); + } + lines.add(elements.toArray(long[][]::new)); + } + + return lines.toArray(long[][][]::new); + } +} diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index f57ab7e..e0fb24e 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -17,8 +17,7 @@ import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.values.*; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Operation; -import me.topchetoeu.jscript.engine.scope.ValueVariable; -import me.topchetoeu.jscript.engine.values.CodeFunction; +import me.topchetoeu.jscript.engine.scope.LocalScopeRecord; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.parsing.ParseRes.State; @@ -90,7 +89,6 @@ public class Parsing { // We allow yield and await, because they're part of the custom async and generator functions } - public static boolean isDigit(char c) { return c >= '0' && c <= '9'; } @@ -396,7 +394,7 @@ public class Parsing { return tokens; } - private static int fromHex(char c) { + public static int fromHex(char c) { if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= '0' && c <= '9') return c - '0'; @@ -807,9 +805,11 @@ public class Parsing { if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res); n += res.n; + var end = getLoc(filename, tokens, i + n - 1); + return ParseRes.res(new ObjProp( name, access, - new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result) + new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result) ), n); } public static ParseRes parseObject(Filename filename, List tokens, int i) { @@ -869,7 +869,7 @@ public class Parsing { return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); } - public static ParseRes parseNew(Filename filename, List tokens, int i) { + public static ParseRes parseNew(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var n = 0; if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); @@ -880,10 +880,10 @@ public class Parsing { var callRes = parseCall(filename, tokens, i + n, valRes.result, 0); n += callRes.n; if (callRes.isError()) return callRes.transform(); - else if (callRes.isFailed()) return ParseRes.res(new NewStatement(loc, valRes.result), n); + else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); var call = (CallStatement)callRes.result; - return ParseRes.res(new NewStatement(loc, call.func, call.args), n); + return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n); } public static ParseRes parseTypeof(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); @@ -896,7 +896,7 @@ public class Parsing { return ParseRes.res(new TypeofStatement(loc, valRes.result), n); } - public static ParseRes parseVoid(Filename filename, List tokens, int i) { + public static ParseRes parseVoid(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var n = 0; if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); @@ -905,7 +905,7 @@ public class Parsing { if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes); n += valRes.n; - return ParseRes.res(new VoidStatement(loc, valRes.result), n); + return ParseRes.res(new DiscardStatement(loc, valRes.result), n); } public static ParseRes parseDelete(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); @@ -966,8 +966,9 @@ public class Parsing { var res = parseCompound(filename, tokens, i + n); n += res.n; + var end = getLoc(filename, tokens, i + n - 1); - if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, name, args.toArray(String[]::new), res.result), n); + if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n); else return ParseRes.error(loc, "Expected a compound statement for function.", res); } @@ -1187,7 +1188,7 @@ public class Parsing { else return ParseRes.failed(); } - return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n); + return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); } public static ParseRes parsePostfixChange(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); @@ -1233,7 +1234,7 @@ public class Parsing { return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); } - public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); var n = 0; @@ -1244,7 +1245,7 @@ public class Parsing { if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res); n += res.n; - return ParseRes.res(new CommaStatement(loc, prev, res.result), n); + return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n); } public static ParseRes parseTernary(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); @@ -1510,7 +1511,7 @@ public class Parsing { statements.add(res.result); } - return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); + return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); } public static ParseRes parseLabel(List tokens, int i) { int n = 0; @@ -1691,7 +1692,7 @@ public class Parsing { if (isOperator(tokens, i + n, Operator.SEMICOLON)) { n++; - decl = new CompoundStatement(loc); + decl = new CompoundStatement(loc, false); } else { var declRes = ParseRes.any( @@ -1717,7 +1718,7 @@ public class Parsing { if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { n++; - inc = new CompoundStatement(loc); + inc = new CompoundStatement(loc, false); } else { var incRes = parseValue(filename, tokens, i + n, 0); @@ -1830,7 +1831,7 @@ public class Parsing { } public static ParseRes parseStatement(Filename filename, List tokens, int i) { - if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i)), 1); + if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i), false), 1); if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed."); return ParseRes.any( parseVariableDeclare(filename, tokens, i), @@ -1873,38 +1874,30 @@ public class Parsing { return list.toArray(Statement[]::new); } - public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Statement ...statements) { - var target = environment.global.globalChild(); - var subscope = target.child(); - var res = new CompileTarget(funcs, breakpoints); - var body = new CompoundStatement(null, statements); - if (body instanceof CompoundStatement) body = (CompoundStatement)body; - else body = new CompoundStatement(null, new Statement[] { body }); + public static CompileTarget compile(Environment environment, Statement ...statements) { + var subscope = new LocalScopeRecord(); + var target = new CompileTarget(new HashMap<>(), new TreeSet<>()); + var stm = new CompoundStatement(null, true, statements); subscope.define("this"); subscope.define("arguments"); - body.declare(target); - try { - body.compile(res, subscope, true); - FunctionStatement.checkBreakAndCont(res, 0); + stm.compile(target, subscope, true); + FunctionStatement.checkBreakAndCont(target, 0); } catch (SyntaxException e) { - res.target.clear(); - res.add(Instruction.throwSyntax(e)); + target.target.clear(); + target.add(Instruction.throwSyntax(e.loc, e)); } - res.add(Instruction.ret()); + target.add(Instruction.ret(stm.loc())); + target.functions.put(0l, new FunctionBody(subscope.localsCount(), 0, target.array(), subscope.captures(), subscope.locals())); - return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals())); + return target; } - public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Filename filename, String raw) { - try { - return compile(funcs, breakpoints, environment, parse(filename, raw)); - } - catch (SyntaxException e) { - return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new FunctionBody(new Instruction[] { Instruction.throwSyntax(e).locate(e.loc) })); - } + public static CompileTarget compile(Environment environment, Filename filename, String raw) { + try { return compile(environment, parse(filename, raw)); } + catch (SyntaxException e) { return compile(environment, new ThrowSyntaxStatement(e)); } } }