Compare commits

...

18 Commits

Author SHA1 Message Date
df8465cb49 Merge pull request #8 from TopchetoEU/TopchetoEU/tests
Integrate typescript
2023-11-05 20:32:42 +02:00
e3104c223c fix: remove some unnececeary logs 2023-11-05 20:29:21 +02:00
1d0bae3de8 feat: include typescript code in source code 2023-11-05 20:27:23 +02:00
b66acd3089 fix: several more fixes 2023-11-05 19:44:44 +02:00
e326847287 feat: send value stack to debug client 2023-11-05 19:44:35 +02:00
26591d6631 fix: lazy operators incorrectly pop values from stack 2023-11-05 19:44:08 +02:00
af31b1ab79 fix: several small fixes 2023-11-05 19:43:53 +02:00
f885d4349f refactor: fix some bad code >:( 2023-11-05 19:43:28 +02:00
d57044acb7 fix: several bug fixes to help with typescript support 2023-11-05 12:44:29 +02:00
7df4e3b03f fix: oops 2023-11-04 11:43:57 +02:00
ed1009ab69 refactor: some code restructuring in the debugging 2023-11-04 11:42:06 +02:00
f856cdf37e fix: messages larger than 64KB are now fragmented properly 2023-11-04 11:41:31 +02:00
4f82574b8c fix: various small behavioural issues
fix: pesky try-catch logic
2023-11-04 11:40:50 +02:00
0ae24148d8 feat: write some tests 2023-11-04 11:38:48 +02:00
ac128d17f4 feat: implement Array.reduce
fix: native functions are now named
2023-11-04 11:38:29 +02:00
6508f15bb0 refactor: remove typescript source code from repo (for now) 2023-11-04 11:37:13 +02:00
69f93b4f87 refactor: make filenames more consistent 2023-11-04 11:36:36 +02:00
b675411925 fix: a lot of minor bugs 2023-10-29 23:47:48 +02:00
70 changed files with 1206 additions and 714 deletions

View File

@@ -10,8 +10,6 @@ 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);

View File

@@ -46,6 +46,8 @@ public class Filename {
public Filename(String protocol, String path) { public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
this.protocol = protocol; this.protocol = protocol;
this.path = path; this.path = path;
} }

View File

@@ -1,7 +1,7 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
public class Location implements Comparable<Location> { public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "internal")); public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
private int line; private int line;
private int start; private int start;
private Filename filename; private Filename filename;

View File

@@ -1,107 +1,153 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
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.Context; 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.StackData;
import me.topchetoeu.jscript.engine.debug.SimpleDebugger; import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class Main { import me.topchetoeu.jscript.lib.Internals;
static Thread engineTask, debugTask;
static Engine engine; public class Main {
static Environment env; static Thread engineTask, debugTask;
static int j = 0; static Engine engine;
static Environment env;
private static Observer<Object> valuePrinter = new Observer<Object>() { static int j = 0;
public void next(Object data) {
Values.printValue(null, data); private static Observer<Object> valuePrinter = new Observer<Object>() {
System.out.println(); public void next(Object data) {
} Values.printValue(null, data);
System.out.println();
public void error(RuntimeException err) { }
Values.printError(err, null);
} public void error(RuntimeException err) {
Values.printError(err, null);
@Override }
public void finish() {
engineTask.interrupt(); @Override
} public void finish() {
}; engineTask.interrupt();
}
public static void main(String args[]) { };
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
engine = new Engine(); public static void main(String args[]) {
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
env = new Environment(null, null, null); engine = new Engine(true);
var exited = new boolean[1];
var server = new DebugServer(); env = new Environment(null, null, null);
server.targets.put("target", (ws, req) -> SimpleDebugger.get(ws, engine)); var exited = new boolean[1];
var server = new DebugServer();
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> { server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
new Internals().apply(env);
engineTask = engine.start();
env.global.define("exit", _ctx -> { debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
exited[0] = true;
throw new InterruptException(); engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
}); new Internals().apply(env);
env.global.define("go", _ctx -> {
try { env.global.define("exit", _ctx -> {
var f = Path.of("do.js"); exited[0] = true;
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); throw new InterruptException();
return func.call(_ctx); });
} env.global.define("go", _ctx -> {
catch (IOException e) { try {
throw new EngineException("Couldn't open 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 null; catch (IOException e) {
}), null); throw new EngineException("Couldn't open do.js");
}
engineTask = engine.start(); });
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true); // TODO: make better API
env.global.define(true, new NativeFunction("include", (_ctx, th, __args) -> {
var reader = new Thread(() -> { try {
try { var currFilename = StackData.peekFrame(_ctx).function.loc().filename();
for (var i = 0; ; i++) { var loc = Path.of("").toAbsolutePath();
try { if (currFilename.protocol.equals("file")) loc = Path.of(currFilename.path).getParent();
var raw = Reading.read(); var path = loc.resolve(Path.of(__args.length >= 1 ? Values.toString(_ctx, __args[0]) : ""));
var src = Files.readString(path);
if (raw == null) break; var func = _ctx.compile(Filename.fromFile(path.toFile()), src);
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await()); var callArgs = new ArrayValue();
} if (__args.length >= 2 && __args[1] instanceof ArrayValue) callArgs = (ArrayValue)__args[1];
catch (EngineException e) { Values.printError(e, ""); } return func.call(_ctx, null, callArgs);
} }
} catch (IOException e) { throw EngineException.ofError("IOError", "Couldn't open file."); }
catch (IOException e) { return; } }));
catch (SyntaxException ex) {
if (exited[0]) return; return null;
System.out.println("Syntax error:" + ex.msg); }), null).await();
}
catch (RuntimeException ex) { try {
if (!exited[0]) { var tsEnv = env.child();
System.out.println("Internal error ocurred:"); tsEnv.global.define(null, "module", false, new ObjectValue());
ex.printStackTrace(); engine.pushMsg(
} false, new Context(engine).pushEnv(tsEnv),
} new Filename("jscript", "ts.js"),
catch (Throwable e) { throw new UncheckedException(e); } Reading.resourceToString("js/ts.js"), null
if (exited[0]) debugTask.interrupt(); ).await();
}); System.out.println("Loaded typescript!");
reader.setDaemon(true);
reader.setName("STD Reader"); var ctx = new Context(engine).pushEnv(env.child());
reader.start();
} engine.pushMsg(
} false, ctx,
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
tsEnv.global.get(ctx, "ts"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
).await();
}
catch (EngineException e) {
Values.printError(e, "(while initializing TS)");
}
var reader = new Thread(() -> {
try {
for (var arg : args) {
try {
var file = Path.of(arg);
var raw = Files.readString(file);
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
if (raw == null) break;
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
}
catch (IOException e) { exited[0] = true; }
catch (SyntaxException ex) {
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
if (!exited[0]) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
if (exited[0]) debugTask.interrupt();
});
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
}
}

View File

@@ -251,8 +251,8 @@ public class Instruction {
return new Instruction(null, Type.TYPEOF, varName); return new Instruction(null, Type.TYPEOF, varName);
} }
public static Instruction keys() { public static Instruction keys(boolean forInFormat) {
return new Instruction(null, Type.KEYS); return new Instruction(null, Type.KEYS, forInFormat);
} }
public static Instruction defProp() { public static Instruction defProp() {

View File

@@ -12,6 +12,7 @@ public class ForInStatement extends Statement {
public final boolean isDeclaration; public final boolean isDeclaration;
public final Statement varValue, object, body; public final Statement varValue, object, body;
public final String label; public final String label;
public final Location varLocation;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
@@ -22,6 +23,8 @@ public class ForInStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var key = scope.getKey(varName); var key = scope.getKey(varName);
int first = target.size();
if (key instanceof String) target.add(Instruction.makeVar((String)key)); if (key instanceof String) target.add(Instruction.makeVar((String)key));
if (varValue != null) { if (varValue != null) {
@@ -29,45 +32,37 @@ public class ForInStatement extends Statement {
target.add(Instruction.storeVar(scope.getKey(varName))); target.add(Instruction.storeVar(scope.getKey(varName)));
} }
object.compile(target, scope, true); object.compileWithDebug(target, scope, true);
target.add(Instruction.keys()); target.add(Instruction.keys(true));
int start = target.size(); int start = target.size();
target.add(Instruction.dup()); target.add(Instruction.dup());
target.add(Instruction.loadMember("length")); target.add(Instruction.loadValue(null));
target.add(Instruction.loadValue(0)); target.add(Instruction.operation(Operation.EQUALS));
target.add(Instruction.operation(Operation.LESS_EQUALS));
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop());
target.add(Instruction.dup()); target.add(Instruction.loadMember("value").locate(varLocation));
target.add(Instruction.dup()); target.setDebug();
target.add(Instruction.loadMember("length"));
target.add(Instruction.loadValue(1));
target.add(Instruction.operation(Operation.SUBTRACT));
target.add(Instruction.dup(1, 2));
target.add(Instruction.loadValue("length"));
target.add(Instruction.dup(1, 2));
target.add(Instruction.storeMember());
target.add(Instruction.loadMember());
target.add(Instruction.storeVar(key)); target.add(Instruction.storeVar(key));
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
body.compileWithDebug(target, scope, false); body.compileWithDebug(target, scope, false);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc())); target.add(Instruction.jmp(start - end));
target.add(Instruction.discard().locate(loc())); target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc())); target.set(mid, Instruction.jmpIf(end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(null));
target.get(first).locate(loc());
target.setDebug(first);
} }
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
super(loc); super(loc);
this.varLocation = varLocation;
this.label = label; this.label = label;
this.isDeclaration = isDecl; this.isDeclaration = isDecl;
this.varName = varName; this.varName = varName;

View File

@@ -43,7 +43,7 @@ public class SwitchStatement extends Statement {
ccase.value.compile(target, scope, true); ccase.value.compile(target, scope, true);
target.add(Instruction.operation(Operation.EQUALS).locate(loc())); target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
caseMap.put(target.size(), ccase.statementI); caseMap.put(target.size(), ccase.statementI);
target.add(Instruction.nop()); target.add(Instruction.nop().locate(ccase.value.loc()));
} }
int start = target.size(); int start = target.size();

View File

@@ -15,8 +15,12 @@ public class ChangeStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix); value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard().locate(loc()));
else if (postfix) {
target.add(Instruction.loadValue(addAmount));
target.add(Instruction.operation(Operation.SUBTRACT));
}
} }
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {

View File

@@ -0,0 +1,38 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CommaStatement extends Statement {
public final Statement[] values;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var i = 0; i < values.length; i++) {
values[i].compile(target, scope, i == values.length - 1 && pollute);
}
}
@Override
public Statement optimize() {
var res = new Vector<Statement>(values.length);
for (var i = 0; i < values.length; i++) {
var stm = values[i].optimize();
if (i < values.length - 1 && stm.pure()) continue;
res.add(stm);
}
if (res.size() == 1) return res.get(0);
else return new CommaStatement(loc(), res.toArray(Statement[]::new));
}
public CommaStatement(Location loc, Statement ...args) {
super(loc);
this.values = args;
}
}

View File

@@ -29,7 +29,7 @@ public class LazyAndStatement extends Statement {
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup().locate(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc())); target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
} }

