Debugging support #7

Merged
TopchetoEU merged 7 commits from TopcehtoEU/debugging into master 2023-10-28 14:11:55 +00:00
89 changed files with 4766 additions and 919 deletions

View File

@ -10,6 +10,8 @@ const conf = {
version: argv[3] version: argv[3]
}; };
console.log(conf)
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10); if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1); if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);

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,52 @@
package me.topchetoeu.jscript;
import java.io.File;
public class Filename {
public final String protocol;
public final String path;
public String toString() {
return protocol + "://" + path;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
var other = (Filename) obj;
if (protocol == null) {
if (other.protocol != null) return false;
}
else if (!protocol.equals(other.protocol)) return false;
if (path == null) {
if (other.path != null) return false;
}
else if (!path.equals(other.path)) return false;
return true;
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
public Filename(String protocol, String path) {
this.protocol = protocol;
this.path = path;
}
}

View File

@ -1,18 +1,18 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
public class Location { public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(0, 0, "<internal>"); public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "internal"));
private int line; private int line;
private int start; private int start;
private String filename; private Filename filename;
public int line() { return line; } public int line() { return line; }
public int start() { return start; } public int start() { return start; }
public String filename() { return filename; } public Filename filename() { return filename; }
@Override @Override
public String toString() { public String toString() {
return filename + ":" + line + ":" + start; return filename.toString() + ":" + line + ":" + start;
} }
public Location add(int n, boolean clone) { public Location add(int n, boolean clone) {
@ -55,7 +55,18 @@ public class Location {
return true; 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.line = line;
this.start = start; this.start = start;
this.filename = filename; this.filename = filename;

View File

@ -1,81 +1,66 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.net.InetSocketAddress;
import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import me.topchetoeu.jscript.engine.Message; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.lib.Internals;
public class Main { public class Main {
static Thread task; static Thread engineTask, debugTask;
static Engine engine; static Engine engine;
static Environment env; static Environment env;
static int j = 0;
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>() { private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) { public void next(Object data) {
try { Values.printValue(null, data); } Values.printValue(null, data);
catch (InterruptedException e) { }
System.out.println(); System.out.println();
} }
public void error(RuntimeException err) { public void error(RuntimeException err) {
try { Values.printError(err, null); } Values.printError(err, null);
catch (InterruptedException ex) { return; } }
@Override
public void finish() {
engineTask.interrupt();
} }
}; };
public static void main(String args[]) { public static void main(String args[]) {
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
var in = new BufferedReader(new InputStreamReader(System.in));
engine = new Engine(); engine = new Engine();
env = new Environment(null, null, null); env = new Environment(null, null, null);
var exited = new boolean[1]; var exited = new boolean[1];
var server = new DebugServer();
server.targets.put("target", (ws, req) -> SimpleDebugger.get(ws, engine));
engine.pushMsg(false, new Message(engine), new NativeFunction((ctx, thisArg, _a) -> { engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
new Internals().apply(env); new Internals().apply(env);
env.global.define("exit", _ctx -> { env.global.define("exit", _ctx -> {
exited[0] = true; exited[0] = true;
task.interrupt(); throw new InterruptException();
throw new InterruptedException();
}); });
env.global.define("go", _ctx -> { env.global.define("go", _ctx -> {
try { try {
var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js")))); var f = Path.of("do.js");
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
return func.call(_ctx); return func.call(_ctx);
} }
catch (IOException e) { catch (IOException e) {
@ -86,41 +71,34 @@ public class Main {
return null; return null;
}), null); }), null);
task = engine.start(); engineTask = engine.start();
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
var reader = new Thread(() -> { var reader = new Thread(() -> {
try { try {
while (true) { for (var i = 0; ; i++) {
try { try {
var raw = in.readLine(); var raw = Reading.read();
if (raw == null) break; if (raw == null) break;
engine.pushMsg(false, env.context(new Message(engine)), "<stdio>", raw, null).toObservable().once(valuePrinter); valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
} }
catch (EngineException e) { catch (EngineException e) { Values.printError(e, ""); }
try {
System.out.println("Uncaught " + e.toString(null));
}
catch (EngineException ex) {
System.out.println("Uncaught [error while converting to string]");
} }
} }
} catch (IOException e) { return; }
}
catch (IOException e) {
e.printStackTrace();
return;
}
catch (SyntaxException ex) { catch (SyntaxException ex) {
if (exited[0]) return; if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg); System.out.println("Syntax error:" + ex.msg);
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
if (exited[0]) return; if (!exited[0]) {
System.out.println("Internal error ocurred:"); System.out.println("Internal error ocurred:");
ex.printStackTrace(); ex.printStackTrace();
} }
catch (InterruptedException e) { return; } }
if (exited[0]) return; catch (Throwable e) { throw new UncheckedException(e); }
if (exited[0]) debugTask.interrupt();
}); });
reader.setDaemon(true); reader.setDaemon(true);
reader.setName("STD Reader"); reader.setName("STD Reader");

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

@ -1,11 +1,15 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import java.util.Vector; import java.util.Vector;
import me.topchetoeu.jscript.Location;
public class CompileTarget { public class CompileTarget {
public final Vector<Instruction> target = new Vector<>(); public final Vector<Instruction> target = new Vector<>();
public final Map<Long, Instruction[]> functions; public final Map<Long, FunctionBody> functions;
public final TreeSet<Location> breakpoints;
public Instruction add(Instruction instr) { public Instruction add(Instruction instr) {
target.add(instr); target.add(instr);
@ -14,6 +18,12 @@ public class CompileTarget {
public Instruction set(int i, Instruction instr) { public Instruction set(int i, Instruction instr) {
return target.set(i, 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) { public Instruction get(int i) {
return target.get(i); return target.get(i);
} }
@ -21,7 +31,8 @@ public class CompileTarget {
public Instruction[] array() { return target.toArray(Instruction[]::new); } public Instruction[] array() { return target.toArray(Instruction[]::new); }
public CompileTarget(Map<Long, Instruction[]> functions) { public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
this.functions = functions; this.functions = functions;
this.breakpoints = breakpoints;
} }
} }

View File

@ -11,6 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement { public class CompoundStatement extends Statement {
public final Statement[] statements; public final Statement[] statements;
public Location end;
@Override @Override
public void declare(ScopeRecord varsScope) { public void declare(ScopeRecord varsScope) {
@ -25,7 +26,7 @@ public class CompoundStatement extends Statement {
if (stm instanceof FunctionStatement) { if (stm instanceof FunctionStatement) {
int start = target.size(); int start = target.size();
((FunctionStatement)stm).compile(target, scope, null, true); ((FunctionStatement)stm).compile(target, scope, null, true);
target.get(start).setDebug(true); target.setDebug(start);
target.add(Instruction.discard()); target.add(Instruction.discard());
} }
} }
@ -37,6 +38,11 @@ public class CompoundStatement extends Statement {
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false); if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
else stm.compileWithDebug(target, scope, pollute); else stm.compileWithDebug(target, scope, pollute);
} }
if (end != null) {
target.add(Instruction.nop().locate(end));
target.setDebug();
}
} }
@Override @Override
@ -59,6 +65,11 @@ public class CompoundStatement extends Statement {
else return new CompoundStatement(loc(), res.toArray(Statement[]::new)); else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
} }
public CompoundStatement setEnd(Location loc) {
this.end = loc;
return this;
}
public CompoundStatement(Location loc, Statement ...statements) { public CompoundStatement(Location loc, Statement ...statements) {
super(loc); super(loc);
this.statements = statements; this.statements = statements;

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

View File

@ -14,7 +14,7 @@ public abstract class Statement {
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) {
int start = target.size(); int start = target.size();
compile(target, scope, pollute); 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; } public Location loc() { return _loc; }

View File

@ -10,10 +10,12 @@ public class VariableDeclareStatement extends Statement {
public static class Pair { public static class Pair {
public final String name; public final String name;
public final Statement value; public final Statement value;
public final Location location;
public Pair(String name, Statement value) { public Pair(String name, Statement value, Location location) {
this.name = name; this.name = name;
this.value = value; this.value = value;
this.location = location;
} }
} }
@ -30,16 +32,20 @@ public class VariableDeclareStatement extends Statement {
for (var entry : values) { for (var entry : values) {
if (entry.name == null) continue; if (entry.name == null) continue;
var key = scope.getKey(entry.name); var key = scope.getKey(entry.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc())); int start = target.size();
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location));
if (entry.value instanceof FunctionStatement) { if (entry.value instanceof FunctionStatement) {
((FunctionStatement)entry.value).compile(target, scope, entry.name, false); ((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
target.add(Instruction.storeVar(key).locate(loc())); target.add(Instruction.storeVar(key).locate(entry.location));
} }
else if (entry.value != null) { else if (entry.value != null) {
entry.value.compile(target, scope, true); entry.value.compile(target, scope, true);
target.add(Instruction.storeVar(key).locate(loc())); target.add(Instruction.storeVar(key).locate(entry.location));
} }
if (target.size() != start) target.setDebug(start);
} }
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(null).locate(loc()));

View File

@ -68,7 +68,8 @@ public class SwitchStatement extends Statement {
var loc = target.get(el.getKey()).location; var loc = target.get(el.getKey()).location;
var i = stmIndexMap.get(el.getValue()); var i = stmIndexMap.get(el.getValue());
if (i == null) i = target.size(); 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())); target.add(Instruction.discard().locate(loc()));

View File

@ -22,7 +22,8 @@ public class CallStatement extends Statement {
for (var arg : args) arg.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())); if (!pollute) target.add(Instruction.discard().locate(loc()));
} }

View File

@ -5,6 +5,7 @@ import java.util.Random;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
@ -51,7 +52,7 @@ public class FunctionStatement extends Statement {
var subscope = scope.child(); var subscope = scope.child();
int start = target.size(); int start = target.size();
var funcTarget = new CompileTarget(target.functions); var funcTarget = new CompileTarget(target.functions, target.breakpoints);
subscope.define("this"); subscope.define("this");
var argsVar = subscope.define("arguments"); var argsVar = subscope.define("arguments");
@ -69,7 +70,6 @@ public class FunctionStatement extends Statement {
} }
body.declare(subscope); body.declare(subscope);
funcTarget.add(Instruction.debugVarNames(subscope.locals()));
body.compile(funcTarget, subscope, false); body.compile(funcTarget, subscope, false);
funcTarget.add(Instruction.ret().locate(loc())); funcTarget.add(Instruction.ret().locate(loc()));
checkBreakAndCont(funcTarget, start); checkBreakAndCont(funcTarget, start);
@ -77,7 +77,7 @@ public class FunctionStatement extends Statement {
var id = rand.nextLong(); var id = rand.nextLong();
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc())); target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
target.functions.put(id, funcTarget.array()); target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals()));
if (name == null) name = this.name; if (name == null) name = this.name;

View File

@ -24,14 +24,16 @@ public class IndexAssignStatement extends Statement {
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc())); 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 { else {
object.compile(target, scope, true); object.compile(target, scope, true);
index.compile(target, scope, true); index.compile(target, scope, true);
value.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

@ -20,17 +20,17 @@ public class IndexStatement extends AssignableStatement {
return new IndexAssignStatement(loc(), object, index, val, operation); return new IndexAssignStatement(loc(), object, index, val, operation);
} }
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
int start = 0;
object.compile(target, scope, true); object.compile(target, scope, true);
if (dupObj) target.add(Instruction.dup().locate(loc())); if (dupObj) target.add(Instruction.dup().locate(loc()));
if (index instanceof ConstantStatement) { if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
target.setDebug();
return; return;
} }
index.compile(target, scope, true); index.compile(target, scope, true);
target.add(Instruction.loadMember().locate(loc())); target.add(Instruction.loadMember().locate(loc()));
target.get(start).setDebug(true); target.setDebug();
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard().locate(loc()));
} }
@Override @Override

View File

@ -16,7 +16,8 @@ public class NewStatement extends Statement {
for (var arg : args) arg.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) { public NewStatement(Location loc, Statement func, Statement ...args) {

View File

@ -49,13 +49,8 @@ public class OperationStatement extends Statement {
vals[i] = ((ConstantStatement)args[i]).value; vals[i] = ((ConstantStatement)args[i]).value;
} }
try { try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); }
return new ConstantStatement(loc(), Values.operation(null, operation, vals)); catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); }
}
catch (EngineException e) {
return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value));
}
catch (InterruptedException e) { return null; }
} }
return new OperationStatement(loc(), operation, args); return new OperationStatement(loc(), operation, args);

View File

@ -1,27 +1,46 @@
package me.topchetoeu.jscript.engine; 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.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.parsing.Parsing;
public class Context { public class Context {
public final Environment env; private final Stack<Environment> env = new Stack<>();
public final Message message; public final Data data;
public final Engine engine;
public FunctionValue compile(String filename, String raw) throws InterruptedException { public Environment environment() {
var res = Values.toString(this, env.compile.call(this, null, raw, filename)); return env.empty() ? null : env.peek();
return Parsing.compile(message.engine.functions, env, filename, res);
} }
public Context setEnv(Environment env) { public Context pushEnv(Environment env) {
return new Context(env, message); this.env.push(env);
return this;
} }
public Context setMsg(Message msg) { public void popEnv() {
return new Context(env, msg); if (!env.empty()) this.env.pop();
} }
public Context(Environment env, Message msg) { public FunctionValue compile(Filename filename, String raw) {
this.env = env; var src = Values.toString(this, environment().compile.call(this, null, raw, filename));
this.message = msg; var debugger = StackData.getDebugger(this);
var breakpoints = new TreeSet<Location>();
var res = Parsing.compile(engine.functions, breakpoints, environment(), filename, src);
if (debugger != null) debugger.onSource(filename, src, breakpoints);
return res;
}
public Context(Engine engine, Data data) {
this.data = new Data(engine.data);
if (data != null) this.data.addAll(data);
this.engine = engine;
}
public Context(Engine engine) {
this(engine, null);
} }
} }

View File

@ -1,43 +1,52 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Map;
import java.util.Map.Entry;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class Data implements Iterable<Entry<DataKey<?>, ?>> { public class Data {
public final Data parent;
private HashMap<DataKey<Object>, Object> data = new HashMap<>(); private HashMap<DataKey<Object>, Object> data = new HashMap<>();
public Data copy() { public Data copy() {
return new Data().addAll(this); return new Data().addAll(this);
} }
public Data addAll(Iterable<Entry<DataKey<?>, ?>> data) { public Data addAll(Map<DataKey<?>, ?> data) {
for (var el : data) { for (var el : data.entrySet()) {
add((DataKey<Object>)el.getKey(), (Object)el.getValue()); 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; return this;
} }
public <T> T remove(DataKey<T> key) {
return (T)data.remove(key);
}
public <T> Data set(DataKey<T> key, T val) { public <T> Data set(DataKey<T> key, T val) {
if (val == null) data.remove(key); data.put((DataKey<Object>)key, (Object)val);
else data.put((DataKey<Object>)key, (Object)val);
return this; return this;
} }
public <T> T add(DataKey<T> key, T val) { public <T> T get(DataKey<T> key, T val) {
if (data.containsKey(key)) return (T)data.get(key); for (var it = this; it != null; it = it.parent) {
else { if (it.data.containsKey(key)) {
if (val == null) data.remove(key); return (T)it.data.get((DataKey<Object>)key);
else data.put((DataKey<Object>)key, (Object)val); }
}
set(key, val);
return val; return val;
} }
}
public <T> T get(DataKey<T> key) { public <T> T get(DataKey<T> key) {
return get(key, null); for (var it = this; it != null; it = it.parent) {
if (it.data.containsKey(key)) return (T)it.data.get((DataKey<Object>)key);
} }
public <T> T get(DataKey<T> key, T defaultVal) { return null;
if (!has(key)) return defaultVal;
else return (T)data.get(key);
} }
public boolean has(DataKey<?> key) { return data.containsKey(key); } 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); return increase(key, 1, 0);
} }
@Override public Data() {
public Iterator<Entry<DataKey<?>, ?>> iterator() { this.parent = null;
return (Iterator<Entry<DataKey<?>, ?>>)data.entrySet(); }
public Data(Data parent) {
this.parent = parent;
} }
} }

View File

@ -3,29 +3,29 @@ package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException;
public class Engine { public class Engine {
private class UncompiledFunction extends FunctionValue { private class UncompiledFunction extends FunctionValue {
public final String filename; public final Filename filename;
public final String raw; public final String raw;
public final Environment env; private FunctionValue compiled = null;
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
ctx = ctx.setEnv(env); if (compiled == null) compiled = ctx.compile(filename, raw);
return ctx.compile(filename, raw).call(ctx, thisArg, args); return compiled.call(ctx, thisArg, args);
} }
public UncompiledFunction(Environment env, String filename, String raw) { public UncompiledFunction(Filename filename, String raw) {
super(filename, 0); super(filename + "", 0);
this.filename = filename; this.filename = filename;
this.raw = raw; this.raw = raw;
this.env = env;
} }
} }
@ -34,10 +34,10 @@ public class Engine {
public final Object thisArg; public final Object thisArg;
public final Object[] args; public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>(); 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) { public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) {
this.msg = ctx; this.ctx = ctx;
this.func = func; this.func = func;
this.thisArg = thisArg; this.thisArg = thisArg;
this.args = args; this.args = args;
@ -51,26 +51,20 @@ public class Engine {
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>(); private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId; public final int id = ++nextId;
public final HashMap<Long, Instruction[]> functions = new HashMap<>(); public final HashMap<Long, FunctionBody> functions = new HashMap<>();
public final Data data = new Data().set(StackData.MAX_FRAMES, 10000);
private void runTask(Task task) throws InterruptedException { private void runTask(Task task) {
try { try {
task.notifier.next(task.func.call(task.msg.context(null), task.thisArg, task.args)); task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
}
catch (InterruptedException e) {
task.notifier.error(new RuntimeException(e));
throw e;
}
catch (EngineException e) {
task.notifier.error(e);
} }
catch (RuntimeException e) { catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e); task.notifier.error(e);
e.printStackTrace();
} }
} }
private void run() { public void run(boolean untilEmpty) {
while (true) { while (!untilEmpty || !macroTasks.isEmpty()) {
try { try {
runTask(macroTasks.take()); runTask(macroTasks.take());
@ -78,9 +72,9 @@ public class Engine {
runTask(microTasks.take()); runTask(microTasks.take());
} }
} }
catch (InterruptedException e) { catch (InterruptedException | InterruptException e) {
for (var msg : macroTasks) { for (var msg : macroTasks) {
msg.notifier.error(new RuntimeException(e)); msg.notifier.error(new InterruptException(e));
} }
break; break;
} }
@ -89,7 +83,7 @@ public class Engine {
public Thread start() { public Thread start() {
if (this.thread == null) { if (this.thread == null) {
this.thread = new Thread(this::run, "JavaScript Runner #" + id); this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
this.thread.start(); this.thread.start();
} }
return this.thread; return this.thread;
@ -105,17 +99,13 @@ public class Engine {
return this.thread != null; return this.thread != null;
} }
public Awaitable<Object> pushMsg(boolean micro, Message ctx, FunctionValue func, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx, func, thisArg, args); var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args);
if (micro) microTasks.addLast(msg); if (micro) microTasks.addLast(msg);
else macroTasks.addLast(msg); else macroTasks.addLast(msg);
return msg.notifier; return msg.notifier;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args); return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
} }
// public Engine() {
// this.typeRegister = new NativeTypeRegister();
// }
} }

