diff --git a/src/assets/index.html b/src/assets/index.html index ee35b77..8a57f1f 100644 --- a/src/assets/index.html +++ b/src/assets/index.html @@ -16,14 +16,15 @@

Here are the available entrypoints:

- Developed by TopchetoEU, MIT License + Running ${NAME} v${VERSION} by ${AUTHOR}

\ No newline at end of file diff --git a/src/me/topchetoeu/jscript/engine/debug/protocol.json b/src/assets/protocol.json similarity index 98% rename from src/me/topchetoeu/jscript/engine/debug/protocol.json rename to src/assets/protocol.json index 0470b51..246e736 100644 --- a/src/me/topchetoeu/jscript/engine/debug/protocol.json +++ b/src/assets/protocol.json @@ -1957,6 +1957,51 @@ ] } ] + }, + { + "domain": "NodeWorker", + "description": "Implemented partly just because of pesky vscode.", + "deprecated": true, + "commands": [ + { + "name": "enable", + "description": "Used to get the attachedToWorker event", + "parameters": [ ] + } + ], + "events": [ + { + "name": "attachedToWorker", + "description": "Issued when attached to a worker.", + "parameters": [ + { "name": "sessionId", + "description": "Identifier assigned to the session used to send/receive messages.", + "$ref": "string" + }, + { + "name": "workerInfo", + "type": "object", + "properties": [ + { "name": "workerId", + "$ref": "string" + }, + { "name": "type", + "type": "string" + }, + { "name": "title", + "type": "string" + }, + { "name": "url", + "type": "string" + } + ] + }, + { "name": "waitingForDebugger", + "type": "boolean" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 80c6620..76407f7 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -23,6 +23,7 @@ public class Main { static Thread engineTask, debugTask; static Engine engine; static Environment env; + static int j = 0; private static Observer valuePrinter = new Observer() { public void next(Object data) { @@ -59,7 +60,7 @@ public class Main { env.global.define("go", _ctx -> { try { var f = Path.of("do.js"); - var func = _ctx.compile(Filename.fromFile(f.toFile()), new String(Files.readAllBytes(f))); + var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); return func.call(_ctx); } catch (IOException e) { diff --git a/src/me/topchetoeu/jscript/compilation/CompileTarget.java b/src/me/topchetoeu/jscript/compilation/CompileTarget.java index 4b529cc..1484d19 100644 --- a/src/me/topchetoeu/jscript/compilation/CompileTarget.java +++ b/src/me/topchetoeu/jscript/compilation/CompileTarget.java @@ -8,7 +8,7 @@ import me.topchetoeu.jscript.Location; public class CompileTarget { public final Vector target = new Vector<>(); - public final Map functions; + public final Map functions; public final TreeSet breakpoints; public Instruction add(Instruction instr) { @@ -31,7 +31,7 @@ public class CompileTarget { public Instruction[] array() { return target.toArray(Instruction[]::new); } - public CompileTarget(Map functions, TreeSet breakpoints) { + public CompileTarget(Map functions, TreeSet breakpoints) { this.functions = functions; this.breakpoints = breakpoints; } diff --git a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java index cbee47b..d86a5f9 100644 --- a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -11,6 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class CompoundStatement extends Statement { public final Statement[] statements; + public Location end; @Override public void declare(ScopeRecord varsScope) { @@ -32,11 +33,16 @@ public class CompoundStatement extends Statement { for (var i = 0; i < statements.length; i++) { var stm = statements[i]; - + if (stm instanceof FunctionStatement) continue; if (i != statements.length - 1) stm.compileWithDebug(target, scope, false); else stm.compileWithDebug(target, scope, pollute); } + + if (end != null) { + target.add(Instruction.nop().locate(end)); + target.setDebug(); + } } @Override @@ -59,6 +65,11 @@ public class CompoundStatement extends Statement { else return new CompoundStatement(loc(), res.toArray(Statement[]::new)); } + public CompoundStatement setEnd(Location loc) { + this.end = loc; + return this; + } + public CompoundStatement(Location loc, Statement ...statements) { super(loc); this.statements = statements; diff --git a/src/me/topchetoeu/jscript/compilation/FunctionBody.java b/src/me/topchetoeu/jscript/compilation/FunctionBody.java new file mode 100644 index 0000000..718eeec --- /dev/null +++ b/src/me/topchetoeu/jscript/compilation/FunctionBody.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.compilation; + +public class FunctionBody { + public final Instruction[] instructions; + public final String[] captureNames, localNames; + + public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) { + this.instructions = instructions; + this.captureNames = captureNames; + this.localNames = localNames; + } + public FunctionBody(Instruction[] instructions) { + this.instructions = instructions; + this.captureNames = new String[0]; + this.localNames = new String[0]; + } +} diff --git a/src/me/topchetoeu/jscript/compilation/Instruction.java b/src/me/topchetoeu/jscript/compilation/Instruction.java index 3099de9..0ba8e06 100644 --- a/src/me/topchetoeu/jscript/compilation/Instruction.java +++ b/src/me/topchetoeu/jscript/compilation/Instruction.java @@ -147,14 +147,6 @@ public class Instruction { public static Instruction debug() { return new Instruction(null, Type.NOP, "debug"); } - public static Instruction debugVarNames(String[] names) { - var args = new Object[names.length + 1]; - args[0] = "dbg_vars"; - - System.arraycopy(names, 0, args, 1, names.length); - - return new Instruction(null, Type.NOP, args); - } public static Instruction nop(Object ...params) { for (var param : params) { diff --git a/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java b/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java index 667a982..c51de30 100644 --- a/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java +++ b/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java @@ -10,10 +10,12 @@ public class VariableDeclareStatement extends Statement { public static class Pair { public final String name; public final Statement value; + public final Location location; - public Pair(String name, Statement value) { + public Pair(String name, Statement value, Location location) { this.name = name; this.value = value; + this.location = location; } } @@ -30,16 +32,20 @@ public class VariableDeclareStatement extends Statement { for (var entry : values) { if (entry.name == null) continue; var key = scope.getKey(entry.name); - if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc())); + int start = target.size(); + + if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location)); if (entry.value instanceof FunctionStatement) { ((FunctionStatement)entry.value).compile(target, scope, entry.name, false); - target.add(Instruction.storeVar(key).locate(loc())); + target.add(Instruction.storeVar(key).locate(entry.location)); } else if (entry.value != null) { entry.value.compile(target, scope, true); - target.add(Instruction.storeVar(key).locate(loc())); + target.add(Instruction.storeVar(key).locate(entry.location)); } + + if (target.size() != start) target.setDebug(start); } if (pollute) target.add(Instruction.loadValue(null).locate(loc())); diff --git a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java index db34e44..a14be7d 100644 --- a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java @@ -16,7 +16,7 @@ public class CallStatement extends Statement { ((IndexStatement)func).compile(target, scope, true, true); } else { - target.add(Instruction.loadValue(null).locate(loc())); + target.add(Instruction.loadValue(null).locate(loc())); func.compile(target, scope, true); } diff --git a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java index e75f80e..52c292a 100644 --- a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -5,6 +5,7 @@ import java.util.Random; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompoundStatement; +import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Instruction.Type; @@ -69,7 +70,6 @@ public class FunctionStatement extends Statement { } body.declare(subscope); - funcTarget.add(Instruction.debugVarNames(subscope.locals())); body.compile(funcTarget, subscope, false); funcTarget.add(Instruction.ret().locate(loc())); checkBreakAndCont(funcTarget, start); @@ -77,7 +77,7 @@ public class FunctionStatement extends Statement { var id = rand.nextLong(); target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc())); - target.functions.put(id, funcTarget.array()); + target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals())); if (name == null) name = this.name; diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java index b2494d6..b821cd0 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -20,17 +20,17 @@ public class IndexStatement extends AssignableStatement { return new IndexAssignStatement(loc(), object, index, val, operation); } public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { - int start = 0; object.compile(target, scope, true); if (dupObj) target.add(Instruction.dup().locate(loc())); if (index instanceof ConstantStatement) { target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); + target.setDebug(); return; } index.compile(target, scope, true); target.add(Instruction.loadMember().locate(loc())); - target.setDebug(start); + target.setDebug(); if (!pollute) target.add(Instruction.discard().locate(loc())); } @Override diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index a9d638d..6c8ed51 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -4,7 +4,7 @@ import java.util.HashMap; import java.util.concurrent.LinkedBlockingDeque; import me.topchetoeu.jscript.Filename; -import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.DataNotifier; @@ -51,7 +51,7 @@ public class Engine { private LinkedBlockingDeque microTasks = new LinkedBlockingDeque<>(); public final int id = ++nextId; - public final HashMap functions = new HashMap<>(); + public final HashMap functions = new HashMap<>(); public final Data data = new Data().set(StackData.MAX_FRAMES, 10000); private void runTask(Task task) { @@ -63,8 +63,8 @@ public class Engine { task.notifier.error(e); } } - private void run() { - while (true) { + public void run(boolean untilEmpty) { + while (!untilEmpty || !macroTasks.isEmpty()) { try { runTask(macroTasks.take()); @@ -83,7 +83,7 @@ public class Engine { public Thread start() { if (this.thread == null) { - this.thread = new Thread(this::run, "JavaScript Runner #" + id); + this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id); this.thread.start(); } return this.thread; diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java index b10f961..7d4e2f9 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java @@ -20,4 +20,16 @@ public interface DebugHandler { void stepOver(V8Message msg); void setPauseOnExceptions(V8Message msg); + + void evaluateOnCallFrame(V8Message msg); + + void getProperties(V8Message msg); + void releaseObjectGroup(V8Message msg); + /** + * This method might not execute the actual code for well-known requests + */ + void callFunctionOn(V8Message msg); + + // void nodeWorkerEnable(V8Message msg); + void runtimeEnable(V8Message msg); } diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index 54eade4..c816f3e 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -8,6 +8,7 @@ import java.security.MessageDigest; import java.util.Base64; import java.util.HashMap; +import me.topchetoeu.jscript.Metadata; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.UncheckedException; @@ -17,11 +18,11 @@ import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; public class DebugServer { - public static String browserDisplayName = "jscript"; + public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION; public final HashMap targets = new HashMap<>(); - private final byte[] favicon, index; + private final byte[] favicon, index, protocol; private static void send(HttpRequest req, String val) throws IOException { req.writeResponse(200, "OK", "application/json", val.getBytes()); @@ -58,7 +59,7 @@ public class DebugServer { try { msg = new V8Message(raw.textData()); - System.out.println(msg.name + ": " + JSON.stringify(msg.params)); + // System.out.println(msg.name + ": " + JSON.stringify(msg.params)); } catch (SyntaxException e) { ws.send(new V8Error(e.getMessage())); @@ -86,6 +87,13 @@ public class DebugServer { case "Debugger.stepOver": debugger.stepOver(msg); continue; case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue; + case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue; + + case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue; + case "Runtime.getProperties": debugger.getProperties(msg); continue; + case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue; + // case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue; + case "Runtime.enable": debugger.runtimeEnable(msg); continue; } if ( @@ -96,8 +104,7 @@ public class DebugServer { msg.name.startsWith("Network.") || msg.name.startsWith("Page.") ) ws.send(new V8Error("This isn't a browser...")); - - if (msg.name.startsWith("Runtime.") || msg.name.startsWith("Profiler.")) ws.send(new V8Error("This API is not supported yet.")); + else ws.send(new V8Error("This API is not supported yet.")); } catch (Throwable e) { e.printStackTrace(); @@ -158,7 +165,7 @@ public class DebugServer { switch (req.path) { case "/json/version": - send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}"); + send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}"); break; case "/json/list": case "/json": { @@ -176,9 +183,11 @@ public class DebugServer { send(req, JSON.stringify(res)); break; } + case "/json/protocol": + req.writeResponse(200, "OK", "application/json", protocol); + break; case "/json/new": case "/json/activate": - case "/json/protocol": case "/json/close": case "/devtools/inspector.html": req.writeResponse( @@ -216,7 +225,13 @@ public class DebugServer { public DebugServer() { try { this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes(); - this.index = getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes(); + this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); + var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); + this.index = index + .replace("${NAME}", Metadata.NAME) + .replace("${VERSION}", Metadata.VERSION) + .replace("${AUTHOR}", Metadata.AUTHOR) + .getBytes(); } catch (IOException e) { throw new UncheckedIOException(e); } } diff --git a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java index d327d8e..bff2bdb 100644 --- a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java +++ b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java @@ -10,8 +10,6 @@ import java.util.HashMap; import java.util.IllegalFormatException; import java.util.Map; -import me.topchetoeu.jscript.exceptions.UncheckedIOException; - public class HttpRequest { public final String method; public final String path; @@ -21,19 +19,19 @@ public class HttpRequest { public void writeCode(int code, String name) { try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeHeader(String name, String value) { try { out.write((name + ": " + value + "\r\n").getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeLastHeader(String name, String value) { try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeHeadersEnd() { try { out.write("\n".getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeResponse(int code, String name, String type, byte[] data) { @@ -44,13 +42,13 @@ public class HttpRequest { out.write(data); out.close(); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeResponse(int code, String name, String type, InputStream data) { try { writeResponse(code, name, type, data.readAllBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public HttpRequest(String method, String path, Map headers, OutputStream out) { @@ -98,7 +96,7 @@ public class HttpRequest { return new HttpRequest(method, path, headers, socket.getOutputStream()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException | NullPointerException e) { return null; } } } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index 96dbe93..0e38bd3 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -3,9 +3,11 @@ package me.topchetoeu.jscript.engine.debug; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Random; import java.util.TreeSet; import java.util.regex.Pattern; +import java.util.stream.Collectors; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; @@ -16,6 +18,7 @@ import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; +import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; @@ -24,6 +27,7 @@ import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Notifier; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; @@ -35,6 +39,8 @@ import me.topchetoeu.jscript.lib.SetLib; import me.topchetoeu.jscript.lib.GeneratorLib.Generator; public class SimpleDebugger implements Debugger { + public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e idToObject = new HashMap<>(); private HashMap objectToId = new HashMap<>(); + private HashMap objectToCtx = new HashMap<>(); + private HashMap> objectGroups = new HashMap<>(); private Notifier updateNotifier = new Notifier(); @@ -191,19 +219,19 @@ public class SimpleDebugger implements Debugger { return tail.first(); } - public Location deserializeLocation(JSONElement el, boolean correct) { + private Location deserializeLocation(JSONElement el, boolean correct) { if (!el.isMap()) throw new RuntimeException("Expected location to be a map."); var id = Integer.parseInt(el.map().string("scriptId")); var line = (int)el.map().number("lineNumber") + 1; var column = (int)el.map().number("columnNumber") + 1; - if (!idToSource.containsKey(id)) throw new RuntimeException("The specified source %s doesn't exist.".formatted(id)); + if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id)); var res = new Location(line, column, idToSource.get(id).filename); if (correct) res = correctLocation(idToSource.get(id), res); return res; } - public JSONMap serializeLocation(Location loc) { + private JSONMap serializeLocation(Location loc) { var source = filenameToId.get(loc.filename()); return new JSONMap() .set("scriptId", source + "") @@ -211,21 +239,21 @@ public class SimpleDebugger implements Debugger { .set("columnNumber", loc.start() - 1); } - private Integer objectId(ObjectValue obj) { + private Integer objectId(Context ctx, ObjectValue obj) { if (objectToId.containsKey(obj)) return objectToId.get(obj); else { int id = nextId(); objectToId.put(obj, id); + if (ctx != null) objectToCtx.put(obj, ctx); idToObject.put(id, obj); return id; } } - private JSONMap serializeObj(Context ctx, Object val) { + private JSONMap serializeObj(Context ctx, Object val, boolean recurse) { val = Values.normalize(null, val); if (val == Values.NULL) { return new JSONMap() - .set("objectId", objectId(null) + "") .set("type", "object") .set("subtype", "null") .setNull("value") @@ -234,13 +262,14 @@ public class SimpleDebugger implements Debugger { if (val instanceof ObjectValue) { var obj = (ObjectValue)val; - var id = objectId(obj); + var id = objectId(ctx, obj); var type = "object"; String subtype = null; String className = null; if (obj instanceof FunctionValue) type = "function"; if (obj instanceof ArrayValue) subtype = "array"; + if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp"; if (Values.isWrapper(val, DateLib.class)) subtype = "date"; if (Values.isWrapper(val, MapLib.class)) subtype = "map"; @@ -253,10 +282,13 @@ public class SimpleDebugger implements Debugger { var res = new JSONMap() .set("type", type) - .set("objetId", id + ""); + .set("objectId", id + ""); if (subtype != null) res.set("subtype", subtype); - if (className != null) res.set("className", className); + if (className != null) { + res.set("className", className); + res.set("description", "{ " + className + " }"); + } return res; } @@ -271,7 +303,7 @@ public class SimpleDebugger implements Debugger { if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity"); else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity"); - else if (num == -0.) res.set("unserializableValue", "-0"); + else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0"); else if (Double.isNaN(num)) res.set("unserializableValue", "NaN"); else res.set("value", num); @@ -280,6 +312,37 @@ public class SimpleDebugger implements Debugger { throw new IllegalArgumentException("Unexpected JS object."); } + private JSONMap serializeObj(Context ctx, Object val) { + return serializeObj(ctx, val, true); + } + private void setObjectGroup(String name, Object val) { + if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + + if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj); + else objectGroups.put(name, new ArrayList<>(List.of(obj))); + } + } + private void releaseGroup(String name) { + var objs = objectGroups.remove(name); + + if (objs != null) { + for (var obj : objs) { + var id = objectToId.remove(obj); + if (id != null) idToObject.remove(id); + } + } + } + private Object deserializeArgument(JSONMap val) { + if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))); + else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) { + case "NaN": return Double.NaN; + case "-Infinity": return Double.NEGATIVE_INFINITY; + case "Infinity": return Double.POSITIVE_INFINITY; + case "-0": return -0.; + } + return JSON.toJs(val.get("value")); + } private void resume(State state) { this.state = state; @@ -296,7 +359,7 @@ public class SimpleDebugger implements Debugger { ws.send(new V8Event("Debugger.paused", map)); } private void pauseException(Context ctx) { - state = State.PAUSED_NORMAL; + state = State.PAUSED_EXCEPTION; var map = new JSONMap() .set("callFrames", serializeFrames(ctx)) .set("reason", "exception"); @@ -322,6 +385,27 @@ public class SimpleDebugger implements Debugger { )); } + private RunResult run(Frame codeFrame, String code) { + var engine = new Engine(); + var env = codeFrame.func.environment.fork(); + + ObjectValue global = env.global.obj, + capture = codeFrame.frame.getCaptureScope(null, enabled), + local = codeFrame.frame.getLocalScope(null, enabled); + + capture.setPrototype(null, global); + local.setPrototype(null, capture); + env.global = new GlobalScope(local); + + var ctx = new Context(engine).pushEnv(env); + var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), code, codeFrame.frame.thisArg, codeFrame.frame.args); + + engine.run(true); + + try { return new RunResult(ctx, awaiter.await(), null); } + catch (EngineException e) { return new RunResult(ctx, null, e); } + } + @Override public void enable(V8Message msg) { enabled = true; ws.send(msg.respond()); @@ -342,9 +426,9 @@ public class SimpleDebugger implements Debugger { ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); } @Override public void getPossibleBreakpoints(V8Message msg) { + var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId"))); var start = deserializeLocation(msg.params.get("start"), false); var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null; - var src = idToSource.get(filenameToId.get(start.filename())); var res = new JSONList(); @@ -464,6 +548,101 @@ public class SimpleDebugger implements Debugger { } } + @Override public void evaluateOnCallFrame(V8Message msg) { + var cfId = Integer.parseInt(msg.params.string("callFrameId")); + var expr = msg.params.string("expression"); + var group = msg.params.string("objectGroup", null); + + var cf = idToFrame.get(cfId); + var res = run(cf, expr); + + if (group != null) setObjectGroup(group, res.result); + + ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result)))); + } + + @Override public void releaseObjectGroup(V8Message msg) { + var group = msg.params.string("objectGroup"); + releaseGroup(group); + ws.send(msg.respond()); + } + @Override public void getProperties(V8Message msg) { + var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var own = msg.params.bool("ownProperties") || true; + var currOwn = true; + + var res = new JSONList(); + + while (obj != null) { + 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", currOwn); + 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", currOwn); + res.add(propDesc); + } + } + + obj = obj.getPrototype(ctx); + + var protoDesc = new JSONMap(); + protoDesc.set("name", "__proto__"); + protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj)); + protoDesc.set("writable", true); + protoDesc.set("enumerable", false); + protoDesc.set("configurable", false); + protoDesc.set("isOwn", currOwn); + res.add(protoDesc); + + currOwn = false; + if (own) break; + } + + ws.send(msg.respond(new JSONMap().set("result", res))); + } + @Override public void callFunctionOn(V8Message msg) { + var src = msg.params.string("functionDeclaration"); + var args = msg.params.list("arguments", new JSONList()).stream().map(v -> v.map()).map(this::deserializeArgument).collect(Collectors.toList()); + var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var ctx = objectToCtx.get(thisArg); + + switch (src) { + case CHROME_GET_PROP_FUNC: { + var path = JSON.parse(new Filename("tmp", "json"), (String)args.get(0)).list(); + Object res = thisArg; + for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el)); + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); + return; + } + default: + ws.send(new V8Error("A non well-known function was used with callFunctionOn.")); + break; + } + } + + @Override + public void runtimeEnable(V8Message msg) { + ws.send(msg.respond()); + } + @Override public void onSource(Filename filename, String source, TreeSet locations) { int id = nextId(); var src = new Source(id, filename, source, locations); @@ -488,6 +667,9 @@ public class SimpleDebugger implements Debugger { updateFrames(ctx); var frame = codeFrameToFrame.get(cf); + + if (!frame.debugData) return false; + if (instruction.location != null) frame.updateLoc(instruction.location); var loc = frame.location; var isBreakpointable = loc != null && ( @@ -501,15 +683,12 @@ public class SimpleDebugger implements Debugger { pauseException(ctx); } else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { - pauseDebug(ctx, locToBreakpoint.get(loc)); - } - else if (isBreakpointable && tmpBreakpts.contains(loc)) { - pauseDebug(ctx, null); - tmpBreakpts.remove(loc); - } - else if (instruction.type == Type.NOP && instruction.match("debug")) { - pauseDebug(ctx, null); + var bp = locToBreakpoint.get(loc); + var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition)); + if (ok) pauseDebug(ctx, locToBreakpoint.get(loc)); } + else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); + else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null); while (enabled) { switch (state) { @@ -527,12 +706,17 @@ public class SimpleDebugger implements Debugger { else return false; break; case STEPPING_OVER: - if ( - stepOutFrame == frame && ( + if (stepOutFrame.frame == frame.frame) { + if (returnVal != Runners.NO_RETURN) { + state = State.STEPPING_OUT; + return false; + } + else if (isBreakpointable && ( !loc.filename().equals(prevLocation.filename()) || loc.line() != prevLocation.line() - ) - ) pauseDebug(ctx, null); + )) pauseDebug(ctx, null); + else return false; + } else return false; break; } @@ -544,14 +728,12 @@ public class SimpleDebugger implements Debugger { @Override public void onFramePop(Context ctx, CodeFrame frame) { updateFrames(ctx); - try { - idToFrame.remove(codeFrameToFrame.remove(frame).id); - } + try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } catch (NullPointerException e) { } if (StackData.frames(ctx).size() == 0) resume(State.RESUMED); else if (stepOutFrame != null && stepOutFrame.frame == frame && - (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) + (state == State.STEPPING_OUT || state == State.STEPPING_IN) ) { pauseDebug(ctx, null); updateNotifier.await(); diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java index 15001ba..6e6efa9 100644 --- a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java +++ b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java @@ -7,7 +7,6 @@ import java.io.OutputStream; import java.net.Socket; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; -import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.UncheckedIOException; public class WebSocket implements AutoCloseable { diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 3fb3d17..1e2b4c7 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -10,6 +10,8 @@ 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.ScopeValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; @@ -56,6 +58,33 @@ public class CodeFrame { public boolean jumpFlag = false; private Location prevLoc = null; + public ObjectValue getLocalScope(Context ctx, boolean props) { + var names = new String[scope.locals.length]; + + for (int i = 0; i < scope.locals.length; i++) { + var name = "local_" + (i - 2); + + if (i == 0) name = "this"; + else if (i == 1) name = "arguments"; + else if (i < function.localNames.length) name = function.localNames[i]; + + names[i] = name; + } + + return new ScopeValue(scope.locals, names); + } + public ObjectValue getCaptureScope(Context ctx, boolean props) { + var names = new String[scope.captures.length]; + + for (int i = 0; i < scope.captures.length; i++) { + var name = "capture_" + (i - 2); + if (i < function.captureNames.length) name = function.captureNames[i]; + names[i] = name; + } + + return new ScopeValue(scope.captures, names); + } + public void addTry(int n, int catchN, int finallyN) { var res = new TryCtx(codePtr + 1, n, catchN, finallyN); @@ -144,7 +173,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; @@ -215,6 +244,7 @@ public class CodeFrame { case TryCtx.STATE_CATCH: scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); codePtr = tryCtx.catchStart; + if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, true); break; default: codePtr = tryCtx.finallyStart; diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 4fac093..0c5366d 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -301,15 +301,6 @@ public class Runners { return NO_RETURN; } public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) { - 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."); - names[i] = (String)instr.params[i + 1]; - } - frame.scope.setNames(names); - } - frame.codePtr++; return NO_RETURN; } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index bccbcdb..7645aaf 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -2,12 +2,7 @@ package me.topchetoeu.jscript.engine.scope; import java.util.ArrayList; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.NativeFunction; -import me.topchetoeu.jscript.engine.values.ObjectValue; - public class LocalScope { - private String[] names; public final ValueVariable[] captures; public final ValueVariable[] locals; public final ArrayList catchVars = new ArrayList<>(); @@ -19,70 +14,11 @@ public class LocalScope { else return captures[~i]; } - public String[] getCaptureNames() { - var res = new String[captures.length]; - - for (int i = 0; i < captures.length; i++) { - if (names == null || i >= names.length) res[i] = "capture_" + (i); - else res[i] = names[i]; - } - - return res; - } - public String[] getLocalNames() { - var res = new String[locals.length]; - - for (int i = captures.length, j = 0; i < locals.length; i++, j++) { - if (names == null || i >= names.length) { - if (j == 0) res[j] = "this"; - else if (j == 1) res[j] = "arguments"; - else res[i] = "local_" + (j - 2); - } - else res[i] = names[i]; - } - - return res; - } - public void setNames(String[] names) { - this.names = names; - } public int size() { return captures.length + locals.length; } - public void applyToObject(Context ctx, ObjectValue locals, ObjectValue captures, boolean props) { - var localNames = getLocalNames(); - var captureNames = getCaptureNames(); - - for (var i = 0; i < this.locals.length; i++) { - var name = localNames[i]; - var _i = i; - - if (props) locals.defineProperty(ctx, name, - new NativeFunction(name, (_ctx, thisArg, args) -> this.locals[_i].get(_ctx)), - this.locals[i].readonly ? null : - new NativeFunction(name, (_ctx, thisArg, args) -> { this.locals[_i].set(_ctx, args.length < 1 ? null : args[0]); return null; }), - true, true - ); - else locals.defineProperty(ctx, name, this.locals[i].get(ctx)); - } - for (var i = 0; i < this.captures.length; i++) { - var name = captureNames[i]; - var _i = i; - - if (props) captures.defineProperty(ctx, name, - new NativeFunction(name, (_ctx, thisArg, args) -> this.captures[_i].get(_ctx)), - this.captures[i].readonly ? null : - new NativeFunction(name, (_ctx, thisArg, args) -> { this.captures[_i].set(_ctx, args.length < 1 ? null : args[0]); return null; }), - true, true - ); - else captures.defineProperty(ctx, name, this.captures[i].get(ctx)); - } - - captures.setPrototype(ctx, locals); - } - public LocalScope(int n, ValueVariable[] captures) { locals = new ValueVariable[n]; this.captures = captures; diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java index 12b7d8c..d196716 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java @@ -11,6 +11,9 @@ public class LocalScopeRecord implements ScopeRecord { private final ArrayList captures = new ArrayList<>(); private final ArrayList locals = new ArrayList<>(); + public String[] captures() { + return captures.toArray(String[]::new); + } public String[] locals() { return locals.toArray(String[]::new); } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index 8b1b924..e2dc2bf 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.engine.values; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; @@ -13,6 +14,7 @@ public class CodeFunction extends FunctionValue { public final int localsN; public final int length; public final Instruction[] body; + public final String[] captureNames, localNames; public final ValueVariable[] captures; public Environment environment; @@ -45,12 +47,14 @@ public class CodeFunction extends FunctionValue { } } - public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { + public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) { super(name, length); this.captures = captures; + this.captureNames = body.captureNames; + this.localNames = body.localNames; this.environment = environment; this.localsN = localsN; this.length = length; - this.body = body; + this.body = body.instructions; } } diff --git a/src/me/topchetoeu/jscript/engine/values/ScopeValue.java b/src/me/topchetoeu/jscript/engine/values/ScopeValue.java new file mode 100644 index 0000000..768e3c2 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/values/ScopeValue.java @@ -0,0 +1,51 @@ +package me.topchetoeu.jscript.engine.values; + +import java.util.HashMap; +import java.util.List; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.scope.ValueVariable; + +public class ScopeValue extends ObjectValue { + public final ValueVariable[] variables; + public final HashMap names = new HashMap<>(); + + @Override + protected Object getField(Context ctx, Object key) { + if (names.containsKey(key)) return variables[names.get(key)].get(ctx); + return super.getField(ctx, key); + } + @Override + protected boolean setField(Context ctx, Object key, Object val) { + if (names.containsKey(key)) { + variables[names.get(key)].set(ctx, val); + return true; + } + + return super.setField(ctx, key, val); + } + @Override + protected void deleteField(Context ctx, Object key) { + if (names.containsKey(key)) return; + super.deleteField(ctx, key); + } + @Override + protected boolean hasField(Context ctx, Object key) { + if (names.containsKey(key)) return true; + return super.hasField(ctx, key); + } + @Override + public List keys(boolean includeNonEnumerable) { + var res = super.keys(includeNonEnumerable); + res.addAll(names.keySet()); + return res; + } + + public ScopeValue(ValueVariable[] variables, String[] names) { + this.variables = variables; + for (var i = 0; i < names.length && i < variables.length; i++) { + this.names.put(names[i], i); + this.nonConfigurableSet.add(names[i]); + } + } +} diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 8e807f7..7d58bf4 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -1,9 +1,15 @@ package me.topchetoeu.jscript.json; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; import me.topchetoeu.jscript.Filename; +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.parsing.Operator; import me.topchetoeu.jscript.parsing.ParseRes; @@ -11,6 +17,63 @@ import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.parsing.Token; public class JSON { + public 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 fromJs(Context ctx, Object val, HashSet prev) { + 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 = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev); + if (jsonEl == null) continue; + if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val instanceof ArrayValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONList(); + + for (var el : ((ArrayValue)val).toArray()) { + var jsonEl = fromJs(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; + } + public static JSONElement fromJs(Context ctx, Object val) { + return fromJs(ctx, val, new HashSet<>()); + } + public static ParseRes parseIdentifier(List tokens, int i) { return Parsing.parseIdentifier(tokens, i); } diff --git a/src/me/topchetoeu/jscript/json/JSONList.java b/src/me/topchetoeu/jscript/json/JSONList.java index b73eb49..214ca40 100644 --- a/src/me/topchetoeu/jscript/json/JSONList.java +++ b/src/me/topchetoeu/jscript/json/JSONList.java @@ -10,6 +10,9 @@ public class JSONList extends ArrayList { public JSONList(JSONElement ...els) { super(List.of(els)); } + public JSONList(Collection els) { + super(els); + } public JSONList addNull() { this.add(JSONElement.NULL); return this; } public JSONList add(String val) { this.add(JSONElement.of(val)); return this; } diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java index d8b202e..8ea57d5 100644 --- a/src/me/topchetoeu/jscript/lib/JSONLib.java +++ b/src/me/topchetoeu/jscript/lib/JSONLib.java @@ -1,84 +1,22 @@ package me.topchetoeu.jscript.lib; -import java.util.HashSet; -import java.util.stream.Collectors; - import me.topchetoeu.jscript.Filename; 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; +import me.topchetoeu.jscript.json.JSON; public class JSONLib { - 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(JSONLib::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) { - 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) { try { - return toJS(me.topchetoeu.jscript.json.JSON.parse(new Filename("jscript", "json"), val)); + return JSON.toJs(JSON.parse(new Filename("jscript", "json"), val)); } catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } } @Native public static String stringify(Context ctx, Object val) { - return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>())); + return me.topchetoeu.jscript.json.JSON.stringify(JSON.fromJs(ctx, val)); } } diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index 1fea791..5411fe4 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -1367,6 +1367,7 @@ public class Parsing { } while (true) { + var nameLoc = getLoc(filename, tokens, i + n); var nameRes = parseIdentifier(tokens, i + n++); if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name."); @@ -1384,7 +1385,7 @@ public class Parsing { val = valRes.result; } - res.add(new Pair(nameRes.result, val)); + res.add(new Pair(nameRes.result, val, nameLoc)); if (isOperator(tokens, i + n, Operator.COMMA)) { n++; @@ -1512,7 +1513,7 @@ public class Parsing { statements.add(res.result); } - return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)), n); + return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); } public static ParseRes parseLabel(List tokens, int i) { int n = 0; @@ -1876,7 +1877,7 @@ public class Parsing { return list.toArray(Statement[]::new); } - public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Statement ...statements) { + public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Statement ...statements) { var target = environment.global.globalChild(); var subscope = target.child(); var res = new CompileTarget(funcs, breakpoints); @@ -1903,14 +1904,14 @@ public class Parsing { } else res.add(Instruction.ret()); - return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.array()); + return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals())); } - public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Filename filename, String raw) { + public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Filename filename, String raw) { try { return compile(funcs, breakpoints, environment, parse(filename, raw)); } catch (SyntaxException e) { - return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) }); + return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new FunctionBody(new Instruction[] { Instruction.throwSyntax(e).locate(e.loc) })); } } }