feat: fully implement local and capture scope object wrappers
feat: implement object sending and receiving
This commit is contained in:
parent
4b84309df6
commit
edb71daef4
@ -16,14 +16,15 @@
|
|||||||
<p>
|
<p>
|
||||||
Here are the available entrypoints:
|
Here are the available entrypoints:
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="json/version">/json/version</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></li>
|
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
|
||||||
<li>/(any target)</a></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>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Developed by TopchetoEU, MIT License
|
Running ${NAME} v${VERSION} by ${AUTHOR}
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -23,6 +23,7 @@ public class Main {
|
|||||||
static Thread engineTask, debugTask;
|
static Thread engineTask, debugTask;
|
||||||
static Engine engine;
|
static Engine engine;
|
||||||
static Environment env;
|
static Environment env;
|
||||||
|
static int j = 0;
|
||||||
|
|
||||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||||
public void next(Object data) {
|
public void next(Object data) {
|
||||||
@ -59,7 +60,7 @@ public class Main {
|
|||||||
env.global.define("go", _ctx -> {
|
env.global.define("go", _ctx -> {
|
||||||
try {
|
try {
|
||||||
var f = Path.of("do.js");
|
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);
|
return func.call(_ctx);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
|
@ -8,7 +8,7 @@ import me.topchetoeu.jscript.Location;
|
|||||||
|
|
||||||
public class CompileTarget {
|
public class CompileTarget {
|
||||||
public final Vector<Instruction> target = new Vector<>();
|
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 final TreeSet<Location> breakpoints;
|
||||||
|
|
||||||
public Instruction add(Instruction instr) {
|
public Instruction add(Instruction instr) {
|
||||||
@ -31,7 +31,7 @@ public class CompileTarget {
|
|||||||
|
|
||||||
public Instruction[] array() { return target.toArray(Instruction[]::new); }
|
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.functions = functions;
|
||||||
this.breakpoints = breakpoints;
|
this.breakpoints = breakpoints;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
|||||||
|
|
||||||
public class CompoundStatement extends Statement {
|
public class CompoundStatement extends Statement {
|
||||||
public final Statement[] statements;
|
public final Statement[] statements;
|
||||||
|
public Location end;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void declare(ScopeRecord varsScope) {
|
public void declare(ScopeRecord varsScope) {
|
||||||
@ -32,11 +33,16 @@ public class CompoundStatement extends Statement {
|
|||||||
|
|
||||||
for (var i = 0; i < statements.length; i++) {
|
for (var i = 0; i < statements.length; i++) {
|
||||||
var stm = statements[i];
|
var stm = statements[i];
|
||||||
|
|
||||||
if (stm instanceof FunctionStatement) continue;
|
if (stm instanceof FunctionStatement) continue;
|
||||||
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
|
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
|
||||||
else stm.compileWithDebug(target, scope, pollute);
|
else stm.compileWithDebug(target, scope, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (end != null) {
|
||||||
|
target.add(Instruction.nop().locate(end));
|
||||||
|
target.setDebug();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -59,6 +65,11 @@ public class CompoundStatement extends Statement {
|
|||||||
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
|
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) {
|
public CompoundStatement(Location loc, Statement ...statements) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.statements = statements;
|
this.statements = statements;
|
||||||
|
17
src/me/topchetoeu/jscript/compilation/FunctionBody.java
Normal file
17
src/me/topchetoeu/jscript/compilation/FunctionBody.java
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
@ -147,14 +147,6 @@ public class Instruction {
|
|||||||
public static Instruction debug() {
|
public static Instruction debug() {
|
||||||
return new Instruction(null, Type.NOP, "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) {
|
public static Instruction nop(Object ...params) {
|
||||||
for (var param : params) {
|
for (var param : params) {
|
||||||
|
@ -10,10 +10,12 @@ public class VariableDeclareStatement extends Statement {
|
|||||||
public static class Pair {
|
public static class Pair {
|
||||||
public final String name;
|
public final String name;
|
||||||
public final Statement value;
|
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.name = name;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.location = location;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,16 +32,20 @@ public class VariableDeclareStatement extends Statement {
|
|||||||
for (var entry : values) {
|
for (var entry : values) {
|
||||||
if (entry.name == null) continue;
|
if (entry.name == null) continue;
|
||||||
var key = scope.getKey(entry.name);
|
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) {
|
if (entry.value instanceof FunctionStatement) {
|
||||||
((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
|
((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) {
|
else if (entry.value != null) {
|
||||||
entry.value.compile(target, scope, true);
|
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()));
|
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||||
|
@ -16,7 +16,7 @@ public class CallStatement extends Statement {
|
|||||||
((IndexStatement)func).compile(target, scope, true, true);
|
((IndexStatement)func).compile(target, scope, true, true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
target.add(Instruction.loadValue(null).locate(loc()));
|
target.add(Instruction.loadValue(null).locate(loc()));
|
||||||
func.compile(target, scope, true);
|
func.compile(target, scope, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import java.util.Random;
|
|||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||||
|
import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.compilation.Statement;
|
import me.topchetoeu.jscript.compilation.Statement;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||||
@ -69,7 +70,6 @@ public class FunctionStatement extends Statement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.declare(subscope);
|
body.declare(subscope);
|
||||||
funcTarget.add(Instruction.debugVarNames(subscope.locals()));
|
|
||||||
body.compile(funcTarget, subscope, false);
|
body.compile(funcTarget, subscope, false);
|
||||||
funcTarget.add(Instruction.ret().locate(loc()));
|
funcTarget.add(Instruction.ret().locate(loc()));
|
||||||
checkBreakAndCont(funcTarget, start);
|
checkBreakAndCont(funcTarget, start);
|
||||||
@ -77,7 +77,7 @@ public class FunctionStatement extends Statement {
|
|||||||
var id = rand.nextLong();
|
var id = rand.nextLong();
|
||||||
|
|
||||||
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
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;
|
if (name == null) name = this.name;
|
||||||
|
|
||||||
|
@ -20,17 +20,17 @@ public class IndexStatement extends AssignableStatement {
|
|||||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
return new IndexAssignStatement(loc(), object, index, val, operation);
|
||||||
}
|
}
|
||||||
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
||||||
int start = 0;
|
|
||||||
object.compile(target, scope, true);
|
object.compile(target, scope, true);
|
||||||
if (dupObj) target.add(Instruction.dup().locate(loc()));
|
if (dupObj) target.add(Instruction.dup().locate(loc()));
|
||||||
if (index instanceof ConstantStatement) {
|
if (index instanceof ConstantStatement) {
|
||||||
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
||||||
|
target.setDebug();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
index.compile(target, scope, true);
|
index.compile(target, scope, true);
|
||||||
target.add(Instruction.loadMember().locate(loc()));
|
target.add(Instruction.loadMember().locate(loc()));
|
||||||
target.setDebug(start);
|
target.setDebug();
|
||||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,7 +4,7 @@ import java.util.HashMap;
|
|||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Filename;
|
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.engine.values.FunctionValue;
|
||||||
import me.topchetoeu.jscript.events.Awaitable;
|
import me.topchetoeu.jscript.events.Awaitable;
|
||||||
import me.topchetoeu.jscript.events.DataNotifier;
|
import me.topchetoeu.jscript.events.DataNotifier;
|
||||||
@ -51,7 +51,7 @@ public class Engine {
|
|||||||
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
||||||
|
|
||||||
public final int id = ++nextId;
|
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);
|
public final Data data = new Data().set(StackData.MAX_FRAMES, 10000);
|
||||||
|
|
||||||
private void runTask(Task task) {
|
private void runTask(Task task) {
|
||||||
@ -63,8 +63,8 @@ public class Engine {
|
|||||||
task.notifier.error(e);
|
task.notifier.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void run() {
|
public void run(boolean untilEmpty) {
|
||||||
while (true) {
|
while (!untilEmpty || !macroTasks.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
runTask(macroTasks.take());
|
runTask(macroTasks.take());
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ public class Engine {
|
|||||||
|
|
||||||
public Thread start() {
|
public Thread start() {
|
||||||
if (this.thread == null) {
|
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();
|
this.thread.start();
|
||||||
}
|
}
|
||||||
return this.thread;
|
return this.thread;
|
||||||
|
@ -20,4 +20,16 @@ public interface DebugHandler {
|
|||||||
void stepOver(V8Message msg);
|
void stepOver(V8Message msg);
|
||||||
|
|
||||||
void setPauseOnExceptions(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);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import java.security.MessageDigest;
|
|||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.Metadata;
|
||||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||||
@ -17,11 +18,11 @@ import me.topchetoeu.jscript.json.JSONList;
|
|||||||
import me.topchetoeu.jscript.json.JSONMap;
|
import me.topchetoeu.jscript.json.JSONMap;
|
||||||
|
|
||||||
public class DebugServer {
|
public class DebugServer {
|
||||||
public static String browserDisplayName = "jscript";
|
public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION;
|
||||||
|
|
||||||
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
|
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 {
|
private static void send(HttpRequest req, String val) throws IOException {
|
||||||
req.writeResponse(200, "OK", "application/json", val.getBytes());
|
req.writeResponse(200, "OK", "application/json", val.getBytes());
|
||||||
@ -58,7 +59,7 @@ public class DebugServer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
msg = new V8Message(raw.textData());
|
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) {
|
catch (SyntaxException e) {
|
||||||
ws.send(new V8Error(e.getMessage()));
|
ws.send(new V8Error(e.getMessage()));
|
||||||
@ -86,6 +87,13 @@ public class DebugServer {
|
|||||||
case "Debugger.stepOver": debugger.stepOver(msg); continue;
|
case "Debugger.stepOver": debugger.stepOver(msg); continue;
|
||||||
|
|
||||||
case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(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 (
|
if (
|
||||||
@ -96,8 +104,7 @@ public class DebugServer {
|
|||||||
msg.name.startsWith("Network.") ||
|
msg.name.startsWith("Network.") ||
|
||||||
msg.name.startsWith("Page.")
|
msg.name.startsWith("Page.")
|
||||||
) ws.send(new V8Error("This isn't a browser..."));
|
) ws.send(new V8Error("This isn't a browser..."));
|
||||||
|
else ws.send(new V8Error("This API is not supported yet."));
|
||||||
if (msg.name.startsWith("Runtime.") || msg.name.startsWith("Profiler.")) ws.send(new V8Error("This API is not supported yet."));
|
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -158,7 +165,7 @@ public class DebugServer {
|
|||||||
|
|
||||||
switch (req.path) {
|
switch (req.path) {
|
||||||
case "/json/version":
|
case "/json/version":
|
||||||
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}");
|
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
|
||||||
break;
|
break;
|
||||||
case "/json/list":
|
case "/json/list":
|
||||||
case "/json": {
|
case "/json": {
|
||||||
@ -176,9 +183,11 @@ public class DebugServer {
|
|||||||
send(req, JSON.stringify(res));
|
send(req, JSON.stringify(res));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "/json/protocol":
|
||||||
|
req.writeResponse(200, "OK", "application/json", protocol);
|
||||||
|
break;
|
||||||
case "/json/new":
|
case "/json/new":
|
||||||
case "/json/activate":
|
case "/json/activate":
|
||||||
case "/json/protocol":
|
|
||||||
case "/json/close":
|
case "/json/close":
|
||||||
case "/devtools/inspector.html":
|
case "/devtools/inspector.html":
|
||||||
req.writeResponse(
|
req.writeResponse(
|
||||||
@ -216,7 +225,13 @@ public class DebugServer {
|
|||||||
public DebugServer() {
|
public DebugServer() {
|
||||||
try {
|
try {
|
||||||
this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes();
|
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); }
|
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ import java.util.HashMap;
|
|||||||
import java.util.IllegalFormatException;
|
import java.util.IllegalFormatException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
|
||||||
|
|
||||||
public class HttpRequest {
|
public class HttpRequest {
|
||||||
public final String method;
|
public final String method;
|
||||||
public final String path;
|
public final String path;
|
||||||
@ -21,19 +19,19 @@ public class HttpRequest {
|
|||||||
|
|
||||||
public void writeCode(int code, String name) {
|
public void writeCode(int code, String name) {
|
||||||
try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); }
|
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) {
|
public void writeHeader(String name, String value) {
|
||||||
try { out.write((name + ": " + value + "\r\n").getBytes()); }
|
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) {
|
public void writeLastHeader(String name, String value) {
|
||||||
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
|
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
|
||||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
catch (IOException e) { }
|
||||||
}
|
}
|
||||||
public void writeHeadersEnd() {
|
public void writeHeadersEnd() {
|
||||||
try { out.write("\n".getBytes()); }
|
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) {
|
public void writeResponse(int code, String name, String type, byte[] data) {
|
||||||
@ -44,13 +42,13 @@ public class HttpRequest {
|
|||||||
out.write(data);
|
out.write(data);
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
catch (IOException e) { }
|
||||||
}
|
}
|
||||||
public void writeResponse(int code, String name, String type, InputStream data) {
|
public void writeResponse(int code, String name, String type, InputStream data) {
|
||||||
try {
|
try {
|
||||||
writeResponse(code, name, type, data.readAllBytes());
|
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) {
|
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());
|
return new HttpRequest(method, path, headers, socket.getOutputStream());
|
||||||
}
|
}
|
||||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
catch (IOException | NullPointerException e) { return null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,11 @@ package me.topchetoeu.jscript.engine.debug;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Filename;
|
import me.topchetoeu.jscript.Filename;
|
||||||
import me.topchetoeu.jscript.Location;
|
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.StackData;
|
||||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
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.ArrayValue;
|
||||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
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.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.events.Notifier;
|
import me.topchetoeu.jscript.events.Notifier;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.json.JSON;
|
||||||
import me.topchetoeu.jscript.json.JSONElement;
|
import me.topchetoeu.jscript.json.JSONElement;
|
||||||
import me.topchetoeu.jscript.json.JSONList;
|
import me.topchetoeu.jscript.json.JSONList;
|
||||||
import me.topchetoeu.jscript.json.JSONMap;
|
import me.topchetoeu.jscript.json.JSONMap;
|
||||||
@ -35,6 +39,8 @@ import me.topchetoeu.jscript.lib.SetLib;
|
|||||||
import me.topchetoeu.jscript.lib.GeneratorLib.Generator;
|
import me.topchetoeu.jscript.lib.GeneratorLib.Generator;
|
||||||
|
|
||||||
public class SimpleDebugger implements Debugger {
|
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 {
|
private static enum State {
|
||||||
RESUMED,
|
RESUMED,
|
||||||
STEPPING_IN,
|
STEPPING_IN,
|
||||||
@ -84,6 +90,7 @@ public class SimpleDebugger implements Debugger {
|
|||||||
this.pattern = pattern;
|
this.pattern = pattern;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
|
if (condition != null && condition.trim().equals("")) condition = null;
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,9 +98,10 @@ public class SimpleDebugger implements Debugger {
|
|||||||
public CodeFrame frame;
|
public CodeFrame frame;
|
||||||
public CodeFunction func;
|
public CodeFunction func;
|
||||||
public int id;
|
public int id;
|
||||||
public ObjectValue local = new ObjectValue(), capture = new ObjectValue(), global;
|
public ObjectValue local, capture, global;
|
||||||
public JSONMap serialized;
|
public JSONMap serialized;
|
||||||
public Location location;
|
public Location location;
|
||||||
|
public boolean debugData = false;
|
||||||
|
|
||||||
public void updateLoc(Location loc) {
|
public void updateLoc(Location loc) {
|
||||||
serialized.set("location", serializeLocation(loc));
|
serialized.set("location", serializeLocation(loc));
|
||||||
@ -108,22 +116,40 @@ public class SimpleDebugger implements Debugger {
|
|||||||
this.location = frame.function.loc();
|
this.location = frame.function.loc();
|
||||||
|
|
||||||
this.global = frame.function.environment.global.obj;
|
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);
|
||||||
|
|
||||||
this.serialized = new JSONMap()
|
if (location != null) {
|
||||||
.set("callFrameId", id + "")
|
debugData = true;
|
||||||
.set("functionName", func.name)
|
this.serialized = new JSONMap()
|
||||||
.set("location", serializeLocation(func.loc()))
|
.set("callFrameId", id + "")
|
||||||
.set("scopeChain", new JSONList()
|
.set("functionName", func.name)
|
||||||
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
|
.set("location", serializeLocation(location))
|
||||||
.add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
|
.set("scopeChain", new JSONList()
|
||||||
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
|
.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)))
|
||||||
.setNull("this");
|
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
|
||||||
|
)
|
||||||
|
.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 CatchType execptionType = CatchType.ALL;
|
||||||
public State state = State.RESUMED;
|
public State state = State.RESUMED;
|
||||||
|
|
||||||
@ -145,6 +171,8 @@ public class SimpleDebugger implements Debugger {
|
|||||||
|
|
||||||
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>();
|
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>();
|
||||||
private HashMap<ObjectValue, Integer> objectToId = 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();
|
private Notifier updateNotifier = new Notifier();
|
||||||
|
|
||||||
@ -191,19 +219,19 @@ public class SimpleDebugger implements Debugger {
|
|||||||
|
|
||||||
return tail.first();
|
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.");
|
if (!el.isMap()) throw new RuntimeException("Expected location to be a map.");
|
||||||
var id = Integer.parseInt(el.map().string("scriptId"));
|
var id = Integer.parseInt(el.map().string("scriptId"));
|
||||||
var line = (int)el.map().number("lineNumber") + 1;
|
var line = (int)el.map().number("lineNumber") + 1;
|
||||||
var column = (int)el.map().number("columnNumber") + 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);
|
var res = new Location(line, column, idToSource.get(id).filename);
|
||||||
if (correct) res = correctLocation(idToSource.get(id), res);
|
if (correct) res = correctLocation(idToSource.get(id), res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
public JSONMap serializeLocation(Location loc) {
|
private JSONMap serializeLocation(Location loc) {
|
||||||
var source = filenameToId.get(loc.filename());
|
var source = filenameToId.get(loc.filename());
|
||||||
return new JSONMap()
|
return new JSONMap()
|
||||||
.set("scriptId", source + "")
|
.set("scriptId", source + "")
|
||||||
@ -211,21 +239,21 @@ public class SimpleDebugger implements Debugger {
|
|||||||
.set("columnNumber", loc.start() - 1);
|
.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);
|
if (objectToId.containsKey(obj)) return objectToId.get(obj);
|
||||||
else {
|
else {
|
||||||
int id = nextId();
|
int id = nextId();
|
||||||
objectToId.put(obj, id);
|
objectToId.put(obj, id);
|
||||||
|
if (ctx != null) objectToCtx.put(obj, ctx);
|
||||||
idToObject.put(id, obj);
|
idToObject.put(id, obj);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private JSONMap serializeObj(Context ctx, Object val) {
|
private JSONMap serializeObj(Context ctx, Object val, boolean recurse) {
|
||||||
val = Values.normalize(null, val);
|
val = Values.normalize(null, val);
|
||||||
|
|
||||||
if (val == Values.NULL) {
|
if (val == Values.NULL) {
|
||||||
return new JSONMap()
|
return new JSONMap()
|
||||||
.set("objectId", objectId(null) + "")
|
|
||||||
.set("type", "object")
|
.set("type", "object")
|
||||||
.set("subtype", "null")
|
.set("subtype", "null")
|
||||||
.setNull("value")
|
.setNull("value")
|
||||||
@ -234,13 +262,14 @@ public class SimpleDebugger implements Debugger {
|
|||||||
|
|
||||||
if (val instanceof ObjectValue) {
|
if (val instanceof ObjectValue) {
|
||||||
var obj = (ObjectValue)val;
|
var obj = (ObjectValue)val;
|
||||||
var id = objectId(obj);
|
var id = objectId(ctx, obj);
|
||||||
var type = "object";
|
var type = "object";
|
||||||
String subtype = null;
|
String subtype = null;
|
||||||
String className = null;
|
String className = null;
|
||||||
|
|
||||||
if (obj instanceof FunctionValue) type = "function";
|
if (obj instanceof FunctionValue) type = "function";
|
||||||
if (obj instanceof ArrayValue) subtype = "array";
|
if (obj instanceof ArrayValue) subtype = "array";
|
||||||
|
|
||||||
if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp";
|
if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp";
|
||||||
if (Values.isWrapper(val, DateLib.class)) subtype = "date";
|
if (Values.isWrapper(val, DateLib.class)) subtype = "date";
|
||||||
if (Values.isWrapper(val, MapLib.class)) subtype = "map";
|
if (Values.isWrapper(val, MapLib.class)) subtype = "map";
|
||||||
@ -253,10 +282,13 @@ public class SimpleDebugger implements Debugger {
|
|||||||
|
|
||||||
var res = new JSONMap()
|
var res = new JSONMap()
|
||||||
.set("type", type)
|
.set("type", type)
|
||||||
.set("objetId", id + "");
|
.set("objectId", id + "");
|
||||||
|
|
||||||
if (subtype != null) res.set("subtype", subtype);
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
@ -271,7 +303,7 @@ public class SimpleDebugger implements Debugger {
|
|||||||
|
|
||||||
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
|
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
|
||||||
else if (Double.NEGATIVE_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 if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
|
||||||
else res.set("value", num);
|
else res.set("value", num);
|
||||||
|
|
||||||
@ -280,6 +312,37 @@ public class SimpleDebugger implements Debugger {
|
|||||||
|
|
||||||
throw new IllegalArgumentException("Unexpected JS object.");
|
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) {
|
private void resume(State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
@ -296,7 +359,7 @@ public class SimpleDebugger implements Debugger {
|
|||||||
ws.send(new V8Event("Debugger.paused", map));
|
ws.send(new V8Event("Debugger.paused", map));
|
||||||
}
|
}
|
||||||
private void pauseException(Context ctx) {
|
private void pauseException(Context ctx) {
|
||||||
state = State.PAUSED_NORMAL;
|
state = State.PAUSED_EXCEPTION;
|
||||||
var map = new JSONMap()
|
var map = new JSONMap()
|
||||||
.set("callFrames", serializeFrames(ctx))
|
.set("callFrames", serializeFrames(ctx))
|
||||||
.set("reason", "exception");
|
.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) {
|
@Override public void enable(V8Message msg) {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
ws.send(msg.respond());
|
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)));
|
ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
|
||||||
}
|
}
|
||||||
@Override public void getPossibleBreakpoints(V8Message msg) {
|
@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 start = deserializeLocation(msg.params.get("start"), false);
|
||||||
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
|
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();
|
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) {
|
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations) {
|
||||||
int id = nextId();
|
int id = nextId();
|
||||||
var src = new Source(id, filename, source, locations);
|
var src = new Source(id, filename, source, locations);
|
||||||
@ -488,6 +667,9 @@ public class SimpleDebugger implements Debugger {
|
|||||||
|
|
||||||
updateFrames(ctx);
|
updateFrames(ctx);
|
||||||
var frame = codeFrameToFrame.get(cf);
|
var frame = codeFrameToFrame.get(cf);
|
||||||
|
|
||||||
|
if (!frame.debugData) return false;
|
||||||
|
|
||||||
if (instruction.location != null) frame.updateLoc(instruction.location);
|
if (instruction.location != null) frame.updateLoc(instruction.location);
|
||||||
var loc = frame.location;
|
var loc = frame.location;
|
||||||
var isBreakpointable = loc != null && (
|
var isBreakpointable = loc != null && (
|
||||||
@ -501,15 +683,12 @@ public class SimpleDebugger implements Debugger {
|
|||||||
pauseException(ctx);
|
pauseException(ctx);
|
||||||
}
|
}
|
||||||
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
|
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
|
||||||
pauseDebug(ctx, locToBreakpoint.get(loc));
|
var bp = locToBreakpoint.get(loc);
|
||||||
}
|
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition));
|
||||||
else if (isBreakpointable && tmpBreakpts.contains(loc)) {
|
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc));
|
||||||
pauseDebug(ctx, null);
|
|
||||||
tmpBreakpts.remove(loc);
|
|
||||||
}
|
|
||||||
else if (instruction.type == Type.NOP && instruction.match("debug")) {
|
|
||||||
pauseDebug(ctx, null);
|
|
||||||
}
|
}
|
||||||
|
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
|
||||||
|
else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null);
|
||||||
|
|
||||||
while (enabled) {
|
while (enabled) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@ -527,12 +706,17 @@ public class SimpleDebugger implements Debugger {
|
|||||||
else return false;
|
else return false;
|
||||||
break;
|
break;
|
||||||
case STEPPING_OVER:
|
case STEPPING_OVER:
|
||||||
if (
|
if (stepOutFrame.frame == frame.frame) {
|
||||||
stepOutFrame == frame && (
|
if (returnVal != Runners.NO_RETURN) {
|
||||||
|
state = State.STEPPING_OUT;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (isBreakpointable && (
|
||||||
!loc.filename().equals(prevLocation.filename()) ||
|
!loc.filename().equals(prevLocation.filename()) ||
|
||||||
loc.line() != prevLocation.line()
|
loc.line() != prevLocation.line()
|
||||||
)
|
)) pauseDebug(ctx, null);
|
||||||
) pauseDebug(ctx, null);
|
else return false;
|
||||||
|
}
|
||||||
else return false;
|
else return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -544,14 +728,12 @@ public class SimpleDebugger implements Debugger {
|
|||||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||||
updateFrames(ctx);
|
updateFrames(ctx);
|
||||||
|
|
||||||
try {
|
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
|
||||||
idToFrame.remove(codeFrameToFrame.remove(frame).id);
|
|
||||||
}
|
|
||||||
catch (NullPointerException e) { }
|
catch (NullPointerException e) { }
|
||||||
|
|
||||||
if (StackData.frames(ctx).size() == 0) resume(State.RESUMED);
|
if (StackData.frames(ctx).size() == 0) resume(State.RESUMED);
|
||||||
else if (stepOutFrame != null && stepOutFrame.frame == frame &&
|
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);
|
pauseDebug(ctx, null);
|
||||||
updateNotifier.await();
|
updateNotifier.await();
|
||||||
|
@ -7,7 +7,6 @@ import java.io.OutputStream;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
||||||
|
|
||||||
public class WebSocket implements AutoCloseable {
|
public class WebSocket implements AutoCloseable {
|
||||||
|
@ -10,6 +10,8 @@ import me.topchetoeu.jscript.engine.scope.LocalScope;
|
|||||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
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.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||||
@ -56,6 +58,33 @@ public class CodeFrame {
|
|||||||
public boolean jumpFlag = false;
|
public boolean jumpFlag = false;
|
||||||
private Location prevLoc = null;
|
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) {
|
public void addTry(int n, int catchN, int finallyN) {
|
||||||
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
|
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
|
||||||
|
|
||||||
@ -144,7 +173,7 @@ public class CodeFrame {
|
|||||||
}
|
}
|
||||||
else if (returnValue != Runners.NO_RETURN) {
|
else if (returnValue != Runners.NO_RETURN) {
|
||||||
if (tryCtx.hasFinally) {
|
if (tryCtx.hasFinally) {
|
||||||
tryCtx.retVal = error;
|
tryCtx.retVal = returnValue;
|
||||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
newState = TryCtx.STATE_FINALLY_RETURNED;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -215,6 +244,7 @@ public class CodeFrame {
|
|||||||
case TryCtx.STATE_CATCH:
|
case TryCtx.STATE_CATCH:
|
||||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
||||||
codePtr = tryCtx.catchStart;
|
codePtr = tryCtx.catchStart;
|
||||||
|
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
codePtr = tryCtx.finallyStart;
|
codePtr = tryCtx.finallyStart;
|
||||||
|
@ -301,15 +301,6 @@ public class Runners {
|
|||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
|
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++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,7 @@ package me.topchetoeu.jscript.engine.scope;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
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 {
|
public class LocalScope {
|
||||||
private String[] names;
|
|
||||||
public final ValueVariable[] captures;
|
public final ValueVariable[] captures;
|
||||||
public final ValueVariable[] locals;
|
public final ValueVariable[] locals;
|
||||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||||
@ -19,70 +14,11 @@ public class LocalScope {
|
|||||||
else return captures[~i];
|
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() {
|
public int size() {
|
||||||
return captures.length + locals.length;
|
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) {
|
public LocalScope(int n, ValueVariable[] captures) {
|
||||||
locals = new ValueVariable[n];
|
locals = new ValueVariable[n];
|
||||||
this.captures = captures;
|
this.captures = captures;
|
||||||
|
@ -11,6 +11,9 @@ public class LocalScopeRecord implements ScopeRecord {
|
|||||||
private final ArrayList<String> captures = new ArrayList<>();
|
private final ArrayList<String> captures = new ArrayList<>();
|
||||||
private final ArrayList<String> locals = new ArrayList<>();
|
private final ArrayList<String> locals = new ArrayList<>();
|
||||||
|
|
||||||
|
public String[] captures() {
|
||||||
|
return captures.toArray(String[]::new);
|
||||||
|
}
|
||||||
public String[] locals() {
|
public String[] locals() {
|
||||||
return locals.toArray(String[]::new);
|
return locals.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package me.topchetoeu.jscript.engine.values;
|
package me.topchetoeu.jscript.engine.values;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.engine.Context;
|
||||||
import me.topchetoeu.jscript.engine.Environment;
|
import me.topchetoeu.jscript.engine.Environment;
|
||||||
@ -13,6 +14,7 @@ public class CodeFunction extends FunctionValue {
|
|||||||
public final int localsN;
|
public final int localsN;
|
||||||
public final int length;
|
public final int length;
|
||||||
public final Instruction[] body;
|
public final Instruction[] body;
|
||||||
|
public final String[] captureNames, localNames;
|
||||||
public final ValueVariable[] captures;
|
public final ValueVariable[] captures;
|
||||||
public Environment environment;
|
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);
|
super(name, length);
|
||||||
this.captures = captures;
|
this.captures = captures;
|
||||||
|
this.captureNames = body.captureNames;
|
||||||
|
this.localNames = body.localNames;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.localsN = localsN;
|
this.localsN = localsN;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.body = body;
|
this.body = body.instructions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
51
src/me/topchetoeu/jscript/engine/values/ScopeValue.java
Normal file
51
src/me/topchetoeu/jscript/engine/values/ScopeValue.java
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,15 @@
|
|||||||
package me.topchetoeu.jscript.json;
|
package me.topchetoeu.jscript.json;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Filename;
|
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.exceptions.SyntaxException;
|
||||||
import me.topchetoeu.jscript.parsing.Operator;
|
import me.topchetoeu.jscript.parsing.Operator;
|
||||||
import me.topchetoeu.jscript.parsing.ParseRes;
|
import me.topchetoeu.jscript.parsing.ParseRes;
|
||||||
@ -11,6 +17,63 @@ import me.topchetoeu.jscript.parsing.Parsing;
|
|||||||
import me.topchetoeu.jscript.parsing.Token;
|
import me.topchetoeu.jscript.parsing.Token;
|
||||||
|
|
||||||
public class JSON {
|
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) {
|
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
|
||||||
return Parsing.parseIdentifier(tokens, i);
|
return Parsing.parseIdentifier(tokens, i);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,9 @@ public class JSONList extends ArrayList<JSONElement> {
|
|||||||
public JSONList(JSONElement ...els) {
|
public JSONList(JSONElement ...els) {
|
||||||
super(List.of(els));
|
super(List.of(els));
|
||||||
}
|
}
|
||||||
|
public JSONList(Collection<JSONElement> els) {
|
||||||
|
super(els);
|
||||||
|
}
|
||||||
|
|
||||||
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
public JSONList addNull() { this.add(JSONElement.NULL); return this; }
|
||||||
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
|
||||||
|
@ -1,84 +1,22 @@
|
|||||||
package me.topchetoeu.jscript.lib;
|
package me.topchetoeu.jscript.lib;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Filename;
|
import me.topchetoeu.jscript.Filename;
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
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.EngineException;
|
||||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||||
import me.topchetoeu.jscript.interop.Native;
|
import me.topchetoeu.jscript.interop.Native;
|
||||||
import me.topchetoeu.jscript.json.JSONElement;
|
import me.topchetoeu.jscript.json.JSON;
|
||||||
import me.topchetoeu.jscript.json.JSONList;
|
|
||||||
import me.topchetoeu.jscript.json.JSONMap;
|
|
||||||
|
|
||||||
public class JSONLib {
|
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
|
@Native
|
||||||
public static Object parse(Context ctx, String val) {
|
public static Object parse(Context ctx, String val) {
|
||||||
try {
|
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); }
|
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
|
||||||
}
|
}
|
||||||
@Native
|
@Native
|
||||||
public static String stringify(Context ctx, Object val) {
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1367,6 +1367,7 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
var nameLoc = getLoc(filename, tokens, i + n);
|
||||||
var nameRes = parseIdentifier(tokens, i + n++);
|
var nameRes = parseIdentifier(tokens, i + n++);
|
||||||
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name.");
|
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name.");
|
||||||
|
|
||||||
@ -1384,7 +1385,7 @@ public class Parsing {
|
|||||||
val = valRes.result;
|
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)) {
|
if (isOperator(tokens, i + n, Operator.COMMA)) {
|
||||||
n++;
|
n++;
|
||||||
@ -1512,7 +1513,7 @@ public class Parsing {
|
|||||||
statements.add(res.result);
|
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) {
|
public static ParseRes<String> parseLabel(List<Token> tokens, int i) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@ -1876,7 +1877,7 @@ public class Parsing {
|
|||||||
return list.toArray(Statement[]::new);
|
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 target = environment.global.globalChild();
|
||||||
var subscope = target.child();
|
var subscope = target.child();
|
||||||
var res = new CompileTarget(funcs, breakpoints);
|
var res = new CompileTarget(funcs, breakpoints);
|
||||||
@ -1903,14 +1904,14 @@ public class Parsing {
|
|||||||
}
|
}
|
||||||
else res.add(Instruction.ret());
|
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 {
|
try {
|
||||||
return compile(funcs, breakpoints, environment, parse(filename, raw));
|
return compile(funcs, breakpoints, environment, parse(filename, raw));
|
||||||
}
|
}
|
||||||
catch (SyntaxException e) {
|
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) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user