View File

@ -20,11 +20,11 @@ public class Environment {
public final HashMap<String, Symbol> symbols = new HashMap<>(); public final HashMap<String, Symbol> symbols = new HashMap<>();
public GlobalScope global; public GlobalScope global;
public WrappersProvider wrappersProvider; public WrappersProvider wrappers;
@Native public FunctionValue compile; @Native public FunctionValue compile;
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @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) { public Environment addData(Data data) {
@ -57,7 +57,7 @@ public class Environment {
} }
@Native public Environment fork() { @Native public Environment fork() {
var res = new Environment(compile, wrappersProvider, global); var res = new Environment(compile, wrappers, global);
res.regexConstructor = regexConstructor; res.regexConstructor = regexConstructor;
res.prototypes = new HashMap<>(prototypes); res.prototypes = new HashMap<>(prototypes);
return res; return res;
@ -68,8 +68,11 @@ public class Environment {
return res; return res;
} }
public Context context(Message msg) { public Context context(Engine engine, Data data) {
return new Context(this, msg); 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) { public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
@ -77,7 +80,7 @@ public class Environment {
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope(); if (global == null) global = new GlobalScope();
this.wrappersProvider = nativeConverter; this.wrappers = nativeConverter;
this.compile = compile; this.compile = compile;
this.global = global; 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,65 @@
package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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();
var dbg = getDebugger(ctx);
if (dbg != null) dbg.onFramePop(ctx, frame);
return true;
}
public static CodeFrame peekFrame(Context ctx) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
if (frames.size() == 0) return null;
return frames.get(frames.size() - 1);
}
public static List<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;
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 (!trace.equals("")) res.add(trace);
}
return res;
}
public static Debugger getDebugger(Context ctx) {
return ctx.data.get(DEBUGGER);
}
}

View File

@ -0,0 +1,43 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.TreeSet;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException;
public interface DebugController {
/**
* Called when a script has been loaded
* @param breakpoints
*/
void onSource(Filename filename, String source, TreeSet<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);
void connect();
void disconnect();
}

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);
/**
* This method might not execute the actual code for well-known requests
*/
void callFunctionOn(V8Message msg);
// void nodeWorkerEnable(V8Message msg);
void runtimeEnable(V8Message msg);
}

View File

@ -0,0 +1,238 @@
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.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 static void send(HttpRequest req, String val) throws IOException {
req.writeResponse(200, "OK", "application/json", val.getBytes());
}
// SILENCE JAVA
private MessageDigest getDigestInstance() {
try {
return MessageDigest.getInstance("sha1");
}
catch (Throwable e) { throw new UncheckedException(e); }
}
private static Thread runAsync(Runnable func, String name) {
var res = new Thread(func);
res.setName(name);
res.start();
return res;
}
private void handle(WebSocket ws, Debugger debugger) {
WebSocketMessage raw;
debugger.connect();
while ((raw = ws.receive()) != null) {
if (raw.type != Type.Text) {
ws.send(new V8Error("Expected a text message."));
continue;
}
V8Message msg;
try {
msg = new V8Message(raw.textData());
// System.out.println(msg.name + ": " + JSON.stringify(msg.params));
}
catch (SyntaxException e) {
ws.send(new V8Error(e.getMessage()));
return;
}
try {
switch (msg.name) {
case "Debugger.enable": debugger.enable(msg); continue;
case "Debugger.disable": debugger.disable(msg); continue;
case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue;
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue;
case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue;
case "Debugger.resume": debugger.resume(msg); continue;
case "Debugger.pause": debugger.pause(msg); continue;
case "Debugger.stepInto": debugger.stepInto(msg); continue;
case "Debugger.stepOut": debugger.stepOut(msg); continue;
case "Debugger.stepOver": debugger.stepOver(msg); continue;
case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue;
case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue;
case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue;
case "Runtime.getProperties": debugger.getProperties(msg); continue;
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
// case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue;
case "Runtime.enable": debugger.runtimeEnable(msg); continue;
}
if (
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 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,5 @@
package me.topchetoeu.jscript.engine.debug;
public interface Debugger extends DebugHandler, DebugController {
}

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,788 @@
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;
import me.topchetoeu.jscript.lib.DateLib;
import me.topchetoeu.jscript.lib.MapLib;
import me.topchetoeu.jscript.lib.PromiseLib;
import me.topchetoeu.jscript.lib.RegExpLib;
import me.topchetoeu.jscript.lib.SetLib;
import me.topchetoeu.jscript.lib.GeneratorLib.Generator;
public class SimpleDebugger implements Debugger {
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
private static enum State {
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;
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.local = new ObjectValue();
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);
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)))
)
.setNull("this");
}
}
}
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 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, boolean recurse) {
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";
if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp";
if (Values.isWrapper(val, DateLib.class)) subtype = "date";
if (Values.isWrapper(val, MapLib.class)) subtype = "map";
if (Values.isWrapper(val, SetLib.class)) subtype = "set";
if (Values.isWrapper(val, Generator.class)) subtype = "generator";
if (Values.isWrapper(val, PromiseLib.class)) subtype = "promise";
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
catch (Exception e) { }
var res = new JSONMap()
.set("type", type)
.set("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.isNaN(num)) res.set("unserializableValue", "NaN");
else res.set("value", num);
return res;
}
throw new IllegalArgumentException("Unexpected JS object.");
}
private JSONMap serializeObj(Context ctx, Object val) {
return serializeObj(ctx, val, true);
}
private void setObjectGroup(String name, Object val) {
if (val instanceof ObjectValue) {
var obj = (ObjectValue)val;
if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj);
else objectGroups.put(name, new ArrayList<>(List.of(obj)));
}
}
private void releaseGroup(String name) {
var objs = objectGroups.remove(name);
if (objs != null) {
for (var obj : objs) {
var id = objectToId.remove(obj);
if (id != null) idToObject.remove(id);
}
}
}
private Object deserializeArgument(JSONMap val) {
if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId")));
else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) {
case "NaN": return Double.NaN;
case "-Infinity": return Double.NEGATIVE_INFINITY;
case "Infinity": return Double.POSITIVE_INFINITY;
case "-0": return -0.;
}
return JSON.toJs(val.get("value"));
}
private 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();
var env = codeFrame.func.environment.fork();
ObjectValue global = env.global.obj,
capture = codeFrame.frame.getCaptureScope(null, enabled),
local = codeFrame.frame.getLocalScope(null, enabled);
capture.setPrototype(null, global);
local.setPrototype(null, capture);
env.global = new GlobalScope(local);
var ctx = new Context(engine).pushEnv(env);
var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
engine.run(true);
try { return new RunResult(ctx, awaiter.await(), null); }
catch (EngineException e) { return new RunResult(ctx, null, e); }
}
@Override public void enable(V8Message msg) {
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;
Pattern regex;
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
else regex = Pattern.compile(msg.params.string("urlRegex"));
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, null);
idToBptCand.put(bpcd.id, bpcd);
var locs = new JSONList();
for (var src : idToSource.values()) {
if (regex.matcher(src.filename.toString()).matches()) {
var loc = correctLocation(src, new Location(line, col, src.filename));
if (loc == null) continue;
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
bpcd.resolvedBreakpoints.add(bp);
locs.add(serializeLocation(loc));
addBreakpoint(bp);
}
}
ws.send(msg.respond(new JSONMap()
.set("breakpointId", bpcd.id + "")
.set("locations", locs)
));
}
@Override public void removeBreakpoint(V8Message msg) {
var id = Integer.parseInt(msg.params.string("breakpointId"));
if (idToBptCand.containsKey(id)) {
var bpcd = idToBptCand.get(id);
for (var bp : bpcd.resolvedBreakpoints) {
idToBreakpoint.remove(bp.id);
locToBreakpoint.remove(bp.location);
}
idToBptCand.remove(id);
}
else if (idToBreakpoint.containsKey(id)) {
var bp = idToBreakpoint.remove(id);
locToBreakpoint.remove(bp.location);
}
ws.send(msg.respond());
}
@Override public void continueToLocation(V8Message msg) {
var loc = deserializeLocation(msg.params.get("location"), true);
tmpBreakpts.add(loc);
resume(State.RESUMED);
ws.send(msg.respond());
}
@Override public void setPauseOnExceptions(V8Message msg) {
ws.send(new V8Error("i dont wanna to"));
}
@Override public void stepInto(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
prevLocation = currFrame.location;
stepOutFrame = currFrame;
resume(State.STEPPING_IN);
ws.send(msg.respond());
}
}
@Override public void stepOut(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
prevLocation = currFrame.location;
stepOutFrame = currFrame;
resume(State.STEPPING_OUT);
ws.send(msg.respond());
}
}
@Override public void stepOver(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
prevLocation = currFrame.location;
stepOutFrame = currFrame;
resume(State.STEPPING_OVER);
ws.send(msg.respond());
}
}
@Override public void 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 getProperties(V8Message msg) {
var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var own = msg.params.bool("ownProperties") || true;
var currOwn = true;
var res = new JSONList();
while (obj != null) {
var ctx = objectToCtx.get(obj);
for (var key : obj.keys(true)) {
var propDesc = new JSONMap();
if (obj.properties.containsKey(key)) {
var prop = obj.properties.get(key);
propDesc.set("name", Values.toString(ctx, key));
if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter));
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", currOwn);
res.add(propDesc);
}
else {
propDesc.set("name", Values.toString(ctx, key));
propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key)));
propDesc.set("writable", obj.memberWritable(key));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", currOwn);
res.add(propDesc);
}
}
obj = obj.getPrototype(ctx);
var protoDesc = new JSONMap();
protoDesc.set("name", "__proto__");
protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj));
protoDesc.set("writable", true);
protoDesc.set("enumerable", false);
protoDesc.set("configurable", false);
protoDesc.set("isOwn", currOwn);
res.add(protoDesc);
currOwn = false;
if (own) break;
}
ws.send(msg.respond(new JSONMap().set("result", res)));
}
@Override public void callFunctionOn(V8Message msg) {
var src = msg.params.string("functionDeclaration");
var args = msg.params.list("arguments", new JSONList()).stream().map(v -> v.map()).map(this::deserializeArgument).collect(Collectors.toList());
var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var ctx = objectToCtx.get(thisArg);
switch (src) {
case CHROME_GET_PROP_FUNC: {
var path = JSON.parse(new Filename("tmp", "json"), (String)args.get(0)).list();
Object res = thisArg;
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
return;
}
default:
ws.send(new V8Error("A non well-known function was used with callFunctionOn."));
break;
}
}
@Override
public void runtimeEnable(V8Message msg) {
ws.send(msg.respond());
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations) {
int id = nextId();
var src = new Source(id, filename, source, locations);
filenameToId.put(filename, id);
idToSource.put(id, src);
for (var bpcd : idToBptCand.values()) {
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename));
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
if (loc == null) continue;
bpcd.resolvedBreakpoints.add(bp);
addBreakpoint(bp);
}
if (!enabled) pendingSources.add(src);
else sendSource(src);
}
@Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (!enabled) return false;
updateFrames(ctx);
var frame = codeFrameToFrame.get(cf);
if (!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
);
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));
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 (returnVal != Runners.NO_RETURN) {
state = State.STEPPING_OUT;
return false;
}
else 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)
) {
pauseDebug(ctx, null);
updateNotifier.await();
}
}
@Override public void connect() {
target.data.set(StackData.DEBUGGER, this);
}
@Override public void disconnect() {
target.data.remove(StackData.DEBUGGER);
enabled = false;
updateNotifier.next();
}
private SimpleDebugger(WebSocket ws, Engine target) {
this.ws = ws;
this.target = target;
}
public static SimpleDebugger get(WebSocket ws, Engine target) {
if (target.data.has(StackData.DEBUGGER)) {
ws.send(new V8Error("A debugger is already attached to this engine."));
return null;
}
else {
var res = new SimpleDebugger(ws, target);
return res;
}
}
}

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,51 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.Map;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONMap;
public class V8Message {
public final String name;
public final int id;
public final JSONMap params;
public V8Message(String name, int id, Map<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(new Filename("jscript", "json-msg"), raw).map());
}
public JSONMap toMap() {
var res = new JSONMap();
return res;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
.set("id", id)
);
}
}

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,207 @@
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 = 2000000;
private Socket socket;
private boolean closed = false;
private OutputStream out() {
try { return socket.getOutputStream(); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
private InputStream in() {
try { return socket.getInputStream(); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
private long readLen(int byteLen) {
long res = 0;
try {
if (byteLen == 126) {
res |= in().read() << 8;
res |= in().read();
return res;
}
else if (byteLen == 127) {
res |= in().read() << 56;
res |= in().read() << 48;
res |= in().read() << 40;
res |= in().read() << 32;
res |= in().read() << 24;
res |= in().read() << 16;
res |= in().read() << 8;
res |= in().read();
return res;
}
else return byteLen;
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
private byte[] readMask(boolean has) {
if (has) {
try { return new byte[] {
(byte)in().read(),
(byte)in().read(),
(byte)in().read(),
(byte)in().read()
}; }
catch (IOException e) { throw new UncheckedIOException(e); }
}
else return new byte[4];
}
private void writeLength(long len) {
try {
if (len < 126) {
out().write((int)len);
}
else if (len < 0xFFFF) {
out().write(126);
out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF);
}
else {
out().write(127);
out().write((int)(len >> 56) & 0xFF);
out().write((int)(len >> 48) & 0xFF);
out().write((int)(len >> 40) & 0xFF);
out().write((int)(len >> 32) & 0xFF);
out().write((int)(len >> 24) & 0xFF);
out().write((int)(len >> 16) & 0xFF);
out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF);
}
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
private synchronized void write(int type, byte[] data) {
try {
out().write(type | 0x80);
writeLength(data.length);
for (int i = 0; i < data.length; i++) {
out().write(data[i]);
}
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public void send(String data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.getBytes());
}
public void send(byte[] data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(2, data);
}
public void send(WebSocketMessage msg) {
if (msg.type == Type.Binary) send(msg.binaryData());
else send(msg.textData());
}
public void send(Object data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes());
}
public void close(String reason) {
if (socket != null) {
try {
write(8, reason.getBytes());
socket.close();
}
catch (Throwable e) { }
}
socket = null;
closed = true;
}
public void close() {
close("");
}
private WebSocketMessage fail(String reason) {
System.out.println("WebSocket Error: " + reason);
close(reason);
return null;
}
private byte[] readData() {
try {
var maskLen = in().read();
var hasMask = (maskLen & 0x80) != 0;
var len = (int)readLen(maskLen & 0x7F);
var mask = readMask(hasMask);
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
else {
var buff = new byte[len];
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
else {
for (int i = 0; i < len; i++) {
buff[i] ^= mask[(int)(i % 4)];
}
return buff;
}
}
return null;
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public WebSocketMessage receive() {
try {
var data = new ByteArrayOutputStream();
var type = 0;
while (socket != null && !closed) {
var finId = in().read();
if (finId < 0) break;
var fin = (finId & 0x80) != 0;
int id = finId & 0x0F;
if (id == 0x8) { close(); return null; }
if (id >= 0x8) {
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
if (id == 0x9) write(0xA, data.toByteArray());
continue;
}
if (type == 0) type = id;
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
var buff = readData();
if (buff == null) break;
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
data.write(buff);
if (!fin) continue;
var raw = data.toByteArray();
if (type == 1) return new WebSocketMessage(new String(raw));
else return new WebSocketMessage(raw);
}
}
catch (IOException e) {
close();
}
return null;
}
public WebSocket(Socket socket) {
this.socket = socket;
}
}

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

@ -3,14 +3,18 @@ package me.topchetoeu.jscript.engine.frame;
import java.util.Stack; import java.util.Stack;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.ScopeValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class CodeFrame { public class CodeFrame {
private class TryCtx { private class TryCtx {
@ -54,6 +58,33 @@ public class CodeFrame {
public boolean jumpFlag = false; public boolean jumpFlag = false;
private Location prevLoc = null; private Location prevLoc = null;
public ObjectValue getLocalScope(Context ctx, boolean props) {
var names = new String[scope.locals.length];
for (int i = 0; i < scope.locals.length; i++) {
var name = "local_" + (i - 2);
if (i == 0) name = "this";
else if (i == 1) name = "arguments";
else if (i < function.localNames.length) name = function.localNames[i];
names[i] = name;
}
return new ScopeValue(scope.locals, names);
}
public ObjectValue getCaptureScope(Context ctx, boolean props) {
var names = new String[scope.captures.length];
for (int i = 0; i < scope.captures.length; i++) {
var name = "capture_" + (i - 2);
if (i < function.captureNames.length) name = function.captureNames[i];
names[i] = name;
}
return new ScopeValue(scope.captures, names);
}
public void addTry(int n, int catchN, int finallyN) { public void addTry(int n, int catchN, int finallyN) {
var res = new TryCtx(codePtr + 1, n, catchN, finallyN); var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
@ -93,35 +124,33 @@ public class CodeFrame {
stack[stackPtr++] = Values.normalize(ctx, val); stack[stackPtr++] = Values.normalize(ctx, val);
} }
private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException { private void setCause(Context ctx, EngineException err, EngineException cause) {
if (err.value instanceof ObjectValue) {
Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause);
}
err.cause = cause; err.cause = cause;
} }
private Object nextNoTry(Context ctx) throws InterruptedException { private Object nextNoTry(Context ctx, Instruction instr) {
if (Thread.currentThread().isInterrupted()) throw new InterruptedException(); if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (codePtr < 0 || codePtr >= function.body.length) return null; if (codePtr < 0 || codePtr >= function.body.length) return null;
var instr = function.body[codePtr];
var loc = instr.location;
if (loc != null) prevLoc = loc;
try { try {
this.jumpFlag = false; this.jumpFlag = false;
return Runners.exec(ctx, instr, this); return Runners.exec(ctx, instr, this);
} }
catch (EngineException e) { catch (EngineException e) {
throw e.add(function.name, prevLoc).setContext(ctx); throw e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
} }
} }
public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException { public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
if (value != Runners.NO_RETURN) push(ctx, value); if (value != Runners.NO_RETURN) push(ctx, value);
var debugger = StackData.getDebugger(ctx);
if (returnValue == Runners.NO_RETURN && error == null) { if (returnValue == Runners.NO_RETURN && error == null) {
try { returnValue = nextNoTry(ctx); } try {
var instr = function.body[codePtr];
if (debugger != null) debugger.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
returnValue = nextNoTry(ctx, instr);
}
catch (EngineException e) { error = e; } catch (EngineException e) { error = e; }
} }
@ -144,7 +173,7 @@ public class CodeFrame {
} }
else if (returnValue != Runners.NO_RETURN) { else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally) { if (tryCtx.hasFinally) {
tryCtx.retVal = error; tryCtx.retVal = returnValue;
newState = TryCtx.STATE_FINALLY_RETURNED; newState = TryCtx.STATE_FINALLY_RETURNED;
} }
break; break;
@ -164,6 +193,7 @@ public class CodeFrame {
tryCtx.err = error; tryCtx.err = error;
newState = TryCtx.STATE_FINALLY_THREW; newState = TryCtx.STATE_FINALLY_THREW;
} }
setCause(ctx, error, tryCtx.err);
break; break;
} }
else if (returnValue != Runners.NO_RETURN) { else if (returnValue != Runners.NO_RETURN) {
@ -214,6 +244,7 @@ public class CodeFrame {
case TryCtx.STATE_CATCH: case TryCtx.STATE_CATCH:
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
codePtr = tryCtx.catchStart; codePtr = tryCtx.catchStart;
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, true);
break; break;
default: default:
codePtr = tryCtx.finallyStart; codePtr = tryCtx.finallyStart;
@ -222,22 +253,16 @@ public class CodeFrame {
return Runners.NO_RETURN; return Runners.NO_RETURN;
} }
if (error != null) throw error.setContext(ctx); if (error != null) {
if (returnValue != Runners.NO_RETURN) return returnValue; if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, false);
return Runners.NO_RETURN; throw error;
}
if (returnValue != Runners.NO_RETURN) {
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
return returnValue;
} }
public Object run(Context ctx) throws InterruptedException { return Runners.NO_RETURN;
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;
}
}
finally {
ctx.message.popFrame(this);
}
} }
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) { public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {

View File

@ -22,15 +22,15 @@ public class Runners {
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) { public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
throw new EngineException(frame.pop()); 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)); throw EngineException.ofSyntax((String)instr.get(0));
} }
private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { private static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
return Values.call(ctx, func, thisArg, args); 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 callArgs = frame.take(instr.get(0));
var func = frame.pop(); var func = frame.pop();
var thisArg = frame.pop(); var thisArg = frame.pop();
@ -40,7 +40,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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 callArgs = frame.take(instr.get(0));
var funcObj = frame.pop(); var funcObj = frame.pop();
@ -61,13 +61,13 @@ public class Runners {
return NO_RETURN; 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); var name = (String)instr.get(0);
ctx.env.global.define(name); ctx.environment().global.define(name);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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 setter = frame.pop();
var getter = frame.pop(); var getter = frame.pop();
var name = frame.pop(); var name = frame.pop();
@ -82,7 +82,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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 type = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@ -97,7 +97,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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 val = frame.pop();
var arr = new ObjectValue(); var arr = new ObjectValue();
@ -117,7 +117,7 @@ public class Runners {
return NO_RETURN; 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.addTry(instr.get(0), instr.get(1), instr.get(2));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
@ -155,10 +155,10 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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); 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)); else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++; frame.codePtr++;
@ -170,7 +170,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { 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++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -191,15 +191,15 @@ public class Runners {
captures[i - 3] = frame.scope.get(instr.get(i)); captures[i - 3] = frame.scope.get(instr.get(i));
} }
var body = ctx.message.engine.functions.get(id); var body = ctx.engine.functions.get(id);
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body); var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
frame.push(ctx, func); frame.push(ctx, func);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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 key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@ -212,12 +212,12 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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)); frame.push(ctx, instr.get(0));
return execLoadMember(ctx, instr, frame); return execLoadMember(ctx, instr, frame);
} }
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -227,7 +227,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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 val = frame.pop();
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@ -237,11 +237,11 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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 val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0); 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); else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++; frame.codePtr++;
@ -258,7 +258,7 @@ public class Runners {
frame.jumpFlag = true; frame.jumpFlag = true;
return NO_RETURN; 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())) { if (Values.toBoolean(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
@ -266,7 +266,7 @@ public class Runners {
else frame.codePtr ++; else frame.codePtr ++;
return NO_RETURN; 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())) { if (Values.not(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
@ -275,7 +275,7 @@ public class Runners {
return NO_RETURN; 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 obj = frame.pop();
var index = frame.pop(); var index = frame.pop();
@ -283,13 +283,13 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; 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); String name = instr.get(0);
Object obj; Object obj;
if (name != null) { if (name != null) {
if (ctx.env.global.has(ctx, name)) { if (ctx.environment().global.has(ctx, name)) {
obj = ctx.env.global.get(ctx, name); obj = ctx.environment().global.get(ctx, name);
} }
else obj = null; else obj = null;
} }
@ -300,21 +300,12 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
if (instr.is(0, "dbg_names")) {
var names = new String[instr.params.length - 1];
for (var i = 0; i < instr.params.length - 1; i++) {
if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters.");
names[i] = (String)instr.params[i + 1];
}
frame.scope.setNames(names);
}
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
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 key = frame.pop();
var val = frame.pop(); var val = frame.pop();
@ -324,7 +315,7 @@ public class Runners {
return NO_RETURN; 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); Operation op = instr.get(0);
var args = new Object[op.operands]; var args = new Object[op.operands];
@ -335,7 +326,7 @@ public class Runners {
return NO_RETURN; 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) { switch (instr.type) {
case NOP: return execNop(ctx, instr, frame); case NOP: return execNop(ctx, instr, frame);
case RETURN: return execReturn(ctx, instr, frame); case RETURN: return execReturn(ctx, instr, frame);

View File

@ -15,7 +15,7 @@ public class GlobalScope implements ScopeRecord {
@Override @Override
public GlobalScope parent() { return null; } 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); return obj.hasMember(ctx, name, false);
} }
public Object getKey(String name) { public Object getKey(String name) {
@ -32,13 +32,7 @@ public class GlobalScope implements ScopeRecord {
} }
public Object define(String name) { public Object define(String name) {
try {
if (obj.hasMember(null, name, true)) return name; if (obj.hasMember(null, name, true)) return name;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return name;
}
obj.defineProperty(null, name, null); obj.defineProperty(null, name, null);
return name; return name;
} }
@ -59,11 +53,11 @@ public class GlobalScope implements ScopeRecord {
define(null, val.name, readonly, val); 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."); if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return obj.getMember(ctx, name); 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.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."); if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
} }

View File

@ -3,7 +3,6 @@ package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList; import java.util.ArrayList;
public class LocalScope { public class LocalScope {
private String[] names;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public final ValueVariable[] locals; public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>(); public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
@ -15,31 +14,11 @@ public class LocalScope {
else return captures[~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() { public int size() {
return captures.length + locals.length; 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) { public LocalScope(int n, ValueVariable[] captures) {
locals = new ValueVariable[n]; locals = new ValueVariable[n];
this.captures = captures; 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> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>(); private final ArrayList<String> locals = new ArrayList<>();
public String[] captures() {
return captures.toArray(String[]::new);
}
public String[] locals() { public String[] locals() {
return locals.toArray(String[]::new); return locals.toArray(String[]::new);
} }
@ -59,7 +62,7 @@ public class LocalScopeRecord implements ScopeRecord {
return name; return name;
} }
public boolean has(Context ctx, String name) throws InterruptedException { public boolean has(Context ctx, String name) {
return return
global.has(ctx, name) || global.has(ctx, name) ||
locals.contains(name) || locals.contains(name) ||

View File

@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public interface Variable { public interface Variable {
Object get(Context ctx) throws InterruptedException; Object get(Context ctx);
default boolean readonly() { return true; } default boolean readonly() { return true; }
default void set(Context ctx, Object val) throws InterruptedException { } default void set(Context ctx, Object val) { }
} }

View File

@ -122,7 +122,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
} }
@Override @Override
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = ((Number)key).doubleValue(); var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@ -133,7 +133,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @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) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@ -145,7 +145,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.setField(ctx, key, val); return super.setField(ctx, key, val);
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@ -156,7 +156,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }
@Override @Override
protected void deleteField(Context ctx, Object key) throws InterruptedException { protected void deleteField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@ -212,7 +212,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]); 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)); return new ArrayValue(ctx, values.toArray(Object[]::new));
} }
} }

View File

@ -1,16 +1,20 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class CodeFunction extends FunctionValue { public class CodeFunction extends FunctionValue {
public final int localsN; public final int localsN;
public final int length; public final int length;
public final Instruction[] body; public final Instruction[] body;
public final String[] captureNames, localNames;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public Environment environment; public Environment environment;
@ -28,16 +32,29 @@ public class CodeFunction extends FunctionValue {
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
return new CodeFrame(ctx, thisArg, args, this).run(ctx.setEnv(environment)); 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); super(name, length);
this.captures = captures; this.captures = captures;
this.captureNames = body.captureNames;
this.localNames = body.localNames;
this.environment = environment; this.environment = environment;
this.localsN = localsN; this.localsN = localsN;
this.length = length; this.length = length;
this.body = body; this.body = body.instructions;
} }
} }

