From d57044acb70e96989acb67b02d72f00d4e642729 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 5 Nov 2023 12:44:29 +0200 Subject: [PATCH] fix: several bug fixes to help with typescript support --- src/me/topchetoeu/jscript/engine/Engine.java | 2 +- .../jscript/engine/debug/SimpleDebugger.java | 98 ++++++++++--------- .../jscript/engine/frame/CodeFrame.java | 2 +- src/me/topchetoeu/jscript/js/bootstrap.js | 2 +- src/me/topchetoeu/jscript/json/JSON.java | 29 ++++-- src/me/topchetoeu/jscript/lib/ArrayLib.java | 10 +- src/me/topchetoeu/jscript/lib/RegExpLib.java | 70 +++++++------ src/me/topchetoeu/jscript/lib/StringLib.java | 8 +- 8 files changed, 127 insertions(+), 94 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index f24e885..df6746e 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -51,6 +51,7 @@ public class Engine implements DebugController { } private static int nextId = 0; + public static final HashMap functions = new HashMap<>(); private Thread thread; private LinkedBlockingDeque macroTasks = new LinkedBlockingDeque<>(); @@ -58,7 +59,6 @@ public class Engine implements DebugController { public final int id = ++nextId; public final Data data = new Data().set(StackData.MAX_FRAMES, 200); - public final HashMap functions = new HashMap<>(); public final boolean debugging; private final HashMap sources = new HashMap<>(); private final HashMap> bpts = new HashMap<>(); diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index e6371bf..32a5984 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -473,13 +473,16 @@ public class SimpleDebugger implements Debugger { @Override public void setBreakpointByUrl(V8Message msg) { var line = (int)msg.params.number("lineNumber") + 1; var col = (int)msg.params.number("columnNumber", 0) + 1; + var cond = msg.params.string("condition", null); + + if (cond != null) cond = "(" + cond + ")"; Pattern regex; if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url"))); else regex = Pattern.compile(msg.params.string("urlRegex")); - var bpcd = new BreakpointCandidate(nextId(), regex, line, col, null); + var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond); idToBptCand.put(bpcd.id, bpcd); var locs = new JSONList(); @@ -592,42 +595,44 @@ public class SimpleDebugger implements Debugger { var res = new JSONList(); var ctx = objectToCtx.get(obj); - for (var key : obj.keys(true)) { - var propDesc = new JSONMap(); - - if (obj.properties.containsKey(key)) { - var prop = obj.properties.get(key); - - propDesc.set("name", Values.toString(ctx, key)); - if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); - if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); - propDesc.set("enumerable", obj.memberEnumerable(key)); - propDesc.set("configurable", obj.memberConfigurable(key)); - propDesc.set("isOwn", true); - res.add(propDesc); - } - else { - propDesc.set("name", Values.toString(ctx, key)); - propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); - propDesc.set("writable", obj.memberWritable(key)); - propDesc.set("enumerable", obj.memberEnumerable(key)); - propDesc.set("configurable", obj.memberConfigurable(key)); - propDesc.set("isOwn", true); - res.add(propDesc); + if (obj != emptyObject) { + for (var key : obj.keys(true)) { + var propDesc = new JSONMap(); + + if (obj.properties.containsKey(key)) { + var prop = obj.properties.get(key); + + propDesc.set("name", Values.toString(ctx, key)); + if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); + if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", true); + res.add(propDesc); + } + else { + propDesc.set("name", Values.toString(ctx, key)); + propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); + propDesc.set("writable", obj.memberWritable(key)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", true); + res.add(propDesc); + } } + + var proto = obj.getPrototype(ctx); + + var protoDesc = new JSONMap(); + protoDesc.set("name", "__proto__"); + protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto)); + protoDesc.set("writable", true); + protoDesc.set("enumerable", false); + protoDesc.set("configurable", false); + protoDesc.set("isOwn", true); + res.add(protoDesc); } - var proto = obj.getPrototype(ctx); - - var protoDesc = new JSONMap(); - protoDesc.set("name", "__proto__"); - protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto)); - protoDesc.set("writable", true); - protoDesc.set("enumerable", false); - protoDesc.set("configurable", false); - protoDesc.set("isOwn", true); - res.add(protoDesc); - ws.send(msg.respond(new JSONMap().set("result", res))); } @Override public void callFunctionOn(V8Message msg) { @@ -662,25 +667,25 @@ public class SimpleDebugger implements Debugger { case VSCODE_STRINGIFY_VAL: case VSCODE_STRINGIFY_PROPS: case VSCODE_SHALLOW_COPY: + case VSCODE_SYMBOL_REQUEST: ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject)))); break; - case VSCODE_FLATTEN_ARRAY: { + case VSCODE_FLATTEN_ARRAY: ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg)))); break; - } - case VSCODE_SYMBOL_REQUEST: - ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, new ArrayValue(ctx))))); - break; case VSCODE_CALL: { var func = (FunctionValue)(args.size() < 1 ? null : args.get(0)); ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg))))); break; } - default: { - var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx); - if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args); - ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); - } + default: + ws.send(new V8Error("Please use well-known functions with callFunctionOn")); + break; + // default: { + // var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx); + // if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args); + // ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); + // } } } catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); } @@ -725,14 +730,15 @@ public class SimpleDebugger implements Debugger { returnVal != Runners.NO_RETURN ); - if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null; + // TODO: FIXXXX + // if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null; if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { pauseException(ctx); } else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { var bp = locToBreakpoint.get(loc); - var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition)); + var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result); if (ok) pauseDebug(ctx, locToBreakpoint.get(loc)); } else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 95eb02b..61e777e 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -15,7 +15,7 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; public class CodeFrame { - private class TryCtx { + public static class TryCtx { public static final int STATE_TRY = 0; public static final int STATE_CATCH = 1; public static final int STATE_FINALLY_THREW = 2; diff --git a/src/me/topchetoeu/jscript/js/bootstrap.js b/src/me/topchetoeu/jscript/js/bootstrap.js index ab68384..3df4707 100644 --- a/src/me/topchetoeu/jscript/js/bootstrap.js +++ b/src/me/topchetoeu/jscript/js/bootstrap.js @@ -42,7 +42,7 @@ service.getEmitOutput('/lib.d.ts'); log('Loaded libraries!'); - function compile(filename, code) { + function compile(code, filename) { src = code, version++; var emit = service.getEmitOutput("/src.ts"); diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 897dcc0..457a885 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -188,13 +188,28 @@ public class JSON { if (el.isNumber()) return Double.toString(el.number()); if (el.isBoolean()) return el.bool() ? "true" : "false"; if (el.isNull()) return "null"; - if (el.isString()) return "\"" + el.string() - .replace("\\", "\\\\") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t") - .replace("\"", "\\\"") - + "\""; + if (el.isString()) { + var res = new StringBuilder("\""); + var alphabet = "0123456789ABCDEF".toCharArray(); + + for (var c : el.string().toCharArray()) { + if (c < 32 || c >= 127) { + res + .append("\\u") + .append(alphabet[(c >> 12) & 0xF]) + .append(alphabet[(c >> 8) & 0xF]) + .append(alphabet[(c >> 4) & 0xF]) + .append(alphabet[(c >> 0) & 0xF]); + } + else if (c == '\\') + res.append("\\\\"); + else if (c == '"') + res.append("\\\""); + else res.append(c); + } + + return res.append('"').toString(); + } if (el.isList()) { var res = new StringBuilder().append("["); for (int i = 0; i < el.list().size(); i++) { diff --git a/src/me/topchetoeu/jscript/lib/ArrayLib.java b/src/me/topchetoeu/jscript/lib/ArrayLib.java index f6108c5..081cdb1 100644 --- a/src/me/topchetoeu/jscript/lib/ArrayLib.java +++ b/src/me/topchetoeu/jscript/lib/ArrayLib.java @@ -93,13 +93,14 @@ import me.topchetoeu.jscript.interop.NativeSetter; return res; } - @Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) { + @Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) { arr.sort((a, b) -> { var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); if (res < 0) return -1; if (res > 0) return 1; return 0; }); + return arr; } private static int normalizeI(int len, int i, boolean clamp) { @@ -299,17 +300,14 @@ import me.topchetoeu.jscript.interop.NativeSetter; return arr.size(); } - @Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, int end) { + @Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, Object _end) { start = normalizeI(arr.size(), start, true); - end = normalizeI(arr.size(), end, true); + int end = normalizeI(arr.size(), (int)(_end == null ? arr.size() : Values.toNumber(ctx, _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) { start = normalizeI(arr.size(), start, true); diff --git a/src/me/topchetoeu/jscript/lib/RegExpLib.java b/src/me/topchetoeu/jscript/lib/RegExpLib.java index 0c5c0ec..d311ac1 100644 --- a/src/me/topchetoeu/jscript/lib/RegExpLib.java +++ b/src/me/topchetoeu/jscript/lib/RegExpLib.java @@ -2,10 +2,12 @@ package me.topchetoeu.jscript.lib; import java.util.ArrayList; import java.util.Iterator; +import java.util.function.Function; 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.NativeWrapper; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; @@ -81,7 +83,6 @@ import me.topchetoeu.jscript.interop.NativeGetter; return res; } - @Native public Object exec(String str) { var matcher = pattern.matcher(str); if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) { @@ -133,7 +134,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; return "/" + source + "/" + flags(); } - @Native("@@Symvol.match") public Object match(Context ctx, String target) { + @Native("@@Symbol.match") public Object match(Context ctx, String target) { if (this.global) { var res = new ArrayValue(); Object val; @@ -150,7 +151,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; } } - @Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) { + @Native("@@Symbol.matchAll") public Object matchAll(Context ctx, String target) { var pattern = new RegExpLib(this.source, this.flags() + "g"); return Values.fromJavaIterator(ctx, new Iterator() { @@ -171,8 +172,8 @@ import me.topchetoeu.jscript.interop.NativeGetter; } }); } - - @Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) { + + @Native("@@Symbol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) { var pattern = new RegExpLib(this.source, this.flags() + "g"); Object match; int lastEnd = 0; @@ -214,29 +215,41 @@ import me.topchetoeu.jscript.interop.NativeGetter; } 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(''); - // }, + + @Native("@@Symbol.replace") public String replace(Context ctx, String target, Object replacement) { + var pattern = new RegExpLib(this.source, this.flags() + "d"); + Object match; + var lastEnd = 0; + var res = new StringBuilder(); + + while ((match = pattern.exec(target)) != Values.NULL) { + var indices = (ArrayValue)((ArrayValue)Values.getMember(ctx, match, "indices")).get(0); + var arrMatch = (ArrayValue)match; + + var start = ((Number)indices.get(0)).intValue(); + var end = ((Number)indices.get(1)).intValue(); + + res.append(target.substring(lastEnd, start)); + if (replacement instanceof FunctionValue) { + var args = new Object[arrMatch.size() + 2]; + args[0] = target.substring(start, end); + arrMatch.copyTo(args, 1, 1, arrMatch.size() - 1); + args[args.length - 2] = start; + args[args.length - 1] = target; + res.append(Values.toString(ctx, ((FunctionValue)replacement).call(ctx, null, args))); + } + else { + res.append(Values.toString(ctx, replacement)); + } + lastEnd = end; + if (!pattern.global) break; + } + if (lastEnd < target.length()) { + res.append(target.substring(lastEnd)); + } + return res.toString(); + } + // [Symbol.search](target, reverse, start) { // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; // if (!reverse) { @@ -258,6 +271,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; // else return -1; // } // }, + @Native public RegExpLib(Context ctx, Object pattern, Object flags) { this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags)); } diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index f163c81..5e5df1f 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -111,7 +111,7 @@ import me.topchetoeu.jscript.interop.NativeInit; 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) { + @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, Object replacement) { var val = passThis(ctx, "replace", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { @@ -121,9 +121,9 @@ import me.topchetoeu.jscript.interop.NativeInit; } } - return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); + return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement)); } - @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) { + @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, Object replacement) { var val = passThis(ctx, "replaceAll", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { @@ -133,7 +133,7 @@ import me.topchetoeu.jscript.interop.NativeInit; } } - return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); + return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement)); } @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) {