Compare commits

...

38 Commits

Author SHA1 Message Date
fdfa8d7713 refactor: some minor fixes, rewrite README example 2023-11-06 10:55:38 +02:00
f5d1287948 fix: some annoying bugs, as well as splice 2023-11-06 00:55:30 +02:00
15f4278cb1 fix: build with java 17 2023-11-05 21:00:39 +02:00
df8465cb49 Merge pull request #8 from TopchetoEU/TopchetoEU/tests
Integrate typescript
2023-11-05 20:32:42 +02:00
e3104c223c fix: remove some unnececeary logs 2023-11-05 20:29:21 +02:00
1d0bae3de8 feat: include typescript code in source code 2023-11-05 20:27:23 +02:00
b66acd3089 fix: several more fixes 2023-11-05 19:44:44 +02:00
e326847287 feat: send value stack to debug client 2023-11-05 19:44:35 +02:00
26591d6631 fix: lazy operators incorrectly pop values from stack 2023-11-05 19:44:08 +02:00
af31b1ab79 fix: several small fixes 2023-11-05 19:43:53 +02:00
f885d4349f refactor: fix some bad code >:( 2023-11-05 19:43:28 +02:00
d57044acb7 fix: several bug fixes to help with typescript support 2023-11-05 12:44:29 +02:00
7df4e3b03f fix: oops 2023-11-04 11:43:57 +02:00
ed1009ab69 refactor: some code restructuring in the debugging 2023-11-04 11:42:06 +02:00
f856cdf37e fix: messages larger than 64KB are now fragmented properly 2023-11-04 11:41:31 +02:00
4f82574b8c fix: various small behavioural issues
fix: pesky try-catch logic
2023-11-04 11:40:50 +02:00
0ae24148d8 feat: write some tests 2023-11-04 11:38:48 +02:00
ac128d17f4 feat: implement Array.reduce
fix: native functions are now named
2023-11-04 11:38:29 +02:00
6508f15bb0 refactor: remove typescript source code from repo (for now) 2023-11-04 11:37:13 +02:00
69f93b4f87 refactor: make filenames more consistent 2023-11-04 11:36:36 +02:00
b675411925 fix: a lot of minor bugs 2023-10-29 23:47:48 +02:00
d1e93c2088 Merge branch 'master' of https://github.com/TopchetoEU/java-jscript 2023-10-29 12:33:18 +02:00
942db54546 feat: improve vscode debugging compatibility 2023-10-29 12:30:43 +02:00
d20df66982 feat: improve vscode debugging compatibility 2023-10-29 12:25:33 +02:00
16a9e5d761 Merge pull request #7 from TopchetoEU/TopcehtoEU/debugging
Debugging support
2023-10-28 17:11:55 +03:00
dc9d84a370 chore: nothing of use 2023-10-28 17:10:27 +03:00
edb71daef4 feat: fully implement local and capture scope object wrappers
feat: implement object sending and receiving
2023-10-28 16:58:33 +03:00
4b84309df6 feat: complete steptrough and breakpoints in debugger 2023-10-27 15:12:14 +03:00
d2d9fa9738 fix: exit now works 2023-10-07 14:08:47 +03:00
a4e5f7f471 refactor: clean up useless catch blocks 2023-10-07 13:55:44 +03:00
cc044374ba refactor: replace InterruptedException with unchecked alternative 2023-10-07 13:42:55 +03:00
517e3e6657 refactor: clean up context and stack data 2023-10-07 13:07:07 +03:00
926b9c17d8 feat: readd JSON to lib 2023-10-06 18:42:35 +03:00
fc705e7383 feat: readd date internals 2023-10-06 12:03:33 +03:00
a17ec737b7 refactor: rename polyfills to libs 2023-10-06 11:57:29 +03:00
952a4d631d fix: keep order of object fields 2023-10-04 22:06:49 +03:00
005610ca40 Merge pull request #6 from TopchetoEU/TopchetoEU/function-optimizations
Cache function bodies
2023-10-04 21:38:29 +03:00
9743a6c078 fix: cache function bodies 2023-10-04 21:34:33 +03:00
138 changed files with 6571 additions and 2298 deletions

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

30
src/assets/index.html Normal file
View 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

File diff suppressed because it is too large Load Diff

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

View File

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

View File

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

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

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

View File

@@ -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,6 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement {
public final Statement[] statements;
public Location end;
@Override
public void declare(ScopeRecord varsScope) {
@@ -21,28 +21,31 @@ public class CompoundStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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.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();
@@ -60,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;

View File

@@ -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,7 +8,7 @@ public class DiscardStatement extends Statement {
public final Statement value;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, false);
}

View File

@@ -0,0 +1,17 @@
package me.topchetoeu.jscript.compilation;
public class FunctionBody {
public final Instruction[] instructions;
public final String[] captureNames, localNames;
public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) {
this.instructions = instructions;
this.captureNames = captureNames;
this.localNames = localNames;
}
public FunctionBody(Instruction[] instructions) {
this.instructions = instructions;
this.captureNames = new String[0];
this.localNames = new String[0];
}
}

View File

@@ -90,16 +90,11 @@ public class Instruction {
public final Type type;
public final Object[] params;
public Location location;
public boolean debugged;
public Instruction locate(Location loc) {
this.location = loc;
return this;
}
public Instruction setDebug(boolean debug) {
debugged = debug;
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(int i) {
@@ -152,14 +147,6 @@ public class Instruction {
public static Instruction debug() {
return new Instruction(null, Type.NOP, "debug");
}
public static Instruction debugVarNames(String[] names) {
var args = new Object[names.length + 1];
args[0] = "dbg_vars";
System.arraycopy(names, 0, args, 1, names.length);
return new Instruction(null, Type.NOP, args);
}
public static Instruction nop(Object ...params) {
for (var param : params) {
@@ -214,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];
@@ -264,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() {

View File

@@ -1,7 +1,5 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -9,14 +7,14 @@ public abstract class Statement {
private Location _loc;
public boolean pure() { return false; }
public abstract void compile(List<Instruction> target, ScopeRecord scope, boolean pollute);
public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute);
public void declare(ScopeRecord varsScope) { }
public Statement optimize() { return this; }
public void compileWithDebug(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) {
int start = target.size();
compile(target, scope, pollute);
if (target.size() != start) target.get(start).setDebug(true);
if (target.size() != start) target.setDebug(start);
}
public Location loc() { return _loc; }

View File

@@ -10,10 +10,12 @@ public class VariableDeclareStatement extends Statement {
public static class Pair {
public final String name;
public final Statement value;
public final Location location;
public Pair(String name, Statement value) {
public Pair(String name, Statement value, Location location) {
this.name = name;
this.value = value;
this.location = location;
}
}
@@ -26,20 +28,24 @@ public class VariableDeclareStatement extends Statement {
}
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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.compile(target, scope, true);
target.add(Instruction.storeVar(key).locate(loc()));
target.add(Instruction.storeVar(key).locate(entry.location));
}
if (target.size() != start) target.setDebug(start);
}
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));

View File

@@ -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;
@@ -14,7 +13,7 @@ public class ArrayStatement extends Statement {
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadArr(statements.length).locate(loc()));
var i = 0;
for (var el : statements) {

View File

@@ -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,7 +10,7 @@ public class BreakStatement extends Statement {
public final String label;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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()));
}

View File

@@ -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,7 +10,7 @@ public class ContinueStatement extends Statement {
public final String label;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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()));
}

View File

@@ -1,15 +1,14 @@
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 void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.debug().locate(loc()));
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
}

