From da4b35f5066d096336864b464c4f9b95e6deb8c3 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:31:50 +0300 Subject: [PATCH 01/23] feat: Create filesystem interface and a physical filesystem --- lib/core.ts | 1 + lib/values/errors.ts | 7 +- .../jscript/exceptions/EngineException.java | 16 ++-- .../jscript/filesystem/EntryType.java | 7 ++ .../topchetoeu/jscript/filesystem/File.java | 27 +++++++ .../jscript/filesystem/Filesystem.java | 10 +++ .../jscript/filesystem/InaccessibleFile.java | 35 +++++++++ .../jscript/filesystem/MemoryFile.java | 53 +++++++++++++ .../jscript/filesystem/Permissions.java | 17 +++++ .../jscript/filesystem/PhysicalFile.java | 51 +++++++++++++ .../filesystem/PhysicalFilesystem.java | 74 +++++++++++++++++++ .../topchetoeu/jscript/parsing/Parsing.java | 69 ++++++++++++----- 12 files changed, 340 insertions(+), 27 deletions(-) create mode 100644 src/me/topchetoeu/jscript/filesystem/EntryType.java create mode 100644 src/me/topchetoeu/jscript/filesystem/File.java create mode 100644 src/me/topchetoeu/jscript/filesystem/Filesystem.java create mode 100644 src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java create mode 100644 src/me/topchetoeu/jscript/filesystem/MemoryFile.java create mode 100644 src/me/topchetoeu/jscript/filesystem/Permissions.java create mode 100644 src/me/topchetoeu/jscript/filesystem/PhysicalFile.java create mode 100644 src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java diff --git a/lib/core.ts b/lib/core.ts index 2dc232a..0199814 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -37,6 +37,7 @@ interface Internals { var env: Environment = arguments[0], internals: Internals = arguments[1]; globalThis.log = internals.constructor.log; +var i = 0.0; try { run('values/object'); diff --git a/lib/values/errors.ts b/lib/values/errors.ts index 4bfc7e5..a8d1ef0 100644 --- a/lib/values/errors.ts +++ b/lib/values/errors.ts @@ -2,11 +2,12 @@ define("values/errors", () => { var Error = env.global.Error = function Error(msg: string) { if (msg === undefined) msg = ''; else msg += ''; - - return Object.setPrototypeOf({ + + return { message: msg, stack: [] as string[], - }, Error.prototype); + __proto__: Error.prototype, + } as any; } as ErrorConstructor; setConstr(Error.prototype, Error); diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index 75adb6a..6119a73 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -44,8 +44,9 @@ public class EngineException extends RuntimeException { return ss.toString(); } - private static Object err(String msg, PlaceholderProto proto) { + private static Object err(String name, String msg, PlaceholderProto proto) { var res = new ObjectValue(proto); + if (name != null) res.defineProperty(null, "name", name); res.defineProperty(null, "message", msg); return res; } @@ -57,19 +58,22 @@ public class EngineException extends RuntimeException { this.cause = null; } + public static EngineException ofError(String name, String msg) { + return new EngineException(err(name, msg, PlaceholderProto.ERROR)); + } public static EngineException ofError(String msg) { - return new EngineException(err(msg, PlaceholderProto.ERROR)); + return new EngineException(err(null, msg, PlaceholderProto.ERROR)); } public static EngineException ofSyntax(SyntaxException e) { - return new EngineException(err(e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc); + return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc); } public static EngineException ofSyntax(String msg) { - return new EngineException(err(msg, PlaceholderProto.SYNTAX_ERROR)); + return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); } public static EngineException ofType(String msg) { - return new EngineException(err(msg, PlaceholderProto.TYPE_ERROR)); + return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR)); } public static EngineException ofRange(String msg) { - return new EngineException(err(msg, PlaceholderProto.RANGE_ERROR)); + return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR)); } } diff --git a/src/me/topchetoeu/jscript/filesystem/EntryType.java b/src/me/topchetoeu/jscript/filesystem/EntryType.java new file mode 100644 index 0000000..26e255a --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/EntryType.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.filesystem; + +public enum EntryType { + NONE, + FILE, + FOLDER, +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/File.java b/src/me/topchetoeu/jscript/filesystem/File.java new file mode 100644 index 0000000..b7a14a8 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/File.java @@ -0,0 +1,27 @@ +package me.topchetoeu.jscript.filesystem; + +import java.io.IOException; + +public interface File { + int read() throws IOException, InterruptedException; + boolean write(byte val) throws IOException, InterruptedException; + long tell() throws IOException, InterruptedException; + void seek(long offset, int pos) throws IOException, InterruptedException; + void close() throws IOException, InterruptedException; + Permissions perms(); + + default String readToString() throws IOException, InterruptedException { + seek(0, 2); + long len = tell(); + if (len < 0) return null; + + seek(0, 0); + byte[] res = new byte[(int)len]; + + for (var i = 0; i < len; i++) { + res[i] = (byte)read(); + } + + return new String(res); + } +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/Filesystem.java b/src/me/topchetoeu/jscript/filesystem/Filesystem.java new file mode 100644 index 0000000..02ae9d5 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/Filesystem.java @@ -0,0 +1,10 @@ +package me.topchetoeu.jscript.filesystem; + +import java.io.IOException; + +public interface Filesystem { + File open(String path) throws IOException, InterruptedException; + boolean mkdir(String path) throws IOException, InterruptedException; + EntryType type(String path) throws IOException, InterruptedException; + boolean rm(String path) throws IOException, InterruptedException; +} diff --git a/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java b/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java new file mode 100644 index 0000000..2cc59b4 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java @@ -0,0 +1,35 @@ +package me.topchetoeu.jscript.filesystem; + +import java.io.IOException; + +public class InaccessibleFile implements File { + public static final InaccessibleFile INSTANCE = new InaccessibleFile(); + + @Override + public int read() throws IOException, InterruptedException { + return -1; + } + + @Override + public boolean write(byte val) throws IOException, InterruptedException { + return false; + } + + @Override + public long tell() throws IOException, InterruptedException { + return 0; + } + + @Override + public void seek(long offset, int pos) throws IOException, InterruptedException { } + + @Override + public void close() throws IOException, InterruptedException { } + + @Override + public Permissions perms() { + return Permissions.NONE; + } + + private InaccessibleFile() { } +} diff --git a/src/me/topchetoeu/jscript/filesystem/MemoryFile.java b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java new file mode 100644 index 0000000..c414a7e --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java @@ -0,0 +1,53 @@ +package me.topchetoeu.jscript.filesystem; + +import java.io.IOException; + +public class MemoryFile implements File { + private int ptr; + private Permissions mode; + public final byte[] data; + + @Override + public int read() throws IOException, InterruptedException { + if (data == null || !mode.readable || ptr >= data.length) return -1; + return data[ptr++]; + } + + @Override + public boolean write(byte val) throws IOException, InterruptedException { + if (data == null || !mode.writable || ptr >= data.length) return false; + data[ptr++] = val; + return true; + } + + @Override + public long tell() throws IOException, InterruptedException { + return ptr; + } + + @Override + public void seek(long offset, int pos) throws IOException, InterruptedException { + if (data == null) return; + + 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() throws IOException, InterruptedException { + mode = null; + ptr = 0; + } + + @Override + public Permissions perms() { + if (data == null) return Permissions.NONE; + return mode; + } + + public MemoryFile(byte[] buff, Permissions mode) { + this.data = buff; + this.mode = mode; + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/Permissions.java b/src/me/topchetoeu/jscript/filesystem/Permissions.java new file mode 100644 index 0000000..5a5ff34 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/Permissions.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.filesystem; + +public enum Permissions { + NONE("", false, false), + READ("r", true, false), + READ_WRITE("rw", true, true); + + public final String readMode; + public final boolean readable; + public final boolean writable; + + private Permissions(String mode, boolean r, boolean w) { + this.readMode = mode; + this.readable = r; + this.writable = w; + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java new file mode 100644 index 0000000..cb5d067 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java @@ -0,0 +1,51 @@ +package me.topchetoeu.jscript.filesystem; + +import java.io.IOException; +import java.io.RandomAccessFile; + +public class PhysicalFile implements File { + private RandomAccessFile file; + private Permissions perms; + + @Override + public int read() throws IOException, InterruptedException { + if (file == null || !perms.readable) return -1; + else return file.read(); + } + + @Override + public boolean write(byte val) throws IOException, InterruptedException { + if (file == null || !perms.writable) return false; + file.write(val); + return true; + } + + @Override + public long tell() throws IOException, InterruptedException { + if (file == null) return 0; + return file.getFilePointer(); + } + @Override + public void seek(long offset, int pos) throws IOException, InterruptedException { + if (file == null) return; + if (pos == 0) file.seek(pos); + else if (pos == 1) file.seek(file.getFilePointer() + pos); + else if (pos == 2) file.seek(file.length() + pos); + } + + @Override + public void close() throws IOException, InterruptedException { + if (file == null) return; + file.close(); + } + + @Override + public Permissions perms() { return perms; } + + public PhysicalFile(String path, Permissions mode) throws IOException { + if (mode == Permissions.NONE) file = null; + else file = new RandomAccessFile(path, mode.readMode); + + perms = mode; + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java new file mode 100644 index 0000000..0241676 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java @@ -0,0 +1,74 @@ +package me.topchetoeu.jscript.filesystem; + +import java.io.IOException; +import java.nio.file.Path; + +public class PhysicalFilesystem implements Filesystem { + public final Path root; + + private Permissions getPerms(Path path) { + var file = path.toFile(); + if (!path.startsWith(root)) return Permissions.NONE; + if (file.canRead() && file.canWrite()) return Permissions.READ_WRITE; + if (file.canRead()) return Permissions.READ; + + return Permissions.NONE; + } + private Path getPath(String name) { + return root.resolve(name); + } + + @Override + public File open(String path) throws IOException, InterruptedException { + var _path = root.resolve(path); + + var perms = getPerms(_path); + if (perms == Permissions.NONE) return InaccessibleFile.INSTANCE; + + var f = _path.toFile(); + + if (f.isDirectory()) { + var res = new StringBuilder(); + + for (var child : f.listFiles()) res.append(child.toString()).append('\n'); + + return new MemoryFile(res.toString().getBytes(), Permissions.READ); + } + else return new PhysicalFile(path, perms); + } + + @Override + public boolean mkdir(String path) throws IOException, InterruptedException { + var _path = getPath(path); + var perms = getPerms(_path); + var f = _path.toFile(); + + if (!perms.writable) return false; + else return f.mkdir(); + } + + @Override + public EntryType type(String path) throws IOException, InterruptedException { + var _path = getPath(path); + var perms = getPerms(_path); + var f = _path.toFile(); + + if (perms == Permissions.NONE) return EntryType.NONE; + else if (f.isFile()) return EntryType.FILE; + else return EntryType.FOLDER; + } + + @Override + public boolean rm(String path) throws IOException, InterruptedException { + var _path = getPath(path); + var perms = getPerms(_path); + var f = _path.toFile(); + + if (!perms.writable) return false; + else return f.delete(); + } + + public PhysicalFilesystem(Path root) { + this.root = root; + } +} diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index cf95238..0672cb5 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -516,12 +516,59 @@ public class Parsing { for (int i = 2; i < literal.length(); i++) { res *= 16; - res += fromHex(literal.charAt(i)); + int dig = fromHex(literal.charAt(i)); + res += dig; } return res; } + public static Double parseNumber(boolean octals, String value) { + if (value.startsWith("0x") || value.startsWith("0X")) { + if (value.length() == 2) return null; + return parseHex(value); + } + if (value.endsWith("e") || value.endsWith("E") || value.endsWith("-")) return null; + + int i = 0; + double res = 0, dotDivisor = 1; + boolean e = false, dot = false; + int exponent = 0; + + for (; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '.') { dot = true; break; } + if (c == 'e') { e = true; break; } + if (!isDigit(c)) break; + + res = res * 10 + c - '0'; + } + + if (dot) for (i++; i < value.length(); i++) { + char c = value.charAt(i); + if (c == 'e') { e = true; break; } + if (!isDigit(c)) break; + + res += (c - '0') / (dotDivisor *= 10); + } + + if (e) for (i++; i < value.length(); i++) { + char c = value.charAt(i); + if (!isDigit(c)) break; + exponent = 10 * exponent + c - '0'; + } + + if (exponent < 0) for (int j = 0; j < -exponent; j++) res /= 10; + else for (int j = 0; j < exponent; j++) res *= 10; + + return res; + } + private static double parseNumber(Location loc, String value) { + var res = parseNumber(false, value); + if (res == null) throw new SyntaxException(loc, "Invalid number format."); + else return res; + } + private static List parseTokens(String filename, Collection tokens) { var res = new ArrayList(); @@ -529,28 +576,14 @@ public class Parsing { var loc = new Location(el.line, el.start, filename); switch (el.type) { case LITERAL: res.add(Token.identifier(el.line, el.start, el.value)); break; - case NUMBER: - if (el.value.startsWith("0x") || el.value.startsWith("0X")) { - if (el.value.endsWith("x") || el.value.endsWith("X")) { - throw new SyntaxException(loc, "Invalid number format."); - } - res.add(Token.number(el.line, el.start, parseHex(el.value))); break; - } - if ( - el.value.endsWith("e") || el.value.endsWith("E") || el.value.endsWith("-") - ) throw new SyntaxException(loc, "Invalid number format."); - else res.add(Token.number(el.line, el.start, Double.parseDouble(el.value))); break; + case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value))); break; + case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value))); break; + case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value))); break; case OPERATOR: Operator op = Operator.parse(el.value); if (op == null) throw new SyntaxException(loc, String.format("Unrecognized operator '%s'.", el.value)); res.add(Token.operator(el.line, el.start, op)); break; - case STRING: - res.add(Token.string(el.line, el.start, parseString(loc, el.value))); - break; - case REGEX: - res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value))); - break; } } -- 2.45.2 From 356a5a5b78d4885d2296b827cd41d187dbea2ef7 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 20 Sep 2023 23:02:23 +0300 Subject: [PATCH 02/23] feat: a lot of typescript corelibs translated to java --- lib/core.ts | 8 +- lib/promise.ts | 371 +++++++++--------- lib/timeout.ts | 4 +- lib/values/function.ts | 137 +------ lib/values/object.ts | 225 +---------- lib/values/symbol.ts | 24 +- src/me/topchetoeu/jscript/Main.java | 39 +- src/me/topchetoeu/jscript/engine/Context.java | 10 +- src/me/topchetoeu/jscript/engine/Engine.java | 6 +- ...{FunctionContext.java => Environment.java} | 25 +- .../jscript/engine/frame/CodeFrame.java | 7 +- .../jscript/engine/frame/Runners.java | 38 +- .../jscript/engine/values/CodeFunction.java | 6 +- .../jscript/engine/values/NativeWrapper.java | 3 +- .../jscript/engine/values/ObjectValue.java | 32 +- .../jscript/engine/values/Values.java | 92 +++-- .../jscript/exceptions/ConvertException.java | 11 + .../jscript/exceptions/EngineException.java | 5 + src/me/topchetoeu/jscript/interop/Native.java | 1 + .../jscript/interop/NativeConstructor.java | 13 + .../jscript/interop/NativeGetter.java | 1 + .../jscript/interop/NativeSetter.java | 1 + .../jscript/interop/NativeTypeRegister.java | 35 +- .../topchetoeu/jscript/interop/Overload.java | 32 +- .../jscript/interop/OverloadFunction.java | 62 ++- .../topchetoeu/jscript/parsing/Parsing.java | 6 +- .../polyfills/AsyncFunctionPolyfill.java | 76 ++++ .../polyfills/AsyncGeneratorPolyfill.java | 133 +++++++ .../jscript/polyfills/FunctionPolyfill.java | 60 +++ ...orFunction.java => GeneratorPolyfill.java} | 10 +- .../jscript/polyfills/Internals.java | 17 +- .../jscript/polyfills/ObjectPolyfill.java | 212 ++++++++++ .../jscript/polyfills/PromisePolyfill.java | 346 ++++++++++++++++ 33 files changed, 1330 insertions(+), 718 deletions(-) rename src/me/topchetoeu/jscript/engine/{FunctionContext.java => Environment.java} (81%) create mode 100644 src/me/topchetoeu/jscript/exceptions/ConvertException.java create mode 100644 src/me/topchetoeu/jscript/interop/NativeConstructor.java create mode 100644 src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java create mode 100644 src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java create mode 100644 src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java rename src/me/topchetoeu/jscript/polyfills/{GeneratorFunction.java => GeneratorPolyfill.java} (91%) create mode 100644 src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java create mode 100644 src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java diff --git a/lib/core.ts b/lib/core.ts index 0199814..7a0c5b4 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -2,12 +2,17 @@ interface Environment { global: typeof globalThis & Record; proto(name: string): object; setProto(name: string, val: object): void; + symbol(name: string): symbol; } interface Internals { + object: ObjectConstructor; + function: FunctionConstructor; + promise: typeof Promise; + markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; setEnv(func: T, env: Environment): T; - apply(func: Function, thisArg: any, args: any[]): any; + apply(func: Function, thisArg: any, args: any[], env?: Environment): any; delay(timeout: number, callback: Function): () => void; pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void; @@ -55,6 +60,7 @@ try { run('timeout'); env.global.log = log; + env.global.NewObject = internals.object; log('Loaded polyfills!'); } diff --git a/lib/promise.ts b/lib/promise.ts index 903c7c2..1f7e8b7 100644 --- a/lib/promise.ts +++ b/lib/promise.ts @@ -1,203 +1,208 @@ define("promise", () => { - const syms = { - callbacks: internals.symbol('Promise.callbacks'), - state: internals.symbol('Promise.state'), - value: internals.symbol('Promise.value'), - handled: internals.symbol('Promise.handled'), - } as { - readonly callbacks: unique symbol, - readonly state: unique symbol, - readonly value: unique symbol, - readonly handled: unique symbol, - } + var Promise = env.global.Promise = internals.promise + return; - type Callback = [ PromiseFulfillFunc, PromiseRejectFunc ]; - enum State { - Pending, - Fulfilled, - Rejected, - } + // const syms = { + // callbacks: internals.symbol('Promise.callbacks'), + // state: internals.symbol('Promise.state'), + // value: internals.symbol('Promise.value'), + // handled: internals.symbol('Promise.handled'), + // } as { + // readonly callbacks: unique symbol, + // readonly state: unique symbol, + // readonly value: unique symbol, + // readonly handled: unique symbol, + // } - function isAwaitable(val: unknown): val is Thenable { - return ( - typeof val === 'object' && - val !== null && - 'then' in val && - typeof val.then === 'function' - ); - } - function resolve(promise: Promise, v: any, state: State) { - if (promise[syms.state] === State.Pending) { - if (typeof v === 'object' && v !== null && 'then' in v && typeof v.then === 'function') { - v.then( - (res: any) => resolve(promise, res, state), - (res: any) => resolve(promise, res, State.Rejected) - ); - return; - } - promise[syms.value] = v; - promise[syms.state] = state; + // type Callback = [ PromiseFulfillFunc, PromiseRejectFunc ]; + // enum State { + // Pending, + // Fulfilled, + // Rejected, + // } - for (let i = 0; i < promise[syms.callbacks]!.length; i++) { - promise[syms.handled] = true; - promise[syms.callbacks]![i][state - 1](v); - } + // function isAwaitable(val: unknown): val is Thenable { + // return ( + // typeof val === 'object' && + // val !== null && + // 'then' in val && + // typeof val.then === 'function' + // ); + // } + // function resolve(promise: Promise, v: any, state: State) { + // if (promise[syms.state] === State.Pending) { + // if (typeof v === 'object' && v !== null && 'then' in v && typeof v.then === 'function') { + // v.then( + // (res: any) => resolve(promise, res, state), + // (res: any) => resolve(promise, res, State.Rejected) + // ); + // return; + // } + // promise[syms.value] = v; + // promise[syms.state] = state; - promise[syms.callbacks] = undefined; + // for (let i = 0; i < promise[syms.callbacks]!.length; i++) { + // promise[syms.handled] = true; + // promise[syms.callbacks]![i][state - 1](v); + // } - internals.pushMessage(true, internals.setEnv(() => { - if (!promise[syms.handled] && state === State.Rejected) { - log('Uncaught (in promise) ' + promise[syms.value]); - } - }, env), undefined, []); - } - } + // promise[syms.callbacks] = undefined; - class Promise { - public static isAwaitable(val: unknown): val is Thenable { - return isAwaitable(val); - } + // internals.pushMessage(true, internals.setEnv(() => { + // if (!promise[syms.handled] && state === State.Rejected) { + // log('Uncaught (in promise) ' + promise[syms.value]); + // } + // }, env), undefined, []); + // } + // } - public static resolve(val: T): Promise> { - return new Promise(res => res(val as any)); - } - public static reject(val: T): Promise> { - return new Promise((_, rej) => rej(val as any)); - } + // class _Promise { + // public static isAwaitable(val: unknown): val is Thenable { + // return isAwaitable(val); + // } - public static race(vals: T[]): Promise> { - if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.race is not variadic.'); - return new Promise((res, rej) => { - for (let i = 0; i < vals.length; i++) { - const val = vals[i]; - if (this.isAwaitable(val)) val.then(res, rej); - else res(val as any); - } - }); - } - public static any(vals: T[]): Promise> { - if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.any is not variadic.'); - return new Promise((res, rej) => { - let n = 0; + // public static resolve(val: T): Promise> { + // return new Promise(res => res(val as any)); + // } + // public static reject(val: T): Promise> { + // return new Promise((_, rej) => rej(val as any)); + // } - for (let i = 0; i < vals.length; i++) { - const val = vals[i]; - if (this.isAwaitable(val)) val.then(res, (err) => { - n++; - if (n === vals.length) throw Error('No promise resolved.'); - }); - else res(val as any); - } + // public static race(vals: T[]): Promise> { + // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.race is not variadic.'); + // return new Promise((res, rej) => { + // for (let i = 0; i < vals.length; i++) { + // const val = vals[i]; + // if (this.isAwaitable(val)) val.then(res, rej); + // else res(val as any); + // } + // }); + // } + // public static any(vals: T[]): Promise> { + // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.any is not variadic.'); + // return new Promise((res, rej) => { + // let n = 0; - if (vals.length === 0) throw Error('No promise resolved.'); - }); - } - public static all(vals: any[]): Promise { - if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.all is not variadic.'); - return new Promise((res, rej) => { - const result: any[] = []; - let n = 0; + // for (let i = 0; i < vals.length; i++) { + // const val = vals[i]; + // if (this.isAwaitable(val)) val.then(res, (err) => { + // n++; + // if (n === vals.length) throw Error('No promise resolved.'); + // }); + // else res(val as any); + // } - for (let i = 0; i < vals.length; i++) { - const val = vals[i]; - if (this.isAwaitable(val)) val.then( - val => { - n++; - result[i] = val; - if (n === vals.length) res(result); - }, - rej - ); - else { - n++; - result[i] = val; - } - } + // if (vals.length === 0) throw Error('No promise resolved.'); + // }); + // } + // public static all(vals: any[]): Promise { + // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.all is not variadic.'); + // return new Promise((res, rej) => { + // const result: any[] = []; + // let n = 0; - if (vals.length === n) res(result); - }); - } - public static allSettled(vals: any[]): Promise { - if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.allSettled is not variadic.'); - return new Promise((res, rej) => { - const result: any[] = []; - let n = 0; + // for (let i = 0; i < vals.length; i++) { + // const val = vals[i]; + // if (this.isAwaitable(val)) val.then( + // val => { + // n++; + // result[i] = val; + // if (n === vals.length) res(result); + // }, + // rej + // ); + // else { + // n++; + // result[i] = val; + // } + // } - for (let i = 0; i < vals.length; i++) { - const value = vals[i]; - if (this.isAwaitable(value)) value.then( - value => { - n++; - result[i] = { status: 'fulfilled', value }; - if (n === vals.length) res(result); - }, - reason => { - n++; - result[i] = { status: 'rejected', reason }; - if (n === vals.length) res(result); - }, - ); - else { - n++; - result[i] = { status: 'fulfilled', value }; - } - } + // if (vals.length === n) res(result); + // }); + // } + // public static allSettled(vals: any[]): Promise { + // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.allSettled is not variadic.'); + // return new Promise((res, rej) => { + // const result: any[] = []; + // let n = 0; - if (vals.length === n) res(result); - }); - } + // for (let i = 0; i < vals.length; i++) { + // const value = vals[i]; + // if (this.isAwaitable(value)) value.then( + // value => { + // n++; + // result[i] = { status: 'fulfilled', value }; + // if (n === vals.length) res(result); + // }, + // reason => { + // n++; + // result[i] = { status: 'rejected', reason }; + // if (n === vals.length) res(result); + // }, + // ); + // else { + // n++; + // result[i] = { status: 'fulfilled', value }; + // } + // } - [syms.callbacks]?: Callback[] = []; - [syms.handled] = false; - [syms.state] = State.Pending; - [syms.value]?: T | unknown; + // if (vals.length === n) res(result); + // }); + // } - public then(onFulfil?: PromiseFulfillFunc, onReject?: PromiseRejectFunc) { - return new Promise((resolve, reject) => { - onFulfil ??= v => v; - onReject ??= v => v; + // [syms.callbacks]?: Callback[] = []; + // [syms.handled] = false; + // [syms.state] = State.Pending; + // [syms.value]?: T | unknown; - const callback = (func: (val: any) => any) => (v: any) => { - try { resolve(func(v)); } - catch (e) { reject(e); } - } - switch (this[syms.state]) { - case State.Pending: - this[syms.callbacks]![this[syms.callbacks]!.length] = [callback(onFulfil), callback(onReject)]; - break; - case State.Fulfilled: - this[syms.handled] = true; - callback(onFulfil)(this[syms.value]); - break; - case State.Rejected: - this[syms.handled] = true; - callback(onReject)(this[syms.value]); - break; - } - }) - } - public catch(func: PromiseRejectFunc) { - return this.then(undefined, func); - } - public finally(func: () => void) { - return this.then( - v => { - func(); - return v; - }, - v => { - func(); - throw v; - } - ); - } + // public then(onFulfil?: PromiseFulfillFunc, onReject?: PromiseRejectFunc) { + // return new Promise((resolve, reject) => { + // onFulfil ??= v => v; + // onReject ??= v => v; - public constructor(func: PromiseFunc) { - internals.pushMessage(true, func, undefined, [ - ((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc, - ((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc - ]); - } - } - env.global.Promise = Promise as any; + // const callback = (func: (val: any) => any) => (v: any) => { + // try { + // resolve(func(v)); + // } + // catch (e) { reject(e); } + // } + // switch (this[syms.state]) { + // case State.Pending: + // this[syms.callbacks]![this[syms.callbacks]!.length] = [callback(onFulfil), callback(onReject)]; + // break; + // case State.Fulfilled: + // this[syms.handled] = true; + // callback(onFulfil)(this[syms.value]); + // break; + // case State.Rejected: + // this[syms.handled] = true; + // callback(onReject)(this[syms.value]); + // break; + // } + // }) + // } + // public catch(func: PromiseRejectFunc) { + // return this.then(undefined, func); + // } + // public finally(func: () => void) { + // return this.then( + // v => { + // func(); + // return v; + // }, + // v => { + // func(); + // throw v; + // } + // ); + // } + + // public constructor(func: PromiseFunc) { + // internals.pushMessage(true, func, undefined, [ + // ((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc, + // ((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc + // ]); + // } + // } + // env.global.Promise = Promise as any; }); diff --git a/lib/timeout.ts b/lib/timeout.ts index 8db82a0..e1f167a 100644 --- a/lib/timeout.ts +++ b/lib/timeout.ts @@ -4,14 +4,14 @@ define("timeout", () => { let timeoutI = 0, intervalI = 0; env.global.setTimeout = (func, delay, ...args) => { - if (typeof func !== 'function') throw new TypeError("func must be a function."); + if (typeof func !== 'function') throw new env.global.TypeError("func must be a function."); delay = (delay ?? 0) - 0; const cancelFunc = internals.delay(delay, () => internals.apply(func, undefined, args)); timeouts[++timeoutI] = cancelFunc; return timeoutI; }; env.global.setInterval = (func, delay, ...args) => { - if (typeof func !== 'function') throw new TypeError("func must be a function."); + if (typeof func !== 'function') throw new env.global.TypeError("func must be a function."); delay = (delay ?? 0) - 0; const i = ++intervalI; diff --git a/lib/values/function.ts b/lib/values/function.ts index 4a161d6..4636d5d 100644 --- a/lib/values/function.ts +++ b/lib/values/function.ts @@ -1,140 +1,5 @@ define("values/function", () => { - var Function = env.global.Function = function() { - throw 'Using the constructor Function() is forbidden.'; - } as unknown as FunctionConstructor; - + var Function = env.global.Function = internals.function; env.setProto('function', Function.prototype); setConstr(Function.prototype, Function); - - setProps(Function.prototype, { - apply(thisArg, args) { - if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.'; - var len = args.length - 0; - let newArgs: any[]; - if (internals.isArray(args)) newArgs = args; - else { - newArgs = []; - - while (len >= 0) { - len--; - newArgs[len] = args[len]; - } - } - - return internals.apply(this, thisArg, newArgs); - }, - call(thisArg, ...args) { - return this.apply(thisArg, args); - }, - bind(thisArg, ...args) { - const func = this; - const res = function() { - const resArgs = []; - - for (let i = 0; i < args.length; i++) { - resArgs[i] = args[i]; - } - for (let i = 0; i < arguments.length; i++) { - resArgs[i + args.length] = arguments[i]; - } - - return func.apply(thisArg, resArgs); - }; - res.name = " " + func.name; - return res; - }, - toString() { - return 'function (...) { ... }'; - }, - }); - setProps(Function, { - async(func) { - if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); - - return function (this: any) { - const args = arguments; - - return new Promise((res, rej) => { - const gen = internals.apply(internals.generator(func as any), this, args as any); - - (function next(type: 'none' | 'err' | 'ret', val?: any) { - try { - let result; - - switch (type) { - case 'err': result = gen.throw(val); break; - case 'ret': result = gen.next(val); break; - case 'none': result = gen.next(); break; - } - if (result.done) res(result.value); - else Promise.resolve(result.value).then( - v => next('ret', v), - v => next('err', v) - ) - } - catch (e) { - rej(e); - } - })('none'); - }); - }; - }, - asyncGenerator(func) { - if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); - - return function(this: any, ...args: any[]) { - const gen = internals.apply(internals.generator((_yield) => func( - val => _yield(['await', val]) as any, - val => _yield(['yield', val]) - )), this, args) as Generator<['await' | 'yield', any]>; - - const next = (resolve: Function, reject: Function, type: 'none' | 'val' | 'ret' | 'err', val?: any) => { - let res; - - try { - switch (type) { - case 'val': res = gen.next(val); break; - case 'ret': res = gen.return(val); break; - case 'err': res = gen.throw(val); break; - default: res = gen.next(); break; - } - } - catch (e) { return reject(e); } - - if (res.done) return { done: true, res: res }; - else if (res.value[0] === 'await') Promise.resolve(res.value[1]).then( - v => next(resolve, reject, 'val', v), - v => next(resolve, reject, 'err', v), - ) - else resolve({ done: false, value: res.value[1] }); - }; - - return { - next() { - const args = arguments; - if (arguments.length === 0) return new Promise((res, rej) => next(res, rej, 'none')); - else return new Promise((res, rej) => next(res, rej, 'val', args[0])); - }, - return: (value) => new Promise((res, rej) => next(res, rej, 'ret', value)), - throw: (value) => new Promise((res, rej) => next(res, rej, 'err', value)), - [env.global.Symbol.asyncIterator]() { return this; } - } - } - }, - generator(func) { - if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); - const gen = internals.generator(func); - return function(this: any, ...args: any[]) { - const it = internals.apply(gen, this, args); - - return { - next: (...args) => internals.apply(it.next, it, args), - return: (val) => internals.apply(it.next, it, [val]), - throw: (val) => internals.apply(it.next, it, [val]), - [env.global.Symbol.iterator]() { return this; } - } - } - } - }) - internals.markSpecial(Function); }); \ No newline at end of file diff --git a/lib/values/object.ts b/lib/values/object.ts index 475e4da..0473a1e 100644 --- a/lib/values/object.ts +++ b/lib/values/object.ts @@ -1,226 +1,5 @@ define("values/object", () => { - var Object = env.global.Object = function(arg: any) { - if (arg === undefined || arg === null) return {}; - else if (typeof arg === 'boolean') return new Boolean(arg); - else if (typeof arg === 'number') return new Number(arg); - else if (typeof arg === 'string') return new String(arg); - return arg; - } as ObjectConstructor; - - env.setProto('object', Object.prototype); + var Object = env.global.Object = internals.object; (Object.prototype as any).__proto__ = null; - setConstr(Object.prototype, Object as any); - - function throwNotObject(obj: any, name: string) { - if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') { - throw new TypeError(`Object.${name} may only be used for objects.`); - } - } - function check(obj: any) { - return typeof obj === 'object' && obj !== null || typeof obj === 'function'; - } - - setProps(Object, { - assign(dst, ...src) { - throwNotObject(dst, 'assign'); - for (let i = 0; i < src.length; i++) { - const obj = src[i]; - throwNotObject(obj, 'assign'); - for (const key of Object.keys(obj)) { - (dst as any)[key] = (obj as any)[key]; - } - } - return dst; - }, - create(obj, props) { - props ??= {}; - return Object.defineProperties({ __proto__: obj }, props as any) as any; - }, - - defineProperty(obj, key, attrib) { - throwNotObject(obj, 'defineProperty'); - if (typeof attrib !== 'object') throw new TypeError('Expected attributes to be an object.'); - - if ('value' in attrib) { - if ('get' in attrib || 'set' in attrib) throw new TypeError('Cannot specify a value and accessors for a property.'); - if (!internals.defineField( - obj, key, - attrib.value, - !!attrib.writable, - !!attrib.enumerable, - !!attrib.configurable - )) throw new TypeError('Can\'t define property \'' + key + '\'.'); - } - else { - if (typeof attrib.get !== 'function' && attrib.get !== undefined) throw new TypeError('Get accessor must be a function.'); - if (typeof attrib.set !== 'function' && attrib.set !== undefined) throw new TypeError('Set accessor must be a function.'); - - if (!internals.defineProp( - obj, key, - attrib.get, - attrib.set, - !!attrib.enumerable, - !!attrib.configurable - )) throw new TypeError('Can\'t define property \'' + key + '\'.'); - } - - return obj; - }, - defineProperties(obj, attrib) { - throwNotObject(obj, 'defineProperties'); - if (typeof attrib !== 'object' && typeof attrib !== 'function') throw 'Expected second argument to be an object.'; - - for (var key in attrib) { - Object.defineProperty(obj, key, attrib[key]); - } - - return obj; - }, - - keys(obj, onlyString) { - return internals.keys(obj, !!(onlyString ?? true)); - }, - entries(obj, onlyString) { - const res = []; - const keys = internals.keys(obj, !!(onlyString ?? true)); - - for (let i = 0; i < keys.length; i++) { - res[i] = [ keys[i], (obj as any)[keys[i]] ]; - } - - return keys; - }, - values(obj, onlyString) { - const res = []; - const keys = internals.keys(obj, !!(onlyString ?? true)); - - for (let i = 0; i < keys.length; i++) { - res[i] = (obj as any)[keys[i]]; - } - - return keys; - }, - - getOwnPropertyDescriptor(obj, key) { - return internals.ownProp(obj, key) as any; - }, - getOwnPropertyDescriptors(obj) { - const res = []; - const keys = internals.ownPropKeys(obj); - - for (let i = 0; i < keys.length; i++) { - res[i] = internals.ownProp(obj, keys[i]); - } - - return res; - }, - - getOwnPropertyNames(obj) { - const arr = internals.ownPropKeys(obj); - const res = []; - - for (let i = 0; i < arr.length; i++) { - if (typeof arr[i] === 'symbol') continue; - res[res.length] = arr[i]; - } - - return res as any; - }, - getOwnPropertySymbols(obj) { - const arr = internals.ownPropKeys(obj); - const res = []; - - for (let i = 0; i < arr.length; i++) { - if (typeof arr[i] !== 'symbol') continue; - res[res.length] = arr[i]; - } - - return res as any; - }, - hasOwn(obj, key) { - const keys = internals.ownPropKeys(obj); - - for (let i = 0; i < keys.length; i++) { - if (keys[i] === key) return true; - } - - return false; - }, - - getPrototypeOf(obj) { - return obj.__proto__; - }, - setPrototypeOf(obj, proto) { - (obj as any).__proto__ = proto; - return obj; - }, - - fromEntries(iterable) { - const res = {} as any; - - for (const el of iterable) { - res[el[0]] = el[1]; - } - - return res; - }, - - preventExtensions(obj) { - throwNotObject(obj, 'preventExtensions'); - internals.lock(obj, 'ext'); - return obj; - }, - seal(obj) { - throwNotObject(obj, 'seal'); - internals.lock(obj, 'seal'); - return obj; - }, - freeze(obj) { - throwNotObject(obj, 'freeze'); - internals.lock(obj, 'freeze'); - return obj; - }, - - isExtensible(obj) { - if (!check(obj)) return false; - return internals.extensible(obj); - }, - isSealed(obj) { - if (!check(obj)) return true; - if (internals.extensible(obj)) return false; - const keys = internals.ownPropKeys(obj); - - for (let i = 0; i < keys.length; i++) { - if (internals.ownProp(obj, keys[i]).configurable) return false; - } - - return true; - }, - isFrozen(obj) { - if (!check(obj)) return true; - if (internals.extensible(obj)) return false; - const keys = internals.ownPropKeys(obj); - - for (let i = 0; i < keys.length; i++) { - const prop = internals.ownProp(obj, keys[i]); - if (prop.configurable) return false; - if ('writable' in prop && prop.writable) return false; - } - - return true; - } - }); - - setProps(Object.prototype, { - valueOf() { - return this; - }, - toString() { - return '[object ' + (this[env.global.Symbol.typeName] ?? 'Unknown') + ']'; - }, - hasOwnProperty(key) { - return Object.hasOwn(this, key); - }, - }); - internals.markSpecial(Object); + env.setProto('object', Object.prototype); }); \ No newline at end of file diff --git a/lib/values/symbol.ts b/lib/values/symbol.ts index 64b4d5e..dff4b4f 100644 --- a/lib/values/symbol.ts +++ b/lib/values/symbol.ts @@ -2,8 +2,8 @@ define("values/symbol", () => { const symbols: Record = { }; var Symbol = env.global.Symbol = function(this: any, val?: string) { - if (this !== undefined && this !== null) throw new TypeError("Symbol may not be called with 'new'."); - if (typeof val !== 'string' && val !== undefined) throw new TypeError('val must be a string or undefined.'); + if (this !== undefined && this !== null) throw new env.global.TypeError("Symbol may not be called with 'new'."); + if (typeof val !== 'string' && val !== undefined) throw new env.global.TypeError('val must be a string or undefined.'); return internals.symbol(val); } as SymbolConstructor; @@ -12,23 +12,23 @@ define("values/symbol", () => { setProps(Symbol, { for(key) { - if (typeof key !== 'string' && key !== undefined) throw new TypeError('key must be a string or undefined.'); + if (typeof key !== 'string' && key !== undefined) throw new env.global.TypeError('key must be a string or undefined.'); if (key in symbols) return symbols[key]; else return symbols[key] = internals.symbol(key); }, keyFor(sym) { - if (typeof sym !== 'symbol') throw new TypeError('sym must be a symbol.'); + if (typeof sym !== 'symbol') throw new env.global.TypeError('sym must be a symbol.'); return internals.symbolToString(sym); }, - typeName: Symbol("Symbol.name") as any, - replace: Symbol('Symbol.replace') as any, - match: Symbol('Symbol.match') as any, - matchAll: Symbol('Symbol.matchAll') as any, - split: Symbol('Symbol.split') as any, - search: Symbol('Symbol.search') as any, - iterator: Symbol('Symbol.iterator') as any, - asyncIterator: Symbol('Symbol.asyncIterator') as any, + typeName: env.symbol("Symbol.typeName") as any, + replace: env.symbol('Symbol.replace') as any, + match: env.symbol('Symbol.match') as any, + matchAll: env.symbol('Symbol.matchAll') as any, + split: env.symbol('Symbol.split') as any, + search: env.symbol('Symbol.search') as any, + iterator: env.symbol('Symbol.iterator') as any, + asyncIterator: env.symbol('Symbol.asyncIterator') as any, }); internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false); diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 09657b8..6f580d1 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -10,7 +10,7 @@ import java.nio.file.Path; import me.topchetoeu.jscript.engine.MessageContext; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; -import me.topchetoeu.jscript.engine.FunctionContext; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; @@ -21,7 +21,7 @@ import me.topchetoeu.jscript.polyfills.Internals; public class Main { static Thread task; static Engine engine; - static FunctionContext env; + static Environment env; public static String streamToString(InputStream in) { try { @@ -47,37 +47,14 @@ public class Main { private static Observer valuePrinter = new Observer() { public void next(Object data) { - try { - Values.printValue(null, data); - } + try { Values.printValue(null, data); } catch (InterruptedException e) { } System.out.println(); } public void error(RuntimeException err) { - try { - try { - if (err instanceof EngineException) { - System.out.println("Uncaught " + ((EngineException)err).toString(new Context(null, new MessageContext(engine)))); - } - else if (err instanceof SyntaxException) { - System.out.println("Syntax error:" + ((SyntaxException)err).msg); - } - else if (err.getCause() instanceof InterruptedException) return; - else { - System.out.println("Internal error ocurred:"); - err.printStackTrace(); - } - } - catch (EngineException ex) { - System.out.println("Uncaught "); - Values.printValue(null, ((EngineException)err).value); - System.out.println(); - } - } - catch (InterruptedException ex) { - return; - } + try { Values.printError(err, null); } + catch (InterruptedException ex) { return; } } }; @@ -85,8 +62,10 @@ public class Main { System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); var in = new BufferedReader(new InputStreamReader(System.in)); engine = new Engine(); - env = new FunctionContext(null, null, null); - var builderEnv = new FunctionContext(null, new NativeTypeRegister(), null); + + // TODO: Replace type register with safer accessor + env = new Environment(null, new NativeTypeRegister(), null); + var builderEnv = new Environment(null, new NativeTypeRegister(), null); var exited = new boolean[1]; env.global.define("exit", ctx -> { diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 417b115..801cbc7 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -5,16 +5,16 @@ import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.parsing.Parsing; public class Context { - public final FunctionContext function; + public final Environment env; public final MessageContext message; public FunctionValue compile(String filename, String raw) throws InterruptedException { - var res = Values.toString(this, function.compile.call(this, null, raw, filename)); - return Parsing.compile(function, filename, res); + var res = Values.toString(this, env.compile.call(this, null, raw, filename)); + return Parsing.compile(env, filename, res); } - public Context(FunctionContext funcCtx, MessageContext msgCtx) { - this.function = funcCtx; + public Context(Environment funcCtx, MessageContext msgCtx) { + this.env = funcCtx; this.message = msgCtx; } } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 3e0b388..7b87e73 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -11,7 +11,7 @@ public class Engine { private class UncompiledFunction extends FunctionValue { public final String filename; public final String raw; - public final FunctionContext ctx; + public final Environment ctx; @Override public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { @@ -19,7 +19,7 @@ public class Engine { return ctx.compile(filename, raw).call(ctx, thisArg, args); } - public UncompiledFunction(FunctionContext ctx, String filename, String raw) { + public UncompiledFunction(Environment ctx, String filename, String raw) { super(filename, 0); this.filename = filename; this.raw = raw; @@ -109,7 +109,7 @@ public class Engine { return msg.notifier; } public Awaitable pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) { - return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.function, filename, raw), thisArg, args); + return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args); } // public Engine() { diff --git a/src/me/topchetoeu/jscript/engine/FunctionContext.java b/src/me/topchetoeu/jscript/engine/Environment.java similarity index 81% rename from src/me/topchetoeu/jscript/engine/FunctionContext.java rename to src/me/topchetoeu/jscript/engine/Environment.java index e21b7d9..164112d 100644 --- a/src/me/topchetoeu/jscript/engine/FunctionContext.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -6,15 +6,20 @@ 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.interop.Native; import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeSetter; -public class FunctionContext { +public class Environment { private HashMap prototypes = new HashMap<>(); public GlobalScope global; public WrappersProvider wrappersProvider; + /** + * NOTE: This is not the register for Symbol.for, but for the symbols like Symbol.iterator + */ + public HashMap symbols = new HashMap<>(); @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @@ -26,6 +31,16 @@ public class FunctionContext { @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; + } + } + // @Native public ObjectValue arrayPrototype = new ObjectValue(); // @Native public ObjectValue boolPrototype = new ObjectValue(); // @Native public ObjectValue functionPrototype = new ObjectValue(); @@ -48,21 +63,21 @@ public class FunctionContext { } @Native - public FunctionContext fork() { - var res = new FunctionContext(compile, wrappersProvider, global); + public Environment fork() { + var res = new Environment(compile, wrappersProvider, global); res.regexConstructor = regexConstructor; res.prototypes = new HashMap<>(prototypes); return res; } @Native - public FunctionContext child() { + public Environment child() { var res = fork(); res.global = res.global.globalChild(); return res; } - public FunctionContext(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { + 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 WrappersProvider() { public ObjectValue getConstr(Class obj) { diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index aeaf264..96b8350 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -107,11 +107,11 @@ public class CodeFrame { return Runners.exec(ctx, instr, this); } catch (EngineException e) { - throw e.add(function.name, prevLoc); + throw e.add(function.name, prevLoc).setContext(ctx); } } - public Object next(Context ctx, Object prevReturn, Object prevError) throws InterruptedException { + public Object next(Context ctx, Object prevValue, Object prevReturn, Object prevError) throws InterruptedException { TryCtx tryCtx = null; if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN; @@ -196,6 +196,7 @@ public class CodeFrame { if (prevError != Runners.NO_RETURN) throw new EngineException(prevError); if (prevReturn != Runners.NO_RETURN) return prevReturn; + if (prevValue != Runners.NO_RETURN) push(ctx, prevValue); if (tryCtx == null) return nextNoTry(ctx); else if (tryCtx.state == TryCtx.STATE_TRY) { @@ -263,7 +264,7 @@ public class CodeFrame { try { ctx.message.pushFrame(this); while (true) { - var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN); + var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); if (res != Runners.NO_RETURN) return res; } } diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 37ce098..08d49e2 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -48,16 +48,18 @@ public class Runners { var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); - if (Values.isFunction(funcObj) && Values.function(funcObj).special) { - frame.push(ctx, call(ctx, funcObj, null, callArgs)); - } - else { - var proto = Values.getMember(ctx, funcObj, "prototype"); - var obj = new ObjectValue(); - obj.setPrototype(ctx, proto); - call(ctx, funcObj, obj, callArgs); - frame.push(ctx, obj); - } + frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); + + // if (Values.isFunction(funcObj) && Values.function(funcObj).special) { + // frame.push(ctx, call(ctx, funcObj, null, callArgs)); + // } + // else { + // var proto = Values.getMember(ctx, funcObj, "prototype"); + // var obj = new ObjectValue(); + // obj.setPrototype(ctx, proto); + // call(ctx, funcObj, obj, callArgs); + // frame.push(ctx, obj); + // } frame.codePtr++; return NO_RETURN; @@ -65,7 +67,7 @@ public class Runners { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { var name = (String)instr.get(0); - ctx.function.global.define(name); + ctx.env.global.define(name); frame.codePtr++; return NO_RETURN; } @@ -160,7 +162,7 @@ public class Runners { public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { var i = instr.get(0); - if (i instanceof String) frame.push(ctx, ctx.function.global.get(ctx, (String)i)); + if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i)); else frame.push(ctx, frame.scope.get((int)i).get(ctx)); frame.codePtr++; @@ -172,7 +174,7 @@ public class Runners { return NO_RETURN; } public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, ctx.function.global.obj); + frame.push(ctx, ctx.env.global.obj); frame.codePtr++; return NO_RETURN; } @@ -198,7 +200,7 @@ public class Runners { var body = new Instruction[end - start]; System.arraycopy(frame.function.body, start, body, 0, end - start); - var func = new CodeFunction(ctx.function, "", localsN, len, captures, body); + var func = new CodeFunction(ctx.env, "", localsN, len, captures, body); frame.push(ctx, func); frame.codePtr += n; @@ -222,7 +224,7 @@ public class Runners { return execLoadMember(ctx, instr, frame); } public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { - frame.push(ctx, ctx.function.regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); + frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.codePtr++; return NO_RETURN; } @@ -246,7 +248,7 @@ public class Runners { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var i = instr.get(0); - if (i instanceof String) ctx.function.global.set(ctx, (String)i, val); + if (i instanceof String) ctx.env.global.set(ctx, (String)i, val); else frame.scope.get((int)i).set(ctx, val); frame.codePtr++; @@ -293,8 +295,8 @@ public class Runners { Object obj; if (name != null) { - if (ctx.function.global.has(ctx, name)) { - obj = ctx.function.global.get(ctx, name); + if (ctx.env.global.has(ctx, name)) { + obj = ctx.env.global.get(ctx, name); } else obj = null; } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index d70b20d..0b34425 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.values; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.FunctionContext; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.scope.ValueVariable; @@ -12,7 +12,7 @@ public class CodeFunction extends FunctionValue { public final int length; public final Instruction[] body; public final ValueVariable[] captures; - public FunctionContext environment; + public Environment environment; public Location loc() { for (var instr : body) { @@ -32,7 +32,7 @@ public class CodeFunction extends FunctionValue { return new CodeFrame(ctx, thisArg, args, this).run(new Context(environment, ctx.message)); } - public CodeFunction(FunctionContext environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { + public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { super(name, length); this.captures = captures; this.environment = environment; diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index 753521d..9005e5f 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -8,7 +8,8 @@ public class NativeWrapper extends ObjectValue { @Override public ObjectValue getPrototype(Context ctx) throws InterruptedException { - if (prototype == NATIVE_PROTO) return ctx.function.wrappersProvider.getProto(wrapped.getClass()); + if (prototype == NATIVE_PROTO) + return ctx.env.wrappersProvider.getProto(wrapped.getClass()); else return super.getPrototype(ctx); } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 9986cc8..054ab3e 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -55,7 +55,7 @@ public class ObjectValue { public final boolean memberWritable(Object key) { if (state == State.FROZEN) return false; - return !nonWritableSet.contains(key); + return values.containsKey(key) && !nonWritableSet.contains(key); } public final boolean memberConfigurable(Object key) { if (state == State.SEALED || state == State.FROZEN) return false; @@ -147,13 +147,13 @@ public class ObjectValue { public ObjectValue getPrototype(Context ctx) throws InterruptedException { try { - if (prototype == OBJ_PROTO) return ctx.function.proto("object"); - if (prototype == ARR_PROTO) return ctx.function.proto("array"); - if (prototype == FUNC_PROTO) return ctx.function.proto("function"); - if (prototype == ERR_PROTO) return ctx.function.proto("error"); - if (prototype == RANGE_ERR_PROTO) return ctx.function.proto("rangeErr"); - if (prototype == SYNTAX_ERR_PROTO) return ctx.function.proto("syntaxErr"); - if (prototype == TYPE_ERR_PROTO) return ctx.function.proto("typeErr"); + if (prototype == OBJ_PROTO) return ctx.env.proto("object"); + if (prototype == ARR_PROTO) return ctx.env.proto("array"); + if (prototype == FUNC_PROTO) return ctx.env.proto("function"); + if (prototype == ERR_PROTO) return ctx.env.proto("error"); + if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr"); + if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr"); + if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr"); } catch (NullPointerException e) { return null; @@ -172,14 +172,14 @@ public class ObjectValue { else if (Values.isObject(val)) { var obj = Values.object(val); - if (ctx != null && ctx.function != null) { - if (obj == ctx.function.proto("object")) prototype = OBJ_PROTO; - else if (obj == ctx.function.proto("array")) prototype = ARR_PROTO; - else if (obj == ctx.function.proto("function")) prototype = FUNC_PROTO; - else if (obj == ctx.function.proto("error")) prototype = ERR_PROTO; - else if (obj == ctx.function.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; - else if (obj == ctx.function.proto("typeErr")) prototype = TYPE_ERR_PROTO; - else if (obj == ctx.function.proto("rangeErr")) prototype = RANGE_ERR_PROTO; + if (ctx != null && ctx.env != null) { + if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO; + else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO; + else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO; + else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO; + else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; + else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO; + else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO; else prototype = obj; } else prototype = obj; diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 73f7029..839c6e3 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -13,7 +13,9 @@ import java.util.Map; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.frame.ConvertHint; +import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.SyntaxException; public class Values { public static final Object NULL = new Object(); @@ -321,10 +323,10 @@ public class Values { if (isObject(obj)) return object(obj).getPrototype(ctx); if (ctx == null) return null; - if (obj instanceof String) return ctx.function.proto("string"); - else if (obj instanceof Number) return ctx.function.proto("number"); - else if (obj instanceof Boolean) return ctx.function.proto("bool"); - else if (obj instanceof Symbol) return ctx.function.proto("symbol"); + if (obj instanceof String) return ctx.env.proto("string"); + else if (obj instanceof Number) return ctx.env.proto("number"); + else if (obj instanceof Boolean) return ctx.env.proto("bool"); + else if (obj instanceof Symbol) return ctx.env.proto("symbol"); return null; } @@ -352,12 +354,39 @@ public class Values { return res; } + public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException { + if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key); + else if (obj instanceof String && key instanceof Number) { + var i = ((Number)key).intValue(); + var _i = ((Number)key).doubleValue(); + if (i - _i != 0) return null; + if (i < 0 || i >= ((String)obj).length()) return null; + + return new ObjectValue(ctx, Map.of( + "value", ((String)obj).charAt(i) + "", + "writable", false, + "enumerable", true, + "configurable", false + )); + } + else return null; + } public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { - if (!isFunction(func)) - throw EngineException.ofType("Tried to call a non-function value."); + if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); return function(func).call(ctx, thisArg, args); } + public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException { + if (func instanceof FunctionValue && ((FunctionValue)func).special) return ((FunctionValue)func).call(ctx, null, args); + + var res = new ObjectValue(); + var proto = Values.getMember(ctx, func, "prototype"); + res.setPrototype(ctx, proto); + + call(ctx, func, res, args); + + return res; + } public static boolean strictEquals(Context ctx, Object a, Object b) { a = normalize(ctx, a); b = normalize(ctx, b); @@ -420,7 +449,7 @@ public class Values { if (val instanceof Class) { if (ctx == null) return null; - else return ctx.function.wrappersProvider.getConstr((Class)val); + else return ctx.env.wrappersProvider.getConstr((Class)val); } return new NativeWrapper(val); @@ -429,17 +458,15 @@ public class Values { @SuppressWarnings("unchecked") public static T convert(Context ctx, Object obj, Class clazz) throws InterruptedException { if (clazz == Void.class) return null; - if (clazz == null || clazz == Object.class) return (T)obj; - - var err = new IllegalArgumentException(String.format("Cannot convert '%s' to '%s'.", type(obj), clazz.getName())); if (obj instanceof NativeWrapper) { var res = ((NativeWrapper)obj).wrapped; if (clazz.isInstance(res)) return (T)res; } + if (clazz == null || clazz == Object.class) return (T)obj; + if (obj instanceof ArrayValue) { - if (clazz.isAssignableFrom(ArrayList.class)) { var raw = array(obj).toArray(); var res = new ArrayList<>(); @@ -480,11 +507,9 @@ public class Values { if (clazz == Character.class || clazz == char.class) { if (obj instanceof Number) return (T)(Character)(char)number(obj); - else if (obj == NULL) throw new IllegalArgumentException("Cannot convert null to character."); - else if (obj == null) throw new IllegalArgumentException("Cannot convert undefined to character."); else { var res = toString(ctx, obj); - if (res.length() == 0) throw new IllegalArgumentException("Cannot convert empty string to character."); + if (res.length() == 0) throw new ConvertException("\"\"", "Character"); else return (T)(Character)res.charAt(0); } } @@ -492,20 +517,22 @@ public class Values { if (obj == null) return null; if (clazz.isInstance(obj)) return (T)obj; - throw err; + throw new ConvertException(type(obj), clazz.getName()); } public static Iterable toJavaIterable(Context ctx, Object obj) throws InterruptedException { return () -> { try { - var constr = getMember(ctx, ctx.function.proto("symbol"), "constructor"); - var symbol = getMember(ctx, constr, "iterator"); + var symbol = ctx.env.symbol("Symbol.iterator"); var iteratorFunc = getMember(ctx, obj, symbol); if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); - var iterator = getMember(ctx, call(ctx, iteratorFunc, obj), "next"); - if (!isFunction(iterator)) return Collections.emptyIterator(); - var iterable = obj; + var iterator = iteratorFunc instanceof FunctionValue ? + ((FunctionValue)iteratorFunc).call(ctx, iteratorFunc, obj) : + iteratorFunc; + var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next"); + + if (!isFunction(nextFunc)) return Collections.emptyIterator(); return new Iterator() { private Object value = null; @@ -515,7 +542,7 @@ public class Values { private void loadNext() throws InterruptedException { if (next == null) value = null; else if (consumed) { - var curr = object(next.call(ctx, iterable)); + var curr = object(next.call(ctx, iterator)); if (curr == null) { next = null; value = null; } if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; } else { @@ -567,7 +594,7 @@ public class Values { var it = iterable.iterator(); try { - var key = getMember(ctx, getMember(ctx, ctx.function.proto("symbol"), "constructor"), "iterator"); + var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator"); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); } catch (IllegalArgumentException | NullPointerException e) { } @@ -655,4 +682,25 @@ public class Values { public static void printValue(Context ctx, Object val) throws InterruptedException { printValue(ctx, val, new HashSet<>(), 0); } + public static void printError(RuntimeException err, String prefix) throws InterruptedException { + prefix = prefix == null ? "Uncauthg" : "Uncaught " + prefix; + try { + if (err instanceof EngineException) { + System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx)); + } + else if (err instanceof SyntaxException) { + System.out.println("Syntax error:" + ((SyntaxException)err).msg); + } + else if (err.getCause() instanceof InterruptedException) return; + else { + System.out.println("Internal error ocurred:"); + err.printStackTrace(); + } + } + catch (EngineException ex) { + System.out.println("Uncaught "); + Values.printValue(null, ((EngineException)err).value); + System.out.println(); + } + } } diff --git a/src/me/topchetoeu/jscript/exceptions/ConvertException.java b/src/me/topchetoeu/jscript/exceptions/ConvertException.java new file mode 100644 index 0000000..967bf7c --- /dev/null +++ b/src/me/topchetoeu/jscript/exceptions/ConvertException.java @@ -0,0 +1,11 @@ +package me.topchetoeu.jscript.exceptions; + +public class ConvertException extends RuntimeException { + public final String source, target; + + public ConvertException(String source, String target) { + super(String.format("Cannot convert '%s' to '%s'.", source, target)); + this.source = source; + this.target = target; + } +} diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index 6119a73..e695426 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -12,6 +12,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; public class EngineException extends RuntimeException { public final Object value; public EngineException cause; + public Context ctx = null; public final List stackTrace = new ArrayList<>(); public EngineException add(String name, Location location) { @@ -27,6 +28,10 @@ public class EngineException extends RuntimeException { this.cause = cause; return this; } + public EngineException setContext(Context ctx) { + this.ctx = ctx; + return this; + } public String toString(Context ctx) throws InterruptedException { var ss = new StringBuilder(); diff --git a/src/me/topchetoeu/jscript/interop/Native.java b/src/me/topchetoeu/jscript/interop/Native.java index f98765a..c71992c 100644 --- a/src/me/topchetoeu/jscript/interop/Native.java +++ b/src/me/topchetoeu/jscript/interop/Native.java @@ -9,4 +9,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface Native { public String value() default ""; + public boolean raw() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeConstructor.java b/src/me/topchetoeu/jscript/interop/NativeConstructor.java new file mode 100644 index 0000000..5603a77 --- /dev/null +++ b/src/me/topchetoeu/jscript/interop/NativeConstructor.java @@ -0,0 +1,13 @@ +package me.topchetoeu.jscript.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeConstructor { + public boolean raw() default false; +} + diff --git a/src/me/topchetoeu/jscript/interop/NativeGetter.java b/src/me/topchetoeu/jscript/interop/NativeGetter.java index ea5651b..5e94a5e 100644 --- a/src/me/topchetoeu/jscript/interop/NativeGetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeGetter.java @@ -9,4 +9,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface NativeGetter { public String value(); + public boolean raw() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeSetter.java b/src/me/topchetoeu/jscript/interop/NativeSetter.java index bcb5cd3..b460831 100644 --- a/src/me/topchetoeu/jscript/interop/NativeSetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeSetter.java @@ -9,4 +9,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface NativeSetter { public String value(); + public boolean raw() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java b/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java index ac6eb85..f927415 100644 --- a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java +++ b/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java @@ -13,25 +13,35 @@ public class NativeTypeRegister implements WrappersProvider { private final HashMap, FunctionValue> constructors = new HashMap<>(); private final HashMap, ObjectValue> prototypes = new HashMap<>(); + private static boolean isMember(Class[] args) { + return args.length == 3; + } + private static void applyMethods(boolean member, ObjectValue target, Class clazz) { for (var method : clazz.getDeclaredMethods()) { - if (!Modifier.isStatic(method.getModifiers()) != member) continue; - var nat = method.getAnnotation(Native.class); var get = method.getAnnotation(NativeGetter.class); var set = method.getAnnotation(NativeSetter.class); + var params = method.getParameterTypes(); + var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; if (nat != null) { + if (nat.raw()) { + if (isMember(params) != member) continue; + } + else if (memberMismatch) continue; + var name = nat.value(); var val = target.values.get(name); if (name.equals("")) name = method.getName(); if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name)); - ((OverloadFunction)val).overloads.add(Overload.fromMethod(method)); + ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.raw())); } else { if (get != null) { + if (get.raw() && isMember(params) != member || memberMismatch) continue; var name = get.value(); var prop = target.properties.get(name); OverloadFunction getter = null; @@ -40,10 +50,11 @@ public class NativeTypeRegister implements WrappersProvider { if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; else getter = new OverloadFunction("get " + name); - getter.overloads.add(Overload.fromMethod(method)); + getter.overloads.add(Overload.fromMethod(method, get.raw())); target.defineProperty(null, name, getter, setter, true, true); } if (set != null) { + if (set.raw() && isMember(params) != member || memberMismatch) continue; var name = set.value(); var prop = target.properties.get(name); var getter = prop == null ? null : prop.getter; @@ -52,7 +63,7 @@ public class NativeTypeRegister implements WrappersProvider { if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; else setter = new OverloadFunction("set " + name); - setter.overloads.add(Overload.fromMethod(method)); + setter.overloads.add(Overload.fromMethod(method, set.raw())); target.defineProperty(null, name, getter, setter, true, true); } } @@ -66,8 +77,8 @@ public class NativeTypeRegister implements WrappersProvider { if (nat != null) { var name = nat.value(); if (name.equals("")) name = field.getName(); - var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field)); - var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field)); + var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field, nat.raw())); + var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field, nat.raw())); target.defineProperty(null, name, getter, setter, true, false); } } @@ -113,8 +124,14 @@ public class NativeTypeRegister implements WrappersProvider { FunctionValue func = new OverloadFunction(clazz.getName()); for (var overload : clazz.getConstructors()) { - if (overload.getAnnotation(Native.class) == null) continue; - ((OverloadFunction)func).add(Overload.fromConstructor(overload)); + var nat = overload.getAnnotation(Native.class); + if (nat == null) continue; + ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.raw())); + } + for (var overload : clazz.getMethods()) { + var constr = overload.getAnnotation(NativeConstructor.class); + if (constr == null) continue; + ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.raw())); } if (((OverloadFunction)func).overloads.size() == 0) { diff --git a/src/me/topchetoeu/jscript/interop/Overload.java b/src/me/topchetoeu/jscript/interop/Overload.java index 191f0e4..740fe99 100644 --- a/src/me/topchetoeu/jscript/interop/Overload.java +++ b/src/me/topchetoeu/jscript/interop/Overload.java @@ -17,53 +17,63 @@ public class Overload { public final OverloadRunner runner; public final boolean variadic; + public final boolean raw; public final Class thisArg; public final Class[] params; - public static Overload fromMethod(Method method) { + public static Overload fromMethod(Method method, boolean raw) { return new Overload( (ctx, th, args) -> method.invoke(th, args), - method.isVarArgs(), + method.isVarArgs(), raw, Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(), method.getParameterTypes() ); } - public static Overload fromConstructor(Constructor method) { + public static Overload fromConstructor(Constructor method, boolean raw) { return new Overload( (ctx, th, args) -> method.newInstance(args), - method.isVarArgs(), + method.isVarArgs(), raw, Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(), method.getParameterTypes() ); } - public static Overload getterFromField(Field field) { + public static Overload getterFromField(Field field, boolean raw) { return new Overload( - (ctx, th, args) -> field.get(th), false, + (ctx, th, args) -> field.get(th), false, raw, Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(), new Class[0] ); } - public static Overload setterFromField(Field field) { + public static Overload setterFromField(Field field, boolean raw) { if (Modifier.isFinal(field.getModifiers())) return null; return new Overload( - (ctx, th, args) -> { field.set(th, args[0]); return null; }, false, + (ctx, th, args) -> { field.set(th, args[0]); return null; }, false, raw, Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(), new Class[0] ); } - public static Overload getter(Class thisArg, OverloadRunner runner) { + public static Overload getter(Class thisArg, OverloadRunner runner, boolean raw) { return new Overload( - (ctx, th, args) -> runner.run(ctx, th, args), false, + (ctx, th, args) -> runner.run(ctx, th, args), false, raw, thisArg, new Class[0] ); } - public Overload(OverloadRunner runner, boolean variadic, Class thisArg, Class args[]) { + public Overload(OverloadRunner runner, boolean variadic, boolean raw, Class thisArg, Class args[]) { this.runner = runner; this.variadic = variadic; + this.raw = raw; this.thisArg = thisArg; this.params = args; + + if (raw) { + if (!( + thisArg == null && ( + args.length == 3 && args[0] == Context.class && args[1] == Object.class && args[2] == Object[].class || + args.length == 2 && args[0] == Context.class && args[1] == Object[].class + ))) throw new IllegalArgumentException("Invalid signature for raw method."); + } } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 321b04d..89a66d9 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -8,41 +8,61 @@ import java.util.List; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.EngineException; public class OverloadFunction extends FunctionValue { public final List overloads = new ArrayList<>(); public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - for (var overload : overloads) { - boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class; - int start = consumesEngine ? 1 : 0; - int end = overload.params.length - (overload.variadic ? 1 : 0); - + loop: for (var overload : overloads) { Object[] newArgs = new Object[overload.params.length]; - for (var i = start; i < end; i++) { - Object val; - - if (i - start >= args.length) val = null; - else val = args[i - start]; - - newArgs[i] = Values.convert(ctx, val, overload.params[i]); + if (overload.raw) { + newArgs[0] = ctx; + newArgs[1] = thisArg; + newArgs[2] = args; } + else { + boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class; + int start = consumesEngine ? 1 : 0; + int end = overload.params.length - (overload.variadic ? 1 : 0); - if (overload.variadic) { - var type = overload.params[overload.params.length - 1].getComponentType(); - var n = Math.max(args.length - end + start, 0); - Object varArg = Array.newInstance(type, n); + for (var i = start; i < end; i++) { + Object val; - for (var i = 0; i < n; i++) { - Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type)); + if (i - start >= args.length) val = null; + else val = args[i - start]; + + try { + newArgs[i] = Values.convert(ctx, val, overload.params[i]); + } + catch (ConvertException e) { + if (overloads.size() > 1) continue loop; + else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); + } } - newArgs[newArgs.length - 1] = varArg; - } + if (overload.variadic) { + var type = overload.params[overload.params.length - 1].getComponentType(); + var n = Math.max(args.length - end + start, 0); + Object varArg = Array.newInstance(type, n); - if (consumesEngine) newArgs[0] = ctx; + for (var i = 0; i < n; i++) { + try { + Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type)); + } + catch (ConvertException e) { + if (overloads.size() > 1) continue loop; + else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); + } + } + + newArgs[newArgs.length - 1] = varArg; + } + + if (consumesEngine) newArgs[0] = ctx; + } Object _this = overload.thisArg == null ? null : Values.convert(ctx, thisArg, overload.thisArg); diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index 0672cb5..54725b9 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -13,7 +13,7 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.values.*; -import me.topchetoeu.jscript.engine.FunctionContext; +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; @@ -1875,7 +1875,7 @@ public class Parsing { return list.toArray(Statement[]::new); } - public static CodeFunction compile(FunctionContext environment, Statement ...statements) { + public static CodeFunction compile(Environment environment, Statement ...statements) { var target = environment.global.globalChild(); var subscope = target.child(); var res = new ArrayList(); @@ -1905,7 +1905,7 @@ public class Parsing { return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.toArray(Instruction[]::new)); } - public static CodeFunction compile(FunctionContext environment, String filename, String raw) { + public static CodeFunction compile(Environment environment, String filename, String raw) { try { return compile(environment, parse(filename, raw)); } diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java new file mode 100644 index 0000000..0661995 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java @@ -0,0 +1,76 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.frame.Runners; +import me.topchetoeu.jscript.engine.values.CodeFunction; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.NativeFunction; +import me.topchetoeu.jscript.exceptions.EngineException; + +public class AsyncFunctionPolyfill extends FunctionValue { + public final FunctionValue factory; + + public static class AsyncHelper { + public PromisePolyfill promise = new PromisePolyfill(); + public CodeFrame frame; + + private boolean awaiting = false; + + private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException { + Object res = null; + ctx.message.pushFrame(frame); + + awaiting = false; + while (!awaiting) { + try { + res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError); + inducedValue = inducedError = Runners.NO_RETURN; + if (res != Runners.NO_RETURN) { + promise.fulfill(ctx, res); + break; + } + } + catch (EngineException e) { + promise.reject(ctx, e.value); + break; + } + } + + ctx.message.popFrame(frame); + + if (awaiting) { + PromisePolyfill.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); + } + } + + public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN); + return null; + } + public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null); + return null; + } + + public Object await(Context ctx, Object thisArg, Object[] args) { + this.awaiting = true; + return args.length > 0 ? args[0] : null; + } + } + + @Override + public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + var handler = new AsyncHelper(); + var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await)); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); + handler.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN); + return handler.promise; + } + + public AsyncFunctionPolyfill(FunctionValue factory) { + super(factory.name, factory.length); + this.factory = factory; + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java new file mode 100644 index 0000000..4328d61 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java @@ -0,0 +1,133 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.Map; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.frame.Runners; +import me.topchetoeu.jscript.engine.values.CodeFunction; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.NativeFunction; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; + +public class AsyncGeneratorPolyfill extends FunctionValue { + public final FunctionValue factory; + + public static class AsyncGenerator { + private int state = 0; + private boolean done = false; + private PromisePolyfill currPromise; + public CodeFrame frame; + + private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException { + if (done) { + if (inducedError != Runners.NO_RETURN) + throw new EngineException(inducedError); + currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of( + "done", true, + "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn + ))); + return; + } + + Object res = null; + ctx.message.pushFrame(frame); + state = 0; + + while (state == 0) { + try { + res = frame.next(ctx, inducedValue, inducedReturn, inducedError); + inducedValue = inducedReturn = inducedError = Runners.NO_RETURN; + if (res != Runners.NO_RETURN) { + var obj = new ObjectValue(); + obj.defineProperty(ctx, "done", true); + obj.defineProperty(ctx, "value", res); + currPromise.fulfill(ctx, obj); + break; + } + } + catch (EngineException e) { + currPromise.reject(ctx, e.value); + break; + } + } + + ctx.message.popFrame(frame); + + if (state == 1) { + PromisePolyfill.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); + } + else if (state == 2) { + var obj = new ObjectValue(); + obj.defineProperty(ctx, "done", false); + obj.defineProperty(ctx, "value", frame.pop()); + currPromise.fulfill(ctx, obj); + } + } + + @Override + public String toString() { + if (done) return "Generator [closed]"; + if (state != 0) return "Generator [suspended]"; + return "Generator [running]"; + } + + public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN); + return null; + } + public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN); + return null; + } + + @Native + public PromisePolyfill next(Context ctx, Object ...args) throws InterruptedException { + this.currPromise = new PromisePolyfill(); + if (args.length == 0) next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); + else next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN); + return this.currPromise; + } + @Native("throw") + public PromisePolyfill _throw(Context ctx, Object error) throws InterruptedException { + this.currPromise = new PromisePolyfill(); + next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error); + return this.currPromise; + } + @Native("return") + public PromisePolyfill _return(Context ctx, Object value) throws InterruptedException { + this.currPromise = new PromisePolyfill(); + next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); + return this.currPromise; + } + + + public Object await(Context ctx, Object thisArg, Object[] args) { + this.state = 1; + return args.length > 0 ? args[0] : null; + } + public Object yield(Context ctx, Object thisArg, Object[] args) { + this.state = 2; + return args.length > 0 ? args[0] : null; + } + } + + @Override + public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + var handler = new AsyncGenerator(); + var func = factory.call(ctx, thisArg, + new NativeFunction("await", handler::await), + new NativeFunction("yield", handler::yield) + ); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); + return handler; + } + + public AsyncGeneratorPolyfill(FunctionValue factory) { + super(factory.name, factory.length); + this.factory = factory; + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java new file mode 100644 index 0000000..89253d0 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -0,0 +1,60 @@ +package me.topchetoeu.jscript.polyfills; + +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.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; + +public class FunctionPolyfill { + @Native(raw=true) public static Object apply(Context ctx, Object func, Object[] args) throws InterruptedException { + var thisArg = args.length > 0 ? args[0] : null; + var _args = args.length > 1 ? args[1] : null; + + if (!(_args instanceof ArrayValue)) throw EngineException.ofError("Expected arguments to be an array."); + if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); + + return ((FunctionValue)func).call(ctx, thisArg, ((ArrayValue)_args).toArray()); + } + @Native(raw=true) public static Object call(Context ctx, Object func, Object[] args) throws InterruptedException { + var thisArg = args.length > 0 ? args[0] : null; + var _args = new Object[args.length > 1 ? args.length - 1 : 0]; + + if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); + + if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length); + + return ((FunctionValue)func).call(ctx, thisArg, _args); + } + @Native(raw=true) public static Object bind(Context ctx, Object func, Object[] args) { + var thisArg = args.length > 0 ? args[0] : null; + var _args = new Object[args.length > 1 ? args.length - 1 : 0]; + FunctionValue _func = (FunctionValue)func; + + if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); + + if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length); + + return new NativeFunction(_func.name + " (bound)", (callCtx, _0, callArgs) -> { + var resArgs = new Object[_args.length + callArgs.length]; + System.arraycopy(_args, 0, resArgs, 0, _args.length); + System.arraycopy(callArgs, 0, resArgs, _args.length, resArgs.length - _args.length); + + return _func.call(ctx, thisArg, resArgs); + }); + } + @Native(raw=true) public static String toString(Context ctx, Object func, Object[] args) { + return "function (...) { ... }"; + } + + @Native public static FunctionValue async(FunctionValue func) { + return new AsyncFunctionPolyfill(func); + } + @Native public static FunctionValue asyncGenerator(FunctionValue func) { + return new AsyncGeneratorPolyfill(func); + } + @Native public static FunctionValue generator(FunctionValue func) { + return new GeneratorPolyfill(func); + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java similarity index 91% rename from src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java rename to src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index 17b098f..47f5080 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -10,7 +10,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; -public class GeneratorFunction extends FunctionValue { +public class GeneratorPolyfill extends FunctionValue { public final FunctionValue factory; public static class Generator { @@ -28,12 +28,12 @@ public class GeneratorFunction extends FunctionValue { } Object res = null; - if (inducedValue != Runners.NO_RETURN) frame.push(ctx, inducedValue); ctx.message.pushFrame(frame); yielding = false; + while (!yielding) { try { - res = frame.next(ctx, inducedReturn, inducedError); + res = frame.next(ctx, inducedValue, inducedReturn, inducedError); inducedReturn = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { done = true; @@ -74,7 +74,7 @@ public class GeneratorFunction extends FunctionValue { public String toString() { if (done) return "Generator [closed]"; if (yielding) return "Generator [suspended]"; - return "Generator " + (done ? "[closed]" : "[suspended]"); + return "Generator [running]"; } public Object yield(Context ctx, Object thisArg, Object[] args) { @@ -92,7 +92,7 @@ public class GeneratorFunction extends FunctionValue { return handler; } - public GeneratorFunction(FunctionValue factory) { + public GeneratorPolyfill(FunctionValue factory) { super(factory.name, factory.length); this.factory = factory; } diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 71fbbab..e584be7 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -1,7 +1,7 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.FunctionContext; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; @@ -12,20 +12,25 @@ import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.interop.Native; public class Internals { + @Native public final Class object = ObjectPolyfill.class; + @Native public final Class function = FunctionPolyfill.class; + @Native public final Class promise = PromisePolyfill.class; + @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { func.special = true; } } - @Native public FunctionContext getEnv(Object func) { + @Native public Environment getEnv(Object func) { if (func instanceof CodeFunction) return ((CodeFunction)func).environment; else return null; } - @Native public Object setEnv(Object func, FunctionContext env) { + @Native public Object setEnv(Object func, Environment env) { if (func instanceof CodeFunction) ((CodeFunction)func).environment = env; return func; } - @Native public Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { + @Native public Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args, Environment env) throws InterruptedException { + if (env != null) ctx = new Context(env, ctx.message); return func.call(ctx, thisArg, args.toArray()); } @Native public FunctionValue delay(Context ctx, double delay, FunctionValue callback) throws InterruptedException { @@ -84,8 +89,8 @@ public class Internals { @Native public boolean isArray(Object obj) { return obj instanceof ArrayValue; } - @Native public GeneratorFunction generator(FunctionValue obj) { - return new GeneratorFunction(obj); + @Native public GeneratorPolyfill generator(FunctionValue obj) { + return new GeneratorPolyfill(obj); } @Native public boolean defineField(Context ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) { diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java new file mode 100644 index 0000000..10226a4 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -0,0 +1,212 @@ +package me.topchetoeu.jscript.polyfills; + +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.Symbol; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeConstructor; + +public class ObjectPolyfill { + @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException { + for (var obj : src) { + for (var key : Values.getMembers(ctx, obj, true, true)) { + Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key)); + } + } + return dst; + } + @Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) throws InterruptedException { + var obj = new ObjectValue(); + obj.setPrototype(ctx, proto); + return defineProperties(ctx, obj, props); + } + + @Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) throws InterruptedException { + var hasVal = attrib.hasMember(ctx, "value", false); + var hasGet = attrib.hasMember(ctx, "get", false); + var hasSet = attrib.hasMember(ctx, "set", false); + + if (hasVal) { + if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property."); + if (!obj.defineProperty( + ctx, key, + attrib.getMember(ctx, "value"), + Values.toBoolean(attrib.getMember(ctx, "writable")), + Values.toBoolean(attrib.getMember(ctx, "configurable")), + Values.toBoolean(attrib.getMember(ctx, "enumerable")) + )) throw EngineException.ofType("Can't define property '" + key + "'."); + } + else { + var get = attrib.getMember(ctx, "get"); + var set = attrib.getMember(ctx, "set"); + if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function."); + if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function."); + + if (!obj.defineProperty( + ctx, key, + (FunctionValue)get, (FunctionValue)set, + Values.toBoolean(attrib.getMember(ctx, "configurable")), + Values.toBoolean(attrib.getMember(ctx, "enumerable")) + )) throw EngineException.ofType("Can't define property '" + key + "'."); + } + + return obj; + } + @Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) throws InterruptedException { + for (var key : Values.getMembers(null, obj, false, false)) { + obj.defineProperty(ctx, key, attrib.getMember(ctx, key)); + } + + return obj; + } + + @Native public static ArrayValue keys(Context ctx, Object obj, Object all) throws InterruptedException { + var res = new ArrayValue(); + var _all = Values.toBoolean(all); + + for (var key : Values.getMembers(ctx, obj, true, false)) { + if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key); + } + + return res; + } + @Native public static ArrayValue entries(Context ctx, Object obj, Object all) throws InterruptedException { + var res = new ArrayValue(); + var _all = Values.toBoolean(all); + + for (var key : Values.getMembers(ctx, obj, true, false)) { + if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), new ArrayValue(ctx, key, Values.getMember(ctx, obj, key))); + } + + return res; + } + @Native public static ArrayValue values(Context ctx, Object obj, Object all) throws InterruptedException { + var res = new ArrayValue(); + var _all = Values.toBoolean(all); + + for (var key : Values.getMembers(ctx, obj, true, false)) { + if (_all || key instanceof String) res.set(ctx, res.size(), Values.getMember(ctx, obj, key)); + } + + return res; + } + + @Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) throws InterruptedException { + return Values.getMemberDescriptor(ctx, obj, key); + } + @Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) throws InterruptedException { + var res = new ObjectValue(); + for (var key : Values.getMembers(ctx, obj, true, true)) { + res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key)); + } + return res; + } + + @Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) throws InterruptedException { + var res = new ArrayValue(); + var _all = Values.toBoolean(all); + + for (var key : Values.getMembers(ctx, obj, true, true)) { + if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key); + } + + return res; + } + @Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) throws InterruptedException { + var res = new ArrayValue(); + + for (var key : Values.getMembers(ctx, obj, true, true)) { + if (key instanceof Symbol) res.set(ctx, res.size(), key); + } + + return res; + } + @Native public static boolean hasOwn(Context ctx, Object obj, Object key) throws InterruptedException { + return Values.hasMember(ctx, obj, key, true); + } + + @Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) throws InterruptedException { + return Values.getPrototype(ctx, obj); + } + @Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) throws InterruptedException { + Values.setPrototype(ctx, obj, proto); + return obj; + } + + @Native public static ObjectValue fromEntries(Context ctx, Object iterable) throws InterruptedException { + var res = new ObjectValue(); + + for (var el : Values.toJavaIterable(ctx, iterable)) { + if (el instanceof ArrayValue) { + res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1)); + } + } + + return res; + } + + @Native public static Object preventExtensions(Context ctx, Object obj) throws InterruptedException { + if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions(); + return obj; + } + @Native public static Object seal(Context ctx, Object obj) throws InterruptedException { + if (obj instanceof ObjectValue) ((ObjectValue)obj).seal(); + return obj; + } + @Native public static Object freeze(Context ctx, Object obj) throws InterruptedException { + if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze(); + return obj; + } + + @Native public static boolean isExtensible(Context ctx, Object obj) throws InterruptedException { + return obj instanceof ObjectValue && ((ObjectValue)obj).extensible(); + } + @Native public static boolean isSealed(Context ctx, Object obj) throws InterruptedException { + if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { + var _obj = (ObjectValue)obj; + for (var key : _obj.keys(true)) { + if (_obj.memberConfigurable(key)) return false; + } + } + + return true; + } + @Native public static boolean isFrozen(Context ctx, Object obj) throws InterruptedException { + if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { + var _obj = (ObjectValue)obj; + for (var key : _obj.keys(true)) { + if (_obj.memberConfigurable(key)) return false; + if (_obj.memberWritable(key)) return false; + } + } + + return true; + } + + @Native(raw = true) public static Object valueOf(Context ctx, Object thisArg, Object[] args) { + return thisArg; + } + @Native(raw = true) public static String toString(Context ctx, Object thisArg, Object[] args) throws InterruptedException { + var name = Values.getMember(ctx, thisArg, ctx.env.symbol("Symbol.typeName")); + if (name == null) name = "Unknown"; + else name = Values.toString(ctx, name); + + return "[object " + name + "]"; + } + @Native(raw = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object[] args) throws InterruptedException { + return ObjectPolyfill.hasOwn(ctx, thisArg, Values.convert(ctx, args.length == 0 ? null : args[0], String.class)); + } + + @NativeConstructor public static Object constructor(Context ctx, Object arg) throws InterruptedException { + if (arg == null || arg == Values.NULL) return new ObjectValue(); + else if (arg instanceof Boolean) return Values.callNew(ctx, ctx.env.global.get(ctx, "Boolean"), arg); + else if (arg instanceof Number) return Values.callNew(ctx, ctx.env.global.get(ctx, "Number"), arg); + else if (arg instanceof String) return Values.callNew(ctx, ctx.env.global.get(ctx, "String"), arg); + else if (arg instanceof Symbol) return Values.callNew(ctx, ctx.env.global.get(ctx, "Symbol"), arg); + else return arg; + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java new file mode 100644 index 0000000..a640572 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -0,0 +1,346 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.MessageContext; +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; + +public class PromisePolyfill { + 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 PromisePolyfill ofResolved(Context ctx, Object val) throws InterruptedException { + var res = new PromisePolyfill(); + res.fulfill(ctx, val); + return res; + } + @Native("reject") + public static PromisePolyfill ofRejected(Context ctx, Object val) throws InterruptedException { + var res = new PromisePolyfill(); + res.reject(ctx, val); + return res; + } + + @Native public static PromisePolyfill any(Context ctx, Object _promises) throws InterruptedException { + 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 PromisePolyfill(); + + 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 PromisePolyfill race(Context ctx, Object _promises) throws InterruptedException { + 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 PromisePolyfill(); + + 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 PromisePolyfill all(Context ctx, Object _promises) throws InterruptedException { + 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 PromisePolyfill(); + + 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 PromisePolyfill allSettled(Context ctx, Object _promises) throws InterruptedException { + 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 PromisePolyfill(); + + 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(raw = true) public static Object then(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + var onFulfill = args.length > 0 ? args[0] : null; + var onReject = args.length > 1 ? args[1]: null; + + if (!(onFulfill instanceof FunctionValue)) onFulfill = null; + if (!(onReject instanceof FunctionValue)) onReject = null; + + var res = new PromisePolyfill(); + + 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 PromisePolyfill) { + 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 PromisePolyfill) ((PromisePolyfill)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", raw = true) public static Object _catch(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + return then(ctx, thisArg, null, args.length > 0 ? args[0] : null); + } + /** + * Thread safe - you can call this from anywhere + * HOWEVER, it's strongly recommended to use this only in javascript + */ + @Native(value = "finally", raw = true) public static Object _finally(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + var handleFunc = args.length > 0 ? args[0] : null; + + return then(ctx, thisArg, + new NativeFunction(null, (e, th, _args) -> { + if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx); + return args[0]; + }), + new NativeFunction(null, (e, th, _args) -> { + if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx); + throw new EngineException(_args[0]); + }) + ); + } + + 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; + + private void resolve(Context ctx, Object val, int state) throws InterruptedException { + if (this.state != STATE_PENDING) return; + + if (val instanceof PromisePolyfill) ((PromisePolyfill)val).handle(ctx, + new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], state); return null; }), + new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], STATE_REJECTED); return null; }) + ); + else { + Object next; + try { next = Values.getMember(ctx, val, "next"); } + catch (IllegalArgumentException e) { next = null; } + + try { + if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, + new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, state); return null; }), + new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, STATE_REJECTED); return null; }) + ); + else { + this.val = val; + this.state = state; + + if (state == STATE_FULFILLED) { + for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val); + } + else if (state == STATE_REJECTED) { + for (var handle : handles) handle.rejected.call(handle.ctx, null, val); + if (handles.size() == 0) { + ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> { + if (!handled) { + try { Values.printError(new EngineException(val), "(in promise)"); } + catch (InterruptedException ex) { } + } + + return null; + }), null); + } + } + + handles = null; + } + } + catch (EngineException err) { + this.reject(ctx, err.value); + } + } + } + + /** + * Thread safe - call from any thread + */ + public void fulfill(Context ctx, Object val) throws InterruptedException { + resolve(ctx, val, STATE_FULFILLED); + } + /** + * Thread safe - call from any thread + */ + public void reject(Context ctx, Object val) throws InterruptedException { + resolve(ctx, val, STATE_REJECTED); + } + + private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { + if (state == STATE_FULFILLED) ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), fulfill, null, val); + else if (state == STATE_REJECTED) { + ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), 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 PromisePolyfill(Context ctx, FunctionValue func) throws InterruptedException { + 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 PromisePolyfill(int state, Object val) { + this.state = state; + this.val = val; + } + public PromisePolyfill() { + this(STATE_PENDING, null); + } +} -- 2.45.2 From 0b7442a3d8bd08321cadeb17167ddbcecb8a383f Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:50:03 +0300 Subject: [PATCH 03/23] fix: remove raw native funcs, use this arg passing instead --- src/me/topchetoeu/jscript/interop/Native.java | 2 +- .../jscript/interop/NativeConstructor.java | 2 +- .../jscript/interop/NativeGetter.java | 2 +- .../jscript/interop/NativeSetter.java | 2 +- .../jscript/interop/NativeTypeRegister.java | 30 ++++----- .../topchetoeu/jscript/interop/Overload.java | 34 ++++------ .../jscript/interop/OverloadFunction.java | 65 +++++++++---------- .../jscript/polyfills/FunctionPolyfill.java | 39 ++++------- .../jscript/polyfills/ObjectPolyfill.java | 8 +-- .../jscript/polyfills/PromisePolyfill.java | 25 +++---- 10 files changed, 84 insertions(+), 125 deletions(-) diff --git a/src/me/topchetoeu/jscript/interop/Native.java b/src/me/topchetoeu/jscript/interop/Native.java index c71992c..b0465a6 100644 --- a/src/me/topchetoeu/jscript/interop/Native.java +++ b/src/me/topchetoeu/jscript/interop/Native.java @@ -9,5 +9,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface Native { public String value() default ""; - public boolean raw() default false; + public boolean thisArg() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeConstructor.java b/src/me/topchetoeu/jscript/interop/NativeConstructor.java index 5603a77..e9aa462 100644 --- a/src/me/topchetoeu/jscript/interop/NativeConstructor.java +++ b/src/me/topchetoeu/jscript/interop/NativeConstructor.java @@ -8,6 +8,6 @@ import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface NativeConstructor { - public boolean raw() default false; + public boolean thisArg() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeGetter.java b/src/me/topchetoeu/jscript/interop/NativeGetter.java index 5e94a5e..62c9061 100644 --- a/src/me/topchetoeu/jscript/interop/NativeGetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeGetter.java @@ -9,5 +9,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface NativeGetter { public String value(); - public boolean raw() default false; + public boolean thisArg() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeSetter.java b/src/me/topchetoeu/jscript/interop/NativeSetter.java index b460831..33b7d73 100644 --- a/src/me/topchetoeu/jscript/interop/NativeSetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeSetter.java @@ -9,5 +9,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface NativeSetter { public String value(); - public boolean raw() default false; + public boolean thisArg() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java b/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java index f927415..3e90161 100644 --- a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java +++ b/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java @@ -13,23 +13,15 @@ public class NativeTypeRegister implements WrappersProvider { private final HashMap, FunctionValue> constructors = new HashMap<>(); private final HashMap, ObjectValue> prototypes = new HashMap<>(); - private static boolean isMember(Class[] args) { - return args.length == 3; - } - private static void applyMethods(boolean member, ObjectValue target, Class clazz) { for (var method : clazz.getDeclaredMethods()) { var nat = method.getAnnotation(Native.class); var get = method.getAnnotation(NativeGetter.class); var set = method.getAnnotation(NativeSetter.class); - var params = method.getParameterTypes(); var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; if (nat != null) { - if (nat.raw()) { - if (isMember(params) != member) continue; - } - else if (memberMismatch) continue; + if (nat.thisArg() != member && memberMismatch) continue; var name = nat.value(); var val = target.values.get(name); @@ -37,11 +29,12 @@ public class NativeTypeRegister implements WrappersProvider { if (name.equals("")) name = method.getName(); if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name)); - ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.raw())); + ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg())); } else { if (get != null) { - if (get.raw() && isMember(params) != member || memberMismatch) continue; + if (get.thisArg() != member && memberMismatch) continue; + var name = get.value(); var prop = target.properties.get(name); OverloadFunction getter = null; @@ -50,11 +43,12 @@ public class NativeTypeRegister implements WrappersProvider { if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; else getter = new OverloadFunction("get " + name); - getter.overloads.add(Overload.fromMethod(method, get.raw())); + getter.overloads.add(Overload.fromMethod(method, get.thisArg())); target.defineProperty(null, name, getter, setter, true, true); } if (set != null) { - if (set.raw() && isMember(params) != member || memberMismatch) continue; + if (set.thisArg() != member && memberMismatch) continue; + var name = set.value(); var prop = target.properties.get(name); var getter = prop == null ? null : prop.getter; @@ -63,7 +57,7 @@ public class NativeTypeRegister implements WrappersProvider { if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; else setter = new OverloadFunction("set " + name); - setter.overloads.add(Overload.fromMethod(method, set.raw())); + setter.overloads.add(Overload.fromMethod(method, set.thisArg())); target.defineProperty(null, name, getter, setter, true, true); } } @@ -77,8 +71,8 @@ public class NativeTypeRegister implements WrappersProvider { if (nat != null) { var name = nat.value(); if (name.equals("")) name = field.getName(); - var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field, nat.raw())); - var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field, nat.raw())); + var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field)); + var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field)); target.defineProperty(null, name, getter, setter, true, false); } } @@ -126,12 +120,12 @@ public class NativeTypeRegister implements WrappersProvider { for (var overload : clazz.getConstructors()) { var nat = overload.getAnnotation(Native.class); if (nat == null) continue; - ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.raw())); + ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg())); } for (var overload : clazz.getMethods()) { var constr = overload.getAnnotation(NativeConstructor.class); if (constr == null) continue; - ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.raw())); + ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); } if (((OverloadFunction)func).overloads.size() == 0) { diff --git a/src/me/topchetoeu/jscript/interop/Overload.java b/src/me/topchetoeu/jscript/interop/Overload.java index 740fe99..b325aeb 100644 --- a/src/me/topchetoeu/jscript/interop/Overload.java +++ b/src/me/topchetoeu/jscript/interop/Overload.java @@ -17,63 +17,55 @@ public class Overload { public final OverloadRunner runner; public final boolean variadic; - public final boolean raw; + public final boolean passThis; public final Class thisArg; public final Class[] params; - public static Overload fromMethod(Method method, boolean raw) { + public static Overload fromMethod(Method method, boolean passThis) { return new Overload( (ctx, th, args) -> method.invoke(th, args), - method.isVarArgs(), raw, + method.isVarArgs(), passThis, Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(), method.getParameterTypes() ); } - public static Overload fromConstructor(Constructor method, boolean raw) { + public static Overload fromConstructor(Constructor method, boolean passThis) { return new Overload( (ctx, th, args) -> method.newInstance(args), - method.isVarArgs(), raw, + method.isVarArgs(), passThis, Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(), method.getParameterTypes() ); } - public static Overload getterFromField(Field field, boolean raw) { + public static Overload getterFromField(Field field) { return new Overload( - (ctx, th, args) -> field.get(th), false, raw, + (ctx, th, args) -> field.get(th), false, false, Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(), new Class[0] ); } - public static Overload setterFromField(Field field, boolean raw) { + public static Overload setterFromField(Field field) { if (Modifier.isFinal(field.getModifiers())) return null; return new Overload( - (ctx, th, args) -> { field.set(th, args[0]); return null; }, false, raw, + (ctx, th, args) -> { field.set(th, args[0]); return null; }, false, false, Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(), new Class[0] ); } - public static Overload getter(Class thisArg, OverloadRunner runner, boolean raw) { + public static Overload getter(Class thisArg, OverloadRunner runner, boolean passThis) { return new Overload( - (ctx, th, args) -> runner.run(ctx, th, args), false, raw, + (ctx, th, args) -> runner.run(ctx, th, args), false, passThis, thisArg, new Class[0] ); } - public Overload(OverloadRunner runner, boolean variadic, boolean raw, Class thisArg, Class args[]) { + public Overload(OverloadRunner runner, boolean variadic, boolean passThis, Class thisArg, Class args[]) { this.runner = runner; this.variadic = variadic; - this.raw = raw; + this.passThis = passThis; this.thisArg = thisArg; this.params = args; - - if (raw) { - if (!( - thisArg == null && ( - args.length == 3 && args[0] == Context.class && args[1] == Object.class && args[2] == Object[].class || - args.length == 2 && args[0] == Context.class && args[1] == Object[].class - ))) throw new IllegalArgumentException("Invalid signature for raw method."); - } } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 89a66d9..0954302 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -18,53 +18,48 @@ public class OverloadFunction extends FunctionValue { loop: for (var overload : overloads) { Object[] newArgs = new Object[overload.params.length]; - if (overload.raw) { - newArgs[0] = ctx; - newArgs[1] = thisArg; - newArgs[2] = args; + boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class; + int start = (consumesEngine ? 1 : 0) + (overload.passThis ? 1 : 0); + int end = overload.params.length - (overload.variadic ? 1 : 0); + + for (var i = start; i < end; i++) { + Object val; + + if (i - start >= args.length) val = null; + else val = args[i - start]; + + try { + newArgs[i] = Values.convert(ctx, val, overload.params[i]); + } + catch (ConvertException e) { + if (overloads.size() > 1) continue loop; + else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); + } } - else { - boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class; - int start = consumesEngine ? 1 : 0; - int end = overload.params.length - (overload.variadic ? 1 : 0); - for (var i = start; i < end; i++) { - Object val; - - if (i - start >= args.length) val = null; - else val = args[i - start]; + if (overload.variadic) { + var type = overload.params[overload.params.length - 1].getComponentType(); + var n = Math.max(args.length - end + start, 0); + Object varArg = Array.newInstance(type, n); + for (var i = 0; i < n; i++) { try { - newArgs[i] = Values.convert(ctx, val, overload.params[i]); + Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type)); } catch (ConvertException e) { if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); + else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); } } - if (overload.variadic) { - var type = overload.params[overload.params.length - 1].getComponentType(); - var n = Math.max(args.length - end + start, 0); - Object varArg = Array.newInstance(type, n); - - for (var i = 0; i < n; i++) { - try { - Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type)); - } - catch (ConvertException e) { - if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); - } - } - - newArgs[newArgs.length - 1] = varArg; - } - - if (consumesEngine) newArgs[0] = ctx; + newArgs[newArgs.length - 1] = varArg; } - Object _this = overload.thisArg == null ? null : Values.convert(ctx, thisArg, overload.thisArg); + var thisArgType = overload.passThis ? overload.params[consumesEngine ? 1 : 0] : overload.thisArg; + Object _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType); + + if (consumesEngine) newArgs[0] = ctx; + if (overload.passThis) newArgs[consumesEngine ? 1 : 0] = _this; try { return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index 89253d0..cec1917 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -8,43 +8,26 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; public class FunctionPolyfill { - @Native(raw=true) public static Object apply(Context ctx, Object func, Object[] args) throws InterruptedException { - var thisArg = args.length > 0 ? args[0] : null; - var _args = args.length > 1 ? args[1] : null; - - if (!(_args instanceof ArrayValue)) throw EngineException.ofError("Expected arguments to be an array."); - if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); - - return ((FunctionValue)func).call(ctx, thisArg, ((ArrayValue)_args).toArray()); + @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { + return func.call(ctx, thisArg, args.toArray()); } - @Native(raw=true) public static Object call(Context ctx, Object func, Object[] args) throws InterruptedException { - var thisArg = args.length > 0 ? args[0] : null; - var _args = new Object[args.length > 1 ? args.length - 1 : 0]; - + @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); - if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length); - - return ((FunctionValue)func).call(ctx, thisArg, _args); + return func.call(ctx, thisArg, args); } - @Native(raw=true) public static Object bind(Context ctx, Object func, Object[] args) { - var thisArg = args.length > 0 ? args[0] : null; - var _args = new Object[args.length > 1 ? args.length - 1 : 0]; - FunctionValue _func = (FunctionValue)func; - + @Native(thisArg = true) public static Object bind(Context ctx, FunctionValue func, Object thisArg, Object... args) { if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); - if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length); + return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { + var resArgs = new Object[args.length + callArgs.length]; + System.arraycopy(args, 0, resArgs, 0, args.length); + System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length); - return new NativeFunction(_func.name + " (bound)", (callCtx, _0, callArgs) -> { - var resArgs = new Object[_args.length + callArgs.length]; - System.arraycopy(_args, 0, resArgs, 0, _args.length); - System.arraycopy(callArgs, 0, resArgs, _args.length, resArgs.length - _args.length); - - return _func.call(ctx, thisArg, resArgs); + return func.call(ctx, thisArg, resArgs); }); } - @Native(raw=true) public static String toString(Context ctx, Object func, Object[] args) { + @Native(thisArg = true) public static String toString(Context ctx, Object func) { return "function (...) { ... }"; } diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java index 10226a4..0c15f92 100644 --- a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -187,18 +187,18 @@ public class ObjectPolyfill { return true; } - @Native(raw = true) public static Object valueOf(Context ctx, Object thisArg, Object[] args) { + @Native(thisArg = true) public static Object valueOf(Context ctx, Object thisArg) { return thisArg; } - @Native(raw = true) public static String toString(Context ctx, Object thisArg, Object[] args) throws InterruptedException { + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { var name = Values.getMember(ctx, thisArg, ctx.env.symbol("Symbol.typeName")); if (name == null) name = "Unknown"; else name = Values.toString(ctx, name); return "[object " + name + "]"; } - @Native(raw = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object[] args) throws InterruptedException { - return ObjectPolyfill.hasOwn(ctx, thisArg, Values.convert(ctx, args.length == 0 ? null : args[0], String.class)); + @Native(thisArg = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object key) throws InterruptedException { + return ObjectPolyfill.hasOwn(ctx, thisArg, Values.convert(ctx, key, String.class)); } @NativeConstructor public static Object constructor(Context ctx, Object arg) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index a640572..dfffc22 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -152,12 +152,9 @@ public class PromisePolyfill { * Thread safe - you can call this from anywhere * HOWEVER, it's strongly recommended to use this only in javascript */ - @Native(raw = true) public static Object then(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - var onFulfill = args.length > 0 ? args[0] : null; - var onReject = args.length > 1 ? args[1]: null; - - if (!(onFulfill instanceof FunctionValue)) onFulfill = null; - if (!(onReject instanceof FunctionValue)) onReject = null; + @Native(thisArg=true) public static Object then(Context ctx, Object thisArg, Object _onFulfill, Object _onReject) throws InterruptedException { + var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null; + var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null; var res = new PromisePolyfill(); @@ -206,24 +203,22 @@ public class PromisePolyfill { * Thread safe - you can call this from anywhere * HOWEVER, it's strongly recommended to use this only in javascript */ - @Native(value = "catch", raw = true) public static Object _catch(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - return then(ctx, thisArg, null, args.length > 0 ? args[0] : null); + @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) throws InterruptedException { + 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", raw = true) public static Object _finally(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - var handleFunc = args.length > 0 ? args[0] : null; - + @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) throws InterruptedException { return then(ctx, thisArg, new NativeFunction(null, (e, th, _args) -> { - if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx); - return args[0]; + if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); + return _args.length > 0 ? _args[0] : null; }), new NativeFunction(null, (e, th, _args) -> { - if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx); - throw new EngineException(_args[0]); + if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); + throw new EngineException(_args.length > 0 ? _args[0] : null); }) ); } -- 2.45.2 From d8071af48083f67f84235163e1ed25baa666ecf6 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:50:48 +0300 Subject: [PATCH 04/23] refactor: remove dead .ts code --- lib/core.ts | 13 ++- lib/promise.ts | 208 ----------------------------------------- lib/tsconfig.json | 3 - lib/values/function.ts | 5 - lib/values/object.ts | 5 - 5 files changed, 10 insertions(+), 224 deletions(-) delete mode 100644 lib/promise.ts delete mode 100644 lib/values/function.ts delete mode 100644 lib/values/object.ts diff --git a/lib/core.ts b/lib/core.ts index 7a0c5b4..addd0b7 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -45,15 +45,22 @@ globalThis.log = internals.constructor.log; var i = 0.0; try { - run('values/object'); + var Object = env.global.Object = internals.object; + var Function = env.global.Function = internals.function; + var Promise = env.global.Promise = internals.promise; + + env.setProto('object', Object.prototype); + env.setProto('function', Function.prototype); + + (Object.prototype as any).__proto__ = null; + run('values/symbol'); - run('values/function'); + run('values/errors'); run('values/string'); run('values/number'); run('values/boolean'); run('values/array'); - run('promise'); run('map'); run('set'); run('regex'); diff --git a/lib/promise.ts b/lib/promise.ts deleted file mode 100644 index 1f7e8b7..0000000 --- a/lib/promise.ts +++ /dev/null @@ -1,208 +0,0 @@ -define("promise", () => { - var Promise = env.global.Promise = internals.promise - return; - - // const syms = { - // callbacks: internals.symbol('Promise.callbacks'), - // state: internals.symbol('Promise.state'), - // value: internals.symbol('Promise.value'), - // handled: internals.symbol('Promise.handled'), - // } as { - // readonly callbacks: unique symbol, - // readonly state: unique symbol, - // readonly value: unique symbol, - // readonly handled: unique symbol, - // } - - // type Callback = [ PromiseFulfillFunc, PromiseRejectFunc ]; - // enum State { - // Pending, - // Fulfilled, - // Rejected, - // } - - // function isAwaitable(val: unknown): val is Thenable { - // return ( - // typeof val === 'object' && - // val !== null && - // 'then' in val && - // typeof val.then === 'function' - // ); - // } - // function resolve(promise: Promise, v: any, state: State) { - // if (promise[syms.state] === State.Pending) { - // if (typeof v === 'object' && v !== null && 'then' in v && typeof v.then === 'function') { - // v.then( - // (res: any) => resolve(promise, res, state), - // (res: any) => resolve(promise, res, State.Rejected) - // ); - // return; - // } - // promise[syms.value] = v; - // promise[syms.state] = state; - - // for (let i = 0; i < promise[syms.callbacks]!.length; i++) { - // promise[syms.handled] = true; - // promise[syms.callbacks]![i][state - 1](v); - // } - - // promise[syms.callbacks] = undefined; - - // internals.pushMessage(true, internals.setEnv(() => { - // if (!promise[syms.handled] && state === State.Rejected) { - // log('Uncaught (in promise) ' + promise[syms.value]); - // } - // }, env), undefined, []); - // } - // } - - // class _Promise { - // public static isAwaitable(val: unknown): val is Thenable { - // return isAwaitable(val); - // } - - // public static resolve(val: T): Promise> { - // return new Promise(res => res(val as any)); - // } - // public static reject(val: T): Promise> { - // return new Promise((_, rej) => rej(val as any)); - // } - - // public static race(vals: T[]): Promise> { - // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.race is not variadic.'); - // return new Promise((res, rej) => { - // for (let i = 0; i < vals.length; i++) { - // const val = vals[i]; - // if (this.isAwaitable(val)) val.then(res, rej); - // else res(val as any); - // } - // }); - // } - // public static any(vals: T[]): Promise> { - // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.any is not variadic.'); - // return new Promise((res, rej) => { - // let n = 0; - - // for (let i = 0; i < vals.length; i++) { - // const val = vals[i]; - // if (this.isAwaitable(val)) val.then(res, (err) => { - // n++; - // if (n === vals.length) throw Error('No promise resolved.'); - // }); - // else res(val as any); - // } - - // if (vals.length === 0) throw Error('No promise resolved.'); - // }); - // } - // public static all(vals: any[]): Promise { - // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.all is not variadic.'); - // return new Promise((res, rej) => { - // const result: any[] = []; - // let n = 0; - - // for (let i = 0; i < vals.length; i++) { - // const val = vals[i]; - // if (this.isAwaitable(val)) val.then( - // val => { - // n++; - // result[i] = val; - // if (n === vals.length) res(result); - // }, - // rej - // ); - // else { - // n++; - // result[i] = val; - // } - // } - - // if (vals.length === n) res(result); - // }); - // } - // public static allSettled(vals: any[]): Promise { - // if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.allSettled is not variadic.'); - // return new Promise((res, rej) => { - // const result: any[] = []; - // let n = 0; - - // for (let i = 0; i < vals.length; i++) { - // const value = vals[i]; - // if (this.isAwaitable(value)) value.then( - // value => { - // n++; - // result[i] = { status: 'fulfilled', value }; - // if (n === vals.length) res(result); - // }, - // reason => { - // n++; - // result[i] = { status: 'rejected', reason }; - // if (n === vals.length) res(result); - // }, - // ); - // else { - // n++; - // result[i] = { status: 'fulfilled', value }; - // } - // } - - // if (vals.length === n) res(result); - // }); - // } - - // [syms.callbacks]?: Callback[] = []; - // [syms.handled] = false; - // [syms.state] = State.Pending; - // [syms.value]?: T | unknown; - - // public then(onFulfil?: PromiseFulfillFunc, onReject?: PromiseRejectFunc) { - // return new Promise((resolve, reject) => { - // onFulfil ??= v => v; - // onReject ??= v => v; - - // const callback = (func: (val: any) => any) => (v: any) => { - // try { - // resolve(func(v)); - // } - // catch (e) { reject(e); } - // } - // switch (this[syms.state]) { - // case State.Pending: - // this[syms.callbacks]![this[syms.callbacks]!.length] = [callback(onFulfil), callback(onReject)]; - // break; - // case State.Fulfilled: - // this[syms.handled] = true; - // callback(onFulfil)(this[syms.value]); - // break; - // case State.Rejected: - // this[syms.handled] = true; - // callback(onReject)(this[syms.value]); - // break; - // } - // }) - // } - // public catch(func: PromiseRejectFunc) { - // return this.then(undefined, func); - // } - // public finally(func: () => void) { - // return this.then( - // v => { - // func(); - // return v; - // }, - // v => { - // func(); - // throw v; - // } - // ); - // } - - // public constructor(func: PromiseFunc) { - // internals.pushMessage(true, func, undefined, [ - // ((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc, - // ((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc - // ]); - // } - // } - // env.global.Promise = Promise as any; -}); diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 8c3e2af..e802b34 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -3,15 +3,12 @@ "lib.d.ts", "modules.ts", "utils.ts", - "values/object.ts", "values/symbol.ts", - "values/function.ts", "values/errors.ts", "values/string.ts", "values/number.ts", "values/boolean.ts", "values/array.ts", - "promise.ts", "map.ts", "set.ts", "regex.ts", diff --git a/lib/values/function.ts b/lib/values/function.ts deleted file mode 100644 index 4636d5d..0000000 --- a/lib/values/function.ts +++ /dev/null @@ -1,5 +0,0 @@ -define("values/function", () => { - var Function = env.global.Function = internals.function; - env.setProto('function', Function.prototype); - setConstr(Function.prototype, Function); -}); \ No newline at end of file diff --git a/lib/values/object.ts b/lib/values/object.ts deleted file mode 100644 index 0473a1e..0000000 --- a/lib/values/object.ts +++ /dev/null @@ -1,5 +0,0 @@ -define("values/object", () => { - var Object = env.global.Object = internals.object; - (Object.prototype as any).__proto__ = null; - env.setProto('object', Object.prototype); -}); \ No newline at end of file -- 2.45.2 From f2cd50726d28c48cf17c6defa03600741aefe641 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 24 Sep 2023 12:56:53 +0300 Subject: [PATCH 05/23] feat: implement array polyfills in java --- lib/core.ts | 22 +- lib/tsconfig.json | 1 - lib/utils.ts | 2 +- lib/values/array.ts | 336 -------------- src/me/topchetoeu/jscript/Main.java | 13 +- .../jscript/engine/Environment.java | 13 +- .../jscript/engine/WrappersProvider.java | 3 +- .../jscript/engine/frame/CodeFrame.java | 4 + .../jscript/engine/values/ArrayValue.java | 156 +++++-- .../jscript/engine/values/ObjectValue.java | 2 +- .../jscript/engine/values/Symbol.java | 2 +- .../jscript/engine/values/Values.java | 4 +- .../jscript/interop/NativeGetter.java | 2 +- .../jscript/interop/NativeSetter.java | 2 +- ...gister.java => NativeWrapperProvider.java} | 437 +++++++++--------- .../jscript/interop/OverloadFunction.java | 5 +- .../jscript/polyfills/ArrayPolyfill.java | 350 ++++++++++++++ .../jscript/polyfills/FunctionPolyfill.java | 2 +- .../jscript/polyfills/Internals.java | 19 +- .../jscript/polyfills/PromisePolyfill.java | 2 +- 20 files changed, 741 insertions(+), 636 deletions(-) delete mode 100644 lib/values/array.ts rename src/me/topchetoeu/jscript/interop/{NativeTypeRegister.java => NativeWrapperProvider.java} (73%) create mode 100644 src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index addd0b7..c1485fe 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -7,7 +7,8 @@ interface Environment { interface Internals { object: ObjectConstructor; function: FunctionConstructor; - promise: typeof Promise; + array: ArrayConstructor; + promise: PromiseConstructor; markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; @@ -35,32 +36,31 @@ interface Internals { sort(arr: any[], comaprator: (a: any, b: any) => number): void; - constructor: { - log(...args: any[]): void; - } + log(...args: any[]): void; } -var env: Environment = arguments[0], internals: Internals = arguments[1]; -globalThis.log = internals.constructor.log; -var i = 0.0; - try { + var env: Environment = arguments[0], internals: Internals = arguments[1]; + var Object = env.global.Object = internals.object; var Function = env.global.Function = internals.function; + var Array = env.global.Array = internals.array; var Promise = env.global.Promise = internals.promise; env.setProto('object', Object.prototype); env.setProto('function', Function.prototype); - + env.setProto('array', Array.prototype); (Object.prototype as any).__proto__ = null; - run('values/symbol'); + internals.getEnv(run)?.setProto('array', Array.prototype); + globalThis.log = (...args) => internals.apply(internals.log, internals, args); + + run('values/symbol'); run('values/errors'); run('values/string'); run('values/number'); run('values/boolean'); - run('values/array'); run('map'); run('set'); run('regex'); diff --git a/lib/tsconfig.json b/lib/tsconfig.json index e802b34..01d7996 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -8,7 +8,6 @@ "values/string.ts", "values/number.ts", "values/boolean.ts", - "values/array.ts", "map.ts", "set.ts", "regex.ts", diff --git a/lib/utils.ts b/lib/utils.ts index 6fdf694..fa3ca0a 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -7,7 +7,7 @@ function setProps< } >(target: TargetT, desc: DescT) { var props = internals.keys(desc, false); - for (var i = 0; i < props.length; i++) { + for (var i = 0; i in props; i++) { var key = props[i]; internals.defineField( target, key, (desc as any)[key], diff --git a/lib/values/array.ts b/lib/values/array.ts deleted file mode 100644 index 12928ba..0000000 --- a/lib/values/array.ts +++ /dev/null @@ -1,336 +0,0 @@ -define("values/array", () => { - var Array = env.global.Array = function(len?: number) { - var res = []; - - if (typeof len === 'number' && arguments.length === 1) { - if (len < 0) throw 'Invalid array length.'; - res.length = len; - } - else { - for (var i = 0; i < arguments.length; i++) { - res[i] = arguments[i]; - } - } - - return res; - } as ArrayConstructor; - - env.setProto('array', Array.prototype); - (Array.prototype as any)[env.global.Symbol.typeName] = "Array"; - setConstr(Array.prototype, Array); - - setProps(Array.prototype, { - [env.global.Symbol.iterator]: function() { - return this.values(); - }, - [env.global.Symbol.typeName]: "Array", - - values() { - var i = 0; - - return { - next: () => { - while (i < this.length) { - if (i++ in this) return { done: false, value: this[i - 1] }; - } - return { done: true, value: undefined }; - }, - [env.global.Symbol.iterator]() { return this; } - }; - }, - keys() { - var i = 0; - - return { - next: () => { - while (i < this.length) { - if (i++ in this) return { done: false, value: i - 1 }; - } - return { done: true, value: undefined }; - }, - [env.global.Symbol.iterator]() { return this; } - }; - }, - entries() { - var i = 0; - - return { - next: () => { - while (i < this.length) { - if (i++ in this) return { done: false, value: [i - 1, this[i - 1]] }; - } - return { done: true, value: undefined }; - }, - [env.global.Symbol.iterator]() { return this; } - }; - }, - concat() { - var res = [] as any[]; - res.push.apply(res, this); - - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - if (arg instanceof Array) { - res.push.apply(res, arg); - } - else { - res.push(arg); - } - } - - return res; - }, - every(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument not a function."); - func = func.bind(thisArg); - - for (var i = 0; i < this.length; i++) { - if (!func(this[i], i, this)) return false; - } - - return true; - }, - some(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument not a function."); - func = func.bind(thisArg); - - for (var i = 0; i < this.length; i++) { - if (func(this[i], i, this)) return true; - } - - return false; - }, - fill(val, start, end) { - if (arguments.length < 3) end = this.length; - if (arguments.length < 2) start = 0; - - start = clampI(this.length, wrapI(this.length + 1, start ?? 0)); - end = clampI(this.length, wrapI(this.length + 1, end ?? this.length)); - - for (; start < end; start++) { - this[start] = val; - } - - return this; - }, - filter(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument is not a function."); - - var res = []; - for (var i = 0; i < this.length; i++) { - if (i in this && func.call(thisArg, this[i], i, this)) res.push(this[i]); - } - return res; - }, - find(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument is not a function."); - - for (var i = 0; i < this.length; i++) { - if (i in this && func.call(thisArg, this[i], i, this)) return this[i]; - } - - return undefined; - }, - findIndex(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument is not a function."); - - for (var i = 0; i < this.length; i++) { - if (i in this && func.call(thisArg, this[i], i, this)) return i; - } - - return -1; - }, - findLast(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument is not a function."); - - for (var i = this.length - 1; i >= 0; i--) { - if (i in this && func.call(thisArg, this[i], i, this)) return this[i]; - } - - return undefined; - }, - findLastIndex(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument is not a function."); - - for (var i = this.length - 1; i >= 0; i--) { - if (i in this && func.call(thisArg, this[i], i, this)) return i; - } - - return -1; - }, - flat(depth) { - var res = [] as any[]; - var buff = []; - res.push(...this); - - for (var i = 0; i < (depth ?? 1); i++) { - var anyArrays = false; - for (var el of res) { - if (el instanceof Array) { - buff.push(...el); - anyArrays = true; - } - else buff.push(el); - } - - res = buff; - buff = []; - if (!anyArrays) break; - } - - return res; - }, - flatMap(func, th) { - return this.map(func, th).flat(); - }, - forEach(func, thisArg) { - for (var i = 0; i < this.length; i++) { - if (i in this) func.call(thisArg, this[i], i, this); - } - }, - map(func, thisArg) { - if (typeof func !== 'function') throw new TypeError("Given argument is not a function."); - - var res = []; - for (var i = 0; i < this.length; i++) { - if (i in this) res[i] = func.call(thisArg, this[i], i, this); - } - return res; - }, - pop() { - if (this.length === 0) return undefined; - var val = this[this.length - 1]; - this.length--; - return val; - }, - push() { - for (var i = 0; i < arguments.length; i++) { - this[this.length] = arguments[i]; - } - return arguments.length; - }, - shift() { - if (this.length === 0) return undefined; - var res = this[0]; - - for (var i = 0; i < this.length - 1; i++) { - this[i] = this[i + 1]; - } - - this.length--; - - return res; - }, - unshift() { - for (var i = this.length - 1; i >= 0; i--) { - this[i + arguments.length] = this[i]; - } - for (var i = 0; i < arguments.length; i++) { - this[i] = arguments[i]; - } - - return arguments.length; - }, - slice(start, end) { - start = clampI(this.length, wrapI(this.length + 1, start ?? 0)); - end = clampI(this.length, wrapI(this.length + 1, end ?? this.length)); - - var res: any[] = []; - var n = end - start; - if (n <= 0) return res; - - for (var i = 0; i < n; i++) { - res[i] = this[start + i]; - } - - return res; - }, - toString() { - let res = ''; - for (let i = 0; i < this.length; i++) { - if (i > 0) res += ','; - if (i in this && this[i] !== undefined && this[i] !== null) res += this[i]; - } - - return res; - }, - indexOf(el, start) { - start = start! | 0; - for (var i = Math.max(0, start); i < this.length; i++) { - if (i in this && this[i] == el) return i; - } - - return -1; - }, - lastIndexOf(el, start) { - start = start! | 0; - for (var i = this.length; i >= start; i--) { - if (i in this && this[i] == el) return i; - } - - return -1; - }, - includes(el, start) { - return this.indexOf(el, start) >= 0; - }, - join(val = ',') { - let res = '', first = true; - - for (let i = 0; i < this.length; i++) { - if (!(i in this)) continue; - if (!first) res += val; - first = false; - res += this[i]; - } - return res; - }, - sort(func) { - func ??= (a, b) => { - const _a = a + ''; - const _b = b + ''; - - if (_a > _b) return 1; - if (_a < _b) return -1; - return 0; - }; - - if (typeof func !== 'function') throw new TypeError('Expected func to be undefined or a function.'); - - internals.sort(this, func); - return this; - }, - splice(start, deleteCount, ...items) { - start = clampI(this.length, wrapI(this.length, start ?? 0)); - deleteCount = (deleteCount ?? Infinity | 0); - if (start + deleteCount >= this.length) deleteCount = this.length - start; - - const res = this.slice(start, start + deleteCount); - const moveN = items.length - deleteCount; - const len = this.length; - - if (moveN < 0) { - for (let i = start - moveN; i < len; i++) { - this[i + moveN] = this[i]; - } - } - else if (moveN > 0) { - for (let i = len - 1; i >= start; i--) { - this[i + moveN] = this[i]; - } - } - - for (let i = 0; i < items.length; i++) { - this[i + start] = items[i]; - } - - this.length = len + moveN; - - return res; - } - }); - - setProps(Array, { - isArray(val: any) { return internals.isArray(val); } - }); - internals.markSpecial(Array); -}); \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 6f580d1..309aeed 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -15,7 +15,6 @@ import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; -import me.topchetoeu.jscript.interop.NativeTypeRegister; import me.topchetoeu.jscript.polyfills.Internals; public class Main { @@ -63,9 +62,8 @@ public class Main { var in = new BufferedReader(new InputStreamReader(System.in)); engine = new Engine(); - // TODO: Replace type register with safer accessor - env = new Environment(null, new NativeTypeRegister(), null); - var builderEnv = new Environment(null, new NativeTypeRegister(), null); + env = new Environment(null, null, null); + var builderEnv = new Environment(null, null, null); var exited = new boolean[1]; env.global.define("exit", ctx -> { @@ -83,7 +81,12 @@ public class Main { } }); - engine.pushMsg(false, new Context(builderEnv, new MessageContext(engine)), "core.js", resourceToString("js/core.js"), null, env, new Internals()).toObservable().on(valuePrinter); + engine.pushMsg( + false, + new Context(builderEnv, new MessageContext(engine)), + "core.js", resourceToString("js/core.js"), + null, env, new Internals(env) + ).toObservable().on(valuePrinter); task = engine.start(); var reader = new Thread(() -> { diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 164112d..c9a4cbe 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -11,6 +11,7 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeSetter; +import me.topchetoeu.jscript.interop.NativeWrapperProvider; public class Environment { private HashMap prototypes = new HashMap<>(); @@ -33,7 +34,8 @@ public class Environment { } @Native public Symbol symbol(String name) { - if (symbols.containsKey(name)) return symbols.get(name); + if (symbols.containsKey(name)) + return symbols.get(name); else { var res = new Symbol(name); symbols.put(name, res); @@ -79,14 +81,7 @@ public class Environment { 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 WrappersProvider() { - public ObjectValue getConstr(Class obj) { - throw EngineException.ofType("Java objects not passable to Javascript."); - } - public ObjectValue getProto(Class obj) { - throw EngineException.ofType("Java objects not passable to Javascript."); - } - }; + if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); if (global == null) global = new GlobalScope(); this.wrappersProvider = nativeConverter; diff --git a/src/me/topchetoeu/jscript/engine/WrappersProvider.java b/src/me/topchetoeu/jscript/engine/WrappersProvider.java index 6205da8..dd27353 100644 --- a/src/me/topchetoeu/jscript/engine/WrappersProvider.java +++ b/src/me/topchetoeu/jscript/engine/WrappersProvider.java @@ -1,8 +1,9 @@ package me.topchetoeu.jscript.engine; +import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.ObjectValue; public interface WrappersProvider { public ObjectValue getProto(Class obj); - public ObjectValue getConstr(Class obj); + public FunctionValue getConstr(Class obj); } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 96b8350..818a4ae 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -268,6 +268,10 @@ public class CodeFrame { if (res != Runners.NO_RETURN) return res; } } + catch (Throwable e) { + // e.printStackTrace(); + throw e; + } finally { ctx.message.popFrame(this); } diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java index 0bd59ea..8841810 100644 --- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java @@ -1,90 +1,128 @@ package me.topchetoeu.jscript.engine.values; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import me.topchetoeu.jscript.engine.Context; -public class ArrayValue extends ObjectValue { - private static final Object EMPTY = new Object(); - private final ArrayList values = new ArrayList<>(); +// TODO: Make methods generic +public class ArrayValue extends ObjectValue implements Iterable { + private static final Object UNDEFINED = new Object(); + private Object[] values; + private int size; - public int size() { return values.size(); } + private void alloc(int index) { + if (index < values.length) return; + if (index < values.length * 2) index = values.length * 2; + + var arr = new Object[index]; + System.arraycopy(values, 0, arr, 0, values.length); + values = arr; + } + + public int size() { return size; } public boolean setSize(int val) { if (val < 0) return false; - while (size() > val) { - values.remove(values.size() - 1); - } - while (size() < val) { - values.add(EMPTY); + if (size > val) shrink(size - val); + else { + alloc(val); + size = val; } return true; } public Object get(int i) { - if (i < 0 || i >= values.size()) return null; - var res = values.get(i); - if (res == EMPTY) return null; + if (i < 0 || i >= size) return null; + var res = values[i]; + if (res == UNDEFINED) return null; else return res; } public void set(Context ctx, int i, Object val) { if (i < 0) return; - while (values.size() <= i) { - values.add(EMPTY); - } + alloc(i); - values.set(i, Values.normalize(ctx, val)); + val = Values.normalize(ctx, val); + if (val == null) val = UNDEFINED; + values[i] = val; + if (i >= size) size = i + 1; } public boolean has(int i) { - return i >= 0 && i < values.size() && values.get(i) != EMPTY; + return i >= 0 && i < values.length && values[i] != null; } public void remove(int i) { - if (i < 0 || i >= values.size()) return; - values.set(i, EMPTY); + if (i < 0 || i >= values.length) return; + values[i] = null; } public void shrink(int n) { - if (n > values.size()) values.clear(); + if (n >= values.length) { + values = new Object[16]; + size = 0; + } else { - for (int i = 0; i < n && values.size() > 0; i++) { - values.remove(values.size() - 1); + for (int i = 0; i < n; i++) { + values[--size] = null; } } } + public Object[] toArray() { + Object[] res = new Object[size]; + copyTo(res, 0, 0, size); + return res; + } + public void copyTo(Object[] arr, int sourceStart, int destStart, int count) { + for (var i = 0; i < count; i++) { + if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null; + if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null; + else arr[i + sourceStart] = values[i + destStart]; + } + } + public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) { + // Iterate in reverse to reallocate at most once + for (var i = count - 1; i >= 0; i--) { + if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null); + if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null); + else arr.set(ctx, i + destStart, values[i + sourceStart]); + } + } + + public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) { + for (var i = 0; i < count; i++) { + set(ctx, i + destStart, arr[i + sourceStart]); + } + } + + public void move(int srcI, int dstI, int n) { + alloc(dstI + n); + + System.arraycopy(values, srcI, values, dstI, n); + + if (dstI + n >= size) size = dstI + n; + } + public void sort(Comparator comparator) { - values.sort((a, b) -> { + Arrays.sort(values, 0, size, (a, b) -> { var _a = 0; var _b = 0; - if (a == null) _a = 1; - if (a == EMPTY) _a = 2; + if (a == UNDEFINED) _a = 1; + if (a == null) _a = 2; - if (b == null) _b = 1; - if (b == EMPTY) _b = 2; + if (b == UNDEFINED) _b = 1; + if (b == null) _b = 2; - if (Integer.compare(_a, _b) != 0) return Integer.compare(_a, _b); + if (_a != 0 || _b != 0) return Integer.compare(_a, _b); return comparator.compare(a, b); }); } - public Object[] toArray() { - Object[] res = new Object[values.size()]; - - for (var i = 0; i < values.size(); i++) { - if (values.get(i) == EMPTY) res[i] = null; - else res[i] = values.get(i); - } - - return res; - } - @Override protected Object getField(Context ctx, Object key) throws InterruptedException { - if (key.equals("length")) return values.size(); if (key instanceof Number) { var i = ((Number)key).doubleValue(); if (i >= 0 && i - Math.floor(i) == 0) { @@ -96,9 +134,6 @@ public class ArrayValue extends ObjectValue { } @Override protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { - if (key.equals("length")) { - return setSize((int)Values.toNumber(ctx, val)); - } if (key instanceof Number) { var i = Values.number(key); if (i >= 0 && i - Math.floor(i) == 0) { @@ -111,7 +146,6 @@ public class ArrayValue extends ObjectValue { } @Override protected boolean hasField(Context ctx, Object key) throws InterruptedException { - if (key.equals("length")) return true; if (key instanceof Number) { var i = Values.number(key); if (i >= 0 && i - Math.floor(i) == 0) { @@ -140,18 +174,42 @@ public class ArrayValue extends ObjectValue { for (var i = 0; i < size(); i++) { if (has(i)) res.add(i); } - if (includeNonEnumerable) res.add("length"); return res; } + @Override + public Iterator iterator() { + return new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return get(i++); + } + }; + } + public ArrayValue() { super(PlaceholderProto.ARRAY); - nonEnumerableSet.add("length"); - nonConfigurableSet.add("length"); + values = new Object[16]; + size = 0; + } + public ArrayValue(int cap) { + super(PlaceholderProto.ARRAY); + values = new Object[cap]; + size = 0; } public ArrayValue(Context ctx, Object ...values) { this(); - for (var i = 0; i < values.length; i++) this.values.add(Values.normalize(ctx, values[i])); + values = new Object[values.length]; + size = values.length; + + for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]); } public static ArrayValue of(Context ctx, Collection values) { diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 054ab3e..eae0dce 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -233,7 +233,7 @@ public class ObjectValue { public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException { key = Values.normalize(ctx, key); - if (key.equals("__proto__")) { + if ("__proto__".equals(key)) { var res = getPrototype(ctx); return res == null ? Values.NULL : res; } diff --git a/src/me/topchetoeu/jscript/engine/values/Symbol.java b/src/me/topchetoeu/jscript/engine/values/Symbol.java index 5715430..116ae0b 100644 --- a/src/me/topchetoeu/jscript/engine/values/Symbol.java +++ b/src/me/topchetoeu/jscript/engine/values/Symbol.java @@ -10,6 +10,6 @@ public final class Symbol { @Override public String toString() { if (value == null) return "Symbol"; - else return "Symbol(" + value + ")"; + else return "@@" + value; } } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 839c6e3..03dffd9 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -517,7 +517,7 @@ public class Values { if (obj == null) return null; if (clazz.isInstance(obj)) return (T)obj; - throw new ConvertException(type(obj), clazz.getName()); + throw new ConvertException(type(obj), clazz.getSimpleName()); } public static Iterable toJavaIterable(Context ctx, Object obj) throws InterruptedException { @@ -631,7 +631,7 @@ public class Values { if (i != 0) System.out.print(", "); else System.out.print(" "); if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab); - else System.out.print(", "); + else System.out.print(""); } System.out.print(" ] "); } diff --git a/src/me/topchetoeu/jscript/interop/NativeGetter.java b/src/me/topchetoeu/jscript/interop/NativeGetter.java index 62c9061..7a625ad 100644 --- a/src/me/topchetoeu/jscript/interop/NativeGetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeGetter.java @@ -8,6 +8,6 @@ import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface NativeGetter { - public String value(); + public String value() default ""; public boolean thisArg() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeSetter.java b/src/me/topchetoeu/jscript/interop/NativeSetter.java index 33b7d73..225eccb 100644 --- a/src/me/topchetoeu/jscript/interop/NativeSetter.java +++ b/src/me/topchetoeu/jscript/interop/NativeSetter.java @@ -8,6 +8,6 @@ import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface NativeSetter { - public String value(); + public String value() default ""; public boolean thisArg() default false; } diff --git a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java similarity index 73% rename from src/me/topchetoeu/jscript/interop/NativeTypeRegister.java rename to src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 3e90161..018a256 100644 --- a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -1,210 +1,227 @@ -package me.topchetoeu.jscript.interop; - -import java.lang.reflect.Modifier; -import java.util.HashMap; - -import me.topchetoeu.jscript.engine.WrappersProvider; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.NativeFunction; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.exceptions.EngineException; - -public class NativeTypeRegister implements WrappersProvider { - private final HashMap, FunctionValue> constructors = new HashMap<>(); - private final HashMap, ObjectValue> prototypes = new HashMap<>(); - - private static void applyMethods(boolean member, ObjectValue target, Class clazz) { - for (var method : clazz.getDeclaredMethods()) { - var nat = method.getAnnotation(Native.class); - var get = method.getAnnotation(NativeGetter.class); - var set = method.getAnnotation(NativeSetter.class); - var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; - - if (nat != null) { - if (nat.thisArg() != member && memberMismatch) continue; - - var name = nat.value(); - var val = target.values.get(name); - - if (name.equals("")) name = method.getName(); - if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name)); - - ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg())); - } - else { - if (get != null) { - if (get.thisArg() != member && memberMismatch) continue; - - var name = get.value(); - var prop = target.properties.get(name); - OverloadFunction getter = null; - var setter = prop == null ? null : prop.setter; - - if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; - else getter = new OverloadFunction("get " + name); - - getter.overloads.add(Overload.fromMethod(method, get.thisArg())); - target.defineProperty(null, name, getter, setter, true, true); - } - if (set != null) { - if (set.thisArg() != member && memberMismatch) continue; - - var name = set.value(); - var prop = target.properties.get(name); - var getter = prop == null ? null : prop.getter; - OverloadFunction setter = null; - - if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; - else setter = new OverloadFunction("set " + name); - - setter.overloads.add(Overload.fromMethod(method, set.thisArg())); - target.defineProperty(null, name, getter, setter, true, true); - } - } - } - } - private static void applyFields(boolean member, ObjectValue target, Class clazz) { - for (var field : clazz.getDeclaredFields()) { - if (!Modifier.isStatic(field.getModifiers()) != member) continue; - var nat = field.getAnnotation(Native.class); - - if (nat != null) { - var name = nat.value(); - if (name.equals("")) name = field.getName(); - var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field)); - var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field)); - target.defineProperty(null, name, getter, setter, true, false); - } - } - } - private static void applyClasses(boolean member, ObjectValue target, Class clazz) { - for (var cl : clazz.getDeclaredClasses()) { - if (!Modifier.isStatic(cl.getModifiers()) != member) continue; - var nat = cl.getAnnotation(Native.class); - - if (nat != null) { - var name = nat.value(); - if (name.equals("")) name = cl.getSimpleName(); - - var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl); - - target.defineProperty(null, name, getter, null, true, false); - } - } - } - - /** - * Generates a prototype for the given class. - * The returned object will have appropriate wrappers for all instance members. - * All accessors and methods will expect the this argument to be a native wrapper of the given class type. - * @param clazz The class for which a prototype should be generated - */ - public static ObjectValue makeProto(Class clazz) { - var res = new ObjectValue(); - - applyMethods(true, res, clazz); - applyFields(true, res, clazz); - applyClasses(true, res, clazz); - - return res; - } - /** - * Generates a constructor for the given class. - * The returned function will have appropriate wrappers for all static members. - * When the function gets called, the underlying constructor will get called, unless the constructor is inaccessible. - * @param clazz The class for which a constructor should be generated - */ - public static FunctionValue makeConstructor(Class clazz) { - FunctionValue func = new OverloadFunction(clazz.getName()); - - for (var overload : clazz.getConstructors()) { - var nat = overload.getAnnotation(Native.class); - if (nat == null) continue; - ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg())); - } - for (var overload : clazz.getMethods()) { - var constr = overload.getAnnotation(NativeConstructor.class); - if (constr == null) continue; - ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); - } - - if (((OverloadFunction)func).overloads.size() == 0) { - func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); - } - - applyMethods(false, func, clazz); - applyFields(false, func, clazz); - applyClasses(false, func, clazz); - - func.special = true; - - return func; - } - /** - * Generates a namespace for the given class. - * The returned function will have appropriate wrappers for all static members. - * This method behaves almost like {@link NativeTypeRegister#makeConstructor}, but will return an object instead. - * @param clazz The class for which a constructor should be generated - */ - public static ObjectValue makeNamespace(Class clazz) { - ObjectValue res = new ObjectValue(); - - applyMethods(false, res, clazz); - applyFields(false, res, clazz); - applyClasses(false, res, clazz); - - return res; - } - - private void initType(Class clazz, FunctionValue constr, ObjectValue proto) { - if (constr != null && proto != null) return; - // i vomit - if ( - clazz == Object.class || - clazz == Void.class || - clazz == Number.class || clazz == Double.class || clazz == Float.class || - clazz == Long.class || clazz == Integer.class || clazz == Short.class || - clazz == Character.class || clazz == Byte.class || clazz == Boolean.class || - clazz.isPrimitive() || - clazz.isArray() || - clazz.isAnonymousClass() || - clazz.isEnum() || - clazz.isInterface() || - clazz.isSynthetic() - ) return; - - if (constr == null) constr = makeConstructor(clazz); - if (proto == null) proto = makeProto(clazz); - - proto.values.put("constructor", constr); - constr.values.put("prototype", proto); - - prototypes.put(clazz, proto); - constructors.put(clazz, constr); - - var parent = clazz.getSuperclass(); - if (parent == null) return; - - var parentProto = getProto(parent); - var parentConstr = getConstr(parent); - - if (parentProto != null) proto.setPrototype(null, parentProto); - if (parentConstr != null) constr.setPrototype(null, parentConstr); - } - - public ObjectValue getProto(Class clazz) { - initType(clazz, constructors.get(clazz), prototypes.get(clazz)); - return prototypes.get(clazz); - } - public FunctionValue getConstr(Class clazz) { - initType(clazz, constructors.get(clazz), prototypes.get(clazz)); - return constructors.get(clazz); - } - - public void setProto(Class clazz, ObjectValue value) { - prototypes.put(clazz, value); - } - public void setConstr(Class clazz, FunctionValue value) { - constructors.put(clazz, value); - } -} +package me.topchetoeu.jscript.interop; + +import java.lang.reflect.Modifier; +import java.util.HashMap; + +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.WrappersProvider; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.NativeFunction; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.exceptions.EngineException; + +public class NativeWrapperProvider implements WrappersProvider { + private final HashMap, FunctionValue> constructors = new HashMap<>(); + private final HashMap, ObjectValue> prototypes = new HashMap<>(); + private final Environment env; + + private static void applyMethods(Environment env, boolean member, ObjectValue target, Class clazz) { + for (var method : clazz.getDeclaredMethods()) { + var nat = method.getAnnotation(Native.class); + var get = method.getAnnotation(NativeGetter.class); + var set = method.getAnnotation(NativeSetter.class); + var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; + + if (nat != null) { + if (nat.thisArg() != member && memberMismatch) continue; + + Object name = nat.value(); + if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + else if (name.equals("")) name = method.getName(); + + var val = target.values.get(name); + + if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString())); + + ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg())); + } + else { + if (get != null) { + if (get.thisArg() != member && memberMismatch) continue; + + Object name = get.value(); + if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + else if (name.equals("")) name = method.getName(); + + var prop = target.properties.get(name); + OverloadFunction getter = null; + var setter = prop == null ? null : prop.setter; + + if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; + else getter = new OverloadFunction("get " + name); + + getter.overloads.add(Overload.fromMethod(method, get.thisArg())); + target.defineProperty(null, name, getter, setter, true, true); + } + if (set != null) { + if (set.thisArg() != member && memberMismatch) continue; + + Object name = set.value(); + if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + else if (name.equals("")) name = method.getName(); + + var prop = target.properties.get(name); + var getter = prop == null ? null : prop.getter; + OverloadFunction setter = null; + + if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; + else setter = new OverloadFunction("set " + name); + + setter.overloads.add(Overload.fromMethod(method, set.thisArg())); + target.defineProperty(null, name, getter, setter, true, true); + } + } + } + } + private static void applyFields(Environment env, boolean member, ObjectValue target, Class clazz) { + for (var field : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers()) != member) continue; + var nat = field.getAnnotation(Native.class); + + if (nat != null) { + Object name = nat.value(); + if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + else if (name.equals("")) name = field.getName(); + + var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field)); + var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field)); + target.defineProperty(null, name, getter, setter, true, false); + } + } + } + private static void applyClasses(Environment env, boolean member, ObjectValue target, Class clazz) { + for (var cl : clazz.getDeclaredClasses()) { + if (!Modifier.isStatic(cl.getModifiers()) != member) continue; + var nat = cl.getAnnotation(Native.class); + + if (nat != null) { + Object name = nat.value(); + if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + else if (name.equals("")) name = cl.getName(); + + var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl); + + target.defineProperty(null, name, getter, null, true, false); + } + } + } + + /** + * Generates a prototype for the given class. + * The returned object will have appropriate wrappers for all instance members. + * All accessors and methods will expect the this argument to be a native wrapper of the given class type. + * @param clazz The class for which a prototype should be generated + */ + public static ObjectValue makeProto(Environment ctx, Class clazz) { + var res = new ObjectValue(); + + applyMethods(ctx, true, res, clazz); + applyFields(ctx, true, res, clazz); + applyClasses(ctx, true, res, clazz); + + return res; + } + /** + * Generates a constructor for the given class. + * The returned function will have appropriate wrappers for all static members. + * When the function gets called, the underlying constructor will get called, unless the constructor is inaccessible. + * @param clazz The class for which a constructor should be generated + */ + public static FunctionValue makeConstructor(Environment ctx, Class clazz) { + FunctionValue func = new OverloadFunction(clazz.getName()); + + for (var overload : clazz.getConstructors()) { + var nat = overload.getAnnotation(Native.class); + if (nat == null) continue; + ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg())); + } + for (var overload : clazz.getMethods()) { + var constr = overload.getAnnotation(NativeConstructor.class); + if (constr == null) continue; + ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); + } + + if (((OverloadFunction)func).overloads.size() == 0) { + func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); + } + + applyMethods(ctx, false, func, clazz); + applyFields(ctx, false, func, clazz); + applyClasses(ctx, false, func, clazz); + + func.special = true; + + return func; + } + /** + * Generates a namespace for the given class. + * The returned function will have appropriate wrappers for all static members. + * This method behaves almost like {@link NativeWrapperProvider#makeConstructor}, but will return an object instead. + * @param clazz The class for which a constructor should be generated + */ + public static ObjectValue makeNamespace(Environment ctx, Class clazz) { + ObjectValue res = new ObjectValue(); + + applyMethods(ctx, false, res, clazz); + applyFields(ctx, false, res, clazz); + applyClasses(ctx, false, res, clazz); + + return res; + } + + private void initType(Class clazz, FunctionValue constr, ObjectValue proto) { + if (constr != null && proto != null) return; + // i vomit + if ( + clazz == Object.class || + clazz == Void.class || + clazz == Number.class || clazz == Double.class || clazz == Float.class || + clazz == Long.class || clazz == Integer.class || clazz == Short.class || + clazz == Character.class || clazz == Byte.class || clazz == Boolean.class || + clazz.isPrimitive() || + clazz.isArray() || + clazz.isAnonymousClass() || + clazz.isEnum() || + clazz.isInterface() || + clazz.isSynthetic() + ) return; + + if (constr == null) constr = makeConstructor(env, clazz); + if (proto == null) proto = makeProto(env, clazz); + + proto.values.put("constructor", constr); + constr.values.put("prototype", proto); + + prototypes.put(clazz, proto); + constructors.put(clazz, constr); + + var parent = clazz.getSuperclass(); + if (parent == null) return; + + var parentProto = getProto(parent); + var parentConstr = getConstr(parent); + + if (parentProto != null) proto.setPrototype(null, parentProto); + if (parentConstr != null) constr.setPrototype(null, parentConstr); + } + + public ObjectValue getProto(Class clazz) { + initType(clazz, constructors.get(clazz), prototypes.get(clazz)); + return prototypes.get(clazz); + } + public FunctionValue getConstr(Class clazz) { + initType(clazz, constructors.get(clazz), prototypes.get(clazz)); + return constructors.get(clazz); + } + + public void setProto(Class clazz, ObjectValue value) { + prototypes.put(clazz, value); + } + public void setConstr(Class clazz, FunctionValue value) { + constructors.put(clazz, value); + } + + public NativeWrapperProvider(Environment env) { + this.env = env; + } +} diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 0954302..2ad62d2 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -59,7 +59,10 @@ public class OverloadFunction extends FunctionValue { Object _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType); if (consumesEngine) newArgs[0] = ctx; - if (overload.passThis) newArgs[consumesEngine ? 1 : 0] = _this; + if (overload.passThis) { + newArgs[consumesEngine ? 1 : 0] = _this; + _this = null; + } try { return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); diff --git a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java new file mode 100644 index 0000000..a2901a7 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java @@ -0,0 +1,350 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.Iterator; +import java.util.Stack; + +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.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeSetter; + +public class ArrayPolyfill { + @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException { + return thisArg.size(); + } + @NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) throws InterruptedException { + thisArg.setSize(len); + } + + @Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) throws InterruptedException { + return Values.fromJavaIterable(ctx, thisArg); + } + @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) throws InterruptedException { + return Values.fromJavaIterable(ctx, () -> new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < thisArg.size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return i++; + } + }); + } + @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) throws InterruptedException { + return Values.fromJavaIterable(ctx, () -> new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < thisArg.size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return new ArrayValue(ctx, i, thisArg.get(i++)); + } + }); + } + + @Native(value = "@@Symbol.iterator", thisArg = true) + public static ObjectValue iterator(Context ctx, ArrayValue thisArg) throws InterruptedException { + return values(ctx, thisArg); + } + @Native(value = "@@Symbol.asyncIterator", thisArg = true) + public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) throws InterruptedException { + return values(ctx, thisArg); + } + + @Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) throws InterruptedException { + // TODO: Fully implement with non-array spreadable objects + var size = 0; + + for (int i = 0; i < others.length; i++) { + if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size(); + else i++; + } + + var res = new ArrayValue(size); + + for (int i = 0, j = 0; i < others.length; i++) { + if (others[i] instanceof ArrayValue) { + int n = ((ArrayValue)others[i]).size(); + ((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n); + j += n; + } + else { + res.set(ctx, j++, others[i]); + } + } + + return res; + } + + @Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) throws InterruptedException { + try { + arr.sort((a, b) -> { + try { + var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); + if (res < 0) return -1; + if (res > 0) return 1; + return 0; + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + } + catch (RuntimeException e) { + if (e.getCause() instanceof InterruptedException) throw (InterruptedException)e.getCause(); + else throw e; + } + } + + private static int normalizeI(int len, int i, boolean clamp) { + if (i < 0) i += len; + if (clamp) { + if (i < 0) i = 0; + if (i >= len) i = len; + } + return i; + } + + @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) throws InterruptedException { + start = normalizeI(arr.size(), start, true); + end = normalizeI(arr.size(), end, true); + + for (; start < end; start++) { + arr.set(ctx, start, val); + } + + return arr; + } + @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { + return fill(ctx, arr, val, start, arr.size()); + } + @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) throws InterruptedException { + return fill(ctx, arr, val, 0, arr.size()); + } + + @Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + for (var i = 0; i < arr.size(); i++) { + if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false; + } + + return true; + } + @Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + for (var i = 0; i < arr.size(); i++) { + if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true; + } + + return false; + } + + @Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + var res = new ArrayValue(arr.size()); + + for (int i = 0, j = 0; i < arr.size(); i++) { + if (arr.has(i) && Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) res.set(ctx, j++, arr.get(i)); + } + return res; + } + @Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + var res = new ArrayValue(arr.size()); + for (int i = 0, j = 0; i < arr.size(); i++) { + if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr)); + } + return res; + } + @Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr); + } + } + + @Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) throws InterruptedException { + var res = new ArrayValue(arr.size()); + var stack = new Stack(); + var depths = new Stack(); + + stack.push(arr); + depths.push(-1); + + while (!stack.empty()) { + var el = stack.pop(); + int d = depths.pop(); + + if (d <= depth && el instanceof ArrayValue) { + for (int i = ((ArrayValue)el).size() - 1; i >= 0; i--) { + stack.push(((ArrayValue)el).get(i)); + depths.push(d + 1); + } + } + else res.set(ctx, depth, arr); + } + + return res; + } + @Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + return flat(ctx, map(ctx, arr, cmp, thisArg), 1); + } + + @Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); + } + + return null; + } + @Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + for (var i = arr.size() - 1; i >= 0; i--) { + if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); + } + + return null; + } + + @Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; + } + + return -1; + } + @Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + for (var i = arr.size() - 1; i >= 0; i--) { + if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; + } + + return -1; + } + + @Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { + start = normalizeI(arr.size(), start, true); + + for (int i = 0; i < arr.size() && i < start; i++) { + if (Values.strictEquals(ctx, arr.get(i), val)) return i; + } + + return -1; + } + @Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { + start = normalizeI(arr.size(), start, true); + + for (int i = arr.size(); i >= start; i--) { + if (Values.strictEquals(ctx, arr.get(i), val)) return i; + } + + return -1; + } + + @Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) throws InterruptedException { + return indexOf(ctx, arr, el, start) >= 0; + } + + @Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) throws InterruptedException { + if (arr.size() == 0) return null; + var val = arr.get(arr.size() - 1); + arr.shrink(1); + return val; + } + @Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException { + arr.copyFrom(ctx, values, 0, arr.size(), values.length); + return arr.size(); + } + + @Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) throws InterruptedException { + if (arr.size() == 0) return null; + var val = arr.get(0); + arr.move(1, 0, arr.size()); + arr.shrink(1); + return val; + } + @Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException { + arr.move(0, values.length, arr.size()); + arr.copyFrom(ctx, values, 0, 0, values.length); + return arr.size(); + } + + @Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, int end) { + start = normalizeI(arr.size(), start, true); + end = normalizeI(arr.size(), end, true); + + var res = new ArrayValue(end - start); + arr.copyTo(ctx, res, start, 0, end - start); + return res; + } + @Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start) { + return slice(ctx, arr, start, arr.size()); + } + + @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, int deleteCount, Object ...items) throws InterruptedException { + start = normalizeI(arr.size(), start, true); + deleteCount = normalizeI(arr.size(), deleteCount, true); + if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start; + + var size = arr.size() - deleteCount + items.length; + var res = new ArrayValue(deleteCount); + arr.copyTo(ctx, res, start, 0, deleteCount); + arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount); + arr.copyFrom(ctx, items, 0, start, items.length); + arr.setSize(size); + + return res; + } + @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) throws InterruptedException { + return splice(ctx, arr, start, arr.size() - start); + } + @Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) throws InterruptedException { + return join(ctx, arr, ","); + } + + @Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) throws InterruptedException { + var res = new StringBuilder(); + var comma = true; + + for (int i = 0; i < arr.size(); i++) { + if (!arr.has(i)) continue; + if (comma) res.append(sep); + comma = false; + var el = arr.get(i); + if (el == null || el == Values.NULL) continue; + + res.append(Values.toString(ctx, el)); + } + + return res.toString(); + } + + @Native public static boolean isArray(Context ctx, Object val) { return val instanceof ArrayValue; } + @Native public static ArrayValue of(Context ctx, Object... args) { + var res = new ArrayValue(args.length); + res.copyFrom(ctx, args, 0, 0, args.length); + return res; + } + + @NativeConstructor public static ArrayValue constructor(Context ctx, Object... args) { + ArrayValue res; + + if (args.length == 1 && args[0] instanceof Number) { + int len = ((Number)args[0]).intValue(); + res = new ArrayValue(len); + res.setSize(len); + } + else { + res = new ArrayValue(args.length); + res.copyFrom(ctx, args, 0, 0, args.length); + } + + return res; + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index cec1917..439642e 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -16,7 +16,7 @@ public class FunctionPolyfill { return func.call(ctx, thisArg, args); } - @Native(thisArg = true) public static Object bind(Context ctx, FunctionValue func, Object thisArg, Object... args) { + @Native(thisArg = true) public static FunctionValue bind(Context ctx, FunctionValue func, Object thisArg, Object... args) { if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index e584be7..d09bb4b 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -12,9 +12,12 @@ import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.interop.Native; public class Internals { - @Native public final Class object = ObjectPolyfill.class; - @Native public final Class function = FunctionPolyfill.class; - @Native public final Class promise = PromisePolyfill.class; + public final Environment targetEnv; + + @Native public final FunctionValue object; + @Native public final FunctionValue function; + @Native public final FunctionValue promise; + @Native public final FunctionValue array; @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { @@ -79,7 +82,7 @@ public class Internals { return str.value; } - @Native public static void log(Context ctx, Object ...args) throws InterruptedException { + @Native public void log(Context ctx, Object ...args) throws InterruptedException { for (var arg : args) { Values.printValue(ctx, arg); } @@ -150,4 +153,12 @@ public class Internals { } }); } + + public Internals(Environment targetEnv) { + this.targetEnv = targetEnv; + this.object = targetEnv.wrappersProvider.getConstr(ObjectPolyfill.class); + this.function = targetEnv.wrappersProvider.getConstr(FunctionPolyfill.class); + this.promise = targetEnv.wrappersProvider.getConstr(PromisePolyfill.class); + this.array = targetEnv.wrappersProvider.getConstr(ArrayPolyfill.class); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index dfffc22..741f052 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -262,7 +262,7 @@ public class PromisePolyfill { if (handles.size() == 0) { ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> { if (!handled) { - try { Values.printError(new EngineException(val), "(in promise)"); } + try { Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); } catch (InterruptedException ex) { } } -- 2.45.2 From 86b206051d0daeea3ad67c901362976ec0ae1ee4 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 24 Sep 2023 13:49:37 +0300 Subject: [PATCH 06/23] feat: implement bool polyfills in java --- lib/core.ts | 6 +++-- lib/lib.d.ts | 1 + lib/tsconfig.json | 1 - lib/values/boolean.ts | 12 ---------- .../jscript/engine/values/Values.java | 5 ++-- .../jscript/polyfills/BooleanPolyfill.java | 24 +++++++++++++++++++ .../jscript/polyfills/Internals.java | 6 ++--- .../jscript/polyfills/ObjectPolyfill.java | 4 ++-- 8 files changed, 35 insertions(+), 24 deletions(-) delete mode 100644 lib/values/boolean.ts create mode 100644 src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index c1485fe..a19dc25 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -9,6 +9,7 @@ interface Internals { function: FunctionConstructor; array: ArrayConstructor; promise: PromiseConstructor; + bool: BooleanConstructor; markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; @@ -46,21 +47,22 @@ try { var Function = env.global.Function = internals.function; var Array = env.global.Array = internals.array; var Promise = env.global.Promise = internals.promise; + var Boolean = env.global.Boolean = internals.bool; env.setProto('object', Object.prototype); env.setProto('function', Function.prototype); env.setProto('array', Array.prototype); + env.setProto('bool', Boolean.prototype); + (Object.prototype as any).__proto__ = null; internals.getEnv(run)?.setProto('array', Array.prototype); - globalThis.log = (...args) => internals.apply(internals.log, internals, args); run('values/symbol'); run('values/errors'); run('values/string'); run('values/number'); - run('values/boolean'); run('map'); run('set'); run('regex'); diff --git a/lib/lib.d.ts b/lib/lib.d.ts index 4e83de2..5ce6aab 100644 --- a/lib/lib.d.ts +++ b/lib/lib.d.ts @@ -244,6 +244,7 @@ interface ArrayConstructor { } interface Boolean { + toString(): string; valueOf(): boolean; } interface BooleanConstructor { diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 01d7996..497b3a9 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -7,7 +7,6 @@ "values/errors.ts", "values/string.ts", "values/number.ts", - "values/boolean.ts", "map.ts", "set.ts", "regex.ts", diff --git a/lib/values/boolean.ts b/lib/values/boolean.ts deleted file mode 100644 index c0d45a2..0000000 --- a/lib/values/boolean.ts +++ /dev/null @@ -1,12 +0,0 @@ -define("values/boolean", () => { - var Boolean = env.global.Boolean = function (this: Boolean | undefined, arg) { - var val; - if (arguments.length === 0) val = false; - else val = !!arg; - if (this === undefined || this === null) return val; - else (this as any).value = val; - } as BooleanConstructor; - - env.setProto('bool', Boolean.prototype); - setConstr(Boolean.prototype, Boolean); -}); diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 03dffd9..8d892e3 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -377,14 +377,13 @@ public class Values { return function(func).call(ctx, thisArg, args); } public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException { - if (func instanceof FunctionValue && ((FunctionValue)func).special) return ((FunctionValue)func).call(ctx, null, args); - var res = new ObjectValue(); var proto = Values.getMember(ctx, func, "prototype"); res.setPrototype(ctx, proto); - call(ctx, func, res, args); + var ret = call(ctx, func, res, args); + if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret; return res; } diff --git a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java new file mode 100644 index 0000000..4d2c77e --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java @@ -0,0 +1,24 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.Context; +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.NativeConstructor; + +public class BooleanPolyfill { + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { + val = Values.toBoolean(val); + if (thisArg instanceof ObjectValue) { + ((ObjectValue)thisArg).defineProperty(ctx, "value", val); + return null; + } + else return val; + } + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { + return Values.toBoolean(thisArg) ? "true" : "false"; + } + @Native(thisArg = true) public static boolean valueOf(Context ctx, Object thisArg) { + return Values.toBoolean(thisArg); + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index d09bb4b..9d3d8cd 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -14,10 +14,7 @@ import me.topchetoeu.jscript.interop.Native; public class Internals { public final Environment targetEnv; - @Native public final FunctionValue object; - @Native public final FunctionValue function; - @Native public final FunctionValue promise; - @Native public final FunctionValue array; + @Native public final FunctionValue object, function, promise, array, bool; @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { @@ -160,5 +157,6 @@ public class Internals { this.function = targetEnv.wrappersProvider.getConstr(FunctionPolyfill.class); this.promise = targetEnv.wrappersProvider.getConstr(PromisePolyfill.class); this.array = targetEnv.wrappersProvider.getConstr(ArrayPolyfill.class); + this.bool = targetEnv.wrappersProvider.getConstr(BooleanPolyfill.class); } } diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java index 0c15f92..66387ee 100644 --- a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -201,9 +201,9 @@ public class ObjectPolyfill { return ObjectPolyfill.hasOwn(ctx, thisArg, Values.convert(ctx, key, String.class)); } - @NativeConstructor public static Object constructor(Context ctx, Object arg) throws InterruptedException { + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) throws InterruptedException { if (arg == null || arg == Values.NULL) return new ObjectValue(); - else if (arg instanceof Boolean) return Values.callNew(ctx, ctx.env.global.get(ctx, "Boolean"), arg); + else if (arg instanceof Boolean) return BooleanPolyfill.constructor(ctx, thisArg, arg); else if (arg instanceof Number) return Values.callNew(ctx, ctx.env.global.get(ctx, "Number"), arg); else if (arg instanceof String) return Values.callNew(ctx, ctx.env.global.get(ctx, "String"), arg); else if (arg instanceof Symbol) return Values.callNew(ctx, ctx.env.global.get(ctx, "Symbol"), arg); -- 2.45.2 From f21cdc831c980d4e7c094e2b91682f28242eb810 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 24 Sep 2023 14:15:12 +0300 Subject: [PATCH 07/23] fix: primitive values will be native classes now --- lib/core.ts | 6 ++- .../jscript/engine/values/Values.java | 6 +-- .../jscript/polyfills/BooleanPolyfill.java | 14 +++++-- .../jscript/polyfills/Internals.java | 3 +- .../jscript/polyfills/NumberPolyfill.java | 38 +++++++++++++++++++ 5 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index a19dc25..e1d0bdd 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -10,6 +10,7 @@ interface Internals { array: ArrayConstructor; promise: PromiseConstructor; bool: BooleanConstructor; + number: NumberConstructor; markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; @@ -48,11 +49,12 @@ try { var Array = env.global.Array = internals.array; var Promise = env.global.Promise = internals.promise; var Boolean = env.global.Boolean = internals.bool; + var Number = env.global.Number = internals.number; env.setProto('object', Object.prototype); env.setProto('function', Function.prototype); env.setProto('array', Array.prototype); - env.setProto('bool', Boolean.prototype); + env.setProto('number', Number.prototype); (Object.prototype as any).__proto__ = null; @@ -62,7 +64,7 @@ try { run('values/symbol'); run('values/errors'); run('values/string'); - run('values/number'); + // run('values/number'); run('map'); run('set'); run('regex'); diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 8d892e3..0d753d2 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -117,8 +117,8 @@ public class Values { public static double toNumber(Context ctx, Object obj) throws InterruptedException { var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF); - if (val instanceof Number) return number(obj); - if (val instanceof Boolean) return ((Boolean)obj) ? 1 : 0; + if (val instanceof Number) return number(val); + if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; if (val instanceof String) { try { return Double.parseDouble((String)val); @@ -134,7 +134,7 @@ public class Values { if (val == NULL) return "null"; if (val instanceof Number) { - var d = number(obj); + var d = number(val); if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; if (d == Double.POSITIVE_INFINITY) return "Infinity"; if (Double.isNaN(d)) return "NaN"; diff --git a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java index 4d2c77e..843e78b 100644 --- a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java @@ -7,12 +7,14 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; public class BooleanPolyfill { + public static final BooleanPolyfill TRUE = new BooleanPolyfill(true); + public static final BooleanPolyfill FALSE = new BooleanPolyfill(false); + + public final boolean value; + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { val = Values.toBoolean(val); - if (thisArg instanceof ObjectValue) { - ((ObjectValue)thisArg).defineProperty(ctx, "value", val); - return null; - } + if (thisArg instanceof ObjectValue) return (boolean)val ? TRUE : FALSE; else return val; } @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { @@ -21,4 +23,8 @@ public class BooleanPolyfill { @Native(thisArg = true) public static boolean valueOf(Context ctx, Object thisArg) { return Values.toBoolean(thisArg); } + + public BooleanPolyfill(boolean val) { + this.value = val; + } } diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 9d3d8cd..0c13990 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -14,7 +14,7 @@ import me.topchetoeu.jscript.interop.Native; public class Internals { public final Environment targetEnv; - @Native public final FunctionValue object, function, promise, array, bool; + @Native public final FunctionValue object, function, promise, array, bool, number; @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { @@ -158,5 +158,6 @@ public class Internals { this.promise = targetEnv.wrappersProvider.getConstr(PromisePolyfill.class); this.array = targetEnv.wrappersProvider.getConstr(ArrayPolyfill.class); this.bool = targetEnv.wrappersProvider.getConstr(BooleanPolyfill.class); + this.number = targetEnv.wrappersProvider.getConstr(NumberPolyfill.class); } } diff --git a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java new file mode 100644 index 0000000..c0c7e6a --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java @@ -0,0 +1,38 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.Context; +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.NativeConstructor; + +public class NumberPolyfill { + @Native public static final double EPSILON = java.lang.Math.ulp(1.0); + @Native public static final double MAX_SAFE_INTEGER = 9007199254740991.; + @Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; + // lmao big number go brrr + @Native public static final double MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.; + @Native public static final double MIN_VALUE = -MAX_VALUE; + @Native public static final double NaN = 0. / 0; + @Native public static final double NEGATIVE_INFINITY = -1. / 0; + @Native public static final double POSITIVE_INFINITY = 1. / 0; + + public final double value; + + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { + val = Values.toNumber(ctx, val); + if (thisArg instanceof ObjectValue) return new NumberPolyfill((double)val); + else return val; + } + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + return Values.toString(ctx, Values.toNumber(ctx, thisArg)); + } + @Native(thisArg = true) public static double valueOf(Context ctx, Object thisArg) throws InterruptedException { + if (thisArg instanceof NumberPolyfill) return ((NumberPolyfill)thisArg).value; + else return Values.toNumber(ctx, thisArg); + } + + public NumberPolyfill(double val) { + this.value = val; + } +} -- 2.45.2 From 4aaf2f26db34d79556028116759f137adb3b6aaf Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 24 Sep 2023 20:50:53 +0300 Subject: [PATCH 08/23] feat: add some standard functions to Number --- .../jscript/polyfills/NumberPolyfill.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java index c0c7e6a..a3859eb 100644 --- a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java @@ -19,6 +19,20 @@ public class NumberPolyfill { public final double value; + @Native public static boolean isFinite(Context ctx, double val) { return Double.isFinite(val); } + @Native public static boolean isInfinite(Context ctx, double val) { return Double.isInfinite(val); } + @Native public static boolean isNaN(Context ctx, double val) { return Double.isNaN(val); } + @Native public static boolean isSafeInteger(Context ctx, double val) { + return val > MIN_SAFE_INTEGER && val < MAX_SAFE_INTEGER; + } + + @Native public static double parseFloat(Context ctx, String val) throws InterruptedException { + return Values.toNumber(ctx, val); + } + @Native public static double parseInt(Context ctx, String val) throws InterruptedException { + return (long)Values.toNumber(ctx, val); + } + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { val = Values.toNumber(ctx, val); if (thisArg instanceof ObjectValue) return new NumberPolyfill((double)val); -- 2.45.2 From 47c62128ab9c1a353404a7c0858a7b3a2ca3889d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:18:36 +0300 Subject: [PATCH 09/23] feat: implement string polyfill in java --- lib/core.ts | 4 +- lib/tsconfig.json | 2 - lib/values/number.ts | 33 --- lib/values/string.ts | 267 ------------------ .../jscript/polyfills/Internals.java | 3 +- .../jscript/polyfills/StringPolyfill.java | 254 +++++++++++++++++ 6 files changed, 259 insertions(+), 304 deletions(-) delete mode 100644 lib/values/number.ts delete mode 100644 lib/values/string.ts create mode 100644 src/me/topchetoeu/jscript/polyfills/StringPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index e1d0bdd..dcab864 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -11,6 +11,7 @@ interface Internals { promise: PromiseConstructor; bool: BooleanConstructor; number: NumberConstructor; + string: StringConstructor; markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; @@ -50,11 +51,13 @@ try { var Promise = env.global.Promise = internals.promise; var Boolean = env.global.Boolean = internals.bool; var Number = env.global.Number = internals.number; + var String = env.global.String = internals.string; env.setProto('object', Object.prototype); env.setProto('function', Function.prototype); env.setProto('array', Array.prototype); env.setProto('number', Number.prototype); + env.setProto('string', String.prototype); (Object.prototype as any).__proto__ = null; @@ -64,7 +67,6 @@ try { run('values/symbol'); run('values/errors'); run('values/string'); - // run('values/number'); run('map'); run('set'); run('regex'); diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 497b3a9..bf26634 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -5,8 +5,6 @@ "utils.ts", "values/symbol.ts", "values/errors.ts", - "values/string.ts", - "values/number.ts", "map.ts", "set.ts", "regex.ts", diff --git a/lib/values/number.ts b/lib/values/number.ts deleted file mode 100644 index 57e8d48..0000000 --- a/lib/values/number.ts +++ /dev/null @@ -1,33 +0,0 @@ -define("values/number", () => { - var Number = env.global.Number = function(this: Number | undefined, arg: any) { - var val; - if (arguments.length === 0) val = 0; - else val = arg - 0; - if (this === undefined || this === null) return val; - else (this as any).value = val; - } as NumberConstructor; - - env.setProto('number', Number.prototype); - setConstr(Number.prototype, Number); - - setProps(Number.prototype, { - valueOf() { - if (typeof this === 'number') return this; - else return (this as any).value; - }, - toString() { - if (typeof this === 'number') return this + ''; - else return (this as any).value + ''; - } - }); - - setProps(Number, { - parseInt(val) { return Math.trunc(val as any - 0); }, - parseFloat(val) { return val as any - 0; }, - }); - - env.global.parseInt = Number.parseInt; - env.global.parseFloat = Number.parseFloat; - env.global.Object.defineProperty(env.global, 'NaN', { value: 0 / 0, writable: false }); - env.global.Object.defineProperty(env.global, 'Infinity', { value: 1 / 0, writable: false }); -}); \ No newline at end of file diff --git a/lib/values/string.ts b/lib/values/string.ts deleted file mode 100644 index bfce164..0000000 --- a/lib/values/string.ts +++ /dev/null @@ -1,267 +0,0 @@ -define("values/string", () => { - var String = env.global.String = function(this: String | undefined, arg: any) { - var val; - if (arguments.length === 0) val = ''; - else val = arg + ''; - if (this === undefined || this === null) return val; - else (this as any).value = val; - } as StringConstructor; - - env.setProto('string', String.prototype); - setConstr(String.prototype, String); - - setProps(String.prototype, { - toString() { - if (typeof this === 'string') return this; - else return (this as any).value; - }, - valueOf() { - if (typeof this === 'string') return this; - else return (this as any).value; - }, - - substring(start, end) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.substring(start, end); - else throw new Error('This function may be used only with primitive or object strings.'); - } - start = start ?? 0 | 0; - end = (end ?? this.length) | 0; - - const res = []; - - for (let i = start; i < end; i++) { - if (i >= 0 && i < this.length) res[res.length] = this[i]; - } - - return internals.stringFromStrings(res); - }, - substr(start, length) { - start = start ?? 0 | 0; - - if (start >= this.length) start = this.length - 1; - if (start < 0) start = 0; - - length = (length ?? this.length - start) | 0; - const end = length + start; - const res = []; - - for (let i = start; i < end; i++) { - if (i >= 0 && i < this.length) res[res.length] = this[i]; - } - - return internals.stringFromStrings(res); - }, - - toLowerCase() { - // TODO: Implement localization - const res = []; - - for (let i = 0; i < this.length; i++) { - const c = internals.char(this[i]); - - if (c >= 65 && c <= 90) res[i] = c - 65 + 97; - else res[i] = c; - } - - return internals.stringFromChars(res); - }, - toUpperCase() { - // TODO: Implement localization - const res = []; - - for (let i = 0; i < this.length; i++) { - const c = internals.char(this[i]); - - if (c >= 97 && c <= 122) res[i] = c - 97 + 65; - else res[i] = c; - } - - return internals.stringFromChars(res); - }, - - charAt(pos) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.charAt(pos); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - pos = pos | 0; - if (pos < 0 || pos >= this.length) return ''; - return this[pos]; - }, - charCodeAt(pos) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.charAt(pos); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - pos = pos | 0; - if (pos < 0 || pos >= this.length) return 0 / 0; - return internals.char(this[pos]); - }, - - startsWith(term, pos) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.startsWith(term, pos); - else throw new Error('This function may be used only with primitive or object strings.'); - } - pos = pos! | 0; - term = term + ""; - - if (pos < 0 || this.length < term.length + pos) return false; - - for (let i = 0; i < term.length; i++) { - if (this[i + pos] !== term[i]) return false; - } - - return true; - }, - endsWith(term, pos) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.endsWith(term, pos); - else throw new Error('This function may be used only with primitive or object strings.'); - } - pos = (pos ?? this.length) | 0; - term = term + ""; - - const start = pos - term.length; - - if (start < 0 || this.length < term.length + start) return false; - - for (let i = 0; i < term.length; i++) { - if (this[i + start] !== term[i]) return false; - } - - return true; - }, - - indexOf(term: any, start) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.indexOf(term, start); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - if (typeof term[env.global.Symbol.search] !== 'function') term = RegExp.escape(term); - - return term[env.global.Symbol.search](this, false, start); - }, - lastIndexOf(term: any, start) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.indexOf(term, start); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - if (typeof term[env.global.Symbol.search] !== 'function') term = RegExp.escape(term); - - return term[env.global.Symbol.search](this, true, start); - }, - includes(term, start) { - return this.indexOf(term, start) >= 0; - }, - - replace(pattern: any, val) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.replace(pattern, val); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - if (typeof pattern[env.global.Symbol.replace] !== 'function') pattern = RegExp.escape(pattern); - - return pattern[env.global.Symbol.replace](this, val); - }, - replaceAll(pattern: any, val) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.replace(pattern, val); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - if (typeof pattern[env.global.Symbol.replace] !== 'function') pattern = RegExp.escape(pattern, "g"); - if (pattern instanceof RegExp && !pattern.global) pattern = new pattern.constructor(pattern.source, pattern.flags + "g"); - - return pattern[env.global.Symbol.replace](this, val); - }, - - match(pattern: any) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.match(pattern); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - if (typeof pattern[env.global.Symbol.match] !== 'function') pattern = RegExp.escape(pattern); - - return pattern[env.global.Symbol.match](this); - }, - matchAll(pattern: any) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.matchAll(pattern); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - if (typeof pattern[env.global.Symbol.match] !== 'function') pattern = RegExp.escape(pattern, "g"); - if (pattern instanceof RegExp && !pattern.global) pattern = new pattern.constructor(pattern.source, pattern.flags + "g"); - - return pattern[env.global.Symbol.match](this); - }, - - split(pattern: any, lim, sensible) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.split(pattern, lim, sensible); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - if (typeof pattern[env.global.Symbol.split] !== 'function') pattern = RegExp.escape(pattern, "g"); - - return pattern[env.global.Symbol.split](this, lim, sensible); - }, - slice(start, end) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.slice(start, end); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - start = wrapI(this.length, start ?? 0 | 0); - end = wrapI(this.length, end ?? this.length | 0); - - if (start > end) return ''; - - return this.substring(start, end); - }, - - concat(...args) { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.concat(...args); - else throw new Error('This function may be used only with primitive or object strings.'); - } - - var res = this; - for (var arg of args) res += arg; - return res; - }, - - trim() { - return this - .replace(/^\s+/g, '') - .replace(/\s+$/g, ''); - } - }); - - setProps(String, { - fromCharCode(val) { - return internals.stringFromChars([val | 0]); - }, - }) - - env.global.Object.defineProperty(String.prototype, 'length', { - get() { - if (typeof this !== 'string') { - if (this instanceof String) return (this as any).value.length; - else throw new Error('This function may be used only with primitive or object strings.'); - } - - return internals.strlen(this); - }, - configurable: true, - enumerable: false, - }); -}); \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 0c13990..d37c148 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -14,7 +14,7 @@ import me.topchetoeu.jscript.interop.Native; public class Internals { public final Environment targetEnv; - @Native public final FunctionValue object, function, promise, array, bool, number; + @Native public final FunctionValue object, function, promise, array, bool, number, string; @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { @@ -159,5 +159,6 @@ public class Internals { this.array = targetEnv.wrappersProvider.getConstr(ArrayPolyfill.class); this.bool = targetEnv.wrappersProvider.getConstr(BooleanPolyfill.class); this.number = targetEnv.wrappersProvider.getConstr(NumberPolyfill.class); + this.string = targetEnv.wrappersProvider.getConstr(StringPolyfill.class); } } diff --git a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java new file mode 100644 index 0000000..29f43ae --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java @@ -0,0 +1,254 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.regex.Pattern; + +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.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeGetter; + +// TODO: implement index wrapping properly +public class StringPolyfill { + public final String value; + + private static String passThis(String funcName, Object val) { + if (val instanceof StringPolyfill) return ((StringPolyfill)val).value; + else if (val instanceof String) return (String)val; + else throw EngineException.ofType(String.format("'%s' may be called upon object and primitve strings.", funcName)); + } + private static int normalizeI(int i, int len, boolean clamp) { + if (i < 0) i += len; + if (clamp) { + if (i < 0) i = 0; + if (i >= len) i = len; + } + return i; + } + + @NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) { + return passThis("substring", thisArg).length(); + } + + @Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { + var val = passThis("substring", thisArg); + start = normalizeI(start, val.length(), true); + int end = normalizeI(_end == null ? val.length() : (int)Values.toNumber(ctx, _end), val.length(), true); + + return val.substring(start, end); + } + @Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) throws InterruptedException { + var val = passThis("substr", thisArg); + int len = _len == null ? val.length() - start : (int)Values.toNumber(ctx, _len); + return substring(ctx, val, start, start + len); + } + + @Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) { + return passThis("toLowerCase", thisArg).toLowerCase(); + } + @Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) { + return passThis("toUpperCase", thisArg).toUpperCase(); + } + + @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) { + return passThis("charAt", thisArg).charAt(i) + ""; + } + @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) { + return passThis("charCodeAt", thisArg).charAt(i); + } + + @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) { + return passThis("startsWith", thisArg).startsWith(term, pos); + } + @Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) throws InterruptedException { + var val = passThis("endsWith", thisArg); + return val.lastIndexOf(term, pos) >= 0; + } + + @Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) throws InterruptedException { + var val = passThis("indexOf", thisArg); + + if (term != null && term != Values.NULL && !(term instanceof String)) { + var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); + if (search instanceof FunctionValue) { + return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start)); + } + } + + return val.indexOf(Values.toString(ctx, term), start); + } + @Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { + var val = passThis("lastIndexOf", thisArg); + + if (term != null && term != Values.NULL && !(term instanceof String)) { + var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); + if (search instanceof FunctionValue) { + return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos)); + } + } + + return val.lastIndexOf(Values.toString(ctx, term), pos); + } + + @Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { + return lastIndexOf(ctx, passThis("includes", thisArg), term, pos) >= 0; + } + + @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + var val = passThis("replace", thisArg); + + if (term != null && term != Values.NULL && !(term instanceof String)) { + var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); + if (replace instanceof FunctionValue) { + return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); + } + } + + return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); + } + @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + var val = passThis("replaceAll", thisArg); + + if (term != null && term != Values.NULL && !(term instanceof String)) { + var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); + if (replace instanceof FunctionValue) { + return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); + } + } + + return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); + } + + @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + var val = passThis("match", thisArg); + + FunctionValue match; + + try { + var _match = Values.getMember(ctx, term, ctx.env.symbol("Symbol.match")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + else if (ctx.env.regexConstructor != null) { + var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), ""); + _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.match")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + else throw EngineException.ofError("Regular expressions don't support matching."); + } + else throw EngineException.ofError("Regular expressions not supported."); + } + catch (IllegalArgumentException e) { return new ArrayValue(ctx, ""); } + + var res = match.call(ctx, term, val); + if (res instanceof ArrayValue) return (ArrayValue)res; + else return new ArrayValue(ctx, ""); + } + @Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + var val = passThis("matchAll", thisArg); + + FunctionValue match = null; + + try { + var _match = Values.getMember(ctx, term, ctx.env.symbol("Symbol.matchAll")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + } + catch (IllegalArgumentException e) { } + + if (match == null && ctx.env.regexConstructor != null) { + var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), "g"); + var _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.matchAll")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + else throw EngineException.ofError("Regular expressions don't support matching."); + } + else throw EngineException.ofError("Regular expressions not supported."); + + return match.call(ctx, term, val); + } + + @Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) throws InterruptedException { + var val = passThis("split", thisArg); + + if (lim != null) lim = Values.toNumber(ctx, lim); + + if (term != null && term != Values.NULL && !(term instanceof String)) { + var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); + if (replace instanceof FunctionValue) { + var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible); + + if (tmp instanceof ArrayValue) { + var parts = new ArrayValue(((ArrayValue)tmp).size()); + for (int i = 0; i < parts.size(); i++) parts.set(ctx, i, Values.toString(ctx, ((ArrayValue)tmp).get(i))); + return parts; + } + } + } + + String[] parts; + var pattern = Pattern.quote(Values.toString(ctx, term)); + + if (lim == null) parts = val.split(pattern); + else if (sensible) parts = val.split(pattern, (int)(double)lim); + else { + var limit = (int)(double)lim; + parts = val.split(pattern, limit + 1); + ArrayValue res; + + if (parts.length > limit) res = new ArrayValue(limit); + else res = new ArrayValue(parts.length); + + for (var i = 0; i < parts.length; i++) res.set(ctx, i, parts[i]); + + return res; + } + + var res = new ArrayValue(parts.length); + var i = 0; + + for (; i < parts.length; i++) { + if (lim != null && (double)lim <= i) break; + res.set(ctx, i, parts[i]); + } + + return res; + } + + @Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { + return substring(ctx, passThis("slice", thisArg), start, _end); + } + + @Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) throws InterruptedException { + var res = new StringBuilder(passThis("concat", thisArg)); + + for (var el : args) res.append(Values.toString(ctx, el)); + + return res.toString(); + } + @Native(thisArg = true) public static String trim(Context ctx, Object thisArg) throws InterruptedException { + return passThis("trim", thisArg).trim(); + } + + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { + val = Values.toString(ctx, val); + if (thisArg instanceof ObjectValue) return new StringPolyfill((String)val); + else return val; + } + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + return Values.toString(ctx, Values.toNumber(ctx, thisArg)); + } + @Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) throws InterruptedException { + if (thisArg instanceof StringPolyfill) return ((StringPolyfill)thisArg).value; + else return Values.toString(ctx, thisArg); + } + + @Native public static String fromCharCode(int ...val) { + char[] arr = new char[val.length]; + for (var i = 0; i < val.length; i++) arr[i] = (char)val[i]; + return new String(arr); + } + + public StringPolyfill(String val) { + this.value = val; + } +} -- 2.45.2 From ff9b57aeb970fd0bd56f6caba3c18e756222bcc0 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:32:59 +0300 Subject: [PATCH 10/23] fix: remove string from core.ts --- lib/core.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/core.ts b/lib/core.ts index dcab864..64c44ef 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -66,7 +66,6 @@ try { run('values/symbol'); run('values/errors'); - run('values/string'); run('map'); run('set'); run('regex'); -- 2.45.2 From cf36b7adc525bd4a281b132ffeaf9859c6cd74b4 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:33:07 +0300 Subject: [PATCH 11/23] fix: some string polyfill fixes --- src/me/topchetoeu/jscript/polyfills/StringPolyfill.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java index 29f43ae..00bb90c 100644 --- a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java @@ -19,7 +19,7 @@ public class StringPolyfill { private static String passThis(String funcName, Object val) { if (val instanceof StringPolyfill) return ((StringPolyfill)val).value; else if (val instanceof String) return (String)val; - else throw EngineException.ofType(String.format("'%s' may be called upon object and primitve strings.", funcName)); + else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); } private static int normalizeI(int i, int len, boolean clamp) { if (i < 0) i += len; @@ -198,7 +198,7 @@ public class StringPolyfill { if (parts.length > limit) res = new ArrayValue(limit); else res = new ArrayValue(parts.length); - for (var i = 0; i < parts.length; i++) res.set(ctx, i, parts[i]); + for (var i = 0; i < parts.length && i < limit; i++) res.set(ctx, i, parts[i]); return res; } @@ -235,11 +235,10 @@ public class StringPolyfill { else return val; } @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { - return Values.toString(ctx, Values.toNumber(ctx, thisArg)); + return passThis("toString", thisArg); } @Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) throws InterruptedException { - if (thisArg instanceof StringPolyfill) return ((StringPolyfill)thisArg).value; - else return Values.toString(ctx, thisArg); + return passThis("valueOf", thisArg); } @Native public static String fromCharCode(int ...val) { -- 2.45.2 From e16c0fedb18bb6b0124bd886f0fc3cac08864f05 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 26 Sep 2023 08:31:27 +0300 Subject: [PATCH 12/23] feat: implement map and set polyfills in java --- lib/core.ts | 22 +++-- lib/map.ts | 93 ------------------- lib/set.ts | 81 ---------------- lib/values/symbol.ts | 4 +- .../jscript/engine/values/ArrayValue.java | 2 +- .../jscript/engine/values/Values.java | 13 ++- .../topchetoeu/jscript/interop/Overload.java | 2 +- .../jscript/interop/OverloadFunction.java | 21 ++++- .../jscript/polyfills/ArrayPolyfill.java | 1 + .../polyfills/AsyncFunctionPolyfill.java | 2 + .../polyfills/AsyncGeneratorPolyfill.java | 1 + .../jscript/polyfills/BooleanPolyfill.java | 2 + .../jscript/polyfills/FunctionPolyfill.java | 4 +- .../jscript/polyfills/GeneratorPolyfill.java | 2 + .../jscript/polyfills/Internals.java | 4 +- .../jscript/polyfills/MapPolyfill.java | 78 ++++++++++++++++ .../jscript/polyfills/NumberPolyfill.java | 2 + .../jscript/polyfills/ObjectPolyfill.java | 8 +- .../jscript/polyfills/PromisePolyfill.java | 2 + .../jscript/polyfills/SetPolyfill.java | 63 +++++++++++++ .../jscript/polyfills/StringPolyfill.java | 2 + 21 files changed, 208 insertions(+), 201 deletions(-) delete mode 100644 lib/map.ts delete mode 100644 lib/set.ts create mode 100644 src/me/topchetoeu/jscript/polyfills/MapPolyfill.java create mode 100644 src/me/topchetoeu/jscript/polyfills/SetPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index 64c44ef..c59bd70 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -12,6 +12,8 @@ interface Internals { bool: BooleanConstructor; number: NumberConstructor; string: StringConstructor; + map: typeof Map; + set: typeof Set; markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; @@ -45,19 +47,23 @@ interface Internals { try { var env: Environment = arguments[0], internals: Internals = arguments[1]; - var Object = env.global.Object = internals.object; - var Function = env.global.Function = internals.function; - var Array = env.global.Array = internals.array; - var Promise = env.global.Promise = internals.promise; - var Boolean = env.global.Boolean = internals.bool; - var Number = env.global.Number = internals.number; - var String = env.global.String = internals.string; + const Object = env.global.Object = internals.object; + const Function = env.global.Function = internals.function; + const Array = env.global.Array = internals.array; + const Promise = env.global.Promise = internals.promise; + const Boolean = env.global.Boolean = internals.bool; + const Number = env.global.Number = internals.number; + const String = env.global.String = internals.string; + + const Map = env.global.Map = internals.map; + const Set = env.global.Set = internals.set; env.setProto('object', Object.prototype); env.setProto('function', Function.prototype); env.setProto('array', Array.prototype); env.setProto('number', Number.prototype); env.setProto('string', String.prototype); + env.setProto('bool', Boolean.prototype); (Object.prototype as any).__proto__ = null; @@ -66,8 +72,6 @@ try { run('values/symbol'); run('values/errors'); - run('map'); - run('set'); run('regex'); run('timeout'); diff --git a/lib/map.ts b/lib/map.ts deleted file mode 100644 index b7745af..0000000 --- a/lib/map.ts +++ /dev/null @@ -1,93 +0,0 @@ -define("map", () => { - const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol }; - const Object = env.global.Object; - - class Map { - [syms.values]: any = {}; - - public [env.global.Symbol.iterator](): IterableIterator<[KeyT, ValueT]> { - return this.entries(); - } - - public clear() { - this[syms.values] = {}; - } - public delete(key: KeyT) { - if ((key as any) in this[syms.values]) { - delete this[syms.values]; - return true; - } - else return false; - } - - public entries(): IterableIterator<[KeyT, ValueT]> { - const keys = internals.ownPropKeys(this[syms.values]); - let i = 0; - - return { - next: () => { - if (i >= keys.length) return { done: true }; - else return { done: false, value: [ keys[i], this[syms.values][keys[i++]] ] } - }, - [env.global.Symbol.iterator]() { return this; } - } - } - public keys(): IterableIterator { - const keys = internals.ownPropKeys(this[syms.values]); - let i = 0; - - return { - next: () => { - if (i >= keys.length) return { done: true }; - else return { done: false, value: keys[i] } - }, - [env.global.Symbol.iterator]() { return this; } - } - } - public values(): IterableIterator { - const keys = internals.ownPropKeys(this[syms.values]); - let i = 0; - - return { - next: () => { - if (i >= keys.length) return { done: true }; - else return { done: false, value: this[syms.values][keys[i++]] } - }, - [env.global.Symbol.iterator]() { return this; } - } - } - - public get(key: KeyT) { - return this[syms.values][key]; - } - public set(key: KeyT, val: ValueT) { - this[syms.values][key] = val; - return this; - } - public has(key: KeyT) { - return (key as any) in this[syms.values][key]; - } - - public get size() { - return internals.ownPropKeys(this[syms.values]).length; - } - - public forEach(func: (key: KeyT, val: ValueT, map: Map) => void, thisArg?: any) { - const keys = internals.ownPropKeys(this[syms.values]); - - for (let i = 0; i < keys.length; i++) { - func(keys[i], this[syms.values][keys[i]], this); - } - } - - public constructor(iterable: Iterable<[KeyT, ValueT]>) { - const it = iterable[env.global.Symbol.iterator](); - - for (let el = it.next(); !el.done; el = it.next()) { - this[syms.values][el.value[0]] = el.value[1]; - } - } - } - - env.global.Map = Map; -}); diff --git a/lib/set.ts b/lib/set.ts deleted file mode 100644 index a0ac277..0000000 --- a/lib/set.ts +++ /dev/null @@ -1,81 +0,0 @@ -define("set", () => { - const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol }; - const Object = env.global.Object; - - class Set { - [syms.values]: any = {}; - - public [env.global.Symbol.iterator](): IterableIterator<[T, T]> { - return this.entries(); - } - - public clear() { - this[syms.values] = {}; - } - public delete(key: T) { - if ((key as any) in this[syms.values]) { - delete this[syms.values]; - return true; - } - else return false; - } - - public entries(): IterableIterator<[T, T]> { - const keys = internals.ownPropKeys(this[syms.values]); - let i = 0; - - return { - next: () => { - if (i >= keys.length) return { done: true }; - else return { done: false, value: [ keys[i], keys[i] ] } - }, - [env.global.Symbol.iterator]() { return this; } - } - } - public keys(): IterableIterator { - const keys = internals.ownPropKeys(this[syms.values]); - let i = 0; - - return { - next: () => { - if (i >= keys.length) return { done: true }; - else return { done: false, value: keys[i] } - }, - [env.global.Symbol.iterator]() { return this; } - } - } - public values(): IterableIterator { - return this.keys(); - } - - public add(val: T) { - this[syms.values][val] = undefined; - return this; - } - public has(key: T) { - return (key as any) in this[syms.values][key]; - } - - public get size() { - return internals.ownPropKeys(this[syms.values]).length; - } - - public forEach(func: (key: T, val: T, map: Set) => void, thisArg?: any) { - const keys = internals.ownPropKeys(this[syms.values]); - - for (let i = 0; i < keys.length; i++) { - func(keys[i], this[syms.values][keys[i]], this); - } - } - - public constructor(iterable: Iterable) { - const it = iterable[env.global.Symbol.iterator](); - - for (let el = it.next(); !el.done; el = it.next()) { - this[syms.values][el.value] = undefined; - } - } - } - - env.global.Set = Set; -}); diff --git a/lib/values/symbol.ts b/lib/values/symbol.ts index dff4b4f..8bcfe65 100644 --- a/lib/values/symbol.ts +++ b/lib/values/symbol.ts @@ -31,6 +31,6 @@ define("values/symbol", () => { asyncIterator: env.symbol('Symbol.asyncIterator') as any, }); - internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false); - internals.defineField(env.global, Symbol.typeName, 'Window', false, false, false); + // internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false); + // internals.defineField(env.global, Symbol.typeName, 'Window', false, false, false); }); \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java index 8841810..006c8d4 100644 --- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java @@ -206,7 +206,7 @@ public class ArrayValue extends ObjectValue implements Iterable { } public ArrayValue(Context ctx, Object ...values) { this(); - values = new Object[values.length]; + this.values = new Object[values.length]; size = values.length; for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]); diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 0d753d2..d909ede 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -519,7 +519,7 @@ public class Values { throw new ConvertException(type(obj), clazz.getSimpleName()); } - public static Iterable toJavaIterable(Context ctx, Object obj) throws InterruptedException { + public static Iterable toJavaIterable(Context ctx, Object obj) { return () -> { try { var symbol = ctx.env.symbol("Symbol.iterator"); @@ -527,7 +527,7 @@ public class Values { var iteratorFunc = getMember(ctx, obj, symbol); if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); var iterator = iteratorFunc instanceof FunctionValue ? - ((FunctionValue)iteratorFunc).call(ctx, iteratorFunc, obj) : + ((FunctionValue)iteratorFunc).call(ctx, obj, obj) : iteratorFunc; var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next"); @@ -536,7 +536,7 @@ public class Values { return new Iterator() { private Object value = null; public boolean consumed = true; - private FunctionValue next = function(iterator); + private FunctionValue next = (FunctionValue)nextFunc; private void loadNext() throws InterruptedException { if (next == null) value = null; @@ -588,9 +588,8 @@ public class Values { }; } - public static ObjectValue fromJavaIterable(Context ctx, Iterable iterable) throws InterruptedException { + public static ObjectValue fromJavaIterator(Context ctx, Iterator it) throws InterruptedException { var res = new ObjectValue(); - var it = iterable.iterator(); try { var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator"); @@ -606,6 +605,10 @@ public class Values { return res; } + public static ObjectValue fromJavaIterable(Context ctx, Iterable it) throws InterruptedException { + return fromJavaIterator(ctx, it.iterator()); + } + private static void printValue(Context ctx, Object val, HashSet passed, int tab) throws InterruptedException { if (passed.contains(val)) { System.out.print("[circular]"); diff --git a/src/me/topchetoeu/jscript/interop/Overload.java b/src/me/topchetoeu/jscript/interop/Overload.java index b325aeb..a4548e1 100644 --- a/src/me/topchetoeu/jscript/interop/Overload.java +++ b/src/me/topchetoeu/jscript/interop/Overload.java @@ -33,7 +33,7 @@ public class Overload { return new Overload( (ctx, th, args) -> method.newInstance(args), method.isVarArgs(), passThis, - Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(), + null, method.getParameterTypes() ); } diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 2ad62d2..bf0f20c 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -5,6 +5,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; +import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.Values; @@ -56,7 +57,15 @@ public class OverloadFunction extends FunctionValue { } var thisArgType = overload.passThis ? overload.params[consumesEngine ? 1 : 0] : overload.thisArg; - Object _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType); + Object _this; + + try { + _this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType); + } + catch (ConvertException e) { + if (overloads.size() > 1) continue loop; + else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target)); + } if (consumesEngine) newArgs[0] = ctx; if (overload.passThis) { @@ -74,15 +83,19 @@ public class OverloadFunction extends FunctionValue { continue; } catch (InvocationTargetException e) { + var loc = new Location(0, 0, ""); if (e.getTargetException() instanceof EngineException) { - throw ((EngineException)e.getTargetException()); + throw ((EngineException)e.getTargetException()).add(name, loc); + } + else if (e.getTargetException() instanceof NullPointerException) { + throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc); } else { - throw EngineException.ofError(e.getTargetException().getMessage()); + throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); } } catch (ReflectiveOperationException e) { - throw EngineException.ofError(e.getMessage()); + throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "")); } catch (Exception e) { throw e; diff --git a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java index a2901a7..499c620 100644 --- a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java @@ -14,6 +14,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeSetter; public class ArrayPolyfill { + @Native("@@Symbol.typeName") public final String name = "AsyncFunction"; @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException { return thisArg.size(); } diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java index 0661995..74d4996 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java @@ -7,11 +7,13 @@ import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; public class AsyncFunctionPolyfill extends FunctionValue { public final FunctionValue factory; public static class AsyncHelper { + @Native("@@Symbol.typeName") public final String name = "AsyncFunction"; public PromisePolyfill promise = new PromisePolyfill(); public CodeFrame frame; diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java index 4328d61..b2eb893 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java @@ -16,6 +16,7 @@ public class AsyncGeneratorPolyfill extends FunctionValue { public final FunctionValue factory; public static class AsyncGenerator { + @Native("@@Symbol.typeName") public final String name = "AsyncGenerator"; private int state = 0; private boolean done = false; private PromisePolyfill currPromise; diff --git a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java index 843e78b..df824b0 100644 --- a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java @@ -7,6 +7,8 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; public class BooleanPolyfill { + @Native("@@Symbol.typeName") public final String name = "Boolean"; + public static final BooleanPolyfill TRUE = new BooleanPolyfill(true); public static final BooleanPolyfill FALSE = new BooleanPolyfill(false); diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index 439642e..d4ba763 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -8,7 +8,9 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; public class FunctionPolyfill { - @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { + @Native("@@Symbol.typeName") public final String name = "Function"; + + @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { return func.call(ctx, thisArg, args.toArray()); } @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index 47f5080..429a2ca 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -18,6 +18,8 @@ public class GeneratorPolyfill extends FunctionValue { private boolean done = false; public CodeFrame frame; + @Native("@@Symbol.typeName") public final String name = "Generator"; + private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException { if (done) { if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index d37c148..6a5085f 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -14,7 +14,7 @@ import me.topchetoeu.jscript.interop.Native; public class Internals { public final Environment targetEnv; - @Native public final FunctionValue object, function, promise, array, bool, number, string; + @Native public final FunctionValue object, function, promise, array, bool, number, string, map, set; @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { @@ -160,5 +160,7 @@ public class Internals { this.bool = targetEnv.wrappersProvider.getConstr(BooleanPolyfill.class); this.number = targetEnv.wrappersProvider.getConstr(NumberPolyfill.class); this.string = targetEnv.wrappersProvider.getConstr(StringPolyfill.class); + this.map = targetEnv.wrappersProvider.getConstr(MapPolyfill.class); + this.set = targetEnv.wrappersProvider.getConstr(SetPolyfill.class); } } diff --git a/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java b/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java new file mode 100644 index 0000000..ce4277e --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java @@ -0,0 +1,78 @@ +package me.topchetoeu.jscript.polyfills; + +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; + +public class MapPolyfill { + @Native("@@Symbol.typeName") public final String name = "Map"; + private LinkedHashMap map = new LinkedHashMap<>(); + + @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { + 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) throws InterruptedException { + var res = map.entrySet().stream().map(v -> { + return new ArrayValue(ctx, v.getKey(), v.getValue()); + }).collect(Collectors.toList()); + return Values.fromJavaIterator(ctx, res.iterator()); + } + @Native public ObjectValue keys(Context ctx) throws InterruptedException { + var res = new ArrayList<>(map.keySet()); + return Values.fromJavaIterator(ctx, res.iterator()); + } + @Native public ObjectValue values(Context ctx) throws InterruptedException { + var res = new ArrayList<>(map.values()); + return Values.fromJavaIterator(ctx, res.iterator()); + } + + @Native public Object get(Object key) { + return map.get(key); + } + @Native public MapPolyfill 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(); + } + + @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { + var keys = new ArrayList<>(map.keySet()); + + for (var el : keys) func.call(ctx, thisArg, el, map.get(el), this); + } + + @Native public MapPolyfill(Context ctx, Object iterable) throws InterruptedException { + for (var el : Values.toJavaIterable(ctx, iterable)) { + try { + set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); + } + catch (IllegalArgumentException e) { } + } + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java index a3859eb..ddc89a1 100644 --- a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java @@ -7,6 +7,8 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; public class NumberPolyfill { + @Native("@@Symbol.typeName") public final String name = "Number"; + @Native public static final double EPSILON = java.lang.Math.ulp(1.0); @Native public static final double MAX_SAFE_INTEGER = 9007199254740991.; @Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java index 66387ee..9875a74 100644 --- a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -11,6 +11,8 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; public class ObjectPolyfill { + @Native("@@Symbol.typeName") public final String name = "Object"; + @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException { for (var obj : src) { for (var key : Values.getMembers(ctx, obj, true, true)) { @@ -204,9 +206,9 @@ public class ObjectPolyfill { @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) throws InterruptedException { if (arg == null || arg == Values.NULL) return new ObjectValue(); else if (arg instanceof Boolean) return BooleanPolyfill.constructor(ctx, thisArg, arg); - else if (arg instanceof Number) return Values.callNew(ctx, ctx.env.global.get(ctx, "Number"), arg); - else if (arg instanceof String) return Values.callNew(ctx, ctx.env.global.get(ctx, "String"), arg); - else if (arg instanceof Symbol) return Values.callNew(ctx, ctx.env.global.get(ctx, "Symbol"), arg); + else if (arg instanceof Number) return NumberPolyfill.constructor(ctx, thisArg, arg); + else if (arg instanceof String) return StringPolyfill.constructor(ctx, thisArg, arg); + // else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg); else return arg; } } diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index 741f052..41e977d 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -28,6 +28,8 @@ public class PromisePolyfill { } } + @Native("@@Symbol.typeName") public final String name = "Promise"; + @Native("resolve") public static PromisePolyfill ofResolved(Context ctx, Object val) throws InterruptedException { var res = new PromisePolyfill(); diff --git a/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java new file mode 100644 index 0000000..7822f2a --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java @@ -0,0 +1,63 @@ +package me.topchetoeu.jscript.polyfills; + +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; + +public class SetPolyfill { + @Native("@@Symbol.typeName") public final String name = "Set"; + private LinkedHashSet set = new LinkedHashSet<>(); + + @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { + return this.values(ctx); + } + + @Native public ObjectValue entries(Context ctx) throws InterruptedException { + var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); + return Values.fromJavaIterator(ctx, res.iterator()); + } + @Native public ObjectValue keys(Context ctx) throws InterruptedException { + var res = new ArrayList<>(set); + return Values.fromJavaIterator(ctx, res.iterator()); + } + @Native public ObjectValue values(Context ctx) throws InterruptedException { + var res = new ArrayList<>(set); + return Values.fromJavaIterator(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(); + } + + @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { + var keys = new ArrayList<>(set); + + for (var el : keys) func.call(ctx, thisArg, el, el, this); + } + + @Native public SetPolyfill(Context ctx, Object iterable) throws InterruptedException { + for (var el : Values.toJavaIterable(ctx, iterable)) add(el); + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java index 00bb90c..2068481 100644 --- a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java @@ -14,6 +14,8 @@ import me.topchetoeu.jscript.interop.NativeGetter; // TODO: implement index wrapping properly public class StringPolyfill { + @Native("@@Symbol.typeName") public final String name = "String"; + public final String value; private static String passThis(String funcName, Object val) { -- 2.45.2 From 6c719115759141b69c1d7a281434273ef5c4938e Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:29:49 +0300 Subject: [PATCH 13/23] feat: implement symbols in java --- lib/core.ts | 7 +- lib/tsconfig.json | 3 - lib/utils.ts | 11 --- lib/values/symbol.ts | 36 ---------- .../jscript/polyfills/Internals.java | 5 +- .../jscript/polyfills/SymbolPolyfill.java | 69 +++++++++++++++++++ 6 files changed, 77 insertions(+), 54 deletions(-) delete mode 100644 lib/values/symbol.ts create mode 100644 src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index c59bd70..61b0cd3 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -12,6 +12,8 @@ interface Internals { bool: BooleanConstructor; number: NumberConstructor; string: StringConstructor; + symbol: SymbolConstructor; + map: typeof Map; set: typeof Set; @@ -26,7 +28,7 @@ interface Internals { char(val: string): number; stringFromStrings(arr: string[]): string; stringFromChars(arr: number[]): string; - symbol(name?: string): symbol; + getSymbol(name?: string): symbol; symbolToString(sym: symbol): string; isArray(obj: any): boolean; @@ -54,6 +56,7 @@ try { const Boolean = env.global.Boolean = internals.bool; const Number = env.global.Number = internals.number; const String = env.global.String = internals.string; + const Symbol = env.global.Symbol = internals.symbol; const Map = env.global.Map = internals.map; const Set = env.global.Set = internals.set; @@ -63,6 +66,7 @@ try { env.setProto('array', Array.prototype); env.setProto('number', Number.prototype); env.setProto('string', String.prototype); + env.setProto('symbol', Symbol.prototype); env.setProto('bool', Boolean.prototype); (Object.prototype as any).__proto__ = null; @@ -70,7 +74,6 @@ try { internals.getEnv(run)?.setProto('array', Array.prototype); globalThis.log = (...args) => internals.apply(internals.log, internals, args); - run('values/symbol'); run('values/errors'); run('regex'); run('timeout'); diff --git a/lib/tsconfig.json b/lib/tsconfig.json index bf26634..cdd853b 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -3,10 +3,7 @@ "lib.d.ts", "modules.ts", "utils.ts", - "values/symbol.ts", "values/errors.ts", - "map.ts", - "set.ts", "regex.ts", "timeout.ts", "core.ts" diff --git a/lib/utils.ts b/lib/utils.ts index fa3ca0a..fe45aaf 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -25,14 +25,3 @@ function setConstr(target: object, constr: Function) { true // configurable ); } - -function wrapI(max: number, i: number) { - i |= 0; - if (i < 0) i = max + i; - return i; -} -function clampI(max: number, i: number) { - if (i < 0) i = 0; - if (i > max) i = max; - return i; -} \ No newline at end of file diff --git a/lib/values/symbol.ts b/lib/values/symbol.ts deleted file mode 100644 index 8bcfe65..0000000 --- a/lib/values/symbol.ts +++ /dev/null @@ -1,36 +0,0 @@ -define("values/symbol", () => { - const symbols: Record = { }; - - var Symbol = env.global.Symbol = function(this: any, val?: string) { - if (this !== undefined && this !== null) throw new env.global.TypeError("Symbol may not be called with 'new'."); - if (typeof val !== 'string' && val !== undefined) throw new env.global.TypeError('val must be a string or undefined.'); - return internals.symbol(val); - } as SymbolConstructor; - - env.setProto('symbol', Symbol.prototype); - setConstr(Symbol.prototype, Symbol); - - setProps(Symbol, { - for(key) { - if (typeof key !== 'string' && key !== undefined) throw new env.global.TypeError('key must be a string or undefined.'); - if (key in symbols) return symbols[key]; - else return symbols[key] = internals.symbol(key); - }, - keyFor(sym) { - if (typeof sym !== 'symbol') throw new env.global.TypeError('sym must be a symbol.'); - return internals.symbolToString(sym); - }, - - typeName: env.symbol("Symbol.typeName") as any, - replace: env.symbol('Symbol.replace') as any, - match: env.symbol('Symbol.match') as any, - matchAll: env.symbol('Symbol.matchAll') as any, - split: env.symbol('Symbol.split') as any, - search: env.symbol('Symbol.search') as any, - iterator: env.symbol('Symbol.iterator') as any, - asyncIterator: env.symbol('Symbol.asyncIterator') as any, - }); - - // internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false); - // internals.defineField(env.global, Symbol.typeName, 'Window', false, false, false); -}); \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 6a5085f..5c2ceed 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -14,7 +14,7 @@ import me.topchetoeu.jscript.interop.Native; public class Internals { public final Environment targetEnv; - @Native public final FunctionValue object, function, promise, array, bool, number, string, map, set; + @Native public final FunctionValue object, function, promise, array, bool, number, string, symbol, map, set; @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { @@ -72,7 +72,7 @@ public class Internals { return stringFromChars(res); } - @Native public Symbol symbol(String str) { + @Native public Symbol getSymbol(String str) { return new Symbol(str); } @Native public String symbolToString(Symbol str) { @@ -160,6 +160,7 @@ public class Internals { this.bool = targetEnv.wrappersProvider.getConstr(BooleanPolyfill.class); this.number = targetEnv.wrappersProvider.getConstr(NumberPolyfill.class); this.string = targetEnv.wrappersProvider.getConstr(StringPolyfill.class); + this.symbol = targetEnv.wrappersProvider.getConstr(SymbolPolyfill.class); this.map = targetEnv.wrappersProvider.getConstr(MapPolyfill.class); this.set = targetEnv.wrappersProvider.getConstr(SetPolyfill.class); } diff --git a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java new file mode 100644 index 0000000..8671c4d --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.HashMap; +import java.util.Map; + +import me.topchetoeu.jscript.engine.Context; +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.interop.Native; +import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeGetter; + +public class SymbolPolyfill { + private static final Map symbols = new HashMap<>(); + @Native("@@Symbol.typeName") public final String name = "Symbol"; + + @NativeGetter public static Symbol typeName(Context ctx) { return ctx.env.symbol("Symbol.typeName"); } + @NativeGetter public static Symbol replace(Context ctx) { return ctx.env.symbol("Symbol.replace"); } + @NativeGetter public static Symbol match(Context ctx) { return ctx.env.symbol("Symbol.match"); } + @NativeGetter public static Symbol matchAll(Context ctx) { return ctx.env.symbol("Symbol.matchAll"); } + @NativeGetter public static Symbol split(Context ctx) { return ctx.env.symbol("Symbol.split"); } + @NativeGetter public static Symbol search(Context ctx) { return ctx.env.symbol("Symbol.search"); } + @NativeGetter public static Symbol iterator(Context ctx) { return ctx.env.symbol("Symbol.iterator"); } + @NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.env.symbol("Symbol.asyncIterator"); } + + public final Symbol value; + + private static Symbol passThis(String funcName, Object val) { + if (val instanceof SymbolPolyfill) return ((SymbolPolyfill)val).value; + else if (val instanceof Symbol) return (Symbol)val; + else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); + } + + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { + if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); + if (val == null) return new Symbol(""); + else return new Symbol(Values.toString(ctx, val)); + } + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + return passThis("toString", thisArg).value; + } + @Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) throws InterruptedException { + return passThis("valueOf", thisArg); + } + + @Native public static String fromCharCode(int ...val) { + char[] arr = new char[val.length]; + for (var i = 0; i < val.length; i++) arr[i] = (char)val[i]; + return new String(arr); + } + + @Native("for") public static Symbol _for(String key) { + if (symbols.containsKey(key)) return symbols.get(key); + else { + var sym = new Symbol(key); + symbols.put(key, sym); + return sym; + } + } + @Native public static String keyFor(Symbol sym) { + return sym.value; + } + + public SymbolPolyfill(Symbol val) { + this.value = val; + } +} -- 2.45.2 From 22ec95a7b51baa3a4ca6f9330b0573795899649f Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 26 Sep 2023 22:54:12 +0300 Subject: [PATCH 14/23] feat: implement errors --- build.js | 2 +- lib/core.ts | 14 +++- lib/tsconfig.json | 1 - lib/values/errors.ts | 47 ----------- .../jscript/engine/Environment.java | 31 ++----- .../jscript/engine/MessageContext.java | 23 +++++- .../jscript/engine/frame/CodeFrame.java | 80 ++++++++++++++----- .../jscript/engine/frame/Runners.java | 22 ++--- .../jscript/engine/scope/GlobalScope.java | 6 +- .../jscript/engine/values/ObjectValue.java | 20 ----- .../jscript/engine/values/Values.java | 10 +-- .../jscript/exceptions/EngineException.java | 54 +++++++------ .../interop/NativeWrapperProvider.java | 14 ++-- .../jscript/interop/OverloadFunction.java | 16 ++-- .../polyfills/AsyncFunctionPolyfill.java | 4 +- .../polyfills/AsyncGeneratorPolyfill.java | 4 +- .../jscript/polyfills/ErrorPolyfill.java | 75 +++++++++++++++++ .../jscript/polyfills/FunctionPolyfill.java | 6 +- .../jscript/polyfills/GeneratorPolyfill.java | 4 +- .../jscript/polyfills/Internals.java | 13 ++- src/me/topchetoeu/jscript/polyfills/JSON.java | 2 +- .../jscript/polyfills/ObjectPolyfill.java | 10 +-- .../jscript/polyfills/PromisePolyfill.java | 10 +-- .../jscript/polyfills/StringPolyfill.java | 68 ++++++++-------- .../jscript/polyfills/SymbolPolyfill.java | 16 ++-- 25 files changed, 313 insertions(+), 239 deletions(-) delete mode 100644 lib/values/errors.ts create mode 100644 src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java diff --git a/build.js b/build.js index 20a1f23..774fabe 100644 --- a/build.js +++ b/build.js @@ -54,7 +54,7 @@ async function compileJava() { .replace('${AUTHOR}', conf.author) ); const args = ['--release', '11', ]; - if (argv[1] === 'debug') args.push('-g'); + if (argv[2] === 'debug') args.push('-g'); args.push('-d', 'dst/classes', 'Metadata.java'); for await (const path of find('src', undefined, v => v.endsWith('.java') && !v.endsWith('Metadata.java'))) args.push(path); diff --git a/lib/core.ts b/lib/core.ts index 61b0cd3..3ce7a25 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -13,6 +13,10 @@ interface Internals { number: NumberConstructor; string: StringConstructor; symbol: SymbolConstructor; + error: ErrorConstructor; + syntax: SyntaxErrorConstructor; + type: TypeErrorConstructor; + range: RangeErrorConstructor; map: typeof Map; set: typeof Set; @@ -57,6 +61,10 @@ try { const Number = env.global.Number = internals.number; const String = env.global.String = internals.string; const Symbol = env.global.Symbol = internals.symbol; + const Error = env.global.Error = internals.error; + const SyntaxError = env.global.SyntaxError = internals.syntax; + const TypeError = env.global.TypeError = internals.type; + const RangeError = env.global.RangeError = internals.range; const Map = env.global.Map = internals.map; const Set = env.global.Set = internals.set; @@ -69,12 +77,16 @@ try { env.setProto('symbol', Symbol.prototype); env.setProto('bool', Boolean.prototype); + env.setProto('error', Error.prototype); + env.setProto('rangeErr', RangeError.prototype); + env.setProto('typeErr', TypeError.prototype); + env.setProto('syntaxErr', SyntaxError.prototype); + (Object.prototype as any).__proto__ = null; internals.getEnv(run)?.setProto('array', Array.prototype); globalThis.log = (...args) => internals.apply(internals.log, internals, args); - run('values/errors'); run('regex'); run('timeout'); diff --git a/lib/tsconfig.json b/lib/tsconfig.json index cdd853b..f0ee4e3 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -3,7 +3,6 @@ "lib.d.ts", "modules.ts", "utils.ts", - "values/errors.ts", "regex.ts", "timeout.ts", "core.ts" diff --git a/lib/values/errors.ts b/lib/values/errors.ts deleted file mode 100644 index a8d1ef0..0000000 --- a/lib/values/errors.ts +++ /dev/null @@ -1,47 +0,0 @@ -define("values/errors", () => { - var Error = env.global.Error = function Error(msg: string) { - if (msg === undefined) msg = ''; - else msg += ''; - - return { - message: msg, - stack: [] as string[], - __proto__: Error.prototype, - } as any; - } as ErrorConstructor; - - setConstr(Error.prototype, Error); - setProps(Error.prototype, { - name: 'Error', - toString: internals.setEnv(function(this: Error) { - if (!(this instanceof Error)) return ''; - - if (this.message === '') return this.name; - else return this.name + ': ' + this.message; - }, env) - }); - env.setProto('error', Error.prototype); - internals.markSpecial(Error); - - function makeError(name: string, proto: string): T1 { - function constr (msg: string) { - var res = new Error(msg); - (res as any).__proto__ = constr.prototype; - return res; - } - - (constr as any).__proto__ = Error; - (constr.prototype as any).__proto__ = env.proto('error'); - setConstr(constr.prototype, constr as ErrorConstructor); - setProps(constr.prototype, { name: name }); - - internals.markSpecial(constr); - env.setProto(proto, constr.prototype); - - return constr as T1; - } - - env.global.RangeError = makeError('RangeError', 'rangeErr'); - env.global.TypeError = makeError('TypeError', 'typeErr'); - env.global.SyntaxError = makeError('SyntaxError', 'syntaxErr'); -}); \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index c9a4cbe..5340774 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -17,14 +17,12 @@ public class Environment { private HashMap prototypes = new HashMap<>(); public GlobalScope global; public WrappersProvider wrappersProvider; - /** - * NOTE: This is not the register for Symbol.for, but for the symbols like Symbol.iterator - */ + /** NOTE: This is not the register for Symbol.for, but for the symbols like Symbol.iterator */ public HashMap symbols = new HashMap<>(); @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported."); + throw EngineException.ofError(ctx, "Regular expressions not supported."); }); @Native public ObjectValue proto(String name) { return prototypes.get(name); @@ -43,37 +41,20 @@ public class Environment { } } - // @Native public ObjectValue arrayPrototype = new ObjectValue(); - // @Native public ObjectValue boolPrototype = new ObjectValue(); - // @Native public ObjectValue functionPrototype = new ObjectValue(); - // @Native public ObjectValue numberPrototype = new ObjectValue(); - // @Native public ObjectValue objectPrototype = new ObjectValue(PlaceholderProto.NONE); - // @Native public ObjectValue stringPrototype = new ObjectValue(); - // @Native public ObjectValue symbolPrototype = new ObjectValue(); - // @Native public ObjectValue errorPrototype = new ObjectValue(); - // @Native public ObjectValue syntaxErrPrototype = new ObjectValue(PlaceholderProto.ERROR); - // @Native public ObjectValue typeErrPrototype = new ObjectValue(PlaceholderProto.ERROR); - // @Native public ObjectValue rangeErrPrototype = new ObjectValue(PlaceholderProto.ERROR); - - @NativeGetter("global") - public ObjectValue getGlobal() { + @NativeGetter("global") public ObjectValue getGlobal() { return global.obj; } - @NativeSetter("global") - public void setGlobal(ObjectValue val) { + @NativeSetter("global") public void setGlobal(ObjectValue val) { global = new GlobalScope(val); } - @Native - public Environment fork() { + @Native public Environment fork() { var res = new Environment(compile, wrappersProvider, global); res.regexConstructor = regexConstructor; res.prototypes = new HashMap<>(prototypes); return res; } - - @Native - public Environment child() { + @Native public Environment child() { var res = fork(); res.global = res.global.globalChild(); return res; diff --git a/src/me/topchetoeu/jscript/engine/MessageContext.java b/src/me/topchetoeu/jscript/engine/MessageContext.java index 2ac19b4..9125018 100644 --- a/src/me/topchetoeu/jscript/engine/MessageContext.java +++ b/src/me/topchetoeu/jscript/engine/MessageContext.java @@ -15,9 +15,9 @@ public class MessageContext { public List frames() { return Collections.unmodifiableList(frames); } - public MessageContext pushFrame(CodeFrame frame) { + public MessageContext pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { this.frames.add(frame); - if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!"); + if (this.frames.size() > maxStackFrames) throw EngineException.ofRange(ctx, "Stack overflow!"); return this; } public boolean popFrame(CodeFrame frame) { @@ -27,6 +27,25 @@ public class MessageContext { return true; } + public List stackTrace() { + var res = new ArrayList(); + + for (var el : frames) { + var name = el.function.name; + var 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 (!res.equals("")) res.add(trace); + } + + return res; + } + public MessageContext(Engine engine) { this.engine = engine; } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 818a4ae..7a94fe1 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -9,6 +9,7 @@ import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; +import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; @@ -93,6 +94,9 @@ public class CodeFrame { stack[stackPtr++] = Values.normalize(ctx, val); } + // TODO: THIS SYSTEM IS SEVERLY BROKEN + // MUST FIX!!!!! + private Object nextNoTry(Context ctx) throws InterruptedException { if (Thread.currentThread().isInterrupted()) throw new InterruptedException(); if (codePtr < 0 || codePtr >= function.body.length) return null; @@ -111,6 +115,58 @@ public class CodeFrame { } } + private void setCause(Context ctx, Object err, Object cause) throws InterruptedException { + if (err instanceof ObjectValue) { + Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause); + } + } + // private void propagateErr(Context ctx, Object err) { + // while (!tryStack.isEmpty()) { + // var tmp = tryStack.get(tryStack.size() - 1); + + // if (tmp.state == TryCtx.STATE_TRY || tmp.state == TryCtx.STATE_CATCH) { + // tmp.jumpPtr = tmp.end; + + // if (tmp.state == TryCtx.STATE_TRY && tmp.hasCatch) { + // tmp.state = TryCtx.STATE_CATCH; + // scope.catchVars.add(new ValueVariable(false, err)); + // codePtr = tmp.catchStart; + // return; + // } + // else if (tmp.hasFinally) { + // tmp.state = TryCtx.STATE_FINALLY_THREW; + // tmp.err = new EngineException(err); + // codePtr = tmp.finallyStart; + // return; + // } + // else if (tmp.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + // } + + // tryStack.remove(tryStack.size() - 1); + // } + // throw new EngineException(err); + // } + // private void propagateRet(Context ctx, Object val) { + // while (!tryStack.isEmpty()) { + // var tmp = tryStack.get(tryStack.size() - 1); + + // if (tmp.state == TryCtx.STATE_TRY || tmp.state == TryCtx.STATE_CATCH) { + // tmp.jumpPtr = tmp.end; + + // if (tmp.hasFinally) { + // tmp.state = TryCtx.STATE_FINALLY_RETURNED; + // tmp.err = new EngineException(err); + // codePtr = tmp.finallyStart; + // return; + // } + // else if (tmp.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + // } + + // tryStack.remove(tryStack.size() - 1); + // } + // return val; + // } + public Object next(Context ctx, Object prevValue, Object prevReturn, Object prevError) throws InterruptedException { TryCtx tryCtx = null; if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN; @@ -210,18 +266,8 @@ public class CodeFrame { else return res; } catch (EngineException e) { - if (tryCtx.hasCatch) { - tryCtx.state = TryCtx.STATE_CATCH; - tryCtx.err = e; - codePtr = tryCtx.catchStart; - scope.catchVars.add(new ValueVariable(false, e.value)); - return Runners.NO_RETURN; - } - else if (tryCtx.hasFinally) { - tryCtx.err = e; - tryCtx.state = TryCtx.STATE_FINALLY_THREW; - } - else throw e; + throw e; + // propagateErr(ctx, e.value); } codePtr = tryCtx.finallyStart; @@ -237,7 +283,7 @@ public class CodeFrame { else return res; } catch (EngineException e) { - e.cause = tryCtx.err; + setCause(ctx, e.value, tryCtx.err); if (tryCtx.hasFinally) { tryCtx.err = e; tryCtx.state = TryCtx.STATE_FINALLY_THREW; @@ -253,7 +299,7 @@ public class CodeFrame { return nextNoTry(ctx); } catch (EngineException e) { - e.cause = tryCtx.err; + setCause(ctx, e.value, tryCtx.err); throw e; } } @@ -262,16 +308,12 @@ public class CodeFrame { public Object run(Context ctx) throws InterruptedException { try { - ctx.message.pushFrame(this); + ctx.message.pushFrame(ctx, this); while (true) { var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); if (res != Runners.NO_RETURN) return res; } } - catch (Throwable e) { - // e.printStackTrace(); - throw e; - } finally { ctx.message.popFrame(this); } diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 08d49e2..3032da1 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -26,8 +26,8 @@ public class Runners { public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) { throw new EngineException(frame.pop()); } - public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) { - throw EngineException.ofSyntax((String)instr.get(0)); + public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + throw EngineException.ofSyntax(ctx, (String)instr.get(0)); } private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { @@ -77,9 +77,9 @@ public class Runners { var name = frame.pop(); var obj = frame.pop(); - if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined."); - if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined."); - if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object."); + if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType(ctx, "Getter must be a function or undefined."); + if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType(ctx, "Setter must be a function or undefined."); + if (!Values.isObject(obj)) throw EngineException.ofType(ctx, "Property apply target must be an object."); Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false); frame.push(ctx, obj); @@ -214,7 +214,7 @@ public class Runners { frame.push(ctx, Values.getMember(ctx, obj, key)); } catch (IllegalArgumentException e) { - throw EngineException.ofType(e.getMessage()); + throw EngineException.ofType(ctx, e.getMessage()); } frame.codePtr++; return NO_RETURN; @@ -239,7 +239,7 @@ public class Runners { var key = frame.pop(); var obj = frame.pop(); - if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); + if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax(ctx, "Can't set member '" + key + "'."); if ((boolean)instr.get(0)) frame.push(ctx, val); frame.codePtr++; return NO_RETURN; @@ -307,11 +307,11 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) { + public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { if (instr.is(0, "dbg_names")) { var names = new String[instr.params.length - 1]; for (var i = 0; i < instr.params.length - 1; i++) { - if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters."); + if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax(ctx, "NOP dbg_names instruction must specify only string parameters."); names[i] = (String)instr.params[i + 1]; } frame.scope.setNames(names); @@ -325,7 +325,7 @@ public class Runners { var key = frame.pop(); var val = frame.pop(); - if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); + if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax(ctx, "Can't delete member '" + key + "'."); frame.push(ctx, true); frame.codePtr++; return NO_RETURN; @@ -383,7 +383,7 @@ public class Runners { case OPERATION: return execOperation(ctx, instr, frame); - default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); + default: throw EngineException.ofSyntax(ctx, "Invalid instruction " + instr.type.name() + "."); } } } diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index e447f2a..d505532 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -60,12 +60,12 @@ public class GlobalScope implements ScopeRecord { } public Object get(Context ctx, String name) throws InterruptedException { - if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); + if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax(ctx, "The variable '" + name + "' doesn't exist."); else return obj.getMember(ctx, name); } public void set(Context ctx, String name, Object val) throws InterruptedException { - if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); - if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); + if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax(ctx, "The variable '" + name + "' doesn't exist."); + if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax(ctx, "The global '" + name + "' is readonly."); } public Set keys() { diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index eae0dce..4eec36f 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -14,10 +14,6 @@ public class ObjectValue { OBJECT, ARRAY, FUNCTION, - ERROR, - SYNTAX_ERROR, - TYPE_ERROR, - RANGE_ERROR, } public static enum State { NORMAL, @@ -39,10 +35,6 @@ public class ObjectValue { private static final Object OBJ_PROTO = new Object(); private static final Object ARR_PROTO = new Object(); private static final Object FUNC_PROTO = new Object(); - private static final Object ERR_PROTO = new Object(); - private static final Object SYNTAX_ERR_PROTO = new Object(); - private static final Object TYPE_ERR_PROTO = new Object(); - private static final Object RANGE_ERR_PROTO = new Object(); protected Object prototype; @@ -150,10 +142,6 @@ public class ObjectValue { if (prototype == OBJ_PROTO) return ctx.env.proto("object"); if (prototype == ARR_PROTO) return ctx.env.proto("array"); if (prototype == FUNC_PROTO) return ctx.env.proto("function"); - if (prototype == ERR_PROTO) return ctx.env.proto("error"); - if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr"); - if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr"); - if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr"); } catch (NullPointerException e) { return null; @@ -176,10 +164,6 @@ public class ObjectValue { if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO; else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO; else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO; - else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO; - else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; - else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO; - else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO; else prototype = obj; } else prototype = obj; @@ -194,10 +178,6 @@ public class ObjectValue { case OBJECT: prototype = OBJ_PROTO; break; case FUNCTION: prototype = FUNC_PROTO; break; case ARRAY: prototype = ARR_PROTO; break; - case ERROR: prototype = ERR_PROTO; break; - case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break; - case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break; - case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break; case NONE: prototype = null; break; } return true; diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index d909ede..ab7a962 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -75,7 +75,7 @@ public class Values { if (isPrimitive(res)) return res; } - throw EngineException.ofType("Value couldn't be converted to a primitive."); + throw EngineException.ofType(ctx, "Value couldn't be converted to a primitive."); } public static boolean isPrimitive(Object obj) { @@ -105,7 +105,7 @@ public class Values { } } - throw EngineException.ofType("Value couldn't be converted to a primitive."); + throw EngineException.ofType(ctx, "Value couldn't be converted to a primitive."); } public static boolean toBoolean(Object obj) { if (obj == NULL || obj == null) return false; @@ -284,8 +284,8 @@ public class Values { } public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException { obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); - if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); - if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); + if (obj == null) throw EngineException.ofType(ctx, "Tried to access member of undefined."); + if (obj == NULL) throw EngineException.ofType(ctx, "Tried to access member of null."); if (key.equals("__proto__")) return setPrototype(ctx, obj, val); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); @@ -373,7 +373,7 @@ public class Values { } public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { - if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); + if (!isFunction(func)) throw EngineException.ofType(ctx, "Tried to call a non-function value."); return function(func).call(ctx, thisArg, args); } public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index e695426..93471cc 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -5,9 +5,8 @@ import java.util.List; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; public class EngineException extends RuntimeException { public final Object value; @@ -41,19 +40,28 @@ public class EngineException extends RuntimeException { catch (EngineException e) { ss.append("[Error while stringifying]\n"); } - for (var line : stackTrace) { - ss.append(" ").append(line).append('\n'); - } - if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n'); + // for (var line : stackTrace) { + // ss.append(" ").append(line).append('\n'); + // } + // if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n'); ss.deleteCharAt(ss.length() - 1); return ss.toString(); } - private static Object err(String name, String msg, PlaceholderProto proto) { - var res = new ObjectValue(proto); - if (name != null) res.defineProperty(null, "name", name); - res.defineProperty(null, "message", msg); - return res; + private static Object err(Context ctx, String type, String name, String msg) throws InterruptedException { + try { + var proto = ctx.env.proto(type); + var constr = Values.getMember(ctx, proto, "constructor"); + + if (constr instanceof FunctionValue) { + var res = Values.callNew(ctx, constr, msg); + if (name != null) Values.setMember(ctx, res, "name", name); + return res; + } + } + catch (IllegalArgumentException e) { } + + return name + ": " + msg; } public EngineException(Object error) { @@ -63,22 +71,22 @@ public class EngineException extends RuntimeException { this.cause = null; } - public static EngineException ofError(String name, String msg) { - return new EngineException(err(name, msg, PlaceholderProto.ERROR)); + public static EngineException ofError(Context ctx, String name, String msg) throws InterruptedException { + return new EngineException(err(ctx, "error", name, msg)); } - public static EngineException ofError(String msg) { - return new EngineException(err(null, msg, PlaceholderProto.ERROR)); + public static EngineException ofError(Context ctx, String msg) throws InterruptedException { + return new EngineException(err(ctx, "error", null, msg)); } - public static EngineException ofSyntax(SyntaxException e) { - return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc); + public static EngineException ofSyntax(Context ctx, SyntaxException e) throws InterruptedException { + return new EngineException(err(ctx, "syntaxErr", null, e.msg)).add(null, e.loc); } - public static EngineException ofSyntax(String msg) { - return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); + public static EngineException ofSyntax(Context ctx, String msg) throws InterruptedException { + return new EngineException(err(ctx, "syntaxErr", null, msg)); } - public static EngineException ofType(String msg) { - return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR)); + public static EngineException ofType(Context ctx, String msg) throws InterruptedException { + return new EngineException(err(ctx, "typeErr", null, msg)); } - public static EngineException ofRange(String msg) { - return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR)); + public static EngineException ofRange(Context ctx, String msg) throws InterruptedException { + return new EngineException(err(ctx, "rangeErr", null, msg)); } } diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 018a256..3d6f1e9 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -20,10 +20,10 @@ public class NativeWrapperProvider implements WrappersProvider { var nat = method.getAnnotation(Native.class); var get = method.getAnnotation(NativeGetter.class); var set = method.getAnnotation(NativeSetter.class); - var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member; + var memberMatch = !Modifier.isStatic(method.getModifiers()) == member; if (nat != null) { - if (nat.thisArg() != member && memberMismatch) continue; + if (nat.thisArg() && !member || !nat.thisArg() && !memberMatch) continue; Object name = nat.value(); if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); @@ -37,7 +37,7 @@ public class NativeWrapperProvider implements WrappersProvider { } else { if (get != null) { - if (get.thisArg() != member && memberMismatch) continue; + if (get.thisArg() && !member || !get.thisArg() && !memberMatch) continue; Object name = get.value(); if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); @@ -54,7 +54,7 @@ public class NativeWrapperProvider implements WrappersProvider { target.defineProperty(null, name, getter, setter, true, true); } if (set != null) { - if (set.thisArg() != member && memberMismatch) continue; + if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue; Object name = set.value(); if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); @@ -130,19 +130,19 @@ public class NativeWrapperProvider implements WrappersProvider { public static FunctionValue makeConstructor(Environment ctx, Class clazz) { FunctionValue func = new OverloadFunction(clazz.getName()); - for (var overload : clazz.getConstructors()) { + for (var overload : clazz.getDeclaredConstructors()) { var nat = overload.getAnnotation(Native.class); if (nat == null) continue; ((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg())); } - for (var overload : clazz.getMethods()) { + for (var overload : clazz.getDeclaredMethods()) { var constr = overload.getAnnotation(NativeConstructor.class); if (constr == null) continue; ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); } 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(clazz.getName(), (a, b, c) -> { throw EngineException.ofError(a, "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 bf0f20c..daf68ee 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -34,7 +34,7 @@ public class OverloadFunction extends FunctionValue { } catch (ConvertException e) { if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); + else throw EngineException.ofType(ctx, String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); } } @@ -49,7 +49,7 @@ public class OverloadFunction extends FunctionValue { } catch (ConvertException e) { if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); + else throw EngineException.ofType(ctx, String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); } } @@ -64,7 +64,7 @@ public class OverloadFunction extends FunctionValue { } catch (ConvertException e) { if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target)); + else throw EngineException.ofType(ctx, String.format("This argument can't be converted from %s to %s", e.source, e.target)); } if (consumesEngine) newArgs[0] = ctx; @@ -77,7 +77,7 @@ public class OverloadFunction extends FunctionValue { return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); } catch (InstantiationException e) { - throw EngineException.ofError("The class may not be instantiated."); + throw EngineException.ofError(ctx, "The class may not be instantiated."); } catch (IllegalAccessException | IllegalArgumentException e) { continue; @@ -88,21 +88,21 @@ public class OverloadFunction extends FunctionValue { throw ((EngineException)e.getTargetException()).add(name, loc); } else if (e.getTargetException() instanceof NullPointerException) { - throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc); + throw EngineException.ofType(ctx, "Unexpected value of 'undefined'.").add(name, loc); } else { - throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); + throw EngineException.ofError(ctx, e.getTargetException().getMessage()).add(name, loc); } } catch (ReflectiveOperationException e) { - throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "")); + throw EngineException.ofError(ctx, e.getMessage()).add(name, new Location(0, 0, "")); } catch (Exception e) { throw e; } } - throw EngineException.ofType("No overload found for native method."); + throw EngineException.ofType(ctx, "No overload found for native method."); } public OverloadFunction add(Overload overload) { diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java index 74d4996..46c024b 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java @@ -21,7 +21,7 @@ public class AsyncFunctionPolyfill extends FunctionValue { private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException { Object res = null; - ctx.message.pushFrame(frame); + ctx.message.pushFrame(ctx, frame); awaiting = false; while (!awaiting) { @@ -65,7 +65,7 @@ public class AsyncFunctionPolyfill extends FunctionValue { public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { var handler = new AsyncHelper(); var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await)); - if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + if (!(func instanceof CodeFunction)) throw EngineException.ofType(ctx, "Return value of argument must be a js function."); handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); handler.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN); return handler.promise; diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java index b2eb893..acf3d07 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java @@ -34,7 +34,7 @@ public class AsyncGeneratorPolyfill extends FunctionValue { } Object res = null; - ctx.message.pushFrame(frame); + ctx.message.pushFrame(ctx, frame); state = 0; while (state == 0) { @@ -122,7 +122,7 @@ public class AsyncGeneratorPolyfill extends FunctionValue { new NativeFunction("await", handler::await), new NativeFunction("yield", handler::yield) ); - if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + if (!(func instanceof CodeFunction)) throw EngineException.ofType(ctx, "Return value of argument must be a js function."); handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); return handler; } diff --git a/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java new file mode 100644 index 0000000..6a530eb --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java @@ -0,0 +1,75 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.interop.Native; + +public class ErrorPolyfill { + public static class SyntaxErrorPolyfill extends ErrorPolyfill { + @Native public SyntaxErrorPolyfill(Context ctx, Object message) throws InterruptedException { + super(ctx, message); + this.name = "SyntaxError"; + } + } + public static class TypeErrorPolyfill extends ErrorPolyfill { + @Native public TypeErrorPolyfill(Context ctx, Object message) throws InterruptedException { + super(ctx, message); + this.name = "TypeError"; + } + } + public static class RangeErrorPolyfill extends ErrorPolyfill { + @Native public RangeErrorPolyfill(Context ctx, Object message) throws InterruptedException { + super(ctx, message); + this.name = "RangeError"; + } + } + + @Native public final ArrayValue stack; + @Native public String message; + @Native public String name = "Error"; + + private static String toString(Context ctx, Object name, Object message, ArrayValue stack) throws InterruptedException { + if (name == null) name = ""; + else name = Values.toString(ctx, name).trim(); + if (message == null) message = ""; + else message = Values.toString(ctx, message).trim(); + StringBuilder res = new StringBuilder(); + + if (!name.equals("")) res.append(name); + if (!message.equals("") && !name.equals("")) res.append(": "); + if (!name.equals("")) res.append(message); + + if (stack != null) { + for (var el : stack) { + var str = Values.toString(ctx, el).trim(); + if (!str.equals("")) res.append("\n ").append(el); + } + } + + return res.toString(); + } + + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + if (thisArg instanceof ErrorPolyfill) { + return toString(ctx, ((ErrorPolyfill)thisArg).name, ((ErrorPolyfill)thisArg).message, ((ErrorPolyfill)thisArg).stack); + } + else if (thisArg instanceof ObjectValue) { + var stack = Values.getMember(ctx, thisArg, "stack"); + if (!(stack instanceof ArrayValue)) stack = null; + return toString(ctx, + Values.getMember(ctx, thisArg, "name"), + Values.getMember(ctx, thisArg, "message"), + (ArrayValue)stack + ); + } + else return "[Invalid error]"; + } + + @Native public ErrorPolyfill(Context ctx, Object message) throws InterruptedException { + this.stack = new ArrayValue(ctx, ctx.message.stackTrace().toArray()); + if (message == null) this.message = ""; + else this.message = Values.toString(ctx, message); + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index d4ba763..9b6f00f 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -14,12 +14,12 @@ public class FunctionPolyfill { return func.call(ctx, thisArg, args.toArray()); } @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { - if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); + if (!(func instanceof FunctionValue)) throw EngineException.ofError(ctx, "Expected this to be a function."); return func.call(ctx, thisArg, args); } - @Native(thisArg = true) public static FunctionValue bind(Context ctx, FunctionValue func, Object thisArg, Object... args) { - if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); + @Native(thisArg = true) public static FunctionValue bind(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { + if (!(func instanceof FunctionValue)) throw EngineException.ofError(ctx, "Expected this to be a function."); return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { var resArgs = new Object[args.length + callArgs.length]; diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index 429a2ca..ca3a35d 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -30,7 +30,7 @@ public class GeneratorPolyfill extends FunctionValue { } Object res = null; - ctx.message.pushFrame(frame); + ctx.message.pushFrame(ctx, frame); yielding = false; while (!yielding) { @@ -89,7 +89,7 @@ public class GeneratorPolyfill extends FunctionValue { public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { var handler = new Generator(); var func = factory.call(ctx, thisArg, new NativeFunction("yield", handler::yield)); - if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + if (!(func instanceof CodeFunction)) throw EngineException.ofType(ctx, "Return value of argument must be a js function."); handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); return handler; } diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 5c2ceed..e53d6f5 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -10,11 +10,18 @@ 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.interop.Native; +import me.topchetoeu.jscript.polyfills.ErrorPolyfill.RangeErrorPolyfill; +import me.topchetoeu.jscript.polyfills.ErrorPolyfill.SyntaxErrorPolyfill; +import me.topchetoeu.jscript.polyfills.ErrorPolyfill.TypeErrorPolyfill; public class Internals { public final Environment targetEnv; - @Native public final FunctionValue object, function, promise, array, bool, number, string, symbol, map, set; + @Native public final FunctionValue + object, function, array, + bool, number, string, symbol, + promise, map, set, + error, syntax, type, range; @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { @@ -163,5 +170,9 @@ public class Internals { this.symbol = targetEnv.wrappersProvider.getConstr(SymbolPolyfill.class); this.map = targetEnv.wrappersProvider.getConstr(MapPolyfill.class); this.set = targetEnv.wrappersProvider.getConstr(SetPolyfill.class); + this.error = targetEnv.wrappersProvider.getConstr(ErrorPolyfill.class); + this.syntax = targetEnv.wrappersProvider.getConstr(SyntaxErrorPolyfill.class); + this.type = targetEnv.wrappersProvider.getConstr(TypeErrorPolyfill.class); + this.range = targetEnv.wrappersProvider.getConstr(RangeErrorPolyfill.class); } } diff --git a/src/me/topchetoeu/jscript/polyfills/JSON.java b/src/me/topchetoeu/jscript/polyfills/JSON.java index 53873a5..bcf7697 100644 --- a/src/me/topchetoeu/jscript/polyfills/JSON.java +++ b/src/me/topchetoeu/jscript/polyfills/JSON.java @@ -75,7 +75,7 @@ public class JSON { return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); } catch (SyntaxException e) { - throw EngineException.ofSyntax(e.msg); + throw EngineException.ofSyntax(ctx, e.msg); } } @Native diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java index 9875a74..2ce6712 100644 --- a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -33,27 +33,27 @@ public class ObjectPolyfill { var hasSet = attrib.hasMember(ctx, "set", false); if (hasVal) { - if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property."); + if (hasGet || hasSet) throw EngineException.ofType(ctx, "Cannot specify a value and accessors for a property."); if (!obj.defineProperty( ctx, key, attrib.getMember(ctx, "value"), Values.toBoolean(attrib.getMember(ctx, "writable")), Values.toBoolean(attrib.getMember(ctx, "configurable")), Values.toBoolean(attrib.getMember(ctx, "enumerable")) - )) throw EngineException.ofType("Can't define property '" + key + "'."); + )) throw EngineException.ofType(ctx, "Can't define property '" + key + "'."); } else { var get = attrib.getMember(ctx, "get"); var set = attrib.getMember(ctx, "set"); - if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function."); - if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function."); + if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType(ctx, "Get accessor must be a function."); + if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType(ctx, "Set accessor must be a function."); if (!obj.defineProperty( ctx, key, (FunctionValue)get, (FunctionValue)set, Values.toBoolean(attrib.getMember(ctx, "configurable")), Values.toBoolean(attrib.getMember(ctx, "enumerable")) - )) throw EngineException.ofType("Can't define property '" + key + "'."); + )) throw EngineException.ofType(ctx, "Can't define property '" + key + "'."); } return obj; diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index 41e977d..dab4f57 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -44,7 +44,7 @@ public class PromisePolyfill { } @Native public static PromisePolyfill any(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "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() }; @@ -69,7 +69,7 @@ public class PromisePolyfill { return res; } @Native public static PromisePolyfill race(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "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 PromisePolyfill(); @@ -85,7 +85,7 @@ public class PromisePolyfill { return res; } @Native public static PromisePolyfill all(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "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() }; @@ -112,7 +112,7 @@ public class PromisePolyfill { return res; } @Native public static PromisePolyfill allSettled(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "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() }; @@ -314,7 +314,7 @@ public class PromisePolyfill { * NOT THREAD SAFE - must be called from the engine executor thread */ @Native public PromisePolyfill(Context ctx, FunctionValue func) throws InterruptedException { - if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); + if (!(func instanceof FunctionValue)) throw EngineException.ofType(ctx, "A function must be passed to the promise constructor."); try { func.call( ctx, null, diff --git a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java index 2068481..51255d6 100644 --- a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java @@ -18,10 +18,10 @@ public class StringPolyfill { public final String value; - private static String passThis(String funcName, Object val) { + private static String passThis(Context ctx, String funcName, Object val) throws InterruptedException { if (val instanceof StringPolyfill) return ((StringPolyfill)val).value; else if (val instanceof String) return (String)val; - else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); + else throw EngineException.ofType(ctx, String.format("'%s' may only be called upon object and primitve strings.", funcName)); } private static int normalizeI(int i, int len, boolean clamp) { if (i < 0) i += len; @@ -32,47 +32,47 @@ public class StringPolyfill { return i; } - @NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) { - return passThis("substring", thisArg).length(); + @NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) throws InterruptedException { + return passThis(ctx, "substring", thisArg).length(); } @Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { - var val = passThis("substring", thisArg); + var val = passThis(ctx, "substring", thisArg); start = normalizeI(start, val.length(), true); int end = normalizeI(_end == null ? val.length() : (int)Values.toNumber(ctx, _end), val.length(), true); return val.substring(start, end); } @Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) throws InterruptedException { - var val = passThis("substr", thisArg); + var val = passThis(ctx, "substr", thisArg); int len = _len == null ? val.length() - start : (int)Values.toNumber(ctx, _len); return substring(ctx, val, start, start + len); } - @Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) { - return passThis("toLowerCase", thisArg).toLowerCase(); + @Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) throws InterruptedException { + return passThis(ctx, "toLowerCase", thisArg).toLowerCase(); } - @Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) { - return passThis("toUpperCase", thisArg).toUpperCase(); + @Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) throws InterruptedException { + return passThis(ctx, "toUpperCase", thisArg).toUpperCase(); } - @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) { - return passThis("charAt", thisArg).charAt(i) + ""; + @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) throws InterruptedException { + return passThis(ctx, "charAt", thisArg).charAt(i) + ""; } - @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) { - return passThis("charCodeAt", thisArg).charAt(i); + @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) throws InterruptedException { + return passThis(ctx, "charCodeAt", thisArg).charAt(i); } - @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) { - return passThis("startsWith", thisArg).startsWith(term, pos); + @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) throws InterruptedException { + return passThis(ctx, "startsWith", thisArg).startsWith(term, pos); } @Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) throws InterruptedException { - var val = passThis("endsWith", thisArg); + var val = passThis(ctx, "endsWith", thisArg); return val.lastIndexOf(term, pos) >= 0; } @Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) throws InterruptedException { - var val = passThis("indexOf", thisArg); + var val = passThis(ctx, "indexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); @@ -84,7 +84,7 @@ public class StringPolyfill { return val.indexOf(Values.toString(ctx, term), start); } @Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { - var val = passThis("lastIndexOf", thisArg); + var val = passThis(ctx, "lastIndexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); @@ -97,11 +97,11 @@ public class StringPolyfill { } @Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { - return lastIndexOf(ctx, passThis("includes", thisArg), term, pos) >= 0; + return lastIndexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0; } @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { - var val = passThis("replace", thisArg); + var val = passThis(ctx, "replace", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); @@ -113,7 +113,7 @@ public class StringPolyfill { return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); } @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { - var val = passThis("replaceAll", thisArg); + var val = passThis(ctx, "replaceAll", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); @@ -126,7 +126,7 @@ public class StringPolyfill { } @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { - var val = passThis("match", thisArg); + var val = passThis(ctx, "match", thisArg); FunctionValue match; @@ -137,9 +137,9 @@ public class StringPolyfill { var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), ""); _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else throw EngineException.ofError("Regular expressions don't support matching."); + else throw EngineException.ofError(ctx, "Regular expressions don't support matching."); } - else throw EngineException.ofError("Regular expressions not supported."); + else throw EngineException.ofError(ctx, "Regular expressions not supported."); } catch (IllegalArgumentException e) { return new ArrayValue(ctx, ""); } @@ -148,7 +148,7 @@ public class StringPolyfill { else return new ArrayValue(ctx, ""); } @Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { - var val = passThis("matchAll", thisArg); + var val = passThis(ctx, "matchAll", thisArg); FunctionValue match = null; @@ -162,15 +162,15 @@ public class StringPolyfill { var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), "g"); var _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else throw EngineException.ofError("Regular expressions don't support matching."); + else throw EngineException.ofError(ctx, "Regular expressions don't support matching."); } - else throw EngineException.ofError("Regular expressions not supported."); + else throw EngineException.ofError(ctx, "Regular expressions not supported."); return match.call(ctx, term, val); } @Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) throws InterruptedException { - var val = passThis("split", thisArg); + var val = passThis(ctx, "split", thisArg); if (lim != null) lim = Values.toNumber(ctx, lim); @@ -217,18 +217,18 @@ public class StringPolyfill { } @Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { - return substring(ctx, passThis("slice", thisArg), start, _end); + return substring(ctx, passThis(ctx, "slice", thisArg), start, _end); } @Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) throws InterruptedException { - var res = new StringBuilder(passThis("concat", thisArg)); + var res = new StringBuilder(passThis(ctx, "concat", thisArg)); for (var el : args) res.append(Values.toString(ctx, el)); return res.toString(); } @Native(thisArg = true) public static String trim(Context ctx, Object thisArg) throws InterruptedException { - return passThis("trim", thisArg).trim(); + return passThis(ctx, "trim", thisArg).trim(); } @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { @@ -237,10 +237,10 @@ public class StringPolyfill { else return val; } @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { - return passThis("toString", thisArg); + return passThis(ctx, "toString", thisArg); } @Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) throws InterruptedException { - return passThis("valueOf", thisArg); + return passThis(ctx, "valueOf", thisArg); } @Native public static String fromCharCode(int ...val) { diff --git a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java index 8671c4d..67ad078 100644 --- a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java @@ -27,28 +27,22 @@ public class SymbolPolyfill { public final Symbol value; - private static Symbol passThis(String funcName, Object val) { + private static Symbol passThis(Context ctx, String funcName, Object val) throws InterruptedException { if (val instanceof SymbolPolyfill) return ((SymbolPolyfill)val).value; else if (val instanceof Symbol) return (Symbol)val; - else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); + else throw EngineException.ofType(ctx, String.format("'%s' may only be called upon object and primitve symbols.", funcName)); } @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { - if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); + if (thisArg instanceof ObjectValue) throw EngineException.ofType(ctx, "Symbol constructor may not be called with new."); if (val == null) return new Symbol(""); else return new Symbol(Values.toString(ctx, val)); } @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { - return passThis("toString", thisArg).value; + return passThis(ctx, "toString", thisArg).value; } @Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) throws InterruptedException { - return passThis("valueOf", thisArg); - } - - @Native public static String fromCharCode(int ...val) { - char[] arr = new char[val.length]; - for (var i = 0; i < val.length; i++) arr[i] = (char)val[i]; - return new String(arr); + return passThis(ctx, "valueOf", thisArg); } @Native("for") public static Symbol _for(String key) { -- 2.45.2 From bd0890219668788f0ced95d2694ebf616ba47bab Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:26:55 +0300 Subject: [PATCH 15/23] fix: revert back to old error protocol --- .../jscript/engine/Environment.java | 2 +- .../jscript/engine/MessageContext.java | 2 +- .../jscript/engine/frame/Runners.java | 18 ++++---- .../jscript/engine/scope/GlobalScope.java | 6 +-- .../jscript/engine/scope/LocalScope.java | 3 +- .../jscript/engine/values/ObjectValue.java | 20 ++++++++ .../jscript/engine/values/Values.java | 10 ++-- .../jscript/exceptions/EngineException.java | 46 ++++++++----------- .../interop/NativeWrapperProvider.java | 2 +- .../jscript/interop/OverloadFunction.java | 16 +++---- .../polyfills/AsyncFunctionPolyfill.java | 2 +- .../polyfills/AsyncGeneratorPolyfill.java | 2 +- .../jscript/polyfills/ErrorPolyfill.java | 45 +++++++++--------- .../jscript/polyfills/FunctionPolyfill.java | 4 +- .../jscript/polyfills/GeneratorPolyfill.java | 2 +- src/me/topchetoeu/jscript/polyfills/JSON.java | 2 +- .../jscript/polyfills/ObjectPolyfill.java | 10 ++-- .../jscript/polyfills/PromisePolyfill.java | 10 ++-- .../jscript/polyfills/StringPolyfill.java | 10 ++-- .../jscript/polyfills/SymbolPolyfill.java | 4 +- 20 files changed, 116 insertions(+), 100 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 5340774..a146cba 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -22,7 +22,7 @@ public class Environment { @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError(ctx, "Regular expressions not supported."); + throw EngineException.ofError("Regular expressions not supported."); }); @Native public ObjectValue proto(String name) { return prototypes.get(name); diff --git a/src/me/topchetoeu/jscript/engine/MessageContext.java b/src/me/topchetoeu/jscript/engine/MessageContext.java index 9125018..febc7f0 100644 --- a/src/me/topchetoeu/jscript/engine/MessageContext.java +++ b/src/me/topchetoeu/jscript/engine/MessageContext.java @@ -17,7 +17,7 @@ public class MessageContext { public MessageContext pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { this.frames.add(frame); - if (this.frames.size() > maxStackFrames) throw EngineException.ofRange(ctx, "Stack overflow!"); + if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!"); return this; } public boolean popFrame(CodeFrame frame) { diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 3032da1..09f3f18 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -27,7 +27,7 @@ public class Runners { throw new EngineException(frame.pop()); } public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { - throw EngineException.ofSyntax(ctx, (String)instr.get(0)); + throw EngineException.ofSyntax((String)instr.get(0)); } private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { @@ -77,9 +77,9 @@ public class Runners { var name = frame.pop(); var obj = frame.pop(); - if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType(ctx, "Getter must be a function or undefined."); - if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType(ctx, "Setter must be a function or undefined."); - if (!Values.isObject(obj)) throw EngineException.ofType(ctx, "Property apply target must be an object."); + if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined."); + if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined."); + if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object."); Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false); frame.push(ctx, obj); @@ -214,7 +214,7 @@ public class Runners { frame.push(ctx, Values.getMember(ctx, obj, key)); } catch (IllegalArgumentException e) { - throw EngineException.ofType(ctx, e.getMessage()); + throw EngineException.ofType(e.getMessage()); } frame.codePtr++; return NO_RETURN; @@ -239,7 +239,7 @@ public class Runners { var key = frame.pop(); var obj = frame.pop(); - if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax(ctx, "Can't set member '" + key + "'."); + if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); if ((boolean)instr.get(0)) frame.push(ctx, val); frame.codePtr++; return NO_RETURN; @@ -311,7 +311,7 @@ public class Runners { if (instr.is(0, "dbg_names")) { var names = new String[instr.params.length - 1]; for (var i = 0; i < instr.params.length - 1; i++) { - if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax(ctx, "NOP dbg_names instruction must specify only string parameters."); + if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters."); names[i] = (String)instr.params[i + 1]; } frame.scope.setNames(names); @@ -325,7 +325,7 @@ public class Runners { var key = frame.pop(); var val = frame.pop(); - if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax(ctx, "Can't delete member '" + key + "'."); + if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); frame.push(ctx, true); frame.codePtr++; return NO_RETURN; @@ -383,7 +383,7 @@ public class Runners { case OPERATION: return execOperation(ctx, instr, frame); - default: throw EngineException.ofSyntax(ctx, "Invalid instruction " + instr.type.name() + "."); + default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); } } } diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index d505532..e447f2a 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -60,12 +60,12 @@ public class GlobalScope implements ScopeRecord { } public Object get(Context ctx, String name) throws InterruptedException { - if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax(ctx, "The variable '" + name + "' doesn't exist."); + if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); else return obj.getMember(ctx, name); } public void set(Context ctx, String name, Object val) throws InterruptedException { - if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax(ctx, "The variable '" + name + "' doesn't exist."); - if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax(ctx, "The global '" + name + "' is readonly."); + if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); + if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); } public Set keys() { diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index 3232f47..4ec8991 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -9,7 +9,8 @@ public class LocalScope { public final ArrayList catchVars = new ArrayList<>(); public ValueVariable get(int i) { - if (i >= locals.length) return catchVars.get(i - locals.length); + if (i >= locals.length) + return catchVars.get(i - locals.length); if (i >= 0) return locals[i]; else return captures[~i]; } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 4eec36f..eae0dce 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -14,6 +14,10 @@ public class ObjectValue { OBJECT, ARRAY, FUNCTION, + ERROR, + SYNTAX_ERROR, + TYPE_ERROR, + RANGE_ERROR, } public static enum State { NORMAL, @@ -35,6 +39,10 @@ public class ObjectValue { private static final Object OBJ_PROTO = new Object(); private static final Object ARR_PROTO = new Object(); private static final Object FUNC_PROTO = new Object(); + private static final Object ERR_PROTO = new Object(); + private static final Object SYNTAX_ERR_PROTO = new Object(); + private static final Object TYPE_ERR_PROTO = new Object(); + private static final Object RANGE_ERR_PROTO = new Object(); protected Object prototype; @@ -142,6 +150,10 @@ public class ObjectValue { if (prototype == OBJ_PROTO) return ctx.env.proto("object"); if (prototype == ARR_PROTO) return ctx.env.proto("array"); if (prototype == FUNC_PROTO) return ctx.env.proto("function"); + if (prototype == ERR_PROTO) return ctx.env.proto("error"); + if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr"); + if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr"); + if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr"); } catch (NullPointerException e) { return null; @@ -164,6 +176,10 @@ public class ObjectValue { if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO; else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO; else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO; + else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO; + else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; + else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO; + else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO; else prototype = obj; } else prototype = obj; @@ -178,6 +194,10 @@ public class ObjectValue { case OBJECT: prototype = OBJ_PROTO; break; case FUNCTION: prototype = FUNC_PROTO; break; case ARRAY: prototype = ARR_PROTO; break; + case ERROR: prototype = ERR_PROTO; break; + case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break; + case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break; + case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break; case NONE: prototype = null; break; } return true; diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index ab7a962..d909ede 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -75,7 +75,7 @@ public class Values { if (isPrimitive(res)) return res; } - throw EngineException.ofType(ctx, "Value couldn't be converted to a primitive."); + throw EngineException.ofType("Value couldn't be converted to a primitive."); } public static boolean isPrimitive(Object obj) { @@ -105,7 +105,7 @@ public class Values { } } - throw EngineException.ofType(ctx, "Value couldn't be converted to a primitive."); + throw EngineException.ofType("Value couldn't be converted to a primitive."); } public static boolean toBoolean(Object obj) { if (obj == NULL || obj == null) return false; @@ -284,8 +284,8 @@ public class Values { } public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException { obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); - if (obj == null) throw EngineException.ofType(ctx, "Tried to access member of undefined."); - if (obj == NULL) throw EngineException.ofType(ctx, "Tried to access member of null."); + if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); + if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (key.equals("__proto__")) return setPrototype(ctx, obj, val); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); @@ -373,7 +373,7 @@ public class Values { } public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { - if (!isFunction(func)) throw EngineException.ofType(ctx, "Tried to call a non-function value."); + if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); return function(func).call(ctx, thisArg, args); } public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index 93471cc..fab5276 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -5,8 +5,9 @@ import java.util.List; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; -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.engine.values.ObjectValue.PlaceholderProto; public class EngineException extends RuntimeException { public final Object value; @@ -48,20 +49,11 @@ public class EngineException extends RuntimeException { return ss.toString(); } - private static Object err(Context ctx, String type, String name, String msg) throws InterruptedException { - try { - var proto = ctx.env.proto(type); - var constr = Values.getMember(ctx, proto, "constructor"); - - if (constr instanceof FunctionValue) { - var res = Values.callNew(ctx, constr, msg); - if (name != null) Values.setMember(ctx, res, "name", name); - return res; - } - } - catch (IllegalArgumentException e) { } - - return name + ": " + msg; + private static Object err(String name, String msg, PlaceholderProto proto) { + var res = new ObjectValue(proto); + if (name != null) res.defineProperty(null, "name", name); + res.defineProperty(null, "message", msg); + return res; } public EngineException(Object error) { @@ -71,22 +63,22 @@ public class EngineException extends RuntimeException { this.cause = null; } - public static EngineException ofError(Context ctx, String name, String msg) throws InterruptedException { - return new EngineException(err(ctx, "error", name, msg)); + public static EngineException ofError(String name, String msg) { + return new EngineException(err(name, msg, PlaceholderProto.ERROR)); } - public static EngineException ofError(Context ctx, String msg) throws InterruptedException { - return new EngineException(err(ctx, "error", null, msg)); + public static EngineException ofError(String msg) { + return new EngineException(err(null, msg, PlaceholderProto.ERROR)); } - public static EngineException ofSyntax(Context ctx, SyntaxException e) throws InterruptedException { - return new EngineException(err(ctx, "syntaxErr", null, e.msg)).add(null, e.loc); + public static EngineException ofSyntax(SyntaxException e) { + return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc); } - public static EngineException ofSyntax(Context ctx, String msg) throws InterruptedException { - return new EngineException(err(ctx, "syntaxErr", null, msg)); + public static EngineException ofSyntax(String msg) { + return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); } - public static EngineException ofType(Context ctx, String msg) throws InterruptedException { - return new EngineException(err(ctx, "typeErr", null, msg)); + public static EngineException ofType(String msg) { + return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR)); } - public static EngineException ofRange(Context ctx, String msg) throws InterruptedException { - return new EngineException(err(ctx, "rangeErr", null, msg)); + public static EngineException ofRange(String msg) { + return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR)); } } diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 3d6f1e9..0098016 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -142,7 +142,7 @@ public class NativeWrapperProvider implements WrappersProvider { } if (((OverloadFunction)func).overloads.size() == 0) { - func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError(a, "This constructor is not invokable."); }); + func = new NativeFunction(clazz.getName(), (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 daf68ee..bf0f20c 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -34,7 +34,7 @@ public class OverloadFunction extends FunctionValue { } catch (ConvertException e) { if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(ctx, String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); + else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target)); } } @@ -49,7 +49,7 @@ public class OverloadFunction extends FunctionValue { } catch (ConvertException e) { if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(ctx, String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); + else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target)); } } @@ -64,7 +64,7 @@ public class OverloadFunction extends FunctionValue { } catch (ConvertException e) { if (overloads.size() > 1) continue loop; - else throw EngineException.ofType(ctx, String.format("This argument can't be converted from %s to %s", e.source, e.target)); + else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target)); } if (consumesEngine) newArgs[0] = ctx; @@ -77,7 +77,7 @@ public class OverloadFunction extends FunctionValue { return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); } catch (InstantiationException e) { - throw EngineException.ofError(ctx, "The class may not be instantiated."); + throw EngineException.ofError("The class may not be instantiated."); } catch (IllegalAccessException | IllegalArgumentException e) { continue; @@ -88,21 +88,21 @@ public class OverloadFunction extends FunctionValue { throw ((EngineException)e.getTargetException()).add(name, loc); } else if (e.getTargetException() instanceof NullPointerException) { - throw EngineException.ofType(ctx, "Unexpected value of 'undefined'.").add(name, loc); + throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc); } else { - throw EngineException.ofError(ctx, e.getTargetException().getMessage()).add(name, loc); + throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); } } catch (ReflectiveOperationException e) { - throw EngineException.ofError(ctx, e.getMessage()).add(name, new Location(0, 0, "")); + throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "")); } catch (Exception e) { throw e; } } - throw EngineException.ofType(ctx, "No overload found for native method."); + throw EngineException.ofType("No overload found for native method."); } public OverloadFunction add(Overload overload) { diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java index 46c024b..3aad543 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java @@ -65,7 +65,7 @@ public class AsyncFunctionPolyfill extends FunctionValue { public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { var handler = new AsyncHelper(); var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await)); - if (!(func instanceof CodeFunction)) throw EngineException.ofType(ctx, "Return value of argument must be a js function."); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); handler.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN); return handler.promise; diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java index acf3d07..801cc8b 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java @@ -122,7 +122,7 @@ public class AsyncGeneratorPolyfill extends FunctionValue { new NativeFunction("await", handler::await), new NativeFunction("yield", handler::yield) ); - if (!(func instanceof CodeFunction)) throw EngineException.ofType(ctx, "Return value of argument must be a js function."); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); return handler; } diff --git a/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java index 6a530eb..540cd55 100644 --- a/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java @@ -5,31 +5,31 @@ import me.topchetoeu.jscript.engine.values.ArrayValue; 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.NativeConstructor; public class ErrorPolyfill { public static class SyntaxErrorPolyfill extends ErrorPolyfill { - @Native public SyntaxErrorPolyfill(Context ctx, Object message) throws InterruptedException { - super(ctx, message); - this.name = "SyntaxError"; + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + var target = ErrorPolyfill.constructor(ctx, thisArg, message); + target.defineProperty(ctx, "name", "SyntaxError"); + return target; } } public static class TypeErrorPolyfill extends ErrorPolyfill { - @Native public TypeErrorPolyfill(Context ctx, Object message) throws InterruptedException { - super(ctx, message); - this.name = "TypeError"; + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + var target = ErrorPolyfill.constructor(ctx, thisArg, message); + target.defineProperty(ctx, "name", "TypeError"); + return target; } } public static class RangeErrorPolyfill extends ErrorPolyfill { - @Native public RangeErrorPolyfill(Context ctx, Object message) throws InterruptedException { - super(ctx, message); - this.name = "RangeError"; + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + var target = ErrorPolyfill.constructor(ctx, thisArg, message); + target.defineProperty(ctx, "name", "RangeError"); + return target; } } - @Native public final ArrayValue stack; - @Native public String message; - @Native public String name = "Error"; - private static String toString(Context ctx, Object name, Object message, ArrayValue stack) throws InterruptedException { if (name == null) name = ""; else name = Values.toString(ctx, name).trim(); @@ -52,10 +52,7 @@ public class ErrorPolyfill { } @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { - if (thisArg instanceof ErrorPolyfill) { - return toString(ctx, ((ErrorPolyfill)thisArg).name, ((ErrorPolyfill)thisArg).message, ((ErrorPolyfill)thisArg).stack); - } - else if (thisArg instanceof ObjectValue) { + if (thisArg instanceof ObjectValue) { var stack = Values.getMember(ctx, thisArg, "stack"); if (!(stack instanceof ArrayValue)) stack = null; return toString(ctx, @@ -67,9 +64,15 @@ public class ErrorPolyfill { else return "[Invalid error]"; } - @Native public ErrorPolyfill(Context ctx, Object message) throws InterruptedException { - this.stack = new ArrayValue(ctx, ctx.message.stackTrace().toArray()); - if (message == null) this.message = ""; - else this.message = Values.toString(ctx, message); + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + var target = new ObjectValue(); + if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; + + target.defineProperty(ctx, "stack", new ArrayValue(ctx, ctx.message.stackTrace().toArray())); + target.defineProperty(ctx, "name", "Error"); + if (message == null) target.defineProperty(ctx, "message", ""); + else target.defineProperty(ctx, "message", Values.toString(ctx, message)); + + return target; } } diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index 9b6f00f..7fbdbc7 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -14,12 +14,12 @@ public class FunctionPolyfill { return func.call(ctx, thisArg, args.toArray()); } @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { - if (!(func instanceof FunctionValue)) throw EngineException.ofError(ctx, "Expected this to be a function."); + if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); return func.call(ctx, thisArg, args); } @Native(thisArg = true) public static FunctionValue bind(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { - if (!(func instanceof FunctionValue)) throw EngineException.ofError(ctx, "Expected this to be a function."); + if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { var resArgs = new Object[args.length + callArgs.length]; diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index ca3a35d..6da8d8f 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -89,7 +89,7 @@ public class GeneratorPolyfill extends FunctionValue { public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { var handler = new Generator(); var func = factory.call(ctx, thisArg, new NativeFunction("yield", handler::yield)); - if (!(func instanceof CodeFunction)) throw EngineException.ofType(ctx, "Return value of argument must be a js function."); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func); return handler; } diff --git a/src/me/topchetoeu/jscript/polyfills/JSON.java b/src/me/topchetoeu/jscript/polyfills/JSON.java index bcf7697..53873a5 100644 --- a/src/me/topchetoeu/jscript/polyfills/JSON.java +++ b/src/me/topchetoeu/jscript/polyfills/JSON.java @@ -75,7 +75,7 @@ public class JSON { return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); } catch (SyntaxException e) { - throw EngineException.ofSyntax(ctx, e.msg); + throw EngineException.ofSyntax(e.msg); } } @Native diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java index 2ce6712..9875a74 100644 --- a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -33,27 +33,27 @@ public class ObjectPolyfill { var hasSet = attrib.hasMember(ctx, "set", false); if (hasVal) { - if (hasGet || hasSet) throw EngineException.ofType(ctx, "Cannot specify a value and accessors for a property."); + if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property."); if (!obj.defineProperty( ctx, key, attrib.getMember(ctx, "value"), Values.toBoolean(attrib.getMember(ctx, "writable")), Values.toBoolean(attrib.getMember(ctx, "configurable")), Values.toBoolean(attrib.getMember(ctx, "enumerable")) - )) throw EngineException.ofType(ctx, "Can't define property '" + key + "'."); + )) throw EngineException.ofType("Can't define property '" + key + "'."); } else { var get = attrib.getMember(ctx, "get"); var set = attrib.getMember(ctx, "set"); - if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType(ctx, "Get accessor must be a function."); - if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType(ctx, "Set accessor must be a function."); + if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function."); + if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function."); if (!obj.defineProperty( ctx, key, (FunctionValue)get, (FunctionValue)set, Values.toBoolean(attrib.getMember(ctx, "configurable")), Values.toBoolean(attrib.getMember(ctx, "enumerable")) - )) throw EngineException.ofType(ctx, "Can't define property '" + key + "'."); + )) throw EngineException.ofType("Can't define property '" + key + "'."); } return obj; diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index dab4f57..41e977d 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -44,7 +44,7 @@ public class PromisePolyfill { } @Native public static PromisePolyfill any(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "Expected argument for any to be an array."); + 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() }; @@ -69,7 +69,7 @@ public class PromisePolyfill { return res; } @Native public static PromisePolyfill race(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "Expected argument for any to be an array."); + 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 PromisePolyfill(); @@ -85,7 +85,7 @@ public class PromisePolyfill { return res; } @Native public static PromisePolyfill all(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "Expected argument for any to be an array."); + 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() }; @@ -112,7 +112,7 @@ public class PromisePolyfill { return res; } @Native public static PromisePolyfill allSettled(Context ctx, Object _promises) throws InterruptedException { - if (!Values.isArray(_promises)) throw EngineException.ofType(ctx, "Expected argument for any to be an array."); + 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() }; @@ -314,7 +314,7 @@ public class PromisePolyfill { * NOT THREAD SAFE - must be called from the engine executor thread */ @Native public PromisePolyfill(Context ctx, FunctionValue func) throws InterruptedException { - if (!(func instanceof FunctionValue)) throw EngineException.ofType(ctx, "A function must be passed to the promise constructor."); + if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); try { func.call( ctx, null, diff --git a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java index 51255d6..41093f5 100644 --- a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java @@ -21,7 +21,7 @@ public class StringPolyfill { private static String passThis(Context ctx, String funcName, Object val) throws InterruptedException { if (val instanceof StringPolyfill) return ((StringPolyfill)val).value; else if (val instanceof String) return (String)val; - else throw EngineException.ofType(ctx, String.format("'%s' may only be called upon object and primitve strings.", funcName)); + else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); } private static int normalizeI(int i, int len, boolean clamp) { if (i < 0) i += len; @@ -137,9 +137,9 @@ public class StringPolyfill { var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), ""); _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else throw EngineException.ofError(ctx, "Regular expressions don't support matching."); + else throw EngineException.ofError("Regular expressions don't support matching."); } - else throw EngineException.ofError(ctx, "Regular expressions not supported."); + else throw EngineException.ofError("Regular expressions not supported."); } catch (IllegalArgumentException e) { return new ArrayValue(ctx, ""); } @@ -162,9 +162,9 @@ public class StringPolyfill { var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), "g"); var _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else throw EngineException.ofError(ctx, "Regular expressions don't support matching."); + else throw EngineException.ofError("Regular expressions don't support matching."); } - else throw EngineException.ofError(ctx, "Regular expressions not supported."); + else throw EngineException.ofError("Regular expressions not supported."); return match.call(ctx, term, val); } diff --git a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java index 67ad078..149b2be 100644 --- a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java @@ -30,11 +30,11 @@ public class SymbolPolyfill { private static Symbol passThis(Context ctx, String funcName, Object val) throws InterruptedException { if (val instanceof SymbolPolyfill) return ((SymbolPolyfill)val).value; else if (val instanceof Symbol) return (Symbol)val; - else throw EngineException.ofType(ctx, String.format("'%s' may only be called upon object and primitve symbols.", funcName)); + else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); } @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { - if (thisArg instanceof ObjectValue) throw EngineException.ofType(ctx, "Symbol constructor may not be called with new."); + if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); if (val == null) return new Symbol(""); else return new Symbol(Values.toString(ctx, val)); } -- 2.45.2 From c1b84689c4141e8c381bd3f57b23d106aaa4cfbc Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:27:19 +0300 Subject: [PATCH 16/23] fix: rewrite try-catch-finally logic --- .../jscript/engine/frame/CodeFrame.java | 294 +++++++----------- 1 file changed, 107 insertions(+), 187 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 7a94fe1..c503931 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.engine.frame; -import java.util.ArrayList; -import java.util.List; +import java.util.Stack; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; @@ -25,8 +24,8 @@ public class CodeFrame { public final int tryStart, catchStart, finallyStart, end; public int state; public Object retVal; + public Object err; public int jumpPtr; - public EngineException err; public TryCtx(int tryStart, int tryN, int catchN, int finallyN) { hasCatch = catchN >= 0; @@ -46,7 +45,7 @@ public class CodeFrame { public final LocalScope scope; public final Object thisArg; public final Object[] args; - public final List tryStack = new ArrayList<>(); + public final Stack tryStack = new Stack<>(); public final CodeFunction function; public Object[] stack = new Object[32]; @@ -94,9 +93,11 @@ public class CodeFrame { stack[stackPtr++] = Values.normalize(ctx, val); } - // TODO: THIS SYSTEM IS SEVERLY BROKEN - // MUST FIX!!!!! - + private void setCause(Context ctx, Object err, Object cause) throws InterruptedException { + if (err instanceof ObjectValue) { + Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause); + } + } private Object nextNoTry(Context ctx) throws InterruptedException { if (Thread.currentThread().isInterrupted()) throw new InterruptedException(); if (codePtr < 0 || codePtr >= function.body.length) return null; @@ -115,195 +116,114 @@ public class CodeFrame { } } - private void setCause(Context ctx, Object err, Object cause) throws InterruptedException { - if (err instanceof ObjectValue) { - Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause); - } - } - // private void propagateErr(Context ctx, Object err) { - // while (!tryStack.isEmpty()) { - // var tmp = tryStack.get(tryStack.size() - 1); + public Object next(Context ctx, Object value, Object returnValue, Object error) throws InterruptedException { + if (value != Runners.NO_RETURN) push(ctx, value); - // if (tmp.state == TryCtx.STATE_TRY || tmp.state == TryCtx.STATE_CATCH) { - // tmp.jumpPtr = tmp.end; - - // if (tmp.state == TryCtx.STATE_TRY && tmp.hasCatch) { - // tmp.state = TryCtx.STATE_CATCH; - // scope.catchVars.add(new ValueVariable(false, err)); - // codePtr = tmp.catchStart; - // return; - // } - // else if (tmp.hasFinally) { - // tmp.state = TryCtx.STATE_FINALLY_THREW; - // tmp.err = new EngineException(err); - // codePtr = tmp.finallyStart; - // return; - // } - // else if (tmp.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); - // } - - // tryStack.remove(tryStack.size() - 1); - // } - // throw new EngineException(err); - // } - // private void propagateRet(Context ctx, Object val) { - // while (!tryStack.isEmpty()) { - // var tmp = tryStack.get(tryStack.size() - 1); - - // if (tmp.state == TryCtx.STATE_TRY || tmp.state == TryCtx.STATE_CATCH) { - // tmp.jumpPtr = tmp.end; - - // if (tmp.hasFinally) { - // tmp.state = TryCtx.STATE_FINALLY_RETURNED; - // tmp.err = new EngineException(err); - // codePtr = tmp.finallyStart; - // return; - // } - // else if (tmp.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); - // } - - // tryStack.remove(tryStack.size() - 1); - // } - // return val; - // } - - public Object next(Context ctx, Object prevValue, Object prevReturn, Object prevError) throws InterruptedException { - TryCtx tryCtx = null; - if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN; - - while (!tryStack.isEmpty()) { - var tmp = tryStack.get(tryStack.size() - 1); - var remove = false; - - if (prevError != Runners.NO_RETURN) { - remove = true; - if (tmp.state == TryCtx.STATE_TRY) { - tmp.jumpPtr = tmp.end; - - if (tmp.hasCatch) { - tmp.state = TryCtx.STATE_CATCH; - scope.catchVars.add(new ValueVariable(false, prevError)); - prevError = Runners.NO_RETURN; - codePtr = tmp.catchStart; - remove = false; - } - else if (tmp.hasFinally) { - tmp.state = TryCtx.STATE_FINALLY_THREW; - tmp.err = new EngineException(prevError); - prevError = Runners.NO_RETURN; - codePtr = tmp.finallyStart; - remove = false; - } - } - } - else if (prevReturn != Runners.NO_RETURN) { - remove = true; - if (tmp.hasFinally && tmp.state <= TryCtx.STATE_CATCH) { - tmp.state = TryCtx.STATE_FINALLY_RETURNED; - tmp.retVal = prevReturn; - prevReturn = Runners.NO_RETURN; - codePtr = tmp.finallyStart; - remove = false; - } - } - else if (tmp.state == TryCtx.STATE_TRY) { - if (codePtr < tmp.tryStart || codePtr >= tmp.catchStart) { - if (jumpFlag) tmp.jumpPtr = codePtr; - else tmp.jumpPtr = tmp.end; - - if (tmp.hasFinally) { - tmp.state = TryCtx.STATE_FINALLY_JUMPED; - codePtr = tmp.finallyStart; - } - else codePtr = tmp.jumpPtr; - remove = !tmp.hasFinally; - } - } - else if (tmp.state == TryCtx.STATE_CATCH) { - if (codePtr < tmp.catchStart || codePtr >= tmp.finallyStart) { - if (jumpFlag) tmp.jumpPtr = codePtr; - else tmp.jumpPtr = tmp.end; - scope.catchVars.remove(scope.catchVars.size() - 1); - - if (tmp.hasFinally) { - tmp.state = TryCtx.STATE_FINALLY_JUMPED; - codePtr = tmp.finallyStart; - } - else codePtr = tmp.jumpPtr; - remove = !tmp.hasFinally; - } - } - else if (codePtr < tmp.finallyStart || codePtr >= tmp.end) { - if (!jumpFlag) { - if (tmp.state == TryCtx.STATE_FINALLY_THREW) throw tmp.err; - else if (tmp.state == TryCtx.STATE_FINALLY_RETURNED) return tmp.retVal; - else if (tmp.state == TryCtx.STATE_FINALLY_JUMPED) codePtr = tmp.jumpPtr; - } - else codePtr = tmp.jumpPtr; - remove = true; - } - - if (remove) tryStack.remove(tryStack.size() - 1); - else { - tryCtx = tmp; - break; - } + if (returnValue == Runners.NO_RETURN && error == Runners.NO_RETURN) { + try { returnValue = nextNoTry(ctx); } + catch (EngineException e) { error = e.value; } } - if (prevError != Runners.NO_RETURN) throw new EngineException(prevError); - if (prevReturn != Runners.NO_RETURN) return prevReturn; - if (prevValue != Runners.NO_RETURN) push(ctx, prevValue); + while (!tryStack.empty()) { + var tryCtx = tryStack.peek(); + var newState = -1; - if (tryCtx == null) return nextNoTry(ctx); - else if (tryCtx.state == TryCtx.STATE_TRY) { - try { - var res = nextNoTry(ctx); - if (res != Runners.NO_RETURN && tryCtx.hasFinally) { - tryCtx.retVal = res; - tryCtx.state = TryCtx.STATE_FINALLY_RETURNED; - } + switch (tryCtx.state) { + case TryCtx.STATE_TRY: + if (error != Runners.NO_RETURN) { + 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 = error; + newState = TryCtx.STATE_FINALLY_RETURNED; + } + break; + } + else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) return Runners.NO_RETURN; - else return res; - } - catch (EngineException e) { - throw e; - // propagateErr(ctx, e.value); + 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 != Runners.NO_RETURN) { + if (tryCtx.hasFinally) { + tryCtx.err = error; + newState = TryCtx.STATE_FINALLY_THREW; + } + break; + } + else if (returnValue != Runners.NO_RETURN) { + if (tryCtx.hasFinally) { + tryCtx.retVal = error; + 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 != Runners.NO_RETURN) setCause(ctx, error, tryCtx.err); + else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err; + else return Runners.NO_RETURN; + break; + case TryCtx.STATE_FINALLY_RETURNED: + 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 (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) { + if (!jumpFlag) codePtr = tryCtx.jumpPtr; + else codePtr = tryCtx.end; + } + else return Runners.NO_RETURN; + break; + } + + if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + + if (newState == -1) { + tryStack.pop(); + continue; + } + + tryCtx.state = newState; + switch (newState) { + case TryCtx.STATE_CATCH: + scope.catchVars.add(new ValueVariable(false, tryCtx.err)); + codePtr = tryCtx.catchStart; + break; + default: + codePtr = tryCtx.finallyStart; } - codePtr = tryCtx.finallyStart; return Runners.NO_RETURN; } - else if (tryCtx.state == TryCtx.STATE_CATCH) { - try { - var res = nextNoTry(ctx); - if (res != Runners.NO_RETURN && tryCtx.hasFinally) { - tryCtx.retVal = res; - tryCtx.state = TryCtx.STATE_FINALLY_RETURNED; - } - else return res; - } - catch (EngineException e) { - setCause(ctx, e.value, tryCtx.err); - if (tryCtx.hasFinally) { - tryCtx.err = e; - tryCtx.state = TryCtx.STATE_FINALLY_THREW; - } - else throw e; - } - - codePtr = tryCtx.finallyStart; - return Runners.NO_RETURN; - } - else if (tryCtx.state == TryCtx.STATE_FINALLY_THREW) { - try { - return nextNoTry(ctx); - } - catch (EngineException e) { - setCause(ctx, e.value, tryCtx.err); - throw e; - } - } - else return nextNoTry(ctx); + + if (error != Runners.NO_RETURN) throw new EngineException(error); + if (returnValue != Runners.NO_RETURN) return returnValue; + return Runners.NO_RETURN; } public Object run(Context ctx) throws InterruptedException { -- 2.45.2 From 0dacaaeb4c4dd127d15627a21f2e7831ce800fdc Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:08:23 +0300 Subject: [PATCH 17/23] a lot of fixes --- lib/core.ts | 76 +++-- lib/tsconfig.json | 1 - lib/utils.ts | 27 -- .../jscript/engine/Environment.java | 2 +- .../jscript/engine/frame/CodeFrame.java | 27 +- .../jscript/engine/values/Values.java | 2 +- .../topchetoeu/jscript/parsing/Parsing.java | 8 +- .../jscript/polyfills/ArrayPolyfill.java | 1 - .../polyfills/AsyncFunctionPolyfill.java | 2 +- .../polyfills/AsyncGeneratorPolyfill.java | 2 +- .../jscript/polyfills/BooleanPolyfill.java | 2 - .../jscript/polyfills/ErrorPolyfill.java | 7 +- .../jscript/polyfills/FunctionPolyfill.java | 4 +- .../jscript/polyfills/GeneratorPolyfill.java | 2 +- .../jscript/polyfills/Internals.java | 3 +- .../{JSON.java => JSONPolyfill.java} | 170 +++++----- .../jscript/polyfills/MapPolyfill.java | 1 - .../jscript/polyfills/NumberPolyfill.java | 2 - .../jscript/polyfills/ObjectPolyfill.java | 2 - .../jscript/polyfills/PromisePolyfill.java | 2 - .../topchetoeu/jscript/polyfills/RegExp.java | 187 ----------- .../jscript/polyfills/RegExpPolyfill.java | 296 ++++++++++++++++++ .../jscript/polyfills/SetPolyfill.java | 1 - .../jscript/polyfills/StringPolyfill.java | 2 - .../jscript/polyfills/SymbolPolyfill.java | 1 - 25 files changed, 452 insertions(+), 378 deletions(-) delete mode 100644 lib/utils.ts rename src/me/topchetoeu/jscript/polyfills/{JSON.java => JSONPolyfill.java} (95%) delete mode 100644 src/me/topchetoeu/jscript/polyfills/RegExp.java create mode 100644 src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index 3ce7a25..fa09628 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -17,7 +17,8 @@ interface Internals { syntax: SyntaxErrorConstructor; type: TypeErrorConstructor; range: RangeErrorConstructor; - + + regexp: typeof RegExp; map: typeof Map; set: typeof Set; @@ -50,44 +51,51 @@ interface Internals { log(...args: any[]): void; } +var env: Environment = arguments[0], internals: Internals = arguments[1]; + try { - var env: Environment = arguments[0], internals: Internals = arguments[1]; - const Object = env.global.Object = internals.object; - const Function = env.global.Function = internals.function; - const Array = env.global.Array = internals.array; - const Promise = env.global.Promise = internals.promise; - const Boolean = env.global.Boolean = internals.bool; - const Number = env.global.Number = internals.number; - const String = env.global.String = internals.string; - const Symbol = env.global.Symbol = internals.symbol; - const Error = env.global.Error = internals.error; - const SyntaxError = env.global.SyntaxError = internals.syntax; - const TypeError = env.global.TypeError = internals.type; - const RangeError = env.global.RangeError = internals.range; + const values = { + Object: env.global.Object = internals.object, + Function: env.global.Function = internals.function, + Array: env.global.Array = internals.array, + Promise: env.global.Promise = internals.promise, + Boolean: env.global.Boolean = internals.bool, + Number: env.global.Number = internals.number, + String: env.global.String = internals.string, + Symbol: env.global.Symbol = internals.symbol, + Error: env.global.Error = internals.error, + SyntaxError: env.global.SyntaxError = internals.syntax, + TypeError: env.global.TypeError = internals.type, + RangeError: env.global.RangeError = internals.range, + RegExp: env.global.RegExp = internals.regexp, + Map: env.global.Map = internals.map, + Set: env.global.Set = internals.set, + } + const Array = values.Array; - const Map = env.global.Map = internals.map; - const Set = env.global.Set = internals.set; + env.setProto('object', env.global.Object.prototype); + env.setProto('function', env.global.Function.prototype); + env.setProto('array', env.global.Array.prototype); + env.setProto('number', env.global.Number.prototype); + env.setProto('string', env.global.String.prototype); + env.setProto('symbol', env.global.Symbol.prototype); + env.setProto('bool', env.global.Boolean.prototype); - env.setProto('object', Object.prototype); - env.setProto('function', Function.prototype); - env.setProto('array', Array.prototype); - env.setProto('number', Number.prototype); - env.setProto('string', String.prototype); - env.setProto('symbol', Symbol.prototype); - env.setProto('bool', Boolean.prototype); - - env.setProto('error', Error.prototype); - env.setProto('rangeErr', RangeError.prototype); - env.setProto('typeErr', TypeError.prototype); - env.setProto('syntaxErr', SyntaxError.prototype); - - (Object.prototype as any).__proto__ = null; + env.setProto('error', env.global.Error.prototype); + env.setProto('rangeErr', env.global.RangeError.prototype); + env.setProto('typeErr', env.global.TypeError.prototype); + env.setProto('syntaxErr', env.global.SyntaxError.prototype); + (env.global.Object.prototype as any).__proto__ = null; internals.getEnv(run)?.setProto('array', Array.prototype); globalThis.log = (...args) => internals.apply(internals.log, internals, args); - run('regex'); + for (const key in values) { + (values as any)[key].prototype[env.symbol('Symbol.typeName')] = key; + log(); + } + run('timeout'); env.global.log = log; @@ -103,7 +111,7 @@ catch (e: any) { if ('name' in e) err += e.name + ": " + e.message; else err += 'Error: ' + e.message; } - else err += e; + else err += "[unknown]"; - log(e); -} \ No newline at end of file + internals.log(err); +} diff --git a/lib/tsconfig.json b/lib/tsconfig.json index f0ee4e3..326c2bd 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -2,7 +2,6 @@ "files": [ "lib.d.ts", "modules.ts", - "utils.ts", "regex.ts", "timeout.ts", "core.ts" diff --git a/lib/utils.ts b/lib/utils.ts deleted file mode 100644 index fe45aaf..0000000 --- a/lib/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -function setProps< - TargetT extends object, - DescT extends { - [x in Exclude ]?: TargetT[x] extends ((...args: infer ArgsT) => infer RetT) ? - ((this: TargetT, ...args: ArgsT) => RetT) : - TargetT[x] - } ->(target: TargetT, desc: DescT) { - var props = internals.keys(desc, false); - for (var i = 0; i in props; i++) { - var key = props[i]; - internals.defineField( - target, key, (desc as any)[key], - true, // writable - false, // enumerable - true // configurable - ); - } -} -function setConstr(target: object, constr: Function) { - internals.defineField( - target, 'constructor', constr, - true, // writable - false, // enumerable - true // configurable - ); -} diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index a146cba..e3e309b 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -22,7 +22,7 @@ public class Environment { @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported."); + throw EngineException.ofError("Regular expressions not supported.").setContext(ctx); }); @Native public ObjectValue proto(String name) { return prototypes.get(name); diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index c503931..2ab02a3 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -24,7 +24,7 @@ public class CodeFrame { public final int tryStart, catchStart, finallyStart, end; public int state; public Object retVal; - public Object err; + public EngineException err; public int jumpPtr; public TryCtx(int tryStart, int tryN, int catchN, int finallyN) { @@ -93,10 +93,11 @@ public class CodeFrame { stack[stackPtr++] = Values.normalize(ctx, val); } - private void setCause(Context ctx, Object err, Object cause) throws InterruptedException { - if (err instanceof ObjectValue) { + private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException { + if (err.value instanceof ObjectValue) { Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause); } + err.cause = cause; } private Object nextNoTry(Context ctx) throws InterruptedException { if (Thread.currentThread().isInterrupted()) throw new InterruptedException(); @@ -116,12 +117,12 @@ public class CodeFrame { } } - public Object next(Context ctx, Object value, Object returnValue, Object error) throws InterruptedException { + public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException { if (value != Runners.NO_RETURN) push(ctx, value); - if (returnValue == Runners.NO_RETURN && error == Runners.NO_RETURN) { + if (returnValue == Runners.NO_RETURN && error == null) { try { returnValue = nextNoTry(ctx); } - catch (EngineException e) { error = e.value; } + catch (EngineException e) { error = e; } } while (!tryStack.empty()) { @@ -130,7 +131,7 @@ public class CodeFrame { switch (tryCtx.state) { case TryCtx.STATE_TRY: - if (error != Runners.NO_RETURN) { + if (error != null) { if (tryCtx.hasCatch) { tryCtx.err = error; newState = TryCtx.STATE_CATCH; @@ -158,7 +159,7 @@ public class CodeFrame { else codePtr = tryCtx.end; break; case TryCtx.STATE_CATCH: - if (error != Runners.NO_RETURN) { + if (error != null) { if (tryCtx.hasFinally) { tryCtx.err = error; newState = TryCtx.STATE_FINALLY_THREW; @@ -167,7 +168,7 @@ public class CodeFrame { } else if (returnValue != Runners.NO_RETURN) { if (tryCtx.hasFinally) { - tryCtx.retVal = error; + tryCtx.retVal = returnValue; newState = TryCtx.STATE_FINALLY_RETURNED; } break; @@ -182,7 +183,7 @@ public class CodeFrame { else codePtr = tryCtx.end; break; case TryCtx.STATE_FINALLY_THREW: - if (error != Runners.NO_RETURN) setCause(ctx, error, tryCtx.err); + if (error != null) setCause(ctx, error, tryCtx.err); else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err; else return Runners.NO_RETURN; break; @@ -211,7 +212,7 @@ public class CodeFrame { tryCtx.state = newState; switch (newState) { case TryCtx.STATE_CATCH: - scope.catchVars.add(new ValueVariable(false, tryCtx.err)); + scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); codePtr = tryCtx.catchStart; break; default: @@ -221,7 +222,7 @@ public class CodeFrame { return Runners.NO_RETURN; } - if (error != Runners.NO_RETURN) throw new EngineException(error); + if (error != null) throw error.setContext(ctx); if (returnValue != Runners.NO_RETURN) return returnValue; return Runners.NO_RETURN; } @@ -230,7 +231,7 @@ public class CodeFrame { try { ctx.message.pushFrame(ctx, this); while (true) { - var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); + var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); if (res != Runners.NO_RETURN) return res; } } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index d909ede..07a0036 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -685,7 +685,7 @@ public class Values { printValue(ctx, val, new HashSet<>(), 0); } public static void printError(RuntimeException err, String prefix) throws InterruptedException { - prefix = prefix == null ? "Uncauthg" : "Uncaught " + prefix; + prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; try { if (err instanceof EngineException) { System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx)); diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index 54725b9..16d2f8c 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -685,7 +685,7 @@ public class Parsing { } } - public static ParseRes parseRegex(String filename, List tokens, int i) { + public static ParseRes parseRegex(String filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); try { if (tokens.get(i).isRegex()) { @@ -693,11 +693,7 @@ public class Parsing { var index = val.lastIndexOf('/'); var first = val.substring(1, index); var second = val.substring(index + 1); - return ParseRes.res(new NewStatement(loc, - new VariableStatement(null, "RegExp"), - new ConstantStatement(loc, first), - new ConstantStatement(loc, second) - ), 1); + return ParseRes.res(new RegexStatement(loc, first, second), 1); } else return ParseRes.failed(); } diff --git a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java index 499c620..a2901a7 100644 --- a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java @@ -14,7 +14,6 @@ import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeSetter; public class ArrayPolyfill { - @Native("@@Symbol.typeName") public final String name = "AsyncFunction"; @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException { return thisArg.size(); } diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java index 3aad543..951c6c5 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java @@ -26,7 +26,7 @@ public class AsyncFunctionPolyfill extends FunctionValue { awaiting = false; while (!awaiting) { try { - res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError); + res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedValue = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { promise.fulfill(ctx, res); diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java index 801cc8b..18950e3 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java @@ -39,7 +39,7 @@ public class AsyncGeneratorPolyfill extends FunctionValue { while (state == 0) { try { - res = frame.next(ctx, inducedValue, inducedReturn, inducedError); + res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedValue = inducedReturn = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { var obj = new ObjectValue(); diff --git a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java index df824b0..843e78b 100644 --- a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java @@ -7,8 +7,6 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; public class BooleanPolyfill { - @Native("@@Symbol.typeName") public final String name = "Boolean"; - public static final BooleanPolyfill TRUE = new BooleanPolyfill(true); public static final BooleanPolyfill FALSE = new BooleanPolyfill(false); diff --git a/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java index 540cd55..2e694a8 100644 --- a/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java @@ -30,7 +30,7 @@ public class ErrorPolyfill { } } - private static String toString(Context ctx, Object name, Object message, ArrayValue stack) throws InterruptedException { + private static String toString(Context ctx, Object cause, Object name, Object message, ArrayValue stack) throws InterruptedException { if (name == null) name = ""; else name = Values.toString(ctx, name).trim(); if (message == null) message = ""; @@ -39,7 +39,7 @@ public class ErrorPolyfill { if (!name.equals("")) res.append(name); if (!message.equals("") && !name.equals("")) res.append(": "); - if (!name.equals("")) res.append(message); + if (!message.equals("")) res.append(message); if (stack != null) { for (var el : stack) { @@ -48,6 +48,8 @@ public class ErrorPolyfill { } } + if (cause instanceof ObjectValue) res.append(toString(ctx, cause)); + return res.toString(); } @@ -56,6 +58,7 @@ public class ErrorPolyfill { var stack = Values.getMember(ctx, thisArg, "stack"); if (!(stack instanceof ArrayValue)) stack = null; return toString(ctx, + Values.getMember(ctx, thisArg, "cause"), Values.getMember(ctx, thisArg, "name"), Values.getMember(ctx, thisArg, "message"), (ArrayValue)stack diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index 7fbdbc7..e46e674 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -8,9 +8,7 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; public class FunctionPolyfill { - @Native("@@Symbol.typeName") public final String name = "Function"; - - @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { + @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { return func.call(ctx, thisArg, args.toArray()); } @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index 6da8d8f..db176ed 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -35,7 +35,7 @@ public class GeneratorPolyfill extends FunctionValue { while (!yielding) { try { - res = frame.next(ctx, inducedValue, inducedReturn, inducedError); + res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedReturn = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { done = true; diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index e53d6f5..643ab8f 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -20,7 +20,7 @@ public class Internals { @Native public final FunctionValue object, function, array, bool, number, string, symbol, - promise, map, set, + promise, map, set, regexp, error, syntax, type, range; @Native public void markSpecial(FunctionValue ...funcs) { @@ -174,5 +174,6 @@ public class Internals { this.syntax = targetEnv.wrappersProvider.getConstr(SyntaxErrorPolyfill.class); this.type = targetEnv.wrappersProvider.getConstr(TypeErrorPolyfill.class); this.range = targetEnv.wrappersProvider.getConstr(RangeErrorPolyfill.class); + this.regexp = targetEnv.wrappersProvider.getConstr(RegExpPolyfill.class); } } diff --git a/src/me/topchetoeu/jscript/polyfills/JSON.java b/src/me/topchetoeu/jscript/polyfills/JSONPolyfill.java similarity index 95% rename from src/me/topchetoeu/jscript/polyfills/JSON.java rename to src/me/topchetoeu/jscript/polyfills/JSONPolyfill.java index 53873a5..9d70719 100644 --- a/src/me/topchetoeu/jscript/polyfills/JSON.java +++ b/src/me/topchetoeu/jscript/polyfills/JSONPolyfill.java @@ -1,85 +1,85 @@ -package me.topchetoeu.jscript.polyfills; - -import java.util.HashSet; -import java.util.stream.Collectors; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.ArrayValue; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.exceptions.SyntaxException; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.json.JSONElement; -import me.topchetoeu.jscript.json.JSONList; -import me.topchetoeu.jscript.json.JSONMap; - -public class JSON { - private static Object toJS(JSONElement val) { - if (val.isBoolean()) return val.bool(); - if (val.isString()) return val.string(); - if (val.isNumber()) return val.number(); - if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJS).collect(Collectors.toList())); - if (val.isMap()) { - var res = new ObjectValue(); - for (var el : val.map().entrySet()) { - res.defineProperty(null, el.getKey(), toJS(el.getValue())); - } - return res; - } - if (val.isNull()) return Values.NULL; - return null; - } - private static JSONElement toJSON(Context ctx, Object val, HashSet prev) throws InterruptedException { - if (val instanceof Boolean) return JSONElement.bool((boolean)val); - 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 = toJSON(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); - - var res = new JSONList(); - - for (var el : ((ArrayValue)val).toArray()) { - var jsonEl = toJSON(ctx, el, prev); - if (jsonEl == null) jsonEl = JSONElement.NULL; - res.add(jsonEl); - } - - prev.remove(val); - return JSONElement.of(res); - } - if (val == null) return null; - return null; - } - - @Native - public static Object parse(Context ctx, String val) throws InterruptedException { - try { - return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); - } - catch (SyntaxException e) { - throw EngineException.ofSyntax(e.msg); - } - } - @Native - public static String stringify(Context ctx, Object val) throws InterruptedException { - return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>())); - } -} +package me.topchetoeu.jscript.polyfills; + +import java.util.HashSet; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.json.JSONElement; +import me.topchetoeu.jscript.json.JSONList; +import me.topchetoeu.jscript.json.JSONMap; + +public class JSONPolyfill { + private static Object toJS(JSONElement val) { + if (val.isBoolean()) return val.bool(); + if (val.isString()) return val.string(); + if (val.isNumber()) return val.number(); + if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSONPolyfill::toJS).collect(Collectors.toList())); + if (val.isMap()) { + var res = new ObjectValue(); + for (var el : val.map().entrySet()) { + res.defineProperty(null, el.getKey(), toJS(el.getValue())); + } + return res; + } + if (val.isNull()) return Values.NULL; + return null; + } + private static JSONElement toJSON(Context ctx, Object val, HashSet prev) throws InterruptedException { + if (val instanceof Boolean) return JSONElement.bool((boolean)val); + 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 = toJSON(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); + + var res = new JSONList(); + + for (var el : ((ArrayValue)val).toArray()) { + var jsonEl = toJSON(ctx, el, prev); + if (jsonEl == null) jsonEl = JSONElement.NULL; + res.add(jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val == null) return null; + return null; + } + + @Native + public static Object parse(Context ctx, String val) throws InterruptedException { + try { + return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); + } + catch (SyntaxException e) { + throw EngineException.ofSyntax(e.msg); + } + } + @Native + public static String stringify(Context ctx, Object val) throws InterruptedException { + return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>())); + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java b/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java index ce4277e..f747164 100644 --- a/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java @@ -13,7 +13,6 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeGetter; public class MapPolyfill { - @Native("@@Symbol.typeName") public final String name = "Map"; private LinkedHashMap map = new LinkedHashMap<>(); @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java index ddc89a1..a3859eb 100644 --- a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java @@ -7,8 +7,6 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; public class NumberPolyfill { - @Native("@@Symbol.typeName") public final String name = "Number"; - @Native public static final double EPSILON = java.lang.Math.ulp(1.0); @Native public static final double MAX_SAFE_INTEGER = 9007199254740991.; @Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java index 9875a74..51c12e9 100644 --- a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -11,8 +11,6 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; public class ObjectPolyfill { - @Native("@@Symbol.typeName") public final String name = "Object"; - @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException { for (var obj : src) { for (var key : Values.getMembers(ctx, obj, true, true)) { diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index 41e977d..741f052 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -28,8 +28,6 @@ public class PromisePolyfill { } } - @Native("@@Symbol.typeName") public final String name = "Promise"; - @Native("resolve") public static PromisePolyfill ofResolved(Context ctx, Object val) throws InterruptedException { var res = new PromisePolyfill(); diff --git a/src/me/topchetoeu/jscript/polyfills/RegExp.java b/src/me/topchetoeu/jscript/polyfills/RegExp.java deleted file mode 100644 index 804d0e7..0000000 --- a/src/me/topchetoeu/jscript/polyfills/RegExp.java +++ /dev/null @@ -1,187 +0,0 @@ -package me.topchetoeu.jscript.polyfills; - -import java.util.ArrayList; -import java.util.regex.Pattern; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.ArrayValue; -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.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeSetter; - -public class RegExp { - // I used Regex to analyze Regex - private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL); - private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]"); - - private static String cleanupPattern(Context ctx, Object val) throws InterruptedException { - if (val == null) return "(?:)"; - if (val instanceof RegExp) return ((RegExp)val).source; - if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExp) { - return ((RegExp)((NativeWrapper)val).wrapped).source; - } - var res = Values.toString(ctx, val); - if (res.equals("")) return "(?:)"; - return res; - } - private static String cleanupFlags(Context ctx, Object val) throws InterruptedException { - if (val == null) return ""; - return Values.toString(ctx, val); - } - - private static boolean checkEscaped(String s, int pos) { - int n = 0; - - while (true) { - if (pos <= 0) break; - if (s.charAt(pos) != '\\') break; - n++; - pos--; - } - - return (n % 2) != 0; - } - - @Native - public static RegExp escape(Context ctx, Object raw, Object flags) throws InterruptedException { - return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags)); - } - public static RegExp escape(String raw, String flags) { - return new RegExp(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags); - } - - private Pattern pattern; - private String[] namedGroups; - private int flags; - private int lastI = 0; - - @Native - public final String source; - @Native - public final boolean hasIndices; - @Native - public final boolean global; - @Native - public final boolean sticky; - - @NativeGetter("ignoreCase") - public boolean ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; } - @NativeGetter("multiline") - public boolean multiline() { return (flags & Pattern.MULTILINE) != 0; } - @NativeGetter("unicode") - public boolean unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; } - @NativeGetter("unicode") - public boolean dotAll() { return (flags & Pattern.DOTALL) != 0; } - - @NativeGetter("lastIndex") - public int lastIndex() { return lastI; } - @NativeSetter("lastIndex") - public void setLastIndex(Context ctx, Object i) throws InterruptedException { - lastI = (int)Values.toNumber(ctx, i); - } - public void setLastIndex(int i) { - lastI = i; - } - - @NativeGetter("flags") - public final String flags() { - String res = ""; - if (hasIndices) res += 'd'; - if (global) res += 'g'; - if (ignoreCase()) res += 'i'; - if (multiline()) res += 'm'; - if (dotAll()) res += 's'; - if (unicode()) res += 'u'; - if (sticky) res += 'y'; - return res; - } - - @Native - public Object exec(Context ctx, Object str) throws InterruptedException { - return exec(Values.toString(ctx, str)); - } - public Object exec(String str) { - var matcher = pattern.matcher(str); - if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) { - lastI = 0; - return Values.NULL; - } - if (sticky || global) { - lastI = matcher.end(); - if (matcher.end() == matcher.start()) lastI++; - } - - var obj = new ArrayValue(); - var groups = new ObjectValue(); - - for (var el : namedGroups) { - try { - groups.defineProperty(null, el, matcher.group(el)); - } - catch (IllegalArgumentException e) { } - } - if (groups.values.size() == 0) groups = null; - - - for (int i = 0; i < matcher.groupCount() + 1; i++) { - obj.set(null, i, matcher.group(i)); - } - obj.defineProperty(null, "groups", groups); - obj.defineProperty(null, "index", matcher.start()); - obj.defineProperty(null, "input", str); - - if (hasIndices) { - var indices = new ArrayValue(); - for (int i = 0; i < matcher.groupCount() + 1; i++) { - indices.set(null, i, new ArrayValue(null, matcher.start(i), matcher.end(i))); - } - var groupIndices = new ObjectValue(); - for (var el : namedGroups) { - groupIndices.defineProperty(null, el, new ArrayValue(null, matcher.start(el), matcher.end(el))); - } - indices.defineProperty(null, "groups", groupIndices); - obj.defineProperty(null, "indices", indices); - } - - return obj; - } - - @Native - public RegExp(Context ctx, Object pattern, Object flags) throws InterruptedException { - this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags)); - } - public RegExp(String pattern, String flags) { - if (pattern == null || pattern.equals("")) pattern = "(?:)"; - if (flags == null || flags.equals("")) flags = ""; - - this.flags = 0; - this.hasIndices = flags.contains("d"); - this.global = flags.contains("g"); - this.sticky = flags.contains("y"); - this.source = pattern; - - if (flags.contains("i")) this.flags |= Pattern.CASE_INSENSITIVE; - if (flags.contains("m")) this.flags |= Pattern.MULTILINE; - if (flags.contains("s")) this.flags |= Pattern.DOTALL; - if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS; - - this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags); - - var matcher = NAMED_PATTERN.matcher(source); - var groups = new ArrayList(); - - while (matcher.find()) { - if (!checkEscaped(source, matcher.start() - 1)) { - groups.add(matcher.group(1)); - } - } - - namedGroups = groups.toArray(String[]::new); - } - - public RegExp(String pattern) { this(pattern, null); } - public RegExp() { this(null, null); } -} diff --git a/src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java b/src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java new file mode 100644 index 0000000..373bff1 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java @@ -0,0 +1,296 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.regex.Pattern; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +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.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; + +public class RegExpPolyfill { + // I used Regex to analyze Regex + private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL); + private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]"); + + private static String cleanupPattern(Context ctx, Object val) throws InterruptedException { + if (val == null) return "(?:)"; + if (val instanceof RegExpPolyfill) return ((RegExpPolyfill)val).source; + if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpPolyfill) { + return ((RegExpPolyfill)((NativeWrapper)val).wrapped).source; + } + var res = Values.toString(ctx, val); + if (res.equals("")) return "(?:)"; + return res; + } + private static String cleanupFlags(Context ctx, Object val) throws InterruptedException { + if (val == null) return ""; + return Values.toString(ctx, val); + } + + private static boolean checkEscaped(String s, int pos) { + int n = 0; + + while (true) { + if (pos <= 0) break; + if (s.charAt(pos) != '\\') break; + n++; + pos--; + } + + return (n % 2) != 0; + } + + @Native + public static RegExpPolyfill escape(Context ctx, Object raw, Object flags) throws InterruptedException { + return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags)); + } + public static RegExpPolyfill escape(String raw, String flags) { + return new RegExpPolyfill(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags); + } + + private Pattern pattern; + private String[] namedGroups; + private int flags; + + @Native public int lastI = 0; + @Native public final String source; + @Native public final boolean hasIndices; + @Native public final boolean global; + @Native public final boolean sticky; + + @NativeGetter public boolean ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; } + @NativeGetter public boolean multiline() { return (flags & Pattern.MULTILINE) != 0; } + @NativeGetter public boolean unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; } + @NativeGetter public boolean dotAll() { return (flags & Pattern.DOTALL) != 0; } + + @NativeGetter("flags") public final String flags() { + String res = ""; + if (hasIndices) res += 'd'; + if (global) res += 'g'; + if (ignoreCase()) res += 'i'; + if (multiline()) res += 'm'; + if (dotAll()) res += 's'; + if (unicode()) res += 'u'; + if (sticky) res += 'y'; + return res; + } + + + @Native public Object exec(String str) { + var matcher = pattern.matcher(str); + if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) { + lastI = 0; + return Values.NULL; + } + if (sticky || global) { + lastI = matcher.end(); + if (matcher.end() == matcher.start()) lastI++; + } + + var obj = new ArrayValue(); + var groups = new ObjectValue(); + + for (var el : namedGroups) { + try { + groups.defineProperty(null, el, matcher.group(el)); + } + catch (IllegalArgumentException e) { } + } + if (groups.values.size() == 0) groups = null; + + + for (int i = 0; i < matcher.groupCount() + 1; i++) { + obj.set(null, i, matcher.group(i)); + } + obj.defineProperty(null, "groups", groups); + obj.defineProperty(null, "index", matcher.start()); + obj.defineProperty(null, "input", str); + + if (hasIndices) { + var indices = new ArrayValue(); + for (int i = 0; i < matcher.groupCount() + 1; i++) { + indices.set(null, i, new ArrayValue(null, matcher.start(i), matcher.end(i))); + } + var groupIndices = new ObjectValue(); + for (var el : namedGroups) { + groupIndices.defineProperty(null, el, new ArrayValue(null, matcher.start(el), matcher.end(el))); + } + indices.defineProperty(null, "groups", groupIndices); + obj.defineProperty(null, "indices", indices); + } + + return obj; + } + + @Native public boolean test(String str) { + return this.exec(str) != Values.NULL; + } + @Native public String toString() { + return "/" + source + "/" + flags(); + } + + @Native("@@Symvol.match") public Object match(Context ctx, String target) throws InterruptedException { + if (this.global) { + var res = new ArrayValue(); + Object val; + while ((val = this.exec(target)) != Values.NULL) { + res.set(ctx, res.size(), Values.getMember(ctx, val, 0)); + } + lastI = 0; + return res; + } + else { + var res = this.exec(target); + if (!this.sticky) this.lastI = 0; + return res; + } + } + + @Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) throws InterruptedException { + var pattern = new RegExpPolyfill(this.source, this.flags() + "g"); + + return Values.fromJavaIterator(ctx, new Iterator() { + private Object val = null; + private boolean updated = false; + + private void update() { + if (!updated) val = pattern.exec(target); + } + @Override public boolean hasNext() { + update(); + return val != Values.NULL; + } + @Override public Object next() { + update(); + updated = false; + return val; + } + }); + } + + @Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) throws InterruptedException { + var pattern = new RegExpPolyfill(this.source, this.flags() + "g"); + Object match; + int lastEnd = 0; + var res = new ArrayValue(); + var lim = limit == null ? 0 : Values.toNumber(ctx, limit); + + while ((match = pattern.exec(target)) != Values.NULL) { + var added = new ArrayList(); + var arrMatch = (ArrayValue)match; + int index = (int)Values.toNumber(ctx, Values.getMember(ctx, match, "index")); + var matchVal = (String)arrMatch.get(0); + + if (index >= target.length()) break; + + if (matchVal.length() == 0 || index - lastEnd > 0) { + added.add(target.substring(lastEnd, pattern.lastI)); + if (pattern.lastI < target.length()) { + for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i)); + } + } + else { + for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i)); + } + + if (sensible) { + if (limit != null && res.size() + added.size() >= lim) break; + else for (var i = 0; i < added.size(); i++) res.set(ctx, res.size(), added.get(i)); + } + else { + for (var i = 0; i < added.size(); i++) { + if (limit != null && res.size() >= lim) return res; + else res.set(ctx, res.size(), added.get(i)); + } + } + lastEnd = pattern.lastI; + } + if (lastEnd < target.length()) { + res.set(ctx, res.size(), target.substring(lastEnd)); + } + return res; + } + // [Symbol.replace](target, replacement) { + // const pattern = new this.constructor(this, this.flags + "d") as RegExp; + // let match: RegExpResult | null; + // let lastEnd = 0; + // const res: string[] = []; + // // log(pattern.toString()); + // while ((match = pattern.exec(target)) !== null) { + // const indices = match.indices![0]; + // res.push(target.substring(lastEnd, indices[0])); + // if (replacement instanceof Function) { + // res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target)); + // } + // else { + // res.push(replacement); + // } + // lastEnd = indices[1]; + // if (!pattern.global) break; + // } + // if (lastEnd < target.length) { + // res.push(target.substring(lastEnd)); + // } + // return res.join(''); + // }, + // [Symbol.search](target, reverse, start) { + // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; + // if (!reverse) { + // pattern.lastIndex = (start as any) | 0; + // const res = pattern.exec(target); + // if (res) return res.index; + // else return -1; + // } + // else { + // start ??= target.length; + // start |= 0; + // let res: RegExpResult | null = null; + // while (true) { + // const tmp = pattern.exec(target); + // if (tmp === null || tmp.index > start) break; + // res = tmp; + // } + // if (res && res.index <= start) return res.index; + // else return -1; + // } + // }, + @Native public RegExpPolyfill(Context ctx, Object pattern, Object flags) throws InterruptedException { + this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags)); + } + public RegExpPolyfill(String pattern, String flags) { + if (pattern == null || pattern.equals("")) pattern = "(?:)"; + if (flags == null || flags.equals("")) flags = ""; + + this.flags = 0; + this.hasIndices = flags.contains("d"); + this.global = flags.contains("g"); + this.sticky = flags.contains("y"); + this.source = pattern; + + if (flags.contains("i")) this.flags |= Pattern.CASE_INSENSITIVE; + if (flags.contains("m")) this.flags |= Pattern.MULTILINE; + if (flags.contains("s")) this.flags |= Pattern.DOTALL; + if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS; + + this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags); + + var matcher = NAMED_PATTERN.matcher(source); + var groups = new ArrayList(); + + while (matcher.find()) { + if (!checkEscaped(source, matcher.start() - 1)) { + groups.add(matcher.group(1)); + } + } + + namedGroups = groups.toArray(String[]::new); + } + + public RegExpPolyfill(String pattern) { this(pattern, null); } + public RegExpPolyfill() { this(null, null); } +} diff --git a/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java index 7822f2a..9fd9bbd 100644 --- a/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java @@ -13,7 +13,6 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeGetter; public class SetPolyfill { - @Native("@@Symbol.typeName") public final String name = "Set"; private LinkedHashSet set = new LinkedHashSet<>(); @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java index 41093f5..cab4021 100644 --- a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java @@ -14,8 +14,6 @@ import me.topchetoeu.jscript.interop.NativeGetter; // TODO: implement index wrapping properly public class StringPolyfill { - @Native("@@Symbol.typeName") public final String name = "String"; - public final String value; private static String passThis(Context ctx, String funcName, Object val) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java index 149b2be..899702c 100644 --- a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java @@ -14,7 +14,6 @@ import me.topchetoeu.jscript.interop.NativeGetter; public class SymbolPolyfill { private static final Map symbols = new HashMap<>(); - @Native("@@Symbol.typeName") public final String name = "Symbol"; @NativeGetter public static Symbol typeName(Context ctx) { return ctx.env.symbol("Symbol.typeName"); } @NativeGetter public static Symbol replace(Context ctx) { return ctx.env.symbol("Symbol.replace"); } -- 2.45.2 From 9c65bacbac62c448fb04e57129a7f00ddf8ab253 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:38:51 +0300 Subject: [PATCH 18/23] fix: type name can f itself --- lib/core.ts | 7 +---- .../topchetoeu/jscript/interop/InitType.java | 7 +++++ .../jscript/interop/NativeInit.java | 12 ++++++++ .../interop/NativeWrapperProvider.java | 30 +++++++++++++++---- .../jscript/interop/OverloadFunction.java | 5 ++++ .../jscript/polyfills/ArrayPolyfill.java | 7 +++++ .../polyfills/AsyncFunctionPolyfill.java | 2 -- .../jscript/polyfills/BooleanPolyfill.java | 6 ++++ .../jscript/polyfills/ErrorPolyfill.java | 30 +++++-------------- .../jscript/polyfills/FunctionPolyfill.java | 8 +++++ .../jscript/polyfills/GeneratorPolyfill.java | 1 + .../jscript/polyfills/Internals.java | 3 -- .../jscript/polyfills/MapPolyfill.java | 1 + .../jscript/polyfills/NumberPolyfill.java | 7 +++++ .../jscript/polyfills/ObjectPolyfill.java | 7 +++++ .../jscript/polyfills/PromisePolyfill.java | 7 +++++ .../jscript/polyfills/RangeErrorPolyfill.java | 20 +++++++++++++ .../jscript/polyfills/RegExpPolyfill.java | 1 + .../jscript/polyfills/SetPolyfill.java | 1 + .../jscript/polyfills/StringPolyfill.java | 7 +++++ .../jscript/polyfills/SymbolPolyfill.java | 7 +++++ .../polyfills/SyntaxErrorPolyfill.java | 20 +++++++++++++ .../jscript/polyfills/TypeErrorPolyfill.java | 20 +++++++++++++ 23 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 src/me/topchetoeu/jscript/interop/InitType.java create mode 100644 src/me/topchetoeu/jscript/interop/NativeInit.java create mode 100644 src/me/topchetoeu/jscript/polyfills/RangeErrorPolyfill.java create mode 100644 src/me/topchetoeu/jscript/polyfills/SyntaxErrorPolyfill.java create mode 100644 src/me/topchetoeu/jscript/polyfills/TypeErrorPolyfill.java diff --git a/lib/core.ts b/lib/core.ts index fa09628..d1795dd 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -54,7 +54,6 @@ interface Internals { var env: Environment = arguments[0], internals: Internals = arguments[1]; try { - const values = { Object: env.global.Object = internals.object, Function: env.global.Function = internals.function, @@ -86,16 +85,12 @@ try { env.setProto('rangeErr', env.global.RangeError.prototype); env.setProto('typeErr', env.global.TypeError.prototype); env.setProto('syntaxErr', env.global.SyntaxError.prototype); + (env.global.Object.prototype as any).__proto__ = null; internals.getEnv(run)?.setProto('array', Array.prototype); globalThis.log = (...args) => internals.apply(internals.log, internals, args); - for (const key in values) { - (values as any)[key].prototype[env.symbol('Symbol.typeName')] = key; - log(); - } - run('timeout'); env.global.log = log; diff --git a/src/me/topchetoeu/jscript/interop/InitType.java b/src/me/topchetoeu/jscript/interop/InitType.java new file mode 100644 index 0000000..89793f8 --- /dev/null +++ b/src/me/topchetoeu/jscript/interop/InitType.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.interop; + +public enum InitType { + CONSTRUCTOR, + PROTOTYPE, + NAMESPACE, +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/NativeInit.java b/src/me/topchetoeu/jscript/interop/NativeInit.java new file mode 100644 index 0000000..66d13fe --- /dev/null +++ b/src/me/topchetoeu/jscript/interop/NativeInit.java @@ -0,0 +1,12 @@ +package me.topchetoeu.jscript.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeInit { + InitType value(); +} diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 0098016..7affa94 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -33,7 +33,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString())); - ((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.thisArg())); + ((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg())); } else { if (get != null) { @@ -50,7 +50,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter; else getter = new OverloadFunction("get " + name); - getter.overloads.add(Overload.fromMethod(method, get.thisArg())); + getter.add(Overload.fromMethod(method, get.thisArg())); target.defineProperty(null, name, getter, setter, true, true); } if (set != null) { @@ -67,7 +67,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter; else setter = new OverloadFunction("set " + name); - setter.overloads.add(Overload.fromMethod(method, set.thisArg())); + setter.add(Overload.fromMethod(method, set.thisArg())); target.defineProperty(null, name, getter, setter, true, true); } } @@ -83,8 +83,8 @@ public class NativeWrapperProvider implements WrappersProvider { if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); else if (name.equals("")) name = field.getName(); - var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field)); - var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field)); + var getter = OverloadFunction.of("get " + name, Overload.getterFromField(field)); + var setter = OverloadFunction.of("set " + name, Overload.setterFromField(field)); target.defineProperty(null, name, getter, setter, true, false); } } @@ -115,6 +115,13 @@ public class NativeWrapperProvider implements WrappersProvider { public static ObjectValue makeProto(Environment ctx, Class clazz) { var res = new ObjectValue(); + for (var overload : clazz.getDeclaredMethods()) { + var init = overload.getAnnotation(NativeInit.class); + if (init == null || init.value() != InitType.PROTOTYPE) continue; + try { overload.invoke(null, ctx, res); } + catch (ReflectiveOperationException e) { e.printStackTrace(); } + } + applyMethods(ctx, true, res, clazz); applyFields(ctx, true, res, clazz); applyClasses(ctx, true, res, clazz); @@ -140,6 +147,12 @@ public class NativeWrapperProvider implements WrappersProvider { if (constr == null) continue; ((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg())); } + for (var overload : clazz.getDeclaredMethods()) { + var init = overload.getAnnotation(NativeInit.class); + if (init == null || init.value() != InitType.CONSTRUCTOR) continue; + try { overload.invoke(null, ctx, func); } + catch (ReflectiveOperationException e) { e.printStackTrace(); } + } if (((OverloadFunction)func).overloads.size() == 0) { func = new NativeFunction(clazz.getName(), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); @@ -162,6 +175,13 @@ public class NativeWrapperProvider implements WrappersProvider { public static ObjectValue makeNamespace(Environment ctx, Class clazz) { ObjectValue res = new ObjectValue(); + for (var overload : clazz.getDeclaredMethods()) { + var init = overload.getAnnotation(NativeInit.class); + if (init == null || init.value() != InitType.NAMESPACE) continue; + try { overload.invoke(null, ctx, res); } + catch (ReflectiveOperationException e) { e.printStackTrace(); } + } + applyMethods(ctx, false, res, clazz); applyFields(ctx, false, res, clazz); applyClasses(ctx, false, res, clazz); diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index bf0f20c..5f54ceb 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -113,4 +113,9 @@ public class OverloadFunction extends FunctionValue { public OverloadFunction(String name) { super(name, 0); } + + public static OverloadFunction of(String name, Overload overload) { + if (overload == null) return null; + else return new OverloadFunction(name).add(overload); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java index a2901a7..7f9edde 100644 --- a/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ArrayPolyfill.java @@ -4,13 +4,16 @@ import java.util.Iterator; import java.util.Stack; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; 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.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeSetter; public class ArrayPolyfill { @@ -347,4 +350,8 @@ public class ArrayPolyfill { return res; } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Array"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java index 951c6c5..b568cf6 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunctionPolyfill.java @@ -7,13 +7,11 @@ import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.Native; public class AsyncFunctionPolyfill extends FunctionValue { public final FunctionValue factory; public static class AsyncHelper { - @Native("@@Symbol.typeName") public final String name = "AsyncFunction"; public PromisePolyfill promise = new PromisePolyfill(); public CodeFrame frame; diff --git a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java index 843e78b..026dad2 100644 --- a/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/BooleanPolyfill.java @@ -1,10 +1,13 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeInit; public class BooleanPolyfill { public static final BooleanPolyfill TRUE = new BooleanPolyfill(true); @@ -27,4 +30,7 @@ public class BooleanPolyfill { public BooleanPolyfill(boolean val) { this.value = val; } + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Boolean"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java index 2e694a8..a466de9 100644 --- a/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ErrorPolyfill.java @@ -1,35 +1,16 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeInit; public class ErrorPolyfill { - public static class SyntaxErrorPolyfill extends ErrorPolyfill { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { - var target = ErrorPolyfill.constructor(ctx, thisArg, message); - target.defineProperty(ctx, "name", "SyntaxError"); - return target; - } - } - public static class TypeErrorPolyfill extends ErrorPolyfill { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { - var target = ErrorPolyfill.constructor(ctx, thisArg, message); - target.defineProperty(ctx, "name", "TypeError"); - return target; - } - } - public static class RangeErrorPolyfill extends ErrorPolyfill { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { - var target = ErrorPolyfill.constructor(ctx, thisArg, message); - target.defineProperty(ctx, "name", "RangeError"); - return target; - } - } - private static String toString(Context ctx, Object cause, Object name, Object message, ArrayValue stack) throws InterruptedException { if (name == null) name = ""; else name = Values.toString(ctx, name).trim(); @@ -78,4 +59,9 @@ public class ErrorPolyfill { return target; } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Error"); + target.defineProperty(null, "name", "Error"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index e46e674..9d5a625 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -1,11 +1,15 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; 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.exceptions.EngineException; +import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeInit; public class FunctionPolyfill { @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { @@ -40,4 +44,8 @@ public class FunctionPolyfill { @Native public static FunctionValue generator(FunctionValue func) { return new GeneratorPolyfill(func); } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Function"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index db176ed..cf4b817 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 643ab8f..dded47a 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -10,9 +10,6 @@ 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.interop.Native; -import me.topchetoeu.jscript.polyfills.ErrorPolyfill.RangeErrorPolyfill; -import me.topchetoeu.jscript.polyfills.ErrorPolyfill.SyntaxErrorPolyfill; -import me.topchetoeu.jscript.polyfills.ErrorPolyfill.TypeErrorPolyfill; public class Internals { public final Environment targetEnv; diff --git a/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java b/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java index f747164..1791c81 100644 --- a/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/MapPolyfill.java @@ -15,6 +15,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; public class MapPolyfill { private LinkedHashMap map = new LinkedHashMap<>(); + @Native("@@Symbol.typeName") public final String name = "Map"; @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { return this.entries(ctx); } diff --git a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java index a3859eb..a9e30c6 100644 --- a/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/NumberPolyfill.java @@ -1,10 +1,13 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeInit; public class NumberPolyfill { @Native public static final double EPSILON = java.lang.Math.ulp(1.0); @@ -49,4 +52,8 @@ public class NumberPolyfill { public NumberPolyfill(double val) { this.value = val; } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Number"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java index 51c12e9..1dca001 100644 --- a/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java @@ -1,14 +1,17 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; 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.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeInit; public class ObjectPolyfill { @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException { @@ -209,4 +212,8 @@ public class ObjectPolyfill { // else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg); else return arg; } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Object"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index 741f052..574a894 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.MessageContext; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; @@ -13,7 +14,9 @@ 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.InitType; import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeInit; public class PromisePolyfill { private static class Handle { @@ -338,4 +341,8 @@ public class PromisePolyfill { public PromisePolyfill() { this(STATE_PENDING, null); } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Promise"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/RangeErrorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/RangeErrorPolyfill.java new file mode 100644 index 0000000..99bfa82 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/RangeErrorPolyfill.java @@ -0,0 +1,20 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.interop.InitType; +import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeInit; + +public class RangeErrorPolyfill extends ErrorPolyfill { + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + var target = ErrorPolyfill.constructor(ctx, thisArg, message); + target.defineProperty(ctx, "name", "RangeError"); + return target; + } + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "RangeError"); + target.defineProperty(null, "name", "RangeError"); + } +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java b/src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java index 373bff1..1fb885c 100644 --- a/src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/RegExpPolyfill.java @@ -62,6 +62,7 @@ public class RegExpPolyfill { @Native public final boolean hasIndices; @Native public final boolean global; @Native public final boolean sticky; + @Native("@@Symbol.typeName") public final String name = "RegExp"; @NativeGetter public boolean ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; } @NativeGetter public boolean multiline() { return (flags & Pattern.MULTILINE) != 0; } diff --git a/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java index 9fd9bbd..c6bc7f3 100644 --- a/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/SetPolyfill.java @@ -15,6 +15,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; public class SetPolyfill { private LinkedHashSet set = new LinkedHashSet<>(); + @Native("@@Symbol.typeName") public final String name = "Set"; @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { return this.values(ctx); } diff --git a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java index cab4021..de56291 100644 --- a/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/StringPolyfill.java @@ -3,14 +3,17 @@ package me.topchetoeu.jscript.polyfills; import java.util.regex.Pattern; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; 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.exceptions.EngineException; +import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeInit; // TODO: implement index wrapping properly public class StringPolyfill { @@ -250,4 +253,8 @@ public class StringPolyfill { public StringPolyfill(String val) { this.value = val; } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "String"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java index 899702c..6dfb33e 100644 --- a/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/SymbolPolyfill.java @@ -4,13 +4,16 @@ import java.util.HashMap; import java.util.Map; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; 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.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeInit; public class SymbolPolyfill { private static final Map symbols = new HashMap<>(); @@ -59,4 +62,8 @@ public class SymbolPolyfill { public SymbolPolyfill(Symbol val) { this.value = val; } + + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "Symbol"); + } } diff --git a/src/me/topchetoeu/jscript/polyfills/SyntaxErrorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/SyntaxErrorPolyfill.java new file mode 100644 index 0000000..27c6ed1 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/SyntaxErrorPolyfill.java @@ -0,0 +1,20 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.interop.InitType; +import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeInit; + +public class SyntaxErrorPolyfill extends ErrorPolyfill { + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + var target = ErrorPolyfill.constructor(ctx, thisArg, message); + target.defineProperty(ctx, "name", "SyntaxError"); + return target; + } + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "SyntaxError"); + target.defineProperty(null, "name", "SyntaxError"); + } +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/polyfills/TypeErrorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/TypeErrorPolyfill.java new file mode 100644 index 0000000..5814f69 --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/TypeErrorPolyfill.java @@ -0,0 +1,20 @@ +package me.topchetoeu.jscript.polyfills; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.interop.InitType; +import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.NativeInit; + +public class TypeErrorPolyfill extends ErrorPolyfill { + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + var target = ErrorPolyfill.constructor(ctx, thisArg, message); + target.defineProperty(ctx, "name", "TypeError"); + return target; + } + @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { + target.defineProperty(null, env.symbol("Symbol.typeName"), "TypeError"); + target.defineProperty(null, "name", "TypeError"); + } +} \ No newline at end of file -- 2.45.2 From 63b04019cf50a936b79c4f095be6b9ca492a71dc Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:25:32 +0300 Subject: [PATCH 19/23] feat: implement timers in java --- lib/core.ts | 56 +++---- lib/modules.ts | 13 -- lib/regex.ts | 143 ------------------ lib/timeout.ts | 38 ----- lib/tsconfig.json | 3 - .../jscript/polyfills/FunctionPolyfill.java | 11 +- .../jscript/polyfills/GeneratorPolyfill.java | 1 - .../jscript/polyfills/Internals.java | 5 + .../jscript/polyfills/TimerPolyfills.java | 60 ++++++++ 9 files changed, 102 insertions(+), 228 deletions(-) delete mode 100644 lib/modules.ts delete mode 100644 lib/regex.ts delete mode 100644 lib/timeout.ts create mode 100644 src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java diff --git a/lib/core.ts b/lib/core.ts index d1795dd..da94bb6 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -17,15 +17,23 @@ interface Internals { syntax: SyntaxErrorConstructor; type: TypeErrorConstructor; range: RangeErrorConstructor; - + regexp: typeof RegExp; map: typeof Map; set: typeof Set; + timers: { + setTimeout: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number, + setInterval: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number, + clearTimeout: (id: number) => void, + clearInterval: (id: number) => void, + } + markSpecial(...funcs: Function[]): void; getEnv(func: Function): Environment | undefined; setEnv(func: T, env: Environment): T; apply(func: Function, thisArg: any, args: any[], env?: Environment): any; + bind(func: Function, thisArg: any): any; delay(timeout: number, callback: Function): () => void; pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void; @@ -54,24 +62,26 @@ interface Internals { var env: Environment = arguments[0], internals: Internals = arguments[1]; try { - const values = { - Object: env.global.Object = internals.object, - Function: env.global.Function = internals.function, - Array: env.global.Array = internals.array, - Promise: env.global.Promise = internals.promise, - Boolean: env.global.Boolean = internals.bool, - Number: env.global.Number = internals.number, - String: env.global.String = internals.string, - Symbol: env.global.Symbol = internals.symbol, - Error: env.global.Error = internals.error, - SyntaxError: env.global.SyntaxError = internals.syntax, - TypeError: env.global.TypeError = internals.type, - RangeError: env.global.RangeError = internals.range, - RegExp: env.global.RegExp = internals.regexp, - Map: env.global.Map = internals.map, - Set: env.global.Set = internals.set, - } - const Array = values.Array; + const Array = env.global.Array = internals.array; + env.global.Object = internals.object; + env.global.Function = internals.function; + env.global.Promise = internals.promise; + env.global.Boolean = internals.bool; + env.global.Number = internals.number; + env.global.String = internals.string; + env.global.Symbol = internals.symbol; + env.global.Error = internals.error; + env.global.SyntaxError = internals.syntax; + env.global.TypeError = internals.type; + env.global.RangeError = internals.range; + env.global.RegExp = internals.regexp; + env.global.Map = internals.map; + env.global.Set = internals.set; + env.global.setInterval = internals.bind(internals.timers.setInterval, internals.timers); + env.global.setTimeout = internals.bind(internals.timers.setTimeout, internals.timers); + env.global.clearInterval = internals.bind(internals.timers.clearInterval, internals.timers); + env.global.clearTimeout = internals.bind(internals.timers.clearTimeout, internals.timers); + const log = env.global.log = internals.bind(internals.log, internals); env.setProto('object', env.global.Object.prototype); env.setProto('function', env.global.Function.prototype); @@ -88,14 +98,6 @@ try { (env.global.Object.prototype as any).__proto__ = null; - internals.getEnv(run)?.setProto('array', Array.prototype); - globalThis.log = (...args) => internals.apply(internals.log, internals, args); - - run('timeout'); - - env.global.log = log; - env.global.NewObject = internals.object; - log('Loaded polyfills!'); } catch (e: any) { diff --git a/lib/modules.ts b/lib/modules.ts deleted file mode 100644 index c15e496..0000000 --- a/lib/modules.ts +++ /dev/null @@ -1,13 +0,0 @@ -var { define, run } = (() => { - const modules: Record = {}; - - function define(name: string, func: Function) { - modules[name] = func; - } - function run(name: string) { - if (typeof modules[name] === 'function') return modules[name](); - else throw "The module '" + name + "' doesn't exist."; - } - - return { define, run }; -})(); diff --git a/lib/regex.ts b/lib/regex.ts deleted file mode 100644 index ed5ce84..0000000 --- a/lib/regex.ts +++ /dev/null @@ -1,143 +0,0 @@ -define("regex", () => { - // var RegExp = env.global.RegExp = env.internals.RegExp; - - // setProps(RegExp.prototype as RegExp, env, { - // [Symbol.typeName]: 'RegExp', - - // test(val) { - // return !!this.exec(val); - // }, - // toString() { - // return '/' + this.source + '/' + this.flags; - // }, - - // [Symbol.match](target) { - // if (this.global) { - // const res: string[] = []; - // let val; - // while (val = this.exec(target)) { - // res.push(val[0]); - // } - // this.lastIndex = 0; - // return res; - // } - // else { - // const res = this.exec(target); - // if (!this.sticky) this.lastIndex = 0; - // return res; - // } - // }, - // [Symbol.matchAll](target) { - // let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; - - // return { - // next: (): IteratorResult => { - // const val = pattern?.exec(target); - - // if (val === null || val === undefined) { - // pattern = undefined; - // return { done: true }; - // } - // else return { value: val }; - // }, - // [Symbol.iterator]() { return this; } - // } - // }, - // [Symbol.split](target, limit, sensible) { - // const pattern = new this.constructor(this, this.flags + "g") as RegExp; - // let match: RegExpResult | null; - // let lastEnd = 0; - // const res: string[] = []; - - // while ((match = pattern.exec(target)) !== null) { - // let added: string[] = []; - - // if (match.index >= target.length) break; - - // if (match[0].length === 0) { - // added = [ target.substring(lastEnd, pattern.lastIndex), ]; - // if (pattern.lastIndex < target.length) added.push(...match.slice(1)); - // } - // else if (match.index - lastEnd > 0) { - // added = [ target.substring(lastEnd, match.index), ...match.slice(1) ]; - // } - // else { - // for (let i = 1; i < match.length; i++) { - // res[res.length - match.length + i] = match[i]; - // } - // } - - // if (sensible) { - // if (limit !== undefined && res.length + added.length >= limit) break; - // else res.push(...added); - // } - // else { - // for (let i = 0; i < added.length; i++) { - // if (limit !== undefined && res.length >= limit) return res; - // else res.push(added[i]); - // } - // } - - // lastEnd = pattern.lastIndex; - // } - - // if (lastEnd < target.length) { - // res.push(target.substring(lastEnd)); - // } - - // return res; - // }, - // [Symbol.replace](target, replacement) { - // const pattern = new this.constructor(this, this.flags + "d") as RegExp; - // let match: RegExpResult | null; - // let lastEnd = 0; - // const res: string[] = []; - - // // log(pattern.toString()); - - // while ((match = pattern.exec(target)) !== null) { - // const indices = match.indices![0]; - // res.push(target.substring(lastEnd, indices[0])); - // if (replacement instanceof Function) { - // res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target)); - // } - // else { - // res.push(replacement); - // } - // lastEnd = indices[1]; - // if (!pattern.global) break; - // } - - // if (lastEnd < target.length) { - // res.push(target.substring(lastEnd)); - // } - - // return res.join(''); - // }, - // [Symbol.search](target, reverse, start) { - // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; - - - // if (!reverse) { - // pattern.lastIndex = (start as any) | 0; - // const res = pattern.exec(target); - // if (res) return res.index; - // else return -1; - // } - // else { - // start ??= target.length; - // start |= 0; - // let res: RegExpResult | null = null; - - // while (true) { - // const tmp = pattern.exec(target); - // if (tmp === null || tmp.index > start) break; - // res = tmp; - // } - - // if (res && res.index <= start) return res.index; - // else return -1; - // } - // }, - // }); -}); \ No newline at end of file diff --git a/lib/timeout.ts b/lib/timeout.ts deleted file mode 100644 index e1f167a..0000000 --- a/lib/timeout.ts +++ /dev/null @@ -1,38 +0,0 @@ -define("timeout", () => { - const timeouts: Record void> = { }; - const intervals: Record void> = { }; - let timeoutI = 0, intervalI = 0; - - env.global.setTimeout = (func, delay, ...args) => { - if (typeof func !== 'function') throw new env.global.TypeError("func must be a function."); - delay = (delay ?? 0) - 0; - const cancelFunc = internals.delay(delay, () => internals.apply(func, undefined, args)); - timeouts[++timeoutI] = cancelFunc; - return timeoutI; - }; - env.global.setInterval = (func, delay, ...args) => { - if (typeof func !== 'function') throw new env.global.TypeError("func must be a function."); - delay = (delay ?? 0) - 0; - - const i = ++intervalI; - intervals[i] = internals.delay(delay, callback); - - return i; - - function callback() { - internals.apply(func, undefined, args); - intervals[i] = internals.delay(delay!, callback); - } - }; - - env.global.clearTimeout = (id) => { - const func = timeouts[id]; - if (func) func(); - timeouts[id] = undefined!; - }; - env.global.clearInterval = (id) => { - const func = intervals[id]; - if (func) func(); - intervals[id] = undefined!; - }; -}); \ No newline at end of file diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 326c2bd..64939d9 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -1,9 +1,6 @@ { "files": [ "lib.d.ts", - "modules.ts", - "regex.ts", - "timeout.ts", "core.ts" ], "compilerOptions": { diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index 9d5a625..2294773 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -24,9 +24,14 @@ public class FunctionPolyfill { if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { - var resArgs = new Object[args.length + callArgs.length]; - System.arraycopy(args, 0, resArgs, 0, args.length); - System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length); + Object[] resArgs; + + if (args.length == 0) resArgs = callArgs; + else { + resArgs = new Object[args.length + callArgs.length]; + System.arraycopy(args, 0, resArgs, 0, args.length); + System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length); + } return func.call(ctx, thisArg, resArgs); }); diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java index cf4b817..db176ed 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorPolyfill.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index dded47a..3e1827f 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -20,6 +20,8 @@ public class Internals { promise, map, set, regexp, error, syntax, type, range; + @Native public final TimerPolyfills timers = new TimerPolyfills(); + @Native public void markSpecial(FunctionValue ...funcs) { for (var func : funcs) { func.special = true; @@ -37,6 +39,9 @@ public class Internals { if (env != null) ctx = new Context(env, ctx.message); return func.call(ctx, thisArg, args.toArray()); } + @Native public FunctionValue bind(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { + return FunctionPolyfill.bind(ctx, func, thisArg); + } @Native public FunctionValue delay(Context ctx, double delay, FunctionValue callback) throws InterruptedException { var thread = new Thread((Runnable)() -> { var ms = (long)delay; diff --git a/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java b/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java new file mode 100644 index 0000000..a0dd02e --- /dev/null +++ b/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java @@ -0,0 +1,60 @@ +package me.topchetoeu.jscript.polyfills; + +import java.util.HashMap; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.interop.Native; + +public class TimerPolyfills { + private HashMap threads = new HashMap<>(); + + private int i = 0; + + @Native public 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.message.engine.pushMsg(false, ctx.message, func, null, args); + }); + thread.start(); + + threads.put(++i, thread); + + return i; + } + @Native public 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.message.engine.pushMsg(false, ctx.message, func, null, args); + } + }); + thread.start(); + + threads.put(++i, thread); + + return i; + } + + @Native public void clearTimeout(Context ctx, int i) { + var thread = threads.remove(i); + if (thread != null) thread.interrupt(); + } + @Native public void clearInterval(Context ctx, int i) { + clearTimeout(ctx, i); + } +} -- 2.45.2 From 68f3b6d926f5204efbcc75ccf7de29b70fe50a8f Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 4 Oct 2023 08:08:54 +0300 Subject: [PATCH 20/23] feat: fully remove typescript init code --- lib/core.ts | 114 ---------- src/me/topchetoeu/jscript/Main.java | 52 +++-- src/me/topchetoeu/jscript/engine/Context.java | 15 +- src/me/topchetoeu/jscript/engine/Engine.java | 6 +- .../jscript/engine/Environment.java | 4 + .../{MessageContext.java => Message.java} | 10 +- .../jscript/engine/values/ObjectValue.java | 2 +- .../jscript/polyfills/FunctionPolyfill.java | 4 +- .../jscript/polyfills/Internals.java | 214 ++++++------------ src/me/topchetoeu/jscript/polyfills/Math.java | 70 +++--- .../jscript/polyfills/PromisePolyfill.java | 6 +- .../jscript/polyfills/TimerPolyfills.java | 60 ----- 12 files changed, 160 insertions(+), 397 deletions(-) delete mode 100644 lib/core.ts rename src/me/topchetoeu/jscript/engine/{MessageContext.java => Message.java} (85%) delete mode 100644 src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java diff --git a/lib/core.ts b/lib/core.ts deleted file mode 100644 index da94bb6..0000000 --- a/lib/core.ts +++ /dev/null @@ -1,114 +0,0 @@ -interface Environment { - global: typeof globalThis & Record; - proto(name: string): object; - setProto(name: string, val: object): void; - symbol(name: string): symbol; -} -interface Internals { - object: ObjectConstructor; - function: FunctionConstructor; - array: ArrayConstructor; - promise: PromiseConstructor; - bool: BooleanConstructor; - number: NumberConstructor; - string: StringConstructor; - symbol: SymbolConstructor; - error: ErrorConstructor; - syntax: SyntaxErrorConstructor; - type: TypeErrorConstructor; - range: RangeErrorConstructor; - - regexp: typeof RegExp; - map: typeof Map; - set: typeof Set; - - timers: { - setTimeout: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number, - setInterval: (handle: (...args: [ ...T, ...any[] ]) => void, delay?: number, ...args: T) => number, - clearTimeout: (id: number) => void, - clearInterval: (id: number) => void, - } - - markSpecial(...funcs: Function[]): void; - getEnv(func: Function): Environment | undefined; - setEnv(func: T, env: Environment): T; - apply(func: Function, thisArg: any, args: any[], env?: Environment): any; - bind(func: Function, thisArg: any): any; - delay(timeout: number, callback: Function): () => void; - pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void; - - strlen(val: string): number; - char(val: string): number; - stringFromStrings(arr: string[]): string; - stringFromChars(arr: number[]): string; - getSymbol(name?: string): symbol; - symbolToString(sym: symbol): string; - - isArray(obj: any): boolean; - generator(func: (_yield: (val: T) => unknown) => (...args: any[]) => unknown): GeneratorFunction; - defineField(obj: object, key: any, val: any, writable: boolean, enumerable: boolean, configurable: boolean): boolean; - defineProp(obj: object, key: any, get: Function | undefined, set: Function | undefined, enumerable: boolean, configurable: boolean): boolean; - keys(obj: object, onlyString: boolean): any[]; - ownProp(obj: any, key: string): PropertyDescriptor; - ownPropKeys(obj: any): any[]; - lock(obj: object, type: 'ext' | 'seal' | 'freeze'): void; - extensible(obj: object): boolean; - - sort(arr: any[], comaprator: (a: any, b: any) => number): void; - - log(...args: any[]): void; -} - -var env: Environment = arguments[0], internals: Internals = arguments[1]; - -try { - const Array = env.global.Array = internals.array; - env.global.Object = internals.object; - env.global.Function = internals.function; - env.global.Promise = internals.promise; - env.global.Boolean = internals.bool; - env.global.Number = internals.number; - env.global.String = internals.string; - env.global.Symbol = internals.symbol; - env.global.Error = internals.error; - env.global.SyntaxError = internals.syntax; - env.global.TypeError = internals.type; - env.global.RangeError = internals.range; - env.global.RegExp = internals.regexp; - env.global.Map = internals.map; - env.global.Set = internals.set; - env.global.setInterval = internals.bind(internals.timers.setInterval, internals.timers); - env.global.setTimeout = internals.bind(internals.timers.setTimeout, internals.timers); - env.global.clearInterval = internals.bind(internals.timers.clearInterval, internals.timers); - env.global.clearTimeout = internals.bind(internals.timers.clearTimeout, internals.timers); - const log = env.global.log = internals.bind(internals.log, internals); - - env.setProto('object', env.global.Object.prototype); - env.setProto('function', env.global.Function.prototype); - env.setProto('array', env.global.Array.prototype); - env.setProto('number', env.global.Number.prototype); - env.setProto('string', env.global.String.prototype); - env.setProto('symbol', env.global.Symbol.prototype); - env.setProto('bool', env.global.Boolean.prototype); - - env.setProto('error', env.global.Error.prototype); - env.setProto('rangeErr', env.global.RangeError.prototype); - env.setProto('typeErr', env.global.TypeError.prototype); - env.setProto('syntaxErr', env.global.SyntaxError.prototype); - - (env.global.Object.prototype as any).__proto__ = null; - - log('Loaded polyfills!'); -} -catch (e: any) { - let err = 'Uncaught error while loading polyfills: '; - - if (typeof Error !== 'undefined' && e instanceof Error && e.toString !== {}.toString) err += e; - else if ('message' in e) { - if ('name' in e) err += e.name + ": " + e.message; - else err += 'Error: ' + e.message; - } - else err += "[unknown]"; - - internals.log(err); -} diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 309aeed..4180c06 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -7,10 +7,11 @@ import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; -import me.topchetoeu.jscript.engine.MessageContext; +import me.topchetoeu.jscript.engine.Message; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; @@ -63,30 +64,35 @@ public class Main { engine = new Engine(); env = new Environment(null, null, null); - var builderEnv = new Environment(null, null, null); var exited = new boolean[1]; - env.global.define("exit", ctx -> { - exited[0] = true; - task.interrupt(); - throw new InterruptedException(); - }); - env.global.define("go", ctx -> { - try { - var func = ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js")))); - return func.call(ctx); - } - catch (IOException e) { - throw new EngineException("Couldn't open do.js"); - } - }); + engine.pushMsg(false, new Message(engine), new NativeFunction((ctx, thisArg, _a) -> { + new Internals().apply(env); - engine.pushMsg( - false, - new Context(builderEnv, new MessageContext(engine)), - "core.js", resourceToString("js/core.js"), - null, env, new Internals(env) - ).toObservable().on(valuePrinter); + env.global.define("exit", _ctx -> { + exited[0] = true; + task.interrupt(); + throw new InterruptedException(); + }); + env.global.define("go", _ctx -> { + try { + var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js")))); + return func.call(_ctx); + } + catch (IOException e) { + throw new EngineException("Couldn't open do.js"); + } + }); + + return null; + }), null); + + // engine.pushMsg( + // false, + // new Context(builderEnv, new MessageContext(engine)), + // "core.js", resourceToString("js/core.js"), + // null, env, new Internals(env) + // ).toObservable().on(valuePrinter); task = engine.start(); var reader = new Thread(() -> { @@ -96,7 +102,7 @@ public class Main { var raw = in.readLine(); if (raw == null) break; - engine.pushMsg(false, new Context(env, new MessageContext(engine)), "", raw, null).toObservable().once(valuePrinter); + engine.pushMsg(false, new Context(env, new Message(engine)), "", raw, null).toObservable().once(valuePrinter); } catch (EngineException e) { try { diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 801cbc7..882e7ce 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -6,15 +6,22 @@ import me.topchetoeu.jscript.parsing.Parsing; public class Context { public final Environment env; - public final MessageContext message; + public final Message message; public FunctionValue compile(String filename, String raw) throws InterruptedException { var res = Values.toString(this, env.compile.call(this, null, raw, filename)); return Parsing.compile(env, filename, res); } - public Context(Environment funcCtx, MessageContext msgCtx) { - this.env = funcCtx; - this.message = msgCtx; + public Context setEnv(Environment env) { + return new Context(env, message); + } + public Context setMsg(Message msg) { + return new Context(env, msg); + } + + public Context(Environment env, Message msg) { + this.env = env; + this.message = msg; } } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 7b87e73..aa83065 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -32,9 +32,9 @@ public class Engine { public final Object thisArg; public final Object[] args; public final DataNotifier notifier = new DataNotifier<>(); - public final MessageContext ctx; + public final Message ctx; - public Task(MessageContext ctx, FunctionValue func, Object thisArg, Object[] args) { + public Task(Message ctx, FunctionValue func, Object thisArg, Object[] args) { this.ctx = ctx; this.func = func; this.thisArg = thisArg; @@ -102,7 +102,7 @@ public class Engine { return this.thread != null; } - public Awaitable pushMsg(boolean micro, MessageContext ctx, FunctionValue func, Object thisArg, Object ...args) { + public Awaitable pushMsg(boolean micro, Message ctx, FunctionValue func, Object thisArg, Object ...args) { var msg = new Task(ctx, func, thisArg, args); if (micro) microTasks.addLast(msg); else macroTasks.addLast(msg); diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index e3e309b..5821c04 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -60,6 +60,10 @@ public class Environment { return res; } + public Context context(Message msg) { + return new Context(this, msg); + } + 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); diff --git a/src/me/topchetoeu/jscript/engine/MessageContext.java b/src/me/topchetoeu/jscript/engine/Message.java similarity index 85% rename from src/me/topchetoeu/jscript/engine/MessageContext.java rename to src/me/topchetoeu/jscript/engine/Message.java index febc7f0..974893f 100644 --- a/src/me/topchetoeu/jscript/engine/MessageContext.java +++ b/src/me/topchetoeu/jscript/engine/Message.java @@ -7,7 +7,7 @@ import java.util.List; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.exceptions.EngineException; -public class MessageContext { +public class Message { public final Engine engine; private final ArrayList frames = new ArrayList<>(); @@ -15,7 +15,7 @@ public class MessageContext { public List frames() { return Collections.unmodifiableList(frames); } - public MessageContext pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { + public Message pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { this.frames.add(frame); if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!"); return this; @@ -46,7 +46,11 @@ public class MessageContext { return res; } - public MessageContext(Engine engine) { + public Context context(Environment env) { + return new Context(env, this); + } + + public Message(Engine engine) { this.engine = engine; } } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index eae0dce..b7dfe2e 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -55,7 +55,7 @@ public class ObjectValue { public final boolean memberWritable(Object key) { if (state == State.FROZEN) return false; - return values.containsKey(key) && !nonWritableSet.contains(key); + return !values.containsKey(key) || !nonWritableSet.contains(key); } public final boolean memberConfigurable(Object key) { if (state == State.SEALED || state == State.FROZEN) return false; diff --git a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java index 2294773..6c5eb6a 100644 --- a/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java @@ -20,7 +20,7 @@ public class FunctionPolyfill { return func.call(ctx, thisArg, args); } - @Native(thisArg = true) public static FunctionValue bind(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { + @Native(thisArg = true) public static FunctionValue bind(FunctionValue func, Object thisArg, Object... args) { if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { @@ -33,7 +33,7 @@ public class FunctionPolyfill { System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length); } - return func.call(ctx, thisArg, resArgs); + return func.call(callCtx, thisArg, resArgs); }); } @Native(thisArg = true) public static String toString(Context ctx, Object func) { diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 3e1827f..24386bd 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -1,49 +1,29 @@ package me.topchetoeu.jscript.polyfills; +import java.util.HashMap; + import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; -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.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.interop.Native; public class Internals { - public final Environment targetEnv; + private HashMap threads = new HashMap<>(); + private int i = 0; - @Native public final FunctionValue - object, function, array, - bool, number, string, symbol, - promise, map, set, regexp, - error, syntax, type, range; - - @Native public final TimerPolyfills timers = new TimerPolyfills(); - - @Native public void markSpecial(FunctionValue ...funcs) { - for (var func : funcs) { - func.special = true; + @Native public FunctionValue bind(FunctionValue func, Object thisArg) throws InterruptedException { + return FunctionPolyfill.bind(func, thisArg); + } + @Native public void log(Context ctx, Object ...args) throws InterruptedException { + for (var arg : args) { + Values.printValue(ctx, arg); } + System.out.println(); } - @Native public Environment getEnv(Object func) { - if (func instanceof CodeFunction) return ((CodeFunction)func).environment; - else return null; - } - @Native public Object setEnv(Object func, Environment env) { - if (func instanceof CodeFunction) ((CodeFunction)func).environment = env; - return func; - } - @Native public Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args, Environment env) throws InterruptedException { - if (env != null) ctx = new Context(env, ctx.message); - return func.call(ctx, thisArg, args.toArray()); - } - @Native public FunctionValue bind(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { - return FunctionPolyfill.bind(ctx, func, thisArg); - } - @Native public FunctionValue delay(Context ctx, double delay, FunctionValue callback) throws InterruptedException { - var thread = new Thread((Runnable)() -> { + + @Native public int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { + var thread = new Thread(() -> { var ms = (long)delay; var ns = (int)((delay - ms) * 10000000); @@ -52,130 +32,66 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.message.engine.pushMsg(false, ctx.message, callback, null); + ctx.message.engine.pushMsg(false, ctx.message, func, null, args); }); thread.start(); - return new NativeFunction((_ctx, thisArg, args) -> { - thread.interrupt(); - return null; - }); - } - @Native public void pushMessage(Context ctx, boolean micro, FunctionValue func, Object thisArg, Object[] args) { - ctx.message.engine.pushMsg(micro, ctx.message, func, thisArg, args); - } + threads.put(++i, thread); - @Native public int strlen(String str) { - return str.length(); + return i; } - @Native("char") public int _char(String str) { - return str.charAt(0); - } - @Native public String stringFromChars(char[] str) { - return new String(str); - } - @Native public String stringFromStrings(String[] str) { - var res = new char[str.length]; + @Native public int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { + var thread = new Thread(() -> { + var ms = (long)delay; + var ns = (int)((delay - ms) * 10000000); - for (var i = 0; i < str.length; i++) res[i] = str[i].charAt(0); - - return stringFromChars(res); - } - @Native public Symbol getSymbol(String str) { - return new Symbol(str); - } - @Native public String symbolToString(Symbol str) { - return str.value; - } - - @Native public void log(Context ctx, Object ...args) throws InterruptedException { - for (var arg : args) { - Values.printValue(ctx, arg); - } - System.out.println(); - } - - @Native public boolean isArray(Object obj) { - return obj instanceof ArrayValue; - } - @Native public GeneratorPolyfill generator(FunctionValue obj) { - return new GeneratorPolyfill(obj); - } - - @Native public boolean defineField(Context ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) { - return obj.defineProperty(ctx, key, val, writable, configurable, enumerable); - } - @Native public boolean defineProp(Context ctx, ObjectValue obj, Object key, FunctionValue getter, FunctionValue setter, boolean enumerable, boolean configurable) { - return obj.defineProperty(ctx, key, getter, setter, configurable, enumerable); - } - - @Native public ArrayValue keys(Context ctx, Object obj, boolean onlyString) throws InterruptedException { - var res = new ArrayValue(); - - var i = 0; - var list = Values.getMembers(ctx, obj, true, false); - - for (var el : list) res.set(ctx, i++, el); - - return res; - } - @Native public ArrayValue ownPropKeys(Context ctx, Object obj, boolean symbols) throws InterruptedException { - var res = new ArrayValue(); - - if (Values.isObject(obj)) { - var i = 0; - var list = Values.object(obj).keys(true); - - for (var el : list) res.set(ctx, i++, el); - } - - return res; - } - @Native public ObjectValue ownProp(Context ctx, ObjectValue val, Object key) throws InterruptedException { - return val.getMemberDescriptor(ctx, key); - } - @Native public void lock(ObjectValue val, String type) { - switch (type) { - case "ext": val.preventExtensions(); break; - case "seal": val.seal(); break; - case "freeze": val.freeze(); break; - } - } - @Native public boolean extensible(ObjectValue val) { - return val.extensible(); - } - - @Native public void sort(Context ctx, ArrayValue arr, FunctionValue cmp) { - arr.sort((a, b) -> { - try { - var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); - if (res < 0) return -1; - if (res > 0) return 1; - return 0; - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return 0; + while (true) { + try { + Thread.sleep(ms, ns); + } + catch (InterruptedException e) { return; } + + ctx.message.engine.pushMsg(false, ctx.message, func, null, args); } }); + thread.start(); + + threads.put(++i, thread); + + return i; } - public Internals(Environment targetEnv) { - this.targetEnv = targetEnv; - this.object = targetEnv.wrappersProvider.getConstr(ObjectPolyfill.class); - this.function = targetEnv.wrappersProvider.getConstr(FunctionPolyfill.class); - this.promise = targetEnv.wrappersProvider.getConstr(PromisePolyfill.class); - this.array = targetEnv.wrappersProvider.getConstr(ArrayPolyfill.class); - this.bool = targetEnv.wrappersProvider.getConstr(BooleanPolyfill.class); - this.number = targetEnv.wrappersProvider.getConstr(NumberPolyfill.class); - this.string = targetEnv.wrappersProvider.getConstr(StringPolyfill.class); - this.symbol = targetEnv.wrappersProvider.getConstr(SymbolPolyfill.class); - this.map = targetEnv.wrappersProvider.getConstr(MapPolyfill.class); - this.set = targetEnv.wrappersProvider.getConstr(SetPolyfill.class); - this.error = targetEnv.wrappersProvider.getConstr(ErrorPolyfill.class); - this.syntax = targetEnv.wrappersProvider.getConstr(SyntaxErrorPolyfill.class); - this.type = targetEnv.wrappersProvider.getConstr(TypeErrorPolyfill.class); - this.range = targetEnv.wrappersProvider.getConstr(RangeErrorPolyfill.class); - this.regexp = targetEnv.wrappersProvider.getConstr(RegExpPolyfill.class); + @Native public void clearTimeout(Context ctx, int i) { + var thread = threads.remove(i); + if (thread != null) thread.interrupt(); + } + @Native public void clearInterval(Context ctx, int i) { + clearTimeout(ctx, i); + } + + public void apply(Environment env) { + var wp = env.wrappersProvider; + var glob = env.global; + + glob.define(null, "Object", false, wp.getConstr(ObjectPolyfill.class)); + glob.define(null, "Function", false, wp.getConstr(FunctionPolyfill.class)); + glob.define(null, "Array", false, wp.getConstr(ArrayPolyfill.class)); + + glob.define(null, "Boolean", false, wp.getConstr(BooleanPolyfill.class)); + glob.define(null, "Number", false, wp.getConstr(NumberPolyfill.class)); + glob.define(null, "String", false, wp.getConstr(StringPolyfill.class)); + glob.define(null, "Symbol", false, wp.getConstr(SymbolPolyfill.class)); + + glob.define(null, "Promise", false, wp.getConstr(PromisePolyfill.class)); + glob.define(null, "RegExp", false, wp.getConstr(RegExpPolyfill.class)); + glob.define(null, "Map", false, wp.getConstr(MapPolyfill.class)); + glob.define(null, "Set", false, wp.getConstr(SetPolyfill.class)); + + glob.define(null, "Error", false, wp.getConstr(ErrorPolyfill.class)); + glob.define(null, "SyntaxError", false, wp.getConstr(SyntaxErrorPolyfill.class)); + glob.define(null, "TypeError", false, wp.getConstr(TypeErrorPolyfill.class)); + glob.define(null, "RangeError", false, wp.getConstr(RangeErrorPolyfill.class)); + + System.out.println("Loaded polyfills!"); } } diff --git a/src/me/topchetoeu/jscript/polyfills/Math.java b/src/me/topchetoeu/jscript/polyfills/Math.java index fe6f0d3..3be504a 100644 --- a/src/me/topchetoeu/jscript/polyfills/Math.java +++ b/src/me/topchetoeu/jscript/polyfills/Math.java @@ -1,6 +1,6 @@ package me.topchetoeu.jscript.polyfills; -import me.topchetoeu.jscript.engine.MessageContext; +import me.topchetoeu.jscript.engine.Message; import me.topchetoeu.jscript.interop.Native; public class Math { @@ -22,19 +22,19 @@ public class Math { public static final double LOG10E = java.lang.Math.log10(java.lang.Math.E); @Native - public static double asin(MessageContext ctx, double x) throws InterruptedException { + public static double asin(Message ctx, double x) throws InterruptedException { return java.lang.Math.asin(x); } @Native - public static double acos(MessageContext ctx, double x) throws InterruptedException { + public static double acos(Message ctx, double x) throws InterruptedException { return java.lang.Math.acos(x); } @Native - public static double atan(MessageContext ctx, double x) throws InterruptedException { + public static double atan(Message ctx, double x) throws InterruptedException { return java.lang.Math.atan(x); } @Native - public static double atan2(MessageContext ctx, double y, double x) throws InterruptedException { + public static double atan2(Message ctx, double y, double x) throws InterruptedException { double _y = y; double _x = x; if (_x == 0) { @@ -51,59 +51,59 @@ public class Math { } @Native - public static double asinh(MessageContext ctx, double x) throws InterruptedException { + public static double asinh(Message ctx, double x) throws InterruptedException { double _x = x; return java.lang.Math.log(_x + java.lang.Math.sqrt(_x * _x + 1)); } @Native - public static double acosh(MessageContext ctx, double x) throws InterruptedException { + public static double acosh(Message ctx, double x) throws InterruptedException { double _x = x; return java.lang.Math.log(_x + java.lang.Math.sqrt(_x * _x - 1)); } @Native - public static double atanh(MessageContext ctx, double x) throws InterruptedException { + public static double atanh(Message ctx, double x) throws InterruptedException { double _x = x; if (_x <= -1 || _x >= 1) return Double.NaN; return .5 * java.lang.Math.log((1 + _x) / (1 - _x)); } @Native - public static double sin(MessageContext ctx, double x) throws InterruptedException { + public static double sin(Message ctx, double x) throws InterruptedException { return java.lang.Math.sin(x); } @Native - public static double cos(MessageContext ctx, double x) throws InterruptedException { + public static double cos(Message ctx, double x) throws InterruptedException { return java.lang.Math.cos(x); } @Native - public static double tan(MessageContext ctx, double x) throws InterruptedException { + public static double tan(Message ctx, double x) throws InterruptedException { return java.lang.Math.tan(x); } @Native - public static double sinh(MessageContext ctx, double x) throws InterruptedException { + public static double sinh(Message ctx, double x) throws InterruptedException { return java.lang.Math.sinh(x); } @Native - public static double cosh(MessageContext ctx, double x) throws InterruptedException { + public static double cosh(Message ctx, double x) throws InterruptedException { return java.lang.Math.cosh(x); } @Native - public static double tanh(MessageContext ctx, double x) throws InterruptedException { + public static double tanh(Message ctx, double x) throws InterruptedException { return java.lang.Math.tanh(x); } @Native - public static double sqrt(MessageContext ctx, double x) throws InterruptedException { + public static double sqrt(Message ctx, double x) throws InterruptedException { return java.lang.Math.sqrt(x); } @Native - public static double cbrt(MessageContext ctx, double x) throws InterruptedException { + public static double cbrt(Message ctx, double x) throws InterruptedException { return java.lang.Math.cbrt(x); } @Native - public static double hypot(MessageContext ctx, double ...vals) throws InterruptedException { + public static double hypot(Message ctx, double ...vals) throws InterruptedException { var res = 0.; for (var el : vals) { var val = el; @@ -112,68 +112,68 @@ public class Math { return java.lang.Math.sqrt(res); } @Native - public static int imul(MessageContext ctx, double a, double b) throws InterruptedException { + public static int imul(Message ctx, double a, double b) throws InterruptedException { return (int)a * (int)b; } @Native - public static double exp(MessageContext ctx, double x) throws InterruptedException { + public static double exp(Message ctx, double x) throws InterruptedException { return java.lang.Math.exp(x); } @Native - public static double expm1(MessageContext ctx, double x) throws InterruptedException { + public static double expm1(Message ctx, double x) throws InterruptedException { return java.lang.Math.expm1(x); } @Native - public static double pow(MessageContext ctx, double x, double y) throws InterruptedException { + public static double pow(Message ctx, double x, double y) throws InterruptedException { return java.lang.Math.pow(x, y); } @Native - public static double log(MessageContext ctx, double x) throws InterruptedException { + public static double log(Message ctx, double x) throws InterruptedException { return java.lang.Math.log(x); } @Native - public static double log10(MessageContext ctx, double x) throws InterruptedException { + public static double log10(Message ctx, double x) throws InterruptedException { return java.lang.Math.log10(x); } @Native - public static double log1p(MessageContext ctx, double x) throws InterruptedException { + public static double log1p(Message ctx, double x) throws InterruptedException { return java.lang.Math.log1p(x); } @Native - public static double log2(MessageContext ctx, double x) throws InterruptedException { + public static double log2(Message ctx, double x) throws InterruptedException { return java.lang.Math.log(x) / LN2; } @Native - public static double ceil(MessageContext ctx, double x) throws InterruptedException { + public static double ceil(Message ctx, double x) throws InterruptedException { return java.lang.Math.ceil(x); } @Native - public static double floor(MessageContext ctx, double x) throws InterruptedException { + public static double floor(Message ctx, double x) throws InterruptedException { return java.lang.Math.floor(x); } @Native - public static double round(MessageContext ctx, double x) throws InterruptedException { + public static double round(Message ctx, double x) throws InterruptedException { return java.lang.Math.round(x); } @Native - public static float fround(MessageContext ctx, double x) throws InterruptedException { + public static float fround(Message ctx, double x) throws InterruptedException { return (float)x; } @Native - public static double trunc(MessageContext ctx, double x) throws InterruptedException { + public static double trunc(Message ctx, double x) throws InterruptedException { var _x = x; return java.lang.Math.floor(java.lang.Math.abs(_x)) * java.lang.Math.signum(_x); } @Native - public static double abs(MessageContext ctx, double x) throws InterruptedException { + public static double abs(Message ctx, double x) throws InterruptedException { return java.lang.Math.abs(x); } @Native - public static double max(MessageContext ctx, double ...vals) throws InterruptedException { + public static double max(Message ctx, double ...vals) throws InterruptedException { var res = Double.NEGATIVE_INFINITY; for (var el : vals) { @@ -184,7 +184,7 @@ public class Math { return res; } @Native - public static double min(MessageContext ctx, double ...vals) throws InterruptedException { + public static double min(Message ctx, double ...vals) throws InterruptedException { var res = Double.POSITIVE_INFINITY; for (var el : vals) { @@ -196,7 +196,7 @@ public class Math { } @Native - public static double sign(MessageContext ctx, double x) throws InterruptedException { + public static double sign(Message ctx, double x) throws InterruptedException { return java.lang.Math.signum(x); } @@ -205,7 +205,7 @@ public class Math { return java.lang.Math.random(); } @Native - public static int clz32(MessageContext ctx, double x) throws InterruptedException { + public static int clz32(Message ctx, double x) throws InterruptedException { return Integer.numberOfLeadingZeros((int)x); } } diff --git a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java index 574a894..0235667 100644 --- a/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java +++ b/src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java @@ -6,7 +6,7 @@ import java.util.Map; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.MessageContext; +import me.topchetoeu.jscript.engine.Message; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -297,9 +297,9 @@ public class PromisePolyfill { } private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { - if (state == STATE_FULFILLED) ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), fulfill, null, val); + if (state == STATE_FULFILLED) ctx.message.engine.pushMsg(true, new Message(ctx.message.engine), fulfill, null, val); else if (state == STATE_REJECTED) { - ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), reject, null, val); + ctx.message.engine.pushMsg(true, new Message(ctx.message.engine), reject, null, val); handled = true; } else handles.add(new Handle(ctx, fulfill, reject)); diff --git a/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java b/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java deleted file mode 100644 index a0dd02e..0000000 --- a/src/me/topchetoeu/jscript/polyfills/TimerPolyfills.java +++ /dev/null @@ -1,60 +0,0 @@ -package me.topchetoeu.jscript.polyfills; - -import java.util.HashMap; - -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.interop.Native; - -public class TimerPolyfills { - private HashMap threads = new HashMap<>(); - - private int i = 0; - - @Native public 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.message.engine.pushMsg(false, ctx.message, func, null, args); - }); - thread.start(); - - threads.put(++i, thread); - - return i; - } - @Native public 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.message.engine.pushMsg(false, ctx.message, func, null, args); - } - }); - thread.start(); - - threads.put(++i, thread); - - return i; - } - - @Native public void clearTimeout(Context ctx, int i) { - var thread = threads.remove(i); - if (thread != null) thread.interrupt(); - } - @Native public void clearInterval(Context ctx, int i) { - clearTimeout(ctx, i); - } -} -- 2.45.2 From 6c7fe6deaf5261da027f0b9c98f85375919835be Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 4 Oct 2023 08:30:33 +0300 Subject: [PATCH 21/23] feat: add old data to both subcontexts --- src/me/topchetoeu/jscript/Main.java | 2 +- src/me/topchetoeu/jscript/engine/Data.java | 58 +++++++++++++++++++ src/me/topchetoeu/jscript/engine/DataKey.java | 3 + src/me/topchetoeu/jscript/engine/Engine.java | 14 ++--- .../jscript/engine/Environment.java | 12 +++- src/me/topchetoeu/jscript/engine/Message.java | 7 +++ .../jscript/engine/values/CodeFunction.java | 2 +- 7 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 src/me/topchetoeu/jscript/engine/Data.java create mode 100644 src/me/topchetoeu/jscript/engine/DataKey.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 4180c06..fe9625e 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -102,7 +102,7 @@ public class Main { var raw = in.readLine(); if (raw == null) break; - engine.pushMsg(false, new Context(env, new Message(engine)), "", raw, null).toObservable().once(valuePrinter); + engine.pushMsg(false, env.context(new Message(engine)), "", raw, null).toObservable().once(valuePrinter); } catch (EngineException e) { try { diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java new file mode 100644 index 0000000..49d18b7 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/Data.java @@ -0,0 +1,58 @@ +package me.topchetoeu.jscript.engine; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map.Entry; + +@SuppressWarnings("unchecked") +public class Data implements Iterable, ?>> { + private HashMap, Object> data = new HashMap<>(); + + public Data copy() { + return new Data().addAll(this); + } + + public Data addAll(Iterable, ?>> data) { + for (var el : data) { + add((DataKey)el.getKey(), (Object)el.getValue()); + } + return this; + } + + public Data set(DataKey key, T val) { + if (val == null) data.remove(key); + else data.put((DataKey)key, (Object)val); + return this; + } + public T add(DataKey key, T val) { + if (data.containsKey(key)) return (T)data.get(key); + else { + if (val == null) data.remove(key); + else data.put((DataKey)key, (Object)val); + return val; + } + } + public T get(DataKey key) { + return get(key, null); + } + public T get(DataKey key, T defaultVal) { + if (!has(key)) return defaultVal; + else return (T)data.get(key); + } + public boolean has(DataKey key) { return data.containsKey(key); } + + public Data increase(DataKey key, int n, int start) { + return set(key, get(key, start) + n); + } + public Data increase(DataKey key, int n) { + return increase(key, n, 0); + } + public Data increase(DataKey key) { + return increase(key, 1, 0); + } + + @Override + public Iterator, ?>> iterator() { + return (Iterator, ?>>)data.entrySet(); + } +} diff --git a/src/me/topchetoeu/jscript/engine/DataKey.java b/src/me/topchetoeu/jscript/engine/DataKey.java new file mode 100644 index 0000000..017fa2a --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/DataKey.java @@ -0,0 +1,3 @@ +package me.topchetoeu.jscript.engine; + +public class DataKey { } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index aa83065..013b66e 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -11,19 +11,19 @@ public class Engine { private class UncompiledFunction extends FunctionValue { public final String filename; public final String raw; - public final Environment ctx; + public final Environment env; @Override public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - ctx = new Context(this.ctx, ctx.message); + ctx = ctx.setEnv(env); return ctx.compile(filename, raw).call(ctx, thisArg, args); } - public UncompiledFunction(Environment ctx, String filename, String raw) { + public UncompiledFunction(Environment env, String filename, String raw) { super(filename, 0); this.filename = filename; this.raw = raw; - this.ctx = ctx; + this.env = env; } } @@ -32,10 +32,10 @@ public class Engine { public final Object thisArg; public final Object[] args; public final DataNotifier notifier = new DataNotifier<>(); - public final Message ctx; + public final Message msg; public Task(Message ctx, FunctionValue func, Object thisArg, Object[] args) { - this.ctx = ctx; + this.msg = ctx; this.func = func; this.thisArg = thisArg; this.args = args; @@ -52,7 +52,7 @@ public class Engine { private void runTask(Task task) throws InterruptedException { try { - task.notifier.next(task.func.call(new Context(null, task.ctx), task.thisArg, task.args)); + task.notifier.next(task.func.call(task.msg.context(null), task.thisArg, task.args)); } catch (InterruptedException e) { task.notifier.error(new RuntimeException(e)); diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 5821c04..c8136cf 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -15,15 +15,23 @@ import me.topchetoeu.jscript.interop.NativeWrapperProvider; public class Environment { private HashMap prototypes = new HashMap<>(); + + public final Data data = new Data(); + public final HashMap symbols = new HashMap<>(); + public GlobalScope global; public WrappersProvider wrappersProvider; - /** NOTE: This is not the register for Symbol.for, but for the symbols like Symbol.iterator */ - public HashMap symbols = new HashMap<>(); @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { throw EngineException.ofError("Regular expressions not supported.").setContext(ctx); }); + + public Environment addData(Data data) { + this.data.addAll(data); + return this; + } + @Native public ObjectValue proto(String name) { return prototypes.get(name); } diff --git a/src/me/topchetoeu/jscript/engine/Message.java b/src/me/topchetoeu/jscript/engine/Message.java index 974893f..b61f522 100644 --- a/src/me/topchetoeu/jscript/engine/Message.java +++ b/src/me/topchetoeu/jscript/engine/Message.java @@ -13,8 +13,15 @@ public class Message { private final ArrayList frames = new ArrayList<>(); public int maxStackFrames = 1000; + public final Data data = new Data(); + public List frames() { return Collections.unmodifiableList(frames); } + public Message addData(Data data) { + this.data.addAll(data); + return this; + } + public Message pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { this.frames.add(frame); if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!"); diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index 0b34425..c0025b3 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -29,7 +29,7 @@ public class CodeFunction extends FunctionValue { @Override public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - return new CodeFrame(ctx, thisArg, args, this).run(new Context(environment, ctx.message)); + return new CodeFrame(ctx, thisArg, args, this).run(ctx.setEnv(environment)); } public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { -- 2.45.2 From 604b752be67f24eb090f0deb4ab317b00fd9729f Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 4 Oct 2023 08:49:20 +0300 Subject: [PATCH 22/23] feat: add global functions --- lib/tsconfig.json | 19 ------- src/me/topchetoeu/jscript/Main.java | 8 --- src/me/topchetoeu/jscript/engine/Data.java | 10 ++-- .../me/topchetoeu/jscript/js}/lib.d.ts | 2 +- .../jscript/polyfills/Internals.java | 52 +++++++++++++++---- 5 files changed, 48 insertions(+), 43 deletions(-) delete mode 100644 lib/tsconfig.json rename {lib => src/me/topchetoeu/jscript/js}/lib.d.ts (99%) diff --git a/lib/tsconfig.json b/lib/tsconfig.json deleted file mode 100644 index 64939d9..0000000 --- a/lib/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "files": [ - "lib.d.ts", - "core.ts" - ], - "compilerOptions": { - "outFile": "../bin/me/topchetoeu/jscript/js/core.js", - // "declarationDir": "", - // "declarationDir": "bin/me/topchetoeu/jscript/dts", - "target": "ES5", - "lib": [], - "module": "None", - "stripInternal": true, - "downlevelIteration": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - } -} diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index fe9625e..b72caea 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -8,7 +8,6 @@ import java.nio.file.Files; import java.nio.file.Path; import me.topchetoeu.jscript.engine.Message; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -87,13 +86,6 @@ public class Main { return null; }), null); - // engine.pushMsg( - // false, - // new Context(builderEnv, new MessageContext(engine)), - // "core.js", resourceToString("js/core.js"), - // null, env, new Internals(env) - // ).toObservable().on(valuePrinter); - task = engine.start(); var reader = new Thread(() -> { try { diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java index 49d18b7..1db4ba8 100644 --- a/src/me/topchetoeu/jscript/engine/Data.java +++ b/src/me/topchetoeu/jscript/engine/Data.java @@ -41,13 +41,15 @@ public class Data implements Iterable, ?>> { } public boolean has(DataKey key) { return data.containsKey(key); } - public Data increase(DataKey key, int n, int start) { - return set(key, get(key, start) + n); + public int increase(DataKey key, int n, int start) { + int res; + set(key, res = get(key, start) + n); + return res; } - public Data increase(DataKey key, int n) { + public int increase(DataKey key, int n) { return increase(key, n, 0); } - public Data increase(DataKey key) { + public int increase(DataKey key) { return increase(key, 1, 0); } diff --git a/lib/lib.d.ts b/src/me/topchetoeu/jscript/js/lib.d.ts similarity index 99% rename from lib/lib.d.ts rename to src/me/topchetoeu/jscript/js/lib.d.ts index 5ce6aab..df95570 100644 --- a/lib/lib.d.ts +++ b/src/me/topchetoeu/jscript/js/lib.d.ts @@ -350,7 +350,7 @@ interface Object { toString(): string; hasOwnProperty(key: any): boolean; } -interface ObjectConstructor extends Function { +interface ObjectConstructor { (arg: string): String; (arg: number): Number; (arg: boolean): Boolean; diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index 24386bd..fd808ad 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -3,26 +3,29 @@ package me.topchetoeu.jscript.polyfills; import java.util.HashMap; 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.NativeWrapperProvider; public class Internals { - private HashMap threads = new HashMap<>(); - private int i = 0; + private static final DataKey> THREADS = new DataKey<>(); + private static final DataKey I = new DataKey<>(); - @Native public FunctionValue bind(FunctionValue func, Object thisArg) throws InterruptedException { + @Native public static FunctionValue bind(FunctionValue func, Object thisArg) throws InterruptedException { return FunctionPolyfill.bind(func, thisArg); } - @Native public void log(Context ctx, Object ...args) throws InterruptedException { + @Native public static void log(Context ctx, Object ...args) throws InterruptedException { for (var arg : args) { Values.printValue(ctx, arg); } System.out.println(); } - @Native public int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { + @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); @@ -36,11 +39,12 @@ public class Internals { }); thread.start(); + int i = ctx.env.data.increase(I, 1, 0); + var threads = ctx.env.data.add(THREADS, new HashMap<>()); threads.put(++i, thread); - return i; } - @Native public int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { + @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); @@ -56,22 +60,32 @@ public class Internals { }); thread.start(); + int i = ctx.env.data.increase(I, 1, 0); + var threads = ctx.env.data.add(THREADS, new HashMap<>()); threads.put(++i, thread); - return i; } - @Native public void clearTimeout(Context ctx, int i) { + @Native public static void clearTimeout(Context ctx, int i) { + var threads = ctx.env.data.add(THREADS, new HashMap<>()); + var thread = threads.remove(i); if (thread != null) thread.interrupt(); } - @Native public void clearInterval(Context ctx, int i) { + @Native public static void clearInterval(Context ctx, int i) { clearTimeout(ctx, i); } + @Native public static double parseInt(Context ctx, String val) throws InterruptedException { + return NumberPolyfill.parseInt(ctx, val); + } + @Native public static double parseFloat(Context ctx, String val) throws InterruptedException { + return NumberPolyfill.parseFloat(ctx, val); + } + public void apply(Environment env) { var wp = env.wrappersProvider; - var glob = env.global; + var glob = env.global = new GlobalScope(NativeWrapperProvider.makeNamespace(env, Internals.class)); glob.define(null, "Object", false, wp.getConstr(ObjectPolyfill.class)); glob.define(null, "Function", false, wp.getConstr(FunctionPolyfill.class)); @@ -92,6 +106,22 @@ public class Internals { glob.define(null, "TypeError", false, wp.getConstr(TypeErrorPolyfill.class)); glob.define(null, "RangeError", false, wp.getConstr(RangeErrorPolyfill.class)); + env.setProto("object", wp.getProto(ObjectPolyfill.class)); + env.setProto("function", wp.getProto(FunctionPolyfill.class)); + env.setProto("array", wp.getProto(ArrayPolyfill.class)); + + env.setProto("bool", wp.getProto(BooleanPolyfill.class)); + env.setProto("number", wp.getProto(NumberPolyfill.class)); + env.setProto("string", wp.getProto(StringPolyfill.class)); + env.setProto("symbol", wp.getProto(SymbolPolyfill.class)); + + env.setProto("error", wp.getProto(ErrorPolyfill.class)); + env.setProto("syntaxErr", wp.getProto(SyntaxErrorPolyfill.class)); + env.setProto("typeErr", wp.getProto(TypeErrorPolyfill.class)); + env.setProto("rangeErr", wp.getProto(RangeErrorPolyfill.class)); + + wp.getProto(ObjectPolyfill.class).setPrototype(null, null); + System.out.println("Loaded polyfills!"); } } -- 2.45.2 From 1cccfa90a846f8aa90b765a80058624b6e3a057d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 4 Oct 2023 08:49:49 +0300 Subject: [PATCH 23/23] refactor: remove pesky node files --- package-lock.json | 6 ------ package.json | 1 - 2 files changed, 7 deletions(-) delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 4bbacb0..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "java-jscript", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/package.json b/package.json deleted file mode 100644 index 0967ef4..0000000 --- a/package.json +++ /dev/null @@ -1 +0,0 @@ -{} -- 2.45.2