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.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<Environment> env = new Stack<>();
private final ArrayList<CodeFrame> 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 <T> 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> 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<CodeFrame> frames() {
return Collections.unmodifiableList(frames);
public Iterable<CodeFrame> 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() {
var res = new ArrayList<String>();
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);
}
}

View File

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

View File

@ -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<String>();
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) {

View File

@ -94,6 +94,7 @@ public class CodeFrame {
public final Object[] args;
public final Stack<TryCtx> 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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