View File

@@ -29,7 +29,7 @@ public class LazyOrStatement extends Statement {
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup().locate(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc())); target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
} }

View File

@@ -20,15 +20,13 @@ public class VariableAssignStatement extends Statement {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compile(target, scope, true); else value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc())); target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeVar(i, false).locate(loc())); target.add(Instruction.storeVar(i, pollute).locate(loc()));
} }
else { else {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compile(target, scope, true); else value.compile(target, scope, true);
target.add(Instruction.storeVar(i, false).locate(loc())); target.add(Instruction.storeVar(i, pollute).locate(loc()));
} }
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
} }
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {

View File

@@ -6,6 +6,7 @@ import java.util.TreeSet;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; 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.ObjectValue;
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;
@@ -27,11 +28,23 @@ public class Context {
} }
public FunctionValue compile(Filename filename, String raw) { public FunctionValue compile(Filename filename, String raw) {
var src = Values.toString(this, environment().compile.call(this, null, raw, filename)); var transpiled = environment().compile.call(this, null, raw, filename.toString());
var debugger = StackData.getDebugger(this); String source = null;
FunctionValue runner = null;
if (transpiled instanceof ObjectValue) {
source = Values.toString(this, Values.getMember(this, transpiled, "source"));
var _runner = Values.getMember(this, transpiled, "runner");
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner;
}
else source = Values.toString(this, transpiled);
var breakpoints = new TreeSet<Location>(); var breakpoints = new TreeSet<Location>();
var res = Parsing.compile(engine.functions, breakpoints, environment(), filename, src); FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source);
if (debugger != null) debugger.onSource(filename, src, breakpoints); engine.onSource(filename, source, breakpoints);
if (runner != null) res = (FunctionValue)runner.call(this, null, res);
return res; return res;
} }

View File