View File

@ -14,26 +14,26 @@ public abstract class FunctionValue extends ObjectValue {
return "function(...) { ...}"; return "function(...) { ...}";
} }
public abstract Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException; public abstract Object call(Context ctx, Object thisArg, Object ...args);
public Object call(Context ctx) throws InterruptedException { public Object call(Context ctx) {
return call(ctx, null); return call(ctx, null);
} }
@Override @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("name")) return name;
if (key.equals("length")) return length; if (key.equals("length")) return length;
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @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); if (key.equals("name")) name = Values.toString(ctx, val);
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val); else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
else return super.setField(ctx, key, val); else return super.setField(ctx, key, val);
return true; return true;
} }
@Override @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("name")) return true;
if (key.equals("length")) return true; if (key.equals("length")) return true;
return super.hasField(ctx, key); return super.hasField(ctx, key);

View File

@ -4,13 +4,13 @@ import me.topchetoeu.jscript.engine.Context;
public class NativeFunction extends FunctionValue { public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner { 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; public final NativeFunctionRunner action;
@Override @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); return action.run(ctx, thisArg, args);
} }

View File

@ -7,9 +7,8 @@ public class NativeWrapper extends ObjectValue {
public final Object wrapped; public final Object wrapped;
@Override @Override
public ObjectValue getPrototype(Context ctx) throws InterruptedException { public ObjectValue getPrototype(Context ctx) {
if (prototype == NATIVE_PROTO) if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass());
return ctx.env.wrappersProvider.getProto(wrapped.getClass());
else return super.getPrototype(ctx); else return super.getPrototype(ctx);
} }

View File

