diff --git a/lib/values/array.ts b/lib/values/array.ts index 888c34d..5144630 100644 --- a/lib/values/array.ts +++ b/lib/values/array.ts @@ -297,6 +297,14 @@ setProps(Array.prototype, { return -1; }, + lastIndexOf(el, start) { + start = start! | 0; + for (var i = this.length; i >= start; i--) { + if (i in this && this[i] == el) return i; + } + + return -1; + }, includes(el, start) { return this.indexOf(el, start) >= 0; }, diff --git a/lib/values/function.ts b/lib/values/function.ts index 0bd101b..81e4be0 100644 --- a/lib/values/function.ts +++ b/lib/values/function.ts @@ -14,7 +14,7 @@ interface FunctionConstructor extends Function { (...args: string[]): (...args: any[]) => any; new (...args: string[]): (...args: any[]) => any; prototype: Function; - async(func: (await: (val: T) => Awaited, args: ArgsT) => RetT): Promise; + async(func: (await: (val: T) => Awaited) => (...args: ArgsT) => RetT): (...args: ArgsT) => Promise; } interface CallableFunction extends Function { diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 1edc5a7..34a2426 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -14,6 +14,7 @@ import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.polyfills.PolyfillEngine; +import me.topchetoeu.jscript.polyfills.TypescriptEngine; public class Main { static Thread task; @@ -53,7 +54,7 @@ public class Main { public static void main(String args[]) { var in = new BufferedReader(new InputStreamReader(System.in)); - engine = new PolyfillEngine(new File(".")); + engine = new TypescriptEngine(new File(".")); var scope = engine.global().globalChild(); var exited = new boolean[1]; diff --git a/src/me/topchetoeu/jscript/compilation/AssignStatement.java b/src/me/topchetoeu/jscript/compilation/AssignStatement.java new file mode 100644 index 0000000..6bb714f --- /dev/null +++ b/src/me/topchetoeu/jscript/compilation/AssignStatement.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.List; + +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.scope.ScopeRecord; + +public abstract class AssignStatement extends Statement { + public abstract void compile(List target, ScopeRecord scope, boolean retPrevValue); + + @Override + public void compile(List target, ScopeRecord scope) { + compile(target, scope, false); + } + + protected AssignStatement(Location loc) { + super(loc); + } +} diff --git a/src/me/topchetoeu/jscript/compilation/AssignableStatement.java b/src/me/topchetoeu/jscript/compilation/AssignableStatement.java index eb88387..810c2f8 100644 --- a/src/me/topchetoeu/jscript/compilation/AssignableStatement.java +++ b/src/me/topchetoeu/jscript/compilation/AssignableStatement.java @@ -1,10 +1,10 @@ package me.topchetoeu.jscript.compilation; import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Operation; public abstract class AssignableStatement extends Statement { - public abstract Statement toAssign(Statement val, Type operation); + public abstract AssignStatement toAssign(Statement val, Operation operation); protected AssignableStatement(Location loc) { super(loc); diff --git a/src/me/topchetoeu/jscript/compilation/Instruction.java b/src/me/topchetoeu/jscript/compilation/Instruction.java index 07a82c1..6e3be1c 100644 --- a/src/me/topchetoeu/jscript/compilation/Instruction.java +++ b/src/me/topchetoeu/jscript/compilation/Instruction.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.compilation; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.exceptions.SyntaxException; public class Instruction { @@ -33,55 +34,58 @@ public class Instruction { LOAD_REGEX, DUP, + MOVE, STORE_VAR, STORE_MEMBER, DISCARD, - + MAKE_VAR, DEF_PROP, KEYS, TYPEOF, - INSTANCEOF(true), - IN(true), + OPERATION; + // TYPEOF, + // INSTANCEOF(true), + // IN(true), - MULTIPLY(true), - DIVIDE(true), - MODULO(true), - ADD(true), - SUBTRACT(true), + // MULTIPLY(true), + // DIVIDE(true), + // MODULO(true), + // ADD(true), + // SUBTRACT(true), - USHIFT_RIGHT(true), - SHIFT_RIGHT(true), - SHIFT_LEFT(true), + // USHIFT_RIGHT(true), + // SHIFT_RIGHT(true), + // SHIFT_LEFT(true), - GREATER(true), - LESS(true), - GREATER_EQUALS(true), - LESS_EQUALS(true), - LOOSE_EQUALS(true), - LOOSE_NOT_EQUALS(true), - EQUALS(true), - NOT_EQUALS(true), + // GREATER(true), + // LESS(true), + // GREATER_EQUALS(true), + // LESS_EQUALS(true), + // LOOSE_EQUALS(true), + // LOOSE_NOT_EQUALS(true), + // EQUALS(true), + // NOT_EQUALS(true), - AND(true), - OR(true), - XOR(true), + // AND(true), + // OR(true), + // XOR(true), - NEG(true), - POS(true), - NOT(true), - INVERSE(true); + // NEG(true), + // POS(true), + // NOT(true), + // INVERSE(true); - final boolean isOperation; + // final boolean isOperation; - private Type(boolean isOperation) { - this.isOperation = isOperation; - } - private Type() { - this(false); - } + // private Type(boolean isOperation) { + // this.isOperation = isOperation; + // } + // private Type() { + // this(false); + // } } public final Type type; @@ -103,6 +107,11 @@ public class Instruction { if (i >= params.length || i < 0) return null; return (T)params[i]; } + @SuppressWarnings("unchecked") + public T get(int i, T defaultVal) { + if (i >= params.length || i < 0) return defaultVal; + return (T)params[i]; + } public boolean match(Object ...args) { if (args.length != params.length) return false; for (int i = 0; i < args.length; i++) { @@ -126,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); @@ -226,11 +235,14 @@ public class Instruction { public static Instruction loadArr(int count) { return new Instruction(null, Type.LOAD_ARR, count); } - public static Instruction dup(int count) { - return new Instruction(null, Type.DUP, count, 0); + public static Instruction dup() { + return new Instruction(null, Type.DUP, 0, 1); } public static Instruction dup(int count, int offset) { - return new Instruction(null, Type.DUP, count, offset); + return new Instruction(null, Type.DUP, offset, count); + } + public static Instruction move(int count, int offset) { + return new Instruction(null, Type.MOVE, offset, count); } public static Instruction storeSelfFunc(int i) { @@ -255,7 +267,7 @@ public class Instruction { public static Instruction typeof() { return new Instruction(null, Type.TYPEOF); } - public static Instruction typeof(String varName) { + public static Instruction typeof(Object varName) { return new Instruction(null, Type.TYPEOF, varName); } @@ -267,9 +279,8 @@ public class Instruction { return new Instruction(null, Type.DEF_PROP); } - public static Instruction operation(Type op) { - if (!op.isOperation) throw new IllegalArgumentException("The instruction type %s is not an operation.".formatted(op)); - return new Instruction(null, op); + public static Instruction operation(Operation op) { + return new Instruction(null, Type.OPERATION, op); } @Override diff --git a/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java b/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java index 3851f9d..5f7538d 100644 --- a/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/ForInStatement.java @@ -5,7 +5,7 @@ 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.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class ForInStatement extends Statement { @@ -37,17 +37,18 @@ public class ForInStatement extends Statement { target.add(Instruction.keys()); int start = target.size(); - target.add(Instruction.dup(1)); + target.add(Instruction.dup()); target.add(Instruction.loadMember("length")); target.add(Instruction.loadValue(0)); - target.add(Instruction.operation(Type.LESS_EQUALS)); + target.add(Instruction.operation(Operation.LESS_EQUALS)); int mid = target.size(); target.add(Instruction.nop()); - target.add(Instruction.dup(2)); + target.add(Instruction.dup()); + target.add(Instruction.dup()); target.add(Instruction.loadMember("length")); target.add(Instruction.loadValue(1)); - target.add(Instruction.operation(Type.SUBTRACT)); + target.add(Instruction.operation(Operation.SUBTRACT)); target.add(Instruction.dup(1, 2)); target.add(Instruction.loadValue("length")); target.add(Instruction.dup(1, 2)); diff --git a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index 25d193c..fd344c7 100644 --- a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -7,6 +7,7 @@ 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.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class SwitchStatement extends Statement { @@ -33,9 +34,9 @@ public class SwitchStatement extends Statement { value.compile(target, scope); for (var ccase : cases) { - target.add(Instruction.dup(1).locate(loc())); + target.add(Instruction.dup().locate(loc())); ccase.value.compileWithPollution(target, scope); - target.add(Instruction.operation(Type.EQUALS).locate(loc())); + target.add(Instruction.operation(Operation.EQUALS).locate(loc())); caseMap.put(target.size(), ccase.statementI); target.add(Instruction.nop()); } diff --git a/src/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/me/topchetoeu/jscript/compilation/control/TryStatement.java index 2af777d..a0c763b 100644 --- a/src/me/topchetoeu/jscript/compilation/control/TryStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/TryStatement.java @@ -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,43 @@ public class TryStatement extends Statement { if (finallyBody != null) finallyBody.declare(globScope); } - private void compileBody(List 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(""); - - 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 target, ScopeRecord scope) { - int start = target.size(); + target.add(Instruction.nop()); - compileBody(target, scope, tryBody, null); + int start = target.size(), tryN, catchN = -1, finN = -1; + + 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, true); + 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 - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc())); } public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) { diff --git a/src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java b/src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java index 1c6fdad..640ac5c 100644 --- a/src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ArrayStatement.java @@ -21,7 +21,7 @@ public class ArrayStatement extends Statement { var i = 0; for (var el : statements) { if (el != null) { - target.add(Instruction.dup(1).locate(loc())); + target.add(Instruction.dup().locate(loc())); target.add(Instruction.loadValue(i).locate(loc())); el.compileWithPollution(target, scope); target.add(Instruction.storeMember().locate(loc())); diff --git a/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java b/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java index bdf4083..3574f95 100644 --- a/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ChangeStatement.java @@ -6,7 +6,7 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class ChangeStatement extends Statement { @@ -19,11 +19,7 @@ public class ChangeStatement extends Statement { @Override public void compile(List target, ScopeRecord scope) { - value.toAssign(new ConstantStatement(loc(), -addAmount), Type.SUBTRACT).compileWithPollution(target, scope); - if (postfix) { - target.add(Instruction.loadValue(addAmount).locate(loc())); - target.add(Instruction.operation(Type.SUBTRACT).locate(loc())); - } + value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix); } public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { diff --git a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java index 2afe17c..eb52f84 100644 --- a/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -53,13 +53,11 @@ public class FunctionStatement extends Statement { target.add(Instruction.nop()); subscope.define("this"); - var argsVar = subscope.define("arguments"); - if (args.length > 0) { - target.add(Instruction.loadVar(argsVar).locate(loc())); - if (args.length != 1) target.add(Instruction.dup(args.length - 1).locate(loc())); + if (args.length > 0) { for (var i = 0; i < args.length; i++) { + target.add(Instruction.loadVar(argsVar).locate(loc())); target.add(Instruction.loadMember(i).locate(loc())); target.add(Instruction.storeVar(subscope.define(args[i])).locate(loc())); } @@ -82,7 +80,7 @@ public class FunctionStatement extends Statement { if (name == null) name = this.name; if (name != null) { - target.add(Instruction.dup(1).locate(loc())); + target.add(Instruction.dup().locate(loc())); target.add(Instruction.loadValue("name").locate(loc())); target.add(Instruction.loadValue(name).locate(loc())); target.add(Instruction.storeMember().locate(loc())); diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java index 1a55646..872e804 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java @@ -3,45 +3,52 @@ package me.topchetoeu.jscript.compilation.values; import java.util.List; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.AssignStatement; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; -public class IndexAssignStatement extends Statement { +public class IndexAssignStatement extends AssignStatement { public final Statement object; public final Statement index; public final Statement value; - public final Type operation; + public final Operation operation; @Override public boolean pollutesStack() { return true; } @Override - public void compile(List target, ScopeRecord scope) { + public void compile(List target, ScopeRecord scope, boolean retPrevValue) { int start = 0; + if (operation != null) { object.compileWithPollution(target, scope); index.compileWithPollution(target, scope); + target.add(Instruction.dup(2, 0).locate(loc())); - target.add(Instruction.dup(1, 1).locate(loc())); - target.add(Instruction.dup(1, 1).locate(loc())); target.add(Instruction.loadMember().locate(loc())); + if (retPrevValue) { + target.add(Instruction.dup().locate(loc())); + target.add(Instruction.move(3, 1).locate(loc())); + } value.compileWithPollution(target, scope); target.add(Instruction.operation(operation).locate(loc())); - target.add(Instruction.storeMember(true).locate(loc())); + target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true)); } else { object.compileWithPollution(target, scope); + if (retPrevValue) target.add(Instruction.dup().locate(loc())); index.compileWithPollution(target, scope); value.compileWithPollution(target, scope); - target.add(Instruction.storeMember(true).locate(loc())); + + target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true)); } - target.get(start).setDebug(true); + target.get(start); } - public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Type operation) { + public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) { super(loc); this.object = object; this.index = index; diff --git a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java index c1fd5fe..39fe76d 100644 --- a/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -3,10 +3,11 @@ package me.topchetoeu.jscript.compilation.values; import java.util.List; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.AssignStatement; import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class IndexStatement extends AssignableStatement { @@ -19,13 +20,13 @@ public class IndexStatement extends AssignableStatement { public boolean pure() { return true; } @Override - public Statement toAssign(Statement val, Type operation) { + public AssignStatement toAssign(Statement val, Operation operation) { return new IndexAssignStatement(loc(), object, index, val, operation); } public void compile(List target, ScopeRecord scope, boolean dupObj) { int start = 0; object.compileWithPollution(target, scope); - if (dupObj) target.add(Instruction.dup(1).locate(loc())); + if (dupObj) target.add(Instruction.dup().locate(loc())); if (index instanceof ConstantStatement) { target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); return; diff --git a/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java b/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java index 00f55e7..59fb3e1 100644 --- a/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java @@ -29,7 +29,7 @@ public class LazyAndStatement extends Statement { } first.compileWithPollution(target, scope); - target.add(Instruction.dup(1).locate(loc())); + target.add(Instruction.dup().locate(loc())); int start = target.size(); target.add(Instruction.nop()); target.add(Instruction.discard().locate(loc())); diff --git a/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java b/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java index 4cc5dd0..78b6104 100644 --- a/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java @@ -29,7 +29,7 @@ public class LazyOrStatement extends Statement { } first.compileWithPollution(target, scope); - target.add(Instruction.dup(1).locate(loc())); + target.add(Instruction.dup().locate(loc())); int start = target.size(); target.add(Instruction.nop()); target.add(Instruction.discard().locate(loc())); diff --git a/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java b/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java index 99a0082..2852aca 100644 --- a/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/ObjectStatement.java @@ -20,9 +20,9 @@ public class ObjectStatement extends Statement { @Override public void compile(List target, ScopeRecord scope) { target.add(Instruction.loadObj().locate(loc())); - if (!map.isEmpty()) target.add(Instruction.dup(map.size()).locate(loc())); for (var el : map.entrySet()) { + target.add(Instruction.dup().locate(loc())); target.add(Instruction.loadValue(el.getKey()).locate(loc())); var val = el.getValue(); if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false); diff --git a/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java b/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java index 642e865..114f7b6 100644 --- a/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/OperationStatement.java @@ -7,13 +7,14 @@ import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.control.ThrowStatement; import me.topchetoeu.jscript.engine.CallContext; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; public class OperationStatement extends Statement { public final Statement[] args; - public final Instruction.Type operation; + public final Operation operation; @Override public void compile(List target, ScopeRecord scope) { @@ -96,7 +97,7 @@ public class OperationStatement extends Statement { } - public OperationStatement(Location loc, Instruction.Type operation, Statement... args) { + public OperationStatement(Location loc, Operation operation, Statement... args) { super(loc); this.operation = operation; this.args = args; diff --git a/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java b/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java index 6825066..0c069fd 100644 --- a/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/TypeofStatement.java @@ -6,8 +6,7 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.engine.scope.ScopeRecord; -import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.Symbol; +import me.topchetoeu.jscript.engine.values.Values; public class TypeofStatement extends Statement { public final Statement value; @@ -22,7 +21,7 @@ public class TypeofStatement extends Statement { if (value instanceof VariableStatement) { var i = scope.getKey(((VariableStatement)value).name); if (i instanceof String) { - target.add(Instruction.typeof((String)i)); + target.add(Instruction.typeof((String)i).locate(loc())); return; } } @@ -35,15 +34,14 @@ public class TypeofStatement extends Statement { var val = value.optimize(); if (val instanceof ConstantStatement) { - var cnst = (ConstantStatement)val; - if (cnst.value == null) return new ConstantStatement(loc(), "undefined"); - if (cnst.value instanceof Number) return new ConstantStatement(loc(), "number"); - if (cnst.value instanceof Boolean) return new ConstantStatement(loc(), "boolean"); - if (cnst.value instanceof String) return new ConstantStatement(loc(), "string"); - if (cnst.value instanceof Symbol) return new ConstantStatement(loc(), "symbol"); - if (cnst.value instanceof FunctionValue) return new ConstantStatement(loc(), "function"); - return new ConstantStatement(loc(), "object"); + return new ConstantStatement(loc(), Values.type(((ConstantStatement)val).value)); } + else if ( + val instanceof ObjectStatement || + val instanceof ArrayStatement || + val instanceof GlobalThisStatement + ) return new ConstantStatement(loc(), "object"); + else if(val instanceof FunctionStatement) return new ConstantStatement(loc(), "function"); return new TypeofStatement(loc(), val); } diff --git a/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java b/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java index 13be56b..5740c11 100644 --- a/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java @@ -4,36 +4,39 @@ import java.util.List; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.AssignStatement; import me.topchetoeu.jscript.compilation.Instruction; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; -public class VariableAssignStatement extends Statement { +public class VariableAssignStatement extends AssignStatement { public final String name; public final Statement value; - public final Type operation; + public final Operation operation; @Override public boolean pollutesStack() { return true; } @Override - public void compile(List target, ScopeRecord scope) { + public void compile(List target, ScopeRecord scope, boolean retPrevValue) { var i = scope.getKey(name); if (operation != null) { target.add(Instruction.loadVar(i).locate(loc())); + if (retPrevValue) target.add(Instruction.dup().locate(loc())); if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); else value.compileWithPollution(target, scope); target.add(Instruction.operation(operation).locate(loc())); + target.add(Instruction.storeVar(i, !retPrevValue).locate(loc())); } else { + if (retPrevValue) target.add(Instruction.loadVar(i).locate(loc())); if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); else value.compileWithPollution(target, scope); + target.add(Instruction.storeVar(i, !retPrevValue).locate(loc())); } - - target.add(Instruction.storeVar(i, true).locate(loc())); } - public VariableAssignStatement(Location loc, String name, Statement val, Type operation) { + public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { super(loc); this.name = name; this.value = val; diff --git a/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java b/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java index 1ccff35..b2db5ae 100644 --- a/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/VariableStatement.java @@ -3,10 +3,11 @@ package me.topchetoeu.jscript.compilation.values; import java.util.List; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.AssignStatement; import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class VariableStatement extends AssignableStatement { @@ -18,7 +19,7 @@ public class VariableStatement extends AssignableStatement { public boolean pure() { return true; } @Override - public Statement toAssign(Statement val, Type operation) { + public AssignStatement toAssign(Statement val, Operation operation) { return new VariableAssignStatement(loc(), name, val, operation); } diff --git a/src/me/topchetoeu/jscript/engine/Operation.java b/src/me/topchetoeu/jscript/engine/Operation.java new file mode 100644 index 0000000..9a1e37a --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/Operation.java @@ -0,0 +1,42 @@ +package me.topchetoeu.jscript.engine; + +public enum Operation { + INSTANCEOF(2, false), + IN(2, false), + + MULTIPLY(2, true), + DIVIDE(2, true), + MODULO(2, true), + ADD(2, true), + SUBTRACT(2, true), + + USHIFT_RIGHT(2, true), + SHIFT_RIGHT(2, true), + SHIFT_LEFT(2, true), + + GREATER(2, true), + LESS(2, true), + GREATER_EQUALS(2, true), + LESS_EQUALS(2, true), + LOOSE_EQUALS(2, true), + LOOSE_NOT_EQUALS(2, true), + EQUALS(2, true), + NOT_EQUALS(2, true), + + AND(2, true), + OR(2, true), + XOR(2, true), + + NEG(1, true), + POS(1, true), + NOT(1, true), + INVERSE(1, true); + + public final int operands; + public final boolean optimizable; + + private Operation(int n, boolean opt) { + this.operands = n; + this.optimizable = opt; + } +} diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 8548535..92a8a4d 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -4,19 +4,47 @@ import java.util.ArrayList; import java.util.List; import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.compilation.Instruction.Type; -import me.topchetoeu.jscript.engine.BreakpointData; import me.topchetoeu.jscript.engine.CallContext; 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 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 STACK_N_KEY = new DataKey<>(); public static final DataKey MAX_STACK_KEY = new DataKey<>(); public static final DataKey STOP_AT_START_KEY = new DataKey<>(); @@ -25,53 +53,70 @@ public class CodeFrame { public final LocalScope scope; public final Object thisArg; public final Object[] args; - public final List stack = new ArrayList<>(); - public final List tryCtxs = new ArrayList<>(); + public final List tryStack = new ArrayList<>(); public final CodeFunction function; + 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); } public Object peek(int offset) { - if (stack.size() <= offset) return null; - else return stack.get(stack.size() - 1 - offset); + if (stackPtr <= offset) return null; + else return stack[stackPtr - 1 - offset]; } public Object pop() { - if (stack.size() == 0) return null; - else return stack.remove(stack.size() - 1); + if (stackPtr == 0) return null; + return stack[--stackPtr]; + } + public Object[] take(int n) { + int srcI = stackPtr - n; + if (srcI < 0) srcI = 0; + + int dstI = n + srcI - stackPtr; + int copyN = stackPtr - srcI; + + Object[] res = new Object[n]; + System.arraycopy(stack, srcI, res, dstI, copyN); + stackPtr -= copyN; + + return res; } public void push(Object val) { - stack.add(stack.size(), Values.normalize(val)); + if (stack.length <= stackPtr) { + var newStack = new Object[stack.length * 2]; + System.arraycopy(stack, 0, newStack, 0, stack.length); + stack = newStack; + } + stack[stackPtr++] = Values.normalize(val); } - public void cleanup(CallContext ctx) { - stack.clear(); - codePtr = 0; - debugCmd = null; + public void start(CallContext ctx) { + if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 10000)) throw EngineException.ofRange("Stack overflow!"); + ctx.changeData(STACK_N_KEY); + + var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); + if (debugState != null) debugState.pushFrame(this); + } + public void end(CallContext ctx) { var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); if (debugState != null) debugState.popFrame(); ctx.changeData(STACK_N_KEY, -1); } - public Object next(CallContext ctx) throws InterruptedException { - var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); - - if (debugCmd == null) { - if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 100000)) - throw EngineException.ofRange("Stack overflow!"); - ctx.changeData(STACK_N_KEY); - - if (ctx.getData(STOP_AT_START_KEY, false)) debugCmd = DebugCommand.STEP_OVER; - else debugCmd = DebugCommand.NORMAL; - - if (debugState != null) debugState.pushFrame(this); - } - + private Object nextNoTry(CallContext ctx) throws InterruptedException { if (Thread.currentThread().isInterrupted()) throw new InterruptedException(); if (codePtr < 0 || codePtr >= function.body.length) return null; @@ -80,22 +125,31 @@ public class CodeFrame { var loc = instr.location; if (loc != null) prevLoc = loc; - if (debugState != null && loc != null) { - if ( - instr.type == Type.NOP && instr.match("debug") || debugState.breakpoints.contains(loc) || ( - ctx.getData(STEPPING_TROUGH_KEY, false) && - (debugCmd == DebugCommand.STEP_INTO || debugCmd == DebugCommand.STEP_OVER) - ) - ) { - ctx.setData(STEPPING_TROUGH_KEY, true); + // var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); + // if (debugCmd == null) { + // if (ctx.getData(STOP_AT_START_KEY, false)) debugCmd = DebugCommand.STEP_OVER; + // else debugCmd = DebugCommand.NORMAL; - debugState.breakpointNotifier.next(new BreakpointData(loc, ctx)); - debugCmd = debugState.commandNotifier.toAwaitable().await(); - if (debugCmd == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false); - } - } + // if (debugState != null) debugState.pushFrame(this); + // } + + // if (debugState != null && loc != null) { + // if ( + // instr.type == Type.NOP && instr.match("debug") || debugState.breakpoints.contains(loc) || ( + // ctx.getData(STEPPING_TROUGH_KEY, false) && + // (debugCmd == DebugCommand.STEP_INTO || debugCmd == DebugCommand.STEP_OVER) + // ) + // ) { + // ctx.setData(STEPPING_TROUGH_KEY, true); + + // debugState.breakpointNotifier.next(new BreakpointData(loc, ctx)); + // debugCmd = debugState.commandNotifier.toAwaitable().await(); + // if (debugCmd == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false); + // } + // } try { + this.jumpFlag = false; return Runners.exec(debugCmd, instr, this, ctx); } catch (EngineException e) { @@ -103,73 +157,143 @@ public class CodeFrame { } } + public Object next(CallContext ctx, EngineException prevError) throws InterruptedException { + TryCtx tryCtx = null; + var handled = prevError == null; + + while (!tryStack.isEmpty()) { + var tmp = tryStack.get(tryStack.size() - 1); + var remove = false; + + if (tmp.state == TryCtx.STATE_TRY) { + if (prevError != null) { + tmp.jumpPtr = tmp.end; + + if (tmp.hasCatch) { + tmp.state = TryCtx.STATE_CATCH; + scope.catchVars.add(new ValueVariable(false, prevError)); + codePtr = tmp.catchStart; + handled = true; + } + else if (tmp.hasFinally) { + tmp.state = TryCtx.STATE_FINALLY_THREW; + tmp.err = prevError; + codePtr = tmp.finallyStart; + handled = true; + } + } + else 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 if (tmp.state == TryCtx.STATE_FINALLY_JUMPED) codePtr = tmp.jumpPtr; + } + else codePtr = tmp.jumpPtr; + remove = true; + } + + if (!handled) throw prevError; + if (remove) tryStack.remove(tryStack.size() - 1); + else { + tryCtx = tmp; + break; + } + } + + if (!handled) throw prevError; + + 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 void handleReturn(Object value) { + + } + public Object run(CallContext ctx) throws InterruptedException { try { + start(ctx); while (true) { - var res = next(ctx); + var res = next(ctx, null); if (res != Runners.NO_RETURN) return res; } } finally { - cleanup(ctx); + end(ctx); } - - - // var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); - // DebugCommand command = ctx.getData(STOP_AT_START_KEY, false) ? DebugCommand.STEP_OVER : DebugCommand.NORMAL; - - // if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 200)) throw EngineException.ofRange("Stack overflow!"); - // ctx.changeData(STACK_N_KEY); - - // if (debugState != null) debugState.pushFrame(this); - - // Location loc = null; - - - // Location loc = null; - - // try { - // while (codePtr >= 0 && codePtr < function.body.length) { - // var _loc = function.body[codePtr].location; - // if (_loc != null) loc = _loc; - - // if (Thread.currentThread().isInterrupted()) throw new InterruptedException(); - // var instr = function.body[codePtr]; - - // if (debugState != null && loc != null) { - // if ( - // instr.type == Type.NOP && instr.match("debug") || - // ( - // (command == DebugCommand.STEP_INTO || command == DebugCommand.STEP_OVER) && - // ctx.getData(STEPPING_TROUGH_KEY, false) - // ) || - // debugState.breakpoints.contains(loc) - // ) { - // ctx.setData(STEPPING_TROUGH_KEY, true); - - // debugState.breakpointNotifier.next(new BreakpointData(loc, ctx)); - // command = debugState.commandNotifier.toAwaitable().await(); - // if (command == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false); - // } - // } - - // try { - // var res = Runners.exec(command, instr, this, ctx); - // if (res != Runners.NO_RETURN) return res; - // } - // catch (EngineException e) { - // throw e.add(function.name, instr.location); - // } - // } - // return null; - // } - // // catch (StackOverflowError e) { - // // e.printStackTrace(); - // // throw EngineException.ofRange("Stack overflow!").add(function.name, loc); - // // } - // finally { - // ctx.changeData(STACK_N_KEY, -1); - // } } public CodeFrame(Object thisArg, Object[] args, CodeFunction func) { diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 97f7818..1e0511f 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -5,6 +5,7 @@ import java.util.Collections; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.DebugCommand; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -38,10 +39,7 @@ public class Runners { } public static Object execCall(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - int n = instr.get(0); - - var callArgs = new Object[n]; - for (var i = n - 1; i >= 0; i--) callArgs[i] = frame.pop(); + var callArgs = frame.take(instr.get(0)); var func = frame.pop(); var thisArg = frame.pop(); @@ -51,10 +49,7 @@ public class Runners { return NO_RETURN; } public static Object execCallNew(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - int n = instr.get(0); - - var callArgs = new Object[n]; - for (var i = n - 1; i >= 0; i--) callArgs[i] = frame.pop(); + var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); if (Values.isFunction(funcObj) && Values.function(funcObj).special) { @@ -129,75 +124,30 @@ 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); - 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.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; + frame.addTry(instr.get(0), instr.get(1), instr.get(2)); + frame.codePtr++; + return NO_RETURN; } public static Object execDup(Instruction instr, CodeFrame frame, CallContext ctx) { - var val = frame.peek(instr.get(1)); - for (int i = 0; i < (int)instr.get(0); i++) { - frame.push(val); + int offset = instr.get(0), count = instr.get(1); + + for (var i = 0; i < count; i++) { + frame.push(frame.peek(offset + count - 1)); } + + frame.codePtr++; + return NO_RETURN; + } + public static Object execMove(Instruction instr, CodeFrame frame, CallContext ctx) { + int offset = instr.get(0), count = instr.get(1); + + var tmp = frame.take(offset); + var res = frame.take(count); + + for (var i = 0; i < offset; i++) frame.push(tmp[i]); + for (var i = 0; i < count; i++) frame.push(res[i]); + frame.codePtr++; return NO_RETURN; } @@ -316,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; } @@ -376,157 +335,13 @@ public class Runners { return NO_RETURN; } - public static Object execAdd(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - frame.push(Values.add(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execSubtract(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - frame.push(Values.subtract(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execMultiply(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - frame.push(Values.multiply(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execDivide(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - frame.push(Values.divide(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execModulo(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - frame.push(Values.modulo(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } + public static Object execOperation(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { + Operation op = instr.get(0); + var args = new Object[op.operands]; - public static Object execAnd(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); + for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); - frame.push(Values.and(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execOr(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.or(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execXor(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.xor(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - - public static Object execLeftShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.shiftLeft(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execRightShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.shiftRight(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execUnsignedRightShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.unsignedShiftRight(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - - public static Object execNot(Instruction instr, CodeFrame frame, CallContext ctx) { - frame.push(Values.not(frame.pop())); - frame.codePtr++; - return NO_RETURN; - } - public static Object execNeg(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - frame.push(Values.negative(ctx, frame.pop())); - frame.codePtr++; - return NO_RETURN; - } - public static Object execPos(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - frame.push(Values.toNumber(ctx, frame.pop())); - frame.codePtr++; - return NO_RETURN; - } - public static Object execInverse(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - frame.push(Values.bitwiseNot(ctx, frame.pop())); - frame.codePtr++; - return NO_RETURN; - } - - public static Object execGreaterThan(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.compare(ctx, a, b) > 0); - frame.codePtr++; - return NO_RETURN; - } - public static Object execLessThan(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.compare(ctx, a, b) < 0); - frame.codePtr++; - return NO_RETURN; - } - public static Object execGreaterThanEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.compare(ctx, a, b) >= 0); - frame.codePtr++; - return NO_RETURN; - } - public static Object execLessThanEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.compare(ctx, a, b) <= 0); - frame.codePtr++; - return NO_RETURN; - } - - public static Object execLooseEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.looseEqual(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execLooseNotEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(!Values.looseEqual(ctx, a, b)); - frame.codePtr++; - return NO_RETURN; - } - - public static Object execEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(Values.strictEquals(a, b)); - frame.codePtr++; - return NO_RETURN; - } - public static Object execNotEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - Object b = frame.pop(), a = frame.pop(); - - frame.push(!Values.strictEquals(a, b)); + frame.push(Values.operation(ctx, op, args)); frame.codePtr++; return NO_RETURN; } @@ -544,6 +359,7 @@ public class Runners { case TRY: return execTry(state, instr, frame, ctx); case DUP: return execDup(instr, frame, ctx); + case MOVE: return execMove(instr, frame, ctx); case LOAD_VALUE: return execLoadValue(instr, frame, ctx); case LOAD_VAR: return execLoadVar(instr, frame, ctx); case LOAD_OBJ: return execLoadObj(instr, frame, ctx); @@ -560,45 +376,16 @@ public class Runners { case STORE_SELF_FUNC: return execStoreSelfFunc(instr, frame, ctx); case MAKE_VAR: return execMakeVar(instr, frame, ctx); - case IN: return execIn(instr, frame, ctx); case KEYS: return execKeys(instr, frame, ctx); case DEF_PROP: return execDefProp(instr, frame, ctx); case TYPEOF: return execTypeof(instr, frame, ctx); case DELETE: return execDelete(instr, frame, ctx); - case INSTANCEOF: return execInstanceof(instr, frame, ctx); case JMP: return execJmp(instr, frame, ctx); case JMP_IF: return execJmpIf(instr, frame, ctx); case JMP_IFN: return execJmpIfNot(instr, frame, ctx); - case ADD: return execAdd(instr, frame, ctx); - case SUBTRACT: return execSubtract(instr, frame, ctx); - case MULTIPLY: return execMultiply(instr, frame, ctx); - case DIVIDE: return execDivide(instr, frame, ctx); - case MODULO: return execModulo(instr, frame, ctx); - - case AND: return execAnd(instr, frame, ctx); - case OR: return execOr(instr, frame, ctx); - case XOR: return execXor(instr, frame, ctx); - - case SHIFT_LEFT: return execLeftShift(instr, frame, ctx); - case SHIFT_RIGHT: return execRightShift(instr, frame, ctx); - case USHIFT_RIGHT: return execUnsignedRightShift(instr, frame, ctx); - - case NOT: return execNot(instr, frame, ctx); - case NEG: return execNeg(instr, frame, ctx); - case POS: return execPos(instr, frame, ctx); - case INVERSE: return execInverse(instr, frame, ctx); - - case GREATER: return execGreaterThan(instr, frame, ctx); - case GREATER_EQUALS: return execGreaterThanEquals(instr, frame, ctx); - case LESS: return execLessThan(instr, frame, ctx); - case LESS_EQUALS: return execLessThanEquals(instr, frame, ctx); - - case LOOSE_EQUALS: return execLooseEquals(instr, frame, ctx); - case LOOSE_NOT_EQUALS: return execLooseNotEquals(instr, frame, ctx); - case EQUALS: return execEquals(instr, frame, ctx); - case NOT_EQUALS: return execNotEquals(instr, frame, ctx); + case OPERATION: return execOperation(instr, frame, ctx); default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); } diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index a14d881..384ceb8 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -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); } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index 39457a8..efa0bb4 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -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 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]; } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java index 0cb5ae1..5839e19 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java @@ -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); } @@ -43,7 +46,7 @@ public class LocalScopeRecord implements ScopeRecord { public Object getKey(String name) { var capI = captures.indexOf(name); - var locI = locals.indexOf(name); + var locI = locals.lastIndexOf(name); if (locI >= 0) return locI; if (capI >= 0) return ~capI; if (parent != null) { @@ -62,11 +65,17 @@ public class LocalScopeRecord implements ScopeRecord { locals.contains(name) || parent != null && parent.has(ctx, name); } - public Object define(String name) { - if (locals.contains(name)) return locals.indexOf(name); + public Object define(String name, boolean force) { + if (!force && locals.contains(name)) return locals.indexOf(name); locals.add(name); return locals.size() - 1; } + public Object define(String name) { + return define(name, false); + } + public void undefine() { + locals.remove(locals.size() - 1); + } public LocalScopeRecord(GlobalScope global) { this.parent = null; diff --git a/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java index fe4d5d3..fa76859 100644 --- a/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java @@ -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(); } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index a5b6851..fd5bcf3 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import me.topchetoeu.jscript.engine.CallContext; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.frame.ConvertHint; import me.topchetoeu.jscript.exceptions.EngineException; @@ -218,6 +219,47 @@ public class Values { return false; } + public static Object operation(CallContext ctx, Operation op, Object... args) throws InterruptedException { + switch (op) { + case ADD: return add(ctx, args[0], args[1]); + case SUBTRACT: return subtract(ctx, args[0], args[1]); + case DIVIDE: return divide(ctx, args[0], args[1]); + case MULTIPLY: return multiply(ctx, args[0], args[1]); + case MODULO: return modulo(ctx, args[0], args[1]); + + case AND: return and(ctx, args[0], args[1]); + case OR: return or(ctx, args[0], args[1]); + case XOR: return xor(ctx, args[0], args[1]); + + case EQUALS: return strictEquals(args[0], args[1]); + case NOT_EQUALS: return !strictEquals(args[0], args[1]); + case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]); + case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]); + + case GREATER: return compare(ctx, args[0], args[1]) > 0; + case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0; + case LESS: return compare(ctx, args[0], args[1]) < 0; + case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0; + + case INVERSE: return bitwiseNot(ctx, args[0]); + case NOT: return not(args[0]); + case POS: return toNumber(ctx, args[0]); + case NEG: return negative(ctx, args[0]); + + case SHIFT_LEFT: return shiftLeft(ctx, args[0], args[1]); + case SHIFT_RIGHT: return shiftRight(ctx, args[0], args[1]); + case USHIFT_RIGHT: return unsignedShiftRight(ctx, args[0], args[1]); + + case IN: return hasMember(ctx, args[1], args[0], false); + case INSTANCEOF: { + var proto = getMember(ctx, args[1], "prototype"); + return isInstanceOf(ctx, args[0], proto); + } + + default: return null; + } + } + public static Object getMember(CallContext ctx, Object obj, Object key) throws InterruptedException { obj = normalize(obj); key = normalize(key); if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); @@ -312,7 +354,8 @@ public class Values { } public static Object call(CallContext ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { - if (!isFunction(func)) throw EngineException.ofType("Attempted 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); } diff --git a/src/me/topchetoeu/jscript/js/bootstrap.js b/src/me/topchetoeu/jscript/js/bootstrap.js index 2cf9d29..3d87c49 100644 --- a/src/me/topchetoeu/jscript/js/bootstrap.js +++ b/src/me/topchetoeu/jscript/js/bootstrap.js @@ -2,7 +2,7 @@ var ts = require('./ts__'); log("Loaded typescript!"); -var src = '', lib = libs.join(''), decls = '', version = 0; +var src = '', lib = libs.concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0; var libSnapshot = ts.ScriptSnapshot.fromString(lib); var settings = { diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 3edfe7c..0df53d5 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -10,31 +10,17 @@ import me.topchetoeu.jscript.parsing.Token; public class JSON { public static ParseRes parseIdentifier(List tokens, int i) { - try { - if (tokens.get(i).isIdentifier()) return ParseRes.res(tokens.get(i).identifier(), 1); - else return ParseRes.failed(); - } - catch (IndexOutOfBoundsException e) { - return ParseRes.failed(); - } + return Parsing.parseIdentifier(tokens, i); } public static ParseRes parseString(String filename, List tokens, int i) { - try { - if (tokens.get(i).isString()) return ParseRes.res(tokens.get(i).string(), 1); - else return ParseRes.failed(); - } - catch (IndexOutOfBoundsException e) { - return ParseRes.failed(); - } + var res = Parsing.parseString(filename, tokens, i); + if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n); + else return res.transform(); } public static ParseRes parseNumber(String filename, List tokens, int i) { - try { - if (tokens.get(i).isNumber()) return ParseRes.res(tokens.get(i).number(), 1); - else return ParseRes.failed(); - } - catch (IndexOutOfBoundsException e) { - return ParseRes.failed(); - } + var res = Parsing.parseNumber(filename, tokens, i); + if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n); + else return res.transform(); } public static ParseRes parseBool(String filename, List tokens, int i) { var id = parseIdentifier(tokens, i); diff --git a/src/me/topchetoeu/jscript/json/JSONElement.java b/src/me/topchetoeu/jscript/json/JSONElement.java index 16aef1b..e26aa3a 100644 --- a/src/me/topchetoeu/jscript/json/JSONElement.java +++ b/src/me/topchetoeu/jscript/json/JSONElement.java @@ -10,7 +10,7 @@ public class JSONElement { MAP, } - public static JSONElement NULL = new JSONElement(Type.NULL, null); + public static final JSONElement NULL = new JSONElement(Type.NULL, null); public static JSONElement map(JSONMap val) { return new JSONElement(Type.MAP, val); diff --git a/src/me/topchetoeu/jscript/parsing/Operator.java b/src/me/topchetoeu/jscript/parsing/Operator.java index 5c2b939..7037516 100644 --- a/src/me/topchetoeu/jscript/parsing/Operator.java +++ b/src/me/topchetoeu/jscript/parsing/Operator.java @@ -3,29 +3,28 @@ package me.topchetoeu.jscript.parsing; import java.util.HashMap; import java.util.Map; -import me.topchetoeu.jscript.compilation.Instruction; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.Operation; public enum Operator { - MULTIPLY("*", Type.MULTIPLY, 13), - DIVIDE("/", Type.DIVIDE, 12), - MODULO("%", Type.MODULO, 12), - SUBTRACT("-", Type.SUBTRACT, 11), - ADD("+", Type.ADD, 11), - SHIFT_RIGHT(">>", Type.SHIFT_RIGHT, 10), - SHIFT_LEFT("<<", Type.SHIFT_LEFT, 10), - USHIFT_RIGHT(">>>", Type.USHIFT_RIGHT, 10), - GREATER(">", Type.GREATER, 9), - LESS("<", Type.LESS, 9), - GREATER_EQUALS(">=", Type.GREATER_EQUALS, 9), - LESS_EQUALS("<=", Type.LESS_EQUALS, 9), - NOT_EQUALS("!=", Type.LOOSE_NOT_EQUALS, 8), - LOOSE_NOT_EQUALS("!==", Type.NOT_EQUALS, 8), - EQUALS("==", Type.LOOSE_EQUALS, 8), - LOOSE_EQUALS("===", Type.EQUALS, 8), - AND("&", Type.AND, 7), - XOR("^", Type.XOR, 6), - OR("|", Type.OR, 5), + MULTIPLY("*", Operation.MULTIPLY, 13), + DIVIDE("/", Operation.DIVIDE, 12), + MODULO("%", Operation.MODULO, 12), + SUBTRACT("-", Operation.SUBTRACT, 11), + ADD("+", Operation.ADD, 11), + SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10), + SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10), + USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10), + GREATER(">", Operation.GREATER, 9), + LESS("<", Operation.LESS, 9), + GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9), + LESS_EQUALS("<=", Operation.LESS_EQUALS, 9), + NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8), + LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8), + EQUALS("==", Operation.LOOSE_EQUALS, 8), + LOOSE_EQUALS("===", Operation.EQUALS, 8), + AND("&", Operation.AND, 7), + XOR("^", Operation.XOR, 6), + OR("|", Operation.OR, 5), LAZY_AND("&&", 4), LAZY_OR("||", 3), ASSIGN_SHIFT_LEFT("<<=", 2, true), @@ -57,7 +56,7 @@ public enum Operator { DECREASE("--"); public final String value; - public final Instruction.Type operation; + public final Operation operation; public final int precedence; public final boolean reverse; private static final Map ops = new HashMap<>(); @@ -99,13 +98,13 @@ public enum Operator { this.reverse = reverse; } - private Operator(String value, Instruction.Type funcName, int precedence) { + private Operator(String value, Operation funcName, int precedence) { this. value = value; this.operation = funcName; this.precedence = precedence; this.reverse = false; } - private Operator(String value, Instruction.Type funcName, int precedence, boolean reverse) { + private Operator(String value, Operation funcName, int precedence, boolean reverse) { this.value = value; this.operation = funcName; this.precedence = precedence; diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index c817931..27804db 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -13,6 +13,7 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.values.*; +import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -938,12 +939,12 @@ public class Parsing { if (!opState.isSuccess()) return ParseRes.failed(); var op = opState.result; - Type operation = null; + Operation operation = null; - if (op == Operator.ADD) operation = Type.POS; - else if (op == Operator.SUBTRACT) operation = Type.NEG; - else if (op == Operator.INVERSE) operation = Type.INVERSE; - else if (op == Operator.NOT) operation = Type.NOT; + if (op == Operator.ADD) operation = Operation.POS; + else if (op == Operator.SUBTRACT) operation = Operation.NEG; + else if (op == Operator.INVERSE) operation = Operation.INVERSE; + else if (op == Operator.NOT) operation = Operation.NOT; else return ParseRes.failed(); var res = parseValue(filename, tokens, n + i, 14); @@ -1103,19 +1104,19 @@ public class Parsing { if (!res.isSuccess()) return ParseRes.error(loc, "Expected value after assignment operator '%s'.".formatted(op.value), res); n += res.n; - Type operation = null; + Operation operation = null; - if (op == Operator.ASSIGN_ADD) operation = Type.ADD; - if (op == Operator.ASSIGN_SUBTRACT) operation = Type.SUBTRACT; - if (op == Operator.ASSIGN_MULTIPLY) operation = Type.MULTIPLY; - if (op == Operator.ASSIGN_DIVIDE) operation = Type.DIVIDE; - if (op == Operator.ASSIGN_MODULO) operation = Type.MODULO; - if (op == Operator.ASSIGN_OR) operation = Type.OR; - if (op == Operator.ASSIGN_XOR) operation = Type.XOR; - if (op == Operator.ASSIGN_AND) operation = Type.AND; - if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Type.SHIFT_LEFT; - if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Type.SHIFT_RIGHT; - if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Type.USHIFT_RIGHT; + if (op == Operator.ASSIGN_ADD) operation = Operation.ADD; + if (op == Operator.ASSIGN_SUBTRACT) operation = Operation.SUBTRACT; + if (op == Operator.ASSIGN_MULTIPLY) operation = Operation.MULTIPLY; + if (op == Operator.ASSIGN_DIVIDE) operation = Operation.DIVIDE; + if (op == Operator.ASSIGN_MODULO) operation = Operation.MODULO; + if (op == Operator.ASSIGN_OR) operation = Operation.OR; + if (op == Operator.ASSIGN_XOR) operation = Operation.XOR; + if (op == Operator.ASSIGN_AND) operation = Operation.AND; + if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Operation.SHIFT_LEFT; + if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Operation.SHIFT_RIGHT; + if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Operation.USHIFT_RIGHT; return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n); } @@ -1180,7 +1181,7 @@ public class Parsing { if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'instanceof'.", valRes); n += valRes.n; - return ParseRes.res(new OperationStatement(loc, Type.INSTANCEOF, prev, valRes.result), n); + return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); } public static ParseRes parseIn(String filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); @@ -1193,7 +1194,7 @@ public class Parsing { if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'in'.", valRes); n += valRes.n; - return ParseRes.res(new OperationStatement(loc, Type.IN, prev, valRes.result), n); + return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); } public static ParseRes parseComma(String filename, List tokens, int i, Statement prev, int precedence) { var loc = getLoc(filename, tokens, i); diff --git a/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java b/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java index 32c4ab3..627ff18 100644 --- a/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java +++ b/src/me/topchetoeu/jscript/polyfills/AsyncFunction.java @@ -3,7 +3,6 @@ package me.topchetoeu.jscript.polyfills; import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; -import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -18,30 +17,38 @@ public class AsyncFunction extends FunctionValue { private Object awaited = null; public final Promise promise = new Promise(); public CodeFrame frame; - private final NativeFunction fulfillFunc = new NativeFunction("", this::fulfill); - private final NativeFunction rejectFunc = new NativeFunction("", this::reject); + private final NativeFunction fulfillFunc = new NativeFunction("", (ctx, thisArg, args) -> { + if (args.length == 0) exec(ctx, null, Runners.NO_RETURN); + else exec(ctx, args[0], Runners.NO_RETURN); - private Object reject(CallContext ctx, Object thisArg, Object[] args) throws InterruptedException { - if (args.length > 0) promise.reject(ctx, args[0]); return null; - } - public Object fulfill(CallContext ctx, Object thisArg, Object[] args) throws InterruptedException { - if (args.length == 1) frame.push(args[0]); + }); + private final NativeFunction rejectFunc = new NativeFunction("", (ctx, thisArg, args) -> { + if (args.length == 0) exec(ctx, Runners.NO_RETURN, null); + else exec(ctx, Runners.NO_RETURN, args[0]); + + return null; + }); + + public Object exec(CallContext ctx, Object val, Object err) throws InterruptedException { + if (val != Runners.NO_RETURN) frame.push(val); + + frame.start(ctx); while (true) { awaiting = false; awaited = null; try { - var res = frame.next(ctx); + var res = frame.next(ctx, err == Runners.NO_RETURN ? null : new EngineException(err)); if (res != Runners.NO_RETURN) { promise.fulfill(ctx, res); - return null; + break; } } catch (EngineException e) { - promise.reject(e); - return null; + promise.reject(e.value); + break; } if (!awaiting) continue; @@ -50,19 +57,24 @@ public class AsyncFunction extends FunctionValue { if (awaited instanceof Promise) ((Promise)awaited).then(ctx, fulfillFunc, rejectFunc); else if (Values.isPrimitive(awaited)) frame.push(awaited); - try { - var res = Values.getMember(ctx, awaited, "then"); - if (res instanceof FunctionValue) { - Values.function(res).call(ctx, awaited, fulfillFunc, rejectFunc); - return null; + else { + try { + var res = Values.getMember(ctx, awaited, "then"); + if (res instanceof FunctionValue) { + Values.function(res).call(ctx, awaited, fulfillFunc, rejectFunc); + break; + } + else frame.push(awaited); + } + catch (EngineException e) { + promise.reject(e); + break; } - else frame.push(awaited); - } - catch (EngineException e) { - promise.reject(e); - return null; } } + + frame.end(ctx); + return null; } public Object await(CallContext ctx, Object thisArg, Object[] args) { @@ -75,8 +87,10 @@ public class AsyncFunction extends FunctionValue { @Override public Object call(CallContext _ctx, Object thisArg, Object... args) throws InterruptedException { var handler = new CallHandler(); - handler.frame = new CodeFrame(thisArg, new Object[] { new NativeFunction("await", handler::await), new ArrayValue(args) }, body); - handler.fulfill(_ctx, null, new Object[0]); + var func = body.call(_ctx, thisArg, new NativeFunction("await", handler::await)); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + handler.frame = new CodeFrame(thisArg, args, (CodeFunction)func); + handler.exec(_ctx, Runners.NO_RETURN, Runners.NO_RETURN); return handler.promise; } diff --git a/src/me/topchetoeu/jscript/polyfills/JSON.java b/src/me/topchetoeu/jscript/polyfills/JSON.java index 953e17c..34fcdd1 100644 --- a/src/me/topchetoeu/jscript/polyfills/JSON.java +++ b/src/me/topchetoeu/jscript/polyfills/JSON.java @@ -1,5 +1,7 @@ package me.topchetoeu.jscript.polyfills; +import java.util.HashSet; + import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ObjectValue; @@ -8,31 +10,75 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.json.JSONElement; +import me.topchetoeu.jscript.json.JSONList; +import me.topchetoeu.jscript.json.JSONMap; public class JSON { - private static Object convert(JSONElement val) { + private static Object toJS(JSONElement val) { if (val.isBoolean()) return val.bool(); if (val.isString()) return val.string(); if (val.isNumber()) return val.number(); - if (val.isList()) return ArrayValue.of(val.list().stream().map(JSON::convert).toList()); + if (val.isList()) return ArrayValue.of(val.list().stream().map(JSON::toJS).toList()); if (val.isMap()) { var res = new ObjectValue(); for (var el : val.map().entrySet()) { - res.defineProperty(el.getKey(), convert(el.getValue())); + res.defineProperty(el.getKey(), toJS(el.getValue())); } return res; } if (val.isNull()) return Values.NULL; return null; } + private static JSONElement toJSON(CallContext ctx, Object val, HashSet prev) throws InterruptedException { + if (val instanceof Boolean) return JSONElement.bool((boolean)val); + if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue()); + if (val instanceof String) return JSONElement.string((String)val); + if (val == Values.NULL) return JSONElement.NULL; + if (val instanceof ObjectValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONMap(); + + for (var el : ((ObjectValue)val).keys(false)) { + var jsonEl = toJSON(ctx, ((ObjectValue)val).getMember(ctx, el), prev); + if (jsonEl == null) continue; + if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val instanceof ArrayValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONList(); + + for (var el : ((ArrayValue)val).toArray()) { + var jsonEl = toJSON(ctx, el, prev); + if (jsonEl == null) jsonEl = JSONElement.NULL; + res.add(jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val == null) return null; + return null; + } @Native public static Object parse(CallContext ctx, String val) throws InterruptedException { try { - return convert(me.topchetoeu.jscript.json.JSON.parse("", val)); + return toJS(me.topchetoeu.jscript.json.JSON.parse("", val)); } catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } } + @Native + public static String stringify(CallContext ctx, Object val) throws InterruptedException { + return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>())); + } }