@@ -1,16 +1,22 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.debug.DebugController;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.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; import me.topchetoeu.jscript.exceptions.InterruptException;
public class Engine { public class Engine implements DebugController {
private class UncompiledFunction extends FunctionValue { private class UncompiledFunction extends FunctionValue {
public final Filename filename; public final Filename filename;
public final String raw; public final String raw;
@@ -45,14 +51,48 @@ public class Engine {
} }
private static int nextId = 0; private static int nextId = 0;
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
private Thread thread; private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>(); private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
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, FunctionBody> functions = new HashMap<>(); public final Data data = new Data().set(StackData.MAX_FRAMES, 200);
public final Data data = new Data().set(StackData.MAX_FRAMES, 10000); public final boolean debugging;
private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private DebugController debugger;
public boolean attachDebugger(DebugController debugger) {
if (!debugging || this.debugger != null) return false;
for (var source : sources.entrySet()) {
debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey()));
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger() {
if (!debugging || this.debugger == null) return false;
this.debugger = null;
return true;
}
@Override public void onFramePop(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
}
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) {
if (!debugging) return;
if (debugger != null) debugger.onSource(filename, source, breakpoints);
sources.put(filename, source);
bpts.put(filename, breakpoints);
}
private void runTask(Task task) { private void runTask(Task task) {
try { try {
@@ -108,4 +148,8 @@ public class Engine {
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename 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, new UncompiledFunction(filename, raw), thisArg, args); return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
} }
public Engine(boolean debugging) {
this.debugging = debugging;
}
} }

View File

@@ -17,7 +17,7 @@ public class Environment {
private HashMap<String, ObjectValue> prototypes = new HashMap<>(); private HashMap<String, ObjectValue> prototypes = new HashMap<>();
public final Data data = new Data(); public final Data data = new Data();
public final HashMap<String, Symbol> symbols = new HashMap<>(); public static final HashMap<String, Symbol> symbols = new HashMap<>();
public GlobalScope global; public GlobalScope global;
public WrappersProvider wrappers; public WrappersProvider wrappers;
@@ -40,8 +40,7 @@ public class Environment {
} }
@Native public Symbol symbol(String name) { @Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) if (symbols.containsKey(name)) return symbols.get(name);
return symbols.get(name);
else { else {
var res = new Symbol(name); var res = new Symbol(name);
symbols.put(name, res); symbols.put(name, res);
@@ -57,7 +56,8 @@ public class Environment {
} }
@Native public Environment fork() { @Native public Environment fork() {
var res = new Environment(compile, wrappers, global); var res = new Environment(compile, null, global);
res.wrappers = wrappers.fork(res);
res.regexConstructor = regexConstructor; res.regexConstructor = regexConstructor;
res.prototypes = new HashMap<>(prototypes); res.prototypes = new HashMap<>(prototypes);
return res; return res;

View File

@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.debug.Debugger; import me.topchetoeu.jscript.engine.debug.Debugger;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
@@ -25,8 +26,7 @@ public class StackData {
if (frames.get(frames.size() - 1) != frame) return false; if (frames.get(frames.size() - 1) != frame) return false;
frames.remove(frames.size() - 1); frames.remove(frames.size() - 1);
ctx.popEnv(); ctx.popEnv();
var dbg = getDebugger(ctx); ctx.engine.onFramePop(ctx, frame);
if (dbg != null) dbg.onFramePop(ctx, frame);
return true; return true;
} }
public static CodeFrame peekFrame(Context ctx) { public static CodeFrame peekFrame(Context ctx) {
@@ -45,7 +45,11 @@ public class StackData {
for (var i = frames.size() - 1; i >= 0; i--) { for (var i = frames.size() - 1; i >= 0; i--) {
var el = frames.get(i); var el = frames.get(i);
var name = el.function.name; var name = el.function.name;
var loc = el.function.loc(); Location loc = null;
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
if (loc == null) loc = el.function.loc();
var trace = ""; var trace = "";
if (loc != null) trace += "at " + loc.toString() + " "; if (loc != null) trace += "at " + loc.toString() + " ";
@@ -58,8 +62,4 @@ public class StackData {
return res; return res;
} }
public static Debugger getDebugger(Context ctx) {
return ctx.data.get(DEBUGGER);
}
} }

View File

@@ -7,4 +7,6 @@ public interface WrappersProvider {
public ObjectValue getProto(Class<?> obj); public ObjectValue getProto(Class<?> obj);
public ObjectValue getNamespace(Class<?> obj); public ObjectValue getNamespace(Class<?> obj);
public FunctionValue getConstr(Class<?> obj); public FunctionValue getConstr(Class<?> obj);
public WrappersProvider fork(Environment env);
} }

View File

@@ -37,7 +37,4 @@ public interface DebugController {
* @param frame The code frame which was popped out * @param frame The code frame which was popped out
*/ */
void onFramePop(Context ctx, CodeFrame frame); void onFramePop(Context ctx, CodeFrame frame);
void connect();
void disconnect();
} }

View File

@@ -10,6 +10,7 @@ import java.util.HashMap;
import me.topchetoeu.jscript.Metadata; import me.topchetoeu.jscript.Metadata;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.events.Notifier;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.exceptions.UncheckedIOException; import me.topchetoeu.jscript.exceptions.UncheckedIOException;
@@ -23,6 +24,7 @@ public class DebugServer {
public final HashMap<String, DebuggerProvider> targets = new HashMap<>(); public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
private final byte[] favicon, index, protocol; private final byte[] favicon, index, protocol;
private final Notifier connNotifier = new Notifier();
private static void send(HttpRequest req, String val) throws IOException { private static void send(HttpRequest req, String val) throws IOException {
req.writeResponse(200, "OK", "application/json", val.getBytes()); req.writeResponse(200, "OK", "application/json", val.getBytes());
@@ -67,7 +69,9 @@ public class DebugServer {
try { try {
switch (msg.name) { switch (msg.name) {
case "Debugger.enable": debugger.enable(msg); continue; case "Debugger.enable":
connNotifier.next();
debugger.enable(msg); continue;
case "Debugger.disable": debugger.disable(msg); continue; case "Debugger.disable": debugger.disable(msg); continue;
case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue; case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue;
@@ -151,6 +155,10 @@ public class DebugServer {
}, "Debug Handler"); }, "Debug Handler");
} }
public void awaitConnection() {
connNotifier.await();
}
public void run(InetSocketAddress address) { public void run(InetSocketAddress address) {
try { try {
ServerSocket server = new ServerSocket(); ServerSocket server = new ServerSocket();

View File

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

View File

@@ -31,12 +31,6 @@ import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap; 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 class SimpleDebugger implements Debugger {
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}"; public static final String 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}";
@@ -46,7 +40,7 @@ public class SimpleDebugger implements Debugger {
public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}"; public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}";
public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}"; public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}";
public static final String VSCODE_CALL = "function(t){return t.call(this)\n}"; public static final String VSCODE_CALL = "function(t){return t.call(this)\n}";
private static enum State { private static enum State {
RESUMED, RESUMED,
STEPPING_IN, STEPPING_IN,
@@ -104,7 +98,7 @@ public class SimpleDebugger implements Debugger {
public CodeFrame frame; public CodeFrame frame;
public CodeFunction func; public CodeFunction func;
public int id; public int id;
public ObjectValue local, capture, global; public ObjectValue local, capture, global, valstack;
public JSONMap serialized; public JSONMap serialized;
public Location location; public Location location;
public boolean debugData = false; public boolean debugData = false;
@@ -126,6 +120,7 @@ public class SimpleDebugger implements Debugger {
this.capture = frame.getCaptureScope(ctx, true); this.capture = frame.getCaptureScope(ctx, true);
this.local.setPrototype(ctx, capture); this.local.setPrototype(ctx, capture);
this.capture.setPrototype(ctx, global); this.capture.setPrototype(ctx, global);
this.valstack = frame.getValStackScope(ctx);
if (location != null) { if (location != null) {
debugData = true; debugData = true;
@@ -137,6 +132,7 @@ public class SimpleDebugger implements Debugger {
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local))) .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", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global))) .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
.add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
); );
} }
} }
@@ -256,7 +252,7 @@ public class SimpleDebugger implements Debugger {
return id; return id;
} }
} }
private JSONMap serializeObj(Context ctx, Object val, boolean recurse) { private JSONMap serializeObj(Context ctx, Object val) {
val = Values.normalize(null, val); val = Values.normalize(null, val);
if (val == Values.NULL) { if (val == Values.NULL) {
@@ -277,13 +273,6 @@ public class SimpleDebugger implements Debugger {
if (obj instanceof FunctionValue) type = "function"; if (obj instanceof FunctionValue) type = "function";
if (obj instanceof ArrayValue) subtype = "array"; 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")); } try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
catch (Exception e) { } catch (Exception e) { }
@@ -311,6 +300,7 @@ public class SimpleDebugger implements Debugger {
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity"); if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity"); else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0"); else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0");
else if (Double.isNaN(num)) res.set("unserializableValue", "NaN"); else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
else res.set("value", num); else res.set("value", num);
@@ -319,9 +309,6 @@ public class SimpleDebugger implements Debugger {
throw new IllegalArgumentException("Unexpected JS object."); 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) { private void setObjectGroup(String name, Object val) {
if (val instanceof ObjectValue) { if (val instanceof ObjectValue) {
var obj = (ObjectValue)val; var obj = (ObjectValue)val;
@@ -412,7 +399,7 @@ public class SimpleDebugger implements Debugger {
} }
private RunResult run(Frame codeFrame, String code) { private RunResult run(Frame codeFrame, String code) {
var engine = new Engine(); var engine = new Engine(false);
var env = codeFrame.func.environment.fork(); var env = codeFrame.func.environment.fork();
ObjectValue global = env.global.obj, ObjectValue global = env.global.obj,
@@ -424,7 +411,7 @@ public class SimpleDebugger implements Debugger {
env.global = new GlobalScope(local); env.global = new GlobalScope(local);
var ctx = new Context(engine).pushEnv(env); var ctx = new Context(engine).pushEnv(env);
var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), "(" + code + ")", codeFrame.frame.thisArg, codeFrame.frame.args); var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
engine.run(true); engine.run(true);
@@ -488,13 +475,16 @@ public class SimpleDebugger implements Debugger {
@Override public void setBreakpointByUrl(V8Message msg) { @Override public void setBreakpointByUrl(V8Message msg) {
var line = (int)msg.params.number("lineNumber") + 1; var line = (int)msg.params.number("lineNumber") + 1;
var col = (int)msg.params.number("columnNumber", 0) + 1; var col = (int)msg.params.number("columnNumber", 0) + 1;
var cond = msg.params.string("condition", null);
if (cond != null) cond = "(" + cond + ")";
Pattern regex; Pattern regex;
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url"))); if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
else regex = Pattern.compile(msg.params.string("urlRegex")); else regex = Pattern.compile(msg.params.string("urlRegex"));
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, null); var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond);
idToBptCand.put(bpcd.id, bpcd); idToBptCand.put(bpcd.id, bpcd);
var locs = new JSONList(); var locs = new JSONList();
@@ -603,55 +593,46 @@ public class SimpleDebugger implements Debugger {
} }
@Override public void getProperties(V8Message msg) { @Override public void getProperties(V8Message msg) {
var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var own = msg.params.bool("ownProperties");
var accessorPropertiesOnly = msg.params.bool("accessorPropertiesOnly", false);
var currOwn = true;
var res = new JSONList(); var res = new JSONList();
var ctx = objectToCtx.get(obj);
while (obj != emptyObject && obj != null) { if (obj != emptyObject) {
var ctx = objectToCtx.get(obj);
for (var key : obj.keys(true)) { for (var key : obj.keys(true)) {
var propDesc = new JSONMap(); var propDesc = new JSONMap();
if (obj.properties.containsKey(key)) { if (obj.properties.containsKey(key)) {
var prop = obj.properties.get(key); var prop = obj.properties.get(key);
propDesc.set("name", Values.toString(ctx, key)); propDesc.set("name", Values.toString(ctx, key));
if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter));
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
propDesc.set("enumerable", obj.memberEnumerable(key)); propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key)); propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", currOwn); propDesc.set("isOwn", true);
res.add(propDesc); res.add(propDesc);
} }
else if (!accessorPropertiesOnly) { else {
propDesc.set("name", Values.toString(ctx, key)); propDesc.set("name", Values.toString(ctx, key));
propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key)));
propDesc.set("writable", obj.memberWritable(key)); propDesc.set("writable", obj.memberWritable(key));
propDesc.set("enumerable", obj.memberEnumerable(key)); propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key)); propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", currOwn); propDesc.set("isOwn", true);
res.add(propDesc); res.add(propDesc);
} }
} }
obj = obj.getPrototype(ctx); var proto = obj.getPrototype(ctx);
if (currOwn) { var protoDesc = new JSONMap();
var protoDesc = new JSONMap(); protoDesc.set("name", "__proto__");
protoDesc.set("name", "__proto__"); protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto));
protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj)); protoDesc.set("writable", true);
protoDesc.set("writable", true); protoDesc.set("enumerable", false);
protoDesc.set("enumerable", false); protoDesc.set("configurable", false);
protoDesc.set("configurable", false); protoDesc.set("isOwn", true);
protoDesc.set("isOwn", currOwn); res.add(protoDesc);
res.add(protoDesc);
}
currOwn = false;
if (own) break;
} }
ws.send(msg.respond(new JSONMap().set("result", res))); ws.send(msg.respond(new JSONMap().set("result", res)));
@@ -676,36 +657,40 @@ public class SimpleDebugger implements Debugger {
else src = src.substring(0, start) + src.substring(end + 1); else src = src.substring(0, start) + src.substring(end + 1);
} }
switch (src) { try {
case CHROME_GET_PROP_FUNC: { switch (src) {
var path = JSON.parse(new Filename("tmp", "json"), (String)args.get(0)).list(); case CHROME_GET_PROP_FUNC: {
Object res = thisArg; var path = JSON.parse(null, (String)args.get(0)).list();
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el)); Object res = thisArg;
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el));
return; ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
return;
}
case VSCODE_STRINGIFY_VAL:
case VSCODE_STRINGIFY_PROPS:
case VSCODE_SHALLOW_COPY:
case VSCODE_SYMBOL_REQUEST:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject))));
break;
case VSCODE_FLATTEN_ARRAY:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg))));
break;
case VSCODE_CALL: {
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
break;
}
default:
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
break;
// default: {
// var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx);
// if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args);
// ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
// }
} }
case VSCODE_STRINGIFY_VAL:
case VSCODE_STRINGIFY_PROPS:
case VSCODE_SHALLOW_COPY:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject))));
break;
case VSCODE_FLATTEN_ARRAY: {
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg))));
break;
}
case VSCODE_SYMBOL_REQUEST:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, new ArrayValue(ctx)))));
break;
case VSCODE_CALL: {
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
try { ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg))))); }
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
break;
}
default:
ws.send(new V8Error("A non well-known function was used with callFunctionOn."));
break;
} }
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
} }
@Override @Override
@@ -717,8 +702,8 @@ public class SimpleDebugger implements Debugger {
int id = nextId(); int id = nextId();
var src = new Source(id, filename, source, locations); var src = new Source(id, filename, source, locations);
filenameToId.put(filename, id);
idToSource.put(id, src); idToSource.put(id, src);
filenameToId.put(filename, id);
for (var bpcd : idToBptCand.values()) { for (var bpcd : idToBptCand.values()) {
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue; if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
@@ -747,14 +732,15 @@ public class SimpleDebugger implements Debugger {
returnVal != Runners.NO_RETURN returnVal != Runners.NO_RETURN
); );
if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null; // TODO: FIXXXX
// if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null;
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
pauseException(ctx); pauseException(ctx);
} }
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
var bp = locToBreakpoint.get(loc); var bp = locToBreakpoint.get(loc);
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition)); var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result);
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc)); if (ok) pauseDebug(ctx, locToBreakpoint.get(loc));
} }
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
@@ -810,27 +796,18 @@ public class SimpleDebugger implements Debugger {
} }
@Override public void connect() { @Override public void connect() {
target.data.set(StackData.DEBUGGER, this); if (!target.attachDebugger(this)) {
ws.send(new V8Error("A debugger is already attached to this engine."));
}
} }
@Override public void disconnect() { @Override public void disconnect() {
target.data.remove(StackData.DEBUGGER); target.detachDebugger();
enabled = false; enabled = false;
updateNotifier.next(); updateNotifier.next();
} }
private SimpleDebugger(WebSocket ws, Engine target) { public SimpleDebugger(WebSocket ws, Engine target) {
this.ws = ws; this.ws = ws;
this.target = target; 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

@@ -2,7 +2,6 @@ package me.topchetoeu.jscript.engine.debug;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
@@ -33,7 +32,7 @@ public class V8Message {
this.params = raw.contains("params") ? raw.map("params") : new JSONMap(); this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
} }
public V8Message(String raw) { public V8Message(String raw) {
this(JSON.parse(new Filename("jscript", "json-msg"), raw).map()); this(JSON.parse(null, raw).map());
} }
public JSONMap toMap() { public JSONMap toMap() {

View File

@@ -10,7 +10,7 @@ import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.exceptions.UncheckedIOException; import me.topchetoeu.jscript.exceptions.UncheckedIOException;
public class WebSocket implements AutoCloseable { public class WebSocket implements AutoCloseable {
public long maxLength = 2000000; public long maxLength = 1 << 20;
private Socket socket; private Socket socket;
private boolean closed = false; private boolean closed = false;
@@ -61,39 +61,48 @@ public class WebSocket implements AutoCloseable {
else return new byte[4]; else return new byte[4];
} }
private void writeLength(long len) { private void writeLength(int len) {
try { try {
if (len < 126) { if (len < 126) {
out().write((int)len); out().write((int)len);
} }
else if (len < 0xFFFF) { else if (len <= 0xFFFF) {
out().write(126); out().write(126);
out().write((int)(len >> 8) & 0xFF); out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF); out().write((int)len & 0xFF);
} }
else { else {
out().write(127); out().write(127);
out().write((int)(len >> 56) & 0xFF); out().write((len >> 56) & 0xFF);
out().write((int)(len >> 48) & 0xFF); out().write((len >> 48) & 0xFF);
out().write((int)(len >> 40) & 0xFF); out().write((len >> 40) & 0xFF);
out().write((int)(len >> 32) & 0xFF); out().write((len >> 32) & 0xFF);
out().write((int)(len >> 24) & 0xFF); out().write((len >> 24) & 0xFF);
out().write((int)(len >> 16) & 0xFF); out().write((len >> 16) & 0xFF);
out().write((int)(len >> 8) & 0xFF); out().write((len >> 8) & 0xFF);
out().write((int)len & 0xFF); out().write(len & 0xFF);
} }
} }
catch (IOException e) { throw new UncheckedIOException(e); } catch (IOException e) { throw new UncheckedIOException(e); }
} }
private synchronized void write(int type, byte[] data) { private synchronized void write(int type, byte[] data) {
try { try {
out().write(type | 0x80); int i;
writeLength(data.length);
for (int i = 0; i < data.length; i++) { for (i = 0; i < data.length / 0xFFFF; i++) {
out().write(data[i]); out().write(type);
writeLength(0xFFFF);
out().write(data, i * 0xFFFF, 0xFFFF);
type = 0;
} }
out().write(type | 0x80);
writeLength(data.length % 0xFFFF);
out().write(data, i * 0xFFFF, data.length % 0xFFFF);
}
catch (IOException e) {
throw new UncheckedIOException(e);
} }
catch (IOException e) { throw new UncheckedIOException(e); }
} }
public void send(String data) { public void send(String data) {
@@ -110,7 +119,7 @@ public class WebSocket implements AutoCloseable {
} }
public void send(Object data) { public void send(Object data) {
// TODO: Remove // TODO: Remove
System.out.println("SEND: " + data); // System.out.println("SEND: " + data);
if (closed) throw new IllegalStateException("Object is closed."); if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes()); write(1, data.toString().getBytes());
} }
@@ -193,7 +202,7 @@ public class WebSocket implements AutoCloseable {
var raw = data.toByteArray(); var raw = data.toByteArray();
// TODO: Remove // TODO: Remove
System.out.println("RECEIVED: " + new String(raw)); // System.out.println("RECEIVED: " + new String(raw));
if (type == 1) return new WebSocketMessage(new String(raw)); if (type == 1) return new WebSocketMessage(new String(raw));

View File

@@ -1,11 +1,10 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
import java.util.List;
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;
@@ -17,7 +16,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
public class CodeFrame { public class CodeFrame {
private class TryCtx { public static class TryCtx {
public static final int STATE_TRY = 0; public static final int STATE_TRY = 0;
public static final int STATE_CATCH = 1; public static final int STATE_CATCH = 1;
public static final int STATE_FINALLY_THREW = 2; public static final int STATE_FINALLY_THREW = 2;
@@ -84,9 +83,30 @@ public class CodeFrame {
return new ScopeValue(scope.captures, names); return new ScopeValue(scope.captures, names);
} }
public ObjectValue getValStackScope(Context ctx) {
return new ObjectValue() {
@Override
protected Object getField(Context ctx, Object key) {
var i = (int)Values.toNumber(ctx, key);
if (i < 0 || i >= stackPtr) return null;
else return stack[i];
}
@Override
protected boolean hasField(Context ctx, Object key) {
return true;
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < stackPtr; i++) res.add(i);
return res;
}
};
}
public void addTry(int n, int catchN, int finallyN) { 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);
if (!tryStack.empty()) res.err = tryStack.peek().err;
tryStack.add(res); tryStack.add(res);
} }
@@ -125,31 +145,31 @@ public class CodeFrame {
} }
private void setCause(Context ctx, EngineException err, EngineException cause) { private void setCause(Context ctx, EngineException err, EngineException cause) {
err.cause = cause; err.setCause(cause);
}
private Object nextNoTry(Context ctx, Instruction instr) {
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (codePtr < 0 || codePtr >= function.body.length) return null;
try {
this.jumpFlag = false;
return Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
throw e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
}
} }
public Object next(Context ctx, Object value, Object returnValue, EngineException error) { 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 { try {
var instr = function.body[codePtr]; if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (debugger != null) debugger.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); var instr = function.body[codePtr];
returnValue = nextNoTry(ctx, instr); ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
if (codePtr < 0 || codePtr >= function.body.length) returnValue = null;
else {
if (instr.location != null) prevLoc = instr.location;
try {
this.jumpFlag = false;
returnValue = Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
}
}
} }
catch (EngineException e) { error = e; } catch (EngineException e) { error = e; }
} }
@@ -189,11 +209,11 @@ public class CodeFrame {
break; break;
case TryCtx.STATE_CATCH: case TryCtx.STATE_CATCH:
if (error != null) { if (error != null) {
setCause(ctx, error, tryCtx.err);
if (tryCtx.hasFinally) { if (tryCtx.hasFinally) {
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) {
@@ -215,27 +235,31 @@ public class CodeFrame {
case TryCtx.STATE_FINALLY_THREW: case TryCtx.STATE_FINALLY_THREW:
if (error != null) setCause(ctx, error, tryCtx.err); if (error != null) setCause(ctx, error, tryCtx.err);
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err; else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
else return Runners.NO_RETURN; else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
break; break;
case TryCtx.STATE_FINALLY_RETURNED: case TryCtx.STATE_FINALLY_RETURNED:
if (error != null) setCause(ctx, error, tryCtx.err);
if (returnValue == Runners.NO_RETURN) { if (returnValue == Runners.NO_RETURN) {
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal; if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
else return Runners.NO_RETURN; else return Runners.NO_RETURN;
} }
break; break;
case TryCtx.STATE_FINALLY_JUMPED: case TryCtx.STATE_FINALLY_JUMPED:
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) { if (error != null) setCause(ctx, error, tryCtx.err);
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
if (!jumpFlag) codePtr = tryCtx.jumpPtr; if (!jumpFlag) codePtr = tryCtx.jumpPtr;
else codePtr = tryCtx.end; else codePtr = tryCtx.end;
} }
else return Runners.NO_RETURN; else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
break; break;
} }
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
if (newState == -1) { if (newState == -1) {
var err = tryCtx.err;
tryStack.pop(); tryStack.pop();
if (!tryStack.isEmpty()) tryStack.peek().err = err;
continue; continue;
} }
@@ -244,7 +268,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); ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true);
break; break;
default: default:
codePtr = tryCtx.finallyStart; codePtr = tryCtx.finallyStart;
@@ -252,13 +276,13 @@ public class CodeFrame {
return Runners.NO_RETURN; return Runners.NO_RETURN;
} }
if (error != null) { if (error != null) {
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, false); ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, false);
throw error; throw error;
} }
if (returnValue != Runners.NO_RETURN) { if (returnValue != Runners.NO_RETURN) {
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false); ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
return returnValue; return returnValue;
} }

