feat: greatly improve Context API

This commit is contained in:
TopchetoEU 2023-12-27 14:22:18 +02:00
parent 9ea5cd9277
commit c0b895e00a
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
15 changed files with 98 additions and 90 deletions

View File

@ -2,9 +2,8 @@ package me.topchetoeu.jscript.engine;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Stack;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors; 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.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.lib.EnvironmentLib; import me.topchetoeu.jscript.lib.EnvironmentLib;
import me.topchetoeu.jscript.mapping.SourceMap; import me.topchetoeu.jscript.mapping.SourceMap;
public class Context extends ExtensionStack { public class Context implements Extensions {
private final Stack<Environment> env = new Stack<>(); public final Context parent;
private final ArrayList<CodeFrame> frames = new ArrayList<>(); public final Environment environment;
public final CodeFrame frame;
public final Engine engine; public final Engine engine;
public final int stackSize;
@Override @Override public <T> void add(Symbol key, T obj) {
protected Extensions[] extensionStack() { if (environment != null) environment.add(key, obj);
return new Extensions[] { environment(), engine.globalEnvironment }; else if (engine != null) engine.globalEnvironment.add(key, obj);
} }
@Override public <T> 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() { if (environment != null) res |= environment.remove(key);
return env.empty() ? null : env.peek(); else if (engine != null) res |= engine.globalEnvironment.remove(key);
}
private Context pushEnv(Environment env) { return res;
this.env.push(env);
return this;
}
private void popEnv() {
if (!env.empty()) this.env.pop();
} }
public FunctionValue compile(Filename filename, String raw) { 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 result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), new EnvironmentLib(env));
var function = (FunctionValue)Values.getMember(this, result, "function"); var function = (FunctionValue)Values.getMember(this, result, "function");
@ -74,33 +82,37 @@ public class Context extends ExtensionStack {
return function; return function;
} }
public void pushFrame(CodeFrame frame) { public Context pushFrame(CodeFrame frame) {
frames.add(frame); var res = new Context(this, frame.function.environment, frame, engine, stackSize + 1);
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); DebugContext.get(res).onFramePush(res, frame);
pushEnv(frame.function.environment); return res;
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 List<CodeFrame> frames() { public Iterable<CodeFrame> frames() {
return Collections.unmodifiableList(frames); var self = this;
return () -> new Iterator<CodeFrame>() {
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<String> stackTrace() { public List<String> stackTrace() {
var res = new ArrayList<String>(); var res = new ArrayList<String>();
for (var i = frames.size() - 1; i >= 0; i--) { for (var el : frames()) {
var el = frames.get(i);
var name = el.function.name; var name = el.function.name;
Location loc = null; Location loc = null;
@ -120,11 +132,20 @@ public class Context extends ExtensionStack {
return res; 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.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) { public Context(Engine engine, Environment env) {
this(engine); this(null, env, null, engine, 0);
if (env != null) this.pushEnv(env);
} }
} }

View File

@ -88,7 +88,7 @@ public class Environment implements Extensions {
} }
public static FunctionValue regexConstructor(Extensions ext) { public static FunctionValue regexConstructor(Extensions ext) {
return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", (ctx, thisArg, args) -> { 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);
})); }));
} }

View File