View File

@@ -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,7 +11,7 @@ public class DeleteStatement extends Statement {
public final Statement value;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, true);
key.compile(target, scope, true);

View File

@@ -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;
@@ -20,7 +19,7 @@ public class DoWhileStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (condition instanceof ConstantStatement) {
int start = target.size();
body.compile(target, scope, false);

View File

@@ -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,6 +12,7 @@ public class ForInStatement extends Statement {
public final boolean isDeclaration;
public final Statement varValue, object, body;
public final String label;
public final Location varLocation;
@Override
public void declare(ScopeRecord globScope) {
@@ -21,8 +21,10 @@ public class ForInStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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) {
@@ -30,45 +32,37 @@ public class ForInStatement extends Statement {
target.add(Instruction.storeVar(scope.getKey(varName)));
}
object.compile(target, scope, true);
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.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()));
if (pollute) target.add(Instruction.loadValue(null).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;

View File

@@ -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;
@@ -20,7 +19,7 @@ public class ForStatement extends Statement {
body.declare(globScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
declaration.compile(target, scope, false);
if (condition instanceof ConstantStatement) {

View File

@@ -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;
@@ -21,7 +20,7 @@ public class IfStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (condition instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)condition).value)) {
if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute);

View File

@@ -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,7 +10,7 @@ public class ReturnStatement extends Statement {
public final Statement value;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
else value.compile(target, scope, true);
target.add(Instruction.ret().locate(loc()));

View File

@@ -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;
@@ -32,7 +32,7 @@ public class SwitchStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var caseMap = new HashMap<Integer, Integer>();
var stmIndexMap = new HashMap<Integer, Integer>();
@@ -43,7 +43,7 @@ public class SwitchStatement extends Statement {
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();
@@ -68,7 +68,8 @@ public class SwitchStatement extends Statement {
var loc = target.get(el.getKey()).location;
var i = stmIndexMap.get(el.getValue());
if (i == null) i = target.size();
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true));
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc));
target.setDebug(el.getKey());
}
target.add(Instruction.discard().locate(loc()));

