From d0ccf00f1428c985ad8f7d841ac70dc75c08bfbc Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:36:33 +0300 Subject: [PATCH] everything all at once --- .../topchetoeu/jscript/common/Compiler.java | 31 +- .../jscript/common/Instruction.java | 113 +-- .../topchetoeu/jscript/common/RefTracker.java | 23 - .../jscript/common/ResultRunnable.java | 5 - .../jscript/common/events/DataNotifier.java | 31 - .../jscript/common/events/Notifier.java | 19 - .../topchetoeu/jscript/common/json/JSON.java | 37 +- .../jscript/common/mapping/FunctionMap.java | 4 +- .../common/{ => parsing}/Filename.java | 2 +- .../common/{ => parsing}/Location.java | 2 +- .../parsing/ParseRes.java | 9 +- .../jscript/common/parsing/Parsing.java | 383 +++++++++ .../parsing/Source.java | 5 +- .../parsing/SourceLocation.java | 5 +- .../compilation/AssignableStatement.java | 69 +- .../jscript/compilation/CompileResult.java | 2 +- .../compilation/CompoundStatement.java | 12 +- .../topchetoeu/jscript/compilation/ES5.java | 302 +++++++ .../jscript/compilation/Statement.java | 2 +- .../compilation/VariableDeclareStatement.java | 16 +- .../compilation/control/BreakStatement.java | 13 +- .../control/ContinueStatement.java | 13 +- .../compilation/control/DebugStatement.java | 11 +- .../compilation/control/DeleteStatement.java | 13 +- .../compilation/control/DoWhileStatement.java | 15 +- .../compilation/control/ForInStatement.java | 13 +- .../compilation/control/ForOfStatement.java | 13 +- .../compilation/control/ForStatement.java | 17 +- .../compilation/control/IfStatement.java | 19 +- .../compilation/control/ReturnStatement.java | 19 +- .../compilation/control/SwitchStatement.java | 15 +- .../compilation/control/ThrowStatement.java | 15 +- .../compilation/control/TryStatement.java | 8 +- .../compilation/control/WhileStatement.java | 13 +- .../jscript/compilation/parsing/Operator.java | 48 -- .../jscript/compilation/parsing/Parsing.java | 539 ------------ .../jscript/compilation/parsing/RawToken.java | 15 - .../jscript/compilation/parsing/TestRes.java | 45 -- .../jscript/compilation/parsing/Token.java | 58 -- .../compilation/parsing/TokenType.java | 9 - .../compilation/values/ArrayStatement.java | 14 +- .../compilation/values/CallStatement.java | 101 --- .../compilation/values/FunctionStatement.java | 27 +- .../values/GlobalThisStatement.java | 2 +- .../compilation/values/ObjectStatement.java | 15 +- .../values/OperationStatement.java | 110 --- .../compilation/values/RegexStatement.java | 8 +- .../compilation/values/VariableStatement.java | 12 +- .../values/constants/BoolStatement.java | 2 +- .../values/constants/NullStatement.java | 2 +- .../values/constants/NumberStatement.java | 10 +- .../values/constants/StringStatement.java | 8 +- .../values/operations/CallStatement.java | 180 +++++ .../{ => operations}/ChangeStatement.java | 15 +- .../{ => operations}/DiscardStatement.java | 13 +- .../IndexAssignStatement.java | 4 +- .../{ => operations}/IndexStatement.java | 13 +- .../{ => operations}/LazyAndStatement.java | 13 +- .../{ => operations}/LazyOrStatement.java | 13 +- .../values/operations/OperationStatement.java | 234 ++++++ .../{ => operations}/TypeofStatement.java | 14 +- .../VariableAssignStatement.java | 5 +- .../VariableIndexStatement.java | 4 +- .../jscript/runtime/ArgumentsValue.java | 13 + .../me/topchetoeu/jscript/runtime/Engine.java | 20 +- .../topchetoeu/jscript/runtime/EventLoop.java | 19 +- .../me/topchetoeu/jscript/runtime/Frame.java | 18 +- .../jscript/runtime/InstructionRunner.java | 39 +- .../jscript/runtime/JSONConverter.java | 2 +- .../jscript/runtime/SimpleRepl.java | 81 +- .../jscript/runtime/WrapperProvider.java | 13 - .../jscript/runtime/debug/DebugContext.java | 4 +- .../jscript/runtime/debug/DebugHandler.java | 2 +- .../runtime/exceptions/ConvertException.java | 11 - .../runtime/exceptions/EngineException.java | 21 +- .../runtime/exceptions/SyntaxException.java | 2 +- .../jscript/runtime/scope/GlobalScope.java | 12 +- .../jscript/runtime/values/ConvertHint.java | 6 - .../jscript/runtime/values/KeyCache.java | 59 ++ .../jscript/runtime/values/Member.java | 21 +- .../jscript/runtime/values/Value.java | 175 +++- .../jscript/runtime/values/Values.java.old | 765 ------------------ .../runtime/values/functions/Arguments.java | 4 +- .../values/functions/CodeFunction.java | 4 +- .../values/functions/FunctionValue.java | 59 +- .../values/functions/NativeFunction.java | 4 +- .../runtime/values/objects/ArrayValue.java | 41 +- .../runtime/values/objects/ObjectValue.java | 28 +- .../runtime/values/objects/ScopeValue.java | 3 +- .../runtime/values/primitives/BoolValue.java | 2 +- .../values/primitives/NumberValue.java | 66 +- .../values/primitives/PrimitiveValue.java | 7 +- .../values/primitives/StringValue.java | 23 +- .../values/primitives/SymbolValue.java | 14 +- .../runtime/values/primitives/VoidValue.java | 13 +- 95 files changed, 1976 insertions(+), 2377 deletions(-) delete mode 100644 src/java/me/topchetoeu/jscript/common/RefTracker.java delete mode 100644 src/java/me/topchetoeu/jscript/common/ResultRunnable.java delete mode 100644 src/java/me/topchetoeu/jscript/common/events/DataNotifier.java delete mode 100644 src/java/me/topchetoeu/jscript/common/events/Notifier.java rename src/java/me/topchetoeu/jscript/common/{ => parsing}/Filename.java (97%) rename src/java/me/topchetoeu/jscript/common/{ => parsing}/Location.java (95%) rename src/java/me/topchetoeu/jscript/{compilation => common}/parsing/ParseRes.java (90%) create mode 100644 src/java/me/topchetoeu/jscript/common/parsing/Parsing.java rename src/java/me/topchetoeu/jscript/{compilation => common}/parsing/Source.java (92%) rename src/java/me/topchetoeu/jscript/{compilation => common}/parsing/SourceLocation.java (89%) create mode 100644 src/java/me/topchetoeu/jscript/compilation/ES5.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/parsing/Operator.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/parsing/Token.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java delete mode 100644 src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java create mode 100644 src/java/me/topchetoeu/jscript/compilation/values/operations/CallStatement.java rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/ChangeStatement.java (89%) rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/DiscardStatement.java (74%) rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/IndexAssignStatement.java (92%) rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/IndexStatement.java (87%) rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/LazyAndStatement.java (79%) rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/LazyOrStatement.java (78%) create mode 100644 src/java/me/topchetoeu/jscript/compilation/values/operations/OperationStatement.java rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/TypeofStatement.java (76%) rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/VariableAssignStatement.java (86%) rename src/java/me/topchetoeu/jscript/compilation/values/{ => operations}/VariableIndexStatement.java (82%) create mode 100644 src/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java delete mode 100644 src/java/me/topchetoeu/jscript/runtime/WrapperProvider.java delete mode 100644 src/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java delete mode 100644 src/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java create mode 100644 src/java/me/topchetoeu/jscript/runtime/values/KeyCache.java delete mode 100644 src/java/me/topchetoeu/jscript/runtime/values/Values.java.old diff --git a/src/java/me/topchetoeu/jscript/common/Compiler.java b/src/java/me/topchetoeu/jscript/common/Compiler.java index 01d2bb0..812530a 100644 --- a/src/java/me/topchetoeu/jscript/common/Compiler.java +++ b/src/java/me/topchetoeu/jscript/common/Compiler.java @@ -1,5 +1,8 @@ package me.topchetoeu.jscript.common; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Key; @@ -8,18 +11,36 @@ import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; public interface Compiler { + public static final Compiler DEFAULT = (env, filename, raw) -> { + var res = ES5.compile(filename, raw); + var body = res.body(); + DebugContext.get(env).onSource(filename, raw); + registerFunc(env, body, res); + + return body; + }; + public Key KEY = new Key<>(); - public FunctionBody compile(Filename filename, String source); + public FunctionBody compile(Environment env, Filename filename, String source); public static Compiler get(Environment ext) { - return ext.get(KEY, (filename, src) -> { + return ext.get(KEY, (env, filename, src) -> { throw EngineException.ofError("No compiler attached to engine."); }); } - public static CodeFunction compile(Environment env, Filename filename, String raw) { - DebugContext.get(env).onSource(filename, raw); - return new CodeFunction(env, filename.toString(), Compiler.get(env).compile(filename, raw), new ValueVariable[0]); + private static void registerFunc(Environment env, FunctionBody body, CompileResult res) { + var map = res.map(); + + DebugContext.get(env).onFunctionLoad(body, map); + + for (var i = 0; i < body.children.length; i++) { + registerFunc(env, body.children[i], res.children.get(i)); + } + } + + 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]); } } diff --git a/src/java/me/topchetoeu/jscript/common/Instruction.java b/src/java/me/topchetoeu/jscript/common/Instruction.java index 9460c39..edc0652 100644 --- a/src/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/java/me/topchetoeu/jscript/common/Instruction.java @@ -9,48 +9,46 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class Instruction { public static enum Type { - NOP(0), - RETURN(1), - THROW(2), - THROW_SYNTAX(3), - DELETE(4), - TRY_START(5), - TRY_END(6), + NOP(0x00), + RETURN(0x01), + THROW(0x02), + THROW_SYNTAX(0x03), + DELETE(0x04), + TRY_START(0x05), + TRY_END(0x06), - CALL(7), - CALL_NEW(8), - JMP_IF(9), - JMP_IFN(10), - JMP(11), + CALL(0x10), + CALL_MEMBER(0x11), + CALL_NEW(0x12), + JMP_IF(0x13), + JMP_IFN(0x14), + JMP(0x15), - PUSH_UNDEFINED(12), - PUSH_NULL(13), - PUSH_BOOL(14), - PUSH_NUMBER(15), - PUSH_STRING(16), + PUSH_UNDEFINED(0x20), + PUSH_NULL(0x21), + PUSH_BOOL(0x22), + PUSH_NUMBER(0x23), + PUSH_STRING(0x24), + DUP(0x25), + DISCARD(0x26), - LOAD_VAR(17), - LOAD_MEMBER(18), - LOAD_GLOB(20), + LOAD_FUNC(0x30), + LOAD_ARR(0x31), + LOAD_OBJ(0x32), + STORE_SELF_FUNC(0x33), + LOAD_REGEX(0x34), - LOAD_FUNC(21), - LOAD_ARR(22), - LOAD_OBJ(23), - STORE_SELF_FUNC(24), - LOAD_REGEX(25), + LOAD_VAR(0x40), + LOAD_MEMBER(0x41), + LOAD_GLOB(0x42), + STORE_VAR(0x43), + STORE_MEMBER(0x44), - DUP(26), - - STORE_VAR(27), - STORE_MEMBER(28), - DISCARD(29), - - MAKE_VAR(30), - DEF_PROP(31), - KEYS(32), - - TYPEOF(33), - OPERATION(34); + MAKE_VAR(0x50), + DEF_PROP(0x51), + KEYS(0x52), + TYPEOF(0x53), + OPERATION(0x54); private static final HashMap types = new HashMap<>(); public final int numeric; @@ -125,8 +123,12 @@ public class Instruction { writer.writeByte(rawType); switch (type) { - case CALL: writer.writeInt(get(0)); break; - case CALL_NEW: writer.writeInt(get(0)); break; + 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; @@ -140,6 +142,7 @@ public class Instruction { } writer.writeInt(get(0)); + writer.writeUTF(get(0)); break; } case LOAD_REGEX: writer.writeUTF(get(0)); break; @@ -174,8 +177,9 @@ public class Instruction { var flag = (rawType & 128) != 0; switch (type) { - case CALL: return call(stream.readInt()); - case CALL_NEW: return callNew(stream.readInt()); + 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(); @@ -192,7 +196,7 @@ public class Instruction { captures[i] = stream.readInt(); } - return loadFunc(stream.readInt(), captures); + return loadFunc(stream.readInt(), stream.readUTF(), captures); } case LOAD_GLOB: return loadGlob(); case LOAD_MEMBER: return loadMember(); @@ -251,11 +255,23 @@ public class Instruction { return new Instruction(Type.NOP, params); } + public static Instruction call(int argn, String name) { + return new Instruction(Type.CALL, argn, name); + } public static Instruction call(int argn) { - return new Instruction(Type.CALL, argn); + return call(argn, ""); + } + public static Instruction callMember(int argn, String name) { + return new Instruction(Type.CALL_MEMBER, argn, name); + } + public static Instruction callMember(int argn) { + return new Instruction(Type.CALL_MEMBER, argn, ""); + } + public static Instruction callNew(int argn, String name) { + return new Instruction(Type.CALL_NEW, argn, name); } public static Instruction callNew(int argn) { - return new Instruction(Type.CALL_NEW, argn); + return new Instruction(Type.CALL_NEW, argn, ""); } public static Instruction jmp(int offset) { return new Instruction(Type.JMP, offset); @@ -299,10 +315,13 @@ public class Instruction { public static Instruction loadRegex(String pattern, String flags) { return new Instruction(Type.LOAD_REGEX, pattern, flags); } - public static Instruction loadFunc(int id, int[] captures) { - var args = new Object[1 + captures.length]; + public static Instruction loadFunc(int id, String name, int[] captures) { + if (name == null) name = ""; + + var args = new Object[2 + captures.length]; args[0] = id; - for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i]; + args[1] = name; + for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i]; return new Instruction(Type.LOAD_FUNC, args); } public static Instruction loadObj() { diff --git a/src/java/me/topchetoeu/jscript/common/RefTracker.java b/src/java/me/topchetoeu/jscript/common/RefTracker.java deleted file mode 100644 index 249605f..0000000 --- a/src/java/me/topchetoeu/jscript/common/RefTracker.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.topchetoeu.jscript.common; - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; - -public class RefTracker { - public static void onDestroy(Object obj, Runnable runnable) { - var queue = new ReferenceQueue<>(); - var ref = new WeakReference<>(obj, queue); - obj = null; - - var th = new Thread(() -> { - try { - queue.remove(); - ref.get(); - runnable.run(); - } - catch (InterruptedException e) { return; } - }); - th.setDaemon(true); - th.start(); - } -} diff --git a/src/java/me/topchetoeu/jscript/common/ResultRunnable.java b/src/java/me/topchetoeu/jscript/common/ResultRunnable.java deleted file mode 100644 index 7bfc608..0000000 --- a/src/java/me/topchetoeu/jscript/common/ResultRunnable.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.common; - -public interface ResultRunnable { - T run(); -} diff --git a/src/java/me/topchetoeu/jscript/common/events/DataNotifier.java b/src/java/me/topchetoeu/jscript/common/events/DataNotifier.java deleted file mode 100644 index b498b85..0000000 --- a/src/java/me/topchetoeu/jscript/common/events/DataNotifier.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.topchetoeu.jscript.common.events; - -public class DataNotifier { - private Notifier notifier = new Notifier(); - private boolean isErr; - private T val; - private RuntimeException err; - - public void error(RuntimeException t) { - err = t; - isErr = true; - notifier.next(); - } - public void next(T val) { - this.val = val; - isErr = false; - notifier.next(); - } - public T await() { - notifier.await(); - - try { - if (isErr) throw err; - else return val; - } - finally { - this.err = null; - this.val = null; - } - } -} diff --git a/src/java/me/topchetoeu/jscript/common/events/Notifier.java b/src/java/me/topchetoeu/jscript/common/events/Notifier.java deleted file mode 100644 index eb6ad4d..0000000 --- a/src/java/me/topchetoeu/jscript/common/events/Notifier.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.common.events; - -import me.topchetoeu.jscript.runtime.exceptions.InterruptException; - -public class Notifier { - private boolean ok = false; - - public synchronized void next() { - ok = true; - notifyAll(); - } - public synchronized void await() { - try { - while (!ok) wait(); - ok = false; - } - catch (InterruptedException e) { throw new InterruptException(e); } - } -} diff --git a/src/java/me/topchetoeu/jscript/common/json/JSON.java b/src/java/me/topchetoeu/jscript/common/json/JSON.java index 66d6ed8..74434ba 100644 --- a/src/java/me/topchetoeu/jscript/common/json/JSON.java +++ b/src/java/me/topchetoeu/jscript/common/json/JSON.java @@ -1,12 +1,13 @@ package me.topchetoeu.jscript.common.json; +import java.math.BigDecimal; import java.util.Map; import java.util.stream.Collectors; -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; +import me.topchetoeu.jscript.common.parsing.Filename; +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.runtime.exceptions.SyntaxException; public class JSON { @@ -16,23 +17,9 @@ public class JSON { return ParseRes.res(JSONElement.string(res.result), res.n); } public static ParseRes parseNumber(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (src.is(i + n, "-")) { - n++; - - var res = Parsing.parseNumber(src, i); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a number after minus"); - n += res.n; - - return ParseRes.res(JSONElement.number(-res.result), n); - } - else { - var res = Parsing.parseNumber(src, i + n).addN(n); - if (!res.isSuccess()) return res.chainError(); - n += res.n; - return ParseRes.res(JSONElement.number(res.result), n); - } + var res = Parsing.parseNumber(src, i, true); + if (!res.isSuccess()) return res.chainError(); + else return ParseRes.res(JSONElement.number(res.result), res.n); } public static ParseRes parseLiteral(Source src, int i) { var id = Parsing.parseIdentifier(src, i); @@ -121,7 +108,13 @@ public class JSON { } public static String stringify(JSONElement el) { - if (el.isNumber()) return Double.toString(el.number()); + if (el.isNumber()) { + var d = el.number(); + if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; + if (d == Double.POSITIVE_INFINITY) return "Infinity"; + if (Double.isNaN(d)) return "NaN"; + return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString(); + } if (el.isBoolean()) return el.bool() ? "true" : "false"; if (el.isNull()) return "null"; if (el.isString()) { diff --git a/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java b/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java index d0d5d47..5d54895 100644 --- a/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java +++ b/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java @@ -11,9 +11,9 @@ import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Collectors; -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.common.Location; 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; public class FunctionMap { diff --git a/src/java/me/topchetoeu/jscript/common/Filename.java b/src/java/me/topchetoeu/jscript/common/parsing/Filename.java similarity index 97% rename from src/java/me/topchetoeu/jscript/common/Filename.java rename to src/java/me/topchetoeu/jscript/common/parsing/Filename.java index 7ced6cb..8e57751 100644 --- a/src/java/me/topchetoeu/jscript/common/Filename.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/Filename.java @@ -1,4 +1,4 @@ -package me.topchetoeu.jscript.common; +package me.topchetoeu.jscript.common.parsing; import java.io.File; import java.nio.file.Path; diff --git a/src/java/me/topchetoeu/jscript/common/Location.java b/src/java/me/topchetoeu/jscript/common/parsing/Location.java similarity index 95% rename from src/java/me/topchetoeu/jscript/common/Location.java rename to src/java/me/topchetoeu/jscript/common/parsing/Location.java index 2118ee3..1ef86e8 100644 --- a/src/java/me/topchetoeu/jscript/common/Location.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/Location.java @@ -1,4 +1,4 @@ -package me.topchetoeu.jscript.common; +package me.topchetoeu.jscript.common.parsing; import java.util.ArrayList; import java.util.Objects; diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java b/src/java/me/topchetoeu/jscript/common/parsing/ParseRes.java similarity index 90% rename from src/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java rename to src/java/me/topchetoeu/jscript/common/parsing/ParseRes.java index ae539aa..d50d289 100644 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/ParseRes.java @@ -1,6 +1,4 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import me.topchetoeu.jscript.common.Location; +package me.topchetoeu.jscript.common.parsing; public class ParseRes { public static interface Parser { @@ -41,11 +39,6 @@ public class ParseRes { if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); return new ParseRes<>(state, error, null, 0); } - public TestRes toTest() { - if (isSuccess()) return TestRes.res(n); - else if (isError()) return TestRes.error(null, error); - else return TestRes.failed(); - } public boolean isSuccess() { return state.isSuccess(); } public boolean isFailed() { return state.isFailed(); } diff --git a/src/java/me/topchetoeu/jscript/common/parsing/Parsing.java b/src/java/me/topchetoeu/jscript/common/parsing/Parsing.java new file mode 100644 index 0000000..a0a5f27 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/common/parsing/Parsing.java @@ -0,0 +1,383 @@ +package me.topchetoeu.jscript.common.parsing; + +import me.topchetoeu.jscript.compilation.values.constants.NumberStatement; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +// TODO: this has to be rewritten +// @SourceFile +public class Parsing { + public static boolean isDigit(Character c) { + return c != null && c >= '0' && c <= '9'; + } + public static boolean isAny(char c, String alphabet) { + return alphabet.contains(Character.toString(c)); + } + + public static int fromHex(char c) { + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= '0' && c <= '9') return c - '0'; + return -1; + } + + public static int skipEmpty(Source src, int i) { + int n = 0; + + while (n < src.size() && src.is(i + n, Character::isWhitespace)) n++; + + return n; + } + + public static ParseRes parseChar(Source src, int i) { + int n = 0; + + if (src.is(i + n, '\\')) { + n++; + char c = src.at(i + n++); + + if (c == 'b') return ParseRes.res('\b', n); + else if (c == 't') return ParseRes.res('\t', n); + else if (c == 'n') return ParseRes.res('\n', n); + else if (c == 'f') return ParseRes.res('\f', n); + else if (c == 'r') return ParseRes.res('\r', n); + else if (c == '0') { + if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); + else return ParseRes.res('\0', n); + } + else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); + else if (c == 'x') { + var newC = 0; + + for (var j = 0; j < 2; j++) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence."); + + int val = fromHex(src.at(i + n)); + if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence."); + n++; + + newC = (newC << 4) | val; + } + + return ParseRes.res((char)newC, n); + } + else if (c == 'u') { + var newC = 0; + + for (var j = 0; j < 4; j++) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence"); + + int val = fromHex(src.at(i + n)); + if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence"); + n++; + + newC = (newC << 4) | val; + } + + return ParseRes.res((char)newC, n); + } + else if (c == '\n') return ParseRes.res(null, n); + } + + return ParseRes.res(src.at(i + n), n + 1); + } + + public static ParseRes parseIdentifier(Source src, int i) { + var n = skipEmpty(src, i); + var res = new StringBuilder(); + var first = true; + + while (true) { + if (i + n > src.size()) break; + char c = src.at(i + n, '\0'); + + if (first && Parsing.isDigit(c)) break; + if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; + res.append(c); + n++; + first = false; + } + + if (res.length() <= 0) return ParseRes.failed(); + else return ParseRes.res(res.toString(), n); + } + public static ParseRes parseIdentifier(Source src, int i, String test) { + var n = skipEmpty(src, i); + var res = new StringBuilder(); + var first = true; + + while (true) { + if (i + n > src.size()) break; + char c = src.at(i + n, '\0'); + + if (first && Parsing.isDigit(c)) break; + if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; + res.append(c); + n++; + first = false; + } + + if (res.length() <= 0) return ParseRes.failed(); + else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n); + else return ParseRes.failed(); + } + public static boolean isIdentifier(Source src, int i, String test) { + return parseIdentifier(src, i, test).isSuccess(); + } + + public static ParseRes parseOperator(Source src, int i, String op) { + var n = skipEmpty(src, i); + + if (src.is(i + n, op)) return ParseRes.res(op, n + op.length()); + else return ParseRes.failed(); + } + + private static ParseRes parseHex(Source src, int i) { + int n = 0; + double res = 0; + + while (true) { + int digit = Parsing.fromHex(src.at(i + n, '\0')); + if (digit < 0) { + if (n <= 0) return ParseRes.failed(); + else return ParseRes.res(res, n); + } + n++; + + res *= 16; + res += digit; + } + } + private static ParseRes parseOct(Source src, int i) { + int n = 0; + double res = 0; + + while (true) { + int digit = src.at(i + n, '\0') - '0'; + if (digit < 0 || digit > 9) break; + if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit); + + if (digit < 0) { + if (n <= 0) return ParseRes.failed(); + else return ParseRes.res(res, n); + } + n++; + + res *= 8; + res += digit; + } + + return ParseRes.res(res, n); + } + + public static ParseRes parseString(Source src, int i) { + var n = skipEmpty(src, i); + + char quote; + + if (src.is(i + n, '\'')) quote = '\''; + else if (src.is(i + n, '"')) quote = '"'; + else return ParseRes.failed(); + n++; + + var res = new StringBuilder(); + + while (true) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal"); + if (src.is(i + n, quote)) { + n++; + return ParseRes.res(res.toString(), n); + } + + var charRes = parseChar(src, i + n); + if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character"); + n += charRes.n; + + if (charRes.result != null) res.append(charRes.result); + } + } + public static ParseRes parseNumber(Source src, int i, boolean withMinus) { + var n = skipEmpty(src, i); + + double whole = 0; + double fract = 0; + long exponent = 0; + boolean parsedAny = false; + boolean negative = false; + + if (withMinus && src.is(i + n, "-")) { + negative = true; + n++; + } + + if (src.is(i + n, "0x") || src.is(i + n, "0X")) { + n += 2; + + var res = parseHex(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); + n += res.n; + + if (negative) return ParseRes.res(-res.result, n); + else return ParseRes.res(res.result, n); + } + else if (src.is(i + n, "0o") || src.is(i + n, "0O")) { + n += 2; + + var res = parseOct(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal"); + n += res.n; + + if (negative) return ParseRes.res(-res.result, n); + else return ParseRes.res(res.result, n); + } + else if (src.is(i + n, '0')) { + n++; + parsedAny = true; + if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed"); + } + + while (src.is(i + n, Parsing::isDigit)) { + parsedAny = true; + whole *= 10; + whole += src.at(i + n++) - '0'; + } + + if (src.is(i + n, '.')) { + parsedAny = true; + n++; + + while (src.is(i + n, Parsing::isDigit)) { + fract += src.at(i + n++) - '0'; + fract /= 10; + } + } + + if (src.is(i + n, 'e') || src.is(i + n, 'E')) { + n++; + parsedAny = true; + boolean expNegative = false; + boolean parsedE = false; + + if (src.is(i + n, '-')) { + expNegative = true; + n++; + } + else if (src.is(i + n, '+')) n++; + + while (src.is(i + n, Parsing::isDigit)) { + parsedE = true; + exponent *= 10; + + if (expNegative) exponent -= src.at(i + n++) - '0'; + else exponent += src.at(i + n++) - '0'; + } + + if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); + } + + if (!parsedAny) { + if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); + return ParseRes.failed(); + } + else if (negative) return ParseRes.res(-(whole + fract) * NumberStatement.power(10, exponent), n); + else return ParseRes.res((whole + fract) * NumberStatement.power(10, exponent), n); + } + public static ParseRes parseFloat(Source src, int i, boolean withMinus) { + var n = skipEmpty(src, i); + + double whole = 0; + double fract = 0; + long exponent = 0; + boolean parsedAny = false; + boolean negative = false; + + if (withMinus && src.is(i + n, "-")) { + negative = true; + n++; + } + + while (src.is(i + n, Parsing::isDigit)) { + parsedAny = true; + whole *= 10; + whole += src.at(i + n++) - '0'; + } + + if (src.is(i + n, '.')) { + parsedAny = true; + n++; + + while (src.is(i + n, Parsing::isDigit)) { + fract += src.at(i + n++) - '0'; + fract /= 10; + } + } + + if (src.is(i + n, 'e') || src.is(i + n, 'E')) { + n++; + parsedAny = true; + boolean expNegative = false; + boolean parsedE = false; + + if (src.is(i + n, '-')) { + expNegative = true; + n++; + } + else if (src.is(i + n, '+')) n++; + + while (src.is(i + n, Parsing::isDigit)) { + parsedE = true; + exponent *= 10; + + if (expNegative) exponent -= src.at(i + n++) - '0'; + else exponent += src.at(i + n++) - '0'; + } + + if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); + } + + if (!parsedAny) { + if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); + return ParseRes.failed(); + } + else if (negative) return ParseRes.res(-(whole + fract) * NumberStatement.power(10, exponent), n); + else return ParseRes.res((whole + fract) * NumberStatement.power(10, exponent), n); + } + public static ParseRes parseInt(Source src, int i, String alphabet, boolean withMinus) { + var n = skipEmpty(src, i); + + double result = 0; + boolean parsedAny = false; + boolean negative = false; + + if (withMinus && src.is(i + n, "-")) { + negative = true; + n++; + } + + if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) { + n += 2; + + var res = parseHex(src, i); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); + n += res.n; + + if (negative) return ParseRes.res(-res.result, n); + else return ParseRes.res(res.result, n); + } + + while (true) { + var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n))); + if (digit < 0) break; + + parsedAny = true; + result += digit; + result *= alphabet.length(); + } + + if (!parsedAny) { + if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); + return ParseRes.failed(); + } + else if (negative) return ParseRes.res(-result, n); + else return ParseRes.res(-result, n); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/Source.java b/src/java/me/topchetoeu/jscript/common/parsing/Source.java similarity index 92% rename from src/java/me/topchetoeu/jscript/compilation/parsing/Source.java rename to src/java/me/topchetoeu/jscript/common/parsing/Source.java index bcfc56e..6fa9095 100644 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/Source.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/Source.java @@ -1,10 +1,7 @@ -package me.topchetoeu.jscript.compilation.parsing; +package me.topchetoeu.jscript.common.parsing; import java.util.function.Predicate; -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.common.Location; - public class Source { public final Filename filename; public final String src; diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/SourceLocation.java b/src/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java similarity index 89% rename from src/java/me/topchetoeu/jscript/compilation/parsing/SourceLocation.java rename to src/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java index 8eafffa..a2050d4 100644 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/SourceLocation.java +++ b/src/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java @@ -1,7 +1,4 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.common.Location; +package me.topchetoeu.jscript.common.parsing; public class SourceLocation extends Location { private int[] lineStarts; diff --git a/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java b/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java index 53a557a..be19a2b 100644 --- a/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java @@ -1,11 +1,7 @@ package me.topchetoeu.jscript.compilation; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.compilation.parsing.Operator; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; +import me.topchetoeu.jscript.common.parsing.Location; public abstract class AssignableStatement extends Statement { public abstract Statement toAssign(Statement val, Operation operation); @@ -13,38 +9,53 @@ public abstract class AssignableStatement extends Statement { protected AssignableStatement(Location loc) { super(loc); } + // private static final Map operations = Map.ofEntries( + // Map.entry("*=", Operation.MULTIPLY), + // Map.entry("/=", Operation.DIVIDE), + // Map.entry("%=", Operation.MODULO), + // Map.entry("-=", Operation.SUBTRACT), + // Map.entry("+=", Operation.ADD), + // Map.entry(">>=", Operation.SHIFT_RIGHT), + // Map.entry("<<=", Operation.SHIFT_LEFT), + // Map.entry(">>>=", Operation.USHIFT_RIGHT), + // Map.entry("&=", Operation.AND), + // Map.entry("^=", Operation.XOR), + // Map.entry("|=", Operation.OR) + // ); + // private static final List operatorsByLength = operations.keySet().stream().sorted().collect(Collectors.toList()); - public static ParseRes parse(Source src, int i, Statement prev, int precedence) { - if (precedence > 2) return ParseRes.failed(); - var n = Parsing.skipEmpty(src, i); + // public static ParseRes parse(Source src, int i, Statement prev, int precedence) { + // if (precedence > 2) return ParseRes.failed(); - for (var op : Operator.opsByLength) { - if (!op.assignable || !src.is(i + n, op.readable + "=")) continue; - n += op.readable.length() + 1; + // var n = Parsing.skipEmpty(src, i); - if (!(prev instanceof AssignableStatement)) { - return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator"); - } + // for (var op : operatorsByLength) { + // if (!src.is(i + n, op)) continue; + // n += op.length() + 1; - var res = Parsing.parseValue(src, i + n, 2); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s=' operator", op.readable)); - n += res.n; + // if (!(prev instanceof AssignableStatement)) { + // return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator"); + // } - return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, op.operation), n); - } + // var res = Parsing.parseValue(src, i + n, 2); + // if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s=' operator", op)); + // n += res.n; - if (!src.is(i + n, "=")) return ParseRes.failed(); - n++; + // return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operations.get(op)), n); + // } - if (!(prev instanceof AssignableStatement)) { - return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator"); - } + // if (!src.is(i + n, "=")) return ParseRes.failed(); + // n++; - var res = Parsing.parseValue(src, i + n, 2); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '=' operator"); - n += res.n; + // if (!(prev instanceof AssignableStatement)) { + // return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator"); + // } - return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, null), n); - } + // var res = Parsing.parseValue(src, i + n, 2); + // if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '=' operator"); + // n += res.n; + + // return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, null), n); + // } } diff --git a/src/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/java/me/topchetoeu/jscript/compilation/CompileResult.java index 00f6db7..f65e663 100644 --- a/src/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -6,10 +6,10 @@ import java.util.Vector; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; 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; public class CompileResult { diff --git a/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java index 5adf30c..f2094c1 100644 --- a/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -5,11 +5,11 @@ import java.util.List; import java.util.Vector; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; +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.FunctionStatement; public class CompoundStatement extends Statement { @@ -75,7 +75,7 @@ public class CompoundStatement extends Statement { if (!src.is(i + n, ",")) return ParseRes.failed(); n++; - var res = Parsing.parseValue(src, i + n, 2); + var res = ES5.parseExpression(src, i + n, 2); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the comma"); n += res.n; @@ -102,7 +102,7 @@ public class CompoundStatement extends Statement { continue; } - var res = Parsing.parseStatement(src, i + n); + var res = ES5.parseStatement(src, i + n); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement"); n += res.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/ES5.java b/src/java/me/topchetoeu/jscript/compilation/ES5.java new file mode 100644 index 0000000..b68d46c --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/ES5.java @@ -0,0 +1,302 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Filename; +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.control.BreakStatement; +import me.topchetoeu.jscript.compilation.control.ContinueStatement; +import me.topchetoeu.jscript.compilation.control.DebugStatement; +import me.topchetoeu.jscript.compilation.control.DeleteStatement; +import me.topchetoeu.jscript.compilation.control.DoWhileStatement; +import me.topchetoeu.jscript.compilation.control.ForInStatement; +import me.topchetoeu.jscript.compilation.control.ForOfStatement; +import me.topchetoeu.jscript.compilation.control.ForStatement; +import me.topchetoeu.jscript.compilation.control.IfStatement; +import me.topchetoeu.jscript.compilation.control.ReturnStatement; +import me.topchetoeu.jscript.compilation.control.SwitchStatement; +import me.topchetoeu.jscript.compilation.control.ThrowStatement; +import me.topchetoeu.jscript.compilation.control.TryStatement; +import me.topchetoeu.jscript.compilation.control.WhileStatement; +import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; +import me.topchetoeu.jscript.compilation.values.ArrayStatement; +import me.topchetoeu.jscript.compilation.values.FunctionStatement; +import me.topchetoeu.jscript.compilation.values.GlobalThisStatement; +import me.topchetoeu.jscript.compilation.values.ObjectStatement; +import me.topchetoeu.jscript.compilation.values.RegexStatement; +import me.topchetoeu.jscript.compilation.values.VariableStatement; +import me.topchetoeu.jscript.compilation.values.constants.BoolStatement; +import me.topchetoeu.jscript.compilation.values.constants.NullStatement; +import me.topchetoeu.jscript.compilation.values.constants.NumberStatement; +import me.topchetoeu.jscript.compilation.values.constants.StringStatement; +import me.topchetoeu.jscript.compilation.values.operations.CallStatement; +import me.topchetoeu.jscript.compilation.values.operations.ChangeStatement; +import me.topchetoeu.jscript.compilation.values.operations.DiscardStatement; +import me.topchetoeu.jscript.compilation.values.operations.IndexStatement; +import me.topchetoeu.jscript.compilation.values.operations.OperationStatement; +import me.topchetoeu.jscript.compilation.values.operations.TypeofStatement; +import me.topchetoeu.jscript.compilation.values.operations.VariableIndexStatement; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class ES5 { + static final Set reserved = Set.of( + "true", "false", "void", "null", "this", "if", "else", "try", "catch", + "finally", "for", "do", "while", "switch", "case", "default", "new", + "function", "var", "return", "throw", "typeof", "delete", "break", + "continue", "debugger", "implements", "interface", "package", "private", + "protected", "public", "static" + ); + + public static ParseRes parseParens(Source src, int i) { + int n = 0; + + var openParen = Parsing.parseOperator(src, i + n, "("); + if (!openParen.isSuccess()) return openParen.chainError(); + n += openParen.n; + + var res = ES5.parseExpression(src, i + n, 0); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens"); + n += res.n; + + var closeParen = Parsing.parseOperator(src, i + n, ")"); + if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren"); + n += closeParen.n; + + return ParseRes.res(res.result, n); + } + + public static ParseRes parseSimple(Source src, int i, boolean statement) { + return ParseRes.first(src, i, + (a, b) -> statement ? ParseRes.failed() : ObjectStatement.parse(a, b), + (a, b) -> statement ? ParseRes.failed() : FunctionStatement.parseFunction(a, b, false), + ES5::parseLiteral, + StringStatement::parse, + RegexStatement::parse, + NumberStatement::parse, + ChangeStatement::parsePrefixDecrease, + ChangeStatement::parsePrefixIncrease, + OperationStatement::parsePrefix, + ArrayStatement::parse, + ES5::parseParens, + CallStatement::parseNew, + TypeofStatement::parse, + DiscardStatement::parse, + DeleteStatement::parse, + VariableStatement::parse + ); + } + + public static ParseRes parseLiteral(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var id = Parsing.parseIdentifier(src, i); + if (!id.isSuccess()) return id.chainError(); + n += id.n; + + if (id.result.equals("true")) return ParseRes.res(new BoolStatement(loc, true), n); + if (id.result.equals("false")) return ParseRes.res(new BoolStatement(loc, false), n); + if (id.result.equals("undefined")) return ParseRes.res(new DiscardStatement(loc, null), n); + if (id.result.equals("null")) return ParseRes.res(new NullStatement(loc), n); + if (id.result.equals("this")) return ParseRes.res(new VariableIndexStatement(loc, 0), n); + if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexStatement(loc, 1), n); + if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisStatement(loc), n); + + return ParseRes.failed(); + } + + public static ParseRes parseExpression(Source src, int i, int precedence, boolean statement) { + var n = Parsing.skipEmpty(src, i); + Statement prev = null; + + while (true) { + if (prev == null) { + var res = parseSimple(src, i + n, statement); + if (res.isSuccess()) { + n += res.n; + prev = res.result; + } + else if (res.isError()) return res.chainError(); + else break; + } + else { + var _prev = prev; + ParseRes res = ParseRes.first(src, i + n, + (s, j) -> OperationStatement.parseInstanceof(s, j, _prev, precedence), + (s, j) -> OperationStatement.parseIn(s, j, _prev, precedence), + (s, j) -> ChangeStatement.parsePostfixIncrease(s, j, _prev, precedence), + (s, j) -> ChangeStatement.parsePostfixDecrease(s, j, _prev, precedence), + (s, j) -> OperationStatement.parseOperator(s, j, _prev, precedence), + (s, j) -> IfStatement.parseTernary(s, j, _prev, precedence), + (s, j) -> IndexStatement.parseMember(s, j, _prev, precedence), + (s, j) -> IndexStatement.parseIndex(s, j, _prev, precedence), + (s, j) -> CallStatement.parseCall(s, j, _prev, precedence), + (s, j) -> CompoundStatement.parseComma(s, j, _prev, precedence) + ); + + if (res.isSuccess()) { + n += res.n; + prev = res.result; + continue; + } + else if (res.isError()) return res.chainError(); + + break; + } + } + + if (prev == null) return ParseRes.failed(); + else return ParseRes.res(prev, n); + } + + public static ParseRes parseExpression(Source src, int i, int precedence) { + return parseExpression(src, i, precedence, false); + } + + public static ParseRes parseExpressionStatement(Source src, int i) { + var res = parseExpression(src, i, 0, true); + if (!res.isSuccess()) return res.chainError(); + + var end = ES5.parseStatementEnd(src, i + res.n); + if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement"); + + return res.addN(end.n); + } + + public static ParseRes parseStatement(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (src.is(i + n, ";")) return ParseRes.res(new DiscardStatement(src.loc(i+ n), null), n + 1); + if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); + + ParseRes res = ParseRes.first(src, i + n, + VariableDeclareStatement::parse, + ReturnStatement::parse, + ThrowStatement::parse, + ContinueStatement::parse, + BreakStatement::parse, + DebugStatement::parse, + IfStatement::parse, + WhileStatement::parse, + SwitchStatement::parse, + ForStatement::parse, + ForInStatement::parse, + ForOfStatement::parse, + DoWhileStatement::parse, + TryStatement::parse, + CompoundStatement::parse, + (s, j) -> FunctionStatement.parseFunction(s, j, true), + ES5::parseExpressionStatement + ); + return res.addN(n); + } + + public static Statement[] parse(Filename filename, String raw) { + var src = new Source(filename, raw); + var list = new ArrayList(); + int i = 0; + + while (true) { + if (i >= src.size()) break; + + var res = parseStatement(src, i); + + if (res.isError()) throw new SyntaxException(src.loc(i), res.error); + else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax"); + + i += res.n; + + list.add(res.result); + } + + return list.toArray(Statement[]::new); + } + + public static ParseRes parseStatementEnd(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + if (i >= src.size()) return ParseRes.res(true, n + 1); + + for (var j = i; j < i + n; j++) { + if (src.is(j, '\n')) return ParseRes.res(true, n); + } + + if (src.is(i + n, ';')) return ParseRes.res(true, n + 1); + if (src.is(i + n, '}')) return ParseRes.res(true, n); + + return ParseRes.failed(); + } + + public static boolean checkVarName(String name) { + return !ES5.reserved.contains(name); + } + + public static ParseRes> parseParamList(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + var openParen = Parsing.parseOperator(src, i + n, "("); + if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list."); + n += openParen.n; + + var args = new ArrayList(); + + var closeParen = Parsing.parseOperator(src, i + n, ")"); + n += closeParen.n; + + if (!closeParen.isSuccess()) { + while (true) { + var argRes = Parsing.parseIdentifier(src, i + n); + if (argRes.isSuccess()) { + args.add(argRes.result); + n += argRes.n; + n += Parsing.skipEmpty(src, i); + + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + } + if (src.is(i + n, ")")) { + n++; + break; + } + } + else return ParseRes.error(src.loc(i + n), "Expected an argument, or a closing brace."); + } + } + + return ParseRes.res(args, n); + } + + public static CompileResult compile(Statement ...statements) { + var target = new CompileResult(new LocalScopeRecord()); + var stm = new CompoundStatement(null, true, statements); + + target.scope.define("this"); + target.scope.define("arguments"); + + try { + stm.compile(target, true); + FunctionStatement.checkBreakAndCont(target, 0); + } + catch (SyntaxException e) { + target = new CompileResult(new LocalScopeRecord()); + + target.scope.define("this"); + target.scope.define("arguments"); + + target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); + } + + target.add(Instruction.ret()).setLocation(stm.loc()); + + return target; + } + + public static CompileResult compile(Filename filename, String raw) { + return ES5.compile(ES5.parse(filename, raw)); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/Statement.java b/src/java/me/topchetoeu/jscript/compilation/Statement.java index de9d23b..c9c14c3 100644 --- a/src/java/me/topchetoeu/jscript/compilation/Statement.java +++ b/src/java/me/topchetoeu/jscript/compilation/Statement.java @@ -1,7 +1,7 @@ package me.topchetoeu.jscript.compilation; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; public abstract class Statement { private Location _loc; diff --git a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java index dd10c58..ef5b7aa 100644 --- a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java @@ -4,11 +4,11 @@ import java.util.ArrayList; import java.util.List; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; +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.FunctionStatement; public class VariableDeclareStatement extends Statement { @@ -63,7 +63,7 @@ public class VariableDeclareStatement extends Statement { var res = new ArrayList(); - var end = Parsing.parseStatementEnd(src, i + n); + var end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new VariableDeclareStatement(loc, res), n); @@ -75,7 +75,7 @@ public class VariableDeclareStatement extends Statement { if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name"); n += name.n; - if (!Parsing.checkVarName(name.result)) { + if (!ES5.checkVarName(name.result)) { return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result)); } @@ -85,7 +85,7 @@ public class VariableDeclareStatement extends Statement { if (src.is(i + n, "=")) { n++; - var valRes = Parsing.parseValue(src, i + n, 2); + var valRes = ES5.parseExpression(src, i + n, 2); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='"); n += valRes.n; @@ -100,7 +100,7 @@ public class VariableDeclareStatement extends Statement { continue; } - end = Parsing.parseStatementEnd(src, i + n); + end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java index 5a62233..7cf3cad 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java @@ -1,12 +1,13 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class BreakStatement extends Statement { public final String label; @@ -28,7 +29,7 @@ public class BreakStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed(); n += 5; - var end = Parsing.parseStatementEnd(src, i + n); + var end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new BreakStatement(loc, null), n); @@ -38,7 +39,7 @@ public class BreakStatement extends Statement { if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); n += label.n; - end = Parsing.parseStatementEnd(src, i + n); + end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new BreakStatement(loc, label.result), n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java index 281e11c..8403f33 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java @@ -1,12 +1,13 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class ContinueStatement extends Statement { public final String label; @@ -28,7 +29,7 @@ public class ContinueStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed(); n += 8; - var end = Parsing.parseStatementEnd(src, i + n); + var end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new ContinueStatement(loc, null), n); @@ -38,7 +39,7 @@ public class ContinueStatement extends Statement { if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); n += label.n; - end = Parsing.parseStatementEnd(src, i + n); + end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new ContinueStatement(loc, label.result), n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java index 4150118..b2fb08e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java @@ -1,12 +1,13 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class DebugStatement extends Statement { @Override public void compile(CompileResult target, boolean pollute) { @@ -25,7 +26,7 @@ public class DebugStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed(); n += 8; - var end = Parsing.parseStatementEnd(src, i + n); + var end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new DebugStatement(loc), n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java index 52a8c7d..cc09617 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java @@ -1,15 +1,16 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; -import me.topchetoeu.jscript.compilation.values.IndexStatement; import me.topchetoeu.jscript.compilation.values.VariableStatement; import me.topchetoeu.jscript.compilation.values.constants.BoolStatement; +import me.topchetoeu.jscript.compilation.values.operations.IndexStatement; public class DeleteStatement extends Statement { public final Statement key; @@ -31,7 +32,7 @@ public class DeleteStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed(); n += 6; - var valRes = Parsing.parseValue(src, i + n, 15); + var valRes = ES5.parseExpression(src, i + n, 15); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'"); n += valRes.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java index 1d64dd8..bb9a42c 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java @@ -1,13 +1,14 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class DoWhileStatement extends Statement { public final Statement condition, body; @@ -47,7 +48,7 @@ public class DoWhileStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed(); n += 2; - var bodyRes = Parsing.parseStatement(src, i + n); + var bodyRes = ES5.parseStatement(src, i + n); if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body."); n += bodyRes.n; @@ -58,7 +59,7 @@ public class DoWhileStatement extends Statement { if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); n++; - var condRes = Parsing.parseValue(src, i + n, 0); + var condRes = ES5.parseExpression(src, i + n, 0); if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition."); n += condRes.n; n += Parsing.skipEmpty(src, i + n); @@ -66,7 +67,7 @@ public class DoWhileStatement extends Statement { if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition."); n++; - var end = Parsing.parseStatementEnd(src, i + n); + var end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new DoWhileStatement(loc, labelRes.result, condRes.result, bodyRes.result), n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java index bbf4e03..8868a52 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java @@ -1,14 +1,15 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; 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.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class ForInStatement extends Statement { public final String varName; @@ -90,7 +91,7 @@ public class ForInStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration"); n += 2; - var obj = Parsing.parseValue(src, i + n, 0); + var obj = ES5.parseExpression(src, i + n, 0); if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); n += obj.n; n += Parsing.skipEmpty(src, i + n); @@ -98,7 +99,7 @@ public class ForInStatement extends Statement { if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); n++; - var bodyRes = Parsing.parseStatement(src, i + n); + var bodyRes = ES5.parseStatement(src, i + n); if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body"); n += bodyRes.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java index b56c3d9..3072384 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java @@ -1,13 +1,14 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class ForOfStatement extends Statement { public final String varName; @@ -102,7 +103,7 @@ public class ForOfStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "fo")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration"); n += 2; - var obj = Parsing.parseValue(src, i + n, 0); + var obj = ES5.parseExpression(src, i + n, 0); if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); n += obj.n; n += Parsing.skipEmpty(src, i + n); @@ -110,7 +111,7 @@ public class ForOfStatement extends Statement { if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); n++; - var bodyRes = Parsing.parseStatement(src, i + n); + var bodyRes = ES5.parseStatement(src, i + n); if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body"); n += bodyRes.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java index 8dbfec7..2ab96b0 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java @@ -1,15 +1,16 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.VariableDeclareStatement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; -import me.topchetoeu.jscript.compilation.values.DiscardStatement; +import me.topchetoeu.jscript.compilation.values.operations.DiscardStatement; public class ForStatement extends Statement { public final Statement declaration, assignment, condition, body; @@ -57,7 +58,7 @@ public class ForStatement extends Statement { private static ParseRes parseCondition(Source src, int i) { var n = Parsing.skipEmpty(src, i); - var res = Parsing.parseValue(src, i + n, 0); + var res = ES5.parseExpression(src, i + n, 0); if (!res.isSuccess()) return res.chainError(); n += res.n; n += Parsing.skipEmpty(src, i + n); @@ -66,7 +67,7 @@ public class ForStatement extends Statement { else return ParseRes.res(res.result, n + 1); } private static ParseRes parseUpdater(Source src, int i) { - return Parsing.parseValue(src, i, 0); + return ES5.parseExpression(src, i, 0); } public static ParseRes parse(Source src, int i) { @@ -107,7 +108,7 @@ public class ForStatement extends Statement { if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater"); n++; - var body = Parsing.parseStatement(src, i + n); + var body = ES5.parseStatement(src, i + n); if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body."); n += body.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java index 5c0fcdb..a751ddd 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java @@ -1,13 +1,14 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class IfStatement extends Statement { public final Statement condition, body, elseBody; @@ -58,7 +59,7 @@ public class IfStatement extends Statement { var loc = src.loc(i + n); n++; - var a = Parsing.parseValue(src, i + n, 2); + var a = ES5.parseExpression(src, i + n, 2); if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator."); n += a.n; n += Parsing.skipEmpty(src, i); @@ -66,7 +67,7 @@ public class IfStatement extends Statement { if (!src.is(i + n, ":")) return ParseRes.failed(); n++; - var b = Parsing.parseValue(src, i + n, 2); + var b = ES5.parseExpression(src, i + n, 2); if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator."); n += b.n; @@ -83,7 +84,7 @@ public class IfStatement extends Statement { if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'."); n++; - var condRes = Parsing.parseValue(src, i + n, 0); + var condRes = ES5.parseExpression(src, i + n, 0); if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition."); n += condRes.n; n += Parsing.skipEmpty(src, i + n); @@ -91,7 +92,7 @@ public class IfStatement extends Statement { if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition."); n++; - var res = Parsing.parseStatement(src, i + n); + var res = ES5.parseStatement(src, i + n); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body."); n += res.n; @@ -99,7 +100,7 @@ public class IfStatement extends Statement { if (!elseKw.isSuccess()) return ParseRes.res(new IfStatement(loc, condRes.result, res.result, null), n); n += elseKw.n; - var elseRes = Parsing.parseStatement(src, i + n); + var elseRes = ES5.parseStatement(src, i + n); if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body."); n += elseRes.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java index 84dd2fa..d5201d1 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java @@ -1,12 +1,13 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class ReturnStatement extends Statement { public final Statement value; @@ -30,21 +31,21 @@ public class ReturnStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed(); n += 6; - var end = Parsing.parseStatementEnd(src, i + n); + var end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new ReturnStatement(loc, null), n); } - var val = Parsing.parseValue(src, i + n, 0); - if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value"); + var val = ES5.parseExpression(src, i + n, 0); + if (val.isError()) return val.chainError(); n += val.n; - end = Parsing.parseStatementEnd(src, i + n); + end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new ReturnStatement(loc, val.result), n); } - else return end.chainError(src.loc(i + n), "Expected end of statement"); + else return end.chainError(src.loc(i + n), "Expected end of statement or a return value"); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index 6d44178..2e738ce 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -4,15 +4,16 @@ import java.util.ArrayList; import java.util.HashMap; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class SwitchStatement extends Statement { public static class SwitchCase { @@ -89,7 +90,7 @@ public class SwitchStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed(); n += 4; - var valRes = Parsing.parseValue(src, i + n, 0); + var valRes = ES5.parseExpression(src, i + n, 0); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'case'"); n += valRes.n; @@ -121,7 +122,7 @@ public class SwitchStatement extends Statement { if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'"); n++; - var valRes = Parsing.parseValue(src, i + n, 0); + var valRes = ES5.parseExpression(src, i + n, 0); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a switch value"); n += valRes.n; n += Parsing.skipEmpty(src, i + n); @@ -166,7 +167,7 @@ public class SwitchStatement extends Statement { } if (caseRes.isError()) return caseRes.chainError(); - var stm = Parsing.parseStatement(src, i + n); + var stm = ES5.parseStatement(src, i + n); if (stm.isSuccess()) { n += stm.n; statements.add(stm.result); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java index 0993321..ddfce87 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java @@ -1,12 +1,13 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class ThrowStatement extends Statement { public final Statement value; @@ -29,17 +30,17 @@ public class ThrowStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed(); n += 5; - var end = Parsing.parseStatementEnd(src, i + n); + var end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new ThrowStatement(loc, null), n); } - var val = Parsing.parseValue(src, i + n, 0); + var val = ES5.parseExpression(src, i + n, 0); if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value"); n += val.n; - end = Parsing.parseStatementEnd(src, i + n); + end = ES5.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; return ParseRes.res(new ThrowStatement(loc, val.result), n); diff --git a/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java index 78120b0..c5b46e1 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java @@ -1,14 +1,14 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.CompileResult; import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class TryStatement extends Statement { public final Statement tryBody; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java index 2aa1cdb..a636b92 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java @@ -1,14 +1,15 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class WhileStatement extends Statement { public final Statement condition, body; @@ -82,7 +83,7 @@ public class WhileStatement extends Statement { if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); n++; - var condRes = Parsing.parseValue(src, i + n, 0); + var condRes = ES5.parseExpression(src, i + n, 0); if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a while condition."); n += condRes.n; n += Parsing.skipEmpty(src, i + n); @@ -90,7 +91,7 @@ public class WhileStatement extends Statement { if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition."); n++; - var res = Parsing.parseStatement(src, i + n); + var res = ES5.parseStatement(src, i + n); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a while body."); n += res.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/Operator.java b/src/java/me/topchetoeu/jscript/compilation/parsing/Operator.java deleted file mode 100644 index acfb9c9..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/Operator.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import java.util.Arrays; -import java.util.LinkedHashSet; - -import me.topchetoeu.jscript.common.Operation; - -public enum Operator { - MULTIPLY("*", Operation.MULTIPLY, 13, true), - DIVIDE("/", Operation.DIVIDE, 12, true), - MODULO("%", Operation.MODULO, 12, true), - SUBTRACT("-", Operation.SUBTRACT, 11, true), - ADD("+", Operation.ADD, 11, true), - SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10, true), - SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10, true), - USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10, true), - GREATER(">", Operation.GREATER, 9, false), - LESS("<", Operation.LESS, 9, false), - GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9, false), - LESS_EQUALS("<=", Operation.LESS_EQUALS, 9, false), - NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8, false), - LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8, false), - EQUALS("==", Operation.LOOSE_EQUALS, 8, false), - LOOSE_EQUALS("===", Operation.EQUALS, 8, false), - AND("&", Operation.AND, 7, true), - XOR("^", Operation.XOR, 6, true), - OR("|", Operation.OR, 5, true); - - public final String readable; - public final Operation operation; - public final int precedence; - public final boolean assignable; - - public static final LinkedHashSet opsByLength = new LinkedHashSet(); - - static { - var vals = Operator.values(); - Arrays.sort(vals, (a, b) -> b.readable.length() - a.readable.length()); - for (var el : vals) opsByLength.add(el); - } - - private Operator(String value, Operation funcName, int precedence, boolean assignable) { - this.readable = value; - this.operation = funcName; - this.precedence = precedence; - this.assignable = assignable; - } -} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java b/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java deleted file mode 100644 index c5b377a..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java +++ /dev/null @@ -1,539 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; - -import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.compilation.*; -import me.topchetoeu.jscript.compilation.control.*; -import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; -import me.topchetoeu.jscript.compilation.values.*; -import me.topchetoeu.jscript.compilation.values.constants.BoolStatement; -import me.topchetoeu.jscript.compilation.values.constants.NullStatement; -import me.topchetoeu.jscript.compilation.values.constants.NumberStatement; -import me.topchetoeu.jscript.compilation.values.constants.StringStatement; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -// TODO: this has to be rewritten -// @SourceFile -public class Parsing { - public static final HashMap> functions = new HashMap<>(); - - private static final Set reserved = Set.of( - "true", "false", "void", "null", "this", "if", "else", "try", "catch", - "finally", "for", "do", "while", "switch", "case", "default", "new", - "function", "var", "return", "throw", "typeof", "delete", "break", - "continue", "debugger", "implements", "interface", "package", "private", - "protected", "public", "static", - - // Although ES5 allow these, we will comply to ES6 here - "const", "let", - - // These are allowed too, however our parser considers them keywords - // We allow yield and await, because they're part of the custom async and generator functions - "undefined", "arguments", "globalThis", "window", "self" - ); - - public static boolean isDigit(Character c) { - return c != null && c >= '0' && c <= '9'; - } - public static boolean isAny(char c, String alphabet) { - return alphabet.contains(Character.toString(c)); - } - - public static int fromHex(char c) { - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= '0' && c <= '9') return c - '0'; - return -1; - } - - public static int skipEmpty(Source src, int i) { - int n = 0; - - while (n < src.size() && src.is(i + n, Character::isWhitespace)) n++; - - return n; - } - - public static ParseRes parseChar(Source src, int i) { - int n = 0; - - if (src.is(i + n, '\\')) { - n++; - char c = src.at(i + n++); - - if (c == 'b') return ParseRes.res('\b', n); - else if (c == 't') return ParseRes.res('\t', n); - else if (c == 'n') return ParseRes.res('\n', n); - else if (c == 'f') return ParseRes.res('\f', n); - else if (c == 'r') return ParseRes.res('\r', n); - else if (c == '0') { - if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); - else return ParseRes.res('\0', n); - } - else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); - else if (c == 'x') { - var newC = 0; - - for (var j = 0; j < 2; j++) { - if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence."); - - int val = fromHex(src.at(i + n)); - if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence."); - n++; - - newC = (newC << 4) | val; - } - - return ParseRes.res((char)newC, n); - } - else if (c == 'u') { - var newC = 0; - - for (var j = 0; j < 4; j++) { - if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence"); - - int val = fromHex(src.at(i + n)); - if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence"); - n++; - - newC = (newC << 4) | val; - } - - return ParseRes.res((char)newC, n); - } - else if (c == '\n') return ParseRes.res(null, n); - } - - return ParseRes.res(src.at(i + n), n + 1); - } - - public static ParseRes parseIdentifier(Source src, int i) { - var n = skipEmpty(src, i); - var res = new StringBuilder(); - var first = false; - - while (true) { - if (i + n > src.size()) break; - char c = src.at(i + n, '\0'); - - if (first && !Character.isLetterOrDigit(c) && c != '_' && c != '$') break; - if (!first && !Character.isLetter(c) && c != '_' && c != '$') break; - res.append(c); - n++; - } - - if (res.length() <= 0) return ParseRes.failed(); - else return ParseRes.res(res.toString(), n); - } - public static ParseRes parseIdentifier(Source src, int i, String test) { - var n = skipEmpty(src, i); - var res = new StringBuilder(); - var first = true; - - while (true) { - if (i + n > src.size()) break; - char c = src.at(i + n, '\0'); - - if (first && !Character.isLetterOrDigit(c) && c != '_' && c != '$') break; - if (!first && !Character.isLetter(c) && c != '_' && c != '$') break; - first = false; - res.append(c); - n++; - } - - if (res.length() <= 0) return ParseRes.failed(); - else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n); - else return ParseRes.failed(); - } - public static boolean isIdentifier(Source src, int i, String test) { - return parseIdentifier(src, i, test).isSuccess(); - } - - public static ParseRes parseOperator(Source src, int i, String op) { - var n = skipEmpty(src, i); - - if (src.is(i + n, op)) return ParseRes.res(op, n + op.length()); - else return ParseRes.failed(); - } - - public static ParseRes parseStatementEnd(Source src, int i) { - var n = skipEmpty(src, i); - if (i >= src.size()) return ParseRes.res(true, n + 1); - - for (var j = i; j < i + n; j++) { - if (src.is(j, '\n')) return ParseRes.res(true, n); - } - - if (src.is(i + n, ';')) return ParseRes.res(true, n + 1); - if (src.is(i + n, '}')) return ParseRes.res(true, n); - - return ParseRes.failed(); - } - public static boolean checkVarName(String name) { - return !reserved.contains(name); - } - - public static ParseRes> parseParamList(Source src, int i) { - var n = skipEmpty(src, i); - - var openParen = parseOperator(src, i + n, "("); - if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list."); - n += openParen.n; - - var args = new ArrayList(); - - var closeParen = parseOperator(src, i + n, ")"); - n += closeParen.n; - - if (!closeParen.isSuccess()) { - while (true) { - var argRes = parseIdentifier(src, i + n); - if (argRes.isSuccess()) { - args.add(argRes.result); - n++; - - n += skipEmpty(src, i); - - if (src.is(i + n, ",")) { - n++; - n += skipEmpty(src, i + n); - } - if (src.is(i + n, ")")) { - n++; - break; - } - } - else return ParseRes.error(src.loc(i + n), "Expected an argument, or a closing brace."); - } - } - - return ParseRes.res(args, n); - } - - public static ParseRes parseParens(Source src, int i) { - int n = 0; - - var openParen = parseOperator(src, i + n, "("); - if (!openParen.isSuccess()) return openParen.chainError(); - n += openParen.n; - - var res = parseValue(src, i + n, 0); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens"); - n += res.n; - - var closeParen = parseOperator(src, i + n, ")"); - if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren"); - n += closeParen.n; - - return ParseRes.res(res.result, n); - } - public static ParseRes parseSimple(Source src, int i, boolean statement) { - return ParseRes.first(src, i, - (a, b) -> statement ? ParseRes.failed() : ObjectStatement.parse(a, b), - (a, b) -> statement ? ParseRes.failed() : FunctionStatement.parseFunction(a, b, false), - VariableStatement::parse, - Parsing::parseLiteral, - StringStatement::parse, - RegexStatement::parse, - NumberStatement::parse, - ChangeStatement::parsePrefixDecrease, - ChangeStatement::parsePrefixIncrease, - OperationStatement::parsePrefix, - ArrayStatement::parse, - Parsing::parseParens, - CallStatement::parseNew, - TypeofStatement::parse, - DiscardStatement::parse, - DeleteStatement::parse - ); - } - - public static ParseRes parseLiteral(Source src, int i) { - var n = skipEmpty(src, i); - var loc = src.loc(i + n); - - var id = parseIdentifier(src, i); - if (!id.isSuccess()) return id.chainError(); - n += id.n; - - if (id.result.equals("true")) return ParseRes.res(new BoolStatement(loc, true), n); - if (id.result.equals("false")) return ParseRes.res(new BoolStatement(loc, false), n); - if (id.result.equals("undefined")) return ParseRes.res(new DiscardStatement(loc, null), n); - if (id.result.equals("null")) return ParseRes.res(new NullStatement(loc), n); - if (id.result.equals("this")) return ParseRes.res(new VariableIndexStatement(loc, 0), n); - if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexStatement(loc, 1), n); - if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisStatement(loc), n); - - return ParseRes.failed(); - } - public static ParseRes parseValue(Source src, int i, int precedence, boolean statement) { - var n = skipEmpty(src, i); - Statement prev = null; - - while (true) { - if (prev == null) { - var res = parseSimple(src, i + n, statement); - if (res.isSuccess()) { - n += res.n; - prev = res.result; - } - else if (res.isError()) return res.chainError(); - else break; - } - else { - var _prev = prev; - ParseRes res = ParseRes.first(src, i + n, - (s, j) -> OperationStatement.parseInstanceof(s, j, _prev, precedence), - (s, j) -> OperationStatement.parseIn(s, j, _prev, precedence), - (s, j) -> LazyOrStatement.parse(s, j, _prev, precedence), - (s, j) -> LazyAndStatement.parse(s, j, _prev, precedence), - (s, j) -> ChangeStatement.parsePostfixIncrease(s, j, _prev, precedence), - (s, j) -> ChangeStatement.parsePostfixDecrease(s, j, _prev, precedence), - (s, j) -> AssignableStatement.parse(s, j, _prev, precedence), - (s, j) -> OperationStatement.parseOperator(s, j, _prev, precedence), - (s, j) -> IfStatement.parseTernary(s, j, _prev, precedence), - (s, j) -> IndexStatement.parseMember(s, j, _prev, precedence), - (s, j) -> IndexStatement.parseIndex(s, j, _prev, precedence), - (s, j) -> CallStatement.parseCall(s, j, _prev, precedence), - (s, j) -> CompoundStatement.parseComma(s, j, _prev, precedence) - ); - - if (res.isSuccess()) { - n += res.n; - prev = res.result; - continue; - } - else if (res.isError()) return res.chainError(); - - break; - } - } - - if (prev == null) return ParseRes.failed(); - else return ParseRes.res(prev, n); - } - public static ParseRes parseValue(Source src, int i, int precedence) { - return parseValue(src, i, precedence, false); - } - - public static ParseRes parseValueStatement(Source src, int i) { - var res = parseValue(src, i, 0, true); - if (!res.isSuccess()) return res.chainError(); - - var end = parseStatementEnd(src, i + res.n); - if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement"); - - return res.addN(end.n); - } - public static ParseRes parseStatement(Source src, int i) { - var n = skipEmpty(src, i); - - if (src.is(i + n, ";")) return ParseRes.res(new DiscardStatement(src.loc(i+ n), null), n + 1); - if (isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); - - ParseRes res = ParseRes.first(src, i + n, - VariableDeclareStatement::parse, - ReturnStatement::parse, - ThrowStatement::parse, - ContinueStatement::parse, - BreakStatement::parse, - DebugStatement::parse, - IfStatement::parse, - WhileStatement::parse, - SwitchStatement::parse, - ForStatement::parse, - ForInStatement::parse, - ForOfStatement::parse, - DoWhileStatement::parse, - TryStatement::parse, - CompoundStatement::parse, - (s, j) -> FunctionStatement.parseFunction(s, j, true), - Parsing::parseValueStatement - ); - return res.addN(n); - } - - public static Statement[] parse(Filename filename, String raw) { - var src = new Source(filename, raw); - var list = new ArrayList(); - int i = 0; - - while (true) { - if (i >= src.size()) break; - - var res = Parsing.parseStatement(src, i); - - if (res.isError()) throw new SyntaxException(src.loc(i), res.error); - else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax"); - - i += res.n; - - list.add(res.result); - } - - return list.toArray(Statement[]::new); - } - - public static CompileResult compile(Statement ...statements) { - var target = new CompileResult(new LocalScopeRecord()); - var stm = new CompoundStatement(null, true, statements); - - target.scope.define("this"); - target.scope.define("arguments"); - - try { - stm.compile(target, true); - FunctionStatement.checkBreakAndCont(target, 0); - } - catch (SyntaxException e) { - target = new CompileResult(new LocalScopeRecord()); - - target.scope.define("this"); - target.scope.define("arguments"); - - target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); - } - - target.add(Instruction.ret()).setLocation(stm.loc()); - - return target; - } - public static CompileResult compile(Filename filename, String raw) { - return compile(parse(filename, raw)); - } - - private static ParseRes parseHex(Source src, int i) { - int n = 0; - double res = 0; - - while (true) { - int digit = Parsing.fromHex(src.at(i + n, '\0')); - if (digit < 0) { - if (n <= 0) return ParseRes.failed(); - else return ParseRes.res(res, n); - } - n++; - - res *= 16; - res += digit; - } - } - private static ParseRes parseOct(Source src, int i) { - int n = 0; - double res = 0; - - while (true) { - int digit = src.at(i + n, '\0') - '0'; - if (digit < 0 || digit > 9) break; - if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit); - - if (digit < 0) { - if (n <= 0) return ParseRes.failed(); - else return ParseRes.res(res, n); - } - n++; - - res *= 8; - res += digit; - } - - return ParseRes.res(res, n); - } - - public static ParseRes parseString(Source src, int i) { - var n = skipEmpty(src, i); - - char quote; - - if (src.is(i + n, '\'')) quote = '\''; - else if (src.is(i + n, '"')) quote = '"'; - else return ParseRes.failed(); - n++; - - var res = new StringBuilder(); - - while (true) { - if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal"); - if (src.is(i + n, quote)) { - n++; - return ParseRes.res(res.toString(), n); - } - - var charRes = parseChar(src, i + n); - if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character"); - n += charRes.n; - - if (charRes.result != null) res.append(charRes.result); - } - } - public static ParseRes parseNumber(Source src, int i) { - var n = skipEmpty(src, i); - - double whole = 0; - double fract = 0; - long exponent = 0; - boolean parsedAny = false; - - if (src.is(i + n, "0x") || src.is(i + n, "0X")) { - n += 2; - - var res = parseHex(src, i); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); - else return res.addN(2); - } - else if (src.is(i + n, "0o") || src.is(i + n, "0O")) { - n += 2; - - var res = parseOct(src, i); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal"); - else return res.addN(2); - } - else if (src.is(i + n, '0')) { - n++; - parsedAny = true; - if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed"); - } - - while (src.is(i + n, Parsing::isDigit)) { - parsedAny = true; - whole *= 10; - whole += src.at(i + n++) - '0'; - } - - if (src.is(i + n, '.')) { - parsedAny = true; - n++; - - while (src.is(i + n, Parsing::isDigit)) { - fract += src.at(i + n++) - '0'; - fract /= 10; - } - } - - if (src.is(i + n, 'e') || src.is(i + n, 'E')) { - n++; - parsedAny = true; - boolean negative = src.is(i + n, '-'); - boolean parsedE = false; - if (negative) n++; - - while (src.is(i + n, Parsing::isDigit)) { - parsedE = true; - exponent *= 10; - - if (negative) exponent -= src.at(i + n++) - '0'; - else exponent += src.at(i + n++) - '0'; - } - - if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); - } - - if (!parsedAny) return ParseRes.failed(); - else return ParseRes.res((whole + fract) * NumberStatement.power(10, exponent), n); - } -} diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java b/src/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java deleted file mode 100644 index e300d6b..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -public class RawToken { - public final String value; - public final TokenType type; - public final int line; - public final int start; - - public RawToken(String value, TokenType type, int line, int start) { - this.value = value; - this.type = type; - this.line = line; - this.start = start; - } -} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java b/src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java deleted file mode 100644 index c399a9e..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.compilation.parsing.ParseRes.State; - -public class TestRes { - public final State state; - public final String error; - public final int i; - - private TestRes(ParseRes.State state, String error, int i) { - this.i = i; - this.state = state; - this.error = error; - } - - public TestRes add(int n) { - return new TestRes(state, null, this.i + n); - } - public ParseRes transform() { - if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed."); - else if (isError()) return ParseRes.error(null, error); - else return ParseRes.failed(); - } - - public boolean isSuccess() { return state.isSuccess(); } - public boolean isFailed() { return state.isFailed(); } - public boolean isError() { return state.isError(); } - - public static TestRes failed() { - return new TestRes(State.FAILED, null, 0); - } - public static TestRes error(Location loc, String error) { - if (loc != null) error = loc + ": " + error; - return new TestRes(State.ERROR, error, 0); - } - public static TestRes error(Location loc, String error, TestRes other) { - if (loc != null) error = loc + ": " + error; - if (!other.isError()) return new TestRes(State.ERROR, error, 0); - return new TestRes(State.ERROR, other.error, 0); - } - public static TestRes res(int i) { - return new TestRes(State.SUCCESS, null, i); - } -} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/Token.java b/src/java/me/topchetoeu/jscript/compilation/parsing/Token.java deleted file mode 100644 index a3f4f0a..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/Token.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -public class Token { - public final Object value; - public final String rawValue; - public final boolean isString; - public final boolean isRegex; - public final int line; - public final int start; - - private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) { - this.value = value; - this.rawValue = rawValue; - this.line = line; - this.start = start; - this.isString = isString; - this.isRegex = isRegex; - } - private Token(int line, int start, Object value, String rawValue) { - this.value = value; - this.rawValue = rawValue; - this.line = line; - this.start = start; - this.isString = false; - this.isRegex = false; - } - - public boolean isString() { return isString; } - public boolean isRegex() { return isRegex; } - public boolean isNumber() { return value instanceof Number; } - public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; } - public boolean isOperator() { return value instanceof Operator; } - - public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); } - public boolean isOperator(Operator op) { return value.equals(op); } - - public String string() { return (String)value; } - public String regex() { return (String)value; } - public double number() { return (double)value; } - public String identifier() { return (String)value; } - public Operator operator() { return (Operator)value; } - - public static Token regex(int line, int start, String val, String rawValue) { - return new Token(line, start, val, rawValue, false, true); - } - public static Token string(int line, int start, String val, String rawValue) { - return new Token(line, start, val, rawValue, true, false); - } - public static Token number(int line, int start, double val, String rawValue) { - return new Token(line, start, val, rawValue); - } - public static Token identifier(int line, int start, String val) { - return new Token(line, start, val, val); - } - public static Token operator(int line, int start, Operator val) { - return new Token(line, start, val, val.readable); - } -} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java b/src/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java deleted file mode 100644 index a34e821..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.topchetoeu.jscript.compilation.parsing; - -enum TokenType { - REGEX, - STRING, - NUMBER, - LITERAL, - OPERATOR, -} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java index 9d60587..b4ef1e6 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java @@ -3,12 +3,13 @@ package me.topchetoeu.jscript.compilation.values; import java.util.ArrayList; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class ArrayStatement extends Statement { public final Statement[] statements; @@ -21,8 +22,7 @@ public class ArrayStatement extends Statement { return true; } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadArr(statements.length)); for (var i = 0; i < statements.length; i++) { @@ -70,7 +70,7 @@ public class ArrayStatement extends Statement { } } - var res = Parsing.parseValue(src, i + n, 2); + var res = ES5.parseExpression(src, i + n, 2); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element."); n += res.n; n += Parsing.skipEmpty(src, i + n); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java deleted file mode 100644 index 4cb52ed..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ /dev/null @@ -1,101 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import java.util.ArrayList; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; - -public class CallStatement extends Statement { - public final Statement func; - public final Statement[] args; - public final boolean isNew; - - @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { - if (isNew) func.compile(target, true); - else if (func instanceof IndexStatement) { - ((IndexStatement)func).compile(target, true, true); - } - else { - target.add(Instruction.pushUndefined()); - func.compile(target, true); - } - - for (var arg : args) arg.compile(target, true); - - if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type); - else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type); - - if (!pollute) target.add(Instruction.discard()); - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, BreakpointType.STEP_IN); - } - - public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) { - super(loc); - this.isNew = isNew; - this.func = func; - this.args = args; - } - - public static ParseRes parseCall(Source src, int i, Statement prev, int precedence) { - if (precedence > 17) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "(")) return ParseRes.failed(); - n++; - - var args = new ArrayList(); - boolean prevArg = false; - - while (true) { - var argRes = Parsing.parseValue(src, i + n, 2); - n += argRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (argRes.isSuccess()) { - args.add(argRes.result); - prevArg = true; - } - else if (argRes.isError()) return argRes.chainError(); - else if (prevArg && src.is(i + n, ",")) { - prevArg = false; - n++; - } - else if (src.is(i + n, ")")) { - n++; - break; - } - else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren"); - else return ParseRes.error(src.loc(i + n), "Expected an expression or a closing paren"); - } - - return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); - } - public static ParseRes parseNew(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed(); - n += 3; - - var valRes = Parsing.parseValue(src, i + n, 18); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword."); - n += valRes.n; - - var callRes = CallStatement.parseCall(src, i + n, valRes.result, 0); - if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); - if (callRes.isError()) return callRes.chainError(); - n += callRes.n; - - return ParseRes.res(new CallStatement(loc, true, callRes.result.func, callRes.result.args), n); - } -} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java index f037bd8..00fe37f 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -1,15 +1,16 @@ package me.topchetoeu.jscript.compilation.values; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; 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.CompoundStatement; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class FunctionStatement extends Statement { @@ -39,7 +40,7 @@ public class FunctionStatement extends Statement { } } - private CompileResult compileBody(CompileResult target, boolean pollute, BreakpointType bp) { + 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])) { @@ -72,7 +73,7 @@ public class FunctionStatement extends Statement { subtarget.add(Instruction.ret()).setLocation(end); checkBreakAndCont(subtarget, 0); - if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures())); + if (pollute) target.add(Instruction.loadFunc(target.children.size(), name, subtarget.scope.getCaptures())); return target.addChild(subtarget); } @@ -80,16 +81,8 @@ public class FunctionStatement extends Statement { if (this.varName != null) name = this.varName; var hasVar = this.varName != null && statement; - var hasName = name != null; - compileBody(target, pollute || hasVar || hasName, bp); - - if (hasName) { - if (pollute || hasVar) target.add(Instruction.dup()); - target.add(Instruction.pushValue("name")); - target.add(Instruction.pushValue(name)); - target.add(Instruction.storeMember()); - } + compileBody(target, name, pollute || hasVar, bp); if (hasVar) { var key = target.scope.getKey(this.varName); @@ -140,12 +133,12 @@ public class FunctionStatement extends Statement { n += nameRes.n; n += Parsing.skipEmpty(src, i + n); - var args = Parsing.parseParamList(src, i + n); + var args = ES5.parseParamList(src, i + n); if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list"); n += args.n; var res = CompoundStatement.parse(src, i + n); - if (!res.isSuccess()) res.chainError(src.loc(i + n), "Expected a compound statement for function."); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a compound statement for function."); n += res.n; return ParseRes.res(new FunctionStatement( diff --git a/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java index b43cfc2..da8aa43 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java @@ -1,7 +1,7 @@ package me.topchetoeu.jscript.compilation.values; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java index 91dbb6f..aeefb24 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java @@ -5,13 +5,14 @@ import java.util.LinkedHashMap; import java.util.Map; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.CompoundStatement; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class ObjectStatement extends Statement { public static class ObjProp { @@ -82,7 +83,7 @@ public class ObjectStatement extends Statement { var res = ParseRes.first(src, i + n, Parsing::parseIdentifier, Parsing::parseString, - Parsing::parseNumber + (s, j) -> Parsing.parseNumber(s, j, false) ); n += res.n; @@ -102,7 +103,7 @@ public class ObjectStatement extends Statement { if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); n += name.n; - var params = Parsing.parseParamList(src, i + n); + var params = ES5.parseParamList(src, i + n); if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); n += params.n; @@ -153,7 +154,7 @@ public class ObjectStatement extends Statement { if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon"); n++; - var valRes = Parsing.parseValue(src, i + n, 2); + var valRes = ES5.parseExpression(src, i + n, 2); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list"); n += valRes.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java deleted file mode 100644 index 2c98e42..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java +++ /dev/null @@ -1,110 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.Operator; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; - -public class OperationStatement extends Statement { - public final Statement[] 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); - } - - if (pollute) target.add(Instruction.operation(operation)); - else target.add(Instruction.discard()); - } - - public OperationStatement(Location loc, Operation operation, Statement ...args) { - super(loc); - this.operation = operation; - this.args = args; - } - - public static ParseRes parsePrefix(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - Operation operation = null; - String op; - - if (src.is(i + n, op = "+")) operation = Operation.POS; - else if (src.is(i + n, op = "-")) operation = Operation.NEG; - else if (src.is(i + n, op = "~")) operation = Operation.INVERSE; - else if (src.is(i + n, op = "!")) operation = Operation.NOT; - else return ParseRes.failed(); - - n++; - - var res = Parsing.parseValue(src, i + n, 14); - - if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); - else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op)); - } - public static ParseRes parseInstanceof(Source src, int i, Statement prev, int precedence) { - if (precedence > 9) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var kw = Parsing.parseIdentifier(src, i + n, "instanceof"); - if (!kw.isSuccess()) return kw.chainError(); - n += kw.n; - - var valRes = Parsing.parseValue(src, i + n, 10); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'."); - n += valRes.n; - - return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); - } - public static ParseRes parseIn(Source src, int i, Statement prev, int precedence) { - if (precedence > 9) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var kw = Parsing.parseIdentifier(src, i + n, "in"); - if (!kw.isSuccess()) return kw.chainError(); - n += kw.n; - - var valRes = Parsing.parseValue(src, i + n, 10); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'."); - n += valRes.n; - - return ParseRes.res(new OperationStatement(loc, Operation.IN, valRes.result, prev), n); - } - public static ParseRes parseOperator(Source src, int i, Statement prev, int precedence) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - for (var op : Operator.opsByLength) { - if (!src.is(i + n, op.readable)) continue; - if (op.precedence < precedence) return ParseRes.failed(); - n += op.readable.length(); - - var res = Parsing.parseValue(src, i + n, op.precedence + 1); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s' operator.", op.readable)); - n += res.n; - - return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); - } - - return ParseRes.failed(); - } -} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java index 8b240d0..5bc7a78 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java @@ -1,12 +1,12 @@ package me.topchetoeu.jscript.compilation.values; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class RegexStatement extends Statement { public final String pattern, flags; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java index 53933bb..0f2f7ba 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java @@ -1,14 +1,16 @@ package me.topchetoeu.jscript.compilation.values; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +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.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; +import me.topchetoeu.jscript.compilation.values.operations.VariableAssignStatement; public class VariableStatement extends AssignableStatement { public final String name; @@ -40,7 +42,7 @@ public class VariableStatement extends AssignableStatement { if (!literal.isSuccess()) return literal.chainError(); n += literal.n; - if (!Parsing.checkVarName(literal.result)) { + if (!ES5.checkVarName(literal.result)) { if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported."); if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported."); if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported."); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolStatement.java index 51c6db7..fa45815 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolStatement.java @@ -1,7 +1,7 @@ package me.topchetoeu.jscript.compilation.values.constants; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/NullStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/NullStatement.java index 7238ce6..afb0dcd 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/NullStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/NullStatement.java @@ -1,7 +1,7 @@ package me.topchetoeu.jscript.compilation.values.constants; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberStatement.java index 2640cd3..a626e86 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberStatement.java @@ -1,12 +1,12 @@ package me.topchetoeu.jscript.compilation.values.constants; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class NumberStatement extends Statement { public final double value; @@ -35,7 +35,7 @@ public class NumberStatement extends Statement { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); - var res = Parsing.parseNumber(src, i + n); + var res = Parsing.parseNumber(src, i + n, false); if (res.isSuccess()) return ParseRes.res(new NumberStatement(loc, res.result), n + res.n); else return res.chainError(); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/StringStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/StringStatement.java index 02fad21..2cb4d8c 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/constants/StringStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/StringStatement.java @@ -1,12 +1,12 @@ package me.topchetoeu.jscript.compilation.values.constants; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class StringStatement extends Statement { public final String value; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/CallStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/CallStatement.java new file mode 100644 index 0000000..e9a08b3 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/CallStatement.java @@ -0,0 +1,180 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import java.util.ArrayList; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; +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.ES5; +import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.values.ArrayStatement; +import me.topchetoeu.jscript.compilation.values.ObjectStatement; +import me.topchetoeu.jscript.compilation.values.VariableStatement; +import me.topchetoeu.jscript.compilation.values.constants.BoolStatement; +import me.topchetoeu.jscript.compilation.values.constants.NumberStatement; +import me.topchetoeu.jscript.compilation.values.constants.StringStatement; + +public class CallStatement extends Statement { + public static boolean ATTACH_NAME = true; + + public final Statement func; + public final Statement[] args; + public final boolean isNew; + + private String generateName(Statement func, Statement index) { + String res = "(intermediate value)"; + boolean shouldParen = false; + + if (func instanceof ObjectStatement) { + var obj = (ObjectStatement)func; + + shouldParen = true; + + if (obj.getters.size() > 0 || obj.setters.size() > 0 || obj.map.size() > 0) res = "{}"; + else res = "{(intermediate value)}"; + } + else if (func instanceof StringStatement) { + res = JSON.stringify(JSONElement.string(((StringStatement)func).value)); + } + else if (func instanceof NumberStatement) { + res = JSON.stringify(JSONElement.number(((NumberStatement)func).value)); + } + else if (func instanceof BoolStatement) { + res = ((BoolStatement)func).value ? "true" : "false"; + } + else if (func instanceof VariableStatement) { + res = ((VariableStatement)func).name; + } + else if (func instanceof VariableIndexStatement) { + var i = ((VariableIndexStatement)func).index; + + if (i == 0) res = "this"; + else if (i == 1) res = "arguments"; + } + else if (func instanceof ArrayStatement) { + var els = new ArrayList(); + + for (var el : ((ArrayStatement)func).statements) { + if (el != null) els.add(generateName(el, null)); + else els.add("(intermediate value)"); + } + + res = "[" + String.join(",", els) + "]"; + } + + if (index == null) return res; + + if (shouldParen) res = "(" + res + ")"; + + if (index instanceof StringStatement) { + var val = ((StringStatement)index).value; + var bracket = JSON.stringify(JSONElement.string(val)); + + if (!bracket.substring(1, bracket.length() - 1).equals(val)) return res + "[" + bracket + "]"; + if (Parsing.parseIdentifier(new Source(null, val), 0).n != val.length()) return res + "[" + bracket + "]"; + + return res + "." + val; + } + + return res + "[" + generateName(index, null) + "]"; + } + + @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { + if (!isNew && func instanceof IndexStatement) { + var obj = ((IndexStatement)func).object; + var index = ((IndexStatement)func).index; + String name = ""; + + obj.compile(target, true); + index.compile(target, true); + for (var arg : args) arg.compile(target, true); + + if (ATTACH_NAME) name = generateName(obj, index); + + target.add(Instruction.callMember(args.length, name)).setLocationAndDebug(loc(), type); + } + else { + String name = ""; + + func.compile(target, true); + for (var arg : args) arg.compile(target, true); + + if (ATTACH_NAME) name = generateName(func, null); + + if (isNew) target.add(Instruction.callNew(args.length, name)).setLocationAndDebug(loc(), type); + else target.add(Instruction.call(args.length, name)).setLocationAndDebug(loc(), type); + } + if (!pollute) target.add(Instruction.discard()); + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, BreakpointType.STEP_IN); + } + + public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) { + super(loc); + this.isNew = isNew; + this.func = func; + this.args = args; + } + + public static ParseRes parseCall(Source src, int i, Statement prev, int precedence) { + if (precedence > 17) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "(")) return ParseRes.failed(); + n++; + + var args = new ArrayList(); + boolean prevArg = false; + + while (true) { + var argRes = ES5.parseExpression(src, i + n, 2); + n += argRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (argRes.isSuccess()) { + args.add(argRes.result); + prevArg = true; + } + else if (argRes.isError()) return argRes.chainError(); + else if (prevArg && src.is(i + n, ",")) { + prevArg = false; + n++; + } + else if (src.is(i + n, ")")) { + n++; + break; + } + else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren"); + else return ParseRes.error(src.loc(i + n), "Expected an expression or a closing paren"); + } + + return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); + } + public static ParseRes parseNew(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed(); + n += 3; + + var valRes = ES5.parseExpression(src, i + n, 18); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword."); + n += valRes.n; + + var callRes = CallStatement.parseCall(src, i + n, valRes.result, 0); + if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); + if (callRes.isError()) return callRes.chainError(); + n += callRes.n; + + return ParseRes.res(new CallStatement(loc, true, callRes.result.func, callRes.result.args), n); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/ChangeStatement.java similarity index 89% rename from src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/ChangeStatement.java index e8cd584..66a3eca 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/ChangeStatement.java @@ -1,14 +1,15 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +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.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; import me.topchetoeu.jscript.compilation.values.constants.NumberStatement; public class ChangeStatement extends Statement { @@ -39,7 +40,7 @@ public class ChangeStatement extends Statement { if (!src.is(i + n, "++")) return ParseRes.failed(); n += 2; - var res = Parsing.parseValue(src, i + n, 15); + var res = ES5.parseExpression(src, i + n, 15); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); else if (!(res.result instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); @@ -52,7 +53,7 @@ public class ChangeStatement extends Statement { if (!src.is(i + n, "--")) return ParseRes.failed(); n += 2; - var res = Parsing.parseValue(src, i + n, 15); + var res = ES5.parseExpression(src, i + n, 15); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); else if (!(res.result instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/DiscardStatement.java similarity index 74% rename from src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/DiscardStatement.java index 14041f6..ddcccda 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/DiscardStatement.java @@ -1,12 +1,13 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class DiscardStatement extends Statement { public final Statement value; @@ -30,7 +31,7 @@ public class DiscardStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed(); n += 4; - var valRes = Parsing.parseValue(src, i + n, 14); + var valRes = ES5.parseExpression(src, i + n, 14); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword."); n += valRes.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignStatement.java similarity index 92% rename from src/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignStatement.java index 1716336..fc11ac2 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignStatement.java @@ -1,9 +1,9 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexStatement.java similarity index 87% rename from src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/IndexStatement.java index 0b4bf48..a0f8598 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/IndexStatement.java @@ -1,15 +1,16 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; 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.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; import me.topchetoeu.jscript.compilation.values.constants.StringStatement; public class IndexStatement extends AssignableStatement { @@ -48,7 +49,7 @@ public class IndexStatement extends AssignableStatement { if (!src.is(i + n, "[")) return ParseRes.failed(); n++; - var valRes = Parsing.parseValue(src, i + n, 0); + var valRes = ES5.parseExpression(src, i + n, 0); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression"); n += valRes.n; n += Parsing.skipEmpty(src, i + n); diff --git a/src/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndStatement.java similarity index 79% rename from src/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndStatement.java index b556e07..8dabe29 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndStatement.java @@ -1,12 +1,13 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class LazyAndStatement extends Statement { public final Statement first, second; @@ -38,7 +39,7 @@ public class LazyAndStatement extends Statement { var loc = src.loc(i + n); n += 2; - var res = Parsing.parseValue(src, i + n, 4); + var res = ES5.parseExpression(src, i + n, 4); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator."); n += res.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrStatement.java similarity index 78% rename from src/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrStatement.java index 80ec4be..aff7f6f 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrStatement.java @@ -1,12 +1,13 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; public class LazyOrStatement extends Statement { public final Statement first, second; @@ -38,7 +39,7 @@ public class LazyOrStatement extends Statement { var loc = src.loc(i + n); n += 2; - var res = Parsing.parseValue(src, i + n, 4); + var res = ES5.parseExpression(src, i + n, 4); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator."); n += res.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/operations/OperationStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/OperationStatement.java new file mode 100644 index 0000000..55b4e6a --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/OperationStatement.java @@ -0,0 +1,234 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; +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.AssignableStatement; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.ES5; +import me.topchetoeu.jscript.compilation.Statement; + +public class OperationStatement extends Statement { + private static interface OperatorFactory { + String token(); + int precedence(); + ParseRes construct(Source src, int i, Statement prev); + } + + private static class NormalOperatorFactory implements OperatorFactory { + public final String token; + public final int precedence; + public final Operation operation; + + @Override public int precedence() { return precedence; } + @Override public String token() { return token; } + @Override public ParseRes construct(Source src, int i, Statement prev) { + var loc = src.loc(i); + + var other = ES5.parseExpression(src, i, precedence + 1); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); + return ParseRes.res(new OperationStatement(loc, operation, prev, (Statement)other.result), other.n); + } + + public NormalOperatorFactory(String token, int precedence, Operation operation) { + this.token = token; + this.precedence = precedence; + this.operation = operation; + } + } + private static class AssignmentOperatorFactory implements OperatorFactory { + public final String token; + public final int precedence; + public final Operation operation; + + @Override public int precedence() { return precedence; } + @Override public String token() { return token; } + @Override public ParseRes construct(Source src, int i, Statement prev) { + var loc = src.loc(i); + + if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); + + var other = ES5.parseExpression(src, i, precedence); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); + return ParseRes.res(((AssignableStatement)prev).toAssign(other.result, operation), other.n); + } + + public AssignmentOperatorFactory(String token, int precedence, Operation operation) { + this.token = token; + this.precedence = precedence; + this.operation = operation; + } + } + private static class LazyAndFactory implements OperatorFactory { + @Override public int precedence() { return 4; } + @Override public String token() { return "&&"; } + @Override public ParseRes construct(Source src, int i, Statement prev) { + var loc = src.loc(i); + + var other = ES5.parseExpression(src, i, 5); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '&&'"); + return ParseRes.res(new LazyAndStatement(loc, prev, (Statement)other.result), other.n); + } + } + private static class LazyOrFactory implements OperatorFactory { + @Override public int precedence() { return 5; } + @Override public String token() { return "||"; } + @Override public ParseRes construct(Source src, int i, Statement prev) { + var loc = src.loc(i); + + var other = ES5.parseExpression(src, i, 6); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '||'"); + return ParseRes.res(new LazyOrStatement(loc, prev, (Statement)other.result), other.n); + } + } + + public final Statement[] 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); + } + + if (pollute) target.add(Instruction.operation(operation)); + else target.add(Instruction.discard()); + } + + public OperationStatement(Location loc, Operation operation, Statement ...args) { + super(loc); + this.operation = operation; + this.args = args; + } + + private static final Map factories = Set.of( + new NormalOperatorFactory("*", 13, Operation.MULTIPLY), + new NormalOperatorFactory("/", 12, Operation.DIVIDE), + new NormalOperatorFactory("%", 12, Operation.MODULO), + new NormalOperatorFactory("-", 11, Operation.SUBTRACT), + new NormalOperatorFactory("+", 11, Operation.ADD), + new NormalOperatorFactory(">>", 10, Operation.SHIFT_RIGHT), + new NormalOperatorFactory("<<", 10, Operation.SHIFT_LEFT), + new NormalOperatorFactory(">>>", 10, Operation.USHIFT_RIGHT), + new NormalOperatorFactory(">", 9, Operation.GREATER), + new NormalOperatorFactory("<", 9, Operation.LESS), + new NormalOperatorFactory(">=", 9, Operation.GREATER_EQUALS), + new NormalOperatorFactory("<=", 9, Operation.LESS_EQUALS), + new NormalOperatorFactory("!=", 8, Operation.LOOSE_NOT_EQUALS), + new NormalOperatorFactory("!==", 8, Operation.NOT_EQUALS), + new NormalOperatorFactory("==", 8, Operation.LOOSE_EQUALS), + new NormalOperatorFactory("===", 8, Operation.EQUALS), + new NormalOperatorFactory("&", 7, Operation.AND), + new NormalOperatorFactory("^", 6, Operation.XOR), + new NormalOperatorFactory("|", 5, Operation.OR), + + new AssignmentOperatorFactory("=", 2, null), + new AssignmentOperatorFactory("*=", 2, Operation.MULTIPLY), + new AssignmentOperatorFactory("/=", 2, Operation.DIVIDE), + new AssignmentOperatorFactory("%=", 2, Operation.MODULO), + new AssignmentOperatorFactory("-=", 2, Operation.SUBTRACT), + new AssignmentOperatorFactory("+=", 2, Operation.ADD), + new AssignmentOperatorFactory(">>=", 2, Operation.SHIFT_RIGHT), + new AssignmentOperatorFactory("<<=", 2, Operation.SHIFT_LEFT), + new AssignmentOperatorFactory(">>>=", 2, Operation.USHIFT_RIGHT), + new AssignmentOperatorFactory("&=", 2, Operation.AND), + new AssignmentOperatorFactory("^=", 2, Operation.XOR), + new AssignmentOperatorFactory("|=", 2, Operation.OR), + + new LazyAndFactory(), + new LazyOrFactory() + ).stream().collect(Collectors.toMap(v -> v.token(), v -> v)); + + private static final List operatorsByLength = factories.keySet().stream().sorted((a, b) -> -a.compareTo(b)).collect(Collectors.toList()); + + public static ParseRes parsePrefix(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + Operation operation = null; + String op; + + if (src.is(i + n, op = "+")) operation = Operation.POS; + else if (src.is(i + n, op = "-")) operation = Operation.NEG; + else if (src.is(i + n, op = "~")) operation = Operation.INVERSE; + else if (src.is(i + n, op = "!")) operation = Operation.NOT; + else return ParseRes.failed(); + + n++; + + var res = ES5.parseExpression(src, i + n, 14); + + if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); + else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op)); + } + public static ParseRes parseInstanceof(Source src, int i, Statement prev, int precedence) { + if (precedence > 9) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var kw = Parsing.parseIdentifier(src, i + n, "instanceof"); + if (!kw.isSuccess()) return kw.chainError(); + n += kw.n; + + var valRes = ES5.parseExpression(src, i + n, 10); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'."); + n += valRes.n; + + return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); + } + public static ParseRes parseIn(Source src, int i, Statement prev, int precedence) { + if (precedence > 9) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var kw = Parsing.parseIdentifier(src, i + n, "in"); + if (!kw.isSuccess()) return kw.chainError(); + n += kw.n; + + var valRes = ES5.parseExpression(src, i + n, 10); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'."); + n += valRes.n; + + return ParseRes.res(new OperationStatement(loc, Operation.IN, valRes.result, prev), n); + } + public static ParseRes parseOperator(Source src, int i, Statement prev, int precedence) { + var n = Parsing.skipEmpty(src, i); + + for (var token : operatorsByLength) { + var factory = factories.get(token); + + if (!src.is(i + n, token)) continue; + if (factory.precedence() < precedence) ParseRes.failed(); + + n += token.length(); + n += Parsing.skipEmpty(src, i + n); + + var res = factory.construct(src, i + n, prev); + return res.addN(n); + // var res = Parsing.parseValue(src, i + n, prec + 1); + // if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s' operator.", token)); + // n += res.n; + + // return ParseRes.res(new OperationStatement(loc, factories.get(token), prev, res.result), n); + } + + return ParseRes.failed(); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/TypeofStatement.java similarity index 76% rename from src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/TypeofStatement.java index 89ddc98..8aaa578 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/TypeofStatement.java @@ -1,12 +1,14 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +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.ES5; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.parsing.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Source; +import me.topchetoeu.jscript.compilation.values.VariableStatement; public class TypeofStatement extends Statement { public final Statement value; @@ -40,7 +42,7 @@ public class TypeofStatement extends Statement { if (!Parsing.isIdentifier(src, i + n, "typeof")) return ParseRes.failed(); n += 6; - var valRes = Parsing.parseValue(src, i + n, 15); + var valRes = ES5.parseExpression(src, i + n, 15); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'typeof' keyword."); n += valRes.n; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignStatement.java similarity index 86% rename from src/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignStatement.java index 31bf6c9..a9e7167 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignStatement.java @@ -1,10 +1,11 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.values.FunctionStatement; public class VariableAssignStatement extends Statement { public final String name; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableIndexStatement.java similarity index 82% rename from src/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java rename to src/java/me/topchetoeu/jscript/compilation/values/operations/VariableIndexStatement.java index db3e4f0..f2f53bc 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/operations/VariableIndexStatement.java @@ -1,7 +1,7 @@ -package me.topchetoeu.jscript.compilation.values; +package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; diff --git a/src/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java b/src/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java new file mode 100644 index 0000000..df5a7e3 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java @@ -0,0 +1,13 @@ +package me.topchetoeu.jscript.runtime; + +import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; + +public class ArgumentsValue extends ArrayValue { + public final Frame frame; + + public ArgumentsValue(Frame frame, Value... args) { + super(args); + this.frame = frame; + } +} diff --git a/src/java/me/topchetoeu/jscript/runtime/Engine.java b/src/java/me/topchetoeu/jscript/runtime/Engine.java index 9e2d6d1..1b83179 100644 --- a/src/java/me/topchetoeu/jscript/runtime/Engine.java +++ b/src/java/me/topchetoeu/jscript/runtime/Engine.java @@ -1,18 +1,19 @@ package me.topchetoeu.jscript.runtime; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import java.util.concurrent.PriorityBlockingQueue; +import java.util.function.Supplier; -import me.topchetoeu.jscript.common.ResultRunnable; -import me.topchetoeu.jscript.common.events.DataNotifier; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; public class Engine implements EventLoop { private static class Task implements Comparable> { - public final ResultRunnable runnable; - public final DataNotifier notifier = new DataNotifier<>(); + public final Supplier runnable; + public final CompletableFuture notifier = new CompletableFuture(); public final boolean micro; - public Task(ResultRunnable runnable, boolean micro) { + public Task(Supplier runnable, boolean micro) { this.runnable = runnable; this.micro = micro; } @@ -26,8 +27,7 @@ public class Engine implements EventLoop { private PriorityBlockingQueue> tasks = new PriorityBlockingQueue<>(); private Thread thread; - @Override - public DataNotifier pushMsg(ResultRunnable runnable, boolean micro) { + @Override public Future pushMsg(Supplier runnable, boolean micro) { var msg = new Task(runnable, micro); tasks.add(msg); return msg.notifier; @@ -40,15 +40,15 @@ public class Engine implements EventLoop { var task = tasks.take(); try { - ((Task)task).notifier.next(task.runnable.run()); + ((Task)task).notifier.complete(task.runnable.get()); } catch (RuntimeException e) { if (e instanceof InterruptException) throw e; - task.notifier.error(e); + task.notifier.completeExceptionally(e); } } catch (InterruptedException | InterruptException e) { - for (var msg : tasks) msg.notifier.error(new InterruptException(e)); + for (var msg : tasks) msg.notifier.cancel(false); break; } } diff --git a/src/java/me/topchetoeu/jscript/runtime/EventLoop.java b/src/java/me/topchetoeu/jscript/runtime/EventLoop.java index adbc6ff..3d48364 100644 --- a/src/java/me/topchetoeu/jscript/runtime/EventLoop.java +++ b/src/java/me/topchetoeu/jscript/runtime/EventLoop.java @@ -1,9 +1,10 @@ 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.Filename; -import me.topchetoeu.jscript.common.ResultRunnable; -import me.topchetoeu.jscript.common.events.DataNotifier; +import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Key; import me.topchetoeu.jscript.runtime.exceptions.EngineException; @@ -16,21 +17,21 @@ public interface EventLoop { public static EventLoop get(Environment ext) { if (ext.hasNotNull(KEY)) return ext.get(KEY); else return new EventLoop() { - @Override public DataNotifier pushMsg(ResultRunnable runnable, boolean micro) { + @Override public Future pushMsg(Supplier runnable, boolean micro) { throw EngineException.ofError("No event loop attached to environment."); } }; } - public DataNotifier pushMsg(ResultRunnable runnable, boolean micro); - public default DataNotifier pushMsg(Runnable runnable, boolean micro) { + public Future pushMsg(Supplier runnable, boolean micro); + public default Future pushMsg(Runnable runnable, boolean micro) { return pushMsg(() -> { runnable.run(); return null; }, micro); } - public default DataNotifier pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) { + public default Future pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) { return pushMsg(() -> func.call(env, thisArg, args), micro); } - public default DataNotifier pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) { - return pushMsg(() -> Compiler.compile(env, filename, raw).call(env, thisArg, args), micro); + public default Future pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) { + return pushMsg(() -> Compiler.compileFunc(env, filename, raw).call(env, thisArg, args), micro); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/Frame.java b/src/java/me/topchetoeu/jscript/runtime/Frame.java index c2a40c7..4741223 100644 --- a/src/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/java/me/topchetoeu/jscript/runtime/Frame.java @@ -13,11 +13,11 @@ 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; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ScopeValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; @@ -101,6 +101,7 @@ public class Frame { public final LocalScope scope; public final Object thisArg; public final Object[] args; + public final boolean isNew; public final Stack tryStack = new Stack<>(); public final CodeFunction function; public final Environment env; @@ -356,14 +357,14 @@ public class Frame { */ public ObjectValue getValStackScope() { return new ObjectValue() { - @Override public Member getOwnMember(Environment env, Value key) { + @Override public Member getOwnMember(Environment env, KeyCache key) { var res = super.getOwnMember(env, key); if (res != null) return res; - var f = key.toNumber(env).value; - var i = (int)f; + var num = key.toNumber(env); + var i = key.toInt(env); - if (i < 0 || i >= stackPtr) return null; + if (num != i || i < 0 || i >= stackPtr) return null; else return new FieldMember(false, true, true) { @Override public Value get(Environment env, Value self) { return stack[i]; } @Override public boolean set(Environment env, Value val, Value self) { @@ -391,12 +392,13 @@ public class Frame { }; } - public Frame(Environment env, Value thisArg, Value[] args, CodeFunction func) { + public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) { this.env = env; - this.args = args.clone(); + 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 ArrayValue(args); + this.scope.get(1).value = new ArgumentsValue(this, args); this.thisArg = thisArg; this.function = func; diff --git a/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index bd25878..67ce302 100644 --- a/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -35,9 +35,18 @@ public class InstructionRunner { private static Value execCall(Environment env, Instruction instr, Frame frame) { var callArgs = frame.take(instr.get(0)); var func = frame.pop(); - var thisArg = frame.pop(); - frame.push(func.call(env, thisArg, callArgs)); + frame.push(func.call(env, false, instr.get(1), VoidValue.UNDEFINED, callArgs)); + + frame.codePtr++; + return null; + } + private static Value execCallMember(Environment env, Instruction instr, Frame frame) { + var callArgs = frame.take(instr.get(0)); + var index = frame.pop(); + var obj = frame.pop(); + + frame.push(obj.getMember(env, index).call(env, false, instr.get(1), obj, callArgs)); frame.codePtr++; return null; @@ -46,7 +55,7 @@ public class InstructionRunner { var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); - frame.push(funcObj.callNew(env, callArgs)); + frame.push(funcObj.callNew(env, instr.get(1), callArgs)); frame.codePtr++; return null; @@ -61,7 +70,7 @@ public class InstructionRunner { private static Value execDefProp(Environment env, Instruction instr, Frame frame) { var setterVal = frame.pop(); var getterVal = frame.pop(); - var name = frame.pop(); + var key = frame.pop(); var obj = frame.pop(); FunctionValue getter, setter; @@ -74,7 +83,7 @@ public class InstructionRunner { else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal; else throw EngineException.ofType("Setter must be a function or undefined."); - obj.defineOwnMember(env, name, new PropertyMember(getter, setter, true, true)); + obj.defineOwnMember(env, key, new PropertyMember(getter, setter, true, true)); frame.push(obj); frame.codePtr++; @@ -90,7 +99,7 @@ public class InstructionRunner { for (var el : members) { var obj = new ObjectValue(); - obj.defineOwnMember(env, new StringValue("value"), FieldMember.of(new StringValue(el))); + obj.defineOwnMember(env, "value", FieldMember.of(new StringValue(el))); frame.push(obj); } @@ -147,7 +156,9 @@ public class InstructionRunner { return null; } private static Value execLoadObj(Environment env, Instruction instr, Frame frame) { - frame.push(new ObjectValue()); + var obj = new ObjectValue(); + obj.setPrototype(Environment.OBJECT_PROTO); + frame.push(obj); frame.codePtr++; return null; } @@ -165,13 +176,14 @@ public class InstructionRunner { } private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) { int id = instr.get(0); - var captures = new ValueVariable[instr.params.length - 1]; + String name = instr.get(1); + var captures = new ValueVariable[instr.params.length - 2]; - for (var i = 1; i < instr.params.length; i++) { - captures[i - 1] = frame.scope.get(instr.get(i)); + for (var i = 2; i < instr.params.length; i++) { + captures[i - 2] = frame.scope.get(instr.get(i)); } - var func = new CodeFunction(env, "", frame.function.body.children[id], captures); + var func = new CodeFunction(env, name, frame.function.body.children[id], captures); frame.push(func); @@ -240,7 +252,7 @@ public class InstructionRunner { return null; } private static Value execJmpIf(Environment env, Instruction instr, Frame frame) { - if (frame.pop().toBoolean().value) { + if (frame.pop().toBoolean()) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; } @@ -248,7 +260,7 @@ public class InstructionRunner { return null; } private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) { - if (frame.pop().not().value) { + if (!frame.pop().toBoolean()) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; } @@ -306,6 +318,7 @@ public class InstructionRunner { case THROW_SYNTAX: return execThrowSyntax(env, instr, frame); case CALL: return execCall(env, instr, frame); case CALL_NEW: return execCallNew(env, instr, frame); + case CALL_MEMBER: return execCallMember(env, instr, frame); case TRY_START: return execTryStart(env, instr, frame); case TRY_END: return execTryEnd(env, instr, frame); diff --git a/src/java/me/topchetoeu/jscript/runtime/JSONConverter.java b/src/java/me/topchetoeu/jscript/runtime/JSONConverter.java index 84b390a..ab4c9fd 100644 --- a/src/java/me/topchetoeu/jscript/runtime/JSONConverter.java +++ b/src/java/me/topchetoeu/jscript/runtime/JSONConverter.java @@ -27,7 +27,7 @@ public class JSONConverter { var res = new ObjectValue(); for (var el : val.map().entrySet()) { - res.defineOwnMember(null, new StringValue(el.getKey()), FieldMember.of(toJs(el.getValue()))); + res.defineOwnMember(null, el.getKey(), FieldMember.of(toJs(el.getValue()))); } return res; diff --git a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 4381f4f..551d4b6 100644 --- a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -3,12 +3,14 @@ package me.topchetoeu.jscript.runtime; import java.io.IOException; import java.nio.file.Files; 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.Filename; import me.topchetoeu.jscript.common.Metadata; import me.topchetoeu.jscript.common.Reading; -import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; @@ -16,6 +18,7 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.scope.GlobalScope; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; +import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public class SimpleRepl { @@ -32,13 +35,16 @@ public class SimpleRepl { try { var file = Path.of(arg); var raw = Files.readString(file); - var res = engine.pushMsg( - false, environment, - Filename.fromFile(file.toFile()), - raw, null - ).await(); - System.err.println(res.toReadable(environment)); + try { + var res = engine.pushMsg( + false, environment, + Filename.fromFile(file.toFile()), raw, null + ).get(); + + System.err.println(res.toReadable(environment)); + } + catch (ExecutionException e) { throw e.getCause(); } } catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } } @@ -48,9 +54,16 @@ public class SimpleRepl { var raw = Reading.readline(); if (raw == null) break; - var func = Compiler.compile(environment, new Filename("jscript", "repl/" + i + ".js"), raw); - var res = engine.pushMsg(false, environment, func, VoidValue.UNDEFINED).await(); - System.err.println(res.toReadable(environment)); + + try { + var res = engine.pushMsg( + false, environment, + new Filename("jscript", "repl/" + i + ".js"), raw, + VoidValue.UNDEFINED + ).get(); + System.err.println(res.toReadable(environment)); + } + catch (ExecutionException e) { throw e.getCause(); } } catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } } @@ -59,62 +72,36 @@ public class SimpleRepl { System.out.println(e.toString()); engine.thread().interrupt(); } - catch (RuntimeException ex) { - if (ex instanceof InterruptException) return; - else { - System.out.println("Internal error ocurred:"); - ex.printStackTrace(); - } + catch (CancellationException | InterruptedException e) { return; } + catch (Throwable ex) { + System.out.println("Internal error ocurred:"); + ex.printStackTrace(); } } private static void initEnv() { - // glob.define(null, false, new NativeFunction("go", args -> { - // try { - // var f = Path.of("do.js"); - // var func = Compiler.compile(args.env, new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); - // return func.call(args.env); - // } - // catch (IOException e) { - // throw new EngineException("Couldn't open do.js"); - // } - // })); - - // var fs = new RootFilesystem(PermissionsProvider.get(environment)); - // fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); - // fs.protocols.put("file", new PhysicalFilesystem(".")); - // fs.protocols.put("std", new STDFilesystem(System.in, System.out, System.err)); - - // environment.add(PermissionsProvider.KEY, PermissionsManager.ALL_PERMS); - // environment.add(Filesystem.KEY, fs); - // 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(EventLoop.KEY, engine); - environment.add(Compiler.KEY, (filename, source) -> { - return Parsing.compile(filename, source).body(); - }); + environment.add(DebugContext.KEY, new DebugContext()); + environment.add(Compiler.KEY, Compiler.DEFAULT); var glob = GlobalScope.get(environment); + glob.define(null, false, new NativeFunction("exit", args -> { + Thread.currentThread().interrupt(); throw new InterruptException(); })); glob.define(null, false, new NativeFunction("log", args -> { for (var el : args.args) { - System.out.print(el.toReadable(args.env)); + if (el instanceof StringValue) System.out.print(((StringValue)el).value); + else System.out.print(el.toReadable(args.env)); } return null; })); } private static void initEngine() { - // var ctx = new DebugContext(); - // environment.add(DebugContext.KEY, ctx); - - // debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx)); engineTask = engine.start(); - // debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); } public static void main(String args[]) throws InterruptedException { diff --git a/src/java/me/topchetoeu/jscript/runtime/WrapperProvider.java b/src/java/me/topchetoeu/jscript/runtime/WrapperProvider.java deleted file mode 100644 index f97fa15..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/WrapperProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import me.topchetoeu.jscript.runtime.environment.Environment; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public interface WrapperProvider { - public ObjectValue getProto(Class obj); - public ObjectValue getNamespace(Class obj); - public FunctionValue getConstr(Class obj); - - public WrapperProvider fork(Environment env); -} diff --git a/src/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java b/src/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java index a1c5e26..0ecc549 100644 --- a/src/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java +++ b/src/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java @@ -5,11 +5,11 @@ import java.util.HashMap; import java.util.List; import java.util.WeakHashMap; -import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.mapping.FunctionMap; +import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.runtime.Frame; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Key; diff --git a/src/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java b/src/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java index aad4c68..5bf3817 100644 --- a/src/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java +++ b/src/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java @@ -2,10 +2,10 @@ package me.topchetoeu.jscript.runtime.debug; import java.util.List; -import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.mapping.FunctionMap; +import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.runtime.Frame; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; diff --git a/src/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java b/src/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java deleted file mode 100644 index 72cb63a..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java +++ /dev/null @@ -1,11 +0,0 @@ -package me.topchetoeu.jscript.runtime.exceptions; - -public class ConvertException extends RuntimeException { - public final String source, target; - - public ConvertException(String source, String target) { - super(String.format("Cannot convert '%s' to '%s'.", source, target)); - this.source = source; - this.target = target; - } -} diff --git a/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index 975db01..7912f09 100644 --- a/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -3,13 +3,14 @@ package me.topchetoeu.jscript.runtime.exceptions; import java.util.ArrayList; import java.util.List; -import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue.PrototypeProvider; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; +import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public class EngineException extends RuntimeException { public static class StackElement { @@ -69,7 +70,19 @@ public class EngineException extends RuntimeException { ss.append(value.toString(env)).append('\n'); } catch (EngineException e) { - ss.append("[Error while stringifying]\n"); + var name = value.getMember(env, "name"); + var desc = value.getMember(env, "message"); + + if (name.isPrimitive() && desc.isPrimitive()) { + if (name instanceof VoidValue) ss.append("Error: "); + else ss.append(name.toString(env).value + ": "); + + if (desc instanceof VoidValue) ss.append("An error occurred"); + else ss.append(desc.toString(env).value); + + ss.append("\n"); + } + else ss.append("[Error while stringifying]\n"); } for (var line : stackTrace) { if (line.visible()) ss.append(" ").append(line.toString()).append("\n"); @@ -83,8 +96,8 @@ public class EngineException extends RuntimeException { var res = new ObjectValue(); res.setPrototype(proto); - if (name != null) res.defineOwnMember(Environment.empty(), new StringValue("name"), FieldMember.of(new StringValue(name))); - res.defineOwnMember(Environment.empty(), new StringValue("message"), FieldMember.of(new StringValue(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 23bd206..2db27e1 100644 --- a/src/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java +++ b/src/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java @@ -1,6 +1,6 @@ package me.topchetoeu.jscript.runtime.exceptions; -import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.parsing.Location; public class SyntaxException extends RuntimeException { public final Location loc; diff --git a/src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java b/src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java index a571bac..88cd8fc 100644 --- a/src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java +++ b/src/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java @@ -29,10 +29,10 @@ public class GlobalScope { } public void define(Environment ext, String name, Variable variable) { - object.defineOwnMember(ext, new StringValue(name), variable.toField(true, true)); + object.defineOwnMember(ext, name, variable.toField(true, true)); } public void define(Environment ext, boolean readonly, String name, Value val) { - object.defineOwnMember(ext, new StringValue(name), FieldMember.of(val, !readonly)); + 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, VoidValue.UNDEFINED)); @@ -42,12 +42,12 @@ public class GlobalScope { } public Value get(Environment env, String name) { - if (!object.hasMember(env, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); - else return object.getMember(env, new StringValue(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, new StringValue(name), false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); - if (!object.setMember(ext, new StringValue(name), val)) throw EngineException.ofSyntax("The global '" + name + "' is read-only."); + 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() { diff --git a/src/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java b/src/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java deleted file mode 100644 index 1717f40..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java +++ /dev/null @@ -1,6 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -public enum ConvertHint { - TOSTRING, - VALUEOF, -} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/runtime/values/KeyCache.java b/src/java/me/topchetoeu/jscript/runtime/values/KeyCache.java new file mode 100644 index 0000000..df1c4c0 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/runtime/values/KeyCache.java @@ -0,0 +1,59 @@ +package me.topchetoeu.jscript.runtime.values; + +import me.topchetoeu.jscript.runtime.environment.Environment; +import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; +import me.topchetoeu.jscript.runtime.values.primitives.StringValue; +import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; + +public class KeyCache { + public final Value value; + private Integer intCache; + private Double doubleCache; + private Boolean booleanCache; + private String stringCache; + + public String toString(Environment env) { + if (stringCache != null) return stringCache; + else return stringCache = value.toString(env).value; + } + public double toNumber(Environment env) { + if (doubleCache != null) return doubleCache; + else return doubleCache = value.toNumber(env).value; + } + public int toInt(Environment env) { + if (intCache != null) return intCache; + else return intCache = (int)toNumber(env); + } + public boolean toBoolean() { + if (booleanCache != null) return booleanCache; + else return booleanCache = value.toBoolean(); + } + public SymbolValue toSymbol() { + if (value instanceof SymbolValue) return (SymbolValue)value; + else return null; + } + public boolean isSymbol() { + return value instanceof SymbolValue; + } + + public KeyCache(Value value) { + this.value = value; + } + public KeyCache(String value) { + this.value = new StringValue(value); + this.stringCache = value; + this.booleanCache = !value.equals(""); + } + public KeyCache(int value) { + this.value = new NumberValue(value); + this.intCache = value; + this.doubleCache = (double)value; + this.booleanCache = value != 0; + } + public KeyCache(double value) { + this.value = new NumberValue(value); + this.intCache = (int)value; + this.doubleCache = value; + this.booleanCache = value != 0; + } +} diff --git a/src/java/me/topchetoeu/jscript/runtime/values/Member.java b/src/java/me/topchetoeu/jscript/runtime/values/Member.java index 91de993..2dae69b 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/Member.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/Member.java @@ -4,7 +4,6 @@ import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public interface Member { @@ -42,14 +41,14 @@ public interface Member { @Override public ObjectValue descriptor(Environment env, Value self) { var res = new ObjectValue(); - if (getter == null) res.defineOwnMember(env, new StringValue("getter"), FieldMember.of(VoidValue.UNDEFINED)); - else res.defineOwnMember(env, new StringValue("getter"), FieldMember.of(getter)); + if (getter == null) res.defineOwnMember(env, "getter", FieldMember.of(VoidValue.UNDEFINED)); + else res.defineOwnMember(env, "getter", FieldMember.of(getter)); - if (setter == null) res.defineOwnMember(env, new StringValue("setter"), FieldMember.of(VoidValue.UNDEFINED)); - else res.defineOwnMember(env, new StringValue("setter"), FieldMember.of(setter)); + if (setter == null) res.defineOwnMember(env, "setter", FieldMember.of(VoidValue.UNDEFINED)); + else res.defineOwnMember(env, "setter", FieldMember.of(setter)); - res.defineOwnMember(env, new StringValue("enumerable"), FieldMember.of(BoolValue.of(enumerable))); - res.defineOwnMember(env, new StringValue("configurable"), FieldMember.of(BoolValue.of(configurable))); + res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); + res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable))); return res; } @@ -98,10 +97,10 @@ public interface Member { @Override public ObjectValue descriptor(Environment env, Value self) { var res = new ObjectValue(); - res.defineOwnMember(env, new StringValue("value"), FieldMember.of(get(env, self))); - res.defineOwnMember(env, new StringValue("writable"), FieldMember.of(BoolValue.of(writable))); - res.defineOwnMember(env, new StringValue("enumerable"), FieldMember.of(BoolValue.of(enumerable))); - res.defineOwnMember(env, new StringValue("configurable"), FieldMember.of(BoolValue.of(configurable))); + res.defineOwnMember(env, "value", FieldMember.of(get(env, self))); + res.defineOwnMember(env, "writable", FieldMember.of(BoolValue.of(writable))); + res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); + res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable))); return res; } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/java/me/topchetoeu/jscript/runtime/values/Value.java index 480fd1e..d15e324 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -53,31 +53,41 @@ public abstract class Value { public abstract StringValue type(); public abstract boolean isPrimitive(); - public abstract BoolValue toBoolean(); public final boolean isNaN() { return this instanceof NumberValue && Double.isNaN(((NumberValue)this).value); } - public Value call(Environment env, Value self, Value ...args) { - throw EngineException.ofType("Tried to call a non-function value."); + public Value call(Environment env, boolean isNew, String name, Value self, Value ...args) { + if (name == null || name.equals("")) name = "(intermediate value)"; + + if (isNew) throw EngineException.ofType(name + " is not a constructor"); + else throw EngineException.ofType(name + " is not a function"); } - public final Value callNew(Environment env, Value ...args) { + public final Value callNew(Environment env, String name, Value ...args) { var res = new ObjectValue(); var proto = getMember(env, new StringValue("prototype")); if (proto instanceof ObjectValue) res.setPrototype(env, (ObjectValue)proto); else res.setPrototype(env, null); - var ret = this.call(env, res, args); + var ret = this.call(env, true, name, res, args); if (!ret.isPrimitive()) return ret; return res; } + public final Value call(Environment env, Value self, Value ...args) { + return call(env, false, "", self, args); + } + public final Value callNew(Environment env, Value ...args) { + return callNew(env, "", args); + } + public abstract Value toPrimitive(Environment env); public abstract NumberValue toNumber(Environment env); public abstract StringValue toString(Environment env); + public abstract boolean toBoolean(); public final int toInt(Environment env) { return (int)toNumber(env).value; } public final long toLong(Environment env) { return (long)toNumber(env).value; } @@ -134,11 +144,11 @@ public abstract class Value { } public CompareResult compare(Environment env, Value other) { - return toPrimitive(env).compare(env, other); - } + var a = this.toPrimitive(env); + var b = other.toPrimitive(env); - public final BoolValue not() { - return toBoolean().value ? BoolValue.FALSE : BoolValue.TRUE; + if (a instanceof StringValue && b instanceof StringValue) return a.compare(env, b); + else return a.toNumber(env).compare(env, b.toNumber(env)); } public final boolean isInstanceOf(Environment env, Value proto) { @@ -172,7 +182,7 @@ public abstract class Value { case LESS_EQUALS: return BoolValue.of(args[0].compare(env, args[1]).lessOrEqual()); case INVERSE: return args[0].bitwiseNot(env); - case NOT: return args[0].not(); + case NOT: return BoolValue.of(!args[0].toBoolean()); case POS: return args[0].toNumber(env); case NEG: return args[0].negative(env); @@ -187,16 +197,71 @@ public abstract class Value { } } - public abstract Member getOwnMember(Environment env, Value key); + public abstract Member getOwnMember(Environment env, KeyCache key); public abstract Map getOwnMembers(Environment env); public abstract Map getOwnSymbolMembers(Environment env); - public abstract boolean defineOwnMember(Environment env, Value key, Member member); - public abstract boolean deleteOwnMember(Environment env, Value key); + public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member); + public abstract boolean deleteOwnMember(Environment env, KeyCache key); public abstract ObjectValue getPrototype(Environment env); public abstract boolean setPrototype(Environment env, ObjectValue val); - public final Value getMember(Environment env, Value key) { + public final Member getOwnMember(Environment env, Value key) { + return getOwnMember(env, new KeyCache(key)); + } + public final Member getOwnMember(Environment env, String key) { + return getOwnMember(env, new KeyCache(key)); + } + public final Member getOwnMember(Environment env, int key) { + return getOwnMember(env, new KeyCache(key)); + } + public final Member getOwnMember(Environment env, double key) { + return getOwnMember(env, new KeyCache(key)); + } + + public final boolean defineOwnMember(Environment env, Value key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + public final boolean defineOwnMember(Environment env, String key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + public final boolean defineOwnMember(Environment env, int key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + public final boolean defineOwnMember(Environment env, double key, Member member) { + return defineOwnMember(env, new KeyCache(key), member); + } + + public final boolean defineOwnMember(Environment env, KeyCache key, Value val) { + return defineOwnMember(env, key, FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, Value key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, String key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, int key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + public final boolean defineOwnMember(Environment env, double key, Value val) { + return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + } + + public final boolean deleteOwnMember(Environment env, Value key) { + return deleteOwnMember(env, new KeyCache(key)); + } + public final boolean deleteOwnMember(Environment env, String key) { + return deleteOwnMember(env, new KeyCache(key)); + } + public final boolean deleteOwnMember(Environment env, int key) { + return deleteOwnMember(env, new KeyCache(key)); + } + public final boolean deleteOwnMember(Environment env, double key) { + return deleteOwnMember(env, new KeyCache(key)); + } + + public final Value getMember(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); @@ -204,15 +269,51 @@ public abstract class Value { return VoidValue.UNDEFINED; } - public final boolean setMember(Environment env, Value key, Value val) { + public final Value getMember(Environment env, Value key) { + return getMember(env, new KeyCache(key)); + } + public final Value getMember(Environment env, String key) { + return getMember(env, new KeyCache(key)); + } + public final Value getMember(Environment env, int key) { + return getMember(env, new KeyCache(key)); + } + public final Value getMember(Environment env, double key) { + return getMember(env, new KeyCache(key)); + } + + public final boolean setMember(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) return member.set(env, val, obj); + 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 defineOwnMember(env, key, FieldMember.of(val)); + if (defineOwnMember(env, key, FieldMember.of(val))) { + if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); + return true; + } + else return false; } - public final boolean hasMember(Environment env, Value key, boolean own) { + public final boolean setMember(Environment env, Value key, Value val) { + return setMember(env, new KeyCache(key), val); + } + public final boolean setMember(Environment env, String key, Value val) { + return setMember(env, new KeyCache(key), val); + } + public final boolean setMember(Environment env, int key, Value val) { + return setMember(env, new KeyCache(key), val); + } + public final boolean setMember(Environment env, double key, Value val) { + return setMember(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; if (own) return false; @@ -220,10 +321,36 @@ public abstract class Value { return false; } - public final boolean deleteMember(Environment env, Value key) { + public final boolean hasMember(Environment env, Value key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + public final boolean hasMember(Environment env, String key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + public final boolean hasMember(Environment env, int key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + public final boolean hasMember(Environment env, double key, boolean own) { + return hasMember(env, new KeyCache(key), own); + } + + public final boolean deleteMember(Environment env, KeyCache key) { if (!hasMember(env, key, true)) return true; return deleteOwnMember(env, key); } + public final boolean deleteMember(Environment env, Value key) { + return deleteMember(env, new KeyCache(key)); + } + public final boolean deleteMember(Environment env, String key) { + return deleteMember(env, new KeyCache(key)); + } + public final boolean deleteMember(Environment env, int key) { + return deleteMember(env, new KeyCache(key)); + } + public final boolean deleteMember(Environment env, double key) { + return deleteMember(env, new KeyCache(key)); + } + public final Map getMembers(Environment env, boolean own, boolean onlyEnumerable) { var res = new LinkedHashMap(); var protos = new ArrayList(); @@ -277,7 +404,7 @@ public abstract class Value { return res; } public final ObjectValue getMemberDescriptor(Environment env, Value key) { - var member = getOwnMember(env, key); + var member = getOwnMember(env, new KeyCache(key)); if (member != null) return member.descriptor(env, this); else return null; @@ -394,7 +521,7 @@ public abstract class Value { var curr = supplier.call(env, VoidValue.UNDEFINED); if (curr == null) { supplier = null; value = null; } - if (curr.getMember(env, new StringValue("done")).toBoolean().value) { supplier = null; value = null; } + if (curr.getMember(env, new StringValue("done")).toBoolean()) { supplier = null; value = null; } else { this.value = curr.getMember(env, new StringValue("value")); consumed = false; @@ -425,8 +552,8 @@ public abstract class Value { return new NativeFunction("", args -> { var obj = new ObjectValue(); - if (!it.hasNext()) obj.defineOwnMember(args.env, new StringValue("done"), FieldMember.of(BoolValue.TRUE)); - else obj.defineOwnMember(args.env, new StringValue("value"), FieldMember.of(it.next())); + if (!it.hasNext()) obj.defineOwnMember(args.env, "done", FieldMember.of(BoolValue.TRUE)); + else obj.defineOwnMember(args.env, "value", FieldMember.of(it.next())); return obj; }); @@ -526,11 +653,11 @@ public abstract class Value { } else if (this instanceof VoidValue) return ((VoidValue)this).name; else if (this instanceof StringValue) return JSON.stringify(JSONElement.string(((StringValue)this).value)); + else if (this instanceof SymbolValue) return this.toString(); else return this.toString(env).value; } public final String toReadable(Environment ext) { - if (this instanceof StringValue) return ((StringValue)this).value; return toReadable(ext, new HashSet<>(), 0); } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/Values.java.old b/src/java/me/topchetoeu/jscript/runtime/values/Values.java.old deleted file mode 100644 index a2482b7..0000000 --- a/src/java/me/topchetoeu/jscript/runtime/values/Values.java.old +++ /dev/null @@ -1,765 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.lang.reflect.Array; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import me.topchetoeu.jscript.common.Operation; -// import me.topchetoeu.jscript.lib.PromiseLib; -import me.topchetoeu.jscript.runtime.debug.DebugContext; -import me.topchetoeu.jscript.runtime.environment.Environment; -import me.topchetoeu.jscript.runtime.exceptions.ConvertException; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider; - -public class Values { - public static enum CompareResult { - NOT_EQUAL, - EQUAL, - LESS, - GREATER; - - public boolean less() { return this == LESS; } - public boolean greater() { return this == GREATER; } - public boolean lessOrEqual() { return this == LESS || this == EQUAL; } - public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; } - - public static CompareResult from(int cmp) { - if (cmp < 0) return LESS; - if (cmp > 0) return GREATER; - return EQUAL; - } - } - - public static final Object NULL = new Object(); - public static final Object NO_RETURN = new Object(); - - public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; } - public static boolean isWrapper(Object val, Class clazz) { - if (!isWrapper(val)) return false; - var res = (NativeWrapper)val; - return res != null && clazz.isInstance(res.wrapped); - } - public static boolean isNan(Object val) { return val instanceof Number && Double.isNaN(number(val)); } - - public static double number(Object val) { - if (val instanceof Number) return ((Number)val).doubleValue(); - else return Double.NaN; - } - - @SuppressWarnings("unchecked") - public static T wrapper(Object val, Class clazz) { - if (isWrapper(val)) val = ((NativeWrapper)val).wrapped; - if (val != null && clazz.isInstance(val)) return (T)val; - else return null; - } - - public static String type(Object val) { - if (val == null) return "undefined"; - if (val instanceof String) return "string"; - if (val instanceof Number) return "number"; - if (val instanceof Boolean) return "boolean"; - if (val instanceof SymbolValue) return "symbol"; - if (val instanceof FunctionValue) return "function"; - return "object"; - } - - private static Object tryCallConvertFunc(Environment ext, Object obj, String name) { - var func = getMember(ext, obj, name); - - if (func instanceof FunctionValue) { - var res = Values.call(ext, func, obj); - if (isPrimitive(res)) return res; - } - - throw EngineException.ofType("Value couldn't be converted to a primitive."); - } - - public static boolean isPrimitive(Object obj) { - return - obj instanceof Number || - obj instanceof String || - obj instanceof Boolean || - obj instanceof SymbolValue || - obj == null || - obj == NULL; - } - - public static Object toPrimitive(Environment ext, Object obj, ConvertHint hint) { - obj = normalize(ext, obj); - if (isPrimitive(obj)) return obj; - - var first = hint == ConvertHint.VALUEOF ? "valueOf" : "toString"; - var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf"; - - if (ext != null) { - try { return tryCallConvertFunc(ext, obj, first); } - catch (EngineException unused) { return tryCallConvertFunc(ext, obj, second); } - } - - throw EngineException.ofType("Value couldn't be converted to a primitive."); - } - public static boolean toBoolean(Object obj) { - if (obj == NULL || obj == null) return false; - if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false; - if (obj instanceof String && ((String)obj).equals("")) return false; - if (obj instanceof Boolean) return (Boolean)obj; - return true; - } - public static double toNumber(Environment ext, Object obj) { - var val = toPrimitive(ext, obj, ConvertHint.VALUEOF); - - if (val instanceof Number) return number(val); - if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; - if (val instanceof String) { - try { return Double.parseDouble((String)val); } - catch (NumberFormatException e) { return Double.NaN; } - } - return Double.NaN; - } - public static String toString(Environment ext, Object obj) { - var val = toPrimitive(ext, obj, ConvertHint.VALUEOF); - - if (val == null) return "undefined"; - if (val == NULL) return "null"; - - if (val instanceof Number) { - var d = number(val); - if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; - if (d == Double.POSITIVE_INFINITY) return "Infinity"; - if (Double.isNaN(d)) return "NaN"; - return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString(); - } - if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; - if (val instanceof String) return (String)val; - if (val instanceof SymbolValue) return val.toString(); - - return "Unknown value"; - } - - public static Object add(Environment ext, Object a, Object b) { - if (a instanceof String || b instanceof String) return toString(ext, a) + toString(ext, b); - else return toNumber(ext, a) + toNumber(ext, b); - } - public static double subtract(Environment ext, Object a, Object b) { - return toNumber(ext, a) - toNumber(ext, b); - } - public static double multiply(Environment ext, Object a, Object b) { - return toNumber(ext, a) * toNumber(ext, b); - } - public static double divide(Environment ext, Object a, Object b) { - return toNumber(ext, a) / toNumber(ext, b); - } - public static double modulo(Environment ext, Object a, Object b) { - return toNumber(ext, a) % toNumber(ext, b); - } - - public static double negative(Environment ext, Object obj) { - return -toNumber(ext, obj); - } - - public static int and(Environment ext, Object a, Object b) { - return (int)toNumber(ext, a) & (int)toNumber(ext, b); - } - public static int or(Environment ext, Object a, Object b) { - return (int)toNumber(ext, a) | (int)toNumber(ext, b); - } - public static int xor(Environment ext, Object a, Object b) { - return (int)toNumber(ext, a) ^ (int)toNumber(ext, b); - } - public static int bitwiseNot(Environment ext, Object obj) { - return ~(int)toNumber(ext, obj); - } - - public static int shiftLeft(Environment ext, Object a, Object b) { - return (int)toNumber(ext, a) << (int)toNumber(ext, b); - } - public static int shiftRight(Environment ext, Object a, Object b) { - return (int)toNumber(ext, a) >> (int)toNumber(ext, b); - } - public static long unsignedShiftRight(Environment ext, Object a, Object b) { - long _a = (long)toNumber(ext, a); - long _b = (long)toNumber(ext, b); - - if (_a < 0) _a += 0x100000000l; - if (_b < 0) _b += 0x100000000l; - return _a >>> _b; - } - - public static CompareResult compare(Environment ext, Object a, Object b) { - a = toPrimitive(ext, a, ConvertHint.VALUEOF); - b = toPrimitive(ext, b, ConvertHint.VALUEOF); - - if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b)); - - var _a = toNumber(ext, a); - var _b = toNumber(ext, b); - - if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL; - - return CompareResult.from(Double.compare(_a, _b)); - } - - public static boolean not(Object obj) { - return !toBoolean(obj); - } - - public static boolean isInstanceOf(Environment ext, Object obj, Object proto) { - if (obj == null || obj == NULL || proto == null || proto == NULL) return false; - var val = getPrototype(ext, obj); - - while (val != null) { - if (val.equals(proto)) return true; - val = val.getPrototype(ext); - } - - return false; - } - - public static Object operation(Environment ext, Operation op, Object ...args) { - switch (op) { - case ADD: return add(ext, args[0], args[1]); - case SUBTRACT: return subtract(ext, args[0], args[1]); - case DIVIDE: return divide(ext, args[0], args[1]); - case MULTIPLY: return multiply(ext, args[0], args[1]); - case MODULO: return modulo(ext, args[0], args[1]); - - case AND: return and(ext, args[0], args[1]); - case OR: return or(ext, args[0], args[1]); - case XOR: return xor(ext, args[0], args[1]); - - case EQUALS: return strictEquals(ext, args[0], args[1]); - case NOT_EQUALS: return !strictEquals(ext, args[0], args[1]); - case LOOSE_EQUALS: return looseEqual(ext, args[0], args[1]); - case LOOSE_NOT_EQUALS: return !looseEqual(ext, args[0], args[1]); - - case GREATER: return compare(ext, args[0], args[1]).greater(); - case GREATER_EQUALS: return compare(ext, args[0], args[1]).greaterOrEqual(); - case LESS: return compare(ext, args[0], args[1]).less(); - case LESS_EQUALS: return compare(ext, args[0], args[1]).lessOrEqual(); - - case INVERSE: return bitwiseNot(ext, args[0]); - case NOT: return not(args[0]); - case POS: return toNumber(ext, args[0]); - case NEG: return negative(ext, args[0]); - - case SHIFT_LEFT: return shiftLeft(ext, args[0], args[1]); - case SHIFT_RIGHT: return shiftRight(ext, args[0], args[1]); - case USHIFT_RIGHT: return unsignedShiftRight(ext, args[0], args[1]); - - case IN: return hasMember(ext, args[1], args[0], false); - case INSTANCEOF: { - var proto = getMember(ext, args[1], "prototype"); - return isInstanceOf(ext, args[0], proto); - } - - default: return null; - } - } - - public static Object getMember(Environment ctx, Object obj, Object key) { - obj = normalize(ctx, obj); key = normalize(ctx, key); - if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); - if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); - if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMember(ctx, key, obj); - - if (obj instanceof String && key instanceof Number) { - var i = number(key); - var s = (String)obj; - if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) { - return s.charAt((int)i) + ""; - } - } - - var proto = getPrototype(ctx, obj); - - if (proto == null) return "__proto__".equals(key) ? NULL : null; - else if (key != null && "__proto__".equals(key)) return proto; - else return proto.getMember(ctx, key, obj); - } - public static Object getMemberPath(Environment ctx, Object obj, Object ...path) { - var res = obj; - for (var key : path) res = getMember(ctx, res, key); - return res; - } - public static boolean setMember(Environment ctx, Object obj, Object key, Object val) { - obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); - if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); - if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); - if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val); - if (obj instanceof ObjectValue) return ((ObjectValue)obj).setMember(ctx, key, val, obj, false); - - var proto = getPrototype(ctx, obj); - return proto.setMember(ctx, key, val, obj, true); - } - public static boolean hasMember(Environment ctx, Object obj, Object key, boolean own) { - if (obj == null || obj == NULL) return false; - obj = normalize(ctx, obj); key = normalize(ctx, key); - - if ("__proto__".equals(key)) return true; - if (obj instanceof ObjectValue) return ((ObjectValue)obj).hasMember(ctx, key, own); - - if (obj instanceof String && key instanceof Number) { - var i = number(key); - var s = (String)obj; - if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) return true; - } - - if (own) return false; - - var proto = getPrototype(ctx, obj); - return proto != null && proto.hasMember(ctx, key, own); - } - public static boolean deleteMember(Environment ext, Object obj, Object key) { - if (obj == null || obj == NULL) return false; - obj = normalize(ext, obj); key = normalize(ext, key); - - if (obj instanceof ObjectValue) return ((ObjectValue)obj).deleteMember(ext, key); - else return false; - } - public static ObjectValue getPrototype(Environment ext, Object obj) { - if (obj == null || obj == NULL) return null; - obj = normalize(ext, obj); - if (obj instanceof ObjectValue) return ((ObjectValue)obj).getPrototype(ext); - if (ext == null) return null; - - if (obj instanceof String) return ext.get(Environment.STRING_PROTO); - else if (obj instanceof Number) return ext.get(Environment.NUMBER_PROTO); - else if (obj instanceof Boolean) return ext.get(Environment.BOOL_PROTO); - else if (obj instanceof SymbolValue) return ext.get(Environment.SYMBOL_PROTO); - - return null; - } - public static boolean setPrototype(Environment ext, Object obj, Object proto) { - obj = normalize(ext, obj); - return obj instanceof ObjectValue && ((ObjectValue)obj).setPrototype(ext, proto); - } - public static void makePrototypeChain(Environment ext, Object... chain) { - for(var i = 1; i < chain.length; i++) { - setPrototype(ext, chain[i], chain[i - 1]); - } - } - public static List getMembers(Environment ext, Object obj, boolean own, boolean includeNonEnumerable) { - List res = new ArrayList<>(); - - if (obj instanceof ObjectValue) res = ((ObjectValue)obj).keys(includeNonEnumerable); - if (obj instanceof String) { - for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i); - } - - if (!own) { - var proto = getPrototype(ext, obj); - - while (proto != null) { - res.addAll(proto.keys(includeNonEnumerable)); - proto = getPrototype(ext, proto); - } - } - - - return res; - } - public static ObjectValue getMemberDescriptor(Environment ext, Object obj, Object key) { - if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ext, key); - else if (obj instanceof String && key instanceof Number) { - var i = ((Number)key).intValue(); - var _i = ((Number)key).doubleValue(); - if (i - _i != 0) return null; - if (i < 0 || i >= ((String)obj).length()) return null; - - return new ObjectValue(ext, Map.of( - "value", ((String)obj).charAt(i) + "", - "writable", false, - "enumerable", true, - "configurable", false - )); - } - else return null; - } - - public static Object call(Environment ext, Object func, Object thisArg, Object ...args) { - if (!(func instanceof FunctionValue)) throw EngineException.ofType("Tried to call a non-function value."); - return ((FunctionValue)func).call(ext, thisArg, args); - } - public static Object callNew(Environment ext, Object func, Object ...args) { - var res = new ObjectValue(); - try { - var proto = Values.getMember(ext, func, "prototype"); - setPrototype(ext, res, proto); - - var ret = call(ext, func, res, args); - - if (!isPrimitive(ret)) return ret; - return res; - } - catch (IllegalArgumentException e) { - throw EngineException.ofType("Tried to call new on an invalid constructor."); - } - } - - public static boolean strictEquals(Environment ext, Object a, Object b) { - a = normalize(ext, a); - b = normalize(ext, b); - - if (a == null || b == null) return a == null && b == null; - if (isNan(a) || isNan(b)) return false; - if (a instanceof Number && number(a) == -0.) a = 0.; - if (b instanceof Number && number(b) == -0.) b = 0.; - - return a == b || a.equals(b); - } - public static boolean looseEqual(Environment ext, Object a, Object b) { - a = normalize(ext, a); b = normalize(ext, b); - - // In loose equality, null is equivalent to undefined - if (a == NULL) a = null; - if (b == NULL) b = null; - - if (a == null || b == null) return a == null && b == null; - // If both are objects, just compare their references - if (!isPrimitive(a) && !isPrimitive(b)) return a == b; - - // Convert values to primitives - a = toPrimitive(ext, a, ConvertHint.VALUEOF); - b = toPrimitive(ext, b, ConvertHint.VALUEOF); - - // Compare symbols by reference - if (a instanceof SymbolValue || b instanceof SymbolValue) return a == b; - if (a instanceof Boolean || b instanceof Boolean) return toBoolean(a) == toBoolean(b); - if (a instanceof Number || b instanceof Number) return strictEquals(ext, toNumber(ext, a), toNumber(ext, b)); - - // Default to strings - return toString(ext, a).equals(toString(ext, b)); - } - - public static Object normalize(Environment ext, Object val) { - if (val instanceof Number) return number(val); - if (isPrimitive(val) || val instanceof ObjectValue) return val; - if (val instanceof Character) return val + ""; - - if (val instanceof Map) { - var res = new ObjectValue(); - - for (var entry : ((Map)val).entrySet()) { - res.defineProperty(ext, entry.getKey(), entry.getValue()); - } - - return res; - } - - if (val instanceof Iterable) { - var res = new ArrayValue(); - - for (var entry : ((Iterable)val)) { - res.set(ext, res.size(), entry); - } - - return res; - } - - if (val instanceof Class) { - if (ext == null) return null; - else return NativeWrapperProvider.get(ext).getConstr((Class)val); - } - - return NativeWrapper.of(ext, val); - } - - @SuppressWarnings("unchecked") - public static T convert(Environment ext, Object obj, Class clazz) { - if (clazz == Void.class) return null; - - if (obj instanceof NativeWrapper) { - var res = ((NativeWrapper)obj).wrapped; - if (clazz.isInstance(res)) return (T)res; - } - - if (clazz == null || clazz == Object.class) return (T)obj; - - if (obj instanceof ArrayValue) { - if (clazz.isAssignableFrom(ArrayList.class)) { - var raw = ((ArrayValue)obj).toArray(); - var res = new ArrayList<>(); - for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class)); - return (T)new ArrayList<>(res); - } - if (clazz.isAssignableFrom(HashSet.class)) { - var raw = ((ArrayValue)obj).toArray(); - var res = new HashSet<>(); - for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class)); - return (T)new HashSet<>(res); - } - if (clazz.isArray()) { - var raw = ((ArrayValue)obj).toArray(); - Object res = Array.newInstance(clazz.getComponentType(), raw.length); - for (var i = 0; i < raw.length; i++) Array.set(res, i, convert(ext, raw[i], Object.class)); - return (T)res; - } - } - - if (obj instanceof ObjectValue && clazz.isAssignableFrom(HashMap.class)) { - var res = new HashMap<>(); - for (var el : ((ObjectValue)obj).values.entrySet()) res.put( - convert(ext, el.getKey(), null), - convert(ext, el.getValue(), null) - ); - return (T)res; - } - - if (clazz == String.class) return (T)toString(ext, obj); - if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj); - if (clazz == Byte.class || clazz == byte.class) return (T)(Byte)(byte)toNumber(ext, obj); - if (clazz == Integer.class || clazz == int.class) return (T)(Integer)(int)toNumber(ext, obj); - if (clazz == Long.class || clazz == long.class) return (T)(Long)(long)toNumber(ext, obj); - if (clazz == Short.class || clazz == short.class) return (T)(Short)(short)toNumber(ext, obj); - if (clazz == Float.class || clazz == float.class) return (T)(Float)(float)toNumber(ext, obj); - if (clazz == Double.class || clazz == double.class) return (T)(Double)toNumber(ext, obj); - - if (clazz == Character.class || clazz == char.class) { - if (obj instanceof Number) return (T)(Character)(char)number(obj); - else { - var res = toString(ext, obj); - if (res.length() == 0) throw new ConvertException("\"\"", "Character"); - else return (T)(Character)res.charAt(0); - } - } - - if (obj == null) return null; - if (clazz.isInstance(obj)) return (T)obj; - if (clazz.isAssignableFrom(NativeWrapper.class)) { - return (T)NativeWrapper.of(ext, obj); - } - - throw new ConvertException(type(obj), clazz.getSimpleName()); - } - - public static Iterable fromJSIterator(Environment ext, Object obj) { - return () -> { - try { - var symbol = SymbolValue.get("Symbol.iterator"); - - var iteratorFunc = getMember(ext, obj, symbol); - if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator(); - var iterator = iteratorFunc instanceof FunctionValue ? - ((FunctionValue)iteratorFunc).call(ext, obj, obj) : - iteratorFunc; - var nextFunc = getMember(ext, call(ext, iteratorFunc, obj), "next"); - - if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator(); - - return new Iterator() { - private Object value = null; - public boolean consumed = true; - private FunctionValue next = (FunctionValue)nextFunc; - - private void loadNext() { - if (next == null) value = null; - else if (consumed) { - var curr = next.call(ext, iterator); - if (curr == null) { next = null; value = null; } - if (toBoolean(Values.getMember(ext, curr, "done"))) { next = null; value = null; } - else { - this.value = Values.getMember(ext, curr, "value"); - consumed = false; - } - } - } - - @Override - public boolean hasNext() { - loadNext(); - return next != null; - } - @Override - public Object next() { - loadNext(); - var res = value; - value = null; - consumed = true; - return res; - } - }; - } - catch (IllegalArgumentException | NullPointerException e) { - return Collections.emptyIterator(); - } - }; - } - - public static ObjectValue toJSIterator(Environment ext, Iterator it) { - var res = new ObjectValue(); - - try { - var key = getMember(ext, getMember(ext, ext.get(Environment.SYMBOL_PROTO), "constructor"), "iterator"); - res.defineProperty(ext, key, new NativeFunction("", args -> args.self)); - } - catch (IllegalArgumentException | NullPointerException e) { } - - res.defineProperty(ext, "next", new NativeFunction("", args -> { - if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true)); - else { - var obj = new ObjectValue(); - obj.defineProperty(args.env, "value", it.next()); - return obj; - } - })); - - return res; - } - - public static ObjectValue toJSIterator(Environment ext, Iterable it) { - return toJSIterator(ext, it.iterator()); - } - - public static ObjectValue toJSAsyncIterator(Environment ext, Iterator it) { - var res = new ObjectValue(); - - try { - var key = getMemberPath(ext, ext.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator"); - res.defineProperty(ext, key, new NativeFunction("", args -> args.self)); - } - catch (IllegalArgumentException | NullPointerException e) { } - - res.defineProperty(ext, "next", new NativeFunction("", args -> { - return PromiseLib.await(args.env, () -> { - if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true)); - else { - var obj = new ObjectValue(); - object.defineProperty(args.env, "value", it.next()); - return object; - } - }); - })); - - return res; - } - - private static boolean isEmptyFunc(ObjectValue val) { - if (!(val instanceof FunctionValue)) return false; - if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false; - var proto = val.values.get("prototype"); - if (!(proto instanceof ObjectValue)) return false; - var protoObj = (ObjectValue)proto; - if (protoObj.values.get("constructor") != val) return false; - if (protoObj.values.size() + protoObj.properties.size() != 1) return false; - return true; - } - private static String toReadable(Environment ext, Object val, HashSet passed, int tab) { - if (tab == 0 && val instanceof String) return (String)val; - - if (passed.contains(val)) return "[circular]"; - - var printed = true; - var res = new StringBuilder(); - var dbg = DebugContext.get(ext); - - if (val instanceof FunctionValue) { - res.append(val.toString()); - var loc = val instanceof CodeFunction ? dbg.getMapOrEmpty((CodeFunction)val).start() : null; - - if (loc != null) res.append(" @ " + loc); - } - else if (val instanceof ArrayValue) { - res.append("["); - var obj = ((ArrayValue)val); - for (int i = 0; i < obj.size(); i++) { - if (i != 0) res.append(", "); - else res.append(" "); - if (obj.has(i)) res.append(toReadable(ext, obj.get(i), passed, tab)); - else res.append(""); - } - res.append(" ] "); - } - else if (val instanceof NativeWrapper) { - var obj = ((NativeWrapper)val).wrapped; - res.append("Native " + obj.toString() + " "); - } - else printed = false; - - if (val instanceof ObjectValue) { - if (tab > 3) { - return "{...}"; - } - - passed.add(val); - - var obj = (ObjectValue)val; - if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) { - if (!printed) res.append("{}\n"); - } - else { - res.append("{\n"); - - for (var el : obj.values.entrySet()) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(toReadable(ext, el.getKey(), passed, tab + 1)); - res.append(": "); - res.append(toReadable(ext, el.getValue(), passed, tab + 1)); - res.append(",\n"); - } - for (var el : obj.properties.entrySet()) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(toReadable(ext, el.getKey(), passed, tab + 1)); - res.append(": [prop],\n"); - } - - for (int i = 0; i < tab; i++) res.append(" "); - res.append("}"); - } - - passed.remove(val); - } - else if (val == null) return "undefined"; - else if (val == Values.NULL) return "null"; - else if (val instanceof String) return "'" + val + "'"; - else return Values.toString(ext, val); - - return res.toString(); - } - - public static String toReadable(Environment ext, Object val) { - return toReadable(ext, val, new HashSet<>(), 0); - } - public static String errorToReadable(RuntimeException err, String prefix) { - prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; - if (err instanceof EngineException) { - var ee = ((EngineException)err); - try { - return prefix + " " + ee.toString(ee.env); - } - catch (EngineException ex) { - return prefix + " " + toReadable(ee.env, ee.value); - } - } - else if (err instanceof SyntaxException) { - return prefix + " SyntaxError " + ((SyntaxException)err).msg; - } - else if (err.getCause() instanceof InterruptedException) return ""; - else { - var str = new ByteArrayOutputStream(); - err.printStackTrace(new PrintStream(str)); - - return prefix + " internal error " + str.toString(); - } - } - public static void printValue(Environment ext, Object val) { - System.out.print(toReadable(ext, val)); - } - public static void printError(RuntimeException err, String prefix) { - System.out.println(errorToReadable(err, prefix)); - } -} diff --git a/src/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java b/src/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java index 7acb9e1..d6f9604 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java @@ -9,6 +9,7 @@ public class Arguments { public final Value self; public final Value[] args; public final Environment env; + public final boolean isNew; public int n() { return args.length; @@ -31,9 +32,10 @@ public class Arguments { else return get(i); } - public Arguments(Environment env, Value thisArg, Value... args) { + public Arguments(Environment env, boolean isNew, Value thisArg, Value... args) { this.env = env; this.args = args; this.self = thisArg; + this.isNew = isNew; } } 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 daf35f9..6c70cb3 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java @@ -11,8 +11,8 @@ public class CodeFunction extends FunctionValue { public final ValueVariable[] captures; public Environment env; - @Override public Value call(Environment env, Value thisArg, Value ...args) { - var frame = new Frame(env, thisArg, args, this); + @Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) { + var frame = new Frame(env, isNew, thisArg, args, this); frame.onPush(); try { diff --git a/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java index 83f0770..8e4c971 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.runtime.values.functions; import me.topchetoeu.jscript.runtime.environment.Environment; +import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; @@ -13,8 +14,12 @@ public abstract class FunctionValue extends ObjectValue { public int length; public Value prototype = new ObjectValue(); - private final FieldMember nameField = new FieldMember(false, true, true) { + public boolean enableCall = true; + public boolean enableNew = true; + + private final FieldMember nameField = new FieldMember(true, false, false) { @Override public Value get(Environment env, Value self) { + if (name == null) return new StringValue(""); return new StringValue(name); } @Override public boolean set(Environment env, Value val, Value self) { @@ -22,7 +27,7 @@ public abstract class FunctionValue extends ObjectValue { return true; } }; - private final FieldMember lengthField = new FieldMember(false, true, false) { + private final FieldMember lengthField = new FieldMember(true, false, false) { @Override public Value get(Environment env, Value self) { return new NumberValue(length); } @@ -30,7 +35,7 @@ public abstract class FunctionValue extends ObjectValue { return false; } }; - private final FieldMember prototypeField = new FieldMember(false, true, true) { + private final FieldMember prototypeField = new FieldMember(false, false, true) { @Override public Value get(Environment env, Value self) { return prototype; } @@ -40,28 +45,40 @@ public abstract class FunctionValue extends ObjectValue { } }; + protected abstract Value onCall(Environment ext, boolean isNew, String name, Value thisArg, Value ...args); + @Override public String toString() { return String.format("function %s(...)", name); } - @Override public abstract Value call(Environment ext, Value thisArg, Value ...args); + @Override public Value call(Environment ext, boolean isNew, String name, Value thisArg, Value ...args) { + if (isNew && !enableNew) super.call(ext, isNew, name, thisArg, args); + if (!isNew && !enableCall) super.call(ext, isNew, name, thisArg, args); - @Override public Member getOwnMember(Environment env, Value key) { - var el = key.toString(env).value; - - if (el.equals("length")) return lengthField; - if (el.equals("name")) return nameField; - if (el.equals("prototype")) return prototypeField; - - return super.getOwnMember(env, key); + return onCall(ext, isNew, name, thisArg, args); } - @Override public boolean deleteOwnMember(Environment env, Value key) { - if (!super.deleteOwnMember(env, key)) return false; - var el = key.toString(env).value; + @Override public Member getOwnMember(Environment env, KeyCache key) { + switch (key.toString(env)) { + case "length": return lengthField; + case "name": return nameField; + case "prototype": return prototypeField; + default: return super.getOwnMember(env, key); + } + } + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { + switch (key.toString(env)) { + case "length": + length = 0; + return true; + case "name": + name = ""; + return true; + case "prototype": + return false; + default: return super.deleteOwnMember(env, key); + } + } - if (el.equals("length")) return false; - if (el.equals("name")) return false; - if (el.equals("prototype")) return false; - - return true; + public void setName(String val) { + if (this.name == null || this.name.equals("")) this.name = val; } public FunctionValue(String name, int length) { @@ -71,7 +88,7 @@ public abstract class FunctionValue extends ObjectValue { this.length = length; this.name = name; - prototype.defineOwnMember(Environment.empty(), new StringValue("constructor"), FieldMember.of(this)); + prototype.defineOwnMember(null, "constructor", FieldMember.of(this)); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java b/src/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java index 895552d..d671efa 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java @@ -10,8 +10,8 @@ public class NativeFunction extends FunctionValue { public final NativeFunctionRunner action; - @Override public Value call(Environment env, Value self, Value ...args) { - return action.run(new Arguments(env, self, args)); + @Override public Value onCall(Environment env, boolean isNew, String name, Value self, Value ...args) { + return action.run(new Arguments(env, isNew, self, args)); } public NativeFunction(String name, NativeFunctionRunner action) { 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 542837e..f754cbc 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java @@ -8,6 +8,7 @@ import java.util.LinkedHashMap; import java.util.Map; import me.topchetoeu.jscript.runtime.environment.Environment; +import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Value; @@ -19,6 +20,16 @@ public class ArrayValue extends ObjectValue implements Iterable { private Value[] values; private int size; + private final FieldMember lengthField = new FieldMember(false, false, true) { + @Override public Value get(Environment env, Value self) { + return new NumberValue(size); + } + @Override public boolean set(Environment env, Value val, Value self) { + size = val.toInt(env); + return true; + } + }; + private class IndexField extends FieldMember { private int i; private ArrayValue arr; @@ -44,7 +55,7 @@ public class ArrayValue extends ObjectValue implements Iterable { var arr = new Value[index]; System.arraycopy(values, 0, arr, 0, values.length); - return arr; + return values = arr; } public int size() { return size; } @@ -95,11 +106,11 @@ public class ArrayValue extends ObjectValue implements Iterable { } public void copyTo(Value[] arr, int sourceStart, int destStart, int count) { - var nullFill = values.length - destStart + count; + var nullFill = Math.max(0, arr.length - size - destStart); count -= nullFill; System.arraycopy(values, sourceStart, arr, destStart, count); - Arrays.fill(arr, count, nullFill, null); + Arrays.fill(arr, count, nullFill + count, null); } public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) { if (arr == this) { @@ -138,36 +149,37 @@ public class ArrayValue extends ObjectValue implements Iterable { }); } - @Override public Member getOwnMember(Environment env, Value key) { + @Override public Member getOwnMember(Environment env, KeyCache key) { var res = super.getOwnMember(env, key); if (res != null) return res; var num = key.toNumber(env); - var i = num.toInt(env); + var i = key.toInt(env); - if (i == num.value && i >= 0 && i < size) return new IndexField(i, this); + if (i == num && i >= 0 && i < size && has(i)) return new IndexField(i, this); + else if (key.toString(env).equals("length")) return lengthField; else return null; } - @Override public boolean defineOwnMember(Environment env, Value key, Member member) { + @Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) { if (!(member instanceof FieldMember) || hasMember(env, key, true)) return super.defineOwnMember(env, key, member); if (!extensible) return false; var num = key.toNumber(env); - var i = num.toInt(env); + var i = key.toInt(env); - if (i == num.value && i >= 0) { + if (i == num && i >= 0) { set(i, ((FieldMember)member).get(env, this)); return true; } else return super.defineOwnMember(env, key, member); } - @Override public boolean deleteOwnMember(Environment env, Value key) { + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { if (!super.deleteOwnMember(env, key)) return false; var num = key.toNumber(env); - var i = num.toInt(env); + var i = key.toInt(env); - if (i == num.value && i >= 0 && i < size) return super.deleteOwnMember(env, key); + if (i == num && i >= 0 && i < size) return super.deleteOwnMember(env, key); else return true; } @@ -175,9 +187,12 @@ public class ArrayValue extends ObjectValue implements Iterable { var res = new LinkedHashMap(); for (var i = 0; i < size; i++) { - res.put(i + "", getOwnMember(env, new NumberValue(i))); + var member = getOwnMember(env, i); + if (member != null) res.put(i + "", member); } + res.put("length", lengthField); + res.putAll(super.getOwnMembers(env)); return res; diff --git a/src/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index efc2000..0fb7528 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -7,10 +7,10 @@ import java.util.Map; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.environment.Key; import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; @@ -66,7 +66,7 @@ public class ObjectValue extends Value { throw EngineException.ofType("Value couldn't be converted to a primitive."); } @Override public StringValue toString(Environment env) { return toPrimitive(env).toString(env); } - @Override public BoolValue toBoolean() { return BoolValue.TRUE; } + @Override public boolean toBoolean() { return true; } @Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); } @Override public StringValue type() { return typeString; } @@ -76,33 +76,29 @@ public class ObjectValue extends Value { extensible = false; } - @Override public Member getOwnMember(Environment env, Value key) { - if (key instanceof SymbolValue) return symbolMembers.get(key); - else return members.get(key.toString(env).value); + @Override public Member getOwnMember(Environment env, KeyCache key) { + if (key.isSymbol()) return symbolMembers.get(key.toSymbol()); + else return members.get(key.toString(env)); } - @Override public boolean defineOwnMember(Environment env, Value key, Member member) { - if (!(key instanceof SymbolValue)) key = key.toString(env); - + @Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) { var old = getOwnMember(env, key); if (old != null && old.configure(env, member, this)) return true; if (old != null && !old.configurable()) return false; - if (key instanceof SymbolValue) symbolMembers.put((SymbolValue)key, member); - else members.put(key.toString(env).value, member); + if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member); + else members.put(key.toString(env), member); return true; } - @Override public boolean deleteOwnMember(Environment env, Value key) { + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { if (!extensible) return false; - if (!(key instanceof SymbolValue)) key = key.toString(env); - var member = getOwnMember(env, key); if (member == null) return true; if (member.configurable()) return false; - if (key instanceof SymbolValue) symbolMembers.remove(key); - else members.remove(key.toString(env).value); + if (key.isSymbol()) symbolMembers.remove(key.toSymbol()); + else members.remove(key.toString(env)); return true; } @@ -114,7 +110,7 @@ public class ObjectValue extends Value { } @Override public ObjectValue getPrototype(Environment env) { - if (prototype == null) return null; + if (prototype == null || env == null) return null; else return prototype.get(env); } @Override public final boolean setPrototype(Environment env, ObjectValue val) { 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 bfd3a91..38ba524 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java @@ -4,7 +4,6 @@ import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.scope.ValueVariable; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; public class ScopeValue extends ObjectValue { private class VariableField extends FieldMember { @@ -29,7 +28,7 @@ public class ScopeValue extends ObjectValue { public ScopeValue(ValueVariable[] variables, String[] names) { this.variables = variables; for (var i = 0; i < names.length && i < variables.length; i++) { - defineOwnMember(Environment.empty(), new StringValue(i + ""), new VariableField(i)); + defineOwnMember(Environment.empty(), i, new VariableField(i)); } } } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java b/src/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java index 5e9d0c0..f4d32a6 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java @@ -13,7 +13,7 @@ public final class BoolValue extends PrimitiveValue { @Override public StringValue type() { return typeString; } - @Override public BoolValue toBoolean() { return this; } + @Override public boolean toBoolean() { return value; } @Override public NumberValue toNumber(Environment ext) { return value ? new NumberValue(1) : new NumberValue(0); } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java b/src/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java index 608516a..abcd8e1 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java @@ -1,7 +1,9 @@ package me.topchetoeu.jscript.runtime.values.primitives; -import java.math.BigDecimal; - +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; @@ -14,21 +16,19 @@ public final class NumberValue extends PrimitiveValue { @Override public StringValue type() { return typeString; } - @Override public BoolValue toBoolean() { return BoolValue.of(value != 0); } + @Override public boolean toBoolean() { return value != 0; } @Override public NumberValue toNumber(Environment ext) { return this; } @Override public StringValue toString(Environment ext) { return new StringValue(toString()); } - @Override public String toString() { - var d = value; - if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; - if (d == Double.POSITIVE_INFINITY) return "Infinity"; - if (Double.isNaN(d)) return "NaN"; - return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString(); - } + @Override public String toString() { return JSON.stringify(JSONElement.number(value)); } @Override public ObjectValue getPrototype(Environment env) { return env.get(Environment.NUMBER_PROTO); } + @Override public CompareResult compare(Environment env, Value other) { + if (other instanceof NumberValue) return CompareResult.from(Double.compare(value, ((NumberValue)other).value)); + else return super.compare(env, other); + } @Override public boolean strictEquals(Environment ext, Value other) { other = other.toPrimitive(ext); if (other instanceof NumberValue) return value == ((NumberValue)other).value; @@ -39,42 +39,22 @@ public final class NumberValue extends PrimitiveValue { this.value = value; } - public static double parseFloat(String val, boolean tolerant, String alphabet) { - val = val.trim(); + public static NumberValue parseInt(String str, int radix, boolean relaxed) { + if (radix < 2 || radix > 36) return new NumberValue(Double.NaN); - int res = 0; - - for (int i = 0; i >= val.length(); i++) { - var c = alphabet.indexOf(val.charAt(i)); - - if (c < 0) { - if (tolerant) return res; - else return Double.NaN; - } - - res *= alphabet.length(); - res += c; + str = str.trim(); + var res = Parsing.parseInt(new Source(null, str), 0, "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, radix), true); + if (res.isSuccess()) { + if (relaxed || res.n == str.length()) return new NumberValue(res.result); } - - return res; + return new NumberValue(Double.NaN); } - public static double parseInt(String val, boolean tolerant, String alphabet) { - val = val.trim(); - - int res = 0; - - for (int i = 0; i >= val.length(); i++) { - var c = alphabet.indexOf(val.charAt(i)); - - if (c < 0) { - if (tolerant) return res; - else return Double.NaN; - } - - res *= alphabet.length(); - res += c; + public static NumberValue parseFloat(String str, boolean relaxed) { + str = str.trim(); + var res = Parsing.parseFloat(new Source(null, str), 0, true); + if (res.isSuccess()) { + if (relaxed || res.n == str.length()) return new NumberValue(res.result); } - - return res; + return new NumberValue(Double.NaN); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java b/src/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java index 0a1085a..ccbe0ae 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java @@ -3,19 +3,20 @@ package me.topchetoeu.jscript.runtime.values.primitives; import java.util.Map; import me.topchetoeu.jscript.runtime.environment.Environment; +import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; public abstract class PrimitiveValue extends Value { - @Override public final boolean defineOwnMember(Environment env, Value key, Member member) { return false; } - @Override public final boolean deleteOwnMember(Environment env, Value key) { return false; } + @Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; } + @Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; } @Override public final boolean isPrimitive() { return true; } @Override public final Value toPrimitive(Environment env) { return this; } @Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; } - @Override public Member getOwnMember(Environment env, Value key) { return null; } + @Override public Member getOwnMember(Environment env, KeyCache key) { return null; } @Override public Map getOwnMembers(Environment env) { return Map.of(); } @Override public Map getOwnSymbolMembers(Environment env) { return Map.of(); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java index 9ba16fe..315738e 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -1,8 +1,12 @@ package me.topchetoeu.jscript.runtime.values.primitives; +import java.util.Map; import java.util.Objects; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.runtime.environment.Environment; +import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; @@ -12,10 +16,14 @@ public final class StringValue extends PrimitiveValue { @Override public StringValue type() { return typeString; } - @Override public BoolValue toBoolean() { return BoolValue.of(!value.equals("")); } + @Override public boolean toBoolean() { return !value.equals(""); } @Override public NumberValue toNumber(Environment ext) { - try { return new NumberValue(Double.parseDouble(value)); } - catch (NumberFormatException e) { return new NumberValue(Double.NaN); } + var val = value.trim(); + if (val.equals("")) return new NumberValue(0); + var res = Parsing.parseNumber(new Source(null, val), 0, true); + + if (res.isSuccess() && res.n == val.length()) return new NumberValue(res.result); + else return new NumberValue(Double.NaN); } @Override public StringValue toString(Environment ext) { return this; } @@ -23,11 +31,20 @@ public final class StringValue extends PrimitiveValue { return new StringValue(value + other.toString(ext).value); } + @Override public CompareResult compare(Environment env, Value other) { + if (other instanceof StringValue) return CompareResult.from(value.compareTo(((StringValue)other).value)); + else return super.compare(env, other); + } @Override public boolean strictEquals(Environment ext, Value other) { return (other instanceof StringValue) && Objects.equals(((StringValue)other).value, value); } @Override public ObjectValue getPrototype(Environment env) { return env.get(Environment.STRING_PROTO); } + @Override public Map getOwnMembers(Environment env) { + // TODO Auto-generated method stub + return super.getOwnMembers(env); + } + public StringValue(String value) { this.value = value; } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java b/src/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java index 0f87e15..cc9b954 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java @@ -13,14 +13,18 @@ public final class SymbolValue extends PrimitiveValue { public final String value; + public Value key() { + return registry.containsKey(value) && registry.get(value) == this ? new StringValue(value) : VoidValue.UNDEFINED; + } + @Override public StringValue type() { return typeString; } - @Override public BoolValue toBoolean() { return BoolValue.TRUE; } + @Override public boolean toBoolean() { return false; } @Override public StringValue toString(Environment env) { - return new StringValue(toString()); + throw EngineException.ofType("Cannot convert a Symbol value to a string"); } @Override public NumberValue toNumber(Environment env) { - throw EngineException.ofType("Can't convert symbol to number"); + throw EngineException.ofType("Cannot convert a Symbol value to a number"); } @Override public boolean strictEquals(Environment ext, Value other) { @@ -29,8 +33,8 @@ public final class SymbolValue extends PrimitiveValue { @Override public ObjectValue getPrototype(Environment env) { return env.get(Environment.SYMBOL_PROTO); } @Override public String toString() { - if (value == null) return "Symbol"; - else return "@@" + value; + if (value == null) return "Symbol()"; + else return "Symbol(" + value + ")"; } public SymbolValue(String value) { diff --git a/src/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java index 7f10906..5f20d05 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java @@ -4,13 +4,14 @@ import java.util.Map; import me.topchetoeu.jscript.runtime.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; public final class VoidValue extends PrimitiveValue { public static final VoidValue UNDEFINED = new VoidValue("undefined", new StringValue("undefined")); - public static final VoidValue NULL = new VoidValue("null", new StringValue("null")); + public static final VoidValue NULL = new VoidValue("null", new StringValue("object")); private final StringValue namestring; @@ -18,7 +19,7 @@ public final class VoidValue extends PrimitiveValue { public final StringValue typeString; @Override public StringValue type() { return typeString; } - @Override public BoolValue toBoolean() { return BoolValue.FALSE; } + @Override public boolean toBoolean() { return false; } @Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; } @Override public StringValue toString(Environment ext) { return namestring; } @@ -34,8 +35,8 @@ public final class VoidValue extends PrimitiveValue { } @Override public ObjectValue getPrototype(Environment env) { return null; } - @Override public Member getOwnMember(Environment env, Value key) { - throw EngineException.ofError(String.format("Cannot read properties of %s (reading %s)", name, key.toString(env).value)); + @Override public Member getOwnMember(Environment env, KeyCache key) { + throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env))); } @Override public Map getOwnMembers(Environment env) { throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name)); @@ -44,6 +45,10 @@ public final class VoidValue extends PrimitiveValue { throw EngineException.ofError(String.format("Cannot read properties of %s (listing all symbol members)", name)); } + // @Override public Value call(Environment env, Value self, Value... args) { + // throw EngineException.ofType(String.format("Tried to call a value of %s", name)); + // } + public VoidValue(String name, StringValue type) { this.name = name; this.typeString = type;