@ -234,7 +234,7 @@ public class SimpleDebugger implements Debugger {
} }
private synchronized void updateFrames(Context ctx) { private synchronized void updateFrames(Context ctx) {
var frame = ctx.peekFrame(); var frame = ctx.frame;
if (frame == null) return; if (frame == null) return;
if (!codeFrameToFrame.containsKey(frame)) { if (!codeFrameToFrame.containsKey(frame)) {
@ -249,10 +249,9 @@ public class SimpleDebugger implements Debugger {
} }
private JSONList serializeFrames(Context ctx) { private JSONList serializeFrames(Context ctx) {
var res = new JSONList(); var res = new JSONList();
var frames = ctx.frames();
for (var i = frames.size() - 1; i >= 0; i--) { for (var el : ctx.frames()) {
var frame = codeFrameToFrame.get(frames.get(i)); var frame = codeFrameToFrame.get(el);
if (frame.location == null) continue; if (frame.location == null) continue;
frame.serialized.set("location", serializeLocation(frame.location)); frame.serialized.set("location", serializeLocation(frame.location));
if (frame.location != null) res.add(frame.serialized); if (frame.location != null) res.add(frame.serialized);
@ -484,7 +483,7 @@ public class SimpleDebugger implements Debugger {
env.global = new GlobalScope(codeFrame.local); env.global = new GlobalScope(codeFrame.local);
var ctx = new Context(engine, env); 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); engine.run(true);
@ -496,7 +495,7 @@ public class SimpleDebugger implements Debugger {
var res = new ArrayValue(); var res = new ArrayValue();
var passed = new HashSet<String>(); var passed = new HashSet<String>();
var tildas = "~"; 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 proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) {
for (var el : Values.getMembers(ctx, proto, true, true)) { 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); } try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
catch (NullPointerException e) { } catch (NullPointerException e) { }
if (ctx.frames().size() == 0) { if (ctx.stackSize == 0) {
if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED); if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED);
} }
else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) { else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) {

View File

@ -94,6 +94,7 @@ public class CodeFrame {
public final Object[] args; public final Object[] args;
public final Stack<TryCtx> tryStack = new Stack<>(); public final Stack<TryCtx> tryStack = new Stack<>();
public final CodeFunction function; public final CodeFunction function;
public final Context ctx;
public Object[] stack = new Object[32]; public Object[] stack = new Object[32];
public int stackPtr = 0; public int stackPtr = 0;
public int codePtr = 0; public int codePtr = 0;
@ -187,7 +188,7 @@ public class CodeFrame {
stack[stackPtr++] = Values.normalize(ctx, val); 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); if (value != Runners.NO_RETURN) push(ctx, value);
Instruction instr = null; Instruction instr = null;
@ -314,5 +315,6 @@ public class CodeFrame {
this.thisArg = thisArg; this.thisArg = thisArg;
this.function = func; this.function = func;
this.ctx = ctx.pushFrame(this);
} }
} }

View File

@ -50,7 +50,7 @@ public class Runners {
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
var name = (String)instr.get(0); var name = (String)instr.get(0);
ctx.environment().global.define(name); ctx.environment.global.define(name);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -142,7 +142,7 @@ public class Runners {
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) frame.push(ctx, ctx.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)); else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++; frame.codePtr++;
@ -154,7 +154,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.environment().global.obj); frame.push(ctx, ctx.environment.global.obj);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -173,7 +173,7 @@ public class Runners {
captures[i - 1] = frame.scope.get(instr.get(i)); 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); frame.push(ctx, func);
@ -227,7 +227,7 @@ public class Runners {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) ctx.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); else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++; frame.codePtr++;
@ -274,8 +274,8 @@ public class Runners {
Object obj; Object obj;
if (name != null) { if (name != null) {
if (ctx.environment().global.has(ctx, name)) { if (ctx.environment.global.has(ctx, name)) {
obj = ctx.environment().global.get(ctx, name); obj = ctx.environment.global.get(ctx, name);
} }
else obj = null; else obj = null;
} }

View File

@ -32,16 +32,10 @@ public class CodeFunction extends FunctionValue {
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) { public Object call(Context ctx, Object thisArg, Object ...args) {
var frame = new CodeFrame(ctx, thisArg, args, this); var frame = new CodeFrame(ctx, thisArg, args, this);
try {
ctx.pushFrame(frame);
while (true) { while (true) {
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); var res = frame.next(Runners.NO_RETURN, Runners.NO_RETURN, null);
if (res != Runners.NO_RETURN) return res; if (res != Runners.NO_RETURN) return res;
}
}
finally {
ctx.popFrame(frame);
} }
} }

View File

@ -8,7 +8,7 @@ public class NativeWrapper extends ObjectValue {
@Override @Override
public ObjectValue getPrototype(Context ctx) { 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); else return super.getPrototype(ctx);
} }

View File

