From 4b84309df608f43cacf7e8985d5ac30f7484e3a4 Mon Sep 17 00:00:00 2001
From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com>
Date: Fri, 27 Oct 2023 15:12:14 +0300
Subject: [PATCH] feat: complete steptrough and breakpoints in debugger
---
build.js | 2 +
src/assets/favicon.png | Bin 0 -> 1720 bytes
src/assets/index.html | 29 +
src/me/topchetoeu/jscript/Filename.java | 52 +
src/me/topchetoeu/jscript/Location.java | 23 +-
src/me/topchetoeu/jscript/Main.java | 55 +-
src/me/topchetoeu/jscript/Reading.java | 36 +
.../jscript/compilation/CompileTarget.java | 13 +-
.../compilation/CompoundStatement.java | 2 +-
.../jscript/compilation/Instruction.java | 5 -
.../jscript/compilation/Statement.java | 2 +-
.../compilation/control/SwitchStatement.java | 3 +-
.../compilation/values/CallStatement.java | 3 +-
.../compilation/values/FunctionStatement.java | 2 +-
.../values/IndexAssignStatement.java | 6 +-
.../compilation/values/IndexStatement.java | 2 +-
.../compilation/values/NewStatement.java | 3 +-
src/me/topchetoeu/jscript/engine/Context.java | 13 +-
src/me/topchetoeu/jscript/engine/Data.java | 5 +-
src/me/topchetoeu/jscript/engine/Engine.java | 12 +-
.../jscript/engine/Environment.java | 2 +-
.../topchetoeu/jscript/engine/StackData.java | 19 +-
.../jscript/engine/debug/DebugController.java | 43 +
.../jscript/engine/debug/DebugHandler.java | 23 +
.../jscript/engine/debug/DebugServer.java | 223 ++
.../jscript/engine/debug/Debugger.java | 5 +
.../engine/debug/DebuggerProvider.java | 5 +
.../jscript/engine/debug/HttpRequest.java | 104 +
.../jscript/engine/debug/SimpleDebugger.java | 585 +++++
.../jscript/engine/debug/V8Error.java | 19 +
.../jscript/engine/debug/V8Event.java | 22 +
.../jscript/engine/debug/V8Message.java | 51 +
.../jscript/engine/debug/V8Result.java | 22 +
.../jscript/engine/debug/WebSocket.java | 208 ++
.../engine/debug/WebSocketMessage.java | 29 +
.../jscript/engine/debug/protocol.json | 1962 +++++++++++++++++
.../jscript/engine/frame/CodeFrame.java | 35 +-
.../jscript/engine/scope/LocalScope.java | 57 +-
.../jscript/engine/values/ArrayValue.java | 2 +-
.../jscript/engine/values/Values.java | 3 +-
.../jscript/exceptions/EngineException.java | 10 +-
.../exceptions/InterruptException.java | 2 +-
.../exceptions/UncheckedIOException.java | 9 +
.../jscript/interop/OverloadFunction.java | 4 +-
src/me/topchetoeu/jscript/json/JSON.java | 22 +-
.../topchetoeu/jscript/json/JSONElement.java | 14 +-
src/me/topchetoeu/jscript/json/JSONList.java | 2 +
src/me/topchetoeu/jscript/json/JSONMap.java | 10 +-
src/me/topchetoeu/jscript/lib/ErrorLib.java | 13 +-
src/me/topchetoeu/jscript/lib/Internals.java | 12 +
src/me/topchetoeu/jscript/lib/JSONLib.java | 3 +-
src/me/topchetoeu/jscript/lib/PromiseLib.java | 79 +-
src/me/topchetoeu/jscript/lib/SymbolLib.java | 1 +
.../topchetoeu/jscript/parsing/Parsing.java | 124 +-
54 files changed, 3779 insertions(+), 213 deletions(-)
create mode 100644 src/assets/favicon.png
create mode 100644 src/assets/index.html
create mode 100644 src/me/topchetoeu/jscript/Filename.java
create mode 100644 src/me/topchetoeu/jscript/Reading.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebugController.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebugHandler.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebugServer.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/Debugger.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Error.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Event.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Message.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Result.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/WebSocket.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
create mode 100644 src/me/topchetoeu/jscript/engine/debug/protocol.json
create mode 100644 src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java
diff --git a/build.js b/build.js
index 34ab125..3906db9 100644
--- a/build.js
+++ b/build.js
@@ -10,6 +10,8 @@ const conf = {
version: argv[3]
};
+console.log(conf)
+
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
diff --git a/src/assets/favicon.png b/src/assets/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..76661fd1edecc7883f125d53e7e4f652cfba0a25
GIT binary patch
literal 1720
zcmV;p21ogcP)EX>4Tx04R}tkv&MmKpe$iTeVUu4t6NwkfAzR1Ql_tRVYG*P%E_RU~=gfG-*gu
zTpR`0f`cE6RRx1kisICAVPqQIx48bLY!8O6cZWRPkQ)=9luB}nOqex
zax9<*6_Voz|AXJ%n#HL}Hz}M1dS7h&V-yJP0jVfq16NwdUuy!hpQJZB
zTI>iI+y*YLJDR))TQYi@7teVjf3S?Vf%0~{Oz
zVeSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{00fXpL_t(o!{wJvXjE4e$A8xu5@|BkMaTyVQ>555hyyd)
zsG9%h!6@!NTU!&7xs-1
zO@by$-VkYx|1LW3`7$#xUv||$TsZH3obPk)eTG*6NwvUMU=i>(U;zWhm<0b7B-Kj#
zUechXza>3aVH|(eg5QzUCuv;M$4`<<3h^^D5bOgQfql+gCX*qRO3~Zfi*4Hs4-Ydm
zG(>rMIZKx=B^r$q3j2M?Sh_a#-%MS+CF
z$+9dhS+eA%!T|*W0bRIoVL~W!`TA$z{!anY=`<%!oZ#-=y8r|N0S+EK$btn6+z{Np
zeVfkCPDV#Zu`J8WYQ3Z;W6ZvpTK}$NLQ6}FYhg`IjoRAU@-nWWp~2nL(9lpQDep`A
zQqsr%X$TJhtpO3f*|uG<=#$Chl+}^N
zpYsX`E|K&JkOE3`*VnFH>(YaRgR`or`uci@{JKzpJ_MSbjPL5|;^D)G04!X%kf%?d
zx*NyF#-gwq2?FAqd3Q=8M4e*;Wrq!QgeLc{ePNz9{?i_~>9m-=E2n1Yu
zWMpJY1&WJ{i9{kqA`$O&3-ArVJdf}(SFUjJ;za;#+n%ig?*U%`U->=vc@5yk3gEzj1Drg0a>B{+
z@o`syIbyw}U!4$a1&VU5Dpcb$#mLAAJv}|Nwzjf+_il_abaZq8yzULbBIjx@EqzgW
zPo6xPvjXSOpXb=IV|k@4X?#KwItYbAF5TVTJ!b{_`ug(bE?>S3aMc)d)$u^TvzV<>
z0NUHz$z(E=l$6X!=}?^RaB%GIs3P!J0K|Ea14e{(tbc(oLH-xoz{t
zjT@7;*_}Ig%CfAyR=i=u26s;~nao`t$d>%SWhf~E#Jv`*T)C3=_I8}A@HUaMvND%$
zZf@rK_3LchxRJ`rO77jeM^8@=H*emY%sJ!0o5mR1+XsHheVBNK^!N8q;1G>QS+{N-
zmo8m$n^9w9rKx^VB_y?HLSEK4mdEn2l|
zRo+dvc=2MFj>qHP5DgVl&<{-7_sh%6NhI?BErP)y!C;VZI80|}Cz(u!;^Jb;%E}0b
z!xR-20Z>s<;Uw^5AU4^(uT!T^HX{oNvfLdd%slDN7>)4F-gy|
+
+
+
+
+ JScript Debugger
+
+
+
+ This is the debugger of JScript. It implement the V8 Debugging protocol ,
+ so you can use the devtools in chrome.
+ The debugger is still in early development, so please report any issues to
+ the github repo .
+
+
+
+ Here are the available entrypoints:
+
+
+
+
+ Developed by TopchetoEU, MIT License
+
+
+
\ No newline at end of file
diff --git a/src/me/topchetoeu/jscript/Filename.java b/src/me/topchetoeu/jscript/Filename.java
new file mode 100644
index 0000000..ff7d17d
--- /dev/null
+++ b/src/me/topchetoeu/jscript/Filename.java
@@ -0,0 +1,52 @@
+package me.topchetoeu.jscript;
+
+import java.io.File;
+
+public class Filename {
+ public final String protocol;
+ public final String path;
+
+ public String toString() {
+ return protocol + "://" + path;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + protocol.hashCode();
+ result = prime * result + path.hashCode();
+ return result;
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ var other = (Filename) obj;
+
+ if (protocol == null) {
+ if (other.protocol != null) return false;
+ }
+ else if (!protocol.equals(other.protocol)) return false;
+
+ if (path == null) {
+ if (other.path != null) return false;
+ }
+ else if (!path.equals(other.path)) return false;
+ return true;
+ }
+
+ public static Filename fromFile(File file) {
+ return new Filename("file", file.getAbsolutePath());
+ }
+
+
+ public Filename(String protocol, String path) {
+ this.protocol = protocol;
+ this.path = path;
+ }
+}
diff --git a/src/me/topchetoeu/jscript/Location.java b/src/me/topchetoeu/jscript/Location.java
index bec95d3..40b3138 100644
--- a/src/me/topchetoeu/jscript/Location.java
+++ b/src/me/topchetoeu/jscript/Location.java
@@ -1,18 +1,18 @@
package me.topchetoeu.jscript;
-public class Location {
- public static final Location INTERNAL = new Location(0, 0, "");
+public class Location implements Comparable {
+ public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "internal"));
private int line;
private int start;
- private String filename;
+ private Filename filename;
public int line() { return line; }
public int start() { return start; }
- public String filename() { return filename; }
+ public Filename filename() { return filename; }
@Override
public String toString() {
- return filename + ":" + line + ":" + start;
+ return filename.toString() + ":" + line + ":" + start;
}
public Location add(int n, boolean clone) {
@@ -55,7 +55,18 @@ public class Location {
return true;
}
- public Location(int line, int start, String filename) {
+ @Override
+ public int compareTo(Location other) {
+ int a = filename.toString().compareTo(other.filename.toString());
+ int b = Integer.compare(line, other.line);
+ int c = Integer.compare(start, other.start);
+
+ if (a != 0) return a;
+ if (b != 0) return b;
+ return c;
+ }
+
+ public Location(int line, int start, Filename filename) {
this.line = line;
this.start = start;
this.filename = filename;
diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java
index e34ddcf..80c6620 100644
--- a/src/me/topchetoeu/jscript/Main.java
+++ b/src/me/topchetoeu/jscript/Main.java
@@ -1,15 +1,15 @@
package me.topchetoeu.jscript;
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment;
+import me.topchetoeu.jscript.engine.debug.DebugServer;
+import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer;
@@ -20,30 +20,10 @@ import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.lib.Internals;
public class Main {
- static Thread task;
+ static Thread engineTask, debugTask;
static Engine engine;
static Environment env;
- public static String streamToString(InputStream in) {
- try {
- StringBuilder out = new StringBuilder();
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
-
- for(var line = br.readLine(); line != null; line = br.readLine()) {
- out.append(line).append('\n');
- }
-
- br.close();
- return out.toString();
- }
- catch (Throwable e) { throw new UncheckedException(e); }
- }
- public static String resourceToString(String name) {
- var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
- if (str == null) return null;
- return streamToString(str);
- }
-
private static Observer valuePrinter = new Observer() {
public void next(Object data) {
Values.printValue(null, data);
@@ -56,17 +36,18 @@ public class Main {
@Override
public void finish() {
- task.interrupt();
+ engineTask.interrupt();
}
};
public static void main(String args[]) {
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
- var in = new BufferedReader(new InputStreamReader(System.in));
engine = new Engine();
env = new Environment(null, null, null);
var exited = new boolean[1];
+ var server = new DebugServer();
+ server.targets.put("target", (ws, req) -> SimpleDebugger.get(ws, engine));
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
new Internals().apply(env);
@@ -77,7 +58,8 @@ public class Main {
});
env.global.define("go", _ctx -> {
try {
- var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js"))));
+ var f = Path.of("do.js");
+ var func = _ctx.compile(Filename.fromFile(f.toFile()), new String(Files.readAllBytes(f)));
return func.call(_ctx);
}
catch (IOException e) {
@@ -88,15 +70,17 @@ public class Main {
return null;
}), null);
- task = engine.start();
+ engineTask = engine.start();
+ debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
+
var reader = new Thread(() -> {
try {
- while (true) {
+ for (var i = 0; ; i++) {
try {
- var raw = in.readLine();
+ var raw = Reading.read();
if (raw == null) break;
- engine.pushMsg(false, new Context(engine).pushEnv(env), "", raw, null).toObservable().once(valuePrinter);
+ valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
@@ -107,12 +91,13 @@ public class Main {
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
- if (exited[0]) return;
- System.out.println("Internal error ocurred:");
- ex.printStackTrace();
+ if (!exited[0]) {
+ System.out.println("Internal error ocurred:");
+ ex.printStackTrace();
+ }
}
catch (Throwable e) { throw new UncheckedException(e); }
- if (exited[0]) return;
+ if (exited[0]) debugTask.interrupt();
});
reader.setDaemon(true);
reader.setName("STD Reader");
diff --git a/src/me/topchetoeu/jscript/Reading.java b/src/me/topchetoeu/jscript/Reading.java
new file mode 100644
index 0000000..6d797c8
--- /dev/null
+++ b/src/me/topchetoeu/jscript/Reading.java
@@ -0,0 +1,36 @@
+package me.topchetoeu.jscript;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import me.topchetoeu.jscript.exceptions.UncheckedException;
+
+public class Reading {
+ private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+
+ public static synchronized String read() throws IOException {
+ return reader.readLine();
+ }
+
+ public static String streamToString(InputStream in) {
+ try {
+ StringBuilder out = new StringBuilder();
+ BufferedReader br = new BufferedReader(new InputStreamReader(in));
+
+ for(var line = br.readLine(); line != null; line = br.readLine()) {
+ out.append(line).append('\n');
+ }
+
+ br.close();
+ return out.toString();
+ }
+ catch (Throwable e) { throw new UncheckedException(e); }
+ }
+ public static String resourceToString(String name) {
+ var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
+ if (str == null) return null;
+ return streamToString(str);
+ }
+}
diff --git a/src/me/topchetoeu/jscript/compilation/CompileTarget.java b/src/me/topchetoeu/jscript/compilation/CompileTarget.java
index 0a207ec..4b529cc 100644
--- a/src/me/topchetoeu/jscript/compilation/CompileTarget.java
+++ b/src/me/topchetoeu/jscript/compilation/CompileTarget.java
@@ -1,11 +1,15 @@
package me.topchetoeu.jscript.compilation;
import java.util.Map;
+import java.util.TreeSet;
import java.util.Vector;
+import me.topchetoeu.jscript.Location;
+
public class CompileTarget {
public final Vector target = new Vector<>();
public final Map functions;
+ public final TreeSet breakpoints;
public Instruction add(Instruction instr) {
target.add(instr);
@@ -14,6 +18,12 @@ public class CompileTarget {
public Instruction set(int i, Instruction instr) {
return target.set(i, instr);
}
+ public void setDebug(int i) {
+ breakpoints.add(target.get(i).location);
+ }
+ public void setDebug() {
+ setDebug(target.size() - 1);
+ }
public Instruction get(int i) {
return target.get(i);
}
@@ -21,7 +31,8 @@ public class CompileTarget {
public Instruction[] array() { return target.toArray(Instruction[]::new); }
- public CompileTarget(Map functions) {
+ public CompileTarget(Map functions, TreeSet breakpoints) {
this.functions = functions;
+ this.breakpoints = breakpoints;
}
}
diff --git a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java
index c76f6ce..cbee47b 100644
--- a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java
+++ b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java
@@ -25,7 +25,7 @@ public class CompoundStatement extends Statement {
if (stm instanceof FunctionStatement) {
int start = target.size();
((FunctionStatement)stm).compile(target, scope, null, true);
- target.get(start).setDebug(true);
+ target.setDebug(start);
target.add(Instruction.discard());
}
}
diff --git a/src/me/topchetoeu/jscript/compilation/Instruction.java b/src/me/topchetoeu/jscript/compilation/Instruction.java
index 4e3efe5..3099de9 100644
--- a/src/me/topchetoeu/jscript/compilation/Instruction.java
+++ b/src/me/topchetoeu/jscript/compilation/Instruction.java
@@ -90,16 +90,11 @@ public class Instruction {
public final Type type;
public final Object[] params;
public Location location;
- public boolean debugged;
public Instruction locate(Location loc) {
this.location = loc;
return this;
}
- public Instruction setDebug(boolean debug) {
- debugged = debug;
- return this;
- }
@SuppressWarnings("unchecked")
public T get(int i) {
diff --git a/src/me/topchetoeu/jscript/compilation/Statement.java b/src/me/topchetoeu/jscript/compilation/Statement.java
index 7b51b38..663ed12 100644
--- a/src/me/topchetoeu/jscript/compilation/Statement.java
+++ b/src/me/topchetoeu/jscript/compilation/Statement.java
@@ -14,7 +14,7 @@ public abstract class Statement {
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) {
int start = target.size();
compile(target, scope, pollute);
- if (target.size() != start) target.get(start).setDebug(true);
+ if (target.size() != start) target.setDebug(start);
}
public Location loc() { return _loc; }
diff --git a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java
index 5ed0a1d..5e5b970 100644
--- a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java
+++ b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java
@@ -68,7 +68,8 @@ public class SwitchStatement extends Statement {
var loc = target.get(el.getKey()).location;
var i = stmIndexMap.get(el.getValue());
if (i == null) i = target.size();
- target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true));
+ target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc));
+ target.setDebug(el.getKey());
}
target.add(Instruction.discard().locate(loc()));
diff --git a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java
index b63a93d..db34e44 100644
--- a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java
+++ b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java
@@ -22,7 +22,8 @@ public class CallStatement extends Statement {
for (var arg : args) arg.compile(target, scope, true);
- target.add(Instruction.call(args.length).locate(loc()).setDebug(true));
+ target.add(Instruction.call(args.length).locate(loc()));
+ target.setDebug();
if (!pollute) target.add(Instruction.discard().locate(loc()));
}
diff --git a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java
index ca11b27..e75f80e 100644
--- a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java
+++ b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java
@@ -51,7 +51,7 @@ public class FunctionStatement extends Statement {
var subscope = scope.child();
int start = target.size();
- var funcTarget = new CompileTarget(target.functions);
+ var funcTarget = new CompileTarget(target.functions, target.breakpoints);
subscope.define("this");
var argsVar = subscope.define("arguments");
diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java
index 55a99cd..2cf8b00 100644
--- a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java
+++ b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java
@@ -24,14 +24,16 @@ public class IndexAssignStatement extends Statement {
value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc()));
- target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
+ target.add(Instruction.storeMember(pollute).locate(loc()));
+ target.setDebug();
}
else {
object.compile(target, scope, true);
index.compile(target, scope, true);
value.compile(target, scope, true);
- target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
+ target.add(Instruction.storeMember(pollute).locate(loc()));
+ target.setDebug();
}
}
diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java
index 6159f4f..b2494d6 100644
--- a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java
+++ b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java
@@ -30,7 +30,7 @@ public class IndexStatement extends AssignableStatement {
index.compile(target, scope, true);
target.add(Instruction.loadMember().locate(loc()));
- target.get(start).setDebug(true);
+ target.setDebug(start);
if (!pollute) target.add(Instruction.discard().locate(loc()));
}
@Override
diff --git a/src/me/topchetoeu/jscript/compilation/values/NewStatement.java b/src/me/topchetoeu/jscript/compilation/values/NewStatement.java
index dbb632c..d60ef3a 100644
--- a/src/me/topchetoeu/jscript/compilation/values/NewStatement.java
+++ b/src/me/topchetoeu/jscript/compilation/values/NewStatement.java
@@ -16,7 +16,8 @@ public class NewStatement extends Statement {
for (var arg : args) arg.compile(target, scope, true);
- target.add(Instruction.callNew(args.length).locate(loc()).setDebug(true));
+ target.add(Instruction.callNew(args.length).locate(loc()));
+ target.setDebug();
}
public NewStatement(Location loc, Statement func, Statement ...args) {
diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java
index 366b0bc..44d7c32 100644
--- a/src/me/topchetoeu/jscript/engine/Context.java
+++ b/src/me/topchetoeu/jscript/engine/Context.java
@@ -1,7 +1,10 @@
package me.topchetoeu.jscript.engine;
import java.util.Stack;
+import java.util.TreeSet;
+import me.topchetoeu.jscript.Filename;
+import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.parsing.Parsing;
@@ -23,9 +26,13 @@ public class Context {
if (!env.empty()) this.env.pop();
}
- public FunctionValue compile(String filename, String raw) {
- var res = Values.toString(this, environment().compile.call(this, null, raw, filename));
- return Parsing.compile(engine.functions, environment(), filename, res);
+ public FunctionValue compile(Filename filename, String raw) {
+ var src = Values.toString(this, environment().compile.call(this, null, raw, filename));
+ var debugger = StackData.getDebugger(this);
+ var breakpoints = new TreeSet();
+ var res = Parsing.compile(engine.functions, breakpoints, environment(), filename, src);
+ if (debugger != null) debugger.onSource(filename, src, breakpoints);
+ return res;
}
public Context(Engine engine, Data data) {
diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java
index 828c25b..6c2d58f 100644
--- a/src/me/topchetoeu/jscript/engine/Data.java
+++ b/src/me/topchetoeu/jscript/engine/Data.java
@@ -35,8 +35,7 @@ public class Data {
public T get(DataKey key, T val) {
for (var it = this; it != null; it = it.parent) {
if (it.data.containsKey(key)) {
- this.set(key, val);
- return (T)data.get((DataKey)key);
+ return (T)it.data.get((DataKey)key);
}
}
@@ -45,7 +44,7 @@ public class Data {
}
public T get(DataKey key) {
for (var it = this; it != null; it = it.parent) {
- if (it.data.containsKey(key)) return (T)data.get((DataKey)key);
+ if (it.data.containsKey(key)) return (T)it.data.get((DataKey)key);
}
return null;
}
diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java
index 9176f7a..a9d638d 100644
--- a/src/me/topchetoeu/jscript/engine/Engine.java
+++ b/src/me/topchetoeu/jscript/engine/Engine.java
@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingDeque;
+import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.events.Awaitable;
@@ -11,7 +12,7 @@ import me.topchetoeu.jscript.exceptions.InterruptException;
public class Engine {
private class UncompiledFunction extends FunctionValue {
- public final String filename;
+ public final Filename filename;
public final String raw;
private FunctionValue compiled = null;
@@ -21,8 +22,8 @@ public class Engine {
return compiled.call(ctx, thisArg, args);
}
- public UncompiledFunction(String filename, String raw) {
- super(filename, 0);
+ public UncompiledFunction(Filename filename, String raw) {
+ super(filename + "", 0);
this.filename = filename;
this.raw = raw;
}
@@ -58,6 +59,7 @@ public class Engine {
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
}
catch (RuntimeException e) {
+ if (e instanceof InterruptException) throw e;
task.notifier.error(e);
}
}
@@ -70,7 +72,7 @@ public class Engine {
runTask(microTasks.take());
}
}
- catch (InterruptedException e) {
+ catch (InterruptedException | InterruptException e) {
for (var msg : macroTasks) {
msg.notifier.error(new InterruptException(e));
}
@@ -103,7 +105,7 @@ public class Engine {
else macroTasks.addLast(msg);
return msg.notifier;
}
- public Awaitable pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) {
+ public Awaitable pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
}
}
diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java
index 5afe1c2..30c055b 100644
--- a/src/me/topchetoeu/jscript/engine/Environment.java
+++ b/src/me/topchetoeu/jscript/engine/Environment.java
@@ -24,7 +24,7 @@ public class Environment {
@Native public FunctionValue compile;
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
- throw EngineException.ofError("Regular expressions not supported.").setContext(ctx);
+ throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
});
public Environment addData(Data data) {
diff --git a/src/me/topchetoeu/jscript/engine/StackData.java b/src/me/topchetoeu/jscript/engine/StackData.java
index 873132e..820016a 100644
--- a/src/me/topchetoeu/jscript/engine/StackData.java
+++ b/src/me/topchetoeu/jscript/engine/StackData.java
@@ -4,14 +4,14 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import me.topchetoeu.jscript.engine.debug.DebugServer;
+import me.topchetoeu.jscript.engine.debug.Debugger;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException;
public class StackData {
public static final DataKey> FRAMES = new DataKey<>();
public static final DataKey MAX_FRAMES = new DataKey<>();
- public static final DataKey DEBUGGER = new DataKey<>();
+ public static final DataKey DEBUGGER = new DataKey<>();
public static void pushFrame(Context ctx, CodeFrame frame) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
@@ -25,16 +25,25 @@ public class StackData {
if (frames.get(frames.size() - 1) != frame) return false;
frames.remove(frames.size() - 1);
ctx.popEnv();
+ var dbg = getDebugger(ctx);
+ if (dbg != null) dbg.onFramePop(ctx, frame);
return true;
}
+ public static CodeFrame peekFrame(Context ctx) {
+ var frames = ctx.data.get(FRAMES, new ArrayList<>());
+ if (frames.size() == 0) return null;
+ return frames.get(frames.size() - 1);
+ }
public static List frames(Context ctx) {
return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>()));
}
public static List stackTrace(Context ctx) {
var res = new ArrayList();
+ var frames = frames(ctx);
- for (var el : frames(ctx)) {
+ for (var i = frames.size() - 1; i >= 0; i--) {
+ var el = frames.get(i);
var name = el.function.name;
var loc = el.function.loc();
var trace = "";
@@ -49,4 +58,8 @@ public class StackData {
return res;
}
+
+ public static Debugger getDebugger(Context ctx) {
+ return ctx.data.get(DEBUGGER);
+ }
}
diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugController.java b/src/me/topchetoeu/jscript/engine/debug/DebugController.java
new file mode 100644
index 0000000..beec045
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/DebugController.java
@@ -0,0 +1,43 @@
+package me.topchetoeu.jscript.engine.debug;
+
+import java.util.TreeSet;
+
+import me.topchetoeu.jscript.Filename;
+import me.topchetoeu.jscript.Location;
+import me.topchetoeu.jscript.compilation.Instruction;
+import me.topchetoeu.jscript.engine.Context;
+import me.topchetoeu.jscript.engine.frame.CodeFrame;
+import me.topchetoeu.jscript.exceptions.EngineException;
+
+public interface DebugController {
+ /**
+ * Called when a script has been loaded
+ * @param breakpoints
+ */
+ void onSource(Filename filename, String source, TreeSet breakpoints);
+
+ /**
+ * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
+ * This function might pause in order to await debugging commands.
+ * @param ctx The context of execution
+ * @param frame The frame in which execution is occuring
+ * @param instruction The instruction which was or will be executed
+ * @param loc The most recent location the code frame has been at
+ * @param returnVal The return value of the instruction, Runners.NO_RETURN if none
+ * @param error The error that the instruction threw, null if none
+ * @param caught Whether or not the error has been caught
+ * @return Whether or not the frame should restart
+ */
+ boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
+
+ /**
+ * Called immediatly after a frame has been popped out of the frame stack.
+ * This function might pause in order to await debugging commands.
+ * @param ctx The context of execution
+ * @param frame The code frame which was popped out
+ */
+ void onFramePop(Context ctx, CodeFrame frame);
+
+ void connect();
+ void disconnect();
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java
new file mode 100644
index 0000000..b10f961
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java
@@ -0,0 +1,23 @@
+package me.topchetoeu.jscript.engine.debug;
+
+public interface DebugHandler {
+ void enable(V8Message msg);
+ void disable(V8Message msg);
+
+ void setBreakpoint(V8Message msg);
+ void setBreakpointByUrl(V8Message msg);
+ void removeBreakpoint(V8Message msg);
+ void continueToLocation(V8Message msg);
+
+ void getScriptSource(V8Message msg);
+ void getPossibleBreakpoints(V8Message msg);
+
+ void resume(V8Message msg);
+ void pause(V8Message msg);
+
+ void stepInto(V8Message msg);
+ void stepOut(V8Message msg);
+ void stepOver(V8Message msg);
+
+ void setPauseOnExceptions(V8Message msg);
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java
new file mode 100644
index 0000000..54eade4
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java
@@ -0,0 +1,223 @@
+package me.topchetoeu.jscript.engine.debug;
+
+import java.io.IOException;
+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.engine.debug.WebSocketMessage.Type;
+import me.topchetoeu.jscript.exceptions.SyntaxException;
+import me.topchetoeu.jscript.exceptions.UncheckedException;
+import me.topchetoeu.jscript.exceptions.UncheckedIOException;
+import me.topchetoeu.jscript.json.JSON;
+import me.topchetoeu.jscript.json.JSONList;
+import me.topchetoeu.jscript.json.JSONMap;
+
+public class DebugServer {
+ public static String browserDisplayName = "jscript";
+
+ public final HashMap targets = new HashMap<>();
+
+ private final byte[] favicon, index;
+
+ 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 UncheckedException(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) {
+ WebSocketMessage raw;
+
+ debugger.connect();
+
+ 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());
+ System.out.println(msg.name + ": " + JSON.stringify(msg.params));
+ }
+ catch (SyntaxException e) {
+ ws.send(new V8Error(e.getMessage()));
+ return;
+ }
+
+ try {
+ switch (msg.name) {
+ case "Debugger.enable": debugger.enable(msg); continue;
+ case "Debugger.disable": debugger.disable(msg); continue;
+
+ case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); 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;
+ }
+
+ 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..."));
+
+ if (msg.name.startsWith("Runtime.") || msg.name.startsWith("Profiler.")) ws.send(new V8Error("This API is not supported yet."));
+ }
+ catch (Throwable e) {
+ e.printStackTrace();
+ throw new UncheckedException(e);
+ }
+ }
+
+ debugger.disconnect();
+ }
+ 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(() -> {
+ try { handle(ws, debugger); }
+ catch (RuntimeException e) {
+ ws.send(new V8Error(e.getMessage()));
+ }
+ finally { ws.close(); debugger.disconnect(); }
+ }, "Debug Handler");
+ }
+
+ 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.2\"}");
+ 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/new":
+ case "/json/activate":
+ case "/json/protocol":
+ 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() {
+ try {
+ this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes();
+ this.index = getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes();
+ }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/Debugger.java b/src/me/topchetoeu/jscript/engine/debug/Debugger.java
new file mode 100644
index 0000000..3c704af
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/Debugger.java
@@ -0,0 +1,5 @@
+package me.topchetoeu.jscript.engine.debug;
+
+public interface Debugger extends DebugHandler, DebugController {
+
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java b/src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java
new file mode 100644
index 0000000..cd15ffc
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java
@@ -0,0 +1,5 @@
+package me.topchetoeu.jscript.engine.debug;
+
+public interface DebuggerProvider {
+ Debugger getDebugger(WebSocket socket, HttpRequest req);
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
new file mode 100644
index 0000000..d327d8e
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
@@ -0,0 +1,104 @@
+package me.topchetoeu.jscript.engine.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.exceptions.UncheckedIOException;
+
+public class HttpRequest {
+ public final String method;
+ public final String path;
+ public final Map 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) { throw new UncheckedIOException(e); }
+ }
+ public void writeHeader(String name, String value) {
+ try { out.write((name + ": " + value + "\r\n").getBytes()); }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+ public void writeLastHeader(String name, String value) {
+ try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+ public void writeHeadersEnd() {
+ try { out.write("\n".getBytes()); }
+ catch (IOException e) { throw new UncheckedIOException(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) { throw new UncheckedIOException(e); }
+ }
+ public void writeResponse(int code, String name, String type, InputStream data) {
+ try {
+ writeResponse(code, name, type, data.readAllBytes());
+ }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+
+ public HttpRequest(String method, String path, Map 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();
+
+ 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 e) { throw new UncheckedIOException(e); }
+ }
+}
+
diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java
new file mode 100644
index 0000000..96dbe93
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java
@@ -0,0 +1,585 @@
+package me.topchetoeu.jscript.engine.debug;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import me.topchetoeu.jscript.Filename;
+import me.topchetoeu.jscript.Location;
+import me.topchetoeu.jscript.compilation.Instruction;
+import me.topchetoeu.jscript.compilation.Instruction.Type;
+import me.topchetoeu.jscript.engine.Context;
+import me.topchetoeu.jscript.engine.Engine;
+import me.topchetoeu.jscript.engine.StackData;
+import me.topchetoeu.jscript.engine.frame.CodeFrame;
+import me.topchetoeu.jscript.engine.frame.Runners;
+import me.topchetoeu.jscript.engine.values.ArrayValue;
+import me.topchetoeu.jscript.engine.values.CodeFunction;
+import me.topchetoeu.jscript.engine.values.FunctionValue;
+import me.topchetoeu.jscript.engine.values.ObjectValue;
+import me.topchetoeu.jscript.engine.values.Symbol;
+import me.topchetoeu.jscript.engine.values.Values;
+import me.topchetoeu.jscript.events.Notifier;
+import me.topchetoeu.jscript.exceptions.EngineException;
+import me.topchetoeu.jscript.json.JSONElement;
+import me.topchetoeu.jscript.json.JSONList;
+import me.topchetoeu.jscript.json.JSONMap;
+import me.topchetoeu.jscript.lib.DateLib;
+import me.topchetoeu.jscript.lib.MapLib;
+import me.topchetoeu.jscript.lib.PromiseLib;
+import me.topchetoeu.jscript.lib.RegExpLib;
+import me.topchetoeu.jscript.lib.SetLib;
+import me.topchetoeu.jscript.lib.GeneratorLib.Generator;
+
+public class SimpleDebugger implements Debugger {
+ private static enum State {
+ RESUMED,
+ STEPPING_IN,
+ STEPPING_OUT,
+ STEPPING_OVER,
+ PAUSED_NORMAL,
+ PAUSED_EXCEPTION,
+ }
+ private static enum CatchType {
+ NONE,
+ UNCAUGHT,
+ ALL,
+ }
+ private static class Source {
+ public final int id;
+ public final Filename filename;
+ public final String source;
+ public final TreeSet breakpoints;
+
+ public Source(int id, Filename filename, String source, TreeSet breakpoints) {
+ this.id = id;
+ this.filename = filename;
+ this.source = source;
+ this.breakpoints = breakpoints;
+ }
+ }
+ private static class Breakpoint {
+ public final int id;
+ public final Location location;
+ public final String condition;
+
+ public Breakpoint(int id, Location location, String condition) {
+ this.id = id;
+ this.location = location;
+ this.condition = condition;
+ }
+ }
+ private static class BreakpointCandidate {
+ public final int id;
+ public final String condition;
+ public final Pattern pattern;
+ public final int line, start;
+ public final HashSet resolvedBreakpoints = new HashSet<>();
+
+ public BreakpointCandidate(int id, Pattern pattern, int line, int start, String condition) {
+ this.id = id;
+ this.pattern = pattern;
+ this.line = line;
+ this.start = start;
+ this.condition = condition;
+ }
+ }
+ private class Frame {
+ public CodeFrame frame;
+ public CodeFunction func;
+ public int id;
+ public ObjectValue local = new ObjectValue(), capture = new ObjectValue(), global;
+ public JSONMap serialized;
+ public Location location;
+
+ public void updateLoc(Location loc) {
+ serialized.set("location", serializeLocation(loc));
+ this.location = loc;
+ }
+
+ public Frame(Context ctx, CodeFrame frame, int id) {
+ this.frame = frame;
+ this.func = frame.function;
+ this.id = id;
+ this.local = new ObjectValue();
+ this.location = frame.function.loc();
+
+ this.global = frame.function.environment.global.obj;
+ frame.scope.applyToObject(ctx, this.local, this.capture, true);
+
+ this.serialized = new JSONMap()
+ .set("callFrameId", id + "")
+ .set("functionName", func.name)
+ .set("location", serializeLocation(func.loc()))
+ .set("scopeChain", new JSONList()
+ .add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
+ .add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
+ .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
+ )
+ .setNull("this");
+ }
+ }
+
+ public boolean enabled = false;
+ public CatchType execptionType = CatchType.ALL;
+ public State state = State.RESUMED;
+
+ public final WebSocket ws;
+ public final Engine target;
+
+ private HashMap idToBptCand = new HashMap<>();
+
+ private HashMap idToBreakpoint = new HashMap<>();
+ private HashMap locToBreakpoint = new HashMap<>();
+ private HashSet tmpBreakpts = new HashSet<>();
+
+ private HashMap filenameToId = new HashMap<>();
+ private HashMap idToSource = new HashMap<>();
+ private ArrayList pendingSources = new ArrayList<>();
+
+ private HashMap idToFrame = new HashMap<>();
+ private HashMap codeFrameToFrame = new HashMap<>();
+
+ private HashMap idToObject = new HashMap<>();
+ private HashMap objectToId = new HashMap<>();
+
+ private Notifier updateNotifier = new Notifier();
+
+ private int nextId = new Random().nextInt() & 0x7FFFFFFF;
+ private Location prevLocation = null;
+ private Frame stepOutFrame = null, currFrame = null;
+
+ private int nextId() {
+ return nextId++ ^ 1630022591 /* big prime */;
+ }
+
+ private void updateFrames(Context ctx) {
+ var frame = StackData.peekFrame(ctx);
+ if (frame == null) return;
+
+ if (!codeFrameToFrame.containsKey(frame)) {
+ var id = nextId();
+ var fr = new Frame(ctx, frame, id);
+
+ idToFrame.put(id, fr);
+ codeFrameToFrame.put(frame, fr);
+ }
+
+ currFrame = codeFrameToFrame.get(frame);
+ }
+ private JSONList serializeFrames(Context ctx) {
+ var res = new JSONList();
+ var frames = StackData.frames(ctx);
+
+ for (var i = frames.size() - 1; i >= 0; i--) {
+ res.add(codeFrameToFrame.get(frames.get(i)).serialized);
+ }
+
+ return res;
+ }
+
+ private Location correctLocation(Source source, Location loc) {
+ var set = source.breakpoints;
+
+ if (set.contains(loc)) return loc;
+
+ var tail = set.tailSet(loc);
+ if (tail.isEmpty()) return null;
+
+ return tail.first();
+ }
+ public Location deserializeLocation(JSONElement el, boolean correct) {
+ if (!el.isMap()) throw new RuntimeException("Expected location to be a map.");
+ var id = Integer.parseInt(el.map().string("scriptId"));
+ var line = (int)el.map().number("lineNumber") + 1;
+ var column = (int)el.map().number("columnNumber") + 1;
+
+ if (!idToSource.containsKey(id)) throw new RuntimeException("The specified source %s doesn't exist.".formatted(id));
+
+ var res = new Location(line, column, idToSource.get(id).filename);
+ if (correct) res = correctLocation(idToSource.get(id), res);
+ return res;
+ }
+ public JSONMap serializeLocation(Location loc) {
+ var source = filenameToId.get(loc.filename());
+ return new JSONMap()
+ .set("scriptId", source + "")
+ .set("lineNumber", loc.line() - 1)
+ .set("columnNumber", loc.start() - 1);
+ }
+
+ private Integer objectId(ObjectValue obj) {
+ if (objectToId.containsKey(obj)) return objectToId.get(obj);
+ else {
+ int id = nextId();
+ objectToId.put(obj, id);
+ idToObject.put(id, obj);
+ return id;
+ }
+ }
+ private JSONMap serializeObj(Context ctx, Object val) {
+ val = Values.normalize(null, val);
+
+ if (val == Values.NULL) {
+ return new JSONMap()
+ .set("objectId", objectId(null) + "")
+ .set("type", "object")
+ .set("subtype", "null")
+ .setNull("value")
+ .set("description", "null");
+ }
+
+ if (val instanceof ObjectValue) {
+ var obj = (ObjectValue)val;
+ var id = objectId(obj);
+ var type = "object";
+ String subtype = null;
+ String className = null;
+
+ if (obj instanceof FunctionValue) type = "function";
+ if (obj instanceof ArrayValue) subtype = "array";
+ if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp";
+ if (Values.isWrapper(val, DateLib.class)) subtype = "date";
+ if (Values.isWrapper(val, MapLib.class)) subtype = "map";
+ if (Values.isWrapper(val, SetLib.class)) subtype = "set";
+ if (Values.isWrapper(val, Generator.class)) subtype = "generator";
+ if (Values.isWrapper(val, PromiseLib.class)) subtype = "promise";
+
+ try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
+ catch (Exception e) { }
+
+ var res = new JSONMap()
+ .set("type", type)
+ .set("objetId", id + "");
+
+ if (subtype != null) res.set("subtype", subtype);
+ if (className != null) res.set("className", className);
+
+ return res;
+ }
+
+ if (val == null) return new JSONMap().set("type", "undefined");
+ if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val);
+ if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val);
+ if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString());
+ if (val instanceof Number) {
+ var num = (double)(Number)val;
+ var res = new JSONMap().set("type", "number");
+
+ if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
+ else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity");
+ else if (num == -0.) res.set("unserializableValue", "-0");
+ else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
+ else res.set("value", num);
+
+ return res;
+ }
+
+ throw new IllegalArgumentException("Unexpected JS object.");
+ }
+
+ private void resume(State state) {
+ this.state = state;
+ ws.send(new V8Event("Debugger.resumed", new JSONMap()));
+ updateNotifier.next();
+ }
+ private void pauseDebug(Context ctx, Breakpoint bp) {
+ state = State.PAUSED_NORMAL;
+ var map = new JSONMap()
+ .set("callFrames", serializeFrames(ctx))
+ .set("reason", "debugCommand");
+
+ if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + ""));
+ ws.send(new V8Event("Debugger.paused", map));
+ }
+ private void pauseException(Context ctx) {
+ state = State.PAUSED_NORMAL;
+ var map = new JSONMap()
+ .set("callFrames", serializeFrames(ctx))
+ .set("reason", "exception");
+
+ ws.send(new V8Event("Debugger.paused", map));
+ }
+
+ private void sendSource(Source src) {
+ ws.send(new V8Event("Debugger.scriptParsed", new JSONMap()
+ .set("scriptId", src.id + "")
+ .set("hash", src.source.hashCode())
+ .set("url", src.filename + "")
+ ));
+ }
+
+ private void addBreakpoint(Breakpoint bpt) {
+ idToBreakpoint.put(bpt.id, bpt);
+ locToBreakpoint.put(bpt.location, bpt);
+
+ ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap()
+ .set("breakpointId", bpt.id)
+ .set("location", serializeLocation(bpt.location))
+ ));
+ }
+
+ @Override public void enable(V8Message msg) {
+ enabled = true;
+ ws.send(msg.respond());
+
+ for (var el : pendingSources) sendSource(el);
+ pendingSources.clear();
+
+ updateNotifier.next();
+ }
+ @Override public void disable(V8Message msg) {
+ enabled = false;
+ ws.send(msg.respond());
+ updateNotifier.next();
+ }
+
+ @Override public void getScriptSource(V8Message msg) {
+ int id = Integer.parseInt(msg.params.string("scriptId"));
+ ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
+ }
+ @Override public void getPossibleBreakpoints(V8Message msg) {
+ var start = deserializeLocation(msg.params.get("start"), false);
+ var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
+ var src = idToSource.get(filenameToId.get(start.filename()));
+
+ var res = new JSONList();
+
+ for (var loc : src.breakpoints.tailSet(start, true)) {
+ if (end != null && loc.compareTo(end) > 0) break;
+ res.add(serializeLocation(loc));
+ }
+
+ ws.send(msg.respond(new JSONMap().set("locations", res)));
+ }
+
+ @Override public void pause(V8Message msg) {
+ }
+
+ @Override public void resume(V8Message msg) {
+ resume(State.RESUMED);
+ ws.send(msg.respond(new JSONMap()));
+ }
+
+ @Override public void setBreakpoint(V8Message msg) {
+ // int id = nextId();
+ // var loc = deserializeLocation(msg.params.get("location"), true);
+ // var bpt = new Breakpoint(id, loc, null);
+ // breakpoints.put(loc, bpt);
+ // idToBrpt.put(id, bpt);
+ // ws.send(msg.respond(new JSONMap()
+ // .set("breakpointId", id)
+ // .set("actualLocation", serializeLocation(loc))
+ // ));
+ }
+ @Override public void setBreakpointByUrl(V8Message msg) {
+ var line = (int)msg.params.number("lineNumber") + 1;
+ var col = (int)msg.params.number("columnNumber", 0) + 1;
+
+ Pattern regex;
+
+ if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
+ else regex = Pattern.compile(msg.params.string("urlRegex"));
+
+ var bpcd = new BreakpointCandidate(nextId(), regex, line, col, null);
+ idToBptCand.put(bpcd.id, bpcd);
+
+ var locs = new JSONList();
+
+ for (var src : idToSource.values()) {
+ if (regex.matcher(src.filename.toString()).matches()) {
+ var loc = correctLocation(src, new Location(line, col, src.filename));
+ if (loc == null) continue;
+ var bp = new Breakpoint(nextId(), loc, bpcd.condition);
+
+ bpcd.resolvedBreakpoints.add(bp);
+ locs.add(serializeLocation(loc));
+ addBreakpoint(bp);
+ }
+ }
+
+ ws.send(msg.respond(new JSONMap()
+ .set("breakpointId", bpcd.id + "")
+ .set("locations", locs)
+ ));
+ }
+ @Override public void removeBreakpoint(V8Message msg) {
+ var id = Integer.parseInt(msg.params.string("breakpointId"));
+
+ if (idToBptCand.containsKey(id)) {
+ var bpcd = idToBptCand.get(id);
+ for (var bp : bpcd.resolvedBreakpoints) {
+ idToBreakpoint.remove(bp.id);
+ locToBreakpoint.remove(bp.location);
+ }
+ idToBptCand.remove(id);
+ }
+ else if (idToBreakpoint.containsKey(id)) {
+ var bp = idToBreakpoint.remove(id);
+ locToBreakpoint.remove(bp.location);
+ }
+ ws.send(msg.respond());
+ }
+ @Override public void continueToLocation(V8Message msg) {
+ var loc = deserializeLocation(msg.params.get("location"), true);
+
+ tmpBreakpts.add(loc);
+
+ resume(State.RESUMED);
+ ws.send(msg.respond());
+ }
+
+ @Override public void setPauseOnExceptions(V8Message msg) {
+ ws.send(new V8Error("i dont wanna to"));
+ }
+
+ @Override public void stepInto(V8Message msg) {
+ if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
+ else {
+ prevLocation = currFrame.location;
+ stepOutFrame = currFrame;
+ resume(State.STEPPING_IN);
+ ws.send(msg.respond());
+ }
+ }
+ @Override public void stepOut(V8Message msg) {
+ if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
+ else {
+ prevLocation = currFrame.location;
+ stepOutFrame = currFrame;
+ resume(State.STEPPING_OUT);
+ ws.send(msg.respond());
+ }
+ }
+ @Override public void stepOver(V8Message msg) {
+ if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
+ else {
+ prevLocation = currFrame.location;
+ stepOutFrame = currFrame;
+ resume(State.STEPPING_OVER);
+ ws.send(msg.respond());
+ }
+ }
+
+ @Override public void onSource(Filename filename, String source, TreeSet locations) {
+ int id = nextId();
+ var src = new Source(id, filename, source, locations);
+
+ filenameToId.put(filename, id);
+ idToSource.put(id, src);
+
+ for (var bpcd : idToBptCand.values()) {
+ if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
+ var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename));
+ var bp = new Breakpoint(nextId(), loc, bpcd.condition);
+ if (loc == null) continue;
+ bpcd.resolvedBreakpoints.add(bp);
+ addBreakpoint(bp);
+ }
+
+ if (!enabled) pendingSources.add(src);
+ else sendSource(src);
+ }
+ @Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
+ if (!enabled) return false;
+
+ updateFrames(ctx);
+ var frame = codeFrameToFrame.get(cf);
+ if (instruction.location != null) frame.updateLoc(instruction.location);
+ var loc = frame.location;
+ var isBreakpointable = loc != null && (
+ idToSource.get(filenameToId.get(loc.filename())).breakpoints.contains(loc) ||
+ returnVal != Runners.NO_RETURN
+ );
+
+ if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null;
+
+ if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
+ pauseException(ctx);
+ }
+ else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
+ pauseDebug(ctx, locToBreakpoint.get(loc));
+ }
+ else if (isBreakpointable && tmpBreakpts.contains(loc)) {
+ pauseDebug(ctx, null);
+ tmpBreakpts.remove(loc);
+ }
+ else if (instruction.type == Type.NOP && instruction.match("debug")) {
+ pauseDebug(ctx, null);
+ }
+
+ while (enabled) {
+ switch (state) {
+ case PAUSED_EXCEPTION:
+ case PAUSED_NORMAL: break;
+
+ case STEPPING_OUT:
+ case RESUMED: return false;
+ case STEPPING_IN:
+ if (!prevLocation.equals(loc)) {
+ if (isBreakpointable) pauseDebug(ctx, null);
+ else if (returnVal != Runners.NO_RETURN) pauseDebug(ctx, null);
+ else return false;
+ }
+ else return false;
+ break;
+ case STEPPING_OVER:
+ if (
+ stepOutFrame == frame && (
+ !loc.filename().equals(prevLocation.filename()) ||
+ loc.line() != prevLocation.line()
+ )
+ ) pauseDebug(ctx, null);
+ else return false;
+ break;
+ }
+ updateNotifier.await();
+ }
+
+ return false;
+ }
+ @Override public void onFramePop(Context ctx, CodeFrame frame) {
+ updateFrames(ctx);
+
+ try {
+ idToFrame.remove(codeFrameToFrame.remove(frame).id);
+ }
+ catch (NullPointerException e) { }
+
+ if (StackData.frames(ctx).size() == 0) resume(State.RESUMED);
+ else if (stepOutFrame != null && stepOutFrame.frame == frame &&
+ (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER)
+ ) {
+ pauseDebug(ctx, null);
+ updateNotifier.await();
+ }
+ }
+
+ @Override public void connect() {
+ target.data.set(StackData.DEBUGGER, this);
+ }
+ @Override public void disconnect() {
+ target.data.remove(StackData.DEBUGGER);
+ enabled = false;
+ updateNotifier.next();
+ }
+
+ private SimpleDebugger(WebSocket ws, Engine target) {
+ this.ws = ws;
+ this.target = target;
+ }
+
+ public static SimpleDebugger get(WebSocket ws, Engine target) {
+ if (target.data.has(StackData.DEBUGGER)) {
+ ws.send(new V8Error("A debugger is already attached to this engine."));
+ return null;
+ }
+ else {
+ var res = new SimpleDebugger(ws, target);
+ return res;
+ }
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Error.java b/src/me/topchetoeu/jscript/engine/debug/V8Error.java
new file mode 100644
index 0000000..854e1cd
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/V8Error.java
@@ -0,0 +1,19 @@
+package me.topchetoeu.jscript.engine.debug;
+
+import me.topchetoeu.jscript.json.JSON;
+import me.topchetoeu.jscript.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)
+ ));
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Event.java b/src/me/topchetoeu/jscript/engine/debug/V8Event.java
new file mode 100644
index 0000000..a83e20b
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/V8Event.java
@@ -0,0 +1,22 @@
+package me.topchetoeu.jscript.engine.debug;
+
+import me.topchetoeu.jscript.json.JSON;
+import me.topchetoeu.jscript.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)
+ );
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Message.java b/src/me/topchetoeu/jscript/engine/debug/V8Message.java
new file mode 100644
index 0000000..82aa121
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/V8Message.java
@@ -0,0 +1,51 @@
+package me.topchetoeu.jscript.engine.debug;
+
+import java.util.Map;
+
+import me.topchetoeu.jscript.Filename;
+import me.topchetoeu.jscript.json.JSON;
+import me.topchetoeu.jscript.json.JSONElement;
+import me.topchetoeu.jscript.json.JSONMap;
+
+public class V8Message {
+ public final String name;
+ public final int id;
+ public final JSONMap params;
+
+ public V8Message(String name, int id, Map 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(new Filename("jscript", "json-msg"), 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)
+ );
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Result.java b/src/me/topchetoeu/jscript/engine/debug/V8Result.java
new file mode 100644
index 0000000..f605cef
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/V8Result.java
@@ -0,0 +1,22 @@
+package me.topchetoeu.jscript.engine.debug;
+
+import me.topchetoeu.jscript.json.JSON;
+import me.topchetoeu.jscript.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)
+ );
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java
new file mode 100644
index 0000000..15001ba
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java
@@ -0,0 +1,208 @@
+package me.topchetoeu.jscript.engine.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.engine.debug.WebSocketMessage.Type;
+import me.topchetoeu.jscript.exceptions.UncheckedException;
+import me.topchetoeu.jscript.exceptions.UncheckedIOException;
+
+public class WebSocket implements AutoCloseable {
+ public long maxLength = 2000000;
+
+ private Socket socket;
+ private boolean closed = false;
+
+ private OutputStream out() {
+ try { return socket.getOutputStream(); }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+ private InputStream in() {
+ try { return socket.getInputStream(); }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+
+ private long readLen(int byteLen) {
+ long res = 0;
+
+ try {
+ 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;
+ }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+ private byte[] readMask(boolean has) {
+ if (has) {
+ try { return new byte[] {
+ (byte)in().read(),
+ (byte)in().read(),
+ (byte)in().read(),
+ (byte)in().read()
+ }; }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+ else return new byte[4];
+ }
+
+ private void writeLength(long len) {
+ try {
+ 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((int)(len >> 56) & 0xFF);
+ out().write((int)(len >> 48) & 0xFF);
+ out().write((int)(len >> 40) & 0xFF);
+ out().write((int)(len >> 32) & 0xFF);
+ out().write((int)(len >> 24) & 0xFF);
+ out().write((int)(len >> 16) & 0xFF);
+ out().write((int)(len >> 8) & 0xFF);
+ out().write((int)len & 0xFF);
+ }
+ }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+ private synchronized void write(int type, byte[] data) {
+ try {
+ out().write(type | 0x80);
+ writeLength(data.length);
+ for (int i = 0; i < data.length; i++) {
+ out().write(data[i]);
+ }
+ }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+
+ public void send(String data) {
+ if (closed) throw new IllegalStateException("Object is closed.");
+ write(1, data.getBytes());
+ }
+ public void send(byte[] data) {
+ if (closed) throw new IllegalStateException("Object is closed.");
+ write(2, data);
+ }
+ public void send(WebSocketMessage msg) {
+ if (msg.type == Type.Binary) send(msg.binaryData());
+ else send(msg.textData());
+ }
+ public void send(Object data) {
+ if (closed) throw new IllegalStateException("Object 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() {
+ try {
+ 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;
+ }
+ catch (IOException e) { throw new UncheckedIOException(e); }
+ }
+
+ public WebSocketMessage receive() {
+ try {
+ 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);
+ }
+ }
+ catch (IOException e) {
+ close();
+ }
+
+ return null;
+ }
+
+ public WebSocket(Socket socket) {
+ this.socket = socket;
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java b/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
new file mode 100644
index 0000000..e0b82b5
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
@@ -0,0 +1,29 @@
+package me.topchetoeu.jscript.engine.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;
+ }
+}
diff --git a/src/me/topchetoeu/jscript/engine/debug/protocol.json b/src/me/topchetoeu/jscript/engine/debug/protocol.json
new file mode 100644
index 0000000..0470b51
--- /dev/null
+++ b/src/me/topchetoeu/jscript/engine/debug/protocol.json
@@ -0,0 +1,1962 @@
+{
+ "version": {
+ "major": "1",
+ "minor": "1"
+ },
+ "domains": [
+ {
+ "domain": "Debugger",
+ "description": "Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing\nbreakpoints, stepping through execution, exploring stack traces, etc.",
+ "dependencies": [ "Runtime" ],
+ "types": [
+ { "id": "BreakpointId",
+ "description": "Breakpoint identifier.",
+ "type": "string"
+ },
+ { "id": "CallFrameId",
+ "description": "Call frame identifier.",
+ "type": "string"
+ },
+ { "id": "CallFrame",
+ "description": "JavaScript call frame. Array of call frames form the call stack.",
+ "type": "object",
+ "properties": [
+ { "name": "callFrameId",
+ "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused.",
+ "$ref": "CallFrameId"
+ },
+ { "name": "functionName",
+ "description": "Name of the JavaScript function called on this call frame.",
+ "type": "string"
+ },
+ { "name": "location",
+ "description": "Location of the defining function.",
+ "$ref": "Location"
+ },
+ { "name": "scopeChain",
+ "description": "Scope chain for this call frame.",
+ "type": "array",
+ "items": { "$ref": "Scope" }
+ },
+ { "name": "this",
+ "description": "Always undefined, since `this` is a variable.",
+ "deprecated": true,
+ "$ref": "Runtime.RemoteObject"
+ },
+ { "name": "returnValue",
+ "description": "The value being returned, if the function is at return point.",
+ "optional": true,
+ "$ref": "Runtime.RemoteObject"
+ }
+ ]
+ },
+ { "id": "Location",
+ "description": "Location in the source code.",
+ "type": "object",
+ "properties": [
+ { "name": "scriptId",
+ "description": "Script identifier as reported in the `Debugger.scriptParsed`.",
+ "$ref": "Runtime.ScriptId"
+ },
+ { "name": "lineNumber",
+ "description": "Line number in the script (0-based).",
+ "type": "integer"
+ },
+ { "name": "columnNumber",
+ "description": "Column number in the script (0-based).",
+ "type": "integer"
+ }
+ ]
+ },
+ { "id": "Scope",
+ "description": "Scope definition.",
+ "type": "object",
+ "properties": [
+ { "name": "type",
+ "description": "Scope type.",
+ "type": "string",
+ "enum": [
+ "global",
+ "local",
+ "closure"
+ ]
+ },
+ { "name": "object",
+ "description": "Object representing the scope. The object is a proxy for the scope, unless the scope is global.",
+ "$ref": "Runtime.RemoteObject"
+ },
+ { "name": "name",
+ "optional": true,
+ "type": "string"
+ }
+ ]
+ }
+ ],
+ "commands": [
+ { "name": "enable",
+ "description": "Enables the debugger. This will have some performance penalty.",
+ "parameters": [ ],
+ "returns": [
+ { "name": "debuggerId",
+ "description": "Unique identifier of the debugger.",
+ "experimental": true,
+ "type": "string"
+ }
+ ]
+ },
+ { "name": "disable",
+ "description": "Disables debugging. This will have some performance benefit."
+ },
+
+ { "name": "continueToLocation",
+ "description": "Sets a one-off breakpoint to the specified location, and continues exectuion.",
+ "parameters": [
+ { "name": "location",
+ "description": "Location to continue to.",
+ "$ref": "Location"
+ }
+ ]
+ },
+ { "name": "evaluateOnCallFrame",
+ "description": "Evaluates expression on a given call frame, without triggering any breakpoints. The expression should be pure.",
+ "parameters": [
+ { "name": "callFrameId",
+ "description": "Call frame to evaluate on.",
+ "$ref": "CallFrameId"
+ },
+ { "name": "expression",
+ "description": "Expression to evaluate.",
+ "type": "string"
+ },
+ { "name": "silent",
+ "description": "Ignores any exceptions that may occur during evaluation. Overrides `setPauseOnException` state.",
+ "optional": true,
+ "type": "boolean"
+ },
+ { "name": "returnByValue",
+ "description": "Whether the result is expected to be a JSON object that should be sent by value.",
+ "deprecated": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ { "name": "timeout",
+ "description": "Terminate execution after timing out (number of milliseconds).",
+ "optional": true,
+ "type": "number"
+ }
+ ],
+ "returns": [
+ {
+ "name": "result",
+ "description": "Object wrapper for the evaluation result.",
+ "$ref": "Runtime.RemoteObject"
+ },
+ {
+ "name": "exceptionDetails",
+ "description": "Exception details.",
+ "optional": true,
+ "$ref": "Runtime.ExceptionDetails"
+ }
+ ]
+ },
+ { "name": "getPossibleBreakpoints",
+ "description": "Returns possible locations for breakpoint. scriptId in start and end range locations should be\nthe same.",
+ "parameters": [
+ {
+ "name": "start",
+ "description": "Start of range to search possible breakpoint locations in.",
+ "$ref": "Location"
+ },
+ {
+ "name": "end",
+ "description": "End of range to search possible breakpoint locations in (excluding). When not specified, end\nof scripts is used as end of range.",
+ "optional": true,
+ "$ref": "Location"
+ },
+ {
+ "name": "restrictToFunction",
+ "description": "Only consider locations which are in the same (non-nested) function as start.",
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ {
+ "name": "locations",
+ "description": "List of the possible breakpoint locations.",
+ "type": "array",
+ "items": {
+ "$ref": "BreakLocation"
+ }
+ }
+ ]
+ },
+ { "name": "getScriptSource",
+ "description": "Returns source for the script with given id.",
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Id of the script to get source for.",
+ "$ref": "Runtime.ScriptId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "scriptSource",
+ "description": "Script source (empty in case of Wasm bytecode).",
+ "type": "string"
+ },
+ {
+ "name": "bytecode",
+ "description": "Wasm bytecode.",
+ "optional": true,
+ "type": "binary"
+ }
+ ]
+ },
+ { "name": "getWasmBytecode",
+ "description": "This command is deprecated. Use getScriptSource instead.",
+ "deprecated": true,
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Id of the Wasm script to get source for.",
+ "$ref": "Runtime.ScriptId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "bytecode",
+ "description": "Script source.",
+ "type": "binary"
+ }
+ ]
+ },
+ { "name": "getStackTrace",
+ "description": "Returns stack trace with given `stackTraceId`.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "stackTraceId",
+ "$ref": "Runtime.StackTraceId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "stackTrace",
+ "$ref": "Runtime.StackTrace"
+ }
+ ]
+ },
+ { "name": "pause",
+ "description": "Stops on the next JavaScript statement."
+ },
+ { "name": "pauseOnAsyncCall",
+ "experimental": true,
+ "deprecated": true,
+ "parameters": [
+ {
+ "name": "parentStackTraceId",
+ "description": "Debugger will pause when async call with given stack trace is started.",
+ "$ref": "Runtime.StackTraceId"
+ }
+ ]
+ },
+ { "name": "removeBreakpoint",
+ "description": "Removes JavaScript breakpoint.",
+ "parameters": [
+ {
+ "name": "breakpointId",
+ "$ref": "BreakpointId"
+ }
+ ]
+ },
+ { "name": "restartFrame",
+ "description": "Restarts particular call frame from the beginning.",
+ "deprecated": true,
+ "parameters": [
+ {
+ "name": "callFrameId",
+ "description": "Call frame identifier to evaluate on.",
+ "$ref": "CallFrameId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "callFrames",
+ "description": "New stack trace.",
+ "type": "array",
+ "items": {
+ "$ref": "CallFrame"
+ }
+ },
+ {
+ "name": "asyncStackTrace",
+ "description": "Async stack trace, if any.",
+ "optional": true,
+ "$ref": "Runtime.StackTrace"
+ },
+ {
+ "name": "asyncStackTraceId",
+ "description": "Async stack trace, if any.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Runtime.StackTraceId"
+ }
+ ]
+ },
+ { "name": "resume",
+ "description": "Resumes JavaScript execution.",
+ "parameters": [
+ {
+ "name": "terminateOnResume",
+ "description": "Set to true to terminate execution upon resuming execution. In contrast\nto Runtime.terminateExecution, this will allows to execute further\nJavaScript (i.e. via evaluation) until execution of the paused code\nis actually resumed, at which point termination is triggered.\nIf execution is currently not paused, this parameter has no effect.",
+ "optional": true,
+ "type": "boolean"
+ }
+ ]
+ },
+ { "name": "searchInContent",
+ "description": "Searches for given string in script content.",
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Id of the script to search in.",
+ "$ref": "Runtime.ScriptId"
+ },
+ {
+ "name": "query",
+ "description": "String to search for.",
+ "type": "string"
+ },
+ {
+ "name": "caseSensitive",
+ "description": "If true, search is case sensitive.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "isRegex",
+ "description": "If true, treats string parameter as regex.",
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ {
+ "name": "result",
+ "description": "List of search matches.",
+ "type": "array",
+ "items": {
+ "$ref": "SearchMatch"
+ }
+ }
+ ]
+ },
+ { "name": "setAsyncCallStackDepth",
+ "description": "Enables or disables async call stacks tracking.",
+ "parameters": [
+ {
+ "name": "maxDepth",
+ "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).",
+ "type": "integer"
+ }
+ ]
+ },
+ { "name": "setBlackboxPatterns",
+ "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in\nscripts with url matching one of the patterns. VM will try to leave blackboxed script by\nperforming 'step in' several times, finally resorting to 'step out' if unsuccessful.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "patterns",
+ "description": "Array of regexps that will be used to check script url for blackbox state.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ { "name": "setBlackboxedRanges",
+ "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted\nscripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.\nPositions array contains positions where blackbox state is changed. First interval isn't\nblackboxed. Array should be sorted.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Id of the script.",
+ "$ref": "Runtime.ScriptId"
+ },
+ {
+ "name": "positions",
+ "type": "array",
+ "items": {
+ "$ref": "ScriptPosition"
+ }
+ }
+ ]
+ },
+ { "name": "setBreakpoint",
+ "description": "Sets JavaScript breakpoint at a given location.",
+ "parameters": [
+ {
+ "name": "location",
+ "description": "Location to set breakpoint in.",
+ "$ref": "Location"
+ },
+ {
+ "name": "condition",
+ "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.",
+ "optional": true,
+ "type": "string"
+ }
+ ],
+ "returns": [
+ {
+ "name": "breakpointId",
+ "description": "Id of the created breakpoint for further reference.",
+ "$ref": "BreakpointId"
+ },
+ {
+ "name": "actualLocation",
+ "description": "Location this breakpoint resolved into.",
+ "$ref": "Location"
+ }
+ ]
+ },
+ { "name": "setInstrumentationBreakpoint",
+ "description": "Sets instrumentation breakpoint.",
+ "parameters": [
+ {
+ "name": "instrumentation",
+ "description": "Instrumentation name.",
+ "type": "string",
+ "enum": [
+ "beforeScriptExecution",
+ "beforeScriptWithSourceMapExecution"
+ ]
+ }
+ ],
+ "returns": [
+ {
+ "name": "breakpointId",
+ "description": "Id of the created breakpoint for further reference.",
+ "$ref": "BreakpointId"
+ }
+ ]
+ },
+ { "name": "setBreakpointByUrl",
+ "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this\ncommand is issued, all existing parsed scripts will have breakpoints resolved and returned in\n`locations` property. Further matching script parsing will result in subsequent\n`breakpointResolved` events issued. This logical breakpoint will survive page reloads.",
+ "parameters": [
+ {
+ "name": "lineNumber",
+ "description": "Line number to set breakpoint at.",
+ "type": "integer"
+ },
+ {
+ "name": "columnNumber",
+ "description": "Offset in the line to set breakpoint at.",
+ "optional": true,
+ "type": "integer"
+ },
+ {
+ "name": "url",
+ "description": "URL of the resources to set breakpoint on.",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "urlRegex",
+ "description": "Regex pattern for the URLs of the resources to set breakpoints on. Either `url` or\n`urlRegex` must be specified.",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "scriptHash",
+ "description": "Script hash of the resources to set breakpoint on.",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "condition",
+ "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.",
+ "optional": true,
+ "type": "string"
+ }
+ ],
+ "returns": [
+ {
+ "name": "breakpointId",
+ "description": "Id of the created breakpoint for further reference.",
+ "$ref": "BreakpointId"
+ },
+ {
+ "name": "locations",
+ "description": "List of the locations this breakpoint resolved into upon addition.",
+ "type": "array",
+ "items": {
+ "$ref": "Location"
+ }
+ }
+ ]
+ },
+ { "name": "setBreakpointOnFunctionCall",
+ "description": "Sets JavaScript breakpoint before each call to the given function.\nIf another function was created from the same source as a given one,\ncalling it will also trigger the breakpoint.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "objectId",
+ "description": "Function object id.",
+ "$ref": "Runtime.RemoteObjectId"
+ },
+ {
+ "name": "condition",
+ "description": "Expression to use as a breakpoint condition. When specified, debugger will\nstop on the breakpoint if this expression evaluates to true.",
+ "optional": true,
+ "type": "string"
+ }
+ ],
+ "returns": [
+ {
+ "name": "breakpointId",
+ "description": "Id of the created breakpoint for further reference.",
+ "$ref": "BreakpointId"
+ }
+ ]
+ },
+ { "name": "setBreakpointsActive",
+ "description": "Activates / deactivates all breakpoints on the page.",
+ "parameters": [
+ {
+ "name": "active",
+ "description": "New value for breakpoints active state.",
+ "type": "boolean"
+ }
+ ]
+ },
+ { "name": "setPauseOnExceptions",
+ "description": "Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or\nno exceptions. Initial pause on exceptions state is `none`.",
+ "parameters": [
+ {
+ "name": "state",
+ "description": "Pause on exceptions mode.",
+ "type": "string",
+ "enum": [
+ "none",
+ "uncaught",
+ "all"
+ ]
+ }
+ ]
+ },
+ { "name": "setReturnValue",
+ "description": "Changes return value in top frame. Available only at return break position.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "newValue",
+ "description": "New return value.",
+ "$ref": "Runtime.CallArgument"
+ }
+ ]
+ },
+ { "name": "setScriptSource",
+ "description": "Edits JavaScript source live.",
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Id of the script to edit.",
+ "$ref": "Runtime.ScriptId"
+ },
+ {
+ "name": "scriptSource",
+ "description": "New content of the script.",
+ "type": "string"
+ },
+ {
+ "name": "dryRun",
+ "description": "If true the change will not actually be applied. Dry run may be used to get result\ndescription without actually modifying the code.",
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ {
+ "name": "callFrames",
+ "description": "New stack trace in case editing has happened while VM was stopped.",
+ "optional": true,
+ "type": "array",
+ "items": {
+ "$ref": "CallFrame"
+ }
+ },
+ {
+ "name": "stackChanged",
+ "description": "Whether current call stack was modified after applying the changes.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "asyncStackTrace",
+ "description": "Async stack trace, if any.",
+ "optional": true,
+ "$ref": "Runtime.StackTrace"
+ },
+ {
+ "name": "asyncStackTraceId",
+ "description": "Async stack trace, if any.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Runtime.StackTraceId"
+ },
+ {
+ "name": "exceptionDetails",
+ "description": "Exception details if any.",
+ "optional": true,
+ "$ref": "Runtime.ExceptionDetails"
+ }
+ ]
+ },
+ { "name": "setSkipAllPauses",
+ "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc).",
+ "parameters": [
+ {
+ "name": "skip",
+ "description": "New value for skip pauses state.",
+ "type": "boolean"
+ }
+ ]
+ },
+ { "name": "setVariableValue",
+ "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be\nmutated manually.",
+ "parameters": [
+ {
+ "name": "scopeNumber",
+ "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch'\nscope types are allowed. Other scopes could be manipulated manually.",
+ "type": "integer"
+ },
+ {
+ "name": "variableName",
+ "description": "Variable name.",
+ "type": "string"
+ },
+ {
+ "name": "newValue",
+ "description": "New variable value.",
+ "$ref": "Runtime.CallArgument"
+ },
+ {
+ "name": "callFrameId",
+ "description": "Id of callframe that holds variable.",
+ "$ref": "CallFrameId"
+ }
+ ]
+ },
+ { "name": "stepInto",
+ "description": "Steps into the function call."
+ },
+ { "name": "stepOut",
+ "description": "Steps out of the function call."
+ },
+ { "name": "stepOver",
+ "description": "Steps over the statement."
+ }
+ ],
+ "events": [
+ { "name": "breakpointResolved",
+ "description": "Fired when breakpoint is resolved to an actual script and location.",
+ "parameters": [
+ {
+ "name": "breakpointId",
+ "description": "Breakpoint unique identifier.",
+ "$ref": "BreakpointId"
+ },
+ {
+ "name": "location",
+ "description": "Actual breakpoint location.",
+ "$ref": "Location"
+ }
+ ]
+ },
+ { "name": "paused",
+ "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria.",
+ "parameters": [
+ {
+ "name": "callFrames",
+ "description": "Call stack the virtual machine stopped on.",
+ "type": "array",
+ "items": {
+ "$ref": "CallFrame"
+ }
+ },
+ {
+ "name": "reason",
+ "description": "Pause reason.",
+ "type": "string",
+ "enum": [
+ "ambiguous",
+ "assert",
+ "CSPViolation",
+ "debugCommand",
+ "DOM",
+ "EventListener",
+ "exception",
+ "instrumentation",
+ "OOM",
+ "other",
+ "promiseRejection",
+ "XHR"
+ ]
+ },
+ {
+ "name": "data",
+ "description": "Object containing break-specific auxiliary properties.",
+ "optional": true,
+ "type": "object"
+ },
+ {
+ "name": "hitBreakpoints",
+ "description": "Hit breakpoints IDs",
+ "optional": true,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "asyncStackTrace",
+ "description": "Async stack trace, if any.",
+ "optional": true,
+ "$ref": "Runtime.StackTrace"
+ },
+ {
+ "name": "asyncStackTraceId",
+ "description": "Async stack trace, if any.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Runtime.StackTraceId"
+ },
+ {
+ "name": "asyncCallStackTraceId",
+ "description": "Never present, will be removed.",
+ "experimental": true,
+ "deprecated": true,
+ "optional": true,
+ "$ref": "Runtime.StackTraceId"
+ }
+ ]
+ },
+ { "name": "resumed",
+ "description": "Fired when the virtual machine resumed execution."
+ },
+ { "name": "scriptFailedToParse",
+ "description": "Fired when virtual machine fails to parse the script.",
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Identifier of the script parsed.",
+ "$ref": "Runtime.ScriptId"
+ },
+ {
+ "name": "url",
+ "description": "URL or name of the script parsed (if any).",
+ "type": "string"
+ },
+ {
+ "name": "startLine",
+ "description": "Line offset of the script within the resource with given URL (for script tags).",
+ "type": "integer"
+ },
+ {
+ "name": "startColumn",
+ "description": "Column offset of the script within the resource with given URL.",
+ "type": "integer"
+ },
+ {
+ "name": "endLine",
+ "description": "Last line of the script.",
+ "type": "integer"
+ },
+ {
+ "name": "endColumn",
+ "description": "Length of the last line of the script.",
+ "type": "integer"
+ },
+ {
+ "name": "hash",
+ "description": "Content hash of the script.",
+ "type": "string"
+ },
+ {
+ "name": "executionContextAuxData",
+ "description": "Embedder-specific auxiliary data.",
+ "optional": true,
+ "type": "object"
+ },
+ {
+ "name": "sourceMapURL",
+ "description": "URL of source map associated with script (if any).",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "hasSourceURL",
+ "description": "True, if this script has sourceURL.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "isModule",
+ "description": "True, if this script is ES6 module.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "length",
+ "description": "This script length.",
+ "optional": true,
+ "type": "integer"
+ },
+ {
+ "name": "stackTrace",
+ "description": "JavaScript top stack frame of where the script parsed event was triggered if available.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Runtime.StackTrace"
+ },
+ {
+ "name": "codeOffset",
+ "description": "If the scriptLanguage is WebAssembly, the code section offset in the module.",
+ "experimental": true,
+ "optional": true,
+ "type": "integer"
+ },
+ {
+ "name": "scriptLanguage",
+ "description": "The language of the script.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Debugger.ScriptLanguage"
+ },
+ {
+ "name": "embedderName",
+ "description": "The name the embedder supplied for this script.",
+ "experimental": true,
+ "optional": true,
+ "type": "string"
+ }
+ ]
+ },
+ { "name": "scriptParsed",
+ "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected\nscripts upon enabling debugger.",
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Identifier of the script parsed.",
+ "$ref": "Runtime.ScriptId"
+ },
+ {
+ "name": "url",
+ "description": "URL or name of the script parsed (if any).",
+ "type": "string"
+ },
+ {
+ "name": "startLine",
+ "description": "Line offset of the script within the resource with given URL (for script tags).",
+ "type": "integer"
+ },
+ {
+ "name": "startColumn",
+ "description": "Column offset of the script within the resource with given URL.",
+ "type": "integer"
+ },
+ {
+ "name": "endLine",
+ "description": "Last line of the script.",
+ "type": "integer"
+ },
+ {
+ "name": "endColumn",
+ "description": "Length of the last line of the script.",
+ "type": "integer"
+ },
+ {
+ "name": "hash",
+ "description": "Content hash of the script.",
+ "type": "string"
+ },
+ {
+ "name": "executionContextAuxData",
+ "description": "Embedder-specific auxiliary data.",
+ "optional": true,
+ "type": "object"
+ },
+ {
+ "name": "isLiveEdit",
+ "description": "True, if this script is generated as a result of the live edit operation.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "sourceMapURL",
+ "description": "URL of source map associated with script (if any).",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "hasSourceURL",
+ "description": "True, if this script has sourceURL.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "isModule",
+ "description": "True, if this script is ES6 module.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "length",
+ "description": "This script length.",
+ "optional": true,
+ "type": "integer"
+ },
+ {
+ "name": "stackTrace",
+ "description": "JavaScript top stack frame of where the script parsed event was triggered if available.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Runtime.StackTrace"
+ },
+ {
+ "name": "codeOffset",
+ "description": "If the scriptLanguage is WebAssembly, the code section offset in the module.",
+ "experimental": true,
+ "optional": true,
+ "type": "integer"
+ },
+ {
+ "name": "scriptLanguage",
+ "description": "The language of the script.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Debugger.ScriptLanguage"
+ },
+ {
+ "name": "debugSymbols",
+ "description": "If the scriptLanguage is WebASsembly, the source of debug symbols for the module.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "Debugger.DebugSymbols"
+ },
+ {
+ "name": "embedderName",
+ "description": "The name the embedder supplied for this script.",
+ "experimental": true,
+ "optional": true,
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "domain": "Runtime",
+ "types": [
+ { "id": "ScriptId",
+ "description": "Unique script identifier.",
+ "type": "string"
+ },
+ { "id": "RemoteObjectId",
+ "description": "Unique object identifier.",
+ "type": "string"
+ },
+
+ { "id": "Timestamp",
+ "description": "Number of milliseconds since epoch.",
+ "type": "number"
+ },
+ { "id": "TimeDelta",
+ "description": "Number of milliseconds.",
+ "type": "number"
+ },
+
+ { "id": "UnserializableValue",
+ "description": "Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,\n`-Infinity`, and bigint literals.",
+ "type": "string"
+ },
+ { "id": "RemoteObject",
+ "description": "Mirror object referencing original JavaScript object.",
+ "type": "object",
+ "properties": [
+ { "name": "type",
+ "description": "Object type.",
+ "type": "string",
+ "enum": [
+ "object",
+ "function",
+ "undefined",
+ "string",
+ "number",
+ "boolean",
+ "symbol"
+ ]
+ },
+ { "name": "subtype",
+ "description": "Object subtype hint. Specified for `object` type values only.",
+ "optional": true,
+ "type": "string",
+ "enum": [
+ "array",
+ "null",
+ "regexp",
+ "date",
+ "map",
+ "set",
+ "generator",
+ "promise"
+ ]
+ },
+ { "name": "className",
+ "description": "Object class (constructor) name. Specified for `object` type values only.",
+ "optional": true,
+ "type": "string"
+ },
+ { "name": "value",
+ "description": "Remote object value in case of primitive values or JSON values (if it was requested).",
+ "optional": true,
+ "type": "any"
+ },
+ { "name": "unserializableValue",
+ "description": "Primitive value which can not be JSON-stringified does not have `value`, but gets this\nproperty.",
+ "optional": true,
+ "$ref": "UnserializableValue"
+ },
+ { "name": "description",
+ "description": "String representation of the object.",
+ "optional": true,
+ "type": "string"
+ },
+ { "name": "objectId",
+ "description": "Unique object identifier (for non-primitive values).",
+ "optional": true,
+ "$ref": "RemoteObjectId"
+ }
+ ]
+ },
+ { "id": "MemberDescriptor",
+ "description": "Object member descriptor, mirroring the object descriptor in JavaScript (https://developer.mozilla.org/en-US/docs/Glossary/Property/JavaScript).",
+ "type": "object",
+ "properties": [
+ { "name": "name",
+ "description": "String representation of the member name.",
+ "type": "string"
+ },
+ { "name": "value",
+ "description": "The value of the member, if it's a field.",
+ "optional": true,
+ "$ref": "RemoteObject"
+ },
+ { "name": "writable",
+ "description": "Whether or not the member is writable. Present only if the member is a field.",
+ "optional": true,
+ "type": "boolean"
+ },
+ { "name": "configurable",
+ "description": "Whether or not the member is configurable.",
+ "type": "boolean"
+ },
+ { "name": "enumerable",
+ "description": "Whether or not the member is enumerable..",
+ "type": "boolean"
+ },
+ { "name": "get",
+ "description": "The getter of the member. Present only if the member is a property and the property has a getter.",
+ "optional": true,
+ "$ref": "RemoteObject"
+ },
+ { "name": "set",
+ "description": "The setter of the member. Present only if the member is a property and the property has a setter.",
+ "optional": true,
+ "$ref": "RemoteObject"
+ },
+ { "name": "wasThrown",
+ "description": "True if the member's value was thrown during the evaluation of the getter.",
+ "optional": true,
+ "type": "boolean"
+ },
+ { "name": "isOwn",
+ "description": "True if the property is owned for the object.",
+ "optional": true,
+ "type": "boolean"
+ },
+ { "name": "symbol",
+ "description": "If the member name is a symbol, this will be the reference of the symbol.",
+ "optional": true,
+ "$ref": "RemoteObject"
+ }
+ ]
+ },
+ { "id": "CallArgument",
+ "description": "Represents function call argument. Only one of the three optional fields should be specified, or none for undefined.",
+ "type": "object",
+ "properties": [
+ { "name": "value",
+ "description": "Primitive value or serializable javascript object.",
+ "optional": true,
+ "type": "any"
+ },
+ { "name": "unserializableValue",
+ "description": "Primitive value which can not be JSON-stringified.",
+ "optional": true,
+ "$ref": "UnserializableValue"
+ },
+ { "name": "objectId",
+ "description": "Remote object handle.",
+ "optional": true,
+ "$ref": "RemoteObjectId"
+ }
+ ]
+ },
+ { "id": "ExceptionDetails",
+ "description": "Detailed information about an exception.",
+ "type": "object",
+ "properties": [
+ { "name": "exceptionId",
+ "description": "Exception id.",
+ "type": "integer"
+ },
+ { "name": "text",
+ "description": "The string representation of the exception.",
+ "type": "string"
+ },
+ { "name": "exception",
+ "description": "Exception object if available.",
+ "optional": true,
+ "$ref": "RemoteObject"
+ },
+
+ { "name": "stackTrace",
+ "description": "JavaScript stack trace if available.",
+ "$ref": "StackTrace"
+ },
+ { "name": "scriptId",
+ "description": "Script ID of the exception location.",
+ "$ref": "ScriptId"
+ },
+ { "name": "lineNumber",
+ "description": "Line number of the exception location (0-based).",
+ "type": "integer"
+ },
+ { "name": "columnNumber",
+ "description": "Column number of the exception location (0-based).",
+ "type": "integer"
+ }
+ ]
+ },
+ { "id": "CallFrame",
+ "description": "Stack entry for runtime errors and assertions.",
+ "type": "object",
+ "properties": [
+ { "name": "functionName",
+ "description": "JavaScript function name.",
+ "type": "string"
+ },
+ { "name": "scriptId",
+ "description": "JavaScript script id.",
+ "$ref": "ScriptId"
+ },
+ { "name": "lineNumber",
+ "description": "JavaScript script line number (0-based).",
+ "type": "integer"
+ },
+ { "name": "columnNumber",
+ "description": "JavaScript script column number (0-based).",
+ "type": "integer"
+ }
+ ]
+ },
+ { "id": "StackTrace",
+ "description": "Call frames for assertions or error messages.",
+ "type": "object",
+ "properties": [
+ { "name": "description",
+ "description": "String label of this stack trace. For async traces this may be a name of the function that\ninitiated the async call.",
+ "optional": true,
+ "type": "string"
+ },
+ { "name": "callFrames",
+ "description": "A list of all the frames in the stack trace (excluding internal functions).",
+ "type": "array",
+ "items": { "$ref": "CallFrame" }
+ }
+ ]
+ }
+ ],
+ "commands": [
+ { "name": "awaitPromise",
+ "description": "Add handler to promise with given promise object id.",
+ "parameters": [
+ { "name": "promiseObjectId",
+ "description": "Identifier of the promise.",
+ "$ref": "RemoteObjectId"
+ },
+ { "name": "returnByValue",
+ "description": "Whether the result is expected to be a JSON object that should be sent by value.",
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ { "name": "result",
+ "description": "Promise result. Will contain rejected value if promise was rejected.",
+ "$ref": "RemoteObject"
+ },
+ { "name": "exceptionDetails",
+ "description": "Exception details if stack strace is available.",
+ "optional": true,
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ },
+ { "name": "callFunctionOn",
+ "description": "Calls function with given declaration on the given object.",
+ "parameters": [
+ { "name": "functionDeclaration",
+ "description": "Declaration of the function to call.",
+ "type": "string"
+ },
+ { "name": "objectId",
+ "description": "Identifier of the object to call function on. Either objectId or executionContextId should be specified.",
+ "optional": true,
+ "$ref": "RemoteObjectId"
+ },
+ { "name": "arguments",
+ "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target\nobject.",
+ "optional": true,
+ "type": "array",
+ "items": {
+ "$ref": "CallArgument"
+ }
+ },
+ { "name": "silent",
+ "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.",
+ "optional": true,
+ "type": "boolean"
+ },
+ { "name": "returnByValue",
+ "description": "Whether the result is expected to be a JSON object which should be sent by value.",
+ "optional": true,
+ "type": "boolean"
+ },
+ { "name": "awaitPromise",
+ "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "objectGroup",
+ "description": "Symbolic group name that can be used to release multiple objects. If objectGroup is not\nspecified and objectId is, objectGroup will be inherited from object.",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "throwOnSideEffect",
+ "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "generateWebDriverValue",
+ "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ {
+ "name": "result",
+ "description": "Call result.",
+ "$ref": "RemoteObject"
+ },
+ {
+ "name": "exceptionDetails",
+ "description": "Exception details.",
+ "optional": true,
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ },
+ {
+ "name": "compileScript",
+ "description": "Compiles expression.",
+ "parameters": [
+ {
+ "name": "expression",
+ "description": "Expression to compile.",
+ "type": "string"
+ },
+ {
+ "name": "sourceURL",
+ "description": "Source url to be set for the script.",
+ "type": "string"
+ },
+ {
+ "name": "persistScript",
+ "description": "Specifies whether the compiled script should be persisted.",
+ "type": "boolean"
+ },
+ {
+ "name": "executionContextId",
+ "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.",
+ "optional": true,
+ "$ref": "ExecutionContextId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "scriptId",
+ "description": "Id of the script.",
+ "optional": true,
+ "$ref": "ScriptId"
+ },
+ {
+ "name": "exceptionDetails",
+ "description": "Exception details.",
+ "optional": true,
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ },
+ {
+ "name": "disable",
+ "description": "Disables reporting of execution contexts creation."
+ },
+ {
+ "name": "discardConsoleEntries",
+ "description": "Discards collected exceptions and console API calls."
+ },
+ {
+ "name": "enable",
+ "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext."
+ },
+ {
+ "name": "evaluate",
+ "description": "Evaluates expression on global object.",
+ "parameters": [
+ {
+ "name": "expression",
+ "description": "Expression to evaluate.",
+ "type": "string"
+ },
+ {
+ "name": "objectGroup",
+ "description": "Symbolic group name that can be used to release multiple objects.",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "includeCommandLineAPI",
+ "description": "Determines whether Command Line API should be available during the evaluation.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "silent",
+ "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "contextId",
+ "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.\nThis is mutually exclusive with `uniqueContextId`, which offers an\nalternative way to identify the execution context that is more reliable\nin a multi-process environment.",
+ "optional": true,
+ "$ref": "ExecutionContextId"
+ },
+ {
+ "name": "returnByValue",
+ "description": "Whether the result is expected to be a JSON object that should be sent by value.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "generatePreview",
+ "description": "Whether preview should be generated for the result.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "userGesture",
+ "description": "Whether execution should be treated as initiated by user in the UI.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "awaitPromise",
+ "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "throwOnSideEffect",
+ "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.\nThis implies `disableBreaks` below.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "timeout",
+ "description": "Terminate execution after timing out (number of milliseconds).",
+ "experimental": true,
+ "optional": true,
+ "$ref": "TimeDelta"
+ },
+ {
+ "name": "disableBreaks",
+ "description": "Disable breakpoints during execution.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "replMode",
+ "description": "Setting this flag to true enables `let` re-declaration and top-level `await`.\nNote that `let` variables can only be re-declared if they originate from\n`replMode` themselves.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "allowUnsafeEvalBlockedByCSP",
+ "description": "The Content Security Policy (CSP) for the target might block 'unsafe-eval'\nwhich includes eval(), Function(), setTimeout() and setInterval()\nwhen called with non-callable arguments. This flag bypasses CSP for this\nevaluation and allows unsafe-eval. Defaults to true.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "uniqueContextId",
+ "description": "An alternative way to specify the execution context to evaluate in.\nCompared to contextId that may be reused across processes, this is guaranteed to be\nsystem-unique, so it can be used to prevent accidental evaluation of the expression\nin context different than intended (e.g. as a result of navigation across process\nboundaries).\nThis is mutually exclusive with `contextId`.",
+ "experimental": true,
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "generateWebDriverValue",
+ "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ {
+ "name": "result",
+ "description": "Evaluation result.",
+ "$ref": "RemoteObject"
+ },
+ {
+ "name": "exceptionDetails",
+ "description": "Exception details.",
+ "optional": true,
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ },
+ {
+ "name": "getIsolateId",
+ "description": "Returns the isolate id.",
+ "experimental": true,
+ "returns": [
+ {
+ "name": "id",
+ "description": "The isolate id.",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "getHeapUsage",
+ "description": "Returns the JavaScript heap usage.\nIt is the total usage of the corresponding isolate not scoped to a particular Runtime.",
+ "experimental": true,
+ "returns": [
+ {
+ "name": "usedSize",
+ "description": "Used heap size in bytes.",
+ "type": "number"
+ },
+ {
+ "name": "totalSize",
+ "description": "Allocated heap size in bytes.",
+ "type": "number"
+ }
+ ]
+ },
+ {
+ "name": "getProperties",
+ "description": "Returns properties of a given object. Object group of the result is inherited from the target\nobject.",
+ "parameters": [
+ {
+ "name": "objectId",
+ "description": "Identifier of the object to return properties for.",
+ "$ref": "RemoteObjectId"
+ },
+ {
+ "name": "ownProperties",
+ "description": "If true, returns properties belonging only to the element itself, not to its prototype\nchain.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "accessorPropertiesOnly",
+ "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not\nreturned either.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "generatePreview",
+ "description": "Whether preview should be generated for the results.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "nonIndexedPropertiesOnly",
+ "description": "If true, returns non-indexed properties only.",
+ "experimental": true,
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ {
+ "name": "result",
+ "description": "Object properties.",
+ "type": "array",
+ "items": {
+ "$ref": "PropertyDescriptor"
+ }
+ },
+ {
+ "name": "internalProperties",
+ "description": "Internal object properties (only of the element itself).",
+ "optional": true,
+ "type": "array",
+ "items": {
+ "$ref": "InternalPropertyDescriptor"
+ }
+ },
+ {
+ "name": "privateProperties",
+ "description": "Object private properties.",
+ "experimental": true,
+ "optional": true,
+ "type": "array",
+ "items": {
+ "$ref": "PrivatePropertyDescriptor"
+ }
+ },
+ {
+ "name": "exceptionDetails",
+ "description": "Exception details.",
+ "optional": true,
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ },
+ {
+ "name": "globalLexicalScopeNames",
+ "description": "Returns all let, const and class variables from global scope.",
+ "parameters": [
+ {
+ "name": "executionContextId",
+ "description": "Specifies in which execution context to lookup global scope variables.",
+ "optional": true,
+ "$ref": "ExecutionContextId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "names",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ {
+ "name": "queryObjects",
+ "parameters": [
+ {
+ "name": "prototypeObjectId",
+ "description": "Identifier of the prototype to return objects for.",
+ "$ref": "RemoteObjectId"
+ },
+ {
+ "name": "objectGroup",
+ "description": "Symbolic group name that can be used to release the results.",
+ "optional": true,
+ "type": "string"
+ }
+ ],
+ "returns": [
+ {
+ "name": "objects",
+ "description": "Array with objects.",
+ "$ref": "RemoteObject"
+ }
+ ]
+ },
+ {
+ "name": "releaseObject",
+ "description": "Releases remote object with given id.",
+ "parameters": [
+ {
+ "name": "objectId",
+ "description": "Identifier of the object to release.",
+ "$ref": "RemoteObjectId"
+ }
+ ]
+ },
+ {
+ "name": "releaseObjectGroup",
+ "description": "Releases all remote objects that belong to a given group.",
+ "parameters": [
+ {
+ "name": "objectGroup",
+ "description": "Symbolic object group name.",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "runIfWaitingForDebugger",
+ "description": "Tells inspected instance to run if it was waiting for debugger to attach."
+ },
+ {
+ "name": "runScript",
+ "description": "Runs script with given id in a given context.",
+ "parameters": [
+ {
+ "name": "scriptId",
+ "description": "Id of the script to run.",
+ "$ref": "ScriptId"
+ },
+ {
+ "name": "executionContextId",
+ "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.",
+ "optional": true,
+ "$ref": "ExecutionContextId"
+ },
+ {
+ "name": "objectGroup",
+ "description": "Symbolic group name that can be used to release multiple objects.",
+ "optional": true,
+ "type": "string"
+ },
+ {
+ "name": "silent",
+ "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "includeCommandLineAPI",
+ "description": "Determines whether Command Line API should be available during the evaluation.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "returnByValue",
+ "description": "Whether the result is expected to be a JSON object which should be sent by value.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "generatePreview",
+ "description": "Whether preview should be generated for the result.",
+ "optional": true,
+ "type": "boolean"
+ },
+ {
+ "name": "awaitPromise",
+ "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.",
+ "optional": true,
+ "type": "boolean"
+ }
+ ],
+ "returns": [
+ {
+ "name": "result",
+ "description": "Run result.",
+ "$ref": "RemoteObject"
+ },
+ {
+ "name": "exceptionDetails",
+ "description": "Exception details.",
+ "optional": true,
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ },
+ {
+ "name": "setAsyncCallStackDepth",
+ "description": "Enables or disables async call stacks tracking.",
+ "redirect": "Debugger",
+ "parameters": [
+ {
+ "name": "maxDepth",
+ "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).",
+ "type": "integer"
+ }
+ ]
+ },
+ {
+ "name": "setCustomObjectFormatterEnabled",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "enabled",
+ "type": "boolean"
+ }
+ ]
+ },
+ {
+ "name": "setMaxCallStackSizeToCapture",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "size",
+ "type": "integer"
+ }
+ ]
+ },
+ {
+ "name": "terminateExecution",
+ "description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends.",
+ "experimental": true
+ },
+ {
+ "name": "addBinding",
+ "description": "If executionContextId is empty, adds binding with the given name on the\nglobal objects of all inspected contexts, including those created later,\nbindings survive reloads.\nBinding function takes exactly one argument, this argument should be string,\nin case of any other input, function throws an exception.\nEach binding function call produces Runtime.bindingCalled notification.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "name": "executionContextId",
+ "description": "If specified, the binding would only be exposed to the specified\nexecution context. If omitted and `executionContextName` is not set,\nthe binding is exposed to all execution contexts of the target.\nThis parameter is mutually exclusive with `executionContextName`.\nDeprecated in favor of `executionContextName` due to an unclear use case\nand bugs in implementation (crbug.com/1169639). `executionContextId` will be\nremoved in the future.",
+ "deprecated": true,
+ "optional": true,
+ "$ref": "ExecutionContextId"
+ },
+ {
+ "name": "executionContextName",
+ "description": "If specified, the binding is exposed to the executionContext with\nmatching name, even for contexts created after the binding is added.\nSee also `ExecutionContext.name` and `worldName` parameter to\n`Page.addScriptToEvaluateOnNewDocument`.\nThis parameter is mutually exclusive with `executionContextId`.",
+ "experimental": true,
+ "optional": true,
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "removeBinding",
+ "description": "This method does not remove binding function from global object but\nunsubscribes current runtime agent from Runtime.bindingCalled notifications.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "getExceptionDetails",
+ "description": "This method tries to lookup and populate exception details for a\nJavaScript Error object.\nNote that the stackTrace portion of the resulting exceptionDetails will\nonly be populated if the Runtime domain was enabled at the time when the\nError was thrown.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "errorObjectId",
+ "description": "The error object for which to resolve the exception details.",
+ "$ref": "RemoteObjectId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "exceptionDetails",
+ "optional": true,
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "bindingCalled",
+ "description": "Notification is issued every time when binding is called.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "name": "payload",
+ "type": "string"
+ },
+ {
+ "name": "executionContextId",
+ "description": "Identifier of the context where the call was made.",
+ "$ref": "ExecutionContextId"
+ }
+ ]
+ },
+ {
+ "name": "consoleAPICalled",
+ "description": "Issued when console API was called.",
+ "parameters": [
+ {
+ "name": "type",
+ "description": "Type of the call.",
+ "type": "string",
+ "enum": [
+ "log",
+ "debug",
+ "info",
+ "error",
+ "warning",
+ "dir",
+ "dirxml",
+ "table",
+ "trace",
+ "clear",
+ "startGroup",
+ "startGroupCollapsed",
+ "endGroup",
+ "assert",
+ "profile",
+ "profileEnd",
+ "count",
+ "timeEnd"
+ ]
+ },
+ {
+ "name": "args",
+ "description": "Call arguments.",
+ "type": "array",
+ "items": {
+ "$ref": "RemoteObject"
+ }
+ },
+ {
+ "name": "executionContextId",
+ "description": "Identifier of the context where the call was made.",
+ "$ref": "ExecutionContextId"
+ },
+ {
+ "name": "timestamp",
+ "description": "Call timestamp.",
+ "$ref": "Timestamp"
+ },
+ {
+ "name": "stackTrace",
+ "description": "Stack trace captured when the call was made. The async stack chain is automatically reported for\nthe following call types: `assert`, `error`, `trace`, `warning`. For other types the async call\nchain can be retrieved using `Debugger.getStackTrace` and `stackTrace.parentId` field.",
+ "optional": true,
+ "$ref": "StackTrace"
+ },
+ {
+ "name": "context",
+ "description": "Console context descriptor for calls on non-default console context (not console.*):\n'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call\non named context.",
+ "experimental": true,
+ "optional": true,
+ "type": "string"
+ }
+ ]
+ },
+ {
+ "name": "exceptionRevoked",
+ "description": "Issued when unhandled exception was revoked.",
+ "parameters": [
+ {
+ "name": "reason",
+ "description": "Reason describing why exception was revoked.",
+ "type": "string"
+ },
+ {
+ "name": "exceptionId",
+ "description": "The id of revoked exception, as reported in `exceptionThrown`.",
+ "type": "integer"
+ }
+ ]
+ },
+ {
+ "name": "exceptionThrown",
+ "description": "Issued when exception was thrown and unhandled.",
+ "parameters": [
+ {
+ "name": "timestamp",
+ "description": "Timestamp of the exception.",
+ "$ref": "Timestamp"
+ },
+ {
+ "name": "exceptionDetails",
+ "$ref": "ExceptionDetails"
+ }
+ ]
+ },
+ {
+ "name": "executionContextCreated",
+ "description": "Issued when new execution context is created.",
+ "parameters": [
+ {
+ "name": "context",
+ "description": "A newly created execution context.",
+ "$ref": "ExecutionContextDescription"
+ }
+ ]
+ },
+ {
+ "name": "executionContextDestroyed",
+ "description": "Issued when execution context is destroyed.",
+ "parameters": [
+ {
+ "name": "executionContextId",
+ "description": "Id of the destroyed context",
+ "$ref": "ExecutionContextId"
+ }
+ ]
+ },
+ {
+ "name": "executionContextsCleared",
+ "description": "Issued when all executionContexts were cleared in browser"
+ },
+ {
+ "name": "inspectRequested",
+ "description": "Issued when object should be inspected (for example, as a result of inspect() command line API\ncall).",
+ "parameters": [
+ {
+ "name": "object",
+ "$ref": "RemoteObject"
+ },
+ {
+ "name": "hints",
+ "type": "object"
+ },
+ {
+ "name": "executionContextId",
+ "description": "Identifier of the context where the call was made.",
+ "experimental": true,
+ "optional": true,
+ "$ref": "ExecutionContextId"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java
index f9b6ae4..3fb3d17 100644
--- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java
+++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java
@@ -3,12 +3,13 @@ package me.topchetoeu.jscript.engine.frame;
import java.util.Stack;
import me.topchetoeu.jscript.Location;
+import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context;
+import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
-import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
@@ -95,34 +96,32 @@ public class CodeFrame {
}
private void setCause(Context ctx, EngineException err, EngineException cause) {
- if (err.value instanceof ObjectValue) {
- Values.setMember(ctx, err, ctx.environment().symbol("Symbol.cause"), cause);
- }
err.cause = cause;
}
- private Object nextNoTry(Context ctx) {
+ private Object nextNoTry(Context ctx, Instruction instr) {
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (codePtr < 0 || codePtr >= function.body.length) return null;
- var instr = function.body[codePtr];
-
- var loc = instr.location;
- if (loc != null) prevLoc = loc;
-
try {
this.jumpFlag = false;
return Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
- throw e.add(function.name, prevLoc).setContext(ctx);
+ throw e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
}
}
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
if (value != Runners.NO_RETURN) push(ctx, value);
+ var debugger = StackData.getDebugger(ctx);
if (returnValue == Runners.NO_RETURN && error == null) {
- try { returnValue = nextNoTry(ctx); }
+ try {
+ var instr = function.body[codePtr];
+
+ if (debugger != null) debugger.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
+ returnValue = nextNoTry(ctx, instr);
+ }
catch (EngineException e) { error = e; }
}
@@ -165,6 +164,7 @@ public class CodeFrame {
tryCtx.err = error;
newState = TryCtx.STATE_FINALLY_THREW;
}
+ setCause(ctx, error, tryCtx.err);
break;
}
else if (returnValue != Runners.NO_RETURN) {
@@ -223,8 +223,15 @@ public class CodeFrame {
return Runners.NO_RETURN;
}
- if (error != null) throw error.setContext(ctx);
- if (returnValue != Runners.NO_RETURN) return returnValue;
+ if (error != null) {
+ if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, false);
+ throw error;
+ }
+ if (returnValue != Runners.NO_RETURN) {
+ if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
+ return returnValue;
+ }
+
return Runners.NO_RETURN;
}
diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java
index 4ec8991..bccbcdb 100644
--- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java
+++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java
@@ -2,6 +2,10 @@ package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList;
+import me.topchetoeu.jscript.engine.Context;
+import me.topchetoeu.jscript.engine.values.NativeFunction;
+import me.topchetoeu.jscript.engine.values.ObjectValue;
+
public class LocalScope {
private String[] names;
public final ValueVariable[] captures;
@@ -15,11 +19,25 @@ public class LocalScope {
else return captures[~i];
}
- public String[] getNames() {
+ 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 (var i = 0; i < locals.length; i++) {
- if (names == null || i >= names.length) res[i] = "local_" + i;
+ 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];
}
@@ -33,11 +51,36 @@ public class LocalScope {
return captures.length + locals.length;
}
- public void toGlobal(GlobalScope global) {
- var names = getNames();
- for (var i = 0; i < names.length; i++) {
- global.define(names[i], locals[i]);
+ 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) {
diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java
index 3a19e79..44a9543 100644
--- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java
+++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java
@@ -212,7 +212,7 @@ public class ArrayValue extends ObjectValue implements Iterable {
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
}
- public static ArrayValue of(Context ctx, Collection values) {
+ public static ArrayValue of(Context ctx, Collection> values) {
return new ArrayValue(ctx, values.toArray(Object[]::new));
}
}
diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java
index 9ca731a..ccc94bf 100644
--- a/src/me/topchetoeu/jscript/engine/values/Values.java
+++ b/src/me/topchetoeu/jscript/engine/values/Values.java
@@ -665,7 +665,8 @@ public class Values {
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
try {
if (err instanceof EngineException) {
- System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx));
+ var ee = ((EngineException)err);
+ System.out.println(prefix + " " + ee.toString(new Context(ee.engine).pushEnv(ee.env)));
}
else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java
index 90444e1..3f740b1 100644
--- a/src/me/topchetoeu/jscript/exceptions/EngineException.java
+++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java
@@ -5,6 +5,8 @@ import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context;
+import me.topchetoeu.jscript.engine.Engine;
+import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
@@ -12,7 +14,8 @@ import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
public class EngineException extends RuntimeException {
public final Object value;
public EngineException cause;
- public Context ctx = null;
+ public Environment env = null;
+ public Engine engine = null;
public final List stackTrace = new ArrayList<>();
public EngineException add(String name, Location location) {
@@ -28,8 +31,9 @@ public class EngineException extends RuntimeException {
this.cause = cause;
return this;
}
- public EngineException setContext(Context ctx) {
- this.ctx = ctx;
+ public EngineException setCtx(Environment env, Engine engine) {
+ if (this.env == null) this.env = env;
+ if (this.engine == null) this.engine = engine;
return this;
}
diff --git a/src/me/topchetoeu/jscript/exceptions/InterruptException.java b/src/me/topchetoeu/jscript/exceptions/InterruptException.java
index ae9e2e8..f198f49 100644
--- a/src/me/topchetoeu/jscript/exceptions/InterruptException.java
+++ b/src/me/topchetoeu/jscript/exceptions/InterruptException.java
@@ -2,7 +2,7 @@ package me.topchetoeu.jscript.exceptions;
public class InterruptException extends RuntimeException {
public InterruptException() { }
- public InterruptException(InterruptedException e) {
+ public InterruptException(Throwable e) {
super(e);
}
}
diff --git a/src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java b/src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java
new file mode 100644
index 0000000..fae7e82
--- /dev/null
+++ b/src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java
@@ -0,0 +1,9 @@
+package me.topchetoeu.jscript.exceptions;
+
+import java.io.IOException;
+
+public class UncheckedIOException extends RuntimeException {
+ public UncheckedIOException(IOException e) {
+ super(e);
+ }
+}
diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java
index 4ea59e2..593378a 100644
--- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java
+++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java
@@ -79,7 +79,7 @@ public class OverloadFunction extends FunctionValue {
catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); }
catch (IllegalAccessException | IllegalArgumentException e) { continue; }
catch (InvocationTargetException e) {
- var loc = new Location(0, 0, "");
+ var loc = Location.INTERNAL;
if (e.getTargetException() instanceof EngineException) {
throw ((EngineException)e.getTargetException()).add(name, loc);
}
@@ -91,7 +91,7 @@ public class OverloadFunction extends FunctionValue {
}
}
catch (ReflectiveOperationException e) {
- throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, ""));
+ throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL);
}
}
diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java
index 0869990..8e807f7 100644
--- a/src/me/topchetoeu/jscript/json/JSON.java
+++ b/src/me/topchetoeu/jscript/json/JSON.java
@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.json;
import java.util.List;
import java.util.stream.Collectors;
+import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.parsing.Operator;
import me.topchetoeu.jscript.parsing.ParseRes;
@@ -13,17 +14,17 @@ public class JSON {
public static ParseRes parseIdentifier(List tokens, int i) {
return Parsing.parseIdentifier(tokens, i);
}
- public static ParseRes parseString(String filename, List tokens, int i) {
+ public static ParseRes parseString(Filename filename, List tokens, int i) {
var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform();
}
- public static ParseRes parseNumber(String filename, List tokens, int i) {
+ public static ParseRes parseNumber(Filename filename, List tokens, int i) {
var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n);
else return res.transform();
}
- public static ParseRes parseBool(String filename, List tokens, int i) {
+ public static ParseRes parseBool(Filename filename, List tokens, int i) {
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return ParseRes.failed();
@@ -32,7 +33,7 @@ public class JSON {
else return ParseRes.failed();
}
- public static ParseRes> parseValue(String filename, List tokens, int i) {
+ public static ParseRes> parseValue(Filename filename, List tokens, int i) {
return ParseRes.any(
parseString(filename, tokens, i),
parseNumber(filename, tokens, i),
@@ -42,7 +43,7 @@ public class JSON {
);
}
- public static ParseRes parseMap(String filename, List tokens, int i) {
+ public static ParseRes parseMap(Filename filename, List tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@@ -82,7 +83,7 @@ public class JSON {
return ParseRes.res(values, n);
}
- public static ParseRes parseList(String filename, List tokens, int i) {
+ public static ParseRes parseList(Filename filename, List tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
@@ -109,7 +110,7 @@ public class JSON {
return ParseRes.res(values, n);
}
- public static JSONElement parse(String filename, String raw) {
+ public static JSONElement parse(Filename filename, String raw) {
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error);
@@ -120,7 +121,12 @@ public class JSON {
if (el.isNumber()) return Double.toString(el.number());
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
- if (el.isString()) return "\"" + el.string().replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
+ if (el.isString()) return "\"" + el.string()
+ .replace("\\", "\\\\")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\"", "\\\"")
+ + "\"";
if (el.isList()) {
var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) {
diff --git a/src/me/topchetoeu/jscript/json/JSONElement.java b/src/me/topchetoeu/jscript/json/JSONElement.java
index e26aa3a..8e03c7f 100644
--- a/src/me/topchetoeu/jscript/json/JSONElement.java
+++ b/src/me/topchetoeu/jscript/json/JSONElement.java
@@ -65,10 +65,22 @@ public class JSONElement {
return (double)value;
}
public boolean bool() {
- if (!isNumber()) throw new IllegalStateException("Element is not a boolean.");
+ if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value;
}
+ @Override
+ public String toString() {
+ if (isMap()) return "{...}";
+ if (isList()) return "[...]";
+ if (isString()) return (String)value;
+ if (isString()) return (String)value;
+ if (isNumber()) return (double)value + "";
+ if (isBoolean()) return (boolean)value + "";
+ if (isNull()) return "null";
+ return "";
+ }
+
private JSONElement(Type type, Object val) {
this.type = type;
this.value = val;
diff --git a/src/me/topchetoeu/jscript/json/JSONList.java b/src/me/topchetoeu/jscript/json/JSONList.java
index eb343ac..b73eb49 100644
--- a/src/me/topchetoeu/jscript/json/JSONList.java
+++ b/src/me/topchetoeu/jscript/json/JSONList.java
@@ -17,5 +17,7 @@ public class JSONList extends ArrayList {
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Map val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Collection val) { this.add(JSONElement.of(val)); return this; }
+ public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
+ public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
}
diff --git a/src/me/topchetoeu/jscript/json/JSONMap.java b/src/me/topchetoeu/jscript/json/JSONMap.java
index 203adaf..dc6d845 100644
--- a/src/me/topchetoeu/jscript/json/JSONMap.java
+++ b/src/me/topchetoeu/jscript/json/JSONMap.java
@@ -51,7 +51,7 @@ public class JSONMap implements Map {
public JSONMap map(String path) {
var el = get(path);
- if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
+ if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.map();
}
public JSONMap map(String path, JSONMap defaultVal) {
@@ -63,7 +63,7 @@ public class JSONMap implements Map {
public JSONList list(String path) {
var el = get(path);
- if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
+ if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.list();
}
public JSONList list(String path, JSONList defaultVal) {
@@ -75,7 +75,7 @@ public class JSONMap implements Map {
public String string(String path) {
var el = get(path);
- if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
+ if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.string();
}
public String string(String path, String defaultVal) {
@@ -87,7 +87,7 @@ public class JSONMap implements Map {
public double number(String path) {
var el = get(path);
- if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
+ if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.number();
}
public double number(String path, double defaultVal) {
@@ -99,7 +99,7 @@ public class JSONMap implements Map {
public boolean bool(String path) {
var el = get(path);
- if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
+ if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.bool();
}
public boolean bool(String path, boolean defaultVal) {
diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java
index 0783038..0991fce 100644
--- a/src/me/topchetoeu/jscript/lib/ErrorLib.java
+++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java
@@ -12,7 +12,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit;
public class ErrorLib {
- private static String toString(Context ctx, Object cause, Object name, Object message, ArrayValue stack) {
+ private static String toString(Context ctx, boolean rethrown, Object cause, Object name, Object message, ArrayValue stack) {
if (name == null) name = "";
else name = Values.toString(ctx, name).trim();
if (message == null) message = "";
@@ -30,7 +30,10 @@ public class ErrorLib {
}
}
- if (cause instanceof ObjectValue) res.append(toString(ctx, cause));
+ if (cause instanceof ObjectValue) {
+ if (rethrown) res.append("\n (rethrown)");
+ else res.append("\nCaused by ").append(toString(ctx, cause));
+ }
return res.toString();
}
@@ -39,8 +42,10 @@ public class ErrorLib {
if (thisArg instanceof ObjectValue) {
var stack = Values.getMember(ctx, thisArg, "stack");
if (!(stack instanceof ArrayValue)) stack = null;
+ var cause = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.cause"));
return toString(ctx,
- Values.getMember(ctx, thisArg, "cause"),
+ thisArg == cause,
+ cause,
Values.getMember(ctx, thisArg, "name"),
Values.getMember(ctx, thisArg, "message"),
(ArrayValue)stack
@@ -53,7 +58,7 @@ public class ErrorLib {
var target = new ObjectValue();
if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg;
- target.defineProperty(ctx, "stack", new ArrayValue(ctx, StackData.stackTrace(ctx)));
+ target.defineProperty(ctx, "stack", ArrayValue.of(ctx, StackData.stackTrace(ctx)));
target.defineProperty(ctx, "name", "Error");
if (message == null) target.defineProperty(ctx, "message", "");
else target.defineProperty(ctx, "message", Values.toString(ctx, message));
diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java
index 811f482..5fa83e2 100644
--- a/src/me/topchetoeu/jscript/lib/Internals.java
+++ b/src/me/topchetoeu/jscript/lib/Internals.java
@@ -1,7 +1,9 @@
package me.topchetoeu.jscript.lib;
+import java.io.IOException;
import java.util.HashMap;
+import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.DataKey;
import me.topchetoeu.jscript.engine.Environment;
@@ -14,12 +16,22 @@ public class Internals {
private static final DataKey> THREADS = new DataKey<>();
private static final DataKey I = new DataKey<>();
+
@Native public static void log(Context ctx, Object ...args) {
for (var arg : args) {
Values.printValue(ctx, arg);
}
System.out.println();
}
+ @Native public static String readline(Context ctx) {
+ try {
+ return Reading.read();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
@Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) {
var thread = new Thread(() -> {
diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java
index 8d612df..d8b202e 100644
--- a/src/me/topchetoeu/jscript/lib/JSONLib.java
+++ b/src/me/topchetoeu/jscript/lib/JSONLib.java
@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib;
import java.util.HashSet;
import java.util.stream.Collectors;
+import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
@@ -72,7 +73,7 @@ public class JSONLib {
@Native
public static Object parse(Context ctx, String val) {
try {
- return toJS(me.topchetoeu.jscript.json.JSON.parse("", val));
+ return toJS(me.topchetoeu.jscript.json.JSON.parse(new Filename("jscript", "json"), val));
}
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
}
diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java
index 1b53d2a..7c4f7db 100644
--- a/src/me/topchetoeu/jscript/lib/PromiseLib.java
+++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java
@@ -234,12 +234,12 @@ public class PromiseLib {
private boolean handled = false;
private Object val;
- private void resolve(Context ctx, Object val, int state) {
+ public void fulfill(Context ctx, Object val) {
if (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
- new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], state); return null; }),
- new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], STATE_REJECTED); return null; })
+ new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }),
+ new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
);
else {
Object next;
@@ -248,29 +248,14 @@ public class PromiseLib {
try {
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
- new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, state); return null; }),
- new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, STATE_REJECTED); return null; })
+ new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }),
+ new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
);
else {
this.val = val;
- this.state = state;
+ this.state = STATE_FULFILLED;
- if (state == STATE_FULFILLED) {
- for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val);
- }
- else if (state == STATE_REJECTED) {
- for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
- if (handles.size() == 0) {
- ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
- if (!handled) {
- Values.printError(new EngineException(val).setContext(ctx), "(in promise)");
- throw new InterruptException();
- }
-
- return null;
- }), null);
- }
- }
+ for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val);
handles = null;
}
@@ -280,18 +265,46 @@ public class PromiseLib {
}
}
}
-
- /**
- * Thread safe - call from any thread
- */
- public void fulfill(Context ctx, Object val) {
- resolve(ctx, val, STATE_FULFILLED);
- }
- /**
- * Thread safe - call from any thread
- */
public void reject(Context ctx, Object val) {
- resolve(ctx, val, STATE_REJECTED);
+ if (this.state != STATE_PENDING) return;
+
+ if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
+ new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }),
+ new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
+ );
+ else {
+ Object next;
+ try { next = Values.getMember(ctx, val, "next"); }
+ catch (IllegalArgumentException e) { next = null; }
+
+ try {
+ if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
+ new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }),
+ new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
+ );
+ else {
+ this.val = val;
+ this.state = STATE_REJECTED;
+
+ for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
+ if (handles.size() == 0) {
+ ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
+ if (!handled) {
+ Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
+ throw new InterruptException();
+ }
+
+ return null;
+ }), null);
+ }
+
+ handles = null;
+ }
+ }
+ catch (EngineException err) {
+ this.reject(ctx, err.value);
+ }
+ }
}
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java
index 63206ae..e7be32a 100644
--- a/src/me/topchetoeu/jscript/lib/SymbolLib.java
+++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java
@@ -26,6 +26,7 @@ public class SymbolLib {
@NativeGetter public static Symbol search(Context ctx) { return ctx.environment().symbol("Symbol.search"); }
@NativeGetter public static Symbol iterator(Context ctx) { return ctx.environment().symbol("Symbol.iterator"); }
@NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.environment().symbol("Symbol.asyncIterator"); }
+ @NativeGetter public static Symbol cause(Context ctx) { return ctx.environment().symbol("Symbol.cause"); }
public final Symbol value;
diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java
index aa74e01..1fea791 100644
--- a/src/me/topchetoeu/jscript/parsing/Parsing.java
+++ b/src/me/topchetoeu/jscript/parsing/Parsing.java
@@ -6,7 +6,9 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.TreeSet;
+import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.*;
import me.topchetoeu.jscript.compilation.Instruction.Type;
@@ -25,7 +27,7 @@ import me.topchetoeu.jscript.parsing.ParseRes.State;
// TODO: this has to be rewritten
public class Parsing {
public static interface Parser {
- ParseRes parse(String filename, List tokens, int i);
+ ParseRes parse(Filename filename, List tokens, int i);
}
private static class ObjProp {
@@ -69,7 +71,7 @@ public class Parsing {
reserved.add("delete");
reserved.add("break");
reserved.add("continue");
- reserved.add("debug");
+ reserved.add("debugger");
reserved.add("implements");
reserved.add("interface");
reserved.add("package");
@@ -122,7 +124,7 @@ public class Parsing {
private static final int CURR_MULTI_COMMENT = 8;
private static final int CURR_SINGLE_COMMENT = 9;
- private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, String filename, List tokens) {
+ private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List tokens) {
var res = currToken.toString();
switch (currStage) {
@@ -143,7 +145,7 @@ public class Parsing {
// This method is so long because we're tokenizing the string using an iterative approach
// instead of a recursive descent parser. This is mainly done for performance reasons.
- private static ArrayList splitTokens(String filename, String raw) {
+ private static ArrayList splitTokens(Filename filename, String raw) {
var tokens = new ArrayList();
var currToken = new StringBuilder(64);
@@ -572,7 +574,7 @@ public class Parsing {
else return res;
}
- private static List parseTokens(String filename, Collection tokens) {
+ private static List parseTokens(Filename filename, Collection tokens) {
var res = new ArrayList();
for (var el : tokens) {
@@ -593,11 +595,11 @@ public class Parsing {
return res;
}
- public static List tokenize(String filename, String raw) {
+ public static List tokenize(Filename filename, String raw) {
return parseTokens(filename, splitTokens(filename, raw));
}
- public static Location getLoc(String filename, List tokens, int i) {
+ public static Location getLoc(Filename filename, List tokens, int i) {
if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename);
if (i >= tokens.size()) i = tokens.size() - 1;
return new Location(tokens.get(i).line, tokens.get(i).start, filename);
@@ -663,7 +665,7 @@ public class Parsing {
return !reserved.contains(name);
}
- public static ParseRes parseString(String filename, List tokens, int i) {
+ public static ParseRes parseString(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
try {
if (tokens.get(i).isString()) {
@@ -675,7 +677,7 @@ public class Parsing {
return ParseRes.failed();
}
}
- public static ParseRes parseNumber(String filename, List tokens, int i) {
+ public static ParseRes parseNumber(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
try {
if (tokens.get(i).isNumber()) {
@@ -688,7 +690,7 @@ public class Parsing {
}
}
- public static ParseRes parseRegex(String filename, List tokens, int i) {
+ public static ParseRes parseRegex(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
try {
if (tokens.get(i).isRegex()) {
@@ -705,7 +707,7 @@ public class Parsing {
}
}
- public static ParseRes parseArray(String filename, List tokens, int i) {
+ public static ParseRes parseArray(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
@@ -744,7 +746,7 @@ public class Parsing {
return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n);
}
- public static ParseRes> parseParamList(String filename, List tokens, int i) {
+ public static ParseRes> parseParamList(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -776,7 +778,7 @@ public class Parsing {
return ParseRes.res(args, n);
}
- public static ParseRes extends Object> parsePropName(String filename, List tokens, int i) {
+ public static ParseRes extends Object> parsePropName(Filename filename, List tokens, int i) {
var idRes = parseIdentifier(tokens, i);
if (idRes.isSuccess()) return ParseRes.res(idRes.result, 1);
var strRes = parseString(null, tokens, i);
@@ -786,7 +788,7 @@ public class Parsing {
return ParseRes.failed();
}
- public static ParseRes parseObjectProp(String filename, List tokens, int i) {
+ public static ParseRes parseObjectProp(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -813,7 +815,7 @@ public class Parsing {
new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result)
), n);
}
- public static ParseRes parseObject(String filename, List tokens, int i) {
+ public static ParseRes parseObject(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@@ -870,7 +872,7 @@ public class Parsing {
return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n);
}
- public static ParseRes parseNew(String filename, List tokens, int i) {
+ public static ParseRes parseNew(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
var n = 0;
if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed();
@@ -886,7 +888,7 @@ public class Parsing {
return ParseRes.res(new NewStatement(loc, call.func, call.args), n);
}
- public static ParseRes parseTypeof(String filename, List tokens, int i) {
+ public static ParseRes parseTypeof(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
var n = 0;
if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed();
@@ -897,7 +899,7 @@ public class Parsing {
return ParseRes.res(new TypeofStatement(loc, valRes.result), n);
}
- public static ParseRes parseVoid(String filename, List tokens, int i) {
+ public static ParseRes parseVoid(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
var n = 0;
if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed();
@@ -908,7 +910,7 @@ public class Parsing {
return ParseRes.res(new VoidStatement(loc, valRes.result), n);
}
- public static ParseRes extends Statement> parseDelete(String filename, List tokens, int i) {
+ public static ParseRes extends Statement> parseDelete(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed();
@@ -929,7 +931,7 @@ public class Parsing {
}
}
- public static ParseRes parseFunction(String filename, List tokens, int i, boolean statement) {
+ public static ParseRes parseFunction(Filename filename, List tokens, int i, boolean statement) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -972,7 +974,7 @@ public class Parsing {
else return ParseRes.error(loc, "Expected a compound statement for function.", res);
}
- public static ParseRes parseUnary(String filename, List tokens, int i) {
+ public static ParseRes parseUnary(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -993,7 +995,7 @@ public class Parsing {
if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n);
else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.value), res);
}
- public static ParseRes parsePrefixChange(String filename, List tokens, int i) {
+ public static ParseRes parsePrefixChange(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1010,7 +1012,7 @@ public class Parsing {
if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator.");
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n);
}
- public static ParseRes extends Statement> parseParens(String filename, List tokens, int i) {
+ public static ParseRes extends Statement> parseParens(Filename filename, List tokens, int i) {
int n = 0;
if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed();
@@ -1023,7 +1025,7 @@ public class Parsing {
return ParseRes.res(res.result, n);
}
@SuppressWarnings("all")
- public static ParseRes extends Statement> parseSimple(String filename, List tokens, int i, boolean statement) {
+ public static ParseRes extends Statement> parseSimple(Filename filename, List tokens, int i, boolean statement) {
var res = new ArrayList<>();
if (!statement) {
@@ -1050,7 +1052,7 @@ public class Parsing {
return ParseRes.any(res.toArray(ParseRes[]::new));
}
- public static ParseRes parseVariable(String filename, List tokens, int i) {
+ public static ParseRes parseVariable(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
var literal = parseIdentifier(tokens, i);
@@ -1066,7 +1068,7 @@ public class Parsing {
return ParseRes.res(new VariableStatement(loc, literal.result), 1);
}
- public static ParseRes extends Statement> parseLiteral(String filename, List tokens, int i) {
+ public static ParseRes extends Statement> parseLiteral(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return id.transform();
@@ -1094,7 +1096,7 @@ public class Parsing {
}
return ParseRes.failed();
}
- public static ParseRes parseMember(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parseMember(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1107,7 +1109,7 @@ public class Parsing {
return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n);
}
- public static ParseRes parseIndex(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parseIndex(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1123,7 +1125,7 @@ public class Parsing {
return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n);
}
- public static ParseRes extends Statement> parseAssign(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes extends Statement> parseAssign(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0 ;
@@ -1157,7 +1159,7 @@ public class Parsing {
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n);
}
- public static ParseRes parseCall(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parseCall(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1189,7 +1191,7 @@ public class Parsing {
return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n);
}
- public static ParseRes parsePostfixChange(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parsePostfixChange(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1207,7 +1209,7 @@ public class Parsing {
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator.");
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n);
}
- public static ParseRes parseInstanceof(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parseInstanceof(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1220,7 +1222,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
}
- public static ParseRes parseIn(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parseIn(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1233,7 +1235,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
}
- public static ParseRes parseComma(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1246,7 +1248,7 @@ public class Parsing {
return ParseRes.res(new CompoundStatement(loc, prev, res.result), n);
}
- public static ParseRes parseTernary(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes parseTernary(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1265,7 +1267,7 @@ public class Parsing {
return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n);
}
- public static ParseRes extends Statement> parseOperator(String filename, List tokens, int i, Statement prev, int precedence) {
+ public static ParseRes extends Statement> parseOperator(Filename filename, List tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
var n = 0;
@@ -1290,7 +1292,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n);
}
- public static ParseRes extends Statement> parseValue(String filename, List tokens, int i, int precedence, boolean statement) {
+ public static ParseRes extends Statement> parseValue(Filename filename, List tokens, int i, int precedence, boolean statement) {
Statement prev = null;
int n = 0;
@@ -1331,11 +1333,11 @@ public class Parsing {
if (prev == null) return ParseRes.failed();
else return ParseRes.res(prev, n);
}
- public static ParseRes extends Statement> parseValue(String filename, List tokens, int i, int precedence) {
+ public static ParseRes extends Statement> parseValue(Filename filename, List tokens, int i, int precedence) {
return parseValue(filename, tokens, i, precedence, false);
}
- public static ParseRes extends Statement> parseValueStatement(String filename, List tokens, int i) {
+ public static ParseRes extends Statement> parseValueStatement(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
var valRes = parseValue(filename, tokens, i, 0, true);
if (!valRes.isSuccess()) return valRes.transform();
@@ -1352,7 +1354,7 @@ public class Parsing {
}
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res);
}
- public static ParseRes parseVariableDeclare(String filename, List tokens, int i) {
+ public static ParseRes parseVariableDeclare(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed();
@@ -1396,7 +1398,7 @@ public class Parsing {
}
}
- public static ParseRes parseReturn(String filename, List tokens, int i) {
+ public static ParseRes parseReturn(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed();
@@ -1420,7 +1422,7 @@ public class Parsing {
else
return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
}
- public static ParseRes parseThrow(String filename, List tokens, int i) {
+ public static ParseRes parseThrow(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed();
@@ -1438,7 +1440,7 @@ public class Parsing {
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
}
- public static ParseRes parseBreak(String filename, List tokens, int i) {
+ public static ParseRes parseBreak(Filename filename, List tokens, int i) {
if (!isIdentifier(tokens, i, "break")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) {
@@ -1456,7 +1458,7 @@ public class Parsing {
}
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
}
- public static ParseRes parseContinue(String filename, List tokens, int i) {
+ public static ParseRes parseContinue(Filename filename, List tokens, int i) {
if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) {
@@ -1474,8 +1476,8 @@ public class Parsing {
}
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
}
- public static ParseRes parseDebug(String filename, List tokens, int i) {
- if (!isIdentifier(tokens, i, "debug")) return ParseRes.failed();
+ public static ParseRes parseDebug(Filename filename, List tokens, int i) {
+ if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) {
if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2);
@@ -1484,7 +1486,7 @@ public class Parsing {
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
}
- public static ParseRes parseCompound(String filename, List tokens, int i) {
+ public static ParseRes parseCompound(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@@ -1520,7 +1522,7 @@ public class Parsing {
return ParseRes.res(nameRes.result, n);
}
- public static ParseRes parseIf(String filename, List tokens, int i) {
+ public static ParseRes parseIf(Filename filename, List tokens, int i) {
var loc = getLoc(filename, tokens, i);
int n = 0;
@@ -1547,7 +1549,7 @@ public class Parsing {
return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n);
}
- public static ParseRes parseWhile(String filename, List tokens, int i) {
+ public static ParseRes parseWhile(Filename filename, List