feat: greatly improve Context API
This commit is contained in:
parent
9ea5cd9277
commit
c0b895e00a
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user