@ -145,19 +145,17 @@ public class ObjectValue {
return true; return true;
} }
public ObjectValue getPrototype(Context ctx) throws InterruptedException { public ObjectValue getPrototype(Context ctx) {
try { try {
if (prototype == OBJ_PROTO) return ctx.env.proto("object"); if (prototype == OBJ_PROTO) return ctx.environment().proto("object");
if (prototype == ARR_PROTO) return ctx.env.proto("array"); if (prototype == ARR_PROTO) return ctx.environment().proto("array");
if (prototype == FUNC_PROTO) return ctx.env.proto("function"); if (prototype == FUNC_PROTO) return ctx.environment().proto("function");
if (prototype == ERR_PROTO) return ctx.env.proto("error"); if (prototype == ERR_PROTO) return ctx.environment().proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr"); if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr"); if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr"); if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr");
}
catch (NullPointerException e) {
return null;
} }
catch (NullPointerException e) { return null; }
return (ObjectValue)prototype; return (ObjectValue)prototype;
} }
@ -172,14 +170,14 @@ public class ObjectValue {
else if (Values.isObject(val)) { else if (Values.isObject(val)) {
var obj = Values.object(val); var obj = Values.object(val);
if (ctx != null && ctx.env != null) { if (ctx != null && ctx.environment() != null) {
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO; if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO; else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO; else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO; else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO; else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO; else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO;
else prototype = obj; else prototype = obj;
} }
else prototype = obj; else prototype = obj;
@ -203,19 +201,19 @@ public class ObjectValue {
return true; 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); if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getProperty(ctx, key); if (proto != null) return proto.getProperty(ctx, key);
else return null; 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); if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getField(ctx, key); if (proto != null) return proto.getField(ctx, key);
else return null; 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("")) { if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
((FunctionValue)val).name = Values.toString(ctx, key); ((FunctionValue)val).name = Values.toString(ctx, key);
} }
@ -223,14 +221,14 @@ public class ObjectValue {
values.put(key, val); values.put(key, val);
return true; return true;
} }
protected void deleteField(Context ctx, Object key) throws InterruptedException { protected void deleteField(Context ctx, Object key) {
values.remove(key); values.remove(key);
} }
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
return values.containsKey(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); key = Values.normalize(ctx, key);
if ("__proto__".equals(key)) { if ("__proto__".equals(key)) {
@ -246,11 +244,11 @@ public class ObjectValue {
} }
else return getField(ctx, key); 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); 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); key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
var prop = getProperty(ctx, key); var prop = getProperty(ctx, key);
@ -269,11 +267,11 @@ public class ObjectValue {
else if (nonWritableSet.contains(key)) return false; else if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val); 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); 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); key = Values.normalize(ctx, key);
if (key != null && key.equals("__proto__")) return true; if (key != null && key.equals("__proto__")) return true;
@ -283,7 +281,7 @@ public class ObjectValue {
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
return proto != null && proto.hasMember(ctx, key, own); 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); key = Values.normalize(ctx, key);
if (!memberConfigurable(key)) return false; if (!memberConfigurable(key)) return false;
@ -294,7 +292,7 @@ public class ObjectValue {
return true; 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); key = Values.normalize(ctx, key);
var prop = properties.get(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.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
public class Values { public class Values {
public static final Object NULL = new Object(); public static final Object NULL = new Object();
@ -67,7 +68,7 @@ public class Values {
return "object"; 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); var func = getMember(ctx, obj, name);
if (func != null) { if (func != null) {
@ -88,7 +89,7 @@ public class Values {
obj == NULL; obj == NULL;
} }
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) throws InterruptedException { public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) {
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
if (isPrimitive(obj)) return obj; if (isPrimitive(obj)) return obj;
@ -96,12 +97,8 @@ public class Values {
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf"; var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
if (ctx != null) { if (ctx != null) {
try { try { return tryCallConvertFunc(ctx, obj, first); }
return tryCallConvertFunc(ctx, obj, first); catch (EngineException unused) { return tryCallConvertFunc(ctx, obj, second); }
}
catch (EngineException unused) {
return tryCallConvertFunc(ctx, obj, second);
}
} }
throw EngineException.ofType("Value couldn't be converted to a primitive."); throw EngineException.ofType("Value couldn't be converted to a primitive.");
@ -113,20 +110,18 @@ public class Values {
if (obj instanceof Boolean) return (Boolean)obj; if (obj instanceof Boolean) return (Boolean)obj;
return true; 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); var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val instanceof Number) return number(val); if (val instanceof Number) return number(val);
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
if (val instanceof String) { if (val instanceof String) {
try { try { return Double.parseDouble((String)val); }
return Double.parseDouble((String)val); catch (Throwable e) { throw new UncheckedException(e); }
}
catch (NumberFormatException e) { }
} }
return Double.NaN; 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); var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val == null) return "undefined"; if (val == null) return "undefined";
@ -146,47 +141,47 @@ public class Values {
return "Unknown value"; 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); if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b);
else return toNumber(ctx, a) + toNumber(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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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 _a = (long)toNumber(ctx, a);
long _b = (long)toNumber(ctx, b); long _b = (long)toNumber(ctx, b);
@ -195,7 +190,7 @@ public class Values {
return _a >>> _b; 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); a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
b = toPrimitive(ctx, b, ConvertHint.VALUEOF); b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
@ -207,7 +202,7 @@ public class Values {
return !toBoolean(obj); 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; if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
var val = getPrototype(ctx, obj); var val = getPrototype(ctx, obj);
@ -219,7 +214,7 @@ public class Values {
return false; 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) { switch (op) {
case ADD: return add(ctx, args[0], args[1]); case ADD: return add(ctx, args[0], args[1]);
case SUBTRACT: return subtract(ctx, args[0], args[1]); case SUBTRACT: return subtract(ctx, args[0], args[1]);
@ -260,7 +255,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); 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 undefined.");
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
@ -280,7 +275,7 @@ public class Values {
else if (key != null && key.equals("__proto__")) return proto; else if (key != null && key.equals("__proto__")) return proto;
else return proto.getMember(ctx, key, obj); 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); 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 undefined.");
if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
@ -290,7 +285,7 @@ public class Values {
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
return proto.setMember(ctx, key, val, obj, true); 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; if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
@ -308,31 +303,31 @@ public class Values {
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
return proto != null && proto.hasMember(ctx, key, own); 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; if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (isObject(obj)) return object(obj).deleteMember(ctx, key); if (isObject(obj)) return object(obj).deleteMember(ctx, key);
else return false; 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; if (obj == null || obj == NULL) return null;
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
if (isObject(obj)) return object(obj).getPrototype(ctx); if (isObject(obj)) return object(obj).getPrototype(ctx);
if (ctx == null) return null; if (ctx == null) return null;
if (obj instanceof String) return ctx.env.proto("string"); if (obj instanceof String) return ctx.environment().proto("string");
else if (obj instanceof Number) return ctx.env.proto("number"); else if (obj instanceof Number) return ctx.environment().proto("number");
else if (obj instanceof Boolean) return ctx.env.proto("bool"); else if (obj instanceof Boolean) return ctx.environment().proto("bool");
else if (obj instanceof Symbol) return ctx.env.proto("symbol"); else if (obj instanceof Symbol) return ctx.environment().proto("symbol");
return null; 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); obj = normalize(ctx, obj);
return isObject(obj) && object(obj).setPrototype(ctx, proto); 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<>(); List<Object> res = new ArrayList<>();
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable); if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
@ -352,7 +347,7 @@ public class Values {
return res; 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); if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
else if (obj instanceof String && key instanceof Number) { else if (obj instanceof String && key instanceof Number) {
var i = ((Number)key).intValue(); var i = ((Number)key).intValue();
@ -370,11 +365,11 @@ public class Values {
else return null; 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."); if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
return function(func).call(ctx, thisArg, args); 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 res = new ObjectValue();
var proto = Values.getMember(ctx, func, "prototype"); var proto = Values.getMember(ctx, func, "prototype");
res.setPrototype(ctx, proto); res.setPrototype(ctx, proto);
@ -395,7 +390,7 @@ public class Values {
return a == b || a.equals(b); 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); a = normalize(ctx, a); b = normalize(ctx, b);
// In loose equality, null is equivalent to undefined // In loose equality, null is equivalent to undefined
@ -446,14 +441,14 @@ public class Values {
if (val instanceof Class) { if (val instanceof Class) {
if (ctx == null) return null; 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); return new NativeWrapper(val);
} }
@SuppressWarnings("unchecked") @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 (clazz == Void.class) return null;
if (obj instanceof NativeWrapper) { if (obj instanceof NativeWrapper) {
@ -520,7 +515,7 @@ public class Values {
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) { public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
return () -> { return () -> {
try { try {
var symbol = ctx.env.symbol("Symbol.iterator"); var symbol = ctx.environment().symbol("Symbol.iterator");
var iteratorFunc = getMember(ctx, obj, symbol); var iteratorFunc = getMember(ctx, obj, symbol);
if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
@ -536,7 +531,7 @@ public class Values {
public boolean consumed = true; public boolean consumed = true;
private FunctionValue next = (FunctionValue)nextFunc; private FunctionValue next = (FunctionValue)nextFunc;
private void loadNext() throws InterruptedException { private void loadNext() {
if (next == null) value = null; if (next == null) value = null;
else if (consumed) { else if (consumed) {
var curr = object(next.call(ctx, iterator)); var curr = object(next.call(ctx, iterator));
@ -551,46 +546,30 @@ public class Values {
@Override @Override
public boolean hasNext() { public boolean hasNext() {
try {
loadNext(); loadNext();
return next != null; return next != null;
} }
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
@Override @Override
public Object next() { public Object next() {
try {
loadNext(); loadNext();
var res = value; var res = value;
value = null; value = null;
consumed = true; consumed = true;
return res; return res;
} }
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
}; };
} }
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
catch (IllegalArgumentException | NullPointerException e) { catch (IllegalArgumentException | NullPointerException e) {
return Collections.emptyIterator(); 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(); var res = new ObjectValue();
try { 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)); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
} }
catch (IllegalArgumentException | NullPointerException e) { } catch (IllegalArgumentException | NullPointerException e) { }
@ -603,11 +582,11 @@ public class Values {
return res; 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()); return fromJavaIterator(ctx, it.iterator());
} }
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException { private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) {
if (passed.contains(val)) { if (passed.contains(val)) {
System.out.print("[circular]"); System.out.print("[circular]");
return; return;
@ -679,14 +658,15 @@ public class Values {
else if (val instanceof String) System.out.print("'" + val + "'"); else if (val instanceof String) System.out.print("'" + val + "'");
else System.out.print(Values.toString(ctx, 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); 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; prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
try { try {
if (err instanceof EngineException) { 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) { else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg); System.out.println("Syntax error:" + ((SyntaxException)err).msg);

View File

@ -1,7 +1,9 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException;
public interface Awaitable<T> { public interface Awaitable<T> {
T await() throws FinishedException, InterruptedException; T await() throws FinishedException;
default Observable<T> toObservable() { default Observable<T> toObservable() {
return sub -> { return sub -> {
@ -10,9 +12,7 @@ public interface Awaitable<T> {
sub.next(await()); sub.next(await());
sub.finish(); sub.finish();
} }
catch (InterruptedException | FinishedException e) { catch (InterruptException | FinishedException e) { sub.finish(); }
sub.finish();
}
catch (RuntimeException e) { catch (RuntimeException e) {
sub.error(e); sub.error(e);
} }

View File

@ -19,7 +19,7 @@ public class DataNotifier<T> implements Awaitable<T> {
isErr = false; isErr = false;
notifier.next(); notifier.next();
} }
public T await() throws InterruptedException { public T await() {
notifier.await(); notifier.await();
try { try {

View File

@ -1,5 +1,7 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class Notifier { public class Notifier {
private boolean ok = false; private boolean ok = false;
@ -7,8 +9,11 @@ public class Notifier {
ok = true; ok = true;
notifyAll(); notifyAll();
} }
public synchronized void await() throws InterruptedException { public synchronized void await() {
try {
while (!ok) wait(); while (!ok) wait();
ok = false; 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.Location;
import me.topchetoeu.jscript.engine.Context; 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.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; 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 class EngineException extends RuntimeException {
public final Object value; public final Object value;
public EngineException cause; public EngineException cause;
public Context ctx = null; public Environment env = null;
public Engine engine = null;
public final List<String> stackTrace = new ArrayList<>(); public final List<String> stackTrace = new ArrayList<>();
public EngineException add(String name, Location location) { public EngineException add(String name, Location location) {
@ -28,12 +31,13 @@ public class EngineException extends RuntimeException {
this.cause = cause; this.cause = cause;
return this; return this;
} }
public EngineException setContext(Context ctx) { public EngineException setCtx(Environment env, Engine engine) {
this.ctx = ctx; if (this.env == null) this.env = env;
if (this.engine == null) this.engine = engine;
return this; return this;
} }
public String toString(Context ctx) throws InterruptedException { public String toString(Context ctx) {
var ss = new StringBuilder(); var ss = new StringBuilder();
try { try {
ss.append(Values.toString(ctx, value)).append('\n'); ss.append(Values.toString(ctx, value)).append('\n');

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,6 +9,7 @@ import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
public class NativeWrapperProvider implements WrappersProvider { public class NativeWrapperProvider implements WrappersProvider {
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>(); private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
@ -120,7 +121,7 @@ public class NativeWrapperProvider implements WrappersProvider {
var init = overload.getAnnotation(NativeInit.class); var init = overload.getAnnotation(NativeInit.class);
if (init == null || init.value() != InitType.PROTOTYPE) continue; if (init == null || init.value() != InitType.PROTOTYPE) continue;
try { overload.invoke(null, ctx, res); } try { overload.invoke(null, ctx, res); }
catch (ReflectiveOperationException e) { e.printStackTrace(); } catch (Throwable e) { throw new UncheckedException(e); }
} }
applyMethods(ctx, true, res, clazz); applyMethods(ctx, true, res, clazz);
@ -152,7 +153,7 @@ public class NativeWrapperProvider implements WrappersProvider {
var init = overload.getAnnotation(NativeInit.class); var init = overload.getAnnotation(NativeInit.class);
if (init == null || init.value() != InitType.CONSTRUCTOR) continue; if (init == null || init.value() != InitType.CONSTRUCTOR) continue;
try { overload.invoke(null, ctx, func); } try { overload.invoke(null, ctx, func); }
catch (ReflectiveOperationException e) { e.printStackTrace(); } catch (Throwable e) { throw new UncheckedException(e); }
} }
if (((OverloadFunction)func).overloads.size() == 0) { if (((OverloadFunction)func).overloads.size() == 0) {
@ -180,7 +181,7 @@ public class NativeWrapperProvider implements WrappersProvider {
var init = overload.getAnnotation(NativeInit.class); var init = overload.getAnnotation(NativeInit.class);
if (init == null || init.value() != InitType.NAMESPACE) continue; if (init == null || init.value() != InitType.NAMESPACE) continue;
try { overload.invoke(null, ctx, res); } try { overload.invoke(null, ctx, res); }
catch (ReflectiveOperationException e) { e.printStackTrace(); } catch (Throwable e) { throw new UncheckedException(e); }
} }
applyMethods(ctx, false, res, clazz); applyMethods(ctx, false, res, clazz);

View File

@ -9,10 +9,7 @@ import me.topchetoeu.jscript.engine.Context;
public class Overload { public class Overload {
public static interface OverloadRunner { public static interface OverloadRunner {
Object run(Context ctx, Object thisArg, Object[] args) throws Object run(Context ctx, Object thisArg, Object[] args) throws ReflectiveOperationException, IllegalArgumentException;
InterruptedException,
ReflectiveOperationException,
IllegalArgumentException;
} }
public final OverloadRunner runner; public final OverloadRunner runner;

View File

@ -15,7 +15,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
public class OverloadFunction extends FunctionValue { public class OverloadFunction extends FunctionValue {
public final List<Overload> overloads = new ArrayList<>(); 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) { loop: for (var overload : overloads) {
Object[] newArgs = new Object[overload.params.length]; Object[] newArgs = new Object[overload.params.length];
@ -76,14 +76,10 @@ public class OverloadFunction extends FunctionValue {
try { try {
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs));
} }
catch (InstantiationException e) { catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); }
throw EngineException.ofError("The class may not be instantiated."); catch (IllegalAccessException | IllegalArgumentException e) { continue; }
}
catch (IllegalAccessException | IllegalArgumentException e) {
continue;
}
catch (InvocationTargetException e) { catch (InvocationTargetException e) {
var loc = new Location(0, 0, "<internal>"); var loc = Location.INTERNAL;
if (e.getTargetException() instanceof EngineException) { if (e.getTargetException() instanceof EngineException) {
throw ((EngineException)e.getTargetException()).add(name, loc); throw ((EngineException)e.getTargetException()).add(name, loc);
} }
@ -95,10 +91,7 @@ public class OverloadFunction extends FunctionValue {
} }
} }
catch (ReflectiveOperationException e) { catch (ReflectiveOperationException e) {
throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "<internal>")); throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL);
}
catch (Exception e) {
throw e;
} }
} }

View File

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

View File

@ -65,10 +65,22 @@ public class JSONElement {
return (double)value; return (double)value;
} }
public boolean bool() { 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; 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) { private JSONElement(Type type, Object val) {
this.type = type; this.type = type;
this.value = val; this.value = val;

View File

@ -10,6 +10,9 @@ public class JSONList extends ArrayList<JSONElement> {
public JSONList(JSONElement ...els) { public JSONList(JSONElement ...els) {
super(List.of(els)); super(List.of(els));
} }
public JSONList(Collection<JSONElement> els) {
super(els);
}
public JSONList addNull() { this.add(JSONElement.NULL); return this; } public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; } public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
@ -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(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(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(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) { public JSONMap map(String path) {
var el = get(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(); return el.map();
} }
public JSONMap map(String path, JSONMap defaultVal) { public JSONMap map(String path, JSONMap defaultVal) {
@ -63,7 +63,7 @@ public class JSONMap implements Map<String, JSONElement> {
public JSONList list(String path) { public JSONList list(String path) {
var el = get(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(); return el.list();
} }
public JSONList list(String path, JSONList defaultVal) { public JSONList list(String path, JSONList defaultVal) {
@ -75,7 +75,7 @@ public class JSONMap implements Map<String, JSONElement> {
public String string(String path) { public String string(String path) {
var el = get(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(); return el.string();
} }
public String string(String path, String defaultVal) { public String string(String path, String defaultVal) {
@ -87,7 +87,7 @@ public class JSONMap implements Map<String, JSONElement> {
public double number(String path) { public double number(String path) {
var el = get(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(); return el.number();
} }
public double number(String path, double defaultVal) { public double number(String path, double defaultVal) {
@ -99,7 +99,7 @@ public class JSONMap implements Map<String, JSONElement> {
public boolean bool(String path) { public boolean bool(String path) {
var el = get(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(); return el.bool();
} }
public boolean bool(String path, boolean defaultVal) { public boolean bool(String path, boolean defaultVal) {

View File

@ -17,17 +17,17 @@ import me.topchetoeu.jscript.interop.NativeInit;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
public class ArrayLib { public class ArrayLib {
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException { @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) {
return thisArg.size(); 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); 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); 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>() { return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() {
private int i = 0; private int i = 0;
@ -42,7 +42,7 @@ public class ArrayLib {
} }
}); });
} }
@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>() { return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() {
private int i = 0; private int i = 0;
@ -59,15 +59,15 @@ public class ArrayLib {
} }
@Native(value = "@@Symbol.iterator", thisArg = true) @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); return values(ctx, thisArg);
} }
@Native(value = "@@Symbol.asyncIterator", thisArg = true) @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); 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 // TODO: Fully implement with non-array spreadable objects
var size = 0; var size = 0;
@ -92,25 +92,14 @@ public class ArrayLib {
return res; return res;
} }
@Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) throws InterruptedException { @Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) {
try {
arr.sort((a, b) -> { arr.sort((a, b) -> {
try {
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b));
if (res < 0) return -1; if (res < 0) return -1;
if (res > 0) return 1; if (res > 0) return 1;
return 0; return 0;
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}); });
} }
catch (RuntimeException e) {
if (e.getCause() instanceof InterruptedException) throw (InterruptedException)e.getCause();
else throw e;
}
}
private static int normalizeI(int len, int i, boolean clamp) { private static int normalizeI(int len, int i, boolean clamp) {
if (i < 0) i += len; if (i < 0) i += len;
@ -121,7 +110,7 @@ public class ArrayLib {
return i; 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); start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true); end = normalizeI(arr.size(), end, true);
@ -131,21 +120,21 @@ public class ArrayLib {
return arr; 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()); 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()); 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++) { for (var i = 0; i < arr.size(); i++) {
if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false; if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false;
} }
return true; 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++) { for (var i = 0; i < arr.size(); i++) {
if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true; if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true;
} }
@ -153,7 +142,7 @@ public class ArrayLib {
return false; 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()); var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) { for (int i = 0, j = 0; i < arr.size(); i++) {
@ -161,20 +150,20 @@ public class ArrayLib {
} }
return res; 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()); var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) { 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)); if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr));
} }
return res; 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++) { for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr); 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 ArrayValue flat(Context ctx, ArrayValue arr, int depth) {
var res = new ArrayValue(arr.size()); var res = new ArrayValue(arr.size());
var stack = new Stack<Object>(); var stack = new Stack<Object>();
var depths = new Stack<Integer>(); var depths = new Stack<Integer>();
@ -197,18 +186,18 @@ public class ArrayLib {
return res; 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); 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++) { 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); if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
} }
return null; 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--) { 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); if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
} }
@ -216,14 +205,14 @@ public class ArrayLib {
return null; 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++) { 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; if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
} }
return -1; 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--) { 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; if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
} }
@ -231,7 +220,7 @@ public class ArrayLib {
return -1; 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); start = normalizeI(arr.size(), start, true);
for (int i = 0; i < arr.size() && i < start; i++) { for (int i = 0; i < arr.size() && i < start; i++) {
@ -240,7 +229,7 @@ public class ArrayLib {
return -1; 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); start = normalizeI(arr.size(), start, true);
for (int i = arr.size(); i >= start; i--) { for (int i = arr.size(); i >= start; i--) {
@ -250,29 +239,29 @@ public class ArrayLib {
return -1; 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; 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; if (arr.size() == 0) return null;
var val = arr.get(arr.size() - 1); var val = arr.get(arr.size() - 1);
arr.shrink(1); arr.shrink(1);
return val; 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); arr.copyFrom(ctx, values, 0, arr.size(), values.length);
return arr.size(); 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; if (arr.size() == 0) return null;
var val = arr.get(0); var val = arr.get(0);
arr.move(1, 0, arr.size()); arr.move(1, 0, arr.size());
arr.shrink(1); arr.shrink(1);
return val; 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.move(0, values.length, arr.size());
arr.copyFrom(ctx, values, 0, 0, values.length); arr.copyFrom(ctx, values, 0, 0, values.length);
return arr.size(); return arr.size();
@ -290,7 +279,7 @@ public class ArrayLib {
return slice(ctx, arr, start, arr.size()); 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, int deleteCount, Object ...items) {
start = normalizeI(arr.size(), start, true); start = normalizeI(arr.size(), start, true);
deleteCount = normalizeI(arr.size(), deleteCount, true); deleteCount = normalizeI(arr.size(), deleteCount, true);
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start; if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
@ -304,14 +293,14 @@ public class ArrayLib {
return res; return res;
} }
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) throws InterruptedException { @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) {
return splice(ctx, arr, start, arr.size() - start); 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, ","); 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 res = new StringBuilder();
var comma = true; var comma = true;

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
@ -17,9 +18,10 @@ public class AsyncFunctionLib extends FunctionValue {
private boolean awaiting = false; private boolean awaiting = false;
private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException { private void next(Context ctx, Object inducedValue, Object inducedError) {
Object res = null; Object res = null;
ctx.message.pushFrame(ctx, frame); StackData.pushFrame(ctx, frame);
ctx.pushEnv(frame.function.environment);
awaiting = false; awaiting = false;
while (!awaiting) { while (!awaiting) {
@ -37,18 +39,18 @@ public class AsyncFunctionLib extends FunctionValue {
} }
} }
ctx.message.popFrame(frame); StackData.popFrame(ctx, frame);
if (awaiting) { if (awaiting) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
} }
} }
public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object fulfill(Context ctx, Object thisArg, Object ...args) {
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN); next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
return null; return null;
} }
public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object reject(Context ctx, Object thisArg, Object ...args) {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null); next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null);
return null; return null;
} }
@ -60,7 +62,7 @@ public class AsyncFunctionLib extends FunctionValue {
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new AsyncHelper(); var handler = new AsyncHelper();
var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await)); var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await));
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");

View File

@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
@ -22,7 +23,7 @@ public class AsyncGeneratorLib extends FunctionValue {
private PromiseLib currPromise; private PromiseLib currPromise;
public CodeFrame frame; public CodeFrame frame;
private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException { private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
if (done) { if (done) {
if (inducedError != Runners.NO_RETURN) if (inducedError != Runners.NO_RETURN)
throw new EngineException(inducedError); throw new EngineException(inducedError);
@ -34,7 +35,7 @@ public class AsyncGeneratorLib extends FunctionValue {
} }
Object res = null; Object res = null;
ctx.message.pushFrame(ctx, frame); StackData.pushFrame(ctx, frame);
state = 0; state = 0;
while (state == 0) { while (state == 0) {
@ -55,7 +56,7 @@ public class AsyncGeneratorLib extends FunctionValue {
} }
} }
ctx.message.popFrame(frame); StackData.popFrame(ctx, frame);
if (state == 1) { if (state == 1) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
@ -75,30 +76,30 @@ public class AsyncGeneratorLib extends FunctionValue {
return "Generator [running]"; return "Generator [running]";
} }
public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object fulfill(Context ctx, Object thisArg, Object ...args) {
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN); next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN);
return null; return null;
} }
public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object reject(Context ctx, Object thisArg, Object ...args) {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN); next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
return null; return null;
} }
@Native @Native
public PromiseLib next(Context ctx, Object ...args) throws InterruptedException { public PromiseLib next(Context ctx, Object ...args) {
this.currPromise = new PromiseLib(); this.currPromise = new PromiseLib();
if (args.length == 0) next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); if (args.length == 0) next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
else next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN); else next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
return this.currPromise; return this.currPromise;
} }
@Native("throw") @Native("throw")
public PromiseLib _throw(Context ctx, Object error) throws InterruptedException { public PromiseLib _throw(Context ctx, Object error) {
this.currPromise = new PromiseLib(); this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error); next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
return this.currPromise; return this.currPromise;
} }
@Native("return") @Native("return")
public PromiseLib _return(Context ctx, Object value) throws InterruptedException { public PromiseLib _return(Context ctx, Object value) {
this.currPromise = new PromiseLib(); this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
return this.currPromise; return this.currPromise;
@ -116,7 +117,7 @@ public class AsyncGeneratorLib extends FunctionValue {
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new AsyncGenerator(); var handler = new AsyncGenerator();
var func = factory.call(ctx, thisArg, var func = factory.call(ctx, thisArg,
new NativeFunction("await", handler::await), new NativeFunction("await", handler::await),

View File

@ -33,7 +33,7 @@ public class DateLib {
return normal.get(Calendar.YEAR) - 1900; return normal.get(Calendar.YEAR) - 1900;
} }
@Native @Native
public double setYear(Context ctx, double real) throws InterruptedException { public double setYear(Context ctx, double real) {
if (real >= 0 && real <= 99) real = real + 1900; if (real >= 0 && real <= 99) real = real + 1900;
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.YEAR, (int)real); else normal.set(Calendar.YEAR, (int)real);
@ -124,56 +124,56 @@ public class DateLib {
} }
@Native @Native
public double setFullYear(Context ctx, double real) throws InterruptedException { public double setFullYear(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.YEAR, (int)real); else normal.set(Calendar.YEAR, (int)real);
updateUTC(); updateUTC();
return getTime(); return getTime();
} }
@Native @Native
public double setMonth(Context ctx, double real) throws InterruptedException { public double setMonth(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MONTH, (int)real); else normal.set(Calendar.MONTH, (int)real);
updateUTC(); updateUTC();
return getTime(); return getTime();
} }
@Native @Native
public double setDate(Context ctx, double real) throws InterruptedException { public double setDate(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.DAY_OF_MONTH, (int)real); else normal.set(Calendar.DAY_OF_MONTH, (int)real);
updateUTC(); updateUTC();
return getTime(); return getTime();
} }
@Native @Native
public double setDay(Context ctx, double real) throws InterruptedException { public double setDay(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.DAY_OF_WEEK, (int)real); else normal.set(Calendar.DAY_OF_WEEK, (int)real);
updateUTC(); updateUTC();
return getTime(); return getTime();
} }
@Native @Native
public double setHours(Context ctx, double real) throws InterruptedException { public double setHours(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.HOUR_OF_DAY, (int)real); else normal.set(Calendar.HOUR_OF_DAY, (int)real);
updateUTC(); updateUTC();
return getTime(); return getTime();
} }
@Native @Native
public double setMinutes(Context ctx, double real) throws InterruptedException { public double setMinutes(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MINUTE, (int)real); else normal.set(Calendar.MINUTE, (int)real);
updateUTC(); updateUTC();
return getTime(); return getTime();
} }
@Native @Native
public double setSeconds(Context ctx, double real) throws InterruptedException { public double setSeconds(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.SECOND, (int)real); else normal.set(Calendar.SECOND, (int)real);
updateUTC(); updateUTC();
return getTime(); return getTime();
} }
@Native @Native
public double setMilliseconds(Context ctx, double real) throws InterruptedException { public double setMilliseconds(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.MILLISECOND, (int)real); else normal.set(Calendar.MILLISECOND, (int)real);
updateUTC(); updateUTC();
@ -181,56 +181,56 @@ public class DateLib {
} }
@Native @Native
public double setUTCFullYear(Context ctx, double real) throws InterruptedException { public double setUTCFullYear(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.YEAR, (int)real); else utc.set(Calendar.YEAR, (int)real);
updateNormal(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setUTCMonth(Context ctx, double real) throws InterruptedException { public double setUTCMonth(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MONTH, (int)real); else utc.set(Calendar.MONTH, (int)real);
updateNormal(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setUTCDate(Context ctx, double real) throws InterruptedException { public double setUTCDate(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.DAY_OF_MONTH, (int)real); else utc.set(Calendar.DAY_OF_MONTH, (int)real);
updateNormal(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setUTCDay(Context ctx, double real) throws InterruptedException { public double setUTCDay(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.DAY_OF_WEEK, (int)real); else utc.set(Calendar.DAY_OF_WEEK, (int)real);
updateNormal(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setUTCHours(Context ctx, double real) throws InterruptedException { public double setUTCHours(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.HOUR_OF_DAY, (int)real); else utc.set(Calendar.HOUR_OF_DAY, (int)real);
updateNormal(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setUTCMinutes(Context ctx, double real) throws InterruptedException { public double setUTCMinutes(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MINUTE, (int)real); else utc.set(Calendar.MINUTE, (int)real);
updateNormal(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setUTCSeconds(Context ctx, double real) throws InterruptedException { public double setUTCSeconds(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.SECOND, (int)real); else utc.set(Calendar.SECOND, (int)real);
updateNormal(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setUTCMilliseconds(Context ctx, double real) throws InterruptedException { public double setUTCMilliseconds(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else utc.set(Calendar.MILLISECOND, (int)real); else utc.set(Calendar.MILLISECOND, (int)real);
updateNormal(); updateNormal();

View File

@ -2,6 +2,7 @@ package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
@ -11,7 +12,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class ErrorLib { public class ErrorLib {
private static String toString(Context ctx, Object cause, Object name, Object message, ArrayValue stack) throws InterruptedException { private static String toString(Context ctx, boolean rethrown, Object cause, Object name, Object message, ArrayValue stack) {
if (name == null) name = ""; if (name == null) name = "";
else name = Values.toString(ctx, name).trim(); else name = Values.toString(ctx, name).trim();
if (message == null) message = ""; if (message == null) message = "";
@ -29,17 +30,22 @@ public class ErrorLib {
} }
} }
if (cause instanceof ObjectValue) res.append(toString(ctx, cause)); if (cause instanceof ObjectValue) {
if (rethrown) res.append("\n (rethrown)");
else res.append("\nCaused by ").append(toString(ctx, cause));
}
return res.toString(); return res.toString();
} }
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
if (thisArg instanceof ObjectValue) { if (thisArg instanceof ObjectValue) {
var stack = Values.getMember(ctx, thisArg, "stack"); var stack = Values.getMember(ctx, thisArg, "stack");
if (!(stack instanceof ArrayValue)) stack = null; if (!(stack instanceof ArrayValue)) stack = null;
var cause = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.cause"));
return toString(ctx, return toString(ctx,
Values.getMember(ctx, thisArg, "cause"), thisArg == cause,
cause,
Values.getMember(ctx, thisArg, "name"), Values.getMember(ctx, thisArg, "name"),
Values.getMember(ctx, thisArg, "message"), Values.getMember(ctx, thisArg, "message"),
(ArrayValue)stack (ArrayValue)stack
@ -48,11 +54,11 @@ public class ErrorLib {
else return "[Invalid error]"; else return "[Invalid error]";
} }
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
var target = new ObjectValue(); var target = new ObjectValue();
if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg;
target.defineProperty(ctx, "stack", new ArrayValue(ctx, ctx.message.stackTrace().toArray())); target.defineProperty(ctx, "stack", ArrayValue.of(ctx, StackData.stackTrace(ctx)));
target.defineProperty(ctx, "name", "Error"); target.defineProperty(ctx, "name", "Error");
if (message == null) target.defineProperty(ctx, "message", ""); if (message == null) target.defineProperty(ctx, "message", "");
else target.defineProperty(ctx, "message", Values.toString(ctx, message)); else target.defineProperty(ctx, "message", Values.toString(ctx, message));

View File

@ -12,10 +12,10 @@ import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class FunctionLib { public class FunctionLib {
@Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) {
return func.call(ctx, thisArg, args.toArray()); return func.call(ctx, thisArg, args.toArray());
} }
@Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) {
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
return func.call(ctx, thisArg, args); return func.call(ctx, thisArg, args);

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
@ -20,7 +21,7 @@ public class GeneratorLib extends FunctionValue {
@Native("@@Symbol.typeName") public final String name = "Generator"; @Native("@@Symbol.typeName") public final String name = "Generator";
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException { private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
if (done) { if (done) {
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
var res = new ObjectValue(); var res = new ObjectValue();
@ -30,7 +31,7 @@ public class GeneratorLib extends FunctionValue {
} }
Object res = null; Object res = null;
ctx.message.pushFrame(ctx, frame); StackData.pushFrame(ctx, frame);
yielding = false; yielding = false;
while (!yielding) { while (!yielding) {
@ -48,7 +49,7 @@ public class GeneratorLib extends FunctionValue {
} }
} }
ctx.message.popFrame(frame); StackData.popFrame(ctx, frame);
if (done) frame = null; if (done) frame = null;
else res = frame.pop(); else res = frame.pop();
@ -59,16 +60,16 @@ public class GeneratorLib extends FunctionValue {
} }
@Native @Native
public ObjectValue next(Context ctx, Object ...args) throws InterruptedException { public ObjectValue next(Context ctx, Object ...args) {
if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN); else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
} }
@Native("throw") @Native("throw")
public ObjectValue _throw(Context ctx, Object error) throws InterruptedException { public ObjectValue _throw(Context ctx, Object error) {
return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error); return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
} }
@Native("return") @Native("return")
public ObjectValue _return(Context ctx, Object value) throws InterruptedException { public ObjectValue _return(Context ctx, Object value) {
return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
} }
@ -86,7 +87,7 @@ public class GeneratorLib extends FunctionValue {
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new Generator(); var handler = new Generator();
var func = factory.call(ctx, thisArg, new NativeFunction("yield", handler::yield)); var func = factory.call(ctx, thisArg, new NativeFunction("yield", handler::yield));
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");

View File

@ -1,7 +1,9 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.Reading;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.DataKey; import me.topchetoeu.jscript.engine.DataKey;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
@ -14,12 +16,22 @@ public class Internals {
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>(); private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
private static final DataKey<Integer> I = new DataKey<>(); private static final DataKey<Integer> I = new DataKey<>();
@Native public static void log(Context ctx, Object ...args) throws InterruptedException {
@Native public static void log(Context ctx, Object ...args) {
for (var arg : args) { for (var arg : args) {
Values.printValue(ctx, arg); Values.printValue(ctx, arg);
} }
System.out.println(); System.out.println();
} }
@Native public static String readline(Context ctx) {
try {
return Reading.read();
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { @Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) {
var thread = new Thread(() -> { var thread = new Thread(() -> {
@ -31,12 +43,12 @@ public class Internals {
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
ctx.message.engine.pushMsg(false, ctx.message, func, null, args); ctx.engine.pushMsg(false, ctx, func, null, args);
}); });
thread.start(); thread.start();
int i = ctx.env.data.increase(I, 1, 0); int i = ctx.environment().data.increase(I, 1, 0);
var threads = ctx.env.data.add(THREADS, new HashMap<>()); var threads = ctx.environment().data.get(THREADS, new HashMap<>());
threads.put(++i, thread); threads.put(++i, thread);
return i; return i;
} }
@ -51,19 +63,19 @@ public class Internals {
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
ctx.message.engine.pushMsg(false, ctx.message, func, null, args); ctx.engine.pushMsg(false, ctx, func, null, args);
} }
}); });
thread.start(); thread.start();
int i = ctx.env.data.increase(I, 1, 0); int i = ctx.environment().data.increase(I, 1, 0);
var threads = ctx.env.data.add(THREADS, new HashMap<>()); var threads = ctx.environment().data.get(THREADS, new HashMap<>());
threads.put(++i, thread); threads.put(++i, thread);
return i; return i;
} }
@Native public static void clearTimeout(Context ctx, int i) { @Native public static void clearTimeout(Context ctx, int i) {
var threads = ctx.env.data.add(THREADS, new HashMap<>()); var threads = ctx.environment().data.get(THREADS, new HashMap<>());
var thread = threads.remove(i); var thread = threads.remove(i);
if (thread != null) thread.interrupt(); if (thread != null) thread.interrupt();
@ -72,15 +84,15 @@ public class Internals {
clearTimeout(ctx, i); clearTimeout(ctx, i);
} }
@Native public static double parseInt(Context ctx, String val) throws InterruptedException { @Native public static double parseInt(Context ctx, String val) {
return NumberLib.parseInt(ctx, val); return NumberLib.parseInt(ctx, val);
} }
@Native public static double parseFloat(Context ctx, String val) throws InterruptedException { @Native public static double parseFloat(Context ctx, String val) {
return NumberLib.parseFloat(ctx, val); return NumberLib.parseFloat(ctx, val);
} }
public void apply(Environment env) { public void apply(Environment env) {
var wp = env.wrappersProvider; var wp = env.wrappers;
var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class)); var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class));
glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); glob.define(null, "Math", false, wp.getNamespace(MathLib.class));

View File

@ -1,85 +1,22 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import java.util.HashSet; import me.topchetoeu.jscript.Filename;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
public class JSONLib { public class JSONLib {
private static Object toJS(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSONLib::toJS).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJS(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement toJSON(Context ctx, Object val, HashSet<Object> prev) throws InterruptedException {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = toJSON(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = toJSON(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
@Native @Native
public static Object parse(Context ctx, String val) throws InterruptedException { public static Object parse(Context ctx, String val) {
try { try {
return toJS(me.topchetoeu.jscript.json.JSON.parse("<value>", val)); return JSON.toJs(JSON.parse(new Filename("jscript", "json"), val));
}
catch (SyntaxException e) {
throw EngineException.ofSyntax(e.msg);
} }
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
} }
@Native @Native
public static String stringify(Context ctx, Object val) throws InterruptedException { public static String stringify(Context ctx, Object val) {
return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>())); return me.topchetoeu.jscript.json.JSON.stringify(JSON.fromJs(ctx, val));
} }
} }

View File

@ -16,7 +16,7 @@ public class MapLib {
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>(); private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
@Native("@@Symbol.typeName") public final String name = "Map"; @Native("@@Symbol.typeName") public final String name = "Map";
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) {
return this.entries(ctx); return this.entries(ctx);
} }
@ -31,17 +31,17 @@ public class MapLib {
return false; return false;
} }
@Native public ObjectValue entries(Context ctx) throws InterruptedException { @Native public ObjectValue entries(Context ctx) {
var res = map.entrySet().stream().map(v -> { var res = map.entrySet().stream().map(v -> {
return new ArrayValue(ctx, v.getKey(), v.getValue()); return new ArrayValue(ctx, v.getKey(), v.getValue());
}).collect(Collectors.toList()); }).collect(Collectors.toList());
return Values.fromJavaIterator(ctx, res.iterator()); return Values.fromJavaIterator(ctx, res.iterator());
} }
@Native public ObjectValue keys(Context ctx) throws InterruptedException { @Native public ObjectValue keys(Context ctx) {
var res = new ArrayList<>(map.keySet()); var res = new ArrayList<>(map.keySet());
return Values.fromJavaIterator(ctx, res.iterator()); return Values.fromJavaIterator(ctx, res.iterator());
} }
@Native public ObjectValue values(Context ctx) throws InterruptedException { @Native public ObjectValue values(Context ctx) {
var res = new ArrayList<>(map.values()); var res = new ArrayList<>(map.values());
return Values.fromJavaIterator(ctx, res.iterator()); return Values.fromJavaIterator(ctx, res.iterator());
} }
@ -61,13 +61,13 @@ public class MapLib {
return map.size(); return map.size();
} }
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) {
var keys = new ArrayList<>(map.keySet()); var keys = new ArrayList<>(map.keySet());
for (var el : keys) func.call(ctx, thisArg, el, map.get(el), this); for (var el : keys) func.call(ctx, thisArg, el, map.get(el), this);
} }
@Native public MapLib(Context ctx, Object iterable) throws InterruptedException { @Native public MapLib(Context ctx, Object iterable) {
for (var el : Values.toJavaIterable(ctx, iterable)) { for (var el : Values.toJavaIterable(ctx, iterable)) {
try { try {
set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));

View File

@ -29,22 +29,22 @@ public class NumberLib {
return val > MIN_SAFE_INTEGER && val < MAX_SAFE_INTEGER; return val > MIN_SAFE_INTEGER && val < MAX_SAFE_INTEGER;
} }
@Native public static double parseFloat(Context ctx, String val) throws InterruptedException { @Native public static double parseFloat(Context ctx, String val) {
return Values.toNumber(ctx, val); return Values.toNumber(ctx, val);
} }
@Native public static double parseInt(Context ctx, String val) throws InterruptedException { @Native public static double parseInt(Context ctx, String val) {
return (long)Values.toNumber(ctx, val); return (long)Values.toNumber(ctx, val);
} }
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
val = Values.toNumber(ctx, val); val = Values.toNumber(ctx, val);
if (thisArg instanceof ObjectValue) return new NumberLib((double)val); if (thisArg instanceof ObjectValue) return new NumberLib((double)val);
else return val; else return val;
} }
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
return Values.toString(ctx, Values.toNumber(ctx, thisArg)); return Values.toString(ctx, Values.toNumber(ctx, thisArg));
} }
@Native(thisArg = true) public static double valueOf(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static double valueOf(Context ctx, Object thisArg) {
if (thisArg instanceof NumberLib) return ((NumberLib)thisArg).value; if (thisArg instanceof NumberLib) return ((NumberLib)thisArg).value;
else return Values.toNumber(ctx, thisArg); else return Values.toNumber(ctx, thisArg);
} }

View File

@ -14,7 +14,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class ObjectLib { public class ObjectLib {
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException { @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) {
for (var obj : src) { for (var obj : src) {
for (var key : Values.getMembers(ctx, obj, true, true)) { for (var key : Values.getMembers(ctx, obj, true, true)) {
Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key)); Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key));
@ -22,13 +22,13 @@ public class ObjectLib {
} }
return dst; return dst;
} }
@Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) throws InterruptedException { @Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) {
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.setPrototype(ctx, proto); obj.setPrototype(ctx, proto);
return defineProperties(ctx, obj, props); return defineProperties(ctx, obj, props);
} }
@Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) throws InterruptedException { @Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) {
var hasVal = attrib.hasMember(ctx, "value", false); var hasVal = attrib.hasMember(ctx, "value", false);
var hasGet = attrib.hasMember(ctx, "get", false); var hasGet = attrib.hasMember(ctx, "get", false);
var hasSet = attrib.hasMember(ctx, "set", false); var hasSet = attrib.hasMember(ctx, "set", false);
@ -59,7 +59,7 @@ public class ObjectLib {
return obj; return obj;
} }
@Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) throws InterruptedException { @Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) {
for (var key : Values.getMembers(null, obj, false, false)) { for (var key : Values.getMembers(null, obj, false, false)) {
obj.defineProperty(ctx, key, attrib.getMember(ctx, key)); obj.defineProperty(ctx, key, attrib.getMember(ctx, key));
} }
@ -67,7 +67,7 @@ public class ObjectLib {
return obj; return obj;
} }
@Native public static ArrayValue keys(Context ctx, Object obj, Object all) throws InterruptedException { @Native public static ArrayValue keys(Context ctx, Object obj, Object all) {
var res = new ArrayValue(); var res = new ArrayValue();
var _all = Values.toBoolean(all); var _all = Values.toBoolean(all);
@ -77,7 +77,7 @@ public class ObjectLib {
return res; return res;
} }
@Native public static ArrayValue entries(Context ctx, Object obj, Object all) throws InterruptedException { @Native public static ArrayValue entries(Context ctx, Object obj, Object all) {
var res = new ArrayValue(); var res = new ArrayValue();
var _all = Values.toBoolean(all); var _all = Values.toBoolean(all);
@ -87,7 +87,7 @@ public class ObjectLib {
return res; return res;
} }
@Native public static ArrayValue values(Context ctx, Object obj, Object all) throws InterruptedException { @Native public static ArrayValue values(Context ctx, Object obj, Object all) {
var res = new ArrayValue(); var res = new ArrayValue();
var _all = Values.toBoolean(all); var _all = Values.toBoolean(all);
@ -98,10 +98,10 @@ public class ObjectLib {
return res; return res;
} }
@Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) throws InterruptedException { @Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) {
return Values.getMemberDescriptor(ctx, obj, key); return Values.getMemberDescriptor(ctx, obj, key);
} }
@Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) throws InterruptedException { @Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) {
var res = new ObjectValue(); var res = new ObjectValue();
for (var key : Values.getMembers(ctx, obj, true, true)) { for (var key : Values.getMembers(ctx, obj, true, true)) {
res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key)); res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key));
@ -109,7 +109,7 @@ public class ObjectLib {
return res; return res;
} }
@Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) throws InterruptedException { @Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) {
var res = new ArrayValue(); var res = new ArrayValue();
var _all = Values.toBoolean(all); var _all = Values.toBoolean(all);
@ -119,7 +119,7 @@ public class ObjectLib {
return res; return res;
} }
@Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) throws InterruptedException { @Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) {
var res = new ArrayValue(); var res = new ArrayValue();
for (var key : Values.getMembers(ctx, obj, true, true)) { for (var key : Values.getMembers(ctx, obj, true, true)) {
@ -128,19 +128,19 @@ public class ObjectLib {
return res; return res;
} }
@Native public static boolean hasOwn(Context ctx, Object obj, Object key) throws InterruptedException { @Native public static boolean hasOwn(Context ctx, Object obj, Object key) {
return Values.hasMember(ctx, obj, key, true); return Values.hasMember(ctx, obj, key, true);
} }
@Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) throws InterruptedException { @Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) {
return Values.getPrototype(ctx, obj); return Values.getPrototype(ctx, obj);
} }
@Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) throws InterruptedException { @Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) {
Values.setPrototype(ctx, obj, proto); Values.setPrototype(ctx, obj, proto);
return obj; return obj;
} }
@Native public static ObjectValue fromEntries(Context ctx, Object iterable) throws InterruptedException { @Native public static ObjectValue fromEntries(Context ctx, Object iterable) {
var res = new ObjectValue(); var res = new ObjectValue();
for (var el : Values.toJavaIterable(ctx, iterable)) { for (var el : Values.toJavaIterable(ctx, iterable)) {
@ -152,23 +152,23 @@ public class ObjectLib {
return res; return res;
} }
@Native public static Object preventExtensions(Context ctx, Object obj) throws InterruptedException { @Native public static Object preventExtensions(Context ctx, Object obj) {
if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions(); if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions();
return obj; return obj;
} }
@Native public static Object seal(Context ctx, Object obj) throws InterruptedException { @Native public static Object seal(Context ctx, Object obj) {
if (obj instanceof ObjectValue) ((ObjectValue)obj).seal(); if (obj instanceof ObjectValue) ((ObjectValue)obj).seal();
return obj; return obj;
} }
@Native public static Object freeze(Context ctx, Object obj) throws InterruptedException { @Native public static Object freeze(Context ctx, Object obj) {
if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze(); if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze();
return obj; return obj;
} }
@Native public static boolean isExtensible(Context ctx, Object obj) throws InterruptedException { @Native public static boolean isExtensible(Context ctx, Object obj) {
return obj instanceof ObjectValue && ((ObjectValue)obj).extensible(); return obj instanceof ObjectValue && ((ObjectValue)obj).extensible();
} }
@Native public static boolean isSealed(Context ctx, Object obj) throws InterruptedException { @Native public static boolean isSealed(Context ctx, Object obj) {
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
var _obj = (ObjectValue)obj; var _obj = (ObjectValue)obj;
for (var key : _obj.keys(true)) { for (var key : _obj.keys(true)) {
@ -178,7 +178,7 @@ public class ObjectLib {
return true; return true;
} }
@Native public static boolean isFrozen(Context ctx, Object obj) throws InterruptedException { @Native public static boolean isFrozen(Context ctx, Object obj) {
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
var _obj = (ObjectValue)obj; var _obj = (ObjectValue)obj;
for (var key : _obj.keys(true)) { for (var key : _obj.keys(true)) {
@ -193,18 +193,18 @@ public class ObjectLib {
@Native(thisArg = true) public static Object valueOf(Context ctx, Object thisArg) { @Native(thisArg = true) public static Object valueOf(Context ctx, Object thisArg) {
return thisArg; return thisArg;
} }
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
var name = Values.getMember(ctx, thisArg, ctx.env.symbol("Symbol.typeName")); var name = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.typeName"));
if (name == null) name = "Unknown"; if (name == null) name = "Unknown";
else name = Values.toString(ctx, name); else name = Values.toString(ctx, name);
return "[object " + name + "]"; return "[object " + name + "]";
} }
@Native(thisArg = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object key) throws InterruptedException { @Native(thisArg = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object key) {
return ObjectLib.hasOwn(ctx, thisArg, Values.convert(ctx, key, String.class)); return ObjectLib.hasOwn(ctx, thisArg, Values.convert(ctx, key, String.class));
} }
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) throws InterruptedException { @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) {
if (arg == null || arg == Values.NULL) return new ObjectValue(); if (arg == null || arg == Values.NULL) return new ObjectValue();
else if (arg instanceof Boolean) return BooleanLib.constructor(ctx, thisArg, arg); else if (arg instanceof Boolean) return BooleanLib.constructor(ctx, thisArg, arg);
else if (arg instanceof Number) return NumberLib.constructor(ctx, thisArg, arg); else if (arg instanceof Number) return NumberLib.constructor(ctx, thisArg, arg);

View File

@ -6,7 +6,6 @@ import java.util.Map;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.Message;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
@ -14,6 +13,7 @@ import me.topchetoeu.jscript.engine.values.NativeWrapper;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
@ -32,19 +32,19 @@ public class PromiseLib {
} }
@Native("resolve") @Native("resolve")
public static PromiseLib ofResolved(Context ctx, Object val) throws InterruptedException { public static PromiseLib ofResolved(Context ctx, Object val) {
var res = new PromiseLib(); var res = new PromiseLib();
res.fulfill(ctx, val); res.fulfill(ctx, val);
return res; return res;
} }
@Native("reject") @Native("reject")
public static PromiseLib ofRejected(Context ctx, Object val) throws InterruptedException { public static PromiseLib ofRejected(Context ctx, Object val) {
var res = new PromiseLib(); var res = new PromiseLib();
res.reject(ctx, val); res.reject(ctx, val);
return res; return res;
} }
@Native public static PromiseLib any(Context ctx, Object _promises) throws InterruptedException { @Native public static PromiseLib any(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
@ -69,7 +69,7 @@ public class PromiseLib {
return res; return res;
} }
@Native public static PromiseLib race(Context ctx, Object _promises) throws InterruptedException { @Native public static PromiseLib race(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
@ -85,7 +85,7 @@ public class PromiseLib {
return res; return res;
} }
@Native public static PromiseLib all(Context ctx, Object _promises) throws InterruptedException { @Native public static PromiseLib all(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
@ -112,7 +112,7 @@ public class PromiseLib {
return res; return res;
} }
@Native public static PromiseLib allSettled(Context ctx, Object _promises) throws InterruptedException { @Native public static PromiseLib allSettled(Context ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
@ -155,7 +155,7 @@ public class PromiseLib {
* Thread safe - you can call this from anywhere * Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript * HOWEVER, it's strongly recommended to use this only in javascript
*/ */
@Native(thisArg=true) public static Object then(Context ctx, Object thisArg, Object _onFulfill, Object _onReject) throws InterruptedException { @Native(thisArg=true) public static Object then(Context ctx, Object thisArg, Object _onFulfill, Object _onReject) {
var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null; var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null;
var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null; var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null;
@ -186,9 +186,7 @@ public class PromiseLib {
if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle); if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle);
else { else {
Object next; Object next;
try { try { next = Values.getMember(ctx, thisArg, "then"); }
next = Values.getMember(ctx, thisArg, "then");
}
catch (IllegalArgumentException e) { next = null; } catch (IllegalArgumentException e) { next = null; }
try { try {
@ -206,14 +204,14 @@ public class PromiseLib {
* Thread safe - you can call this from anywhere * Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript * HOWEVER, it's strongly recommended to use this only in javascript
*/ */
@Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) throws InterruptedException { @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) {
return then(ctx, thisArg, null, _onReject); return then(ctx, thisArg, null, _onReject);
} }
/** /**
* Thread safe - you can call this from anywhere * Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript * HOWEVER, it's strongly recommended to use this only in javascript
*/ */
@Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) throws InterruptedException { @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) {
return then(ctx, thisArg, return then(ctx, thisArg,
new NativeFunction(null, (e, th, _args) -> { new NativeFunction(null, (e, th, _args) -> {
if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx);
@ -236,12 +234,12 @@ public class PromiseLib {
private boolean handled = false; private boolean handled = false;
private Object val; private Object val;
private void resolve(Context ctx, Object val, int state) throws InterruptedException { public void fulfill(Context ctx, Object val) {
if (this.state != STATE_PENDING) return; if (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], state); return null; }), new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }),
new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], STATE_REJECTED); return null; }) new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
); );
else { else {
Object next; Object next;
@ -250,29 +248,55 @@ public class PromiseLib {
try { try {
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, state); return null; }), new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }),
new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, STATE_REJECTED); return null; }) new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
); );
else { else {
this.val = val; this.val = val;
this.state = state; this.state = STATE_FULFILLED;
if (state == STATE_FULFILLED) {
for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val); for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val);
handles = null;
} }
else if (state == STATE_REJECTED) { }
catch (EngineException err) {
this.reject(ctx, err.value);
}
}
}
public void reject(Context ctx, Object val) {
if (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }),
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
);
else {
Object next;
try { next = Values.getMember(ctx, val, "next"); }
catch (IllegalArgumentException e) { next = null; }
try {
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }),
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
);
else {
this.val = val;
this.state = STATE_REJECTED;
for (var handle : handles) handle.rejected.call(handle.ctx, null, val); for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
if (handles.size() == 0) { if (handles.size() == 0) {
ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> { ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
if (!handled) { if (!handled) {
try { Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); } Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
catch (InterruptedException ex) { } throw new InterruptException();
} }
return null; return null;
}), null); }), null);
} }
}
handles = null; handles = null;
} }
@ -283,23 +307,10 @@ public class PromiseLib {
} }
} }
/**
* Thread safe - call from any thread
*/
public void fulfill(Context ctx, Object val) throws InterruptedException {
resolve(ctx, val, STATE_FULFILLED);
}
/**
* Thread safe - call from any thread
*/
public void reject(Context ctx, Object val) throws InterruptedException {
resolve(ctx, val, STATE_REJECTED);
}
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
if (state == STATE_FULFILLED) ctx.message.engine.pushMsg(true, new Message(ctx.message.engine), fulfill, null, val); if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val);
else if (state == STATE_REJECTED) { else if (state == STATE_REJECTED) {
ctx.message.engine.pushMsg(true, new Message(ctx.message.engine), reject, null, val); ctx.engine.pushMsg(true, ctx, reject, null, val);
handled = true; handled = true;
} }
else handles.add(new Handle(ctx, fulfill, reject)); else handles.add(new Handle(ctx, fulfill, reject));
@ -314,7 +325,7 @@ public class PromiseLib {
/** /**
* NOT THREAD SAFE - must be called from the engine executor thread * NOT THREAD SAFE - must be called from the engine executor thread
*/ */
@Native public PromiseLib(Context ctx, FunctionValue func) throws InterruptedException { @Native public PromiseLib(Context ctx, FunctionValue func) {
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
try { try {
func.call( func.call(

View File

@ -8,7 +8,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class RangeErrorLib extends ErrorLib { public class RangeErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
var target = ErrorLib.constructor(ctx, thisArg, message); var target = ErrorLib.constructor(ctx, thisArg, message);
target.defineProperty(ctx, "name", "RangeError"); target.defineProperty(ctx, "name", "RangeError");
return target; return target;

View File

@ -17,7 +17,7 @@ public class RegExpLib {
private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL); private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL);
private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]"); private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]");
private static String cleanupPattern(Context ctx, Object val) throws InterruptedException { private static String cleanupPattern(Context ctx, Object val) {
if (val == null) return "(?:)"; if (val == null) return "(?:)";
if (val instanceof RegExpLib) return ((RegExpLib)val).source; if (val instanceof RegExpLib) return ((RegExpLib)val).source;
if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) { if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) {
@ -27,7 +27,7 @@ public class RegExpLib {
if (res.equals("")) return "(?:)"; if (res.equals("")) return "(?:)";
return res; return res;
} }
private static String cleanupFlags(Context ctx, Object val) throws InterruptedException { private static String cleanupFlags(Context ctx, Object val) {
if (val == null) return ""; if (val == null) return "";
return Values.toString(ctx, val); return Values.toString(ctx, val);
} }
@ -46,7 +46,7 @@ public class RegExpLib {
} }
@Native @Native
public static RegExpLib escape(Context ctx, Object raw, Object flags) throws InterruptedException { public static RegExpLib escape(Context ctx, Object raw, Object flags) {
return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags)); return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags));
} }
public static RegExpLib escape(String raw, String flags) { public static RegExpLib escape(String raw, String flags) {
@ -97,9 +97,7 @@ public class RegExpLib {
var groups = new ObjectValue(); var groups = new ObjectValue();
for (var el : namedGroups) { for (var el : namedGroups) {
try { try { groups.defineProperty(null, el, matcher.group(el)); }
groups.defineProperty(null, el, matcher.group(el));
}
catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) { }
} }
if (groups.values.size() == 0) groups = null; if (groups.values.size() == 0) groups = null;
@ -135,7 +133,7 @@ public class RegExpLib {
return "/" + source + "/" + flags(); return "/" + source + "/" + flags();
} }
@Native("@@Symvol.match") public Object match(Context ctx, String target) throws InterruptedException { @Native("@@Symvol.match") public Object match(Context ctx, String target) {
if (this.global) { if (this.global) {
var res = new ArrayValue(); var res = new ArrayValue();
Object val; Object val;
@ -152,7 +150,7 @@ public class RegExpLib {
} }
} }
@Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) throws InterruptedException { @Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) {
var pattern = new RegExpLib(this.source, this.flags() + "g"); var pattern = new RegExpLib(this.source, this.flags() + "g");
return Values.fromJavaIterator(ctx, new Iterator<Object>() { return Values.fromJavaIterator(ctx, new Iterator<Object>() {
@ -174,7 +172,7 @@ public class RegExpLib {
}); });
} }
@Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) throws InterruptedException { @Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) {
var pattern = new RegExpLib(this.source, this.flags() + "g"); var pattern = new RegExpLib(this.source, this.flags() + "g");
Object match; Object match;
int lastEnd = 0; int lastEnd = 0;
@ -260,7 +258,7 @@ public class RegExpLib {
// else return -1; // else return -1;
// } // }
// }, // },
@Native public RegExpLib(Context ctx, Object pattern, Object flags) throws InterruptedException { @Native public RegExpLib(Context ctx, Object pattern, Object flags) {
this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags)); this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags));
} }
public RegExpLib(String pattern, String flags) { public RegExpLib(String pattern, String flags) {

View File

@ -16,19 +16,19 @@ public class SetLib {
private LinkedHashSet<Object> set = new LinkedHashSet<>(); private LinkedHashSet<Object> set = new LinkedHashSet<>();
@Native("@@Symbol.typeName") public final String name = "Set"; @Native("@@Symbol.typeName") public final String name = "Set";
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) {
return this.values(ctx); return this.values(ctx);
} }
@Native public ObjectValue entries(Context ctx) throws InterruptedException { @Native public ObjectValue entries(Context ctx) {
var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList());
return Values.fromJavaIterator(ctx, res.iterator()); return Values.fromJavaIterator(ctx, res.iterator());
} }
@Native public ObjectValue keys(Context ctx) throws InterruptedException { @Native public ObjectValue keys(Context ctx) {
var res = new ArrayList<>(set); var res = new ArrayList<>(set);
return Values.fromJavaIterator(ctx, res.iterator()); return Values.fromJavaIterator(ctx, res.iterator());
} }
@Native public ObjectValue values(Context ctx) throws InterruptedException { @Native public ObjectValue values(Context ctx) {
var res = new ArrayList<>(set); var res = new ArrayList<>(set);
return Values.fromJavaIterator(ctx, res.iterator()); return Values.fromJavaIterator(ctx, res.iterator());
} }
@ -51,13 +51,13 @@ public class SetLib {
return set.size(); return set.size();
} }
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) {
var keys = new ArrayList<>(set); var keys = new ArrayList<>(set);
for (var el : keys) func.call(ctx, thisArg, el, el, this); for (var el : keys) func.call(ctx, thisArg, el, el, this);
} }
@Native public SetLib(Context ctx, Object iterable) throws InterruptedException { @Native public SetLib(Context ctx, Object iterable) {
for (var el : Values.toJavaIterable(ctx, iterable)) add(el); for (var el : Values.toJavaIterable(ctx, iterable)) add(el);
} }
} }

