From 517e3e66575188c6069ee8fe60d94ca8c93a1068 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:07:07 +0300 Subject: [PATCH 1/7] refactor: clean up context and stack data --- src/me/topchetoeu/jscript/Main.java | 6 +- src/me/topchetoeu/jscript/engine/Context.java | 38 +++++++---- src/me/topchetoeu/jscript/engine/Data.java | 56 ++++++++++------- src/me/topchetoeu/jscript/engine/Engine.java | 28 ++++----- .../jscript/engine/Environment.java | 13 ++-- src/me/topchetoeu/jscript/engine/Message.java | 63 ------------------- .../topchetoeu/jscript/engine/StackData.java | 52 +++++++++++++++ .../jscript/engine/frame/CodeFrame.java | 15 +---- .../jscript/engine/frame/Runners.java | 18 +++--- .../jscript/engine/values/CodeFunction.java | 15 ++++- .../jscript/engine/values/NativeWrapper.java | 3 +- .../jscript/engine/values/ObjectValue.java | 30 ++++----- .../jscript/engine/values/Values.java | 14 ++--- .../jscript/lib/AsyncFunctionLib.java | 6 +- .../jscript/lib/AsyncGeneratorLib.java | 5 +- src/me/topchetoeu/jscript/lib/ErrorLib.java | 3 +- .../topchetoeu/jscript/lib/GeneratorLib.java | 5 +- src/me/topchetoeu/jscript/lib/Internals.java | 16 ++--- src/me/topchetoeu/jscript/lib/ObjectLib.java | 2 +- src/me/topchetoeu/jscript/lib/PromiseLib.java | 7 +-- src/me/topchetoeu/jscript/lib/StringLib.java | 26 ++++---- src/me/topchetoeu/jscript/lib/SymbolLib.java | 16 ++--- 22 files changed, 226 insertions(+), 211 deletions(-) delete mode 100644 src/me/topchetoeu/jscript/engine/Message.java create mode 100644 src/me/topchetoeu/jscript/engine/StackData.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 314f9c6..021ad62 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -7,7 +7,7 @@ import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; -import me.topchetoeu.jscript.engine.Message; +import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -65,7 +65,7 @@ public class Main { env = new Environment(null, null, null); var exited = new boolean[1]; - engine.pushMsg(false, new Message(engine), new NativeFunction((ctx, thisArg, _a) -> { + engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> { new Internals().apply(env); env.global.define("exit", _ctx -> { @@ -94,7 +94,7 @@ public class Main { var raw = in.readLine(); if (raw == null) break; - engine.pushMsg(false, env.context(new Message(engine)), "", raw, null).toObservable().once(valuePrinter); + engine.pushMsg(false, new Context(engine).pushEnv(env), "", raw, null).toObservable().once(valuePrinter); } catch (EngineException e) { try { diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 480453f..92ffb1d 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -1,27 +1,39 @@ package me.topchetoeu.jscript.engine; +import java.util.Stack; + import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.parsing.Parsing; public class Context { - public final Environment env; - public final Message message; + private final Stack env = new Stack<>(); + public final Data data; + public final Engine engine; + + public Environment environment() { + return env.empty() ? null : env.peek(); + } + + public Context pushEnv(Environment env) { + this.env.push(env); + return this; + } + public void popEnv() { + if (!env.empty()) this.env.pop(); + } public FunctionValue compile(String filename, String raw) throws InterruptedException { - var res = Values.toString(this, env.compile.call(this, null, raw, filename)); - return Parsing.compile(message.engine.functions, env, filename, res); + var res = Values.toString(this, environment().compile.call(this, null, raw, filename)); + return Parsing.compile(engine.functions, environment(), filename, res); } - public Context setEnv(Environment env) { - return new Context(env, message); + public Context(Engine engine, Data data) { + this.data = new Data(engine.data); + if (data != null) this.data.addAll(data); + this.engine = engine; } - public Context setMsg(Message msg) { - return new Context(env, msg); - } - - public Context(Environment env, Message msg) { - this.env = env; - this.message = msg; + public Context(Engine engine) { + this(engine, null); } } diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java index 1db4ba8..828c25b 100644 --- a/src/me/topchetoeu/jscript/engine/Data.java +++ b/src/me/topchetoeu/jscript/engine/Data.java @@ -1,43 +1,53 @@ package me.topchetoeu.jscript.engine; import java.util.HashMap; -import java.util.Iterator; -import java.util.Map.Entry; +import java.util.Map; @SuppressWarnings("unchecked") -public class Data implements Iterable, ?>> { +public class Data { + public final Data parent; private HashMap, Object> data = new HashMap<>(); public Data copy() { return new Data().addAll(this); } - public Data addAll(Iterable, ?>> data) { - for (var el : data) { - add((DataKey)el.getKey(), (Object)el.getValue()); + public Data addAll(Map, ?> data) { + for (var el : data.entrySet()) { + get((DataKey)el.getKey(), (Object)el.getValue()); + } + return this; + } + public Data addAll(Data data) { + for (var el : data.data.entrySet()) { + get((DataKey)el.getKey(), (Object)el.getValue()); } return this; } + public T remove(DataKey key) { + return (T)data.remove(key); + } public Data set(DataKey key, T val) { - if (val == null) data.remove(key); - else data.put((DataKey)key, (Object)val); + data.put((DataKey)key, (Object)val); return this; } - public T add(DataKey key, T val) { - if (data.containsKey(key)) return (T)data.get(key); - else { - if (val == null) data.remove(key); - else data.put((DataKey)key, (Object)val); - return val; + public T get(DataKey key, T val) { + for (var it = this; it != null; it = it.parent) { + if (it.data.containsKey(key)) { + this.set(key, val); + return (T)data.get((DataKey)key); + } } + + set(key, val); + return val; } public T get(DataKey key) { - return get(key, null); - } - public T get(DataKey key, T defaultVal) { - if (!has(key)) return defaultVal; - else return (T)data.get(key); + for (var it = this; it != null; it = it.parent) { + if (it.data.containsKey(key)) return (T)data.get((DataKey)key); + } + return null; } public boolean has(DataKey key) { return data.containsKey(key); } @@ -53,8 +63,10 @@ public class Data implements Iterable, ?>> { return increase(key, 1, 0); } - @Override - public Iterator, ?>> iterator() { - return (Iterator, ?>>)data.entrySet(); + public Data() { + this.parent = null; + } + public Data(Data parent) { + this.parent = parent; } } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index ae4b3a7..9c59378 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -13,19 +13,18 @@ public class Engine { private class UncompiledFunction extends FunctionValue { public final String filename; public final String raw; - public final Environment env; + private FunctionValue compiled = null; @Override public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - ctx = ctx.setEnv(env); - return ctx.compile(filename, raw).call(ctx, thisArg, args); + if (compiled == null) compiled = ctx.compile(filename, raw); + return compiled.call(ctx, thisArg, args); } - public UncompiledFunction(Environment env, String filename, String raw) { + public UncompiledFunction(String filename, String raw) { super(filename, 0); this.filename = filename; this.raw = raw; - this.env = env; } } @@ -34,10 +33,10 @@ public class Engine { public final Object thisArg; public final Object[] args; public final DataNotifier notifier = new DataNotifier<>(); - public final Message msg; + public final Context ctx; - public Task(Message ctx, FunctionValue func, Object thisArg, Object[] args) { - this.msg = ctx; + public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) { + this.ctx = ctx; this.func = func; this.thisArg = thisArg; this.args = args; @@ -52,10 +51,11 @@ public class Engine { public final int id = ++nextId; public final HashMap functions = new HashMap<>(); + public final Data data = new Data().set(StackData.MAX_FRAMES, 10000); private void runTask(Task task) throws InterruptedException { try { - task.notifier.next(task.func.call(task.msg.context(null), task.thisArg, task.args)); + task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); } catch (InterruptedException e) { task.notifier.error(new RuntimeException(e)); @@ -105,17 +105,13 @@ public class Engine { return this.thread != null; } - public Awaitable pushMsg(boolean micro, Message ctx, FunctionValue func, Object thisArg, Object ...args) { - var msg = new Task(ctx, func, thisArg, args); + public Awaitable pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) { + var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args); if (micro) microTasks.addLast(msg); else macroTasks.addLast(msg); return msg.notifier; } public Awaitable pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) { - return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args); + return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); } - - // public Engine() { - // this.typeRegister = new NativeTypeRegister(); - // } } diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index c8136cf..5afe1c2 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -20,7 +20,7 @@ public class Environment { public final HashMap symbols = new HashMap<>(); public GlobalScope global; - public WrappersProvider wrappersProvider; + public WrappersProvider wrappers; @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @@ -57,7 +57,7 @@ public class Environment { } @Native public Environment fork() { - var res = new Environment(compile, wrappersProvider, global); + var res = new Environment(compile, wrappers, global); res.regexConstructor = regexConstructor; res.prototypes = new HashMap<>(prototypes); return res; @@ -68,8 +68,11 @@ public class Environment { return res; } - public Context context(Message msg) { - return new Context(this, msg); + public Context context(Engine engine, Data data) { + return new Context(engine, data).pushEnv(this); + } + public Context context(Engine engine) { + return new Context(engine).pushEnv(this); } public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { @@ -77,7 +80,7 @@ public class Environment { if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); if (global == null) global = new GlobalScope(); - this.wrappersProvider = nativeConverter; + this.wrappers = nativeConverter; this.compile = compile; this.global = global; } diff --git a/src/me/topchetoeu/jscript/engine/Message.java b/src/me/topchetoeu/jscript/engine/Message.java deleted file mode 100644 index b61f522..0000000 --- a/src/me/topchetoeu/jscript/engine/Message.java +++ /dev/null @@ -1,63 +0,0 @@ -package me.topchetoeu.jscript.engine; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.exceptions.EngineException; - -public class Message { - public final Engine engine; - - private final ArrayList frames = new ArrayList<>(); - public int maxStackFrames = 1000; - - public final Data data = new Data(); - - public List frames() { return Collections.unmodifiableList(frames); } - - public Message addData(Data data) { - this.data.addAll(data); - return this; - } - - public Message pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { - this.frames.add(frame); - if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!"); - return this; - } - public boolean popFrame(CodeFrame frame) { - if (this.frames.size() == 0) return false; - if (this.frames.get(this.frames.size() - 1) != frame) return false; - this.frames.remove(this.frames.size() - 1); - return true; - } - - public List stackTrace() { - var res = new ArrayList(); - - for (var el : frames) { - var name = el.function.name; - var loc = el.function.loc(); - var trace = ""; - - if (loc != null) trace += "at " + loc.toString() + " "; - if (name != null && !name.equals("")) trace += "in " + name + " "; - - trace = trace.trim(); - - if (!res.equals("")) res.add(trace); - } - - return res; - } - - public Context context(Environment env) { - return new Context(env, this); - } - - public Message(Engine engine) { - this.engine = engine; - } -} diff --git a/src/me/topchetoeu/jscript/engine/StackData.java b/src/me/topchetoeu/jscript/engine/StackData.java new file mode 100644 index 0000000..9bb0fab --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/StackData.java @@ -0,0 +1,52 @@ +package me.topchetoeu.jscript.engine; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import me.topchetoeu.jscript.engine.debug.DebugServer; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.exceptions.EngineException; + +public class StackData { + public static final DataKey> FRAMES = new DataKey<>(); + public static final DataKey MAX_FRAMES = new DataKey<>(); + public static final DataKey DEBUGGER = new DataKey<>(); + + public static void pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { + var frames = ctx.data.get(FRAMES, new ArrayList<>()); + frames.add(frame); + if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) throw EngineException.ofRange("Stack overflow!"); + ctx.pushEnv(frame.function.environment); + } + public static boolean popFrame(Context ctx, CodeFrame frame) { + var frames = ctx.data.get(FRAMES, new ArrayList<>()); + if (frames.size() == 0) return false; + if (frames.get(frames.size() - 1) != frame) return false; + frames.remove(frames.size() - 1); + ctx.popEnv(); + return true; + } + + public static List frames(Context ctx) { + return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>())); + } + public static List stackTrace(Context ctx) { + var res = new ArrayList(); + + for (var el : frames(ctx)) { + var name = el.function.name; + var loc = el.function.loc(); + var trace = ""; + + if (loc != null) trace += "at " + loc.toString() + " "; + if (name != null && !name.equals("")) trace += "in " + name + " "; + + trace = trace.trim(); + + if (!trace.equals("")) res.add(trace); + } + + return res; + } +} diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 2ab02a3..84c12b1 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -95,7 +95,7 @@ public class CodeFrame { private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException { if (err.value instanceof ObjectValue) { - Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause); + Values.setMember(ctx, err, ctx.environment().symbol("Symbol.cause"), cause); } err.cause = cause; } @@ -227,19 +227,6 @@ public class CodeFrame { return Runners.NO_RETURN; } - public Object run(Context ctx) throws InterruptedException { - try { - ctx.message.pushFrame(ctx, this); - while (true) { - var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); - if (res != Runners.NO_RETURN) return res; - } - } - finally { - ctx.message.popFrame(this); - } - } - public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) { this.args = args.clone(); this.scope = new LocalScope(func.localsN, func.captures); diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index ba56ad2..f25345a 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -63,7 +63,7 @@ public class Runners { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { var name = (String)instr.get(0); - ctx.env.global.define(name); + ctx.environment().global.define(name); frame.codePtr++; return NO_RETURN; } @@ -158,7 +158,7 @@ public class Runners { public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { var i = instr.get(0); - if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i)); + if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i)); else frame.push(ctx, frame.scope.get((int)i).get(ctx)); frame.codePtr++; @@ -170,7 +170,7 @@ public class Runners { return NO_RETURN; } public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, ctx.env.global.obj); + frame.push(ctx, ctx.environment().global.obj); frame.codePtr++; return NO_RETURN; } @@ -191,8 +191,8 @@ public class Runners { captures[i - 3] = frame.scope.get(instr.get(i)); } - var body = ctx.message.engine.functions.get(id); - var func = new CodeFunction(ctx.env, "", localsN, len, captures, body); + var body = ctx.engine.functions.get(id); + var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body); frame.push(ctx, func); @@ -217,7 +217,7 @@ public class Runners { return execLoadMember(ctx, instr, frame); } public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { - frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); + frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.codePtr++; return NO_RETURN; } @@ -241,7 +241,7 @@ public class Runners { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var i = instr.get(0); - if (i instanceof String) ctx.env.global.set(ctx, (String)i, val); + if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val); else frame.scope.get((int)i).set(ctx, val); frame.codePtr++; @@ -288,8 +288,8 @@ public class Runners { Object obj; if (name != null) { - if (ctx.env.global.has(ctx, name)) { - obj = ctx.env.global.get(ctx, name); + if (ctx.environment().global.has(ctx, name)) { + obj = ctx.environment().global.get(ctx, name); } else obj = null; } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index c0025b3..1333cf2 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -4,7 +4,9 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.ValueVariable; public class CodeFunction extends FunctionValue { @@ -29,7 +31,18 @@ public class CodeFunction extends FunctionValue { @Override public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { - return new CodeFrame(ctx, thisArg, args, this).run(ctx.setEnv(environment)); + var frame = new CodeFrame(ctx, thisArg, args, this); + try { + StackData.pushFrame(ctx, frame); + + while (true) { + var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); + if (res != Runners.NO_RETURN) return res; + } + } + finally { + StackData.popFrame(ctx, frame); + } } public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index 9005e5f..5501129 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -8,8 +8,7 @@ public class NativeWrapper extends ObjectValue { @Override public ObjectValue getPrototype(Context ctx) throws InterruptedException { - if (prototype == NATIVE_PROTO) - return ctx.env.wrappersProvider.getProto(wrapped.getClass()); + if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass()); else return super.getPrototype(ctx); } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index fbd474f..e3de9d2 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -147,13 +147,13 @@ public class ObjectValue { public ObjectValue getPrototype(Context ctx) throws InterruptedException { try { - if (prototype == OBJ_PROTO) return ctx.env.proto("object"); - if (prototype == ARR_PROTO) return ctx.env.proto("array"); - if (prototype == FUNC_PROTO) return ctx.env.proto("function"); - if (prototype == ERR_PROTO) return ctx.env.proto("error"); - if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr"); - if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr"); - if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr"); + if (prototype == OBJ_PROTO) return ctx.environment().proto("object"); + if (prototype == ARR_PROTO) return ctx.environment().proto("array"); + if (prototype == FUNC_PROTO) return ctx.environment().proto("function"); + if (prototype == ERR_PROTO) return ctx.environment().proto("error"); + if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr"); + if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr"); + if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr"); } catch (NullPointerException e) { return null; @@ -172,14 +172,14 @@ public class ObjectValue { else if (Values.isObject(val)) { var obj = Values.object(val); - if (ctx != null && ctx.env != null) { - if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO; - else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO; - else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO; - else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO; - else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; - else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO; - else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO; + if (ctx != null && ctx.environment() != null) { + if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO; + else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO; + else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO; + else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO; + else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; + else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO; + else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO; else prototype = obj; } else prototype = obj; diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index cc27f10..c4381d3 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -321,10 +321,10 @@ public class Values { if (isObject(obj)) return object(obj).getPrototype(ctx); if (ctx == null) return null; - if (obj instanceof String) return ctx.env.proto("string"); - else if (obj instanceof Number) return ctx.env.proto("number"); - else if (obj instanceof Boolean) return ctx.env.proto("bool"); - else if (obj instanceof Symbol) return ctx.env.proto("symbol"); + if (obj instanceof String) return ctx.environment().proto("string"); + else if (obj instanceof Number) return ctx.environment().proto("number"); + else if (obj instanceof Boolean) return ctx.environment().proto("bool"); + else if (obj instanceof Symbol) return ctx.environment().proto("symbol"); return null; } @@ -446,7 +446,7 @@ public class Values { if (val instanceof Class) { if (ctx == null) return null; - else return ctx.env.wrappersProvider.getConstr((Class)val); + else return ctx.environment().wrappers.getConstr((Class)val); } return new NativeWrapper(val); @@ -520,7 +520,7 @@ public class Values { public static Iterable toJavaIterable(Context ctx, Object obj) { return () -> { try { - var symbol = ctx.env.symbol("Symbol.iterator"); + var symbol = ctx.environment().symbol("Symbol.iterator"); var iteratorFunc = getMember(ctx, obj, symbol); if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); @@ -590,7 +590,7 @@ public class Values { var res = new ObjectValue(); try { - var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator"); + var key = getMember(ctx, getMember(ctx, ctx.environment().proto("symbol"), "constructor"), "iterator"); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); } catch (IllegalArgumentException | NullPointerException e) { } diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index f14078c..13f0314 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -19,7 +20,8 @@ public class AsyncFunctionLib extends FunctionValue { private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException { Object res = null; - ctx.message.pushFrame(ctx, frame); + StackData.pushFrame(ctx, frame); + ctx.pushEnv(frame.function.environment); awaiting = false; while (!awaiting) { @@ -37,7 +39,7 @@ public class AsyncFunctionLib extends FunctionValue { } } - ctx.message.popFrame(frame); + StackData.popFrame(ctx, frame); if (awaiting) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java index 81fcd23..99df988 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib; import java.util.Map; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -34,7 +35,7 @@ public class AsyncGeneratorLib extends FunctionValue { } Object res = null; - ctx.message.pushFrame(ctx, frame); + StackData.pushFrame(ctx, frame); state = 0; while (state == 0) { @@ -55,7 +56,7 @@ public class AsyncGeneratorLib extends FunctionValue { } } - ctx.message.popFrame(frame); + StackData.popFrame(ctx, frame); if (state == 1) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index ffa5f73..ebe27bd 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -2,6 +2,7 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; @@ -52,7 +53,7 @@ public class ErrorLib { var target = new ObjectValue(); if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; - target.defineProperty(ctx, "stack", new ArrayValue(ctx, ctx.message.stackTrace().toArray())); + target.defineProperty(ctx, "stack", new ArrayValue(ctx, StackData.stackTrace(ctx))); target.defineProperty(ctx, "name", "Error"); if (message == null) target.defineProperty(ctx, "message", ""); else target.defineProperty(ctx, "message", Values.toString(ctx, message)); diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index 109491e..35c3e4d 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -30,7 +31,7 @@ public class GeneratorLib extends FunctionValue { } Object res = null; - ctx.message.pushFrame(ctx, frame); + StackData.pushFrame(ctx, frame); yielding = false; while (!yielding) { @@ -48,7 +49,7 @@ public class GeneratorLib extends FunctionValue { } } - ctx.message.popFrame(frame); + StackData.popFrame(ctx, frame); if (done) frame = null; else res = frame.pop(); diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index e09d58e..631c2e1 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -31,12 +31,12 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.message.engine.pushMsg(false, ctx.message, func, null, args); + ctx.engine.pushMsg(false, ctx, func, null, args); }); thread.start(); - int i = ctx.env.data.increase(I, 1, 0); - var threads = ctx.env.data.add(THREADS, new HashMap<>()); + int i = ctx.environment().data.increase(I, 1, 0); + var threads = ctx.environment().data.get(THREADS, new HashMap<>()); threads.put(++i, thread); return i; } @@ -51,19 +51,19 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.message.engine.pushMsg(false, ctx.message, func, null, args); + ctx.engine.pushMsg(false, ctx, func, null, args); } }); thread.start(); - int i = ctx.env.data.increase(I, 1, 0); - var threads = ctx.env.data.add(THREADS, new HashMap<>()); + int i = ctx.environment().data.increase(I, 1, 0); + var threads = ctx.environment().data.get(THREADS, new HashMap<>()); threads.put(++i, thread); return i; } @Native public static void clearTimeout(Context ctx, int i) { - var threads = ctx.env.data.add(THREADS, new HashMap<>()); + var threads = ctx.environment().data.get(THREADS, new HashMap<>()); var thread = threads.remove(i); if (thread != null) thread.interrupt(); @@ -80,7 +80,7 @@ public class Internals { } public void apply(Environment env) { - var wp = env.wrappersProvider; + var wp = env.wrappers; var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class)); glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 35f9c6b..8b0b264 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -194,7 +194,7 @@ public class ObjectLib { return thisArg; } @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { - var name = Values.getMember(ctx, thisArg, ctx.env.symbol("Symbol.typeName")); + var name = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.typeName")); if (name == null) name = "Unknown"; else name = Values.toString(ctx, name); diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index bd393e8..f0a4ed3 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -6,7 +6,6 @@ import java.util.Map; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.Message; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -263,7 +262,7 @@ public class PromiseLib { else if (state == STATE_REJECTED) { for (var handle : handles) handle.rejected.call(handle.ctx, null, val); if (handles.size() == 0) { - ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> { + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { if (!handled) { try { Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); } catch (InterruptedException ex) { } @@ -297,9 +296,9 @@ public class PromiseLib { } private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { - if (state == STATE_FULFILLED) ctx.message.engine.pushMsg(true, new Message(ctx.message.engine), fulfill, null, val); + if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx, fulfill, null, val); else if (state == STATE_REJECTED) { - ctx.message.engine.pushMsg(true, new Message(ctx.message.engine), reject, null, val); + ctx.engine.pushMsg(true, ctx, reject, null, val); handled = true; } else handles.add(new Handle(ctx, fulfill, reject)); diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index 9621487..d491045 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -76,7 +76,7 @@ public class StringLib { var val = passThis(ctx, "indexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); + var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search")); if (search instanceof FunctionValue) { return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start)); } @@ -88,7 +88,7 @@ public class StringLib { var val = passThis(ctx, "lastIndexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var search = Values.getMember(ctx, term, ctx.env.symbol("Symbol.search")); + var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search")); if (search instanceof FunctionValue) { return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos)); } @@ -105,7 +105,7 @@ public class StringLib { var val = passThis(ctx, "replace", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); + var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace")); if (replace instanceof FunctionValue) { return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); } @@ -117,7 +117,7 @@ public class StringLib { var val = passThis(ctx, "replaceAll", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); + var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace")); if (replace instanceof FunctionValue) { return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); } @@ -132,11 +132,11 @@ public class StringLib { FunctionValue match; try { - var _match = Values.getMember(ctx, term, ctx.env.symbol("Symbol.match")); + var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else if (ctx.env.regexConstructor != null) { - var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), ""); - _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.match")); + else if (ctx.environment().regexConstructor != null) { + var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), ""); + _match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); } @@ -154,14 +154,14 @@ public class StringLib { FunctionValue match = null; try { - var _match = Values.getMember(ctx, term, ctx.env.symbol("Symbol.matchAll")); + var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; } catch (IllegalArgumentException e) { } - if (match == null && ctx.env.regexConstructor != null) { - var regex = Values.callNew(ctx, ctx.env.regexConstructor, Values.toString(ctx, term), "g"); - var _match = Values.getMember(ctx, regex, ctx.env.symbol("Symbol.matchAll")); + if (match == null && ctx.environment().regexConstructor != null) { + var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), "g"); + var _match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); } @@ -176,7 +176,7 @@ public class StringLib { if (lim != null) lim = Values.toNumber(ctx, lim); if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, ctx.env.symbol("Symbol.replace")); + var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace")); if (replace instanceof FunctionValue) { var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible); diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index 8edb69f..8c76cce 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -18,14 +18,14 @@ import me.topchetoeu.jscript.interop.NativeInit; public class SymbolLib { private static final Map symbols = new HashMap<>(); - @NativeGetter public static Symbol typeName(Context ctx) { return ctx.env.symbol("Symbol.typeName"); } - @NativeGetter public static Symbol replace(Context ctx) { return ctx.env.symbol("Symbol.replace"); } - @NativeGetter public static Symbol match(Context ctx) { return ctx.env.symbol("Symbol.match"); } - @NativeGetter public static Symbol matchAll(Context ctx) { return ctx.env.symbol("Symbol.matchAll"); } - @NativeGetter public static Symbol split(Context ctx) { return ctx.env.symbol("Symbol.split"); } - @NativeGetter public static Symbol search(Context ctx) { return ctx.env.symbol("Symbol.search"); } - @NativeGetter public static Symbol iterator(Context ctx) { return ctx.env.symbol("Symbol.iterator"); } - @NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.env.symbol("Symbol.asyncIterator"); } + @NativeGetter public static Symbol typeName(Context ctx) { return ctx.environment().symbol("Symbol.typeName"); } + @NativeGetter public static Symbol replace(Context ctx) { return ctx.environment().symbol("Symbol.replace"); } + @NativeGetter public static Symbol match(Context ctx) { return ctx.environment().symbol("Symbol.match"); } + @NativeGetter public static Symbol matchAll(Context ctx) { return ctx.environment().symbol("Symbol.matchAll"); } + @NativeGetter public static Symbol split(Context ctx) { return ctx.environment().symbol("Symbol.split"); } + @NativeGetter public static Symbol search(Context ctx) { return ctx.environment().symbol("Symbol.search"); } + @NativeGetter public static Symbol iterator(Context ctx) { return ctx.environment().symbol("Symbol.iterator"); } + @NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.environment().symbol("Symbol.asyncIterator"); } public final Symbol value; -- 2.45.2 From cc044374ba8adf23b21bb164c4d409023b852e59 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:42:55 +0300 Subject: [PATCH 2/7] refactor: replace InterruptedException with unchecked alternative --- src/me/topchetoeu/jscript/Main.java | 21 +--- src/me/topchetoeu/jscript/engine/Context.java | 2 +- src/me/topchetoeu/jscript/engine/Engine.java | 8 +- .../topchetoeu/jscript/engine/StackData.java | 2 +- .../jscript/engine/frame/CodeFrame.java | 9 +- .../jscript/engine/frame/Runners.java | 46 ++++---- .../jscript/engine/scope/GlobalScope.java | 14 +-- .../engine/scope/LocalScopeRecord.java | 2 +- .../jscript/engine/scope/Variable.java | 4 +- .../jscript/engine/values/ArrayValue.java | 8 +- .../jscript/engine/values/CodeFunction.java | 2 +- .../jscript/engine/values/FunctionValue.java | 10 +- .../jscript/engine/values/NativeFunction.java | 4 +- .../jscript/engine/values/NativeWrapper.java | 2 +- .../jscript/engine/values/ObjectValue.java | 26 ++--- .../jscript/engine/values/Values.java | 105 ++++++++---------- .../jscript/events/DataNotifier.java | 2 +- .../topchetoeu/jscript/events/Notifier.java | 11 +- .../jscript/exceptions/EngineException.java | 2 +- .../exceptions/InterruptException.java | 8 ++ .../topchetoeu/jscript/interop/Overload.java | 5 +- .../jscript/interop/OverloadFunction.java | 5 +- src/me/topchetoeu/jscript/lib/ArrayLib.java | 91 +++++++-------- .../jscript/lib/AsyncFunctionLib.java | 8 +- .../jscript/lib/AsyncGeneratorLib.java | 14 +-- src/me/topchetoeu/jscript/lib/DateLib.java | 34 +++--- src/me/topchetoeu/jscript/lib/ErrorLib.java | 6 +- .../topchetoeu/jscript/lib/FunctionLib.java | 4 +- .../topchetoeu/jscript/lib/GeneratorLib.java | 10 +- src/me/topchetoeu/jscript/lib/Internals.java | 6 +- src/me/topchetoeu/jscript/lib/JSONLib.java | 6 +- src/me/topchetoeu/jscript/lib/MapLib.java | 12 +- src/me/topchetoeu/jscript/lib/NumberLib.java | 10 +- src/me/topchetoeu/jscript/lib/ObjectLib.java | 48 ++++---- src/me/topchetoeu/jscript/lib/PromiseLib.java | 31 +++--- .../topchetoeu/jscript/lib/RangeErrorLib.java | 2 +- src/me/topchetoeu/jscript/lib/RegExpLib.java | 14 +-- src/me/topchetoeu/jscript/lib/SetLib.java | 12 +- src/me/topchetoeu/jscript/lib/StringLib.java | 48 ++++---- src/me/topchetoeu/jscript/lib/SymbolLib.java | 8 +- .../jscript/lib/SyntaxErrorLib.java | 2 +- .../topchetoeu/jscript/lib/TypeErrorLib.java | 2 +- 42 files changed, 317 insertions(+), 349 deletions(-) create mode 100644 src/me/topchetoeu/jscript/exceptions/InterruptException.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 021ad62..80872d7 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -14,6 +14,7 @@ import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.lib.Internals; @@ -46,14 +47,12 @@ public class Main { private static Observer valuePrinter = new Observer() { public void next(Object data) { - try { Values.printValue(null, data); } - catch (InterruptedException e) { } + Values.printValue(null, data); System.out.println(); } public void error(RuntimeException err) { - try { Values.printError(err, null); } - catch (InterruptedException ex) { return; } + Values.printError(err, null); } }; @@ -70,8 +69,7 @@ public class Main { env.global.define("exit", _ctx -> { exited[0] = true; - task.interrupt(); - throw new InterruptedException(); + throw new InterruptException(); }); env.global.define("go", _ctx -> { try { @@ -96,14 +94,7 @@ public class Main { if (raw == null) break; engine.pushMsg(false, new Context(engine).pushEnv(env), "", raw, null).toObservable().once(valuePrinter); } - catch (EngineException e) { - try { - System.out.println("Uncaught " + e.toString(null)); - } - catch (EngineException ex) { - System.out.println("Uncaught [error while converting to string]"); - } - } + catch (EngineException e) { Values.printError(e, ""); } } } catch (IOException e) { @@ -114,12 +105,12 @@ public class Main { if (exited[0]) return; System.out.println("Syntax error:" + ex.msg); } + catch (InterruptException e) { return; } catch (RuntimeException ex) { if (exited[0]) return; System.out.println("Internal error ocurred:"); ex.printStackTrace(); } - catch (InterruptedException e) { return; } if (exited[0]) return; }); reader.setDaemon(true); diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 92ffb1d..366b0bc 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -23,7 +23,7 @@ public class Context { if (!env.empty()) this.env.pop(); } - public FunctionValue compile(String filename, String raw) throws InterruptedException { + public FunctionValue compile(String filename, String raw) { var res = Values.toString(this, environment().compile.call(this, null, raw, filename)); return Parsing.compile(engine.functions, environment(), filename, res); } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 9c59378..c82dbde 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -16,7 +16,7 @@ public class Engine { private FunctionValue compiled = null; @Override - public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object call(Context ctx, Object thisArg, Object ...args) { if (compiled == null) compiled = ctx.compile(filename, raw); return compiled.call(ctx, thisArg, args); } @@ -53,14 +53,10 @@ public class Engine { public final HashMap functions = new HashMap<>(); public final Data data = new Data().set(StackData.MAX_FRAMES, 10000); - private void runTask(Task task) throws InterruptedException { + private void runTask(Task task) { try { task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); } - catch (InterruptedException e) { - task.notifier.error(new RuntimeException(e)); - throw e; - } catch (EngineException e) { task.notifier.error(e); } diff --git a/src/me/topchetoeu/jscript/engine/StackData.java b/src/me/topchetoeu/jscript/engine/StackData.java index 9bb0fab..873132e 100644 --- a/src/me/topchetoeu/jscript/engine/StackData.java +++ b/src/me/topchetoeu/jscript/engine/StackData.java @@ -13,7 +13,7 @@ public class StackData { public static final DataKey MAX_FRAMES = new DataKey<>(); public static final DataKey DEBUGGER = new DataKey<>(); - public static void pushFrame(Context ctx, CodeFrame frame) throws InterruptedException { + public static void pushFrame(Context ctx, CodeFrame frame) { var frames = ctx.data.get(FRAMES, new ArrayList<>()); frames.add(frame); if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) throw EngineException.ofRange("Stack overflow!"); diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 84c12b1..f9b6ae4 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -11,6 +11,7 @@ import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.InterruptException; public class CodeFrame { private class TryCtx { @@ -93,14 +94,14 @@ public class CodeFrame { stack[stackPtr++] = Values.normalize(ctx, val); } - private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException { + private void setCause(Context ctx, EngineException err, EngineException cause) { if (err.value instanceof ObjectValue) { Values.setMember(ctx, err, ctx.environment().symbol("Symbol.cause"), cause); } err.cause = cause; } - private Object nextNoTry(Context ctx) throws InterruptedException { - if (Thread.currentThread().isInterrupted()) throw new InterruptedException(); + private Object nextNoTry(Context ctx) { + if (Thread.currentThread().isInterrupted()) throw new InterruptException(); if (codePtr < 0 || codePtr >= function.body.length) return null; var instr = function.body[codePtr]; @@ -117,7 +118,7 @@ public class CodeFrame { } } - public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException { + public Object next(Context ctx, Object value, Object returnValue, EngineException error) { if (value != Runners.NO_RETURN) push(ctx, value); if (returnValue == Runners.NO_RETURN && error == null) { diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index f25345a..4fac093 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -22,15 +22,15 @@ public class Runners { public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) { throw new EngineException(frame.pop()); } - public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) { throw EngineException.ofSyntax((String)instr.get(0)); } - private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { + 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) throws InterruptedException { + public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) { var callArgs = frame.take(instr.get(0)); var func = frame.pop(); var thisArg = frame.pop(); @@ -40,7 +40,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) { var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); @@ -61,13 +61,13 @@ public class Runners { return NO_RETURN; } - public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) { var name = (String)instr.get(0); ctx.environment().global.define(name); frame.codePtr++; return NO_RETURN; } - public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) { var setter = frame.pop(); var getter = frame.pop(); var name = frame.pop(); @@ -82,7 +82,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) { var type = frame.pop(); var obj = frame.pop(); @@ -97,7 +97,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) { var val = frame.pop(); var arr = new ObjectValue(); @@ -117,7 +117,7 @@ public class Runners { return NO_RETURN; } - public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) { frame.addTry(instr.get(0), instr.get(1), instr.get(2)); frame.codePtr++; return NO_RETURN; @@ -155,7 +155,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) { var i = instr.get(0); if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i)); @@ -199,7 +199,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) { var key = frame.pop(); var obj = frame.pop(); @@ -212,11 +212,11 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, instr.get(0)); return execLoadMember(ctx, instr, frame); } - public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.codePtr++; return NO_RETURN; @@ -227,7 +227,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) { var val = frame.pop(); var key = frame.pop(); var obj = frame.pop(); @@ -237,7 +237,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var i = instr.get(0); @@ -258,7 +258,7 @@ public class Runners { frame.jumpFlag = true; return NO_RETURN; } - public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) { if (Values.toBoolean(frame.pop())) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; @@ -266,7 +266,7 @@ public class Runners { else frame.codePtr ++; return NO_RETURN; } - public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) { if (Values.not(frame.pop())) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; @@ -275,7 +275,7 @@ public class Runners { return NO_RETURN; } - public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) { var obj = frame.pop(); var index = frame.pop(); @@ -283,7 +283,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) { String name = instr.get(0); Object obj; @@ -300,7 +300,7 @@ public class Runners { frame.codePtr++; return NO_RETURN; } - public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) { if (instr.is(0, "dbg_names")) { var names = new String[instr.params.length - 1]; for (var i = 0; i < instr.params.length - 1; i++) { @@ -314,7 +314,7 @@ public class Runners { return NO_RETURN; } - public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) { var key = frame.pop(); var val = frame.pop(); @@ -324,7 +324,7 @@ public class Runners { return NO_RETURN; } - public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) { Operation op = instr.get(0); var args = new Object[op.operands]; @@ -335,7 +335,7 @@ public class Runners { return NO_RETURN; } - public static Object exec(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { + public static Object exec(Context ctx, Instruction instr, CodeFrame frame) { switch (instr.type) { case NOP: return execNop(ctx, instr, frame); case RETURN: return execReturn(ctx, instr, frame); diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index e447f2a..62027d8 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -15,7 +15,7 @@ public class GlobalScope implements ScopeRecord { @Override public GlobalScope parent() { return null; } - public boolean has(Context ctx, String name) throws InterruptedException { + public boolean has(Context ctx, String name) { return obj.hasMember(ctx, name, false); } public Object getKey(String name) { @@ -32,13 +32,7 @@ public class GlobalScope implements ScopeRecord { } public Object define(String name) { - try { - if (obj.hasMember(null, name, true)) return name; - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return name; - } + if (obj.hasMember(null, name, true)) return name; obj.defineProperty(null, name, null); return name; } @@ -59,11 +53,11 @@ public class GlobalScope implements ScopeRecord { define(null, val.name, readonly, val); } - public Object get(Context ctx, String name) throws InterruptedException { + public Object get(Context ctx, String name) { if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); else return obj.getMember(ctx, name); } - public void set(Context ctx, String name, Object val) throws InterruptedException { + public void set(Context ctx, String name, Object val) { if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java index 7d5cf12..12b7d8c 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java @@ -59,7 +59,7 @@ public class LocalScopeRecord implements ScopeRecord { return name; } - public boolean has(Context ctx, String name) throws InterruptedException { + public boolean has(Context ctx, String name) { return global.has(ctx, name) || locals.contains(name) || diff --git a/src/me/topchetoeu/jscript/engine/scope/Variable.java b/src/me/topchetoeu/jscript/engine/scope/Variable.java index 6bd3394..8715bd0 100644 --- a/src/me/topchetoeu/jscript/engine/scope/Variable.java +++ b/src/me/topchetoeu/jscript/engine/scope/Variable.java @@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.scope; import me.topchetoeu.jscript.engine.Context; public interface Variable { - Object get(Context ctx) throws InterruptedException; + Object get(Context ctx); default boolean readonly() { return true; } - default void set(Context ctx, Object val) throws InterruptedException { } + default void set(Context ctx, Object val) { } } diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java index 006c8d4..3a19e79 100644 --- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java @@ -122,7 +122,7 @@ public class ArrayValue extends ObjectValue implements Iterable { } @Override - protected Object getField(Context ctx, Object key) throws InterruptedException { + protected Object getField(Context ctx, Object key) { if (key instanceof Number) { var i = ((Number)key).doubleValue(); if (i >= 0 && i - Math.floor(i) == 0) { @@ -133,7 +133,7 @@ public class ArrayValue extends ObjectValue implements Iterable { return super.getField(ctx, key); } @Override - protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { + protected boolean setField(Context ctx, Object key, Object val) { if (key instanceof Number) { var i = Values.number(key); if (i >= 0 && i - Math.floor(i) == 0) { @@ -145,7 +145,7 @@ public class ArrayValue extends ObjectValue implements Iterable { return super.setField(ctx, key, val); } @Override - protected boolean hasField(Context ctx, Object key) throws InterruptedException { + protected boolean hasField(Context ctx, Object key) { if (key instanceof Number) { var i = Values.number(key); if (i >= 0 && i - Math.floor(i) == 0) { @@ -156,7 +156,7 @@ public class ArrayValue extends ObjectValue implements Iterable { return super.hasField(ctx, key); } @Override - protected void deleteField(Context ctx, Object key) throws InterruptedException { + protected void deleteField(Context ctx, Object key) { if (key instanceof Number) { var i = Values.number(key); if (i >= 0 && i - Math.floor(i) == 0) { diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index 1333cf2..8b1b924 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -30,7 +30,7 @@ public class CodeFunction extends FunctionValue { } @Override - public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object call(Context ctx, Object thisArg, Object ...args) { var frame = new CodeFrame(ctx, thisArg, args, this); try { StackData.pushFrame(ctx, frame); diff --git a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java index 612f9b8..865a381 100644 --- a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java +++ b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java @@ -14,26 +14,26 @@ public abstract class FunctionValue extends ObjectValue { return "function(...) { ...}"; } - public abstract Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException; - public Object call(Context ctx) throws InterruptedException { + public abstract Object call(Context ctx, Object thisArg, Object ...args); + public Object call(Context ctx) { return call(ctx, null); } @Override - protected Object getField(Context ctx, Object key) throws InterruptedException { + protected Object getField(Context ctx, Object key) { if (key.equals("name")) return name; if (key.equals("length")) return length; return super.getField(ctx, key); } @Override - protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { + protected boolean setField(Context ctx, Object key, Object val) { if (key.equals("name")) name = Values.toString(ctx, val); else if (key.equals("length")) length = (int)Values.toNumber(ctx, val); else return super.setField(ctx, key, val); return true; } @Override - protected boolean hasField(Context ctx, Object key) throws InterruptedException { + protected boolean hasField(Context ctx, Object key) { if (key.equals("name")) return true; if (key.equals("length")) return true; return super.hasField(ctx, key); diff --git a/src/me/topchetoeu/jscript/engine/values/NativeFunction.java b/src/me/topchetoeu/jscript/engine/values/NativeFunction.java index 2056717..c9b3def 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeFunction.java @@ -4,13 +4,13 @@ import me.topchetoeu.jscript.engine.Context; public class NativeFunction extends FunctionValue { public static interface NativeFunctionRunner { - Object run(Context ctx, Object thisArg, Object[] args) throws InterruptedException; + Object run(Context ctx, Object thisArg, Object[] args); } public final NativeFunctionRunner action; @Override - public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object call(Context ctx, Object thisArg, Object ...args) { return action.run(ctx, thisArg, args); } diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index 5501129..49d785e 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -7,7 +7,7 @@ public class NativeWrapper extends ObjectValue { public final Object wrapped; @Override - public ObjectValue getPrototype(Context ctx) throws InterruptedException { + public ObjectValue getPrototype(Context ctx) { if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass()); else return super.getPrototype(ctx); } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index e3de9d2..2f21e3f 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -145,7 +145,7 @@ public class ObjectValue { return true; } - public ObjectValue getPrototype(Context ctx) throws InterruptedException { + public ObjectValue getPrototype(Context ctx) { try { if (prototype == OBJ_PROTO) return ctx.environment().proto("object"); if (prototype == ARR_PROTO) return ctx.environment().proto("array"); @@ -203,19 +203,19 @@ public class ObjectValue { return true; } - protected Property getProperty(Context ctx, Object key) throws InterruptedException { + protected Property getProperty(Context ctx, Object key) { if (properties.containsKey(key)) return properties.get(key); var proto = getPrototype(ctx); if (proto != null) return proto.getProperty(ctx, key); else return null; } - protected Object getField(Context ctx, Object key) throws InterruptedException { + protected Object getField(Context ctx, Object key) { if (values.containsKey(key)) return values.get(key); var proto = getPrototype(ctx); if (proto != null) return proto.getField(ctx, key); else return null; } - protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { + protected boolean setField(Context ctx, Object key, Object val) { if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { ((FunctionValue)val).name = Values.toString(ctx, key); } @@ -223,14 +223,14 @@ public class ObjectValue { values.put(key, val); return true; } - protected void deleteField(Context ctx, Object key) throws InterruptedException { + protected void deleteField(Context ctx, Object key) { values.remove(key); } - protected boolean hasField(Context ctx, Object key) throws InterruptedException { + protected boolean hasField(Context ctx, Object key) { return values.containsKey(key); } - public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException { + public final Object getMember(Context ctx, Object key, Object thisArg) { key = Values.normalize(ctx, key); if ("__proto__".equals(key)) { @@ -246,11 +246,11 @@ public class ObjectValue { } else return getField(ctx, key); } - public final Object getMember(Context ctx, Object key) throws InterruptedException { + public final Object getMember(Context ctx, Object key) { return getMember(ctx, key, this); } - public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException { + public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) { key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); var prop = getProperty(ctx, key); @@ -269,11 +269,11 @@ public class ObjectValue { else if (nonWritableSet.contains(key)) return false; else return setField(ctx, key, val); } - public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) throws InterruptedException { + public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) { return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps); } - public final boolean hasMember(Context ctx, Object key, boolean own) throws InterruptedException { + public final boolean hasMember(Context ctx, Object key, boolean own) { key = Values.normalize(ctx, key); if (key != null && key.equals("__proto__")) return true; @@ -283,7 +283,7 @@ public class ObjectValue { var proto = getPrototype(ctx); return proto != null && proto.hasMember(ctx, key, own); } - public final boolean deleteMember(Context ctx, Object key) throws InterruptedException { + public final boolean deleteMember(Context ctx, Object key) { key = Values.normalize(ctx, key); if (!memberConfigurable(key)) return false; @@ -294,7 +294,7 @@ public class ObjectValue { return true; } - public final ObjectValue getMemberDescriptor(Context ctx, Object key) throws InterruptedException { + public final ObjectValue getMemberDescriptor(Context ctx, Object key) { key = Values.normalize(ctx, key); var prop = properties.get(key); diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index c4381d3..ec261d5 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -15,6 +15,7 @@ import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.frame.ConvertHint; import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; public class Values { @@ -67,7 +68,7 @@ public class Values { return "object"; } - private static Object tryCallConvertFunc(Context ctx, Object obj, String name) throws InterruptedException { + private static Object tryCallConvertFunc(Context ctx, Object obj, String name) { var func = getMember(ctx, obj, name); if (func != null) { @@ -88,7 +89,7 @@ public class Values { obj == NULL; } - public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) throws InterruptedException { + public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) { obj = normalize(ctx, obj); if (isPrimitive(obj)) return obj; @@ -113,7 +114,7 @@ public class Values { if (obj instanceof Boolean) return (Boolean)obj; return true; } - public static double toNumber(Context ctx, Object obj) throws InterruptedException { + public static double toNumber(Context ctx, Object obj) { var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF); if (val instanceof Number) return number(val); @@ -126,7 +127,7 @@ public class Values { } return Double.NaN; } - public static String toString(Context ctx, Object obj) throws InterruptedException { + public static String toString(Context ctx, Object obj) { var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF); if (val == null) return "undefined"; @@ -146,47 +147,47 @@ public class Values { return "Unknown value"; } - public static Object add(Context ctx, Object a, Object b) throws InterruptedException { + public static Object add(Context ctx, Object a, Object b) { if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b); else return toNumber(ctx, a) + toNumber(ctx, b); } - public static double subtract(Context ctx, Object a, Object b) throws InterruptedException { + public static double subtract(Context ctx, Object a, Object b) { return toNumber(ctx, a) - toNumber(ctx, b); } - public static double multiply(Context ctx, Object a, Object b) throws InterruptedException { + public static double multiply(Context ctx, Object a, Object b) { return toNumber(ctx, a) * toNumber(ctx, b); } - public static double divide(Context ctx, Object a, Object b) throws InterruptedException { + public static double divide(Context ctx, Object a, Object b) { return toNumber(ctx, a) / toNumber(ctx, b); } - public static double modulo(Context ctx, Object a, Object b) throws InterruptedException { + public static double modulo(Context ctx, Object a, Object b) { return toNumber(ctx, a) % toNumber(ctx, b); } - public static double negative(Context ctx, Object obj) throws InterruptedException { + public static double negative(Context ctx, Object obj) { return -toNumber(ctx, obj); } - public static int and(Context ctx, Object a, Object b) throws InterruptedException { + public static int and(Context ctx, Object a, Object b) { return (int)toNumber(ctx, a) & (int)toNumber(ctx, b); } - public static int or(Context ctx, Object a, Object b) throws InterruptedException { + public static int or(Context ctx, Object a, Object b) { return (int)toNumber(ctx, a) | (int)toNumber(ctx, b); } - public static int xor(Context ctx, Object a, Object b) throws InterruptedException { + public static int xor(Context ctx, Object a, Object b) { return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b); } - public static int bitwiseNot(Context ctx, Object obj) throws InterruptedException { + public static int bitwiseNot(Context ctx, Object obj) { return ~(int)toNumber(ctx, obj); } - public static int shiftLeft(Context ctx, Object a, Object b) throws InterruptedException { + public static int shiftLeft(Context ctx, Object a, Object b) { return (int)toNumber(ctx, a) << (int)toNumber(ctx, b); } - public static int shiftRight(Context ctx, Object a, Object b) throws InterruptedException { + public static int shiftRight(Context ctx, Object a, Object b) { return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b); } - public static long unsignedShiftRight(Context ctx, Object a, Object b) throws InterruptedException { + public static long unsignedShiftRight(Context ctx, Object a, Object b) { long _a = (long)toNumber(ctx, a); long _b = (long)toNumber(ctx, b); @@ -195,7 +196,7 @@ public class Values { return _a >>> _b; } - public static int compare(Context ctx, Object a, Object b) throws InterruptedException { + public static int compare(Context ctx, Object a, Object b) { a = toPrimitive(ctx, a, ConvertHint.VALUEOF); b = toPrimitive(ctx, b, ConvertHint.VALUEOF); @@ -207,7 +208,7 @@ public class Values { return !toBoolean(obj); } - public static boolean isInstanceOf(Context ctx, Object obj, Object proto) throws InterruptedException { + public static boolean isInstanceOf(Context ctx, Object obj, Object proto) { if (obj == null || obj == NULL || proto == null || proto == NULL) return false; var val = getPrototype(ctx, obj); @@ -219,7 +220,7 @@ public class Values { return false; } - public static Object operation(Context ctx, Operation op, Object ...args) throws InterruptedException { + public static Object operation(Context ctx, Operation op, Object ...args) { switch (op) { case ADD: return add(ctx, args[0], args[1]); case SUBTRACT: return subtract(ctx, args[0], args[1]); @@ -260,7 +261,7 @@ public class Values { } } - public static Object getMember(Context ctx, Object obj, Object key) throws InterruptedException { + public static Object getMember(Context ctx, Object obj, Object key) { obj = normalize(ctx, obj); key = normalize(ctx, key); if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); @@ -280,7 +281,7 @@ public class Values { else if (key != null && key.equals("__proto__")) return proto; else return proto.getMember(ctx, key, obj); } - public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException { + public static boolean setMember(Context ctx, Object obj, Object key, Object val) { obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); @@ -290,7 +291,7 @@ public class Values { var proto = getPrototype(ctx, obj); return proto.setMember(ctx, key, val, obj, true); } - public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) throws InterruptedException { + public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) { if (obj == null || obj == NULL) return false; obj = normalize(ctx, obj); key = normalize(ctx, key); @@ -308,14 +309,14 @@ public class Values { var proto = getPrototype(ctx, obj); return proto != null && proto.hasMember(ctx, key, own); } - public static boolean deleteMember(Context ctx, Object obj, Object key) throws InterruptedException { + public static boolean deleteMember(Context ctx, Object obj, Object key) { if (obj == null || obj == NULL) return false; obj = normalize(ctx, obj); key = normalize(ctx, key); if (isObject(obj)) return object(obj).deleteMember(ctx, key); else return false; } - public static ObjectValue getPrototype(Context ctx, Object obj) throws InterruptedException { + public static ObjectValue getPrototype(Context ctx, Object obj) { if (obj == null || obj == NULL) return null; obj = normalize(ctx, obj); if (isObject(obj)) return object(obj).getPrototype(ctx); @@ -328,11 +329,11 @@ public class Values { return null; } - public static boolean setPrototype(Context ctx, Object obj, Object proto) throws InterruptedException { + public static boolean setPrototype(Context ctx, Object obj, Object proto) { obj = normalize(ctx, obj); return isObject(obj) && object(obj).setPrototype(ctx, proto); } - public static List getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException { + public static List getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) { List res = new ArrayList<>(); if (isObject(obj)) res = object(obj).keys(includeNonEnumerable); @@ -352,7 +353,7 @@ public class Values { return res; } - public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException { + public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) { if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key); else if (obj instanceof String && key instanceof Number) { var i = ((Number)key).intValue(); @@ -370,11 +371,11 @@ public class Values { else return null; } - public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { + public static Object call(Context ctx, Object func, Object thisArg, Object ...args) { if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); return function(func).call(ctx, thisArg, args); } - public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException { + public static Object callNew(Context ctx, Object func, Object ...args) { var res = new ObjectValue(); var proto = Values.getMember(ctx, func, "prototype"); res.setPrototype(ctx, proto); @@ -395,7 +396,7 @@ public class Values { return a == b || a.equals(b); } - public static boolean looseEqual(Context ctx, Object a, Object b) throws InterruptedException { + public static boolean looseEqual(Context ctx, Object a, Object b) { a = normalize(ctx, a); b = normalize(ctx, b); // In loose equality, null is equivalent to undefined @@ -453,7 +454,7 @@ public class Values { } @SuppressWarnings("unchecked") - public static T convert(Context ctx, Object obj, Class clazz) throws InterruptedException { + public static T convert(Context ctx, Object obj, Class clazz) { if (clazz == Void.class) return null; if (obj instanceof NativeWrapper) { @@ -536,7 +537,7 @@ public class Values { public boolean consumed = true; private FunctionValue next = (FunctionValue)nextFunc; - private void loadNext() throws InterruptedException { + private void loadNext() { if (next == null) value = null; else if (consumed) { var curr = object(next.call(ctx, iterator)); @@ -551,32 +552,20 @@ public class Values { @Override public boolean hasNext() { - try { - loadNext(); - return next != null; - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return false; - } + loadNext(); + return next != null; } @Override public Object next() { - try { - loadNext(); - var res = value; - value = null; - consumed = true; - return res; - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return null; - } + loadNext(); + var res = value; + value = null; + consumed = true; + return res; } }; } - catch (InterruptedException e) { + catch (InterruptException e) { Thread.currentThread().interrupt(); return null; } @@ -586,7 +575,7 @@ public class Values { }; } - public static ObjectValue fromJavaIterator(Context ctx, Iterator it) throws InterruptedException { + public static ObjectValue fromJavaIterator(Context ctx, Iterator it) { var res = new ObjectValue(); try { @@ -603,11 +592,11 @@ public class Values { return res; } - public static ObjectValue fromJavaIterable(Context ctx, Iterable it) throws InterruptedException { + public static ObjectValue fromJavaIterable(Context ctx, Iterable it) { return fromJavaIterator(ctx, it.iterator()); } - private static void printValue(Context ctx, Object val, HashSet passed, int tab) throws InterruptedException { + private static void printValue(Context ctx, Object val, HashSet passed, int tab) { if (passed.contains(val)) { System.out.print("[circular]"); return; @@ -679,10 +668,10 @@ public class Values { else if (val instanceof String) System.out.print("'" + val + "'"); else System.out.print(Values.toString(ctx, val)); } - public static void printValue(Context ctx, Object val) throws InterruptedException { + public static void printValue(Context ctx, Object val) { printValue(ctx, val, new HashSet<>(), 0); } - public static void printError(RuntimeException err, String prefix) throws InterruptedException { + public static void printError(RuntimeException err, String prefix) { prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; try { if (err instanceof EngineException) { diff --git a/src/me/topchetoeu/jscript/events/DataNotifier.java b/src/me/topchetoeu/jscript/events/DataNotifier.java index e52d16e..cfcff15 100644 --- a/src/me/topchetoeu/jscript/events/DataNotifier.java +++ b/src/me/topchetoeu/jscript/events/DataNotifier.java @@ -19,7 +19,7 @@ public class DataNotifier implements Awaitable { isErr = false; notifier.next(); } - public T await() throws InterruptedException { + public T await() { notifier.await(); try { diff --git a/src/me/topchetoeu/jscript/events/Notifier.java b/src/me/topchetoeu/jscript/events/Notifier.java index 36e45b9..96dbee8 100644 --- a/src/me/topchetoeu/jscript/events/Notifier.java +++ b/src/me/topchetoeu/jscript/events/Notifier.java @@ -1,5 +1,7 @@ package me.topchetoeu.jscript.events; +import me.topchetoeu.jscript.exceptions.InterruptException; + public class Notifier { private boolean ok = false; @@ -7,8 +9,11 @@ public class Notifier { ok = true; notifyAll(); } - public synchronized void await() throws InterruptedException { - while (!ok) wait(); - ok = false; + public synchronized void await() { + try { + while (!ok) wait(); + ok = false; + } + catch (InterruptedException e) { throw new InterruptException(e); } } } diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index fab5276..90444e1 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -33,7 +33,7 @@ public class EngineException extends RuntimeException { return this; } - public String toString(Context ctx) throws InterruptedException { + public String toString(Context ctx) { var ss = new StringBuilder(); try { ss.append(Values.toString(ctx, value)).append('\n'); diff --git a/src/me/topchetoeu/jscript/exceptions/InterruptException.java b/src/me/topchetoeu/jscript/exceptions/InterruptException.java new file mode 100644 index 0000000..ae9e2e8 --- /dev/null +++ b/src/me/topchetoeu/jscript/exceptions/InterruptException.java @@ -0,0 +1,8 @@ +package me.topchetoeu.jscript.exceptions; + +public class InterruptException extends RuntimeException { + public InterruptException() { } + public InterruptException(InterruptedException e) { + super(e); + } +} diff --git a/src/me/topchetoeu/jscript/interop/Overload.java b/src/me/topchetoeu/jscript/interop/Overload.java index a4548e1..0da14c1 100644 --- a/src/me/topchetoeu/jscript/interop/Overload.java +++ b/src/me/topchetoeu/jscript/interop/Overload.java @@ -9,10 +9,7 @@ import me.topchetoeu.jscript.engine.Context; public class Overload { public static interface OverloadRunner { - Object run(Context ctx, Object thisArg, Object[] args) throws - InterruptedException, - ReflectiveOperationException, - IllegalArgumentException; + Object run(Context ctx, Object thisArg, Object[] args) throws ReflectiveOperationException, IllegalArgumentException; } public final OverloadRunner runner; diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 5f54ceb..87eebd0 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -15,7 +15,7 @@ import me.topchetoeu.jscript.exceptions.EngineException; public class OverloadFunction extends FunctionValue { public final List overloads = new ArrayList<>(); - public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object call(Context ctx, Object thisArg, Object ...args) { loop: for (var overload : overloads) { Object[] newArgs = new Object[overload.params.length]; @@ -97,9 +97,6 @@ public class OverloadFunction extends FunctionValue { catch (ReflectiveOperationException e) { throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "")); } - catch (Exception e) { - throw e; - } } throw EngineException.ofType("No overload found for native method."); diff --git a/src/me/topchetoeu/jscript/lib/ArrayLib.java b/src/me/topchetoeu/jscript/lib/ArrayLib.java index fc5fcf1..1d2bd65 100644 --- a/src/me/topchetoeu/jscript/lib/ArrayLib.java +++ b/src/me/topchetoeu/jscript/lib/ArrayLib.java @@ -17,17 +17,17 @@ import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeSetter; public class ArrayLib { - @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException { + @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) { return thisArg.size(); } - @NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) throws InterruptedException { + @NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) { thisArg.setSize(len); } - @Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) throws InterruptedException { + @Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) { return Values.fromJavaIterable(ctx, thisArg); } - @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) throws InterruptedException { + @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) { return Values.fromJavaIterable(ctx, () -> new Iterator() { private int i = 0; @@ -42,7 +42,7 @@ public class ArrayLib { } }); } - @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) throws InterruptedException { + @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) { return Values.fromJavaIterable(ctx, () -> new Iterator() { private int i = 0; @@ -59,15 +59,15 @@ public class ArrayLib { } @Native(value = "@@Symbol.iterator", thisArg = true) - public static ObjectValue iterator(Context ctx, ArrayValue thisArg) throws InterruptedException { + public static ObjectValue iterator(Context ctx, ArrayValue thisArg) { return values(ctx, thisArg); } @Native(value = "@@Symbol.asyncIterator", thisArg = true) - public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) throws InterruptedException { + public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) { return values(ctx, thisArg); } - @Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) { // TODO: Fully implement with non-array spreadable objects var size = 0; @@ -92,24 +92,13 @@ public class ArrayLib { return res; } - @Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) throws InterruptedException { - try { - arr.sort((a, b) -> { - try { - var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); - if (res < 0) return -1; - if (res > 0) return 1; - return 0; - } - catch (InterruptedException e) { - throw new RuntimeException(e); - } - }); - } - catch (RuntimeException e) { - if (e.getCause() instanceof InterruptedException) throw (InterruptedException)e.getCause(); - else throw e; - } + @Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) { + arr.sort((a, b) -> { + var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); + if (res < 0) return -1; + if (res > 0) return 1; + return 0; + }); } private static int normalizeI(int len, int i, boolean clamp) { @@ -121,7 +110,7 @@ public class ArrayLib { return i; } - @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) { start = normalizeI(arr.size(), start, true); end = normalizeI(arr.size(), end, true); @@ -131,21 +120,21 @@ public class ArrayLib { return arr; } - @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) { return fill(ctx, arr, val, start, arr.size()); } - @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) { return fill(ctx, arr, val, 0, arr.size()); } - @Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { for (var i = 0; i < arr.size(); i++) { if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false; } return true; } - @Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { for (var i = 0; i < arr.size(); i++) { if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true; } @@ -153,7 +142,7 @@ public class ArrayLib { return false; } - @Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { var res = new ArrayValue(arr.size()); for (int i = 0, j = 0; i < arr.size(); i++) { @@ -161,20 +150,20 @@ public class ArrayLib { } return res; } - @Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { var res = new ArrayValue(arr.size()); for (int i = 0, j = 0; i < arr.size(); i++) { if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr)); } return res; } - @Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { for (int i = 0; i < arr.size(); i++) { if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr); } } - @Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) { var res = new ArrayValue(arr.size()); var stack = new Stack(); var depths = new Stack(); @@ -197,18 +186,18 @@ public class ArrayLib { return res; } - @Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { return flat(ctx, map(ctx, arr, cmp, thisArg), 1); } - @Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { for (int i = 0; i < arr.size(); i++) { if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); } return null; } - @Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { for (var i = arr.size() - 1; i >= 0; i--) { if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); } @@ -216,14 +205,14 @@ public class ArrayLib { return null; } - @Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { for (int i = 0; i < arr.size(); i++) { if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; } return -1; } - @Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { for (var i = arr.size() - 1; i >= 0; i--) { if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; } @@ -231,7 +220,7 @@ public class ArrayLib { return -1; } - @Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { + @Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) { start = normalizeI(arr.size(), start, true); for (int i = 0; i < arr.size() && i < start; i++) { @@ -240,7 +229,7 @@ public class ArrayLib { return -1; } - @Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { + @Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) { start = normalizeI(arr.size(), start, true); for (int i = arr.size(); i >= start; i--) { @@ -250,29 +239,29 @@ public class ArrayLib { return -1; } - @Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) throws InterruptedException { + @Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) { return indexOf(ctx, arr, el, start) >= 0; } - @Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) throws InterruptedException { + @Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) { if (arr.size() == 0) return null; var val = arr.get(arr.size() - 1); arr.shrink(1); return val; } - @Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException { + @Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) { arr.copyFrom(ctx, values, 0, arr.size(), values.length); return arr.size(); } - @Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) throws InterruptedException { + @Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) { if (arr.size() == 0) return null; var val = arr.get(0); arr.move(1, 0, arr.size()); arr.shrink(1); return val; } - @Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException { + @Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) { arr.move(0, values.length, arr.size()); arr.copyFrom(ctx, values, 0, 0, values.length); return arr.size(); @@ -290,7 +279,7 @@ public class ArrayLib { return slice(ctx, arr, start, arr.size()); } - @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, int deleteCount, Object ...items) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, int deleteCount, Object ...items) { start = normalizeI(arr.size(), start, true); deleteCount = normalizeI(arr.size(), deleteCount, true); if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start; @@ -304,14 +293,14 @@ public class ArrayLib { return res; } - @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) { return splice(ctx, arr, start, arr.size() - start); } - @Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) throws InterruptedException { + @Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) { return join(ctx, arr, ","); } - @Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) throws InterruptedException { + @Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) { var res = new StringBuilder(); var comma = true; diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index 13f0314..e372d83 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -18,7 +18,7 @@ public class AsyncFunctionLib extends FunctionValue { private boolean awaiting = false; - private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException { + private void next(Context ctx, Object inducedValue, Object inducedError) { Object res = null; StackData.pushFrame(ctx, frame); ctx.pushEnv(frame.function.environment); @@ -46,11 +46,11 @@ public class AsyncFunctionLib extends FunctionValue { } } - public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object fulfill(Context ctx, Object thisArg, Object ...args) { next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN); return null; } - public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object reject(Context ctx, Object thisArg, Object ...args) { next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null); return null; } @@ -62,7 +62,7 @@ public class AsyncFunctionLib extends FunctionValue { } @Override - public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object call(Context ctx, Object thisArg, Object ...args) { var handler = new AsyncHelper(); var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await)); if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java index 99df988..ef11c5a 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -23,7 +23,7 @@ public class AsyncGeneratorLib extends FunctionValue { private PromiseLib currPromise; public CodeFrame frame; - private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException { + private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) { if (done) { if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); @@ -76,30 +76,30 @@ public class AsyncGeneratorLib extends FunctionValue { return "Generator [running]"; } - public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object fulfill(Context ctx, Object thisArg, Object ...args) { next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN); return null; } - public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object reject(Context ctx, Object thisArg, Object ...args) { next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN); return null; } @Native - public PromiseLib next(Context ctx, Object ...args) throws InterruptedException { + 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) throws InterruptedException { + 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) throws InterruptedException { + public PromiseLib _return(Context ctx, Object value) { this.currPromise = new PromiseLib(); next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); return this.currPromise; @@ -117,7 +117,7 @@ public class AsyncGeneratorLib extends FunctionValue { } @Override - public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object call(Context ctx, Object thisArg, Object ...args) { var handler = new AsyncGenerator(); var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await), diff --git a/src/me/topchetoeu/jscript/lib/DateLib.java b/src/me/topchetoeu/jscript/lib/DateLib.java index 5a3d892..575d72b 100644 --- a/src/me/topchetoeu/jscript/lib/DateLib.java +++ b/src/me/topchetoeu/jscript/lib/DateLib.java @@ -33,7 +33,7 @@ public class DateLib { return normal.get(Calendar.YEAR) - 1900; } @Native - public double setYear(Context ctx, double real) throws InterruptedException { + public double setYear(Context ctx, double real) { if (real >= 0 && real <= 99) real = real + 1900; if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.YEAR, (int)real); @@ -124,56 +124,56 @@ public class DateLib { } @Native - public double setFullYear(Context ctx, double real) throws InterruptedException { + public double setFullYear(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.YEAR, (int)real); updateUTC(); return getTime(); } @Native - public double setMonth(Context ctx, double real) throws InterruptedException { + public double setMonth(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.MONTH, (int)real); updateUTC(); return getTime(); } @Native - public double setDate(Context ctx, double real) throws InterruptedException { + public double setDate(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.DAY_OF_MONTH, (int)real); updateUTC(); return getTime(); } @Native - public double setDay(Context ctx, double real) throws InterruptedException { + public double setDay(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.DAY_OF_WEEK, (int)real); updateUTC(); return getTime(); } @Native - public double setHours(Context ctx, double real) throws InterruptedException { + public double setHours(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.HOUR_OF_DAY, (int)real); updateUTC(); return getTime(); } @Native - public double setMinutes(Context ctx, double real) throws InterruptedException { + public double setMinutes(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.MINUTE, (int)real); updateUTC(); return getTime(); } @Native - public double setSeconds(Context ctx, double real) throws InterruptedException { + public double setSeconds(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.SECOND, (int)real); updateUTC(); return getTime(); } @Native - public double setMilliseconds(Context ctx, double real) throws InterruptedException { + public double setMilliseconds(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.MILLISECOND, (int)real); updateUTC(); @@ -181,56 +181,56 @@ public class DateLib { } @Native - public double setUTCFullYear(Context ctx, double real) throws InterruptedException { + public double setUTCFullYear(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.YEAR, (int)real); updateNormal(); return getTime(); } @Native - public double setUTCMonth(Context ctx, double real) throws InterruptedException { + public double setUTCMonth(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.MONTH, (int)real); updateNormal(); return getTime(); } @Native - public double setUTCDate(Context ctx, double real) throws InterruptedException { + public double setUTCDate(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.DAY_OF_MONTH, (int)real); updateNormal(); return getTime(); } @Native - public double setUTCDay(Context ctx, double real) throws InterruptedException { + public double setUTCDay(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.DAY_OF_WEEK, (int)real); updateNormal(); return getTime(); } @Native - public double setUTCHours(Context ctx, double real) throws InterruptedException { + public double setUTCHours(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.HOUR_OF_DAY, (int)real); updateNormal(); return getTime(); } @Native - public double setUTCMinutes(Context ctx, double real) throws InterruptedException { + public double setUTCMinutes(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.MINUTE, (int)real); updateNormal(); return getTime(); } @Native - public double setUTCSeconds(Context ctx, double real) throws InterruptedException { + public double setUTCSeconds(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.SECOND, (int)real); updateNormal(); return getTime(); } @Native - public double setUTCMilliseconds(Context ctx, double real) throws InterruptedException { + public double setUTCMilliseconds(Context ctx, double real) { if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.MILLISECOND, (int)real); updateNormal(); diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index ebe27bd..0783038 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -12,7 +12,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeInit; public class ErrorLib { - private static String toString(Context ctx, Object cause, Object name, Object message, ArrayValue stack) throws InterruptedException { + private static String toString(Context ctx, Object cause, Object name, Object message, ArrayValue stack) { if (name == null) name = ""; else name = Values.toString(ctx, name).trim(); if (message == null) message = ""; @@ -35,7 +35,7 @@ public class ErrorLib { return res.toString(); } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { if (thisArg instanceof ObjectValue) { var stack = Values.getMember(ctx, thisArg, "stack"); if (!(stack instanceof ArrayValue)) stack = null; @@ -49,7 +49,7 @@ public class ErrorLib { else return "[Invalid error]"; } - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { var target = new ObjectValue(); if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; diff --git a/src/me/topchetoeu/jscript/lib/FunctionLib.java b/src/me/topchetoeu/jscript/lib/FunctionLib.java index 2c05aa7..9cdc3ac 100644 --- a/src/me/topchetoeu/jscript/lib/FunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/FunctionLib.java @@ -12,10 +12,10 @@ import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeInit; public class FunctionLib { - @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { + @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) { return func.call(ctx, thisArg, args.toArray()); } - @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) throws InterruptedException { + @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) { if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); return func.call(ctx, thisArg, args); diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index 35c3e4d..e39062b 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -21,7 +21,7 @@ public class GeneratorLib extends FunctionValue { @Native("@@Symbol.typeName") public final String name = "Generator"; - private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException { + private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) { if (done) { if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); var res = new ObjectValue(); @@ -60,16 +60,16 @@ public class GeneratorLib extends FunctionValue { } @Native - public ObjectValue next(Context ctx, Object ...args) throws InterruptedException { + public ObjectValue next(Context ctx, Object ...args) { if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN); } @Native("throw") - public ObjectValue _throw(Context ctx, Object error) throws InterruptedException { + public ObjectValue _throw(Context ctx, Object error) { return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error); } @Native("return") - public ObjectValue _return(Context ctx, Object value) throws InterruptedException { + public ObjectValue _return(Context ctx, Object value) { return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); } @@ -87,7 +87,7 @@ public class GeneratorLib extends FunctionValue { } @Override - public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { + public Object call(Context ctx, Object thisArg, Object ...args) { var handler = new Generator(); var 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."); diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 631c2e1..811f482 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -14,7 +14,7 @@ public class Internals { private static final DataKey> THREADS = new DataKey<>(); private static final DataKey I = new DataKey<>(); - @Native public static void log(Context ctx, Object ...args) throws InterruptedException { + @Native public static void log(Context ctx, Object ...args) { for (var arg : args) { Values.printValue(ctx, arg); } @@ -72,10 +72,10 @@ public class Internals { clearTimeout(ctx, i); } - @Native public static double parseInt(Context ctx, String val) throws InterruptedException { + @Native public static double parseInt(Context ctx, String val) { return NumberLib.parseInt(ctx, val); } - @Native public static double parseFloat(Context ctx, String val) throws InterruptedException { + @Native public static double parseFloat(Context ctx, String val) { return NumberLib.parseFloat(ctx, val); } diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java index d36a4ea..4ce8acf 100644 --- a/src/me/topchetoeu/jscript/lib/JSONLib.java +++ b/src/me/topchetoeu/jscript/lib/JSONLib.java @@ -30,7 +30,7 @@ public class JSONLib { if (val.isNull()) return Values.NULL; return null; } - private static JSONElement toJSON(Context ctx, Object val, HashSet prev) throws InterruptedException { + private static JSONElement toJSON(Context ctx, Object val, HashSet prev) { if (val instanceof Boolean) return JSONElement.bool((boolean)val); if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue()); if (val instanceof String) return JSONElement.string((String)val); @@ -70,7 +70,7 @@ public class JSONLib { } @Native - public static Object parse(Context ctx, String val) throws InterruptedException { + public static Object parse(Context ctx, String val) { try { return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); } @@ -79,7 +79,7 @@ public class JSONLib { } } @Native - public static String stringify(Context ctx, Object val) throws InterruptedException { + public static String stringify(Context ctx, Object val) { return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>())); } } diff --git a/src/me/topchetoeu/jscript/lib/MapLib.java b/src/me/topchetoeu/jscript/lib/MapLib.java index 6fc6dab..bd1217e 100644 --- a/src/me/topchetoeu/jscript/lib/MapLib.java +++ b/src/me/topchetoeu/jscript/lib/MapLib.java @@ -16,7 +16,7 @@ public class MapLib { private LinkedHashMap map = new LinkedHashMap<>(); @Native("@@Symbol.typeName") public final String name = "Map"; - @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { + @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { return this.entries(ctx); } @@ -31,17 +31,17 @@ public class MapLib { return false; } - @Native public ObjectValue entries(Context ctx) throws InterruptedException { + @Native public ObjectValue entries(Context ctx) { var res = map.entrySet().stream().map(v -> { return new ArrayValue(ctx, v.getKey(), v.getValue()); }).collect(Collectors.toList()); return Values.fromJavaIterator(ctx, res.iterator()); } - @Native public ObjectValue keys(Context ctx) throws InterruptedException { + @Native public ObjectValue keys(Context ctx) { var res = new ArrayList<>(map.keySet()); return Values.fromJavaIterator(ctx, res.iterator()); } - @Native public ObjectValue values(Context ctx) throws InterruptedException { + @Native public ObjectValue values(Context ctx) { var res = new ArrayList<>(map.values()); return Values.fromJavaIterator(ctx, res.iterator()); } @@ -61,13 +61,13 @@ public class MapLib { return map.size(); } - @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { + @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) { var keys = new ArrayList<>(map.keySet()); for (var el : keys) func.call(ctx, thisArg, el, map.get(el), this); } - @Native public MapLib(Context ctx, Object iterable) throws InterruptedException { + @Native public MapLib(Context ctx, Object iterable) { for (var el : Values.toJavaIterable(ctx, iterable)) { try { set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); diff --git a/src/me/topchetoeu/jscript/lib/NumberLib.java b/src/me/topchetoeu/jscript/lib/NumberLib.java index 28dac9c..11556e3 100644 --- a/src/me/topchetoeu/jscript/lib/NumberLib.java +++ b/src/me/topchetoeu/jscript/lib/NumberLib.java @@ -29,22 +29,22 @@ public class NumberLib { return val > MIN_SAFE_INTEGER && val < MAX_SAFE_INTEGER; } - @Native public static double parseFloat(Context ctx, String val) throws InterruptedException { + @Native public static double parseFloat(Context ctx, String val) { return Values.toNumber(ctx, val); } - @Native public static double parseInt(Context ctx, String val) throws InterruptedException { + @Native public static double parseInt(Context ctx, String val) { return (long)Values.toNumber(ctx, val); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { val = Values.toNumber(ctx, val); if (thisArg instanceof ObjectValue) return new NumberLib((double)val); else return val; } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { return Values.toString(ctx, Values.toNumber(ctx, thisArg)); } - @Native(thisArg = true) public static double valueOf(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static double valueOf(Context ctx, Object thisArg) { if (thisArg instanceof NumberLib) return ((NumberLib)thisArg).value; else return Values.toNumber(ctx, thisArg); } diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 8b0b264..f7fce62 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -14,7 +14,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeInit; public class ObjectLib { - @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException { + @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) { for (var obj : src) { for (var key : Values.getMembers(ctx, obj, true, true)) { Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key)); @@ -22,13 +22,13 @@ public class ObjectLib { } return dst; } - @Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) throws InterruptedException { + @Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) { var obj = new ObjectValue(); obj.setPrototype(ctx, proto); return defineProperties(ctx, obj, props); } - @Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) throws InterruptedException { + @Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) { var hasVal = attrib.hasMember(ctx, "value", false); var hasGet = attrib.hasMember(ctx, "get", false); var hasSet = attrib.hasMember(ctx, "set", false); @@ -59,7 +59,7 @@ public class ObjectLib { return obj; } - @Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) throws InterruptedException { + @Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) { for (var key : Values.getMembers(null, obj, false, false)) { obj.defineProperty(ctx, key, attrib.getMember(ctx, key)); } @@ -67,7 +67,7 @@ public class ObjectLib { return obj; } - @Native public static ArrayValue keys(Context ctx, Object obj, Object all) throws InterruptedException { + @Native public static ArrayValue keys(Context ctx, Object obj, Object all) { var res = new ArrayValue(); var _all = Values.toBoolean(all); @@ -77,7 +77,7 @@ public class ObjectLib { return res; } - @Native public static ArrayValue entries(Context ctx, Object obj, Object all) throws InterruptedException { + @Native public static ArrayValue entries(Context ctx, Object obj, Object all) { var res = new ArrayValue(); var _all = Values.toBoolean(all); @@ -87,7 +87,7 @@ public class ObjectLib { return res; } - @Native public static ArrayValue values(Context ctx, Object obj, Object all) throws InterruptedException { + @Native public static ArrayValue values(Context ctx, Object obj, Object all) { var res = new ArrayValue(); var _all = Values.toBoolean(all); @@ -98,10 +98,10 @@ public class ObjectLib { return res; } - @Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) throws InterruptedException { + @Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) { return Values.getMemberDescriptor(ctx, obj, key); } - @Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) throws InterruptedException { + @Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) { var res = new ObjectValue(); for (var key : Values.getMembers(ctx, obj, true, true)) { res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key)); @@ -109,7 +109,7 @@ public class ObjectLib { return res; } - @Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) throws InterruptedException { + @Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) { var res = new ArrayValue(); var _all = Values.toBoolean(all); @@ -119,7 +119,7 @@ public class ObjectLib { return res; } - @Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) throws InterruptedException { + @Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) { var res = new ArrayValue(); for (var key : Values.getMembers(ctx, obj, true, true)) { @@ -128,19 +128,19 @@ public class ObjectLib { return res; } - @Native public static boolean hasOwn(Context ctx, Object obj, Object key) throws InterruptedException { + @Native public static boolean hasOwn(Context ctx, Object obj, Object key) { return Values.hasMember(ctx, obj, key, true); } - @Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) throws InterruptedException { + @Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) { return Values.getPrototype(ctx, obj); } - @Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) throws InterruptedException { + @Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) { Values.setPrototype(ctx, obj, proto); return obj; } - @Native public static ObjectValue fromEntries(Context ctx, Object iterable) throws InterruptedException { + @Native public static ObjectValue fromEntries(Context ctx, Object iterable) { var res = new ObjectValue(); for (var el : Values.toJavaIterable(ctx, iterable)) { @@ -152,23 +152,23 @@ public class ObjectLib { return res; } - @Native public static Object preventExtensions(Context ctx, Object obj) throws InterruptedException { + @Native public static Object preventExtensions(Context ctx, Object obj) { if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions(); return obj; } - @Native public static Object seal(Context ctx, Object obj) throws InterruptedException { + @Native public static Object seal(Context ctx, Object obj) { if (obj instanceof ObjectValue) ((ObjectValue)obj).seal(); return obj; } - @Native public static Object freeze(Context ctx, Object obj) throws InterruptedException { + @Native public static Object freeze(Context ctx, Object obj) { if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze(); return obj; } - @Native public static boolean isExtensible(Context ctx, Object obj) throws InterruptedException { + @Native public static boolean isExtensible(Context ctx, Object obj) { return obj instanceof ObjectValue && ((ObjectValue)obj).extensible(); } - @Native public static boolean isSealed(Context ctx, Object obj) throws InterruptedException { + @Native public static boolean isSealed(Context ctx, Object obj) { if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { var _obj = (ObjectValue)obj; for (var key : _obj.keys(true)) { @@ -178,7 +178,7 @@ public class ObjectLib { return true; } - @Native public static boolean isFrozen(Context ctx, Object obj) throws InterruptedException { + @Native public static boolean isFrozen(Context ctx, Object obj) { if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { var _obj = (ObjectValue)obj; for (var key : _obj.keys(true)) { @@ -193,18 +193,18 @@ public class ObjectLib { @Native(thisArg = true) public static Object valueOf(Context ctx, Object thisArg) { return thisArg; } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { var name = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.typeName")); if (name == null) name = "Unknown"; else name = Values.toString(ctx, name); return "[object " + name + "]"; } - @Native(thisArg = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object key) throws InterruptedException { + @Native(thisArg = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object key) { return ObjectLib.hasOwn(ctx, thisArg, Values.convert(ctx, key, String.class)); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) throws InterruptedException { + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) { if (arg == null || arg == Values.NULL) return new ObjectValue(); else if (arg instanceof Boolean) return BooleanLib.constructor(ctx, thisArg, arg); else if (arg instanceof Number) return NumberLib.constructor(ctx, thisArg, arg); diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index f0a4ed3..40afcd4 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -13,6 +13,7 @@ import me.topchetoeu.jscript.engine.values.NativeWrapper; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeInit; @@ -31,19 +32,19 @@ public class PromiseLib { } @Native("resolve") - public static PromiseLib ofResolved(Context ctx, Object val) throws InterruptedException { + public static PromiseLib ofResolved(Context ctx, Object val) { var res = new PromiseLib(); res.fulfill(ctx, val); return res; } @Native("reject") - public static PromiseLib ofRejected(Context ctx, Object val) throws InterruptedException { + public static PromiseLib ofRejected(Context ctx, Object val) { var res = new PromiseLib(); res.reject(ctx, val); return res; } - @Native public static PromiseLib any(Context ctx, Object _promises) throws InterruptedException { + @Native public static PromiseLib any(Context ctx, Object _promises) { if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); var promises = Values.array(_promises); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); @@ -68,7 +69,7 @@ public class PromiseLib { return res; } - @Native public static PromiseLib race(Context ctx, Object _promises) throws InterruptedException { + @Native public static PromiseLib race(Context ctx, Object _promises) { if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); var promises = Values.array(_promises); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); @@ -84,7 +85,7 @@ public class PromiseLib { return res; } - @Native public static PromiseLib all(Context ctx, Object _promises) throws InterruptedException { + @Native public static PromiseLib all(Context ctx, Object _promises) { if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); var promises = Values.array(_promises); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); @@ -111,7 +112,7 @@ public class PromiseLib { return res; } - @Native public static PromiseLib allSettled(Context ctx, Object _promises) throws InterruptedException { + @Native public static PromiseLib allSettled(Context ctx, Object _promises) { if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); var promises = Values.array(_promises); if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); @@ -154,7 +155,7 @@ public class PromiseLib { * Thread safe - you can call this from anywhere * HOWEVER, it's strongly recommended to use this only in javascript */ - @Native(thisArg=true) public static Object then(Context ctx, Object thisArg, Object _onFulfill, Object _onReject) throws InterruptedException { + @Native(thisArg=true) public static Object then(Context ctx, Object thisArg, Object _onFulfill, Object _onReject) { var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null; var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null; @@ -205,14 +206,14 @@ public class PromiseLib { * Thread safe - you can call this from anywhere * HOWEVER, it's strongly recommended to use this only in javascript */ - @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) throws InterruptedException { + @Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) { return then(ctx, thisArg, null, _onReject); } /** * Thread safe - you can call this from anywhere * HOWEVER, it's strongly recommended to use this only in javascript */ - @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) throws InterruptedException { + @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) { return then(ctx, thisArg, new NativeFunction(null, (e, th, _args) -> { if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); @@ -235,7 +236,7 @@ public class PromiseLib { private boolean handled = false; private Object val; - private void resolve(Context ctx, Object val, int state) throws InterruptedException { + private void resolve(Context ctx, Object val, int state) { if (this.state != STATE_PENDING) return; if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, @@ -264,8 +265,8 @@ public class PromiseLib { if (handles.size() == 0) { ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { if (!handled) { - try { Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); } - catch (InterruptedException ex) { } + Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); + throw new InterruptException(); } return null; @@ -285,13 +286,13 @@ public class PromiseLib { /** * Thread safe - call from any thread */ - public void fulfill(Context ctx, Object val) throws InterruptedException { + public void fulfill(Context ctx, Object val) { resolve(ctx, val, STATE_FULFILLED); } /** * Thread safe - call from any thread */ - public void reject(Context ctx, Object val) throws InterruptedException { + public void reject(Context ctx, Object val) { resolve(ctx, val, STATE_REJECTED); } @@ -313,7 +314,7 @@ public class PromiseLib { /** * NOT THREAD SAFE - must be called from the engine executor thread */ - @Native public PromiseLib(Context ctx, FunctionValue func) throws InterruptedException { + @Native public PromiseLib(Context ctx, FunctionValue func) { if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); try { func.call( diff --git a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java index b0ce327..fc21e47 100644 --- a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java @@ -8,7 +8,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeInit; public class RangeErrorLib extends ErrorLib { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { var target = ErrorLib.constructor(ctx, thisArg, message); target.defineProperty(ctx, "name", "RangeError"); return target; diff --git a/src/me/topchetoeu/jscript/lib/RegExpLib.java b/src/me/topchetoeu/jscript/lib/RegExpLib.java index 3e54df0..39284f1 100644 --- a/src/me/topchetoeu/jscript/lib/RegExpLib.java +++ b/src/me/topchetoeu/jscript/lib/RegExpLib.java @@ -17,7 +17,7 @@ public class RegExpLib { private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL); private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]"); - private static String cleanupPattern(Context ctx, Object val) throws InterruptedException { + private static String cleanupPattern(Context ctx, Object val) { if (val == null) return "(?:)"; if (val instanceof RegExpLib) return ((RegExpLib)val).source; if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) { @@ -27,7 +27,7 @@ public class RegExpLib { if (res.equals("")) return "(?:)"; return res; } - private static String cleanupFlags(Context ctx, Object val) throws InterruptedException { + private static String cleanupFlags(Context ctx, Object val) { if (val == null) return ""; return Values.toString(ctx, val); } @@ -46,7 +46,7 @@ public class RegExpLib { } @Native - public static RegExpLib escape(Context ctx, Object raw, Object flags) throws InterruptedException { + public static RegExpLib escape(Context ctx, Object raw, Object flags) { return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags)); } public static RegExpLib escape(String raw, String flags) { @@ -135,7 +135,7 @@ public class RegExpLib { return "/" + source + "/" + flags(); } - @Native("@@Symvol.match") public Object match(Context ctx, String target) throws InterruptedException { + @Native("@@Symvol.match") public Object match(Context ctx, String target) { if (this.global) { var res = new ArrayValue(); Object val; @@ -152,7 +152,7 @@ public class RegExpLib { } } - @Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) throws InterruptedException { + @Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) { var pattern = new RegExpLib(this.source, this.flags() + "g"); return Values.fromJavaIterator(ctx, new Iterator() { @@ -174,7 +174,7 @@ public class RegExpLib { }); } - @Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) throws InterruptedException { + @Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) { var pattern = new RegExpLib(this.source, this.flags() + "g"); Object match; int lastEnd = 0; @@ -260,7 +260,7 @@ public class RegExpLib { // else return -1; // } // }, - @Native public RegExpLib(Context ctx, Object pattern, Object flags) throws InterruptedException { + @Native public RegExpLib(Context ctx, Object pattern, Object flags) { this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags)); } public RegExpLib(String pattern, String flags) { diff --git a/src/me/topchetoeu/jscript/lib/SetLib.java b/src/me/topchetoeu/jscript/lib/SetLib.java index 069c79f..331f023 100644 --- a/src/me/topchetoeu/jscript/lib/SetLib.java +++ b/src/me/topchetoeu/jscript/lib/SetLib.java @@ -16,19 +16,19 @@ public class SetLib { private LinkedHashSet set = new LinkedHashSet<>(); @Native("@@Symbol.typeName") public final String name = "Set"; - @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) throws InterruptedException { + @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { return this.values(ctx); } - @Native public ObjectValue entries(Context ctx) throws InterruptedException { + @Native public ObjectValue entries(Context ctx) { var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); return Values.fromJavaIterator(ctx, res.iterator()); } - @Native public ObjectValue keys(Context ctx) throws InterruptedException { + @Native public ObjectValue keys(Context ctx) { var res = new ArrayList<>(set); return Values.fromJavaIterator(ctx, res.iterator()); } - @Native public ObjectValue values(Context ctx) throws InterruptedException { + @Native public ObjectValue values(Context ctx) { var res = new ArrayList<>(set); return Values.fromJavaIterator(ctx, res.iterator()); } @@ -51,13 +51,13 @@ public class SetLib { return set.size(); } - @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) throws InterruptedException { + @NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) { var keys = new ArrayList<>(set); for (var el : keys) func.call(ctx, thisArg, el, el, this); } - @Native public SetLib(Context ctx, Object iterable) throws InterruptedException { + @Native public SetLib(Context ctx, Object iterable) { for (var el : Values.toJavaIterable(ctx, iterable)) add(el); } } diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index d491045..ae5b8f6 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -19,7 +19,7 @@ import me.topchetoeu.jscript.interop.NativeInit; public class StringLib { public final String value; - private static String passThis(Context ctx, String funcName, Object val) throws InterruptedException { + private static String passThis(Context ctx, String funcName, Object val) { if (val instanceof StringLib) return ((StringLib)val).value; else if (val instanceof String) return (String)val; else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); @@ -33,46 +33,46 @@ public class StringLib { return i; } - @NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) throws InterruptedException { + @NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) { return passThis(ctx, "substring", thisArg).length(); } - @Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { + @Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) { var val = passThis(ctx, "substring", thisArg); start = normalizeI(start, val.length(), true); int end = normalizeI(_end == null ? val.length() : (int)Values.toNumber(ctx, _end), val.length(), true); return val.substring(start, end); } - @Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) throws InterruptedException { + @Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) { var val = passThis(ctx, "substr", thisArg); int len = _len == null ? val.length() - start : (int)Values.toNumber(ctx, _len); return substring(ctx, val, start, start + len); } - @Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) { return passThis(ctx, "toLowerCase", thisArg).toLowerCase(); } - @Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) { return passThis(ctx, "toUpperCase", thisArg).toUpperCase(); } - @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) throws InterruptedException { + @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) { return passThis(ctx, "charAt", thisArg).charAt(i) + ""; } - @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) throws InterruptedException { + @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) { return passThis(ctx, "charCodeAt", thisArg).charAt(i); } - @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) throws InterruptedException { + @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) { return passThis(ctx, "startsWith", thisArg).startsWith(term, pos); } - @Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) throws InterruptedException { + @Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) { var val = passThis(ctx, "endsWith", thisArg); return val.lastIndexOf(term, pos) >= 0; } - @Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) throws InterruptedException { + @Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) { var val = passThis(ctx, "indexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { @@ -84,7 +84,7 @@ public class StringLib { return val.indexOf(Values.toString(ctx, term), start); } - @Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { + @Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) { var val = passThis(ctx, "lastIndexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { @@ -97,11 +97,11 @@ public class StringLib { return val.lastIndexOf(Values.toString(ctx, term), pos); } - @Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) throws InterruptedException { + @Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) { return lastIndexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0; } - @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, String replacement) { var val = passThis(ctx, "replace", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { @@ -113,7 +113,7 @@ public class StringLib { return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); } - @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) { var val = passThis(ctx, "replaceAll", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { @@ -126,7 +126,7 @@ public class StringLib { return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement); } - @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) { var val = passThis(ctx, "match", thisArg); FunctionValue match; @@ -148,7 +148,7 @@ public class StringLib { if (res instanceof ArrayValue) return (ArrayValue)res; else return new ArrayValue(ctx, ""); } - @Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) throws InterruptedException { + @Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) { var val = passThis(ctx, "matchAll", thisArg); FunctionValue match = null; @@ -170,7 +170,7 @@ public class StringLib { return match.call(ctx, term, val); } - @Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) throws InterruptedException { + @Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) { var val = passThis(ctx, "split", thisArg); if (lim != null) lim = Values.toNumber(ctx, lim); @@ -217,30 +217,30 @@ public class StringLib { return res; } - @Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) throws InterruptedException { + @Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) { return substring(ctx, passThis(ctx, "slice", thisArg), start, _end); } - @Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) throws InterruptedException { + @Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) { var res = new StringBuilder(passThis(ctx, "concat", thisArg)); for (var el : args) res.append(Values.toString(ctx, el)); return res.toString(); } - @Native(thisArg = true) public static String trim(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String trim(Context ctx, Object thisArg) { return passThis(ctx, "trim", thisArg).trim(); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { val = Values.toString(ctx, val); if (thisArg instanceof ObjectValue) return new StringLib((String)val); else return val; } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { return passThis(ctx, "toString", thisArg); } - @Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) { return passThis(ctx, "valueOf", thisArg); } diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index 8c76cce..63206ae 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -29,21 +29,21 @@ public class SymbolLib { public final Symbol value; - private static Symbol passThis(Context ctx, String funcName, Object val) throws InterruptedException { + private static Symbol passThis(Context ctx, String funcName, Object val) { if (val instanceof SymbolLib) return ((SymbolLib)val).value; else if (val instanceof Symbol) return (Symbol)val; else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) throws InterruptedException { + @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); if (val == null) return new Symbol(""); else return new Symbol(Values.toString(ctx, val)); } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { return passThis(ctx, "toString", thisArg).value; } - @Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) throws InterruptedException { + @Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) { return passThis(ctx, "valueOf", thisArg); } diff --git a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java index 70d61fa..c3b2de5 100644 --- a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java @@ -8,7 +8,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeInit; public class SyntaxErrorLib extends ErrorLib { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { var target = ErrorLib.constructor(ctx, thisArg, message); target.defineProperty(ctx, "name", "SyntaxError"); return target; diff --git a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java index 9df269c..b15a5b3 100644 --- a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java @@ -8,7 +8,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeInit; public class TypeErrorLib extends ErrorLib { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) throws InterruptedException { + @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { var target = ErrorLib.constructor(ctx, thisArg, message); target.defineProperty(ctx, "name", "TypeError"); return target; -- 2.45.2 From a4e5f7f4715fbf2b541bdc6efe59bb247cd40975 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:55:44 +0300 Subject: [PATCH 3/7] refactor: clean up useless catch blocks --- src/me/topchetoeu/jscript/Main.java | 12 ++++------- .../values/OperationStatement.java | 9 ++------- src/me/topchetoeu/jscript/engine/Engine.java | 3 ++- .../jscript/engine/values/ObjectValue.java | 4 +--- .../jscript/engine/values/Values.java | 20 +++++-------------- .../topchetoeu/jscript/events/Awaitable.java | 8 ++++---- .../exceptions/UncheckedException.java | 7 +++++++ .../interop/NativeWrapperProvider.java | 7 ++++--- .../jscript/interop/OverloadFunction.java | 8 ++------ src/me/topchetoeu/jscript/lib/JSONLib.java | 4 +--- src/me/topchetoeu/jscript/lib/PromiseLib.java | 4 +--- src/me/topchetoeu/jscript/lib/RegExpLib.java | 4 +--- 12 files changed, 34 insertions(+), 56 deletions(-) create mode 100644 src/me/topchetoeu/jscript/exceptions/UncheckedException.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 80872d7..8922290 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -16,6 +16,7 @@ import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.lib.Internals; public class Main { @@ -35,9 +36,7 @@ public class Main { br.close(); return out.toString(); } - catch (IOException e) { - return null; - } + catch (Throwable e) { throw new UncheckedException(e); } } public static String resourceToString(String name) { var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); @@ -97,20 +96,17 @@ public class Main { catch (EngineException e) { Values.printError(e, ""); } } } - catch (IOException e) { - e.printStackTrace(); - return; - } + catch (InterruptException e) { return; } catch (SyntaxException ex) { if (exited[0]) return; System.out.println("Syntax error:" + ex.msg); } - catch (InterruptException e) { return; } catch (RuntimeException ex) { if (exited[0]) return; System.out.println("Internal error ocurred:"); ex.printStackTrace(); } + catch (Throwable e) { throw new UncheckedException(e); } if (exited[0]) return; }); reader.setDaemon(true); diff --git a/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java b/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java index 3016ee2..97479bd 100644 --- a/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java @@ -49,13 +49,8 @@ public class OperationStatement extends Statement { vals[i] = ((ConstantStatement)args[i]).value; } - try { - return new ConstantStatement(loc(), Values.operation(null, operation, vals)); - } - catch (EngineException e) { - return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); - } - catch (InterruptedException e) { return null; } + try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); } + catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); } } return new OperationStatement(loc(), operation, args); diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index c82dbde..6504b82 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -8,6 +8,7 @@ import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.InterruptException; public class Engine { private class UncompiledFunction extends FunctionValue { @@ -76,7 +77,7 @@ public class Engine { } catch (InterruptedException e) { for (var msg : macroTasks) { - msg.notifier.error(new RuntimeException(e)); + msg.notifier.error(new InterruptException(e)); } break; } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 2f21e3f..7bdf0a3 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -155,9 +155,7 @@ public class ObjectValue { if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr"); if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr"); } - catch (NullPointerException e) { - return null; - } + catch (NullPointerException e) { return null; } return (ObjectValue)prototype; } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index ec261d5..9ca731a 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -15,8 +15,8 @@ import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.frame.ConvertHint; import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.exceptions.UncheckedException; public class Values { public static final Object NULL = new Object(); @@ -97,12 +97,8 @@ public class Values { var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf"; if (ctx != null) { - try { - return tryCallConvertFunc(ctx, obj, first); - } - catch (EngineException unused) { - return tryCallConvertFunc(ctx, obj, second); - } + try { return tryCallConvertFunc(ctx, obj, first); } + catch (EngineException unused) { return tryCallConvertFunc(ctx, obj, second); } } throw EngineException.ofType("Value couldn't be converted to a primitive."); @@ -120,10 +116,8 @@ public class Values { if (val instanceof Number) return number(val); if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; if (val instanceof String) { - try { - return Double.parseDouble((String)val); - } - catch (NumberFormatException e) { } + try { return Double.parseDouble((String)val); } + catch (Throwable e) { throw new UncheckedException(e); } } return Double.NaN; } @@ -565,10 +559,6 @@ public class Values { } }; } - catch (InterruptException e) { - Thread.currentThread().interrupt(); - return null; - } catch (IllegalArgumentException | NullPointerException e) { return Collections.emptyIterator(); } diff --git a/src/me/topchetoeu/jscript/events/Awaitable.java b/src/me/topchetoeu/jscript/events/Awaitable.java index 7583892..8f2d9ec 100644 --- a/src/me/topchetoeu/jscript/events/Awaitable.java +++ b/src/me/topchetoeu/jscript/events/Awaitable.java @@ -1,7 +1,9 @@ package me.topchetoeu.jscript.events; +import me.topchetoeu.jscript.exceptions.InterruptException; + public interface Awaitable { - T await() throws FinishedException, InterruptedException; + T await() throws FinishedException; default Observable toObservable() { return sub -> { @@ -10,9 +12,7 @@ public interface Awaitable { sub.next(await()); sub.finish(); } - catch (InterruptedException | FinishedException e) { - sub.finish(); - } + catch (InterruptException | FinishedException e) { sub.finish(); } catch (RuntimeException e) { sub.error(e); } diff --git a/src/me/topchetoeu/jscript/exceptions/UncheckedException.java b/src/me/topchetoeu/jscript/exceptions/UncheckedException.java new file mode 100644 index 0000000..4e730e7 --- /dev/null +++ b/src/me/topchetoeu/jscript/exceptions/UncheckedException.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.exceptions; + +public class UncheckedException extends RuntimeException { + public UncheckedException(Throwable err) { + super(err); + } +} diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 3a12212..c70043c 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -9,6 +9,7 @@ import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.exceptions.UncheckedException; public class NativeWrapperProvider implements WrappersProvider { private final HashMap, FunctionValue> constructors = new HashMap<>(); @@ -120,7 +121,7 @@ public class NativeWrapperProvider implements WrappersProvider { var init = overload.getAnnotation(NativeInit.class); if (init == null || init.value() != InitType.PROTOTYPE) continue; try { overload.invoke(null, ctx, res); } - catch (ReflectiveOperationException e) { e.printStackTrace(); } + catch (Throwable e) { throw new UncheckedException(e); } } applyMethods(ctx, true, res, clazz); @@ -152,7 +153,7 @@ public class NativeWrapperProvider implements WrappersProvider { var init = overload.getAnnotation(NativeInit.class); if (init == null || init.value() != InitType.CONSTRUCTOR) continue; try { overload.invoke(null, ctx, func); } - catch (ReflectiveOperationException e) { e.printStackTrace(); } + catch (Throwable e) { throw new UncheckedException(e); } } if (((OverloadFunction)func).overloads.size() == 0) { @@ -180,7 +181,7 @@ public class NativeWrapperProvider implements WrappersProvider { var init = overload.getAnnotation(NativeInit.class); if (init == null || init.value() != InitType.NAMESPACE) continue; try { overload.invoke(null, ctx, res); } - catch (ReflectiveOperationException e) { e.printStackTrace(); } + catch (Throwable e) { throw new UncheckedException(e); } } applyMethods(ctx, false, res, clazz); diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 87eebd0..4ea59e2 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -76,12 +76,8 @@ public class OverloadFunction extends FunctionValue { try { return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); } - catch (InstantiationException e) { - throw EngineException.ofError("The class may not be instantiated."); - } - catch (IllegalAccessException | IllegalArgumentException e) { - continue; - } + catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); } + catch (IllegalAccessException | IllegalArgumentException e) { continue; } catch (InvocationTargetException e) { var loc = new Location(0, 0, ""); if (e.getTargetException() instanceof EngineException) { diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java index 4ce8acf..8d612df 100644 --- a/src/me/topchetoeu/jscript/lib/JSONLib.java +++ b/src/me/topchetoeu/jscript/lib/JSONLib.java @@ -74,9 +74,7 @@ public class JSONLib { try { return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); } - catch (SyntaxException e) { - throw EngineException.ofSyntax(e.msg); - } + catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } } @Native public static String stringify(Context ctx, Object val) { diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 40afcd4..1b53d2a 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -186,9 +186,7 @@ public class PromiseLib { if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle); else { Object next; - try { - next = Values.getMember(ctx, thisArg, "then"); - } + try { next = Values.getMember(ctx, thisArg, "then"); } catch (IllegalArgumentException e) { next = null; } try { diff --git a/src/me/topchetoeu/jscript/lib/RegExpLib.java b/src/me/topchetoeu/jscript/lib/RegExpLib.java index 39284f1..9828577 100644 --- a/src/me/topchetoeu/jscript/lib/RegExpLib.java +++ b/src/me/topchetoeu/jscript/lib/RegExpLib.java @@ -97,9 +97,7 @@ public class RegExpLib { var groups = new ObjectValue(); for (var el : namedGroups) { - try { - groups.defineProperty(null, el, matcher.group(el)); - } + try { groups.defineProperty(null, el, matcher.group(el)); } catch (IllegalArgumentException e) { } } if (groups.values.size() == 0) groups = null; -- 2.45.2 From d2d9fa97383b8e7e3aa32c308dfe6b40eca04442 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:08:47 +0300 Subject: [PATCH 4/7] fix: exit now works --- src/me/topchetoeu/jscript/Main.java | 7 ++++++- src/me/topchetoeu/jscript/engine/Engine.java | 5 ----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 8922290..e34ddcf 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -53,6 +53,11 @@ public class Main { public void error(RuntimeException err) { Values.printError(err, null); } + + @Override + public void finish() { + task.interrupt(); + } }; public static void main(String args[]) { @@ -96,7 +101,7 @@ public class Main { catch (EngineException e) { Values.printError(e, ""); } } } - catch (InterruptException e) { return; } + catch (IOException e) { return; } catch (SyntaxException ex) { if (exited[0]) return; System.out.println("Syntax error:" + ex.msg); diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 6504b82..9176f7a 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -7,7 +7,6 @@ import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.DataNotifier; -import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; public class Engine { @@ -58,12 +57,8 @@ public class Engine { try { task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); } - catch (EngineException e) { - task.notifier.error(e); - } catch (RuntimeException e) { task.notifier.error(e); - e.printStackTrace(); } } private void run() { -- 2.45.2 From 4b84309df608f43cacf7e8985d5ac30f7484e3a4 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:12:14 +0300 Subject: [PATCH 5/7] feat: complete steptrough and breakpoints in debugger --- build.js | 2 + src/assets/favicon.png | Bin 0 -> 1720 bytes src/assets/index.html | 29 + src/me/topchetoeu/jscript/Filename.java | 52 + src/me/topchetoeu/jscript/Location.java | 23 +- src/me/topchetoeu/jscript/Main.java | 55 +- src/me/topchetoeu/jscript/Reading.java | 36 + .../jscript/compilation/CompileTarget.java | 13 +- .../compilation/CompoundStatement.java | 2 +- .../jscript/compilation/Instruction.java | 5 - .../jscript/compilation/Statement.java | 2 +- .../compilation/control/SwitchStatement.java | 3 +- .../compilation/values/CallStatement.java | 3 +- .../compilation/values/FunctionStatement.java | 2 +- .../values/IndexAssignStatement.java | 6 +- .../compilation/values/IndexStatement.java | 2 +- .../compilation/values/NewStatement.java | 3 +- src/me/topchetoeu/jscript/engine/Context.java | 13 +- src/me/topchetoeu/jscript/engine/Data.java | 5 +- src/me/topchetoeu/jscript/engine/Engine.java | 12 +- .../jscript/engine/Environment.java | 2 +- .../topchetoeu/jscript/engine/StackData.java | 19 +- .../jscript/engine/debug/DebugController.java | 43 + .../jscript/engine/debug/DebugHandler.java | 23 + .../jscript/engine/debug/DebugServer.java | 223 ++ .../jscript/engine/debug/Debugger.java | 5 + .../engine/debug/DebuggerProvider.java | 5 + .../jscript/engine/debug/HttpRequest.java | 104 + .../jscript/engine/debug/SimpleDebugger.java | 585 +++++ .../jscript/engine/debug/V8Error.java | 19 + .../jscript/engine/debug/V8Event.java | 22 + .../jscript/engine/debug/V8Message.java | 51 + .../jscript/engine/debug/V8Result.java | 22 + .../jscript/engine/debug/WebSocket.java | 208 ++ .../engine/debug/WebSocketMessage.java | 29 + .../jscript/engine/debug/protocol.json | 1962 +++++++++++++++++ .../jscript/engine/frame/CodeFrame.java | 35 +- .../jscript/engine/scope/LocalScope.java | 57 +- .../jscript/engine/values/ArrayValue.java | 2 +- .../jscript/engine/values/Values.java | 3 +- .../jscript/exceptions/EngineException.java | 10 +- .../exceptions/InterruptException.java | 2 +- .../exceptions/UncheckedIOException.java | 9 + .../jscript/interop/OverloadFunction.java | 4 +- src/me/topchetoeu/jscript/json/JSON.java | 22 +- .../topchetoeu/jscript/json/JSONElement.java | 14 +- src/me/topchetoeu/jscript/json/JSONList.java | 2 + src/me/topchetoeu/jscript/json/JSONMap.java | 10 +- src/me/topchetoeu/jscript/lib/ErrorLib.java | 13 +- src/me/topchetoeu/jscript/lib/Internals.java | 12 + src/me/topchetoeu/jscript/lib/JSONLib.java | 3 +- src/me/topchetoeu/jscript/lib/PromiseLib.java | 79 +- src/me/topchetoeu/jscript/lib/SymbolLib.java | 1 + .../topchetoeu/jscript/parsing/Parsing.java | 124 +- 54 files changed, 3779 insertions(+), 213 deletions(-) create mode 100644 src/assets/favicon.png create mode 100644 src/assets/index.html create mode 100644 src/me/topchetoeu/jscript/Filename.java create mode 100644 src/me/topchetoeu/jscript/Reading.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebugController.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebugHandler.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebugServer.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/Debugger.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/HttpRequest.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Error.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Event.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Message.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/V8Result.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/WebSocket.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/protocol.json create mode 100644 src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java diff --git a/build.js b/build.js index 34ab125..3906db9 100644 --- a/build.js +++ b/build.js @@ -10,6 +10,8 @@ const conf = { version: argv[3] }; +console.log(conf) + if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10); if (conf.version.startsWith('v')) conf.version = conf.version.substring(1); diff --git a/src/assets/favicon.png b/src/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..76661fd1edecc7883f125d53e7e4f652cfba0a25 GIT binary patch literal 1720 zcmV;p21ogcP)EX>4Tx04R}tkv&MmKpe$iTeVUu4t6NwkfAzR1Ql_tRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRx1kisICAVPqQIx48bLY!8O6cZWRPkQ)=9luB}nOqex zax9<*6_Voz|AXJ%n#HL}Hz}M1dS7h&V-yJP0jVfq16NwdUuy!hpQJZB zTI>iI+y*YLJDR))TQYi@7teVjf3S?Vf%0~{Oz zVeSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00fXpL_t(o!{wJvXjE4e$A8xu5@|BkMaTyVQ>555hyyd) zsG9%h!6@!NTU!&7xs-1 zO@by$-VkYx|1LW3`7$#xUv||$TsZH3obPk)eTG*6NwvUMU=i>(U;zWhm<0b7B-Kj# zUechXza>3aVH|(eg5QzUCuv;M$4`<<3h^^D5bOgQfql+gCX*qRO3~Zfi*4Hs4-Ydm zG(>rMIZKx=B^r$q3j2M?Sh_a#-%MS+CF z$+9dhS+eA%!T|*W0bRIoVL~W!`TA$z{!anY=`<%!oZ#-=y8r|N0S+EK$btn6+z{Np zeVfkCPDV#Zu`J8WYQ3Z;W6ZvpTK}$NLQ6}FYhg`IjoRAU@-nWWp~2nL(9lpQDep`A zQqsr%X$TJhtpO3f*|uG<=#$Chl+}^N zpYsX`E|K&JkOE3`*VnFH>(YaRgR`or`uci@{JKzpJ_MSbjPL5|;^D)G04!X%kf%?d zx*NyF#-gwq2?FAqd3Q=8M4e*;Wrq!QgeLc{ePNz9{?i_~>9m-=E2n1Yu zWMpJY1&WJ{i9{kqA`$O&3-ArVJdf}(SFUjJ;za;#+n%ig?*U%`U->=vc@5yk3gEzj1Drg0a>B{+ z@o`syIbyw}U!4$a1&VU5Dpcb$#mLAAJv}|Nwzjf+_il_abaZq8yzULbBIjx@EqzgW zPo6xPvjXSOpXb=IV|k@4X?#KwItYbAF5TVTJ!b{_`ug(bE?>S3aMc)d)$u^TvzV<> z0NUHz$z(E=l$6X!=}?^RaB%GIs3P!J0K|Ea14e{(tbc(oLH-xoz{t zjT@7;*_}Ig%CfAyR=i=u26s;~nao`t$d>%SWhf~E#Jv`*T)C3=_I8}A@HUaMvND%$ zZf@rK_3LchxRJ`rO77jeM^8@=H*emY%sJ!0o5mR1+XsHheVBNK^!N8q;1G>QS+{N- zmo8m$n^9w9rKx^VB_y?HLSEK4mdEn2l| zRo+dvc=2MFj>qHP5DgVl&<{-7_sh%6NhI?BErP)y!C;VZI80|}Cz(u!;^Jb;%E}0b z!xR-20Z>s<;Uw^5AU4^(uT!T^HX{oNvfLdd%slDN7>)4F-gy| + + + + + JScript Debugger + + +

+ This is the debugger of JScript. It implement the V8 Debugging protocol, + so you can use the devtools in chrome.
+ The debugger is still in early development, so please report any issues to + the github repo. +

+ +

+ Here are the available entrypoints: +

+

+ +

+ Developed by TopchetoEU, MIT License +

+ + \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/Filename.java b/src/me/topchetoeu/jscript/Filename.java new file mode 100644 index 0000000..ff7d17d --- /dev/null +++ b/src/me/topchetoeu/jscript/Filename.java @@ -0,0 +1,52 @@ +package me.topchetoeu.jscript; + +import java.io.File; + +public class Filename { + public final String protocol; + public final String path; + + public String toString() { + return protocol + "://" + path; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + protocol.hashCode(); + result = prime * result + path.hashCode(); + return result; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + + var other = (Filename) obj; + + if (protocol == null) { + if (other.protocol != null) return false; + } + else if (!protocol.equals(other.protocol)) return false; + + if (path == null) { + if (other.path != null) return false; + } + else if (!path.equals(other.path)) return false; + return true; + } + + public static Filename fromFile(File file) { + return new Filename("file", file.getAbsolutePath()); + } + + + public Filename(String protocol, String path) { + this.protocol = protocol; + this.path = path; + } +} diff --git a/src/me/topchetoeu/jscript/Location.java b/src/me/topchetoeu/jscript/Location.java index bec95d3..40b3138 100644 --- a/src/me/topchetoeu/jscript/Location.java +++ b/src/me/topchetoeu/jscript/Location.java @@ -1,18 +1,18 @@ package me.topchetoeu.jscript; -public class Location { - public static final Location INTERNAL = new Location(0, 0, ""); +public class Location implements Comparable { + public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "internal")); private int line; private int start; - private String filename; + private Filename filename; public int line() { return line; } public int start() { return start; } - public String filename() { return filename; } + public Filename filename() { return filename; } @Override public String toString() { - return filename + ":" + line + ":" + start; + return filename.toString() + ":" + line + ":" + start; } public Location add(int n, boolean clone) { @@ -55,7 +55,18 @@ public class Location { return true; } - public Location(int line, int start, String filename) { + @Override + public int compareTo(Location other) { + int a = filename.toString().compareTo(other.filename.toString()); + int b = Integer.compare(line, other.line); + int c = Integer.compare(start, other.start); + + if (a != 0) return a; + if (b != 0) return b; + return c; + } + + public Location(int line, int start, Filename filename) { this.line = line; this.start = start; this.filename = filename; diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index e34ddcf..80c6620 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -1,15 +1,15 @@ package me.topchetoeu.jscript; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.debug.DebugServer; +import me.topchetoeu.jscript.engine.debug.SimpleDebugger; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Observer; @@ -20,30 +20,10 @@ import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.lib.Internals; public class Main { - static Thread task; + static Thread engineTask, debugTask; static Engine engine; static Environment env; - public static String streamToString(InputStream in) { - try { - StringBuilder out = new StringBuilder(); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - for(var line = br.readLine(); line != null; line = br.readLine()) { - out.append(line).append('\n'); - } - - br.close(); - return out.toString(); - } - catch (Throwable e) { throw new UncheckedException(e); } - } - public static String resourceToString(String name) { - var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); - if (str == null) return null; - return streamToString(str); - } - private static Observer valuePrinter = new Observer() { public void next(Object data) { Values.printValue(null, data); @@ -56,17 +36,18 @@ public class Main { @Override public void finish() { - task.interrupt(); + 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)); - var in = new BufferedReader(new InputStreamReader(System.in)); engine = new Engine(); env = new Environment(null, null, null); var exited = new boolean[1]; + var server = new DebugServer(); + server.targets.put("target", (ws, req) -> SimpleDebugger.get(ws, engine)); engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> { new Internals().apply(env); @@ -77,7 +58,8 @@ public class Main { }); env.global.define("go", _ctx -> { try { - var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js")))); + var f = Path.of("do.js"); + var func = _ctx.compile(Filename.fromFile(f.toFile()), new String(Files.readAllBytes(f))); return func.call(_ctx); } catch (IOException e) { @@ -88,15 +70,17 @@ public class Main { return null; }), null); - task = engine.start(); + engineTask = engine.start(); + debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true); + var reader = new Thread(() -> { try { - while (true) { + for (var i = 0; ; i++) { try { - var raw = in.readLine(); + var raw = Reading.read(); if (raw == null) break; - engine.pushMsg(false, new Context(engine).pushEnv(env), "", raw, null).toObservable().once(valuePrinter); + valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await()); } catch (EngineException e) { Values.printError(e, ""); } } @@ -107,12 +91,13 @@ public class Main { System.out.println("Syntax error:" + ex.msg); } catch (RuntimeException ex) { - if (exited[0]) return; - System.out.println("Internal error ocurred:"); - ex.printStackTrace(); + if (!exited[0]) { + System.out.println("Internal error ocurred:"); + ex.printStackTrace(); + } } catch (Throwable e) { throw new UncheckedException(e); } - if (exited[0]) return; + if (exited[0]) debugTask.interrupt(); }); reader.setDaemon(true); reader.setName("STD Reader"); diff --git a/src/me/topchetoeu/jscript/Reading.java b/src/me/topchetoeu/jscript/Reading.java new file mode 100644 index 0000000..6d797c8 --- /dev/null +++ b/src/me/topchetoeu/jscript/Reading.java @@ -0,0 +1,36 @@ +package me.topchetoeu.jscript; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import me.topchetoeu.jscript.exceptions.UncheckedException; + +public class Reading { + private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + + public static synchronized String read() throws IOException { + return reader.readLine(); + } + + public static String streamToString(InputStream in) { + try { + StringBuilder out = new StringBuilder(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + for(var line = br.readLine(); line != null; line = br.readLine()) { + out.append(line).append('\n'); + } + + br.close(); + return out.toString(); + } + catch (Throwable e) { throw new UncheckedException(e); } + } + public static String resourceToString(String name) { + var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); + if (str == null) return null; + return streamToString(str); + } +} diff --git a/src/me/topchetoeu/jscript/compilation/CompileTarget.java b/src/me/topchetoeu/jscript/compilation/CompileTarget.java index 0a207ec..4b529cc 100644 --- a/src/me/topchetoeu/jscript/compilation/CompileTarget.java +++ b/src/me/topchetoeu/jscript/compilation/CompileTarget.java @@ -1,11 +1,15 @@ package me.topchetoeu.jscript.compilation; import java.util.Map; +import java.util.TreeSet; import java.util.Vector; +import me.topchetoeu.jscript.Location; + public class CompileTarget { public final Vector target = new Vector<>(); public final Map functions; + public final TreeSet breakpoints; public Instruction add(Instruction instr) { target.add(instr); @@ -14,6 +18,12 @@ public class CompileTarget { public Instruction set(int i, Instruction instr) { return target.set(i, instr); } + public void setDebug(int i) { + breakpoints.add(target.get(i).location); + } + public void setDebug() { + setDebug(target.size() - 1); + } public Instruction get(int i) { return target.get(i); } @@ -21,7 +31,8 @@ public class CompileTarget { public Instruction[] array() { return target.toArray(Instruction[]::new); } - public CompileTarget(Map functions) { + public CompileTarget(Map functions, TreeSet breakpoints) { this.functions = functions; + this.breakpoints = breakpoints; } } diff --git a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java index c76f6ce..cbee47b 100644 --- a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -25,7 +25,7 @@ public class CompoundStatement extends Statement { if (stm instanceof FunctionStatement) { int start = target.size(); ((FunctionStatement)stm).compile(target, scope, null, true); - target.get(start).setDebug(true); + target.setDebug(start); target.add(Instruction.discard()); } } diff --git a/src/me/topchetoeu/jscript/compilation/Instruction.java b/src/me/topchetoeu/jscript/compilation/Instruction.java index 4e3efe5..3099de9 100644 --- a/src/me/topchetoeu/jscript/compilation/Instruction.java +++ b/src/me/topchetoeu/jscript/compilation/Instruction.java @@ -90,16 +90,11 @@ public class Instruction { public final Type type; public final Object[] params; public Location location; - public boolean debugged; public Instruction locate(Location loc) { this.location = loc; return this; } - public Instruction setDebug(boolean debug) { - debugged = debug; - return this; - } @SuppressWarnings("unchecked") public T get(int i) { diff --git a/src/me/topchetoeu/jscript/compilation/Statement.java b/src/me/topchetoeu/jscript/compilation/Statement.java index 7b51b38..663ed12 100644 --- a/src/me/topchetoeu/jscript/compilation/Statement.java +++ b/src/me/topchetoeu/jscript/compilation/Statement.java @@ -14,7 +14,7 @@ public abstract class Statement { public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) { int start = target.size(); compile(target, scope, pollute); - if (target.size() != start) target.get(start).setDebug(true); + if (target.size() != start) target.setDebug(start); } public Location loc() { return _loc; } diff --git a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index 5ed0a1d..5e5b970 100644 --- a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -68,7 +68,8 @@ public class SwitchStatement extends Statement { var loc = target.get(el.getKey()).location; var i = stmIndexMap.get(el.getValue()); if (i == null) i = target.size(); - target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true)); + target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc)); + target.setDebug(el.getKey()); } target.add(Instruction.discard().locate(loc())); diff --git a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java index b63a93d..db34e44 100644 --- a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java @@ -22,7 +22,8 @@ public class CallStatement extends Statement { for (var arg : args) arg.compile(target, scope, true); - target.add(Instruction.call(args.length).locate(loc()).setDebug(true)); + target.add(Instruction.call(args.length).locate(loc())); + target.setDebug(); if (!pollute) target.add(Instruction.discard().locate(loc())); } diff --git a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java index ca11b27..e75f80e 100644 --- a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -51,7 +51,7 @@ public class FunctionStatement extends Statement { var subscope = scope.child(); int start = target.size(); - var funcTarget = new CompileTarget(target.functions); + var funcTarget = new CompileTarget(target.functions, target.breakpoints); subscope.define("this"); var argsVar = subscope.define("arguments"); diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java index 55a99cd..2cf8b00 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java @@ -24,14 +24,16 @@ public class IndexAssignStatement extends Statement { value.compile(target, scope, true); target.add(Instruction.operation(operation).locate(loc())); - target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true)); + target.add(Instruction.storeMember(pollute).locate(loc())); + target.setDebug(); } else { object.compile(target, scope, true); index.compile(target, scope, true); value.compile(target, scope, true); - target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true)); + target.add(Instruction.storeMember(pollute).locate(loc())); + target.setDebug(); } } diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java index 6159f4f..b2494d6 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -30,7 +30,7 @@ public class IndexStatement extends AssignableStatement { index.compile(target, scope, true); target.add(Instruction.loadMember().locate(loc())); - target.get(start).setDebug(true); + target.setDebug(start); if (!pollute) target.add(Instruction.discard().locate(loc())); } @Override diff --git a/src/me/topchetoeu/jscript/compilation/values/NewStatement.java b/src/me/topchetoeu/jscript/compilation/values/NewStatement.java index dbb632c..d60ef3a 100644 --- a/src/me/topchetoeu/jscript/compilation/values/NewStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/NewStatement.java @@ -16,7 +16,8 @@ public class NewStatement extends Statement { for (var arg : args) arg.compile(target, scope, true); - target.add(Instruction.callNew(args.length).locate(loc()).setDebug(true)); + target.add(Instruction.callNew(args.length).locate(loc())); + target.setDebug(); } public NewStatement(Location loc, Statement func, Statement ...args) { diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 366b0bc..44d7c32 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -1,7 +1,10 @@ package me.topchetoeu.jscript.engine; import java.util.Stack; +import java.util.TreeSet; +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.parsing.Parsing; @@ -23,9 +26,13 @@ public class Context { if (!env.empty()) this.env.pop(); } - public FunctionValue compile(String filename, String raw) { - var res = Values.toString(this, environment().compile.call(this, null, raw, filename)); - return Parsing.compile(engine.functions, environment(), filename, res); + public FunctionValue compile(Filename filename, String raw) { + var src = Values.toString(this, environment().compile.call(this, null, raw, filename)); + var debugger = StackData.getDebugger(this); + var breakpoints = new TreeSet(); + var res = Parsing.compile(engine.functions, breakpoints, environment(), filename, src); + if (debugger != null) debugger.onSource(filename, src, breakpoints); + return res; } public Context(Engine engine, Data data) { diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java index 828c25b..6c2d58f 100644 --- a/src/me/topchetoeu/jscript/engine/Data.java +++ b/src/me/topchetoeu/jscript/engine/Data.java @@ -35,8 +35,7 @@ public class Data { public T get(DataKey key, T val) { for (var it = this; it != null; it = it.parent) { if (it.data.containsKey(key)) { - this.set(key, val); - return (T)data.get((DataKey)key); + return (T)it.data.get((DataKey)key); } } @@ -45,7 +44,7 @@ public class Data { } public T get(DataKey key) { for (var it = this; it != null; it = it.parent) { - if (it.data.containsKey(key)) return (T)data.get((DataKey)key); + if (it.data.containsKey(key)) return (T)it.data.get((DataKey)key); } return null; } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 9176f7a..a9d638d 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.engine; import java.util.HashMap; import java.util.concurrent.LinkedBlockingDeque; +import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.events.Awaitable; @@ -11,7 +12,7 @@ import me.topchetoeu.jscript.exceptions.InterruptException; public class Engine { private class UncompiledFunction extends FunctionValue { - public final String filename; + public final Filename filename; public final String raw; private FunctionValue compiled = null; @@ -21,8 +22,8 @@ public class Engine { return compiled.call(ctx, thisArg, args); } - public UncompiledFunction(String filename, String raw) { - super(filename, 0); + public UncompiledFunction(Filename filename, String raw) { + super(filename + "", 0); this.filename = filename; this.raw = raw; } @@ -58,6 +59,7 @@ public class Engine { task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); } catch (RuntimeException e) { + if (e instanceof InterruptException) throw e; task.notifier.error(e); } } @@ -70,7 +72,7 @@ public class Engine { runTask(microTasks.take()); } } - catch (InterruptedException e) { + catch (InterruptedException | InterruptException e) { for (var msg : macroTasks) { msg.notifier.error(new InterruptException(e)); } @@ -103,7 +105,7 @@ public class Engine { else macroTasks.addLast(msg); return msg.notifier; } - public Awaitable pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) { + public Awaitable pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) { return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); } } diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 5afe1c2..30c055b 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -24,7 +24,7 @@ public class Environment { @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported.").setContext(ctx); + throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); }); public Environment addData(Data data) { diff --git a/src/me/topchetoeu/jscript/engine/StackData.java b/src/me/topchetoeu/jscript/engine/StackData.java index 873132e..820016a 100644 --- a/src/me/topchetoeu/jscript/engine/StackData.java +++ b/src/me/topchetoeu/jscript/engine/StackData.java @@ -4,14 +4,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import me.topchetoeu.jscript.engine.debug.DebugServer; +import me.topchetoeu.jscript.engine.debug.Debugger; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.exceptions.EngineException; public class StackData { public static final DataKey> FRAMES = new DataKey<>(); public static final DataKey MAX_FRAMES = new DataKey<>(); - public static final DataKey DEBUGGER = new DataKey<>(); + public static final DataKey DEBUGGER = new DataKey<>(); public static void pushFrame(Context ctx, CodeFrame frame) { var frames = ctx.data.get(FRAMES, new ArrayList<>()); @@ -25,16 +25,25 @@ public class StackData { if (frames.get(frames.size() - 1) != frame) return false; frames.remove(frames.size() - 1); ctx.popEnv(); + var dbg = getDebugger(ctx); + if (dbg != null) dbg.onFramePop(ctx, frame); return true; } + public static CodeFrame peekFrame(Context ctx) { + var frames = ctx.data.get(FRAMES, new ArrayList<>()); + if (frames.size() == 0) return null; + return frames.get(frames.size() - 1); + } public static List frames(Context ctx) { return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>())); } public static List stackTrace(Context ctx) { var res = new ArrayList(); + var frames = frames(ctx); - for (var el : frames(ctx)) { + for (var i = frames.size() - 1; i >= 0; i--) { + var el = frames.get(i); var name = el.function.name; var loc = el.function.loc(); var trace = ""; @@ -49,4 +58,8 @@ public class StackData { return res; } + + public static Debugger getDebugger(Context ctx) { + return ctx.data.get(DEBUGGER); + } } diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugController.java b/src/me/topchetoeu/jscript/engine/debug/DebugController.java new file mode 100644 index 0000000..beec045 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/DebugController.java @@ -0,0 +1,43 @@ +package me.topchetoeu.jscript.engine.debug; + +import java.util.TreeSet; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.exceptions.EngineException; + +public interface DebugController { + /** + * Called when a script has been loaded + * @param breakpoints + */ + void onSource(Filename filename, String source, TreeSet breakpoints); + + /** + * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. + * This function might pause in order to await debugging commands. + * @param ctx The context of execution + * @param frame The frame in which execution is occuring + * @param instruction The instruction which was or will be executed + * @param loc The most recent location the code frame has been at + * @param returnVal The return value of the instruction, Runners.NO_RETURN if none + * @param error The error that the instruction threw, null if none + * @param caught Whether or not the error has been caught + * @return Whether or not the frame should restart + */ + boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); + + /** + * Called immediatly after a frame has been popped out of the frame stack. + * This function might pause in order to await debugging commands. + * @param ctx The context of execution + * @param frame The code frame which was popped out + */ + void onFramePop(Context ctx, CodeFrame frame); + + void connect(); + void disconnect(); +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java new file mode 100644 index 0000000..b10f961 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java @@ -0,0 +1,23 @@ +package me.topchetoeu.jscript.engine.debug; + +public interface DebugHandler { + void enable(V8Message msg); + void disable(V8Message msg); + + void setBreakpoint(V8Message msg); + void setBreakpointByUrl(V8Message msg); + void removeBreakpoint(V8Message msg); + void continueToLocation(V8Message msg); + + void getScriptSource(V8Message msg); + void getPossibleBreakpoints(V8Message msg); + + void resume(V8Message msg); + void pause(V8Message msg); + + void stepInto(V8Message msg); + void stepOut(V8Message msg); + void stepOver(V8Message msg); + + void setPauseOnExceptions(V8Message msg); +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java new file mode 100644 index 0000000..54eade4 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -0,0 +1,223 @@ +package me.topchetoeu.jscript.engine.debug; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.MessageDigest; +import java.util.Base64; +import java.util.HashMap; + +import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; +import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.exceptions.UncheckedException; +import me.topchetoeu.jscript.exceptions.UncheckedIOException; +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONList; +import me.topchetoeu.jscript.json.JSONMap; + +public class DebugServer { + public static String browserDisplayName = "jscript"; + + public final HashMap targets = new HashMap<>(); + + private final byte[] favicon, index; + + private static void send(HttpRequest req, String val) throws IOException { + req.writeResponse(200, "OK", "application/json", val.getBytes()); + } + + // SILENCE JAVA + private MessageDigest getDigestInstance() { + try { + return MessageDigest.getInstance("sha1"); + } + catch (Throwable e) { throw new UncheckedException(e); } + + } + + private static Thread runAsync(Runnable func, String name) { + var res = new Thread(func); + res.setName(name); + res.start(); + return res; + } + + private void handle(WebSocket ws, Debugger debugger) { + WebSocketMessage raw; + + debugger.connect(); + + while ((raw = ws.receive()) != null) { + if (raw.type != Type.Text) { + ws.send(new V8Error("Expected a text message.")); + continue; + } + + V8Message msg; + + try { + msg = new V8Message(raw.textData()); + System.out.println(msg.name + ": " + JSON.stringify(msg.params)); + } + catch (SyntaxException e) { + ws.send(new V8Error(e.getMessage())); + return; + } + + try { + switch (msg.name) { + case "Debugger.enable": debugger.enable(msg); continue; + case "Debugger.disable": debugger.disable(msg); continue; + + case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue; + case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue; + case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue; + case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue; + + case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue; + case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue; + + case "Debugger.resume": debugger.resume(msg); continue; + case "Debugger.pause": debugger.pause(msg); continue; + + case "Debugger.stepInto": debugger.stepInto(msg); continue; + case "Debugger.stepOut": debugger.stepOut(msg); continue; + case "Debugger.stepOver": debugger.stepOver(msg); continue; + + case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue; + } + + if ( + msg.name.startsWith("DOM.") || + msg.name.startsWith("DOMDebugger.") || + msg.name.startsWith("Emulation.") || + msg.name.startsWith("Input.") || + msg.name.startsWith("Network.") || + msg.name.startsWith("Page.") + ) ws.send(new V8Error("This isn't a browser...")); + + if (msg.name.startsWith("Runtime.") || msg.name.startsWith("Profiler.")) ws.send(new V8Error("This API is not supported yet.")); + } + catch (Throwable e) { + e.printStackTrace(); + throw new UncheckedException(e); + } + } + + debugger.disconnect(); + } + private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) { + var key = req.headers.get("sec-websocket-key"); + + if (key == null) { + req.writeResponse( + 426, "Upgrade Required", "text/txt", + "Expected a WS upgrade".getBytes() + ); + return; + } + + var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest( + (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes() + )); + + req.writeCode(101, "Switching Protocols"); + req.writeHeader("Connection", "Upgrade"); + req.writeHeader("Sec-WebSocket-Accept", resKey); + req.writeLastHeader("Upgrade", "WebSocket"); + + var ws = new WebSocket(socket); + var debugger = debuggerProvider.getDebugger(ws, req); + + if (debugger == null) { + ws.close(); + return; + } + + runAsync(() -> { + try { handle(ws, debugger); } + catch (RuntimeException e) { + ws.send(new V8Error(e.getMessage())); + } + finally { ws.close(); debugger.disconnect(); } + }, "Debug Handler"); + } + + public void run(InetSocketAddress address) { + try { + ServerSocket server = new ServerSocket(); + server.bind(address); + + try { + while (true) { + var socket = server.accept(); + var req = HttpRequest.read(socket); + + if (req == null) continue; + + switch (req.path) { + case "/json/version": + send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}"); + break; + case "/json/list": + case "/json": { + var res = new JSONList(); + + for (var el : targets.entrySet()) { + res.add(new JSONMap() + .set("description", "JScript debugger") + .set("favicon", "/favicon.ico") + .set("id", el.getKey()) + .set("type", "node") + .set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey()) + ); + } + send(req, JSON.stringify(res)); + break; + } + case "/json/new": + case "/json/activate": + case "/json/protocol": + case "/json/close": + case "/devtools/inspector.html": + req.writeResponse( + 501, "Not Implemented", "text/txt", + "This feature isn't (and probably won't be) implemented.".getBytes() + ); + break; + case "/": + case "/index.html": + req.writeResponse(200, "OK", "text/html", index); + break; + case "/favicon.ico": + req.writeResponse(200, "OK", "image/png", favicon); + break; + default: + if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) { + onWsConnect(req, socket, targets.get(req.path.substring(1))); + } + break; + } + } + } + finally { server.close(); } + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + public Thread start(InetSocketAddress address, boolean daemon) { + var res = new Thread(() -> run(address), "Debug Server"); + res.setDaemon(daemon); + res.start(); + return res; + } + + public DebugServer() { + try { + this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes(); + this.index = getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes(); + } + catch (IOException e) { throw new UncheckedIOException(e); } + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/Debugger.java b/src/me/topchetoeu/jscript/engine/debug/Debugger.java new file mode 100644 index 0000000..3c704af --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/Debugger.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.engine.debug; + +public interface Debugger extends DebugHandler, DebugController { + +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java b/src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java new file mode 100644 index 0000000..cd15ffc --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/DebuggerProvider.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.engine.debug; + +public interface DebuggerProvider { + Debugger getDebugger(WebSocket socket, HttpRequest req); +} diff --git a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java new file mode 100644 index 0000000..d327d8e --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java @@ -0,0 +1,104 @@ +package me.topchetoeu.jscript.engine.debug; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.util.HashMap; +import java.util.IllegalFormatException; +import java.util.Map; + +import me.topchetoeu.jscript.exceptions.UncheckedIOException; + +public class HttpRequest { + public final String method; + public final String path; + public final Map headers; + public final OutputStream out; + + + public void writeCode(int code, String name) { + try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); } + catch (IOException e) { throw new UncheckedIOException(e); } + } + public void writeHeader(String name, String value) { + try { out.write((name + ": " + value + "\r\n").getBytes()); } + catch (IOException e) { throw new UncheckedIOException(e); } + } + public void writeLastHeader(String name, String value) { + try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); } + catch (IOException e) { throw new UncheckedIOException(e); } + } + public void writeHeadersEnd() { + try { out.write("\n".getBytes()); } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + public void writeResponse(int code, String name, String type, byte[] data) { + writeCode(code, name); + writeHeader("Content-Type", type); + writeLastHeader("Content-Length", data.length + ""); + try { + out.write(data); + out.close(); + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + public void writeResponse(int code, String name, String type, InputStream data) { + try { + writeResponse(code, name, type, data.readAllBytes()); + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + public HttpRequest(String method, String path, Map headers, OutputStream out) { + this.method = method; + this.path = path; + this.headers = headers; + this.out = out; + } + + // We dont need no http library + public static HttpRequest read(Socket socket) { + try { + var str = socket.getInputStream(); + var lines = new BufferedReader(new InputStreamReader(str)); + var line = lines.readLine(); + var i1 = line.indexOf(" "); + var i2 = line.indexOf(" ", i1 + 1); + + if (i1 < 0 || i2 < 0) { + socket.close(); + return null; + } + + var method = line.substring(0, i1).trim().toUpperCase(); + var path = line.substring(i1 + 1, i2).trim(); + var headers = new HashMap(); + + while (!(line = lines.readLine()).isEmpty()) { + var i = line.indexOf(":"); + if (i < 0) continue; + var name = line.substring(0, i).trim().toLowerCase(); + var value = line.substring(i + 1).trim(); + + if (name.length() == 0) continue; + headers.put(name, value); + } + + if (headers.containsKey("content-length")) { + try { + var i = Integer.parseInt(headers.get("content-length")); + str.skip(i); + } + catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ } + } + + return new HttpRequest(method, path, headers, socket.getOutputStream()); + } + catch (IOException e) { throw new UncheckedIOException(e); } + } +} + diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java new file mode 100644 index 0000000..96dbe93 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -0,0 +1,585 @@ +package me.topchetoeu.jscript.engine.debug; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Random; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.StackData; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.frame.Runners; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.CodeFunction; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Symbol; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.events.Notifier; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.json.JSONElement; +import me.topchetoeu.jscript.json.JSONList; +import me.topchetoeu.jscript.json.JSONMap; +import me.topchetoeu.jscript.lib.DateLib; +import me.topchetoeu.jscript.lib.MapLib; +import me.topchetoeu.jscript.lib.PromiseLib; +import me.topchetoeu.jscript.lib.RegExpLib; +import me.topchetoeu.jscript.lib.SetLib; +import me.topchetoeu.jscript.lib.GeneratorLib.Generator; + +public class SimpleDebugger implements Debugger { + private static enum State { + RESUMED, + STEPPING_IN, + STEPPING_OUT, + STEPPING_OVER, + PAUSED_NORMAL, + PAUSED_EXCEPTION, + } + private static enum CatchType { + NONE, + UNCAUGHT, + ALL, + } + private static class Source { + public final int id; + public final Filename filename; + public final String source; + public final TreeSet breakpoints; + + public Source(int id, Filename filename, String source, TreeSet breakpoints) { + this.id = id; + this.filename = filename; + this.source = source; + this.breakpoints = breakpoints; + } + } + private static class Breakpoint { + public final int id; + public final Location location; + public final String condition; + + public Breakpoint(int id, Location location, String condition) { + this.id = id; + this.location = location; + this.condition = condition; + } + } + private static class BreakpointCandidate { + public final int id; + public final String condition; + public final Pattern pattern; + public final int line, start; + public final HashSet resolvedBreakpoints = new HashSet<>(); + + public BreakpointCandidate(int id, Pattern pattern, int line, int start, String condition) { + this.id = id; + this.pattern = pattern; + this.line = line; + this.start = start; + this.condition = condition; + } + } + private class Frame { + public CodeFrame frame; + public CodeFunction func; + public int id; + public ObjectValue local = new ObjectValue(), capture = new ObjectValue(), global; + public JSONMap serialized; + public Location location; + + public void updateLoc(Location loc) { + serialized.set("location", serializeLocation(loc)); + this.location = loc; + } + + public Frame(Context ctx, CodeFrame frame, int id) { + this.frame = frame; + this.func = frame.function; + this.id = id; + this.local = new ObjectValue(); + this.location = frame.function.loc(); + + this.global = frame.function.environment.global.obj; + frame.scope.applyToObject(ctx, this.local, this.capture, true); + + this.serialized = new JSONMap() + .set("callFrameId", id + "") + .set("functionName", func.name) + .set("location", serializeLocation(func.loc())) + .set("scopeChain", new JSONList() + .add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local))) + .add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture))) + .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global))) + ) + .setNull("this"); + } + } + + public boolean enabled = false; + public CatchType execptionType = CatchType.ALL; + public State state = State.RESUMED; + + public final WebSocket ws; + public final Engine target; + + private HashMap idToBptCand = new HashMap<>(); + + private HashMap idToBreakpoint = new HashMap<>(); + private HashMap locToBreakpoint = new HashMap<>(); + private HashSet tmpBreakpts = new HashSet<>(); + + private HashMap filenameToId = new HashMap<>(); + private HashMap idToSource = new HashMap<>(); + private ArrayList pendingSources = new ArrayList<>(); + + private HashMap idToFrame = new HashMap<>(); + private HashMap codeFrameToFrame = new HashMap<>(); + + private HashMap idToObject = new HashMap<>(); + private HashMap objectToId = new HashMap<>(); + + private Notifier updateNotifier = new Notifier(); + + private int nextId = new Random().nextInt() & 0x7FFFFFFF; + private Location prevLocation = null; + private Frame stepOutFrame = null, currFrame = null; + + private int nextId() { + return nextId++ ^ 1630022591 /* big prime */; + } + + private void updateFrames(Context ctx) { + var frame = StackData.peekFrame(ctx); + if (frame == null) return; + + if (!codeFrameToFrame.containsKey(frame)) { + var id = nextId(); + var fr = new Frame(ctx, frame, id); + + idToFrame.put(id, fr); + codeFrameToFrame.put(frame, fr); + } + + currFrame = codeFrameToFrame.get(frame); + } + private JSONList serializeFrames(Context ctx) { + var res = new JSONList(); + var frames = StackData.frames(ctx); + + for (var i = frames.size() - 1; i >= 0; i--) { + res.add(codeFrameToFrame.get(frames.get(i)).serialized); + } + + return res; + } + + private Location correctLocation(Source source, Location loc) { + var set = source.breakpoints; + + if (set.contains(loc)) return loc; + + var tail = set.tailSet(loc); + if (tail.isEmpty()) return null; + + return tail.first(); + } + public Location deserializeLocation(JSONElement el, boolean correct) { + if (!el.isMap()) throw new RuntimeException("Expected location to be a map."); + var id = Integer.parseInt(el.map().string("scriptId")); + var line = (int)el.map().number("lineNumber") + 1; + var column = (int)el.map().number("columnNumber") + 1; + + if (!idToSource.containsKey(id)) throw new RuntimeException("The specified source %s doesn't exist.".formatted(id)); + + var res = new Location(line, column, idToSource.get(id).filename); + if (correct) res = correctLocation(idToSource.get(id), res); + return res; + } + public JSONMap serializeLocation(Location loc) { + var source = filenameToId.get(loc.filename()); + return new JSONMap() + .set("scriptId", source + "") + .set("lineNumber", loc.line() - 1) + .set("columnNumber", loc.start() - 1); + } + + private Integer objectId(ObjectValue obj) { + if (objectToId.containsKey(obj)) return objectToId.get(obj); + else { + int id = nextId(); + objectToId.put(obj, id); + idToObject.put(id, obj); + return id; + } + } + private JSONMap serializeObj(Context ctx, Object val) { + val = Values.normalize(null, val); + + if (val == Values.NULL) { + return new JSONMap() + .set("objectId", objectId(null) + "") + .set("type", "object") + .set("subtype", "null") + .setNull("value") + .set("description", "null"); + } + + if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + var id = objectId(obj); + var type = "object"; + String subtype = null; + String className = null; + + if (obj instanceof FunctionValue) type = "function"; + if (obj instanceof ArrayValue) subtype = "array"; + if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp"; + if (Values.isWrapper(val, DateLib.class)) subtype = "date"; + if (Values.isWrapper(val, MapLib.class)) subtype = "map"; + if (Values.isWrapper(val, SetLib.class)) subtype = "set"; + if (Values.isWrapper(val, Generator.class)) subtype = "generator"; + if (Values.isWrapper(val, PromiseLib.class)) subtype = "promise"; + + try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); } + catch (Exception e) { } + + var res = new JSONMap() + .set("type", type) + .set("objetId", id + ""); + + if (subtype != null) res.set("subtype", subtype); + if (className != null) res.set("className", className); + + return res; + } + + if (val == null) return new JSONMap().set("type", "undefined"); + if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val); + if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val); + if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString()); + if (val instanceof Number) { + var num = (double)(Number)val; + var res = new JSONMap().set("type", "number"); + + if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity"); + else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity"); + else if (num == -0.) res.set("unserializableValue", "-0"); + else if (Double.isNaN(num)) res.set("unserializableValue", "NaN"); + else res.set("value", num); + + return res; + } + + throw new IllegalArgumentException("Unexpected JS object."); + } + + private void resume(State state) { + this.state = state; + ws.send(new V8Event("Debugger.resumed", new JSONMap())); + updateNotifier.next(); + } + private void pauseDebug(Context ctx, Breakpoint bp) { + state = State.PAUSED_NORMAL; + var map = new JSONMap() + .set("callFrames", serializeFrames(ctx)) + .set("reason", "debugCommand"); + + if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + "")); + ws.send(new V8Event("Debugger.paused", map)); + } + private void pauseException(Context ctx) { + state = State.PAUSED_NORMAL; + var map = new JSONMap() + .set("callFrames", serializeFrames(ctx)) + .set("reason", "exception"); + + ws.send(new V8Event("Debugger.paused", map)); + } + + private void sendSource(Source src) { + ws.send(new V8Event("Debugger.scriptParsed", new JSONMap() + .set("scriptId", src.id + "") + .set("hash", src.source.hashCode()) + .set("url", src.filename + "") + )); + } + + private void addBreakpoint(Breakpoint bpt) { + idToBreakpoint.put(bpt.id, bpt); + locToBreakpoint.put(bpt.location, bpt); + + ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap() + .set("breakpointId", bpt.id) + .set("location", serializeLocation(bpt.location)) + )); + } + + @Override public void enable(V8Message msg) { + enabled = true; + ws.send(msg.respond()); + + for (var el : pendingSources) sendSource(el); + pendingSources.clear(); + + updateNotifier.next(); + } + @Override public void disable(V8Message msg) { + enabled = false; + ws.send(msg.respond()); + updateNotifier.next(); + } + + @Override public void getScriptSource(V8Message msg) { + int id = Integer.parseInt(msg.params.string("scriptId")); + ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); + } + @Override public void getPossibleBreakpoints(V8Message msg) { + var start = deserializeLocation(msg.params.get("start"), false); + var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null; + var src = idToSource.get(filenameToId.get(start.filename())); + + var res = new JSONList(); + + for (var loc : src.breakpoints.tailSet(start, true)) { + if (end != null && loc.compareTo(end) > 0) break; + res.add(serializeLocation(loc)); + } + + ws.send(msg.respond(new JSONMap().set("locations", res))); + } + + @Override public void pause(V8Message msg) { + } + + @Override public void resume(V8Message msg) { + resume(State.RESUMED); + ws.send(msg.respond(new JSONMap())); + } + + @Override public void setBreakpoint(V8Message msg) { + // int id = nextId(); + // var loc = deserializeLocation(msg.params.get("location"), true); + // var bpt = new Breakpoint(id, loc, null); + // breakpoints.put(loc, bpt); + // idToBrpt.put(id, bpt); + // ws.send(msg.respond(new JSONMap() + // .set("breakpointId", id) + // .set("actualLocation", serializeLocation(loc)) + // )); + } + @Override public void setBreakpointByUrl(V8Message msg) { + var line = (int)msg.params.number("lineNumber") + 1; + var col = (int)msg.params.number("columnNumber", 0) + 1; + + Pattern regex; + + if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url"))); + else regex = Pattern.compile(msg.params.string("urlRegex")); + + var bpcd = new BreakpointCandidate(nextId(), regex, line, col, null); + idToBptCand.put(bpcd.id, bpcd); + + var locs = new JSONList(); + + for (var src : idToSource.values()) { + if (regex.matcher(src.filename.toString()).matches()) { + var loc = correctLocation(src, new Location(line, col, src.filename)); + if (loc == null) continue; + var bp = new Breakpoint(nextId(), loc, bpcd.condition); + + bpcd.resolvedBreakpoints.add(bp); + locs.add(serializeLocation(loc)); + addBreakpoint(bp); + } + } + + ws.send(msg.respond(new JSONMap() + .set("breakpointId", bpcd.id + "") + .set("locations", locs) + )); + } + @Override public void removeBreakpoint(V8Message msg) { + var id = Integer.parseInt(msg.params.string("breakpointId")); + + if (idToBptCand.containsKey(id)) { + var bpcd = idToBptCand.get(id); + for (var bp : bpcd.resolvedBreakpoints) { + idToBreakpoint.remove(bp.id); + locToBreakpoint.remove(bp.location); + } + idToBptCand.remove(id); + } + else if (idToBreakpoint.containsKey(id)) { + var bp = idToBreakpoint.remove(id); + locToBreakpoint.remove(bp.location); + } + ws.send(msg.respond()); + } + @Override public void continueToLocation(V8Message msg) { + var loc = deserializeLocation(msg.params.get("location"), true); + + tmpBreakpts.add(loc); + + resume(State.RESUMED); + ws.send(msg.respond()); + } + + @Override public void setPauseOnExceptions(V8Message msg) { + ws.send(new V8Error("i dont wanna to")); + } + + @Override public void stepInto(V8Message msg) { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + prevLocation = currFrame.location; + stepOutFrame = currFrame; + resume(State.STEPPING_IN); + ws.send(msg.respond()); + } + } + @Override public void stepOut(V8Message msg) { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + prevLocation = currFrame.location; + stepOutFrame = currFrame; + resume(State.STEPPING_OUT); + ws.send(msg.respond()); + } + } + @Override public void stepOver(V8Message msg) { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + prevLocation = currFrame.location; + stepOutFrame = currFrame; + resume(State.STEPPING_OVER); + ws.send(msg.respond()); + } + } + + @Override public void onSource(Filename filename, String source, TreeSet locations) { + int id = nextId(); + var src = new Source(id, filename, source, locations); + + filenameToId.put(filename, id); + idToSource.put(id, src); + + for (var bpcd : idToBptCand.values()) { + if (!bpcd.pattern.matcher(filename.toString()).matches()) continue; + var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename)); + var bp = new Breakpoint(nextId(), loc, bpcd.condition); + if (loc == null) continue; + bpcd.resolvedBreakpoints.add(bp); + addBreakpoint(bp); + } + + if (!enabled) pendingSources.add(src); + else sendSource(src); + } + @Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + if (!enabled) return false; + + updateFrames(ctx); + var frame = codeFrameToFrame.get(cf); + if (instruction.location != null) frame.updateLoc(instruction.location); + var loc = frame.location; + var isBreakpointable = loc != null && ( + idToSource.get(filenameToId.get(loc.filename())).breakpoints.contains(loc) || + returnVal != Runners.NO_RETURN + ); + + if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null; + + if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { + pauseException(ctx); + } + else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { + pauseDebug(ctx, locToBreakpoint.get(loc)); + } + else if (isBreakpointable && tmpBreakpts.contains(loc)) { + pauseDebug(ctx, null); + tmpBreakpts.remove(loc); + } + else if (instruction.type == Type.NOP && instruction.match("debug")) { + pauseDebug(ctx, null); + } + + while (enabled) { + switch (state) { + case PAUSED_EXCEPTION: + case PAUSED_NORMAL: break; + + case STEPPING_OUT: + case RESUMED: return false; + case STEPPING_IN: + if (!prevLocation.equals(loc)) { + if (isBreakpointable) pauseDebug(ctx, null); + else if (returnVal != Runners.NO_RETURN) pauseDebug(ctx, null); + else return false; + } + else return false; + break; + case STEPPING_OVER: + if ( + stepOutFrame == frame && ( + !loc.filename().equals(prevLocation.filename()) || + loc.line() != prevLocation.line() + ) + ) pauseDebug(ctx, null); + else return false; + break; + } + updateNotifier.await(); + } + + return false; + } + @Override public void onFramePop(Context ctx, CodeFrame frame) { + updateFrames(ctx); + + try { + idToFrame.remove(codeFrameToFrame.remove(frame).id); + } + catch (NullPointerException e) { } + + if (StackData.frames(ctx).size() == 0) resume(State.RESUMED); + else if (stepOutFrame != null && stepOutFrame.frame == frame && + (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) + ) { + pauseDebug(ctx, null); + updateNotifier.await(); + } + } + + @Override public void connect() { + target.data.set(StackData.DEBUGGER, this); + } + @Override public void disconnect() { + target.data.remove(StackData.DEBUGGER); + enabled = false; + updateNotifier.next(); + } + + private SimpleDebugger(WebSocket ws, Engine target) { + this.ws = ws; + this.target = target; + } + + public static SimpleDebugger get(WebSocket ws, Engine target) { + if (target.data.has(StackData.DEBUGGER)) { + ws.send(new V8Error("A debugger is already attached to this engine.")); + return null; + } + else { + var res = new SimpleDebugger(ws, target); + return res; + } + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Error.java b/src/me/topchetoeu/jscript/engine/debug/V8Error.java new file mode 100644 index 0000000..854e1cd --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/V8Error.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.engine.debug; + +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Error { + public final String message; + + public V8Error(String message) { + this.message = message; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap().set("error", new JSONMap() + .set("message", message) + )); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Event.java b/src/me/topchetoeu/jscript/engine/debug/V8Event.java new file mode 100644 index 0000000..a83e20b --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/V8Event.java @@ -0,0 +1,22 @@ +package me.topchetoeu.jscript.engine.debug; + +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Event { + public final String name; + public final JSONMap params; + + public V8Event(String name, JSONMap params) { + this.name = name; + this.params = params; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("method", name) + .set("params", params) + ); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Message.java b/src/me/topchetoeu/jscript/engine/debug/V8Message.java new file mode 100644 index 0000000..82aa121 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/V8Message.java @@ -0,0 +1,51 @@ +package me.topchetoeu.jscript.engine.debug; + +import java.util.Map; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONElement; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Message { + public final String name; + public final int id; + public final JSONMap params; + + public V8Message(String name, int id, Map params) { + this.name = name; + this.params = new JSONMap(params); + this.id = id; + } + public V8Result respond(JSONMap result) { + return new V8Result(id, result); + } + public V8Result respond() { + return new V8Result(id, new JSONMap()); + } + + public V8Message(JSONMap raw) { + if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'."); + if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'."); + + this.name = raw.string("method"); + this.id = (int)raw.number("id"); + this.params = raw.contains("params") ? raw.map("params") : new JSONMap(); + } + public V8Message(String raw) { + this(JSON.parse(new Filename("jscript", "json-msg"), raw).map()); + } + + public JSONMap toMap() { + var res = new JSONMap(); + return res; + } + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("method", name) + .set("params", params) + .set("id", id) + ); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Result.java b/src/me/topchetoeu/jscript/engine/debug/V8Result.java new file mode 100644 index 0000000..f605cef --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/V8Result.java @@ -0,0 +1,22 @@ +package me.topchetoeu.jscript.engine.debug; + +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Result { + public final int id; + public final JSONMap result; + + public V8Result(int id, JSONMap result) { + this.id = id; + this.result = result; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("id", id) + .set("result", result) + ); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java new file mode 100644 index 0000000..15001ba --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java @@ -0,0 +1,208 @@ +package me.topchetoeu.jscript.engine.debug; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; +import me.topchetoeu.jscript.exceptions.UncheckedException; +import me.topchetoeu.jscript.exceptions.UncheckedIOException; + +public class WebSocket implements AutoCloseable { + public long maxLength = 2000000; + + private Socket socket; + private boolean closed = false; + + private OutputStream out() { + try { return socket.getOutputStream(); } + catch (IOException e) { throw new UncheckedIOException(e); } + } + private InputStream in() { + try { return socket.getInputStream(); } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + private long readLen(int byteLen) { + long res = 0; + + try { + if (byteLen == 126) { + res |= in().read() << 8; + res |= in().read(); + return res; + } + else if (byteLen == 127) { + res |= in().read() << 56; + res |= in().read() << 48; + res |= in().read() << 40; + res |= in().read() << 32; + res |= in().read() << 24; + res |= in().read() << 16; + res |= in().read() << 8; + res |= in().read(); + return res; + } + else return byteLen; + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + private byte[] readMask(boolean has) { + if (has) { + try { return new byte[] { + (byte)in().read(), + (byte)in().read(), + (byte)in().read(), + (byte)in().read() + }; } + catch (IOException e) { throw new UncheckedIOException(e); } + } + else return new byte[4]; + } + + private void writeLength(long len) { + try { + if (len < 126) { + out().write((int)len); + } + else if (len < 0xFFFF) { + out().write(126); + out().write((int)(len >> 8) & 0xFF); + out().write((int)len & 0xFF); + } + else { + out().write(127); + out().write((int)(len >> 56) & 0xFF); + out().write((int)(len >> 48) & 0xFF); + out().write((int)(len >> 40) & 0xFF); + out().write((int)(len >> 32) & 0xFF); + out().write((int)(len >> 24) & 0xFF); + out().write((int)(len >> 16) & 0xFF); + out().write((int)(len >> 8) & 0xFF); + out().write((int)len & 0xFF); + } + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + private synchronized void write(int type, byte[] data) { + try { + out().write(type | 0x80); + writeLength(data.length); + for (int i = 0; i < data.length; i++) { + out().write(data[i]); + } + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + public void send(String data) { + if (closed) throw new IllegalStateException("Object is closed."); + write(1, data.getBytes()); + } + public void send(byte[] data) { + if (closed) throw new IllegalStateException("Object is closed."); + write(2, data); + } + public void send(WebSocketMessage msg) { + if (msg.type == Type.Binary) send(msg.binaryData()); + else send(msg.textData()); + } + public void send(Object data) { + if (closed) throw new IllegalStateException("Object is closed."); + write(1, data.toString().getBytes()); + } + + public void close(String reason) { + if (socket != null) { + try { + write(8, reason.getBytes()); + socket.close(); + } + catch (Throwable e) { } + } + + socket = null; + closed = true; + } + public void close() { + close(""); + } + + private WebSocketMessage fail(String reason) { + System.out.println("WebSocket Error: " + reason); + close(reason); + return null; + } + + private byte[] readData() { + try { + var maskLen = in().read(); + var hasMask = (maskLen & 0x80) != 0; + var len = (int)readLen(maskLen & 0x7F); + var mask = readMask(hasMask); + + if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size"); + else { + var buff = new byte[len]; + + if (in().read(buff) < len) fail("WebSocket Error: payload too short"); + else { + for (int i = 0; i < len; i++) { + buff[i] ^= mask[(int)(i % 4)]; + } + return buff; + } + } + + return null; + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + public WebSocketMessage receive() { + try { + var data = new ByteArrayOutputStream(); + var type = 0; + + while (socket != null && !closed) { + var finId = in().read(); + if (finId < 0) break; + var fin = (finId & 0x80) != 0; + int id = finId & 0x0F; + + if (id == 0x8) { close(); return null; } + if (id >= 0x8) { + if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented"); + if (id == 0x9) write(0xA, data.toByteArray()); + continue; + } + + if (type == 0) type = id; + if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment"); + + var buff = readData(); + if (buff == null) break; + + if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size"); + data.write(buff); + + if (!fin) continue; + var raw = data.toByteArray(); + + if (type == 1) return new WebSocketMessage(new String(raw)); + else return new WebSocketMessage(raw); + } + } + catch (IOException e) { + close(); + } + + return null; + } + + public WebSocket(Socket socket) { + this.socket = socket; + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java b/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java new file mode 100644 index 0000000..e0b82b5 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java @@ -0,0 +1,29 @@ +package me.topchetoeu.jscript.engine.debug; + +public class WebSocketMessage { + public static enum Type { + Text, + Binary, + } + + public final Type type; + private final Object data; + + public final String textData() { + if (type != Type.Text) throw new IllegalStateException("Message is not text."); + return (String)data; + } + public final byte[] binaryData() { + if (type != Type.Binary) throw new IllegalStateException("Message is not binary."); + return (byte[])data; + } + + public WebSocketMessage(String data) { + this.type = Type.Text; + this.data = data; + } + public WebSocketMessage(byte[] data) { + this.type = Type.Binary; + this.data = data; + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/protocol.json b/src/me/topchetoeu/jscript/engine/debug/protocol.json new file mode 100644 index 0000000..0470b51 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/protocol.json @@ -0,0 +1,1962 @@ +{ + "version": { + "major": "1", + "minor": "1" + }, + "domains": [ + { + "domain": "Debugger", + "description": "Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing\nbreakpoints, stepping through execution, exploring stack traces, etc.", + "dependencies": [ "Runtime" ], + "types": [ + { "id": "BreakpointId", + "description": "Breakpoint identifier.", + "type": "string" + }, + { "id": "CallFrameId", + "description": "Call frame identifier.", + "type": "string" + }, + { "id": "CallFrame", + "description": "JavaScript call frame. Array of call frames form the call stack.", + "type": "object", + "properties": [ + { "name": "callFrameId", + "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused.", + "$ref": "CallFrameId" + }, + { "name": "functionName", + "description": "Name of the JavaScript function called on this call frame.", + "type": "string" + }, + { "name": "location", + "description": "Location of the defining function.", + "$ref": "Location" + }, + { "name": "scopeChain", + "description": "Scope chain for this call frame.", + "type": "array", + "items": { "$ref": "Scope" } + }, + { "name": "this", + "description": "Always undefined, since `this` is a variable.", + "deprecated": true, + "$ref": "Runtime.RemoteObject" + }, + { "name": "returnValue", + "description": "The value being returned, if the function is at return point.", + "optional": true, + "$ref": "Runtime.RemoteObject" + } + ] + }, + { "id": "Location", + "description": "Location in the source code.", + "type": "object", + "properties": [ + { "name": "scriptId", + "description": "Script identifier as reported in the `Debugger.scriptParsed`.", + "$ref": "Runtime.ScriptId" + }, + { "name": "lineNumber", + "description": "Line number in the script (0-based).", + "type": "integer" + }, + { "name": "columnNumber", + "description": "Column number in the script (0-based).", + "type": "integer" + } + ] + }, + { "id": "Scope", + "description": "Scope definition.", + "type": "object", + "properties": [ + { "name": "type", + "description": "Scope type.", + "type": "string", + "enum": [ + "global", + "local", + "closure" + ] + }, + { "name": "object", + "description": "Object representing the scope. The object is a proxy for the scope, unless the scope is global.", + "$ref": "Runtime.RemoteObject" + }, + { "name": "name", + "optional": true, + "type": "string" + } + ] + } + ], + "commands": [ + { "name": "enable", + "description": "Enables the debugger. This will have some performance penalty.", + "parameters": [ ], + "returns": [ + { "name": "debuggerId", + "description": "Unique identifier of the debugger.", + "experimental": true, + "type": "string" + } + ] + }, + { "name": "disable", + "description": "Disables debugging. This will have some performance benefit." + }, + + { "name": "continueToLocation", + "description": "Sets a one-off breakpoint to the specified location, and continues exectuion.", + "parameters": [ + { "name": "location", + "description": "Location to continue to.", + "$ref": "Location" + } + ] + }, + { "name": "evaluateOnCallFrame", + "description": "Evaluates expression on a given call frame, without triggering any breakpoints. The expression should be pure.", + "parameters": [ + { "name": "callFrameId", + "description": "Call frame to evaluate on.", + "$ref": "CallFrameId" + }, + { "name": "expression", + "description": "Expression to evaluate.", + "type": "string" + }, + { "name": "silent", + "description": "Ignores any exceptions that may occur during evaluation. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "deprecated": true, + "optional": true, + "type": "boolean" + }, + { "name": "timeout", + "description": "Terminate execution after timing out (number of milliseconds).", + "optional": true, + "type": "number" + } + ], + "returns": [ + { + "name": "result", + "description": "Object wrapper for the evaluation result.", + "$ref": "Runtime.RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "Runtime.ExceptionDetails" + } + ] + }, + { "name": "getPossibleBreakpoints", + "description": "Returns possible locations for breakpoint. scriptId in start and end range locations should be\nthe same.", + "parameters": [ + { + "name": "start", + "description": "Start of range to search possible breakpoint locations in.", + "$ref": "Location" + }, + { + "name": "end", + "description": "End of range to search possible breakpoint locations in (excluding). When not specified, end\nof scripts is used as end of range.", + "optional": true, + "$ref": "Location" + }, + { + "name": "restrictToFunction", + "description": "Only consider locations which are in the same (non-nested) function as start.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "locations", + "description": "List of the possible breakpoint locations.", + "type": "array", + "items": { + "$ref": "BreakLocation" + } + } + ] + }, + { "name": "getScriptSource", + "description": "Returns source for the script with given id.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to get source for.", + "$ref": "Runtime.ScriptId" + } + ], + "returns": [ + { + "name": "scriptSource", + "description": "Script source (empty in case of Wasm bytecode).", + "type": "string" + }, + { + "name": "bytecode", + "description": "Wasm bytecode.", + "optional": true, + "type": "binary" + } + ] + }, + { "name": "getWasmBytecode", + "description": "This command is deprecated. Use getScriptSource instead.", + "deprecated": true, + "parameters": [ + { + "name": "scriptId", + "description": "Id of the Wasm script to get source for.", + "$ref": "Runtime.ScriptId" + } + ], + "returns": [ + { + "name": "bytecode", + "description": "Script source.", + "type": "binary" + } + ] + }, + { "name": "getStackTrace", + "description": "Returns stack trace with given `stackTraceId`.", + "experimental": true, + "parameters": [ + { + "name": "stackTraceId", + "$ref": "Runtime.StackTraceId" + } + ], + "returns": [ + { + "name": "stackTrace", + "$ref": "Runtime.StackTrace" + } + ] + }, + { "name": "pause", + "description": "Stops on the next JavaScript statement." + }, + { "name": "pauseOnAsyncCall", + "experimental": true, + "deprecated": true, + "parameters": [ + { + "name": "parentStackTraceId", + "description": "Debugger will pause when async call with given stack trace is started.", + "$ref": "Runtime.StackTraceId" + } + ] + }, + { "name": "removeBreakpoint", + "description": "Removes JavaScript breakpoint.", + "parameters": [ + { + "name": "breakpointId", + "$ref": "BreakpointId" + } + ] + }, + { "name": "restartFrame", + "description": "Restarts particular call frame from the beginning.", + "deprecated": true, + "parameters": [ + { + "name": "callFrameId", + "description": "Call frame identifier to evaluate on.", + "$ref": "CallFrameId" + } + ], + "returns": [ + { + "name": "callFrames", + "description": "New stack trace.", + "type": "array", + "items": { + "$ref": "CallFrame" + } + }, + { + "name": "asyncStackTrace", + "description": "Async stack trace, if any.", + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "asyncStackTraceId", + "description": "Async stack trace, if any.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + } + ] + }, + { "name": "resume", + "description": "Resumes JavaScript execution.", + "parameters": [ + { + "name": "terminateOnResume", + "description": "Set to true to terminate execution upon resuming execution. In contrast\nto Runtime.terminateExecution, this will allows to execute further\nJavaScript (i.e. via evaluation) until execution of the paused code\nis actually resumed, at which point termination is triggered.\nIf execution is currently not paused, this parameter has no effect.", + "optional": true, + "type": "boolean" + } + ] + }, + { "name": "searchInContent", + "description": "Searches for given string in script content.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to search in.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "query", + "description": "String to search for.", + "type": "string" + }, + { + "name": "caseSensitive", + "description": "If true, search is case sensitive.", + "optional": true, + "type": "boolean" + }, + { + "name": "isRegex", + "description": "If true, treats string parameter as regex.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "List of search matches.", + "type": "array", + "items": { + "$ref": "SearchMatch" + } + } + ] + }, + { "name": "setAsyncCallStackDepth", + "description": "Enables or disables async call stacks tracking.", + "parameters": [ + { + "name": "maxDepth", + "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", + "type": "integer" + } + ] + }, + { "name": "setBlackboxPatterns", + "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in\nscripts with url matching one of the patterns. VM will try to leave blackboxed script by\nperforming 'step in' several times, finally resorting to 'step out' if unsuccessful.", + "experimental": true, + "parameters": [ + { + "name": "patterns", + "description": "Array of regexps that will be used to check script url for blackbox state.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + { "name": "setBlackboxedRanges", + "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted\nscripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.\nPositions array contains positions where blackbox state is changed. First interval isn't\nblackboxed. Array should be sorted.", + "experimental": true, + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "positions", + "type": "array", + "items": { + "$ref": "ScriptPosition" + } + } + ] + }, + { "name": "setBreakpoint", + "description": "Sets JavaScript breakpoint at a given location.", + "parameters": [ + { + "name": "location", + "description": "Location to set breakpoint in.", + "$ref": "Location" + }, + { + "name": "condition", + "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + }, + { + "name": "actualLocation", + "description": "Location this breakpoint resolved into.", + "$ref": "Location" + } + ] + }, + { "name": "setInstrumentationBreakpoint", + "description": "Sets instrumentation breakpoint.", + "parameters": [ + { + "name": "instrumentation", + "description": "Instrumentation name.", + "type": "string", + "enum": [ + "beforeScriptExecution", + "beforeScriptWithSourceMapExecution" + ] + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + } + ] + }, + { "name": "setBreakpointByUrl", + "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this\ncommand is issued, all existing parsed scripts will have breakpoints resolved and returned in\n`locations` property. Further matching script parsing will result in subsequent\n`breakpointResolved` events issued. This logical breakpoint will survive page reloads.", + "parameters": [ + { + "name": "lineNumber", + "description": "Line number to set breakpoint at.", + "type": "integer" + }, + { + "name": "columnNumber", + "description": "Offset in the line to set breakpoint at.", + "optional": true, + "type": "integer" + }, + { + "name": "url", + "description": "URL of the resources to set breakpoint on.", + "optional": true, + "type": "string" + }, + { + "name": "urlRegex", + "description": "Regex pattern for the URLs of the resources to set breakpoints on. Either `url` or\n`urlRegex` must be specified.", + "optional": true, + "type": "string" + }, + { + "name": "scriptHash", + "description": "Script hash of the resources to set breakpoint on.", + "optional": true, + "type": "string" + }, + { + "name": "condition", + "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + }, + { + "name": "locations", + "description": "List of the locations this breakpoint resolved into upon addition.", + "type": "array", + "items": { + "$ref": "Location" + } + } + ] + }, + { "name": "setBreakpointOnFunctionCall", + "description": "Sets JavaScript breakpoint before each call to the given function.\nIf another function was created from the same source as a given one,\ncalling it will also trigger the breakpoint.", + "experimental": true, + "parameters": [ + { + "name": "objectId", + "description": "Function object id.", + "$ref": "Runtime.RemoteObjectId" + }, + { + "name": "condition", + "description": "Expression to use as a breakpoint condition. When specified, debugger will\nstop on the breakpoint if this expression evaluates to true.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + } + ] + }, + { "name": "setBreakpointsActive", + "description": "Activates / deactivates all breakpoints on the page.", + "parameters": [ + { + "name": "active", + "description": "New value for breakpoints active state.", + "type": "boolean" + } + ] + }, + { "name": "setPauseOnExceptions", + "description": "Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or\nno exceptions. Initial pause on exceptions state is `none`.", + "parameters": [ + { + "name": "state", + "description": "Pause on exceptions mode.", + "type": "string", + "enum": [ + "none", + "uncaught", + "all" + ] + } + ] + }, + { "name": "setReturnValue", + "description": "Changes return value in top frame. Available only at return break position.", + "experimental": true, + "parameters": [ + { + "name": "newValue", + "description": "New return value.", + "$ref": "Runtime.CallArgument" + } + ] + }, + { "name": "setScriptSource", + "description": "Edits JavaScript source live.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to edit.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "scriptSource", + "description": "New content of the script.", + "type": "string" + }, + { + "name": "dryRun", + "description": "If true the change will not actually be applied. Dry run may be used to get result\ndescription without actually modifying the code.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "callFrames", + "description": "New stack trace in case editing has happened while VM was stopped.", + "optional": true, + "type": "array", + "items": { + "$ref": "CallFrame" + } + }, + { + "name": "stackChanged", + "description": "Whether current call stack was modified after applying the changes.", + "optional": true, + "type": "boolean" + }, + { + "name": "asyncStackTrace", + "description": "Async stack trace, if any.", + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "asyncStackTraceId", + "description": "Async stack trace, if any.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + }, + { + "name": "exceptionDetails", + "description": "Exception details if any.", + "optional": true, + "$ref": "Runtime.ExceptionDetails" + } + ] + }, + { "name": "setSkipAllPauses", + "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc).", + "parameters": [ + { + "name": "skip", + "description": "New value for skip pauses state.", + "type": "boolean" + } + ] + }, + { "name": "setVariableValue", + "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be\nmutated manually.", + "parameters": [ + { + "name": "scopeNumber", + "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch'\nscope types are allowed. Other scopes could be manipulated manually.", + "type": "integer" + }, + { + "name": "variableName", + "description": "Variable name.", + "type": "string" + }, + { + "name": "newValue", + "description": "New variable value.", + "$ref": "Runtime.CallArgument" + }, + { + "name": "callFrameId", + "description": "Id of callframe that holds variable.", + "$ref": "CallFrameId" + } + ] + }, + { "name": "stepInto", + "description": "Steps into the function call." + }, + { "name": "stepOut", + "description": "Steps out of the function call." + }, + { "name": "stepOver", + "description": "Steps over the statement." + } + ], + "events": [ + { "name": "breakpointResolved", + "description": "Fired when breakpoint is resolved to an actual script and location.", + "parameters": [ + { + "name": "breakpointId", + "description": "Breakpoint unique identifier.", + "$ref": "BreakpointId" + }, + { + "name": "location", + "description": "Actual breakpoint location.", + "$ref": "Location" + } + ] + }, + { "name": "paused", + "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria.", + "parameters": [ + { + "name": "callFrames", + "description": "Call stack the virtual machine stopped on.", + "type": "array", + "items": { + "$ref": "CallFrame" + } + }, + { + "name": "reason", + "description": "Pause reason.", + "type": "string", + "enum": [ + "ambiguous", + "assert", + "CSPViolation", + "debugCommand", + "DOM", + "EventListener", + "exception", + "instrumentation", + "OOM", + "other", + "promiseRejection", + "XHR" + ] + }, + { + "name": "data", + "description": "Object containing break-specific auxiliary properties.", + "optional": true, + "type": "object" + }, + { + "name": "hitBreakpoints", + "description": "Hit breakpoints IDs", + "optional": true, + "type": "array", + "items": { + "type": "string" + } + }, + { + "name": "asyncStackTrace", + "description": "Async stack trace, if any.", + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "asyncStackTraceId", + "description": "Async stack trace, if any.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + }, + { + "name": "asyncCallStackTraceId", + "description": "Never present, will be removed.", + "experimental": true, + "deprecated": true, + "optional": true, + "$ref": "Runtime.StackTraceId" + } + ] + }, + { "name": "resumed", + "description": "Fired when the virtual machine resumed execution." + }, + { "name": "scriptFailedToParse", + "description": "Fired when virtual machine fails to parse the script.", + "parameters": [ + { + "name": "scriptId", + "description": "Identifier of the script parsed.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "url", + "description": "URL or name of the script parsed (if any).", + "type": "string" + }, + { + "name": "startLine", + "description": "Line offset of the script within the resource with given URL (for script tags).", + "type": "integer" + }, + { + "name": "startColumn", + "description": "Column offset of the script within the resource with given URL.", + "type": "integer" + }, + { + "name": "endLine", + "description": "Last line of the script.", + "type": "integer" + }, + { + "name": "endColumn", + "description": "Length of the last line of the script.", + "type": "integer" + }, + { + "name": "hash", + "description": "Content hash of the script.", + "type": "string" + }, + { + "name": "executionContextAuxData", + "description": "Embedder-specific auxiliary data.", + "optional": true, + "type": "object" + }, + { + "name": "sourceMapURL", + "description": "URL of source map associated with script (if any).", + "optional": true, + "type": "string" + }, + { + "name": "hasSourceURL", + "description": "True, if this script has sourceURL.", + "optional": true, + "type": "boolean" + }, + { + "name": "isModule", + "description": "True, if this script is ES6 module.", + "optional": true, + "type": "boolean" + }, + { + "name": "length", + "description": "This script length.", + "optional": true, + "type": "integer" + }, + { + "name": "stackTrace", + "description": "JavaScript top stack frame of where the script parsed event was triggered if available.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "codeOffset", + "description": "If the scriptLanguage is WebAssembly, the code section offset in the module.", + "experimental": true, + "optional": true, + "type": "integer" + }, + { + "name": "scriptLanguage", + "description": "The language of the script.", + "experimental": true, + "optional": true, + "$ref": "Debugger.ScriptLanguage" + }, + { + "name": "embedderName", + "description": "The name the embedder supplied for this script.", + "experimental": true, + "optional": true, + "type": "string" + } + ] + }, + { "name": "scriptParsed", + "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected\nscripts upon enabling debugger.", + "parameters": [ + { + "name": "scriptId", + "description": "Identifier of the script parsed.", + "$ref": "Runtime.ScriptId" + }, + { + "name": "url", + "description": "URL or name of the script parsed (if any).", + "type": "string" + }, + { + "name": "startLine", + "description": "Line offset of the script within the resource with given URL (for script tags).", + "type": "integer" + }, + { + "name": "startColumn", + "description": "Column offset of the script within the resource with given URL.", + "type": "integer" + }, + { + "name": "endLine", + "description": "Last line of the script.", + "type": "integer" + }, + { + "name": "endColumn", + "description": "Length of the last line of the script.", + "type": "integer" + }, + { + "name": "hash", + "description": "Content hash of the script.", + "type": "string" + }, + { + "name": "executionContextAuxData", + "description": "Embedder-specific auxiliary data.", + "optional": true, + "type": "object" + }, + { + "name": "isLiveEdit", + "description": "True, if this script is generated as a result of the live edit operation.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "sourceMapURL", + "description": "URL of source map associated with script (if any).", + "optional": true, + "type": "string" + }, + { + "name": "hasSourceURL", + "description": "True, if this script has sourceURL.", + "optional": true, + "type": "boolean" + }, + { + "name": "isModule", + "description": "True, if this script is ES6 module.", + "optional": true, + "type": "boolean" + }, + { + "name": "length", + "description": "This script length.", + "optional": true, + "type": "integer" + }, + { + "name": "stackTrace", + "description": "JavaScript top stack frame of where the script parsed event was triggered if available.", + "experimental": true, + "optional": true, + "$ref": "Runtime.StackTrace" + }, + { + "name": "codeOffset", + "description": "If the scriptLanguage is WebAssembly, the code section offset in the module.", + "experimental": true, + "optional": true, + "type": "integer" + }, + { + "name": "scriptLanguage", + "description": "The language of the script.", + "experimental": true, + "optional": true, + "$ref": "Debugger.ScriptLanguage" + }, + { + "name": "debugSymbols", + "description": "If the scriptLanguage is WebASsembly, the source of debug symbols for the module.", + "experimental": true, + "optional": true, + "$ref": "Debugger.DebugSymbols" + }, + { + "name": "embedderName", + "description": "The name the embedder supplied for this script.", + "experimental": true, + "optional": true, + "type": "string" + } + ] + } + ] + }, + { + "domain": "Runtime", + "types": [ + { "id": "ScriptId", + "description": "Unique script identifier.", + "type": "string" + }, + { "id": "RemoteObjectId", + "description": "Unique object identifier.", + "type": "string" + }, + + { "id": "Timestamp", + "description": "Number of milliseconds since epoch.", + "type": "number" + }, + { "id": "TimeDelta", + "description": "Number of milliseconds.", + "type": "number" + }, + + { "id": "UnserializableValue", + "description": "Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,\n`-Infinity`, and bigint literals.", + "type": "string" + }, + { "id": "RemoteObject", + "description": "Mirror object referencing original JavaScript object.", + "type": "object", + "properties": [ + { "name": "type", + "description": "Object type.", + "type": "string", + "enum": [ + "object", + "function", + "undefined", + "string", + "number", + "boolean", + "symbol" + ] + }, + { "name": "subtype", + "description": "Object subtype hint. Specified for `object` type values only.", + "optional": true, + "type": "string", + "enum": [ + "array", + "null", + "regexp", + "date", + "map", + "set", + "generator", + "promise" + ] + }, + { "name": "className", + "description": "Object class (constructor) name. Specified for `object` type values only.", + "optional": true, + "type": "string" + }, + { "name": "value", + "description": "Remote object value in case of primitive values or JSON values (if it was requested).", + "optional": true, + "type": "any" + }, + { "name": "unserializableValue", + "description": "Primitive value which can not be JSON-stringified does not have `value`, but gets this\nproperty.", + "optional": true, + "$ref": "UnserializableValue" + }, + { "name": "description", + "description": "String representation of the object.", + "optional": true, + "type": "string" + }, + { "name": "objectId", + "description": "Unique object identifier (for non-primitive values).", + "optional": true, + "$ref": "RemoteObjectId" + } + ] + }, + { "id": "MemberDescriptor", + "description": "Object member descriptor, mirroring the object descriptor in JavaScript (https://developer.mozilla.org/en-US/docs/Glossary/Property/JavaScript).", + "type": "object", + "properties": [ + { "name": "name", + "description": "String representation of the member name.", + "type": "string" + }, + { "name": "value", + "description": "The value of the member, if it's a field.", + "optional": true, + "$ref": "RemoteObject" + }, + { "name": "writable", + "description": "Whether or not the member is writable. Present only if the member is a field.", + "optional": true, + "type": "boolean" + }, + { "name": "configurable", + "description": "Whether or not the member is configurable.", + "type": "boolean" + }, + { "name": "enumerable", + "description": "Whether or not the member is enumerable..", + "type": "boolean" + }, + { "name": "get", + "description": "The getter of the member. Present only if the member is a property and the property has a getter.", + "optional": true, + "$ref": "RemoteObject" + }, + { "name": "set", + "description": "The setter of the member. Present only if the member is a property and the property has a setter.", + "optional": true, + "$ref": "RemoteObject" + }, + { "name": "wasThrown", + "description": "True if the member's value was thrown during the evaluation of the getter.", + "optional": true, + "type": "boolean" + }, + { "name": "isOwn", + "description": "True if the property is owned for the object.", + "optional": true, + "type": "boolean" + }, + { "name": "symbol", + "description": "If the member name is a symbol, this will be the reference of the symbol.", + "optional": true, + "$ref": "RemoteObject" + } + ] + }, + { "id": "CallArgument", + "description": "Represents function call argument. Only one of the three optional fields should be specified, or none for undefined.", + "type": "object", + "properties": [ + { "name": "value", + "description": "Primitive value or serializable javascript object.", + "optional": true, + "type": "any" + }, + { "name": "unserializableValue", + "description": "Primitive value which can not be JSON-stringified.", + "optional": true, + "$ref": "UnserializableValue" + }, + { "name": "objectId", + "description": "Remote object handle.", + "optional": true, + "$ref": "RemoteObjectId" + } + ] + }, + { "id": "ExceptionDetails", + "description": "Detailed information about an exception.", + "type": "object", + "properties": [ + { "name": "exceptionId", + "description": "Exception id.", + "type": "integer" + }, + { "name": "text", + "description": "The string representation of the exception.", + "type": "string" + }, + { "name": "exception", + "description": "Exception object if available.", + "optional": true, + "$ref": "RemoteObject" + }, + + { "name": "stackTrace", + "description": "JavaScript stack trace if available.", + "$ref": "StackTrace" + }, + { "name": "scriptId", + "description": "Script ID of the exception location.", + "$ref": "ScriptId" + }, + { "name": "lineNumber", + "description": "Line number of the exception location (0-based).", + "type": "integer" + }, + { "name": "columnNumber", + "description": "Column number of the exception location (0-based).", + "type": "integer" + } + ] + }, + { "id": "CallFrame", + "description": "Stack entry for runtime errors and assertions.", + "type": "object", + "properties": [ + { "name": "functionName", + "description": "JavaScript function name.", + "type": "string" + }, + { "name": "scriptId", + "description": "JavaScript script id.", + "$ref": "ScriptId" + }, + { "name": "lineNumber", + "description": "JavaScript script line number (0-based).", + "type": "integer" + }, + { "name": "columnNumber", + "description": "JavaScript script column number (0-based).", + "type": "integer" + } + ] + }, + { "id": "StackTrace", + "description": "Call frames for assertions or error messages.", + "type": "object", + "properties": [ + { "name": "description", + "description": "String label of this stack trace. For async traces this may be a name of the function that\ninitiated the async call.", + "optional": true, + "type": "string" + }, + { "name": "callFrames", + "description": "A list of all the frames in the stack trace (excluding internal functions).", + "type": "array", + "items": { "$ref": "CallFrame" } + } + ] + } + ], + "commands": [ + { "name": "awaitPromise", + "description": "Add handler to promise with given promise object id.", + "parameters": [ + { "name": "promiseObjectId", + "description": "Identifier of the promise.", + "$ref": "RemoteObjectId" + }, + { "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { "name": "result", + "description": "Promise result. Will contain rejected value if promise was rejected.", + "$ref": "RemoteObject" + }, + { "name": "exceptionDetails", + "description": "Exception details if stack strace is available.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { "name": "callFunctionOn", + "description": "Calls function with given declaration on the given object.", + "parameters": [ + { "name": "functionDeclaration", + "description": "Declaration of the function to call.", + "type": "string" + }, + { "name": "objectId", + "description": "Identifier of the object to call function on. Either objectId or executionContextId should be specified.", + "optional": true, + "$ref": "RemoteObjectId" + }, + { "name": "arguments", + "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target\nobject.", + "optional": true, + "type": "array", + "items": { + "$ref": "CallArgument" + } + }, + { "name": "silent", + "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object which should be sent by value.", + "optional": true, + "type": "boolean" + }, + { "name": "awaitPromise", + "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", + "optional": true, + "type": "boolean" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release multiple objects. If objectGroup is not\nspecified and objectId is, objectGroup will be inherited from object.", + "optional": true, + "type": "string" + }, + { + "name": "throwOnSideEffect", + "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "generateWebDriverValue", + "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.", + "experimental": true, + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Call result.", + "$ref": "RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "compileScript", + "description": "Compiles expression.", + "parameters": [ + { + "name": "expression", + "description": "Expression to compile.", + "type": "string" + }, + { + "name": "sourceURL", + "description": "Source url to be set for the script.", + "type": "string" + }, + { + "name": "persistScript", + "description": "Specifies whether the compiled script should be persisted.", + "type": "boolean" + }, + { + "name": "executionContextId", + "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", + "optional": true, + "$ref": "ExecutionContextId" + } + ], + "returns": [ + { + "name": "scriptId", + "description": "Id of the script.", + "optional": true, + "$ref": "ScriptId" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "disable", + "description": "Disables reporting of execution contexts creation." + }, + { + "name": "discardConsoleEntries", + "description": "Discards collected exceptions and console API calls." + }, + { + "name": "enable", + "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext." + }, + { + "name": "evaluate", + "description": "Evaluates expression on global object.", + "parameters": [ + { + "name": "expression", + "description": "Expression to evaluate.", + "type": "string" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release multiple objects.", + "optional": true, + "type": "string" + }, + { + "name": "includeCommandLineAPI", + "description": "Determines whether Command Line API should be available during the evaluation.", + "optional": true, + "type": "boolean" + }, + { + "name": "silent", + "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { + "name": "contextId", + "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.\nThis is mutually exclusive with `uniqueContextId`, which offers an\nalternative way to identify the execution context that is more reliable\nin a multi-process environment.", + "optional": true, + "$ref": "ExecutionContextId" + }, + { + "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the result.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "userGesture", + "description": "Whether execution should be treated as initiated by user in the UI.", + "optional": true, + "type": "boolean" + }, + { + "name": "awaitPromise", + "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", + "optional": true, + "type": "boolean" + }, + { + "name": "throwOnSideEffect", + "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.\nThis implies `disableBreaks` below.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "timeout", + "description": "Terminate execution after timing out (number of milliseconds).", + "experimental": true, + "optional": true, + "$ref": "TimeDelta" + }, + { + "name": "disableBreaks", + "description": "Disable breakpoints during execution.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "replMode", + "description": "Setting this flag to true enables `let` re-declaration and top-level `await`.\nNote that `let` variables can only be re-declared if they originate from\n`replMode` themselves.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "allowUnsafeEvalBlockedByCSP", + "description": "The Content Security Policy (CSP) for the target might block 'unsafe-eval'\nwhich includes eval(), Function(), setTimeout() and setInterval()\nwhen called with non-callable arguments. This flag bypasses CSP for this\nevaluation and allows unsafe-eval. Defaults to true.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "uniqueContextId", + "description": "An alternative way to specify the execution context to evaluate in.\nCompared to contextId that may be reused across processes, this is guaranteed to be\nsystem-unique, so it can be used to prevent accidental evaluation of the expression\nin context different than intended (e.g. as a result of navigation across process\nboundaries).\nThis is mutually exclusive with `contextId`.", + "experimental": true, + "optional": true, + "type": "string" + }, + { + "name": "generateWebDriverValue", + "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.", + "experimental": true, + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Evaluation result.", + "$ref": "RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "getIsolateId", + "description": "Returns the isolate id.", + "experimental": true, + "returns": [ + { + "name": "id", + "description": "The isolate id.", + "type": "string" + } + ] + }, + { + "name": "getHeapUsage", + "description": "Returns the JavaScript heap usage.\nIt is the total usage of the corresponding isolate not scoped to a particular Runtime.", + "experimental": true, + "returns": [ + { + "name": "usedSize", + "description": "Used heap size in bytes.", + "type": "number" + }, + { + "name": "totalSize", + "description": "Allocated heap size in bytes.", + "type": "number" + } + ] + }, + { + "name": "getProperties", + "description": "Returns properties of a given object. Object group of the result is inherited from the target\nobject.", + "parameters": [ + { + "name": "objectId", + "description": "Identifier of the object to return properties for.", + "$ref": "RemoteObjectId" + }, + { + "name": "ownProperties", + "description": "If true, returns properties belonging only to the element itself, not to its prototype\nchain.", + "optional": true, + "type": "boolean" + }, + { + "name": "accessorPropertiesOnly", + "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not\nreturned either.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the results.", + "experimental": true, + "optional": true, + "type": "boolean" + }, + { + "name": "nonIndexedPropertiesOnly", + "description": "If true, returns non-indexed properties only.", + "experimental": true, + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Object properties.", + "type": "array", + "items": { + "$ref": "PropertyDescriptor" + } + }, + { + "name": "internalProperties", + "description": "Internal object properties (only of the element itself).", + "optional": true, + "type": "array", + "items": { + "$ref": "InternalPropertyDescriptor" + } + }, + { + "name": "privateProperties", + "description": "Object private properties.", + "experimental": true, + "optional": true, + "type": "array", + "items": { + "$ref": "PrivatePropertyDescriptor" + } + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "globalLexicalScopeNames", + "description": "Returns all let, const and class variables from global scope.", + "parameters": [ + { + "name": "executionContextId", + "description": "Specifies in which execution context to lookup global scope variables.", + "optional": true, + "$ref": "ExecutionContextId" + } + ], + "returns": [ + { + "name": "names", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + { + "name": "queryObjects", + "parameters": [ + { + "name": "prototypeObjectId", + "description": "Identifier of the prototype to return objects for.", + "$ref": "RemoteObjectId" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release the results.", + "optional": true, + "type": "string" + } + ], + "returns": [ + { + "name": "objects", + "description": "Array with objects.", + "$ref": "RemoteObject" + } + ] + }, + { + "name": "releaseObject", + "description": "Releases remote object with given id.", + "parameters": [ + { + "name": "objectId", + "description": "Identifier of the object to release.", + "$ref": "RemoteObjectId" + } + ] + }, + { + "name": "releaseObjectGroup", + "description": "Releases all remote objects that belong to a given group.", + "parameters": [ + { + "name": "objectGroup", + "description": "Symbolic object group name.", + "type": "string" + } + ] + }, + { + "name": "runIfWaitingForDebugger", + "description": "Tells inspected instance to run if it was waiting for debugger to attach." + }, + { + "name": "runScript", + "description": "Runs script with given id in a given context.", + "parameters": [ + { + "name": "scriptId", + "description": "Id of the script to run.", + "$ref": "ScriptId" + }, + { + "name": "executionContextId", + "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", + "optional": true, + "$ref": "ExecutionContextId" + }, + { + "name": "objectGroup", + "description": "Symbolic group name that can be used to release multiple objects.", + "optional": true, + "type": "string" + }, + { + "name": "silent", + "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", + "optional": true, + "type": "boolean" + }, + { + "name": "includeCommandLineAPI", + "description": "Determines whether Command Line API should be available during the evaluation.", + "optional": true, + "type": "boolean" + }, + { + "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object which should be sent by value.", + "optional": true, + "type": "boolean" + }, + { + "name": "generatePreview", + "description": "Whether preview should be generated for the result.", + "optional": true, + "type": "boolean" + }, + { + "name": "awaitPromise", + "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", + "optional": true, + "type": "boolean" + } + ], + "returns": [ + { + "name": "result", + "description": "Run result.", + "$ref": "RemoteObject" + }, + { + "name": "exceptionDetails", + "description": "Exception details.", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "setAsyncCallStackDepth", + "description": "Enables or disables async call stacks tracking.", + "redirect": "Debugger", + "parameters": [ + { + "name": "maxDepth", + "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", + "type": "integer" + } + ] + }, + { + "name": "setCustomObjectFormatterEnabled", + "experimental": true, + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ] + }, + { + "name": "setMaxCallStackSizeToCapture", + "experimental": true, + "parameters": [ + { + "name": "size", + "type": "integer" + } + ] + }, + { + "name": "terminateExecution", + "description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends.", + "experimental": true + }, + { + "name": "addBinding", + "description": "If executionContextId is empty, adds binding with the given name on the\nglobal objects of all inspected contexts, including those created later,\nbindings survive reloads.\nBinding function takes exactly one argument, this argument should be string,\nin case of any other input, function throws an exception.\nEach binding function call produces Runtime.bindingCalled notification.", + "experimental": true, + "parameters": [ + { + "name": "name", + "type": "string" + }, + { + "name": "executionContextId", + "description": "If specified, the binding would only be exposed to the specified\nexecution context. If omitted and `executionContextName` is not set,\nthe binding is exposed to all execution contexts of the target.\nThis parameter is mutually exclusive with `executionContextName`.\nDeprecated in favor of `executionContextName` due to an unclear use case\nand bugs in implementation (crbug.com/1169639). `executionContextId` will be\nremoved in the future.", + "deprecated": true, + "optional": true, + "$ref": "ExecutionContextId" + }, + { + "name": "executionContextName", + "description": "If specified, the binding is exposed to the executionContext with\nmatching name, even for contexts created after the binding is added.\nSee also `ExecutionContext.name` and `worldName` parameter to\n`Page.addScriptToEvaluateOnNewDocument`.\nThis parameter is mutually exclusive with `executionContextId`.", + "experimental": true, + "optional": true, + "type": "string" + } + ] + }, + { + "name": "removeBinding", + "description": "This method does not remove binding function from global object but\nunsubscribes current runtime agent from Runtime.bindingCalled notifications.", + "experimental": true, + "parameters": [ + { + "name": "name", + "type": "string" + } + ] + }, + { + "name": "getExceptionDetails", + "description": "This method tries to lookup and populate exception details for a\nJavaScript Error object.\nNote that the stackTrace portion of the resulting exceptionDetails will\nonly be populated if the Runtime domain was enabled at the time when the\nError was thrown.", + "experimental": true, + "parameters": [ + { + "name": "errorObjectId", + "description": "The error object for which to resolve the exception details.", + "$ref": "RemoteObjectId" + } + ], + "returns": [ + { + "name": "exceptionDetails", + "optional": true, + "$ref": "ExceptionDetails" + } + ] + } + ], + "events": [ + { + "name": "bindingCalled", + "description": "Notification is issued every time when binding is called.", + "experimental": true, + "parameters": [ + { + "name": "name", + "type": "string" + }, + { + "name": "payload", + "type": "string" + }, + { + "name": "executionContextId", + "description": "Identifier of the context where the call was made.", + "$ref": "ExecutionContextId" + } + ] + }, + { + "name": "consoleAPICalled", + "description": "Issued when console API was called.", + "parameters": [ + { + "name": "type", + "description": "Type of the call.", + "type": "string", + "enum": [ + "log", + "debug", + "info", + "error", + "warning", + "dir", + "dirxml", + "table", + "trace", + "clear", + "startGroup", + "startGroupCollapsed", + "endGroup", + "assert", + "profile", + "profileEnd", + "count", + "timeEnd" + ] + }, + { + "name": "args", + "description": "Call arguments.", + "type": "array", + "items": { + "$ref": "RemoteObject" + } + }, + { + "name": "executionContextId", + "description": "Identifier of the context where the call was made.", + "$ref": "ExecutionContextId" + }, + { + "name": "timestamp", + "description": "Call timestamp.", + "$ref": "Timestamp" + }, + { + "name": "stackTrace", + "description": "Stack trace captured when the call was made. The async stack chain is automatically reported for\nthe following call types: `assert`, `error`, `trace`, `warning`. For other types the async call\nchain can be retrieved using `Debugger.getStackTrace` and `stackTrace.parentId` field.", + "optional": true, + "$ref": "StackTrace" + }, + { + "name": "context", + "description": "Console context descriptor for calls on non-default console context (not console.*):\n'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call\non named context.", + "experimental": true, + "optional": true, + "type": "string" + } + ] + }, + { + "name": "exceptionRevoked", + "description": "Issued when unhandled exception was revoked.", + "parameters": [ + { + "name": "reason", + "description": "Reason describing why exception was revoked.", + "type": "string" + }, + { + "name": "exceptionId", + "description": "The id of revoked exception, as reported in `exceptionThrown`.", + "type": "integer" + } + ] + }, + { + "name": "exceptionThrown", + "description": "Issued when exception was thrown and unhandled.", + "parameters": [ + { + "name": "timestamp", + "description": "Timestamp of the exception.", + "$ref": "Timestamp" + }, + { + "name": "exceptionDetails", + "$ref": "ExceptionDetails" + } + ] + }, + { + "name": "executionContextCreated", + "description": "Issued when new execution context is created.", + "parameters": [ + { + "name": "context", + "description": "A newly created execution context.", + "$ref": "ExecutionContextDescription" + } + ] + }, + { + "name": "executionContextDestroyed", + "description": "Issued when execution context is destroyed.", + "parameters": [ + { + "name": "executionContextId", + "description": "Id of the destroyed context", + "$ref": "ExecutionContextId" + } + ] + }, + { + "name": "executionContextsCleared", + "description": "Issued when all executionContexts were cleared in browser" + }, + { + "name": "inspectRequested", + "description": "Issued when object should be inspected (for example, as a result of inspect() command line API\ncall).", + "parameters": [ + { + "name": "object", + "$ref": "RemoteObject" + }, + { + "name": "hints", + "type": "object" + }, + { + "name": "executionContextId", + "description": "Identifier of the context where the call was made.", + "experimental": true, + "optional": true, + "$ref": "ExecutionContextId" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index f9b6ae4..3fb3d17 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -3,12 +3,13 @@ package me.topchetoeu.jscript.engine.frame; import java.util.Stack; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction; 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.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; -import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; @@ -95,34 +96,32 @@ public class CodeFrame { } private void setCause(Context ctx, EngineException err, EngineException cause) { - if (err.value instanceof ObjectValue) { - Values.setMember(ctx, err, ctx.environment().symbol("Symbol.cause"), cause); - } err.cause = cause; } - private Object nextNoTry(Context ctx) { + private Object nextNoTry(Context ctx, Instruction instr) { if (Thread.currentThread().isInterrupted()) throw new InterruptException(); if (codePtr < 0 || codePtr >= function.body.length) return null; - var instr = function.body[codePtr]; - - var loc = instr.location; - if (loc != null) prevLoc = loc; - try { this.jumpFlag = false; return Runners.exec(ctx, instr, this); } catch (EngineException e) { - throw e.add(function.name, prevLoc).setContext(ctx); + throw e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine); } } public Object next(Context ctx, Object value, Object returnValue, EngineException error) { if (value != Runners.NO_RETURN) push(ctx, value); + var debugger = StackData.getDebugger(ctx); if (returnValue == Runners.NO_RETURN && error == null) { - try { returnValue = nextNoTry(ctx); } + try { + var instr = function.body[codePtr]; + + if (debugger != null) debugger.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); + returnValue = nextNoTry(ctx, instr); + } catch (EngineException e) { error = e; } } @@ -165,6 +164,7 @@ public class CodeFrame { tryCtx.err = error; newState = TryCtx.STATE_FINALLY_THREW; } + setCause(ctx, error, tryCtx.err); break; } else if (returnValue != Runners.NO_RETURN) { @@ -223,8 +223,15 @@ public class CodeFrame { return Runners.NO_RETURN; } - if (error != null) throw error.setContext(ctx); - if (returnValue != Runners.NO_RETURN) return returnValue; + if (error != null) { + if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, false); + throw error; + } + if (returnValue != Runners.NO_RETURN) { + if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false); + return returnValue; + } + return Runners.NO_RETURN; } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index 4ec8991..bccbcdb 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -2,6 +2,10 @@ package me.topchetoeu.jscript.engine.scope; import java.util.ArrayList; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.NativeFunction; +import me.topchetoeu.jscript.engine.values.ObjectValue; + public class LocalScope { private String[] names; public final ValueVariable[] captures; @@ -15,11 +19,25 @@ public class LocalScope { else return captures[~i]; } - public String[] getNames() { + public String[] getCaptureNames() { + var res = new String[captures.length]; + + for (int i = 0; i < captures.length; i++) { + if (names == null || i >= names.length) res[i] = "capture_" + (i); + else res[i] = names[i]; + } + + return res; + } + public String[] getLocalNames() { var res = new String[locals.length]; - for (var i = 0; i < locals.length; i++) { - if (names == null || i >= names.length) res[i] = "local_" + i; + for (int i = captures.length, j = 0; i < locals.length; i++, j++) { + if (names == null || i >= names.length) { + if (j == 0) res[j] = "this"; + else if (j == 1) res[j] = "arguments"; + else res[i] = "local_" + (j - 2); + } else res[i] = names[i]; } @@ -33,11 +51,36 @@ public class LocalScope { return captures.length + locals.length; } - public void toGlobal(GlobalScope global) { - var names = getNames(); - for (var i = 0; i < names.length; i++) { - global.define(names[i], locals[i]); + public void applyToObject(Context ctx, ObjectValue locals, ObjectValue captures, boolean props) { + var localNames = getLocalNames(); + var captureNames = getCaptureNames(); + + for (var i = 0; i < this.locals.length; i++) { + var name = localNames[i]; + var _i = i; + + if (props) locals.defineProperty(ctx, name, + new NativeFunction(name, (_ctx, thisArg, args) -> this.locals[_i].get(_ctx)), + this.locals[i].readonly ? null : + new NativeFunction(name, (_ctx, thisArg, args) -> { this.locals[_i].set(_ctx, args.length < 1 ? null : args[0]); return null; }), + true, true + ); + else locals.defineProperty(ctx, name, this.locals[i].get(ctx)); } + for (var i = 0; i < this.captures.length; i++) { + var name = captureNames[i]; + var _i = i; + + if (props) captures.defineProperty(ctx, name, + new NativeFunction(name, (_ctx, thisArg, args) -> this.captures[_i].get(_ctx)), + this.captures[i].readonly ? null : + new NativeFunction(name, (_ctx, thisArg, args) -> { this.captures[_i].set(_ctx, args.length < 1 ? null : args[0]); return null; }), + true, true + ); + else captures.defineProperty(ctx, name, this.captures[i].get(ctx)); + } + + captures.setPrototype(ctx, locals); } public LocalScope(int n, ValueVariable[] captures) { diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java index 3a19e79..44a9543 100644 --- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java @@ -212,7 +212,7 @@ public class ArrayValue extends ObjectValue implements Iterable { for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]); } - public static ArrayValue of(Context ctx, Collection values) { + public static ArrayValue of(Context ctx, Collection values) { return new ArrayValue(ctx, values.toArray(Object[]::new)); } } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 9ca731a..ccc94bf 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -665,7 +665,8 @@ public class Values { prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; try { if (err instanceof EngineException) { - System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx)); + var ee = ((EngineException)err); + System.out.println(prefix + " " + ee.toString(new Context(ee.engine).pushEnv(ee.env))); } else if (err instanceof SyntaxException) { System.out.println("Syntax error:" + ((SyntaxException)err).msg); diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index 90444e1..3f740b1 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -5,6 +5,8 @@ import java.util.List; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; @@ -12,7 +14,8 @@ import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; public class EngineException extends RuntimeException { public final Object value; public EngineException cause; - public Context ctx = null; + public Environment env = null; + public Engine engine = null; public final List stackTrace = new ArrayList<>(); public EngineException add(String name, Location location) { @@ -28,8 +31,9 @@ public class EngineException extends RuntimeException { this.cause = cause; return this; } - public EngineException setContext(Context ctx) { - this.ctx = ctx; + public EngineException setCtx(Environment env, Engine engine) { + if (this.env == null) this.env = env; + if (this.engine == null) this.engine = engine; return this; } diff --git a/src/me/topchetoeu/jscript/exceptions/InterruptException.java b/src/me/topchetoeu/jscript/exceptions/InterruptException.java index ae9e2e8..f198f49 100644 --- a/src/me/topchetoeu/jscript/exceptions/InterruptException.java +++ b/src/me/topchetoeu/jscript/exceptions/InterruptException.java @@ -2,7 +2,7 @@ package me.topchetoeu.jscript.exceptions; public class InterruptException extends RuntimeException { public InterruptException() { } - public InterruptException(InterruptedException e) { + public InterruptException(Throwable e) { super(e); } } diff --git a/src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java b/src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java new file mode 100644 index 0000000..fae7e82 --- /dev/null +++ b/src/me/topchetoeu/jscript/exceptions/UncheckedIOException.java @@ -0,0 +1,9 @@ +package me.topchetoeu.jscript.exceptions; + +import java.io.IOException; + +public class UncheckedIOException extends RuntimeException { + public UncheckedIOException(IOException e) { + super(e); + } +} diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index 4ea59e2..593378a 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -79,7 +79,7 @@ public class OverloadFunction extends FunctionValue { catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); } catch (IllegalAccessException | IllegalArgumentException e) { continue; } catch (InvocationTargetException e) { - var loc = new Location(0, 0, ""); + var loc = Location.INTERNAL; if (e.getTargetException() instanceof EngineException) { throw ((EngineException)e.getTargetException()).add(name, loc); } @@ -91,7 +91,7 @@ public class OverloadFunction extends FunctionValue { } } catch (ReflectiveOperationException e) { - throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "")); + throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL); } } diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 0869990..8e807f7 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.json; import java.util.List; import java.util.stream.Collectors; +import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.parsing.Operator; import me.topchetoeu.jscript.parsing.ParseRes; @@ -13,17 +14,17 @@ public class JSON { public static ParseRes parseIdentifier(List tokens, int i) { return Parsing.parseIdentifier(tokens, i); } - public static ParseRes parseString(String filename, List tokens, int i) { + public static ParseRes parseString(Filename filename, List tokens, int i) { var res = Parsing.parseString(filename, tokens, i); if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n); else return res.transform(); } - public static ParseRes parseNumber(String filename, List tokens, int i) { + public static ParseRes parseNumber(Filename filename, List tokens, int i) { var res = Parsing.parseNumber(filename, tokens, i); if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n); else return res.transform(); } - public static ParseRes parseBool(String filename, List tokens, int i) { + public static ParseRes parseBool(Filename filename, List tokens, int i) { var id = parseIdentifier(tokens, i); if (!id.isSuccess()) return ParseRes.failed(); @@ -32,7 +33,7 @@ public class JSON { else return ParseRes.failed(); } - public static ParseRes parseValue(String filename, List tokens, int i) { + public static ParseRes parseValue(Filename filename, List tokens, int i) { return ParseRes.any( parseString(filename, tokens, i), parseNumber(filename, tokens, i), @@ -42,7 +43,7 @@ public class JSON { ); } - public static ParseRes parseMap(String filename, List tokens, int i) { + public static ParseRes parseMap(Filename filename, List tokens, int i) { int n = 0; if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); @@ -82,7 +83,7 @@ public class JSON { return ParseRes.res(values, n); } - public static ParseRes parseList(String filename, List tokens, int i) { + public static ParseRes parseList(Filename filename, List tokens, int i) { int n = 0; if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); @@ -109,7 +110,7 @@ public class JSON { return ParseRes.res(values, n); } - public static JSONElement parse(String filename, String raw) { + public static JSONElement parse(Filename filename, String raw) { var res = parseValue(filename, Parsing.tokenize(filename, raw), 0); if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given."); else if (res.isError()) throw new SyntaxException(null, res.error); @@ -120,7 +121,12 @@ public class JSON { if (el.isNumber()) return Double.toString(el.number()); if (el.isBoolean()) return el.bool() ? "true" : "false"; if (el.isNull()) return "null"; - if (el.isString()) return "\"" + el.string().replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + if (el.isString()) return "\"" + el.string() + .replace("\\", "\\\\") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\"", "\\\"") + + "\""; if (el.isList()) { var res = new StringBuilder().append("["); for (int i = 0; i < el.list().size(); i++) { diff --git a/src/me/topchetoeu/jscript/json/JSONElement.java b/src/me/topchetoeu/jscript/json/JSONElement.java index e26aa3a..8e03c7f 100644 --- a/src/me/topchetoeu/jscript/json/JSONElement.java +++ b/src/me/topchetoeu/jscript/json/JSONElement.java @@ -65,10 +65,22 @@ public class JSONElement { return (double)value; } public boolean bool() { - if (!isNumber()) throw new IllegalStateException("Element is not a boolean."); + if (!isBoolean()) throw new IllegalStateException("Element is not a boolean."); return (boolean)value; } + @Override + public String toString() { + if (isMap()) return "{...}"; + if (isList()) return "[...]"; + if (isString()) return (String)value; + if (isString()) return (String)value; + if (isNumber()) return (double)value + ""; + if (isBoolean()) return (boolean)value + ""; + if (isNull()) return "null"; + return ""; + } + private JSONElement(Type type, Object val) { this.type = type; this.value = val; diff --git a/src/me/topchetoeu/jscript/json/JSONList.java b/src/me/topchetoeu/jscript/json/JSONList.java index eb343ac..b73eb49 100644 --- a/src/me/topchetoeu/jscript/json/JSONList.java +++ b/src/me/topchetoeu/jscript/json/JSONList.java @@ -17,5 +17,7 @@ public class JSONList extends ArrayList { public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; } public JSONList add(Map val) { this.add(JSONElement.of(val)); return this; } public JSONList add(Collection val) { this.add(JSONElement.of(val)); return this; } + public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; } + public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; } } diff --git a/src/me/topchetoeu/jscript/json/JSONMap.java b/src/me/topchetoeu/jscript/json/JSONMap.java index 203adaf..dc6d845 100644 --- a/src/me/topchetoeu/jscript/json/JSONMap.java +++ b/src/me/topchetoeu/jscript/json/JSONMap.java @@ -51,7 +51,7 @@ public class JSONMap implements Map { public JSONMap map(String path) { var el = get(path); - if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); return el.map(); } public JSONMap map(String path, JSONMap defaultVal) { @@ -63,7 +63,7 @@ public class JSONMap implements Map { public JSONList list(String path) { var el = get(path); - if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); return el.list(); } public JSONList list(String path, JSONList defaultVal) { @@ -75,7 +75,7 @@ public class JSONMap implements Map { public String string(String path) { var el = get(path); - if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); return el.string(); } public String string(String path, String defaultVal) { @@ -87,7 +87,7 @@ public class JSONMap implements Map { public double number(String path) { var el = get(path); - if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); return el.number(); } public double number(String path, double defaultVal) { @@ -99,7 +99,7 @@ public class JSONMap implements Map { public boolean bool(String path) { var el = get(path); - if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); return el.bool(); } public boolean bool(String path, boolean defaultVal) { diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index 0783038..0991fce 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -12,7 +12,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeInit; public class ErrorLib { - private static String toString(Context ctx, 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 = ""; else name = Values.toString(ctx, name).trim(); if (message == null) message = ""; @@ -30,7 +30,10 @@ public class ErrorLib { } } - if (cause instanceof ObjectValue) res.append(toString(ctx, cause)); + if (cause instanceof ObjectValue) { + if (rethrown) res.append("\n (rethrown)"); + else res.append("\nCaused by ").append(toString(ctx, cause)); + } return res.toString(); } @@ -39,8 +42,10 @@ public class ErrorLib { if (thisArg instanceof ObjectValue) { var stack = Values.getMember(ctx, thisArg, "stack"); if (!(stack instanceof ArrayValue)) stack = null; + var cause = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.cause")); return toString(ctx, - Values.getMember(ctx, thisArg, "cause"), + thisArg == cause, + cause, Values.getMember(ctx, thisArg, "name"), Values.getMember(ctx, thisArg, "message"), (ArrayValue)stack @@ -53,7 +58,7 @@ public class ErrorLib { var target = new ObjectValue(); if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; - target.defineProperty(ctx, "stack", new ArrayValue(ctx, StackData.stackTrace(ctx))); + target.defineProperty(ctx, "stack", ArrayValue.of(ctx, StackData.stackTrace(ctx))); target.defineProperty(ctx, "name", "Error"); if (message == null) target.defineProperty(ctx, "message", ""); else target.defineProperty(ctx, "message", Values.toString(ctx, message)); diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 811f482..5fa83e2 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -1,7 +1,9 @@ package me.topchetoeu.jscript.lib; +import java.io.IOException; import java.util.HashMap; +import me.topchetoeu.jscript.Reading; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.DataKey; import me.topchetoeu.jscript.engine.Environment; @@ -14,12 +16,22 @@ public class Internals { private static final DataKey> THREADS = new DataKey<>(); private static final DataKey I = new DataKey<>(); + @Native public static void log(Context ctx, Object ...args) { for (var arg : args) { Values.printValue(ctx, arg); } System.out.println(); } + @Native public static String readline(Context ctx) { + try { + return Reading.read(); + } + catch (IOException e) { + e.printStackTrace(); + return null; + } + } @Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { var thread = new Thread(() -> { diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java index 8d612df..d8b202e 100644 --- a/src/me/topchetoeu/jscript/lib/JSONLib.java +++ b/src/me/topchetoeu/jscript/lib/JSONLib.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib; import java.util.HashSet; import java.util.stream.Collectors; +import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ObjectValue; @@ -72,7 +73,7 @@ public class JSONLib { @Native public static Object parse(Context ctx, String val) { try { - return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); + return toJS(me.topchetoeu.jscript.json.JSON.parse(new Filename("jscript", "json"), val)); } catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } } diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 1b53d2a..7c4f7db 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -234,12 +234,12 @@ public class PromiseLib { private boolean handled = false; private Object val; - private void resolve(Context ctx, Object val, int state) { + public void fulfill(Context ctx, Object val) { if (this.state != STATE_PENDING) return; if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, - new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], state); return null; }), - new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], STATE_REJECTED); return null; }) + new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }), + new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) ); else { Object next; @@ -248,29 +248,14 @@ public class PromiseLib { try { if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, - new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, state); return null; }), - new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, STATE_REJECTED); return null; }) + new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }), + new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) ); else { this.val = val; - this.state = state; + this.state = STATE_FULFILLED; - if (state == STATE_FULFILLED) { - for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val); - } - else if (state == STATE_REJECTED) { - for (var handle : handles) handle.rejected.call(handle.ctx, null, val); - if (handles.size() == 0) { - ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { - if (!handled) { - Values.printError(new EngineException(val).setContext(ctx), "(in promise)"); - throw new InterruptException(); - } - - return null; - }), null); - } - } + for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val); handles = null; } @@ -280,18 +265,46 @@ public class PromiseLib { } } } - - /** - * Thread safe - call from any thread - */ - public void fulfill(Context ctx, Object val) { - resolve(ctx, val, STATE_FULFILLED); - } - /** - * Thread safe - call from any thread - */ public void reject(Context ctx, Object val) { - resolve(ctx, val, STATE_REJECTED); + if (this.state != STATE_PENDING) return; + + if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, + new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }), + new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) + ); + else { + Object next; + try { next = Values.getMember(ctx, val, "next"); } + catch (IllegalArgumentException e) { next = null; } + + try { + if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, + new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }), + new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) + ); + else { + this.val = val; + this.state = STATE_REJECTED; + + for (var handle : handles) handle.rejected.call(handle.ctx, null, val); + if (handles.size() == 0) { + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + if (!handled) { + Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); + throw new InterruptException(); + } + + return null; + }), null); + } + + handles = null; + } + } + catch (EngineException err) { + this.reject(ctx, err.value); + } + } } private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index 63206ae..e7be32a 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -26,6 +26,7 @@ public class SymbolLib { @NativeGetter public static Symbol search(Context ctx) { return ctx.environment().symbol("Symbol.search"); } @NativeGetter public static Symbol iterator(Context ctx) { return ctx.environment().symbol("Symbol.iterator"); } @NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.environment().symbol("Symbol.asyncIterator"); } + @NativeGetter public static Symbol cause(Context ctx) { return ctx.environment().symbol("Symbol.cause"); } public final Symbol value; diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index aa74e01..1fea791 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -6,7 +6,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.TreeSet; +import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.*; import me.topchetoeu.jscript.compilation.Instruction.Type; @@ -25,7 +27,7 @@ import me.topchetoeu.jscript.parsing.ParseRes.State; // TODO: this has to be rewritten public class Parsing { public static interface Parser { - ParseRes parse(String filename, List tokens, int i); + ParseRes parse(Filename filename, List tokens, int i); } private static class ObjProp { @@ -69,7 +71,7 @@ public class Parsing { reserved.add("delete"); reserved.add("break"); reserved.add("continue"); - reserved.add("debug"); + reserved.add("debugger"); reserved.add("implements"); reserved.add("interface"); reserved.add("package"); @@ -122,7 +124,7 @@ public class Parsing { private static final int CURR_MULTI_COMMENT = 8; private static final int CURR_SINGLE_COMMENT = 9; - private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, String filename, List tokens) { + private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List tokens) { var res = currToken.toString(); switch (currStage) { @@ -143,7 +145,7 @@ public class Parsing { // This method is so long because we're tokenizing the string using an iterative approach // instead of a recursive descent parser. This is mainly done for performance reasons. - private static ArrayList splitTokens(String filename, String raw) { + private static ArrayList splitTokens(Filename filename, String raw) { var tokens = new ArrayList(); var currToken = new StringBuilder(64); @@ -572,7 +574,7 @@ public class Parsing { else return res; } - private static List parseTokens(String filename, Collection tokens) { + private static List parseTokens(Filename filename, Collection tokens) { var res = new ArrayList(); for (var el : tokens) { @@ -593,11 +595,11 @@ public class Parsing { return res; } - public static List tokenize(String filename, String raw) { + public static List tokenize(Filename filename, String raw) { return parseTokens(filename, splitTokens(filename, raw)); } - public static Location getLoc(String filename, List tokens, int i) { + public static Location getLoc(Filename filename, List tokens, int i) { if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename); if (i >= tokens.size()) i = tokens.size() - 1; return new Location(tokens.get(i).line, tokens.get(i).start, filename); @@ -663,7 +665,7 @@ public class Parsing { return !reserved.contains(name); } - public static ParseRes parseString(String filename, List tokens, int i) { + public static ParseRes parseString(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); try { if (tokens.get(i).isString()) { @@ -675,7 +677,7 @@ public class Parsing { return ParseRes.failed(); } } - public static ParseRes parseNumber(String filename, List tokens, int i) { + public static ParseRes parseNumber(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); try { if (tokens.get(i).isNumber()) { @@ -688,7 +690,7 @@ public class Parsing { } } - public static ParseRes parseRegex(String filename, List tokens, int i) { + public static ParseRes parseRegex(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); try { if (tokens.get(i).isRegex()) { @@ -705,7 +707,7 @@ public class Parsing { } } - public static ParseRes parseArray(String filename, List tokens, int i) { + public static ParseRes parseArray(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); @@ -744,7 +746,7 @@ public class Parsing { return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n); } - public static ParseRes> parseParamList(String filename, List tokens, int i) { + public static ParseRes> parseParamList(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -776,7 +778,7 @@ public class Parsing { return ParseRes.res(args, n); } - public static ParseRes parsePropName(String filename, List tokens, int i) { + public static ParseRes parsePropName(Filename filename, List tokens, int i) { var idRes = parseIdentifier(tokens, i); if (idRes.isSuccess()) return ParseRes.res(idRes.result, 1); var strRes = parseString(null, tokens, i); @@ -786,7 +788,7 @@ public class Parsing { return ParseRes.failed(); } - public static ParseRes parseObjectProp(String filename, List tokens, int i) { + public static ParseRes parseObjectProp(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -813,7 +815,7 @@ public class Parsing { new FunctionStatement(loc, access + " " + name.toString(), argsRes.result.toArray(String[]::new), res.result) ), n); } - public static ParseRes parseObject(String filename, List tokens, int i) { + public static ParseRes parseObject(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); @@ -870,7 +872,7 @@ public class Parsing { return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); } - public static ParseRes parseNew(String filename, List tokens, int i) { + public static ParseRes parseNew(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var n = 0; if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); @@ -886,7 +888,7 @@ public class Parsing { return ParseRes.res(new NewStatement(loc, call.func, call.args), n); } - public static ParseRes parseTypeof(String filename, List tokens, int i) { + public static ParseRes parseTypeof(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var n = 0; if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed(); @@ -897,7 +899,7 @@ public class Parsing { return ParseRes.res(new TypeofStatement(loc, valRes.result), n); } - public static ParseRes parseVoid(String filename, List tokens, int i) { + public static ParseRes parseVoid(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var n = 0; if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); @@ -908,7 +910,7 @@ public class Parsing { return ParseRes.res(new VoidStatement(loc, valRes.result), n); } - public static ParseRes parseDelete(String filename, List tokens, int i) { + public static ParseRes parseDelete(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed(); @@ -929,7 +931,7 @@ public class Parsing { } } - public static ParseRes parseFunction(String filename, List tokens, int i, boolean statement) { + public static ParseRes parseFunction(Filename filename, List tokens, int i, boolean statement) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -972,7 +974,7 @@ public class Parsing { else return ParseRes.error(loc, "Expected a compound statement for function.", res); } - public static ParseRes parseUnary(String filename, List tokens, int i) { + public static ParseRes parseUnary(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -993,7 +995,7 @@ public class Parsing { if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.value), res); } - public static ParseRes parsePrefixChange(String filename, List tokens, int i) { + public static ParseRes parsePrefixChange(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1010,7 +1012,7 @@ public class Parsing { if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator."); return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n); } - public static ParseRes parseParens(String filename, List tokens, int i) { + public static ParseRes parseParens(Filename filename, List tokens, int i) { int n = 0; if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); @@ -1023,7 +1025,7 @@ public class Parsing { return ParseRes.res(res.result, n); } @SuppressWarnings("all") - public static ParseRes parseSimple(String filename, List tokens, int i, boolean statement) { + public static ParseRes parseSimple(Filename filename, List tokens, int i, boolean statement) { var res = new ArrayList<>(); if (!statement) { @@ -1050,7 +1052,7 @@ public class Parsing { return ParseRes.any(res.toArray(ParseRes[]::new)); } - public static ParseRes parseVariable(String filename, List tokens, int i) { + public static ParseRes parseVariable(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var literal = parseIdentifier(tokens, i); @@ -1066,7 +1068,7 @@ public class Parsing { return ParseRes.res(new VariableStatement(loc, literal.result), 1); } - public static ParseRes parseLiteral(String filename, List tokens, int i) { + public static ParseRes parseLiteral(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var id = parseIdentifier(tokens, i); if (!id.isSuccess()) return id.transform(); @@ -1094,7 +1096,7 @@ public class Parsing { } return ParseRes.failed(); } - public static ParseRes parseMember(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseMember(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); var n = 0; @@ -1107,7 +1109,7 @@ public class Parsing { return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n); } - public static ParseRes parseIndex(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseIndex(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); var n = 0; @@ -1123,7 +1125,7 @@ public class Parsing { return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n); } - public static ParseRes parseAssign(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseAssign(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); int n = 0 ; @@ -1157,7 +1159,7 @@ public class Parsing { return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n); } - public static ParseRes parseCall(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseCall(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); var n = 0; @@ -1189,7 +1191,7 @@ public class Parsing { return ParseRes.res(new CallStatement(loc, prev, args.toArray(Statement[]::new)), n); } - public static ParseRes parsePostfixChange(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parsePostfixChange(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1207,7 +1209,7 @@ public class Parsing { if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator."); return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n); } - public static ParseRes parseInstanceof(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseInstanceof(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1220,7 +1222,7 @@ public class Parsing { return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); } - public static ParseRes parseIn(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseIn(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1233,7 +1235,7 @@ public class Parsing { return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); } - public static ParseRes parseComma(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); var n = 0; @@ -1246,7 +1248,7 @@ public class Parsing { return ParseRes.res(new CompoundStatement(loc, prev, res.result), n); } - public static ParseRes parseTernary(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseTernary(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); var n = 0; @@ -1265,7 +1267,7 @@ public class Parsing { return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n); } - public static ParseRes parseOperator(String filename, List tokens, int i, Statement prev, int precedence) { + public static ParseRes parseOperator(Filename filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); var n = 0; @@ -1290,7 +1292,7 @@ public class Parsing { return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); } - public static ParseRes parseValue(String filename, List tokens, int i, int precedence, boolean statement) { + public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence, boolean statement) { Statement prev = null; int n = 0; @@ -1331,11 +1333,11 @@ public class Parsing { if (prev == null) return ParseRes.failed(); else return ParseRes.res(prev, n); } - public static ParseRes parseValue(String filename, List tokens, int i, int precedence) { + public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence) { return parseValue(filename, tokens, i, precedence, false); } - public static ParseRes parseValueStatement(String filename, List tokens, int i) { + public static ParseRes parseValueStatement(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var valRes = parseValue(filename, tokens, i, 0, true); if (!valRes.isSuccess()) return valRes.transform(); @@ -1352,7 +1354,7 @@ public class Parsing { } else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res); } - public static ParseRes parseVariableDeclare(String filename, List tokens, int i) { + public static ParseRes parseVariableDeclare(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed(); @@ -1396,7 +1398,7 @@ public class Parsing { } } - public static ParseRes parseReturn(String filename, List tokens, int i) { + public static ParseRes parseReturn(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed(); @@ -1420,7 +1422,7 @@ public class Parsing { else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); } - public static ParseRes parseThrow(String filename, List tokens, int i) { + public static ParseRes parseThrow(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed(); @@ -1438,7 +1440,7 @@ public class Parsing { else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); } - public static ParseRes parseBreak(String filename, List tokens, int i) { + public static ParseRes parseBreak(Filename filename, List tokens, int i) { if (!isIdentifier(tokens, i, "break")) return ParseRes.failed(); if (isStatementEnd(tokens, i + 1)) { @@ -1456,7 +1458,7 @@ public class Parsing { } else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); } - public static ParseRes parseContinue(String filename, List tokens, int i) { + public static ParseRes parseContinue(Filename filename, List tokens, int i) { if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed(); if (isStatementEnd(tokens, i + 1)) { @@ -1474,8 +1476,8 @@ public class Parsing { } else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); } - public static ParseRes parseDebug(String filename, List tokens, int i) { - if (!isIdentifier(tokens, i, "debug")) return ParseRes.failed(); + public static ParseRes parseDebug(Filename filename, List tokens, int i) { + if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed(); if (isStatementEnd(tokens, i + 1)) { if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2); @@ -1484,7 +1486,7 @@ public class Parsing { else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); } - public static ParseRes parseCompound(String filename, List tokens, int i) { + public static ParseRes parseCompound(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); @@ -1520,7 +1522,7 @@ public class Parsing { return ParseRes.res(nameRes.result, n); } - public static ParseRes parseIf(String filename, List tokens, int i) { + public static ParseRes parseIf(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1547,7 +1549,7 @@ public class Parsing { return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n); } - public static ParseRes parseWhile(String filename, List tokens, int i) { + public static ParseRes parseWhile(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1569,7 +1571,7 @@ public class Parsing { return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n); } - public static ParseRes parseSwitchCase(String filename, List tokens, int i) { + public static ParseRes parseSwitchCase(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1589,7 +1591,7 @@ public class Parsing { return ParseRes.res(null, 2); } - public static ParseRes parseSwitch(String filename, List tokens, int i) { + public static ParseRes parseSwitch(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1649,7 +1651,7 @@ public class Parsing { statements.toArray(Statement[]::new) ), n); } - public static ParseRes parseDoWhile(String filename, List tokens, int i) { + public static ParseRes parseDoWhile(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1678,7 +1680,7 @@ public class Parsing { } else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon."); } - public static ParseRes parseFor(String filename, List tokens, int i) { + public static ParseRes parseFor(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1736,7 +1738,7 @@ public class Parsing { return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n); } - public static ParseRes parseForIn(String filename, List tokens, int i) { + public static ParseRes parseForIn(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1789,7 +1791,7 @@ public class Parsing { return ParseRes.res(new ForInStatement(loc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); } - public static ParseRes parseCatch(String filename, List tokens, int i) { + public static ParseRes parseCatch(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -1830,7 +1832,7 @@ public class Parsing { return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n); } - public static ParseRes parseStatement(String filename, List tokens, int i) { + public static ParseRes parseStatement(Filename filename, List tokens, int i) { if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i)), 1); if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed."); return ParseRes.any( @@ -1853,7 +1855,7 @@ public class Parsing { ); } - public static Statement[] parse(String filename, String raw) { + public static Statement[] parse(Filename filename, String raw) { var tokens = tokenize(filename, raw); var list = new ArrayList(); int i = 0; @@ -1874,10 +1876,10 @@ public class Parsing { return list.toArray(Statement[]::new); } - public static CodeFunction compile(HashMap funcs, Environment environment, Statement ...statements) { + public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Statement ...statements) { var target = environment.global.globalChild(); var subscope = target.child(); - var res = new CompileTarget(funcs); + var res = new CompileTarget(funcs, breakpoints); var body = new CompoundStatement(null, statements); if (body instanceof CompoundStatement) body = (CompoundStatement)body; else body = new CompoundStatement(null, new Statement[] { body }); @@ -1903,9 +1905,9 @@ public class Parsing { return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.array()); } - public static CodeFunction compile(HashMap funcs, Environment environment, String filename, String raw) { + public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Filename filename, String raw) { try { - return compile(funcs, environment, parse(filename, raw)); + return compile(funcs, breakpoints, environment, parse(filename, raw)); } catch (SyntaxException e) { return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) }); -- 2.45.2 From edb71daef4d6b66d4309af277928c72aa869f07c Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:58:33 +0300 Subject: [PATCH 6/7] feat: fully implement local and capture scope object wrappers feat: implement object sending and receiving --- src/assets/index.html | 9 +- .../engine/debug => assets}/protocol.json | 45 +++ src/me/topchetoeu/jscript/Main.java | 3 +- .../jscript/compilation/CompileTarget.java | 4 +- .../compilation/CompoundStatement.java | 13 +- .../jscript/compilation/FunctionBody.java | 17 ++ .../jscript/compilation/Instruction.java | 8 - .../compilation/VariableDeclareStatement.java | 14 +- .../compilation/values/CallStatement.java | 2 +- .../compilation/values/FunctionStatement.java | 4 +- .../compilation/values/IndexStatement.java | 4 +- src/me/topchetoeu/jscript/engine/Engine.java | 10 +- .../jscript/engine/debug/DebugHandler.java | 12 + .../jscript/engine/debug/DebugServer.java | 31 +- .../jscript/engine/debug/HttpRequest.java | 16 +- .../jscript/engine/debug/SimpleDebugger.java | 264 +++++++++++++++--- .../jscript/engine/debug/WebSocket.java | 1 - .../jscript/engine/frame/CodeFrame.java | 32 ++- .../jscript/engine/frame/Runners.java | 9 - .../jscript/engine/scope/LocalScope.java | 64 ----- .../engine/scope/LocalScopeRecord.java | 3 + .../jscript/engine/values/CodeFunction.java | 8 +- .../jscript/engine/values/ScopeValue.java | 51 ++++ src/me/topchetoeu/jscript/json/JSON.java | 63 +++++ src/me/topchetoeu/jscript/json/JSONList.java | 3 + src/me/topchetoeu/jscript/lib/JSONLib.java | 68 +---- .../topchetoeu/jscript/parsing/Parsing.java | 13 +- 27 files changed, 535 insertions(+), 236 deletions(-) rename src/{me/topchetoeu/jscript/engine/debug => assets}/protocol.json (98%) create mode 100644 src/me/topchetoeu/jscript/compilation/FunctionBody.java create mode 100644 src/me/topchetoeu/jscript/engine/values/ScopeValue.java diff --git a/src/assets/index.html b/src/assets/index.html index ee35b77..8a57f1f 100644 --- a/src/assets/index.html +++ b/src/assets/index.html @@ -16,14 +16,15 @@

Here are the available entrypoints:

- Developed by TopchetoEU, MIT License + Running ${NAME} v${VERSION} by ${AUTHOR}

\ No newline at end of file diff --git a/src/me/topchetoeu/jscript/engine/debug/protocol.json b/src/assets/protocol.json similarity index 98% rename from src/me/topchetoeu/jscript/engine/debug/protocol.json rename to src/assets/protocol.json index 0470b51..246e736 100644 --- a/src/me/topchetoeu/jscript/engine/debug/protocol.json +++ b/src/assets/protocol.json @@ -1957,6 +1957,51 @@ ] } ] + }, + { + "domain": "NodeWorker", + "description": "Implemented partly just because of pesky vscode.", + "deprecated": true, + "commands": [ + { + "name": "enable", + "description": "Used to get the attachedToWorker event", + "parameters": [ ] + } + ], + "events": [ + { + "name": "attachedToWorker", + "description": "Issued when attached to a worker.", + "parameters": [ + { "name": "sessionId", + "description": "Identifier assigned to the session used to send/receive messages.", + "$ref": "string" + }, + { + "name": "workerInfo", + "type": "object", + "properties": [ + { "name": "workerId", + "$ref": "string" + }, + { "name": "type", + "type": "string" + }, + { "name": "title", + "type": "string" + }, + { "name": "url", + "type": "string" + } + ] + }, + { "name": "waitingForDebugger", + "type": "boolean" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 80c6620..76407f7 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -23,6 +23,7 @@ public class Main { static Thread engineTask, debugTask; static Engine engine; static Environment env; + static int j = 0; private static Observer valuePrinter = new Observer() { public void next(Object data) { @@ -59,7 +60,7 @@ public class Main { env.global.define("go", _ctx -> { try { var f = Path.of("do.js"); - var func = _ctx.compile(Filename.fromFile(f.toFile()), new String(Files.readAllBytes(f))); + var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); return func.call(_ctx); } catch (IOException e) { diff --git a/src/me/topchetoeu/jscript/compilation/CompileTarget.java b/src/me/topchetoeu/jscript/compilation/CompileTarget.java index 4b529cc..1484d19 100644 --- a/src/me/topchetoeu/jscript/compilation/CompileTarget.java +++ b/src/me/topchetoeu/jscript/compilation/CompileTarget.java @@ -8,7 +8,7 @@ import me.topchetoeu.jscript.Location; public class CompileTarget { public final Vector target = new Vector<>(); - public final Map functions; + public final Map functions; public final TreeSet breakpoints; public Instruction add(Instruction instr) { @@ -31,7 +31,7 @@ public class CompileTarget { public Instruction[] array() { return target.toArray(Instruction[]::new); } - public CompileTarget(Map functions, TreeSet breakpoints) { + public CompileTarget(Map functions, TreeSet breakpoints) { this.functions = functions; this.breakpoints = breakpoints; } diff --git a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java index cbee47b..d86a5f9 100644 --- a/src/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ b/src/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -11,6 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class CompoundStatement extends Statement { public final Statement[] statements; + public Location end; @Override public void declare(ScopeRecord varsScope) { @@ -32,11 +33,16 @@ public class CompoundStatement extends Statement { for (var i = 0; i < statements.length; i++) { var stm = statements[i]; - + if (stm instanceof FunctionStatement) continue; if (i != statements.length - 1) stm.compileWithDebug(target, scope, false); else stm.compileWithDebug(target, scope, pollute); } + + if (end != null) { + target.add(Instruction.nop().locate(end)); + target.setDebug(); + } } @Override @@ -59,6 +65,11 @@ public class CompoundStatement extends Statement { else return new CompoundStatement(loc(), res.toArray(Statement[]::new)); } + public CompoundStatement setEnd(Location loc) { + this.end = loc; + return this; + } + public CompoundStatement(Location loc, Statement ...statements) { super(loc); this.statements = statements; diff --git a/src/me/topchetoeu/jscript/compilation/FunctionBody.java b/src/me/topchetoeu/jscript/compilation/FunctionBody.java new file mode 100644 index 0000000..718eeec --- /dev/null +++ b/src/me/topchetoeu/jscript/compilation/FunctionBody.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.compilation; + +public class FunctionBody { + public final Instruction[] instructions; + public final String[] captureNames, localNames; + + public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) { + this.instructions = instructions; + this.captureNames = captureNames; + this.localNames = localNames; + } + public FunctionBody(Instruction[] instructions) { + this.instructions = instructions; + this.captureNames = new String[0]; + this.localNames = new String[0]; + } +} diff --git a/src/me/topchetoeu/jscript/compilation/Instruction.java b/src/me/topchetoeu/jscript/compilation/Instruction.java index 3099de9..0ba8e06 100644 --- a/src/me/topchetoeu/jscript/compilation/Instruction.java +++ b/src/me/topchetoeu/jscript/compilation/Instruction.java @@ -147,14 +147,6 @@ public class Instruction { public static Instruction debug() { return new Instruction(null, Type.NOP, "debug"); } - public static Instruction debugVarNames(String[] names) { - var args = new Object[names.length + 1]; - args[0] = "dbg_vars"; - - System.arraycopy(names, 0, args, 1, names.length); - - return new Instruction(null, Type.NOP, args); - } public static Instruction nop(Object ...params) { for (var param : params) { diff --git a/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java b/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java index 667a982..c51de30 100644 --- a/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java +++ b/src/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java @@ -10,10 +10,12 @@ public class VariableDeclareStatement extends Statement { public static class Pair { public final String name; public final Statement value; + public final Location location; - public Pair(String name, Statement value) { + public Pair(String name, Statement value, Location location) { this.name = name; this.value = value; + this.location = location; } } @@ -30,16 +32,20 @@ public class VariableDeclareStatement extends Statement { for (var entry : values) { if (entry.name == null) continue; var key = scope.getKey(entry.name); - if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc())); + int start = target.size(); + + if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location)); if (entry.value instanceof FunctionStatement) { ((FunctionStatement)entry.value).compile(target, scope, entry.name, false); - target.add(Instruction.storeVar(key).locate(loc())); + target.add(Instruction.storeVar(key).locate(entry.location)); } else if (entry.value != null) { entry.value.compile(target, scope, true); - target.add(Instruction.storeVar(key).locate(loc())); + target.add(Instruction.storeVar(key).locate(entry.location)); } + + if (target.size() != start) target.setDebug(start); } if (pollute) target.add(Instruction.loadValue(null).locate(loc())); diff --git a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java index db34e44..a14be7d 100644 --- a/src/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/CallStatement.java @@ -16,7 +16,7 @@ public class CallStatement extends Statement { ((IndexStatement)func).compile(target, scope, true, true); } else { - target.add(Instruction.loadValue(null).locate(loc())); + target.add(Instruction.loadValue(null).locate(loc())); func.compile(target, scope, true); } diff --git a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java index e75f80e..52c292a 100644 --- a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -5,6 +5,7 @@ import java.util.Random; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompoundStatement; +import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Instruction.Type; @@ -69,7 +70,6 @@ public class FunctionStatement extends Statement { } body.declare(subscope); - funcTarget.add(Instruction.debugVarNames(subscope.locals())); body.compile(funcTarget, subscope, false); funcTarget.add(Instruction.ret().locate(loc())); checkBreakAndCont(funcTarget, start); @@ -77,7 +77,7 @@ public class FunctionStatement extends Statement { var id = rand.nextLong(); target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc())); - target.functions.put(id, funcTarget.array()); + target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals())); if (name == null) name = this.name; diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java index b2494d6..b821cd0 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -20,17 +20,17 @@ public class IndexStatement extends AssignableStatement { return new IndexAssignStatement(loc(), object, index, val, operation); } public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { - int start = 0; object.compile(target, scope, true); if (dupObj) target.add(Instruction.dup().locate(loc())); if (index instanceof ConstantStatement) { target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); + target.setDebug(); return; } index.compile(target, scope, true); target.add(Instruction.loadMember().locate(loc())); - target.setDebug(start); + target.setDebug(); if (!pollute) target.add(Instruction.discard().locate(loc())); } @Override diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index a9d638d..6c8ed51 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -4,7 +4,7 @@ import java.util.HashMap; import java.util.concurrent.LinkedBlockingDeque; import me.topchetoeu.jscript.Filename; -import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.DataNotifier; @@ -51,7 +51,7 @@ public class Engine { private LinkedBlockingDeque microTasks = new LinkedBlockingDeque<>(); public final int id = ++nextId; - public final HashMap functions = new HashMap<>(); + public final HashMap functions = new HashMap<>(); public final Data data = new Data().set(StackData.MAX_FRAMES, 10000); private void runTask(Task task) { @@ -63,8 +63,8 @@ public class Engine { task.notifier.error(e); } } - private void run() { - while (true) { + public void run(boolean untilEmpty) { + while (!untilEmpty || !macroTasks.isEmpty()) { try { runTask(macroTasks.take()); @@ -83,7 +83,7 @@ public class Engine { public Thread start() { if (this.thread == null) { - this.thread = new Thread(this::run, "JavaScript Runner #" + id); + this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id); this.thread.start(); } return this.thread; diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java index b10f961..7d4e2f9 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java @@ -20,4 +20,16 @@ public interface DebugHandler { void stepOver(V8Message msg); void setPauseOnExceptions(V8Message msg); + + void evaluateOnCallFrame(V8Message msg); + + void getProperties(V8Message msg); + void releaseObjectGroup(V8Message msg); + /** + * This method might not execute the actual code for well-known requests + */ + void callFunctionOn(V8Message msg); + + // void nodeWorkerEnable(V8Message msg); + void runtimeEnable(V8Message msg); } diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index 54eade4..c816f3e 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -8,6 +8,7 @@ import java.security.MessageDigest; import java.util.Base64; import java.util.HashMap; +import me.topchetoeu.jscript.Metadata; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.UncheckedException; @@ -17,11 +18,11 @@ import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; public class DebugServer { - public static String browserDisplayName = "jscript"; + public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION; public final HashMap targets = new HashMap<>(); - private final byte[] favicon, index; + private final byte[] favicon, index, protocol; private static void send(HttpRequest req, String val) throws IOException { req.writeResponse(200, "OK", "application/json", val.getBytes()); @@ -58,7 +59,7 @@ public class DebugServer { try { msg = new V8Message(raw.textData()); - System.out.println(msg.name + ": " + JSON.stringify(msg.params)); + // System.out.println(msg.name + ": " + JSON.stringify(msg.params)); } catch (SyntaxException e) { ws.send(new V8Error(e.getMessage())); @@ -86,6 +87,13 @@ public class DebugServer { case "Debugger.stepOver": debugger.stepOver(msg); continue; case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue; + case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue; + + case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue; + case "Runtime.getProperties": debugger.getProperties(msg); continue; + case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue; + // case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue; + case "Runtime.enable": debugger.runtimeEnable(msg); continue; } if ( @@ -96,8 +104,7 @@ public class DebugServer { msg.name.startsWith("Network.") || msg.name.startsWith("Page.") ) ws.send(new V8Error("This isn't a browser...")); - - if (msg.name.startsWith("Runtime.") || msg.name.startsWith("Profiler.")) ws.send(new V8Error("This API is not supported yet.")); + else ws.send(new V8Error("This API is not supported yet.")); } catch (Throwable e) { e.printStackTrace(); @@ -158,7 +165,7 @@ public class DebugServer { switch (req.path) { case "/json/version": - send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}"); + send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}"); break; case "/json/list": case "/json": { @@ -176,9 +183,11 @@ public class DebugServer { send(req, JSON.stringify(res)); break; } + case "/json/protocol": + req.writeResponse(200, "OK", "application/json", protocol); + break; case "/json/new": case "/json/activate": - case "/json/protocol": case "/json/close": case "/devtools/inspector.html": req.writeResponse( @@ -216,7 +225,13 @@ public class DebugServer { public DebugServer() { try { this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes(); - this.index = getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes(); + this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); + var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); + this.index = index + .replace("${NAME}", Metadata.NAME) + .replace("${VERSION}", Metadata.VERSION) + .replace("${AUTHOR}", Metadata.AUTHOR) + .getBytes(); } catch (IOException e) { throw new UncheckedIOException(e); } } diff --git a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java index d327d8e..bff2bdb 100644 --- a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java +++ b/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java @@ -10,8 +10,6 @@ import java.util.HashMap; import java.util.IllegalFormatException; import java.util.Map; -import me.topchetoeu.jscript.exceptions.UncheckedIOException; - public class HttpRequest { public final String method; public final String path; @@ -21,19 +19,19 @@ public class HttpRequest { public void writeCode(int code, String name) { try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeHeader(String name, String value) { try { out.write((name + ": " + value + "\r\n").getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeLastHeader(String name, String value) { try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeHeadersEnd() { try { out.write("\n".getBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeResponse(int code, String name, String type, byte[] data) { @@ -44,13 +42,13 @@ public class HttpRequest { out.write(data); out.close(); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public void writeResponse(int code, String name, String type, InputStream data) { try { writeResponse(code, name, type, data.readAllBytes()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException e) { } } public HttpRequest(String method, String path, Map headers, OutputStream out) { @@ -98,7 +96,7 @@ public class HttpRequest { return new HttpRequest(method, path, headers, socket.getOutputStream()); } - catch (IOException e) { throw new UncheckedIOException(e); } + catch (IOException | NullPointerException e) { return null; } } } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index 96dbe93..0e38bd3 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -3,9 +3,11 @@ package me.topchetoeu.jscript.engine.debug; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Random; import java.util.TreeSet; import java.util.regex.Pattern; +import java.util.stream.Collectors; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; @@ -16,6 +18,7 @@ import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; +import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; @@ -24,6 +27,7 @@ import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Notifier; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; @@ -35,6 +39,8 @@ import me.topchetoeu.jscript.lib.SetLib; import me.topchetoeu.jscript.lib.GeneratorLib.Generator; public class SimpleDebugger implements Debugger { + public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e idToObject = new HashMap<>(); private HashMap objectToId = new HashMap<>(); + private HashMap objectToCtx = new HashMap<>(); + private HashMap> objectGroups = new HashMap<>(); private Notifier updateNotifier = new Notifier(); @@ -191,19 +219,19 @@ public class SimpleDebugger implements Debugger { return tail.first(); } - public Location deserializeLocation(JSONElement el, boolean correct) { + private Location deserializeLocation(JSONElement el, boolean correct) { if (!el.isMap()) throw new RuntimeException("Expected location to be a map."); var id = Integer.parseInt(el.map().string("scriptId")); var line = (int)el.map().number("lineNumber") + 1; var column = (int)el.map().number("columnNumber") + 1; - if (!idToSource.containsKey(id)) throw new RuntimeException("The specified source %s doesn't exist.".formatted(id)); + if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id)); var res = new Location(line, column, idToSource.get(id).filename); if (correct) res = correctLocation(idToSource.get(id), res); return res; } - public JSONMap serializeLocation(Location loc) { + private JSONMap serializeLocation(Location loc) { var source = filenameToId.get(loc.filename()); return new JSONMap() .set("scriptId", source + "") @@ -211,21 +239,21 @@ public class SimpleDebugger implements Debugger { .set("columnNumber", loc.start() - 1); } - private Integer objectId(ObjectValue obj) { + private Integer objectId(Context ctx, ObjectValue obj) { if (objectToId.containsKey(obj)) return objectToId.get(obj); else { int id = nextId(); objectToId.put(obj, id); + if (ctx != null) objectToCtx.put(obj, ctx); idToObject.put(id, obj); return id; } } - private JSONMap serializeObj(Context ctx, Object val) { + private JSONMap serializeObj(Context ctx, Object val, boolean recurse) { val = Values.normalize(null, val); if (val == Values.NULL) { return new JSONMap() - .set("objectId", objectId(null) + "") .set("type", "object") .set("subtype", "null") .setNull("value") @@ -234,13 +262,14 @@ public class SimpleDebugger implements Debugger { if (val instanceof ObjectValue) { var obj = (ObjectValue)val; - var id = objectId(obj); + var id = objectId(ctx, obj); var type = "object"; String subtype = null; String className = null; if (obj instanceof FunctionValue) type = "function"; if (obj instanceof ArrayValue) subtype = "array"; + if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp"; if (Values.isWrapper(val, DateLib.class)) subtype = "date"; if (Values.isWrapper(val, MapLib.class)) subtype = "map"; @@ -253,10 +282,13 @@ public class SimpleDebugger implements Debugger { var res = new JSONMap() .set("type", type) - .set("objetId", id + ""); + .set("objectId", id + ""); if (subtype != null) res.set("subtype", subtype); - if (className != null) res.set("className", className); + if (className != null) { + res.set("className", className); + res.set("description", "{ " + className + " }"); + } return res; } @@ -271,7 +303,7 @@ public class SimpleDebugger implements Debugger { if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity"); else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity"); - else if (num == -0.) res.set("unserializableValue", "-0"); + else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0"); else if (Double.isNaN(num)) res.set("unserializableValue", "NaN"); else res.set("value", num); @@ -280,6 +312,37 @@ public class SimpleDebugger implements Debugger { throw new IllegalArgumentException("Unexpected JS object."); } + private JSONMap serializeObj(Context ctx, Object val) { + return serializeObj(ctx, val, true); + } + private void setObjectGroup(String name, Object val) { + if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + + if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj); + else objectGroups.put(name, new ArrayList<>(List.of(obj))); + } + } + private void releaseGroup(String name) { + var objs = objectGroups.remove(name); + + if (objs != null) { + for (var obj : objs) { + var id = objectToId.remove(obj); + if (id != null) idToObject.remove(id); + } + } + } + private Object deserializeArgument(JSONMap val) { + if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))); + else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) { + case "NaN": return Double.NaN; + case "-Infinity": return Double.NEGATIVE_INFINITY; + case "Infinity": return Double.POSITIVE_INFINITY; + case "-0": return -0.; + } + return JSON.toJs(val.get("value")); + } private void resume(State state) { this.state = state; @@ -296,7 +359,7 @@ public class SimpleDebugger implements Debugger { ws.send(new V8Event("Debugger.paused", map)); } private void pauseException(Context ctx) { - state = State.PAUSED_NORMAL; + state = State.PAUSED_EXCEPTION; var map = new JSONMap() .set("callFrames", serializeFrames(ctx)) .set("reason", "exception"); @@ -322,6 +385,27 @@ public class SimpleDebugger implements Debugger { )); } + private RunResult run(Frame codeFrame, String code) { + var engine = new Engine(); + var env = codeFrame.func.environment.fork(); + + ObjectValue global = env.global.obj, + capture = codeFrame.frame.getCaptureScope(null, enabled), + local = codeFrame.frame.getLocalScope(null, enabled); + + capture.setPrototype(null, global); + local.setPrototype(null, capture); + env.global = new GlobalScope(local); + + var ctx = new Context(engine).pushEnv(env); + var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), code, codeFrame.frame.thisArg, codeFrame.frame.args); + + engine.run(true); + + try { return new RunResult(ctx, awaiter.await(), null); } + catch (EngineException e) { return new RunResult(ctx, null, e); } + } + @Override public void enable(V8Message msg) { enabled = true; ws.send(msg.respond()); @@ -342,9 +426,9 @@ public class SimpleDebugger implements Debugger { ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); } @Override public void getPossibleBreakpoints(V8Message msg) { + var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId"))); var start = deserializeLocation(msg.params.get("start"), false); var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null; - var src = idToSource.get(filenameToId.get(start.filename())); var res = new JSONList(); @@ -464,6 +548,101 @@ public class SimpleDebugger implements Debugger { } } + @Override public void evaluateOnCallFrame(V8Message msg) { + var cfId = Integer.parseInt(msg.params.string("callFrameId")); + var expr = msg.params.string("expression"); + var group = msg.params.string("objectGroup", null); + + var cf = idToFrame.get(cfId); + var res = run(cf, expr); + + if (group != null) setObjectGroup(group, res.result); + + ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result)))); + } + + @Override public void releaseObjectGroup(V8Message msg) { + var group = msg.params.string("objectGroup"); + releaseGroup(group); + ws.send(msg.respond()); + } + @Override public void getProperties(V8Message msg) { + var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var own = msg.params.bool("ownProperties") || true; + var currOwn = true; + + var res = new JSONList(); + + while (obj != null) { + var ctx = objectToCtx.get(obj); + + for (var key : obj.keys(true)) { + var propDesc = new JSONMap(); + + if (obj.properties.containsKey(key)) { + var prop = obj.properties.get(key); + + propDesc.set("name", Values.toString(ctx, key)); + if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); + if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", currOwn); + res.add(propDesc); + } + else { + propDesc.set("name", Values.toString(ctx, key)); + propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); + propDesc.set("writable", obj.memberWritable(key)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", currOwn); + res.add(propDesc); + } + } + + obj = obj.getPrototype(ctx); + + var protoDesc = new JSONMap(); + protoDesc.set("name", "__proto__"); + protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj)); + protoDesc.set("writable", true); + protoDesc.set("enumerable", false); + protoDesc.set("configurable", false); + protoDesc.set("isOwn", currOwn); + res.add(protoDesc); + + currOwn = false; + if (own) break; + } + + ws.send(msg.respond(new JSONMap().set("result", res))); + } + @Override public void callFunctionOn(V8Message msg) { + var src = msg.params.string("functionDeclaration"); + var args = msg.params.list("arguments", new JSONList()).stream().map(v -> v.map()).map(this::deserializeArgument).collect(Collectors.toList()); + var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var ctx = objectToCtx.get(thisArg); + + switch (src) { + case CHROME_GET_PROP_FUNC: { + var path = JSON.parse(new Filename("tmp", "json"), (String)args.get(0)).list(); + Object res = thisArg; + for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el)); + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); + return; + } + default: + ws.send(new V8Error("A non well-known function was used with callFunctionOn.")); + break; + } + } + + @Override + public void runtimeEnable(V8Message msg) { + ws.send(msg.respond()); + } + @Override public void onSource(Filename filename, String source, TreeSet locations) { int id = nextId(); var src = new Source(id, filename, source, locations); @@ -488,6 +667,9 @@ public class SimpleDebugger implements Debugger { updateFrames(ctx); var frame = codeFrameToFrame.get(cf); + + if (!frame.debugData) return false; + if (instruction.location != null) frame.updateLoc(instruction.location); var loc = frame.location; var isBreakpointable = loc != null && ( @@ -501,15 +683,12 @@ public class SimpleDebugger implements Debugger { pauseException(ctx); } else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { - pauseDebug(ctx, locToBreakpoint.get(loc)); - } - else if (isBreakpointable && tmpBreakpts.contains(loc)) { - pauseDebug(ctx, null); - tmpBreakpts.remove(loc); - } - else if (instruction.type == Type.NOP && instruction.match("debug")) { - pauseDebug(ctx, null); + var bp = locToBreakpoint.get(loc); + var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition)); + if (ok) pauseDebug(ctx, locToBreakpoint.get(loc)); } + else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); + else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null); while (enabled) { switch (state) { @@ -527,12 +706,17 @@ public class SimpleDebugger implements Debugger { else return false; break; case STEPPING_OVER: - if ( - stepOutFrame == frame && ( + if (stepOutFrame.frame == frame.frame) { + if (returnVal != Runners.NO_RETURN) { + state = State.STEPPING_OUT; + return false; + } + else if (isBreakpointable && ( !loc.filename().equals(prevLocation.filename()) || loc.line() != prevLocation.line() - ) - ) pauseDebug(ctx, null); + )) pauseDebug(ctx, null); + else return false; + } else return false; break; } @@ -544,14 +728,12 @@ public class SimpleDebugger implements Debugger { @Override public void onFramePop(Context ctx, CodeFrame frame) { updateFrames(ctx); - try { - idToFrame.remove(codeFrameToFrame.remove(frame).id); - } + try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } catch (NullPointerException e) { } if (StackData.frames(ctx).size() == 0) resume(State.RESUMED); else if (stepOutFrame != null && stepOutFrame.frame == frame && - (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) + (state == State.STEPPING_OUT || state == State.STEPPING_IN) ) { pauseDebug(ctx, null); updateNotifier.await(); diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java index 15001ba..6e6efa9 100644 --- a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java +++ b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java @@ -7,7 +7,6 @@ import java.io.OutputStream; import java.net.Socket; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; -import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.UncheckedIOException; public class WebSocket implements AutoCloseable { diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 3fb3d17..1e2b4c7 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -10,6 +10,8 @@ import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.ScopeValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; @@ -56,6 +58,33 @@ public class CodeFrame { public boolean jumpFlag = false; private Location prevLoc = null; + public ObjectValue getLocalScope(Context ctx, boolean props) { + var names = new String[scope.locals.length]; + + for (int i = 0; i < scope.locals.length; i++) { + var name = "local_" + (i - 2); + + if (i == 0) name = "this"; + else if (i == 1) name = "arguments"; + else if (i < function.localNames.length) name = function.localNames[i]; + + names[i] = name; + } + + return new ScopeValue(scope.locals, names); + } + public ObjectValue getCaptureScope(Context ctx, boolean props) { + var names = new String[scope.captures.length]; + + for (int i = 0; i < scope.captures.length; i++) { + var name = "capture_" + (i - 2); + if (i < function.captureNames.length) name = function.captureNames[i]; + names[i] = name; + } + + return new ScopeValue(scope.captures, names); + } + public void addTry(int n, int catchN, int finallyN) { var res = new TryCtx(codePtr + 1, n, catchN, finallyN); @@ -144,7 +173,7 @@ public class CodeFrame { } else if (returnValue != Runners.NO_RETURN) { if (tryCtx.hasFinally) { - tryCtx.retVal = error; + tryCtx.retVal = returnValue; newState = TryCtx.STATE_FINALLY_RETURNED; } break; @@ -215,6 +244,7 @@ public class CodeFrame { case TryCtx.STATE_CATCH: scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); codePtr = tryCtx.catchStart; + if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, true); break; default: codePtr = tryCtx.finallyStart; diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 4fac093..0c5366d 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -301,15 +301,6 @@ public class Runners { return NO_RETURN; } public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) { - if (instr.is(0, "dbg_names")) { - var names = new String[instr.params.length - 1]; - for (var i = 0; i < instr.params.length - 1; i++) { - if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters."); - names[i] = (String)instr.params[i + 1]; - } - frame.scope.setNames(names); - } - frame.codePtr++; return NO_RETURN; } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index bccbcdb..7645aaf 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -2,12 +2,7 @@ package me.topchetoeu.jscript.engine.scope; import java.util.ArrayList; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.NativeFunction; -import me.topchetoeu.jscript.engine.values.ObjectValue; - public class LocalScope { - private String[] names; public final ValueVariable[] captures; public final ValueVariable[] locals; public final ArrayList catchVars = new ArrayList<>(); @@ -19,70 +14,11 @@ public class LocalScope { else return captures[~i]; } - public String[] getCaptureNames() { - var res = new String[captures.length]; - - for (int i = 0; i < captures.length; i++) { - if (names == null || i >= names.length) res[i] = "capture_" + (i); - else res[i] = names[i]; - } - - return res; - } - public String[] getLocalNames() { - var res = new String[locals.length]; - - for (int i = captures.length, j = 0; i < locals.length; i++, j++) { - if (names == null || i >= names.length) { - if (j == 0) res[j] = "this"; - else if (j == 1) res[j] = "arguments"; - else res[i] = "local_" + (j - 2); - } - else res[i] = names[i]; - } - - return res; - } - public void setNames(String[] names) { - this.names = names; - } public int size() { return captures.length + locals.length; } - public void applyToObject(Context ctx, ObjectValue locals, ObjectValue captures, boolean props) { - var localNames = getLocalNames(); - var captureNames = getCaptureNames(); - - for (var i = 0; i < this.locals.length; i++) { - var name = localNames[i]; - var _i = i; - - if (props) locals.defineProperty(ctx, name, - new NativeFunction(name, (_ctx, thisArg, args) -> this.locals[_i].get(_ctx)), - this.locals[i].readonly ? null : - new NativeFunction(name, (_ctx, thisArg, args) -> { this.locals[_i].set(_ctx, args.length < 1 ? null : args[0]); return null; }), - true, true - ); - else locals.defineProperty(ctx, name, this.locals[i].get(ctx)); - } - for (var i = 0; i < this.captures.length; i++) { - var name = captureNames[i]; - var _i = i; - - if (props) captures.defineProperty(ctx, name, - new NativeFunction(name, (_ctx, thisArg, args) -> this.captures[_i].get(_ctx)), - this.captures[i].readonly ? null : - new NativeFunction(name, (_ctx, thisArg, args) -> { this.captures[_i].set(_ctx, args.length < 1 ? null : args[0]); return null; }), - true, true - ); - else captures.defineProperty(ctx, name, this.captures[i].get(ctx)); - } - - captures.setPrototype(ctx, locals); - } - public LocalScope(int n, ValueVariable[] captures) { locals = new ValueVariable[n]; this.captures = captures; diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java index 12b7d8c..d196716 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java @@ -11,6 +11,9 @@ public class LocalScopeRecord implements ScopeRecord { private final ArrayList captures = new ArrayList<>(); private final ArrayList locals = new ArrayList<>(); + public String[] captures() { + return captures.toArray(String[]::new); + } public String[] locals() { return locals.toArray(String[]::new); } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index 8b1b924..e2dc2bf 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.engine.values; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; @@ -13,6 +14,7 @@ public class CodeFunction extends FunctionValue { public final int localsN; public final int length; public final Instruction[] body; + public final String[] captureNames, localNames; public final ValueVariable[] captures; public Environment environment; @@ -45,12 +47,14 @@ public class CodeFunction extends FunctionValue { } } - public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { + public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) { super(name, length); this.captures = captures; + this.captureNames = body.captureNames; + this.localNames = body.localNames; this.environment = environment; this.localsN = localsN; this.length = length; - this.body = body; + this.body = body.instructions; } } diff --git a/src/me/topchetoeu/jscript/engine/values/ScopeValue.java b/src/me/topchetoeu/jscript/engine/values/ScopeValue.java new file mode 100644 index 0000000..768e3c2 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/values/ScopeValue.java @@ -0,0 +1,51 @@ +package me.topchetoeu.jscript.engine.values; + +import java.util.HashMap; +import java.util.List; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.scope.ValueVariable; + +public class ScopeValue extends ObjectValue { + public final ValueVariable[] variables; + public final HashMap names = new HashMap<>(); + + @Override + protected Object getField(Context ctx, Object key) { + if (names.containsKey(key)) return variables[names.get(key)].get(ctx); + return super.getField(ctx, key); + } + @Override + protected boolean setField(Context ctx, Object key, Object val) { + if (names.containsKey(key)) { + variables[names.get(key)].set(ctx, val); + return true; + } + + return super.setField(ctx, key, val); + } + @Override + protected void deleteField(Context ctx, Object key) { + if (names.containsKey(key)) return; + super.deleteField(ctx, key); + } + @Override + protected boolean hasField(Context ctx, Object key) { + if (names.containsKey(key)) return true; + return super.hasField(ctx, key); + } + @Override + public List keys(boolean includeNonEnumerable) { + var res = super.keys(includeNonEnumerable); + res.addAll(names.keySet()); + return res; + } + + public ScopeValue(ValueVariable[] variables, String[] names) { + this.variables = variables; + for (var i = 0; i < names.length && i < variables.length; i++) { + this.names.put(names[i], i); + this.nonConfigurableSet.add(names[i]); + } + } +} diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 8e807f7..7d58bf4 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -1,9 +1,15 @@ package me.topchetoeu.jscript.json; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.parsing.Operator; import me.topchetoeu.jscript.parsing.ParseRes; @@ -11,6 +17,63 @@ import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.parsing.Token; public class JSON { + public static Object toJs(JSONElement val) { + if (val.isBoolean()) return val.bool(); + if (val.isString()) return val.string(); + if (val.isNumber()) return val.number(); + if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList())); + if (val.isMap()) { + var res = new ObjectValue(); + for (var el : val.map().entrySet()) { + res.defineProperty(null, el.getKey(), toJs(el.getValue())); + } + return res; + } + if (val.isNull()) return Values.NULL; + return null; + } + private static JSONElement fromJs(Context ctx, Object val, HashSet prev) { + if (val instanceof Boolean) return JSONElement.bool((boolean)val); + if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue()); + if (val instanceof String) return JSONElement.string((String)val); + if (val == Values.NULL) return JSONElement.NULL; + if (val instanceof ObjectValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONMap(); + + for (var el : ((ObjectValue)val).keys(false)) { + var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev); + if (jsonEl == null) continue; + if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val instanceof ArrayValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONList(); + + for (var el : ((ArrayValue)val).toArray()) { + var jsonEl = fromJs(ctx, el, prev); + if (jsonEl == null) jsonEl = JSONElement.NULL; + res.add(jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val == null) return null; + return null; + } + public static JSONElement fromJs(Context ctx, Object val) { + return fromJs(ctx, val, new HashSet<>()); + } + public static ParseRes parseIdentifier(List tokens, int i) { return Parsing.parseIdentifier(tokens, i); } diff --git a/src/me/topchetoeu/jscript/json/JSONList.java b/src/me/topchetoeu/jscript/json/JSONList.java index b73eb49..214ca40 100644 --- a/src/me/topchetoeu/jscript/json/JSONList.java +++ b/src/me/topchetoeu/jscript/json/JSONList.java @@ -10,6 +10,9 @@ public class JSONList extends ArrayList { public JSONList(JSONElement ...els) { super(List.of(els)); } + public JSONList(Collection els) { + super(els); + } public JSONList addNull() { this.add(JSONElement.NULL); return this; } public JSONList add(String val) { this.add(JSONElement.of(val)); return this; } diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java index d8b202e..8ea57d5 100644 --- a/src/me/topchetoeu/jscript/lib/JSONLib.java +++ b/src/me/topchetoeu/jscript/lib/JSONLib.java @@ -1,84 +1,22 @@ package me.topchetoeu.jscript.lib; -import java.util.HashSet; -import java.util.stream.Collectors; - import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.values.ArrayValue; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.json.JSONElement; -import me.topchetoeu.jscript.json.JSONList; -import me.topchetoeu.jscript.json.JSONMap; +import me.topchetoeu.jscript.json.JSON; public class JSONLib { - private static Object toJS(JSONElement val) { - if (val.isBoolean()) return val.bool(); - if (val.isString()) return val.string(); - if (val.isNumber()) return val.number(); - if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSONLib::toJS).collect(Collectors.toList())); - if (val.isMap()) { - var res = new ObjectValue(); - for (var el : val.map().entrySet()) { - res.defineProperty(null, el.getKey(), toJS(el.getValue())); - } - return res; - } - if (val.isNull()) return Values.NULL; - return null; - } - private static JSONElement toJSON(Context ctx, Object val, HashSet prev) { - if (val instanceof Boolean) return JSONElement.bool((boolean)val); - if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue()); - if (val instanceof String) return JSONElement.string((String)val); - if (val == Values.NULL) return JSONElement.NULL; - if (val instanceof ObjectValue) { - if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); - prev.add(val); - - var res = new JSONMap(); - - for (var el : ((ObjectValue)val).keys(false)) { - var jsonEl = toJSON(ctx, ((ObjectValue)val).getMember(ctx, el), prev); - if (jsonEl == null) continue; - if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); - } - - prev.remove(val); - return JSONElement.of(res); - } - if (val instanceof ArrayValue) { - if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); - prev.add(val); - - var res = new JSONList(); - - for (var el : ((ArrayValue)val).toArray()) { - var jsonEl = toJSON(ctx, el, prev); - if (jsonEl == null) jsonEl = JSONElement.NULL; - res.add(jsonEl); - } - - prev.remove(val); - return JSONElement.of(res); - } - if (val == null) return null; - return null; - } - @Native public static Object parse(Context ctx, String val) { try { - return toJS(me.topchetoeu.jscript.json.JSON.parse(new Filename("jscript", "json"), val)); + return JSON.toJs(JSON.parse(new Filename("jscript", "json"), val)); } catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } } @Native public static String stringify(Context ctx, Object val) { - return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>())); + return me.topchetoeu.jscript.json.JSON.stringify(JSON.fromJs(ctx, val)); } } diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index 1fea791..5411fe4 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -1367,6 +1367,7 @@ public class Parsing { } while (true) { + var nameLoc = getLoc(filename, tokens, i + n); var nameRes = parseIdentifier(tokens, i + n++); if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name."); @@ -1384,7 +1385,7 @@ public class Parsing { val = valRes.result; } - res.add(new Pair(nameRes.result, val)); + res.add(new Pair(nameRes.result, val, nameLoc)); if (isOperator(tokens, i + n, Operator.COMMA)) { n++; @@ -1512,7 +1513,7 @@ public class Parsing { statements.add(res.result); } - return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)), n); + return ParseRes.res(new CompoundStatement(loc, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); } public static ParseRes parseLabel(List tokens, int i) { int n = 0; @@ -1876,7 +1877,7 @@ public class Parsing { return list.toArray(Statement[]::new); } - public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Statement ...statements) { + public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Statement ...statements) { var target = environment.global.globalChild(); var subscope = target.child(); var res = new CompileTarget(funcs, breakpoints); @@ -1903,14 +1904,14 @@ public class Parsing { } else res.add(Instruction.ret()); - return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.array()); + return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals())); } - public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Filename filename, String raw) { + public static CodeFunction compile(HashMap funcs, TreeSet breakpoints, Environment environment, Filename filename, String raw) { try { return compile(funcs, breakpoints, environment, parse(filename, raw)); } catch (SyntaxException e) { - return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) }); + return new CodeFunction(environment, null, 2, 0, new ValueVariable[0], new FunctionBody(new Instruction[] { Instruction.throwSyntax(e).locate(e.loc) })); } } } -- 2.45.2 From dc9d84a370ecbd1c6e46c87df52928cada0cd6ea Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:10:27 +0300 Subject: [PATCH 7/7] chore: nothing of use --- .../jscript/engine/debug/SimpleDebugger.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index 0e38bd3..a900973 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -344,6 +344,25 @@ public class SimpleDebugger implements Debugger { return JSON.toJs(val.get("value")); } + private JSONMap serializeException(Context ctx, EngineException err) { + String text = null; + + try { + text = Values.toString(ctx, err.value); + } + catch (EngineException e) { + text = "[error while stringifying]"; + } + + var res = new JSONMap() + .set("exceptionId", nextId()) + .set("exception", serializeObj(ctx, err.value)) + .set("exception", serializeObj(ctx, err.value)) + .set("text", text); + + return res; + } + private void resume(State state) { this.state = state; ws.send(new V8Event("Debugger.resumed", new JSONMap())); @@ -558,6 +577,8 @@ public class SimpleDebugger implements Debugger { if (group != null) setObjectGroup(group, res.result); + if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeObj(res.ctx, res.result)))); + ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result)))); } -- 2.45.2