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"); }