View File

@ -19,7 +19,7 @@ import me.topchetoeu.jscript.interop.NativeInit;
public class StringLib { public class StringLib {
public final String value; public final String value;
private static String passThis(Context ctx, String funcName, Object val) throws InterruptedException { private static String passThis(Context ctx, String funcName, Object val) {
if (val instanceof StringLib) return ((StringLib)val).value; if (val instanceof StringLib) return ((StringLib)val).value;
else if (val instanceof String) return (String)val; else if (val instanceof String) return (String)val;
else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName));
@ -33,50 +33,50 @@ public class StringLib {
return i; return i;
} }
@NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) throws InterruptedException { @NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) {
return passThis(ctx, "substring", thisArg).length(); return passThis(ctx, "substring", thisArg).length();
} }
@Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { @Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) {
var val = passThis(ctx, "substring", thisArg); var val = passThis(ctx, "substring", thisArg);
start = normalizeI(start, val.length(), true); start = normalizeI(start, val.length(), true);
int end = normalizeI(_end == null ? val.length() : (int)Values.toNumber(ctx, _end), val.length(), true); int end = normalizeI(_end == null ? val.length() : (int)Values.toNumber(ctx, _end), val.length(), true);
return val.substring(start, end); return val.substring(start, end);
} }
@Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) throws InterruptedException { @Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) {
var val = passThis(ctx, "substr", thisArg); var val = passThis(ctx, "substr", thisArg);
int len = _len == null ? val.length() - start : (int)Values.toNumber(ctx, _len); int len = _len == null ? val.length() - start : (int)Values.toNumber(ctx, _len);
return substring(ctx, val, start, start + len); return substring(ctx, val, start, start + len);
} }
@Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) {
return passThis(ctx, "toLowerCase", thisArg).toLowerCase(); return passThis(ctx, "toLowerCase", thisArg).toLowerCase();
} }
@Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) {
return passThis(ctx, "toUpperCase", thisArg).toUpperCase(); return passThis(ctx, "toUpperCase", thisArg).toUpperCase();
} }
@Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) throws InterruptedException { @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) {
return passThis(ctx, "charAt", thisArg).charAt(i) + ""; return passThis(ctx, "charAt", thisArg).charAt(i) + "";
} }
@Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) throws InterruptedException { @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) {
return passThis(ctx, "charCodeAt", thisArg).charAt(i); return passThis(ctx, "charCodeAt", thisArg).charAt(i);
} }
@Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) throws InterruptedException { @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) {
return passThis(ctx, "startsWith", thisArg).startsWith(term, pos); return passThis(ctx, "startsWith", thisArg).startsWith(term, pos);
} }
@Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) throws InterruptedException { @Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) {
var val = passThis(ctx, "endsWith", thisArg); var val = passThis(ctx, "endsWith", thisArg);
return val.lastIndexOf(term, pos) >= 0; return val.lastIndexOf(term, pos) >= 0;
} }
@Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) throws InterruptedException { @Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) {
var val = passThis(ctx, "indexOf", thisArg); var val = passThis(ctx, "indexOf", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) { if (term != null && term != Values.NULL && !(term instanceof String)) {
var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search"));
if (search instanceof FunctionValue) { if (search instanceof FunctionValue) {
return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start)); return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start));
} }
@ -84,11 +84,11 @@ public class StringLib {
return val.indexOf(Values.toString(ctx, term), start); return val.indexOf(Values.toString(ctx, term), start);
} }
@Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { @Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) {
var val = passThis(ctx, "lastIndexOf", thisArg); var val = passThis(ctx, "lastIndexOf", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) { if (term != null && term != Values.NULL && !(term instanceof String)) {
var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search"));
if (search instanceof FunctionValue) { if (search instanceof FunctionValue) {
return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos)); return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos));
} }
@ -97,15 +97,15 @@ public class StringLib {
return val.lastIndexOf(Values.toString(ctx, term), pos); return val.lastIndexOf(Values.toString(ctx, term), pos);
} }
@Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { @Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) {
return lastIndexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0; return lastIndexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0;
} }
@Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, String replacement) {
var val = passThis(ctx, "replace", thisArg); var val = passThis(ctx, "replace", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) { if (term != null && term != Values.NULL && !(term instanceof String)) {
var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
if (replace instanceof FunctionValue) { if (replace instanceof FunctionValue) {
return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement));
} }
@ -113,11 +113,11 @@ public class StringLib {
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement);
} }
@Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) {
var val = passThis(ctx, "replaceAll", thisArg); var val = passThis(ctx, "replaceAll", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) { if (term != null && term != Values.NULL && !(term instanceof String)) {
var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
if (replace instanceof FunctionValue) { if (replace instanceof FunctionValue) {
return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement));
} }
@ -126,17 +126,17 @@ public class StringLib {
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement);
} }
@Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) {
var val = passThis(ctx, "match", thisArg); var val = passThis(ctx, "match", thisArg);
FunctionValue match; FunctionValue match;
try { try {
var _match = Values.getMember(ctx, term, ctx.env.symbol("Symbol.match")); var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.match"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match; if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else if (ctx.env.regexConstructor != null) { else if (ctx.environment().regexConstructor != null) {
var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), ""); var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), "");
_match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.match")); _match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.match"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match; if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else throw EngineException.ofError("Regular expressions don't support matching."); else throw EngineException.ofError("Regular expressions don't support matching.");
} }
@ -148,20 +148,20 @@ public class StringLib {
if (res instanceof ArrayValue) return (ArrayValue)res; if (res instanceof ArrayValue) return (ArrayValue)res;
else return new ArrayValue(ctx, ""); else return new ArrayValue(ctx, "");
} }
@Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { @Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) {
var val = passThis(ctx, "matchAll", thisArg); var val = passThis(ctx, "matchAll", thisArg);
FunctionValue match = null; FunctionValue match = null;
try { try {
var _match = Values.getMember(ctx, term, ctx.env.symbol("Symbol.matchAll")); var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.matchAll"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match; if (_match instanceof FunctionValue) match = (FunctionValue)_match;
} }
catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) { }
if (match == null && ctx.env.regexConstructor != null) { if (match == null && ctx.environment().regexConstructor != null) {
var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), "g"); var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), "g");
var _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.matchAll")); var _match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.matchAll"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match; if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else throw EngineException.ofError("Regular expressions don't support matching."); else throw EngineException.ofError("Regular expressions don't support matching.");
} }
@ -170,13 +170,13 @@ public class StringLib {
return match.call(ctx, term, val); return match.call(ctx, term, val);
} }
@Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) throws InterruptedException { @Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) {
var val = passThis(ctx, "split", thisArg); var val = passThis(ctx, "split", thisArg);
if (lim != null) lim = Values.toNumber(ctx, lim); if (lim != null) lim = Values.toNumber(ctx, lim);
if (term != null && term != Values.NULL && !(term instanceof String)) { if (term != null && term != Values.NULL && !(term instanceof String)) {
var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
if (replace instanceof FunctionValue) { if (replace instanceof FunctionValue) {
var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible); var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible);
@ -217,30 +217,30 @@ public class StringLib {
return res; return res;
} }
@Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { @Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) {
return substring(ctx, passThis(ctx, "slice", thisArg), start, _end); return substring(ctx, passThis(ctx, "slice", thisArg), start, _end);
} }
@Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) throws InterruptedException { @Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) {
var res = new StringBuilder(passThis(ctx, "concat", thisArg)); var res = new StringBuilder(passThis(ctx, "concat", thisArg));
for (var el : args) res.append(Values.toString(ctx, el)); for (var el : args) res.append(Values.toString(ctx, el));
return res.toString(); return res.toString();
} }
@Native(thisArg = true) public static String trim(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String trim(Context ctx, Object thisArg) {
return passThis(ctx, "trim", thisArg).trim(); return passThis(ctx, "trim", thisArg).trim();
} }
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
val = Values.toString(ctx, val); val = Values.toString(ctx, val);
if (thisArg instanceof ObjectValue) return new StringLib((String)val); if (thisArg instanceof ObjectValue) return new StringLib((String)val);
else return val; else return val;
} }
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
return passThis(ctx, "toString", thisArg); return passThis(ctx, "toString", thisArg);
} }
@Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) {
return passThis(ctx, "valueOf", thisArg); return passThis(ctx, "valueOf", thisArg);
} }

