From c0b895e00ac3b320fdc678e5c98f73496af47ca0 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:22:18 +0200 Subject: [PATCH] feat: greatly improve Context API --- src/me/topchetoeu/jscript/engine/Context.java | 105 +++++++++++------- .../jscript/engine/Environment.java | 2 +- .../jscript/engine/debug/SimpleDebugger.java | 13 +-- .../jscript/engine/frame/CodeFrame.java | 4 +- .../jscript/engine/frame/Runners.java | 14 +-- .../jscript/engine/values/CodeFunction.java | 12 +- .../jscript/engine/values/NativeWrapper.java | 2 +- .../jscript/engine/values/Values.java | 2 +- .../jscript/exceptions/EngineException.java | 4 +- .../jscript/lib/AsyncFunctionLib.java | 5 +- .../jscript/lib/AsyncGeneratorLib.java | 5 +- .../topchetoeu/jscript/lib/GeneratorLib.java | 4 +- src/me/topchetoeu/jscript/lib/Internals.java | 4 +- src/me/topchetoeu/jscript/lib/PromiseLib.java | 10 +- .../jscript/modules/ModuleRepo.java | 2 +- 15 files changed, 98 insertions(+), 90 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 361d9e2..e5c6138 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -2,9 +2,8 @@ package me.topchetoeu.jscript.engine; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.Iterator; import java.util.List; -import java.util.Stack; import java.util.TreeSet; import java.util.stream.Collectors; @@ -14,35 +13,44 @@ import me.topchetoeu.jscript.engine.debug.DebugContext; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.lib.EnvironmentLib; import me.topchetoeu.jscript.mapping.SourceMap; -public class Context extends ExtensionStack { - private final Stack env = new Stack<>(); - private final ArrayList frames = new ArrayList<>(); +public class Context implements Extensions { + public final Context parent; + public final Environment environment; + public final CodeFrame frame; public final Engine engine; + public final int stackSize; - @Override - protected Extensions[] extensionStack() { - return new Extensions[] { environment(), engine.globalEnvironment }; + @Override public void add(Symbol key, T obj) { + if (environment != null) environment.add(key, obj); + else if (engine != null) engine.globalEnvironment.add(key, obj); } + @Override public T get(Symbol key) { + if (environment != null && environment.has(key)) return environment.get(key); + else if (engine != null && engine.globalEnvironment.has(key)) return engine.globalEnvironment.get(key); + return null; + } + @Override public boolean has(Symbol key) { + return + environment != null && environment.has(key) || + engine != null && engine.globalEnvironment.has(key); + } + @Override public boolean remove(Symbol key) { + var res = false; - public Environment environment() { - return env.empty() ? null : env.peek(); - } + if (environment != null) res |= environment.remove(key); + else if (engine != null) res |= engine.globalEnvironment.remove(key); - private Context pushEnv(Environment env) { - this.env.push(env); - return this; - } - private void popEnv() { - if (!env.empty()) this.env.pop(); + return res; } public FunctionValue compile(Filename filename, String raw) { - var env = environment(); + var env = environment; var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), new EnvironmentLib(env)); var function = (FunctionValue)Values.getMember(this, result, "function"); @@ -74,33 +82,37 @@ public class Context extends ExtensionStack { return function; } - public void pushFrame(CodeFrame frame) { - frames.add(frame); - if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); - pushEnv(frame.function.environment); - DebugContext.get(this).onFramePush(this, frame); - } - public boolean popFrame(CodeFrame frame) { - if (frames.size() == 0) return false; - if (frames.get(frames.size() - 1) != frame) return false; - frames.remove(frames.size() - 1); - popEnv(); - DebugContext.get(this).onFramePop(this, frame); - return true; - } - public CodeFrame peekFrame() { - if (frames.size() == 0) return null; - return frames.get(frames.size() - 1); + public Context pushFrame(CodeFrame frame) { + var res = new Context(this, frame.function.environment, frame, engine, stackSize + 1); + DebugContext.get(res).onFramePush(res, frame); + return res; } - public List frames() { - return Collections.unmodifiableList(frames); + public Iterable frames() { + var self = this; + return () -> new Iterator() { + private Context curr = self; + + private void update() { + while (curr.frame == null && curr != null) curr = curr.parent; + } + + @Override public boolean hasNext() { + update(); + return curr != null; + } + @Override public CodeFrame next() { + update(); + var res = curr.frame; + curr = curr.parent; + return res; + } + }; } public List stackTrace() { var res = new ArrayList(); - for (var i = frames.size() - 1; i >= 0; i--) { - var el = frames.get(i); + for (var el : frames()) { var name = el.function.name; Location loc = null; @@ -120,11 +132,20 @@ public class Context extends ExtensionStack { return res; } - public Context(Engine engine) { + private Context(Context parent, Environment environment, CodeFrame frame, Engine engine, int stackSize) { + this.parent = parent; + this.environment = environment; + this.frame = frame; this.engine = engine; + this.stackSize = stackSize; + + if (engine != null && stackSize > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); + } + + public Context(Engine engine) { + this(null, null, null, engine, 0); } public Context(Engine engine, Environment env) { - this(engine); - if (env != null) this.pushEnv(env); + this(null, env, null, engine, 0); } } diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index a8202f3..4a0c0a5 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -88,7 +88,7 @@ public class Environment implements Extensions { } public static FunctionValue regexConstructor(Extensions ext) { return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); + throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment, ctx.engine); })); } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index fdcd510..eb16afd 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -234,7 +234,7 @@ public class SimpleDebugger implements Debugger { } private synchronized void updateFrames(Context ctx) { - var frame = ctx.peekFrame(); + var frame = ctx.frame; if (frame == null) return; if (!codeFrameToFrame.containsKey(frame)) { @@ -249,10 +249,9 @@ public class SimpleDebugger implements Debugger { } private JSONList serializeFrames(Context ctx) { var res = new JSONList(); - var frames = ctx.frames(); - for (var i = frames.size() - 1; i >= 0; i--) { - var frame = codeFrameToFrame.get(frames.get(i)); + for (var el : ctx.frames()) { + var frame = codeFrameToFrame.get(el); if (frame.location == null) continue; frame.serialized.set("location", serializeLocation(frame.location)); if (frame.location != null) res.add(frame.serialized); @@ -484,7 +483,7 @@ public class SimpleDebugger implements Debugger { env.global = new GlobalScope(codeFrame.local); var ctx = new Context(engine, env); - var awaiter = engine.pushMsg(false, ctx.environment(), new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); + var awaiter = engine.pushMsg(false, ctx.environment, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); engine.run(true); @@ -496,7 +495,7 @@ public class SimpleDebugger implements Debugger { var res = new ArrayValue(); var passed = new HashSet(); var tildas = "~"; - if (target == null) target = ctx.environment().global; + if (target == null) target = ctx.environment.global; for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) { for (var el : Values.getMembers(ctx, proto, true, true)) { @@ -944,7 +943,7 @@ public class SimpleDebugger implements Debugger { try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } catch (NullPointerException e) { } - if (ctx.frames().size() == 0) { + if (ctx.stackSize == 0) { if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED); } else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) { diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index bc2d032..9cecef2 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -94,6 +94,7 @@ public class CodeFrame { public final Object[] args; public final Stack tryStack = new Stack<>(); public final CodeFunction function; + public final Context ctx; public Object[] stack = new Object[32]; public int stackPtr = 0; public int codePtr = 0; @@ -187,7 +188,7 @@ public class CodeFrame { stack[stackPtr++] = Values.normalize(ctx, val); } - public Object next(Context ctx, Object value, Object returnValue, EngineException error) { + public Object next(Object value, Object returnValue, EngineException error) { if (value != Runners.NO_RETURN) push(ctx, value); Instruction instr = null; @@ -314,5 +315,6 @@ public class CodeFrame { this.thisArg = thisArg; this.function = func; + this.ctx = ctx.pushFrame(this); } } diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index c9f0bb1..7a5d59c 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -50,7 +50,7 @@ public class Runners { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) { var name = (String)instr.get(0); - ctx.environment().global.define(name); + ctx.environment.global.define(name); frame.codePtr++; return NO_RETURN; } @@ -142,7 +142,7 @@ public class Runners { 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)); + 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++; @@ -154,7 +154,7 @@ public class Runners { return NO_RETURN; } public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, ctx.environment().global.obj); + frame.push(ctx, ctx.environment.global.obj); frame.codePtr++; return NO_RETURN; } @@ -173,7 +173,7 @@ public class Runners { captures[i - 1] = frame.scope.get(instr.get(i)); } - var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures); + var func = new CodeFunction(ctx.environment, "", Engine.functions.get(id), captures); frame.push(ctx, func); @@ -227,7 +227,7 @@ public class Runners { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var i = instr.get(0); - if (i instanceof String) ctx.environment().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++; @@ -274,8 +274,8 @@ public class Runners { Object obj; if (name != null) { - if (ctx.environment().global.has(ctx, name)) { - obj = ctx.environment().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 d506b81..deaa0de 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -32,16 +32,10 @@ public class CodeFunction extends FunctionValue { @Override public Object call(Context ctx, Object thisArg, Object ...args) { var frame = new CodeFrame(ctx, thisArg, args, this); - try { - ctx.pushFrame(frame); - while (true) { - var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); - if (res != Runners.NO_RETURN) return res; - } - } - finally { - ctx.popFrame(frame); + while (true) { + var res = frame.next(Runners.NO_RETURN, Runners.NO_RETURN, null); + if (res != Runners.NO_RETURN) return res; } } diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index f0b847c..f76c7bc 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -8,7 +8,7 @@ public class NativeWrapper extends ObjectValue { @Override public ObjectValue getPrototype(Context ctx) { - if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.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/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 851bdfb..7107513 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -477,7 +477,7 @@ public class Values { if (val instanceof Class) { if (ctx == null) return null; - else return ctx.environment().wrappers.getConstr((Class)val); + else return ctx.environment.wrappers.getConstr((Class)val); } return new NativeWrapper(val); diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index e5b619f..3b784d1 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -38,7 +38,7 @@ public class EngineException extends RuntimeException { if (function.equals("")) function = null; if (ctx == null) this.ctx = null; - else this.ctx = new Context(ctx.engine, ctx.environment()); + else this.ctx = new Context(ctx.engine, ctx.environment); this.location = location; this.function = function; } @@ -53,7 +53,7 @@ public class EngineException extends RuntimeException { public EngineException add(Context ctx, String name, Location location) { var el = new StackElement(ctx, location, name); if (el.function == null && el.location == null) return this; - setCtx(ctx.environment(), ctx.engine); + setCtx(ctx.environment, ctx.engine); stackTrace.add(el); return this; } diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index 385a9b4..e8b65cb 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -20,12 +20,11 @@ import me.topchetoeu.jscript.interop.Native; private void next(Context ctx, Object inducedValue, Object inducedError) { Object res = null; - ctx.pushFrame(frame); awaiting = false; while (!awaiting) { try { - res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); + res = frame.next(inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedValue = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { promise.fulfill(ctx, res); @@ -38,8 +37,6 @@ import me.topchetoeu.jscript.interop.Native; } } - ctx.popFrame(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 0a2dd47..23688cc 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -28,12 +28,11 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - ctx.pushFrame(frame); state = 0; while (state == 0) { try { - res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); + res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedValue = inducedReturn = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { var obj = new ObjectValue(); @@ -49,8 +48,6 @@ import me.topchetoeu.jscript.interop.Native; } } - ctx.popFrame(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/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index 9df2175..101f10c 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -24,12 +24,11 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - ctx.pushFrame(frame); yielding = false; while (!yielding) { try { - res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); + res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedReturn = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { done = true; @@ -42,7 +41,6 @@ import me.topchetoeu.jscript.interop.Native; } } - ctx.popFrame(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 c36df08..4ad447e 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -58,7 +58,7 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.engine.pushMsg(false, ctx.environment(), func, null, args); + ctx.engine.pushMsg(false, ctx.environment, func, null, args); }); thread.start(); @@ -75,7 +75,7 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.engine.pushMsg(false, ctx.environment(), func, null, args); + ctx.engine.pushMsg(false, ctx.environment, func, null, args); } }); diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 66f5191..3adac3a 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -253,7 +253,7 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_FULFILLED; - ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> { + ctx.engine.pushMsg(true, ctx.environment, new NativeFunction((_ctx, _thisArg, _args) -> { for (var handle : handles) { handle.fulfilled.call(handle.ctx, null, val); } @@ -288,10 +288,10 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_REJECTED; - ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> { + ctx.engine.pushMsg(true, ctx.environment, new NativeFunction((_ctx, _thisArg, _args) -> { for (var handle : handles) handle.rejected.call(handle.ctx, null, val); if (!handled) { - Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); + Values.printError(new EngineException(val).setCtx(ctx.environment, ctx.engine), "(in promise)"); } handles = null; return null; @@ -305,9 +305,9 @@ import me.topchetoeu.jscript.interop.Native; } private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { - if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx.environment(), fulfill, null, val); + if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx.environment, fulfill, null, val); else if (state == STATE_REJECTED) { - ctx.engine.pushMsg(true, ctx.environment(), reject, null, val); + ctx.engine.pushMsg(true, ctx.environment, reject, null, val); handled = true; } else handles.add(new Handle(ctx, fulfill, reject)); diff --git a/src/me/topchetoeu/jscript/modules/ModuleRepo.java b/src/me/topchetoeu/jscript/modules/ModuleRepo.java index 9a14d23..90d1d8d 100644 --- a/src/me/topchetoeu/jscript/modules/ModuleRepo.java +++ b/src/me/topchetoeu/jscript/modules/ModuleRepo.java @@ -25,7 +25,7 @@ public interface ModuleRepo { if (modules.containsKey(name)) return modules.get(name); - var env = ctx.environment().child(); + var env = ctx.environment.child(); env.add(CWD, fs.normalize(name, "..")); var mod = new SourceModule(filename, src, env);