From 4f82574b8c64fd77ea656e1425c71f371e8b84d8 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 4 Nov 2023 11:40:50 +0200 Subject: [PATCH] fix: various small behavioural issues fix: pesky try-catch logic --- .../compilation/values/ChangeStatement.java | 4 ++ .../jscript/engine/frame/CodeFrame.java | 58 +++++++++---------- .../jscript/engine/frame/Runners.java | 17 +----- .../jscript/engine/scope/LocalScope.java | 3 +- .../jscript/engine/values/ArrayValue.java | 5 +- .../jscript/engine/values/ObjectValue.java | 3 +- .../jscript/engine/values/Values.java | 22 ++++--- .../topchetoeu/jscript/parsing/Parsing.java | 16 ++--- 8 files changed, 58 insertions(+), 70 deletions(-) diff --git a/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java b/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java index 2221d0b..9f8db27 100644 --- a/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java @@ -17,6 +17,10 @@ public class ChangeStatement extends Statement { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true); if (!pollute) target.add(Instruction.discard().locate(loc())); + else if (postfix) { + target.add(Instruction.loadValue(addAmount)); + target.add(Instruction.operation(Operation.SUBTRACT)); + } } public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index bbddf6c..95eb02b 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -3,9 +3,7 @@ package me.topchetoeu.jscript.engine.frame; import java.util.Stack; import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; @@ -87,6 +85,7 @@ public class CodeFrame { public void addTry(int n, int catchN, int finallyN) { var res = new TryCtx(codePtr + 1, n, catchN, finallyN); + if (!tryStack.empty()) res.err = tryStack.peek().err; tryStack.add(res); } @@ -125,34 +124,31 @@ public class CodeFrame { } private void setCause(Context ctx, EngineException err, EngineException cause) { - // err.cause = cause; err.setCause(cause); } - private Object nextNoTry(Context ctx, Instruction instr) { - if (Thread.currentThread().isInterrupted()) throw new InterruptException(); - if (codePtr < 0 || codePtr >= function.body.length) return null; - - if (instr.location != null) prevLoc = instr.location; - - try { - this.jumpFlag = false; - return Runners.exec(ctx, instr, this); - } - catch (EngineException e) { - throw e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine); - } - } public Object next(Context ctx, Object value, Object returnValue, EngineException error) { if (value != Runners.NO_RETURN) push(ctx, value); - var debugger = StackData.getDebugger(ctx); if (returnValue == Runners.NO_RETURN && error == null) { try { - var instr = function.body[codePtr]; + if (Thread.currentThread().isInterrupted()) throw new InterruptException(); - if (debugger != null) debugger.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); - returnValue = nextNoTry(ctx, instr); + var instr = function.body[codePtr]; + ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); + + if (codePtr < 0 || codePtr >= function.body.length) returnValue = null; + else { + if (instr.location != null) prevLoc = instr.location; + + try { + this.jumpFlag = false; + returnValue = Runners.exec(ctx, instr, this); + } + catch (EngineException e) { + error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine); + } + } } catch (EngineException e) { error = e; } } @@ -192,11 +188,11 @@ public class CodeFrame { break; case TryCtx.STATE_CATCH: if (error != null) { + setCause(ctx, error, tryCtx.err); if (tryCtx.hasFinally) { tryCtx.err = error; newState = TryCtx.STATE_FINALLY_THREW; } - setCause(ctx, error, tryCtx.err); break; } else if (returnValue != Runners.NO_RETURN) { @@ -218,27 +214,31 @@ public class CodeFrame { case TryCtx.STATE_FINALLY_THREW: if (error != null) setCause(ctx, error, tryCtx.err); else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err; - else return Runners.NO_RETURN; + else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN; break; case TryCtx.STATE_FINALLY_RETURNED: + if (error != null) setCause(ctx, error, tryCtx.err); if (returnValue == Runners.NO_RETURN) { if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal; else return Runners.NO_RETURN; } break; case TryCtx.STATE_FINALLY_JUMPED: - if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) { + if (error != null) setCause(ctx, error, tryCtx.err); + else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) { if (!jumpFlag) codePtr = tryCtx.jumpPtr; else codePtr = tryCtx.end; } - else return Runners.NO_RETURN; + else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN; break; } if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (newState == -1) { + var err = tryCtx.err; tryStack.pop(); + if (!tryStack.isEmpty()) tryStack.peek().err = err; continue; } @@ -247,7 +247,7 @@ public class CodeFrame { case TryCtx.STATE_CATCH: scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); codePtr = tryCtx.catchStart; - if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, true); + ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true); break; default: codePtr = tryCtx.finallyStart; @@ -255,13 +255,13 @@ public class CodeFrame { return Runners.NO_RETURN; } - + if (error != null) { - if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, false); + ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, false); throw error; } if (returnValue != Runners.NO_RETURN) { - if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false); + ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false); return returnValue; } diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index bba29a9..84758fd 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -26,16 +26,12 @@ public class Runners { throw EngineException.ofSyntax((String)instr.get(0)); } - private static Object call(Context ctx, Object func, Object thisArg, Object ...args) { - return Values.call(ctx, func, thisArg, args); - } - public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) { var callArgs = frame.take(instr.get(0)); var func = frame.pop(); var thisArg = frame.pop(); - frame.push(ctx, call(ctx, func, thisArg, callArgs)); + frame.push(ctx, Values.call(ctx, func, thisArg, callArgs)); frame.codePtr++; return NO_RETURN; @@ -46,17 +42,6 @@ public class Runners { frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); - // if (Values.isFunction(funcObj) && Values.function(funcObj).special) { - // frame.push(ctx, call(ctx, funcObj, null, callArgs)); - // } - // else { - // var proto = Values.getMember(ctx, funcObj, "prototype"); - // var obj = new ObjectValue(); - // obj.setPrototype(ctx, proto); - // call(ctx, funcObj, obj, callArgs); - // frame.push(ctx, obj); - // } - frame.codePtr++; return NO_RETURN; } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index 7645aaf..41b484b 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -8,8 +8,7 @@ public class LocalScope { public final ArrayList catchVars = new ArrayList<>(); public ValueVariable get(int i) { - if (i >= locals.length) - return catchVars.get(i - locals.length); + if (i >= locals.length) return catchVars.get(i - locals.length); if (i >= 0) return locals[i]; else return captures[~i]; } diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java index f0bc2ea..539fdf0 100644 --- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java @@ -52,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable { if (i >= size) size = i + 1; } public boolean has(int i) { - return i >= 0 && i < values.length && values[i] != null; + return i >= 0 && i < size && values[i] != null; } public void remove(int i) { if (i < 0 || i >= values.length) return; @@ -85,8 +85,9 @@ public class ArrayValue extends ObjectValue implements Iterable { public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) { // Iterate in reverse to reallocate at most once for (var i = count - 1; i >= 0; i--) { - if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null); + if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart); if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null); + else if (values[i + sourceStart] == null) arr.remove(i + destStart); else arr.set(ctx, i + destStart, values[i + sourceStart]); } } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 7bdf0a3..f6b4552 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -103,8 +103,7 @@ public class ObjectValue { ) return true; if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; - if (!memberConfigurable(key)) - return false; + if (!memberConfigurable(key)) return false; nonWritableSet.remove(key); nonEnumerableSet.remove(key); diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 1911f4f..6cc6f90 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -258,8 +258,7 @@ public class Values { public static Object getMember(Context ctx, Object obj, Object key) { obj = normalize(ctx, obj); key = normalize(ctx, key); - if (obj == null) - throw new IllegalArgumentException("Tried to access member of undefined."); + if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); if (isObject(obj)) return object(obj).getMember(ctx, key); @@ -279,8 +278,7 @@ public class Values { } public static boolean setMember(Context ctx, Object obj, Object key, Object val) { obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); - if (obj == null) - throw EngineException.ofType("Tried to access member of undefined."); + if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (key.equals("__proto__")) return setPrototype(ctx, obj, val); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); @@ -369,8 +367,7 @@ public class Values { } public static Object call(Context ctx, Object func, Object thisArg, Object ...args) { - if (!isFunction(func)) - throw EngineException.ofType("Tried to call a non-function value."); + if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); return function(func).call(ctx, thisArg, args); } public static Object callNew(Context ctx, Object func, Object ...args) { @@ -524,8 +521,6 @@ public class Values { public static Iterable toJavaIterable(Context ctx, Object obj) { return () -> { try { - var _ctx = ctx; - var _obj = obj; var symbol = ctx.environment().symbol("Symbol.iterator"); var iteratorFunc = getMember(ctx, obj, symbol); @@ -587,7 +582,11 @@ public class Values { res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> { if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true)); - else return new ObjectValue(ctx, Map.of("value", it.next())); + else { + var obj = new ObjectValue(); + obj.defineProperty(_ctx, "value", it.next()); + return obj; + } })); return res; @@ -598,6 +597,11 @@ public class Values { } private static void printValue(Context ctx, Object val, HashSet passed, int tab) { + if (tab == 0 && val instanceof String) { + System.out.print(val); + return; + } + if (passed.contains(val)) { System.out.print("[circular]"); return; diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index e4391cf..9c9117d 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -82,8 +82,6 @@ public class Parsing { // Although ES5 allow these, we will comply to ES6 here reserved.add("const"); reserved.add("let"); - reserved.add("async"); - reserved.add("super"); // These are allowed too, however our parser considers them keywords reserved.add("undefined"); reserved.add("arguments"); @@ -1060,7 +1058,6 @@ public class Parsing { if (!checkVarName(literal.result)) { if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported."); - if (literal.result.equals("async")) return ParseRes.error(loc, "'async' is not supported."); if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result)); @@ -1137,7 +1134,9 @@ public class Parsing { var op = opRes.result; if (!op.isAssign()) return ParseRes.failed(); - if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); + if (!(prev instanceof AssignableStatement)) { + return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); + } var res = parseValue(filename, tokens, i + n, 2); if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.value), res); @@ -1411,8 +1410,7 @@ public class Parsing { var valRes = parseValue(filename, tokens, i + n, 0); n += valRes.n; - if (valRes.isError()) - return ParseRes.error(loc, "Expected a return value.", valRes); + if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes); var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n); @@ -1534,8 +1532,7 @@ public class Parsing { if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes); n += condRes.n; - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) - return ParseRes.error(loc, "Expected a closing paren after if condition."); + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition."); var res = parseStatement(filename, tokens, i + n); if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); @@ -1702,8 +1699,7 @@ public class Parsing { parseVariableDeclare(filename, tokens, i + n), parseValueStatement(filename, tokens, i + n) ); - if (!declRes.isSuccess()) - return ParseRes.error(loc, "Expected a declaration or an expression.", declRes); + if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes); n += declRes.n; decl = declRes.result; }