View File

@ -18,32 +18,33 @@ import me.topchetoeu.jscript.interop.NativeInit;
public class SymbolLib { public class SymbolLib {
private static final Map<String, Symbol> symbols = new HashMap<>(); private static final Map<String, Symbol> symbols = new HashMap<>();
@NativeGetter public static Symbol typeName(Context ctx) { return ctx.env.symbol("Symbol.typeName"); } @NativeGetter public static Symbol typeName(Context ctx) { return ctx.environment().symbol("Symbol.typeName"); }
@NativeGetter public static Symbol replace(Context ctx) { return ctx.env.symbol("Symbol.replace"); } @NativeGetter public static Symbol replace(Context ctx) { return ctx.environment().symbol("Symbol.replace"); }
@NativeGetter public static Symbol match(Context ctx) { return ctx.env.symbol("Symbol.match"); } @NativeGetter public static Symbol match(Context ctx) { return ctx.environment().symbol("Symbol.match"); }
@NativeGetter public static Symbol matchAll(Context ctx) { return ctx.env.symbol("Symbol.matchAll"); } @NativeGetter public static Symbol matchAll(Context ctx) { return ctx.environment().symbol("Symbol.matchAll"); }
@NativeGetter public static Symbol split(Context ctx) { return ctx.env.symbol("Symbol.split"); } @NativeGetter public static Symbol split(Context ctx) { return ctx.environment().symbol("Symbol.split"); }
@NativeGetter public static Symbol search(Context ctx) { return ctx.env.symbol("Symbol.search"); } @NativeGetter public static Symbol search(Context ctx) { return ctx.environment().symbol("Symbol.search"); }
@NativeGetter public static Symbol iterator(Context ctx) { return ctx.env.symbol("Symbol.iterator"); } @NativeGetter public static Symbol iterator(Context ctx) { return ctx.environment().symbol("Symbol.iterator"); }
@NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.env.symbol("Symbol.asyncIterator"); } @NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.environment().symbol("Symbol.asyncIterator"); }
@NativeGetter public static Symbol cause(Context ctx) { return ctx.environment().symbol("Symbol.cause"); }
public final Symbol value; public final Symbol value;
private static Symbol passThis(Context ctx, String funcName, Object val) throws InterruptedException { private static Symbol passThis(Context ctx, String funcName, Object val) {
if (val instanceof SymbolLib) return ((SymbolLib)val).value; if (val instanceof SymbolLib) return ((SymbolLib)val).value;
else if (val instanceof Symbol) return (Symbol)val; else if (val instanceof Symbol) return (Symbol)val;
else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName));
} }
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new.");
if (val == null) return new Symbol(""); if (val == null) return new Symbol("");
else return new Symbol(Values.toString(ctx, val)); else return new Symbol(Values.toString(ctx, val));
} }
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
return passThis(ctx, "toString", thisArg).value; return passThis(ctx, "toString", thisArg).value;
} }
@Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) {
return passThis(ctx, "valueOf", thisArg); return passThis(ctx, "valueOf", thisArg);
} }

