From f13bf584a56f063589e5987d0e86d291b4e71a8a Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 15:25:34 +0300 Subject: [PATCH] feat: add some missing features in the polyfills --- .../topchetoeu/jscript/runtime/Compiler.java | 4 +- .../jscript/runtime/SimpleRepl.java | 42 ++++-- .../runtime/exceptions/EngineException.java | 2 +- .../jscript/runtime/values/Value.java | 24 ++-- src/main/resources/lib/index.js | 136 ++++++++++++------ 5 files changed, 136 insertions(+), 72 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java index 70edd70..fea091c 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java @@ -22,7 +22,9 @@ public interface Compiler { return body; } catch (SyntaxException e) { - throw EngineException.ofSyntax(e.loc + ": " + e.msg); + var res = EngineException.ofSyntax(e.msg); + res.add(env, e.loc.filename() + "", e.loc); + throw res; } }; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index c1b1ca9..daa0d1b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -41,7 +41,7 @@ public class SimpleRepl { try { try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); } } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } for (var arg : args) { try { @@ -58,7 +58,7 @@ public class SimpleRepl { } catch (ExecutionException e) { throw e.getCause(); } } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } } for (var i = 0; ; i++) { @@ -77,7 +77,7 @@ public class SimpleRepl { } catch (ExecutionException e) { throw e.getCause(); } } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } } } catch (IOException e) { @@ -293,18 +293,6 @@ public class SimpleRepl { var obj = (ObjectValue)args.get(1); switch (type) { - case "string": - args.env.add(Value.STRING_PROTO, obj); - break; - case "number": - args.env.add(Value.NUMBER_PROTO, obj); - break; - case "boolean": - args.env.add(Value.BOOL_PROTO, obj); - break; - case "symbol": - args.env.add(Value.SYMBOL_PROTO, obj); - break; case "object": args.env.add(Value.OBJECT_PROTO, obj); break; @@ -314,6 +302,30 @@ public class SimpleRepl { case "array": args.env.add(Value.ARRAY_PROTO, obj); break; + case "boolean": + args.env.add(Value.BOOL_PROTO, obj); + break; + case "number": + args.env.add(Value.NUMBER_PROTO, obj); + break; + case "string": + args.env.add(Value.STRING_PROTO, obj); + break; + case "symbol": + args.env.add(Value.SYMBOL_PROTO, obj); + break; + case "error": + args.env.add(Value.ERROR_PROTO, obj); + break; + case "syntax": + args.env.add(Value.SYNTAX_ERR_PROTO, obj); + break; + case "type": + args.env.add(Value.TYPE_ERR_PROTO, obj); + break; + case "range": + args.env.add(Value.RANGE_ERR_PROTO, obj); + break; } return Value.UNDEFINED; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index 60657c0..20aed1a 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -66,7 +66,7 @@ public class EngineException extends RuntimeException { public String toString(Environment env) { var ss = new StringBuilder(); try { - ss.append(value.toString(env)).append('\n'); + ss.append(value.toString(env).value).append('\n'); } catch (EngineException e) { var name = value.getMember(env, "name"); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 92bc03e..9a13d6d 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -236,8 +236,8 @@ public abstract class Value { public final boolean setMember(Environment env, KeyCache key, Value val) { for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { var member = obj.getOwnMember(env, key); - if (member != null) { - if (member.set(env, val, obj)) { + if (member instanceof PropertyMember prop) { + if (prop.set(env, val, obj)) { if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); return true; } @@ -670,22 +670,22 @@ public abstract class Value { return a.toString(env).equals(b.toString(env)); } - // public static Value operation(Environment env, Operation op, Value ...args) { - // } - - public static final String errorToReadable(RuntimeException err, String prefix) { + public static final String errorToReadable(Environment env, RuntimeException err, String prefix) { prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; - if (err instanceof EngineException) { - var ee = ((EngineException)err); + if (err instanceof EngineException ee) { + if (env == null) env = ee.env; + try { - return prefix + " " + ee.toString(ee.env); + return prefix + " " + ee.toString(env); } catch (EngineException ex) { - return prefix + " " + ee.value.toReadable(ee.env); + return prefix + " " + ee.value.toReadable(env); } } - else if (err instanceof SyntaxException) { - return prefix + " SyntaxError " + ((SyntaxException)err).msg; + else if (err instanceof SyntaxException syntax) { + var newErr = EngineException.ofSyntax(syntax.msg); + newErr.add(null, syntax.loc.filename() + "", syntax.loc); + return errorToReadable(env, newErr, prefix); } else if (err.getCause() instanceof InterruptedException) return ""; else { diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index ea223fc..28185d9 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -1,16 +1,32 @@ const target = arguments[0]; const primordials = arguments[1]; -const makeSymbol = primordials.symbol.makeSymbol; -const getSymbol = primordials.symbol.getSymbol; -const getSymbolKey = primordials.symbol.getSymbolKey; -const getSymbolDescription = primordials.symbol.getSymbolDescription; +const symbol = primordials.symbol || (() => { + const repo = {}; -const parseInt = primordials.number.parseInt; -const parseFloat = primordials.number.parseFloat; -const isNaN = primordials.number.isNaN; -const NaN = primordials.number.NaN; -const Infinity = primordials.number.Infinity; + return { + makeSymbol: (name) => { name }, + getSymbol(name) { + if (name in repo) return repo[name]; + else return repo[name] = { name }; + }, + getSymbolKey(symbol) { + if (symbol.name in repo && repo[symbol.name] === symbol) return symbol.name; + else return undefined; + }, + getSymbolDescription: ({ name }) => name, + }; +}); + +const number = primordials.number || (() => { + return { + parseInt() { throw new Error("parseInt not supported"); }, + parseFloat() { throw new Error("parseFloat not supported"); }, + isNaN: (val) => val !== val, + NaN: 0 / 0, + Infinity: 1 / 0, + }; +}); const fromCharCode = primordials.string.fromCharCode; const fromCodePoint = primordials.string.fromCodePoint; @@ -37,7 +53,7 @@ const setGlobalPrototype = primordials.setGlobalPrototype; const compile = primordials.compile; const setIntrinsic = primordials.setIntrinsic; -const valueKey = makeSymbol("Primitive.value"); +const valueKey = symbol.makeSymbol("Primitive.value"); const undefined = ({}).definitelyDefined; target.undefined = undefined; @@ -54,13 +70,13 @@ const unwrapThis = (self, type, constr, name, arg, defaultVal) => { const wrapIndex = (i, len) => {}; -const Symbol = (name = "") => makeSymbol(name); +const Symbol = (name = "") => symbol.makeSymbol(name); defineField(Symbol, "for", true, false, true, function(name) { - return getSymbol(name + ""); + return symbol.getSymbol(name + ""); }); -defineField(Symbol, "keyFor", true, false, true, function(symbol) { - return getSymbolKey(unwrapThis(symbol, "symbol", Symbol, "Symbol.keyFor")); +defineField(Symbol, "keyFor", true, false, true, function(value) { + return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor")); }); defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); @@ -74,7 +90,7 @@ defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringT defineField(Symbol, "prototype", false, false, false, {}); defineProperty(Symbol.prototype, "description", false, true, function () { - return getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); + return symbol.getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); }, undefined); defineField(Symbol.prototype, "toString", true, false, true, function() { return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; @@ -97,7 +113,7 @@ const Number = function(value) { defineField(Number, "isFinite", true, false, true, function(value) { value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); - if (value === undefined || isNaN(value)) return false; + if (value === undefined || value !== value) return false; if (value === Infinity || value === -Infinity) return false; return true; @@ -105,34 +121,34 @@ defineField(Number, "isFinite", true, false, true, function(value) { defineField(Number, "isInteger", true, false, true, function(value) { value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); if (value === undefined) return false; - return parseInt(value) === value; + return number.parseInt(value) === value; }); defineField(Number, "isNaN", true, false, true, function(value) { - return isNaN(value); + return number.isNaN(value); }); defineField(Number, "isSafeInteger", true, false, true, function(value) { value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); - if (value === undefined || parseInt(value) !== value) return false; + if (value === undefined || number.parseInt(value) !== value) return false; return value >= -9007199254740991 && value <= 9007199254740991; }); defineField(Number, "parseFloat", true, false, true, function(value) { value = 0 + value; - return parseFloat(value); + return number.parseFloat(value); }); defineField(Number, "parseInt", true, false, true, function(value, radix) { value = 0 + value; radix = +radix; - if (isNaN(radix)) radix = 10; + if (number.isNaN(radix)) radix = 10; - return parseInt(value, radix); + return number.parseInt(value, radix); }); defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991); -defineField(Number, "POSITIVE_INFINITY", false, false, false, +Infinity); -defineField(Number, "NEGATIVE_INFINITY", false, false, false, -Infinity); -defineField(Number, "NaN", false, false, false, NaN); +defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity); +defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity); +defineField(Number, "NaN", false, false, false, number.NaN); defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); defineField(Number, "MIN_VALUE", false, false, false, 5e-324); defineField(Number, "prototype", false, false, false, {}); @@ -146,6 +162,10 @@ defineField(Number.prototype, "valueOf", true, false, true, function() { }); target.Number = Number; +target.parseInt = Number.parseInt; +target.parseFloat = Number.parseFloat; +target.NaN = Number.NaN; +target.Infinity = Number.POSITIVE_INFINITY; const String = function(value) { if (invokeType(arguments) === "call") { @@ -337,31 +357,61 @@ defineField(Function.prototype, "valueOf", true, false, true, function() { target.Function = Function; -setIntrinsic("spread_obj", target.spread_obj = (target, obj) => { - if (obj === null || obj === undefined) return; - const members = getOwnMembers(obj, true); - const symbols = getOwnSymbolMembers(obj, true); +// setIntrinsic("spread_obj", target.spread_obj = (target, obj) => { +// if (obj === null || obj === undefined) return; +// const members = getOwnMembers(obj, true); +// const symbols = getOwnSymbolMembers(obj, true); - for (let i = 0; i < members.length; i++) { - const member = members[i]; - target[member] = obj[member]; - } +// for (let i = 0; i < members.length; i++) { +// const member = members[i]; +// target[member] = obj[member]; +// } - for (let i = 0; i < symbols.length; i++) { - const member = symbols[i]; - target[member] = obj[member]; - } -}); -setIntrinsic("apply", target.spread_call = (func, self, args) => { - return invoke(func, self, args); -}); -setIntrinsic("apply", target.spread_new = (func, args) => { - return invoke(func, null, args); +// for (let i = 0; i < symbols.length; i++) { +// const member = symbols[i]; +// target[member] = obj[member]; +// } +// }); +// setIntrinsic("apply", target.spread_call = (func, self, args) => { +// return invoke(func, self, args); +// }); +// setIntrinsic("apply", target.spread_new = (func, args) => { +// return invoke(func, null, args); +// }); + +const Error = function(msg = "") { + if (invokeType(arguments) === "call") return new Error(msg); + this.message = msg + ""; +}; +defineField(Error.prototype, "name", true, false, true, "Error"); +defineField(Error.prototype, "message", true, false, true, ""); +defineField(Error.prototype, "toString", true, false, true, function toString() { + let res = this.name || "Error"; + + const msg = this.message; + if (msg) res += ": " + msg; + + return res; }); +target.Error = Error; + +const SyntaxError = function(msg = "") { + if (invokeType(arguments) === "call") return new SyntaxError(msg); + this.message = msg + ""; +}; +defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError"); + +setPrototype(SyntaxError, Error); +setPrototype(SyntaxError.prototype, Error.prototype); + +target.SyntaxError = SyntaxError; + setGlobalPrototype("string", String.prototype); setGlobalPrototype("number", Number.prototype); setGlobalPrototype("boolean", Boolean.prototype); setGlobalPrototype("symbol", Symbol.prototype); setGlobalPrototype("object", Object.prototype); setGlobalPrototype("function", Function.prototype); +setGlobalPrototype("error", Error.prototype); +setGlobalPrototype("syntax", SyntaxError.prototype);