Merge pull request #8 from TopchetoEU/TopchetoEU/tests

Integrate typescript
This commit is contained in:
TopchetoEU 2023-11-05 20:32:42 +02:00 committed by GitHub
commit df8465cb49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 931 additions and 86691 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,131 +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.ArrayValue; import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.lib.Internals;
public class Main {
static Thread engineTask, debugTask; public class Main {
static Engine engine; static Thread engineTask, debugTask;
static Environment env; static Engine engine;
static int j = 0; static Environment env;
static int j = 0;
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) { private static Observer<Object> valuePrinter = new Observer<Object>() {
Values.printValue(null, data); public void next(Object data) {
System.out.println(); 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() { @Override
engineTask.interrupt(); 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)); public static void main(String args[]) {
engine = new Engine(); System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
engine = new Engine(true);
env = new Environment(null, null, null);
var exited = new boolean[1]; env = new Environment(null, null, null);
var server = new DebugServer(); var exited = new boolean[1];
server.targets.put("target", (ws, req) -> SimpleDebugger.get(ws, engine)); var server = new DebugServer();
server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
engineTask = engine.start();
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true); engineTask = engine.start();
// server.awaitConnection(); debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> { engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
new Internals().apply(env); new Internals().apply(env);
env.global.define("exit", _ctx -> { env.global.define("exit", _ctx -> {
exited[0] = true; exited[0] = true;
throw new InterruptException(); throw new InterruptException();
}); });
env.global.define("go", _ctx -> { env.global.define("go", _ctx -> {
try { try {
var f = Path.of("do.js"); var f = Path.of("do.js");
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
return func.call(_ctx); return func.call(_ctx);
} }
catch (IOException e) { catch (IOException e) {
throw new EngineException("Couldn't open do.js"); throw new EngineException("Couldn't open do.js");
} }
}); });
// TODO: make better API
return null; env.global.define(true, new NativeFunction("include", (_ctx, th, __args) -> {
}), null).await(); try {
var currFilename = StackData.peekFrame(_ctx).function.loc().filename();
try { var loc = Path.of("").toAbsolutePath();
var ts = engine.pushMsg( if (currFilename.protocol.equals("file")) loc = Path.of(currFilename.path).getParent();
false, new Context(engine).pushEnv(env), var path = loc.resolve(Path.of(__args.length >= 1 ? Values.toString(_ctx, __args[0]) : ""));
new Filename("file", "/mnt/data/repos/java-jscript/src/me/topchetoeu/jscript/js/ts.js"), var src = Files.readString(path);
Reading.resourceToString("js/ts.js"), null var func = _ctx.compile(Filename.fromFile(path.toFile()), src);
).await(); var callArgs = new ArrayValue();
System.out.println("Loaded typescript!"); if (__args.length >= 2 && __args[1] instanceof ArrayValue) callArgs = (ArrayValue)__args[1];
engine.pushMsg( return func.call(_ctx, null, callArgs);
false, new Context(engine).pushEnv(env.child()), }
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, catch (IOException e) { throw EngineException.ofError("IOError", "Couldn't open file."); }
ts, env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) }));
).await();
} return null;
catch (EngineException e) { }), null).await();
Values.printError(e, "(while initializing TS)");
System.out.println("engine reported stack trace:"); try {
for (var el : e.stackTrace) { var tsEnv = env.child();
System.out.println(el); tsEnv.global.define(null, "module", false, new ObjectValue());
} engine.pushMsg(
} false, new Context(engine).pushEnv(tsEnv),
new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null
var reader = new Thread(() -> { ).await();
try { System.out.println("Loaded typescript!");
for (var i = 0; ; i++) {
try { var ctx = new Context(engine).pushEnv(env.child());
var raw = Reading.read();
engine.pushMsg(
if (raw == null) break; false, ctx,
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await()); 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"))
catch (EngineException e) { Values.printError(e, ""); } ).await();
} }
} catch (EngineException e) {
catch (IOException e) { return; } Values.printError(e, "(while initializing TS)");
catch (SyntaxException ex) { }
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg); var reader = new Thread(() -> {
} try {
catch (RuntimeException ex) { for (var arg : args) {
if (!exited[0]) { try {
System.out.println("Internal error ocurred:"); var file = Path.of(arg);
ex.printStackTrace(); var raw = Files.readString(file);
} valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await());
} }
catch (Throwable e) { throw new UncheckedException(e); } catch (EngineException e) { Values.printError(e, ""); }
if (exited[0]) debugTask.interrupt(); }
}); for (var i = 0; ; i++) {
reader.setDaemon(true); try {
reader.setName("STD Reader"); var raw = Reading.read();
reader.start();
} 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

@ -17,6 +17,10 @@ public class ChangeStatement extends Statement {
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, true); 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

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

@ -6,7 +6,6 @@ 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.NativeFunction;
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.parsing.Parsing; import me.topchetoeu.jscript.parsing.Parsing;
@ -40,10 +39,9 @@ public class Context {
} }
else source = Values.toString(this, transpiled); else source = Values.toString(this, transpiled);
var debugger = StackData.getDebugger(this);
var breakpoints = new TreeSet<Location>(); var breakpoints = new TreeSet<Location>();
FunctionValue res = Parsing.compile(engine.functions, breakpoints, environment(), filename, source); FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source);
if (debugger != null) debugger.onSource(filename, source, breakpoints); engine.onSource(filename, source, breakpoints);
if (runner != null) res = (FunctionValue)runner.call(this, null, res); if (runner != null) res = (FunctionValue)runner.call(this, null, 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

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

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

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

@ -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)))
); );
} }
} }
@ -403,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,
@ -415,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);
@ -479,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();
@ -594,27 +593,23 @@ 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 { else {
@ -623,26 +618,21 @@ public class SimpleDebugger implements Debugger {
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 (true) break;
} }
ws.send(msg.respond(new JSONMap().set("result", res))); ws.send(msg.respond(new JSONMap().set("result", res)));
@ -667,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
@ -708,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;
@ -738,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);
@ -801,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