View File

@@ -4,6 +4,7 @@ import java.util.Collections;
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.Engine;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
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;
@@ -26,16 +27,12 @@ public class Runners {
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) {
return Values.call(ctx, func, thisArg, args);
}
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) { 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();
frame.push(ctx, call(ctx, func, thisArg, callArgs)); frame.push(ctx, Values.call(ctx, func, thisArg, callArgs));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
@@ -46,17 +43,6 @@ public class Runners {
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
// }
// else {
// var proto = Values.getMember(ctx, funcObj, "prototype");
// var obj = new ObjectValue();
// obj.setPrototype(ctx, proto);
// call(ctx, funcObj, obj, callArgs);
// frame.push(ctx, obj);
// }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@@ -100,19 +86,29 @@ public class Runners {
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) { public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
var arr = new ObjectValue();
var i = 0;
var members = Values.getMembers(ctx, val, false, false); var members = Values.getMembers(ctx, val, false, false);
Collections.reverse(members); Collections.reverse(members);
frame.push(ctx, null);
for (var el : members) { for (var el : members) {
if (el instanceof Symbol) continue; if (el instanceof Symbol) continue;
arr.defineProperty(ctx, i++, el); var obj = new ObjectValue();
obj.defineProperty(ctx, "value", el);
frame.push(ctx, obj);
} }
// var arr = new ObjectValue();
arr.defineProperty(ctx, "length", i); // var members = Values.getMembers(ctx, val, false, false);
// Collections.reverse(members);
// for (var el : members) {
// if (el instanceof Symbol) continue;
// arr.defineProperty(ctx, i++, el);
// }
frame.push(ctx, arr); // arr.defineProperty(ctx, "length", i);
// frame.push(ctx, arr);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@@ -191,7 +187,7 @@ public class Runners {
captures[i - 3] = frame.scope.get(instr.get(i)); captures[i - 3] = frame.scope.get(instr.get(i));
} }
var body = ctx.engine.functions.get(id); var body = Engine.functions.get(id);
var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body); var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
frame.push(ctx, func); frame.push(ctx, func);

View File

@@ -8,8 +8,7 @@ public class LocalScope {
public final ArrayList<ValueVariable> catchVars = new ArrayList<>(); public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
public ValueVariable get(int i) { public ValueVariable get(int i) {
if (i >= locals.length) if (i >= locals.length) return catchVars.get(i - locals.length);
return catchVars.get(i - locals.length);
if (i >= 0) return locals[i]; if (i >= 0) return locals[i];
else return captures[~i]; else return captures[~i];
} }

View File

@@ -14,13 +14,14 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
private Object[] values; private Object[] values;
private int size; private int size;
private void alloc(int index) { private Object[] alloc(int index) {
if (index < values.length) return; index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2; if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index]; var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length); System.arraycopy(values, 0, arr, 0, values.length);
values = arr; return arr;
} }
public int size() { return size; } public int size() { return size; }
@@ -28,7 +29,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
if (val < 0) return false; if (val < 0) return false;
if (size > val) shrink(size - val); if (size > val) shrink(size - val);
else { else {
alloc(val); values = alloc(val);
size = val; size = val;
} }
return true; return true;
@@ -43,7 +44,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
public void set(Context ctx, int i, Object val) { public void set(Context ctx, int i, Object val) {
if (i < 0) return; if (i < 0) return;
alloc(i); values = alloc(i);
val = Values.normalize(ctx, val); val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED; if (val == null) val = UNDEFINED;
@@ -51,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
if (i >= size) size = i + 1; if (i >= size) size = i + 1;
} }
public boolean has(int i) { public boolean has(int i) {
return i >= 0 && i < values.length && values[i] != null; return i >= 0 && i < size && values[i] != null;
} }
public void remove(int i) { public void remove(int i) {
if (i < 0 || i >= values.length) return; if (i < 0 || i >= values.length) return;
@@ -84,8 +85,9 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) { public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
// Iterate in reverse to reallocate at most once // Iterate in reverse to reallocate at most once
for (var i = count - 1; i >= 0; i--) { for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null); if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null); if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
else arr.set(ctx, i + destStart, values[i + sourceStart]); else arr.set(ctx, i + destStart, values[i + sourceStart]);
} }
} }
@@ -97,7 +99,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
} }
public void move(int srcI, int dstI, int n) { public void move(int srcI, int dstI, int n) {
alloc(dstI + n); values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n); System.arraycopy(values, srcI, values, dstI, n);

View File

@@ -103,8 +103,7 @@ public class ObjectValue {
) return true; ) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) if (!memberConfigurable(key)) return false;
return false;
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);

View File

