From a9a19824fe94f6a467dcfab399dea319f8ecb173 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 1 Sep 2024 16:44:51 +0300 Subject: [PATCH] major rewrite: clean up a lot of code and lay ground for ES6 support --- build.gradle | 24 +- src/assets/lib/index.js | 2 + .../jscript/common/FunctionBody.java | 5 +- .../jscript/common/Instruction.java | 279 ++++++++++-------- .../common/environment/Environment.java | 14 +- .../jscript/common/json/JSONElement.java | 3 +- .../jscript/common/json/JSONMap.java | 36 +-- .../jscript/common/mapping/FunctionMap.java | 6 +- .../jscript/common/parsing/Location.java | 6 +- .../jscript/common/parsing/ParseRes.java | 27 +- .../jscript/common/parsing/Parsing.java | 41 ++- .../common/parsing/SourceLocation.java | 30 +- .../jscript/compilation/CompileResult.java | 65 +++- .../jscript/compilation/CompoundNode.java | 46 ++- .../compilation/DeferredIntSupplier.java | 19 ++ .../jscript/compilation/ExpressionNode.java | 5 - .../jscript/compilation/FunctionNode.java | 130 ++++++++ .../compilation/FunctionStatementNode.java | 25 ++ .../compilation/FunctionValueNode.java | 19 ++ .../jscript/compilation/JavaScript.java | 53 ++-- .../jscript/compilation/LabelContext.java | 85 ++++++ .../topchetoeu/jscript/compilation/Node.java | 11 +- .../jscript/compilation/NodeChildren.java | 93 ++++++ .../topchetoeu/jscript/compilation/Path.java | 29 ++ .../jscript/compilation/ThrowSyntaxNode.java | 18 -- .../compilation/VariableDeclareNode.java | 25 +- .../compilation/control/BreakNode.java | 11 +- .../compilation/control/ContinueNode.java | 11 +- .../compilation/control/DeleteNode.java | 3 +- .../compilation/control/DoWhileNode.java | 31 +- .../compilation/control/ForInNode.java | 36 ++- .../jscript/compilation/control/ForNode.java | 40 ++- .../compilation/control/ForOfNode.java | 14 +- .../jscript/compilation/control/IfNode.java | 75 ++++- .../compilation/control/ReturnNode.java | 3 +- .../compilation/control/SwitchNode.java | 82 +++-- .../compilation/control/ThrowNode.java | 3 +- .../jscript/compilation/control/TryNode.java | 74 +++-- .../compilation/control/WhileNode.java | 86 +++--- .../compilation/scope/FunctionScope.java | 51 +++- .../compilation/scope/GlobalScope.java | 10 + .../jscript/compilation/scope/LocalScope.java | 14 +- .../jscript/compilation/scope/Scope.java | 6 +- .../compilation/scope/VariableList.java | 115 ++++++-- .../compilation/values/ArgumentsNode.java | 17 ++ .../jscript/compilation/values/ArrayNode.java | 9 +- .../compilation/values/FunctionNode.java | 150 ---------- .../compilation/values/GlobalThisNode.java | 6 +- .../compilation/values/ObjectNode.java | 19 +- .../jscript/compilation/values/RegexNode.java | 6 +- .../jscript/compilation/values/ThisNode.java | 17 ++ .../compilation/values/VariableNode.java | 57 +++- .../values/constants/BoolNode.java | 2 - .../values/constants/NullNode.java | 4 +- .../values/constants/NumberNode.java | 2 - .../values/constants/StringNode.java | 2 - .../values/operations/CallNode.java | 18 +- .../values/operations/DiscardNode.java | 10 +- .../values/operations/IndexAssignNode.java | 3 +- .../values/operations/IndexNode.java | 6 +- .../values/operations/LazyAndNode.java | 11 +- .../values/operations/LazyOrNode.java | 12 +- .../values/operations/OperationNode.java | 8 - .../values/operations/TypeofNode.java | 28 +- .../values/operations/VariableAssignNode.java | 15 +- .../values/operations/VariableIndexNode.java | 22 -- .../jscript/{common => runtime}/Compiler.java | 9 +- .../me/topchetoeu/jscript/runtime/Engine.java | 3 +- .../topchetoeu/jscript/runtime/EventLoop.java | 1 - .../me/topchetoeu/jscript/runtime/Frame.java | 89 ++++-- .../jscript/runtime/InstructionRunner.java | 120 +++++--- .../jscript/runtime/SimpleRepl.java | 11 +- .../runtime/exceptions/EngineException.java | 2 + .../runtime/exceptions/SyntaxException.java | 2 +- .../jscript/runtime/scope/GlobalScope.java | 74 ----- .../jscript/runtime/scope/LocalScope.java | 29 -- .../jscript/runtime/scope/ValueVariable.java | 25 -- .../jscript/runtime/scope/Variable.java | 24 -- .../jscript/runtime/values/Value.java | 60 +++- .../values/functions/CodeFunction.java | 5 +- .../runtime/values/objects/ArrayValue.java | 6 +- .../runtime/values/objects/ScopeValue.java | 10 +- 82 files changed, 1657 insertions(+), 998 deletions(-) create mode 100644 src/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/ExpressionNode.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/FunctionNode.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/LabelContext.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/NodeChildren.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/Path.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/ThrowSyntaxNode.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/values/FunctionNode.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/values/ThisNode.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/values/operations/VariableIndexNode.java rename src/java/me/topchetoeu/jscript/{common => runtime}/Compiler.java (84%) delete mode 100644 src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java delete mode 100644 src/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java delete mode 100644 src/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java delete mode 100644 src/java/me/topchetoeu/jscript/runtime/scope/Variable.java diff --git a/build.gradle b/build.gradle index 187c46b..34358c7 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,27 @@ plugins { id "application" } +repositories { + mavenCentral() +} + +dependencies { + annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' + // Genuinely fuck Java + compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' +} + java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - toolchain.languageVersion = JavaLanguageVersion.of(11) - withSourcesJar() + toolchain.languageVersion = JavaLanguageVersion.of(17) +} + +configure([tasks.compileJava]) { + sourceCompatibility = 17 // for the IDE support + options.release = 11 + + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(17) + } } jar { diff --git a/src/assets/lib/index.js b/src/assets/lib/index.js index 6176ce2..96d0e60 100644 --- a/src/assets/lib/index.js +++ b/src/assets/lib/index.js @@ -1,3 +1,5 @@ +#! my special comment lol + (function(target, primordials) { var makeSymbol = primordials.symbol.makeSymbol; var getSymbol = primordials.symbol.getSymbol; diff --git a/src/java/me/topchetoeu/jscript/common/FunctionBody.java b/src/java/me/topchetoeu/jscript/common/FunctionBody.java index d0328a0..9d3cab1 100644 --- a/src/java/me/topchetoeu/jscript/common/FunctionBody.java +++ b/src/java/me/topchetoeu/jscript/common/FunctionBody.java @@ -3,12 +3,13 @@ package me.topchetoeu.jscript.common; public class FunctionBody { public final FunctionBody[] children; public final Instruction[] instructions; - public final int localsN, argsN; + public final int localsN, capturesN, argsN; - public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) { + public FunctionBody(int localsN, int capturesN, int argsN, Instruction[] instructions, FunctionBody[] children) { this.children = children; this.argsN = argsN; this.localsN = localsN; + this.capturesN = capturesN; this.instructions = instructions; } } diff --git a/src/java/me/topchetoeu/jscript/common/Instruction.java b/src/java/me/topchetoeu/jscript/common/Instruction.java index edc0652..478fddc 100644 --- a/src/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/java/me/topchetoeu/jscript/common/Instruction.java @@ -1,8 +1,8 @@ package me.topchetoeu.jscript.common; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +// import java.io.DataInputStream; +// import java.io.DataOutputStream; +// import java.io.IOException; import java.util.HashMap; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; @@ -35,20 +35,27 @@ public class Instruction { LOAD_FUNC(0x30), LOAD_ARR(0x31), LOAD_OBJ(0x32), - STORE_SELF_FUNC(0x33), + LOAD_GLOB(0x33), LOAD_REGEX(0x34), LOAD_VAR(0x40), LOAD_MEMBER(0x41), - LOAD_GLOB(0x42), - STORE_VAR(0x43), - STORE_MEMBER(0x44), + LOAD_ARGS(0x42), + LOAD_THIS(0x43), + STORE_VAR(0x48), + STORE_MEMBER(0x49), - MAKE_VAR(0x50), - DEF_PROP(0x51), - KEYS(0x52), - TYPEOF(0x53), - OPERATION(0x54); + DEF_PROP(0x50), + KEYS(0x51), + TYPEOF(0x52), + OPERATION(0x53), + + GLOB_GET(0x60), + GLOB_SET(0x61), + GLOB_DEF(0x62), + + STACK_ALLOC(0x70), + STACK_FREE(0x71); private static final HashMap types = new HashMap<>(); public final int numeric; @@ -108,123 +115,128 @@ public class Instruction { return params[i].equals(arg); } - public void write(DataOutputStream writer) throws IOException { - var rawType = type.numeric; + // public void write(DataOutputStream writer) throws IOException { + // var rawType = type.numeric; - switch (type) { - case KEYS: - case PUSH_BOOL: - case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break; - case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break; - case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break; - default: - } + // switch (type) { + // case KEYS: + // case PUSH_BOOL: + // case STORE_MEMBER: + // case GLOB_SET: + // rawType |= (boolean)get(0) ? 128 : 0; break; + // case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break; + // default: + // } - writer.writeByte(rawType); + // writer.writeByte(rawType); - switch (type) { - case CALL: - case CALL_NEW: - case CALL_MEMBER: - writer.writeInt(get(0)); - writer.writeUTF(get(1)); - break; - case DUP: writer.writeInt(get(0)); break; - case JMP: writer.writeInt(get(0)); break; - case JMP_IF: writer.writeInt(get(0)); break; - case JMP_IFN: writer.writeInt(get(0)); break; - case LOAD_ARR: writer.writeInt(get(0)); break; - case LOAD_FUNC: { - writer.writeInt(params.length - 1); + // switch (type) { + // case CALL: + // case CALL_NEW: + // case CALL_MEMBER: + // writer.writeInt(get(0)); + // writer.writeUTF(get(1)); + // break; + // case DUP: writer.writeInt(get(0)); break; + // case JMP: writer.writeInt(get(0)); break; + // case JMP_IF: writer.writeInt(get(0)); break; + // case JMP_IFN: writer.writeInt(get(0)); break; + // case LOAD_ARR: writer.writeInt(get(0)); break; + // case LOAD_FUNC: { + // writer.writeInt(params.length - 1); - for (var i = 0; i < params.length; i++) { - writer.writeInt(get(i + 1)); - } + // for (var i = 0; i < params.length; i++) { + // writer.writeInt(get(i + 1)); + // } - writer.writeInt(get(0)); - writer.writeUTF(get(0)); - break; - } - case LOAD_REGEX: writer.writeUTF(get(0)); break; - case LOAD_VAR: writer.writeInt(get(0)); break; - case MAKE_VAR: writer.writeUTF(get(0)); break; - case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break; - case PUSH_NUMBER: writer.writeDouble(get(0)); break; - case PUSH_STRING: writer.writeUTF(get(0)); break; - case STORE_SELF_FUNC: writer.writeInt(get(0)); break; - case STORE_VAR: writer.writeInt(get(0)); break; - case THROW_SYNTAX: writer.writeUTF(get(0)); - case TRY_START: - writer.writeInt(get(0)); - writer.writeInt(get(1)); - writer.writeInt(get(2)); - break; - case TYPEOF: - if (params.length > 0) writer.writeUTF(get(0)); - break; - default: - } - } + // writer.writeInt(get(0)); + // writer.writeUTF(get(0)); + // break; + // } + // case LOAD_REGEX: writer.writeUTF(get(0)); break; + // case LOAD_VAR: writer.writeInt(get(0)); break; + // case GLOB_DEF: writer.writeUTF(get(0)); break; + // case GLOB_GET: writer.writeUTF(get(0)); break; + // case GLOB_SET: + // writer.writeUTF(get(0)); + // break; + // case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break; + // case PUSH_NUMBER: writer.writeDouble(get(0)); break; + // case PUSH_STRING: writer.writeUTF(get(0)); break; + // case STORE_VAR: writer.writeInt(get(0)); break; + // case THROW_SYNTAX: writer.writeUTF(get(0)); + // case TRY_START: + // writer.writeInt(get(0)); + // writer.writeInt(get(1)); + // writer.writeInt(get(2)); + // break; + // case TYPEOF: + // if (params.length > 0) writer.writeUTF(get(0)); + // break; + // default: + // } + // } private Instruction(Type type, Object ...params) { this.type = type; this.params = params; } - public static Instruction read(DataInputStream stream) throws IOException { - var rawType = stream.readUnsignedByte(); - var type = Type.fromNumeric(rawType & 127); - var flag = (rawType & 128) != 0; + // public static Instruction read(DataInputStream stream) throws IOException { + // var rawType = stream.readUnsignedByte(); + // var type = Type.fromNumeric(rawType & 127); + // var flag = (rawType & 128) != 0; - switch (type) { - case CALL: return call(stream.readInt(), stream.readUTF()); - case CALL_NEW: return callNew(stream.readInt(), stream.readUTF()); - case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF()); - case DEF_PROP: return defProp(); - case DELETE: return delete(); - case DISCARD: return discard(); - case DUP: return dup(stream.readInt()); - case JMP: return jmp(stream.readInt()); - case JMP_IF: return jmpIf(stream.readInt()); - case JMP_IFN: return jmpIfNot(stream.readInt()); - case KEYS: return keys(flag); - case LOAD_ARR: return loadArr(stream.readInt()); - case LOAD_FUNC: { - var captures = new int[stream.readInt()]; + // switch (type) { + // case CALL: return call(stream.readInt(), stream.readUTF()); + // case CALL_NEW: return callNew(stream.readInt(), stream.readUTF()); + // case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF()); + // case DEF_PROP: return defProp(); + // case DELETE: return delete(); + // case DISCARD: return discard(); + // case DUP: return dup(stream.readInt()); + // case JMP: return jmp(stream.readInt()); + // case JMP_IF: return jmpIf(stream.readInt()); + // case JMP_IFN: return jmpIfNot(stream.readInt()); + // case KEYS: return keys(flag); + // case LOAD_ARR: return loadArr(stream.readInt()); + // case LOAD_FUNC: { + // var captures = new int[stream.readInt()]; - for (var i = 0; i < captures.length; i++) { - captures[i] = stream.readInt(); - } + // for (var i = 0; i < captures.length; i++) { + // captures[i] = stream.readInt(); + // } - return loadFunc(stream.readInt(), stream.readUTF(), captures); - } - case LOAD_GLOB: return loadGlob(); - case LOAD_MEMBER: return loadMember(); - case LOAD_OBJ: return loadObj(); - case LOAD_REGEX: return loadRegex(stream.readUTF(), null); - case LOAD_VAR: return loadVar(stream.readInt()); - case MAKE_VAR: return makeVar(stream.readUTF()); - case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte())); - case PUSH_NULL: return pushNull(); - case PUSH_UNDEFINED: return pushUndefined(); - case PUSH_BOOL: return pushValue(flag); - case PUSH_NUMBER: return pushValue(stream.readDouble()); - case PUSH_STRING: return pushValue(stream.readUTF()); - case RETURN: return ret(); - case STORE_MEMBER: return storeMember(flag); - case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt()); - case STORE_VAR: return storeVar(stream.readInt(), flag); - case THROW: return throwInstr(); - case THROW_SYNTAX: return throwSyntax(stream.readUTF()); - case TRY_END: return tryEnd(); - case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt()); - case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof(); - case NOP: - if (flag) return null; - else return nop(); - default: return null; - } - } + // return loadFunc(stream.readInt(), stream.readUTF(), captures); + // } + // case LOAD_GLOB: return loadGlob(); + // case LOAD_MEMBER: return loadMember(); + // case LOAD_OBJ: return loadObj(); + // case LOAD_REGEX: return loadRegex(stream.readUTF(), null); + // case LOAD_VAR: return loadVar(stream.readInt()); + // case GLOB_DEF: return globDef(stream.readUTF()); + // case GLOB_GET: return globGet(stream.readUTF()); + // case GLOB_SET: return globSet(stream.readUTF(), flag); + // case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte())); + // case PUSH_NULL: return pushNull(); + // case PUSH_UNDEFINED: return pushUndefined(); + // case PUSH_BOOL: return pushValue(flag); + // case PUSH_NUMBER: return pushValue(stream.readDouble()); + // case PUSH_STRING: return pushValue(stream.readUTF()); + // case RETURN: return ret(); + // case STORE_MEMBER: return storeMember(flag); + // case STORE_VAR: return storeVar(stream.readInt(), flag); + // case THROW: return throwInstr(); + // case THROW_SYNTAX: return throwSyntax(stream.readUTF()); + // case TRY_END: return tryEnd(); + // case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt(), stream.readInt()); + // case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof(); + // case NOP: + // if (flag) return null; + // else return nop(); + // default: return null; + // } + // } public static Instruction tryStart(int catchStart, int finallyStart, int end) { return new Instruction(Type.TRY_START, catchStart, finallyStart, end); @@ -299,12 +311,26 @@ public class Instruction { return new Instruction(Type.PUSH_STRING, val); } - public static Instruction makeVar(String name) { - return new Instruction(Type.MAKE_VAR, name); + public static Instruction globDef(String name) { + return new Instruction(Type.GLOB_GET, name); } - public static Instruction loadVar(Object i) { + + public static Instruction globGet(String name) { + return new Instruction(Type.GLOB_GET, name); + } + public static Instruction globSet(String name, boolean keep, boolean define) { + return new Instruction(Type.GLOB_SET, name, keep, define); + } + + public static Instruction loadVar(int i) { return new Instruction(Type.LOAD_VAR, i); } + public static Instruction loadThis() { + return new Instruction(Type.LOAD_THIS); + } + public static Instruction loadArgs() { + return new Instruction(Type.LOAD_ARGS); + } public static Instruction loadGlob() { return new Instruction(Type.LOAD_GLOB); } @@ -337,13 +363,10 @@ public class Instruction { return new Instruction(Type.DUP, count); } - public static Instruction storeSelfFunc(int i) { - return new Instruction(Type.STORE_SELF_FUNC, i); - } - public static Instruction storeVar(Object i) { + public static Instruction storeVar(int i) { return new Instruction(Type.STORE_VAR, i, false); } - public static Instruction storeVar(Object i, boolean keep) { + public static Instruction storeVar(int i, boolean keep) { return new Instruction(Type.STORE_VAR, i, keep); } public static Instruction storeMember() { @@ -375,8 +398,14 @@ public class Instruction { return new Instruction(Type.OPERATION, op); } - @Override - public String toString() { + public static Instruction stackAlloc(int i) { + return new Instruction(Type.STACK_ALLOC, i); + } + public static Instruction stackFree(int i) { + return new Instruction(Type.STACK_FREE, i); + } + + @Override public String toString() { var res = type.toString(); for (int i = 0; i < params.length; i++) { diff --git a/src/java/me/topchetoeu/jscript/common/environment/Environment.java b/src/java/me/topchetoeu/jscript/common/environment/Environment.java index 7bb724e..6195fa2 100644 --- a/src/java/me/topchetoeu/jscript/common/environment/Environment.java +++ b/src/java/me/topchetoeu/jscript/common/environment/Environment.java @@ -160,13 +160,17 @@ public class Environment { return this; } - public Environment init(Key key, T val) { + public T init(Key key, T val) { if (!has(key)) this.add(key, val); - return this; + return val; } - public Environment init(Key key, Supplier val) { - if (!has(key)) this.add(key, val.get()); - return this; + public T initFrom(Key key, Supplier val) { + if (!has(key)) { + var res = val.get(); + this.add(key, res); + return res; + } + else return get(key); } public Environment child() { diff --git a/src/java/me/topchetoeu/jscript/common/json/JSONElement.java b/src/java/me/topchetoeu/jscript/common/json/JSONElement.java index 0b3af33..a8d8490 100644 --- a/src/java/me/topchetoeu/jscript/common/json/JSONElement.java +++ b/src/java/me/topchetoeu/jscript/common/json/JSONElement.java @@ -69,8 +69,7 @@ public class JSONElement { return (boolean)value; } - @Override - public String toString() { + @Override public String toString() { if (isMap()) return "{...}"; if (isList()) return "[...]"; if (isString()) return (String)value; diff --git a/src/java/me/topchetoeu/jscript/common/json/JSONMap.java b/src/java/me/topchetoeu/jscript/common/json/JSONMap.java index ac0cb49..2e2f1f9 100644 --- a/src/java/me/topchetoeu/jscript/common/json/JSONMap.java +++ b/src/java/me/topchetoeu/jscript/common/json/JSONMap.java @@ -116,32 +116,20 @@ public class JSONMap implements Map { public JSONMap set(String key, Map val) { elements.put(key, JSONElement.of(val)); return this; } public JSONMap set(String key, Collection val) { elements.put(key, JSONElement.of(val)); return this; } - @Override - public int size() { return elements.size(); } - @Override - public boolean isEmpty() { return elements.isEmpty(); } - @Override - public boolean containsKey(Object key) { return elements.containsKey(key); } - @Override - public boolean containsValue(Object value) { return elements.containsValue(value); } - @Override - public JSONElement get(Object key) { return elements.get(key); } - @Override - public JSONElement put(String key, JSONElement value) { return elements.put(key, value); } - @Override - public JSONElement remove(Object key) { return elements.remove(key); } - @Override - public void putAll(Map m) { elements.putAll(m); } + @Override public int size() { return elements.size(); } + @Override public boolean isEmpty() { return elements.isEmpty(); } + @Override public boolean containsKey(Object key) { return elements.containsKey(key); } + @Override public boolean containsValue(Object value) { return elements.containsValue(value); } + @Override public JSONElement get(Object key) { return elements.get(key); } + @Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); } + @Override public JSONElement remove(Object key) { return elements.remove(key); } + @Override public void putAll(Map m) { elements.putAll(m); } - @Override - public void clear() { elements.clear(); } + @Override public void clear() { elements.clear(); } - @Override - public Set keySet() { return elements.keySet(); } - @Override - public Collection values() { return elements.values(); } - @Override - public Set> entrySet() { return elements.entrySet(); } + @Override public Set keySet() { return elements.keySet(); } + @Override public Collection values() { return elements.values(); } + @Override public Set> entrySet() { return elements.entrySet(); } public JSONMap() { } public JSONMap(Map els) { diff --git a/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java b/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java index 5d54895..fea96ff 100644 --- a/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java +++ b/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java @@ -14,7 +14,7 @@ import java.util.stream.Collectors; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; +import me.topchetoeu.jscript.compilation.scope.Scope; public class FunctionMap { public static class FunctionMapBuilder { @@ -53,8 +53,8 @@ public class FunctionMap { public FunctionMap build(String[] localNames, String[] captureNames) { return new FunctionMap(sourceMap, breakpoints, localNames, captureNames); } - public FunctionMap build(LocalScopeRecord scope) { - return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures()); + public FunctionMap build(Scope scope) { + return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); } public FunctionMap build() { return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); diff --git a/src/java/me/topchetoeu/jscript/common/parsing/Location.java b/src/java/me/topchetoeu/jscript/common/parsing/Location.java index 1ef86e8..1d86866 100644 --- a/src/java/me/topchetoeu/jscript/common/parsing/Location.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/Location.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.Objects; public abstract class Location implements Comparable { - public static final Location INTERNAL = Location.of("jscript://native"); + public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1); public abstract int line(); public abstract int start(); @@ -39,10 +39,10 @@ public abstract class Location implements Comparable { }; } - @Override public final int hashCode() { + @Override public int hashCode() { return Objects.hash(line(), start(), filename()); } - @Override public final boolean equals(Object obj) { + @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Location)) return false; var other = (Location)obj; diff --git a/src/java/me/topchetoeu/jscript/common/parsing/ParseRes.java b/src/java/me/topchetoeu/jscript/common/parsing/ParseRes.java index c1af040..7c1f553 100644 --- a/src/java/me/topchetoeu/jscript/common/parsing/ParseRes.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/ParseRes.java @@ -12,28 +12,35 @@ public class ParseRes { } public final ParseRes.State state; + public final Location errorLocation; public final String error; public final T result; public final int n; - private ParseRes(ParseRes.State state, String error, T result, int readN) { + private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) { this.result = result; this.n = readN; this.state = state; this.error = error; + this.errorLocation = errorLocation; } public ParseRes setN(int i) { if (!state.isSuccess()) return this; - return new ParseRes<>(state, null, result, i); + return new ParseRes<>(state, null, null, result, i); } public ParseRes addN(int n) { if (!state.isSuccess()) return this; - return new ParseRes<>(state, null, result, this.n + n); + return new ParseRes<>(state, null, null, result, this.n + n); } public ParseRes chainError() { if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); - return new ParseRes<>(state, error, null, 0); + return new ParseRes<>(state, errorLocation, error, null, 0); + } + @SuppressWarnings("unchecked") + public ParseRes chainError(Location loc, String error) { + if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0); + return (ParseRes) this; } public boolean isSuccess() { return state.isSuccess(); } @@ -41,19 +48,13 @@ public class ParseRes { public boolean isError() { return state.isError(); } public static ParseRes failed() { - return new ParseRes(State.FAILED, null, null, 0); + return new ParseRes(State.FAILED, null, null, null, 0); } public static ParseRes error(Location loc, String error) { - if (loc != null) error = loc + ": " + error; - return new ParseRes<>(State.ERROR, error, null, 0); - } - public ParseRes chainError(Location loc, String error) { - if (loc != null) error = loc + ": " + error; - if (!this.isError()) return new ParseRes<>(State.ERROR, error, null, 0); - return new ParseRes<>(State.ERROR, this.error, null, 0); + return new ParseRes<>(State.ERROR, loc, error, null, 0); } public static ParseRes res(T val, int i) { - return new ParseRes<>(State.SUCCESS, null, val, i); + return new ParseRes<>(State.SUCCESS, null, null, val, i); } @SafeVarargs diff --git a/src/java/me/topchetoeu/jscript/common/parsing/Parsing.java b/src/java/me/topchetoeu/jscript/common/parsing/Parsing.java index 07047d4..925c3d3 100644 --- a/src/java/me/topchetoeu/jscript/common/parsing/Parsing.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/Parsing.java @@ -19,9 +19,48 @@ public class Parsing { } public static int skipEmpty(Source src, int i) { + return skipEmpty(src, i, true); + } + + public static int skipEmpty(Source src, int i, boolean noComments) { int n = 0; - while (n < src.size() && src.is(i + n, Character::isWhitespace)) n++; + if (i == 0 && src.is(0, "#!")) { + while (!src.is(n, '\n')) n++; + n++; + } + + var isSingle = false; + var isMulti = false; + + while (i + n < src.size()) { + if (isSingle) { + if (src.is(i + n, '\n')) { + n++; + isSingle = false; + } + else n++; + } + else if (isMulti) { + if (src.is(i + n, "*/")) { + n += 2; + isMulti = false; + } + else n++; + } + else if (src.is(i + n, "//")) { + n += 2; + isSingle = true; + } + else if (src.is(i + n, "/*")) { + n += 2; + isMulti = true; + } + else if (src.is(i + n, Character::isWhitespace)) { + n++; + } + else break; + } return n; } diff --git a/src/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java b/src/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java index a2050d4..5819565 100644 --- a/src/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java @@ -1,5 +1,7 @@ package me.topchetoeu.jscript.common.parsing; +import java.util.Objects; + public class SourceLocation extends Location { private int[] lineStarts; private int line; @@ -10,16 +12,16 @@ public class SourceLocation extends Location { private void update() { if (lineStarts == null) return; - int start = 0; - int end = lineStarts.length - 1; + int a = 0; + int b = lineStarts.length; while (true) { - if (start + 1 >= end) break; - var mid = -((-start - end) >> 1); + if (a + 1 >= b) break; + var mid = -((-a - b) >> 1); var el = lineStarts[mid]; - if (el < offset) start = mid; - else if (el > offset) end = mid; + if (el < offset) a = mid; + else if (el > offset) b = mid; else { this.line = mid; this.start = 0; @@ -28,8 +30,8 @@ public class SourceLocation extends Location { } } - this.line = start; - this.start = offset - lineStarts[start]; + this.line = a; + this.start = offset - lineStarts[a]; this.lineStarts = null; return; } @@ -44,6 +46,18 @@ public class SourceLocation extends Location { return start; } + @Override public int hashCode() { + return Objects.hash(offset); + } + @Override public int compareTo(Location other) { + if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset); + else return super.compareTo(other); + } + @Override public boolean equals(Object obj) { + if (obj instanceof SourceLocation other) return this.offset == other.offset; + else return super.equals(obj); + } + public SourceLocation(Filename filename, int[] lineStarts, int offset) { this.filename = filename; this.lineStarts = lineStarts; diff --git a/src/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/java/me/topchetoeu/jscript/compilation/CompileResult.java index f65e663..59ce8fd 100644 --- a/src/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -1,23 +1,27 @@ package me.topchetoeu.jscript.compilation; import java.util.List; +import java.util.ArrayList; import java.util.LinkedList; -import java.util.Vector; +import java.util.function.Supplier; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.mapping.FunctionMap; import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder; import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; +import me.topchetoeu.jscript.compilation.scope.LocalScope; +import me.topchetoeu.jscript.compilation.scope.Scope; -public class CompileResult { - public final Vector instructions = new Vector<>(); - public final List children = new LinkedList<>(); - public final FunctionMapBuilder map = FunctionMap.builder(); - public final LocalScopeRecord scope; +public final class CompileResult { + public final List> instructions; + public final List children; + public final FunctionMapBuilder map; + public final Environment env; public int length = 0; + public final Scope scope; public int temp() { instructions.add(null); @@ -25,16 +29,24 @@ public class CompileResult { } public CompileResult add(Instruction instr) { + instructions.add(() -> instr); + return this; + } + public CompileResult add(Supplier instr) { instructions.add(instr); return this; } public CompileResult set(int i, Instruction instr) { + instructions.set(i, () -> instr); + return this; + } + public CompileResult set(int i, Supplierinstr) { instructions.set(i, instr); return this; } - public Instruction get(int i) { - return instructions.get(i); - } + // public Instruction get(int i) { + // return instructions.get(i); + // } public int size() { return instructions.size(); } public void setDebug(Location loc, BreakpointType type) { @@ -61,6 +73,13 @@ public class CompileResult { return child; } + public Instruction[] instructions() { + var res = new Instruction[instructions.size()]; + var i = 0; + for (var suppl : instructions) res[i++] = suppl.get(); + return res; + } + public FunctionMap map() { return map.build(scope); } @@ -69,10 +88,32 @@ public class CompileResult { for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body(); - return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren); + var instrRes = new Instruction[instructions.size()]; + var i = 0; + for (var suppl : instructions) instrRes[i++] = suppl.get(); + + return new FunctionBody( + scope.localsCount() + scope.allocCount(), scope.capturesCount(), length, + instrRes, builtChildren + ); } - public CompileResult(LocalScopeRecord scope) { + public CompileResult subtarget() { + return new CompileResult(new LocalScope(scope), this); + } + + public CompileResult(Environment env, Scope scope) { this.scope = scope; + instructions = new ArrayList<>(); + children = new LinkedList<>(); + map = FunctionMap.builder(); + this.env = env; + } + private CompileResult(Scope scope, CompileResult parent) { + this.scope = scope; + this.instructions = parent.instructions; + this.children = parent.children; + this.map = parent.map; + this.env = parent.env; } } diff --git a/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java index 305e6fd..4db2815 100644 --- a/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java @@ -2,7 +2,6 @@ package me.topchetoeu.jscript.compilation; import java.util.ArrayList; import java.util.List; -import java.util.Vector; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; @@ -10,36 +9,25 @@ import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.values.FunctionNode; + public class CompoundNode extends Node { public final Node[] statements; - public final boolean separateFuncs; public Location end; - @Override public boolean pure() { - for (var stm : statements) { - if (!stm.pure()) return false; - } - - return true; + @Override public void resolve(CompileResult target) { + for (var stm : statements) stm.resolve(target); } - @Override - public void declare(CompileResult target) { - for (var stm : statements) stm.declare(target); - } + @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { + List statements = new ArrayList(); - @Override - public void compile(CompileResult target, boolean pollute, BreakpointType type) { - List statements = new Vector(); - if (separateFuncs) for (var stm : this.statements) { - if (stm instanceof FunctionNode && ((FunctionNode)stm).statement) { + for (var stm : this.statements) { + if (stm instanceof FunctionStatementNode) { stm.compile(target, false); } else statements.add(stm); } - else statements = List.of(this.statements); var polluted = false; @@ -60,9 +48,8 @@ public class CompoundNode extends Node { return this; } - public CompoundNode(Location loc, boolean separateFuncs, Node ...statements) { + public CompoundNode(Location loc, Node ...statements) { super(loc); - this.separateFuncs = separateFuncs; this.statements = statements; } @@ -75,11 +62,18 @@ public class CompoundNode extends Node { if (!src.is(i + n, ",")) return ParseRes.failed(); n++; - var res = JavaScript.parseExpression(src, i + n, 2); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the comma"); - n += res.n; + var curr = JavaScript.parseExpression(src, i + n, 2); + if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma"); + n += curr.n; - return ParseRes.res(new CompoundNode(loc, false, prev, res.result), n); + if (prev instanceof CompoundNode) { + var children = new ArrayList(); + children.addAll(List.of(((CompoundNode)prev).statements)); + children.add(curr.result); + + return ParseRes.res(new CompoundNode(loc, children.toArray(Node[]::new)), n); + } + else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n); } public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); @@ -109,6 +103,6 @@ public class CompoundNode extends Node { statements.add(res.result); } - return ParseRes.res(new CompoundNode(loc, true, statements.toArray(Node[]::new)).setEnd(src.loc(i + n - 1)), n); + return ParseRes.res(new CompoundNode(loc, statements.toArray(Node[]::new)).setEnd(src.loc(i + n - 1)), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java b/src/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java new file mode 100644 index 0000000..e0cb484 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.function.IntSupplier; + +public final class DeferredIntSupplier implements IntSupplier { + private int value; + private boolean set; + + public void set(int val) { + if (set) throw new RuntimeException("A deferred int supplier may be set only once"); + value = val; + set = true; + } + + @Override public int getAsInt() { + if (!set) throw new RuntimeException("Deferred int supplier accessed too early"); + return value; + } +} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/ExpressionNode.java b/src/java/me/topchetoeu/jscript/compilation/ExpressionNode.java deleted file mode 100644 index b8b8e0b..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/ExpressionNode.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -public class ExpressionNode { - -} diff --git a/src/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/java/me/topchetoeu/jscript/compilation/FunctionNode.java new file mode 100644 index 0000000..2b05291 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -0,0 +1,130 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.scope.FunctionScope; +import me.topchetoeu.jscript.compilation.scope.LocalScope; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public abstract class FunctionNode extends Node { + public final CompoundNode body; + public final String[] args; + public final Location end; + + public abstract String name(); + + // @Override public void declare(CompileResult target) { + // if (varName != null && statement) target.scope.define(varName); + // } + + // public static void checkBreakAndCont(CompileResult target, int start) { + // for (int i = start; i < target.size(); i++) { + // if (target.get(i).type == Type.NOP) { + // if (target.get(i).is(0, "break") ) { + // throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop."); + // } + // if (target.get(i).is(0, "cont")) { + // throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop."); + // } + // } + // } + // } + + protected void compileLoadFunc(CompileResult target, int[] captures, String name) { + target.add(Instruction.loadFunc(target.children.size(), name, captures)); + } + + private CompileResult compileBody(CompileResult target, String name, boolean storeSelf, boolean pollute, BreakpointType bp) { + for (var i = 0; i < args.length; i++) { + for (var j = 0; j < i; j++) { + if (args[i].equals(args[j])) { + throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."); + } + } + } + + var funcScope = new FunctionScope(storeSelf ? name : null, args, target.scope); + var subtarget = new CompileResult(target.env, new LocalScope(funcScope)); + + // compileStoreSelf(subtarget, pollute, bp); + + body.resolve(subtarget); + body.compile(subtarget, false); + + subtarget.length = args.length; + subtarget.scope.end(); + funcScope.end(); + subtarget.add(Instruction.ret()).setLocation(end); + + if (pollute) compileLoadFunc(target, funcScope.getCaptureIndices(), name); + + return target.addChild(subtarget); + } + + public void compile(CompileResult target, boolean pollute, boolean storeSelf, String name, BreakpointType bp) { + if (this.name() != null) name = this.name(); + + compileBody(target, name, storeSelf, pollute, bp); + } + public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp); + public void compile(CompileResult target, boolean pollute, String name) { + compile(target, pollute, name, BreakpointType.NONE); + } + @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { + compile(target, pollute, (String)null, bp); + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, (String)null, BreakpointType.NONE); + } + + public FunctionNode(Location loc, Location end, String[] args, CompoundNode body) { + super(loc); + + this.end = end; + this.args = args; + this.body = body; + } + + public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { + if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name); + else stm.compile(target, pollute); + } + public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) { + if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp); + else stm.compile(target, pollute, bp); + } + + public static ParseRes parseFunction(Source src, int i, boolean statement) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed(); + n += 8; + + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + var args = JavaScript.parseParamList(src, i + n); + if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list"); + n += args.n; + + var body = CompoundNode.parse(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function."); + n += body.n; + + if (statement) return ParseRes.res(new FunctionStatementNode( + loc, src.loc(i + n - 1), + args.result.toArray(String[]::new), body.result, name.result + ), n); + else return ParseRes.res(new FunctionValueNode( + loc, src.loc(i + n - 1), + args.result.toArray(String[]::new), body.result, name.result + ), n); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java b/src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java new file mode 100644 index 0000000..1b404f3 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java @@ -0,0 +1,25 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class FunctionStatementNode extends FunctionNode { + public final String name; + + @Override public String name() { return name; } + + @Override public void resolve(CompileResult target) { + target.scope.define(name, false, end); + } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + compile(target, true, false, this.name, bp); + target.add(VariableNode.toSet(target, end, this.name, pollute, true)); + } + + public FunctionStatementNode(Location loc, Location end, String[] args, CompoundNode body, String name) { + super(loc, end, args, body); + this.name = name; + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java b/src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java new file mode 100644 index 0000000..c89d48d --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; + +public class FunctionValueNode extends FunctionNode { + public final String name; + + @Override public String name() { return name; } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + compile(target, pollute, true, name, bp); + } + + public FunctionValueNode(Location loc, Location end, String[] args, CompoundNode body, String name) { + super(loc, end, args, body); + this.name = name; + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/java/me/topchetoeu/jscript/compilation/JavaScript.java index 2434885..75aaa83 100644 --- a/src/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -16,7 +16,6 @@ import me.topchetoeu.jscript.compilation.control.DebugNode; import me.topchetoeu.jscript.compilation.control.DeleteNode; import me.topchetoeu.jscript.compilation.control.DoWhileNode; import me.topchetoeu.jscript.compilation.control.ForInNode; -import me.topchetoeu.jscript.compilation.control.ForOfNode; import me.topchetoeu.jscript.compilation.control.ForNode; import me.topchetoeu.jscript.compilation.control.IfNode; import me.topchetoeu.jscript.compilation.control.ReturnNode; @@ -24,12 +23,14 @@ import me.topchetoeu.jscript.compilation.control.SwitchNode; import me.topchetoeu.jscript.compilation.control.ThrowNode; import me.topchetoeu.jscript.compilation.control.TryNode; import me.topchetoeu.jscript.compilation.control.WhileNode; -import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; +import me.topchetoeu.jscript.compilation.scope.GlobalScope; +import me.topchetoeu.jscript.compilation.scope.LocalScope; +import me.topchetoeu.jscript.compilation.values.ArgumentsNode; import me.topchetoeu.jscript.compilation.values.ArrayNode; -import me.topchetoeu.jscript.compilation.values.FunctionNode; import me.topchetoeu.jscript.compilation.values.GlobalThisNode; import me.topchetoeu.jscript.compilation.values.ObjectNode; import me.topchetoeu.jscript.compilation.values.RegexNode; +import me.topchetoeu.jscript.compilation.values.ThisNode; import me.topchetoeu.jscript.compilation.values.VariableNode; import me.topchetoeu.jscript.compilation.values.constants.BoolNode; import me.topchetoeu.jscript.compilation.values.constants.NullNode; @@ -41,7 +42,6 @@ import me.topchetoeu.jscript.compilation.values.operations.DiscardNode; import me.topchetoeu.jscript.compilation.values.operations.IndexNode; import me.topchetoeu.jscript.compilation.values.operations.OperationNode; import me.topchetoeu.jscript.compilation.values.operations.TypeofNode; -import me.topchetoeu.jscript.compilation.values.operations.VariableIndexNode; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class JavaScript { @@ -104,8 +104,8 @@ public class JavaScript { if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n); if (id.result.equals("undefined")) return ParseRes.res(new DiscardNode(loc, null), n); if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n); - if (id.result.equals("this")) return ParseRes.res(new VariableIndexNode(loc, 0), n); - if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexNode(loc, 1), n); + if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n); + if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n); if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n); return ParseRes.failed(); @@ -187,7 +187,7 @@ public class JavaScript { SwitchNode::parse, ForNode::parse, ForInNode::parse, - ForOfNode::parse, + // ForOfNode::parse, DoWhileNode::parse, TryNode::parse, CompoundNode::parse, @@ -257,7 +257,7 @@ public class JavaScript { var res = parseStatement(src, i); - if (res.isError()) throw new SyntaxException(src.loc(i), res.error); + if (res.isError()) throw new SyntaxException(res.errorLocation, res.error); else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax"); i += res.n; @@ -272,22 +272,17 @@ public class JavaScript { return !JavaScript.reserved.contains(name); } - public static CompileResult compile(Node ...statements) { - var target = new CompileResult(new LocalScopeRecord()); - var stm = new CompoundNode(null, true, statements); - - target.scope.define("this"); - target.scope.define("arguments"); + public static CompileResult compile(Environment env, Node ...statements) { + var target = new CompileResult(env, new LocalScope(new GlobalScope())); + var stm = new CompoundNode(null, statements); try { + stm.resolve(target); stm.compile(target, true); - FunctionNode.checkBreakAndCont(target, 0); + // FunctionNode.checkBreakAndCont(target, 0); } catch (SyntaxException e) { - target = new CompileResult(new LocalScopeRecord()); - - target.scope.define("this"); - target.scope.define("arguments"); + target = new CompileResult(env, new LocalScope(new GlobalScope())); target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); } @@ -298,6 +293,24 @@ public class JavaScript { } public static CompileResult compile(Environment env, Filename filename, String raw) { - return JavaScript.compile(JavaScript.parse(env, filename, raw)); + return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); + } + public static CompileResult compile(Filename filename, String raw) { + var env = new Environment(); + return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); + } + + public static ParseRes parseLabel(Source src, int i) { + int n = Parsing.skipEmpty(src, i); + + var nameRes = Parsing.parseIdentifier(src, i + n); + if (!nameRes.isSuccess()) return nameRes.chainError(); + n += nameRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.failed(); + n++; + + return ParseRes.res(nameRes.result, n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/LabelContext.java b/src/java/me/topchetoeu/jscript/compilation/LabelContext.java new file mode 100644 index 0000000..5cfb1a3 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/LabelContext.java @@ -0,0 +1,85 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.function.IntSupplier; +import java.util.function.Supplier; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class LabelContext { + public static final Key BREAK_CTX = Key.of(); + public static final Key CONTINUE_CTX = Key.of(); + + private final LinkedList list = new LinkedList<>(); + private final HashMap map = new HashMap<>(); + + public IntSupplier get() { + return list.peekLast(); + } + public IntSupplier get(String name) { + return map.get(name); + } + + public Supplier getJump(int offset) { + var res = get(); + if (res == null) return null; + else return () -> Instruction.jmp(res.getAsInt() - offset); + } + public Supplier getJump(int offset, String name) { + var res = get(name); + if (res == null) return null; + else return () -> Instruction.jmp(res.getAsInt() - offset); + } + + public void push(IntSupplier jumpTarget) { + list.add(jumpTarget); + } + public void push(Location loc, String name, IntSupplier jumpTarget) { + if (name == null) return; + if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name)); + map.put(name, jumpTarget); + } + + public void pushLoop(Location loc, String name, IntSupplier jumpTarget) { + push(jumpTarget); + push(loc, name, jumpTarget); + } + + public void pop() { + list.removeLast(); + } + public void pop(String name) { + if (name == null) return; + map.remove(name); + } + + public void popLoop(String name) { + pop(); + pop(name); + } + + public static LabelContext getBreak(Environment env) { + return env.initFrom(BREAK_CTX, () -> new LabelContext()); + } + public static LabelContext getCont(Environment env) { + return env.initFrom(CONTINUE_CTX, () -> new LabelContext()); + } + + public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) { + LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); + LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget); + } + public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) { + LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); + LabelContext.getCont(env).pushLoop(loc, name, contTarget); + } + public static void popLoop(Environment env, String name) { + LabelContext.getBreak(env).popLoop(name); + LabelContext.getCont(env).popLoop(name); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/Node.java b/src/java/me/topchetoeu/jscript/compilation/Node.java index cf2c2fb..7da1c3e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/Node.java +++ b/src/java/me/topchetoeu/jscript/compilation/Node.java @@ -4,10 +4,9 @@ import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Location; public abstract class Node { - private Location _loc; + private Location loc; - public boolean pure() { return false; } - public void declare(CompileResult target) { } + public void resolve(CompileResult target) {} public void compile(CompileResult target, boolean pollute, BreakpointType type) { int start = target.size(); @@ -18,10 +17,10 @@ public abstract class Node { compile(target, pollute, BreakpointType.NONE); } - public Location loc() { return _loc; } - public void setLoc(Location loc) { _loc = loc; } + public Location loc() { return loc; } + public void setLoc(Location loc) { this.loc = loc; } protected Node(Location loc) { - this._loc = loc; + this.loc = loc; } } \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/NodeChildren.java b/src/java/me/topchetoeu/jscript/compilation/NodeChildren.java new file mode 100644 index 0000000..920c6d0 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/NodeChildren.java @@ -0,0 +1,93 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.function.Function; + +public final class NodeChildren implements Iterable { + public static final class Slot { + private Node node; + private final Function replacer; + + public final void replace(Node node) { + this.node = this.replacer.apply(node); + } + + public Slot(Node nodes, Function replacer) { + this.node = nodes; + this.replacer = replacer; + } + } + + private final Slot[] slots; + + private NodeChildren(Slot[] slots) { + this.slots = slots; + } + + @Override public Iterator iterator() { + return new Iterator() { + private int i = 0; + private Slot[] arr = slots; + + @Override public boolean hasNext() { + if (arr == null) return false; + else if (i >= arr.length) { + arr = null; + return false; + } + else return true; + } + @Override public Node next() { + if (!hasNext()) return null; + return arr[i++].node; + } + }; + } + public Iterable slots() { + return () -> new Iterator() { + private int i = 0; + private Slot[] arr = slots; + + @Override public boolean hasNext() { + if (arr == null) return false; + else if (i >= arr.length) { + arr = null; + return false; + } + else return true; + } + @Override public Slot next() { + if (!hasNext()) return null; + return arr[i++]; + } + }; + } + + public static final class Builder { + private final ArrayList slots = new ArrayList<>(); + + public final Builder add(Slot ...children) { + for (var child : children) { + this.slots.add(child); + } + + return this; + } + public final Builder add(Iterable children) { + for (var child : children) { + this.slots.add(child); + } + + return this; + } + public final Builder add(Node child, Function replacer) { + slots.add(new Slot(child, replacer)); + return this; + } + + public final NodeChildren build() { + return new NodeChildren(slots.toArray(Slot[]::new)); + } + } +} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/Path.java b/src/java/me/topchetoeu/jscript/compilation/Path.java new file mode 100644 index 0000000..9d5f897 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/Path.java @@ -0,0 +1,29 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.function.Predicate; + +public class Path { + public final Path parent; + public final Node node; + + public Path getParent(Predicate> predicate) { + for (Path it = this; it != null; it = it.parent) { + if (predicate.test(it)) return it; + } + + return null; + } + + public Path getParent(Class type, Predicate> predicate) { + for (Path it = this; it != null; it = it.parent) { + if (type.isInstance(it.node) && predicate.test(it)) return it; + } + + return null; + } + + public Path(Path parent, Node node) { + this.parent = parent; + this.node = node; + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/ThrowSyntaxNode.java b/src/java/me/topchetoeu/jscript/compilation/ThrowSyntaxNode.java deleted file mode 100644 index b2d29b5..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/ThrowSyntaxNode.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class ThrowSyntaxNode extends Node { - public final String name; - - @Override - public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.throwSyntax(name)); - } - - public ThrowSyntaxNode(SyntaxException e) { - super(e.loc); - this.name = e.msg; - } -} diff --git a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java index 4d0ba04..84c7014 100644 --- a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java @@ -9,7 +9,7 @@ import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.values.FunctionNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; public class VariableDeclareNode extends Node { public static class Pair { @@ -26,23 +26,26 @@ public class VariableDeclareNode extends Node { public final List values; - @Override - public void declare(CompileResult target) { - for (var key : values) { - target.scope.define(key.name); + @Override public void resolve(CompileResult target) { + for (var entry : values) { + target.scope.define(entry.name, false, entry.location); } } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { for (var entry : values) { if (entry.name == null) continue; - var key = target.scope.getKey(entry.name); - - if (key instanceof String) target.add(Instruction.makeVar((String)key)); if (entry.value != null) { FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); - target.add(Instruction.storeVar(key)); + target.add(VariableNode.toSet(target, entry.location, entry.name, false, true)); + } + else { + target.add(() -> { + var i = target.scope.get(entry.name, true); + + if (i == null) return Instruction.globDef(entry.name); + else return Instruction.nop(); + }); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/BreakNode.java b/src/java/me/topchetoeu/jscript/compilation/control/BreakNode.java index f3f95fd..e052b30 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/BreakNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/BreakNode.java @@ -7,13 +7,22 @@ import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class BreakNode extends Node { public final String label; @Override public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.nop("break", label)); + var res = LabelContext.getBreak(target.env).getJump(target.size()); + if (res == null) { + if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); + else throw new SyntaxException(loc(), "Illegal break statement"); + } + target.add(res); + + // target.add(Instruction.nop("break", label)); if (pollute) target.add(Instruction.pushUndefined()); } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java index b3b5761..be09e72 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java @@ -7,13 +7,22 @@ import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class ContinueNode extends Node { public final String label; @Override public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.nop("cont", label)); + var res = LabelContext.getCont(target.env).getJump(target.size()); + if (res == null) { + if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); + else throw new SyntaxException(loc(), "Illegal continue statement"); + } + target.add(res); + + // () -> Instruction.nop("cont", label)); if (pollute) target.add(Instruction.pushUndefined()); } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java b/src/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java index 57071e6..6df811d 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java @@ -16,8 +16,7 @@ public class DeleteNode extends Node { public final Node key; public final Node value; - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { value.compile(target, true); key.compile(target, true); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java index 616cd6a..c07a004 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java @@ -7,28 +7,35 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; public class DoWhileNode extends Node { public final Node condition, body; public final String label; - @Override - public void declare(CompileResult target) { - body.declare(target); + @Override public void resolve(CompileResult target) { + body.resolve(target); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { int start = target.size(); - body.compile(target, false, BreakpointType.STEP_OVER); - int mid = target.size(); - condition.compile(target, true, BreakpointType.STEP_OVER); - int end = target.size(); + var end = new DeferredIntSupplier(); + var mid = new DeferredIntSupplier(); - WhileNode.replaceBreaks(target, label, start, mid - 1, mid, end + 1); - target.add(Instruction.jmpIf(start - end)); + LabelContext.pushLoop(target.env, loc(), label, end, start); + body.compile(target, false, BreakpointType.STEP_OVER); + LabelContext.popLoop(target.env, label); + + mid.set(target.size()); + condition.compile(target, true, BreakpointType.STEP_OVER); + int endI = target.size(); + end.set(endI + 1); + + // WhileNode.replaceBreaks(target, label, start, mid - 1, mid, end + 1); + target.add(Instruction.jmpIf(start - endI)); } public DoWhileNode(Location loc, String label, Node condition, Node body) { @@ -42,7 +49,7 @@ public class DoWhileNode extends Node { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); - var labelRes = WhileNode.parseLabel(src, i + n); + var labelRes = JavaScript.parseLabel(src, i + n); n += labelRes.n; if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed(); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java index ae89d7c..1ae1dfa 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java @@ -8,8 +8,11 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.VariableNode; public class ForInNode extends Node { public final String varName; @@ -18,16 +21,12 @@ public class ForInNode extends Node { public final String label; public final Location varLocation; - @Override public void declare(CompileResult target) { - body.declare(target); - if (isDeclaration) target.scope.define(varName); + @Override public void resolve(CompileResult target) { + body.resolve(target); + if (isDeclaration) target.scope.define(varName, false, loc()); } @Override public void compile(CompileResult target, boolean pollute) { - var key = target.scope.getKey(varName); - - if (key instanceof String) target.add(Instruction.makeVar((String)key)); - object.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.keys(true)); @@ -39,17 +38,28 @@ public class ForInNode extends Node { target.add(Instruction.pushValue("value")).setLocation(varLocation); target.add(Instruction.loadMember()).setLocation(varLocation); - target.add(Instruction.storeVar(key)).setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); + target.add(VariableNode.toSet(target, loc(), varName, pollute, isDeclaration)); + target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); + + var end = new DeferredIntSupplier(); + + LabelContext.pushLoop(target.env, loc(), label, end, start); + var subtarget = target.subtarget(); + subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount())); body.compile(target, false, BreakpointType.STEP_OVER); - int end = target.size(); + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); + LabelContext.popLoop(target.env, label); - WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1); + int endI = target.size(); - target.add(Instruction.jmp(start - end)); + // WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1); + + target.add(Instruction.jmp(start - endI)); target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(end - mid + 1)); + target.set(mid, Instruction.jmpIf(endI - mid + 1)); if (pollute) target.add(Instruction.pushUndefined()); } @@ -67,7 +77,7 @@ public class ForInNode extends Node { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); - var label = WhileNode.parseLabel(src, i + n); + var label = JavaScript.parseLabel(src, i + n); n += label.n; n += Parsing.skipEmpty(src, i + n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java index 44e28fc..bf361f4 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java @@ -7,7 +7,9 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.VariableDeclareNode; import me.topchetoeu.jscript.compilation.values.operations.DiscardNode; @@ -16,27 +18,39 @@ public class ForNode extends Node { public final Node declaration, assignment, condition, body; public final String label; - @Override - public void declare(CompileResult target) { - declaration.declare(target); - body.declare(target); + @Override public void resolve(CompileResult target) { + declaration.resolve(target); + body.resolve(target); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { declaration.compile(target, false, BreakpointType.STEP_OVER); int start = target.size(); condition.compile(target, true, BreakpointType.STEP_OVER); int mid = target.temp(); - body.compile(target, false, BreakpointType.STEP_OVER); - int beforeAssign = target.size(); + + var end = new DeferredIntSupplier(); + LabelContext.pushLoop(target.env, loc(), label, end, start); + + var subtarget = target.subtarget(); + subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount())); + + body.compile(subtarget, false, BreakpointType.STEP_OVER); + + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); + + LabelContext.popLoop(target.env, label); + + // int beforeAssign = target.size(); assignment.compile(target, false, BreakpointType.STEP_OVER); - int end = target.size(); + int endI = target.size(); + end.set(endI); - WhileNode.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); + // WhileNode.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); - target.add(Instruction.jmp(start - end)); - target.set(mid, Instruction.jmpIfNot(end - mid + 1)); + target.add(Instruction.jmp(start - endI)); + target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); if (pollute) target.add(Instruction.pushUndefined()); } @@ -74,7 +88,7 @@ public class ForNode extends Node { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); - var labelRes = WhileNode.parseLabel(src, i + n); + var labelRes = JavaScript.parseLabel(src, i + n); n += labelRes.n; n += Parsing.skipEmpty(src, i + n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java index ce214ce..d963c5e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java @@ -17,17 +17,15 @@ public class ForOfNode extends Node { public final String label; public final Location varLocation; - @Override - public void declare(CompileResult target) { - body.declare(target); - if (isDeclaration) target.scope.define(varName); + @Override public void resolve(CompileResult target) { + body.resolve(target); + if (isDeclaration) target.scope.resolve(varName); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { var key = target.scope.getKey(varName); - if (key instanceof String) target.add(Instruction.makeVar((String)key)); + if (key instanceof String) target.add(Instruction.globDef((String)key)); iterable.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.dup()); @@ -79,7 +77,7 @@ public class ForOfNode extends Node { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); - var label = WhileNode.parseLabel(src, i + n); + var label = JavaScript.parseLabel(src, i + n); n += label.n; n += Parsing.skipEmpty(src, i + n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/IfNode.java b/src/java/me/topchetoeu/jscript/compilation/control/IfNode.java index f22c2dd..e4045a4 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/IfNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/IfNode.java @@ -7,47 +7,87 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; public class IfNode extends Node { public final Node condition, body, elseBody; + public final String label; - @Override - public void declare(CompileResult target) { - body.declare(target); - if (elseBody != null) elseBody.declare(target); + @Override public void resolve(CompileResult target) { + body.resolve(target); + if (elseBody != null) elseBody.resolve(target); } @Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) { condition.compile(target, true, breakpoint); if (elseBody == null) { - int i = target.temp(); - body.compile(target, pollute, breakpoint); + int start = target.temp(); + var end = new DeferredIntSupplier(); + + LabelContext.getBreak(target.env).push(loc(), label, end); + + var subtarget = target.subtarget(); + subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount())); + + body.compile(subtarget, false, BreakpointType.STEP_OVER); + + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); + + LabelContext.getBreak(target.env).pop(label); + int endI = target.size(); - target.set(i, Instruction.jmpIfNot(endI - i)); + end.set(endI); + + target.set(start, Instruction.jmpIfNot(endI - start)); } else { int start = target.temp(); - body.compile(target, pollute, breakpoint); + var end = new DeferredIntSupplier(); + + LabelContext.getBreak(target.env).push(loc(), label, end); + + var bodyTarget = target.subtarget(); + bodyTarget.add(() -> Instruction.stackAlloc(bodyTarget.scope.allocCount())); + + body.compile(bodyTarget, false, BreakpointType.STEP_OVER); + + bodyTarget.scope.end(); + bodyTarget.add(Instruction.stackFree(bodyTarget.scope.allocCount())); + int mid = target.temp(); - elseBody.compile(target, pollute, breakpoint); - int end = target.size(); + + var elseTarget = target.subtarget(); + elseTarget.add(() -> Instruction.stackAlloc(elseTarget.scope.allocCount())); + + body.compile(elseTarget, false, BreakpointType.STEP_OVER); + + elseTarget.scope.end(); + elseTarget.add(Instruction.stackFree(elseTarget.scope.allocCount())); + + LabelContext.getBreak(target.env).pop(label); + + int endI = target.size(); + end.set(endI); target.set(start, Instruction.jmpIfNot(mid - start + 1)); - target.set(mid, Instruction.jmp(end - mid)); + target.set(mid, Instruction.jmp(endI - mid)); } } @Override public void compile(CompileResult target, boolean pollute) { compile(target, pollute, BreakpointType.STEP_IN); } - public IfNode(Location loc, Node condition, Node body, Node elseBody) { + public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) { super(loc); this.condition = condition; this.body = body; this.elseBody = elseBody; + this.label = label; } public static ParseRes parseTernary(Source src, int i, Node prev, int precedence) { @@ -71,12 +111,16 @@ public class IfNode extends Node { if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator."); n += b.n; - return ParseRes.res(new IfNode(loc, prev, a.result, b.result), n); + return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n); } public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); + var label = JavaScript.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed(); n += 2; n += Parsing.skipEmpty(src, i + n); @@ -97,14 +141,13 @@ public class IfNode extends Node { n += res.n; var elseKw = Parsing.parseIdentifier(src, i + n, "else"); - if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null), n); + if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n); n += elseKw.n; var elseRes = JavaScript.parseStatement(src, i + n); if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body."); n += elseRes.n; - return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result), n); + return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n); } - } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java index 19582ae..3caccea 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java @@ -12,8 +12,7 @@ import me.topchetoeu.jscript.compilation.Node; public class ReturnNode extends Node { public final Node value; - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { if (value == null) target.add(Instruction.pushUndefined()); else value.compile(target, true); target.add(Instruction.ret()).setLocation(loc()); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java b/src/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java index 0d50aab..311a1a9 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java @@ -6,13 +6,14 @@ import java.util.HashMap; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.Instruction.Type; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; public class SwitchNode extends Node { @@ -30,9 +31,10 @@ public class SwitchNode extends Node { public final SwitchCase[] cases; public final Node[] body; public final int defaultI; + public final String label; - @Override public void declare(CompileResult target) { - for (var stm : body) stm.declare(target); + @Override public void resolve(CompileResult target) { + for (var stm : body) stm.resolve(target); } @Override public void compile(CompileResult target, boolean pollute) { @@ -41,43 +43,55 @@ public class SwitchNode extends Node { value.compile(target, true, BreakpointType.STEP_OVER); + var subtarget = target.subtarget(); + subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount())); + + // TODO: create a jump map for (var ccase : cases) { - target.add(Instruction.dup()); - ccase.value.compile(target, true); - target.add(Instruction.operation(Operation.EQUALS)); - caseToStatement.put(target.temp(), ccase.statementI); + subtarget.add(Instruction.dup()); + ccase.value.compile(subtarget, true); + subtarget.add(Instruction.operation(Operation.EQUALS)); + caseToStatement.put(subtarget.temp(), ccase.statementI); } - int start = target.temp(); + int start = subtarget.temp(); + var end = new DeferredIntSupplier(); + LabelContext.getBreak(target.env).push(loc(), label, end); for (var stm : body) { - statementToIndex.put(statementToIndex.size(), target.size()); - stm.compile(target, false, BreakpointType.STEP_OVER); + statementToIndex.put(statementToIndex.size(), subtarget.size()); + stm.compile(subtarget, false, BreakpointType.STEP_OVER); } + LabelContext.getBreak(target.env).pop(label); - int end = target.size(); - target.add(Instruction.discard()); - if (pollute) target.add(Instruction.pushUndefined()); + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); - if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start)); - else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); + int endI = subtarget.size(); + end.set(endI); + subtarget.add(Instruction.discard()); + if (pollute) subtarget.add(Instruction.pushUndefined()); - for (int i = start; i < end; i++) { - var instr = target.get(i); - if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { - target.set(i, Instruction.jmp(end - i)); - } - } + if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start)); + else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); + + // for (int i = start; i < end; i++) { + // var instr = target.get(i); + // if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { + // target.set(i, Instruction.jmp(end - i)); + // } + // } for (var el : caseToStatement.entrySet()) { var i = statementToIndex.get(el.getValue()); - if (i == null) i = end; - target.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); + if (i == null) i = endI; + subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); } } - public SwitchNode(Location loc, Node value, int defaultI, SwitchCase[] cases, Node[] body) { + public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) { super(loc); + this.label = label; this.value = value; this.defaultI = defaultI; this.cases = cases; @@ -90,14 +104,14 @@ public class SwitchNode extends Node { if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed(); n += 4; - var valRes = JavaScript.parseExpression(src, i + n, 0); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'case'"); - n += valRes.n; + var val = JavaScript.parseExpression(src, i + n, 0); + if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'"); + n += val.n; if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value"); n++; - return ParseRes.res(valRes.result, n); + return ParseRes.res(val.result, n); } private static ParseRes parseDefaultCase(Source src, int i) { var n = Parsing.skipEmpty(src, i); @@ -116,15 +130,19 @@ public class SwitchNode extends Node { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); + var label = JavaScript.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed(); n += 6; n += Parsing.skipEmpty(src, i + n); if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'"); n++; - var valRes = JavaScript.parseExpression(src, i + n, 0); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a switch value"); - n += valRes.n; + var val = JavaScript.parseExpression(src, i + n, 0); + if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value"); + n += val.n; n += Parsing.skipEmpty(src, i + n); if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value"); @@ -177,7 +195,7 @@ public class SwitchNode extends Node { } return ParseRes.res(new SwitchNode( - loc, valRes.result, defaultI, + loc, label.result, val.result, defaultI, cases.toArray(SwitchCase[]::new), statements.toArray(Node[]::new) ), n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java index 3552adc..2c6cb37 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java @@ -12,8 +12,7 @@ import me.topchetoeu.jscript.compilation.Node; public class ThrowNode extends Node { public final Node value; - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { value.compile(target, true); target.add(Instruction.throwInstr()).setLocation(loc()); } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/TryNode.java b/src/java/me/topchetoeu/jscript/compilation/control/TryNode.java index e9b34a2..52d6723 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/TryNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/TryNode.java @@ -8,60 +8,98 @@ import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; public class TryNode extends Node { public final Node tryBody; public final Node catchBody; public final Node finallyBody; - public final String name; + public final String captureName; + public final String label; - @Override public void declare(CompileResult target) { - tryBody.declare(target); - if (catchBody != null) catchBody.declare(target); - if (finallyBody != null) finallyBody.declare(target); + @Override public void resolve(CompileResult target) { + tryBody.resolve(target); + catchBody.resolve(target); + finallyBody.resolve(target); } @Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { int replace = target.temp(); + var endSuppl = new DeferredIntSupplier(); int start = replace + 1, catchStart = -1, finallyStart = -1; - tryBody.compile(target, false); - target.add(Instruction.tryEnd()); + LabelContext.getBreak(target.env).push(loc(), label, endSuppl); + + { + var subtarget = target.subtarget(); + subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount())); + + tryBody.compile(subtarget, false); + subtarget.add(Instruction.tryEnd()); + + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); + } if (catchBody != null) { catchStart = target.size() - start; - target.scope.define(name, true); - catchBody.compile(target, false); - target.scope.undefine(); - target.add(Instruction.tryEnd()); + + var subtarget = target.subtarget(); + var decN = captureName != null ? 1 : 0; + + if (captureName != null) subtarget.scope.defineStrict(captureName, false, catchBody.loc()); + + var _subtarget = subtarget; + + subtarget.add(() -> Instruction.stackAlloc(_subtarget.scope.allocCount() - decN)); + + catchBody.compile(subtarget, false); + + subtarget.add(Instruction.tryEnd()); + + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount() - decN)); } if (finallyBody != null) { finallyStart = target.size() - start; - finallyBody.compile(target, false); - target.add(Instruction.tryEnd()); + + var subtarget = target.subtarget(); + finallyBody.compile(subtarget, false); + subtarget.add(Instruction.tryEnd()); } + LabelContext.getBreak(target.env).pop(label); + + endSuppl.set(target.size()); + target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start)); target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER); if (pollute) target.add(Instruction.pushUndefined()); } - public TryNode(Location loc, Node tryBody, Node catchBody, Node finallyBody, String name) { + public TryNode(Location loc, String label, Node tryBody, Node catchBody, Node finallyBody, String captureName) { super(loc); this.tryBody = tryBody; this.catchBody = catchBody; this.finallyBody = finallyBody; - this.name = name; + this.captureName = captureName; + this.label = label; } public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); + var labelRes = JavaScript.parseLabel(src, i + n); + n += labelRes.n; + n += Parsing.skipEmpty(src, i + n); + if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed(); n += 3; @@ -70,7 +108,7 @@ public class TryNode extends Node { n += tryBody.n; n += Parsing.skipEmpty(src, i + n); - String name = null; + String capture = null; Node catchBody = null, finallyBody = null; if (Parsing.isIdentifier(src, i + n, "catch")) { @@ -80,7 +118,7 @@ public class TryNode extends Node { n++; var nameRes = Parsing.parseIdentifier(src, i + n); if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name"); - name = nameRes.result; + capture = nameRes.result; n += nameRes.n; n += Parsing.skipEmpty(src, i + n); @@ -108,6 +146,6 @@ public class TryNode extends Node { if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally"); - return ParseRes.res(new TryNode(loc, tryBody.result, catchBody, finallyBody, name), n); + return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/WhileNode.java b/src/java/me/topchetoeu/jscript/compilation/control/WhileNode.java index 484818b..fda05e1 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/WhileNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/WhileNode.java @@ -2,53 +2,51 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.Instruction.Type; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; public class WhileNode extends Node { public final Node condition, body; public final String label; - @Override - public void declare(CompileResult target) { - body.declare(target); + @Override public void resolve(CompileResult target) { + body.resolve(target); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { int start = target.size(); condition.compile(target, true); int mid = target.temp(); + + var end = new DeferredIntSupplier(); + + + LabelContext.pushLoop(target.env, loc(), label, end, start); + var subtarget = target.subtarget(); + subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount())); + body.compile(target, false, BreakpointType.STEP_OVER); - int end = target.size(); + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); + LabelContext.popLoop(target.env, label); - replaceBreaks(target, label, mid + 1, end, start, end + 1); + var endI = target.size(); + end.set(endI + 1); - target.add(Instruction.jmp(start - end)); - target.set(mid, Instruction.jmpIfNot(end - mid + 1)); + // replaceBreaks(target, label, mid + 1, end, start, end + 1); + + target.add(Instruction.jmp(start - end.getAsInt())); + target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1)); if (pollute) target.add(Instruction.pushUndefined()); } - public static ParseRes parseLabel(Source src, int i) { - int n = Parsing.skipEmpty(src, i); - - var nameRes = Parsing.parseIdentifier(src, i + n); - if (!nameRes.isSuccess()) return nameRes.chainError(); - n += nameRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ":")) return ParseRes.failed(); - n++; - - return ParseRes.res(nameRes.result, n); - } - public WhileNode(Location loc, String label, Node condition, Node body) { super(loc); this.label = label; @@ -56,24 +54,24 @@ public class WhileNode extends Node { this.body = body; } - public static void replaceBreaks(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) { - for (int i = start; i < end; i++) { - var instr = target.get(i); - if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) { - target.set(i, Instruction.jmp(continuePoint - i)); - } - if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) { - target.set(i, Instruction.jmp(breakPoint - i)); - } - } - } + // public static void replaceBreaks(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) { + // for (int i = start; i < end; i++) { + // var instr = target.get(i); + // if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) { + // target.set(i, Instruction.jmp(continuePoint - i)); + // } + // if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) { + // target.set(i, Instruction.jmp(breakPoint - i)); + // } + // } + // } public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); - var labelRes = WhileNode.parseLabel(src, i + n); - n += labelRes.n; + var label = JavaScript.parseLabel(src, i + n); + n += label.n; n += Parsing.skipEmpty(src, i + n); if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); @@ -83,18 +81,18 @@ public class WhileNode extends Node { if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); n++; - var condRes = JavaScript.parseExpression(src, i + n, 0); - if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a while condition."); - n += condRes.n; + var cond = JavaScript.parseExpression(src, i + n, 0); + if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition."); + n += cond.n; n += Parsing.skipEmpty(src, i + n); if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition."); n++; - var res = JavaScript.parseStatement(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a while body."); - n += res.n; + var body = JavaScript.parseStatement(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body."); + n += body.n; - return ParseRes.res(new WhileNode(loc, labelRes.result, condRes.result, res.result), n); + return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java b/src/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java index 72f7468..691ca0e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java +++ b/src/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java @@ -5,9 +5,12 @@ import java.util.HashMap; import me.topchetoeu.jscript.common.parsing.Location; public class FunctionScope extends Scope { - private final VariableList captures = new VariableList(); - private final VariableList locals = new VariableList(captures); + private final VariableList captures = new VariableList().setIndexMap(v -> ~v); + private final VariableList specials = new VariableList(); + private final VariableList locals = new VariableList(specials); private HashMap childToParent = new HashMap<>(); + public final String selfName; + public final VariableDescriptor selfVar; private void removeCapture(String name) { var res = captures.remove(name); @@ -28,10 +31,14 @@ public class FunctionScope extends Scope { } @Override public VariableDescriptor get(String name, boolean capture) { + if (specials.has(name)) return specials.get(name); if (locals.has(name)) return locals.get(name); if (captures.has(name)) return captures.get(name); + if (selfName != null && selfName.equals(name)) return selfVar; var parentVar = parent.get(name, true); + if (parentVar == null) return null; + var childVar = captures.add(parentVar); childToParent.put(childVar, parentVar); @@ -40,6 +47,7 @@ public class FunctionScope extends Scope { } @Override public boolean has(String name) { + if (specials.has(name)) return true; if (locals.has(name)) return true; if (captures.has(name)) return true; if (parent != null) return parent.has(name); @@ -47,9 +55,24 @@ public class FunctionScope extends Scope { return false; } - public int localsCount() { - return locals.size(); + @Override public boolean end() { + if (!super.end()) return false; + + captures.freeze(); + locals.freeze(); + return true; } + + @Override public int localsCount() { + return locals.size() + specials.size(); + } + @Override public int capturesCount() { + return captures.size(); + } + @Override public int allocCount() { + return 0; + } + public int offset() { return captures.size() + locals.size(); } @@ -66,6 +89,22 @@ public class FunctionScope extends Scope { return res; } - public FunctionScope() { super(); } - public FunctionScope(Scope parent) { super(parent); } + public FunctionScope(String selfName, String[] args) { + super(); + this.selfName = selfName; + + if (selfName != null) this.selfVar = VariableDescriptor.of(selfName, true, -1); + else this.selfVar = null; + + for (var arg : args) specials.add(arg, false); + } + public FunctionScope(String selfName, String[] args, Scope parent) { + super(parent); + this.selfName = selfName; + + if (selfName != null) this.selfVar = VariableDescriptor.of(selfName, true, -1); + else this.selfVar = null; + + for (var arg : args) specials.add(arg, false); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java b/src/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java index 1f7e8a3..6151a8e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java +++ b/src/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java @@ -19,5 +19,15 @@ public final class GlobalScope extends Scope { return false; } + @Override public int localsCount() { + return 0; + } + @Override public int capturesCount() { + return 0; + } + @Override public int allocCount() { + return 0; + } + public GlobalScope() { super(); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java b/src/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java index 9244fb6..c348168 100644 --- a/src/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java +++ b/src/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java @@ -2,7 +2,7 @@ package me.topchetoeu.jscript.compilation.scope; import me.topchetoeu.jscript.common.parsing.Location; -public class LocalScope extends Scope { +public final class LocalScope extends Scope { private final VariableList locals = new VariableList(); @Override public int offset() { @@ -43,6 +43,18 @@ public class LocalScope extends Scope { return true; } + @Override public int localsCount() { + if (parent == null) return 0; + else return parent.localsCount(); + } + @Override public int capturesCount() { + if (parent == null) return 0; + else return parent.capturesCount(); + } + @Override public int allocCount() { + return locals.size(); + } + public Iterable all() { return () -> locals.iterator(); } diff --git a/src/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/java/me/topchetoeu/jscript/compilation/scope/Scope.java index 51cf155..c41c2bd 100644 --- a/src/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -41,13 +41,17 @@ public abstract class Scope { */ public abstract int offset(); + public abstract int localsCount(); + public abstract int capturesCount(); + public abstract int allocCount(); + public boolean end() { if (!active) return false; this.active = false; if (this.parent != null) { assert this.parent.child == this; - this.parent.child = this; + this.parent.child = null; } return true; diff --git a/src/java/me/topchetoeu/jscript/compilation/scope/VariableList.java b/src/java/me/topchetoeu/jscript/compilation/scope/VariableList.java index 2fca3a8..dc583af 100644 --- a/src/java/me/topchetoeu/jscript/compilation/scope/VariableList.java +++ b/src/java/me/topchetoeu/jscript/compilation/scope/VariableList.java @@ -4,22 +4,49 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.function.IntSupplier; +import java.util.function.IntUnaryOperator; -public class VariableList implements Iterable { +public final class VariableList implements Iterable { private class ListVar extends VariableDescriptor { private ListVar next; private ListVar prev; + private boolean frozen; + private int index; @Override public int index() { - throw new RuntimeException("The index of a variable may not be retrieved until the scope has been finalized"); - // var res = 0; - // if (offset != null) res = offset.getAsInt(); + if (frozen) { + if (offset == null) { + return indexConverter == null ? index : indexConverter.applyAsInt(index); + } + else { + return indexConverter == null ? + index + offset.getAsInt() : + indexConverter.applyAsInt(index + offset.getAsInt()); + } + } - // for (var it = prev; it != null; it = it.prev) { - // res++; - // } + var res = 0; + if (offset != null) res = offset.getAsInt(); - // return res; + for (var it = prev; it != null; it = it.prev) { + res++; + } + + return indexConverter == null ? res : indexConverter.applyAsInt(res); + } + + public ListVar freeze() { + if (frozen) return this; + this.frozen = true; + + if (prev == null) return this; + assert prev.frozen; + + this.index = prev.index + 1; + this.next = null; + this.prev = null; + + return this; } public ListVar(String name, boolean readonly, ListVar next, ListVar prev) { @@ -38,6 +65,7 @@ public class VariableList implements Iterable { private ArrayList frozenList = null; private final IntSupplier offset; + private IntUnaryOperator indexConverter = null; public boolean frozen() { if (frozenMap != null) { @@ -60,26 +88,52 @@ public class VariableList implements Iterable { public VariableDescriptor add(VariableDescriptor val) { return add(val.name, val.readonly); - } - public VariableDescriptor add(String name, boolean readonly) { - if (frozen()) throw new RuntimeException("The scope has been frozen"); - if (map.containsKey(name)) return map.get(name); + } + public VariableDescriptor add(String name, boolean readonly) { + if (frozen()) throw new RuntimeException("The scope has been frozen"); + if (map.containsKey(name)) return map.get(name); - var res = new ListVar(name, readonly, null, last); - last.next = res; - last = res; - map.put(name, res); + var res = new ListVar(name, readonly, null, last); - return res; - } + if (last != null) { + assert first != null; + + last.next = res; + res.prev = last; + + last = res; + } + else { + first = last = res; + } + + map.put(name, res); + + return res; + } public VariableDescriptor remove(String name) { if (frozen()) throw new RuntimeException("The scope has been frozen"); var el = map.get(name); if (el == null) return null; - el.prev.next = el.next; - el.next.prev = el.prev; + if (el.prev != null) { + assert el != first; + el.prev.next = el.next; + } + else { + assert el == first; + first = first.next; + } + + if (el.next != null) { + assert el != last; + el.next.prev = el.prev; + } + else { + assert el == last; + last = last.prev; + } el.next = null; el.prev = null; @@ -88,7 +142,8 @@ public class VariableList implements Iterable { } public VariableDescriptor get(String name) { - return map.get(name); + if (frozen()) return frozenMap.get(name); + else return map.get(name); } public VariableDescriptor get(int i) { if (frozen()) { @@ -126,12 +181,17 @@ public class VariableList implements Iterable { frozenMap = new HashMap<>(); frozenList = new ArrayList<>(); - var i = 0; - if (offset != null) i = offset.getAsInt(); + for (var it = first; it != null; ) { + frozenMap.put(it.name, it); + frozenList.add(it); - for (var it = first; it != null; it = it.next) { - frozenMap.put(it.name, VariableDescriptor.of(it.name, it.readonly, i++)); + var tmp = it; + it = it.next; + tmp.freeze(); } + + map = null; + first = last = null; } @Override public Iterator iterator() { @@ -161,6 +221,11 @@ public class VariableList implements Iterable { return res; } + public VariableList setIndexMap(IntUnaryOperator map) { + indexConverter = map; + return this; + } + public VariableList(IntSupplier offset) { this.offset = offset; } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java b/src/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java new file mode 100644 index 0000000..104340f --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + + +public class ArgumentsNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.loadArgs()); + } + + public ArgumentsNode(Location loc) { + super(loc); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java b/src/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java index a2f2fe2..148a2a4 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java @@ -11,17 +11,10 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; + public class ArrayNode extends Node { public final Node[] statements; - @Override public boolean pure() { - for (var stm : statements) { - if (!stm.pure()) return false; - } - - return true; - } - @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadArr(statements.length)); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/FunctionNode.java b/src/java/me/topchetoeu/jscript/compilation/values/FunctionNode.java deleted file mode 100644 index 2a73052..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/values/FunctionNode.java +++ /dev/null @@ -1,150 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.Instruction.Type; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class FunctionNode extends Node { - public final CompoundNode body; - public final String varName; - public final String[] args; - public final boolean statement; - public final Location end; - - @Override public boolean pure() { return varName == null && statement; } - - @Override - public void declare(CompileResult target) { - if (varName != null && statement) target.scope.define(varName); - } - - public static void checkBreakAndCont(CompileResult target, int start) { - for (int i = start; i < target.size(); i++) { - if (target.get(i).type == Type.NOP) { - if (target.get(i).is(0, "break") ) { - throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop."); - } - if (target.get(i).is(0, "cont")) { - throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop."); - } - } - } - } - - private CompileResult compileBody(CompileResult target, String name, boolean pollute, BreakpointType bp) { - for (var i = 0; i < args.length; i++) { - for (var j = 0; j < i; j++) { - if (args[i].equals(args[j])) { - throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."); - } - } - } - - var subtarget = new CompileResult(target.scope.child()); - - subtarget.scope.define("this"); - var argsVar = subtarget.scope.define("arguments"); - - if (args.length > 0) { - for (var i = 0; i < args.length; i++) { - subtarget.add(Instruction.loadVar(argsVar)); - subtarget.add(Instruction.pushValue(i)); - subtarget.add(Instruction.loadMember()); - subtarget.add(Instruction.storeVar(subtarget.scope.define(args[i]))); - } - } - - if (!statement && this.varName != null) { - subtarget.add(Instruction.storeSelfFunc((int)subtarget.scope.define(this.varName))).setLocationAndDebug(loc(), bp); - } - - body.declare(subtarget); - body.compile(subtarget, false); - subtarget.length = args.length; - subtarget.add(Instruction.ret()).setLocation(end); - checkBreakAndCont(subtarget, 0); - - if (pollute) target.add(Instruction.loadFunc(target.children.size(), name, subtarget.scope.getCaptures())); - return target.addChild(subtarget); - } - - public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - if (this.varName != null) name = this.varName; - - var hasVar = this.varName != null && statement; - - compileBody(target, name, pollute || hasVar, bp); - - if (hasVar) { - var key = target.scope.getKey(this.varName); - - if (key instanceof String) target.add(Instruction.makeVar((String)key)); - target.add(Instruction.storeVar(target.scope.getKey(this.varName), false)); - } - } - public void compile(CompileResult target, boolean pollute, String name) { - compile(target, pollute, name, BreakpointType.NONE); - } - @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { - compile(target, pollute, (String)null, bp); - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, (String)null, BreakpointType.NONE); - } - - public FunctionNode(Location loc, Location end, String varName, String[] args, boolean statement, CompoundNode body) { - super(loc); - - this.end = end; - this.varName = varName; - this.statement = statement; - - this.args = args; - this.body = body; - } - - public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { - if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name); - else stm.compile(target, pollute); - } - public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) { - if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp); - else stm.compile(target, pollute, bp); - } - - public static ParseRes parseFunction(Source src, int i, boolean statement) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed(); - n += 8; - - var nameRes = Parsing.parseIdentifier(src, i + n); - if (!nameRes.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name"); - n += nameRes.n; - n += Parsing.skipEmpty(src, i + n); - - var args = JavaScript.parseParamList(src, i + n); - if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list"); - n += args.n; - - var res = CompoundNode.parse(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a compound statement for function."); - n += res.n; - - return ParseRes.res(new FunctionNode( - loc, src.loc(i + n - 1), - nameRes.result, args.result.toArray(String[]::new), - statement, res.result - ), n); - } -} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java b/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java index d80658b..11823a3 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java @@ -5,11 +5,9 @@ import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Node; -public class GlobalThisNode extends Node { - @Override public boolean pure() { return true; } - @Override - public void compile(CompileResult target, boolean pollute) { +public class GlobalThisNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.loadGlob()); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java index 257ed81..48bab87 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -11,16 +11,19 @@ import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.FunctionNode; +import me.topchetoeu.jscript.compilation.FunctionValueNode; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; + public class ObjectNode extends Node { public static class ObjProp { public final String name; public final String access; - public final FunctionNode func; - - public ObjProp(String name, String access, FunctionNode func) { + public final FunctionValueNode func; + + public ObjProp(String name, String access, FunctionValueNode func) { this.name = name; this.access = access; this.func = func; @@ -31,14 +34,6 @@ public class ObjectNode extends Node { public final Map getters; public final Map setters; - @Override public boolean pure() { - for (var el : map.values()) { - if (!el.pure()) return false; - } - - return true; - } - @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadObj()); @@ -114,7 +109,7 @@ public class ObjectNode extends Node { return ParseRes.res(new ObjProp( name.result, access.result, - new FunctionNode(loc, end, access + " " + name.result.toString(), params.result.toArray(String[]::new), false, body.result) + new FunctionValueNode(loc, end, params.result.toArray(String[]::new), body.result, access + " " + name.result.toString()) ), n); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/RegexNode.java b/src/java/me/topchetoeu/jscript/compilation/values/RegexNode.java index 11d00b5..7ac7c51 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/RegexNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/RegexNode.java @@ -11,11 +11,7 @@ import me.topchetoeu.jscript.compilation.Node; public class RegexNode extends Node { public final String pattern, flags; - // Not really pure, since a function is called, but can be ignored. - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadRegex(pattern, flags)); if (!pollute) target.add(Instruction.discard()); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ThisNode.java b/src/java/me/topchetoeu/jscript/compilation/values/ThisNode.java new file mode 100644 index 0000000..ad74007 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/ThisNode.java @@ -0,0 +1,17 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + + +public class ThisNode extends Node { + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.loadThis()); + } + + public ThisNode(Location loc) { + super(loc); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index 8f4d614..bea5355 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -1,5 +1,7 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.function.Supplier; + import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.parsing.Location; @@ -11,22 +13,61 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.values.operations.VariableAssignNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class VariableNode extends Node implements AssignableNode { public final String name; - @Override public boolean pure() { return false; } + // @Override public EvalResult evaluate(CompileResult target) { + // var i = target.scope.getKey(name); - @Override - public Node toAssign(Node val, Operation operation) { + // if (i instanceof String) return EvalResult.NONE; + // else return EvalResult.UNKNOWN; + // } + + @Override public Node toAssign(Node val, Operation operation) { return new VariableAssignNode(loc(), name, val, operation); } - @Override - public void compile(CompileResult target, boolean pollute) { - var i = target.scope.getKey(name); - target.add(Instruction.loadVar(i)); - if (!pollute) target.add(Instruction.discard()); + @Override public void compile(CompileResult target, boolean pollute) { + var i = target.scope.get(name, true); + + if (i == null) { + target.add((Supplier)() -> { + if (target.scope.has(name)) throw new SyntaxException(loc(), String.format("Cannot access '%s' before initialization", name)); + return Instruction.globGet(name); + }); + + if (!pollute) target.add(Instruction.discard()); + } + else if (pollute) { + target.add(Instruction.loadVar(i.index())); + } + } + + public static Supplier toGet(CompileResult target, Location loc, String name, Supplier onGlobal) { + var i = target.scope.get(name, true); + + if (i == null) return () -> { + if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name)); + else return onGlobal.get(); + }; + else return () -> Instruction.loadVar(i.index()); + } + public static Supplier toGet(CompileResult target, Location loc, String name) { + return toGet(target, loc, name, () -> Instruction.globGet(name)); + } + + + public static Supplier toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) { + var i = target.scope.get(name, true); + + if (i == null) return () -> { + if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name)); + else return Instruction.globSet(name, keep, define); + }; + else return () -> Instruction.storeVar(i.index(), keep); + } public VariableNode(Location loc, String name) { diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java index a15b16a..8a5462b 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java @@ -8,8 +8,6 @@ import me.topchetoeu.jscript.compilation.Node; public class BoolNode extends Node { public final boolean value; - @Override public boolean pure() { return true; } - @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.pushValue(value)); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java index e8c4d54..cae8bd6 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java @@ -6,10 +6,8 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Node; public class NullNode extends Node { - @Override public boolean pure() { return true; } - @Override public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.pushNull()); + if (pollute) target.add(Instruction.pushNull()); } public NullNode(Location loc) { super(loc); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java index 246bf68..5afd40e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java @@ -11,8 +11,6 @@ import me.topchetoeu.jscript.compilation.Node; public class NumberNode extends Node { public final double value; - @Override public boolean pure() { return true; } - @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.pushValue(value)); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java index b288b12..e50d82b 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java @@ -11,8 +11,6 @@ import me.topchetoeu.jscript.compilation.Node; public class StringNode extends Node { public final String value; - @Override public boolean pure() { return true; } - @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.pushValue(value)); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java index b4635fb..bdd3b74 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java @@ -13,8 +13,10 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.ArgumentsNode; import me.topchetoeu.jscript.compilation.values.ArrayNode; import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.ThisNode; import me.topchetoeu.jscript.compilation.values.VariableNode; import me.topchetoeu.jscript.compilation.values.constants.BoolNode; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; @@ -51,12 +53,18 @@ public class CallNode extends Node { else if (func instanceof VariableNode) { res = ((VariableNode)func).name; } - else if (func instanceof VariableIndexNode) { - var i = ((VariableIndexNode)func).index; - - if (i == 0) res = "this"; - else if (i == 1) res = "arguments"; + else if (func instanceof ThisNode) { + res = "this"; } + else if (func instanceof ArgumentsNode) { + res = "arguments"; + } + // else if (func instanceof VariableIndexNode) { + // var i = ((VariableIndexNode)func).index; + + // if (i == 0) res = "this"; + // else if (i == 1) res = "arguments"; + // } else if (func instanceof ArrayNode) { var els = new ArrayList(); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java index 063f503..a68ebb6 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java @@ -9,10 +9,18 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; + public class DiscardNode extends Node { public final Node value; - @Override public boolean pure() { return value.pure(); } + // @Override public EvalResult evaluate(CompileResult target) { + // if (value == null) return EvalResult.FALSY; + // var res = value.evaluate(target); + + // if (res.isPure) return EvalResult.FALSY; + // else if (res.never) return EvalResult.NEVER; + // else return EvalResult.FALSY_IMPURE; + // } @Override public void compile(CompileResult target, boolean pollute) { if (value != null) value.compile(target, false); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java index 2b5c68e..e9e2e3d 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java @@ -13,8 +13,7 @@ public class IndexAssignNode extends Node { public final Node value; public final Operation operation; - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { if (operation != null) { object.compile(target, true); index.compile(target, true); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java index f8c1387..85a6da4 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java @@ -17,8 +17,7 @@ public class IndexNode extends Node implements AssignableNode { public final Node object; public final Node index; - @Override - public Node toAssign(Node val, Operation operation) { + @Override public Node toAssign(Node val, Operation operation) { return new IndexAssignNode(loc(), object, index, val, operation); } public void compile(CompileResult target, boolean dupObj, boolean pollute) { @@ -29,8 +28,7 @@ public class IndexNode extends Node implements AssignableNode { target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN); if (!pollute) target.add(Instruction.discard()); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { compile(target, false, pollute); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java index 71294b3..a63506b 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java @@ -12,10 +12,15 @@ import me.topchetoeu.jscript.compilation.Node; public class LazyAndNode extends Node { public final Node first, second; - @Override public boolean pure() { return first.pure() && second.pure(); } + // @Override public EvalResult evaluate(CompileResult target) { + // var firstRes = first.evaluate(target); + // if (firstRes.falsy) return firstRes; + // if (!firstRes.isPure) return firstRes; - @Override - public void compile(CompileResult target, boolean pollute) { + // return second.evaluate(target); + // } + + @Override public void compile(CompileResult target, boolean pollute) { first.compile(target, true); if (pollute) target.add(Instruction.dup()); int start = target.temp(); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java index 225961e..ac374ce 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java @@ -9,13 +9,19 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; + public class LazyOrNode extends Node { public final Node first, second; - @Override public boolean pure() { return first.pure() && second.pure(); } + // @Override public EvalResult evaluate(CompileResult target) { + // var firstRes = first.evaluate(target); + // if (firstRes.truthy) return firstRes; + // if (!firstRes.isPure) return firstRes; - @Override - public void compile(CompileResult target, boolean pollute) { + // return second.evaluate(target); + // } + + @Override public void compile(CompileResult target, boolean pollute) { first.compile(target, true); if (pollute) target.add(Instruction.dup()); int start = target.temp(); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java index d59951b..57039c6 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java @@ -93,14 +93,6 @@ public class OperationNode extends Node { public final Node[] args; public final Operation operation; - @Override public boolean pure() { - for (var el : args) { - if (!el.pure()) return false; - } - - return true; - } - @Override public void compile(CompileResult target, boolean pollute) { for (var arg : args) { arg.compile(target, true); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java index 1eabc75..15f8387 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java @@ -8,26 +8,32 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; + import me.topchetoeu.jscript.compilation.values.VariableNode; public class TypeofNode extends Node { public final Node value; - // Not really pure, since a variable from the global scope could be accessed, - // which could lead to code execution, that would get omitted - @Override public boolean pure() { return true; } + // @Override public EvalResult evaluate(CompileResult target) { + // if (value instanceof VariableNode) { + // var i = target.scope.getKey(((VariableNode)value).name); + // if (i instanceof String) return EvalResult.NONE; + // } - @Override - public void compile(CompileResult target, boolean pollute) { - if (value instanceof VariableNode) { - var i = target.scope.getKey(((VariableNode)value).name); - if (i instanceof String) { - target.add(Instruction.typeof((String)i)); - return; - } + // return EvalResult.UNKNOWN; + // } + + @Override public void compile(CompileResult target, boolean pollute) { + if (value instanceof VariableNode varNode) { + target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name))); + if (!pollute) target.add(Instruction.discard()); + + return; } + value.compile(target, pollute); target.add(Instruction.typeof()); + if (!pollute) target.add(Instruction.discard()); } public TypeofNode(Location loc, Node value) { diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java index f51f92d..940a27c 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java @@ -4,28 +4,25 @@ import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.FunctionNode; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.FunctionNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; public class VariableAssignNode extends Node { public final String name; public final Node value; public final Operation operation; - @Override public boolean pure() { return false; } - - @Override - public void compile(CompileResult target, boolean pollute) { - var i = target.scope.getKey(name); + @Override public void compile(CompileResult target, boolean pollute) { if (operation != null) { - target.add(Instruction.loadVar(i)); + target.add(VariableNode.toGet(target, loc(), name)); FunctionNode.compileWithName(value, target, true, name); target.add(Instruction.operation(operation)); - target.add(Instruction.storeVar(i, pollute)); + target.add(VariableNode.toSet(target, loc(), name, pollute, false)); } else { FunctionNode.compileWithName(value, target, true, name); - target.add(Instruction.storeVar(i, pollute)); + target.add(VariableNode.toSet(target, loc(), name, pollute, false)); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableIndexNode.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableIndexNode.java deleted file mode 100644 index a5dbb48..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableIndexNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - -public class VariableIndexNode extends Node { - public final int index; - - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.loadVar(index)); - } - - public VariableIndexNode(Location loc, int i) { - super(loc); - this.index = i; - } -} diff --git a/src/java/me/topchetoeu/jscript/common/Compiler.java b/src/java/me/topchetoeu/jscript/runtime/Compiler.java similarity index 84% rename from src/java/me/topchetoeu/jscript/common/Compiler.java rename to src/java/me/topchetoeu/jscript/runtime/Compiler.java index 1d6e7d2..de430b2 100644 --- a/src/java/me/topchetoeu/jscript/common/Compiler.java +++ b/src/java/me/topchetoeu/jscript/runtime/Compiler.java @@ -1,5 +1,6 @@ -package me.topchetoeu.jscript.common; +package me.topchetoeu.jscript.runtime; +import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.parsing.Filename; @@ -7,7 +8,7 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.scope.ValueVariable; +import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; public interface Compiler { @@ -30,7 +31,7 @@ public interface Compiler { }); } - private static void registerFunc(Environment env, FunctionBody body, CompileResult res) { + static void registerFunc(Environment env, FunctionBody body, CompileResult res) { var map = res.map(); DebugContext.get(env).onFunctionLoad(body, map); @@ -41,6 +42,6 @@ public interface Compiler { } public static CodeFunction compileFunc(Environment env, Filename filename, String raw) { - return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new ValueVariable[0]); + return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new Value[0][]); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/Engine.java b/src/java/me/topchetoeu/jscript/runtime/Engine.java index 1b83179..e3ada89 100644 --- a/src/java/me/topchetoeu/jscript/runtime/Engine.java +++ b/src/java/me/topchetoeu/jscript/runtime/Engine.java @@ -18,8 +18,7 @@ public class Engine implements EventLoop { this.micro = micro; } - @Override - public int compareTo(Task other) { + @Override public int compareTo(Task other) { return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/EventLoop.java b/src/java/me/topchetoeu/jscript/runtime/EventLoop.java index 0227682..2c548ba 100644 --- a/src/java/me/topchetoeu/jscript/runtime/EventLoop.java +++ b/src/java/me/topchetoeu/jscript/runtime/EventLoop.java @@ -3,7 +3,6 @@ package me.topchetoeu.jscript.runtime; import java.util.concurrent.Future; import java.util.function.Supplier; -import me.topchetoeu.jscript.common.Compiler; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.parsing.Filename; diff --git a/src/java/me/topchetoeu/jscript/runtime/Frame.java b/src/java/me/topchetoeu/jscript/runtime/Frame.java index cc56651..1ccf5bb 100644 --- a/src/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,7 +1,9 @@ package me.topchetoeu.jscript.runtime; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Stack; @@ -11,8 +13,6 @@ import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; -import me.topchetoeu.jscript.runtime.scope.LocalScope; -import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; @@ -97,14 +97,24 @@ public class Frame { } } - public final LocalScope scope; - public final Object thisArg; - public final Object[] args; + /** + * A list of one-element arrays of values. This is so that we can pass captures to other functions + */ + public final Value[][] captures; + public final List locals = new ArrayList<>(); + public final Value self; + public final Value argsVal; + public final Value[] args; public final boolean isNew; public final Stack tryStack = new Stack<>(); public final CodeFunction function; public final Environment env; + public Value[] getVar(int i) { + if (i < 0) return captures[~i]; + else return locals.get(i); + } + public Value[] stack = new Value[32]; public int stackPtr = 0; public int codePtr = 0; @@ -178,6 +188,7 @@ public class Frame { } } catch (EngineException e) { error = e; } + // catch (RuntimeException e) { error = EngineException.ofError("InternalError", e.getMessage()); } } while (!tryStack.empty()) { @@ -201,12 +212,12 @@ public class Frame { if (newCtx != tryCtx) { switch (newCtx.state) { case CATCH: - if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value)); + if (tryCtx.state != TryState.CATCH) locals.add(new Value[] { error.value }); codePtr = tryCtx.catchStart; stackPtr = tryCtx.restoreStackPtr; break; case FINALLY: - if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); codePtr = tryCtx.finallyStart; stackPtr = tryCtx.restoreStackPtr; default: @@ -222,7 +233,7 @@ public class Frame { } else { popTryFlag = false; - if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); + if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) { codePtr = tryCtx.finallyStart; @@ -318,41 +329,45 @@ public class Frame { } /** - * Gets an object proxy of the local scope + * Gets an object proxy of the local locals */ public ObjectValue getLocalScope() { - var names = new String[scope.locals.length]; - var map = DebugContext.get(env).getMapOrEmpty(function); + throw new RuntimeException("Not supported"); - for (int i = 0; i < scope.locals.length; i++) { - var name = "local_" + (i - 2); + // var names = new String[locals.locals.length]; + // var map = DebugContext.get(env).getMapOrEmpty(function); - if (i == 0) name = "this"; - else if (i == 1) name = "arguments"; - else if (i < map.localNames.length) name = map.localNames[i]; + // for (int i = 0; i < locals.locals.length; i++) { + // var name = "local_" + (i - 2); - names[i] = name; - } + // if (i == 0) name = "this"; + // else if (i == 1) name = "arguments"; + // else if (i < map.localNames.length) name = map.localNames[i]; - return new ScopeValue(scope.locals, names); + // names[i] = name; + // } + + // return new ScopeValue(locals, names); } /** - * Gets an object proxy of the capture scope + * Gets an object proxy of the capture locals */ public ObjectValue getCaptureScope() { - var names = new String[scope.captures.length]; + // throw new RuntimeException("Not supported"); + + var names = new String[captures.length]; var map = DebugContext.get(env).getMapOrEmpty(function); - for (int i = 0; i < scope.captures.length; i++) { + for (int i = 0; i < captures.length; i++) { var name = "capture_" + (i - 2); if (i < map.captureNames.length) name = map.captureNames[i]; names[i] = name; } - return new ScopeValue(scope.captures, names); + return new ScopeValue(captures, names); } /** - * Gets an array proxy of the local scope + * Gets an array proxy of the local locals */ public ObjectValue getValStackScope() { return new ObjectValue() { @@ -393,13 +408,25 @@ public class Frame { public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) { this.env = env; - this.args = args; - this.isNew = isNew; - this.scope = new LocalScope(func.body.localsN, func.captures); - this.scope.get(0).set(thisArg); - this.scope.get(1).value = new ArgumentsValue(this, args); - - this.thisArg = thisArg; this.function = func; + this.isNew = isNew; + + this.self = thisArg; + this.args = args; + this.argsVal = new ArgumentsValue(this, args); + this.captures = func.captures; + + for (var i = 0; i < func.body.argsN; i++) { + this.locals.add(new Value[] { args[i] }); + } + + for (var i = 0; i < func.body.localsN; i++) { + this.locals.add(new Value[] { Value.UNDEFINED }); + } + + // this.locals = new LocalScope(func.body.localsN, func.captures); + // this.locals.get(0).set(thisArg); + // this.locals.get(1).value = new ArgumentsValue(this, args); + } } diff --git a/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 4b0db70..9a7829e 100644 --- a/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -7,8 +7,6 @@ import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.scope.GlobalScope; -import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.Value; @@ -60,12 +58,6 @@ public class InstructionRunner { return null; } - private static Value execMakeVar(Environment env, Instruction instr, Frame frame) { - var name = (String)instr.get(0); - GlobalScope.get(env).define(env, false, name); - frame.codePtr++; - return null; - } private static Value execDefProp(Environment env, Instruction instr, Frame frame) { var setterVal = frame.pop(); var getterVal = frame.pop(); @@ -146,12 +138,11 @@ public class InstructionRunner { return null; } private static Value execLoadVar(Environment env, Instruction instr, Frame frame) { - var i = instr.get(0); - - if (i instanceof String) frame.push(GlobalScope.get(env).get(env, (String)i)); - else frame.push(frame.scope.get((int)i).get(env)); + int i = instr.get(0); + frame.push(frame.getVar(i)[0]); frame.codePtr++; + return null; } private static Value execLoadObj(Environment env, Instruction instr, Frame frame) { @@ -162,7 +153,7 @@ public class InstructionRunner { return null; } private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) { - frame.push(GlobalScope.get(env).object); + frame.push(Value.global(env)); frame.codePtr++; return null; } @@ -176,10 +167,10 @@ public class InstructionRunner { private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) { int id = instr.get(0); String name = instr.get(1); - var captures = new ValueVariable[instr.params.length - 2]; + var captures = new Value[instr.params.length - 2][]; for (var i = 2; i < instr.params.length; i++) { - captures[i - 2] = frame.scope.get(instr.get(i)); + captures[i - 2] = frame.getVar(instr.get(i)); } var func = new CodeFunction(env, name, frame.function.body.children[id], captures); @@ -231,20 +222,14 @@ public class InstructionRunner { } private static Value execStoreVar(Environment env, Instruction instr, Frame frame) { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); - var i = instr.get(0); - - if (i instanceof String) GlobalScope.get(env).set(env, (String)i, val); - else frame.scope.get((int)i).set(env, val); + int i = instr.get(0); + frame.getVar(i)[0] = val; frame.codePtr++; + return null; } - private static Value execStoreSelfFunc(Environment env, Instruction instr, Frame frame) { - frame.scope.locals[(int)instr.get(0)].set(env, frame.function); - frame.codePtr++; - return null; - } - + private static Value execJmp(Environment env, Instruction instr, Frame frame) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; @@ -271,12 +256,7 @@ public class InstructionRunner { String name = instr.get(0); Value obj; - if (name != null) { - if (GlobalScope.get(env).has(env, name)) { - obj = GlobalScope.get(env).get(env, name); - } - else obj = null; - } + if (name != null) obj = Value.global(env).getMember(env, name); else obj = frame.pop(); frame.push(obj.type()); @@ -309,6 +289,73 @@ public class InstructionRunner { return null; } + private static Value exexGlobDef(Environment env, Instruction instr, Frame frame) { + var name = (String)instr.get(0); + + if (!Value.global(env).hasMember(env, name, false)) { + if (!Value.global(env).defineOwnMember(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name); + } + + frame.codePtr++; + return null; + } + private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) { + var name = (String)instr.get(0); + var res = Value.global(env).getMemberOrNull(env, name); + + if (res == null) throw EngineException.ofSyntax(name + " is not defined"); + else frame.push(res); + + frame.codePtr++; + return null; + } + private static Value exexGlobSet(Environment env, Instruction instr, Frame frame) { + var name = (String)instr.get(0); + var keep = (boolean)instr.get(1); + var define = (boolean)instr.get(2); + + var val = keep ? frame.peek() : frame.pop(); + var res = false; + + if (define) res = Value.global(env).setMember(env, name, val); + else res = Value.global(env).setMemberIfExists(env, name, val); + + if (!res) throw EngineException.ofError("Couldn't set variable " + name); + + frame.codePtr++; + return null; + } + + private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) { + frame.push(frame.argsVal); + frame.codePtr++; + return null; + } + private static Value execLoadThis(Environment env, Instruction instr, Frame frame) { + frame.push(frame.self); + frame.codePtr++; + return null; + } + + private static Value execStackAlloc(Environment env, Instruction instr, Frame frame) { + int n = instr.get(0); + + for (var i = 0; i < n; i++) frame.locals.add(new Value[] { Value.UNDEFINED }); + + frame.codePtr++; + return null; + } + private static Value execStackFree(Environment env, Instruction instr, Frame frame) { + int n = instr.get(0); + + for (var i = 0; i < n; i++) { + frame.locals.remove(frame.locals.size() - 1); + } + + frame.codePtr++; + return null; + } + public static Value exec(Environment env, Instruction instr, Frame frame) { switch (instr.type) { case NOP: return execNop(env, instr, frame); @@ -335,12 +382,12 @@ public class InstructionRunner { case LOAD_MEMBER: return execLoadMember(env, instr, frame); case LOAD_REGEX: return execLoadRegEx(env, instr, frame); case LOAD_GLOB: return execLoadGlob(env, instr, frame); + case LOAD_ARGS: return execLoadArgs(env, instr, frame); + case LOAD_THIS: return execLoadThis(env, instr, frame); case DISCARD: return execDiscard(env, instr, frame); case STORE_MEMBER: return execStoreMember(env, instr, frame); case STORE_VAR: return execStoreVar(env, instr, frame); - case STORE_SELF_FUNC: return execStoreSelfFunc(env, instr, frame); - case MAKE_VAR: return execMakeVar(env, instr, frame); case KEYS: return execKeys(env, instr, frame); case DEF_PROP: return execDefProp(env, instr, frame); @@ -353,6 +400,13 @@ public class InstructionRunner { case OPERATION: return execOperation(env, instr, frame); + case GLOB_DEF: return exexGlobDef(env, instr, frame); + case GLOB_GET: return exexGlobGet(env, instr, frame); + case GLOB_SET: return exexGlobSet(env, instr, frame); + + case STACK_ALLOC: return execStackAlloc(env, instr, frame); + case STACK_FREE: return execStackFree(env, instr, frame); + default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 18a4278..21ddfaa 100644 --- a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -6,7 +6,6 @@ import java.nio.file.Path; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; -import me.topchetoeu.jscript.common.Compiler; import me.topchetoeu.jscript.common.Metadata; import me.topchetoeu.jscript.common.Reading; import me.topchetoeu.jscript.common.environment.Environment; @@ -16,7 +15,6 @@ import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -import me.topchetoeu.jscript.runtime.scope.GlobalScope; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.Value; @@ -324,18 +322,17 @@ public class SimpleRepl { // environment.add(ModuleRepo.KEY, ModuleRepo.ofFilesystem(fs)); // environment.add(Compiler.KEY, new JSCompiler(environment)); environment.add(EventLoop.KEY, engine); - environment.add(GlobalScope.KEY, new GlobalScope()); environment.add(DebugContext.KEY, new DebugContext()); // environment.add(EventLoop.KEY, engine); environment.add(Compiler.KEY, Compiler.DEFAULT); - var glob = GlobalScope.get(environment); + var glob = Value.global(environment); - glob.define(null, false, new NativeFunction("exit", args -> { + glob.defineOwnMember(null, "exit", new NativeFunction("exit", args -> { Thread.currentThread().interrupt(); throw new InterruptException(); })); - glob.define(null, false, new NativeFunction("log", args -> { + glob.defineOwnMember(null, "print", new NativeFunction("print", args -> { for (var el : args.args) { if (el instanceof StringValue) System.out.print(((StringValue)el).value); else System.out.print(el.toReadable(args.env)); @@ -356,7 +353,7 @@ public class SimpleRepl { EventLoop.get(environment).pushMsg( false, environment, Filename.parse("jscript://init.js"), Reading.resourceToString("lib/index.js"), - Value.UNDEFINED, GlobalScope.get(environment).object, primordials(environment) + Value.UNDEFINED, Value.global(environment), primordials(environment) ).get(); } diff --git a/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index 1c0706d..f9a21f4 100644 --- a/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -96,6 +96,8 @@ public class EngineException extends RuntimeException { var res = new ObjectValue(); res.setPrototype(proto); + if (msg == null) msg = ""; + if (name != null) res.defineOwnMember(Environment.empty(), "name", FieldMember.of(new StringValue(name))); res.defineOwnMember(Environment.empty(), "message", FieldMember.of(new StringValue(msg))); return res; diff --git a/src/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java b/src/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java index 2db27e1..023df78 100644 --- a/src/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java +++ b/src/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java @@ -7,7 +7,7 @@ public class SyntaxException extends RuntimeException { public final String msg; public SyntaxException(Location loc, String msg) { - super(String.format("Syntax error (at %s): %s", loc, msg)); + super(String.format("Syntax error %s: %s", loc, msg)); this.loc = loc; this.msg = msg; } diff --git a/src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java b/src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java deleted file mode 100644 index 4092749..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java +++ /dev/null @@ -1,74 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import java.util.HashSet; -import java.util.Set; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; - -public class GlobalScope { - public static final Key KEY = Key.of(); - - public final ObjectValue object; - - public boolean has(Environment ext, String name) { - return object.hasMember(ext, new StringValue(name), false); - } - - public GlobalScope child() { - var res = new GlobalScope(); - res.object.setPrototype(null, this.object); - return res; - } - - public void define(Environment ext, String name, Variable variable) { - object.defineOwnMember(ext, name, variable.toField(true, true)); - } - public void define(Environment ext, boolean readonly, String name, Value val) { - object.defineOwnMember(ext, name, FieldMember.of(val, !readonly)); - } - public void define(Environment ext, boolean readonly, String ...names) { - for (var name : names) define(ext, name, new ValueVariable(readonly, Value.UNDEFINED)); - } - public void define(Environment ext, boolean readonly, FunctionValue val) { - define(ext, readonly, val.name, val); - } - - public Value get(Environment env, String name) { - if (!object.hasMember(env, name, false)) throw EngineException.ofSyntax(name + " is not defined"); - else return object.getMember(env, name); - } - public void set(Environment ext, String name, Value val) { - if (!object.hasMember(ext, name, false)) throw EngineException.ofSyntax(name + " is not defined"); - if (!object.setMember(ext, name, val)) throw EngineException.ofSyntax("Assignment to constant variable"); - } - - public Set keys() { - var res = new HashSet(); - - for (var key : keys()) { - if (key instanceof String) res.add((String)key); - } - - return res; - } - - public GlobalScope() { - this.object = new ObjectValue(); - this.object.setPrototype(null, null); - } - public GlobalScope(ObjectValue val) { - this.object = val; - } - - public static GlobalScope get(Environment ext) { - if (ext.has(KEY)) return ext.get(KEY); - else return new GlobalScope(); - } -} diff --git a/src/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java b/src/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java deleted file mode 100644 index 6adce80..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java +++ /dev/null @@ -1,29 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import java.util.ArrayList; - -public class LocalScope { - 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]; - } - - - public int size() { - return captures.length + locals.length; - } - - public LocalScope(int n, ValueVariable[] captures) { - locals = new ValueVariable[n]; - this.captures = captures; - - for (int i = 0; i < n; i++) { - locals[i] = new ValueVariable(false, null); - } - } -} diff --git a/src/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java b/src/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java deleted file mode 100644 index 22e2329..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.Value; - -public class ValueVariable implements Variable { - public boolean readonly; - public Value value; - - @Override public boolean readonly() { return readonly; } - @Override public final Value get(Environment env) { return get(); } - @Override public final boolean set(Environment env, Value val) { return set(val); } - - public Value get() { return value; } - public boolean set(Value val) { - if (readonly) return false; - this.value = val; - return true; - } - - public ValueVariable(boolean readonly, Value val) { - this.readonly = readonly; - this.value = val; - } -} diff --git a/src/java/me/topchetoeu/jscript/runtime/scope/Variable.java b/src/java/me/topchetoeu/jscript/runtime/scope/Variable.java deleted file mode 100644 index 43393a1..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/scope/Variable.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.topchetoeu.jscript.runtime.scope; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; - -public interface Variable { - Value get(Environment env); - default boolean readonly() { return true; } - default boolean set(Environment env, Value val) { return false; } - - default FieldMember toField(boolean configurable, boolean enumerable) { - var self = this; - - return new FieldMember(!readonly(), configurable, enumerable) { - @Override public Value get(Environment env, Value _self) { - return self.get(env); - } - @Override public boolean set(Environment env, Value val, Value _self) { - return self.set(env, val); - } - }; - } -} diff --git a/src/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/java/me/topchetoeu/jscript/runtime/values/Value.java index 9282791..ed90d04 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -64,6 +64,7 @@ public abstract class Value { public static final Key SYNTAX_ERR_PROTO = Key.of(); public static final Key TYPE_ERR_PROTO = Key.of(); public static final Key RANGE_ERR_PROTO = Key.of(); + public static final Key GLOBAL = Key.of(); public static final VoidValue UNDEFINED = new VoidValue("undefined", new StringValue("undefined")); public static final VoidValue NULL = new VoidValue("null", new StringValue("object")); @@ -278,13 +279,31 @@ public abstract class Value { return deleteOwnMember(env, new KeyCache(key)); } - public final Value getMember(Environment env, KeyCache key) { + public final Value getMemberOrNull(Environment env, KeyCache key) { for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { var member = obj.getOwnMember(env, key); if (member != null) return member.get(env, obj); } - return Value.UNDEFINED; + return null; + } + public final Value getMemberOrNull(Environment env, Value key) { + return getMemberOrNull(env, new KeyCache(key)); + } + public final Value getMemberOrNull(Environment env, String key) { + return getMemberOrNull(env, new KeyCache(key)); + } + public final Value getMemberOrNull(Environment env, int key) { + return getMemberOrNull(env, new KeyCache(key)); + } + public final Value getMemberOrNull(Environment env, double key) { + return getMemberOrNull(env, new KeyCache(key)); + } + + public final Value getMember(Environment env, KeyCache key) { + var res = getMemberOrNull(env, key); + if (res != null) return res; + else return Value.UNDEFINED; } public final Value getMember(Environment env, Value key) { return getMember(env, new KeyCache(key)); @@ -330,6 +349,33 @@ public abstract class Value { return setMember(env, new KeyCache(key), val); } + public final boolean setMemberIfExists(Environment env, KeyCache key, Value val) { + for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { + var member = obj.getOwnMember(env, key); + if (member != null) { + if (member.set(env, val, obj)) { + if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); + return true; + } + else return false; + } + } + + return false; + } + public final boolean setMemberIfExists(Environment env, Value key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + public final boolean setMemberIfExists(Environment env, String key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + public final boolean setMemberIfExists(Environment env, int key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + public final boolean setMemberIfExists(Environment env, double key, Value val) { + return setMemberIfExists(env, new KeyCache(key), val); + } + public final boolean hasMember(Environment env, KeyCache key, boolean own) { for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { if (obj.getOwnMember(env, key) != null) return true; @@ -478,13 +524,11 @@ public abstract class Value { } } - @Override - public boolean hasNext() { + @Override public boolean hasNext() { loadNext(); return supplier != null; } - @Override - public Object next() { + @Override public Object next() { loadNext(); var res = value; value = null; @@ -632,4 +676,8 @@ public abstract class Value { return prefix + " internal error " + str.toString(); } } + + public static final ObjectValue global(Environment env) { + return env.initFrom(GLOBAL, () -> new ObjectValue()); + } } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java b/src/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java index b462e5f..d3e9e5c 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java @@ -3,12 +3,11 @@ package me.topchetoeu.jscript.runtime.values.functions; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.Frame; -import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.values.Value; public class CodeFunction extends FunctionValue { public final FunctionBody body; - public final ValueVariable[] captures; + public final Value[][] captures; public Environment env; @Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) { @@ -26,7 +25,7 @@ public class CodeFunction extends FunctionValue { } } - public CodeFunction(Environment env, String name, FunctionBody body, ValueVariable[] captures) { + public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) { super(name, body.argsN); this.captures = captures; this.env = env; diff --git a/src/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java b/src/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java index 23299ce..43a326b 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java @@ -201,12 +201,10 @@ public class ArrayValue extends ObjectValue implements Iterable { return new Iterator<>() { private int i = 0; - @Override - public boolean hasNext() { + @Override public boolean hasNext() { return i < size(); } - @Override - public Value next() { + @Override public Value next() { if (!hasNext()) return null; return get(i++); } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java b/src/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java index 0d4ae0e..c84a295 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.runtime.values.objects; import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; @@ -15,17 +14,18 @@ public class ScopeValue extends ObjectValue { } @Override public Value get(Environment env, Value self) { - return variables[i].get(env); + return variables[i][0]; } @Override public boolean set(Environment env, Value val, Value self) { - return variables[i].set(env, val); + variables[i][0] = val; + return true; } } - public final ValueVariable[] variables; + public final Value[][] variables; - public ScopeValue(ValueVariable[] variables, String[] names) { + public ScopeValue(Value[][] variables, String[] names) { this.variables = variables; for (var i = 0; i < names.length && i < variables.length; i++) { defineOwnMember(Environment.empty(), i, new VariableField(i));