View File

@@ -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,7 +10,7 @@ public class ThrowStatement extends Statement {
public final Statement value;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, true);
target.add(Instruction.throwInstr().locate(loc()));
}

View File

@@ -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;
@@ -23,7 +22,7 @@ public class TryStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop());
int start = target.size(), tryN, catchN = -1, finN = -1;

View File

@@ -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;
@@ -21,7 +20,7 @@ public class WhileStatement extends Statement {
body.declare(globScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (condition instanceof ConstantStatement) {
if (Values.toBoolean(((ConstantStatement)condition).value)) {
int start = target.size();
@@ -68,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))) {

View File

@@ -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,18 +11,19 @@ public class CallStatement extends Statement {
public final Statement[] args;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, scope, true, true);
}
else {
target.add(Instruction.loadValue(null).locate(loc()));
target.add(Instruction.loadValue(null).locate(loc()));
func.compile(target, scope, true);
}
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()));
}

View File

@@ -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,9 +14,13 @@ public class ChangeStatement extends Statement {
public final boolean postfix;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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) {

View File

@@ -0,0 +1,38 @@
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;
}
}

View File

@@ -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;
@@ -14,7 +13,7 @@ public class ConstantStatement extends Statement {
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadValue(value).locate(loc()));
}

View File

@@ -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,6 +17,8 @@ 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; }
@@ -23,7 +27,7 @@ public class FunctionStatement extends Statement {
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") ) {
@@ -36,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])){
@@ -48,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, false);
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,9 +94,10 @@ public class FunctionStatement extends Statement {
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
}
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, null, false);
if (!pollute) target.add(Instruction.discard().locate(loc()));
}

View File

@@ -1,9 +1,8 @@
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;
@@ -12,7 +11,7 @@ public class GlobalThisStatement extends Statement {
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadGlob().locate(loc()));
}

View File

@@ -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.Operation;
@@ -15,7 +14,7 @@ public class IndexAssignStatement extends Statement {
public final Operation operation;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (operation != null) {
object.compile(target, scope, true);
index.compile(target, scope, true);
@@ -25,14 +24,16 @@ public class IndexAssignStatement extends Statement {
value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
target.add(Instruction.storeMember(pollute).locate(loc()));
target.setDebug();
}
else {
object.compile(target, scope, true);
index.compile(target, scope, true);
value.compile(target, scope, true);
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
target.add(Instruction.storeMember(pollute).locate(loc()));
target.setDebug();
}
}

View File

@@ -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;
@@ -20,22 +19,22 @@ public class IndexStatement extends AssignableStatement {
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, boolean pollute) {
int start = 0;
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.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, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, false, pollute);
}

View File