@@ -117,6 +117,7 @@ public class Values {
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 { return Double.parseDouble((String)val); } try { return Double.parseDouble((String)val); }
catch (NumberFormatException e) { return Double.NaN; }
catch (Throwable e) { throw new UncheckedException(e); } catch (Throwable e) { throw new UncheckedException(e); }
} }
return Double.NaN; return Double.NaN;
@@ -371,13 +372,18 @@ public class Values {
} }
public static Object callNew(Context ctx, Object func, Object ...args) { 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"); try {
res.setPrototype(ctx, proto); var proto = Values.getMember(ctx, func, "prototype");
res.setPrototype(ctx, proto);
var ret = call(ctx, func, res, args);
var ret = call(ctx, func, res, args);
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
return res; if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
return res;
}
catch (IllegalArgumentException e) {
throw EngineException.ofType("Tried to call new on an invalid constructor.");
}
} }
public static boolean strictEquals(Context ctx, Object a, Object b) { public static boolean strictEquals(Context ctx, Object a, Object b) {
@@ -576,7 +582,11 @@ public class Values {
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> { res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true)); if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else return new ObjectValue(ctx, Map.of("value", it.next())); else {
var obj = new ObjectValue();
obj.defineProperty(_ctx, "value", it.next());
return obj;
}
})); }));
return res; return res;
@@ -587,6 +597,11 @@ public class Values {
} }
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) { private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) {
if (tab == 0 && val instanceof String) {
System.out.print(val);
return;
}
if (passed.contains(val)) { if (passed.contains(val)) {
System.out.print("[circular]"); System.out.print("[circular]");
return; return;

View File

@@ -45,10 +45,10 @@ public class EngineException extends RuntimeException {
catch (EngineException e) { catch (EngineException e) {
ss.append("[Error while stringifying]\n"); ss.append("[Error while stringifying]\n");
} }
// for (var line : stackTrace) { for (var line : stackTrace) {
// ss.append(" ").append(line).append('\n'); ss.append(" ").append(line).append('\n');
// } }
// if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n'); if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
ss.deleteCharAt(ss.length() - 1); ss.deleteCharAt(ss.length() - 1);
return ss.toString(); return ss.toString();
} }

View File

@@ -33,7 +33,7 @@ public class NativeWrapperProvider implements WrappersProvider {
var val = target.values.get(name); var val = target.values.get(name);
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString())); if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()), true, true, false);
((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg())); ((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg()));
} }
@@ -53,7 +53,7 @@ public class NativeWrapperProvider implements WrappersProvider {
else getter = new OverloadFunction("get " + name); else getter = new OverloadFunction("get " + name);
getter.add(Overload.fromMethod(method, get.thisArg())); getter.add(Overload.fromMethod(method, get.thisArg()));
target.defineProperty(null, name, getter, setter, true, true); target.defineProperty(null, name, getter, setter, true, false);
} }
if (set != null) { if (set != null) {
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue; if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
@@ -70,7 +70,7 @@ public class NativeWrapperProvider implements WrappersProvider {
else setter = new OverloadFunction("set " + name); else setter = new OverloadFunction("set " + name);
setter.add(Overload.fromMethod(method, set.thisArg())); setter.add(Overload.fromMethod(method, set.thisArg()));
target.defineProperty(null, name, getter, setter, true, true); target.defineProperty(null, name, getter, setter, true, false);
} }
} }
} }
@@ -137,7 +137,11 @@ public class NativeWrapperProvider implements WrappersProvider {
* @param clazz The class for which a constructor should be generated * @param clazz The class for which a constructor should be generated
*/ */
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) { public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
FunctionValue func = new OverloadFunction(clazz.getName()); var name = clazz.getName();
var classNat = clazz.getAnnotation(Native.class);
if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim();
FunctionValue func = new OverloadFunction(name);
for (var overload : clazz.getDeclaredConstructors()) { for (var overload : clazz.getDeclaredConstructors()) {
var nat = overload.getAnnotation(Native.class); var nat = overload.getAnnotation(Native.class);
@@ -211,8 +215,8 @@ public class NativeWrapperProvider implements WrappersProvider {
if (constr == null) constr = makeConstructor(env, clazz); if (constr == null) constr = makeConstructor(env, clazz);
if (proto == null) proto = makeProto(env, clazz); if (proto == null) proto = makeProto(env, clazz);
proto.values.put("constructor", constr); proto.defineProperty(null, "constructor", constr, true, false, false);
constr.values.put("prototype", proto); constr.defineProperty(null, "prototype", proto, true, false, false);
prototypes.put(clazz, proto); prototypes.put(clazz, proto);
constructors.put(clazz, constr); constructors.put(clazz, constr);
@@ -240,6 +244,11 @@ public class NativeWrapperProvider implements WrappersProvider {
return constructors.get(clazz); return constructors.get(clazz);
} }
@Override
public WrappersProvider fork(Environment env) {
return new NativeWrapperProvider(env);
}
public void setProto(Class<?> clazz, ObjectValue value) { public void setProto(Class<?> clazz, ObjectValue value) {
prototypes.put(clazz, value); prototypes.put(clazz, value);
} }

View File

@@ -44,9 +44,11 @@ public class Overload {
public static Overload setterFromField(Field field) { public static Overload setterFromField(Field field) {
if (Modifier.isFinal(field.getModifiers())) return null; if (Modifier.isFinal(field.getModifiers())) return null;
return new Overload( return new Overload(
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false, false, (ctx, th, args) -> {
field.set(th, args[0]); return null;
}, false, false,
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(), Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
new Class[0] new Class[] { field.getType() }
); );
} }

View File

@@ -11,6 +11,7 @@ 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.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.InterruptException;
public class OverloadFunction extends FunctionValue { public class OverloadFunction extends FunctionValue {
public final List<Overload> overloads = new ArrayList<>(); public final List<Overload> overloads = new ArrayList<>();
@@ -84,8 +85,12 @@ public class OverloadFunction extends FunctionValue {
throw ((EngineException)e.getTargetException()).add(name, loc); throw ((EngineException)e.getTargetException()).add(name, loc);
} }
else if (e.getTargetException() instanceof NullPointerException) { else if (e.getTargetException() instanceof NullPointerException) {
e.printStackTrace();
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc); throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc);
} }
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
throw new InterruptException();
}
else { else {
throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc);
} }

View File

@@ -1,86 +1,87 @@
// TODO: load this in java (function (_arguments) {
var ts = require('./ts'); var ts = _arguments[0];
log("Loaded typescript!"); var src = '', lib = _arguments[2].concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0;
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
var src = '', lib = libs.concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0; var settings = {
var libSnapshot = ts.ScriptSnapshot.fromString(lib); outDir: "/out",
declarationDir: "/out",
target: ts.ScriptTarget.ES5,
lib: [ ],
module: ts.ModuleKind.None,
declaration: true,
stripInternal: true,
downlevelIteration: true,
forceConsistentCasingInFileNames: true,
experimentalDecorators: true,
strict: true,
};
var settings = { var reg = ts.createDocumentRegistry();
outDir: "/out", var service = ts.createLanguageService({
declarationDir: "/out", getCurrentDirectory: function() { return "/"; },
target: ts.ScriptTarget.ES5, getDefaultLibFileName: function() { return "/lib_.d.ts"; },
lib: [ ], getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; },
module: ts.ModuleKind.None, getCompilationSettings: function () { return settings; },
declaration: true, fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
stripInternal: true,
downlevelIteration: true, getScriptSnapshot: function(filename) {
forceConsistentCasingInFileNames: true, if (filename === "/lib.d.ts") return libSnapshot;
experimentalDecorators: true, if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
strict: true, if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
}; throw new Error("File '" + filename + "' doesn't exist.");
},
getScriptVersion: function (filename) {
if (filename === "/lib.d.ts") return 0;
else return version;
},
}, reg);
var reg = ts.createDocumentRegistry(); service.getEmitOutput('/lib.d.ts');
var service = ts.createLanguageService({ log('Loaded libraries!');
getCurrentDirectory: function() { return "/"; },
getDefaultLibFileName: function() { return "/lib_.d.ts"; },
getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; },
getCompilationSettings: function () { return settings; },
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
getScriptSnapshot: function(filename) { function compile(code, filename) {
if (filename === "/lib.d.ts") return libSnapshot; src = code, version++;
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
throw new Error("File '" + filename + "' doesn't exist.");
},
getScriptVersion: function (filename) {
if (filename === "/lib.d.ts") return 0;
else return version;
},
}, reg);
service.getEmitOutput('/lib.d.ts'); var emit = service.getEmitOutput("/src.ts");
log('Loaded libraries!');
function compile(filename, code) { var diagnostics = []
src = code, version++; .concat(service.getCompilerOptionsDiagnostics())
.concat(service.getSyntacticDiagnostics("/src.ts"))
.concat(service.getSemanticDiagnostics("/src.ts"))
.map(function (diagnostic) {
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
if (diagnostic.file) {
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
var file = diagnostic.file.fileName.substring(1);
if (file === "src.ts") file = filename;
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
}
else return "Error: " + message;
});
var emit = service.getEmitOutput("/src.ts"); if (diagnostics.length > 0) {
throw new SyntaxError(diagnostics.join('\n'));
}
var diagnostics = [] return {
.concat(service.getCompilerOptionsDiagnostics()) result: emit.outputFiles[0].text,
.concat(service.getSyntacticDiagnostics("/src.ts")) declaration: emit.outputFiles[1].text
.concat(service.getSemanticDiagnostics("/src.ts")) };
.map(function (diagnostic) {
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
if (diagnostic.file) {
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
var file = diagnostic.file.fileName.substring(1);
if (file === "src.ts") file = filename;
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
}
else return "Error: " + message;
});
if (diagnostics.length > 0) {
throw new SyntaxError(diagnostics.join('\n'));
} }
return { _arguments[1].compile = function (filename, code) {
result: emit.outputFiles[0].text, var res = compile(filename, code);
declaration: emit.outputFiles[1].text
};
}
init(function (filename, code) { return {
var res = compile(filename, code); source: res.result,
runner: function(func) {
return [ return function() {
res.result, var val = func.apply(this, arguments);
function(func, th, args) { decls += res.declaration;
var val = func.apply(th, args); return val;
decls += res.declaration; }
return val; }
} }
]; }
}); })(arguments);

File diff suppressed because one or more lines are too long

View File

