feat: make try-catch call-less
This commit is contained in:
parent
bef35ebb1b
commit
0ea01996ee
@ -54,7 +54,7 @@ public class Main {
|
||||
|
||||
public static void main(String args[]) {
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
engine = new TypescriptEngine(new File("."));
|
||||
engine = new PolyfillEngine(new File("."));
|
||||
var scope = engine.global().globalChild();
|
||||
var exited = new boolean[1];
|
||||
|
||||
|
@ -135,8 +135,8 @@ public class Instruction {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public static Instruction tryInstr(boolean hasCatch, boolean hasFinally) {
|
||||
return new Instruction(null, Type.TRY, hasCatch, hasFinally);
|
||||
public static Instruction tryInstr(int n, int catchN, int finallyN) {
|
||||
return new Instruction(null, Type.TRY, n, catchN, finallyN);
|
||||
}
|
||||
public static Instruction throwInstr() {
|
||||
return new Instruction(null, Type.THROW);
|
||||
|
@ -5,7 +5,8 @@ import java.util.List;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class TryStatement extends Statement {
|
||||
@ -24,57 +25,42 @@ public class TryStatement extends Statement {
|
||||
if (finallyBody != null) finallyBody.declare(globScope);
|
||||
}
|
||||
|
||||
private void compileBody(List<Instruction> target, ScopeRecord scope, Statement body, String arg) {
|
||||
var subscope = scope.child();
|
||||
int start = target.size();
|
||||
|
||||
target.add(Instruction.nop());
|
||||
|
||||
subscope.define("this");
|
||||
var argsVar = subscope.define("<catchargs>");
|
||||
|
||||
if (arg != null) {
|
||||
target.add(Instruction.loadVar(argsVar));
|
||||
target.add(Instruction.loadMember(0));
|
||||
target.add(Instruction.storeVar(subscope.define(arg)));
|
||||
}
|
||||
|
||||
int bodyStart = target.size();
|
||||
body.compile(target, subscope);
|
||||
target.add(Instruction.signal("no_return"));
|
||||
|
||||
target.get(bodyStart).locate(body.loc());
|
||||
|
||||
|
||||
target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), 0, subscope.getCaptures()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
int start = target.size();
|
||||
int start = target.size(), tryN, catchN = -1, finN = -1;
|
||||
|
||||
compileBody(target, scope, tryBody, null);
|
||||
target.add(Instruction.nop());
|
||||
tryBody.compileNoPollution(target, scope);
|
||||
tryN = target.size() - start;
|
||||
|
||||
if (catchBody != null) {
|
||||
compileBody(target, scope, catchBody, name);
|
||||
int tmp = target.size();
|
||||
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
||||
local.define(name);
|
||||
catchBody.compileNoPollution(target, scope);
|
||||
local.undefine();
|
||||
catchN = target.size() - tmp;
|
||||
}
|
||||
|
||||
if (finallyBody != null) {
|
||||
compileBody(target, scope, finallyBody, null);
|
||||
int tmp = target.size();
|
||||
finallyBody.compileNoPollution(target, scope);
|
||||
finN = target.size() - tmp;
|
||||
}
|
||||
|
||||
for (int i = start; i < target.size(); i++) {
|
||||
if (target.get(i).type == Type.NOP) {
|
||||
var instr = target.get(i);
|
||||
if (instr.is(0, "break")) {
|
||||
target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location));
|
||||
}
|
||||
else if (instr.is(0, "cont")) {
|
||||
target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location));
|
||||
}
|
||||
}
|
||||
}
|
||||
// for (int i = start; i < target.size(); i++) {
|
||||
// if (target.get(i).type == Type.NOP) {
|
||||
// var instr = target.get(i);
|
||||
// if (instr.is(0, "break")) {
|
||||
// target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location));
|
||||
// }
|
||||
// else if (instr.is(0, "cont")) {
|
||||
// target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
target.add(Instruction.tryInstr(catchBody != null, finallyBody != null).locate(loc()));
|
||||
target.set(start, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
|
||||
}
|
||||
|
||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||
|
@ -9,13 +9,41 @@ import me.topchetoeu.jscript.engine.DebugCommand;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.CallContext.DataKey;
|
||||
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.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class CodeFrame {
|
||||
private record TryCtx(int tryStart, int tryEnd, int catchStart, int catchEnd, int finallyStart, int finallyEnd) { }
|
||||
private class TryCtx {
|
||||
public static final int STATE_TRY = 0;
|
||||
public static final int STATE_CATCH = 1;
|
||||
public static final int STATE_FINALLY_THREW = 2;
|
||||
public static final int STATE_FINALLY_RETURNED = 3;
|
||||
public static final int STATE_FINALLY_JUMPED = 4;
|
||||
|
||||
public final boolean hasCatch, hasFinally;
|
||||
public final int tryStart, catchStart, finallyStart, end;
|
||||
public int state;
|
||||
public Object retVal;
|
||||
public int jumpPtr;
|
||||
public EngineException err;
|
||||
|
||||
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) {
|
||||
hasCatch = catchN >= 0;
|
||||
hasFinally = finallyN >= 0;
|
||||
|
||||
if (catchN < 0) catchN = 0;
|
||||
if (finallyN < 0) finallyN = 0;
|
||||
|
||||
this.tryStart = tryStart;
|
||||
this.catchStart = tryStart + tryN;
|
||||
this.finallyStart = catchStart + catchN;
|
||||
this.end = finallyStart + finallyN;
|
||||
this.jumpPtr = end;
|
||||
}
|
||||
}
|
||||
|
||||
public static final DataKey<Integer> STACK_N_KEY = new DataKey<>();
|
||||
public static final DataKey<Integer> MAX_STACK_KEY = new DataKey<>();
|
||||
@ -31,9 +59,16 @@ public class CodeFrame {
|
||||
public Object[] stack = new Object[32];
|
||||
public int stackPtr = 0;
|
||||
public int codePtr = 0;
|
||||
public boolean jumpFlag = false;
|
||||
private DebugCommand debugCmd = null;
|
||||
private Location prevLoc = null;
|
||||
|
||||
public void addTry(int n, int catchN, int finallyN) {
|
||||
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
|
||||
|
||||
tryStack.add(res);
|
||||
}
|
||||
|
||||
public Object peek() {
|
||||
return peek(0);
|
||||
}
|
||||
@ -114,6 +149,7 @@ public class CodeFrame {
|
||||
// }
|
||||
|
||||
try {
|
||||
this.jumpFlag = false;
|
||||
return Runners.exec(debugCmd, instr, this, ctx);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
@ -122,7 +158,104 @@ public class CodeFrame {
|
||||
}
|
||||
|
||||
public Object next(CallContext ctx) throws InterruptedException {
|
||||
return nextNoTry(ctx);
|
||||
TryCtx tryCtx = null;
|
||||
|
||||
while (!tryStack.isEmpty()) {
|
||||
var tmp = tryStack.get(tryStack.size() - 1);
|
||||
var remove = false;
|
||||
|
||||
if (tmp.state == TryCtx.STATE_TRY) {
|
||||
if (codePtr < tmp.tryStart || codePtr >= tmp.catchStart) {
|
||||
if (jumpFlag) tmp.jumpPtr = codePtr;
|
||||
else tmp.jumpPtr = tmp.end;
|
||||
|
||||
if (tmp.hasFinally) {
|
||||
tmp.state = TryCtx.STATE_FINALLY_JUMPED;
|
||||
codePtr = tmp.finallyStart;
|
||||
}
|
||||
else codePtr = tmp.jumpPtr;
|
||||
remove = !tmp.hasFinally;
|
||||
}
|
||||
}
|
||||
else if (tmp.state == TryCtx.STATE_CATCH) {
|
||||
if (codePtr < tmp.catchStart || codePtr >= tmp.finallyStart) {
|
||||
if (jumpFlag) tmp.jumpPtr = codePtr;
|
||||
else tmp.jumpPtr = tmp.end;
|
||||
scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (tmp.hasFinally) {
|
||||
tmp.state = TryCtx.STATE_FINALLY_JUMPED;
|
||||
codePtr = tmp.finallyStart;
|
||||
}
|
||||
else codePtr = tmp.jumpPtr;
|
||||
remove = !tmp.hasFinally;
|
||||
}
|
||||
}
|
||||
else if (codePtr < tmp.finallyStart || codePtr >= tmp.end) {
|
||||
if (!jumpFlag) {
|
||||
if (tmp.state == TryCtx.STATE_FINALLY_THREW) throw tmp.err;
|
||||
else if (tmp.state == TryCtx.STATE_FINALLY_RETURNED) return tmp.retVal;
|
||||
}
|
||||
else codePtr = tmp.jumpPtr;
|
||||
remove = true;
|
||||
}
|
||||
|
||||
if (remove) tryStack.remove(tryStack.size() - 1);
|
||||
else {
|
||||
tryCtx = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tryCtx == null) return nextNoTry(ctx);
|
||||
else if (tryCtx.state == TryCtx.STATE_TRY) {
|
||||
try {
|
||||
var res = nextNoTry(ctx);
|
||||
if (res != Runners.NO_RETURN && tryCtx.hasFinally) {
|
||||
tryCtx.retVal = res;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
|
||||
else return res;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
if (tryCtx.hasCatch) {
|
||||
tryCtx.state = TryCtx.STATE_CATCH;
|
||||
codePtr = tryCtx.catchStart;
|
||||
scope.catchVars.add(new ValueVariable(false, e.value));
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
else if (tryCtx.hasFinally) {
|
||||
tryCtx.err = e;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
else throw e;
|
||||
}
|
||||
|
||||
codePtr = tryCtx.finallyStart;
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
else if (tryCtx.state == TryCtx.STATE_CATCH) {
|
||||
try {
|
||||
var res = nextNoTry(ctx);
|
||||
if (res != Runners.NO_RETURN && tryCtx.hasFinally) {
|
||||
tryCtx.retVal = res;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
else return res;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.err = e;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
else throw e;
|
||||
}
|
||||
|
||||
codePtr = tryCtx.finallyStart;
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
else return nextNoTry(ctx);
|
||||
}
|
||||
|
||||
public Object run(CallContext ctx) throws InterruptedException {
|
||||
|
@ -124,69 +124,10 @@ public class Runners {
|
||||
}
|
||||
|
||||
public static Object execTry(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var finallyFunc = (boolean)instr.get(1) ? frame.pop() : null;
|
||||
var catchFunc = (boolean)instr.get(0) ? frame.pop() : null;
|
||||
var func = frame.pop();
|
||||
|
||||
if (
|
||||
!Values.isFunction(func) ||
|
||||
catchFunc != null && !Values.isFunction(catchFunc) ||
|
||||
finallyFunc != null && !Values.isFunction(finallyFunc)
|
||||
) throw EngineException.ofType("TRY instruction can be applied only upon functions.");
|
||||
|
||||
Object res = new SignalValue("no_return");
|
||||
EngineException exception = null;
|
||||
|
||||
Values.function(func).name = frame.function.name + "::try";
|
||||
if (catchFunc != null) Values.function(catchFunc).name = frame.function.name + "::catch";
|
||||
if (finallyFunc != null) Values.function(finallyFunc).name = frame.function.name + "::finally";
|
||||
|
||||
try {
|
||||
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
|
||||
res = Values.call(ctx, func, frame.thisArg);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
exception = e.setCause(exception);
|
||||
}
|
||||
|
||||
if (exception != null && catchFunc != null) {
|
||||
var exc = exception;
|
||||
exception = null;
|
||||
try {
|
||||
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
|
||||
var _res = Values.call(ctx, catchFunc, frame.thisArg, exc.value);
|
||||
if (!SignalValue.isSignal(_res, "no_return")) res = _res;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
exception = e.setCause(exc);
|
||||
}
|
||||
}
|
||||
|
||||
if (finallyFunc != null) {
|
||||
try {
|
||||
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
|
||||
var _res = Values.call(ctx, finallyFunc, frame.thisArg);
|
||||
if (!SignalValue.isSignal(_res, "no_return")) {
|
||||
res = _res;
|
||||
exception = null;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
exception = e.setCause(exception);
|
||||
}
|
||||
}
|
||||
|
||||
if (exception != null) throw exception;
|
||||
if (SignalValue.isSignal(res, "no_return")) {
|
||||
frame.addTry(instr.get(0), instr.get(1), instr.get(2));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
else if (SignalValue.isSignal(res, "jmp_*")) {
|
||||
frame.codePtr += Integer.parseInt(((SignalValue)res).data.substring(4));
|
||||
return NO_RETURN;
|
||||
}
|
||||
else return res;
|
||||
}
|
||||
|
||||
public static Object execDup(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
int offset = instr.get(0), count = instr.get(1);
|
||||
@ -325,14 +266,23 @@ public class Runners {
|
||||
|
||||
public static Object execJmp(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIf(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
frame.codePtr += Values.toBoolean(frame.pop()) ? (int)instr.get(0) : 1;
|
||||
if (Values.toBoolean(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIfNot(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
frame.codePtr += Values.not(frame.pop()) ? (int)instr.get(0) : 1;
|
||||
if (Values.not(frame.pop())) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,10 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class GlobalScope implements ScopeRecord {
|
||||
public final ObjectValue obj;
|
||||
public final GlobalScope parent;
|
||||
|
||||
@Override
|
||||
public GlobalScope parent() { return parent; }
|
||||
|
||||
public boolean has(CallContext ctx, String name) throws InterruptedException {
|
||||
return obj.hasMember(ctx, name, false);
|
||||
@ -74,9 +78,11 @@ public class GlobalScope implements ScopeRecord {
|
||||
}
|
||||
|
||||
public GlobalScope() {
|
||||
this.parent = null;
|
||||
this.obj = new ObjectValue();
|
||||
}
|
||||
public GlobalScope(GlobalScope parent) {
|
||||
this.parent = null;
|
||||
this.obj = new ObjectValue();
|
||||
this.obj.setPrototype(null, parent.obj);
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LocalScope {
|
||||
private String[] names;
|
||||
private LocalScope parent;
|
||||
public final ValueVariable[] captures;
|
||||
public final ValueVariable[] locals;
|
||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||
|
||||
public ValueVariable get(int i) {
|
||||
if (i >= locals.length) return catchVars.get(i - locals.length);
|
||||
if (i >= 0) return locals[i];
|
||||
else return captures[~i];
|
||||
}
|
||||
|
@ -15,6 +15,9 @@ public class LocalScopeRecord implements ScopeRecord {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalScopeRecord parent() { return parent; }
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this, global);
|
||||
}
|
||||
@ -67,6 +70,9 @@ public class LocalScopeRecord implements ScopeRecord {
|
||||
locals.add(name);
|
||||
return locals.size() - 1;
|
||||
}
|
||||
public void undefine() {
|
||||
locals.remove(locals.size() - 1);
|
||||
}
|
||||
|
||||
public LocalScopeRecord(GlobalScope global) {
|
||||
this.parent = null;
|
||||
|
@ -3,5 +3,6 @@ package me.topchetoeu.jscript.engine.scope;
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public ScopeRecord parent();
|
||||
public LocalScopeRecord child();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user