@@ -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;
@@ -17,7 +16,7 @@ public class LazyAndStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) {
first.compile(target, scope, pollute);
@@ -30,7 +29,7 @@ public class LazyAndStatement extends Statement {
if (pollute) target.add(Instruction.dup().locate(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));
if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
}

View File

@@ -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;
@@ -17,7 +16,7 @@ public class LazyOrStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (first instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)first).value)) {
second.compile(target, scope, pollute);
@@ -30,7 +29,7 @@ public class LazyOrStatement extends Statement {
if (pollute) target.add(Instruction.dup().locate(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));
if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
}

View File

@@ -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,12 +11,13 @@ public class NewStatement extends Statement {
public final Statement[] args;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
func.compile(target, scope, true);
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) {

View File

@@ -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,7 +15,7 @@ public class ObjectStatement extends Statement {
public final Map<Object, FunctionStatement> setters;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadObj().locate(loc()));
for (var el : map.entrySet()) {

View File

@@ -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,7 +15,7 @@ public class OperationStatement extends Statement {
public final Operation operation;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var arg : args) {
arg.compile(target, scope, true);
}
@@ -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);

View File

@@ -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;
@@ -14,7 +13,7 @@ public class RegexStatement extends Statement {
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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()));
}

View File

@@ -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.ArrayStatement;
@@ -16,7 +15,7 @@ public class TypeofStatement extends Statement {
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value instanceof VariableStatement) {
var i = scope.getKey(((VariableStatement)value).name);
if (i instanceof String) {

View File

@@ -1,9 +1,8 @@
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.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -14,22 +13,20 @@ public class VariableAssignStatement extends Statement {
public final Operation operation;
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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 (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeVar(i, false).locate(loc()));
target.add(Instruction.storeVar(i, pollute).locate(loc()));
}
else {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compile(target, scope, true);
target.add(Instruction.storeVar(i, false).locate(loc()));
target.add(Instruction.storeVar(i, pollute).locate(loc()));
}
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
}
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {

View File

@@ -1,9 +1,8 @@
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;
@@ -14,7 +13,7 @@ public class VariableIndexStatement extends Statement {
public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (pollute) target.add(Instruction.loadVar(index).locate(loc()));
}

View File

@@ -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;
@@ -21,7 +20,7 @@ public class VariableStatement extends AssignableStatement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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()));

View File

@@ -1,17 +1,16 @@
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 void compile(List<Instruction> target, ScopeRecord scope, boolean pollute) {
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()));
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

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

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

View File

@@ -0,0 +1,6 @@
package me.topchetoeu.jscript.engine.debug;
public interface Debugger extends DebugHandler, DebugController {
void connect();
void disconnect();
}

View File

@@ -0,0 +1,5 @@
package me.topchetoeu.jscript.engine.debug;
public interface DebuggerProvider {
Debugger getDebugger(WebSocket socket, HttpRequest req);
}

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

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

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

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

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

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

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

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

View File

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

View File

@@ -4,6 +4,7 @@ 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;
@@ -22,52 +23,37 @@ public class Runners {
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();
@@ -82,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();
@@ -97,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;
@@ -155,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++;
@@ -170,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;
}
@@ -182,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];
@@ -191,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();
@@ -215,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;
}
@@ -230,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();
@@ -240,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++;
@@ -261,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;
@@ -269,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;
@@ -278,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();
@@ -286,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;
}
@@ -303,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();
@@ -327,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];
@@ -338,7 +322,7 @@ public class Runners {
return NO_RETURN;
}
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
package me.topchetoeu.jscript.engine.values;
import java.util.HashMap;
import java.util.List;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class ScopeValue extends ObjectValue {
public final ValueVariable[] variables;
public final HashMap<String, Integer> names = new HashMap<>();
@Override
protected Object getField(Context ctx, Object key) {
if (names.containsKey(key)) return variables[names.get(key)].get(ctx);
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if (names.containsKey(key)) {
variables[names.get(key)].set(ctx, val);
return true;
}
return super.setField(ctx, key, val);
}
@Override
protected void deleteField(Context ctx, Object key) {
if (names.containsKey(key)) return;
super.deleteField(ctx, key);
}
@Override
protected boolean hasField(Context ctx, Object key) {
if (names.containsKey(key)) return true;
return super.hasField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
res.addAll(names.keySet());
return res;
}
public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) {
this.names.put(names[i], i);
this.nonConfigurableSet.add(names[i]);
}
}
}