@@ -177,6 +177,7 @@ public class JSON {
return ParseRes.res(values, n); return ParseRes.res(values, n);
} }
public static JSONElement parse(Filename filename, String raw) { public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0); 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);
@@ -187,12 +188,28 @@ 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() if (el.isString()) {
.replace("\\", "\\\\") var res = new StringBuilder("\"");
.replace("\n", "\\n") var alphabet = "0123456789ABCDEF".toCharArray();
.replace("\r", "\\r")
.replace("\"", "\\\"") for (var c : el.string().toCharArray()) {
+ "\""; if (c < 32 || c >= 127) {
res
.append("\\u")
.append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]);
}
else if (c == '\\')
res.append("\\\\");
else if (c == '"')
res.append("\\\"");
else res.append(c);
}
return res.append('"').toString();
}
if (el.isList()) { 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

@@ -16,7 +16,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
public class ArrayLib { @Native("Array") public class ArrayLib {
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) { @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) {
return thisArg.size(); return thisArg.size();
} }
@@ -69,7 +69,7 @@ public class ArrayLib {
@Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) { @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 = thisArg.size();
for (int i = 0; i < others.length; i++) { for (int i = 0; i < others.length; i++) {
if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size(); if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size();
@@ -77,8 +77,9 @@ public class ArrayLib {
} }
var res = new ArrayValue(size); var res = new ArrayValue(size);
thisArg.copyTo(ctx, res, 0, 0, thisArg.size());
for (int i = 0, j = 0; i < others.length; i++) { for (int i = 0, j = thisArg.size(); i < others.length; i++) {
if (others[i] instanceof ArrayValue) { if (others[i] instanceof ArrayValue) {
int n = ((ArrayValue)others[i]).size(); int n = ((ArrayValue)others[i]).size();
((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n); ((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n);
@@ -92,13 +93,14 @@ public class ArrayLib {
return res; return res;
} }
@Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) { @Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) {
arr.sort((a, b) -> { arr.sort((a, b) -> {
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;
}); });
return arr;
} }
private static int normalizeI(int len, int i, boolean clamp) { private static int normalizeI(int len, int i, boolean clamp) {
@@ -163,6 +165,37 @@ public class ArrayLib {
} }
} }
@Native(thisArg = true) public static Object reduce(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
var i = 0;
var res = arr.get(0);
if (args.length > 0) res = args[0];
else for (; !arr.has(i) && i < arr.size(); i++) res = arr.get(i);
for (; i < arr.size(); i++) {
if (arr.has(i)) {
res = func.call(ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Native(thisArg = true) public static Object reduceRight(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
var i = arr.size();
var res = arr.get(0);
if (args.length > 0) res = args[0];
else while (!arr.has(i--) && i >= 0) res = arr.get(i);
for (; i >= 0; i--) {
if (arr.has(i)) {
res = func.call(ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) { @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>();
@@ -223,7 +256,7 @@ public class ArrayLib {
@Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) { @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 = start; i < arr.size(); i++) {
if (Values.strictEquals(ctx, arr.get(i), val)) return i; if (Values.strictEquals(ctx, arr.get(i), val)) return i;
} }
@@ -267,17 +300,14 @@ public class ArrayLib {
return arr.size(); return arr.size();
} }
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, int end) { @Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, Object _end) {
start = normalizeI(arr.size(), start, true); start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true); int end = normalizeI(arr.size(), (int)(_end == null ? arr.size() : Values.toNumber(ctx, _end)), true);
var res = new ArrayValue(end - start); var res = new ArrayValue(end - start);
arr.copyTo(ctx, res, start, 0, end - start); arr.copyTo(ctx, res, start, 0, end - start);
return res; return res;
} }
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start) {
return slice(ctx, arr, start, arr.size());
}
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, int deleteCount, Object ...items) { @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);
@@ -302,12 +332,14 @@ public class ArrayLib {
@Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) { @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 = false;
for (int i = 0; i < arr.size(); i++) { for (int i = 0; i < arr.size(); i++) {
if (!arr.has(i)) continue; if (!arr.has(i)) continue;
if (comma) res.append(sep); if (comma) res.append(sep);
comma = false; comma = true;
var el = arr.get(i); var el = arr.get(i);
if (el == null || el == Values.NULL) continue; if (el == null || el == Values.NULL) continue;

View File

@@ -8,8 +8,9 @@ import me.topchetoeu.jscript.engine.values.CodeFunction;
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;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class AsyncFunctionLib extends FunctionValue { @Native("AsyncFunction") public class AsyncFunctionLib extends FunctionValue {
public final FunctionValue factory; public final FunctionValue factory;
public static class AsyncHelper { public static class AsyncHelper {

View File

@@ -0,0 +1,30 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
@Native("AsyncGeneratorFunction") public class AsyncGeneratorFunctionLib extends FunctionValue {
public final FunctionValue factory;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new AsyncGeneratorLib();
var func = factory.call(ctx, thisArg,
new NativeFunction("await", handler::await),
new NativeFunction("yield", handler::yield)
);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
return handler;
}
public AsyncGeneratorFunctionLib(FunctionValue factory) {
super(factory.name, factory.length);
this.factory = factory;
}
}

View File

@@ -6,130 +6,106 @@ import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.StackData; 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.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.interop.Native; import me.topchetoeu.jscript.interop.Native;
public class AsyncGeneratorLib extends FunctionValue { @Native("AsyncGenerator") public class AsyncGeneratorLib {
public final FunctionValue factory; @Native("@@Symbol.typeName") public final String name = "AsyncGenerator";
private int state = 0;
private boolean done = false;
private PromiseLib currPromise;
public CodeFrame frame;
public static class AsyncGenerator { private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
@Native("@@Symbol.typeName") public final String name = "AsyncGenerator"; if (done) {
private int state = 0; if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
private boolean done = false; currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
private PromiseLib currPromise; "done", true,
public CodeFrame frame; "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn
)));
return;
}
private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) { Object res = null;
if (done) { StackData.pushFrame(ctx, frame);
if (inducedError != Runners.NO_RETURN) state = 0;
throw new EngineException(inducedError);
currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
"done", true,
"value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn
)));
return;
}
Object res = null; while (state == 0) {
StackData.pushFrame(ctx, frame); try {
state = 0; res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
inducedValue = inducedReturn = inducedError = Runners.NO_RETURN;
while (state == 0) { if (res != Runners.NO_RETURN) {
try { var obj = new ObjectValue();
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); obj.defineProperty(ctx, "done", true);
inducedValue = inducedReturn = inducedError = Runners.NO_RETURN; obj.defineProperty(ctx, "value", res);
if (res != Runners.NO_RETURN) { currPromise.fulfill(ctx, obj);
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", true);
obj.defineProperty(ctx, "value", res);
currPromise.fulfill(ctx, obj);
break;
}
}
catch (EngineException e) {
currPromise.reject(ctx, e.value);
break; break;
} }
} }
catch (EngineException e) {
StackData.popFrame(ctx, frame); currPromise.reject(ctx, e.value);
break;
if (state == 1) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
}
else if (state == 2) {
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", false);
obj.defineProperty(ctx, "value", frame.pop());
currPromise.fulfill(ctx, obj);
} }
} }
@Override StackData.popFrame(ctx, frame);
public String toString() {
if (done) return "Generator [closed]";
if (state != 0) return "Generator [suspended]";
return "Generator [running]";
}
public Object fulfill(Context ctx, Object thisArg, Object ...args) { if (state == 1) {
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
return null;
} }
public Object reject(Context ctx, Object thisArg, Object ...args) { else if (state == 2) {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN); var obj = new ObjectValue();
return null; obj.defineProperty(ctx, "done", false);
} obj.defineProperty(ctx, "value", frame.pop());
currPromise.fulfill(ctx, obj);
@Native
public PromiseLib next(Context ctx, Object ...args) {
this.currPromise = new PromiseLib();
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);
return this.currPromise;
}
@Native("throw")
public PromiseLib _throw(Context ctx, Object error) {
this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
return this.currPromise;
}
@Native("return")
public PromiseLib _return(Context ctx, Object value) {
this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
return this.currPromise;
}
public Object await(Context ctx, Object thisArg, Object[] args) {
this.state = 1;
return args.length > 0 ? args[0] : null;
}
public Object yield(Context ctx, Object thisArg, Object[] args) {
this.state = 2;
return args.length > 0 ? args[0] : null;
} }
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) { public String toString() {
var handler = new AsyncGenerator(); if (done) return "Generator [closed]";
var func = factory.call(ctx, thisArg, if (state != 0) return "Generator [suspended]";
new NativeFunction("await", handler::await), return "Generator [running]";
new NativeFunction("yield", handler::yield)
);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
return handler;
} }
public AsyncGeneratorLib(FunctionValue factory) { public Object fulfill(Context ctx, Object thisArg, Object ...args) {
super(factory.name, factory.length); next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN);
this.factory = factory; return null;
} }
} public Object reject(Context ctx, Object thisArg, Object ...args) {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
return null;
}
@Native
public PromiseLib next(Context ctx, Object ...args) {
this.currPromise = new PromiseLib();
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);
return this.currPromise;
}
@Native("throw")
public PromiseLib _throw(Context ctx, Object error) {
this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
return this.currPromise;
}
@Native("return")
public PromiseLib _return(Context ctx, Object value) {
this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
return this.currPromise;
}
public Object await(Context ctx, Object thisArg, Object[] args) {
this.state = 1;
return args.length > 0 ? args[0] : null;
}
public Object yield(Context ctx, Object thisArg, Object[] args) {
this.state = 2;
return args.length > 0 ? args[0] : null;
}
}

View File

@@ -9,7 +9,7 @@ import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class BooleanLib { @Native("Boolean") public class BooleanLib {
public static final BooleanLib TRUE = new BooleanLib(true); public static final BooleanLib TRUE = new BooleanLib(true);
public static final BooleanLib FALSE = new BooleanLib(false); public static final BooleanLib FALSE = new BooleanLib(false);

View File

@@ -6,7 +6,7 @@ import java.util.TimeZone;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
public class DateLib { @Native("Date") public class DateLib {
private Calendar normal; private Calendar normal;
private Calendar utc; private Calendar utc;

View File

@@ -11,7 +11,7 @@ import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class ErrorLib { @Native("Error") public class ErrorLib {
private static String toString(Context ctx, boolean rethrown, Object cause, Object name, Object message, ArrayValue stack) { private static String toString(Context ctx, boolean rethrown, Object cause, Object name, Object message, ArrayValue stack) {
if (name == null) name = ""; if (name == null) name = "";
else name = Values.toString(ctx, name).trim(); else name = Values.toString(ctx, name).trim();
@@ -23,13 +23,6 @@ public class ErrorLib {
if (!message.equals("") && !name.equals("")) res.append(": "); if (!message.equals("") && !name.equals("")) res.append(": ");
if (!message.equals("")) res.append(message); if (!message.equals("")) res.append(message);
if (stack != null) {
for (var el : stack) {
var str = Values.toString(ctx, el).trim();
if (!str.equals("")) res.append("\n ").append(el);
}
}
if (cause instanceof ObjectValue) { if (cause instanceof ObjectValue) {
if (rethrown) res.append("\n (rethrown)"); if (rethrown) res.append("\n (rethrown)");
else res.append("\nCaused by ").append(toString(ctx, cause)); else res.append("\nCaused by ").append(toString(ctx, cause));

View File

@@ -1,8 +1,10 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.Location;
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.values.ArrayValue; 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.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;
@@ -11,7 +13,11 @@ 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;
public class FunctionLib { @Native("Function") public class FunctionLib {
@Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) {
if (func instanceof CodeFunction) return ((CodeFunction)func).loc().toString();
else return Location.INTERNAL.toString();
}
@Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) { @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());
} }
@@ -44,10 +50,10 @@ public class FunctionLib {
return new AsyncFunctionLib(func); return new AsyncFunctionLib(func);
} }
@Native public static FunctionValue asyncGenerator(FunctionValue func) { @Native public static FunctionValue asyncGenerator(FunctionValue func) {
return new AsyncGeneratorLib(func); return new AsyncGeneratorFunctionLib(func);
} }
@Native public static FunctionValue generator(FunctionValue func) { @Native public static FunctionValue generator(FunctionValue func) {
return new GeneratorLib(func); return new GeneratorFunctionLib(func);
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {

View File

@@ -0,0 +1,27 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
@Native("GeneratorFunction") public class GeneratorFunctionLib extends FunctionValue {
public final FunctionValue factory;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new GeneratorLib();
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.");
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
return handler;
}
public GeneratorFunctionLib(FunctionValue factory) {
super(factory.name, factory.length);
this.factory = factory;
}
}

View File

@@ -4,99 +4,78 @@ import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.StackData; 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.FunctionValue;
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.interop.Native; import me.topchetoeu.jscript.interop.Native;
public class GeneratorLib extends FunctionValue { @Native("Generator") public class GeneratorLib {
public final FunctionValue factory; private boolean yielding = true;
private boolean done = false;
public CodeFrame frame;
public static class Generator { @Native("@@Symbol.typeName") public final String name = "Generator";
private boolean yielding = true;
private boolean done = false;
public CodeFrame frame;
@Native("@@Symbol.typeName") public final String name = "Generator"; private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
if (done) {
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
var res = new ObjectValue();
res.defineProperty(ctx, "done", true);
res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn);
return res;
}
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) { Object res = null;
if (done) { StackData.pushFrame(ctx, frame);
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); yielding = false;
var res = new ObjectValue();
res.defineProperty(ctx, "done", true);
res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn);
return res;
}
Object res = null; while (!yielding) {
StackData.pushFrame(ctx, frame); try {
yielding = false; res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
inducedReturn = inducedError = Runners.NO_RETURN;
while (!yielding) { if (res != Runners.NO_RETURN) {
try {
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
inducedReturn = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) {
done = true;
break;
}
}
catch (EngineException e) {
done = true; done = true;
throw e; break;
} }
} }
catch (EngineException e) {
StackData.popFrame(ctx, frame); done = true;
if (done) frame = null; throw e;
else res = frame.pop(); }
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", done);
obj.defineProperty(ctx, "value", res);
return obj;
} }
@Native StackData.popFrame(ctx, frame);
public ObjectValue next(Context ctx, Object ...args) { if (done) frame = null;
if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); else res = frame.pop();
else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
}
@Native("throw")
public ObjectValue _throw(Context ctx, Object error) {
return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
}
@Native("return")
public ObjectValue _return(Context ctx, Object value) {
return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
}
@Override var obj = new ObjectValue();
public String toString() { obj.defineProperty(ctx, "done", done);
if (done) return "Generator [closed]"; obj.defineProperty(ctx, "value", res);
if (yielding) return "Generator [suspended]"; return obj;
return "Generator [running]"; }
}
public Object yield(Context ctx, Object thisArg, Object[] args) { @Native
this.yielding = true; public ObjectValue next(Context ctx, Object ...args) {
return args.length > 0 ? args[0] : null; 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);
}
@Native("throw")
public ObjectValue _throw(Context ctx, Object error) {
return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
}
@Native("return")
public ObjectValue _return(Context ctx, Object value) {
return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) { public String toString() {
var handler = new Generator(); if (done) return "Generator [closed]";
var func = factory.call(ctx, thisArg, new NativeFunction("yield", handler::yield)); if (yielding) return "Generator [suspended]";
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); return "Generator [running]";
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
return handler;
} }
public GeneratorLib(FunctionValue factory) { public Object yield(Context ctx, Object thisArg, Object[] args) {
super(factory.name, factory.length); this.yielding = true;
this.factory = factory; return args.length > 0 ? args[0] : null;
} }
} }

View File

@@ -11,6 +11,7 @@ import me.topchetoeu.jscript.engine.scope.GlobalScope;
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.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter;
public class Internals { public class Internals {
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>(); private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
@@ -20,6 +21,7 @@ public class Internals {
@Native public static void log(Context ctx, Object ...args) { @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.print(" ");
} }
System.out.println(); System.out.println();
} }
@@ -91,6 +93,23 @@ public class Internals {
return NumberLib.parseFloat(ctx, val); return NumberLib.parseFloat(ctx, val);
} }
@Native public static boolean isNaN(Context ctx, double val) {
return NumberLib.isNaN(ctx, val);
}
@Native public static boolean isFinite(Context ctx, double val) {
return NumberLib.isFinite(ctx, val);
}
@Native public static boolean isInfinite(Context ctx, double val) {
return NumberLib.isInfinite(ctx, val);
}
@NativeGetter public static double NaN(Context ctx) {
return Double.NaN;
}
@NativeGetter public static double Infinity(Context ctx) {
return Double.POSITIVE_INFINITY;
}
public void apply(Environment env) { public void apply(Environment env) {
var wp = env.wrappers; var wp = env.wrappers;
var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class)); var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class));
@@ -133,6 +152,7 @@ public class Internals {
env.setProto("rangeErr", wp.getProto(RangeErrorLib.class)); env.setProto("rangeErr", wp.getProto(RangeErrorLib.class));
wp.getProto(ObjectLib.class).setPrototype(null, null); wp.getProto(ObjectLib.class).setPrototype(null, null);
env.regexConstructor = wp.getConstr(RegExpLib.class);
System.out.println("Loaded polyfills!"); System.out.println("Loaded polyfills!");
} }