View File

@ -8,7 +8,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class SyntaxErrorLib extends ErrorLib { public class SyntaxErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
var target = ErrorLib.constructor(ctx, thisArg, message); var target = ErrorLib.constructor(ctx, thisArg, message);
target.defineProperty(ctx, "name", "SyntaxError"); target.defineProperty(ctx, "name", "SyntaxError");
return target; return target;

View File

@ -8,7 +8,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class TypeErrorLib extends ErrorLib { public class TypeErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
var target = ErrorLib.constructor(ctx, thisArg, message); var target = ErrorLib.constructor(ctx, thisArg, message);
target.defineProperty(ctx, "name", "TypeError"); target.defineProperty(ctx, "name", "TypeError");
return target; return target;

View File

@ -6,7 +6,9 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.TreeSet;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.*; import me.topchetoeu.jscript.compilation.*;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
@ -25,7 +27,7 @@ import me.topchetoeu.jscript.parsing.ParseRes.State;
// TODO: this has to be rewritten // TODO: this has to be rewritten
public class Parsing { public class Parsing {
public static interface Parser<T> { public static interface Parser<T> {
ParseRes<T> parse(String filename, List<Token> tokens, int i); ParseRes<T> parse(Filename filename, List<Token> tokens, int i);
} }
private static class ObjProp { private static class ObjProp {
@ -69,7 +71,7 @@ public class Parsing {
reserved.add("delete"); reserved.add("delete");
reserved.add("break"); reserved.add("break");
reserved.add("continue"); reserved.add("continue");
reserved.add("debug"); reserved.add("debugger");
reserved.add("implements"); reserved.add("implements");
reserved.add("interface"); reserved.add("interface");
reserved.add("package"); reserved.add("package");
@ -122,7 +124,7 @@ public class Parsing {
private static final int CURR_MULTI_COMMENT = 8; private static final int CURR_MULTI_COMMENT = 8;
private static final int CURR_SINGLE_COMMENT = 9; private static final int CURR_SINGLE_COMMENT = 9;
private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, String filename, List<RawToken> tokens) { private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List<RawToken> tokens) {
var res = currToken.toString(); var res = currToken.toString();
switch (currStage) { switch (currStage) {
@ -143,7 +145,7 @@ public class Parsing {
// This method is so long because we're tokenizing the string using an iterative approach // This method is so long because we're tokenizing the string using an iterative approach
// instead of a recursive descent parser. This is mainly done for performance reasons. // instead of a recursive descent parser. This is mainly done for performance reasons.
private static ArrayList<RawToken> splitTokens(String filename, String raw) { private static ArrayList<RawToken> splitTokens(Filename filename, String raw) {
var tokens = new ArrayList<RawToken>(); var tokens = new ArrayList<RawToken>();
var currToken = new StringBuilder(64); var currToken = new StringBuilder(64);
@ -572,7 +574,7 @@ public class Parsing {
else return res; else return res;
} }
private static List<Token> parseTokens(String filename, Collection<RawToken> tokens) { private static List<Token> parseTokens(Filename filename, Collection<RawToken> tokens) {
var res = new ArrayList<Token>(); var res = new ArrayList<Token>();
for (var el : tokens) { for (var el : tokens) {
@ -593,11 +595,11 @@ public class Parsing {
return res; return res;
} }
public static List<Token> tokenize(String filename, String raw) { public static List<Token> tokenize(Filename filename, String raw) {
return parseTokens(filename, splitTokens(filename, raw)); return parseTokens(filename, splitTokens(filename, raw));
} }
public static Location getLoc(String filename, List<Token> tokens, int i) { public static Location getLoc(Filename filename, List<Token> tokens, int i) {
if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename); if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename);
if (i >= tokens.size()) i = tokens.size() - 1; if (i >= tokens.size()) i = tokens.size() - 1;
return new Location(tokens.get(i).line, tokens.get(i).start, filename); return new Location(tokens.get(i).line, tokens.get(i).start, filename);
@ -663,7 +665,7 @@ public class Parsing {
return !reserved.contains(name); return !reserved.contains(name);
} }
public static ParseRes<ConstantStatement> parseString(String filename, List<Token> tokens, int i) { public static ParseRes<ConstantStatement> parseString(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
try { try {
if (tokens.get(i).isString()) { if (tokens.get(i).isString()) {
@ -675,7 +677,7 @@ public class Parsing {
return ParseRes.failed(); return ParseRes.failed();
} }
} }
public static ParseRes<ConstantStatement> parseNumber(String filename, List<Token> tokens, int i) { public static ParseRes<ConstantStatement> parseNumber(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
try { try {
if (tokens.get(i).isNumber()) { if (tokens.get(i).isNumber()) {
@ -688,7 +690,7 @@ public class Parsing {
} }
} }
public static ParseRes<RegexStatement> parseRegex(String filename, List<Token> tokens, int i) { public static ParseRes<RegexStatement> parseRegex(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
try { try {
if (tokens.get(i).isRegex()) { if (tokens.get(i).isRegex()) {
@ -705,7 +707,7 @@ public class Parsing {
} }
} }
public static ParseRes<ArrayStatement> parseArray(String filename, List<Token> tokens, int i) { public static ParseRes<ArrayStatement> parseArray(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
@ -744,7 +746,7 @@ public class Parsing {
return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n); return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n);
} }
public static ParseRes<List<String>> parseParamList(String filename, List<Token> tokens, int i) { public static ParseRes<List<String>> parseParamList(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -776,7 +778,7 @@ public class Parsing {
return ParseRes.res(args, n); return ParseRes.res(args, n);
} }
public static ParseRes<? extends Object> parsePropName(String filename, List<Token> tokens, int i) { public static ParseRes<? extends Object> parsePropName(Filename filename, List<Token> tokens, int i) {
var idRes = parseIdentifier(tokens, i); var idRes = parseIdentifier(tokens, i);
if (idRes.isSuccess()) return ParseRes.res(idRes.result, 1); if (idRes.isSuccess()) return ParseRes.res(idRes.result, 1);
var strRes = parseString(null, tokens, i); var strRes = parseString(null, tokens, i);
@ -786,7 +788,7 @@ public class Parsing {
return ParseRes.failed(); return ParseRes.failed();
} }
public static ParseRes<ObjProp> parseObjectProp(String filename, List<Token> tokens, int i) { public static ParseRes<ObjProp> parseObjectProp(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -813,7 +815,7 @@ public class Parsing {
new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result) new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result)
), n); ), n);
} }
public static ParseRes<ObjectStatement> parseObject(String filename, List<Token> tokens, int i) { public static ParseRes<ObjectStatement> parseObject(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@ -870,7 +872,7 @@ public class Parsing {
return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n);
} }
public static ParseRes<NewStatement> parseNew(String filename, List<Token> tokens, int i) { public static ParseRes<NewStatement> parseNew(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed();
@ -886,7 +888,7 @@ public class Parsing {
return ParseRes.res(new NewStatement(loc, call.func, call.args), n); return ParseRes.res(new NewStatement(loc, call.func, call.args), n);
} }
public static ParseRes<TypeofStatement> parseTypeof(String filename, List<Token> tokens, int i) { public static ParseRes<TypeofStatement> parseTypeof(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed();
@ -897,7 +899,7 @@ public class Parsing {
return ParseRes.res(new TypeofStatement(loc, valRes.result), n); return ParseRes.res(new TypeofStatement(loc, valRes.result), n);
} }
public static ParseRes<VoidStatement> parseVoid(String filename, List<Token> tokens, int i) { public static ParseRes<VoidStatement> parseVoid(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed();
@ -908,7 +910,7 @@ public class Parsing {
return ParseRes.res(new VoidStatement(loc, valRes.result), n); return ParseRes.res(new VoidStatement(loc, valRes.result), n);
} }
public static ParseRes<? extends Statement> parseDelete(String filename, List<Token> tokens, int i) { public static ParseRes<? extends Statement> parseDelete(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed();
@ -929,7 +931,7 @@ public class Parsing {
} }
} }
public static ParseRes<FunctionStatement> parseFunction(String filename, List<Token> tokens, int i, boolean statement) { public static ParseRes<FunctionStatement> parseFunction(Filename filename, List<Token> tokens, int i, boolean statement) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -972,7 +974,7 @@ public class Parsing {
else return ParseRes.error(loc, "Expected a compound statement for function.", res); else return ParseRes.error(loc, "Expected a compound statement for function.", res);
} }
public static ParseRes<OperationStatement> parseUnary(String filename, List<Token> tokens, int i) { public static ParseRes<OperationStatement> parseUnary(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -993,7 +995,7 @@ public class Parsing {
if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n);
else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.value), res); else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.value), res);
} }
public static ParseRes<ChangeStatement> parsePrefixChange(String filename, List<Token> tokens, int i) { public static ParseRes<ChangeStatement> parsePrefixChange(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1010,7 +1012,7 @@ public class Parsing {
if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator."); if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator.");
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n); return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n);
} }
public static ParseRes<? extends Statement> parseParens(String filename, List<Token> tokens, int i) { public static ParseRes<? extends Statement> parseParens(Filename filename, List<Token> tokens, int i) {
int n = 0; int n = 0;
if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed();
@ -1023,7 +1025,7 @@ public class Parsing {
return ParseRes.res(res.result, n); return ParseRes.res(res.result, n);
} }
@SuppressWarnings("all") @SuppressWarnings("all")
public static ParseRes<? extends Statement> parseSimple(String filename, List<Token> tokens, int i, boolean statement) { public static ParseRes<? extends Statement> parseSimple(Filename filename, List<Token> tokens, int i, boolean statement) {
var res = new ArrayList<>(); var res = new ArrayList<>();
if (!statement) { if (!statement) {
@ -1050,7 +1052,7 @@ public class Parsing {
return ParseRes.any(res.toArray(ParseRes[]::new)); return ParseRes.any(res.toArray(ParseRes[]::new));
} }
public static ParseRes<VariableStatement> parseVariable(String filename, List<Token> tokens, int i) { public static ParseRes<VariableStatement> parseVariable(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var literal = parseIdentifier(tokens, i); var literal = parseIdentifier(tokens, i);
@ -1066,7 +1068,7 @@ public class Parsing {
return ParseRes.res(new VariableStatement(loc, literal.result), 1); return ParseRes.res(new VariableStatement(loc, literal.result), 1);
} }
public static ParseRes<? extends Statement> parseLiteral(String filename, List<Token> tokens, int i) { public static ParseRes<? extends Statement> parseLiteral(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var id = parseIdentifier(tokens, i); var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return id.transform(); if (!id.isSuccess()) return id.transform();
@ -1094,7 +1096,7 @@ public class Parsing {
} }
return ParseRes.failed(); return ParseRes.failed();
} }
public static ParseRes<IndexStatement> parseMember(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<IndexStatement> parseMember(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
@ -1107,7 +1109,7 @@ public class Parsing {
return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n); return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n);
} }
public static ParseRes<IndexStatement> parseIndex(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<IndexStatement> parseIndex(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
@ -1123,7 +1125,7 @@ public class Parsing {
return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n); return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n);
} }
public static ParseRes<? extends Statement> parseAssign(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<? extends Statement> parseAssign(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0 ; int n = 0 ;
@ -1157,7 +1159,7 @@ public class Parsing {
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n); return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n);
} }
public static ParseRes<CallStatement> parseCall(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<CallStatement> parseCall(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
@ -1189,7 +1191,7 @@ public class Parsing {
return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n); return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n);
} }
public static ParseRes<ChangeStatement> parsePostfixChange(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<ChangeStatement> parsePostfixChange(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1207,7 +1209,7 @@ public class Parsing {
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator."); if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator.");
return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n); return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n);
} }
public static ParseRes<OperationStatement> parseInstanceof(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<OperationStatement> parseInstanceof(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1220,7 +1222,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
} }
public static ParseRes<OperationStatement> parseIn(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<OperationStatement> parseIn(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1233,7 +1235,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
} }
public static ParseRes<CompoundStatement> parseComma(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<CompoundStatement> parseComma(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
@ -1246,7 +1248,7 @@ public class Parsing {
return ParseRes.res(new CompoundStatement(loc, prev, res.result), n); return ParseRes.res(new CompoundStatement(loc, prev, res.result), n);
} }
public static ParseRes<IfStatement> parseTernary(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<IfStatement> parseTernary(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
@ -1265,7 +1267,7 @@ public class Parsing {
return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n); return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n);
} }
public static ParseRes<? extends Statement> parseOperator(String filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<? extends Statement> parseOperator(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var n = 0; var n = 0;
@ -1290,7 +1292,7 @@ public class Parsing {
return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n);
} }
public static ParseRes<? extends Statement> parseValue(String filename, List<Token> tokens, int i, int precedence, boolean statement) { public static ParseRes<? extends Statement> parseValue(Filename filename, List<Token> tokens, int i, int precedence, boolean statement) {
Statement prev = null; Statement prev = null;
int n = 0; int n = 0;
@ -1331,11 +1333,11 @@ public class Parsing {
if (prev == null) return ParseRes.failed(); if (prev == null) return ParseRes.failed();
else return ParseRes.res(prev, n); else return ParseRes.res(prev, n);
} }
public static ParseRes<? extends Statement> parseValue(String filename, List<Token> tokens, int i, int precedence) { public static ParseRes<? extends Statement> parseValue(Filename filename, List<Token> tokens, int i, int precedence) {
return parseValue(filename, tokens, i, precedence, false); return parseValue(filename, tokens, i, precedence, false);
} }
public static ParseRes<? extends Statement> parseValueStatement(String filename, List<Token> tokens, int i) { public static ParseRes<? extends Statement> parseValueStatement(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
var valRes = parseValue(filename, tokens, i, 0, true); var valRes = parseValue(filename, tokens, i, 0, true);
if (!valRes.isSuccess()) return valRes.transform(); if (!valRes.isSuccess()) return valRes.transform();
@ -1352,7 +1354,7 @@ public class Parsing {
} }
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res); else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res);
} }
public static ParseRes<VariableDeclareStatement> parseVariableDeclare(String filename, List<Token> tokens, int i) { public static ParseRes<VariableDeclareStatement> parseVariableDeclare(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed();
@ -1365,6 +1367,7 @@ public class Parsing {
} }
while (true) { while (true) {
var nameLoc = getLoc(filename, tokens, i + n);
var nameRes = parseIdentifier(tokens, i + n++); var nameRes = parseIdentifier(tokens, i + n++);
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name."); if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name.");
@ -1382,7 +1385,7 @@ public class Parsing {
val = valRes.result; val = valRes.result;
} }
res.add(new Pair(nameRes.result, val)); res.add(new Pair(nameRes.result, val, nameLoc));
if (isOperator(tokens, i + n, Operator.COMMA)) { if (isOperator(tokens, i + n, Operator.COMMA)) {
n++; n++;
@ -1396,7 +1399,7 @@ public class Parsing {
} }
} }
public static ParseRes<ReturnStatement> parseReturn(String filename, List<Token> tokens, int i) { public static ParseRes<ReturnStatement> parseReturn(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed();
@ -1420,7 +1423,7 @@ public class Parsing {
else else
return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
} }
public static ParseRes<ThrowStatement> parseThrow(String filename, List<Token> tokens, int i) { public static ParseRes<ThrowStatement> parseThrow(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed(); if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed();
@ -1438,7 +1441,7 @@ public class Parsing {
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes);
} }
public static ParseRes<BreakStatement> parseBreak(String filename, List<Token> tokens, int i) { public static ParseRes<BreakStatement> parseBreak(Filename filename, List<Token> tokens, int i) {
if (!isIdentifier(tokens, i, "break")) return ParseRes.failed(); if (!isIdentifier(tokens, i, "break")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) { if (isStatementEnd(tokens, i + 1)) {
@ -1456,7 +1459,7 @@ public class Parsing {
} }
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
} }
public static ParseRes<ContinueStatement> parseContinue(String filename, List<Token> tokens, int i) { public static ParseRes<ContinueStatement> parseContinue(Filename filename, List<Token> tokens, int i) {
if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed(); if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) { if (isStatementEnd(tokens, i + 1)) {
@ -1474,8 +1477,8 @@ public class Parsing {
} }
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
} }
public static ParseRes<DebugStatement> parseDebug(String filename, List<Token> tokens, int i) { public static ParseRes<DebugStatement> parseDebug(Filename filename, List<Token> tokens, int i) {
if (!isIdentifier(tokens, i, "debug")) return ParseRes.failed(); if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed();
if (isStatementEnd(tokens, i + 1)) { if (isStatementEnd(tokens, i + 1)) {
if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2); if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2);
@ -1484,7 +1487,7 @@ public class Parsing {
else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.");
} }
public static ParseRes<CompoundStatement> parseCompound(String filename, List<Token> tokens, int i) { public static ParseRes<CompoundStatement> parseCompound(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@ -1510,7 +1513,7 @@ public class Parsing {
statements.add(res.result); statements.add(res.result);
} }
return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)), n); return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n);
} }
public static ParseRes<String> parseLabel(List<Token> tokens, int i) { public static ParseRes<String> parseLabel(List<Token> tokens, int i) {
int n = 0; int n = 0;
@ -1520,7 +1523,7 @@ public class Parsing {
return ParseRes.res(nameRes.result, n); return ParseRes.res(nameRes.result, n);
} }
public static ParseRes<IfStatement> parseIf(String filename, List<Token> tokens, int i) { public static ParseRes<IfStatement> parseIf(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1547,7 +1550,7 @@ public class Parsing {
return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n); return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n);
} }
public static ParseRes<WhileStatement> parseWhile(String filename, List<Token> tokens, int i) { public static ParseRes<WhileStatement> parseWhile(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1569,7 +1572,7 @@ public class Parsing {
return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n); return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n);
} }
public static ParseRes<Statement> parseSwitchCase(String filename, List<Token> tokens, int i) { public static ParseRes<Statement> parseSwitchCase(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1589,7 +1592,7 @@ public class Parsing {
return ParseRes.res(null, 2); return ParseRes.res(null, 2);
} }
public static ParseRes<SwitchStatement> parseSwitch(String filename, List<Token> tokens, int i) { public static ParseRes<SwitchStatement> parseSwitch(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1649,7 +1652,7 @@ public class Parsing {
statements.toArray(Statement[]::new) statements.toArray(Statement[]::new)
), n); ), n);
} }
public static ParseRes<DoWhileStatement> parseDoWhile(String filename, List<Token> tokens, int i) { public static ParseRes<DoWhileStatement> parseDoWhile(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1678,7 +1681,7 @@ public class Parsing {
} }
else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon."); else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon.");
} }
public static ParseRes<Statement> parseFor(String filename, List<Token> tokens, int i) { public static ParseRes<Statement> parseFor(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1736,7 +1739,7 @@ public class Parsing {
return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n); return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n);
} }
public static ParseRes<ForInStatement> parseForIn(String filename, List<Token> tokens, int i) { public static ParseRes<ForInStatement> parseForIn(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1789,7 +1792,7 @@ public class Parsing {
return ParseRes.res(new ForInStatement(loc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); return ParseRes.res(new ForInStatement(loc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
} }
public static ParseRes<TryStatement> parseCatch(String filename, List<Token> tokens, int i) { public static ParseRes<TryStatement> parseCatch(Filename filename, List<Token> tokens, int i) {
var loc = getLoc(filename, tokens, i); var loc = getLoc(filename, tokens, i);
int n = 0; int n = 0;
@ -1830,7 +1833,7 @@ public class Parsing {
return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n); return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n);
} }
public static ParseRes<? extends Statement> parseStatement(String filename, List<Token> tokens, int i) { public static ParseRes<? extends Statement> parseStatement(Filename filename, List<Token> tokens, int i) {
if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i)), 1); if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i)), 1);
if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed."); if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed.");
return ParseRes.any( return ParseRes.any(
@ -1853,7 +1856,7 @@ public class Parsing {
); );
} }
public static Statement[] parse(String filename, String raw) { public static Statement[] parse(Filename filename, String raw) {
var tokens = tokenize(filename, raw); var tokens = tokenize(filename, raw);
var list = new ArrayList<Statement>(); var list = new ArrayList<Statement>();
int i = 0; int i = 0;
@ -1874,10 +1877,10 @@ public class Parsing {
return list.toArray(Statement[]::new); return list.toArray(Statement[]::new);
} }
public static CodeFunction compile(HashMap<Long, Instruction[]> funcs, Environment environment, Statement ...statements) { public static CodeFunction compile(HashMap<Long, FunctionBody> funcs, TreeSet<Location> breakpoints, Environment environment, Statement ...statements) {
var target = environment.global.globalChild(); var target = environment.global.globalChild();
var subscope = target.child(); var subscope = target.child();
var res = new CompileTarget(funcs); var res = new CompileTarget(funcs, breakpoints);
var body = new CompoundStatement(null, statements); var body = new CompoundStatement(null, statements);
if (body instanceof CompoundStatement) body = (CompoundStatement)body; if (body instanceof CompoundStatement) body = (CompoundStatement)body;
else body = new CompoundStatement(null, new Statement[] { body }); else body = new CompoundStatement(null, new Statement[] { body });
@ -1901,14 +1904,14 @@ public class Parsing {
} }
else res.add(Instruction.ret()); else res.add(Instruction.ret());
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.array()); return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals()));
} }
public static CodeFunction compile(HashMap<Long, Instruction[]> funcs, Environment environment, String filename, String raw) { public static CodeFunction compile(HashMap<Long, FunctionBody> funcs, TreeSet<Location> breakpoints, Environment environment, Filename filename, String raw) {
try { try {
return compile(funcs, environment, parse(filename, raw)); return compile(funcs, breakpoints, environment, parse(filename, raw));
} }
catch (SyntaxException e) { catch (SyntaxException e) {
return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) }); return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new FunctionBody(new Instruction[] { Instruction.throwSyntax(e).locate(e.loc) }));
} }
} }
} }