View File

@@ -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) {
@@ -88,7 +89,7 @@ public class Values {
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;
@@ -96,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.");
@@ -113,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";
@@ -146,47 +142,47 @@ public class Values {
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);
@@ -195,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);
@@ -207,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);
@@ -219,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]);
@@ -260,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.");
@@ -280,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.");
@@ -290,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);
@@ -308,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);
@@ -352,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();
@@ -370,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) {
@@ -395,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
@@ -446,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) {
@@ -520,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();
@@ -536,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));
@@ -551,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;
@@ -649,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 {
@@ -667,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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package me.topchetoeu.jscript.exceptions;
public class InterruptException extends RuntimeException {
public InterruptException() { }
public InterruptException(Throwable e) {
super(e);
}
}

View File

@@ -0,0 +1,7 @@
package me.topchetoeu.jscript.exceptions;
public class UncheckedException extends RuntimeException {
public UncheckedException(Throwable err) {
super(err);
}
}

View File

@@ -0,0 +1,9 @@
package me.topchetoeu.jscript.exceptions;
import java.io.IOException;
public class UncheckedIOException extends RuntimeException {
public UncheckedIOException(IOException e) {
super(e);
}
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,15 @@
package me.topchetoeu.jscript.json;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.parsing.Operator;
import me.topchetoeu.jscript.parsing.ParseRes;
@@ -10,20 +17,80 @@ import me.topchetoeu.jscript.parsing.Parsing;
import me.topchetoeu.jscript.parsing.Token;
public class JSON {
public static Object toJs(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement fromJs(Context ctx, Object val, HashSet<Object> prev) {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
public static JSONElement fromJs(Context ctx, Object val) {
return fromJs(ctx, val, new HashSet<>());
}
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i);
}
public static ParseRes<String> parseString(String filename, List<Token> tokens, int i) {
public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Double> parseNumber(String filename, List<Token> tokens, int i) {
public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
if (minus) i++;
var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n);
if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
else return res.transform();
}
public static ParseRes<Boolean> parseBool(String filename, List<Token> tokens, int i) {
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return ParseRes.failed();
@@ -32,7 +99,7 @@ public class JSON {
else return ParseRes.failed();
}
public static ParseRes<?> parseValue(String filename, List<Token> tokens, int i) {
public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
return ParseRes.any(
parseString(filename, tokens, i),
parseNumber(filename, tokens, i),
@@ -42,7 +109,7 @@ public class JSON {
);
}
public static ParseRes<JSONMap> parseMap(String filename, List<Token> tokens, int i) {
public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@@ -82,7 +149,7 @@ public class JSON {
return ParseRes.res(values, n);
}
public static ParseRes<JSONList> parseList(String filename, List<Token> tokens, int i) {
public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
@@ -109,7 +176,8 @@ public class JSON {
return ParseRes.res(values, n);
}
public static JSONElement parse(String filename, String raw) {
public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error);
@@ -120,7 +188,28 @@ public class JSON {
if (el.isNumber()) return Double.toString(el.number());
if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null";
if (el.isString()) return "\"" + el.string().replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
if (el.isString()) {
var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : el.string().toCharArray()) {
if (c < 32 || c >= 127) {
res
.append("\\u")
.append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]);
}
else if (c == '\\')
res.append("\\\\");
else if (c == '"')
res.append("\\\"");
else res.append(c);
}
return res.append('"').toString();
}
if (el.isList()) {
var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) {

View File

@@ -65,10 +65,22 @@ public class JSONElement {
return (double)value;
}
public boolean bool() {
if (!isNumber()) throw new IllegalStateException("Element is not a boolean.");
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value;
}
@Override
public String toString() {
if (isMap()) return "{...}";
if (isList()) return "[...]";
if (isString()) return (String)value;
if (isString()) return (String)value;
if (isNumber()) return (double)value + "";
if (isBoolean()) return (boolean)value + "";
if (isNull()) return "null";
return "";
}
private JSONElement(Type type, Object val) {
this.type = type;
this.value = val;

View File

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

View File

@@ -51,7 +51,7 @@ public class JSONMap implements Map<String, JSONElement> {
public JSONMap map(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.map();
}
public JSONMap map(String path, JSONMap defaultVal) {
@@ -63,7 +63,7 @@ public class JSONMap implements Map<String, JSONElement> {
public JSONList list(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.list();
}
public JSONList list(String path, JSONList defaultVal) {
@@ -75,7 +75,7 @@ public class JSONMap implements Map<String, JSONElement> {
public String string(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.string();
}
public String string(String path, String defaultVal) {
@@ -87,7 +87,7 @@ public class JSONMap implements Map<String, JSONElement> {
public double number(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.number();
}
public double number(String path, double defaultVal) {
@@ -99,7 +99,7 @@ public class JSONMap implements Map<String, JSONElement> {
public boolean bool(String path) {
var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path));
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.bool();
}
public boolean bool(String path, boolean defaultVal) {

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.polyfills;
package me.topchetoeu.jscript.lib;
import java.util.Iterator;
import java.util.Stack;
@@ -16,18 +16,18 @@ import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit;
import me.topchetoeu.jscript.interop.NativeSetter;
public class ArrayPolyfill {
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException {
@Native("Array") public class ArrayLib {
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) {
return thisArg.size();
}
@NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) throws InterruptedException {
@NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) {
thisArg.setSize(len);
}
@Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) throws InterruptedException {
@Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, thisArg);
}
@Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) throws InterruptedException {
@Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() {
private int i = 0;
@@ -42,7 +42,7 @@ public class ArrayPolyfill {
}
});
}
@Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) throws InterruptedException {
@Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() {
private int i = 0;
@@ -59,17 +59,17 @@ public class ArrayPolyfill {
}
@Native(value = "@@Symbol.iterator", thisArg = true)
public static ObjectValue iterator(Context ctx, ArrayValue thisArg) throws InterruptedException {
public static ObjectValue iterator(Context ctx, ArrayValue thisArg) {
return values(ctx, thisArg);
}
@Native(value = "@@Symbol.asyncIterator", thisArg = true)
public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) throws InterruptedException {
public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) {
return values(ctx, thisArg);
}
@Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) {
// TODO: Fully implement with non-array spreadable objects
var size = 0;
var size = thisArg.size();
for (int i = 0; i < others.length; i++) {
if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size();
@@ -77,8 +77,9 @@ public class ArrayPolyfill {
}
var res = new ArrayValue(size);
thisArg.copyTo(ctx, res, 0, 0, thisArg.size());
for (int i = 0, j = 0; i < others.length; i++) {
for (int i = 0, j = thisArg.size(); i < others.length; i++) {
if (others[i] instanceof ArrayValue) {
int n = ((ArrayValue)others[i]).size();
((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n);
@@ -92,24 +93,14 @@ public class ArrayPolyfill {
return res;
}
@Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) throws InterruptedException {
try {
arr.sort((a, b) -> {
try {
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b));
if (res < 0) return -1;
if (res > 0) return 1;
return 0;
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
catch (RuntimeException e) {
if (e.getCause() instanceof InterruptedException) throw (InterruptedException)e.getCause();
else throw e;
}
@Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) {
arr.sort((a, b) -> {
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b));
if (res < 0) return -1;
if (res > 0) return 1;
return 0;
});
return arr;
}
private static int normalizeI(int len, int i, boolean clamp) {
@@ -121,7 +112,7 @@ public class ArrayPolyfill {
return i;
}
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) {
start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true);
@@ -131,21 +122,21 @@ public class ArrayPolyfill {
return arr;
}
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) {
return fill(ctx, arr, val, start, arr.size());
}
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) {
return fill(ctx, arr, val, 0, arr.size());
}
@Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
for (var i = 0; i < arr.size(); i++) {
if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false;
}
return true;
}
@Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
for (var i = 0; i < arr.size(); i++) {
if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true;
}
@@ -153,7 +144,7 @@ public class ArrayPolyfill {
return false;
}
@Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) {
@@ -161,20 +152,51 @@ public class ArrayPolyfill {
}
return res;
}
@Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) {
if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr));
}
return res;
}
@Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr);
}
}
@Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) throws InterruptedException {
@Native(thisArg = true) public static Object reduce(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
var i = 0;
var res = arr.get(0);
if (args.length > 0) res = args[0];
else for (; !arr.has(i) && i < arr.size(); i++) res = arr.get(i);
for (; i < arr.size(); i++) {
if (arr.has(i)) {
res = func.call(ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Native(thisArg = true) public static Object reduceRight(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
var i = arr.size();
var res = arr.get(0);
if (args.length > 0) res = args[0];
else while (!arr.has(i--) && i >= 0) res = arr.get(i);
for (; i >= 0; i--) {
if (arr.has(i)) {
res = func.call(ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) {
var res = new ArrayValue(arr.size());
var stack = new Stack<Object>();
var depths = new Stack<Integer>();
@@ -197,18 +219,18 @@ public class ArrayPolyfill {
return res;
}
@Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
return flat(ctx, map(ctx, arr, cmp, thisArg), 1);
}
@Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
}
return null;
}
@Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
}
@@ -216,14 +238,14 @@ public class ArrayPolyfill {
return null;
}
@Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
}
return -1;
}
@Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException {
@Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
}
@@ -231,16 +253,16 @@ public class ArrayPolyfill {
return -1;
}
@Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException {
@Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) {
start = normalizeI(arr.size(), start, true);
for (int i = 0; i < arr.size() && i < start; i++) {
for (int i = start; i < arr.size(); i++) {
if (Values.strictEquals(ctx, arr.get(i), val)) return i;
}
return -1;
}
@Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException {
@Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) {
start = normalizeI(arr.size(), start, true);
for (int i = arr.size(); i >= start; i--) {
@@ -250,48 +272,46 @@ public class ArrayPolyfill {
return -1;
}
@Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) throws InterruptedException {
@Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) {
return indexOf(ctx, arr, el, start) >= 0;
}
@Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) throws InterruptedException {
@Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) {
if (arr.size() == 0) return null;
var val = arr.get(arr.size() - 1);
arr.shrink(1);
return val;
}
@Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException {
@Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) {
arr.copyFrom(ctx, values, 0, arr.size(), values.length);
return arr.size();
}
@Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) throws InterruptedException {
@Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) {
if (arr.size() == 0) return null;
var val = arr.get(0);
arr.move(1, 0, arr.size());
arr.shrink(1);
return val;
}
@Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException {
@Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) {
arr.move(0, values.length, arr.size());
arr.copyFrom(ctx, values, 0, 0, values.length);
return arr.size();
}
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, int end) {
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, Object _end) {
start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true);
int end = normalizeI(arr.size(), (int)(_end == null ? arr.size() : Values.toNumber(ctx, _end)), true);
var res = new ArrayValue(end - start);
arr.copyTo(ctx, res, start, 0, end - start);
return res;
}
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start) {
return slice(ctx, arr, start, arr.size());
}
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, int deleteCount, Object ...items) throws InterruptedException {
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, Object _deleteCount, Object ...items) {
start = normalizeI(arr.size(), start, true);
int deleteCount = _deleteCount == null ? arr.size() - 1 : (int)Values.toNumber(ctx, _deleteCount);
deleteCount = normalizeI(arr.size(), deleteCount, true);
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
@@ -304,21 +324,20 @@ public class ArrayPolyfill {
return res;
}
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) throws InterruptedException {
return splice(ctx, arr, start, arr.size() - start);
}
@Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) throws InterruptedException {
@Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) {
return join(ctx, arr, ",");
}
@Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) throws InterruptedException {
@Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) {
var res = new StringBuilder();
var comma = true;
var comma = false;
for (int i = 0; i < arr.size(); i++) {
if (!arr.has(i)) continue;
if (comma) res.append(sep);
comma = false;
comma = true;
var el = arr.get(i);
if (el == null || el == Values.NULL) continue;

Some files were not shown because too many files have changed in this diff Show More