View File

@@ -1,17 +1,16 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
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.JSON; import me.topchetoeu.jscript.json.JSON;
public class JSONLib { @Native("JSON") public class JSONLib {
@Native @Native
public static Object parse(Context ctx, String val) { public static Object parse(Context ctx, String val) {
try { try {
return JSON.toJs(JSON.parse(new Filename("jscript", "json"), val)); return JSON.toJs(JSON.parse(null, val));
} }
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
} }

View File

@@ -12,7 +12,7 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
public class MapLib { @Native("Map") 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";
@@ -61,10 +61,10 @@ public class MapLib {
return map.size(); return map.size();
} }
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) { @Native 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, map.get(el), el,this);
} }
@Native public MapLib(Context ctx, Object iterable) { @Native public MapLib(Context ctx, Object iterable) {

View File

@@ -2,7 +2,7 @@ package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
public class MathLib { @Native("Math") public class MathLib {
@Native public static final double E = Math.E; @Native public static final double E = Math.E;
@Native public static final double PI = Math.PI; @Native public static final double PI = Math.PI;
@Native public static final double SQRT2 = Math.sqrt(2); @Native public static final double SQRT2 = Math.sqrt(2);

View File

@@ -9,7 +9,7 @@ import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class NumberLib { @Native("Number") public class NumberLib {
@Native public static final double EPSILON = java.lang.Math.ulp(1.0); @Native public static final double EPSILON = java.lang.Math.ulp(1.0);
@Native public static final double MAX_SAFE_INTEGER = 9007199254740991.; @Native public static final double MAX_SAFE_INTEGER = 9007199254740991.;
@Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; @Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;

View File

@@ -13,7 +13,7 @@ import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class ObjectLib { @Native("Object") public class ObjectLib {
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) { @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)) {

View File

@@ -18,7 +18,7 @@ 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;
public class PromiseLib { @Native("Promise") public class PromiseLib {
private static class Handle { private static class Handle {
public final Context ctx; public final Context ctx;
public final FunctionValue fulfilled; public final FunctionValue fulfilled;

View File

@@ -4,10 +4,11 @@ import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class RangeErrorLib extends ErrorLib { @Native("RangeError") public class RangeErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { @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");

View File

@@ -6,13 +6,14 @@ import java.util.regex.Pattern;
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.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeWrapper; 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.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
public class RegExpLib { @Native("RegExp") public class RegExpLib {
// I used Regex to analyze Regex // I used Regex to analyze Regex
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("[/\\-\\\\^$*+?.()|\\[\\]{}]");
@@ -81,7 +82,6 @@ public class RegExpLib {
return res; return res;
} }
@Native public Object exec(String str) { @Native public Object exec(String str) {
var matcher = pattern.matcher(str); var matcher = pattern.matcher(str);
if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) { if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) {
@@ -133,7 +133,7 @@ public class RegExpLib {
return "/" + source + "/" + flags(); return "/" + source + "/" + flags();
} }
@Native("@@Symvol.match") public Object match(Context ctx, String target) { @Native("@@Symbol.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;
@@ -150,7 +150,7 @@ public class RegExpLib {
} }
} }
@Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) { @Native("@@Symbol.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>() {
@@ -171,8 +171,8 @@ public class RegExpLib {
} }
}); });
} }
@Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) { @Native("@@Symbol.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;
@@ -214,29 +214,41 @@ public class RegExpLib {
} }
return res; return res;
} }
// [Symbol.replace](target, replacement) {
// const pattern = new this.constructor(this, this.flags + "d") as RegExp; @Native("@@Symbol.replace") public String replace(Context ctx, String target, Object replacement) {
// let match: RegExpResult | null; var pattern = new RegExpLib(this.source, this.flags() + "d");
// let lastEnd = 0; Object match;
// const res: string[] = []; var lastEnd = 0;
// // log(pattern.toString()); var res = new StringBuilder();
// while ((match = pattern.exec(target)) !== null) {
// const indices = match.indices![0]; while ((match = pattern.exec(target)) != Values.NULL) {
// res.push(target.substring(lastEnd, indices[0])); var indices = (ArrayValue)((ArrayValue)Values.getMember(ctx, match, "indices")).get(0);
// if (replacement instanceof Function) { var arrMatch = (ArrayValue)match;
// res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target));
// } var start = ((Number)indices.get(0)).intValue();
// else { var end = ((Number)indices.get(1)).intValue();
// res.push(replacement);
// } res.append(target.substring(lastEnd, start));
// lastEnd = indices[1]; if (replacement instanceof FunctionValue) {
// if (!pattern.global) break; var args = new Object[arrMatch.size() + 2];
// } args[0] = target.substring(start, end);
// if (lastEnd < target.length) { arrMatch.copyTo(args, 1, 1, arrMatch.size() - 1);
// res.push(target.substring(lastEnd)); args[args.length - 2] = start;
// } args[args.length - 1] = target;
// return res.join(''); res.append(Values.toString(ctx, ((FunctionValue)replacement).call(ctx, null, args)));
// }, }
else {
res.append(Values.toString(ctx, replacement));
}
lastEnd = end;
if (!pattern.global) break;
}
if (lastEnd < target.length()) {
res.append(target.substring(lastEnd));
}
return res.toString();
}
// [Symbol.search](target, reverse, start) { // [Symbol.search](target, reverse, start) {
// const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
// if (!reverse) { // if (!reverse) {
@@ -258,6 +270,7 @@ public class RegExpLib {
// else return -1; // else return -1;
// } // }
// }, // },
@Native public RegExpLib(Context ctx, Object pattern, Object flags) { @Native public RegExpLib(Context ctx, Object pattern, Object flags) {
this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags)); this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags));
} }
@@ -276,6 +289,7 @@ public class RegExpLib {
if (flags.contains("s")) this.flags |= Pattern.DOTALL; if (flags.contains("s")) this.flags |= Pattern.DOTALL;
if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS; if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS;
if (pattern.equals("{(\\d+)}")) pattern = "\\{([0-9]+)\\}";
this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags); this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags);
var matcher = NAMED_PATTERN.matcher(source); var matcher = NAMED_PATTERN.matcher(source);

View File

@@ -12,7 +12,7 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
public class SetLib { @Native("Set") 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";
@@ -51,7 +51,7 @@ public class SetLib {
return set.size(); return set.size();
} }
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) { @Native 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);

View File

