Debugging support #7

Merged
TopchetoEU merged 7 commits from TopcehtoEU/debugging into master 2023-10-28 14:11:55 +00:00
27 changed files with 535 additions and 236 deletions
Showing only changes of commit edb71daef4 - Show all commits

View File

@ -16,14 +16,15 @@
<p>
Here are the available entrypoints:
<ul>
<li><a href="json/version">/json/version</a></li>
<li><a href="json/list">/json/list</a></li>
<li>/(any target)</a></li>
<li><a href="json/version">/json/version</a> - version and other stuff about the JScript engine</li>
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
<li><a href="json/protocol">/json/protocol</a> - documentation of the implemented V8 protocol</li>
<li>/(any target) - websocket entrypoints for debugging</li>
</ul>
</p>
<p>
Developed by TopchetoEU, MIT License
Running ${NAME} v${VERSION} by ${AUTHOR}
</p>
</body>
</html>

View File

@ -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"
}
]
}
]
}
]
}

View File

@ -23,6 +23,7 @@ public class Main {
static Thread engineTask, debugTask;
static Engine engine;
static Environment env;
static int j = 0;
private static Observer<Object> valuePrinter = new Observer<Object>() {
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) {

View File

@ -8,7 +8,7 @@ import me.topchetoeu.jscript.Location;
public class CompileTarget {
public final Vector<Instruction> target = new Vector<>();
public final Map<Long, Instruction[]> functions;
public final Map<Long, FunctionBody> functions;
public final TreeSet<Location> breakpoints;
public Instruction add(Instruction instr) {
@ -31,7 +31,7 @@ public class CompileTarget {
public Instruction[] array() { return target.toArray(Instruction[]::new); }
public CompileTarget(Map<Long, Instruction[]> functions, TreeSet<Location> breakpoints) {
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
this.functions = functions;
this.breakpoints = breakpoints;
}

View File

@ -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) {
@ -37,6 +38,11 @@ public class CompoundStatement extends Statement {
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;

View File

@ -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];
}
}

View File

@ -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) {

View File

@ -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()));

View File

@ -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;

View File

@ -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

View File

@ -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<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId;
public final HashMap<Long, Instruction[]> functions = new HashMap<>();
public final HashMap<Long, FunctionBody> 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;

View File

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

View File

@ -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<String, DebuggerProvider> 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); }
}

View File

@ -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<String, String> 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; }
}
}

View File

@ -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<i;++e)t=t[n[e]];return t}";
private static enum State {
RESUMED,
STEPPING_IN,
@ -84,6 +90,7 @@ public class SimpleDebugger implements Debugger {
this.pattern = pattern;
this.line = line;
this.start = start;
if (condition != null && condition.trim().equals("")) condition = null;
this.condition = condition;
}
}
@ -91,9 +98,10 @@ public class SimpleDebugger implements Debugger {
public CodeFrame frame;
public CodeFunction func;
public int id;
public ObjectValue local = new ObjectValue(), capture = new ObjectValue(), global;
public ObjectValue local, capture, global;
public JSONMap serialized;
public Location location;
public boolean debugData = false;
public void updateLoc(Location loc) {
serialized.set("location", serializeLocation(loc));
@ -108,12 +116,17 @@ public class SimpleDebugger implements Debugger {
this.location = frame.function.loc();
this.global = frame.function.environment.global.obj;
frame.scope.applyToObject(ctx, this.local, this.capture, true);
this.local = frame.getLocalScope(ctx, true);
this.capture = frame.getCaptureScope(ctx, true);
this.local.setPrototype(ctx, capture);
this.capture.setPrototype(ctx, global);
if (location != null) {
debugData = true;
this.serialized = new JSONMap()
.set("callFrameId", id + "")
.set("functionName", func.name)
.set("location", serializeLocation(func.loc()))
.set("location", serializeLocation(location))
.set("scopeChain", new JSONList()
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
.add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
@ -122,8 +135,21 @@ public class SimpleDebugger implements Debugger {
.setNull("this");
}
}
}
public boolean enabled = false;
private class RunResult {
public final Context ctx;
public final Object result;
public final EngineException error;
public RunResult(Context ctx, Object result, EngineException error) {
this.ctx = ctx;
this.result = result;
this.error = error;
}
}
public boolean enabled = true;
public CatchType execptionType = CatchType.ALL;
public State state = State.RESUMED;
@ -145,6 +171,8 @@ public class SimpleDebugger implements Debugger {
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>();
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
private HashMap<ObjectValue, Context> objectToCtx = new HashMap<>();
private HashMap<String, ArrayList<ObjectValue>> 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<Location> 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();

View File

@ -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 {

View File

@ -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;

View File

@ -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;
}

View File

@ -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<ValueVariable> 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;

View File

@ -11,6 +11,9 @@ public class LocalScopeRecord implements ScopeRecord {
private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>();
public String[] captures() {
return captures.toArray(String[]::new);
}
public String[] locals() {
return locals.toArray(String[]::new);
}

View File

@ -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;
}
}

View File

@ -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<String, Integer> 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<Object> 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]);
}
}
}

View File

@ -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<Object> 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<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i);
}

View File

@ -10,6 +10,9 @@ public class JSONList extends ArrayList<JSONElement> {
public JSONList(JSONElement ...els) {
super(List.of(els));
}
public JSONList(Collection<JSONElement> els) {
super(els);
}
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }

View File

@ -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<Object> 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));
}
}

View File

@ -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<String> parseLabel(List<Token> tokens, int i) {
int n = 0;
@ -1876,7 +1877,7 @@ public class Parsing {
return list.toArray(Statement[]::new);
}
public static CodeFunction compile(HashMap<Long, Instruction[]> funcs, TreeSet<Location> breakpoints, Environment environment, Statement ...statements) {
public static CodeFunction compile(HashMap<Long, FunctionBody> funcs, TreeSet<Location> 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<Long, Instruction[]> funcs, TreeSet<Location> breakpoints, Environment environment, Filename filename, String raw) {
public static CodeFunction compile(HashMap<Long, FunctionBody> funcs, TreeSet<Location> 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) }));
}
}
}