feat: add back debugger from old version
This commit is contained in:
parent
3a6401094f
commit
29bea68525
@ -1,4 +1,4 @@
|
|||||||
project_group = me.topchetoeu
|
project_group = me.topchetoeu
|
||||||
project_name = jscript
|
project_name = jscript
|
||||||
project_version = 0.9.41-beta
|
project_version = 0.9.41-beta
|
||||||
main_class = me.topchetoeu.jscript.runtime.SimpleRepl
|
main_class = me.topchetoeu.jscript.repl.SimpleRepl
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package me.topchetoeu.jscript.runtime;
|
package me.topchetoeu.jscript.repl;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -10,6 +14,7 @@ import java.util.Optional;
|
|||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.regex.PatternSyntaxException;
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
@ -20,6 +25,17 @@ import me.topchetoeu.jscript.common.environment.Environment;
|
|||||||
import me.topchetoeu.jscript.common.environment.Key;
|
import me.topchetoeu.jscript.common.environment.Key;
|
||||||
import me.topchetoeu.jscript.common.json.JSON;
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.repl.debug.DebugServer;
|
||||||
|
import me.topchetoeu.jscript.repl.debug.Debugger;
|
||||||
|
import me.topchetoeu.jscript.repl.debug.SimpleDebugger;
|
||||||
|
import me.topchetoeu.jscript.repl.mapping.NativeMapper;
|
||||||
|
import me.topchetoeu.jscript.repl.mapping.SourceMap;
|
||||||
|
import me.topchetoeu.jscript.runtime.ArgumentsValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.Engine;
|
||||||
|
import me.topchetoeu.jscript.runtime.EventLoop;
|
||||||
|
import me.topchetoeu.jscript.runtime.Frame;
|
||||||
|
import me.topchetoeu.jscript.runtime.JSONConverter;
|
||||||
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.runtime.values.Value;
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
@ -33,11 +49,17 @@ import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
|
|||||||
import me.topchetoeu.jscript.runtime.values.primitives.UserValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.UserValue;
|
||||||
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.VoidValue;
|
||||||
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.Compiler;
|
||||||
|
|
||||||
public class SimpleRepl {
|
public class SimpleRepl {
|
||||||
static Thread engineTask;
|
static Thread engineTask, debugTask;
|
||||||
static Engine engine = new Engine();
|
static Engine engine = new Engine();
|
||||||
static Environment environment = Environment.empty();
|
static Environment environment = Environment.empty(), tsEnvironment;
|
||||||
|
static DebugServer server;
|
||||||
|
static Debugger debugger;
|
||||||
|
static Key<OutputStream> STDOUT = new Key<>();
|
||||||
|
static Key<OutputStream> STDERR = new Key<>();
|
||||||
|
static Key<InputStream> STDIN = new Key<>();
|
||||||
|
|
||||||
static int j = 0;
|
static int j = 0;
|
||||||
static String[] args;
|
static String[] args;
|
||||||
@ -49,6 +71,13 @@ public class SimpleRepl {
|
|||||||
}
|
}
|
||||||
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
|
catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); }
|
||||||
|
|
||||||
|
server = new DebugServer();
|
||||||
|
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||||
|
server.targets.put("default", (socket, req) -> new SimpleDebugger(socket)
|
||||||
|
.attach(DebugContext.get(environment))
|
||||||
|
.attach(DebugContext.get(tsEnvironment))
|
||||||
|
);
|
||||||
|
|
||||||
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
|
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
|
||||||
|
|
||||||
for (var arg : args) {
|
for (var arg : args) {
|
||||||
@ -142,6 +171,9 @@ public class SimpleRepl {
|
|||||||
getArgs.self(Map.class).clear();
|
getArgs.self(Map.class).clear();
|
||||||
return Value.UNDEFINED;
|
return Value.UNDEFINED;
|
||||||
}));
|
}));
|
||||||
|
mapConstr.prototype.defineOwnField(env, "size", new NativeFunction(getArgs -> {
|
||||||
|
return NumberValue.of(getArgs.self(Map.class).size());
|
||||||
|
}));
|
||||||
prototype[0] = (ObjectValue)mapConstr.prototype;
|
prototype[0] = (ObjectValue)mapConstr.prototype;
|
||||||
|
|
||||||
return mapConstr;
|
return mapConstr;
|
||||||
@ -286,6 +318,10 @@ public class SimpleRepl {
|
|||||||
res.defineOwnField(env, "NaN", NumberValue.NAN);
|
res.defineOwnField(env, "NaN", NumberValue.NAN);
|
||||||
res.defineOwnField(env, "Infinity", NumberValue.of(Double.POSITIVE_INFINITY));
|
res.defineOwnField(env, "Infinity", NumberValue.of(Double.POSITIVE_INFINITY));
|
||||||
|
|
||||||
|
res.defineOwnField(env, "pow", new NativeFunction(args -> {
|
||||||
|
return NumberValue.of(Math.pow(args.get(0).toNumber(args.env).getDouble(), args.get(1).toNumber(args.env).getDouble()));
|
||||||
|
}));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,6 +489,16 @@ public class SimpleRepl {
|
|||||||
|
|
||||||
return VoidValue.UNDEFINED;
|
return VoidValue.UNDEFINED;
|
||||||
}));
|
}));
|
||||||
|
res.defineOwnField(env, "sort", new NativeFunction(args -> {
|
||||||
|
var arr = (ArrayValue)args.get(0);
|
||||||
|
var func = (FunctionValue)args.get(1);
|
||||||
|
|
||||||
|
arr.sort((a, b) -> {
|
||||||
|
return func.apply(args.env, Value.UNDEFINED, a, b).toNumber(args.env).getInt();
|
||||||
|
});
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -513,7 +559,12 @@ public class SimpleRepl {
|
|||||||
return StringValue.of(JSON.stringify(JSONConverter.fromJs(env, args.get(0))));
|
return StringValue.of(JSON.stringify(JSONConverter.fromJs(env, args.get(0))));
|
||||||
}));
|
}));
|
||||||
res.defineOwnField(env, "parse", new NativeFunction(args -> {
|
res.defineOwnField(env, "parse", new NativeFunction(args -> {
|
||||||
return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env)));
|
try {
|
||||||
|
return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env)));
|
||||||
|
}
|
||||||
|
catch (SyntaxException e) {
|
||||||
|
throw EngineException.ofSyntax(e.msg).add(env, e.loc.filename() + "", e.loc);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@ -633,19 +684,66 @@ public class SimpleRepl {
|
|||||||
}
|
}
|
||||||
private static void initGlobals() throws InterruptedException, ExecutionException {
|
private static void initGlobals() throws InterruptedException, ExecutionException {
|
||||||
environment = createESEnv();
|
environment = createESEnv();
|
||||||
var tsEnv = createESEnv();
|
|
||||||
var res = new FunctionValue[1];
|
var res = new FunctionValue[1];
|
||||||
var setter = new NativeFunction(args -> {
|
var setter = new NativeFunction(args -> {
|
||||||
res[0] = (FunctionValue)args.get(0);
|
res[0] = (FunctionValue)args.get(0);
|
||||||
return Value.UNDEFINED;
|
return Value.UNDEFINED;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tsEnvironment = createESEnv();
|
||||||
|
var tsGlob = Value.global(tsEnvironment);
|
||||||
|
var tsCompilerFactory = new FunctionValue[1];
|
||||||
|
|
||||||
|
tsGlob.defineOwnField(tsEnvironment, "getResource", new NativeFunction(args -> {
|
||||||
|
var name = args.get(0).toString(args.env);
|
||||||
|
var src = Reading.resourceToString("lib/" + name);
|
||||||
|
|
||||||
|
if (src == null) return Value.UNDEFINED;
|
||||||
|
else return StringValue.of(src);
|
||||||
|
}));
|
||||||
|
tsGlob.defineOwnField(tsEnvironment, "register", new NativeFunction(args -> {
|
||||||
|
var func = (FunctionValue)args.get(0);
|
||||||
|
tsCompilerFactory[0] = func;
|
||||||
|
return Value.UNDEFINED;
|
||||||
|
}));
|
||||||
|
tsGlob.defineOwnField(tsEnvironment, "parseVLQ", new NativeFunction(args -> {
|
||||||
|
var compiled = Filename.parse(args.get(0).toString(args.env));
|
||||||
|
var original = Filename.parse(args.get(1).toString(args.env));
|
||||||
|
var map = args.get(2).toString(args.env);
|
||||||
|
|
||||||
|
var mapper = SourceMap.parse(compiled, original, map);
|
||||||
|
return new NativeMapper(mapper::toOriginal);
|
||||||
|
}));
|
||||||
|
tsGlob.defineOwnField(tsEnvironment, "chainMaps", new NativeFunction(args -> {
|
||||||
|
var list = new ArrayList<Function<Location, Location>>();
|
||||||
|
for (var arg : args.args) {
|
||||||
|
list.add(NativeMapper.unwrap(args.env, (FunctionValue)arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NativeMapper(v -> {
|
||||||
|
for (var el : list) {
|
||||||
|
v = el.apply(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
tsGlob.defineOwnField(tsEnvironment, "registerSource", new NativeFunction(args -> {
|
||||||
|
var filename = Filename.parse(args.get(0).toString(args.env));
|
||||||
|
var src = args.get(1).toString(args.env);
|
||||||
|
DebugContext.get(environment).onSource(filename, src);
|
||||||
|
return Value.UNDEFINED;
|
||||||
|
}));
|
||||||
|
|
||||||
var ts = Reading.resourceToString("lib/ts.js");
|
var ts = Reading.resourceToString("lib/ts.js");
|
||||||
if (ts != null) EventLoop.get(tsEnv).pushMsg(
|
if (ts != null) EventLoop.get(tsEnvironment).pushMsg(
|
||||||
false, tsEnv,
|
false, tsEnvironment,
|
||||||
Filename.parse("jscript://ts.js"), ts,
|
Filename.parse("jscript://ts.js"), ts,
|
||||||
Value.UNDEFINED, setter
|
Value.UNDEFINED, setter
|
||||||
).get();
|
).get();
|
||||||
|
|
||||||
|
var tsCompiler = Compiler.get(environment).wrap(tsEnvironment, environment, tsCompilerFactory[0]);
|
||||||
|
environment.add(Compiler.KEY, tsCompiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String args[]) throws InterruptedException {
|
public static void main(String args[]) throws InterruptedException {
|
||||||
@ -653,6 +751,7 @@ public class SimpleRepl {
|
|||||||
var reader = new Thread(SimpleRepl::reader);
|
var reader = new Thread(SimpleRepl::reader);
|
||||||
|
|
||||||
environment = initEnv();
|
environment = initEnv();
|
||||||
|
|
||||||
initEngine();
|
initEngine();
|
||||||
|
|
||||||
reader.setDaemon(true);
|
reader.setDaemon(true);
|
||||||
@ -661,5 +760,6 @@ public class SimpleRepl {
|
|||||||
|
|
||||||
engine.thread().join();
|
engine.thread().join();
|
||||||
engineTask.interrupt();
|
engineTask.interrupt();
|
||||||
|
debugTask.interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
19
src/main/java/me/topchetoeu/jscript/repl/V8Error.java
Normal file
19
src/main/java/me/topchetoeu/jscript/repl/V8Error.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package me.topchetoeu.jscript.repl;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONMap;
|
||||||
|
|
||||||
|
public class V8Error {
|
||||||
|
public final String message;
|
||||||
|
|
||||||
|
public V8Error(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return JSON.stringify(new JSONMap().set("error", new JSONMap()
|
||||||
|
.set("message", message)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
249
src/main/java/me/topchetoeu/jscript/repl/debug/DebugServer.java
Normal file
249
src/main/java/me/topchetoeu/jscript/repl/debug/DebugServer.java
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Metadata;
|
||||||
|
import me.topchetoeu.jscript.common.Reading;
|
||||||
|
import me.topchetoeu.jscript.common.SyntaxException;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONList;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONMap;
|
||||||
|
import me.topchetoeu.jscript.repl.debug.WebSocketMessage.Type;
|
||||||
|
|
||||||
|
public class DebugServer {
|
||||||
|
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version();
|
||||||
|
|
||||||
|
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
|
||||||
|
|
||||||
|
private final byte[] favicon, index, protocol;
|
||||||
|
private final Object connNotifier = new Object();
|
||||||
|
|
||||||
|
private static void send(HttpRequest req, String val) throws IOException {
|
||||||
|
req.writeResponse(200, "OK", "application/json", val.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SILENCE JAVA
|
||||||
|
private MessageDigest getDigestInstance() {
|
||||||
|
try {
|
||||||
|
return MessageDigest.getInstance("sha1");
|
||||||
|
}
|
||||||
|
catch (Throwable e) { throw new RuntimeException(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Thread runAsync(Runnable func, String name) {
|
||||||
|
var res = new Thread(func);
|
||||||
|
res.setName(name);
|
||||||
|
res.start();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handle(WebSocket ws, Debugger debugger) throws IOException {
|
||||||
|
WebSocketMessage raw;
|
||||||
|
|
||||||
|
while ((raw = ws.receive()) != null) {
|
||||||
|
if (raw.type != Type.Text) {
|
||||||
|
ws.send(new V8Error("Expected a text message."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
V8Message msg;
|
||||||
|
|
||||||
|
try {
|
||||||
|
msg = new V8Message(raw.textData());
|
||||||
|
}
|
||||||
|
catch (SyntaxException e) {
|
||||||
|
ws.send(new V8Error(e.getMessage()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(msg);
|
||||||
|
|
||||||
|
switch (msg.name) {
|
||||||
|
case "Debugger.enable":
|
||||||
|
synchronized (connNotifier) {
|
||||||
|
connNotifier.notify();
|
||||||
|
}
|
||||||
|
debugger.enable(msg);
|
||||||
|
continue;
|
||||||
|
case "Debugger.disable": debugger.close(); continue;
|
||||||
|
|
||||||
|
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
|
||||||
|
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
|
||||||
|
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
|
||||||
|
|
||||||
|
case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue;
|
||||||
|
case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue;
|
||||||
|
|
||||||
|
case "Debugger.resume": debugger.resume(msg); continue;
|
||||||
|
case "Debugger.pause": debugger.pause(msg); continue;
|
||||||
|
|
||||||
|
case "Debugger.stepInto": debugger.stepInto(msg); continue;
|
||||||
|
case "Debugger.stepOut": debugger.stepOut(msg); continue;
|
||||||
|
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.releaseObject": debugger.releaseObject(msg); continue;
|
||||||
|
case "Runtime.getProperties": debugger.getProperties(msg); continue;
|
||||||
|
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
|
||||||
|
case "Runtime.enable": debugger.runtimeEnable(msg); continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
msg.name.startsWith("DOM.") ||
|
||||||
|
msg.name.startsWith("DOMDebugger.") ||
|
||||||
|
msg.name.startsWith("Emulation.") ||
|
||||||
|
msg.name.startsWith("Input.") ||
|
||||||
|
msg.name.startsWith("Network.") ||
|
||||||
|
msg.name.startsWith("Page.")
|
||||||
|
) ws.send(new V8Error("This isn't a browser..."));
|
||||||
|
else ws.send(new V8Error("This API is not supported yet."));
|
||||||
|
}
|
||||||
|
|
||||||
|
debugger.close();
|
||||||
|
}
|
||||||
|
private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) {
|
||||||
|
var key = req.headers.get("sec-websocket-key");
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
req.writeResponse(
|
||||||
|
426, "Upgrade Required", "text/txt",
|
||||||
|
"Expected a WS upgrade".getBytes()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
|
||||||
|
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
|
||||||
|
));
|
||||||
|
|
||||||
|
req.writeCode(101, "Switching Protocols");
|
||||||
|
req.writeHeader("Connection", "Upgrade");
|
||||||
|
req.writeHeader("Sec-WebSocket-Accept", resKey);
|
||||||
|
req.writeLastHeader("Upgrade", "WebSocket");
|
||||||
|
|
||||||
|
var ws = new WebSocket(socket);
|
||||||
|
var debugger = debuggerProvider.getDebugger(ws, req);
|
||||||
|
|
||||||
|
if (debugger == null) {
|
||||||
|
ws.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
runAsync(() -> {
|
||||||
|
var handle = new Thread(() -> {
|
||||||
|
System.out.println("test");
|
||||||
|
debugger.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(handle);
|
||||||
|
|
||||||
|
try { handle(ws, debugger); }
|
||||||
|
catch (RuntimeException | IOException e) {
|
||||||
|
try {
|
||||||
|
e.printStackTrace();
|
||||||
|
ws.send(new V8Error(e.getMessage()));
|
||||||
|
}
|
||||||
|
catch (IOException e2) { /* Shit outta luck */ }
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Runtime.getRuntime().removeShutdownHook(handle);
|
||||||
|
ws.close();
|
||||||
|
debugger.close();
|
||||||
|
}
|
||||||
|
}, "Debug Handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void awaitConnection() throws InterruptedException {
|
||||||
|
connNotifier.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(InetSocketAddress address) {
|
||||||
|
try {
|
||||||
|
ServerSocket server = new ServerSocket();
|
||||||
|
server.bind(address);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
var socket = server.accept();
|
||||||
|
var req = HttpRequest.read(socket);
|
||||||
|
|
||||||
|
if (req == null) continue;
|
||||||
|
switch (req.path) {
|
||||||
|
case "/json/version":
|
||||||
|
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
|
||||||
|
break;
|
||||||
|
case "/json/list":
|
||||||
|
case "/json": {
|
||||||
|
var res = new JSONList();
|
||||||
|
|
||||||
|
for (var el : targets.entrySet()) {
|
||||||
|
res.add(new JSONMap()
|
||||||
|
.set("description", "JScript debugger")
|
||||||
|
.set("favicon", "/favicon.ico")
|
||||||
|
.set("id", el.getKey())
|
||||||
|
.set("type", "node")
|
||||||
|
.set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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/close":
|
||||||
|
case "/devtools/inspector.html":
|
||||||
|
req.writeResponse(
|
||||||
|
501, "Not Implemented", "text/txt",
|
||||||
|
"This feature isn't (and probably won't be) implemented.".getBytes()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "/":
|
||||||
|
case "/index.html":
|
||||||
|
req.writeResponse(200, "OK", "text/html", index);
|
||||||
|
break;
|
||||||
|
case "/favicon.ico":
|
||||||
|
req.writeResponse(200, "OK", "image/png", favicon);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) {
|
||||||
|
onWsConnect(req, socket, targets.get(req.path.substring(1)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { server.close(); }
|
||||||
|
}
|
||||||
|
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread start(InetSocketAddress address, boolean daemon) {
|
||||||
|
var res = new Thread(() -> run(address), "Debug Server");
|
||||||
|
res.setDaemon(daemon);
|
||||||
|
res.start();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugServer() {
|
||||||
|
this.favicon = Reading.resourceToBytes("debugger/favicon.png");
|
||||||
|
this.protocol = Reading.resourceToBytes("debugger/protocol.json");
|
||||||
|
this.index = Reading.resourceToString("debugger/index.html")
|
||||||
|
.replace("${NAME}", Metadata.name())
|
||||||
|
.replace("${VERSION}", Metadata.version())
|
||||||
|
.replace("${AUTHOR}", Metadata.author())
|
||||||
|
.getBytes();
|
||||||
|
}
|
||||||
|
}
|
37
src/main/java/me/topchetoeu/jscript/repl/debug/Debugger.java
Normal file
37
src/main/java/me/topchetoeu/jscript/repl/debug/Debugger.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.runtime.debug.DebugHandler;
|
||||||
|
|
||||||
|
public interface Debugger extends DebugHandler {
|
||||||
|
void close();
|
||||||
|
|
||||||
|
void enable(V8Message msg) throws IOException;
|
||||||
|
void disable(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void setBreakpointByUrl(V8Message msg) throws IOException;
|
||||||
|
void removeBreakpoint(V8Message msg) throws IOException;
|
||||||
|
void continueToLocation(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void getScriptSource(V8Message msg) throws IOException;
|
||||||
|
void getPossibleBreakpoints(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void resume(V8Message msg) throws IOException;
|
||||||
|
void pause(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void stepInto(V8Message msg) throws IOException;
|
||||||
|
void stepOut(V8Message msg) throws IOException;
|
||||||
|
void stepOver(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void setPauseOnExceptions(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void evaluateOnCallFrame(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void getProperties(V8Message msg) throws IOException;
|
||||||
|
void releaseObjectGroup(V8Message msg) throws IOException;
|
||||||
|
void releaseObject(V8Message msg) throws IOException;
|
||||||
|
void callFunctionOn(V8Message msg) throws IOException;
|
||||||
|
|
||||||
|
void runtimeEnable(V8Message msg) throws IOException;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
public interface DebuggerProvider {
|
||||||
|
Debugger getDebugger(WebSocket socket, HttpRequest req);
|
||||||
|
}
|
101
src/main/java/me/topchetoeu/jscript/repl/debug/HttpRequest.java
Normal file
101
src/main/java/me/topchetoeu/jscript/repl/debug/HttpRequest.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.IllegalFormatException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Reading;
|
||||||
|
|
||||||
|
public class HttpRequest {
|
||||||
|
public final String method;
|
||||||
|
public final String path;
|
||||||
|
public final Map<String, String> headers;
|
||||||
|
public final OutputStream out;
|
||||||
|
|
||||||
|
|
||||||
|
public void writeCode(int code, String name) {
|
||||||
|
try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); }
|
||||||
|
catch (IOException e) { }
|
||||||
|
}
|
||||||
|
public void writeHeader(String name, String value) {
|
||||||
|
try { out.write((name + ": " + value + "\r\n").getBytes()); }
|
||||||
|
catch (IOException e) { }
|
||||||
|
}
|
||||||
|
public void writeLastHeader(String name, String value) {
|
||||||
|
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
|
||||||
|
catch (IOException e) { }
|
||||||
|
}
|
||||||
|
public void writeHeadersEnd() {
|
||||||
|
try { out.write("\n".getBytes()); }
|
||||||
|
catch (IOException e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeResponse(int code, String name, String type, byte[] data) {
|
||||||
|
writeCode(code, name);
|
||||||
|
writeHeader("Content-Type", type);
|
||||||
|
writeLastHeader("Content-Length", data.length + "");
|
||||||
|
try {
|
||||||
|
out.write(data);
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) { }
|
||||||
|
}
|
||||||
|
public void writeResponse(int code, String name, String type, InputStream data) {
|
||||||
|
writeResponse(code, name, type, Reading.streamToBytes(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequest(String method, String path, Map<String, String> headers, OutputStream out) {
|
||||||
|
this.method = method;
|
||||||
|
this.path = path;
|
||||||
|
this.headers = headers;
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We dont need no http library
|
||||||
|
public static HttpRequest read(Socket socket) {
|
||||||
|
try {
|
||||||
|
var str = socket.getInputStream();
|
||||||
|
var lines = new BufferedReader(new InputStreamReader(str));
|
||||||
|
var line = lines.readLine();
|
||||||
|
var i1 = line.indexOf(" ");
|
||||||
|
var i2 = line.indexOf(" ", i1 + 1);
|
||||||
|
|
||||||
|
if (i1 < 0 || i2 < 0) {
|
||||||
|
socket.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = line.substring(0, i1).trim().toUpperCase();
|
||||||
|
var path = line.substring(i1 + 1, i2).trim();
|
||||||
|
var headers = new HashMap<String, String>();
|
||||||
|
|
||||||
|
while (!(line = lines.readLine()).isEmpty()) {
|
||||||
|
var i = line.indexOf(":");
|
||||||
|
if (i < 0) continue;
|
||||||
|
var name = line.substring(0, i).trim().toLowerCase();
|
||||||
|
var value = line.substring(i + 1).trim();
|
||||||
|
|
||||||
|
if (name.length() == 0) continue;
|
||||||
|
headers.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers.containsKey("content-length")) {
|
||||||
|
try {
|
||||||
|
var i = Integer.parseInt(headers.get("content-length"));
|
||||||
|
str.skip(i);
|
||||||
|
}
|
||||||
|
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HttpRequest(method, path, headers, socket.getOutputStream());
|
||||||
|
}
|
||||||
|
catch (IOException | NullPointerException e) { return null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
216
src/main/java/me/topchetoeu/jscript/repl/debug/ScopeObject.java
Normal file
216
src/main/java/me/topchetoeu/jscript/repl/debug/ScopeObject.java
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.IntUnaryOperator;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
import me.topchetoeu.jscript.runtime.Frame;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.KeyCache;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.Member;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
|
||||||
|
public class ScopeObject extends Value {
|
||||||
|
public static final class ScopeMember extends FieldMember {
|
||||||
|
public final Frame frame;
|
||||||
|
public final int i;
|
||||||
|
|
||||||
|
@Override public Value get(Environment env, Value self) {
|
||||||
|
return frame.getVar(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean set(Environment env, Value val, Value self) {
|
||||||
|
frame.setVar(i, val);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopeMember(Value self, Frame frame, int i) {
|
||||||
|
super(self, false, true, true);
|
||||||
|
|
||||||
|
this.frame = frame;
|
||||||
|
this.i = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, FieldMember> fields = new HashMap<>();
|
||||||
|
public final ObjectValue proto;
|
||||||
|
|
||||||
|
@Override public StringValue type() {
|
||||||
|
return StringValue.of("object");
|
||||||
|
}
|
||||||
|
@Override public boolean isPrimitive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public Value toPrimitive(Environment env) {
|
||||||
|
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
||||||
|
}
|
||||||
|
@Override public NumberValue toNumber(Environment env) {
|
||||||
|
return NumberValue.NAN;
|
||||||
|
}
|
||||||
|
@Override public String toString(Environment env) {
|
||||||
|
return "[Scope]";
|
||||||
|
}
|
||||||
|
@Override public boolean toBoolean() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@Override public Member getOwnMember(Environment env, KeyCache key) {
|
||||||
|
if (key.isSymbol()) return null;
|
||||||
|
var strKey = key.toString(env);
|
||||||
|
return fields.get(strKey);
|
||||||
|
}
|
||||||
|
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
|
||||||
|
return fields.keySet();
|
||||||
|
}
|
||||||
|
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
@Override public boolean defineOwnField(Environment env, KeyCache key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
|
||||||
|
if (key.isSymbol()) return false;
|
||||||
|
var strKey = key.toString(env);
|
||||||
|
var field = fields.get(strKey);
|
||||||
|
if (field == null) return false;
|
||||||
|
return field.reconfigure(env, this, val, writable, enumerable, configurable);
|
||||||
|
}
|
||||||
|
@Override public boolean defineOwnProperty(Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
|
||||||
|
return key.isSymbol() || !fields.containsKey(key.toString(env));
|
||||||
|
}
|
||||||
|
@Override public boolean setPrototype(Environment env, ObjectValue val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public State getState() {
|
||||||
|
return State.SEALED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void preventExtensions() { }
|
||||||
|
@Override public void seal() { }
|
||||||
|
@Override public void freeze() { }
|
||||||
|
|
||||||
|
@Override public ObjectValue getPrototype(Environment env) {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String name, Value val) {
|
||||||
|
fields.put(name, FieldMember.of(this, val, false));
|
||||||
|
}
|
||||||
|
public void remove(String name) {
|
||||||
|
fields.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScopeObject(ObjectValue proto) {
|
||||||
|
this.proto = proto;
|
||||||
|
}
|
||||||
|
public ScopeObject(Frame frame, String[] names, IntUnaryOperator transformer, ObjectValue proto) {
|
||||||
|
this.proto = proto;
|
||||||
|
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
fields.put(names[i], new ScopeMember(this, frame, transformer.applyAsInt(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] fixCaptures(Frame frame, String[] names) {
|
||||||
|
if (names == null) {
|
||||||
|
names = new String[frame.captures.length];
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
names[i] = "var_" + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (names.length > frame.captures.length) {
|
||||||
|
var newNames = new String[frame.captures.length];
|
||||||
|
System.arraycopy(names, 0, newNames, 0, frame.captures.length);
|
||||||
|
names = newNames;
|
||||||
|
}
|
||||||
|
else if (names.length < frame.captures.length) {
|
||||||
|
var newNames = new String[frame.captures.length];
|
||||||
|
System.arraycopy(names, 0, newNames, 0, names.length);
|
||||||
|
for (var i = names.length; i < frame.captures.length; i++) {
|
||||||
|
names[i] = "cap_" + i;
|
||||||
|
}
|
||||||
|
names = newNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
private static String[] fixLocals(Frame frame, String[] names) {
|
||||||
|
if (names == null) {
|
||||||
|
names = new String[frame.locals.length];
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
names[i] = "var_" + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (names.length > frame.locals.length) {
|
||||||
|
var newNames = new String[frame.locals.length];
|
||||||
|
System.arraycopy(names, 0, newNames, 0, frame.locals.length);
|
||||||
|
names = newNames;
|
||||||
|
}
|
||||||
|
else if (names.length < frame.locals.length) {
|
||||||
|
var newNames = new String[frame.locals.length];
|
||||||
|
System.arraycopy(names, 0, newNames, 0, names.length);
|
||||||
|
for (var i = names.length; i < frame.locals.length; i++) {
|
||||||
|
names[i] = "var_" + i;
|
||||||
|
}
|
||||||
|
names = newNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
private static String[] fixCapturables(Frame frame, String[] names) {
|
||||||
|
if (names == null) {
|
||||||
|
names = new String[frame.capturables.length];
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
names[i] = "var_" + (frame.locals.length + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (names.length > frame.capturables.length) {
|
||||||
|
var newNames = new String[frame.capturables.length];
|
||||||
|
System.arraycopy(names, 0, newNames, 0, frame.capturables.length);
|
||||||
|
names = newNames;
|
||||||
|
}
|
||||||
|
else if (names.length < frame.capturables.length) {
|
||||||
|
var newNames = new String[frame.capturables.length];
|
||||||
|
System.arraycopy(names, 0, newNames, 0, names.length);
|
||||||
|
for (var i = names.length; i < frame.capturables.length; i++) {
|
||||||
|
names[i] = "var_" + (frame.locals.length + i);
|
||||||
|
}
|
||||||
|
names = newNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScopeObject locals(Frame frame, String[] names) {
|
||||||
|
return new ScopeObject(frame, fixLocals(frame, names), v -> v, null);
|
||||||
|
}
|
||||||
|
public static ScopeObject capturables(Frame frame, String[] names) {
|
||||||
|
return new ScopeObject(frame, fixCapturables(frame, names), v -> v + frame.locals.length, null);
|
||||||
|
}
|
||||||
|
public static ScopeObject captures(Frame frame, String[] names) {
|
||||||
|
return new ScopeObject(frame, fixCaptures(frame, names), v -> ~v, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScopeObject combine(ObjectValue proto, ScopeObject ...objs) {
|
||||||
|
var res = new ScopeObject(proto);
|
||||||
|
|
||||||
|
for (var el : objs) {
|
||||||
|
res.fields.putAll(el.fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScopeObject all(Frame frame, String[] local, String[] capturables, String[] captures) {
|
||||||
|
return combine((ObjectValue)Value.global(frame.env), locals(frame, local), capturables(frame, capturables), captures(frame, captures));
|
||||||
|
}
|
||||||
|
}
|
1155
src/main/java/me/topchetoeu/jscript/repl/debug/SimpleDebugger.java
Normal file
1155
src/main/java/me/topchetoeu/jscript/repl/debug/SimpleDebugger.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,39 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
import me.topchetoeu.jscript.runtime.Frame;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue;
|
||||||
|
|
||||||
|
public class StackObject extends ArrayLikeValue {
|
||||||
|
public final Frame frame;
|
||||||
|
|
||||||
|
@Override public Value get(int i) {
|
||||||
|
if (!has(i)) return null;
|
||||||
|
return frame.stack[i];
|
||||||
|
}
|
||||||
|
@Override public boolean set(Environment env, int i, Value val) {
|
||||||
|
if (!has(i)) return false;
|
||||||
|
frame.stack[i] = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@Override public boolean has(int i) {
|
||||||
|
return i >= 0 && i < frame.stackPtr;
|
||||||
|
}
|
||||||
|
@Override public boolean remove(int i) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public boolean setSize(int val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public int size() {
|
||||||
|
return frame.stackPtr;
|
||||||
|
}
|
||||||
|
// @Override public void set(int i, Value val) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
public StackObject(Frame frame) {
|
||||||
|
super();
|
||||||
|
this.frame = frame;
|
||||||
|
}
|
||||||
|
}
|
19
src/main/java/me/topchetoeu/jscript/repl/debug/V8Error.java
Normal file
19
src/main/java/me/topchetoeu/jscript/repl/debug/V8Error.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONMap;
|
||||||
|
|
||||||
|
public class V8Error {
|
||||||
|
public final String message;
|
||||||
|
|
||||||
|
public V8Error(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return JSON.stringify(new JSONMap().set("error", new JSONMap()
|
||||||
|
.set("message", message)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
22
src/main/java/me/topchetoeu/jscript/repl/debug/V8Event.java
Normal file
22
src/main/java/me/topchetoeu/jscript/repl/debug/V8Event.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONMap;
|
||||||
|
|
||||||
|
public class V8Event {
|
||||||
|
public final String name;
|
||||||
|
public final JSONMap params;
|
||||||
|
|
||||||
|
public V8Event(String name, JSONMap params) {
|
||||||
|
this.name = name;
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return JSON.stringify(new JSONMap()
|
||||||
|
.set("method", name)
|
||||||
|
.set("params", params)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONElement;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONMap;
|
||||||
|
|
||||||
|
public class V8Message {
|
||||||
|
public final String name;
|
||||||
|
public final int id;
|
||||||
|
public final JSONMap params;
|
||||||
|
|
||||||
|
public V8Message(String name, int id, Map<String, JSONElement> params) {
|
||||||
|
this.name = name;
|
||||||
|
this.params = new JSONMap(params);
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
public V8Result respond(JSONMap result) {
|
||||||
|
return new V8Result(id, result);
|
||||||
|
}
|
||||||
|
public V8Result respond() {
|
||||||
|
return new V8Result(id, new JSONMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
public V8Message(JSONMap raw) {
|
||||||
|
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
|
||||||
|
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
|
||||||
|
|
||||||
|
this.name = raw.string("method");
|
||||||
|
this.id = (int)raw.number("id");
|
||||||
|
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
|
||||||
|
}
|
||||||
|
public V8Message(String raw) {
|
||||||
|
this(JSON.parse(null, raw).map());
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONMap toMap() {
|
||||||
|
var res = new JSONMap();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return JSON.stringify(new JSONMap()
|
||||||
|
.set("method", name)
|
||||||
|
.set("params", params)
|
||||||
|
.set("id", id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
src/main/java/me/topchetoeu/jscript/repl/debug/V8Result.java
Normal file
22
src/main/java/me/topchetoeu/jscript/repl/debug/V8Result.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
import me.topchetoeu.jscript.common.json.JSONMap;
|
||||||
|
|
||||||
|
public class V8Result {
|
||||||
|
public final int id;
|
||||||
|
public final JSONMap result;
|
||||||
|
|
||||||
|
public V8Result(int id, JSONMap result) {
|
||||||
|
this.id = id;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return JSON.stringify(new JSONMap()
|
||||||
|
.set("id", id)
|
||||||
|
.set("result", result)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
186
src/main/java/me/topchetoeu/jscript/repl/debug/WebSocket.java
Normal file
186
src/main/java/me/topchetoeu/jscript/repl/debug/WebSocket.java
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.repl.debug.WebSocketMessage.Type;
|
||||||
|
|
||||||
|
public class WebSocket implements AutoCloseable {
|
||||||
|
public long maxLength = 1 << 20;
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
private boolean closed = false;
|
||||||
|
|
||||||
|
private OutputStream out() throws IOException {
|
||||||
|
return socket.getOutputStream();
|
||||||
|
}
|
||||||
|
private InputStream in() throws IOException {
|
||||||
|
return socket.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long readLen(int byteLen) throws IOException {
|
||||||
|
long res = 0;
|
||||||
|
|
||||||
|
if (byteLen == 126) {
|
||||||
|
res |= in().read() << 8;
|
||||||
|
res |= in().read();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
else if (byteLen == 127) {
|
||||||
|
res |= in().read() << 56;
|
||||||
|
res |= in().read() << 48;
|
||||||
|
res |= in().read() << 40;
|
||||||
|
res |= in().read() << 32;
|
||||||
|
res |= in().read() << 24;
|
||||||
|
res |= in().read() << 16;
|
||||||
|
res |= in().read() << 8;
|
||||||
|
res |= in().read();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
else return byteLen;
|
||||||
|
}
|
||||||
|
private byte[] readMask(boolean has) throws IOException {
|
||||||
|
if (has) {
|
||||||
|
return new byte[] {
|
||||||
|
(byte)in().read(),
|
||||||
|
(byte)in().read(),
|
||||||
|
(byte)in().read(),
|
||||||
|
(byte)in().read()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else return new byte[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLength(int len) throws IOException {
|
||||||
|
if (len < 126) {
|
||||||
|
out().write((int)len);
|
||||||
|
}
|
||||||
|
else if (len <= 0xFFFF) {
|
||||||
|
out().write(126);
|
||||||
|
out().write((int)(len >> 8) & 0xFF);
|
||||||
|
out().write((int)len & 0xFF);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out().write(127);
|
||||||
|
out().write(0);
|
||||||
|
out().write(0);
|
||||||
|
out().write(0);
|
||||||
|
out().write(0);
|
||||||
|
out().write((len >> 24) & 0xFF);
|
||||||
|
out().write((len >> 16) & 0xFF);
|
||||||
|
out().write((len >> 8) & 0xFF);
|
||||||
|
out().write(len & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private synchronized void write(int type, byte[] data) throws IOException {
|
||||||
|
out().write(type | 0x80);
|
||||||
|
writeLength(data.length);
|
||||||
|
out().write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String data) throws IOException {
|
||||||
|
if (closed) throw new IllegalStateException("Websocket is closed.");
|
||||||
|
write(1, data.getBytes());
|
||||||
|
}
|
||||||
|
public void send(byte[] data) throws IOException {
|
||||||
|
if (closed) throw new IllegalStateException("Websocket is closed.");
|
||||||
|
write(2, data);
|
||||||
|
}
|
||||||
|
public void send(WebSocketMessage msg) throws IOException {
|
||||||
|
if (msg.type == Type.Binary) send(msg.binaryData());
|
||||||
|
else send(msg.textData());
|
||||||
|
}
|
||||||
|
public void send(Object data) throws IOException {
|
||||||
|
if (closed) throw new IllegalStateException("Websocket is closed.");
|
||||||
|
write(1, data.toString().getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(String reason) {
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
write(8, reason.getBytes());
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (Throwable e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
socket = null;
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
public void close() {
|
||||||
|
close("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebSocketMessage fail(String reason) {
|
||||||
|
System.out.println("WebSocket Error: " + reason);
|
||||||
|
close(reason);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readData() throws IOException {
|
||||||
|
var maskLen = in().read();
|
||||||
|
var hasMask = (maskLen & 0x80) != 0;
|
||||||
|
var len = (int)readLen(maskLen & 0x7F);
|
||||||
|
var mask = readMask(hasMask);
|
||||||
|
|
||||||
|
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
|
||||||
|
else {
|
||||||
|
var buff = new byte[len];
|
||||||
|
|
||||||
|
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
|
||||||
|
else {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
buff[i] ^= mask[(int)(i % 4)];
|
||||||
|
}
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocketMessage receive() throws IOException {
|
||||||
|
var data = new ByteArrayOutputStream();
|
||||||
|
var type = 0;
|
||||||
|
|
||||||
|
while (socket != null && !closed) {
|
||||||
|
var finId = in().read();
|
||||||
|
if (finId < 0) break;
|
||||||
|
var fin = (finId & 0x80) != 0;
|
||||||
|
int id = finId & 0x0F;
|
||||||
|
|
||||||
|
if (id == 0x8) { close(); return null; }
|
||||||
|
if (id >= 0x8) {
|
||||||
|
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
|
||||||
|
if (id == 0x9) write(0xA, data.toByteArray());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 0) type = id;
|
||||||
|
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
|
||||||
|
|
||||||
|
var buff = readData();
|
||||||
|
if (buff == null) break;
|
||||||
|
|
||||||
|
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
|
||||||
|
data.write(buff);
|
||||||
|
|
||||||
|
if (!fin) continue;
|
||||||
|
var raw = data.toByteArray();
|
||||||
|
|
||||||
|
if (type == 1) {
|
||||||
|
return new WebSocketMessage(new String(raw));
|
||||||
|
}
|
||||||
|
else return new WebSocketMessage(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocket(Socket socket) {
|
||||||
|
this.socket = socket;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.debug;
|
||||||
|
|
||||||
|
public class WebSocketMessage {
|
||||||
|
public static enum Type {
|
||||||
|
Text,
|
||||||
|
Binary,
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Type type;
|
||||||
|
private final Object data;
|
||||||
|
|
||||||
|
public final String textData() {
|
||||||
|
if (type != Type.Text) throw new IllegalStateException("Message is not text.");
|
||||||
|
return (String)data;
|
||||||
|
}
|
||||||
|
public final byte[] binaryData() {
|
||||||
|
if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
|
||||||
|
return (byte[])data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocketMessage(String data) {
|
||||||
|
this.type = Type.Text;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
public WebSocketMessage(byte[] data) {
|
||||||
|
this.type = Type.Binary;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.mapping;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
|
||||||
|
|
||||||
|
public class NativeMapper extends FunctionValue {
|
||||||
|
public final Function<Location, Location> mapper;
|
||||||
|
|
||||||
|
@Override protected Value onApply(Environment env, Value thisArg, Value... args) {
|
||||||
|
var rawLoc = (ArrayLikeValue)args[0];
|
||||||
|
var loc = Location.of(
|
||||||
|
Filename.parse(rawLoc.get(0).toString(env)),
|
||||||
|
rawLoc.get(1).toNumber(env).getInt(),
|
||||||
|
rawLoc.get(2).toNumber(env).getInt()
|
||||||
|
);
|
||||||
|
|
||||||
|
var res = mapper.apply(loc);
|
||||||
|
|
||||||
|
return new ArrayValue(
|
||||||
|
StringValue.of(res.filename().toString()),
|
||||||
|
NumberValue.of(res.line()),
|
||||||
|
NumberValue.of(res.start())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected Value onConstruct(Environment ext, Value target, Value... args) {
|
||||||
|
throw EngineException.ofType("Function cannot be constructed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeMapper(Function<Location, Location> mapper) {
|
||||||
|
super("mapper", 1);
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function<Location, Location> unwrap(Environment env, FunctionValue func) {
|
||||||
|
if (func instanceof NativeMapper nat) return nat.mapper;
|
||||||
|
|
||||||
|
return loc -> {
|
||||||
|
var rawLoc = new ArrayValue(
|
||||||
|
StringValue.of(loc.filename().toString()),
|
||||||
|
NumberValue.of(loc.line()),
|
||||||
|
NumberValue.of(loc.start())
|
||||||
|
);
|
||||||
|
|
||||||
|
var rawRes = (ArrayLikeValue)func.apply(env, Value.UNDEFINED, rawLoc);
|
||||||
|
return Location.of(
|
||||||
|
Filename.parse(rawRes.get(0).toString(env)),
|
||||||
|
rawRes.get(1).toNumber(env).getInt(),
|
||||||
|
rawRes.get(2).toNumber(env).getInt()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
123
src/main/java/me/topchetoeu/jscript/repl/mapping/SourceMap.java
Normal file
123
src/main/java/me/topchetoeu/jscript/repl/mapping/SourceMap.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.mapping;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.json.JSON;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
|
||||||
|
public class SourceMap {
|
||||||
|
private final TreeMap<Long, Long> origToComp = new TreeMap<>();
|
||||||
|
private final TreeMap<Long, Long> compToOrig = new TreeMap<>();
|
||||||
|
private final Filename compiled, original;
|
||||||
|
|
||||||
|
public Location toCompiled(Location loc) { return convert(original, compiled, loc, origToComp); }
|
||||||
|
public Location toOriginal(Location loc) { return convert(compiled, original, loc, compToOrig); }
|
||||||
|
|
||||||
|
private void add(long orig, long comp) {
|
||||||
|
var a = origToComp.remove(orig);
|
||||||
|
var b = compToOrig.remove(comp);
|
||||||
|
|
||||||
|
if (b != null) origToComp.remove(b);
|
||||||
|
if (a != null) compToOrig.remove(a);
|
||||||
|
|
||||||
|
origToComp.put(orig, comp);
|
||||||
|
compToOrig.put(comp, orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceMap apply(SourceMap map) {
|
||||||
|
var res = new SourceMap(map.compiled, map.original);
|
||||||
|
|
||||||
|
for (var el : new ArrayList<>(origToComp.entrySet())) {
|
||||||
|
var mapped = convert(el.getValue(), map.origToComp);
|
||||||
|
res.origToComp.put(el.getKey(), mapped);
|
||||||
|
}
|
||||||
|
for (var el : new ArrayList<>(compToOrig.entrySet())) {
|
||||||
|
var mapped = convert(el.getKey(), map.compToOrig);
|
||||||
|
res.compToOrig.put(mapped, el.getValue());
|
||||||
|
res.add(el.getValue(), mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceMap clone() {
|
||||||
|
var res = new SourceMap(this.compiled, this.original);
|
||||||
|
res.origToComp.putAll(this.origToComp);
|
||||||
|
res.compToOrig.putAll(this.compToOrig);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceMap(Filename compiled, Filename original) {
|
||||||
|
this.compiled = compiled;
|
||||||
|
this.original = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SourceMap parse(Filename compiled, Filename original, String raw) {
|
||||||
|
var mapping = VLQ.decodeMapping(raw);
|
||||||
|
var res = new SourceMap(compiled, original);
|
||||||
|
|
||||||
|
var compRow = 0l;
|
||||||
|
var compCol = 0l;
|
||||||
|
|
||||||
|
for (var origRow = 0; origRow < mapping.length; origRow++) {
|
||||||
|
var origCol = 0;
|
||||||
|
|
||||||
|
for (var rawSeg : mapping[origRow]) {
|
||||||
|
if (rawSeg.length > 1 && rawSeg[1] != 0) throw new IllegalArgumentException("Source mapping is to more than one files.");
|
||||||
|
origCol += rawSeg.length > 0 ? rawSeg[0] : 0;
|
||||||
|
compRow += rawSeg.length > 2 ? rawSeg[2] : 0;
|
||||||
|
compCol += rawSeg.length > 3 ? rawSeg[3] : 0;
|
||||||
|
|
||||||
|
var compPacked = ((long)compRow << 32) | compCol;
|
||||||
|
var origPacked = ((long)origRow << 32) | origCol;
|
||||||
|
|
||||||
|
res.add(origPacked, compPacked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public static List<String> getSources(String raw) {
|
||||||
|
var json = JSON.parse(null, raw).map();
|
||||||
|
return json
|
||||||
|
.list("sourcesContent")
|
||||||
|
.stream()
|
||||||
|
.map(v -> v.string())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SourceMap chain(SourceMap ...maps) {
|
||||||
|
if (maps.length == 0) return null;
|
||||||
|
var res = maps[0];
|
||||||
|
|
||||||
|
for (var i = 1; i < maps.length; i++) res = res.apply(maps[i]);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Long convert(long packed, TreeMap<Long, Long> map) {
|
||||||
|
if (map.containsKey(packed)) return map.get(packed);
|
||||||
|
var key = map.floorKey(packed);
|
||||||
|
if (key == null) return null;
|
||||||
|
else return map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Location convert(Filename src, Filename dst, Location loc, TreeMap<Long, Long> map) {
|
||||||
|
if (!loc.filename().equals(src)) return loc;
|
||||||
|
|
||||||
|
var packed = ((loc.line()) << 32) | (loc.start());
|
||||||
|
var resPacked = convert(packed, map);
|
||||||
|
|
||||||
|
if (resPacked == null) return null;
|
||||||
|
else return Location.of(dst, (int)(resPacked >> 32), (int)(resPacked & 0xFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static SourceMap of(String filename, String raw) {
|
||||||
|
// var json = JSON.parse(Filename.parse(filename), raw);
|
||||||
|
// return new SourceMap();
|
||||||
|
// }
|
||||||
|
}
|
95
src/main/java/me/topchetoeu/jscript/repl/mapping/VLQ.java
Normal file
95
src/main/java/me/topchetoeu/jscript/repl/mapping/VLQ.java
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package me.topchetoeu.jscript.repl.mapping;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VLQ {
|
||||||
|
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
private static long[] toArray(List<Long> list) {
|
||||||
|
var arr = new long[list.size()];
|
||||||
|
for (var i = 0; i < list.size(); i++) arr[i] = list.get(i);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encode(long... arr) {
|
||||||
|
var raw = new StringBuilder();
|
||||||
|
|
||||||
|
for (var data : arr) {
|
||||||
|
var b = data < 0 ? 1 : 0;
|
||||||
|
data = Math.abs(data);
|
||||||
|
b |= (int)(data & 0b1111) << 1;
|
||||||
|
data >>= 4;
|
||||||
|
b |= data > 0 ? 0x20 : 0;;
|
||||||
|
raw.append(ALPHABET.charAt(b));
|
||||||
|
|
||||||
|
while (data > 0) {
|
||||||
|
b = (int)(data & 0b11111);
|
||||||
|
data >>= 5;
|
||||||
|
b |= data > 0 ? 0x20 : 0;
|
||||||
|
raw.append(ALPHABET.charAt(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.toString();
|
||||||
|
}
|
||||||
|
public static long[] decode(String val) {
|
||||||
|
if (val.length() == 0) return new long[0];
|
||||||
|
|
||||||
|
var list = new ArrayList<Long>();
|
||||||
|
|
||||||
|
for (var i = 0; i < val.length();) {
|
||||||
|
var sign = 1;
|
||||||
|
var curr = ALPHABET.indexOf(val.charAt(i++));
|
||||||
|
var cont = (curr & 0x20) == 0x20;
|
||||||
|
if ((curr & 1) == 1) sign = -1;
|
||||||
|
long res = (curr & 0b11110) >> 1;
|
||||||
|
var n = 4;
|
||||||
|
|
||||||
|
for (; i < val.length() && cont;) {
|
||||||
|
curr = ALPHABET.indexOf(val.charAt(i++));
|
||||||
|
cont = (curr & 0x20) == 0x20;
|
||||||
|
res |= (curr & 0b11111) << n;
|
||||||
|
n += 5;
|
||||||
|
if (!cont) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(res * sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toArray(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeMapping(long[][][] arr) {
|
||||||
|
var res = new StringBuilder();
|
||||||
|
var semicolon = false;
|
||||||
|
|
||||||
|
for (var line : arr) {
|
||||||
|
var comma = false;
|
||||||
|
|
||||||
|
if (semicolon) res.append(";");
|
||||||
|
semicolon = true;
|
||||||
|
|
||||||
|
for (var el : line) {
|
||||||
|
if (comma) res.append(",");
|
||||||
|
comma = true;
|
||||||
|
res.append(encode(el));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.toString();
|
||||||
|
}
|
||||||
|
public static long[][][] decodeMapping(String val) {
|
||||||
|
var lines = new ArrayList<long[][]>();
|
||||||
|
|
||||||
|
for (var line : val.split(";", -1)) {
|
||||||
|
var elements = new ArrayList<long[]>();
|
||||||
|
for (var el : line.split(",", -1)) {
|
||||||
|
elements.add(decode(el));
|
||||||
|
}
|
||||||
|
lines.add(elements.toArray(new long[0][]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.toArray(new long[0][][]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,32 @@
|
|||||||
package me.topchetoeu.jscript.runtime;
|
package me.topchetoeu.jscript.runtime;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.FunctionBody;
|
import me.topchetoeu.jscript.common.FunctionBody;
|
||||||
import me.topchetoeu.jscript.common.SyntaxException;
|
import me.topchetoeu.jscript.common.SyntaxException;
|
||||||
import me.topchetoeu.jscript.common.environment.Environment;
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
import me.topchetoeu.jscript.common.environment.Key;
|
import me.topchetoeu.jscript.common.environment.Key;
|
||||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
import me.topchetoeu.jscript.common.parsing.Filename;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.repl.mapping.NativeMapper;
|
||||||
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
import me.topchetoeu.jscript.runtime.debug.DebugContext;
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.runtime.values.Value;
|
import me.topchetoeu.jscript.runtime.values.Value;
|
||||||
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction;
|
||||||
|
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
|
||||||
|
|
||||||
public interface Compiler {
|
public interface Compiler {
|
||||||
public static final Compiler DEFAULT = (env, filename, raw) -> {
|
public static final Compiler DEFAULT = (env, filename, raw, mapper) -> {
|
||||||
try {
|
try {
|
||||||
var res = JavaScript.compile(env, filename, raw, true);
|
var res = JavaScript.compile(env, filename, raw, true);
|
||||||
var body = res.body();
|
var body = res.body();
|
||||||
DebugContext.get(env).onSource(filename, raw);
|
DebugContext.get(env).onSource(filename, raw);
|
||||||
registerFunc(env, body, res);
|
registerFunc(env, body, res, mapper);
|
||||||
return body;
|
return new CodeFunction(env, filename.toString(), body, new Value[0][]);
|
||||||
}
|
}
|
||||||
catch (SyntaxException e) {
|
catch (SyntaxException e) {
|
||||||
var res = EngineException.ofSyntax(e.msg);
|
var res = EngineException.ofSyntax(e.msg);
|
||||||
@ -30,25 +37,45 @@ public interface Compiler {
|
|||||||
|
|
||||||
public Key<Compiler> KEY = new Key<>();
|
public Key<Compiler> KEY = new Key<>();
|
||||||
|
|
||||||
public FunctionBody compile(Environment env, Filename filename, String source);
|
public FunctionValue compile(Environment env, Filename filename, String source, Function<Location, Location> map);
|
||||||
|
|
||||||
|
public default Compiler wrap(Environment compilerEnv, Environment targetEnv, FunctionValue factory) {
|
||||||
|
var curr = new NativeFunction(args -> {
|
||||||
|
var filename = Filename.parse(args.get(0).toString(args.env));
|
||||||
|
var src = args.get(1).toString(args.env);
|
||||||
|
var mapper = (FunctionValue)args.get(2);
|
||||||
|
return this.compile(targetEnv, filename, src, NativeMapper.unwrap(args.env, mapper));
|
||||||
|
});
|
||||||
|
|
||||||
|
var next = (FunctionValue)factory.apply(compilerEnv, Value.UNDEFINED, curr);
|
||||||
|
|
||||||
|
return (env, filename, source, map) -> {
|
||||||
|
return (FunctionValue)next.apply(
|
||||||
|
compilerEnv, Value.UNDEFINED,
|
||||||
|
StringValue.of(filename.toString()),
|
||||||
|
StringValue.of(source),
|
||||||
|
new NativeMapper(map)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static Compiler get(Environment ext) {
|
public static Compiler get(Environment ext) {
|
||||||
return ext.get(KEY, (env, filename, src) -> {
|
return ext.get(KEY, (env, filename, src, map) -> {
|
||||||
throw EngineException.ofError("No compiler attached to engine");
|
throw EngineException.ofError("No compiler attached to engine");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerFunc(Environment env, FunctionBody body, CompileResult res) {
|
static void registerFunc(Environment env, FunctionBody body, CompileResult res, Function<Location, Location> mapper) {
|
||||||
var map = res.map();
|
var map = res.map(mapper);
|
||||||
|
|
||||||
DebugContext.get(env).onFunctionLoad(body, map);
|
DebugContext.get(env).onFunctionLoad(body, map);
|
||||||
|
|
||||||
for (var i = 0; i < body.children.length; i++) {
|
for (var i = 0; i < body.children.length; i++) {
|
||||||
registerFunc(env, body.children[i], res.children.get(i));
|
registerFunc(env, body.children[i], res.children.get(i), mapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CodeFunction compileFunc(Environment env, Filename filename, String raw) {
|
public static FunctionValue compileFunc(Environment env, Filename filename, String raw) {
|
||||||
return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new Value[0][]);
|
return get(env).compile(env, filename, raw, v -> v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user