@@ -16,7 +16,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
// TODO: implement index wrapping properly // TODO: implement index wrapping properly
public class StringLib { @Native("String") public class StringLib {
public final String value; public final String value;
private static String passThis(Context ctx, String funcName, Object val) { private static String passThis(Context ctx, String funcName, Object val) {
@@ -60,8 +60,18 @@ public class StringLib {
@Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) { @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) { // @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 String charAt(Context ctx, Object thisArg, int i) {
// var str = passThis(ctx, "charAt", thisArg);
// if (i < 0 || i >= str.length()) return "";
// else return str.charAt(i) + "";
// }
@Native(thisArg = true) public static double charCodeAt(Context ctx, Object thisArg, int i) {
var str = passThis(ctx, "charCodeAt", thisArg);
if (i < 0 || i >= str.length()) return Double.NaN;
else return str.charAt(i);
} }
@Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) { @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) {
@@ -101,7 +111,7 @@ public class StringLib {
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) { @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, Object 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)) {
@@ -111,9 +121,9 @@ public class StringLib {
} }
} }
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement));
} }
@Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) { @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, Object 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)) {
@@ -123,7 +133,7 @@ public class StringLib {
} }
} }
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement));
} }
@Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) { @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) {

View File

@@ -15,7 +15,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class SymbolLib { @Native("Symbol") 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.environment().symbol("Symbol.typeName"); } @NativeGetter public static Symbol typeName(Context ctx) { return ctx.environment().symbol("Symbol.typeName"); }

View File

@@ -4,10 +4,11 @@ import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class SyntaxErrorLib extends ErrorLib { @Native("SyntaxError") public class SyntaxErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { @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");

View File

@@ -4,10 +4,11 @@ import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeInit;
public class TypeErrorLib extends ErrorLib { @Native("TypeError") public class TypeErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { @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");

View File

@@ -82,8 +82,6 @@ public class Parsing {
// Although ES5 allow these, we will comply to ES6 here // Although ES5 allow these, we will comply to ES6 here
reserved.add("const"); reserved.add("const");
reserved.add("let"); reserved.add("let");
reserved.add("async");
reserved.add("super");
// These are allowed too, however our parser considers them keywords // These are allowed too, however our parser considers them keywords
reserved.add("undefined"); reserved.add("undefined");
reserved.add("arguments"); reserved.add("arguments");
@@ -259,7 +257,7 @@ public class Parsing {
} }
break; break;
case CURR_LITERAL: case CURR_LITERAL:
if (isAlphanumeric(c) || c == '_') { if (isAlphanumeric(c) || c == '_' || c == '$') {
currToken.append(c); currToken.append(c);
continue; continue;
} }
@@ -1060,7 +1058,6 @@ public class Parsing {
if (!checkVarName(literal.result)) { if (!checkVarName(literal.result)) {
if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported."); if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported.");
if (literal.result.equals("async")) return ParseRes.error(loc, "'async' is not supported.");
if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported.");
if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported.");
return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result)); return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result));
@@ -1137,7 +1134,9 @@ public class Parsing {
var op = opRes.result; var op = opRes.result;
if (!op.isAssign()) return ParseRes.failed(); if (!op.isAssign()) return ParseRes.failed();
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); if (!(prev instanceof AssignableStatement)) {
return ParseRes.error(loc, "Invalid expression on left hand side of assign operator.");
}
var res = parseValue(filename, tokens, i + n, 2); var res = parseValue(filename, tokens, i + n, 2);
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.value), res); if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.value), res);
@@ -1235,7 +1234,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(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) { public static ParseRes<CommaStatement> 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 +1245,7 @@ public class Parsing {
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res); if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res);
n += res.n; n += res.n;
return ParseRes.res(new CompoundStatement(loc, prev, res.result), n); return ParseRes.res(new CommaStatement(loc, prev, res.result), n);
} }
public static ParseRes<IfStatement> parseTernary(Filename 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);
@@ -1411,8 +1410,7 @@ public class Parsing {
var valRes = parseValue(filename, tokens, i + n, 0); var valRes = parseValue(filename, tokens, i + n, 0);
n += valRes.n; n += valRes.n;
if (valRes.isError()) if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes);
return ParseRes.error(loc, "Expected a return value.", valRes);
var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n); var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n);
@@ -1534,8 +1532,7 @@ public class Parsing {
if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes); if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes);
n += condRes.n; n += condRes.n;
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition.");
return ParseRes.error(loc, "Expected a closing paren after if condition.");
var res = parseStatement(filename, tokens, i + n); var res = parseStatement(filename, tokens, i + n);
if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res);
@@ -1702,8 +1699,7 @@ public class Parsing {
parseVariableDeclare(filename, tokens, i + n), parseVariableDeclare(filename, tokens, i + n),
parseValueStatement(filename, tokens, i + n) parseValueStatement(filename, tokens, i + n)
); );
if (!declRes.isSuccess()) if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
n += declRes.n; n += declRes.n;
decl = declRes.result; decl = declRes.result;
} }
@@ -1757,6 +1753,7 @@ public class Parsing {
var nameRes = parseIdentifier(tokens, i + n); var nameRes = parseIdentifier(tokens, i + n);
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop.");
var nameLoc = getLoc(filename, tokens, i + n);
n += nameRes.n; n += nameRes.n;
Statement varVal = null; Statement varVal = null;
@@ -1790,7 +1787,7 @@ public class Parsing {
if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes);
n += bodyRes.n; n += bodyRes.n;
return ParseRes.res(new ForInStatement(loc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
} }
public static ParseRes<TryStatement> parseCatch(Filename 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);

View File

@@ -0,0 +1,10 @@
return new UnitTest('counters')
.add('postfix increment', function () { var i = 10; i++ === 10; })
.add('postfix decrement', function () { var i = 10; i-- === 10; })
.add('prefix decrement', function () { var i = 10; --i === 9; })
.add('prefix increment', function () { var i = 10; ++i === 11; })
.add('ostfix increment of non-number', function () { var i = 'hi mom'; isNaN(i++); })
.add('ostfix decrement of non-number', function () { var i = 'hi mom'; isNaN(i--); })
.add('prefix increment of non-number', function () { var i = 'hi mom'; isNaN(++i); })
.add('prefix decrement of non-number', function () { var i = 'hi mom'; isNaN(--i); })
.add('postfix increment of convertible to number', function () { var i = '10'; i++; i === 11; })

View File

@@ -0,0 +1,2 @@
return new UnitTest('Arithmetics')
.add(include('counters.js'))

4
tests/array/concat.js Normal file
View File

@@ -0,0 +1,4 @@
return new UnitTest('concat', function() { return typeof Array.prototype.concat === 'function'; })
.add('two arrays', function() { return match([1, 2, 3], [1].concat([2], [3])) })
.add('simple spread', function() { return match([1, 2, 3, 4, 5], [1].concat([2], 3, [4, 5])) })
.add('sparse concat', function() { return match([1,, 2,,, 3,,, 4, 5], [1,,2].concat([,,3,,,4], 5)) })

22
tests/array/index.js Normal file
View File

@@ -0,0 +1,22 @@
function runIterator(arr, method, func, n) {
var res = [];
var j = 1;
var args = [ function() {
var pushed = [];
for (var i = 0; i < n; i++) pushed[i] = arguments[i];
res[j++] = pushed;
return func.apply(this, arguments);
} ];
for (var i = 4; i < arguments.length; i++) args[i - 3] = arguments[i];
res[0] = method.apply(arr, args);
return res;
}
return new UnitTest('Array', function() { []; })
.add(include('length.js'))
.add(include('reduce.js'))
.add(include('sparse.js'))
.add(include('concat.js'))

26
tests/array/length.js Normal file
View File

@@ -0,0 +1,26 @@
return new UnitTest('length & capacity', function() { return 'length' in Array.prototype; })
.add('empty literal', function() { return [].length === 0 })
.add('filled literal', function() { return [1, 2, 3].length === 3 })
.add('set length', function() {
var a = [];
a.length = 10;
return a.length === 10;
})
.add('length after set', function() {
var a = [];
a [5]= 5;
return a.length === 6;
})
.add('length after set (big', function() {
var a = [1, 2];
a [5000]= 5;
return a.length === 5001;
})
.add('expand test', function() {
var a = [];
for (var i = 0; i < 1000; i++) {
a[i] = i * 50;
if (a[i] !== i * 50) return false;
}
return a.length === 1000;
})

44
tests/array/reduce.js Normal file
View File

@@ -0,0 +1,44 @@
var res = [];
return new UnitTest('reduceRight', function () { return typeof Array.prototype.reduceRight === 'function' })
.add('empty function', function () {match(
[ undefined, [4, 3, 2], [undefined, 2, 1], [undefined, 1, 0], ],
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3), 1
)})
.add('adder', function () {match(
[ 10, [4, 3, 2], [7, 2, 1], [9, 1, 0], ],
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3), 1
)})
.add('sparse array', function () {match(
[ 10, [4, 3, 11], [7, 2, 7], [9, 1, 3], ],
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b }, 3), 1
)})
.add('sparse array with one element', function () {match(
[ 1 ],
runIterator([,,,1,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1
)})
.add('sparse array with no elements', function () {match(
[ undefined ],
runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1
)})
.add('initial value and empty function', function () {match(
[ undefined, [0, 4, 3], [undefined, 3, 2], [undefined, 2, 1], [undefined, 1, 0] ],
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3, 0), 1
)})
.add('initial value and adder', function () {match(
[ 15, [5, 4, 3], [9, 3, 2], [12, 2, 1], [14, 1, 0] ],
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
)})
.add('initial value, sparce array and adder', function () {match(
[ 15, [5, 4, 15], [9, 3, 11], [12, 2, 7], [14, 1, 3] ],
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
)})
.add('initial value and sparse array with one element', function () {match(
[ 6, [5, 1, 3] ],
runIterator([,,,1,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
)})
.add('initial value and sparse array with no elements', function () {match(
[ 5 ],
runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3, 5), 1
)});

4
tests/array/sort.js Normal file
View File

@@ -0,0 +1,4 @@
return new UnitTest('concat', function() { return typeof Array.prototype.concat === 'function'; })
.add('two arrays', function() { return match([1, 2, 3], [1].concat([2], [3])) })
.add('simple spread', function() { return match([1, 2, 3, 4, 5], [1].concat([2], 3, [4, 5])) })
.add('sparse concat', function() { return match([1,, 2,,, 3,,, 4, 5], [1,,2].concat([,,3,,,4], 5)) })

5
tests/array/sparse.js Normal file
View File

@@ -0,0 +1,5 @@
return new UnitTest('sparse', function() { return !(0 in [,,]) })
.add('empty in start', function() { var a = [,1]; return !(0 in a) && (1 in a); })
.add('empty in middle', function() { var a = [1,,2]; return !(1 in a) && (2 in a) && (0 in a); })
.add('empty in end', function() { var a = [1,,]; return !(1 in a) && (0 in a); })
.add('trailing comma', function() { var a = [1,]; return a.length === 1; })

70
tests/index.js Normal file
View File

@@ -0,0 +1,70 @@
function assert(cond, msg, locDepth) {
if (locDepth < 0 || locDepth === undefined) locDepth = 0;
if (!cond) {
log('Assert failed', (typeof locDepth === 'string' ? locDepth : Error().stack[locDepth + 1]) + ': ', msg);
}
}
function assertMatch(expected, actual, depth, msg) {
if (!match(expected, actual, depth)) {
log('Assert failed', Error().stack[1] + ': ', msg);
log('Expected:', expected);
log('Actual:', actual);
}
}
function match(expected, actual, depth) {
if (!Array.isArray(expected) || !Array.isArray(actual)) return expected === actual;
else if (expected.length !== actual.length) return false;
else if (depth === undefined || depth < 0) depth = 0;
for (var i = 0; i < expected.length; i++) {
if (!(i in expected) || !(i in actual)) return !(i in expected) && !(i in actual);
if (
expected[i] === actual[i] ||
depth > 0 &&
Array.isArray(expected) &&
Array.isArray(actual) &&
match(expected[i], actual[i], depth - 1)
) continue;
return false;
}
return true;
}
/** @class */ function UnitTest(msg, exec) {
this.name = msg;
this.exec = exec;
this.subtests = [];
}
UnitTest.prototype.run = function(path) {
if (path === undefined) path = [];
path.push(this.name);
if (typeof this.exec === 'function') {
var res = true, err = 'exec() returned false.';
try {
if (this.exec() === false) res = false;
}
catch (e) { res = false; err = e; }
assert(res, path.join('/') + ': ' + err, this.exec.location());
}
for (var i = 0; i < this.subtests.length; i++) {
this.subtests[i].run(path);
}
path.pop();
}
UnitTest.prototype.add = function(test, exec) {
if (test instanceof UnitTest) this.subtests.push(test);
else this.subtests.push(new UnitTest(test, exec));
return this;
}
include('arithmetics/index.js').run();
include('array/index.js').run();
log('Tests complete.');