@ -87,15 +87,18 @@ public class WebSocket implements AutoCloseable {
} }
private synchronized void write(int type, byte[] data) { private synchronized void write(int type, byte[] data) {
try { try {
for (int i = 0; i < (data.length >> 16); i++) { int i;
for (i = 0; i < data.length / 0xFFFF; i++) {
out().write(type); out().write(type);
writeLength(0xFFFF); writeLength(0xFFFF);
out().write(data, i << 16, 0xFFFF); out().write(data, i * 0xFFFF, 0xFFFF);
type = 0;
} }
out().write(type | 0x80); out().write(type | 0x80);
writeLength(data.length & 0xFFFF); writeLength(data.length % 0xFFFF);
out().write(data, data.length & 0xFFFF0000, data.length & 0xFFFF); out().write(data, i * 0xFFFF, data.length % 0xFFFF);
} }
catch (IOException e) { catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
@ -116,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());
} }
@ -199,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,34 +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); 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;
if (instr.location != null) prevLoc = instr.location;
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; }
} }
@ -192,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) {
@ -218,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;
} }
@ -247,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;
@ -255,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;
} }
@ -201,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

@ -52,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;
@ -85,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]);
} }
} }

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

@ -258,8 +258,7 @@ public class Values {
public static Object getMember(Context ctx, Object obj, Object key) { public static Object getMember(Context ctx, Object obj, Object key) {
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (obj == null) if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
throw new IllegalArgumentException("Tried to access member of undefined.");
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
if (isObject(obj)) return object(obj).getMember(ctx, key); if (isObject(obj)) return object(obj).getMember(ctx, key);
@ -279,8 +278,7 @@ public class Values {
} }
public static boolean setMember(Context ctx, Object obj, Object key, Object val) { public static boolean setMember(Context ctx, Object obj, Object key, Object val) {
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
if (obj == null) if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
throw EngineException.ofType("Tried to access member of undefined.");
if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
if (key.equals("__proto__")) return setPrototype(ctx, obj, val); if (key.equals("__proto__")) return setPrototype(ctx, obj, val);
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
@ -369,8 +367,7 @@ public class Values {
} }
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) { public static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
if (!isFunction(func)) if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
throw EngineException.ofType("Tried to call a non-function value.");
return function(func).call(ctx, thisArg, args); return function(func).call(ctx, thisArg, args);
} }
public static Object callNew(Context ctx, Object func, Object ...args) { public static Object callNew(Context ctx, Object func, Object ...args) {
@ -524,8 +521,6 @@ public class Values {
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) { public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
return () -> { return () -> {
try { try {
var _ctx = ctx;
var _obj = obj;
var symbol = ctx.environment().symbol("Symbol.iterator"); var symbol = ctx.environment().symbol("Symbol.iterator");
var iteratorFunc = getMember(ctx, obj, symbol); var iteratorFunc = getMember(ctx, obj, symbol);
@ -587,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;
@ -598,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

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

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<>();
@ -87,6 +88,9 @@ public class OverloadFunction extends FunctionValue {
e.printStackTrace(); 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,7 +1,5 @@
(function (_arguments) { (function (_arguments) {
var ts = _arguments[0]; var ts = _arguments[0];
log("Loaded typescript!");
var src = '', lib = _arguments[2].concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0; var src = '', lib = _arguments[2].concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0;
var libSnapshot = ts.ScriptSnapshot.fromString(lib); var libSnapshot = ts.ScriptSnapshot.fromString(lib);
@ -42,7 +40,7 @@
service.getEmitOutput('/lib.d.ts'); service.getEmitOutput('/lib.d.ts');
log('Loaded libraries!'); log('Loaded libraries!');
function compile(filename, code) { function compile(code, filename) {
src = code, version++; src = code, version++;
var emit = service.getEmitOutput("/src.ts"); var emit = service.getEmitOutput("/src.ts");

File diff suppressed because one or more lines are too long

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();
} }
@ -93,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) {
@ -164,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>();
@ -224,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;
} }
@ -268,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);
@ -303,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();
} }
@ -101,6 +103,13 @@ public class Internals {
return NumberLib.isInfinite(ctx, 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));

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";
@ -64,7 +64,7 @@ public class MapLib {
@Native 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";

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) {
@ -111,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)) {
@ -121,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)) {
@ -133,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);
@ -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;
} }

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.');