@ -477,7 +477,7 @@ public class Values {
if (val instanceof Class) { if (val instanceof Class) {
if (ctx == null) return null; 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); return new NativeWrapper(val);

View File

@ -38,7 +38,7 @@ public class EngineException extends RuntimeException {
if (function.equals("")) function = null; if (function.equals("")) function = null;
if (ctx == null) this.ctx = 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.location = location;
this.function = function; this.function = function;
} }
@ -53,7 +53,7 @@ public class EngineException extends RuntimeException {
public EngineException add(Context ctx, String name, Location location) { public EngineException add(Context ctx, String name, Location location) {
var el = new StackElement(ctx, location, name); var el = new StackElement(ctx, location, name);
if (el.function == null && el.location == null) return this; if (el.function == null && el.location == null) return this;
setCtx(ctx.environment(), ctx.engine); setCtx(ctx.environment, ctx.engine);
stackTrace.add(el); stackTrace.add(el);
return this; return this;
} }

View File

@ -20,12 +20,11 @@ import me.topchetoeu.jscript.interop.Native;
private void next(Context ctx, Object inducedValue, Object inducedError) { private void next(Context ctx, Object inducedValue, Object inducedError) {
Object res = null; Object res = null;
ctx.pushFrame(frame);
awaiting = false; awaiting = false;
while (!awaiting) { while (!awaiting) {
try { 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; inducedValue = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) { if (res != Runners.NO_RETURN) {
promise.fulfill(ctx, res); promise.fulfill(ctx, res);
@ -38,8 +37,6 @@ import me.topchetoeu.jscript.interop.Native;
} }
} }
ctx.popFrame(frame);
if (awaiting) { if (awaiting) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
} }

View File

@ -28,12 +28,11 @@ import me.topchetoeu.jscript.interop.Native;
} }
Object res = null; Object res = null;
ctx.pushFrame(frame);
state = 0; state = 0;
while (state == 0) { while (state == 0) {
try { 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; inducedValue = inducedReturn = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) { if (res != Runners.NO_RETURN) {
var obj = new ObjectValue(); var obj = new ObjectValue();
@ -49,8 +48,6 @@ import me.topchetoeu.jscript.interop.Native;
} }
} }
ctx.popFrame(frame);
if (state == 1) { if (state == 1) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
} }

View File

@ -24,12 +24,11 @@ import me.topchetoeu.jscript.interop.Native;
} }
Object res = null; Object res = null;
ctx.pushFrame(frame);
yielding = false; yielding = false;
while (!yielding) { while (!yielding) {
try { 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; inducedReturn = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) { if (res != Runners.NO_RETURN) {
done = true; done = true;
@ -42,7 +41,6 @@ import me.topchetoeu.jscript.interop.Native;
} }
} }
ctx.popFrame(frame);
if (done) frame = null; if (done) frame = null;
else res = frame.pop(); else res = frame.pop();

View File

@ -58,7 +58,7 @@ public class Internals {
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
ctx.engine.pushMsg(false, ctx.environment(), func, null, args); ctx.engine.pushMsg(false, ctx.environment, func, null, args);
}); });
thread.start(); thread.start();
@ -75,7 +75,7 @@ public class Internals {
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
ctx.engine.pushMsg(false, ctx.environment(), func, null, args); ctx.engine.pushMsg(false, ctx.environment, func, null, args);
} }
}); });

View File

@ -253,7 +253,7 @@ import me.topchetoeu.jscript.interop.Native;
this.val = val; this.val = val;
this.state = STATE_FULFILLED; 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) { for (var handle : handles) {
handle.fulfilled.call(handle.ctx, null, val); handle.fulfilled.call(handle.ctx, null, val);
} }
@ -288,10 +288,10 @@ import me.topchetoeu.jscript.interop.Native;
this.val = val; this.val = val;
this.state = STATE_REJECTED; 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); for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
if (!handled) { 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; handles = null;
return null; return null;
@ -305,9 +305,9 @@ import me.topchetoeu.jscript.interop.Native;
} }
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { 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) { 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; handled = true;
} }
else handles.add(new Handle(ctx, fulfill, reject)); else handles.add(new Handle(ctx, fulfill, reject));

View File

@ -25,7 +25,7 @@ public interface ModuleRepo {
if (modules.containsKey(name)) return modules.get(name); if (modules.containsKey(name)) return modules.get(name);
var env = ctx.environment().child(); var env = ctx.environment.child();
env.add(CWD, fs.normalize(name, "..")); env.add(CWD, fs.normalize(name, ".."));
var mod = new SourceModule(filename, src, env); var mod = new SourceModule(filename, src, env);