From 515011b3ef58762996246d1f484560da8b6c9c26 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:01:34 +0300 Subject: [PATCH 01/15] refactor: improve meber listing system --- .../me/topchetoeu/jscript/runtime/Frame.java | 20 +++----- .../jscript/runtime/InstructionRunner.java | 5 +- .../jscript/runtime/JSONConverter.java | 9 ++-- .../jscript/runtime/values/Value.java | 51 ++++++++----------- .../runtime/values/objects/ArrayValue.java | 17 +++---- .../runtime/values/objects/ObjectValue.java | 30 ++++++++--- .../values/primitives/PrimitiveValue.java | 6 +-- .../values/primitives/StringValue.java | 31 +++++++++-- .../runtime/values/primitives/VoidValue.java | 10 ++-- 9 files changed, 98 insertions(+), 81 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index 90c0af9..fa4c7f2 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,8 +1,8 @@ package me.topchetoeu.jscript.runtime; import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.LinkedHashSet; +import java.util.Set; import java.util.Stack; import me.topchetoeu.jscript.common.Instruction; @@ -402,19 +402,11 @@ public final class Frame { } }; } - @Override public Map getOwnMembers(Environment env) { - var res = new LinkedHashMap(); + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { + var res = new LinkedHashSet(); + res.addAll(super.getOwnMembers(env, onlyEnumerable)); - for (var i = 0; i < stackPtr; i++) { - var _i = i; - res.put(i + "", 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) { - stack[_i] = val; - return true; - } - }); - } + for (var i = 0; i < stackPtr; i++) res.add(i + ""); return res; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index a6186ac..ecfa152 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -7,7 +7,6 @@ import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; @@ -83,14 +82,14 @@ public class InstructionRunner { private static Value execKeys(Environment env, Instruction instr, Frame frame) { var val = frame.pop(); - var members = new ArrayList<>(val.getMembers(env, false, true).keySet()); + var members = new ArrayList<>(val.getMembers(env, false, true)); Collections.reverse(members); frame.push(null); for (var el : members) { var obj = new ObjectValue(); - obj.defineOwnMember(env, "value", FieldMember.of(new StringValue(el))); + obj.defineOwnMember(env, "value", new StringValue(el)); frame.push(obj); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java index b895dea..8a19ccc 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java @@ -70,10 +70,11 @@ public class JSONConverter { var res = new JSONMap(); - for (var el : val.getMembers(env, true, true).entrySet()) { - var jsonEl = fromJs(env, el.getValue().get(env, val), prev); - if (jsonEl == null) continue; - res.put(el.getKey(), jsonEl); + for (var key : val.getOwnMembers(env, true)) { + var el = fromJs(env, val.getMember(env, key), prev); + if (el == null) continue; + + res.put(key, el); } prev.remove(val); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index bf17a7f..f96e7fb 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -7,8 +7,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; @@ -119,8 +120,8 @@ public abstract class Value { } public abstract Member getOwnMember(Environment env, KeyCache key); - public abstract Map getOwnMembers(Environment env); - public abstract Map getOwnSymbolMembers(Environment env); + public abstract Set getOwnMembers(Environment env, boolean onlyEnumerable); + public abstract Set getOwnSymbolMembers(Environment env, boolean onlyEnumerable); public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member); public abstract boolean deleteOwnMember(Environment env, KeyCache key); @@ -317,8 +318,8 @@ public abstract class Value { return deleteMember(env, new KeyCache(key)); } - public final Map getMembers(Environment env, boolean own, boolean onlyEnumerable) { - var res = new LinkedHashMap(); + public final Set getMembers(Environment env, boolean own, boolean onlyEnumerable) { + var res = new LinkedHashSet(); var protos = new ArrayList(); for (var proto = this; proto != null; proto = proto.getPrototype(env)) { @@ -329,19 +330,13 @@ public abstract class Value { Collections.reverse(protos); for (var proto : protos) { - if (onlyEnumerable) { - for (var el : proto.getOwnMembers(env).entrySet()) { - if (!el.getValue().enumerable()) continue; - res.put(el.getKey(), el.getValue()); - } - } - else res.putAll(proto.getOwnMembers(env)); + res.addAll(proto.getOwnMembers(env, onlyEnumerable)); } return res; } - public final Map getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) { - var res = new LinkedHashMap(); + public final Set getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) { + var res = new LinkedHashSet(); var protos = new ArrayList(); for (var proto = this; proto != null; proto = proto.getPrototype(env)) { @@ -352,13 +347,7 @@ public abstract class Value { Collections.reverse(protos); for (var proto : protos) { - if (onlyEnumerable) { - for (var el : proto.getOwnSymbolMembers(env).entrySet()) { - if (!el.getValue().enumerable()) continue; - res.put(el.getKey(), el.getValue()); - } - } - else res.putAll(proto.getOwnSymbolMembers(env)); + res.addAll(proto.getOwnSymbolMembers(env, onlyEnumerable)); } return res; @@ -444,7 +433,7 @@ public abstract class Value { if ( func.prototype instanceof ObjectValue objProto && objProto.getMember(env, "constructor") == func && - objProto.getOwnMembers(env).size() + objProto.getOwnSymbolMembers(env).size() == 1 + objProto.getOwnMembers(env, true).size() + objProto.getOwnSymbolMembers(env, true).size() == 1 ) { keys.remove("constructor"); } } else if (this instanceof ArrayValue) { @@ -469,29 +458,29 @@ public abstract class Value { passed.add(this); - if (keys.size() + obj.getOwnSymbolMembers(env).size() == 0) { + if (keys.size() + obj.getOwnSymbolMembers(env, true).size() == 0) { if (!printed) res.append("{}\n"); } else if (!printed) { if (tab > 3) return "{...}"; res.append("{\n"); - for (var entry : obj.getOwnSymbolMembers(env).entrySet()) { + for (var entry : obj.getOwnSymbolMembers(env, true)) { for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append("[" + entry.getKey().value + "]" + ": "); + res.append("[" + entry.value + "]" + ": "); - var member = entry.getValue(); - if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); + var member = obj.getOwnMember(env, entry); + if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); else res.append("[property]"); res.append(",\n"); } - for (var entry : obj.getOwnMembers(env).entrySet()) { + for (var entry : obj.getOwnMembers(env, true)) { for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(entry.getKey() + ": "); + res.append(entry + ": "); - var member = entry.getValue(); - if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); + var member = obj.getOwnMember(env, entry); + if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); else res.append("[property]"); res.append(",\n"); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java index 43a326b..397ab5b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java @@ -4,8 +4,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.LinkedHashSet; +import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.values.KeyCache; @@ -183,17 +183,16 @@ public class ArrayValue extends ObjectValue implements Iterable { else return true; } - @Override public Map getOwnMembers(Environment env) { - var res = new LinkedHashMap(); + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { + var res = new LinkedHashSet(); + + res.addAll(super.getOwnMembers(env, onlyEnumerable)); for (var i = 0; i < size; i++) { - var member = getOwnMember(env, i); - if (member != null) res.put(i + "", member); + if (has(i)) res.add(i + ""); } - res.put("length", lengthField); - - res.putAll(super.getOwnMembers(env)); + if (!onlyEnumerable) res.add("length"); return res; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index da5cba4..ead6365 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -1,8 +1,8 @@ package me.topchetoeu.jscript.runtime.values.objects; -import java.util.Collections; import java.util.LinkedHashMap; -import java.util.Map; +import java.util.LinkedHashSet; +import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; @@ -100,11 +100,29 @@ public class ObjectValue extends Value { return true; } - @Override public Map getOwnMembers(Environment env) { - return members; + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { + if (onlyEnumerable) { + var res = new LinkedHashSet(); + + for (var el : members.entrySet()) { + if (el.getValue().enumerable()) res.add(el.getKey()); + } + + return res; + } + else return members.keySet(); } - @Override public Map getOwnSymbolMembers(Environment env) { - return Collections.unmodifiableMap(symbolMembers); + @Override public Set getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { + if (onlyEnumerable) { + var res = new LinkedHashSet(); + + for (var el : symbolMembers.entrySet()) { + if (el.getValue().enumerable()) res.add(el.getKey()); + } + + return res; + } + else return symbolMembers.keySet(); } @Override public ObjectValue getPrototype(Environment env) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java index 4d18f7a..6806775 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java @@ -1,6 +1,6 @@ package me.topchetoeu.jscript.runtime.values.primitives; -import java.util.Map; +import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.values.KeyCache; @@ -17,6 +17,6 @@ public abstract class PrimitiveValue extends Value { @Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; } @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(); } + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { return Set.of(); } + @Override public Set getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return Set.of(); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java index 8df3f52..b2c58ca 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -1,11 +1,14 @@ package me.topchetoeu.jscript.runtime.values.primitives; -import java.util.Map; +import java.util.LinkedHashSet; +import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; +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.objects.ObjectValue; public final class StringValue extends PrimitiveValue { @@ -32,9 +35,29 @@ public final class StringValue extends PrimitiveValue { @Override public ObjectValue getPrototype(Environment env) { return env.get(STRING_PROTO); } - @Override public Map getOwnMembers(Environment env) { - // TODO Auto-generated method stub - return super.getOwnMembers(env); + @Override public Member getOwnMember(Environment env, KeyCache key) { + var num = key.toNumber(env); + var i = key.toInt(env); + + if (i == num && i >= 0 && i < value.length()) { + return FieldMember.of(new StringValue(value.charAt(i) + ""), false, true, false); + } + else if (key.toString(env).equals("length")) { + return FieldMember.of(new NumberValue(value.length()), false, false, false); + } + else return super.getOwnMember(env, key); + } + + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { + var res = new LinkedHashSet(); + + res.addAll(super.getOwnMembers(env, onlyEnumerable)); + + for (var i = 0; i < value.length(); i++) res.add(i + ""); + + if (!onlyEnumerable) res.add("length"); + + return res; } public StringValue(String value) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java index 9e0539d..4c7c199 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java @@ -1,6 +1,6 @@ package me.topchetoeu.jscript.runtime.values.primitives; -import java.util.Map; +import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; @@ -24,17 +24,13 @@ public final class VoidValue extends PrimitiveValue { @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) { + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name)); } - @Override public Map getOwnSymbolMembers(Environment env) { + @Override public Set getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { 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; -- 2.45.2 From 63ccd5757e0dba7ee1523acc7fac283ab262ae34 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:02:11 +0300 Subject: [PATCH 02/15] feat: implement spread_obj intrinsic --- .../jscript/runtime/SimpleRepl.java | 22 ++++++++++++++++ src/main/resources/lib/index.js | 25 +++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 8fc9e68..65f32a3 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -195,6 +195,19 @@ public class SimpleRepl { args.get(0).setPrototype(env, proto); return args.get(0); })); + res.defineOwnMember(env, "getOwnMembers", new NativeFunction(args -> { + var val = new ArrayValue(); + + for (var key : args.get(0).getOwnMembers(env, args.get(1).toBoolean())) { + val.set(val.size(), new StringValue(key)); + } + + return val; + })); + res.defineOwnMember(env, "getOwnSymbolMembers", new NativeFunction(args -> { + return ArrayValue.of(args.get(0).getOwnSymbolMembers(env, args.get(1).toBoolean())); + })); + return res; } @@ -294,9 +307,18 @@ public class SimpleRepl { return Value.UNDEFINED; })); + res.defineOwnMember(env, "setIntrinsic", new NativeFunction(args -> { + var name = args.get(0).toString(env).value; + var val = args.get(1); + + Value.intrinsics(environment).put(name, val); + + return Value.UNDEFINED; + })); res.defineOwnMember(env, "compile", new NativeFunction(args -> { return Compiler.compileFunc(env, new Filename("jscript", "func" + i[0]++ + ".js"), args.get(0).toString(env).value); })); + return res; } diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index 43e8837..b901359 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -30,10 +30,11 @@ const setConstructable = primordials.function.setConstructable; const setCallable = primordials.function.setCallable; const invoke = primordials.function.invoke; +const json = primordials.json; + const setGlobalPrototype = primordials.setGlobalPrototype; const compile = primordials.compile; - -const json = primordials.json; +const setIntrinsic = primordials.setIntrinsic; const valueKey = makeSymbol("Primitive.value"); const undefined = ({}).definitelyDefined; @@ -304,6 +305,26 @@ defineField(Function.prototype, "valueOf", true, false, true, function() { target.Function = Function; +let spread_obj; + +setIntrinsic("spread_obj", spread_obj = (target, obj) => { + if (obj === null || obj === undefined) return; + const members = getOwnMembers(obj, true); + const symbols = getOwnSymbolMembers(obj, true); + + for (let i = 0; i < members.length; i++) { + const member = members[i]; + target[member] = obj[member]; + } + + for (let i = 0; i < symbols.length; i++) { + const member = symbols[i]; + target[member] = obj[member]; + } +}); + +target.spread_obj = spread_obj; + setGlobalPrototype("string", String.prototype); setGlobalPrototype("number", Number.prototype); setGlobalPrototype("boolean", Boolean.prototype); -- 2.45.2 From b9268518f631ac5decc7d0039ec16963d3e536f3 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:46:10 +0300 Subject: [PATCH 03/15] fix: array statements broken when empty elements --- .../me/topchetoeu/jscript/compilation/values/ArrayNode.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java index 4cbf300..53ba93d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java @@ -18,11 +18,10 @@ public class ArrayNode extends Node { @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadArr(statements.length)); - if (statements.length > 0) target.add(Instruction.dup(statements.length)); - for (var i = 0; i < statements.length; i++) { var el = statements[i]; if (el != null) { + target.add(Instruction.dup()); el.compile(target, true); target.add(Instruction.storeMember(i)); } -- 2.45.2 From 5f88061ee7599110672fa4bf794c314c9f1a0b4b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:48:07 +0300 Subject: [PATCH 04/15] refactor: rename callNew -> construct and call -> invoke --- .../topchetoeu/jscript/runtime/EventLoop.java | 4 ++-- .../jscript/runtime/InstructionRunner.java | 11 ++++++---- .../jscript/runtime/values/Value.java | 20 +++++++++++-------- .../runtime/values/objects/ObjectValue.java | 4 ++-- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java index 2c548ba..6a929a0 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java @@ -28,9 +28,9 @@ public interface EventLoop { } public default Future pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) { - return pushMsg(() -> func.call(env, thisArg, args), micro); + return pushMsg(() -> func.invoke(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); + return pushMsg(() -> Compiler.compileFunc(env, filename, raw).invoke(env, thisArg, args), micro); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index ecfa152..6d1d67f 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -51,7 +51,7 @@ public class InstructionRunner { var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); - frame.push(funcObj.callNew(env, instr.get(1), callArgs)); + frame.push(funcObj.construct(env, instr.get(1), callArgs)); frame.codePtr++; return null; @@ -82,7 +82,7 @@ public class InstructionRunner { private static Value execKeys(Environment env, Instruction instr, Frame frame) { var val = frame.pop(); - var members = new ArrayList<>(val.getMembers(env, false, true)); + var members = new ArrayList<>(val.getMembers(env, instr.get(0), instr.get(1))); Collections.reverse(members); frame.push(null); @@ -115,9 +115,12 @@ public class InstructionRunner { private static Value execDup(Environment env, Instruction instr, Frame frame) { int count = instr.get(0); + int offset = instr.get(1); + + var el = frame.stack[frame.stackPtr - offset - 1]; for (var i = 0; i < count; i++) { - frame.push(frame.peek()); + frame.push(el); } frame.codePtr++; @@ -230,7 +233,7 @@ public class InstructionRunner { } private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) { if (env.hasNotNull(Value.REGEX_CONSTR)) { - frame.push(env.get(Value.REGEX_CONSTR).callNew(env, instr.get(0), instr.get(1))); + frame.push(env.get(Value.REGEX_CONSTR).construct(env, instr.get(0), instr.get(1))); } else { throw EngineException.ofSyntax("Regex is not supported."); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index f96e7fb..4c112a5 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -83,7 +83,11 @@ public abstract class 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, String name, Value ...args) { + + public final Value invoke(Environment env, String name, Value self, Value ...args) { + return call(env, false, name, self, args); + } + public final Value construct(Environment env, String name, Value ...args) { var res = new ObjectValue(); var proto = getMember(env, new StringValue("prototype")); @@ -96,11 +100,11 @@ public abstract class Value { return res; } - public final Value call(Environment env, Value self, Value ...args) { - return call(env, false, "", self, args); + public final Value invoke(Environment env, Value self, Value ...args) { + return invoke(env, "", self, args); } - public final Value callNew(Environment env, Value ...args) { - return callNew(env, "", args); + public final Value construct(Environment env, Value ...args) { + return construct(env, "", args); } public abstract Value toPrimitive(Environment env); @@ -378,7 +382,7 @@ public abstract class Value { private void loadNext() { if (supplier == null) value = null; else if (consumed) { - var curr = supplier.call(env, Value.UNDEFINED); + var curr = supplier.invoke(env, Value.UNDEFINED); if (curr == null) { supplier = null; value = null; } if (curr.getMember(env, new StringValue("done")).toBoolean()) { supplier = null; value = null; } @@ -406,12 +410,12 @@ public abstract class Value { public void callWith(Environment env, Iterable it) { for (var el : it) { - this.call(env, Value.UNDEFINED, el); + this.invoke(env, Value.UNDEFINED, el); } } public void callWithAsync(Environment env, Iterable it, boolean async) { for (var el : it) { - env.get(EventLoop.KEY).pushMsg(() -> this.call(env, Value.UNDEFINED, el), true); + env.get(EventLoop.KEY).pushMsg(() -> this.invoke(env, Value.UNDEFINED, el), true); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index ead6365..1e3ffe8 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -52,13 +52,13 @@ public class ObjectValue extends Value { var valueOf = getMember(env, new StringValue("valueOf")); if (valueOf instanceof FunctionValue) { - var res = valueOf.call(env, this); + var res = valueOf.invoke(env, this); if (res.isPrimitive()) return res; } var toString = getMember(env, new StringValue("toString")); if (toString instanceof FunctionValue) { - var res = toString.call(env, this); + var res = toString.invoke(env, this); if (res.isPrimitive()) return res; } } -- 2.45.2 From 8e64d13c873cc36b786a662175611f3659093925 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:48:22 +0300 Subject: [PATCH 05/15] refactor: clean up assigning --- .../jscript/common/Instruction.java | 6 +- .../jscript/compilation/AssignableNode.java | 8 +- .../jscript/compilation/FunctionNode.java | 2 +- .../jscript/compilation/JavaScript.java | 5 +- .../compilation/values/VariableNode.java | 13 +++- .../values/operations/AssignNode.java | 23 ++++++ .../values/operations/ChangeNode.java | 51 +++---------- .../values/operations/IndexAssignNode.java | 74 ------------------- .../values/operations/IndexNode.java | 42 ++++++++++- .../values/operations/OperationNode.java | 4 +- .../values/operations/PostfixNode.java | 52 +++++++++++++ .../me/topchetoeu/jscript/runtime/Engine.java | 6 +- .../jscript/runtime/SimpleRepl.java | 19 ++++- .../jscript/runtime/values/Member.java | 4 +- .../runtime/values/primitives/VoidValue.java | 8 -- src/main/resources/lib/index.js | 14 ++-- 16 files changed, 180 insertions(+), 151 deletions(-) create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index c418e10..f64c718 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -404,10 +404,10 @@ public class Instruction { return new Instruction(Type.LOAD_ARR, count); } public static Instruction dup() { - return new Instruction(Type.DUP, 1); + return new Instruction(Type.DUP, 1, 0); } - public static Instruction dup(int count) { - return new Instruction(Type.DUP, count); + public static Instruction dup(int count, int offset) { + return new Instruction(Type.DUP, count, offset); } public static Instruction storeVar(int i) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java index e684c44..6dd3333 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java @@ -1,7 +1,9 @@ package me.topchetoeu.jscript.compilation; -import me.topchetoeu.jscript.common.Operation; - public interface AssignableNode { - public abstract Node toAssign(Node val, Operation operation); + public void compileBeforeAssign(CompileResult target, boolean operator); + public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute); + public default String assignName() { + return null; + } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index 9e09ff2..f0ade70 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -33,7 +33,7 @@ public abstract class FunctionNode extends Node { return new CompileResult(env, scope, params.params.size(), target -> { if (params.params.size() > 0) { target.add(Instruction.loadArgs(true)); - if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1)); + if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0)); var i = 0; for (var param : params.params) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java index 87d3d7e..082d7ec 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -39,6 +39,7 @@ import me.topchetoeu.jscript.compilation.values.operations.ChangeNode; import me.topchetoeu.jscript.compilation.values.operations.DiscardNode; import me.topchetoeu.jscript.compilation.values.operations.IndexNode; import me.topchetoeu.jscript.compilation.values.operations.OperationNode; +import me.topchetoeu.jscript.compilation.values.operations.PostfixNode; import me.topchetoeu.jscript.compilation.values.operations.TypeofNode; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; @@ -140,8 +141,8 @@ public final class JavaScript { ParseRes res = ParseRes.first(src, i + n, (s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence), (s, j) -> OperationNode.parseIn(s, j, _prev, precedence), - (s, j) -> ChangeNode.parsePostfixIncrease(s, j, _prev, precedence), - (s, j) -> ChangeNode.parsePostfixDecrease(s, j, _prev, precedence), + (s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence), + (s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence), (s, j) -> OperationNode.parseOperator(s, j, _prev, precedence), (s, j) -> IfNode.parseTernary(s, j, _prev, precedence), (s, j) -> IndexNode.parseMember(s, j, _prev, precedence), diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index eb4db6f..c4b7edb 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -4,7 +4,6 @@ import java.util.function.IntFunction; import java.util.function.Supplier; 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; @@ -13,14 +12,20 @@ import me.topchetoeu.jscript.compilation.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.operations.VariableAssignNode; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class VariableNode extends Node implements AssignableNode { public final String name; - @Override public Node toAssign(Node val, Operation operation) { - return new VariableAssignNode(loc(), name, val, operation); + @Override public String assignName() { return name; } + + @Override public void compileBeforeAssign(CompileResult target, boolean operator) { + if (operator) { + target.add(VariableNode.toGet(target, loc(), name)); + } + } + @Override public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute) { + target.add(VariableNode.toSet(target, loc(), name, pollute, false)); } @Override public void compile(CompileResult target, boolean pollute) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java new file mode 100644 index 0000000..e80f6f7 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java @@ -0,0 +1,23 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.AssignableNode; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; + +public class AssignNode extends Node { + public final AssignableNode assignable; + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + assignable.compileBeforeAssign(target, false); + value.compile(target, true); + assignable.compileAfterAssign(target, false, pollute); + } + + public AssignNode(Location loc, AssignableNode assignable, Node value) { + super(loc); + this.assignable = assignable; + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java index e8b403a..e72b937 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java @@ -13,24 +13,22 @@ import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; public class ChangeNode extends Node { - public final AssignableNode value; - public final double addAmount; - public final boolean postfix; + public final AssignableNode assignable; + public final Node value; + public final Operation op; @Override public void compile(CompileResult target, boolean pollute) { - value.toAssign(new NumberNode(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); - if (!pollute) target.add(Instruction.discard()); - else if (postfix) { - target.add(Instruction.pushValue(addAmount)); - target.add(Instruction.operation(Operation.SUBTRACT)); - } + assignable.compileBeforeAssign(target, true); + value.compile(target, true); + target.add(Instruction.operation(op)); + assignable.compileAfterAssign(target, true, pollute); } - public ChangeNode(Location loc, AssignableNode value, double addAmount, boolean postfix) { + public ChangeNode(Location loc, AssignableNode assignable, Node value, Operation op) { super(loc); + this.assignable = assignable; this.value = value; - this.addAmount = addAmount; - this.postfix = postfix; + this.op = op; } public static ParseRes parsePrefixIncrease(Source src, int i) { @@ -44,7 +42,7 @@ public class ChangeNode extends Node { if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, 1, false), n + res.n); + return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n); } public static ParseRes parsePrefixDecrease(Source src, int i) { var n = Parsing.skipEmpty(src, i); @@ -57,31 +55,6 @@ public class ChangeNode extends Node { if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, -1, false), n + res.n); - } - - public static ParseRes parsePostfixIncrease(Source src, int i, Node prev, int precedence) { - if (precedence > 15) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "++")) return ParseRes.failed(); - if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); - n += 2; - - return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, 1, true), n); - } - public static ParseRes parsePostfixDecrease(Source src, int i, Node prev, int precedence) { - if (precedence > 15) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "--")) return ParseRes.failed(); - if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); - n += 2; - - return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, -1, true), n); + return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java deleted file mode 100644 index 8765690..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java +++ /dev/null @@ -1,74 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -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.Node; -import me.topchetoeu.jscript.compilation.values.constants.NumberNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; - -public class IndexAssignNode extends Node { - public final Node object; - public final Node index; - public final Node value; - public final Operation operation; - - @Override public void compile(CompileResult target, boolean pollute) { - if (operation != null) { - object.compile(target, true); - - if (index instanceof NumberNode num && (int)num.value == num.value) { - target.add(Instruction.loadMember((int)num.value)); - value.compile(target, true); - target.add(Instruction.operation(operation)); - target.add(Instruction.storeMember((int)num.value, pollute)); - } - else if (index instanceof StringNode str) { - target.add(Instruction.loadMember(str.value)); - value.compile(target, true); - target.add(Instruction.operation(operation)); - target.add(Instruction.storeMember(str.value, pollute)); - } - else { - index.compile(target, true); - target.add(Instruction.dup(2)); - - target.add(Instruction.loadMember()); - value.compile(target, true); - target.add(Instruction.operation(operation)); - - target.add(Instruction.storeMember(pollute)); - } - target.setLocationAndDebug(loc(), BreakpointType.STEP_IN); - } - else { - object.compile(target, true); - - if (index instanceof NumberNode num && (int)num.value == num.value) { - value.compile(target, true); - target.add(Instruction.storeMember((int)num.value, pollute)); - } - else if (index instanceof StringNode str) { - value.compile(target, true); - target.add(Instruction.storeMember(str.value, pollute)); - } - else { - index.compile(target, true); - value.compile(target, true); - target.add(Instruction.storeMember(pollute)); - } - - target.setLocationAndDebug(loc(), BreakpointType.STEP_IN);; - } - } - - public IndexAssignNode(Location loc, Node object, Node index, Node value, Operation operation) { - super(loc); - this.object = object; - this.index = index; - this.value = value; - this.operation = operation; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java index d2791ac..cba4934 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.compilation.values.operations; import me.topchetoeu.jscript.common.Instruction; -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; @@ -18,9 +17,46 @@ public class IndexNode extends Node implements AssignableNode { public final Node object; public final Node index; - @Override public Node toAssign(Node val, Operation operation) { - return new IndexAssignNode(loc(), object, index, val, operation); + @Override public void compileBeforeAssign(CompileResult target, boolean op) { + object.compile(target, true); + + if (index instanceof NumberNode num && (int)num.value == num.value) { + if (op) { + target.add(Instruction.dup()); + target.add(Instruction.loadMember((int)num.value)); + } + } + else if (index instanceof StringNode str) { + if (op) { + target.add(Instruction.dup()); + target.add(Instruction.loadMember(str.value)); + } + } + else { + index.compile(target, true); + + if (op) { + target.add(Instruction.dup(1, 1)); + target.add(Instruction.dup(1, 1)); + target.add(Instruction.loadMember()); + } + } } + @Override public void compileAfterAssign(CompileResult target, boolean op, boolean pollute) { + if (index instanceof NumberNode num && (int)num.value == num.value) { + target.add(Instruction.storeMember((int)num.value, pollute)); + } + else if (index instanceof StringNode str) { + target.add(Instruction.storeMember(str.value, pollute)); + } + else { + target.add(Instruction.storeMember(pollute)); + } + } + + // @Override public Node toAssign(Node val, Operation operation) { + // return new IndexAssignNode(loc(), object, index, val, operation); + // } public void compile(CompileResult target, boolean dupObj, boolean pollute) { object.compile(target, true); if (dupObj) target.add(Instruction.dup()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java index 57039c6..b3f4350 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java @@ -58,7 +58,9 @@ public class OperationNode extends Node { var other = JavaScript.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(((AssignableNode)prev).toAssign(other.result, operation), other.n); + + if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignableNode)prev), other.result), other.n); + else return ParseRes.res(new ChangeNode(loc, ((AssignableNode)prev), other.result, operation), other.n); } public AssignmentOperatorFactory(String token, int precedence, Operation operation) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java new file mode 100644 index 0000000..420ea74 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java @@ -0,0 +1,52 @@ +package me.topchetoeu.jscript.compilation.values.operations; + +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.AssignableNode; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; + +public class PostfixNode extends ChangeNode { + @Override public void compile(CompileResult target, boolean pollute) { + super.compile(target, pollute); + + if (pollute) { + value.compile(target, true); + target.add(Instruction.operation(Operation.ADD)); + } + } + + public PostfixNode(Location loc, AssignableNode value, double addAmount) { + super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT); + } + + public static ParseRes parsePostfixIncrease(Source src, int i, Node prev, int precedence) { + if (precedence > 15) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "++")) return ParseRes.failed(); + if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + n += 2; + + return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, 1), n); + } + public static ParseRes parsePostfixDecrease(Source src, int i, Node prev, int precedence) { + if (precedence > 15) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "--")) return ParseRes.failed(); + if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + n += 2; + + return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, -1), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Engine.java b/src/main/java/me/topchetoeu/jscript/runtime/Engine.java index e3cb05e..f9e80f9 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Engine.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Engine.java @@ -41,10 +41,8 @@ public final class Engine implements EventLoop { try { ((Task)task).notifier.complete(task.runnable.get()); } - catch (RuntimeException e) { - if (e instanceof InterruptException) throw e; - task.notifier.completeExceptionally(e); - } + catch (InterruptException e) { throw e; } + catch (RuntimeException e) { task.notifier.completeExceptionally(e); } } catch (InterruptedException | InterruptException e) { for (var msg : tasks) msg.notifier.cancel(false); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 65f32a3..653655b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -229,12 +229,21 @@ public class SimpleRepl { if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new"); else return new StringValue("call"); })); + res.defineOwnMember(env, "invoke", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); var self = args.get(1); var funcArgs = (ArrayValue)args.get(2); + var name = args.get(3).toString(env).value; - return func.call(env, self, funcArgs.toArray()); + return func.invoke(env, name, self, funcArgs.toArray()); + })); + res.defineOwnMember(env, "construct", new NativeFunction(args -> { + var func = (FunctionValue)args.get(0); + var funcArgs = (ArrayValue)args.get(1); + var name = args.get(2).toString(env).value; + + return func.construct(env, name, funcArgs.toArray()); })); return res; @@ -264,7 +273,7 @@ public class SimpleRepl { var self = args.get(1); var funcArgs = (ArrayValue)args.get(2); - return func.call(env, self, funcArgs.toArray()); + return func.invoke(env, self, funcArgs.toArray()); })); return res; @@ -303,6 +312,12 @@ public class SimpleRepl { case "object": args.env.add(Value.OBJECT_PROTO, obj); break; + case "function": + args.env.add(Value.FUNCTION_PROTO, obj); + break; + case "array": + args.env.add(Value.ARRAY_PROTO, obj); + break; } return Value.UNDEFINED; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java index 92828a0..5b2895b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java @@ -13,12 +13,12 @@ public interface Member { public final boolean enumerable; @Override public Value get(Environment env, Value self) { - if (getter != null) return getter.call(env, self); + if (getter != null) return getter.call(env, false, "", self); else return Value.UNDEFINED; } @Override public boolean set(Environment env, Value val, Value self) { if (setter == null) return false; - setter.call(env, self, val); + setter.call(env, false, "", self, val); return true; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java index 4c7c199..0a40322 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java @@ -1,7 +1,5 @@ package me.topchetoeu.jscript.runtime.values.primitives; -import java.util.Set; - import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; @@ -24,12 +22,6 @@ public final class VoidValue extends PrimitiveValue { @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 Set getOwnMembers(Environment env, boolean onlyEnumerable) { - throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name)); - } - @Override public Set getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { - throw EngineException.ofError(String.format("Cannot read properties of %s (listing all symbol members)", name)); - } public VoidValue(String name, StringValue type) { this.name = name; diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index b901359..550cfa5 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -29,6 +29,7 @@ const invokeType = primordials.function.invokeType; const setConstructable = primordials.function.setConstructable; const setCallable = primordials.function.setCallable; const invoke = primordials.function.invoke; +const construct = primordials.function.construct; const json = primordials.json; @@ -305,9 +306,7 @@ defineField(Function.prototype, "valueOf", true, false, true, function() { target.Function = Function; -let spread_obj; - -setIntrinsic("spread_obj", spread_obj = (target, obj) => { +setIntrinsic("spread_obj", target.spread_obj = (target, obj) => { if (obj === null || obj === undefined) return; const members = getOwnMembers(obj, true); const symbols = getOwnSymbolMembers(obj, true); @@ -322,11 +321,16 @@ setIntrinsic("spread_obj", spread_obj = (target, obj) => { target[member] = obj[member]; } }); - -target.spread_obj = spread_obj; +setIntrinsic("apply", target.spread_call = (func, self, args) => { + return invoke(func, self, args); +}); +setIntrinsic("apply", target.spread_new = (func, args) => { + return invoke(func, null, args); +}); setGlobalPrototype("string", String.prototype); setGlobalPrototype("number", Number.prototype); setGlobalPrototype("boolean", Boolean.prototype); setGlobalPrototype("symbol", Symbol.prototype); setGlobalPrototype("object", Object.prototype); +setGlobalPrototype("function", Function.prototype); -- 2.45.2 From 2a01b3d86ec9429b1d7d70d3d17e46b63cc0742b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 7 Sep 2024 21:06:08 +0300 Subject: [PATCH 06/15] some work losl --- .../jscript/common/Instruction.java | 4 +- .../jscript/compilation/AssignableNode.java | 9 - .../compilation/control/ForInNode.java | 7 +- .../destructing/AssignDestructorNode.java | 69 ++++++ .../compilation/destructing/AssignTarget.java | 4 + .../compilation/destructing/ChangeTarget.java | 8 + .../compilation/destructing/Destructor.java | 38 ++++ .../destructing/NamedDestructor.java | 20 ++ .../jscript/compilation/scope/Scope.java | 10 + .../values/ObjectDestructorNode.java | 199 ++++++++++++++++++ .../compilation/values/ObjectNode.java | 5 +- .../compilation/values/VariableNode.java | 29 ++- .../values/operations/AssignNode.java | 63 +++++- .../values/operations/ChangeNode.java | 18 +- .../values/operations/IndexNode.java | 59 ++++-- .../values/operations/OperationNode.java | 6 +- .../values/operations/PostfixNode.java | 10 +- 17 files changed, 494 insertions(+), 64 deletions(-) delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index f64c718..f9d75ad 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -449,8 +449,8 @@ public class Instruction { return new Instruction(Type.TYPEOF, varName); } - public static Instruction keys(boolean forInFormat) { - return new Instruction(Type.KEYS, forInFormat); + public static Instruction keys(boolean own, boolean onlyEnumerable) { + return new Instruction(Type.KEYS, own, onlyEnumerable); } public static Instruction defProp() { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java deleted file mode 100644 index 6dd3333..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -public interface AssignableNode { - public void compileBeforeAssign(CompileResult target, boolean operator); - public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute); - public default String assignName() { - return null; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java index 59688a5..b331267 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -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; @@ -33,12 +32,10 @@ public class ForInNode extends Node { if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation); object.compile(target, true, BreakpointType.STEP_OVER); - target.add(Instruction.keys(true)); + target.add(Instruction.keys(false, true)); int start = target.size(); target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); int mid = target.temp(); target.add(Instruction.loadMember("value")).setLocation(varLocation); @@ -55,7 +52,7 @@ public class ForInNode extends Node { target.add(Instruction.jmp(start - endI)); target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(endI - mid + 1)); + target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); if (pollute) target.add(Instruction.pushUndefined()); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java new file mode 100644 index 0000000..755f043 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.compilation.destructing; + +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.CompileResult; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public class AssignDestructorNode extends Node implements Destructor { + public final String name; + public final Node value; + + @Override public void destructDeclResolve(CompileResult target) { + target.scope.define(new Variable(name, false), loc()); + } + @Override public void destructArg(CompileResult target) { + var v = target.scope.define(new Variable(name, false), loc()); + destructAssign(target); + target.add(_i -> v.index().toSet(false)); + } + @Override public void afterAssign(CompileResult target, DeclarationType decl) { + if (decl != null && decl.strict) target.scope.define(new Variable(name, decl.readonly), loc()); + var end = new DeferredIntSupplier(); + + target.add(Instruction.dup()); + target.add(Instruction.pushUndefined()); + target.add(Instruction.operation(Operation.EQUALS)); + target.add(Instruction.jmpIfNot(end)); + target.add(Instruction.discard()); + value.compile(target, true); + + end.set(target.size()); + + target.add(VariableNode.toSet(target, loc(), name, false, decl != null)); + } + + public AssignDestructorNode(Location loc, String name, Node value) { + super(loc); + this.name = name; + this.value = value; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var name = Parsing.parseIdentifier(src, i); + if (!JavaScript.checkVarName(null)) return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", name.result)); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i, "=")) return ParseRes.failed(); + n++; + + var value = JavaScript.parseExpression(src, i, 2); + if (value.isError()) return ParseRes.error(src.loc(i + n), "Expected a value after '='"); + n += value.n; + + return ParseRes.res(new AssignDestructorNode(loc, name.result, value.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java new file mode 100644 index 0000000..1e60d9f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java @@ -0,0 +1,4 @@ +package me.topchetoeu.jscript.compilation.destructing; + +public interface AssignTarget extends Destructor { +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java new file mode 100644 index 0000000..0736444 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java @@ -0,0 +1,8 @@ +package me.topchetoeu.jscript.compilation.destructing; + +import me.topchetoeu.jscript.compilation.CompileResult; + +public interface ChangeTarget extends AssignTarget { + void beforeChange(CompileResult target); + void afterChange(CompileResult target, boolean pollute); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java new file mode 100644 index 0000000..264cdde --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java @@ -0,0 +1,38 @@ +package me.topchetoeu.jscript.compilation.destructing; + +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.JavaScript; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.values.ObjectDestructorNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +public interface Destructor { + void destructDeclResolve(CompileResult target); + + default void destructArg(CompileResult target) { + beforeAssign(target, null); + afterAssign(target, null); + } + default void beforeAssign(CompileResult target, DeclarationType decl) {} + void afterAssign(CompileResult target, DeclarationType decl, boolean pollute); + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + ParseRes first = ParseRes.first(src, i + n, + ObjectDestructorNode::parse, + AssignDestructorNode::parse, + VariableNode::parse + ); + if (first.isSuccess()) return first.addN(n); + + var exp = JavaScript.parseExpression(src, i, 2); + if (!(exp.result instanceof Destructor destructor)) return ParseRes.error(src.loc(i + n), "Expected a destructor expression"); + n += exp.n; + + return ParseRes.res(destructor, n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java new file mode 100644 index 0000000..47b5a4b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java @@ -0,0 +1,20 @@ +package me.topchetoeu.jscript.compilation.destructing; + +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.VariableNode; + +public interface NamedDestructor extends Destructor { + String name(); + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + ParseRes first = ParseRes.first(src, i + n, + AssignDestructorNode::parse, + VariableNode::parse + ); + return first.addN(n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java index 4059df0..206e990 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -61,6 +61,16 @@ public class Scope { if (ended) throw new IllegalStateException("Cannot define in an ended scope"); } + /** + * Defines a nameless variable for holding intermediate temporary values + * + * @throws RuntimeException If the scope is finalized or has an active child + */ + public Variable defineTemp() { + checkNotEnded(); + return this.variables.add(new Variable("", false)); + } + /** * Defines an ES5-style variable * diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java new file mode 100644 index 0000000..68501f0 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java @@ -0,0 +1,199 @@ +package me.topchetoeu.jscript.compilation.values; + +import java.util.LinkedHashMap; +import java.util.Map; + +import me.topchetoeu.jscript.common.Instruction; +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.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.destructing.Destructor; + + +public class ObjectDestructorNode extends Node implements Destructor { + public final LinkedHashMap destructors; + public final VariableNode restDestructor; + + private void compileRestObjBuilder(CompileResult target, int srcDupN) { + var subtarget = target.subtarget(); + var src = subtarget.scope.defineTemp(); + var dst = subtarget.scope.defineTemp(); + + target.add(Instruction.loadObj()); + target.add(_i -> src.index().toSet(true)); + target.add(_i -> dst.index().toSet(destructors.size() > 0)); + + target.add(Instruction.keys(true, true)); + var start = target.size(); + + target.add(Instruction.dup()); + var mid = target.temp(); + + target.add(_i -> src.index().toGet()); + target.add(Instruction.dup(1, 1)); + target.add(Instruction.loadMember()); + + target.add(_i -> dst.index().toGet()); + target.add(Instruction.dup(1, 1)); + target.add(Instruction.storeMember()); + + target.add(Instruction.discard()); + var end = target.size(); + target.add(Instruction.jmp(start - end)); + target.set(mid, Instruction.jmpIfNot(end - mid + 1)); + + target.add(Instruction.discard()); + + target.add(Instruction.dup(srcDupN, 1)); + + target.scope.end(); + } + + @Override public void destructDeclResolve(CompileResult target) { + for (var el : destructors.values()) { + el.destructDeclResolve(target); + } + + if (restDestructor != null) restDestructor.destructDeclResolve(target); + } + @Override public void destructArg(CompileResult target) { + if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2); + else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0)); + + for (var el : destructors.entrySet()) { + if (restDestructor != null) { + target.add(Instruction.pushValue(el.getKey())); + target.add(Instruction.delete()); + } + + target.add(Instruction.loadMember(el.getKey())); + el.getValue().destructArg(target); + } + + if (restDestructor != null) restDestructor.destructArg(target); + + target.add(Instruction.discard()); + } + @Override public void afterAssign(CompileResult target, DeclarationType decl) { + if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2); + else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0)); + + for (var el : destructors.entrySet()) { + if (restDestructor != null) { + target.add(Instruction.pushValue(el.getKey())); + target.add(Instruction.delete()); + } + + target.add(Instruction.loadMember(el.getKey())); + el.getValue().afterAssign(target, decl); + } + + if (restDestructor != null) restDestructor.afterAssign(target, decl); + + target.add(Instruction.discard()); + } + @Override public void destructAssign(CompileResult target, boolean pollute) { + if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2); + else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0)); + + for (var el : destructors.entrySet()) { + if (restDestructor != null) { + target.add(Instruction.pushValue(el.getKey())); + target.add(Instruction.delete()); + } + + target.add(Instruction.loadMember(el.getKey())); + el.getValue().destructAssign(target, pollute); + } + + if (restDestructor != null) restDestructor.destructAssign(target, pollute); + + if (!pollute) target.add(Instruction.discard()); + } + + public ObjectDestructorNode(Location loc, Map map, VariableNode rest) { + super(loc); + this.destructors = new LinkedHashMap<>(map); + this.restDestructor = rest; + } + public ObjectDestructorNode(Location loc, Map map) { + super(loc); + this.destructors = new LinkedHashMap<>(map); + this.restDestructor = null; + } + + private static ParseRes parsePropName(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + var res = ParseRes.first(src, i + n, + Parsing::parseIdentifier, + Parsing::parseString, + (s, j) -> Parsing.parseNumber(s, j, false) + ); + if (!res.isSuccess()) return res.chainError(); + n += res.n; + + if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon"); + n++; + + return ParseRes.res(res.result.toString(), n); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; + n += Parsing.skipEmpty(src, i + n); + + var destructors = new LinkedHashMap(); + + if (src.is(i + n, "}")) { + n++; + return ParseRes.res(new ObjectDestructorNode(loc, destructors), n); + } + + while (true) { + n += Parsing.skipEmpty(src, i + n); + + // if (src.is(i, null)) + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a field name"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + var destructor = Destructor.parse(src, i + n); + if (!destructor.isSuccess()) return destructor.chainError(src.loc(i + n), "Expected a value in array list"); + n += destructor.n; + + destructors.put(name.result, destructor.result); + + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { + n++; + break; + } + + continue; + } + else if (src.is(i + n, "}")) { + n++; + break; + } + else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); + } + + return ParseRes.res(new ObjectDestructorNode(loc, destructors), n); + } + +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java index 5a5de0e..8e4483e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -15,9 +15,10 @@ import me.topchetoeu.jscript.compilation.FunctionNode; import me.topchetoeu.jscript.compilation.FunctionValueNode; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.destructing.Destructor; -public class ObjectNode extends Node { +public class ObjectNode extends Node implements Destructor { public static class ObjProp { public final String name; public final String access; @@ -148,7 +149,7 @@ public class ObjectNode extends Node { n++; var valRes = JavaScript.parseExpression(src, i + n, 2); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list"); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after property key"); n += valRes.n; values.put(name.result, valRes.result); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index c4b7edb..c990238 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -12,9 +12,11 @@ import me.topchetoeu.jscript.compilation.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.scope.Variable; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class VariableNode extends Node implements AssignableNode { +public class VariableNode extends Node implements AssignTarget { public final String name; @Override public String assignName() { return name; } @@ -28,6 +30,25 @@ public class VariableNode extends Node implements AssignableNode { target.add(VariableNode.toSet(target, loc(), name, pollute, false)); } + @Override public void destructArg(CompileResult target) { + target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false)); + } + @Override public void destructDeclResolve(CompileResult target) { + target.scope.define(new Variable(name, false), loc()); + } + @Override public void afterAssign(CompileResult target, DeclarationType decl) { + if (decl.strict) { + var v = target.scope.defineStrict(new Variable(name, decl.readonly), loc()); + target.add(_i -> v.index().toSet(false)); + } + else { + target.add(VariableNode.toSet(target, loc(), name, false, true)); + } + } + @Override public void destructAssign(CompileResult target, boolean pollute) { + target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + } + @Override public void compile(CompileResult target, boolean pollute) { var i = target.scope.get(name, false); @@ -81,10 +102,8 @@ public class VariableNode extends Node implements AssignableNode { n += literal.n; if (!JavaScript.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."); - return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'.", literal.result)); + if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported"); + return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", literal.result)); } return ParseRes.res(new VariableNode(loc, literal.result), n); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java index e80f6f7..e33bdd2 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java @@ -1,21 +1,68 @@ package me.topchetoeu.jscript.compilation.values.operations; +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.destructing.AssignTarget; +import me.topchetoeu.jscript.compilation.destructing.Destructor; +import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class AssignNode extends Node { - public final AssignableNode assignable; +public class AssignNode extends Node implements Destructor { + public final AssignTarget assignable; public final Node value; - @Override public void compile(CompileResult target, boolean pollute) { - assignable.compileBeforeAssign(target, false); - value.compile(target, true); - assignable.compileAfterAssign(target, false, pollute); + @Override public void destructDeclResolve(CompileResult target) { + if (!(assignable instanceof VariableNode var)) { + throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); + } + + target.scope.define(new Variable(var.name, false), var.loc()); } - public AssignNode(Location loc, AssignableNode assignable, Node value) { + @Override public void destructArg(CompileResult target) { + if (!(assignable instanceof VariableNode var)) { + throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); + } + + var v = target.scope.define(new Variable(var.name, false), var.loc()); + afterAssign(target, null, false); + target.add(_i -> v.index().toSet(false)); + } + @Override public void afterAssign(CompileResult target, DeclarationType decl, boolean pollute) { + if (decl != null && decl.strict) { + if (!(assignable instanceof VariableNode var)) { + throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); + } + target.scope.define(new Variable(var.name, decl.strict), var.loc()); + } + + assignable.beforeAssign(target, decl); + + target.add(Instruction.dup()); + target.add(Instruction.pushUndefined()); + target.add(Instruction.operation(Operation.EQUALS)); + var start = target.temp(); + target.add(Instruction.discard()); + + value.compile(target, true); + + target.set(start, Instruction.jmp(target.size() - start)); + + assignable.afterAssign(target, decl, pollute); + } + + @Override public void compile(CompileResult target, boolean pollute) { + assignable.beforeAssign(target, null); + value.compile(target, true); + assignable.afterAssign(target, null, pollute); + } + + public AssignNode(Location loc, AssignTarget assignable, Node value) { super(loc); this.assignable = assignable; this.value = value; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java index e72b937..bf68d55 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java @@ -6,25 +6,25 @@ import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.destructing.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; public class ChangeNode extends Node { - public final AssignableNode assignable; + public final ChangeTarget assignable; public final Node value; public final Operation op; @Override public void compile(CompileResult target, boolean pollute) { - assignable.compileBeforeAssign(target, true); + assignable.beforeChange(target); value.compile(target, true); target.add(Instruction.operation(op)); - assignable.compileAfterAssign(target, true, pollute); + assignable.afterChange(target, pollute); } - public ChangeNode(Location loc, AssignableNode assignable, Node value, Operation op) { + public ChangeNode(Location loc, ChangeTarget assignable, Node value, Operation op) { super(loc); this.assignable = assignable; this.value = value; @@ -40,9 +40,9 @@ public class ChangeNode extends Node { var res = JavaScript.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 AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n); + return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n); } public static ParseRes parsePrefixDecrease(Source src, int i) { var n = Parsing.skipEmpty(src, i); @@ -53,8 +53,8 @@ public class ChangeNode extends Node { var res = JavaScript.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 AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n); + return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java index cba4934..0f049bd 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java @@ -10,39 +10,56 @@ import me.topchetoeu.jscript.compilation.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.destructing.AssignTarget; +import me.topchetoeu.jscript.compilation.destructing.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; import me.topchetoeu.jscript.compilation.values.constants.StringNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class IndexNode extends Node implements AssignableNode { +public class IndexNode extends Node implements ChangeTarget { public final Node object; public final Node index; - @Override public void compileBeforeAssign(CompileResult target, boolean op) { + @Override public void destructDeclResolve(CompileResult target) { + throw new SyntaxException(loc(), "Unexpected index in destructor"); + } + @Override public void destructArg(CompileResult target) { + throw new SyntaxException(loc(), "Unexpected index in destructor"); + } + + @Override public void beforeAssign(CompileResult target, DeclarationType decl) { + if (decl != null) throw new SyntaxException(loc(), "Unexpected index in destructor"); + object.compile(target, true); + + if (index instanceof NumberNode num && (int)num.value == num.value) return; + if (index instanceof StringNode) return; + index.compile(target, true); + + target.add(Instruction.dup(1, 1)); + target.add(Instruction.dup(1, 1)); + target.add(Instruction.loadMember()); + } + @Override public void beforeChange(CompileResult target) { object.compile(target, true); if (index instanceof NumberNode num && (int)num.value == num.value) { - if (op) { - target.add(Instruction.dup()); - target.add(Instruction.loadMember((int)num.value)); - } + target.add(Instruction.dup()); + target.add(Instruction.loadMember((int)num.value)); } else if (index instanceof StringNode str) { - if (op) { - target.add(Instruction.dup()); - target.add(Instruction.loadMember(str.value)); - } + target.add(Instruction.dup()); + target.add(Instruction.loadMember(str.value)); } else { index.compile(target, true); - if (op) { - target.add(Instruction.dup(1, 1)); - target.add(Instruction.dup(1, 1)); - target.add(Instruction.loadMember()); - } + target.add(Instruction.dup(1, 1)); + target.add(Instruction.dup(1, 1)); + target.add(Instruction.loadMember()); } } - @Override public void compileAfterAssign(CompileResult target, boolean op, boolean pollute) { + @Override public void afterAssign(CompileResult target, boolean op, boolean pollute) { if (index instanceof NumberNode num && (int)num.value == num.value) { target.add(Instruction.storeMember((int)num.value, pollute)); } @@ -54,6 +71,16 @@ public class IndexNode extends Node implements AssignableNode { } } + @Override public void afterAssign(CompileResult target, DeclarationType decl) { + throw new SyntaxException(loc(), "Illegal index in declaration destruction context"); + } + @Override public void destructAssign(CompileResult target, boolean pollute) { + object.compile(target, true); + target.add(Instruction.dup(1, 1)); + compileAfterAssign(target, false, false); + if (!pollute) target.add(Instruction.discard()); + } + // @Override public Node toAssign(Node val, Operation operation) { // return new IndexAssignNode(loc(), object, index, val, operation); // } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java index b3f4350..dcb2335 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java @@ -54,13 +54,13 @@ public class OperationNode extends Node { @Override public ParseRes construct(Source src, int i, Node prev) { var loc = src.loc(i); - if (!(prev instanceof AssignableNode)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); + if (!(prev instanceof AssignTarget)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); var other = JavaScript.parseExpression(src, i, precedence); if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); - if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignableNode)prev), other.result), other.n); - else return ParseRes.res(new ChangeNode(loc, ((AssignableNode)prev), other.result, operation), other.n); + if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignTarget)prev), other.result), other.n); + else return ParseRes.res(new ChangeNode(loc, ((AssignTarget)prev), other.result, operation), other.n); } public AssignmentOperatorFactory(String token, int precedence, Operation operation) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java index 420ea74..cd3f133 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java @@ -21,7 +21,7 @@ public class PostfixNode extends ChangeNode { } } - public PostfixNode(Location loc, AssignableNode value, double addAmount) { + public PostfixNode(Location loc, AssignTarget value, double addAmount) { super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT); } @@ -32,10 +32,10 @@ public class PostfixNode extends ChangeNode { var loc = src.loc(i + n); if (!src.is(i + n, "++")) return ParseRes.failed(); - if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); n += 2; - return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, 1), n); + return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, 1), n); } public static ParseRes parsePostfixDecrease(Source src, int i, Node prev, int precedence) { if (precedence > 15) return ParseRes.failed(); @@ -44,9 +44,9 @@ public class PostfixNode extends ChangeNode { var loc = src.loc(i + n); if (!src.is(i + n, "--")) return ParseRes.failed(); - if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); n += 2; - return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, -1), n); + return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, -1), n); } } -- 2.45.2 From 3e6816cb2c86257399c54cf9bff5d5b0263bfc22 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:25:11 +0300 Subject: [PATCH 07/15] fix: properly hande variable collisions --- .../jscript/compilation/CompoundNode.java | 2 +- .../jscript/compilation/FunctionNode.java | 1 + .../jscript/compilation/scope/FunctionScope.java | 14 +++++++------- .../jscript/compilation/scope/Scope.java | 6 ++++++ 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java index 958b825..9d8bd93 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java @@ -13,7 +13,7 @@ import me.topchetoeu.jscript.common.parsing.Source; public class CompoundNode extends Node { public final Node[] statements; - public final boolean hasScope; + public boolean hasScope; public Location end; @Override public void resolve(CompileResult target) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index f0ade70..9c369b8 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -107,6 +107,7 @@ public abstract class FunctionNode extends Node { this.end = end; this.params = params; this.body = body; + this.body.hasScope = false; } public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java index e935be3..5daf8aa 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java @@ -19,6 +19,13 @@ public class FunctionScope extends Scope { public final boolean passtrough; + @Override public boolean hasNonStrict(String name) { + if (functionVarMap.containsKey(name)) return true; + if (blacklistNames.contains(name)) return true; + + return false; + } + @Override public Variable define(Variable var, Location loc) { checkNotEnded(); if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); @@ -32,13 +39,6 @@ public class FunctionScope extends Scope { return variables.add(var); } } - @Override public Variable defineStrict(Variable var, Location loc) { - checkNotEnded(); - if (functionVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); - if (blacklistNames.contains(var.name)) throw alreadyDefinedErr(loc, var.name); - - return super.defineStrict(var, loc); - } public Variable defineSpecial(Variable var, Location loc) { checkNotEnded(); if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java index 206e990..3e210f7 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -86,6 +86,11 @@ public class Scope { return null; } + /** + * Checks if this scope's function parent has a non-strict variable of the given name + */ + public boolean hasNonStrict(String name) { return false; } + /** * Defines an ES2015-style variable * @param readonly True if const, false if let @@ -96,6 +101,7 @@ public class Scope { public Variable defineStrict(Variable var, Location loc) { checkNotEnded(); if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); + if (hasNonStrict(var.name)) throw alreadyDefinedErr(loc, var.name); strictVarMap.put(var.name, var); return variables.add(var); -- 2.45.2 From 1f42263051eec1ababf72b31ad64cd1d5ea6625c Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 13:53:58 +0300 Subject: [PATCH 08/15] clean up member logic --- .../me/topchetoeu/jscript/runtime/Frame.java | 56 ++++---- .../jscript/runtime/InstructionRunner.java | 2 +- .../jscript/runtime/JSONConverter.java | 3 +- .../jscript/runtime/SimpleRepl.java | 8 +- .../runtime/exceptions/EngineException.java | 5 +- .../jscript/runtime/values/Member.java | 131 ++++++++++++------ .../jscript/runtime/values/Value.java | 65 ++++++--- .../values/functions/FunctionValue.java | 12 +- .../runtime/values/objects/ObjectValue.java | 29 ++-- .../runtime/values/objects/ScopeValue.java | 12 +- .../values/primitives/PrimitiveValue.java | 6 + .../values/primitives/StringValue.java | 4 +- 12 files changed, 201 insertions(+), 132 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index fa4c7f2..2725a7d 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,8 +1,6 @@ package me.topchetoeu.jscript.runtime; import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; import java.util.Stack; import me.topchetoeu.jscript.common.Instruction; @@ -11,11 +9,9 @@ import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; -import me.topchetoeu.jscript.runtime.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.ArrayLikeValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; public final class Frame { @@ -385,31 +381,39 @@ public final class Frame { * Gets an array proxy of the local locals */ public ObjectValue getValStackScope() { - return new ObjectValue() { - @Override public Member getOwnMember(Environment env, KeyCache key) { - var res = super.getOwnMember(env, key); - if (res != null) return res; + return new ArrayLikeValue() { + @Override public Value get(int i) { return stack[i]; } + @Override public void set(int i, Value val) { stack[i] = val; } + @Override public boolean has(int i) { return i >= 0 && i < size(); } + @Override public void remove(int i) { } - var num = key.toNumber(env); - var i = key.toInt(env); + @Override public int size() { return stackPtr; } + @Override public boolean setSize(int val) { return false; } - 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) { - stack[i] = val; - return true; - } - }; - } - @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { - var res = new LinkedHashSet(); - res.addAll(super.getOwnMembers(env, onlyEnumerable)); + // @Override public Member getOwnMember(Environment env, KeyCache key) { + // var res = super.getOwnMember(env, key); + // if (res != null) return res; - for (var i = 0; i < stackPtr; i++) res.add(i + ""); + // var num = key.toNumber(env); + // var i = key.toInt(env); - return res; - } + // if (num != i || i < 0 || i >= stackPtr) return null; + // else return new FieldMember(this, false, true, true) { + // @Override public Value get(Environment env, Value self) { return stack[i]; } + // @Override public boolean set(Environment env, Value val, Value self) { + // stack[i] = val; + // return true; + // } + // }; + // } + // @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { + // var res = new LinkedHashSet(); + // res.addAll(super.getOwnMembers(env, onlyEnumerable)); + + // for (var i = 0; i < stackPtr; i++) res.add(i + ""); + + // return res; + // } }; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 6d1d67f..094b2bb 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -73,7 +73,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, key, new PropertyMember(getter, setter, true, true)); + obj.defineOwnMember(env, key, new PropertyMember(obj, getter, setter, true, true)); frame.push(obj); frame.codePtr++; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java index 8a19ccc..de18785 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java @@ -8,7 +8,6 @@ import me.topchetoeu.jscript.common.json.JSONElement; import me.topchetoeu.jscript.common.json.JSONList; import me.topchetoeu.jscript.common.json.JSONMap; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; @@ -27,7 +26,7 @@ public class JSONConverter { var res = new ObjectValue(); for (var el : val.map().entrySet()) { - res.defineOwnMember(null, el.getKey(), FieldMember.of(toJs(el.getValue()))); + res.defineOwnMember(null, el.getKey(), toJs(el.getValue())); } return res; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 653655b..c1b1ca9 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -171,9 +171,7 @@ public class SimpleRepl { var configurable = args.get(4).toBoolean(); var value = args.get(5); - obj.defineOwnMember(args.env, key, FieldMember.of(value, enumerable, configurable, writable)); - - return Value.UNDEFINED; + return BoolValue.of(obj.defineOwnMember(args.env, key, FieldMember.of(obj, value, configurable, enumerable, writable))); })); res.defineOwnMember(env, "defineProperty", new NativeFunction(args -> { var obj = (ObjectValue)args.get(0); @@ -183,9 +181,7 @@ public class SimpleRepl { var getter = args.get(4) instanceof VoidValue ? null : (FunctionValue)args.get(4); var setter = args.get(5) instanceof VoidValue ? null : (FunctionValue)args.get(5); - obj.defineOwnMember(args.env, key, new PropertyMember(getter, setter, configurable, enumerable)); - - return Value.UNDEFINED; + return BoolValue.of(obj.defineOwnMember(args.env, key, new PropertyMember(obj, getter, setter, configurable, enumerable))); })); res.defineOwnMember(env, "getPrototype", new NativeFunction(args -> { return args.get(0).getPrototype(env); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index f9a21f4..60657c0 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -6,7 +6,6 @@ import java.util.List; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.parsing.Location; 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; @@ -98,8 +97,8 @@ public class EngineException extends RuntimeException { if (msg == null) msg = ""; - if (name != null) res.defineOwnMember(Environment.empty(), "name", FieldMember.of(new StringValue(name))); - res.defineOwnMember(Environment.empty(), "message", FieldMember.of(new StringValue(msg))); + if (name != null) res.defineOwnMember(Environment.empty(), "name", new StringValue(name)); + res.defineOwnMember(Environment.empty(), "message", new StringValue(msg)); return res; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java index 5b2895b..36c7ac1 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java @@ -7,10 +7,11 @@ import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; public interface Member { public static final class PropertyMember implements Member { - public final FunctionValue getter; - public final FunctionValue setter; - public final boolean configurable; - public final boolean enumerable; + public final Value self; + public FunctionValue getter; + public FunctionValue setter; + public boolean configurable; + public boolean enumerable; @Override public Value get(Environment env, Value self) { if (getter != null) return getter.call(env, false, "", self); @@ -22,36 +23,51 @@ public interface Member { return true; } - @Override public boolean configurable() { return configurable; } + @Override public boolean configurable() { return configurable && self.getState().configurable; } @Override public boolean enumerable() { return enumerable; } - @Override public boolean configure(Environment env, Member newMember, Value self) { - if (!(newMember instanceof PropertyMember)) return false; - var prop = (PropertyMember)newMember; + @Override public boolean redefine(Environment env, Member newMember, Value self) { + // If the given member isn't a property, we can't redefine + if (!(newMember instanceof PropertyMember prop)) return false; - if (prop.configurable != configurable) return false; - if (prop.enumerable != enumerable) return false; + if (configurable()) { + // We will overlay the getters and setters of the new member + enumerable = prop.enumerable; + configurable = prop.configurable; - if (prop.getter == getter) return true; - if (prop.setter == setter) return true; - return false; + if (prop.getter != null) getter = prop.getter; + if (prop.setter != null) setter = prop.setter; + + return true; + } + else { + // We will pretend that a redefinition has occurred if the two members match exactly + if (prop.configurable() != configurable()) return false; + if (prop.enumerable != enumerable) return false; + if (prop.getter != getter || prop.setter != setter) return false; + + return true; + } } @Override public ObjectValue descriptor(Environment env, Value self) { var res = new ObjectValue(); - if (getter == null) res.defineOwnMember(env, "getter", FieldMember.of(Value.UNDEFINED)); - else res.defineOwnMember(env, "getter", FieldMember.of(getter)); + // Don't touch the ordering, as it's emulating V8 - if (setter == null) res.defineOwnMember(env, "setter", FieldMember.of(Value.UNDEFINED)); - else res.defineOwnMember(env, "setter", FieldMember.of(setter)); + if (getter == null) res.defineOwnMember(env, "getter", Value.UNDEFINED); + else res.defineOwnMember(env, "getter", getter); - res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); - res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable))); + if (setter == null) res.defineOwnMember(env, "setter", Value.UNDEFINED); + else res.defineOwnMember(env, "setter", setter); + + res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable)); + res.defineOwnMember(env, "configurable", BoolValue.of(configurable)); return res; } - public PropertyMember(FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) { + public PropertyMember(Value self, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) { + this.self = self; this.getter = getter; this.setter = setter; this.configurable = configurable; @@ -69,60 +85,87 @@ public interface Member { value = val; return true; } - public SimpleFieldMember(Value value, boolean configurable, boolean enumerable, boolean writable) { - super(configurable, enumerable, writable); + public SimpleFieldMember(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) { + super(self, configurable, enumerable, writable); this.value = value; } } + public final Value self; public boolean configurable; public boolean enumerable; public boolean writable; - @Override public final boolean configurable() { return configurable; } + @Override public final boolean configurable() { return configurable && self.getState().configurable; } @Override public final boolean enumerable() { return enumerable; } - @Override public final boolean configure(Environment env, Member newMember, Value self) { - if (!(newMember instanceof FieldMember)) return false; - var field = (FieldMember)newMember; + public final boolean writable() { return writable && self.getState().writable; } - if (field.configurable != configurable) return false; - if (field.enumerable != enumerable) return false; - if (!writable) return field.get(env, self).equals(get(env, self)); + @Override public final boolean redefine(Environment env, Member newMember, Value self) { + // If the given member isn't a field, we can't redefine + if (!(newMember instanceof FieldMember field)) return false; - set(env, field.get(env, self), self); - writable = field.writable; - return true; + if (configurable()) { + configurable = field.configurable; + enumerable = field.enumerable; + writable = field.enumerable; + + // We will try to set a new value. However, the underlying field might be immutably readonly + // In such case, we will silently fail, since this is not covered by the specification + if (!set(env, field.get(env, self), self)) writable = false; + return true; + } + else { + // New field settings must be an exact match + if (configurable() != field.configurable()) return false; + if (enumerable() != field.enumerable()) return false; + + if (!writable()) { + // If the field isn't writable, the redefinition should be an exact match + if (field.writable()) return false; + if (field.get(env, self).equals(this.get(env, self))) return false; + + return true; + } + else { + // Writable non-configurable fields may be made readonly or their values may be changed + writable = field.writable; + + if (!set(env, field.get(env, self), self)) writable = false; + return true; + } + } } @Override public ObjectValue descriptor(Environment env, Value self) { var res = new ObjectValue(); - 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))); + res.defineOwnMember(env, "value", get(env, self)); + res.defineOwnMember(env, "writable", BoolValue.of(writable)); + res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable)); + res.defineOwnMember(env, "configurable", BoolValue.of(configurable)); return res; } - public FieldMember(boolean configurable, boolean enumerable, boolean writable) { + public FieldMember(Value self, boolean configurable, boolean enumerable, boolean writable) { + this.self = self; this.configurable = configurable; this.enumerable = enumerable; this.writable = writable; } - public static FieldMember of(Value value) { - return new SimpleFieldMember(value, true, true, true); + public static FieldMember of(Value self, Value value) { + return new SimpleFieldMember(self, value, true, true, true); } - public static FieldMember of(Value value, boolean writable) { - return new SimpleFieldMember(value, true, true, writable); + public static FieldMember of(Value self, Value value, boolean writable) { + return new SimpleFieldMember(self, value, true, true, writable); } - public static FieldMember of(Value value, boolean configurable, boolean enumerable, boolean writable) { - return new SimpleFieldMember(value, configurable, enumerable, writable); + public static FieldMember of(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) { + return new SimpleFieldMember(self, value, configurable, enumerable, writable); } } public boolean configurable(); public boolean enumerable(); - public boolean configure(Environment env, Member newMember, Value self); + public boolean redefine(Environment env, Member newMember, Value self); public ObjectValue descriptor(Environment env, Value self); public Value get(Environment env, Value self); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 4c112a5..739f0d87 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -20,6 +20,7 @@ import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; +import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; @@ -31,21 +32,21 @@ import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public abstract class Value { - public static enum CompareResult { - NOT_EQUAL, - EQUAL, - LESS, - GREATER; + public static enum State { + NORMAL(true, true, true), + NON_EXTENDABLE(false, true, true), + SEALED(false, false, true), + FROZEN(false, false, false); - 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 final boolean extendable; + public final boolean configurable; + public final boolean writable; + + private State(boolean extendable, boolean configurable, boolean writable) { + this.extendable = extendable; + this.writable = writable; + this.configurable = configurable; } } @@ -132,6 +133,12 @@ public abstract class Value { public abstract ObjectValue getPrototype(Environment env); public abstract boolean setPrototype(Environment env, ObjectValue val); + public abstract State getState(); + + public abstract void preventExtensions(); + public abstract void seal(); + public abstract void freeze(); + public final Member getOwnMember(Environment env, Value key) { return getOwnMember(env, new KeyCache(key)); } @@ -159,19 +166,19 @@ public abstract class Value { } public final boolean defineOwnMember(Environment env, KeyCache key, Value val) { - return defineOwnMember(env, key, FieldMember.of(val)); + return defineOwnMember(env, key, FieldMember.of(this, val)); } public final boolean defineOwnMember(Environment env, Value key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + return defineOwnMember(env, new KeyCache(key), val); } public final boolean defineOwnMember(Environment env, String key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + return defineOwnMember(env, new KeyCache(key), val); } public final boolean defineOwnMember(Environment env, int key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + return defineOwnMember(env, new KeyCache(key), val); } public final boolean defineOwnMember(Environment env, double key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); + return defineOwnMember(env, new KeyCache(key), val); } public final boolean deleteOwnMember(Environment env, Value key) { @@ -238,7 +245,7 @@ public abstract class Value { } } - if (defineOwnMember(env, key, FieldMember.of(val))) { + if (defineOwnMember(env, key, val)) { if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); return true; } @@ -475,7 +482,13 @@ public abstract class Value { var member = obj.getOwnMember(env, entry); if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); - else res.append("[property]"); + else if (member instanceof PropertyMember prop) { + if (prop.getter == null && prop.setter == null) res.append("[No accessors]"); + else if (prop.getter == null) res.append("[Setter]"); + else if (prop.setter == null) res.append("[Getter]"); + else res.append("[Getter/Setter]"); + } + else res.append("[???]"); res.append(",\n"); } @@ -485,7 +498,13 @@ public abstract class Value { var member = obj.getOwnMember(env, entry); if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); - else res.append("[property]"); + else if (member instanceof PropertyMember prop) { + if (prop.getter == null && prop.setter == null) res.append("[No accessors]"); + else if (prop.getter == null) res.append("[Setter]"); + else if (prop.setter == null) res.append("[Getter]"); + else res.append("[Getter/Setter]"); + } + else res.append("[???]"); res.append(",\n"); } @@ -520,8 +539,8 @@ public abstract class Value { return new NativeFunction("", args -> { var obj = new ObjectValue(); - if (!it.hasNext()) obj.defineOwnMember(args.env, "done", FieldMember.of(BoolValue.TRUE)); - else obj.defineOwnMember(args.env, "value", FieldMember.of(it.next())); + if (!it.hasNext()) obj.defineOwnMember(args.env, "done", BoolValue.TRUE); + else obj.defineOwnMember(args.env, "value", it.next()); return obj; }); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java index 543a6d1..20f7d80 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -10,6 +10,8 @@ import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; public abstract class FunctionValue extends ObjectValue { + private static final StringValue typeString = new StringValue("function"); + public String name = ""; public int length; public Value prototype = new ObjectValue(); @@ -17,7 +19,7 @@ public abstract class FunctionValue extends ObjectValue { public boolean enableCall = true; public boolean enableNew = true; - private final FieldMember nameField = new FieldMember(true, false, false) { + private final FieldMember nameField = new FieldMember(this, true, false, false) { @Override public Value get(Environment env, Value self) { if (name == null) return new StringValue(""); return new StringValue(name); @@ -27,7 +29,7 @@ public abstract class FunctionValue extends ObjectValue { return true; } }; - private final FieldMember lengthField = new FieldMember(true, false, false) { + private final FieldMember lengthField = new FieldMember(this, true, false, false) { @Override public Value get(Environment env, Value self) { return new NumberValue(length); } @@ -35,7 +37,7 @@ public abstract class FunctionValue extends ObjectValue { return false; } }; - private final FieldMember prototypeField = new FieldMember(false, false, true) { + private final FieldMember prototypeField = new FieldMember(this, false, false, true) { @Override public Value get(Environment env, Value self) { return prototype; } @@ -77,6 +79,8 @@ public abstract class FunctionValue extends ObjectValue { } } + @Override public StringValue type() { return typeString; } + public void setName(String val) { if (this.name == null || this.name.equals("")) this.name = val; } @@ -88,7 +92,7 @@ public abstract class FunctionValue extends ObjectValue { this.length = length; this.name = name; - prototype.defineOwnMember(null, "constructor", FieldMember.of(this)); + prototype.defineOwnMember(null, "constructor", this); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index 1e3ffe8..ac70a26 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -20,13 +20,6 @@ public class ObjectValue extends Value { public ObjectValue get(Environment env); } - public static enum State { - NORMAL, - NO_EXTENSIONS, - SEALED, - FROZEN, - } - public static class Property { public final FunctionValue getter; public final FunctionValue setter; @@ -41,8 +34,6 @@ public class ObjectValue extends Value { protected PrototypeProvider prototype; - public boolean extensible = true; - public LinkedHashMap members = new LinkedHashMap<>(); public LinkedHashMap symbolMembers = new LinkedHashMap<>(); @@ -70,9 +61,17 @@ public class ObjectValue extends Value { @Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); } @Override public StringValue type() { return typeString; } + private State state = State.NORMAL; + + @Override public State getState() { return state; } + public final void preventExtensions() { - extensible = false; + if (state == State.NORMAL) state = State.NON_EXTENDABLE; } + public final void seal() { + if (state == State.NORMAL || state == State.NON_EXTENDABLE) state = State.SEALED; + } + @Override public final void freeze() { state = State.FROZEN; } @Override public Member getOwnMember(Environment env, KeyCache key) { if (key.isSymbol()) return symbolMembers.get(key.toSymbol()); @@ -80,7 +79,7 @@ public class ObjectValue extends Value { } @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.redefine(env, member, this)) return true; if (old != null && !old.configurable()) return false; if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member); @@ -89,11 +88,11 @@ public class ObjectValue extends Value { return true; } @Override public boolean deleteOwnMember(Environment env, KeyCache key) { - if (!extensible) return false; + if (!getState().extendable) return false; var member = getOwnMember(env, key); if (member == null) return true; - if (member.configurable()) return false; + if (!member.configurable()) return false; if (key.isSymbol()) symbolMembers.remove(key.toSymbol()); else members.remove(key.toString(env)); @@ -134,12 +133,12 @@ public class ObjectValue extends Value { } public final boolean setPrototype(PrototypeProvider val) { - if (!extensible) return false; + if (!getState().extendable) return false; prototype = val; return true; } public final boolean setPrototype(Key key) { - if (!extensible) return false; + if (!getState().extendable) return false; prototype = env -> env.get(key); return true; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java index a968637..dc60a83 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java @@ -5,20 +5,20 @@ import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; public final class ScopeValue extends ObjectValue { - private class VariableField extends FieldMember { + private static class VariableField extends FieldMember { private int i; - public VariableField(int i) { - super(false, true, true); + public VariableField(int i, ScopeValue self) { + super(self, false, true, true); this.i = i; } @Override public Value get(Environment env, Value self) { - return variables[i][0]; + return ((ScopeValue)self).variables[i][0]; } @Override public boolean set(Environment env, Value val, Value self) { - variables[i][0] = val; + ((ScopeValue)self).variables[i][0] = val; return true; } } @@ -28,7 +28,7 @@ public final class ScopeValue extends ObjectValue { public ScopeValue(Value[][] variables, String[] names) { this.variables = variables; for (var i = 0; i < names.length && i < variables.length; i++) { - defineOwnMember(Environment.empty(), i, new VariableField(i)); + defineOwnMember(Environment.empty(), i, new VariableField(i, this)); } } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java index 6806775..5709b3d 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java @@ -19,4 +19,10 @@ public abstract class PrimitiveValue extends Value { @Override public Member getOwnMember(Environment env, KeyCache key) { return null; } @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { return Set.of(); } @Override public Set getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return Set.of(); } + + @Override public State getState() { return State.FROZEN; } + + @Override public void preventExtensions() {} + @Override public void seal() {} + @Override public void freeze() {} } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java index b2c58ca..e2d29d3 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -40,10 +40,10 @@ public final class StringValue extends PrimitiveValue { var i = key.toInt(env); if (i == num && i >= 0 && i < value.length()) { - return FieldMember.of(new StringValue(value.charAt(i) + ""), false, true, false); + return FieldMember.of(this, new StringValue(value.charAt(i) + ""), false, true, false); } else if (key.toString(env).equals("length")) { - return FieldMember.of(new NumberValue(value.length()), false, false, false); + return FieldMember.of(this, new NumberValue(value.length()), false, false, false); } else return super.getOwnMember(env, key); } -- 2.45.2 From d87e53264db1e8d1bb5f28b6521379d1370549f6 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:22:31 +0300 Subject: [PATCH 09/15] refactor: split array logic --- .../values/objects/ArrayLikeValue.java | 97 +++++++++++++++++++ .../runtime/values/objects/ArrayValue.java | 93 ++---------------- 2 files changed, 103 insertions(+), 87 deletions(-) create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java new file mode 100644 index 0000000..6b4caf3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java @@ -0,0 +1,97 @@ +package me.topchetoeu.jscript.runtime.values.objects; + +import java.util.LinkedHashSet; +import java.util.Set; + +import me.topchetoeu.jscript.common.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; +import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; + +public abstract class ArrayLikeValue extends ObjectValue { + private static class IndexField extends FieldMember { + private int i; + private ArrayLikeValue arr; + + @Override public Value get(Environment env, Value self) { + return arr.get(i); + } + @Override public boolean set(Environment env, Value val, Value self) { + arr.set(i, val); + return true; + } + public IndexField(int i, ArrayLikeValue arr) { + super(arr, true, true, true); + this.arr = arr; + this.i = i; + } + } + + private final FieldMember lengthField = new FieldMember(this, 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) { + return setSize(val.toInt(env)); + } + }; + + public abstract int size(); + public abstract boolean setSize(int val); + + public abstract Value get(int i); + public abstract void set(int i, Value val); + public abstract boolean has(int i); + public abstract void remove(int i); + + @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 = key.toInt(env); + + 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, KeyCache key, Member member) { + if (!(member instanceof FieldMember) || super.getOwnMember(env, key) != null) return super.defineOwnMember(env, key, member); + if (!getState().writable) return false; + + var num = key.toNumber(env); + var i = key.toInt(env); + + if (i == num && i >= 0) { + if (!getState().extendable && !has(i)) return false; + set(i, ((FieldMember)member).get(env, this)); + return true; + } + else return super.defineOwnMember(env, key, member); + } + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { + if (!super.deleteOwnMember(env, key)) return false; + + var num = key.toNumber(env); + var i = key.toInt(env); + + if (i == num && i >= 0 && i < size()) return super.deleteOwnMember(env, key); + else return true; + } + + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { + var res = new LinkedHashSet(); + + res.addAll(super.getOwnMembers(env, onlyEnumerable)); + + for (var i = 0; i < size(); i++) { + if (has(i)) res.add(i + ""); + } + + if (!onlyEnumerable) res.add("length"); + + return res; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java index 397ab5b..5ad0b56 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java @@ -4,50 +4,15 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; -import me.topchetoeu.jscript.common.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; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; // TODO: Make methods generic -public class ArrayValue extends ObjectValue implements Iterable { +public class ArrayValue extends ArrayLikeValue 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; - - @Override public Value get(Environment env, Value self) { - return arr.get(i); - } - @Override public boolean set(Environment env, Value val, Value self) { - arr.set(i, val); - return true; - } - public IndexField(int i, ArrayValue arr) { - super(true, true, true); - this.arr = arr; - this.i = i; - } - } - private Value[] alloc(int index) { index++; if (index < values.length) return values; @@ -69,26 +34,27 @@ public class ArrayValue extends ObjectValue implements Iterable { return true; } - public Value get(int i) { + @Override public Value get(int i) { if (i < 0 || i >= size) return null; var res = values[i]; if (res == null) return Value.UNDEFINED; else return res; } - public void set(int i, Value val) { + @Override public void set(int i, Value val) { if (i < 0) return; alloc(i)[i] = val; if (i >= size) size = i + 1; } - public boolean has(int i) { + @Override public boolean has(int i) { return i >= 0 && i < size && values[i] != null; } - public void remove(int i) { + @Override public void remove(int i) { if (i < 0 || i >= values.length) return; values[i] = null; } + public void shrink(int n) { if (n >= values.length) { values = new Value[16]; @@ -149,53 +115,6 @@ public class ArrayValue extends ObjectValue implements Iterable { }); } - @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 = key.toInt(env); - - 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, 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 = key.toInt(env); - - 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, KeyCache key) { - if (!super.deleteOwnMember(env, key)) return false; - - var num = key.toNumber(env); - var i = key.toInt(env); - - if (i == num && i >= 0 && i < size) return super.deleteOwnMember(env, key); - else return true; - } - - @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { - var res = new LinkedHashSet(); - - res.addAll(super.getOwnMembers(env, onlyEnumerable)); - - for (var i = 0; i < size; i++) { - if (has(i)) res.add(i + ""); - } - - if (!onlyEnumerable) res.add("length"); - - return res; - } @Override public Iterator iterator() { return new Iterator<>() { private int i = 0; -- 2.45.2 From 0b34c681396f317f47a10db4edc8741bb826c596 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:22:51 +0300 Subject: [PATCH 10/15] fix: unnecessary new line in toReadable --- src/main/java/me/topchetoeu/jscript/runtime/values/Value.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 739f0d87..92bc03e 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -470,7 +470,7 @@ public abstract class Value { passed.add(this); if (keys.size() + obj.getOwnSymbolMembers(env, true).size() == 0) { - if (!printed) res.append("{}\n"); + if (!printed) res.append("{}"); } else if (!printed) { if (tab > 3) return "{...}"; -- 2.45.2 From 55613ef2c989059190ea8861837b7a3600de46d0 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:23:28 +0300 Subject: [PATCH 11/15] feat: extend the instruction set --- .../jscript/common/Instruction.java | 20 ++++++---- .../jscript/runtime/InstructionRunner.java | 37 ++++++++++++------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index f9d75ad..9b4cc65 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -56,9 +56,10 @@ public class Instruction { STORE_MEMBER_STR(0x4B), DEF_PROP(0x50), - KEYS(0x51), - TYPEOF(0x52), - OPERATION(0x53), + DEF_FIELD(0x51), + KEYS(0x52), + TYPEOF(0x53), + OPERATION(0x54), GLOB_GET(0x60), GLOB_SET(0x61), @@ -341,8 +342,8 @@ public class Instruction { return new Instruction(Type.GLOB_DEF, name); } - public static Instruction globGet(String name) { - return new Instruction(Type.GLOB_GET, name); + public static Instruction globGet(String name, boolean force) { + return new Instruction(Type.GLOB_GET, name, force); } public static Instruction globSet(String name, boolean keep, boolean define) { return new Instruction(Type.GLOB_SET, name, keep, define); @@ -435,7 +436,7 @@ public class Instruction { return new Instruction(Type.STORE_MEMBER_INT, key, false); } public static Instruction storeMember(int key, boolean keep) { - return new Instruction(Type.STORE_MEMBER_STR, key, keep); + return new Instruction(Type.STORE_MEMBER_INT, key, keep); } public static Instruction discard() { @@ -453,8 +454,11 @@ public class Instruction { return new Instruction(Type.KEYS, own, onlyEnumerable); } - public static Instruction defProp() { - return new Instruction(Type.DEF_PROP); + public static Instruction defProp(boolean setter) { + return new Instruction(Type.DEF_PROP, setter); + } + public static Instruction defField() { + return new Instruction(Type.DEF_FIELD); } public static Instruction operation(Operation op) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 094b2bb..de09d95 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -58,24 +58,29 @@ public class InstructionRunner { } private static Value execDefProp(Environment env, Instruction instr, Frame frame) { - var setterVal = frame.pop(); - var getterVal = frame.pop(); + var val = frame.pop(); var key = frame.pop(); var obj = frame.pop(); - FunctionValue getter, setter; + FunctionValue accessor; - if (getterVal == Value.UNDEFINED) getter = null; - else if (getterVal instanceof FunctionValue) getter = (FunctionValue)getterVal; + if (val == Value.UNDEFINED) accessor = null; + else if (val instanceof FunctionValue func) accessor = func; else throw EngineException.ofType("Getter must be a function or undefined."); - if (setterVal == Value.UNDEFINED) setter = null; - else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal; - else throw EngineException.ofType("Setter must be a function or undefined."); + if ((boolean)instr.get(0)) obj.defineOwnMember(env, key, new PropertyMember(obj, null, accessor, true, true)); + else obj.defineOwnMember(env, key, new PropertyMember(obj, accessor, null, true, true)); - obj.defineOwnMember(env, key, new PropertyMember(obj, getter, setter, true, true)); + frame.codePtr++; + return null; + } + private static Value execDefField(Environment env, Instruction instr, Frame frame) { + var val = frame.pop(); + var key = frame.pop(); + var obj = frame.pop(); + + obj.defineOwnMember(env, key, val); - frame.push(obj); frame.codePtr++; return null; } @@ -449,10 +454,15 @@ public class InstructionRunner { } private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) { var name = (String)instr.get(0); - var res = Value.global(env).getMemberOrNull(env, name); + if ((boolean)instr.get(1)) { + frame.push(Value.global(env).getMember(env, name)); + } + else { + var res = Value.global(env).getMemberOrNull(env, name); - if (res == null) throw EngineException.ofSyntax(name + " is not defined"); - else frame.push(res); + if (res == null) throw EngineException.ofSyntax(name + " is not defined"); + else frame.push(res); + } frame.codePtr++; return null; @@ -576,6 +586,7 @@ public class InstructionRunner { case KEYS: return execKeys(env, instr, frame); case DEF_PROP: return execDefProp(env, instr, frame); + case DEF_FIELD: return execDefField(env, instr, frame); case TYPEOF: return execTypeof(env, instr, frame); case DELETE: return execDelete(env, instr, frame); -- 2.45.2 From 23ae2b2e469d06b4107d5554fc4cd3c757f3895a Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:23:35 +0300 Subject: [PATCH 12/15] todo --- src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java b/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java index 17873e4..3bf2bb8 100644 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java @@ -51,6 +51,7 @@ public class ParseRes { return new ParseRes(State.FAILED, null, null, null, 0); } public static ParseRes error(Location loc, String error) { + // TODO: differentiate definitive and probable errors return new ParseRes<>(State.ERROR, loc, error, null, 0); } public static ParseRes res(T val, int i) { -- 2.45.2 From cb82f4cf321fbfc548d738aa3346b211a09293b3 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:23:46 +0300 Subject: [PATCH 13/15] feat: implement patterns --- .../compilation/FunctionArrowNode.java | 9 +- .../jscript/compilation/FunctionNode.java | 48 +-- .../jscript/compilation/JavaScript.java | 65 --- .../jscript/compilation/Parameters.java | 75 +++- .../compilation/VariableDeclareNode.java | 40 +- .../destructing/AssignDestructorNode.java | 69 --- .../compilation/destructing/AssignTarget.java | 4 - .../compilation/destructing/Destructor.java | 38 -- .../destructing/NamedDestructor.java | 20 - .../compilation/patterns/AssignPattern.java | 69 +++ .../compilation/patterns/AssignTarget.java | 27 ++ .../patterns/AssignTargetLike.java | 8 + .../ChangeTarget.java | 3 +- .../compilation/patterns/NamedDestructor.java | 15 + .../patterns/ObjectAssignable.java | 16 + .../patterns/ObjectDestructor.java | 44 ++ .../compilation/patterns/ObjectPattern.java | 118 +++++ .../jscript/compilation/patterns/Pattern.java | 47 ++ .../compilation/patterns/PatternLike.java | 5 + .../jscript/compilation/scope/Scope.java | 20 +- .../values/ObjectDestructorNode.java | 199 --------- .../compilation/values/ObjectNode.java | 402 +++++++++++++----- .../compilation/values/VariableNode.java | 74 ++-- .../values/operations/AssignNode.java | 52 +-- .../values/operations/CallNode.java | 2 +- .../values/operations/ChangeNode.java | 12 +- .../values/operations/IndexNode.java | 79 ++-- .../values/operations/OperationNode.java | 24 +- .../values/operations/PostfixNode.java | 12 +- .../values/operations/TypeofNode.java | 8 +- src/main/resources/lib/index.js | 33 +- 31 files changed, 927 insertions(+), 710 deletions(-) delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java rename src/main/java/me/topchetoeu/jscript/compilation/{destructing => patterns}/ChangeTarget.java (57%) create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java index 036533c..aa5ca8c 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java @@ -9,6 +9,7 @@ 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.ReturnNode; +import me.topchetoeu.jscript.compilation.patterns.Pattern; public class FunctionArrowNode extends FunctionNode { @Override public String name() { return null; } @@ -34,7 +35,7 @@ public class FunctionArrowNode extends FunctionNode { Parameters params; if (src.is(i + n, "(")) { - var paramsRes = JavaScript.parseParameters(src, i + n); + var paramsRes = Parameters.parseParameters(src, i + n); if (!paramsRes.isSuccess()) return paramsRes.chainError(); n += paramsRes.n; n += Parsing.skipEmpty(src, i + n); @@ -42,14 +43,12 @@ public class FunctionArrowNode extends FunctionNode { params = paramsRes.result; } else { - var singleParam = Parsing.parseIdentifier(src, i + n); + var singleParam = Pattern.parse(src, i + n, true); if (!singleParam.isSuccess()) return ParseRes.failed(); - - var paramLoc = src.loc(i + n); n += singleParam.n; n += Parsing.skipEmpty(src, i + n); - params = new Parameters(List.of(new Parameter(paramLoc, singleParam.result, null))); + params = new Parameters(List.of(singleParam.result)); } if (!src.is(i + n, "=>")) return ParseRes.failed(); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index 9c369b8..b8b2bed 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -1,16 +1,15 @@ package me.topchetoeu.jscript.compilation; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.environment.Environment; 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.JavaScript.DeclarationType; import me.topchetoeu.jscript.compilation.scope.FunctionScope; import me.topchetoeu.jscript.compilation.scope.Variable; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public abstract class FunctionNode extends Node { public final CompoundNode body; @@ -37,36 +36,37 @@ public abstract class FunctionNode extends Node { var i = 0; for (var param : params.params) { - if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed"); - if (!JavaScript.checkVarName(param.name)) { - throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name)); - } - var varI = scope.define(new Variable(param.name, false), param.loc); - target.add(Instruction.loadMember(i++)); + param.destruct(target, DeclarationType.VAR, true); + // if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed"); + // if (!JavaScript.checkVarName(param.name)) { + // throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name)); + // } + // var varI = scope.define(new Variable(param.name, false), param.loc); - if (param.node != null) { - var end = new DeferredIntSupplier(); + // if (param.node != null) { + // var end = new DeferredIntSupplier(); - target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); - target.add(Instruction.jmpIfNot(end)); - target.add(Instruction.discard()); - param.node.compile(target, true); + // target.add(Instruction.dup()); + // target.add(Instruction.pushUndefined()); + // target.add(Instruction.operation(Operation.EQUALS)); + // target.add(Instruction.jmpIfNot(end)); + // target.add(Instruction.discard()); + // param.node.compile(target, true); - end.set(target.size()); - } + // end.set(target.size()); + // } - target.add(_i -> varI.index().toSet(false)); + // target.add(_i -> varI.index().toSet(false)); } } - if (params.restName != null) { - if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed"); - var restVar = scope.define(new Variable(params.restName, false), params.restLocation); + if (params.rest != null) { target.add(Instruction.loadRestArgs(params.params.size())); - target.add(_i -> restVar.index().toSet(false)); + params.rest.destruct(target, DeclarationType.VAR, true); + // if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed"); + // var restVar = scope.define(new Variable(params.restName, false), params.restLocation); + // target.add(_i -> restVar.index().toSet(false)); } if (selfName != null && !scope.has(name, false)) { @@ -131,7 +131,7 @@ public abstract class FunctionNode extends Node { n += name.n; n += Parsing.skipEmpty(src, i + n); - var params = JavaScript.parseParameters(src, i + n); + var params = Parameters.parseParameters(src, i + n); if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list"); n += params.n; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java index 082d7ec..cab60c3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -222,71 +222,6 @@ public final class JavaScript { return ParseRes.failed(); } - public static ParseRes parseParameters(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 params = new ArrayList(); - - var closeParen = Parsing.parseOperator(src, i + n, ")"); - n += closeParen.n; - - if (!closeParen.isSuccess()) { - while (true) { - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "...")) { - n += 3; - var restLoc = src.loc(i); - - var restName = Parsing.parseIdentifier(src, i + n); - if (!restName.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter"); - n += restName.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter"); - n++; - - return ParseRes.res(new Parameters(params, restName.result, restLoc), n); - } - - var paramLoc = src.loc(i); - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace"); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "=")) { - n++; - - var val = parseExpression(src, i + n, 2); - if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter default value"); - n += val.n; - n += Parsing.skipEmpty(src, i + n); - - params.add(new Parameter(paramLoc, name.result, val.result)); - } - else params.add(new Parameter(paramLoc, name.result, null)); - - if (src.is(i + n, ",")) { - n++; - n += Parsing.skipEmpty(src, i + n); - } - - if (src.is(i + n, ")")) { - n++; - break; - } - } - } - - return ParseRes.res(new Parameters(params), n); - } - public static ParseRes parseDeclarationType(Source src, int i) { var res = Parsing.parseIdentifier(src, i); if (!res.isSuccess()) return res.chainError(); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java index 307170f..712a2e8 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java @@ -1,29 +1,84 @@ package me.topchetoeu.jscript.compilation; +import java.util.ArrayList; import java.util.List; -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.patterns.Pattern; +import me.topchetoeu.jscript.compilation.values.operations.AssignNode; public final class Parameters { public final int length; - public final List params; - public final String restName; - public final Location restLocation; + public final List params; + public final Pattern rest; - public Parameters(List params, String restName, Location restLocation) { + public Parameters(List params, Pattern rest) { var len = params.size(); for (var i = params.size() - 1; i >= 0; i--) { - if (params.get(i).node == null) break; + if (!(params.get(i) instanceof AssignNode)) break; len--; } this.params = params; this.length = len; - this.restName = restName; - this.restLocation = restLocation; + this.rest = rest; } - public Parameters(List params) { - this(params, null, null); + public Parameters(List params) { + this(params, null); + } + + public static ParseRes parseParameters(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 params = new ArrayList(); + + var closeParen = Parsing.parseOperator(src, i + n, ")"); + n += closeParen.n; + + if (!closeParen.isSuccess()) { + while (true) { + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "...")) { + n += 3; + + var rest = Pattern.parse(src, i + n, true); + if (!rest.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter"); + n += rest.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter"); + n++; + + return ParseRes.res(new Parameters(params, rest.result), n); + } + + var param = Pattern.parse(src, i + n, true); + if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace"); + n += param.n; + n += Parsing.skipEmpty(src, i + n); + + params.add(param.result); + + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + } + + if (src.is(i + n, ")")) { + n++; + break; + } + } + } + + return ParseRes.res(new Parameters(params), n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java index 7584d48..d762e8e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java @@ -4,23 +4,21 @@ import java.util.ArrayList; import java.util.List; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.scope.Variable; -import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.patterns.Pattern; public class VariableDeclareNode extends Node { public static class Pair { - public final String name; + public final Pattern destructor; public final Node value; public final Location location; - public Pair(String name, Node value, Location location) { - this.name = name; + public Pair(Pattern destr, Node value, Location location) { + this.destructor = destr; this.value = value; this.location = location; } @@ -32,25 +30,22 @@ public class VariableDeclareNode extends Node { @Override public void resolve(CompileResult target) { if (!declType.strict) { for (var entry : values) { - target.scope.define(new Variable(entry.name, false), entry.location); + entry.destructor.destructDeclResolve(target); } } } @Override public void compile(CompileResult target, boolean pollute) { for (var entry : values) { - if (entry.name == null) continue; - if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location); - - if (entry.value != null) { - FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); - target.add(VariableNode.toSet(target, entry.location, entry.name, false, true)); + if (entry.value == null) { + if (declType == DeclarationType.VAR) entry.destructor.declare(target, null); + else entry.destructor.declare(target, declType); } - else target.add(_i -> { - var i = target.scope.get(entry.name, false); + else { + entry.value.compile(target, true); - if (i == null) return Instruction.globDef(entry.name); - else return Instruction.nop(); - }); + if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true); + else entry.destructor.destruct(target, declType, true); + } } if (pollute) target.add(Instruction.pushUndefined()); @@ -80,13 +75,10 @@ public class VariableDeclareNode extends Node { while (true) { var nameLoc = src.loc(i + n); - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name"); - n += name.n; - if (!JavaScript.checkVarName(name.result)) { - return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result)); - } + var name = Pattern.parse(src, i + n, false); + if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor"); + n += name.n; Node val = null; var endN = n; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java deleted file mode 100644 index 755f043..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java +++ /dev/null @@ -1,69 +0,0 @@ -package me.topchetoeu.jscript.compilation.destructing; - -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.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.scope.Variable; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public class AssignDestructorNode extends Node implements Destructor { - public final String name; - public final Node value; - - @Override public void destructDeclResolve(CompileResult target) { - target.scope.define(new Variable(name, false), loc()); - } - @Override public void destructArg(CompileResult target) { - var v = target.scope.define(new Variable(name, false), loc()); - destructAssign(target); - target.add(_i -> v.index().toSet(false)); - } - @Override public void afterAssign(CompileResult target, DeclarationType decl) { - if (decl != null && decl.strict) target.scope.define(new Variable(name, decl.readonly), loc()); - var end = new DeferredIntSupplier(); - - target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); - target.add(Instruction.jmpIfNot(end)); - target.add(Instruction.discard()); - value.compile(target, true); - - end.set(target.size()); - - target.add(VariableNode.toSet(target, loc(), name, false, decl != null)); - } - - public AssignDestructorNode(Location loc, String name, Node value) { - super(loc); - this.name = name; - this.value = value; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var name = Parsing.parseIdentifier(src, i); - if (!JavaScript.checkVarName(null)) return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", name.result)); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i, "=")) return ParseRes.failed(); - n++; - - var value = JavaScript.parseExpression(src, i, 2); - if (value.isError()) return ParseRes.error(src.loc(i + n), "Expected a value after '='"); - n += value.n; - - return ParseRes.res(new AssignDestructorNode(loc, name.result, value.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java deleted file mode 100644 index 1e60d9f..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java +++ /dev/null @@ -1,4 +0,0 @@ -package me.topchetoeu.jscript.compilation.destructing; - -public interface AssignTarget extends Destructor { -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java deleted file mode 100644 index 264cdde..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.topchetoeu.jscript.compilation.destructing; - -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.JavaScript; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.values.ObjectDestructorNode; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public interface Destructor { - void destructDeclResolve(CompileResult target); - - default void destructArg(CompileResult target) { - beforeAssign(target, null); - afterAssign(target, null); - } - default void beforeAssign(CompileResult target, DeclarationType decl) {} - void afterAssign(CompileResult target, DeclarationType decl, boolean pollute); - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - ParseRes first = ParseRes.first(src, i + n, - ObjectDestructorNode::parse, - AssignDestructorNode::parse, - VariableNode::parse - ); - if (first.isSuccess()) return first.addN(n); - - var exp = JavaScript.parseExpression(src, i, 2); - if (!(exp.result instanceof Destructor destructor)) return ParseRes.error(src.loc(i + n), "Expected a destructor expression"); - n += exp.n; - - return ParseRes.res(destructor, n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java deleted file mode 100644 index 47b5a4b..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.topchetoeu.jscript.compilation.destructing; - -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.VariableNode; - -public interface NamedDestructor extends Destructor { - String name(); - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - ParseRes first = ParseRes.first(src, i + n, - AssignDestructorNode::parse, - VariableNode::parse - ); - return first.addN(n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java new file mode 100644 index 0000000..f7fa3b8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.compilation.patterns; + +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.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class AssignPattern implements Pattern { + public final Location loc; + public final Pattern assignable; + public final Node value; + + @Override public Location loc() { return loc; } + + @Override public void destructDeclResolve(CompileResult target) { + assignable.destructDeclResolve(target); + } + + @Override public void declare(CompileResult target, DeclarationType decl) { + throw new SyntaxException(loc(), "Expected an assignment value for destructor declaration"); + } + @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { + // if (assignable instanceof AssignPattern other) throw new SyntaxException(other.loc(), "Unexpected destruction target"); + + target.add(Instruction.dup()); + target.add(Instruction.pushUndefined()); + target.add(Instruction.operation(Operation.EQUALS)); + var start = target.temp(); + target.add(Instruction.discard()); + + value.compile(target, true); + + target.set(start, Instruction.jmpIfNot(target.size() - start)); + + assignable.destruct(target, decl, shouldDeclare); + } + + public AssignPattern(Location loc, Pattern assignable, Node value) { + this.loc = loc; + this.assignable = assignable; + this.value = value; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var pattern = Pattern.parse(src, i + n, false); + if (!pattern.isSuccess()) return pattern.chainError(); + n += pattern.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "=")) return ParseRes.failed(); + n++; + + var value = JavaScript.parseExpression(src, i + n, 2); + if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a default value"); + n += value.n; + + return ParseRes.res(new AssignPattern(loc, pattern.result, value.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java new file mode 100644 index 0000000..f8b4739 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java @@ -0,0 +1,27 @@ +package me.topchetoeu.jscript.compilation.patterns; + +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; + +/** + * Represents all nodes that can be assign targets + */ +public interface AssignTarget extends AssignTargetLike { + Location loc(); + + /** + * Called to perform calculations before the assigned value is calculated + */ + default void beforeAssign(CompileResult target) {} + /** + * Called to perform the actual assignemnt. Between the `beforeAssign` and this call a single value will have been pushed to the stack + * @param pollute Whether or not to leave the original value on the stack + */ + void afterAssign(CompileResult target, boolean pollute); + + default void assign(CompileResult target, boolean pollute) { + afterAssign(target, pollute); + } + + @Override default AssignTarget toAssignTarget() { return this; } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java new file mode 100644 index 0000000..ce1dcee --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java @@ -0,0 +1,8 @@ +package me.topchetoeu.jscript.compilation.patterns; + +/** + * Represents all nodes that can be assign targets + */ +public interface AssignTargetLike { + AssignTarget toAssignTarget(); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ChangeTarget.java similarity index 57% rename from src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java rename to src/main/java/me/topchetoeu/jscript/compilation/patterns/ChangeTarget.java index 0736444..68993af 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ChangeTarget.java @@ -1,8 +1,7 @@ -package me.topchetoeu.jscript.compilation.destructing; +package me.topchetoeu.jscript.compilation.patterns; import me.topchetoeu.jscript.compilation.CompileResult; public interface ChangeTarget extends AssignTarget { void beforeChange(CompileResult target); - void afterChange(CompileResult target, boolean pollute); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java new file mode 100644 index 0000000..818471a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java @@ -0,0 +1,15 @@ +package me.topchetoeu.jscript.compilation.patterns; + +public interface NamedDestructor extends Pattern { + String name(); + + // public static ParseRes parse(Source src, int i) { + // var n = Parsing.skipEmpty(src, i); + + // ParseRes first = ParseRes.first(src, i + n, + // AssignDestructorNode::parse, + // VariableNode::parse + // ); + // return first.addN(n); + // } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java new file mode 100644 index 0000000..f3abc5e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java @@ -0,0 +1,16 @@ +package me.topchetoeu.jscript.compilation.patterns; + +import java.util.List; + +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; + +public class ObjectAssignable extends ObjectDestructor implements AssignTarget { + @Override public void afterAssign(CompileResult target, boolean pollute) { + compile(target, t -> t.assign(target, false), pollute); + } + + public ObjectAssignable(Location loc, List> members) { + super(loc, members); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java new file mode 100644 index 0000000..de7b51f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java @@ -0,0 +1,44 @@ +package me.topchetoeu.jscript.compilation.patterns; + +import java.util.List; +import java.util.function.Consumer; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.operations.IndexNode; + +public abstract class ObjectDestructor extends Node { + public static final class Member { + public final Node key; + public final T consumable; + + public Member(Node key, T consumer) { + this.key = key; + this.consumable = consumer; + } + } + + public final List> members; + + public void consume(Consumer consumer) { + for (var el : members) { + consumer.accept(el.consumable); + } + } + public void compile(CompileResult target, Consumer consumer, boolean pollute) { + for (var el : members) { + target.add(Instruction.dup()); + IndexNode.indexLoad(target, el.key, true); + consumer.accept(el.consumable); + } + + if (!pollute) target.add(Instruction.discard()); + } + + public ObjectDestructor(Location loc, List> members) { + super(loc); + this.members = members; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java new file mode 100644 index 0000000..cd9e120 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java @@ -0,0 +1,118 @@ +package me.topchetoeu.jscript.compilation.patterns; + +import java.util.LinkedList; +import java.util.List; + +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.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class ObjectPattern extends ObjectDestructor implements Pattern { + @Override public void destructDeclResolve(CompileResult target) { + consume(t -> t.destructDeclResolve(target)); + } + + @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { + compile(target, t -> t.destruct(target, decl, shouldDeclare), false); + } + + @Override public void declare(CompileResult target, DeclarationType decl) { + throw new SyntaxException(loc(), "Object pattern must be initialized"); + } + + public ObjectPattern(Location loc, List> members) { + super(loc, members); + } + + private static ParseRes> parseShorthand(Source src, int i) { + ParseRes res = ParseRes.first(src, i, + AssignPattern::parse, + VariableNode::parse + ); + + if (res.isSuccess()) { + if (res.result instanceof AssignPattern assign) { + if (assign.assignable instanceof VariableNode var) { + return ParseRes.res(new Member<>(new StringNode(var.loc(), var.name), res.result), res.n); + } + } + else if (res.result instanceof VariableNode var) { + return ParseRes.res(new Member<>(new StringNode(var.loc(), var.name), res.result), res.n); + } + } + + return res.chainError(); + } + private static ParseRes> parseKeyed(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + var key = ObjectNode.parsePropName(src, i + n); + if (!key.isSuccess()) return key.chainError(); + n += key.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n , ":")) return ParseRes.failed(); + n++; + + ParseRes res = Pattern.parse(src, i + n, true); + if (!res.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a pattern after colon"); + n += res.n; + + return ParseRes.res(new Member<>(key.result, res.result), n); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; + n += Parsing.skipEmpty(src, i + n); + + var members = new LinkedList>(); + + if (src.is(i + n, "}")) { + n++; + return ParseRes.res(new ObjectPattern(loc, members), n); + } + + while (true) { + ParseRes> prop = ParseRes.first(src, i + n, + ObjectPattern::parseKeyed, + ObjectPattern::parseShorthand + ); + + if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object pattern"); + n += prop.n; + + members.add(prop.result); + + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { + n++; + break; + } + + continue; + } + else if (src.is(i + n, "}")) { + n++; + break; + } + else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); + } + + return ParseRes.res(new ObjectPattern(loc, members), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java new file mode 100644 index 0000000..80781af --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java @@ -0,0 +1,47 @@ +package me.topchetoeu.jscript.compilation.patterns; + +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.parsing.ParseRes; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.values.VariableNode; + +/** + * Represents all nodes that can be a destructors (note that all destructors are assign targets, too) + */ +public interface Pattern extends PatternLike { + Location loc(); + + /** + * Called when the destructor has to declare + * @param target + */ + void destructDeclResolve(CompileResult target); + + /** + * Called when a declaration-like is being destructed + * @param decl The variable type the destructor must declare, if it is a named pne + */ + void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare); + + /** + * Run when destructing a declaration without an initializer + */ + void declare(CompileResult target, DeclarationType decl); + + public static ParseRes parse(Source src, int i, boolean withDefault) { + return withDefault ? + ParseRes.first(src, i, + AssignPattern::parse, + ObjectPattern::parse, + VariableNode::parse + ) : + ParseRes.first(src, i, + ObjectPattern::parse, + VariableNode::parse + ); + } + + @Override default Pattern toPattern() { return this; } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java new file mode 100644 index 0000000..8df31c1 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.compilation.patterns; + +public interface PatternLike { + Pattern toPattern(); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java index 3e210f7..3d3006a 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.LinkedList; import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class Scope { @@ -41,15 +42,6 @@ public class Scope { return var; } - // private final int parentVarOffset() { - // if (parent != null) return parent.variableOffset(); - // else return 0; - // } - // private final int parentCapOffset() { - // if (parent != null) return parent.capturedOffset(); - // else return localsCount(); - // } - protected final SyntaxException alreadyDefinedErr(Location loc, String name) { return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name)); } @@ -141,9 +133,6 @@ public class Scope { } return res; - - // if (parent != null) return parent.variableOffset() + variables.size(); - // else return variables.size(); } public final int capturablesOffset() { var res = 0; @@ -154,8 +143,11 @@ public class Scope { } return res; - // if (parent != null) return parent.capturedOffset() + captured.size(); - // else return localsCount() + captured.size(); + } + + public final Variable define(DeclarationType type, String name, Location loc) { + if (type.strict) return defineStrict(new Variable(name, type.readonly), loc); + else return define(new Variable(name, type.readonly), loc); } public int localsCount() { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java deleted file mode 100644 index 68501f0..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java +++ /dev/null @@ -1,199 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import java.util.LinkedHashMap; -import java.util.Map; - -import me.topchetoeu.jscript.common.Instruction; -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.Node; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.destructing.Destructor; - - -public class ObjectDestructorNode extends Node implements Destructor { - public final LinkedHashMap destructors; - public final VariableNode restDestructor; - - private void compileRestObjBuilder(CompileResult target, int srcDupN) { - var subtarget = target.subtarget(); - var src = subtarget.scope.defineTemp(); - var dst = subtarget.scope.defineTemp(); - - target.add(Instruction.loadObj()); - target.add(_i -> src.index().toSet(true)); - target.add(_i -> dst.index().toSet(destructors.size() > 0)); - - target.add(Instruction.keys(true, true)); - var start = target.size(); - - target.add(Instruction.dup()); - var mid = target.temp(); - - target.add(_i -> src.index().toGet()); - target.add(Instruction.dup(1, 1)); - target.add(Instruction.loadMember()); - - target.add(_i -> dst.index().toGet()); - target.add(Instruction.dup(1, 1)); - target.add(Instruction.storeMember()); - - target.add(Instruction.discard()); - var end = target.size(); - target.add(Instruction.jmp(start - end)); - target.set(mid, Instruction.jmpIfNot(end - mid + 1)); - - target.add(Instruction.discard()); - - target.add(Instruction.dup(srcDupN, 1)); - - target.scope.end(); - } - - @Override public void destructDeclResolve(CompileResult target) { - for (var el : destructors.values()) { - el.destructDeclResolve(target); - } - - if (restDestructor != null) restDestructor.destructDeclResolve(target); - } - @Override public void destructArg(CompileResult target) { - if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2); - else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0)); - - for (var el : destructors.entrySet()) { - if (restDestructor != null) { - target.add(Instruction.pushValue(el.getKey())); - target.add(Instruction.delete()); - } - - target.add(Instruction.loadMember(el.getKey())); - el.getValue().destructArg(target); - } - - if (restDestructor != null) restDestructor.destructArg(target); - - target.add(Instruction.discard()); - } - @Override public void afterAssign(CompileResult target, DeclarationType decl) { - if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2); - else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0)); - - for (var el : destructors.entrySet()) { - if (restDestructor != null) { - target.add(Instruction.pushValue(el.getKey())); - target.add(Instruction.delete()); - } - - target.add(Instruction.loadMember(el.getKey())); - el.getValue().afterAssign(target, decl); - } - - if (restDestructor != null) restDestructor.afterAssign(target, decl); - - target.add(Instruction.discard()); - } - @Override public void destructAssign(CompileResult target, boolean pollute) { - if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2); - else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0)); - - for (var el : destructors.entrySet()) { - if (restDestructor != null) { - target.add(Instruction.pushValue(el.getKey())); - target.add(Instruction.delete()); - } - - target.add(Instruction.loadMember(el.getKey())); - el.getValue().destructAssign(target, pollute); - } - - if (restDestructor != null) restDestructor.destructAssign(target, pollute); - - if (!pollute) target.add(Instruction.discard()); - } - - public ObjectDestructorNode(Location loc, Map map, VariableNode rest) { - super(loc); - this.destructors = new LinkedHashMap<>(map); - this.restDestructor = rest; - } - public ObjectDestructorNode(Location loc, Map map) { - super(loc); - this.destructors = new LinkedHashMap<>(map); - this.restDestructor = null; - } - - private static ParseRes parsePropName(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - var res = ParseRes.first(src, i + n, - Parsing::parseIdentifier, - Parsing::parseString, - (s, j) -> Parsing.parseNumber(s, j, false) - ); - if (!res.isSuccess()) return res.chainError(); - n += res.n; - - if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon"); - n++; - - return ParseRes.res(res.result.toString(), n); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "{")) return ParseRes.failed(); - n++; - n += Parsing.skipEmpty(src, i + n); - - var destructors = new LinkedHashMap(); - - if (src.is(i + n, "}")) { - n++; - return ParseRes.res(new ObjectDestructorNode(loc, destructors), n); - } - - while (true) { - n += Parsing.skipEmpty(src, i + n); - - // if (src.is(i, null)) - - var name = parsePropName(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a field name"); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - var destructor = Destructor.parse(src, i + n); - if (!destructor.isSuccess()) return destructor.chainError(src.loc(i + n), "Expected a value in array list"); - n += destructor.n; - - destructors.put(name.result, destructor.result); - - n += Parsing.skipEmpty(src, i + n); - if (src.is(i + n, ",")) { - n++; - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "}")) { - n++; - break; - } - - continue; - } - else if (src.is(i + n, "}")) { - n++; - break; - } - else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); - } - - return ParseRes.res(new ObjectDestructorNode(loc, destructors), n); - } - -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java index 8e4483e..b959352 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -1,10 +1,10 @@ package me.topchetoeu.jscript.compilation.values; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.LinkedList; +import java.util.List; import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; @@ -12,105 +12,308 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompoundNode; import me.topchetoeu.jscript.compilation.FunctionNode; -import me.topchetoeu.jscript.compilation.FunctionValueNode; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.destructing.Destructor; +import me.topchetoeu.jscript.compilation.Parameters; +import me.topchetoeu.jscript.compilation.patterns.AssignTarget; +import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike; +import me.topchetoeu.jscript.compilation.patterns.ObjectAssignable; +import me.topchetoeu.jscript.compilation.patterns.Pattern; +import me.topchetoeu.jscript.compilation.patterns.ObjectDestructor.Member; +import me.topchetoeu.jscript.compilation.values.constants.NumberNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; +import me.topchetoeu.jscript.compilation.values.operations.AssignNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +public class ObjectNode extends Node implements AssignTargetLike { + public static class PropertyMemberNode extends FunctionNode { + public final Node key; + public final Pattern argument; -public class ObjectNode extends Node implements Destructor { - public static class ObjProp { - public final String name; - public final String access; - public final FunctionValueNode func; + @Override public String name() { + if (key instanceof StringNode str) { + if (isGetter()) return "get " + str.value; + else return "set " + str.value; + } + else return null; + } - public ObjProp(String name, String access, FunctionValueNode func) { - this.name = name; - this.access = access; - this.func = func; + public boolean isGetter() { return argument == null; } + public boolean isSetter() { return argument != null; } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + key.compile(target, true); + + var id = target.addChild(compileBody(target, name, null)); + target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target))); + + target.add(Instruction.defProp(isSetter())); + } + + public PropertyMemberNode(Location loc, Location end, Node key, Pattern argument, CompoundNode body) { + super(loc, end, argument == null ? new Parameters(List.of()) : new Parameters(List.of(argument)), body); + this.key = key; + this.argument = argument; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var access = Parsing.parseIdentifier(src, i + n); + if (!access.isSuccess()) return ParseRes.failed(); + if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed(); + n += access.n; + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); + n += name.n; + + var params = Parameters.parseParameters(src, i + n); + if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); + if (access.result.equals("get") && params.result.params.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters"); + if (access.result.equals("set") && params.result.params.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter"); + if (params.result.rest != null) return ParseRes.error(params.result.rest.loc(), "Property members may not have rest arguments"); + n += params.n; + + var body = CompoundNode.parse(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor."); + n += body.n; + + var end = src.loc(i + n - 1); + + return ParseRes.res(new PropertyMemberNode( + loc, end, name.result, access.result.equals("get") ? null : params.result.params.get(0), body.result + ), n); + } + } + public static class MethodMemberNode extends FunctionNode { + public final Node key; + + @Override public String name() { + if (key instanceof StringNode str) return str.value; + else return null; + } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + key.compile(target, true); + + var id = target.addChild(compileBody(target, name, null)); + target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target))); + + target.add(Instruction.defField()); + } + + public MethodMemberNode(Location loc, Location end, Node key, Parameters params, CompoundNode body) { + super(loc, end, params, body); + this.key = key; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return name.chainError(); + n += name.n; + + var params = Parameters.parseParameters(src, i + n); + if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); + n += params.n; + + var body = CompoundNode.parse(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor."); + n += body.n; + + var end = src.loc(i + n - 1); + + return ParseRes.res(new MethodMemberNode( + loc, end, name.result, params.result, body.result + ), n); + } + } + public static class FieldMemberNode extends Node { + public final Node key; + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { + key.compile(target, true); + + if (value == null) target.add(Instruction.pushUndefined()); + else value.compile(target, true); + + target.add(Instruction.defField()); + } + + public FieldMemberNode(Location loc, Node key, Node value) { + super(loc); + this.key = key; + this.value = value; + } + + public static ParseRes parseObject(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return name.chainError(); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.failed(); + n++; + + var value = JavaScript.parseExpression(src, i + n, 2); + if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value"); + n += value.n; + + return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n); + } + + public static ParseRes parseShorthand(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var var = VariableNode.parse(src, i + n); + if (!var.isSuccess()) return var.chainError(); + n += var.n; + + if (!src.is(i + n, "=")) return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n); + var equalsLoc = src.loc(i + n); + n++; + + var value = JavaScript.parseExpression(src, i + n, 2); + if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer"); + n += value.n; + + return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), new AssignNode(equalsLoc, var.result, value.result)), n); + } + + public static ParseRes parseClass(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return name.chainError(); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "=")) { + var end = JavaScript.parseStatement(src, i + n); + if (!end.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an end of statement or a field initializer"); + n += end.n; + + return ParseRes.res(new FieldMemberNode(loc, name.result, null), n); + } + n++; + + var value = JavaScript.parseExpression(src, i + n, 2); + if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value"); + n += value.n; + + return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n); } } - public final Map map; - public final Map getters; - public final Map setters; + public final List members; + + // private void compileRestObjBuilder(CompileResult target, int srcDupN) { + // var subtarget = target.subtarget(); + // var src = subtarget.scope.defineTemp(); + // var dst = subtarget.scope.defineTemp(); + + // target.add(Instruction.loadObj()); + // target.add(_i -> src.index().toSet(true)); + // target.add(_i -> dst.index().toSet(destructors.size() > 0)); + + // target.add(Instruction.keys(true, true)); + // var start = target.size(); + + // target.add(Instruction.dup()); + // var mid = target.temp(); + + // target.add(_i -> src.index().toGet()); + // target.add(Instruction.dup(1, 1)); + // target.add(Instruction.loadMember()); + + // target.add(_i -> dst.index().toGet()); + // target.add(Instruction.dup(1, 1)); + // target.add(Instruction.storeMember()); + + // target.add(Instruction.discard()); + // var end = target.size(); + // target.add(Instruction.jmp(start - end)); + // target.set(mid, Instruction.jmpIfNot(end - mid + 1)); + + // target.add(Instruction.discard()); + + // target.add(Instruction.dup(srcDupN, 1)); + + // target.scope.end(); + // } + + // @Override public void destruct(CompileResult target, DeclarationType decl) { + // if (getters.size() > 0) throw new SyntaxException(getters.values().iterator().next().loc(), "Unexpected getter in destructor"); + // if (setters.size() > 0) throw new SyntaxException(setters.values().iterator().next().loc(), "Unexpected setter in destructor"); + // } @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadObj()); - for (var el : map.entrySet()) { + for (var el : members) { target.add(Instruction.dup()); - var val = el.getValue(); - FunctionNode.compileWithName(val, target, true, el.getKey().toString()); - target.add(Instruction.storeMember(el.getKey())); + el.compile(target, false); } - - var keys = new ArrayList(); - keys.addAll(getters.keySet()); - keys.addAll(setters.keySet()); - - for (var key : keys) { - target.add(Instruction.pushValue((String)key)); - - if (getters.containsKey(key)) getters.get(key).compile(target, true); - else target.add(Instruction.pushUndefined()); - - if (setters.containsKey(key)) setters.get(key).compile(target, true); - else target.add(Instruction.pushUndefined()); - - target.add(Instruction.defProp()); - } - - if (!pollute) target.add(Instruction.discard()); } - public ObjectNode(Location loc, Map map, Map getters, Map setters) { + @Override public AssignTarget toAssignTarget() { + var newMembers = new LinkedList>(); + + for (var el : members) { + if (el instanceof FieldMemberNode field) { + if (field.value instanceof AssignTargetLike target) newMembers.add(new Member<>(field.key, target.toAssignTarget())); + else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor"); + } + else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor"); + } + + return new ObjectAssignable(loc(), newMembers); + } + + public ObjectNode(Location loc, List map) { super(loc); - this.map = map; - this.getters = getters; - this.setters = setters; + this.members = map; } - private static ParseRes parsePropName(Source src, int i) { + private static ParseRes parseComputePropName(Source src, int i) { var n = Parsing.skipEmpty(src, i); + if (!src.is(i + n, "[")) return ParseRes.failed(); + n++; - var res = ParseRes.first(src, i + n, - Parsing::parseIdentifier, - Parsing::parseString, - (s, j) -> Parsing.parseNumber(s, j, false) + var val = JavaScript.parseExpression(src, i, 0); + if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected an expression in compute property"); + n += val.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket after compute property"); + n++; + + return ParseRes.res(val.result, n); + } + public static ParseRes parsePropName(Source src, int i) { + return ParseRes.first(src, i, + (s, j) -> { + var m = Parsing.skipEmpty(s, j); + var l = s.loc(j + m); + + var r = Parsing.parseIdentifier(s, j + m); + if (r.isSuccess()) return ParseRes.res(new StringNode(l, r.result), r.n); + else return r.chainError(); + }, + StringNode::parse, + NumberNode::parse, + ObjectNode::parseComputePropName ); - n += res.n; - - if (!res.isSuccess()) return res.chainError(); - return ParseRes.res(res.result.toString(), n); - } - private static ParseRes parseObjectProp(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var access = Parsing.parseIdentifier(src, i + n); - if (!access.isSuccess()) return ParseRes.failed(); - if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed(); - n += access.n; - - var name = parsePropName(src, i + n); - if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); - n += name.n; - - var params = JavaScript.parseParameters(src, i + n); - if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); - n += params.n; - - var body = CompoundNode.parse(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor."); - n += body.n; - - var end = src.loc(i + n - 1); - - return ParseRes.res(new ObjProp( - name.result, access.result, - new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString()) - ), n); } public static ParseRes parse(Source src, int i) { @@ -121,39 +324,23 @@ public class ObjectNode extends Node implements Destructor { n++; n += Parsing.skipEmpty(src, i + n); - var values = new LinkedHashMap(); - var getters = new LinkedHashMap(); - var setters = new LinkedHashMap(); + var members = new LinkedList(); if (src.is(i + n, "}")) { n++; - return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); + return ParseRes.res(new ObjectNode(loc, members), n); } while (true) { - var prop = parseObjectProp(src, i + n); + ParseRes prop = ParseRes.first(src, i + n, + MethodMemberNode::parse, + PropertyMemberNode::parse, + FieldMemberNode::parseObject + ); + if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal"); + n += prop.n; - if (prop.isSuccess()) { - n += prop.n; - - if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func); - else getters.put(prop.result.name, prop.result.func); - } - else { - var name = parsePropName(src, i + n); - if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name"); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon"); - n++; - - var valRes = JavaScript.parseExpression(src, i + n, 2); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after property key"); - n += valRes.n; - - values.put(name.result, valRes.result); - } + members.add(prop.result); n += Parsing.skipEmpty(src, i + n); if (src.is(i + n, ",")) { @@ -174,7 +361,6 @@ public class ObjectNode extends Node implements Destructor { else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); } - return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); + return ParseRes.res(new ObjectNode(loc, members), n); } - } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index c990238..9c2d3d7 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -1,53 +1,73 @@ package me.topchetoeu.jscript.compilation.values; import java.util.function.IntFunction; -import java.util.function.Supplier; import me.topchetoeu.jscript.common.Instruction; 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.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; +import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; +import me.topchetoeu.jscript.compilation.patterns.Pattern; import me.topchetoeu.jscript.compilation.scope.Variable; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class VariableNode extends Node implements AssignTarget { +public class VariableNode extends Node implements Pattern, ChangeTarget { public final String name; - @Override public String assignName() { return name; } + public String assignName() { return name; } - @Override public void compileBeforeAssign(CompileResult target, boolean operator) { - if (operator) { - target.add(VariableNode.toGet(target, loc(), name)); - } - } - @Override public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute) { - target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + // @Override public void compileBeforeAssign(CompileResult target, boolean operator) { + // if (operator) { + // target.add(VariableNode.toGet(target, loc(), name)); + // } + // } + // @Override public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute) { + // target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + // } + + @Override public void beforeChange(CompileResult target) { + target.add(VariableNode.toGet(target, loc(), name)); } - @Override public void destructArg(CompileResult target) { - target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false)); - } + // @Override public void destructArg(CompileResult target) { + // target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false)); + // } @Override public void destructDeclResolve(CompileResult target) { target.scope.define(new Variable(name, false), loc()); } - @Override public void afterAssign(CompileResult target, DeclarationType decl) { - if (decl.strict) { - var v = target.scope.defineStrict(new Variable(name, decl.readonly), loc()); - target.add(_i -> v.index().toSet(false)); + + @Override public void afterAssign(CompileResult target, boolean pollute) { + target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + } + + @Override public void declare(CompileResult target, DeclarationType decl) { + if (decl != null) { + if (decl.strict) target.scope.defineStrict(new Variable(name, decl.readonly), loc()); + else target.scope.define(new Variable(name, decl.readonly), loc()); + } + else target.add(_i -> { + var i = target.scope.get(name, false); + + if (i == null) return Instruction.globDef(name); + else return Instruction.nop(); + }); + } + + @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { + if (!shouldDeclare || decl == null) { + target.add(VariableNode.toSet(target, loc(), name, false, shouldDeclare)); } else { - target.add(VariableNode.toSet(target, loc(), name, false, true)); + if (decl == DeclarationType.VAR && target.scope.has(name, false)) throw new SyntaxException(loc(), "Duplicate parameter name not allowed"); + var v = target.scope.define(decl, name, loc()); + target.add(_i -> v.index().toSet(false)); } } - @Override public void destructAssign(CompileResult target, boolean pollute) { - target.add(VariableNode.toSet(target, loc(), name, pollute, false)); - } @Override public void compile(CompileResult target, boolean pollute) { var i = target.scope.get(name, false); @@ -55,7 +75,7 @@ public class VariableNode extends Node implements AssignTarget { if (i == null) { target.add(_i -> { if (target.scope.has(name, false)) return Instruction.throwSyntax(loc(), String.format("Cannot access '%s' before initialization", name)); - return Instruction.globGet(name); + return Instruction.globGet(name, false); }); if (!pollute) target.add(Instruction.discard()); @@ -63,18 +83,18 @@ public class VariableNode extends Node implements AssignTarget { else if (pollute) target.add(_i -> i.index().toGet()); } - public static IntFunction toGet(CompileResult target, Location loc, String name, Supplier onGlobal) { + public static IntFunction toGet(CompileResult target, Location loc, String name, boolean forceGet) { var i = target.scope.get(name, false); if (i == null) return _i -> { if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name)); - else return onGlobal.get(); + else return Instruction.globGet(name, forceGet); }; else return _i -> i.index().toGet(); } public static IntFunction toGet(CompileResult target, Location loc, String name) { - return toGet(target, loc, name, () -> Instruction.globGet(name)); - } + return toGet(target, loc, name, false); + } public static IntFunction toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java index e33bdd2..bea0b41 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java @@ -5,45 +5,26 @@ import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.destructing.AssignTarget; -import me.topchetoeu.jscript.compilation.destructing.Destructor; -import me.topchetoeu.jscript.compilation.scope.Variable; -import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.patterns.AssignTarget; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class AssignNode extends Node implements Destructor { +public class AssignNode extends Node implements AssignTarget { public final AssignTarget assignable; public final Node value; - @Override public void destructDeclResolve(CompileResult target) { - if (!(assignable instanceof VariableNode var)) { - throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); - } + @Override public void compile(CompileResult target, boolean pollute) { + if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here"); - target.scope.define(new Variable(var.name, false), var.loc()); + assignable.beforeAssign(target); + value.compile(target, true); + assignable.afterAssign(target, pollute); } - @Override public void destructArg(CompileResult target) { - if (!(assignable instanceof VariableNode var)) { - throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); - } + @Override public void afterAssign(CompileResult target, boolean pollute) { + if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Double assign deconstructor not allowed"); - var v = target.scope.define(new Variable(var.name, false), var.loc()); - afterAssign(target, null, false); - target.add(_i -> v.index().toSet(false)); - } - @Override public void afterAssign(CompileResult target, DeclarationType decl, boolean pollute) { - if (decl != null && decl.strict) { - if (!(assignable instanceof VariableNode var)) { - throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); - } - target.scope.define(new Variable(var.name, decl.strict), var.loc()); - } - - assignable.beforeAssign(target, decl); - - target.add(Instruction.dup()); + if (pollute) target.add(Instruction.dup(2, 0)); + else target.add(Instruction.dup()); target.add(Instruction.pushUndefined()); target.add(Instruction.operation(Operation.EQUALS)); var start = target.temp(); @@ -51,15 +32,10 @@ public class AssignNode extends Node implements Destructor { value.compile(target, true); - target.set(start, Instruction.jmp(target.size() - start)); + target.set(start, Instruction.jmpIfNot(target.size() - start)); - assignable.afterAssign(target, decl, pollute); - } - - @Override public void compile(CompileResult target, boolean pollute) { - assignable.beforeAssign(target, null); - value.compile(target, true); - assignable.afterAssign(target, null, pollute); + assignable.assign(target, false); + if (!pollute) target.add(Instruction.discard()); } public AssignNode(Location loc, AssignTarget assignable, Node value) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java index 74f11c0..5aa242b 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java @@ -38,7 +38,7 @@ public class CallNode extends Node { shouldParen = true; - if (obj.getters.size() > 0 || obj.setters.size() > 0 || obj.map.size() > 0) res = "{}"; + if (obj.members.size() > 0) res = "{}"; else res = "{(intermediate value)}"; } else if (func instanceof StringNode) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java index bf68d55..e5c316c 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java @@ -9,24 +9,24 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.destructing.ChangeTarget; +import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; public class ChangeNode extends Node { - public final ChangeTarget assignable; + public final ChangeTarget changable; public final Node value; public final Operation op; @Override public void compile(CompileResult target, boolean pollute) { - assignable.beforeChange(target); + changable.beforeChange(target); value.compile(target, true); target.add(Instruction.operation(op)); - assignable.afterChange(target, pollute); + changable.afterAssign(target, pollute); } - public ChangeNode(Location loc, ChangeTarget assignable, Node value, Operation op) { + public ChangeNode(Location loc, ChangeTarget changable, Node value, Operation op) { super(loc); - this.assignable = assignable; + this.changable = changable; this.value = value; this.op = op; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java index 0f049bd..6299813 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java @@ -6,39 +6,21 @@ 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.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.destructing.AssignTarget; -import me.topchetoeu.jscript.compilation.destructing.ChangeTarget; +import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; import me.topchetoeu.jscript.compilation.values.constants.StringNode; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class IndexNode extends Node implements ChangeTarget { public final Node object; public final Node index; - @Override public void destructDeclResolve(CompileResult target) { - throw new SyntaxException(loc(), "Unexpected index in destructor"); - } - @Override public void destructArg(CompileResult target) { - throw new SyntaxException(loc(), "Unexpected index in destructor"); - } - - @Override public void beforeAssign(CompileResult target, DeclarationType decl) { - if (decl != null) throw new SyntaxException(loc(), "Unexpected index in destructor"); + @Override public void beforeAssign(CompileResult target) { object.compile(target, true); - if (index instanceof NumberNode num && (int)num.value == num.value) return; - if (index instanceof StringNode) return; - index.compile(target, true); - - target.add(Instruction.dup(1, 1)); - target.add(Instruction.dup(1, 1)); - target.add(Instruction.loadMember()); + indexStorePushKey(target, index); } @Override public void beforeChange(CompileResult target) { object.compile(target, true); @@ -59,26 +41,16 @@ public class IndexNode extends Node implements ChangeTarget { target.add(Instruction.loadMember()); } } - @Override public void afterAssign(CompileResult target, boolean op, boolean pollute) { - if (index instanceof NumberNode num && (int)num.value == num.value) { - target.add(Instruction.storeMember((int)num.value, pollute)); - } - else if (index instanceof StringNode str) { - target.add(Instruction.storeMember(str.value, pollute)); - } - else { - target.add(Instruction.storeMember(pollute)); - } - } - @Override public void afterAssign(CompileResult target, DeclarationType decl) { - throw new SyntaxException(loc(), "Illegal index in declaration destruction context"); - } - @Override public void destructAssign(CompileResult target, boolean pollute) { + @Override public void assign(CompileResult target, boolean pollute) { object.compile(target, true); target.add(Instruction.dup(1, 1)); - compileAfterAssign(target, false, false); - if (!pollute) target.add(Instruction.discard()); + indexStorePushKey(target, index); + indexStore(target, index, pollute); + } + + @Override public void afterAssign(CompileResult target, boolean pollute) { + indexStore(target, index, pollute); } // @Override public Node toAssign(Node val, Operation operation) { @@ -147,4 +119,35 @@ public class IndexNode extends Node implements ChangeTarget { return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n); } + + public static void indexStorePushKey(CompileResult target, Node index) { + if (index instanceof NumberNode num && (int)num.value == num.value) return; + if (index instanceof StringNode) return; + index.compile(target, true); + } + public static void indexStore(CompileResult target, Node index, boolean pollute) { + if (index instanceof NumberNode num && (int)num.value == num.value) { + target.add(Instruction.storeMember((int)num.value, pollute)); + } + else if (index instanceof StringNode str) { + target.add(Instruction.storeMember(str.value, pollute)); + } + else { + target.add(Instruction.storeMember(pollute)); + } + } + public static void indexLoad(CompileResult target, Node index, boolean pollute) { + if (index instanceof NumberNode num && (int)num.value == num.value) { + target.add(Instruction.loadMember((int)num.value)); + } + else if (index instanceof StringNode str) { + target.add(Instruction.loadMember(str.value)); + } + else { + index.compile(target, true); + target.add(Instruction.loadMember()); + } + + if (!pollute) target.add(Instruction.discard()); + } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java index dcb2335..fa3f77e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java @@ -11,10 +11,11 @@ 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.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike; +import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; public class OperationNode extends Node { private static interface OperatorFactory { @@ -54,13 +55,22 @@ public class OperationNode extends Node { @Override public ParseRes construct(Source src, int i, Node prev) { var loc = src.loc(i); - if (!(prev instanceof AssignTarget)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); + if (operation == null) { + if (!(prev instanceof AssignTargetLike target)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); - var other = JavaScript.parseExpression(src, i, precedence); - if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); + var other = JavaScript.parseExpression(src, i, precedence); + if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); - if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignTarget)prev), other.result), other.n); - else return ParseRes.res(new ChangeNode(loc, ((AssignTarget)prev), other.result, operation), other.n); + return ParseRes.res(new AssignNode(loc, target.toAssignTarget(), other.result), other.n); + } + else { + if (!(prev instanceof ChangeTarget target)) return ParseRes.error(loc, String.format("Expected a changeable expression before '%s'", token)); + + var other = JavaScript.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(new ChangeNode(loc, target, other.result, operation), other.n); + } } public AssignmentOperatorFactory(String token, int precedence, Operation operation) { @@ -209,7 +219,7 @@ public class OperationNode extends Node { var factory = factories.get(token); if (!src.is(i + n, token)) continue; - if (factory.precedence() < precedence) ParseRes.failed(); + if (factory.precedence() < precedence) return ParseRes.failed(); n += token.length(); n += Parsing.skipEmpty(src, i + n); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java index cd3f133..14b5f77 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java @@ -6,9 +6,9 @@ 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.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; public class PostfixNode extends ChangeNode { @@ -21,7 +21,7 @@ public class PostfixNode extends ChangeNode { } } - public PostfixNode(Location loc, AssignTarget value, double addAmount) { + public PostfixNode(Location loc, ChangeTarget value, double addAmount) { super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT); } @@ -32,10 +32,10 @@ public class PostfixNode extends ChangeNode { var loc = src.loc(i + n); if (!src.is(i + n, "++")) return ParseRes.failed(); - if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); n += 2; - return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, 1), n); + return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, 1), n); } public static ParseRes parsePostfixDecrease(Source src, int i, Node prev, int precedence) { if (precedence > 15) return ParseRes.failed(); @@ -44,9 +44,9 @@ public class PostfixNode extends ChangeNode { var loc = src.loc(i + n); if (!src.is(i + n, "--")) return ParseRes.failed(); - if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); n += 2; - return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, -1), n); + return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, -1), n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java index 4d3b07d..19b3798 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java @@ -16,15 +16,15 @@ public class TypeofNode extends Node { @Override public void compile(CompileResult target, boolean pollute) { if (value instanceof VariableNode varNode) { - target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name))); - if (!pollute) target.add(Instruction.discard()); + target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true)); + if (pollute) target.add(Instruction.typeof()); + else target.add(Instruction.discard()); return; } value.compile(target, pollute); - target.add(Instruction.typeof()); - if (!pollute) target.add(Instruction.discard()); + if (pollute) target.add(Instruction.typeof()); } public TypeofNode(Location loc, Node value) { diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index 550cfa5..ea223fc 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -233,6 +233,37 @@ const Object = function(value) { defineField(Object, "prototype", false, false, false, setPrototype({}, null)); +defineField(Object, "defineProperty", true, false, true, (obj, key, desc) => { + if (typeof obj !== "object" || obj === null) { + print(obj); + print(typeof obj); + throw new TypeError("Object.defineProperty called on non-object"); + } + if (typeof desc !== "object" || desc === null) throw new TypeError("Property description must be an object: " + desc); + + if ("get" in desc || "set" in desc) { + let get = desc.get, set = desc.set; + + print(typeof get); + + if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get); + if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set); + + if ("value" in desc || "writable" in desc) { + throw new TypeError("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"); + } + + if (!defineProperty(obj, key, desc.enumerable, desc.configurable, get, set)) { + throw new TypeError("Cannot redefine property: " + key); + } + } + else if (!defineField(obj, key, desc.writable, desc.enumerable, desc.configurable, desc.value)) { + throw new TypeError("Cannot redefine property: " + key); + } + + return obj; +}); + defineField(Object.prototype, "toString", true, false, true, function() { if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]"; else if (typeof this === "number" || this instanceof Number) return "[object Number]"; @@ -246,7 +277,7 @@ defineField(Object.prototype, "valueOf", true, false, true, function() { return this; }); -target.Boolean = Boolean; +target.Object = Object; const Function = function() { const parts = ["return function annonymous("]; -- 2.45.2 From 4e8b110fc4f2d5f4b612bb573aeb532b8d30d889 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:33:09 +0300 Subject: [PATCH 14/15] feat: add assign shorthands --- .../compilation/values/ObjectNode.java | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java index b959352..3fc9d3b 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -136,7 +136,7 @@ public class ObjectNode extends Node implements AssignTargetLike { public final Node key; public final Node value; - @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { + @Override public void compile(CompileResult target, boolean pollute) { key.compile(target, true); if (value == null) target.add(Instruction.pushUndefined()); @@ -178,15 +178,7 @@ public class ObjectNode extends Node implements AssignTargetLike { if (!var.isSuccess()) return var.chainError(); n += var.n; - if (!src.is(i + n, "=")) return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n); - var equalsLoc = src.loc(i + n); - n++; - - var value = JavaScript.parseExpression(src, i + n, 2); - if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer"); - n += value.n; - - return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), new AssignNode(equalsLoc, var.result, value.result)), n); + return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n); } public static ParseRes parseClass(Source src, int i) { @@ -214,9 +206,50 @@ public class ObjectNode extends Node implements AssignTargetLike { return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n); } } + public static class AssignShorthandNode extends Node { + public final Node key; + public final AssignTarget target; + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context"); + } + + public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) { + super(loc); + this.key = key; + this.target = target; + this.value = value; + } + + public AssignTarget target() { + return new AssignNode(loc(), target, value); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var var = VariableNode.parse(src, i + n); + if (!var.isSuccess()) return var.chainError(); + n += var.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "=")) return ParseRes.failed(); + n++; + + var value = JavaScript.parseExpression(src, i + n, 2); + if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer"); + n += value.n; + + return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n); + } + } public final List members; + // TODO: Implement spreading into object + // private void compileRestObjBuilder(CompileResult target, int srcDupN) { // var subtarget = target.subtarget(); // var src = subtarget.scope.defineTemp(); @@ -274,6 +307,7 @@ public class ObjectNode extends Node implements AssignTargetLike { if (field.value instanceof AssignTargetLike target) newMembers.add(new Member<>(field.key, target.toAssignTarget())); else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor"); } + else if (el instanceof AssignShorthandNode shorthand) newMembers.add(new Member<>(shorthand.key, shorthand.target())); else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor"); } @@ -335,7 +369,9 @@ public class ObjectNode extends Node implements AssignTargetLike { ParseRes prop = ParseRes.first(src, i + n, MethodMemberNode::parse, PropertyMemberNode::parse, - FieldMemberNode::parseObject + FieldMemberNode::parseObject, + AssignShorthandNode::parse, + FieldMemberNode::parseShorthand ); if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal"); n += prop.n; -- 2.45.2 From f13bf584a56f063589e5987d0e86d291b4e71a8a Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 15:25:34 +0300 Subject: [PATCH 15/15] feat: add some missing features in the polyfills --- .../topchetoeu/jscript/runtime/Compiler.java | 4 +- .../jscript/runtime/SimpleRepl.java | 42 ++++-- .../runtime/exceptions/EngineException.java | 2 +- .../jscript/runtime/values/Value.java | 24 ++-- src/main/resources/lib/index.js | 136 ++++++++++++------ 5 files changed, 136 insertions(+), 72 deletions(-) diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java index 70edd70..fea091c 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java @@ -22,7 +22,9 @@ public interface Compiler { return body; } catch (SyntaxException e) { - throw EngineException.ofSyntax(e.loc + ": " + e.msg); + var res = EngineException.ofSyntax(e.msg); + res.add(env, e.loc.filename() + "", e.loc); + throw res; } }; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index c1b1ca9..daa0d1b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -41,7 +41,7 @@ public class SimpleRepl { try { try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); } } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } for (var arg : args) { try { @@ -58,7 +58,7 @@ public class SimpleRepl { } catch (ExecutionException e) { throw e.getCause(); } } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } } for (var i = 0; ; i++) { @@ -77,7 +77,7 @@ public class SimpleRepl { } catch (ExecutionException e) { throw e.getCause(); } } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } } } catch (IOException e) { @@ -293,18 +293,6 @@ public class SimpleRepl { var obj = (ObjectValue)args.get(1); switch (type) { - case "string": - args.env.add(Value.STRING_PROTO, obj); - break; - case "number": - args.env.add(Value.NUMBER_PROTO, obj); - break; - case "boolean": - args.env.add(Value.BOOL_PROTO, obj); - break; - case "symbol": - args.env.add(Value.SYMBOL_PROTO, obj); - break; case "object": args.env.add(Value.OBJECT_PROTO, obj); break; @@ -314,6 +302,30 @@ public class SimpleRepl { case "array": args.env.add(Value.ARRAY_PROTO, obj); break; + case "boolean": + args.env.add(Value.BOOL_PROTO, obj); + break; + case "number": + args.env.add(Value.NUMBER_PROTO, obj); + break; + case "string": + args.env.add(Value.STRING_PROTO, obj); + break; + case "symbol": + args.env.add(Value.SYMBOL_PROTO, obj); + break; + case "error": + args.env.add(Value.ERROR_PROTO, obj); + break; + case "syntax": + args.env.add(Value.SYNTAX_ERR_PROTO, obj); + break; + case "type": + args.env.add(Value.TYPE_ERR_PROTO, obj); + break; + case "range": + args.env.add(Value.RANGE_ERR_PROTO, obj); + break; } return Value.UNDEFINED; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index 60657c0..20aed1a 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -66,7 +66,7 @@ public class EngineException extends RuntimeException { public String toString(Environment env) { var ss = new StringBuilder(); try { - ss.append(value.toString(env)).append('\n'); + ss.append(value.toString(env).value).append('\n'); } catch (EngineException e) { var name = value.getMember(env, "name"); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 92bc03e..9a13d6d 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -236,8 +236,8 @@ public abstract class Value { 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) { - if (member.set(env, val, obj)) { + if (member instanceof PropertyMember prop) { + if (prop.set(env, val, obj)) { if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); return true; } @@ -670,22 +670,22 @@ public abstract class Value { return a.toString(env).equals(b.toString(env)); } - // public static Value operation(Environment env, Operation op, Value ...args) { - // } - - public static final String errorToReadable(RuntimeException err, String prefix) { + public static final String errorToReadable(Environment env, RuntimeException err, String prefix) { prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; - if (err instanceof EngineException) { - var ee = ((EngineException)err); + if (err instanceof EngineException ee) { + if (env == null) env = ee.env; + try { - return prefix + " " + ee.toString(ee.env); + return prefix + " " + ee.toString(env); } catch (EngineException ex) { - return prefix + " " + ee.value.toReadable(ee.env); + return prefix + " " + ee.value.toReadable(env); } } - else if (err instanceof SyntaxException) { - return prefix + " SyntaxError " + ((SyntaxException)err).msg; + else if (err instanceof SyntaxException syntax) { + var newErr = EngineException.ofSyntax(syntax.msg); + newErr.add(null, syntax.loc.filename() + "", syntax.loc); + return errorToReadable(env, newErr, prefix); } else if (err.getCause() instanceof InterruptedException) return ""; else { diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index ea223fc..28185d9 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -1,16 +1,32 @@ const target = arguments[0]; const primordials = arguments[1]; -const makeSymbol = primordials.symbol.makeSymbol; -const getSymbol = primordials.symbol.getSymbol; -const getSymbolKey = primordials.symbol.getSymbolKey; -const getSymbolDescription = primordials.symbol.getSymbolDescription; +const symbol = primordials.symbol || (() => { + const repo = {}; -const parseInt = primordials.number.parseInt; -const parseFloat = primordials.number.parseFloat; -const isNaN = primordials.number.isNaN; -const NaN = primordials.number.NaN; -const Infinity = primordials.number.Infinity; + return { + makeSymbol: (name) => { name }, + getSymbol(name) { + if (name in repo) return repo[name]; + else return repo[name] = { name }; + }, + getSymbolKey(symbol) { + if (symbol.name in repo && repo[symbol.name] === symbol) return symbol.name; + else return undefined; + }, + getSymbolDescription: ({ name }) => name, + }; +}); + +const number = primordials.number || (() => { + return { + parseInt() { throw new Error("parseInt not supported"); }, + parseFloat() { throw new Error("parseFloat not supported"); }, + isNaN: (val) => val !== val, + NaN: 0 / 0, + Infinity: 1 / 0, + }; +}); const fromCharCode = primordials.string.fromCharCode; const fromCodePoint = primordials.string.fromCodePoint; @@ -37,7 +53,7 @@ const setGlobalPrototype = primordials.setGlobalPrototype; const compile = primordials.compile; const setIntrinsic = primordials.setIntrinsic; -const valueKey = makeSymbol("Primitive.value"); +const valueKey = symbol.makeSymbol("Primitive.value"); const undefined = ({}).definitelyDefined; target.undefined = undefined; @@ -54,13 +70,13 @@ const unwrapThis = (self, type, constr, name, arg, defaultVal) => { const wrapIndex = (i, len) => {}; -const Symbol = (name = "") => makeSymbol(name); +const Symbol = (name = "") => symbol.makeSymbol(name); defineField(Symbol, "for", true, false, true, function(name) { - return getSymbol(name + ""); + return symbol.getSymbol(name + ""); }); -defineField(Symbol, "keyFor", true, false, true, function(symbol) { - return getSymbolKey(unwrapThis(symbol, "symbol", Symbol, "Symbol.keyFor")); +defineField(Symbol, "keyFor", true, false, true, function(value) { + return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor")); }); defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); @@ -74,7 +90,7 @@ defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringT defineField(Symbol, "prototype", false, false, false, {}); defineProperty(Symbol.prototype, "description", false, true, function () { - return getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); + return symbol.getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); }, undefined); defineField(Symbol.prototype, "toString", true, false, true, function() { return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; @@ -97,7 +113,7 @@ const Number = function(value) { defineField(Number, "isFinite", true, false, true, function(value) { value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); - if (value === undefined || isNaN(value)) return false; + if (value === undefined || value !== value) return false; if (value === Infinity || value === -Infinity) return false; return true; @@ -105,34 +121,34 @@ defineField(Number, "isFinite", true, false, true, function(value) { defineField(Number, "isInteger", true, false, true, function(value) { value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); if (value === undefined) return false; - return parseInt(value) === value; + return number.parseInt(value) === value; }); defineField(Number, "isNaN", true, false, true, function(value) { - return isNaN(value); + return number.isNaN(value); }); defineField(Number, "isSafeInteger", true, false, true, function(value) { value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); - if (value === undefined || parseInt(value) !== value) return false; + if (value === undefined || number.parseInt(value) !== value) return false; return value >= -9007199254740991 && value <= 9007199254740991; }); defineField(Number, "parseFloat", true, false, true, function(value) { value = 0 + value; - return parseFloat(value); + return number.parseFloat(value); }); defineField(Number, "parseInt", true, false, true, function(value, radix) { value = 0 + value; radix = +radix; - if (isNaN(radix)) radix = 10; + if (number.isNaN(radix)) radix = 10; - return parseInt(value, radix); + return number.parseInt(value, radix); }); defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991); -defineField(Number, "POSITIVE_INFINITY", false, false, false, +Infinity); -defineField(Number, "NEGATIVE_INFINITY", false, false, false, -Infinity); -defineField(Number, "NaN", false, false, false, NaN); +defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity); +defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity); +defineField(Number, "NaN", false, false, false, number.NaN); defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); defineField(Number, "MIN_VALUE", false, false, false, 5e-324); defineField(Number, "prototype", false, false, false, {}); @@ -146,6 +162,10 @@ defineField(Number.prototype, "valueOf", true, false, true, function() { }); target.Number = Number; +target.parseInt = Number.parseInt; +target.parseFloat = Number.parseFloat; +target.NaN = Number.NaN; +target.Infinity = Number.POSITIVE_INFINITY; const String = function(value) { if (invokeType(arguments) === "call") { @@ -337,31 +357,61 @@ defineField(Function.prototype, "valueOf", true, false, true, function() { target.Function = Function; -setIntrinsic("spread_obj", target.spread_obj = (target, obj) => { - if (obj === null || obj === undefined) return; - const members = getOwnMembers(obj, true); - const symbols = getOwnSymbolMembers(obj, true); +// setIntrinsic("spread_obj", target.spread_obj = (target, obj) => { +// if (obj === null || obj === undefined) return; +// const members = getOwnMembers(obj, true); +// const symbols = getOwnSymbolMembers(obj, true); - for (let i = 0; i < members.length; i++) { - const member = members[i]; - target[member] = obj[member]; - } +// for (let i = 0; i < members.length; i++) { +// const member = members[i]; +// target[member] = obj[member]; +// } - for (let i = 0; i < symbols.length; i++) { - const member = symbols[i]; - target[member] = obj[member]; - } -}); -setIntrinsic("apply", target.spread_call = (func, self, args) => { - return invoke(func, self, args); -}); -setIntrinsic("apply", target.spread_new = (func, args) => { - return invoke(func, null, args); +// for (let i = 0; i < symbols.length; i++) { +// const member = symbols[i]; +// target[member] = obj[member]; +// } +// }); +// setIntrinsic("apply", target.spread_call = (func, self, args) => { +// return invoke(func, self, args); +// }); +// setIntrinsic("apply", target.spread_new = (func, args) => { +// return invoke(func, null, args); +// }); + +const Error = function(msg = "") { + if (invokeType(arguments) === "call") return new Error(msg); + this.message = msg + ""; +}; +defineField(Error.prototype, "name", true, false, true, "Error"); +defineField(Error.prototype, "message", true, false, true, ""); +defineField(Error.prototype, "toString", true, false, true, function toString() { + let res = this.name || "Error"; + + const msg = this.message; + if (msg) res += ": " + msg; + + return res; }); +target.Error = Error; + +const SyntaxError = function(msg = "") { + if (invokeType(arguments) === "call") return new SyntaxError(msg); + this.message = msg + ""; +}; +defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError"); + +setPrototype(SyntaxError, Error); +setPrototype(SyntaxError.prototype, Error.prototype); + +target.SyntaxError = SyntaxError; + setGlobalPrototype("string", String.prototype); setGlobalPrototype("number", Number.prototype); setGlobalPrototype("boolean", Boolean.prototype); setGlobalPrototype("symbol", Symbol.prototype); setGlobalPrototype("object", Object.prototype); setGlobalPrototype("function", Function.prototype); +setGlobalPrototype("error", Error.prototype); +setGlobalPrototype("syntax", SyntaxError.prototype); -- 2.45.2