Compare commits
39 Commits
v0.2.0-alp
...
v0.4.2-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
fdfa8d7713
|
|||
|
f5d1287948
|
|||
|
15f4278cb1
|
|||
| df8465cb49 | |||
|
e3104c223c
|
|||
|
1d0bae3de8
|
|||
|
b66acd3089
|
|||
|
e326847287
|
|||
|
26591d6631
|
|||
|
af31b1ab79
|
|||
|
f885d4349f
|
|||
|
d57044acb7
|
|||
|
7df4e3b03f
|
|||
|
ed1009ab69
|
|||
|
f856cdf37e
|
|||
|
4f82574b8c
|
|||
|
0ae24148d8
|
|||
|
ac128d17f4
|
|||
|
6508f15bb0
|
|||
|
69f93b4f87
|
|||
|
b675411925
|
|||
|
d1e93c2088
|
|||
|
942db54546
|
|||
|
d20df66982
|
|||
| 16a9e5d761 | |||
|
dc9d84a370
|
|||
|
edb71daef4
|
|||
|
4b84309df6
|
|||
|
d2d9fa9738
|
|||
|
a4e5f7f471
|
|||
|
cc044374ba
|
|||
|
517e3e6657
|
|||
|
926b9c17d8
|
|||
|
fc705e7383
|
|||
|
a17ec737b7
|
|||
|
952a4d631d
|
|||
| 005610ca40 | |||
|
9743a6c078
|
|||
|
21a6d20ac5
|
5
.github/workflows/tagged-release.yml
vendored
5
.github/workflows/tagged-release.yml
vendored
@@ -11,6 +11,11 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- name: Clone repository
|
||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||
with:
|
||||
|
||||
37
README.md
37
README.md
@@ -4,31 +4,42 @@
|
||||
|
||||
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
|
||||
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
|
||||
|
||||
## Example
|
||||
|
||||
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
|
||||
|
||||
```java
|
||||
var engine = new PolyfillEngine(new File("."));
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
var engine = new Engine(true /* false if you dont want debugging */);
|
||||
var env = new Environment(null, null, null);
|
||||
var debugger = new DebugServer();
|
||||
|
||||
// Create one target for the engine and start debugging server
|
||||
debugger.targets.put("target", (socket, req) -> new SimpleDebugger(socket, engine));
|
||||
debugger.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
|
||||
// Queue code to load internal libraries and start engine
|
||||
engine.pushMsg(false, null, new Internals().getApplier(env));
|
||||
engine.start();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
var raw = in.readLine();
|
||||
var raw = Reading.read();
|
||||
if (raw == null) break;
|
||||
|
||||
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
|
||||
Values.printValue(engine.context(), res);
|
||||
System.out.println();
|
||||
// Push a message to the engine with the raw REPL code
|
||||
var res = engine.pushMsg(
|
||||
false, new Context(engine).pushEnv(env),
|
||||
new Filename("jscript", "repl.js"), raw, null
|
||||
).await();
|
||||
|
||||
Values.printValue(null, res);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
||||
}
|
||||
catch (InterruptedException _e) { return; }
|
||||
catch (EngineException e) { Values.printError(e, ""); }
|
||||
catch (SyntaxException ex) {
|
||||
System.out.println("Syntax error:" + ex.msg);
|
||||
}
|
||||
catch (IOException | InterruptedException e) { return; }
|
||||
catch (IOException e) { }
|
||||
}
|
||||
```
|
||||
|
||||
BIN
src/assets/favicon.png
Normal file
BIN
src/assets/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
30
src/assets/index.html
Normal file
30
src/assets/index.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JScript Debugger</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
This is the debugger of JScript. It implement the <a href="https://chromedevtools.github.io/devtools-protocol/1-2/">V8 Debugging protocol</a>,
|
||||
so you can use the devtools in chrome. <br>
|
||||
The debugger is still in early development, so please report any issues to
|
||||
<a href="https://github.com/TopchetoEU/java-jscript/issues">the github repo</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here are the available entrypoints:
|
||||
<ul>
|
||||
<li><a href="json/version">/json/version</a> - version and other stuff about the JScript engine</li>
|
||||
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
|
||||
<li><a href="json/protocol">/json/protocol</a> - documentation of the implemented V8 protocol</li>
|
||||
<li>/(any target) - websocket entrypoints for debugging</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Running ${NAME} v${VERSION} by ${AUTHOR}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
2007
src/assets/protocol.json
Normal file
2007
src/assets/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
54
src/me/topchetoeu/jscript/Filename.java
Normal file
54
src/me/topchetoeu/jscript/Filename.java
Normal file
@@ -0,0 +1,54 @@
|
||||
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) {
|
||||
path = path.trim();
|
||||
protocol = protocol.trim();
|
||||
this.protocol = protocol;
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public class Location {
|
||||
public static final Location INTERNAL = new Location(0, 0, "<internal>");
|
||||
public class Location implements Comparable<Location> {
|
||||
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
|
||||
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;
|
||||
|
||||
@@ -1,129 +1,131 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Message;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.polyfills.Internals;
|
||||
|
||||
public class Main {
|
||||
static Thread task;
|
||||
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 (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
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<Object> valuePrinter = new Observer<Object>() {
|
||||
public void next(Object data) {
|
||||
try { Values.printValue(null, data); }
|
||||
catch (InterruptedException e) { }
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
try { Values.printError(err, null); }
|
||||
catch (InterruptedException ex) { return; }
|
||||
}
|
||||
};
|
||||
|
||||
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];
|
||||
|
||||
engine.pushMsg(false, new Message(engine), new NativeFunction((ctx, thisArg, _a) -> {
|
||||
new Internals().apply(env);
|
||||
|
||||
env.global.define("exit", _ctx -> {
|
||||
exited[0] = true;
|
||||
task.interrupt();
|
||||
throw new InterruptedException();
|
||||
});
|
||||
env.global.define("go", _ctx -> {
|
||||
try {
|
||||
var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js"))));
|
||||
return func.call(_ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}), null);
|
||||
|
||||
task = engine.start();
|
||||
var reader = new Thread(() -> {
|
||||
try {
|
||||
while (true) {
|
||||
try {
|
||||
var raw = in.readLine();
|
||||
|
||||
if (raw == null) break;
|
||||
engine.pushMsg(false, env.context(new Message(engine)), "<stdio>", raw, null).toObservable().once(valuePrinter);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(null));
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught [error while converting to string]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
catch (SyntaxException ex) {
|
||||
if (exited[0]) return;
|
||||
System.out.println("Syntax error:" + ex.msg);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (exited[0]) return;
|
||||
System.out.println("Internal error ocurred:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
if (exited[0]) return;
|
||||
});
|
||||
reader.setDaemon(true);
|
||||
reader.setName("STD Reader");
|
||||
reader.start();
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
import java.io.IOException;
|
||||
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.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.lib.Internals;
|
||||
|
||||
public class Main {
|
||||
static Thread engineTask, debugTask;
|
||||
static Engine engine;
|
||||
static Environment env;
|
||||
static int j = 0;
|
||||
|
||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||
public void next(Object data) {
|
||||
Values.printValue(null, data);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
Values.printError(err, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
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));
|
||||
engine = new Engine(true);
|
||||
|
||||
var exited = new boolean[1];
|
||||
var server = new DebugServer();
|
||||
server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
|
||||
|
||||
env = Internals.apply(new Environment(null, null, null));
|
||||
|
||||
env.global.define("exit", _ctx -> {
|
||||
exited[0] = true;
|
||||
throw new InterruptException();
|
||||
});
|
||||
env.global.define("go", _ctx -> {
|
||||
try {
|
||||
var f = Path.of("do.js");
|
||||
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
||||
return func.call(_ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
|
||||
engineTask = engine.start();
|
||||
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
|
||||
try {
|
||||
var tsEnv = Internals.apply(new Environment(null, null, null));
|
||||
var bsEnv = Internals.apply(new Environment(null, null, null));
|
||||
|
||||
engine.pushMsg(
|
||||
false, new Context(engine, tsEnv),
|
||||
new Filename("jscript", "ts.js"),
|
||||
Reading.resourceToString("js/ts.js"), null
|
||||
).await();
|
||||
System.out.println("Loaded typescript!");
|
||||
|
||||
var ctx = new Context(engine, bsEnv);
|
||||
|
||||
engine.pushMsg(
|
||||
false, ctx,
|
||||
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
|
||||
tsEnv.global.get(ctx, "ts"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
|
||||
).await();
|
||||
}
|
||||
catch (EngineException e) {
|
||||
Values.printError(e, "(while initializing TS)");
|
||||
}
|
||||
|
||||
var reader = new Thread(() -> {
|
||||
try {
|
||||
for (var arg : args) {
|
||||
try {
|
||||
var file = Path.of(arg);
|
||||
var raw = Files.readString(file);
|
||||
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await());
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, ""); }
|
||||
}
|
||||
for (var i = 0; ; i++) {
|
||||
try {
|
||||
var raw = Reading.read();
|
||||
|
||||
if (raw == null) break;
|
||||
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, ""); }
|
||||
}
|
||||
}
|
||||
catch (IOException e) { exited[0] = true; }
|
||||
catch (SyntaxException ex) {
|
||||
if (exited[0]) return;
|
||||
System.out.println("Syntax error:" + ex.msg);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (!exited[0]) {
|
||||
System.out.println("Internal error ocurred:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (exited[0]) debugTask.interrupt();
|
||||
});
|
||||
reader.setDaemon(true);
|
||||
reader.setName("STD Reader");
|
||||
reader.start();
|
||||
}
|
||||
}
|
||||
|
||||
36
src/me/topchetoeu/jscript/Reading.java
Normal file
36
src/me/topchetoeu/jscript/Reading.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public abstract class AssignStatement extends Statement {
|
||||
public abstract void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue);
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
compile(target, scope, false);
|
||||
}
|
||||
|
||||
protected AssignStatement(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
|
||||
public abstract class AssignableStatement extends Statement {
|
||||
public abstract AssignStatement toAssign(Statement val, Operation operation);
|
||||
public abstract Statement toAssign(Statement val, Operation operation);
|
||||
|
||||
protected AssignableStatement(Location loc) {
|
||||
super(loc);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
public class CompileOptions {
|
||||
public final boolean emitBpMap;
|
||||
public final boolean emitVarNames;
|
||||
|
||||
public CompileOptions(boolean emitBpMap, boolean emitVarNames) {
|
||||
this.emitBpMap = emitBpMap;
|
||||
this.emitVarNames = emitVarNames;
|
||||
}
|
||||
}
|
||||
38
src/me/topchetoeu/jscript/compilation/CompileTarget.java
Normal file
38
src/me/topchetoeu/jscript/compilation/CompileTarget.java
Normal file
@@ -0,0 +1,38 @@
|
||||
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<Instruction> target = new Vector<>();
|
||||
public final Map<Long, FunctionBody> functions;
|
||||
public final TreeSet<Location> breakpoints;
|
||||
|
||||
public Instruction add(Instruction instr) {
|
||||
target.add(instr);
|
||||
return instr;
|
||||
}
|
||||
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);
|
||||
}
|
||||
public int size() { return target.size(); }
|
||||
|
||||
public Instruction[] array() { return target.toArray(Instruction[]::new); }
|
||||
|
||||
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
|
||||
this.functions = functions;
|
||||
this.breakpoints = breakpoints;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.control.ContinueStatement;
|
||||
@@ -12,16 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CompoundStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public Location end;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
@@ -31,28 +21,31 @@ public class CompoundStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) {
|
||||
int start = target.size();
|
||||
((FunctionStatement)stm).compile(target, scope, null, true);
|
||||
target.get(start).setDebug(true);
|
||||
target.add(Instruction.discard());
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var stm = statements[i];
|
||||
|
||||
|
||||
if (stm instanceof FunctionStatement) continue;
|
||||
if (i != statements.length - 1) stm.compileNoPollution(target, scope, true);
|
||||
else stm.compileWithPollution(target, scope);
|
||||
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
|
||||
else stm.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
|
||||
if (end != null) {
|
||||
target.add(Instruction.nop().locate(end));
|
||||
target.setDebug();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var res = new ArrayList<Statement>();
|
||||
var res = new Vector<Statement>(statements.length);
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var stm = statements[i].optimize();
|
||||
@@ -70,6 +63,11 @@ public class CompoundStatement extends Statement {
|
||||
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
|
||||
}
|
||||
|
||||
public CompoundStatement setEnd(Location loc) {
|
||||
this.end = loc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompoundStatement(Location loc, Statement ...statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -10,13 +8,9 @@ public class DiscardStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value == null) return;
|
||||
value.compile(target, scope);
|
||||
if (value.pollutesStack()) target.add(Instruction.discard());
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, false);
|
||||
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
|
||||
17
src/me/topchetoeu/jscript/compilation/FunctionBody.java
Normal file
17
src/me/topchetoeu/jscript/compilation/FunctionBody.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
public class FunctionBody {
|
||||
public final Instruction[] instructions;
|
||||
public final String[] captureNames, localNames;
|
||||
|
||||
public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) {
|
||||
this.instructions = instructions;
|
||||
this.captureNames = captureNames;
|
||||
this.localNames = localNames;
|
||||
}
|
||||
public FunctionBody(Instruction[] instructions) {
|
||||
this.instructions = instructions;
|
||||
this.captureNames = new String[0];
|
||||
this.localNames = new String[0];
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
public class Instruction {
|
||||
public static enum Type {
|
||||
RETURN,
|
||||
SIGNAL,
|
||||
THROW,
|
||||
THROW_SYNTAX,
|
||||
DELETE,
|
||||
@@ -91,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> T get(int i) {
|
||||
@@ -153,21 +147,7 @@ public class Instruction {
|
||||
public static Instruction debug() {
|
||||
return new Instruction(null, Type.NOP, "debug");
|
||||
}
|
||||
public static Instruction debugVarNames(String[] names) {
|
||||
var args = new Object[names.length + 1];
|
||||
args[0] = "dbg_vars";
|
||||
|
||||
System.arraycopy(names, 0, args, 1, names.length);
|
||||
|
||||
return new Instruction(null, Type.NOP, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTENTION: Usage outside of try/catch is broken af
|
||||
*/
|
||||
public static Instruction signal(String name) {
|
||||
return new Instruction(null, Type.SIGNAL, name);
|
||||
}
|
||||
public static Instruction nop(Object ...params) {
|
||||
for (var param : params) {
|
||||
if (param instanceof String) continue;
|
||||
@@ -221,9 +201,9 @@ public class Instruction {
|
||||
public static Instruction loadRegex(String pattern, String flags) {
|
||||
return new Instruction(null, Type.LOAD_REGEX, pattern, flags);
|
||||
}
|
||||
public static Instruction loadFunc(int instrN, int varN, int len, int[] captures) {
|
||||
public static Instruction loadFunc(long id, int varN, int len, int[] captures) {
|
||||
var args = new Object[3 + captures.length];
|
||||
args[0] = instrN;
|
||||
args[0] = id;
|
||||
args[1] = varN;
|
||||
args[2] = len;
|
||||
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
|
||||
@@ -271,8 +251,8 @@ public class Instruction {
|
||||
return new Instruction(null, Type.TYPEOF, varName);
|
||||
}
|
||||
|
||||
public static Instruction keys() {
|
||||
return new Instruction(null, Type.KEYS);
|
||||
public static Instruction keys(boolean forInFormat) {
|
||||
return new Instruction(null, Type.KEYS, forInFormat);
|
||||
}
|
||||
|
||||
public static Instruction defProp() {
|
||||
|
||||
@@ -1,36 +1,20 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public abstract class Statement {
|
||||
private Location _loc;
|
||||
|
||||
public abstract boolean pollutesStack();
|
||||
public boolean pure() { return false; }
|
||||
public abstract void compile(List<Instruction> target, ScopeRecord scope);
|
||||
public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute);
|
||||
public void declare(ScopeRecord varsScope) { }
|
||||
public Statement optimize() { return this; }
|
||||
|
||||
public void compileNoPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
|
||||
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
int start = target.size();
|
||||
compile(target, scope);
|
||||
if (debug && target.size() != start) target.get(start).setDebug(true);
|
||||
if (pollutesStack()) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
public void compileWithPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
|
||||
int start = target.size();
|
||||
compile(target, scope);
|
||||
if (debug && target.size() != start) target.get(start).setDebug(true);
|
||||
if (!pollutesStack()) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
public void compileNoPollution(List<Instruction> target, ScopeRecord scope) {
|
||||
compileNoPollution(target, scope, false);
|
||||
}
|
||||
public void compileWithPollution(List<Instruction> target, ScopeRecord scope) {
|
||||
compileWithPollution(target, scope, false);
|
||||
compile(target, scope, pollute);
|
||||
if (target.size() != start) target.setDebug(start);
|
||||
}
|
||||
|
||||
public Location loc() { return _loc; }
|
||||
|
||||
@@ -10,17 +10,17 @@ public class VariableDeclareStatement extends Statement {
|
||||
public static class Pair {
|
||||
public final String name;
|
||||
public final Statement value;
|
||||
public final Location location;
|
||||
|
||||
public Pair(String name, Statement value) {
|
||||
public Pair(String name, Statement value, Location location) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
|
||||
public final List<Pair> values;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var key : values) {
|
||||
@@ -28,21 +28,27 @@ public class VariableDeclareStatement extends Statement {
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var entry : values) {
|
||||
if (entry.name == null) continue;
|
||||
var key = scope.getKey(entry.name);
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
||||
int start = target.size();
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location));
|
||||
|
||||
if (entry.value instanceof FunctionStatement) {
|
||||
((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
target.add(Instruction.storeVar(key).locate(entry.location));
|
||||
}
|
||||
else if (entry.value != null) {
|
||||
entry.value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
entry.value.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(key).locate(entry.location));
|
||||
}
|
||||
|
||||
if (target.size() != start) target.setDebug(start);
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class ArrayStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadArr(statements.length).locate(loc()));
|
||||
var i = 0;
|
||||
for (var el : statements) {
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.loadValue(i).locate(loc()));
|
||||
el.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayStatement(Location loc, Statement[] statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class ArrayStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadArr(statements.length).locate(loc()));
|
||||
var i = 0;
|
||||
for (var el : statements) {
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.loadValue(i).locate(loc()));
|
||||
el.compile(target, scope, true);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public ArrayStatement(Location loc, Statement[] statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,11 +10,9 @@ public class BreakStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop("break", label).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public BreakStatement(Location loc, String label) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,11 +10,9 @@ public class ContinueStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop("cont", label).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public ContinueStatement(Location loc, String label) {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class DebugStatement extends Statement {
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.debug().locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public DebugStatement(Location loc) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -12,13 +11,12 @@ public class DeleteStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
key.compile(target, scope, true);
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
value.compile(target, scope);
|
||||
key.compile(target, scope);
|
||||
target.add(Instruction.delete().locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
@@ -14,19 +13,16 @@ public class DoWhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int end = target.size();
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
|
||||
@@ -35,13 +31,14 @@ public class DoWhileStatement extends Statement {
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
|
||||
}
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
return;
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, false);
|
||||
int mid = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -13,9 +12,7 @@ public class ForInStatement extends Statement {
|
||||
public final boolean isDeclaration;
|
||||
public final Statement varValue, object, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
public final Location varLocation;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
@@ -24,54 +21,48 @@ public class ForInStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var key = scope.getKey(varName);
|
||||
|
||||
int first = target.size();
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||
|
||||
if (varValue != null) {
|
||||
varValue.compileWithPollution(target, scope);
|
||||
varValue.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(scope.getKey(varName)));
|
||||
}
|
||||
|
||||
object.compileWithPollution(target, scope);
|
||||
target.add(Instruction.keys());
|
||||
|
||||
object.compileWithDebug(target, scope, true);
|
||||
target.add(Instruction.keys(true));
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember("length"));
|
||||
target.add(Instruction.loadValue(0));
|
||||
target.add(Instruction.operation(Operation.LESS_EQUALS));
|
||||
target.add(Instruction.loadValue(null));
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember("length"));
|
||||
target.add(Instruction.loadValue(1));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
target.add(Instruction.dup(1, 2));
|
||||
target.add(Instruction.loadValue("length"));
|
||||
target.add(Instruction.dup(1, 2));
|
||||
target.add(Instruction.storeMember());
|
||||
target.add(Instruction.loadMember());
|
||||
target.add(Instruction.loadMember("value").locate(varLocation));
|
||||
target.setDebug();
|
||||
target.add(Instruction.storeVar(key));
|
||||
|
||||
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
|
||||
|
||||
body.compileNoPollution(target, scope, true);
|
||||
|
||||
body.compileWithDebug(target, scope, false);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc()));
|
||||
target.add(Instruction.jmp(start - end));
|
||||
target.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(null));
|
||||
target.get(first).locate(loc());
|
||||
target.setDebug(first);
|
||||
}
|
||||
|
||||
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
super(loc);
|
||||
this.varLocation = varLocation;
|
||||
this.label = label;
|
||||
this.isDeclaration = isDecl;
|
||||
this.varName = varName;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
@@ -14,44 +13,43 @@ public class ForStatement extends Statement {
|
||||
public final Statement declaration, assignment, condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
declaration.declare(globScope);
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
declaration.compile(target, scope);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
declaration.compile(target, scope, false);
|
||||
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int mid = target.size();
|
||||
assignment.compileNoPollution(target, scope, true);
|
||||
assignment.compileWithDebug(target, scope, false);
|
||||
int end = target.size();
|
||||
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
return;
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int beforeAssign = target.size();
|
||||
assignment.compile(target, scope);
|
||||
assignment.compileWithDebug(target, scope, false);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
@@ -14,9 +13,6 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class IfStatement extends Statement {
|
||||
public final Statement condition, body, elseBody;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
@@ -24,33 +20,34 @@ public class IfStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)condition).value)) {
|
||||
if (elseBody != null) elseBody.compileNoPollution(target, scope, true);
|
||||
if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
else {
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
|
||||
if (elseBody == null) {
|
||||
int i = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
int endI = target.size();
|
||||
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc()));
|
||||
}
|
||||
else {
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
target.add(Instruction.nop());
|
||||
int mid = target.size();
|
||||
elseBody.compileNoPollution(target, scope, true);
|
||||
elseBody.compileWithDebug(target, scope, pollute);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,12 +10,9 @@ public class ReturnStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
else value.compileWithPollution(target, scope);
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.ret().locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
@@ -21,9 +21,6 @@ public class SwitchStatement extends Statement {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
public final Statement value;
|
||||
public final SwitchCase[] cases;
|
||||
public final Statement[] body;
|
||||
@@ -35,18 +32,18 @@ public class SwitchStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var caseMap = new HashMap<Integer, Integer>();
|
||||
var stmIndexMap = new HashMap<Integer, Integer>();
|
||||
|
||||
value.compile(target, scope);
|
||||
value.compile(target, scope, true);
|
||||
|
||||
for (var ccase : cases) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
ccase.value.compileWithPollution(target, scope);
|
||||
ccase.value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
|
||||
caseMap.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.nop().locate(ccase.value.loc()));
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
@@ -55,7 +52,7 @@ public class SwitchStatement extends Statement {
|
||||
|
||||
for (var stm : body) {
|
||||
stmIndexMap.put(stmIndexMap.size(), target.size());
|
||||
stm.compileNoPollution(target, scope, true);
|
||||
stm.compileWithDebug(target, scope, false);
|
||||
}
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
|
||||
@@ -71,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()));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,11 +10,8 @@ public class ThrowStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
value.compileWithPollution(target, scope);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.throwInstr().locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
@@ -15,9 +14,6 @@ public class TryStatement extends Statement {
|
||||
public final Statement finallyBody;
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
tryBody.declare(globScope);
|
||||
@@ -26,30 +22,31 @@ public class TryStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop());
|
||||
|
||||
int start = target.size(), tryN, catchN = -1, finN = -1;
|
||||
|
||||
tryBody.compileNoPollution(target, scope);
|
||||
tryBody.compile(target, scope, false);
|
||||
tryN = target.size() - start;
|
||||
|
||||
if (catchBody != null) {
|
||||
int tmp = target.size();
|
||||
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
||||
local.define(name, true);
|
||||
catchBody.compileNoPollution(target, scope);
|
||||
catchBody.compile(target, scope, false);
|
||||
local.undefine();
|
||||
catchN = target.size() - tmp;
|
||||
}
|
||||
|
||||
if (finallyBody != null) {
|
||||
int tmp = target.size();
|
||||
finallyBody.compileNoPollution(target, scope);
|
||||
finallyBody.compile(target, scope, false);
|
||||
finN = target.size() - tmp;
|
||||
}
|
||||
|
||||
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
@@ -16,19 +15,16 @@ public class WhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int end = target.size();
|
||||
replaceBreaks(target, label, start, end, start, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
@@ -37,10 +33,10 @@ public class WhileStatement extends Statement {
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
@@ -48,6 +44,7 @@ public class WhileStatement extends Statement {
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
@@ -70,7 +67,7 @@ public class WhileStatement extends Statement {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static void replaceBreaks(List<Instruction> target, String label, int start, int end, int continuePoint, int breakPoint) {
|
||||
public static void replaceBreaks(CompileTarget target, String label, int start, int end, int continuePoint, int breakPoint) {
|
||||
for (int i = start; i < end; i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -12,23 +11,20 @@ public class CallStatement extends Statement {
|
||||
public final Statement[] args;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (func instanceof IndexStatement) {
|
||||
((IndexStatement)func).compile(target, scope, true);
|
||||
((IndexStatement)func).compile(target, scope, true, true);
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
func.compileWithPollution(target, scope);
|
||||
func.compile(target, scope, true);
|
||||
}
|
||||
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
}
|
||||
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()));
|
||||
}
|
||||
|
||||
public CallStatement(Location loc, Statement func, Statement ...args) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -15,11 +14,13 @@ public class ChangeStatement extends Statement {
|
||||
public final boolean postfix;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
else if (postfix) {
|
||||
target.add(Instruction.loadValue(addAmount));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
}
|
||||
}
|
||||
|
||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CommaStatement extends Statement {
|
||||
public final Statement first;
|
||||
public final Statement second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return first.pure() && second.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
first.compileNoPollution(target, scope);
|
||||
second.compileWithPollution(target, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var f = first.optimize();
|
||||
var s = second.optimize();
|
||||
if (f.pure()) return s;
|
||||
else return new CommaStatement(loc(), f, s);
|
||||
}
|
||||
|
||||
public CommaStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CommaStatement extends Statement {
|
||||
public final Statement[] values;
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
values[i].compile(target, scope, i == values.length - 1 && pollute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var res = new Vector<Statement>(values.length);
|
||||
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var stm = values[i].optimize();
|
||||
if (i < values.length - 1 && stm.pure()) continue;
|
||||
res.add(stm);
|
||||
}
|
||||
|
||||
if (res.size() == 1) return res.get(0);
|
||||
else return new CommaStatement(loc(), res.toArray(Statement[]::new));
|
||||
}
|
||||
|
||||
public CommaStatement(Location loc, Statement ...args) {
|
||||
super(loc);
|
||||
this.values = args;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -10,14 +9,12 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class ConstantStatement extends Statement {
|
||||
public final Object value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadValue(value).locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadValue(value).locate(loc()));
|
||||
}
|
||||
|
||||
public ConstantStatement(Location loc, Object val) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
@@ -15,17 +17,17 @@ public class FunctionStatement extends Statement {
|
||||
public final String name;
|
||||
public final String[] args;
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
@Override
|
||||
public boolean pure() { return name == null; }
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord scope) {
|
||||
if (name != null) scope.define(name);
|
||||
}
|
||||
|
||||
public static void checkBreakAndCont(List<Instruction> target, int start) {
|
||||
public static void checkBreakAndCont(CompileTarget target, int start) {
|
||||
for (int i = start; i < target.size(); i++) {
|
||||
if (target.get(i).type == Type.NOP) {
|
||||
if (target.get(i).is(0, "break") ) {
|
||||
@@ -38,7 +40,7 @@ public class FunctionStatement extends Statement {
|
||||
}
|
||||
}
|
||||
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, String name, boolean isStatement) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, String name, boolean isStatement) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
for (var j = 0; j < i; j++) {
|
||||
if (args[i].equals(args[j])){
|
||||
@@ -50,32 +52,32 @@ public class FunctionStatement extends Statement {
|
||||
var subscope = scope.child();
|
||||
|
||||
int start = target.size();
|
||||
var funcTarget = new CompileTarget(target.functions, target.breakpoints);
|
||||
|
||||
target.add(Instruction.nop());
|
||||
subscope.define("this");
|
||||
var argsVar = subscope.define("arguments");
|
||||
|
||||
if (args.length > 0) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
target.add(Instruction.loadVar(argsVar).locate(loc()));
|
||||
target.add(Instruction.loadMember(i).locate(loc()));
|
||||
target.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
|
||||
funcTarget.add(Instruction.loadVar(argsVar).locate(loc()));
|
||||
funcTarget.add(Instruction.loadMember(i).locate(loc()));
|
||||
funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStatement && this.name != null) {
|
||||
target.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
|
||||
funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
|
||||
}
|
||||
|
||||
body.declare(subscope);
|
||||
target.add(Instruction.debugVarNames(subscope.locals()));
|
||||
body.compile(target, subscope);
|
||||
body.compile(funcTarget, subscope, false);
|
||||
funcTarget.add(Instruction.ret().locate(loc()));
|
||||
checkBreakAndCont(funcTarget, start);
|
||||
|
||||
checkBreakAndCont(target, start);
|
||||
var id = rand.nextLong();
|
||||
|
||||
if (!(body instanceof CompoundStatement)) target.add(Instruction.ret().locate(loc()));
|
||||
|
||||
target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
||||
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
||||
target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals()));
|
||||
|
||||
if (name == null) name = this.name;
|
||||
|
||||
@@ -90,12 +92,14 @@ public class FunctionStatement extends Statement {
|
||||
var key = scope.getKey(this.name);
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
||||
target.add(Instruction.storeVar(scope.getKey(this.name), true).locate(loc()));
|
||||
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, null, false);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class GlobalThisStatement extends Statement {
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadGlob().locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadGlob().locate(loc()));
|
||||
}
|
||||
|
||||
public GlobalThisStatement(Location loc) {
|
||||
|
||||
@@ -1,51 +1,40 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class IndexAssignStatement extends AssignStatement {
|
||||
public class IndexAssignStatement extends Statement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue) {
|
||||
int start = 0;
|
||||
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (operation != null) {
|
||||
object.compileWithPollution(target, scope);
|
||||
index.compileWithPollution(target, scope);
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.dup(2, 0).locate(loc()));
|
||||
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
if (retPrevValue) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.move(3, 1).locate(loc()));
|
||||
}
|
||||
value.compileWithPollution(target, scope);
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
|
||||
target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true));
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()));
|
||||
target.setDebug();
|
||||
}
|
||||
else {
|
||||
object.compileWithPollution(target, scope);
|
||||
if (retPrevValue) target.add(Instruction.dup().locate(loc()));
|
||||
index.compileWithPollution(target, scope);
|
||||
value.compileWithPollution(target, scope);
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
value.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true));
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()));
|
||||
target.setDebug();
|
||||
}
|
||||
target.get(start);
|
||||
}
|
||||
|
||||
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -14,31 +12,30 @@ public class IndexStatement extends AssignableStatement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public AssignStatement toAssign(Statement val, Operation operation) {
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
||||
}
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, boolean dupObj) {
|
||||
int start = 0;
|
||||
object.compileWithPollution(target, scope);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
||||
object.compile(target, scope, true);
|
||||
if (dupObj) target.add(Instruction.dup().locate(loc()));
|
||||
if (index instanceof ConstantStatement) {
|
||||
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
||||
target.setDebug();
|
||||
return;
|
||||
}
|
||||
|
||||
index.compileWithPollution(target, scope);
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
target.get(start).setDebug(true);
|
||||
target.setDebug();
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
compile(target, scope, false);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, false, pollute);
|
||||
}
|
||||
|
||||
public IndexStatement(Location loc, Statement object, Statement index) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,29 +10,27 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class LazyAndStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
first.compileWithPollution(target, scope);
|
||||
first.compile(target, scope, pollute);
|
||||
}
|
||||
else second.compileWithPollution(target, scope);
|
||||
else second.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compileWithPollution(target, scope);
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compileWithPollution(target, scope);
|
||||
if (pollute) target.add(Instruction.discard().locate(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,29 +10,27 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class LazyOrStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
second.compileWithPollution(target, scope);
|
||||
second.compile(target, scope, pollute);
|
||||
}
|
||||
else first.compileWithPollution(target, scope);
|
||||
else first.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compileWithPollution(target, scope);
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compileWithPollution(target, scope);
|
||||
if (pollute) target.add(Instruction.discard().locate(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -12,16 +11,13 @@ public class NewStatement extends Statement {
|
||||
public final Statement[] args;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
func.compile(target, scope, true);
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
func.compileWithPollution(target, scope);
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -15,10 +15,7 @@ public class ObjectStatement extends Statement {
|
||||
public final Map<Object, FunctionStatement> setters;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadObj().locate(loc()));
|
||||
|
||||
for (var el : map.entrySet()) {
|
||||
@@ -26,7 +23,7 @@ public class ObjectStatement extends Statement {
|
||||
target.add(Instruction.loadValue(el.getKey()).locate(loc()));
|
||||
var val = el.getValue();
|
||||
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);
|
||||
else val.compileWithPollution(target, scope);
|
||||
else val.compile(target, scope, true);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
|
||||
@@ -38,14 +35,16 @@ public class ObjectStatement extends Statement {
|
||||
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc()));
|
||||
else target.add(Instruction.loadValue((Double)key).locate(loc()));
|
||||
|
||||
if (getters.containsKey(key)) getters.get(key).compileWithPollution(target, scope);
|
||||
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
|
||||
if (setters.containsKey(key)) setters.get(key).compileWithPollution(target, scope);
|
||||
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
|
||||
target.add(Instruction.defProp().locate(loc()));
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
|
||||
@@ -16,15 +15,15 @@ public class OperationStatement extends Statement {
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
arg.compile(target, scope, true);
|
||||
}
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
|
||||
if (pollute) target.add(Instruction.operation(operation).locate(loc()));
|
||||
else target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
for (var arg : args) {
|
||||
@@ -50,13 +49,8 @@ public class OperationStatement extends Statement {
|
||||
vals[i] = ((ConstantStatement)args[i]).value;
|
||||
}
|
||||
|
||||
try {
|
||||
return new ConstantStatement(loc(), Values.operation(null, operation, vals));
|
||||
}
|
||||
catch (EngineException e) {
|
||||
return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value));
|
||||
}
|
||||
catch (InterruptedException e) { return null; }
|
||||
try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); }
|
||||
catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); }
|
||||
}
|
||||
|
||||
return new OperationStatement(loc(), operation, args);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -10,14 +9,13 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class RegexStatement extends Statement {
|
||||
public final String pattern, flags;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadRegex(pattern, flags).locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public RegexStatement(Location loc, String pattern, String flags) {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class TernaryStatement extends Statement {
|
||||
public final Statement condition;
|
||||
public final Statement first;
|
||||
public final Statement second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (!Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
second.compileWithPollution(target, scope);
|
||||
}
|
||||
else first.compileWithPollution(target, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
condition.compileWithPollution(target, scope);
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
first.compileWithPollution(target, scope);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
second.compileWithPollution(target, scope);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(mid - start + 1).locate(loc()));
|
||||
target.set(mid, Instruction.jmp(end - mid).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var f = first.optimize();
|
||||
var s = second.optimize();
|
||||
return new TernaryStatement(loc(), cond, f, s);
|
||||
}
|
||||
|
||||
public TernaryStatement(Location loc, Statement condition, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.condition = condition;
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.control.ArrayStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class TypeofStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value instanceof VariableStatement) {
|
||||
var i = scope.getKey(((VariableStatement)value).name);
|
||||
if (i instanceof String) {
|
||||
@@ -25,7 +23,7 @@ public class TypeofStatement extends Statement {
|
||||
return;
|
||||
}
|
||||
}
|
||||
value.compileWithPollution(target, scope);
|
||||
value.compile(target, scope, pollute);
|
||||
target.add(Instruction.typeof().locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,31 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableAssignStatement extends AssignStatement {
|
||||
public class VariableAssignStatement extends Statement {
|
||||
public final String name;
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
if (operation != null) {
|
||||
target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (retPrevValue) target.add(Instruction.dup().locate(loc()));
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compileWithPollution(target, scope);
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
target.add(Instruction.storeVar(i, !retPrevValue).locate(loc()));
|
||||
target.add(Instruction.storeVar(i, pollute).locate(loc()));
|
||||
}
|
||||
else {
|
||||
if (retPrevValue) target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeVar(i, !retPrevValue).locate(loc()));
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(i, pollute).locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableIndexStatement extends Statement {
|
||||
public final int index;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadVar(index).locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadVar(index).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableIndexStatement(Location loc, int i) {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -13,20 +11,19 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class VariableStatement extends AssignableStatement {
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public AssignStatement toAssign(Statement val, Operation operation) {
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new VariableAssignStatement(loc(), name, val, operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public VariableStatement(Location loc, String name) {
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
|
||||
public class VoidStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value != null) value.compileNoPollution(target, scope);
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value != null) value.compile(target, scope, false);
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,27 +1,64 @@
|
||||
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.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
|
||||
public class Context {
|
||||
public final Environment env;
|
||||
public final Message message;
|
||||
private final Stack<Environment> env = new Stack<>();
|
||||
public final Data data;
|
||||
public final Engine engine;
|
||||
|
||||
public FunctionValue compile(String filename, String raw) throws InterruptedException {
|
||||
var res = Values.toString(this, env.compile.call(this, null, raw, filename));
|
||||
return Parsing.compile(env, filename, res);
|
||||
public Environment environment() {
|
||||
return env.empty() ? null : env.peek();
|
||||
}
|
||||
|
||||
public Context setEnv(Environment env) {
|
||||
return new Context(env, message);
|
||||
public Context pushEnv(Environment env) {
|
||||
this.env.push(env);
|
||||
return this;
|
||||
}
|
||||
public Context setMsg(Message msg) {
|
||||
return new Context(env, msg);
|
||||
public void popEnv() {
|
||||
if (!env.empty()) this.env.pop();
|
||||
}
|
||||
|
||||
public Context(Environment env, Message msg) {
|
||||
this.env = env;
|
||||
this.message = msg;
|
||||
public FunctionValue compile(Filename filename, String raw) {
|
||||
var transpiled = environment().compile.call(this, null, raw, filename.toString());
|
||||
String source = null;
|
||||
FunctionValue runner = null;
|
||||
|
||||
if (transpiled instanceof ObjectValue) {
|
||||
source = Values.toString(this, Values.getMember(this, transpiled, "source"));
|
||||
var _runner = Values.getMember(this, transpiled, "runner");
|
||||
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner;
|
||||
}
|
||||
else source = Values.toString(this, transpiled);
|
||||
|
||||
var breakpoints = new TreeSet<Location>();
|
||||
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source);
|
||||
engine.onSource(filename, source, breakpoints);
|
||||
|
||||
if (runner != null) res = (FunctionValue)runner.call(this, null, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context(Engine engine, Data data) {
|
||||
this.data = new Data(engine.data);
|
||||
if (data != null) this.data.addAll(data);
|
||||
this.engine = engine;
|
||||
}
|
||||
public Context(Engine engine) {
|
||||
this(engine, (Data)null);
|
||||
}
|
||||
public Context(Engine engine, Environment env) {
|
||||
this(engine, (Data)null);
|
||||
this.pushEnv(env);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,52 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Data implements Iterable<Entry<DataKey<?>, ?>> {
|
||||
public class Data {
|
||||
public final Data parent;
|
||||
private HashMap<DataKey<Object>, Object> data = new HashMap<>();
|
||||
|
||||
public Data copy() {
|
||||
return new Data().addAll(this);
|
||||
}
|
||||
|
||||
public Data addAll(Iterable<Entry<DataKey<?>, ?>> data) {
|
||||
for (var el : data) {
|
||||
add((DataKey<Object>)el.getKey(), (Object)el.getValue());
|
||||
public Data addAll(Map<DataKey<?>, ?> data) {
|
||||
for (var el : data.entrySet()) {
|
||||
get((DataKey<Object>)el.getKey(), (Object)el.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public Data addAll(Data data) {
|
||||
for (var el : data.data.entrySet()) {
|
||||
get((DataKey<Object>)el.getKey(), (Object)el.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> T remove(DataKey<T> key) {
|
||||
return (T)data.remove(key);
|
||||
}
|
||||
public <T> Data set(DataKey<T> key, T val) {
|
||||
if (val == null) data.remove(key);
|
||||
else data.put((DataKey<Object>)key, (Object)val);
|
||||
data.put((DataKey<Object>)key, (Object)val);
|
||||
return this;
|
||||
}
|
||||
public <T> T add(DataKey<T> key, T val) {
|
||||
if (data.containsKey(key)) return (T)data.get(key);
|
||||
else {
|
||||
if (val == null) data.remove(key);
|
||||
else data.put((DataKey<Object>)key, (Object)val);
|
||||
return val;
|
||||
public <T> T get(DataKey<T> key, T val) {
|
||||
for (var it = this; it != null; it = it.parent) {
|
||||
if (it.data.containsKey(key)) {
|
||||
return (T)it.data.get((DataKey<Object>)key);
|
||||
}
|
||||
}
|
||||
|
||||
set(key, val);
|
||||
return val;
|
||||
}
|
||||
public <T> T get(DataKey<T> key) {
|
||||
return get(key, null);
|
||||
}
|
||||
public <T> T get(DataKey<T> key, T defaultVal) {
|
||||
if (!has(key)) return defaultVal;
|
||||
else return (T)data.get(key);
|
||||
for (var it = this; it != null; it = it.parent) {
|
||||
if (it.data.containsKey(key)) return (T)it.data.get((DataKey<Object>)key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public boolean has(DataKey<?> key) { return data.containsKey(key); }
|
||||
|
||||
@@ -53,8 +62,10 @@ public class Data implements Iterable<Entry<DataKey<?>, ?>> {
|
||||
return increase(key, 1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<DataKey<?>, ?>> iterator() {
|
||||
return (Iterator<Entry<DataKey<?>, ?>>)data.entrySet();
|
||||
public Data() {
|
||||
this.parent = null;
|
||||
}
|
||||
public Data(Data parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugController;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.events.Awaitable;
|
||||
import me.topchetoeu.jscript.events.DataNotifier;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class Engine {
|
||||
public class Engine implements DebugController {
|
||||
private class UncompiledFunction extends FunctionValue {
|
||||
public final String filename;
|
||||
public final Filename filename;
|
||||
public final String raw;
|
||||
public final Environment env;
|
||||
private FunctionValue compiled = null;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
ctx = ctx.setEnv(env);
|
||||
return ctx.compile(filename, raw).call(ctx, thisArg, args);
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
if (compiled == null) compiled = ctx.compile(filename, raw);
|
||||
return compiled.call(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public UncompiledFunction(Environment env, String filename, String raw) {
|
||||
super(filename, 0);
|
||||
public UncompiledFunction(Filename filename, String raw) {
|
||||
super(filename + "", 0);
|
||||
this.filename = filename;
|
||||
this.raw = raw;
|
||||
this.env = env;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +40,10 @@ public class Engine {
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final Message msg;
|
||||
public final Context ctx;
|
||||
|
||||
public Task(Message ctx, FunctionValue func, Object thisArg, Object[] args) {
|
||||
this.msg = ctx;
|
||||
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) {
|
||||
this.ctx = ctx;
|
||||
this.func = func;
|
||||
this.thisArg = thisArg;
|
||||
this.args = args;
|
||||
@@ -43,31 +51,60 @@ public class Engine {
|
||||
}
|
||||
|
||||
private static int nextId = 0;
|
||||
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
||||
|
||||
private Thread thread;
|
||||
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
|
||||
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
||||
|
||||
public final int id = ++nextId;
|
||||
public final Data data = new Data().set(StackData.MAX_FRAMES, 10000);
|
||||
public final boolean debugging;
|
||||
private final HashMap<Filename, String> sources = new HashMap<>();
|
||||
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
|
||||
private DebugController debugger;
|
||||
|
||||
private void runTask(Task task) throws InterruptedException {
|
||||
public boolean attachDebugger(DebugController debugger) {
|
||||
if (!debugging || this.debugger != null) return false;
|
||||
|
||||
for (var source : sources.entrySet()) {
|
||||
debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey()));
|
||||
}
|
||||
|
||||
this.debugger = debugger;
|
||||
return true;
|
||||
}
|
||||
public boolean detachDebugger() {
|
||||
if (!debugging || this.debugger == null) return false;
|
||||
this.debugger = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
|
||||
}
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||
else return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) {
|
||||
if (!debugging) return;
|
||||
if (debugger != null) debugger.onSource(filename, source, breakpoints);
|
||||
sources.put(filename, source);
|
||||
bpts.put(filename, breakpoints);
|
||||
}
|
||||
|
||||
private void runTask(Task task) {
|
||||
try {
|
||||
task.notifier.next(task.func.call(task.msg.context(null), task.thisArg, task.args));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
task.notifier.error(new RuntimeException(e));
|
||||
throw e;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
task.notifier.error(e);
|
||||
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);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private void run() {
|
||||
while (true) {
|
||||
public void run(boolean untilEmpty) {
|
||||
while (!untilEmpty || !macroTasks.isEmpty()) {
|
||||
try {
|
||||
runTask(macroTasks.take());
|
||||
|
||||
@@ -75,9 +112,9 @@ public class Engine {
|
||||
runTask(microTasks.take());
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
catch (InterruptedException | InterruptException e) {
|
||||
for (var msg : macroTasks) {
|
||||
msg.notifier.error(new RuntimeException(e));
|
||||
msg.notifier.error(new InterruptException(e));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -86,7 +123,7 @@ public class Engine {
|
||||
|
||||
public Thread start() {
|
||||
if (this.thread == null) {
|
||||
this.thread = new Thread(this::run, "JavaScript Runner #" + id);
|
||||
this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
|
||||
this.thread.start();
|
||||
}
|
||||
return this.thread;
|
||||
@@ -102,17 +139,17 @@ public class Engine {
|
||||
return this.thread != null;
|
||||
}
|
||||
|
||||
public Awaitable<Object> pushMsg(boolean micro, Message ctx, FunctionValue func, Object thisArg, Object ...args) {
|
||||
var msg = new Task(ctx, func, thisArg, args);
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
|
||||
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args);
|
||||
if (micro) microTasks.addLast(msg);
|
||||
else macroTasks.addLast(msg);
|
||||
return msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args);
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
|
||||
}
|
||||
|
||||
// public Engine() {
|
||||
// this.typeRegister = new NativeTypeRegister();
|
||||
// }
|
||||
public Engine(boolean debugging) {
|
||||
this.debugging = debugging;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ public class Environment {
|
||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||
|
||||
public final Data data = new Data();
|
||||
public final HashMap<String, Symbol> symbols = new HashMap<>();
|
||||
public static final HashMap<String, Symbol> symbols = new HashMap<>();
|
||||
|
||||
public GlobalScope global;
|
||||
public WrappersProvider wrappersProvider;
|
||||
public WrappersProvider wrappers;
|
||||
|
||||
@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) {
|
||||
@@ -40,8 +40,7 @@ public class Environment {
|
||||
}
|
||||
|
||||
@Native public Symbol symbol(String name) {
|
||||
if (symbols.containsKey(name))
|
||||
return symbols.get(name);
|
||||
if (symbols.containsKey(name)) return symbols.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
symbols.put(name, res);
|
||||
@@ -57,7 +56,8 @@ public class Environment {
|
||||
}
|
||||
|
||||
@Native public Environment fork() {
|
||||
var res = new Environment(compile, wrappersProvider, global);
|
||||
var res = new Environment(compile, null, global);
|
||||
res.wrappers = wrappers.fork(res);
|
||||
res.regexConstructor = regexConstructor;
|
||||
res.prototypes = new HashMap<>(prototypes);
|
||||
return res;
|
||||
@@ -68,8 +68,11 @@ public class Environment {
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context context(Message msg) {
|
||||
return new Context(this, msg);
|
||||
public Context context(Engine engine, Data data) {
|
||||
return new Context(engine, data).pushEnv(this);
|
||||
}
|
||||
public Context context(Engine engine) {
|
||||
return new Context(engine).pushEnv(this);
|
||||
}
|
||||
|
||||
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
|
||||
@@ -77,7 +80,7 @@ public class Environment {
|
||||
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
|
||||
if (global == null) global = new GlobalScope();
|
||||
|
||||
this.wrappersProvider = nativeConverter;
|
||||
this.wrappers = nativeConverter;
|
||||
this.compile = compile;
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class Message {
|
||||
public final Engine engine;
|
||||
|
||||
private final ArrayList<CodeFrame> frames = new ArrayList<>();
|
||||
public int maxStackFrames = 1000;
|
||||
|
||||
public final Data data = new Data();
|
||||
|
||||
public List<CodeFrame> frames() { return Collections.unmodifiableList(frames); }
|
||||
|
||||
public Message addData(Data data) {
|
||||
this.data.addAll(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message pushFrame(Context ctx, CodeFrame frame) throws InterruptedException {
|
||||
this.frames.add(frame);
|
||||
if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!");
|
||||
return this;
|
||||
}
|
||||
public boolean popFrame(CodeFrame frame) {
|
||||
if (this.frames.size() == 0) return false;
|
||||
if (this.frames.get(this.frames.size() - 1) != frame) return false;
|
||||
this.frames.remove(this.frames.size() - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<String> stackTrace() {
|
||||
var res = new ArrayList<String>();
|
||||
|
||||
for (var el : frames) {
|
||||
var name = el.function.name;
|
||||
var loc = el.function.loc();
|
||||
var trace = "";
|
||||
|
||||
if (loc != null) trace += "at " + loc.toString() + " ";
|
||||
if (name != null && !name.equals("")) trace += "in " + name + " ";
|
||||
|
||||
trace = trace.trim();
|
||||
|
||||
if (!res.equals("")) res.add(trace);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context context(Environment env) {
|
||||
return new Context(env, this);
|
||||
}
|
||||
|
||||
public Message(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
}
|
||||
66
src/me/topchetoeu/jscript/engine/StackData.java
Normal file
66
src/me/topchetoeu/jscript/engine/StackData.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
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<ArrayList<CodeFrame>> FRAMES = new DataKey<>();
|
||||
public static final DataKey<Integer> MAX_FRAMES = new DataKey<>();
|
||||
public static final DataKey<Debugger> DEBUGGER = new DataKey<>();
|
||||
|
||||
public static void pushFrame(Context ctx, CodeFrame frame) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
frames.add(frame);
|
||||
if (frames.size() > ctx.data.get(MAX_FRAMES, 10000))
|
||||
throw EngineException.ofRange("Stack overflow!");
|
||||
ctx.pushEnv(frame.function.environment);
|
||||
}
|
||||
public static boolean popFrame(Context ctx, CodeFrame frame) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
if (frames.size() == 0) return false;
|
||||
if (frames.get(frames.size() - 1) != frame) return false;
|
||||
frames.remove(frames.size() - 1);
|
||||
ctx.popEnv();
|
||||
ctx.engine.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<CodeFrame> frames(Context ctx) {
|
||||
return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>()));
|
||||
}
|
||||
public static List<String> stackTrace(Context ctx) {
|
||||
var res = new ArrayList<String>();
|
||||
var frames = frames(ctx);
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
var el = frames.get(i);
|
||||
var name = el.function.name;
|
||||
Location loc = null;
|
||||
|
||||
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
|
||||
if (loc == null) loc = el.function.loc();
|
||||
|
||||
var trace = "";
|
||||
|
||||
if (loc != null) trace += "at " + loc.toString() + " ";
|
||||
if (name != null && !name.equals("")) trace += "in " + name + " ";
|
||||
|
||||
trace = trace.trim();
|
||||
|
||||
if (!trace.equals("")) res.add(trace);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,8 @@ import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
|
||||
public interface WrappersProvider {
|
||||
public ObjectValue getProto(Class<?> obj);
|
||||
public ObjectValue getNamespace(Class<?> obj);
|
||||
public FunctionValue getConstr(Class<?> obj);
|
||||
|
||||
public WrappersProvider fork(Environment env);
|
||||
}
|
||||
|
||||
40
src/me/topchetoeu/jscript/engine/debug/DebugController.java
Normal file
40
src/me/topchetoeu/jscript/engine/debug/DebugController.java
Normal file
@@ -0,0 +1,40 @@
|
||||
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<Location> 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);
|
||||
}
|
||||
35
src/me/topchetoeu/jscript/engine/debug/DebugHandler.java
Normal file
35
src/me/topchetoeu/jscript/engine/debug/DebugHandler.java
Normal file
@@ -0,0 +1,35 @@
|
||||
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);
|
||||
|
||||
void evaluateOnCallFrame(V8Message msg);
|
||||
|
||||
void getProperties(V8Message msg);
|
||||
void releaseObjectGroup(V8Message msg);
|
||||
void releaseObject(V8Message msg);
|
||||
/**
|
||||
* This method might not execute the actual code for well-known requests
|
||||
*/
|
||||
void callFunctionOn(V8Message msg);
|
||||
|
||||
void runtimeEnable(V8Message msg);
|
||||
}
|
||||
246
src/me/topchetoeu/jscript/engine/debug/DebugServer.java
Normal file
246
src/me/topchetoeu/jscript/engine/debug/DebugServer.java
Normal file
@@ -0,0 +1,246 @@
|
||||
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.Metadata;
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||
import me.topchetoeu.jscript.events.Notifier;
|
||||
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 = Metadata.NAME + "/" + Metadata.VERSION;
|
||||
|
||||
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
|
||||
|
||||
private final byte[] favicon, index, protocol;
|
||||
private final Notifier connNotifier = new Notifier();
|
||||
|
||||
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());
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
ws.send(new V8Error(e.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (msg.name) {
|
||||
case "Debugger.enable":
|
||||
connNotifier.next();
|
||||
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;
|
||||
case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue;
|
||||
|
||||
case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue;
|
||||
case "Runtime.releaseObject": debugger.releaseObject(msg); continue;
|
||||
case "Runtime.getProperties": debugger.getProperties(msg); continue;
|
||||
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
|
||||
// case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue;
|
||||
case "Runtime.enable": debugger.runtimeEnable(msg); continue;
|
||||
}
|
||||
|
||||
if (
|
||||
msg.name.startsWith("DOM.") ||
|
||||
msg.name.startsWith("DOMDebugger.") ||
|
||||
msg.name.startsWith("Emulation.") ||
|
||||
msg.name.startsWith("Input.") ||
|
||||
msg.name.startsWith("Network.") ||
|
||||
msg.name.startsWith("Page.")
|
||||
) ws.send(new V8Error("This isn't a browser..."));
|
||||
else ws.send(new V8Error("This API is not supported yet."));
|
||||
}
|
||||
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 awaitConnection() {
|
||||
connNotifier.await();
|
||||
}
|
||||
|
||||
public void run(InetSocketAddress address) {
|
||||
try {
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(address);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var socket = server.accept();
|
||||
var req = HttpRequest.read(socket);
|
||||
|
||||
if (req == null) continue;
|
||||
|
||||
switch (req.path) {
|
||||
case "/json/version":
|
||||
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
|
||||
break;
|
||||
case "/json/list":
|
||||
case "/json": {
|
||||
var res = new JSONList();
|
||||
|
||||
for (var el : targets.entrySet()) {
|
||||
res.add(new JSONMap()
|
||||
.set("description", "JScript debugger")
|
||||
.set("favicon", "/favicon.ico")
|
||||
.set("id", el.getKey())
|
||||
.set("type", "node")
|
||||
.set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey())
|
||||
);
|
||||
}
|
||||
send(req, JSON.stringify(res));
|
||||
break;
|
||||
}
|
||||
case "/json/protocol":
|
||||
req.writeResponse(200, "OK", "application/json", protocol);
|
||||
break;
|
||||
case "/json/new":
|
||||
case "/json/activate":
|
||||
case "/json/close":
|
||||
case "/devtools/inspector.html":
|
||||
req.writeResponse(
|
||||
501, "Not Implemented", "text/txt",
|
||||
"This feature isn't (and probably won't be) implemented.".getBytes()
|
||||
);
|
||||
break;
|
||||
case "/":
|
||||
case "/index.html":
|
||||
req.writeResponse(200, "OK", "text/html", index);
|
||||
break;
|
||||
case "/favicon.ico":
|
||||
req.writeResponse(200, "OK", "image/png", favicon);
|
||||
break;
|
||||
default:
|
||||
if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) {
|
||||
onWsConnect(req, socket, targets.get(req.path.substring(1)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally { server.close(); }
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
}
|
||||
|
||||
public Thread start(InetSocketAddress address, boolean daemon) {
|
||||
var res = new Thread(() -> run(address), "Debug Server");
|
||||
res.setDaemon(daemon);
|
||||
res.start();
|
||||
return res;
|
||||
}
|
||||
|
||||
public DebugServer() {
|
||||
try {
|
||||
this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes();
|
||||
this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes();
|
||||
var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes());
|
||||
this.index = index
|
||||
.replace("${NAME}", Metadata.NAME)
|
||||
.replace("${VERSION}", Metadata.VERSION)
|
||||
.replace("${AUTHOR}", Metadata.AUTHOR)
|
||||
.getBytes();
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
}
|
||||
}
|
||||
6
src/me/topchetoeu/jscript/engine/debug/Debugger.java
Normal file
6
src/me/topchetoeu/jscript/engine/debug/Debugger.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
public interface Debugger extends DebugHandler, DebugController {
|
||||
void connect();
|
||||
void disconnect();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
public interface DebuggerProvider {
|
||||
Debugger getDebugger(WebSocket socket, HttpRequest req);
|
||||
}
|
||||
102
src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
Normal file
102
src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
Normal file
@@ -0,0 +1,102 @@
|
||||
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;
|
||||
|
||||
public class HttpRequest {
|
||||
public final String method;
|
||||
public final String path;
|
||||
public final Map<String, String> headers;
|
||||
public final OutputStream out;
|
||||
|
||||
|
||||
public void writeCode(int code, String name) {
|
||||
try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); }
|
||||
catch (IOException e) { }
|
||||
}
|
||||
public void writeHeader(String name, String value) {
|
||||
try { out.write((name + ": " + value + "\r\n").getBytes()); }
|
||||
catch (IOException e) { }
|
||||
}
|
||||
public void writeLastHeader(String name, String value) {
|
||||
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
|
||||
catch (IOException e) { }
|
||||
}
|
||||
public void writeHeadersEnd() {
|
||||
try { out.write("\n".getBytes()); }
|
||||
catch (IOException e) { }
|
||||
}
|
||||
|
||||
public void writeResponse(int code, String name, String type, byte[] data) {
|
||||
writeCode(code, name);
|
||||
writeHeader("Content-Type", type);
|
||||
writeLastHeader("Content-Length", data.length + "");
|
||||
try {
|
||||
out.write(data);
|
||||
out.close();
|
||||
}
|
||||
catch (IOException e) { }
|
||||
}
|
||||
public void writeResponse(int code, String name, String type, InputStream data) {
|
||||
try {
|
||||
writeResponse(code, name, type, data.readAllBytes());
|
||||
}
|
||||
catch (IOException e) { }
|
||||
}
|
||||
|
||||
public HttpRequest(String method, String path, Map<String, String> headers, OutputStream out) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.headers = headers;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
// We dont need no http library
|
||||
public static HttpRequest read(Socket socket) {
|
||||
try {
|
||||
var str = socket.getInputStream();
|
||||
var lines = new BufferedReader(new InputStreamReader(str));
|
||||
var line = lines.readLine();
|
||||
var i1 = line.indexOf(" ");
|
||||
var i2 = line.indexOf(" ", i1 + 1);
|
||||
|
||||
if (i1 < 0 || i2 < 0) {
|
||||
socket.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
var method = line.substring(0, i1).trim().toUpperCase();
|
||||
var path = line.substring(i1 + 1, i2).trim();
|
||||
var headers = new HashMap<String, String>();
|
||||
|
||||
while (!(line = lines.readLine()).isEmpty()) {
|
||||
var i = line.indexOf(":");
|
||||
if (i < 0) continue;
|
||||
var name = line.substring(0, i).trim().toLowerCase();
|
||||
var value = line.substring(i + 1).trim();
|
||||
|
||||
if (name.length() == 0) continue;
|
||||
headers.put(name, value);
|
||||
}
|
||||
|
||||
if (headers.containsKey("content-length")) {
|
||||
try {
|
||||
var i = Integer.parseInt(headers.get("content-length"));
|
||||
str.skip(i);
|
||||
}
|
||||
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
|
||||
}
|
||||
|
||||
return new HttpRequest(method, path, headers, socket.getOutputStream());
|
||||
}
|
||||
catch (IOException | NullPointerException e) { return null; }
|
||||
}
|
||||
}
|
||||
|
||||
812
src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java
Normal file
812
src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java
Normal file
@@ -0,0 +1,812 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
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.scope.GlobalScope;
|
||||
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.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONElement;
|
||||
import me.topchetoeu.jscript.json.JSONList;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class SimpleDebugger implements Debugger {
|
||||
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
|
||||
public static final String VSCODE_STRINGIFY_VAL = "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}";
|
||||
public static final String VSCODE_STRINGIFY_PROPS = "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}";
|
||||
public static final String VSCODE_SYMBOL_REQUEST = "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}";
|
||||
public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}";
|
||||
public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}";
|
||||
public static final String VSCODE_CALL = "function(t){return t.call(this)\n}";
|
||||
|
||||
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<Location> breakpoints;
|
||||
|
||||
public Source(int id, Filename filename, String source, TreeSet<Location> 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<Breakpoint> 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;
|
||||
if (condition != null && condition.trim().equals("")) condition = null;
|
||||
this.condition = condition;
|
||||
}
|
||||
}
|
||||
private class Frame {
|
||||
public CodeFrame frame;
|
||||
public CodeFunction func;
|
||||
public int id;
|
||||
public ObjectValue local, capture, global, valstack;
|
||||
public JSONMap serialized;
|
||||
public Location location;
|
||||
public boolean debugData = false;
|
||||
|
||||
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.location = frame.function.loc();
|
||||
|
||||
this.global = frame.function.environment.global.obj;
|
||||
this.local = frame.getLocalScope(ctx, true);
|
||||
this.capture = frame.getCaptureScope(ctx, true);
|
||||
this.local.setPrototype(ctx, capture);
|
||||
this.capture.setPrototype(ctx, global);
|
||||
this.valstack = frame.getValStackScope(ctx);
|
||||
|
||||
if (location != null) {
|
||||
debugData = true;
|
||||
this.serialized = new JSONMap()
|
||||
.set("callFrameId", id + "")
|
||||
.set("functionName", func.name)
|
||||
.set("location", serializeLocation(location))
|
||||
.set("scopeChain", new JSONList()
|
||||
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
|
||||
.add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
|
||||
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
|
||||
.add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RunResult {
|
||||
public final Context ctx;
|
||||
public final Object result;
|
||||
public final EngineException error;
|
||||
|
||||
public RunResult(Context ctx, Object result, EngineException error) {
|
||||
this.ctx = ctx;
|
||||
this.result = result;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean enabled = true;
|
||||
public CatchType execptionType = CatchType.ALL;
|
||||
public State state = State.RESUMED;
|
||||
|
||||
public final WebSocket ws;
|
||||
public final Engine target;
|
||||
|
||||
private ObjectValue emptyObject = new ObjectValue();
|
||||
|
||||
private HashMap<Integer, BreakpointCandidate> idToBptCand = new HashMap<>();
|
||||
|
||||
private HashMap<Integer, Breakpoint> idToBreakpoint = new HashMap<>();
|
||||
private HashMap<Location, Breakpoint> locToBreakpoint = new HashMap<>();
|
||||
private HashSet<Location> tmpBreakpts = new HashSet<>();
|
||||
|
||||
private HashMap<Filename, Integer> filenameToId = new HashMap<>();
|
||||
private HashMap<Integer, Source> idToSource = new HashMap<>();
|
||||
private ArrayList<Source> pendingSources = new ArrayList<>();
|
||||
|
||||
private HashMap<Integer, Frame> idToFrame = new HashMap<>();
|
||||
private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>();
|
||||
|
||||
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>();
|
||||
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
|
||||
private HashMap<ObjectValue, Context> objectToCtx = new HashMap<>();
|
||||
private HashMap<String, ArrayList<ObjectValue>> objectGroups = new HashMap<>();
|
||||
|
||||
private Notifier updateNotifier = new Notifier();
|
||||
|
||||
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();
|
||||
}
|
||||
private Location deserializeLocation(JSONElement el, boolean correct) {
|
||||
if (!el.isMap()) throw new RuntimeException("Expected location to be a map.");
|
||||
var id = Integer.parseInt(el.map().string("scriptId"));
|
||||
var line = (int)el.map().number("lineNumber") + 1;
|
||||
var column = (int)el.map().number("columnNumber") + 1;
|
||||
|
||||
if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id));
|
||||
|
||||
var res = new Location(line, column, idToSource.get(id).filename);
|
||||
if (correct) res = correctLocation(idToSource.get(id), res);
|
||||
return res;
|
||||
}
|
||||
private 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(Context ctx, ObjectValue obj) {
|
||||
if (objectToId.containsKey(obj)) return objectToId.get(obj);
|
||||
else {
|
||||
int id = nextId();
|
||||
objectToId.put(obj, id);
|
||||
if (ctx != null) objectToCtx.put(obj, ctx);
|
||||
idToObject.put(id, obj);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
private JSONMap serializeObj(Context ctx, Object val) {
|
||||
val = Values.normalize(null, val);
|
||||
|
||||
if (val == Values.NULL) {
|
||||
return new JSONMap()
|
||||
.set("type", "object")
|
||||
.set("subtype", "null")
|
||||
.setNull("value")
|
||||
.set("description", "null");
|
||||
}
|
||||
|
||||
if (val instanceof ObjectValue) {
|
||||
var obj = (ObjectValue)val;
|
||||
var id = objectId(ctx, obj);
|
||||
var type = "object";
|
||||
String subtype = null;
|
||||
String className = null;
|
||||
|
||||
if (obj instanceof FunctionValue) type = "function";
|
||||
if (obj instanceof ArrayValue) subtype = "array";
|
||||
|
||||
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
|
||||
catch (Exception e) { }
|
||||
|
||||
var res = new JSONMap()
|
||||
.set("type", type)
|
||||
.set("objectId", id + "");
|
||||
|
||||
if (subtype != null) res.set("subtype", subtype);
|
||||
if (className != null) {
|
||||
res.set("className", className);
|
||||
res.set("description", "{ " + 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 (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0");
|
||||
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0");
|
||||
else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
|
||||
else res.set("value", num);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unexpected JS object.");
|
||||
}
|
||||
private void setObjectGroup(String name, Object val) {
|
||||
if (val instanceof ObjectValue) {
|
||||
var obj = (ObjectValue)val;
|
||||
|
||||
if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj);
|
||||
else objectGroups.put(name, new ArrayList<>(List.of(obj)));
|
||||
}
|
||||
}
|
||||
private void releaseGroup(String name) {
|
||||
var objs = objectGroups.remove(name);
|
||||
|
||||
if (objs != null) {
|
||||
for (var obj : objs) {
|
||||
var id = objectToId.remove(obj);
|
||||
if (id != null) idToObject.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
private Object deserializeArgument(JSONMap val) {
|
||||
if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId")));
|
||||
else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) {
|
||||
case "NaN": return Double.NaN;
|
||||
case "-Infinity": return Double.NEGATIVE_INFINITY;
|
||||
case "Infinity": return Double.POSITIVE_INFINITY;
|
||||
case "-0": return -0.;
|
||||
}
|
||||
return JSON.toJs(val.get("value"));
|
||||
}
|
||||
|
||||
private JSONMap serializeException(Context ctx, EngineException err) {
|
||||
String text = null;
|
||||
|
||||
try {
|
||||
text = Values.toString(ctx, err.value);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
text = "[error while stringifying]";
|
||||
}
|
||||
|
||||
var res = new JSONMap()
|
||||
.set("exceptionId", nextId())
|
||||
.set("exception", serializeObj(ctx, err.value))
|
||||
.set("exception", serializeObj(ctx, err.value))
|
||||
.set("text", text);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
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_EXCEPTION;
|
||||
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))
|
||||
));
|
||||
}
|
||||
|
||||
private RunResult run(Frame codeFrame, String code) {
|
||||
var engine = new Engine(false);
|
||||
var env = codeFrame.func.environment.fork();
|
||||
|
||||
ObjectValue global = env.global.obj,
|
||||
capture = codeFrame.frame.getCaptureScope(null, enabled),
|
||||
local = codeFrame.frame.getLocalScope(null, enabled);
|
||||
|
||||
capture.setPrototype(null, global);
|
||||
local.setPrototype(null, capture);
|
||||
env.global = new GlobalScope(local);
|
||||
|
||||
var ctx = new Context(engine).pushEnv(env);
|
||||
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||
|
||||
engine.run(true);
|
||||
|
||||
try { return new RunResult(ctx, awaiter.await(), null); }
|
||||
catch (EngineException e) { return new RunResult(ctx, null, e); }
|
||||
}
|
||||
|
||||
@Override public void enable(V8Message msg) {
|
||||
enabled = true;
|
||||
ws.send(msg.respond());
|
||||
|
||||
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 src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId")));
|
||||
var start = deserializeLocation(msg.params.get("start"), false);
|
||||
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
|
||||
|
||||
var 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;
|
||||
var cond = msg.params.string("condition", null);
|
||||
|
||||
if (cond != null) cond = "(" + cond + ")";
|
||||
|
||||
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, cond);
|
||||
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 evaluateOnCallFrame(V8Message msg) {
|
||||
var cfId = Integer.parseInt(msg.params.string("callFrameId"));
|
||||
var expr = msg.params.string("expression");
|
||||
var group = msg.params.string("objectGroup", null);
|
||||
|
||||
var cf = idToFrame.get(cfId);
|
||||
var res = run(cf, expr);
|
||||
|
||||
if (group != null) setObjectGroup(group, res.result);
|
||||
|
||||
if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeObj(res.ctx, res.result))));
|
||||
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result))));
|
||||
}
|
||||
|
||||
@Override public void releaseObjectGroup(V8Message msg) {
|
||||
var group = msg.params.string("objectGroup");
|
||||
releaseGroup(group);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
@Override
|
||||
public void releaseObject(V8Message msg) {
|
||||
var id = Integer.parseInt(msg.params.string("objectId"));
|
||||
var obj = idToObject.remove(id);
|
||||
if (obj != null) objectToId.remove(obj);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
@Override public void getProperties(V8Message msg) {
|
||||
var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
|
||||
|
||||
var res = new JSONList();
|
||||
var ctx = objectToCtx.get(obj);
|
||||
|
||||
if (obj != emptyObject) {
|
||||
for (var key : obj.keys(true)) {
|
||||
var propDesc = new JSONMap();
|
||||
|
||||
if (obj.properties.containsKey(key)) {
|
||||
var prop = obj.properties.get(key);
|
||||
|
||||
propDesc.set("name", Values.toString(ctx, key));
|
||||
if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter));
|
||||
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
|
||||
propDesc.set("enumerable", obj.memberEnumerable(key));
|
||||
propDesc.set("configurable", obj.memberConfigurable(key));
|
||||
propDesc.set("isOwn", true);
|
||||
res.add(propDesc);
|
||||
}
|
||||
else {
|
||||
propDesc.set("name", Values.toString(ctx, key));
|
||||
propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key)));
|
||||
propDesc.set("writable", obj.memberWritable(key));
|
||||
propDesc.set("enumerable", obj.memberEnumerable(key));
|
||||
propDesc.set("configurable", obj.memberConfigurable(key));
|
||||
propDesc.set("isOwn", true);
|
||||
res.add(propDesc);
|
||||
}
|
||||
}
|
||||
|
||||
var proto = obj.getPrototype(ctx);
|
||||
|
||||
var protoDesc = new JSONMap();
|
||||
protoDesc.set("name", "__proto__");
|
||||
protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto));
|
||||
protoDesc.set("writable", true);
|
||||
protoDesc.set("enumerable", false);
|
||||
protoDesc.set("configurable", false);
|
||||
protoDesc.set("isOwn", true);
|
||||
res.add(protoDesc);
|
||||
}
|
||||
|
||||
ws.send(msg.respond(new JSONMap().set("result", res)));
|
||||
}
|
||||
@Override public void callFunctionOn(V8Message msg) {
|
||||
var src = msg.params.string("functionDeclaration");
|
||||
var args = msg.params
|
||||
.list("arguments", new JSONList())
|
||||
.stream()
|
||||
.map(v -> v.map())
|
||||
.map(this::deserializeArgument)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
|
||||
var ctx = objectToCtx.get(thisArg);
|
||||
|
||||
while (true) {
|
||||
var start = src.lastIndexOf("//# sourceURL=");
|
||||
if (start < 0) break;
|
||||
var end = src.indexOf("\n", start);
|
||||
if (end < 0) src = src.substring(0, start);
|
||||
else src = src.substring(0, start) + src.substring(end + 1);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (src) {
|
||||
case CHROME_GET_PROP_FUNC: {
|
||||
var path = JSON.parse(null, (String)args.get(0)).list();
|
||||
Object res = thisArg;
|
||||
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el));
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
|
||||
return;
|
||||
}
|
||||
case VSCODE_STRINGIFY_VAL:
|
||||
case VSCODE_STRINGIFY_PROPS:
|
||||
case VSCODE_SHALLOW_COPY:
|
||||
case VSCODE_SYMBOL_REQUEST:
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject))));
|
||||
break;
|
||||
case VSCODE_FLATTEN_ARRAY:
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg))));
|
||||
break;
|
||||
case VSCODE_CALL: {
|
||||
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
|
||||
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
|
||||
break;
|
||||
// default: {
|
||||
// var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx);
|
||||
// if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args);
|
||||
// ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
|
||||
// }
|
||||
}
|
||||
}
|
||||
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runtimeEnable(V8Message msg) {
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations) {
|
||||
int id = nextId();
|
||||
var src = new Source(id, filename, source, locations);
|
||||
|
||||
idToSource.put(id, src);
|
||||
filenameToId.put(filename, id);
|
||||
|
||||
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 (!frame.debugData) return false;
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
// TODO: FIXXXX
|
||||
// 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)) {
|
||||
var bp = locToBreakpoint.get(loc);
|
||||
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result);
|
||||
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc));
|
||||
}
|
||||
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
|
||||
else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null);
|
||||
|
||||
while (enabled) {
|
||||
switch (state) {
|
||||
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 == frame.frame) {
|
||||
if (isBreakpointable && (
|
||||
!loc.filename().equals(prevLocation.filename()) ||
|
||||
loc.line() != prevLocation.line()
|
||||
)) pauseDebug(ctx, null);
|
||||
else return false;
|
||||
}
|
||||
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)
|
||||
) {
|
||||
// if (state == State.STEPPING_OVER) state = State.STEPPING_IN;
|
||||
// else {
|
||||
pauseDebug(ctx, null);
|
||||
updateNotifier.await();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void connect() {
|
||||
if (!target.attachDebugger(this)) {
|
||||
ws.send(new V8Error("A debugger is already attached to this engine."));
|
||||
}
|
||||
}
|
||||
@Override public void disconnect() {
|
||||
target.detachDebugger();
|
||||
enabled = false;
|
||||
updateNotifier.next();
|
||||
}
|
||||
|
||||
public SimpleDebugger(WebSocket ws, Engine target) {
|
||||
this.ws = ws;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
19
src/me/topchetoeu/jscript/engine/debug/V8Error.java
Normal file
19
src/me/topchetoeu/jscript/engine/debug/V8Error.java
Normal file
@@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
22
src/me/topchetoeu/jscript/engine/debug/V8Event.java
Normal file
22
src/me/topchetoeu/jscript/engine/debug/V8Event.java
Normal file
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
50
src/me/topchetoeu/jscript/engine/debug/V8Message.java
Normal file
50
src/me/topchetoeu/jscript/engine/debug/V8Message.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
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<String, JSONElement> params) {
|
||||
this.name = name;
|
||||
this.params = new JSONMap(params);
|
||||
this.id = id;
|
||||
}
|
||||
public V8Result respond(JSONMap result) {
|
||||
return new V8Result(id, result);
|
||||
}
|
||||
public V8Result respond() {
|
||||
return new V8Result(id, new JSONMap());
|
||||
}
|
||||
|
||||
public V8Message(JSONMap raw) {
|
||||
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
|
||||
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
|
||||
|
||||
this.name = raw.string("method");
|
||||
this.id = (int)raw.number("id");
|
||||
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
|
||||
}
|
||||
public V8Message(String raw) {
|
||||
this(JSON.parse(null, raw).map());
|
||||
}
|
||||
|
||||
public JSONMap toMap() {
|
||||
var res = new JSONMap();
|
||||
return res;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap()
|
||||
.set("method", name)
|
||||
.set("params", params)
|
||||
.set("id", id)
|
||||
);
|
||||
}
|
||||
}
|
||||
22
src/me/topchetoeu/jscript/engine/debug/V8Result.java
Normal file
22
src/me/topchetoeu/jscript/engine/debug/V8Result.java
Normal file
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
222
src/me/topchetoeu/jscript/engine/debug/WebSocket.java
Normal file
222
src/me/topchetoeu/jscript/engine/debug/WebSocket.java
Normal file
@@ -0,0 +1,222 @@
|
||||
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.UncheckedIOException;
|
||||
|
||||
public class WebSocket implements AutoCloseable {
|
||||
public long maxLength = 1 << 20;
|
||||
|
||||
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(int 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((len >> 56) & 0xFF);
|
||||
out().write((len >> 48) & 0xFF);
|
||||
out().write((len >> 40) & 0xFF);
|
||||
out().write((len >> 32) & 0xFF);
|
||||
out().write((len >> 24) & 0xFF);
|
||||
out().write((len >> 16) & 0xFF);
|
||||
out().write((len >> 8) & 0xFF);
|
||||
out().write(len & 0xFF);
|
||||
}
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
}
|
||||
private synchronized void write(int type, byte[] data) {
|
||||
try {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < data.length / 0xFFFF; i++) {
|
||||
out().write(type);
|
||||
writeLength(0xFFFF);
|
||||
out().write(data, i * 0xFFFF, 0xFFFF);
|
||||
type = 0;
|
||||
}
|
||||
|
||||
out().write(type | 0x80);
|
||||
writeLength(data.length % 0xFFFF);
|
||||
out().write(data, i * 0xFFFF, data.length % 0xFFFF);
|
||||
}
|
||||
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) {
|
||||
// TODO: Remove
|
||||
// System.out.println("SEND: " + 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();
|
||||
|
||||
// TODO: Remove
|
||||
// System.out.println("RECEIVED: " + new String(raw));
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
29
src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
Normal file
29
src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
@@ -9,11 +10,13 @@ import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.ScopeValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class CodeFrame {
|
||||
private class TryCtx {
|
||||
public static class TryCtx {
|
||||
public static final int STATE_TRY = 0;
|
||||
public static final int STATE_CATCH = 1;
|
||||
public static final int STATE_FINALLY_THREW = 2;
|
||||
@@ -54,8 +57,56 @@ public class CodeFrame {
|
||||
public boolean jumpFlag = false;
|
||||
private Location prevLoc = null;
|
||||
|
||||
public ObjectValue getLocalScope(Context ctx, boolean props) {
|
||||
var names = new String[scope.locals.length];
|
||||
|
||||
for (int i = 0; i < scope.locals.length; i++) {
|
||||
var name = "local_" + (i - 2);
|
||||
|
||||
if (i == 0) name = "this";
|
||||
else if (i == 1) name = "arguments";
|
||||
else if (i < function.localNames.length) name = function.localNames[i];
|
||||
|
||||
names[i] = name;
|
||||
}
|
||||
|
||||
return new ScopeValue(scope.locals, names);
|
||||
}
|
||||
public ObjectValue getCaptureScope(Context ctx, boolean props) {
|
||||
var names = new String[scope.captures.length];
|
||||
|
||||
for (int i = 0; i < scope.captures.length; i++) {
|
||||
var name = "capture_" + (i - 2);
|
||||
if (i < function.captureNames.length) name = function.captureNames[i];
|
||||
names[i] = name;
|
||||
}
|
||||
|
||||
return new ScopeValue(scope.captures, names);
|
||||
}
|
||||
public ObjectValue getValStackScope(Context ctx) {
|
||||
return new ObjectValue() {
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
var i = (int)Values.toNumber(ctx, key);
|
||||
if (i < 0 || i >= stackPtr) return null;
|
||||
else return stack[i];
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
for (var i = 0; i < stackPtr; i++) res.add(i);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void addTry(int n, int catchN, int finallyN) {
|
||||
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
|
||||
if (!tryStack.empty()) res.err = tryStack.peek().err;
|
||||
|
||||
tryStack.add(res);
|
||||
}
|
||||
@@ -93,35 +144,33 @@ public class CodeFrame {
|
||||
stack[stackPtr++] = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException {
|
||||
if (err.value instanceof ObjectValue) {
|
||||
Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause);
|
||||
}
|
||||
err.cause = cause;
|
||||
}
|
||||
private Object nextNoTry(Context ctx) throws InterruptedException {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
|
||||
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);
|
||||
}
|
||||
private void setCause(Context ctx, EngineException err, EngineException cause) {
|
||||
err.setCause(cause);
|
||||
}
|
||||
|
||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException {
|
||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
|
||||
if (value != Runners.NO_RETURN) push(ctx, value);
|
||||
|
||||
if (returnValue == Runners.NO_RETURN && error == null) {
|
||||
try { returnValue = nextNoTry(ctx); }
|
||||
try {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
||||
|
||||
var instr = function.body[codePtr];
|
||||
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
|
||||
|
||||
if (codePtr < 0 || codePtr >= function.body.length) returnValue = null;
|
||||
else {
|
||||
if (instr.location != null) prevLoc = instr.location;
|
||||
|
||||
try {
|
||||
this.jumpFlag = false;
|
||||
returnValue = Runners.exec(ctx, instr, this);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EngineException e) { error = e; }
|
||||
}
|
||||
|
||||
@@ -144,7 +193,7 @@ public class CodeFrame {
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.retVal = error;
|
||||
tryCtx.retVal = returnValue;
|
||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
break;
|
||||
@@ -160,6 +209,7 @@ public class CodeFrame {
|
||||
break;
|
||||
case TryCtx.STATE_CATCH:
|
||||
if (error != null) {
|
||||
setCause(ctx, error, tryCtx.err);
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_FINALLY_THREW;
|
||||
@@ -185,27 +235,31 @@ public class CodeFrame {
|
||||
case TryCtx.STATE_FINALLY_THREW:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
|
||||
else return Runners.NO_RETURN;
|
||||
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_RETURNED:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
if (returnValue == Runners.NO_RETURN) {
|
||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
|
||||
else return Runners.NO_RETURN;
|
||||
}
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_JUMPED:
|
||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
|
||||
if (!jumpFlag) codePtr = tryCtx.jumpPtr;
|
||||
else codePtr = tryCtx.end;
|
||||
}
|
||||
else return Runners.NO_RETURN;
|
||||
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (newState == -1) {
|
||||
var err = tryCtx.err;
|
||||
tryStack.pop();
|
||||
if (!tryStack.isEmpty()) tryStack.peek().err = err;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -214,6 +268,7 @@ public class CodeFrame {
|
||||
case TryCtx.STATE_CATCH:
|
||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
||||
codePtr = tryCtx.catchStart;
|
||||
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true);
|
||||
break;
|
||||
default:
|
||||
codePtr = tryCtx.finallyStart;
|
||||
@@ -221,23 +276,17 @@ public class CodeFrame {
|
||||
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
|
||||
if (error != null) throw error.setContext(ctx);
|
||||
if (returnValue != Runners.NO_RETURN) return returnValue;
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
|
||||
public Object run(Context ctx) throws InterruptedException {
|
||||
try {
|
||||
ctx.message.pushFrame(ctx, this);
|
||||
while (true) {
|
||||
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||
if (res != Runners.NO_RETURN) return res;
|
||||
}
|
||||
if (error != null) {
|
||||
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, false);
|
||||
throw error;
|
||||
}
|
||||
finally {
|
||||
ctx.message.popFrame(this);
|
||||
if (returnValue != Runners.NO_RETURN) {
|
||||
ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
|
||||
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
|
||||
|
||||
@@ -4,12 +4,12 @@ import java.util.Collections;
|
||||
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
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.SignalValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
@@ -20,58 +20,40 @@ public class Runners {
|
||||
public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
return frame.pop();
|
||||
}
|
||||
public static Object execSignal(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
return new SignalValue(instr.get(0));
|
||||
}
|
||||
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
throw new EngineException(frame.pop());
|
||||
}
|
||||
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
throw EngineException.ofSyntax((String)instr.get(0));
|
||||
}
|
||||
|
||||
private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
|
||||
return Values.call(ctx, func, thisArg, args);
|
||||
}
|
||||
|
||||
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var func = frame.pop();
|
||||
var thisArg = frame.pop();
|
||||
|
||||
frame.push(ctx, call(ctx, func, thisArg, callArgs));
|
||||
frame.push(ctx, Values.call(ctx, func, thisArg, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
||||
|
||||
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
||||
// }
|
||||
// else {
|
||||
// var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||
// var obj = new ObjectValue();
|
||||
// obj.setPrototype(ctx, proto);
|
||||
// call(ctx, funcObj, obj, callArgs);
|
||||
// frame.push(ctx, obj);
|
||||
// }
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
ctx.env.global.define(name);
|
||||
ctx.environment().global.define(name);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var setter = frame.pop();
|
||||
var getter = frame.pop();
|
||||
var name = frame.pop();
|
||||
@@ -86,7 +68,7 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var type = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
@@ -101,27 +83,37 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = frame.pop();
|
||||
|
||||
var arr = new ObjectValue();
|
||||
var i = 0;
|
||||
|
||||
var members = Values.getMembers(ctx, val, false, false);
|
||||
Collections.reverse(members);
|
||||
|
||||
frame.push(ctx, null);
|
||||
|
||||
for (var el : members) {
|
||||
if (el instanceof Symbol) continue;
|
||||
arr.defineProperty(ctx, i++, el);
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "value", el);
|
||||
frame.push(ctx, obj);
|
||||
}
|
||||
// var arr = new ObjectValue();
|
||||
|
||||
arr.defineProperty(ctx, "length", i);
|
||||
// var members = Values.getMembers(ctx, val, false, false);
|
||||
// Collections.reverse(members);
|
||||
// for (var el : members) {
|
||||
// if (el instanceof Symbol) continue;
|
||||
// arr.defineProperty(ctx, i++, el);
|
||||
// }
|
||||
|
||||
frame.push(ctx, arr);
|
||||
// arr.defineProperty(ctx, "length", i);
|
||||
|
||||
// frame.push(ctx, arr);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.addTry(instr.get(0), instr.get(1), instr.get(2));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
@@ -159,10 +151,10 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i));
|
||||
if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i));
|
||||
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -174,7 +166,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, ctx.env.global.obj);
|
||||
frame.push(ctx, ctx.environment().global.obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -186,7 +178,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int n = (Integer)instr.get(0);
|
||||
long id = (Long)instr.get(0);
|
||||
int localsN = (Integer)instr.get(1);
|
||||
int len = (Integer)instr.get(2);
|
||||
var captures = new ValueVariable[instr.params.length - 3];
|
||||
@@ -195,18 +187,15 @@ public class Runners {
|
||||
captures[i - 3] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var start = frame.codePtr + 1;
|
||||
var end = start + n - 1;
|
||||
var body = new Instruction[end - start];
|
||||
System.arraycopy(frame.function.body, start, body, 0, end - start);
|
||||
var body = Engine.functions.get(id);
|
||||
var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
|
||||
|
||||
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body);
|
||||
frame.push(ctx, func);
|
||||
|
||||
frame.codePtr += n;
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
@@ -219,12 +208,12 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, instr.get(0));
|
||||
return execLoadMember(ctx, instr, frame);
|
||||
}
|
||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -234,7 +223,7 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = frame.pop();
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
@@ -244,11 +233,11 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) ctx.env.global.set(ctx, (String)i, val);
|
||||
if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -265,7 +254,7 @@ public class Runners {
|
||||
frame.jumpFlag = true;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
if (Values.toBoolean(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
@@ -273,7 +262,7 @@ public class Runners {
|
||||
else frame.codePtr ++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
if (Values.not(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
@@ -282,7 +271,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var obj = frame.pop();
|
||||
var index = frame.pop();
|
||||
|
||||
@@ -290,13 +279,13 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
String name = instr.get(0);
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (ctx.env.global.has(ctx, name)) {
|
||||
obj = ctx.env.global.get(ctx, name);
|
||||
if (ctx.environment().global.has(ctx, name)) {
|
||||
obj = ctx.environment().global.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
@@ -307,21 +296,12 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
if (instr.is(0, "dbg_names")) {
|
||||
var names = new String[instr.params.length - 1];
|
||||
for (var i = 0; i < instr.params.length - 1; i++) {
|
||||
if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters.");
|
||||
names[i] = (String)instr.params[i + 1];
|
||||
}
|
||||
frame.scope.setNames(names);
|
||||
}
|
||||
|
||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var key = frame.pop();
|
||||
var val = frame.pop();
|
||||
|
||||
@@ -331,7 +311,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
Operation op = instr.get(0);
|
||||
var args = new Object[op.operands];
|
||||
|
||||
@@ -342,12 +322,10 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
// System.out.println(instr + "@" + instr.location);
|
||||
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
switch (instr.type) {
|
||||
case NOP: return execNop(ctx, instr, frame);
|
||||
case RETURN: return execReturn(ctx, instr, frame);
|
||||
case SIGNAL: return execSignal(ctx, instr, frame);
|
||||
case THROW: return execThrow(ctx, instr, frame);
|
||||
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
|
||||
case CALL: return execCall(ctx, instr, frame);
|
||||
|
||||
@@ -15,7 +15,7 @@ public class GlobalScope implements ScopeRecord {
|
||||
@Override
|
||||
public GlobalScope parent() { return null; }
|
||||
|
||||
public boolean has(Context ctx, String name) throws InterruptedException {
|
||||
public boolean has(Context ctx, String name) {
|
||||
return obj.hasMember(ctx, name, false);
|
||||
}
|
||||
public Object getKey(String name) {
|
||||
@@ -32,13 +32,7 @@ public class GlobalScope implements ScopeRecord {
|
||||
}
|
||||
|
||||
public Object define(String name) {
|
||||
try {
|
||||
if (obj.hasMember(null, name, true)) return name;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return name;
|
||||
}
|
||||
if (obj.hasMember(null, name, true)) return name;
|
||||
obj.defineProperty(null, name, null);
|
||||
return name;
|
||||
}
|
||||
@@ -59,11 +53,11 @@ public class GlobalScope implements ScopeRecord {
|
||||
define(null, val.name, readonly, val);
|
||||
}
|
||||
|
||||
public Object get(Context ctx, String name) throws InterruptedException {
|
||||
public Object get(Context ctx, String name) {
|
||||
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
else return obj.getMember(ctx, name);
|
||||
}
|
||||
public void set(Context ctx, String name, Object val) throws InterruptedException {
|
||||
public void set(Context ctx, String name, Object val) {
|
||||
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
|
||||
}
|
||||
|
||||
@@ -3,43 +3,21 @@ package me.topchetoeu.jscript.engine.scope;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LocalScope {
|
||||
private String[] names;
|
||||
public final ValueVariable[] captures;
|
||||
public final ValueVariable[] locals;
|
||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||
|
||||
public ValueVariable get(int i) {
|
||||
if (i >= locals.length)
|
||||
return catchVars.get(i - locals.length);
|
||||
if (i >= locals.length) return catchVars.get(i - locals.length);
|
||||
if (i >= 0) return locals[i];
|
||||
else return captures[~i];
|
||||
}
|
||||
|
||||
public String[] getNames() {
|
||||
var res = new String[locals.length];
|
||||
|
||||
for (var i = 0; i < locals.length; i++) {
|
||||
if (names == null || i >= names.length) res[i] = "local_" + i;
|
||||
else res[i] = names[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
public void setNames(String[] names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return captures.length + locals.length;
|
||||
}
|
||||
|
||||
public void toGlobal(GlobalScope global) {
|
||||
var names = getNames();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
global.define(names[i], locals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalScope(int n, ValueVariable[] captures) {
|
||||
locals = new ValueVariable[n];
|
||||
this.captures = captures;
|
||||
|
||||
@@ -11,6 +11,9 @@ public class LocalScopeRecord implements ScopeRecord {
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
|
||||
public String[] captures() {
|
||||
return captures.toArray(String[]::new);
|
||||
}
|
||||
public String[] locals() {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
@@ -59,7 +62,7 @@ public class LocalScopeRecord implements ScopeRecord {
|
||||
|
||||
return name;
|
||||
}
|
||||
public boolean has(Context ctx, String name) throws InterruptedException {
|
||||
public boolean has(Context ctx, String name) {
|
||||
return
|
||||
global.has(ctx, name) ||
|
||||
locals.contains(name) ||
|
||||
|
||||
@@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.scope;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public interface Variable {
|
||||
Object get(Context ctx) throws InterruptedException;
|
||||
Object get(Context ctx);
|
||||
default boolean readonly() { return true; }
|
||||
default void set(Context ctx, Object val) throws InterruptedException { }
|
||||
default void set(Context ctx, Object val) { }
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
private void alloc(int index) {
|
||||
if (index < values.length) return;
|
||||
private Object[] alloc(int index) {
|
||||
index++;
|
||||
if (index < values.length) return values;
|
||||
if (index < values.length * 2) index = values.length * 2;
|
||||
|
||||
var arr = new Object[index];
|
||||
System.arraycopy(values, 0, arr, 0, values.length);
|
||||
values = arr;
|
||||
return arr;
|
||||
}
|
||||
|
||||
public int size() { return size; }
|
||||
@@ -28,7 +29,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
if (val < 0) return false;
|
||||
if (size > val) shrink(size - val);
|
||||
else {
|
||||
alloc(val);
|
||||
values = alloc(val);
|
||||
size = val;
|
||||
}
|
||||
return true;
|
||||
@@ -43,7 +44,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
public void set(Context ctx, int i, Object val) {
|
||||
if (i < 0) return;
|
||||
|
||||
alloc(i);
|
||||
values = alloc(i);
|
||||
|
||||
val = Values.normalize(ctx, val);
|
||||
if (val == null) val = UNDEFINED;
|
||||
@@ -51,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
if (i >= size) size = i + 1;
|
||||
}
|
||||
public boolean has(int i) {
|
||||
return i >= 0 && i < values.length && values[i] != null;
|
||||
return i >= 0 && i < size && values[i] != null;
|
||||
}
|
||||
public void remove(int i) {
|
||||
if (i < 0 || i >= values.length) return;
|
||||
@@ -84,8 +85,9 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||
// Iterate in reverse to reallocate at most once
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
|
||||
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
|
||||
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
|
||||
else arr.set(ctx, i + destStart, values[i + sourceStart]);
|
||||
}
|
||||
}
|
||||
@@ -97,7 +99,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
}
|
||||
|
||||
public void move(int srcI, int dstI, int n) {
|
||||
alloc(dstI + n);
|
||||
values = alloc(dstI + n);
|
||||
|
||||
System.arraycopy(values, srcI, values, dstI, n);
|
||||
|
||||
@@ -122,7 +124,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (key instanceof Number) {
|
||||
var i = ((Number)key).doubleValue();
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
@@ -133,7 +135,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
@@ -145,7 +147,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
return super.setField(ctx, key, val);
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
@@ -156,7 +158,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected void deleteField(Context ctx, Object key) throws InterruptedException {
|
||||
protected void deleteField(Context ctx, Object key) {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
@@ -212,7 +214,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
|
||||
}
|
||||
|
||||
public static ArrayValue of(Context ctx, Collection<Object> values) {
|
||||
public static ArrayValue of(Context ctx, Collection<?> values) {
|
||||
return new ArrayValue(ctx, values.toArray(Object[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
|
||||
public class CodeFunction extends FunctionValue {
|
||||
public final int localsN;
|
||||
public final int length;
|
||||
public final Instruction[] body;
|
||||
public final String[] captureNames, localNames;
|
||||
public final ValueVariable[] captures;
|
||||
public Environment environment;
|
||||
|
||||
@@ -28,16 +32,29 @@ public class CodeFunction extends FunctionValue {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
return new CodeFrame(ctx, thisArg, args, this).run(ctx.setEnv(environment));
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var frame = new CodeFrame(ctx, thisArg, args, this);
|
||||
try {
|
||||
StackData.pushFrame(ctx, frame);
|
||||
|
||||
while (true) {
|
||||
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||
if (res != Runners.NO_RETURN) return res;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
StackData.popFrame(ctx, frame);
|
||||
}
|
||||
}
|
||||
|
||||
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) {
|
||||
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) {
|
||||
super(name, length);
|
||||
this.captures = captures;
|
||||
this.captureNames = body.captureNames;
|
||||
this.localNames = body.localNames;
|
||||
this.environment = environment;
|
||||
this.localsN = localsN;
|
||||
this.length = length;
|
||||
this.body = body;
|
||||
this.body = body.instructions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,26 +14,26 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
return "function(...) { ...}";
|
||||
}
|
||||
|
||||
public abstract Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException;
|
||||
public Object call(Context ctx) throws InterruptedException {
|
||||
public abstract Object call(Context ctx, Object thisArg, Object ...args);
|
||||
public Object call(Context ctx) {
|
||||
return call(ctx, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (key.equals("name")) return name;
|
||||
if (key.equals("length")) return length;
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (key.equals("name")) name = Values.toString(ctx, val);
|
||||
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
|
||||
else return super.setField(ctx, key, val);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if (key.equals("name")) return true;
|
||||
if (key.equals("length")) return true;
|
||||
return super.hasField(ctx, key);
|
||||
|
||||
@@ -4,13 +4,13 @@ import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class NativeFunction extends FunctionValue {
|
||||
public static interface NativeFunctionRunner {
|
||||
Object run(Context ctx, Object thisArg, Object[] args) throws InterruptedException;
|
||||
Object run(Context ctx, Object thisArg, Object[] args);
|
||||
}
|
||||
|
||||
public final NativeFunctionRunner action;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
return action.run(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,8 @@ public class NativeWrapper extends ObjectValue {
|
||||
public final Object wrapped;
|
||||
|
||||
@Override
|
||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||
if (prototype == NATIVE_PROTO)
|
||||
return ctx.env.wrappersProvider.getProto(wrapped.getClass());
|
||||
public ObjectValue getPrototype(Context ctx) {
|
||||
if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass());
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -47,11 +47,11 @@ public class ObjectValue {
|
||||
protected Object prototype;
|
||||
|
||||
public State state = State.NORMAL;
|
||||
public HashMap<Object, Object> values = new HashMap<>();
|
||||
public HashMap<Object, Property> properties = new HashMap<>();
|
||||
public HashSet<Object> nonWritableSet = new HashSet<>();
|
||||
public HashSet<Object> nonConfigurableSet = new HashSet<>();
|
||||
public HashSet<Object> nonEnumerableSet = new HashSet<>();
|
||||
public LinkedHashMap<Object, Object> values = new LinkedHashMap<>();
|
||||
public LinkedHashMap<Object, Property> properties = new LinkedHashMap<>();
|
||||
public LinkedHashSet<Object> nonWritableSet = new LinkedHashSet<>();
|
||||
public LinkedHashSet<Object> nonConfigurableSet = new LinkedHashSet<>();
|
||||
public LinkedHashSet<Object> nonEnumerableSet = new LinkedHashSet<>();
|
||||
|
||||
public final boolean memberWritable(Object key) {
|
||||
if (state == State.FROZEN) return false;
|
||||
@@ -103,8 +103,7 @@ public class ObjectValue {
|
||||
) return true;
|
||||
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key))
|
||||
return false;
|
||||
if (!memberConfigurable(key)) return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
@@ -145,19 +144,17 @@ public class ObjectValue {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||
public ObjectValue getPrototype(Context ctx) {
|
||||
try {
|
||||
if (prototype == OBJ_PROTO) return ctx.env.proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.env.proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.env.proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.env.proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr");
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
return null;
|
||||
if (prototype == OBJ_PROTO) return ctx.environment().proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.environment().proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.environment().proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.environment().proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr");
|
||||
}
|
||||
catch (NullPointerException e) { return null; }
|
||||
|
||||
return (ObjectValue)prototype;
|
||||
}
|
||||
@@ -172,14 +169,14 @@ public class ObjectValue {
|
||||
else if (Values.isObject(val)) {
|
||||
var obj = Values.object(val);
|
||||
|
||||
if (ctx != null && ctx.env != null) {
|
||||
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
if (ctx != null && ctx.environment() != null) {
|
||||
if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
@@ -203,19 +200,19 @@ public class ObjectValue {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Property getProperty(Context ctx, Object key) throws InterruptedException {
|
||||
protected Property getProperty(Context ctx, Object key) {
|
||||
if (properties.containsKey(key)) return properties.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getProperty(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (values.containsKey(key)) return values.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getField(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
|
||||
((FunctionValue)val).name = Values.toString(ctx, key);
|
||||
}
|
||||
@@ -223,14 +220,14 @@ public class ObjectValue {
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
protected void deleteField(Context ctx, Object key) throws InterruptedException {
|
||||
protected void deleteField(Context ctx, Object key) {
|
||||
values.remove(key);
|
||||
}
|
||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
return values.containsKey(key);
|
||||
}
|
||||
|
||||
public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException {
|
||||
public final Object getMember(Context ctx, Object key, Object thisArg) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if ("__proto__".equals(key)) {
|
||||
@@ -246,11 +243,11 @@ public class ObjectValue {
|
||||
}
|
||||
else return getField(ctx, key);
|
||||
}
|
||||
public final Object getMember(Context ctx, Object key) throws InterruptedException {
|
||||
public final Object getMember(Context ctx, Object key) {
|
||||
return getMember(ctx, key, this);
|
||||
}
|
||||
|
||||
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException {
|
||||
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) {
|
||||
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
|
||||
|
||||
var prop = getProperty(ctx, key);
|
||||
@@ -269,11 +266,11 @@ public class ObjectValue {
|
||||
else if (nonWritableSet.contains(key)) return false;
|
||||
else return setField(ctx, key, val);
|
||||
}
|
||||
public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) throws InterruptedException {
|
||||
public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) {
|
||||
return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps);
|
||||
}
|
||||
|
||||
public final boolean hasMember(Context ctx, Object key, boolean own) throws InterruptedException {
|
||||
public final boolean hasMember(Context ctx, Object key, boolean own) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (key != null && key.equals("__proto__")) return true;
|
||||
@@ -283,7 +280,7 @@ public class ObjectValue {
|
||||
var proto = getPrototype(ctx);
|
||||
return proto != null && proto.hasMember(ctx, key, own);
|
||||
}
|
||||
public final boolean deleteMember(Context ctx, Object key) throws InterruptedException {
|
||||
public final boolean deleteMember(Context ctx, Object key) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (!memberConfigurable(key)) return false;
|
||||
@@ -294,7 +291,7 @@ public class ObjectValue {
|
||||
return true;
|
||||
}
|
||||
|
||||
public final ObjectValue getMemberDescriptor(Context ctx, Object key) throws InterruptedException {
|
||||
public final ObjectValue getMemberDescriptor(Context ctx, Object key) {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
var prop = properties.get(key);
|
||||
|
||||
51
src/me/topchetoeu/jscript/engine/values/ScopeValue.java
Normal file
51
src/me/topchetoeu/jscript/engine/values/ScopeValue.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
|
||||
public class ScopeValue extends ObjectValue {
|
||||
public final ValueVariable[] variables;
|
||||
public final HashMap<String, Integer> names = new HashMap<>();
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (names.containsKey(key)) return variables[names.get(key)].get(ctx);
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (names.containsKey(key)) {
|
||||
variables[names.get(key)].set(ctx, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.setField(ctx, key, val);
|
||||
}
|
||||
@Override
|
||||
protected void deleteField(Context ctx, Object key) {
|
||||
if (names.containsKey(key)) return;
|
||||
super.deleteField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if (names.containsKey(key)) return true;
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
res.addAll(names.keySet());
|
||||
return res;
|
||||
}
|
||||
|
||||
public ScopeValue(ValueVariable[] variables, String[] names) {
|
||||
this.variables = variables;
|
||||
for (var i = 0; i < names.length && i < variables.length; i++) {
|
||||
this.names.put(names[i], i);
|
||||
this.nonConfigurableSet.add(names[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
public final class SignalValue {
|
||||
public final String data;
|
||||
|
||||
public SignalValue(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static boolean isSignal(Object signal, String value) {
|
||||
if (!(signal instanceof SignalValue)) return false;
|
||||
var val = ((SignalValue)signal).data;
|
||||
|
||||
if (value.endsWith("*")) return val.startsWith(value.substring(0, value.length() - 1));
|
||||
else return val.equals(value);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||
|
||||
public class Values {
|
||||
public static final Object NULL = new Object();
|
||||
@@ -67,7 +68,7 @@ public class Values {
|
||||
return "object";
|
||||
}
|
||||
|
||||
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) throws InterruptedException {
|
||||
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) {
|
||||
var func = getMember(ctx, obj, name);
|
||||
|
||||
if (func != null) {
|
||||
@@ -84,12 +85,11 @@ public class Values {
|
||||
obj instanceof String ||
|
||||
obj instanceof Boolean ||
|
||||
obj instanceof Symbol ||
|
||||
obj instanceof SignalValue ||
|
||||
obj == null ||
|
||||
obj == NULL;
|
||||
}
|
||||
|
||||
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) throws InterruptedException {
|
||||
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) {
|
||||
obj = normalize(ctx, obj);
|
||||
if (isPrimitive(obj)) return obj;
|
||||
|
||||
@@ -97,12 +97,8 @@ public class Values {
|
||||
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
|
||||
|
||||
if (ctx != null) {
|
||||
try {
|
||||
return tryCallConvertFunc(ctx, obj, first);
|
||||
}
|
||||
catch (EngineException unused) {
|
||||
return tryCallConvertFunc(ctx, obj, second);
|
||||
}
|
||||
try { return tryCallConvertFunc(ctx, obj, first); }
|
||||
catch (EngineException unused) { return tryCallConvertFunc(ctx, obj, second); }
|
||||
}
|
||||
|
||||
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
||||
@@ -114,20 +110,19 @@ public class Values {
|
||||
if (obj instanceof Boolean) return (Boolean)obj;
|
||||
return true;
|
||||
}
|
||||
public static double toNumber(Context ctx, Object obj) throws InterruptedException {
|
||||
public static double toNumber(Context ctx, Object obj) {
|
||||
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
|
||||
|
||||
if (val instanceof Number) return number(val);
|
||||
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
|
||||
if (val instanceof String) {
|
||||
try {
|
||||
return Double.parseDouble((String)val);
|
||||
}
|
||||
catch (NumberFormatException e) { }
|
||||
try { return Double.parseDouble((String)val); }
|
||||
catch (NumberFormatException e) { return Double.NaN; }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
return Double.NaN;
|
||||
}
|
||||
public static String toString(Context ctx, Object obj) throws InterruptedException {
|
||||
public static String toString(Context ctx, Object obj) {
|
||||
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
|
||||
|
||||
if (val == null) return "undefined";
|
||||
@@ -143,52 +138,51 @@ public class Values {
|
||||
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
|
||||
if (val instanceof String) return (String)val;
|
||||
if (val instanceof Symbol) return ((Symbol)val).toString();
|
||||
if (val instanceof SignalValue) return "[signal '" + ((SignalValue)val).data + "']";
|
||||
|
||||
return "Unknown value";
|
||||
}
|
||||
|
||||
public static Object add(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static Object add(Context ctx, Object a, Object b) {
|
||||
if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b);
|
||||
else return toNumber(ctx, a) + toNumber(ctx, b);
|
||||
}
|
||||
public static double subtract(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double subtract(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) - toNumber(ctx, b);
|
||||
}
|
||||
public static double multiply(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double multiply(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) * toNumber(ctx, b);
|
||||
}
|
||||
public static double divide(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double divide(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) / toNumber(ctx, b);
|
||||
}
|
||||
public static double modulo(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static double modulo(Context ctx, Object a, Object b) {
|
||||
return toNumber(ctx, a) % toNumber(ctx, b);
|
||||
}
|
||||
|
||||
public static double negative(Context ctx, Object obj) throws InterruptedException {
|
||||
public static double negative(Context ctx, Object obj) {
|
||||
return -toNumber(ctx, obj);
|
||||
}
|
||||
|
||||
public static int and(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int and(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) & (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int or(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int or(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) | (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int xor(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int xor(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int bitwiseNot(Context ctx, Object obj) throws InterruptedException {
|
||||
public static int bitwiseNot(Context ctx, Object obj) {
|
||||
return ~(int)toNumber(ctx, obj);
|
||||
}
|
||||
|
||||
public static int shiftLeft(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int shiftLeft(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) << (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int shiftRight(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int shiftRight(Context ctx, Object a, Object b) {
|
||||
return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b);
|
||||
}
|
||||
public static long unsignedShiftRight(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static long unsignedShiftRight(Context ctx, Object a, Object b) {
|
||||
long _a = (long)toNumber(ctx, a);
|
||||
long _b = (long)toNumber(ctx, b);
|
||||
|
||||
@@ -197,7 +191,7 @@ public class Values {
|
||||
return _a >>> _b;
|
||||
}
|
||||
|
||||
public static int compare(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static int compare(Context ctx, Object a, Object b) {
|
||||
a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
|
||||
b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
|
||||
|
||||
@@ -209,7 +203,7 @@ public class Values {
|
||||
return !toBoolean(obj);
|
||||
}
|
||||
|
||||
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) throws InterruptedException {
|
||||
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) {
|
||||
if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
|
||||
var val = getPrototype(ctx, obj);
|
||||
|
||||
@@ -221,7 +215,7 @@ public class Values {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Object operation(Context ctx, Operation op, Object ...args) throws InterruptedException {
|
||||
public static Object operation(Context ctx, Operation op, Object ...args) {
|
||||
switch (op) {
|
||||
case ADD: return add(ctx, args[0], args[1]);
|
||||
case SUBTRACT: return subtract(ctx, args[0], args[1]);
|
||||
@@ -262,7 +256,7 @@ public class Values {
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getMember(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
public static Object getMember(Context ctx, Object obj, Object key) {
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
|
||||
@@ -282,7 +276,7 @@ public class Values {
|
||||
else if (key != null && key.equals("__proto__")) return proto;
|
||||
else return proto.getMember(ctx, key, obj);
|
||||
}
|
||||
public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException {
|
||||
public static boolean setMember(Context ctx, Object obj, Object key, Object val) {
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
|
||||
if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
|
||||
@@ -292,7 +286,7 @@ public class Values {
|
||||
var proto = getPrototype(ctx, obj);
|
||||
return proto.setMember(ctx, key, val, obj, true);
|
||||
}
|
||||
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) throws InterruptedException {
|
||||
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
|
||||
@@ -310,31 +304,31 @@ public class Values {
|
||||
var proto = getPrototype(ctx, obj);
|
||||
return proto != null && proto.hasMember(ctx, key, own);
|
||||
}
|
||||
public static boolean deleteMember(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
public static boolean deleteMember(Context ctx, Object obj, Object key) {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
|
||||
if (isObject(obj)) return object(obj).deleteMember(ctx, key);
|
||||
else return false;
|
||||
}
|
||||
public static ObjectValue getPrototype(Context ctx, Object obj) throws InterruptedException {
|
||||
public static ObjectValue getPrototype(Context ctx, Object obj) {
|
||||
if (obj == null || obj == NULL) return null;
|
||||
obj = normalize(ctx, obj);
|
||||
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
||||
if (ctx == null) return null;
|
||||
|
||||
if (obj instanceof String) return ctx.env.proto("string");
|
||||
else if (obj instanceof Number) return ctx.env.proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.env.proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.env.proto("symbol");
|
||||
if (obj instanceof String) return ctx.environment().proto("string");
|
||||
else if (obj instanceof Number) return ctx.environment().proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.environment().proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.environment().proto("symbol");
|
||||
|
||||
return null;
|
||||
}
|
||||
public static boolean setPrototype(Context ctx, Object obj, Object proto) throws InterruptedException {
|
||||
public static boolean setPrototype(Context ctx, Object obj, Object proto) {
|
||||
obj = normalize(ctx, obj);
|
||||
return isObject(obj) && object(obj).setPrototype(ctx, proto);
|
||||
}
|
||||
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException {
|
||||
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) {
|
||||
List<Object> res = new ArrayList<>();
|
||||
|
||||
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
|
||||
@@ -354,7 +348,7 @@ public class Values {
|
||||
|
||||
return res;
|
||||
}
|
||||
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) {
|
||||
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
|
||||
else if (obj instanceof String && key instanceof Number) {
|
||||
var i = ((Number)key).intValue();
|
||||
@@ -372,19 +366,24 @@ public class Values {
|
||||
else return null;
|
||||
}
|
||||
|
||||
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
|
||||
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
|
||||
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||
return function(func).call(ctx, thisArg, args);
|
||||
}
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException {
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) {
|
||||
var res = new ObjectValue();
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
res.setPrototype(ctx, proto);
|
||||
|
||||
var ret = call(ctx, func, res, args);
|
||||
|
||||
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
||||
return res;
|
||||
try {
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
res.setPrototype(ctx, proto);
|
||||
|
||||
var ret = call(ctx, func, res, args);
|
||||
|
||||
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
||||
return res;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw EngineException.ofType("Tried to call new on an invalid constructor.");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean strictEquals(Context ctx, Object a, Object b) {
|
||||
@@ -397,7 +396,7 @@ public class Values {
|
||||
|
||||
return a == b || a.equals(b);
|
||||
}
|
||||
public static boolean looseEqual(Context ctx, Object a, Object b) throws InterruptedException {
|
||||
public static boolean looseEqual(Context ctx, Object a, Object b) {
|
||||
a = normalize(ctx, a); b = normalize(ctx, b);
|
||||
|
||||
// In loose equality, null is equivalent to undefined
|
||||
@@ -448,14 +447,14 @@ public class Values {
|
||||
|
||||
if (val instanceof Class) {
|
||||
if (ctx == null) return null;
|
||||
else return ctx.env.wrappersProvider.getConstr((Class<?>)val);
|
||||
else return ctx.environment().wrappers.getConstr((Class<?>)val);
|
||||
}
|
||||
|
||||
return new NativeWrapper(val);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
|
||||
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) {
|
||||
if (clazz == Void.class) return null;
|
||||
|
||||
if (obj instanceof NativeWrapper) {
|
||||
@@ -522,7 +521,7 @@ public class Values {
|
||||
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
|
||||
return () -> {
|
||||
try {
|
||||
var symbol = ctx.env.symbol("Symbol.iterator");
|
||||
var symbol = ctx.environment().symbol("Symbol.iterator");
|
||||
|
||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
||||
@@ -538,7 +537,7 @@ public class Values {
|
||||
public boolean consumed = true;
|
||||
private FunctionValue next = (FunctionValue)nextFunc;
|
||||
|
||||
private void loadNext() throws InterruptedException {
|
||||
private void loadNext() {
|
||||
if (next == null) value = null;
|
||||
else if (consumed) {
|
||||
var curr = object(next.call(ctx, iterator));
|
||||
@@ -553,63 +552,66 @@ public class Values {
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
try {
|
||||
loadNext();
|
||||
return next != null;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
loadNext();
|
||||
return next != null;
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
try {
|
||||
loadNext();
|
||||
var res = value;
|
||||
value = null;
|
||||
consumed = true;
|
||||
return res;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
loadNext();
|
||||
var res = value;
|
||||
value = null;
|
||||
consumed = true;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) throws InterruptedException {
|
||||
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
try {
|
||||
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator");
|
||||
var key = getMember(ctx, getMember(ctx, ctx.environment().proto("symbol"), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||
else return new ObjectValue(ctx, Map.of("value", it.next()));
|
||||
else {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(_ctx, "value", it.next());
|
||||
return obj;
|
||||
}
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) throws InterruptedException {
|
||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) {
|
||||
return fromJavaIterator(ctx, it.iterator());
|
||||
}
|
||||
|
||||
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException {
|
||||
private static boolean isEmptyFunc(ObjectValue val) {
|
||||
if (!(val instanceof FunctionValue)) return false;
|
||||
if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false;
|
||||
var proto = val.values.get("prototype");
|
||||
if (!(proto instanceof ObjectValue)) return false;
|
||||
var protoObj = (ObjectValue)proto;
|
||||
if (protoObj.values.get("constructor") != val) return false;
|
||||
if (protoObj.values.size() + protoObj.properties.size() != 1) return false;
|
||||
return true;
|
||||
}
|
||||
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) {
|
||||
if (tab == 0 && val instanceof String) {
|
||||
System.out.print(val);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passed.contains(val)) {
|
||||
System.out.print("[circular]");
|
||||
return;
|
||||
@@ -651,7 +653,7 @@ public class Values {
|
||||
passed.add(val);
|
||||
|
||||
var obj = (ObjectValue)val;
|
||||
if (obj.values.size() + obj.properties.size() == 0) {
|
||||
if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) {
|
||||
if (!printed) System.out.println("{}");
|
||||
}
|
||||
else {
|
||||
@@ -669,26 +671,28 @@ public class Values {
|
||||
printValue(ctx, el.getKey(), passed, tab + 1);
|
||||
System.out.println(": [prop],");
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < tab; i++) System.out.print(" ");
|
||||
System.out.print("}");
|
||||
|
||||
passed.remove(val);
|
||||
|
||||
}
|
||||
|
||||
passed.remove(val);
|
||||
}
|
||||
else if (val == null) System.out.print("undefined");
|
||||
else if (val == Values.NULL) System.out.print("null");
|
||||
else if (val instanceof String) System.out.print("'" + val + "'");
|
||||
else System.out.print(Values.toString(ctx, val));
|
||||
}
|
||||
public static void printValue(Context ctx, Object val) throws InterruptedException {
|
||||
public static void printValue(Context ctx, Object val) {
|
||||
printValue(ctx, val, new HashSet<>(), 0);
|
||||
}
|
||||
public static void printError(RuntimeException err, String prefix) throws InterruptedException {
|
||||
public static void printError(RuntimeException err, String prefix) {
|
||||
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);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public interface Awaitable<T> {
|
||||
T await() throws FinishedException, InterruptedException;
|
||||
T await() throws FinishedException;
|
||||
|
||||
default Observable<T> toObservable() {
|
||||
return sub -> {
|
||||
@@ -10,9 +12,7 @@ public interface Awaitable<T> {
|
||||
sub.next(await());
|
||||
sub.finish();
|
||||
}
|
||||
catch (InterruptedException | FinishedException e) {
|
||||
sub.finish();
|
||||
}
|
||||
catch (InterruptException | FinishedException e) { sub.finish(); }
|
||||
catch (RuntimeException e) {
|
||||
sub.error(e);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class DataNotifier<T> implements Awaitable<T> {
|
||||
isErr = false;
|
||||
notifier.next();
|
||||
}
|
||||
public T await() throws InterruptedException {
|
||||
public T await() {
|
||||
notifier.await();
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class Notifier {
|
||||
private boolean ok = false;
|
||||
|
||||
@@ -7,8 +9,11 @@ public class Notifier {
|
||||
ok = true;
|
||||
notifyAll();
|
||||
}
|
||||
public synchronized void await() throws InterruptedException {
|
||||
while (!ok) wait();
|
||||
ok = false;
|
||||
public synchronized void await() {
|
||||
try {
|
||||
while (!ok) wait();
|
||||
ok = false;
|
||||
}
|
||||
catch (InterruptedException e) { throw new InterruptException(e); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> stackTrace = new ArrayList<>();
|
||||
|
||||
public EngineException add(String name, Location location) {
|
||||
@@ -28,12 +31,13 @@ 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;
|
||||
}
|
||||
|
||||
public String toString(Context ctx) throws InterruptedException {
|
||||
public String toString(Context ctx) {
|
||||
var ss = new StringBuilder();
|
||||
try {
|
||||
ss.append(Values.toString(ctx, value)).append('\n');
|
||||
@@ -41,10 +45,10 @@ public class EngineException extends RuntimeException {
|
||||
catch (EngineException e) {
|
||||
ss.append("[Error while stringifying]\n");
|
||||
}
|
||||
// for (var line : stackTrace) {
|
||||
// ss.append(" ").append(line).append('\n');
|
||||
// }
|
||||
// if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
for (var line : stackTrace) {
|
||||
ss.append(" ").append(line).append('\n');
|
||||
}
|
||||
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
ss.deleteCharAt(ss.length() - 1);
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
public class InterruptException extends RuntimeException {
|
||||
public InterruptException() { }
|
||||
public InterruptException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
public class UncheckedException extends RuntimeException {
|
||||
public UncheckedException(Throwable err) {
|
||||
super(err);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UncheckedIOException extends RuntimeException {
|
||||
public UncheckedIOException(IOException e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,12 @@ import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||
|
||||
public class NativeWrapperProvider implements WrappersProvider {
|
||||
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
|
||||
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
|
||||
private final HashMap<Class<?>, ObjectValue> namespaces = new HashMap<>();
|
||||
private final Environment env;
|
||||
|
||||
private static void applyMethods(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
|
||||
@@ -31,7 +33,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
|
||||
var val = target.values.get(name);
|
||||
|
||||
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()));
|
||||
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()), true, true, false);
|
||||
|
||||
((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg()));
|
||||
}
|
||||
@@ -51,7 +53,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
else getter = new OverloadFunction("get " + name);
|
||||
|
||||
getter.add(Overload.fromMethod(method, get.thisArg()));
|
||||
target.defineProperty(null, name, getter, setter, true, true);
|
||||
target.defineProperty(null, name, getter, setter, true, false);
|
||||
}
|
||||
if (set != null) {
|
||||
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
|
||||
@@ -68,7 +70,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
else setter = new OverloadFunction("set " + name);
|
||||
|
||||
setter.add(Overload.fromMethod(method, set.thisArg()));
|
||||
target.defineProperty(null, name, getter, setter, true, true);
|
||||
target.defineProperty(null, name, getter, setter, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +121,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.PROTOTYPE) continue;
|
||||
try { overload.invoke(null, ctx, res); }
|
||||
catch (ReflectiveOperationException e) { e.printStackTrace(); }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
|
||||
applyMethods(ctx, true, res, clazz);
|
||||
@@ -135,7 +137,11 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
* @param clazz The class for which a constructor should be generated
|
||||
*/
|
||||
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
|
||||
FunctionValue func = new OverloadFunction(clazz.getName());
|
||||
var name = clazz.getName();
|
||||
var classNat = clazz.getAnnotation(Native.class);
|
||||
if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim();
|
||||
|
||||
FunctionValue func = new OverloadFunction(name);
|
||||
|
||||
for (var overload : clazz.getDeclaredConstructors()) {
|
||||
var nat = overload.getAnnotation(Native.class);
|
||||
@@ -151,7 +157,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.CONSTRUCTOR) continue;
|
||||
try { overload.invoke(null, ctx, func); }
|
||||
catch (ReflectiveOperationException e) { e.printStackTrace(); }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
|
||||
if (((OverloadFunction)func).overloads.size() == 0) {
|
||||
@@ -179,7 +185,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.NAMESPACE) continue;
|
||||
try { overload.invoke(null, ctx, res); }
|
||||
catch (ReflectiveOperationException e) { e.printStackTrace(); }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
|
||||
applyMethods(ctx, false, res, clazz);
|
||||
@@ -209,8 +215,8 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
if (constr == null) constr = makeConstructor(env, clazz);
|
||||
if (proto == null) proto = makeProto(env, clazz);
|
||||
|
||||
proto.values.put("constructor", constr);
|
||||
constr.values.put("prototype", proto);
|
||||
proto.defineProperty(null, "constructor", constr, true, false, false);
|
||||
constr.defineProperty(null, "prototype", proto, true, false, false);
|
||||
|
||||
prototypes.put(clazz, proto);
|
||||
constructors.put(clazz, constr);
|
||||
@@ -229,11 +235,20 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
initType(clazz, constructors.get(clazz), prototypes.get(clazz));
|
||||
return prototypes.get(clazz);
|
||||
}
|
||||
public ObjectValue getNamespace(Class<?> clazz) {
|
||||
if (!namespaces.containsKey(clazz)) namespaces.put(clazz, makeNamespace(env, clazz));
|
||||
return namespaces.get(clazz);
|
||||
}
|
||||
public FunctionValue getConstr(Class<?> clazz) {
|
||||
initType(clazz, constructors.get(clazz), prototypes.get(clazz));
|
||||
return constructors.get(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappersProvider fork(Environment env) {
|
||||
return new NativeWrapperProvider(env);
|
||||
}
|
||||
|
||||
public void setProto(Class<?> clazz, ObjectValue value) {
|
||||
prototypes.put(clazz, value);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@ import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class Overload {
|
||||
public static interface OverloadRunner {
|
||||
Object run(Context ctx, Object thisArg, Object[] args) throws
|
||||
InterruptedException,
|
||||
ReflectiveOperationException,
|
||||
IllegalArgumentException;
|
||||
Object run(Context ctx, Object thisArg, Object[] args) throws ReflectiveOperationException, IllegalArgumentException;
|
||||
}
|
||||
|
||||
public final OverloadRunner runner;
|
||||
@@ -47,9 +44,11 @@ public class Overload {
|
||||
public static Overload setterFromField(Field field) {
|
||||
if (Modifier.isFinal(field.getModifiers())) return null;
|
||||
return new Overload(
|
||||
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false, false,
|
||||
(ctx, th, args) -> {
|
||||
field.set(th, args[0]); return null;
|
||||
}, false, false,
|
||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||
new Class[0]
|
||||
new Class[] { field.getType() }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,12 @@ import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class OverloadFunction extends FunctionValue {
|
||||
public final List<Overload> overloads = new ArrayList<>();
|
||||
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
loop: for (var overload : overloads) {
|
||||
Object[] newArgs = new Object[overload.params.length];
|
||||
|
||||
@@ -76,29 +77,26 @@ public class OverloadFunction extends FunctionValue {
|
||||
try {
|
||||
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs));
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
throw EngineException.ofError("The class may not be instantiated.");
|
||||
}
|
||||
catch (IllegalAccessException | IllegalArgumentException e) {
|
||||
continue;
|
||||
}
|
||||
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, "<internal>");
|
||||
var loc = Location.INTERNAL;
|
||||
if (e.getTargetException() instanceof EngineException) {
|
||||
throw ((EngineException)e.getTargetException()).add(name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof NullPointerException) {
|
||||
e.printStackTrace();
|
||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
||||
throw new InterruptException();
|
||||
}
|
||||
else {
|
||||
throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc);
|
||||
}
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "<internal>"));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
148
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
148
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
@@ -1,86 +1,88 @@
|
||||
// TODO: load this in java
|
||||
var ts = require('./ts');
|
||||
log("Loaded typescript!");
|
||||
(function (_arguments) {
|
||||
var ts = _arguments[0];
|
||||
var src = '', lib = _arguments[2].concat([ 'declare const exit: never; declare const go: any;' ]).join(''), decls = '', version = 0;
|
||||
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
|
||||
|
||||
var src = '', lib = libs.concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0;
|
||||
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
|
||||
var settings = {
|
||||
outDir: "/out",
|
||||
declarationDir: "/out",
|
||||
target: ts.ScriptTarget.ES5,
|
||||
lib: [ ],
|
||||
module: ts.ModuleKind.None,
|
||||
declaration: true,
|
||||
stripInternal: true,
|
||||
downlevelIteration: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
experimentalDecorators: true,
|
||||
strict: true,
|
||||
};
|
||||
|
||||
var settings = {
|
||||
outDir: "/out",
|
||||
declarationDir: "/out",
|
||||
target: ts.ScriptTarget.ES5,
|
||||
lib: [ ],
|
||||
module: ts.ModuleKind.None,
|
||||
declaration: true,
|
||||
stripInternal: true,
|
||||
downlevelIteration: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
experimentalDecorators: true,
|
||||
strict: true,
|
||||
};
|
||||
var reg = ts.createDocumentRegistry();
|
||||
var service = ts.createLanguageService({
|
||||
getCurrentDirectory: function() { return "/"; },
|
||||
getDefaultLibFileName: function() { return "/lib.d.ts"; },
|
||||
getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; },
|
||||
getCompilationSettings: function () { return settings; },
|
||||
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
|
||||
|
||||
var reg = ts.createDocumentRegistry();
|
||||
var service = ts.createLanguageService({
|
||||
getCurrentDirectory: function() { return "/"; },
|
||||
getDefaultLibFileName: function() { return "/lib_.d.ts"; },
|
||||
getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; },
|
||||
getCompilationSettings: function () { return settings; },
|
||||
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
|
||||
getScriptSnapshot: function(filename) {
|
||||
if (filename === "/lib.d.ts") return libSnapshot;
|
||||
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
|
||||
if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
|
||||
throw new Error("File '" + filename + "' doesn't exist.");
|
||||
},
|
||||
getScriptVersion: function (filename) {
|
||||
if (filename === "/lib.d.ts") return 0;
|
||||
else return version;
|
||||
},
|
||||
}, reg);
|
||||
|
||||
getScriptSnapshot: function(filename) {
|
||||
if (filename === "/lib.d.ts") return libSnapshot;
|
||||
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
|
||||
if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
|
||||
throw new Error("File '" + filename + "' doesn't exist.");
|
||||
},
|
||||
getScriptVersion: function (filename) {
|
||||
if (filename === "/lib.d.ts") return 0;
|
||||
else return version;
|
||||
},
|
||||
}, reg);
|
||||
service.getEmitOutput("/lib.d.ts");
|
||||
log("Loaded libraries!");
|
||||
|
||||
service.getEmitOutput('/lib.d.ts');
|
||||
log('Loaded libraries!');
|
||||
function compile(code, filename) {
|
||||
src = code;
|
||||
version++;
|
||||
|
||||
function compile(filename, code) {
|
||||
src = code, version++;
|
||||
var emit = service.getEmitOutput("/src.ts");
|
||||
|
||||
var emit = service.getEmitOutput("/src.ts");
|
||||
var diagnostics = []
|
||||
.concat(service.getCompilerOptionsDiagnostics())
|
||||
.concat(service.getSyntacticDiagnostics("/src.ts"))
|
||||
.concat(service.getSemanticDiagnostics("/src.ts"))
|
||||
.map(function (diagnostic) {
|
||||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
||||
if (diagnostic.file) {
|
||||
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||
var file = diagnostic.file.fileName.substring(1);
|
||||
if (file === "src.ts") file = filename;
|
||||
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
|
||||
}
|
||||
else return "Error: " + message;
|
||||
});
|
||||
|
||||
var diagnostics = []
|
||||
.concat(service.getCompilerOptionsDiagnostics())
|
||||
.concat(service.getSyntacticDiagnostics("/src.ts"))
|
||||
.concat(service.getSemanticDiagnostics("/src.ts"))
|
||||
.map(function (diagnostic) {
|
||||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
||||
if (diagnostic.file) {
|
||||
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||
var file = diagnostic.file.fileName.substring(1);
|
||||
if (file === "src.ts") file = filename;
|
||||
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
|
||||
}
|
||||
else return "Error: " + message;
|
||||
});
|
||||
if (diagnostics.length > 0) {
|
||||
throw new SyntaxError(diagnostics.join("\n"));
|
||||
}
|
||||
|
||||
if (diagnostics.length > 0) {
|
||||
throw new SyntaxError(diagnostics.join('\n'));
|
||||
return {
|
||||
result: emit.outputFiles[0].text,
|
||||
declaration: emit.outputFiles[1].text
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
result: emit.outputFiles[0].text,
|
||||
declaration: emit.outputFiles[1].text
|
||||
};
|
||||
}
|
||||
_arguments[1].compile = function (filename, code) {
|
||||
var res = compile(filename, code);
|
||||
|
||||
init(function (filename, code) {
|
||||
var res = compile(filename, code);
|
||||
|
||||
return [
|
||||
res.result,
|
||||
function(func, th, args) {
|
||||
var val = func.apply(th, args);
|
||||
decls += res.declaration;
|
||||
return val;
|
||||
return {
|
||||
source: res.result,
|
||||
runner: function(func) {
|
||||
return function() {
|
||||
var val = func.apply(this, arguments);
|
||||
decls += res.declaration;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
})(arguments);
|
||||
|
||||
18
src/me/topchetoeu/jscript/js/ts.js
Normal file
18
src/me/topchetoeu/jscript/js/ts.js
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user