From 00aeef5321957e5260a5901a6fa01d5d096bbc3b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 13 Dec 2024 02:26:12 +0200 Subject: [PATCH] fix: make member algorithms correct --- .../jscript/runtime/InstructionRunner.java | 19 ++- .../runtime/exceptions/EngineException.java | 4 +- .../jscript/runtime/values/Member.java | 108 +++++++------ .../jscript/runtime/values/Value.java | 64 +++++--- .../values/functions/FunctionValue.java | 11 +- .../values/objects/ArrayLikeValue.java | 20 ++- .../runtime/values/objects/ObjectValue.java | 153 +++++++++++++++--- .../runtime/values/objects/ScopeValue.java | 34 ---- .../values/primitives/PrimitiveValue.java | 15 +- .../runtime/values/primitives/UserValue.java | 15 +- 10 files changed, 288 insertions(+), 155 deletions(-) delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 7146a5c..ef38992 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -2,13 +2,12 @@ package me.topchetoeu.jscript.runtime; import java.util.ArrayList; import java.util.Collections; +import java.util.Optional; 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; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; @@ -60,8 +59,8 @@ public class InstructionRunner { else if (val instanceof FunctionValue func) accessor = func; else throw EngineException.ofType("Getter 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)); + if ((boolean)instr.get(0)) obj.defineOwnProperty(env, key, null, Optional.of(accessor), true, true); + else obj.defineOwnProperty(env, key, Optional.of(accessor), null, true, true); frame.codePtr++; return null; @@ -71,7 +70,7 @@ public class InstructionRunner { var key = frame.pop(); var obj = frame.pop(); - obj.defineOwnMember(env, key, FieldMember.of(obj, val, true, true, true)); + obj.defineOwnField(env, key, val, true, true, true); frame.codePtr++; return null; @@ -86,7 +85,7 @@ public class InstructionRunner { for (var el : members) { var obj = new ObjectValue(); - obj.defineOwnMember(env, "value", StringValue.of(el)); + obj.defineOwnField(env, "value", StringValue.of(el)); frame.push(obj); } @@ -405,13 +404,13 @@ public class InstructionRunner { break; case SHIFT_LEFT: - res = Value.shiftLeft(env, stack[ptr], stack[ptr]); + res = Value.shiftLeft(env, stack[ptr - 1], stack[ptr]); break; case SHIFT_RIGHT: - res = Value.shiftRight(env, stack[ptr], stack[ptr]); + res = Value.shiftRight(env, stack[ptr - 1], stack[ptr]); break; case USHIFT_RIGHT: - res = Value.unsignedShiftRight(env, stack[ptr], stack[ptr]); + res = Value.unsignedShiftRight(env, stack[ptr - 1], stack[ptr]); break; case IN: @@ -433,7 +432,7 @@ public class InstructionRunner { var name = (String)instr.get(0); if (!Value.global(env).hasMember(env, name, false)) { - if (!Value.global(env).defineOwnMember(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name); + if (!Value.global(env).defineOwnField(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name); } frame.codePtr++; 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 eace8cb..360a5a7 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -97,8 +97,8 @@ public class EngineException extends RuntimeException { if (msg == null) msg = ""; - if (name != null) res.defineOwnMember(Environment.empty(), "name", StringValue.of(name)); - res.defineOwnMember(Environment.empty(), "message", StringValue.of(msg)); + if (name != null) res.defineOwnField(Environment.empty(), "name", StringValue.of(name)); + res.defineOwnField(Environment.empty(), "message", StringValue.of(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 91efadf..edcfc31 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java @@ -1,5 +1,7 @@ package me.topchetoeu.jscript.runtime.values; +import java.util.Optional; + import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; @@ -26,25 +28,27 @@ public interface Member { @Override public boolean configurable() { return configurable && self.getState().configurable; } @Override public boolean enumerable() { return enumerable; } - @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 (configurable()) { + public boolean reconfigure( + Environment env, Value self, + Optional get, Optional set, + Boolean enumerable, Boolean configurable + ) { + if (this.configurable) { // We will overlay the getters and setters of the new member - enumerable = prop.enumerable; - configurable = prop.configurable; + if (enumerable != null) this.enumerable = enumerable; + if (configurable != null) this.configurable = configurable; - if (prop.getter != null) getter = prop.getter; - if (prop.setter != null) setter = prop.setter; + if (get != null) this.getter = get.orElse(null); + if (set != null) this.setter = set.orElse(null); 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; + if (configurable != null && this.configurable != configurable) return false; + if (enumerable != null && this.enumerable != enumerable) return false; + if (get != null && get.orElse(null) != getter) return false; + if (set != null && set.orElse(null) != setter) return false; return true; } @@ -55,14 +59,14 @@ public interface Member { // Don't touch the ordering, as it's emulating V8 - if (getter == null) res.defineOwnMember(env, "getter", Value.UNDEFINED); - else res.defineOwnMember(env, "getter", getter); + if (getter == null) res.defineOwnField(env, "getter", Value.UNDEFINED); + else res.defineOwnField(env, "getter", getter); - if (setter == null) res.defineOwnMember(env, "setter", Value.UNDEFINED); - else res.defineOwnMember(env, "setter", setter); + if (setter == null) res.defineOwnField(env, "setter", Value.UNDEFINED); + else res.defineOwnField(env, "setter", setter); - res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable)); - res.defineOwnMember(env, "configurable", BoolValue.of(configurable)); + res.defineOwnField(env, "enumerable", BoolValue.of(enumerable)); + res.defineOwnField(env, "configurable", BoolValue.of(configurable)); return res; } @@ -73,6 +77,13 @@ public interface Member { this.configurable = configurable; this.enumerable = enumerable; } + public PropertyMember(Value self, Optional getter, Optional setter, Boolean configurable, Boolean enumerable) { + this.self = self; + this.getter = getter == null ? null : getter.orElse(null); + this.setter = setter == null ? null : setter.orElse(null); + this.configurable = configurable == null ? false : configurable; + this.enumerable = enumerable == null ? false : enumerable; + } } public static abstract class FieldMember implements Member { @@ -85,8 +96,10 @@ public interface Member { value = val; return true; } - public SimpleFieldMember(Value self, Value value, boolean configurable, boolean enumerable, boolean writable) { + public SimpleFieldMember(Value self, Value value, Boolean configurable, Boolean enumerable, Boolean writable) { super(self, configurable, enumerable, writable); + if (value == null) value = Value.UNDEFINED; + this.value = value; } } @@ -100,37 +113,39 @@ public interface Member { @Override public final boolean enumerable() { return enumerable; } public final boolean writable() { return writable && self.getState().writable; } - @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; + public final boolean reconfigure( + Environment env, Value self, Value val, + Boolean writable, Boolean enumerable, Boolean configurable + ) { + if (this.configurable) { + if (writable != null) this.writable = writable; + if (enumerable != null) this.enumerable = enumerable; + if (configurable != null) this.configurable = configurable; + if (val != null) { + // 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, val, self)) writable = false; + } - 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 (configurable != null && this.configurable != configurable) return false; + if (enumerable != null && this.enumerable != enumerable) return false; - if (!writable()) { + if (this.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; + if (writable != null && writable) return false; + if (val != null && val.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 (writable != null) this.writable = writable; - if (!set(env, field.get(env, self), self)) writable = false; + if (!set(env, val, self)) writable = false; return true; } } @@ -138,14 +153,18 @@ public interface Member { @Override public ObjectValue descriptor(Environment env, Value self) { var res = new ObjectValue(); - 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)); + res.defineOwnField(env, "value", get(env, self)); + res.defineOwnField(env, "writable", BoolValue.of(writable)); + res.defineOwnField(env, "enumerable", BoolValue.of(enumerable)); + res.defineOwnField(env, "configurable", BoolValue.of(configurable)); return res; } - public FieldMember(Value self, boolean configurable, boolean enumerable, boolean writable) { + public FieldMember(Value self, Boolean configurable, Boolean enumerable, Boolean writable) { + if (writable == null) writable = false; + if (enumerable == null) enumerable = false; + if (configurable == null) configurable = false; + this.self = self; this.configurable = configurable; this.enumerable = enumerable; @@ -155,17 +174,16 @@ public interface Member { public static FieldMember of(Value self, Value value) { return new SimpleFieldMember(self, value, true, true, true); } - public static FieldMember of(Value self, Value value, boolean writable) { + public static FieldMember of(Value self, Value value, Boolean writable) { return new SimpleFieldMember(self, value, true, true, writable); } - public static FieldMember of(Value self, Value value, boolean configurable, boolean enumerable, boolean 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 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 72e4ba2..5b87a66 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -11,6 +11,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import me.topchetoeu.jscript.common.SyntaxException; @@ -18,7 +19,6 @@ import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.runtime.EventLoop; 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.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; @@ -110,7 +110,8 @@ public abstract class Value { public abstract Member getOwnMember(Environment env, KeyCache key); 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 defineOwnField(Environment env, KeyCache key, Value val, Boolean writable, Boolean enumerable, Boolean configurable); + public abstract boolean defineOwnProperty(Environment env, KeyCache key, Optional get, Optional set, Boolean enumerable, Boolean configurable); public abstract boolean deleteOwnMember(Environment env, KeyCache key); public abstract ObjectValue getPrototype(Environment env); @@ -135,33 +136,46 @@ public abstract class Value { return getOwnMember(env, new KeyCache(key)); } - public final boolean defineOwnMember(Environment env, Value key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); + public final boolean defineOwnProperty(Environment env, Value key, Optional get, Optional set, Boolean enumerable, Boolean configurable) { + return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, String key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); + public final boolean defineOwnProperty(Environment env, String key, Optional get, Optional set, Boolean enumerable, Boolean configurable) { + return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, int key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); + public final boolean defineOwnProperty(Environment env, int key, Optional get, Optional set, Boolean enumerable, Boolean configurable) { + return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, double key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); + public final boolean defineOwnProperty(Environment env, double key, Optional get, Optional set, Boolean enumerable, Boolean configurable) { + return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, KeyCache key, Value val) { - return defineOwnMember(env, key, FieldMember.of(this, val)); + public final boolean defineOwnField(Environment env, Value key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) { + return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, Value key, Value val) { - return defineOwnMember(env, new KeyCache(key), val); + public final boolean defineOwnField(Environment env, String key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) { + return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, String key, Value val) { - return defineOwnMember(env, new KeyCache(key), val); + public final boolean defineOwnField(Environment env, int key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) { + return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, int key, Value val) { - return defineOwnMember(env, new KeyCache(key), val); + public final boolean defineOwnField(Environment env, double key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) { + return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable); } - public final boolean defineOwnMember(Environment env, double key, Value val) { - return defineOwnMember(env, new KeyCache(key), val); + + public final boolean defineOwnField(Environment env, KeyCache key, Value val) { + return defineOwnField(env, key, val, true, true, true); + } + public final boolean defineOwnField(Environment env, Value key, Value val) { + return defineOwnField(env, new KeyCache(key), val); + } + public final boolean defineOwnField(Environment env, String key, Value val) { + return defineOwnField(env, new KeyCache(key), val); + } + public final boolean defineOwnField(Environment env, int key, Value val) { + return defineOwnField(env, new KeyCache(key), val); + } + public final boolean defineOwnField(Environment env, double key, Value val) { + return defineOwnField(env, new KeyCache(key), val); } public final boolean deleteOwnMember(Environment env, Value key) { @@ -221,14 +235,14 @@ public abstract class Value { var member = obj.getOwnMember(env, key); if (member != null && (member instanceof PropertyMember || obj == this)) { if (member.set(env, val, this)) { - if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); + if (val instanceof FunctionValue && !key.isSymbol()) ((FunctionValue)val).setName(key.toString(env)); return true; } else return false; } } - if (defineOwnMember(env, key, val)) { + if (defineOwnField(env, key, val)) { if (val instanceof FunctionValue func) { if (key.isSymbol()) func.setName(key.toSymbol().toString()); else func.setName(key.toString(env)); @@ -255,7 +269,7 @@ public abstract class Value { var member = obj.getOwnMember(env, key); if (member != null) { if (member.set(env, val, obj)) { - if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); + if (!key.isSymbol() && val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); return true; } else return false; @@ -434,8 +448,8 @@ public abstract class Value { return new NativeFunction("", args -> { var obj = new ObjectValue(); - if (!it.hasNext()) obj.defineOwnMember(args.env, "done", BoolValue.TRUE); - else obj.defineOwnMember(args.env, "value", it.next()); + if (!it.hasNext()) obj.defineOwnField(args.env, "done", BoolValue.TRUE); + else obj.defineOwnField(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 d080ef2..7a0aa67 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 @@ -66,15 +66,16 @@ public abstract class FunctionValue extends ObjectValue { } @Override public Member getOwnMember(Environment env, KeyCache key) { - switch (key.toString(env)) { + if (!key.isSymbol()) switch (key.toString(env)) { case "length": return lengthField; case "name": return nameField; case "prototype": return prototypeField; - default: return super.getOwnMember(env, key); } + + return super.getOwnMember(env, key); } @Override public boolean deleteOwnMember(Environment env, KeyCache key) { - switch (key.toString(env)) { + if (!key.isSymbol()) switch (key.toString(env)) { case "length": length = 0; return true; @@ -83,8 +84,8 @@ public abstract class FunctionValue extends ObjectValue { return true; case "prototype": return false; - default: return super.deleteOwnMember(env, key); } + return super.deleteOwnMember(env, key); } @Override public StringValue type() { return StringValue.of("function"); } @@ -114,7 +115,7 @@ public abstract class FunctionValue extends ObjectValue { this.length = length; this.name = name; - prototype.defineOwnMember(null, "constructor", this); + prototype.defineOwnField(null, "constructor", this); } } 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 index d8a79ef..d219647 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java @@ -65,11 +65,13 @@ public abstract class ArrayLikeValue extends ObjectValue { 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 if (!key.isSymbol() && 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); + @Override public boolean defineOwnField( + Environment env, KeyCache key, Value val, + Boolean writable, Boolean enumerable, Boolean configurable + ) { if (!getState().writable) return false; if (!key.isSymbol()) { @@ -77,12 +79,18 @@ public abstract class ArrayLikeValue extends ObjectValue { var i = key.toInt(env); if (i == num) { - if (!getState().extendable && !has(i)) return false; - if (set(env, i, ((FieldMember)member).get(env, this))) return true; + if (writable == null) writable = true; + if (configurable == null) configurable = true; + if (enumerable == null) enumerable = true; + + if (writable && configurable && enumerable) { + if (!getState().extendable && !has(i)) return false; + if (set(env, i, val)) return true; + } } } - return super.defineOwnMember(env, key, member); + return super.defineOwnField(env, key, val, writable, enumerable, configurable); } @Override public boolean deleteOwnMember(Environment env, KeyCache key) { if (!super.deleteOwnMember(env, key)) return false; 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 2e2f7e9..0895859 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,11 +1,13 @@ package me.topchetoeu.jscript.runtime.values.objects; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; @@ -14,6 +16,7 @@ import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; @@ -37,8 +40,13 @@ public class ObjectValue extends Value { protected PrototypeProvider prototype; - public LinkedHashMap members = new LinkedHashMap<>(); - public LinkedHashMap symbolMembers = new LinkedHashMap<>(); + private HashMap fields = new HashMap<>(); + private HashMap symbolFields = new HashMap<>(); + private HashMap properties = new HashMap<>(); + private HashMap symbolProperties = new HashMap<>(); + + private LinkedHashMap keys = new LinkedHashMap<>(); + private LinkedHashMap symbols = new LinkedHashMap<>(); @Override public boolean isPrimitive() { return false; } @Override public Value toPrimitive(Environment env) { @@ -78,57 +86,154 @@ public class ObjectValue extends Value { @Override public Member getOwnMember(Environment env, KeyCache key) { if (key.isSymbol()) { - if (symbolMembers.size() > 0) return symbolMembers.get(key.toSymbol()); - else return null; + if (!symbols.containsKey(key.toSymbol())) return null; + + if (symbols.get(key.toSymbol())) return symbolProperties.get(key.toSymbol()); + else return symbolFields.get(key.toSymbol()); + } + else if (keys.containsKey(key.toString(env))) { + if (keys.get(key.toString(env))) return properties.get(key.toString(env)); + else return fields.get(key.toString(env)); } - else if (members.size() > 0) return members.get(key.toString(env)); else return null; } - @Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) { - var old = getOwnMember(env, key); - if (old != null && old.redefine(env, member, this)) return true; - if (old != null && !old.configurable()) return false; + @Override public boolean defineOwnField( + Environment env, KeyCache key, Value val, + Boolean writable, Boolean enumerable, Boolean configurable + ) { + if (key.isSymbol()) { - if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member); - else members.put(key.toString(env), member); + if (symbols.containsKey(key.toSymbol())) { + if (symbols.get(key.toSymbol())) { + var prop = symbolProperties.get(key.toSymbol()); + if (!prop.configurable) return false; + symbolProperties.remove(key.toSymbol()); + } + else return symbolFields.get(key.toSymbol()).reconfigure(env, this, val, writable, enumerable, configurable); + } + + symbols.put(key.toSymbol(), false); + symbolFields.put(key.toSymbol(), FieldMember.of(this, val, writable, enumerable, configurable)); + return true; + } + else if (keys.containsKey(key.toString(env))) { + if (keys.get(key.toString(env))) { + var prop = properties.get(key.toString(env)); + if (!prop.configurable) return false; + + properties.remove(key.toString(env)); + } + else return fields.get(key.toString(env)).reconfigure(env, this, val, writable, enumerable, configurable); + } + + keys.put(key.toString(env), false); + fields.put(key.toString(env), FieldMember.of(this, val, writable, enumerable, configurable)); + return true; + } + @Override public boolean defineOwnProperty( + Environment env, KeyCache key, + Optional get, Optional set, + Boolean enumerable, Boolean configurable + ) { + if (key.isSymbol()) { + if (symbols.containsKey(key.toSymbol())) { + if (!symbols.get(key.toSymbol())) { + var field = symbolFields.get(key.toSymbol()); + if (!field.configurable) return false; + + symbolFields.remove(key.toSymbol()); + } + else return symbolProperties.get(key.toSymbol()).reconfigure(env, this, get, set, enumerable, configurable); + } + + symbols.put(key.toSymbol(), true); + symbolProperties.put(key.toSymbol(), new PropertyMember(this, get, set, enumerable, configurable)); + return true; + } + else if (keys.containsKey(key.toString(env))) { + if (!keys.get(key.toString(env))) { + var field = fields.get(key.toString(env)); + if (!field.configurable) return false; + + fields.remove(key.toString(env)); + } + else return properties.get(key.toString(env)).reconfigure(env, this, get, set, enumerable, configurable); + } + + keys.put(key.toString(env), true); + properties.put(key.toString(env), new PropertyMember(this, get, set, enumerable, configurable)); return true; } @Override public boolean deleteOwnMember(Environment env, KeyCache key) { if (!getState().extendable) return false; - var member = getOwnMember(env, key); - if (member == null) return true; - if (!member.configurable()) return false; - - if (key.isSymbol()) symbolMembers.remove(key.toSymbol()); - else members.remove(key.toString(env)); - return true; + if (key.isSymbol()) { + if (!symbols.containsKey(key.toSymbol())) return true; + + if (symbols.get(key.toSymbol())) { + if (!symbolProperties.get(key.toSymbol()).configurable) return false; + symbolProperties.remove(key.toSymbol()); + symbols.remove(key.toSymbol()); + return true; + } + else { + if (!symbolFields.get(key.toSymbol()).configurable) return false; + symbolFields.remove(key.toSymbol()); + keys.remove(key.toString(env)); + return true; + } + } + else if (keys.containsKey(key.toString(env))) { + if (keys.get(key.toString(env))) { + if (!properties.get(key.toString(env)).configurable) return false; + properties.remove(key.toString(env)); + symbols.remove(key.toSymbol()); + return true; + } + else { + if (!fields.get(key.toString(env)).configurable) return false; + fields.remove(key.toString(env)); + keys.remove(key.toString(env)); + return true; + } + } + else return true; } @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()); + for (var el : keys.entrySet()) { + if (el.getValue()) { + if (properties.get(el.getKey()).enumerable) res.add(el.getKey()); + } + else { + if (fields.get(el.getKey()).enumerable) res.add(el.getKey()); + } } return res; } - else return members.keySet(); + else return keys.keySet(); } @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()); + for (var el : symbols.entrySet()) { + if (el.getValue()) { + if (symbolProperties.get(el.getKey()).enumerable) res.add(el.getKey()); + } + else { + if (symbolFields.get(el.getKey()).enumerable) res.add(el.getKey()); + } } return res; } - else return symbolMembers.keySet(); + else return symbols.keySet(); } @Override public ObjectValue getPrototype(Environment env) { 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 deleted file mode 100644 index 2c12c8f..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.objects; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; - -public final class ScopeValue extends ObjectValue { - private static class VariableField extends FieldMember { - private int i; - - public VariableField(int i, ScopeValue self) { - super(self, false, true, true); - this.i = i; - } - - @Override public Value get(Environment env, Value self) { - return ((ScopeValue)self).variables[i][0]; - } - - @Override public boolean set(Environment env, Value val, Value self) { - ((ScopeValue)self).variables[i][0] = val; - return true; - } - } - - public final Value[][] variables; - - 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, 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 f1be82b..f9b9a4f 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,17 +1,28 @@ package me.topchetoeu.jscript.runtime.values.primitives; import java.util.HashSet; +import java.util.Optional; 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.Value; +import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; public abstract class PrimitiveValue extends Value { - @Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; } - @Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; } + @Override public boolean defineOwnField( + Environment env, KeyCache key, Value val, + Boolean writable, Boolean enumerable, Boolean configurable + ) { return false; } + @Override + public boolean defineOwnProperty( + Environment env, KeyCache key, Optional get, Optional set, + Boolean enumerable, Boolean configurable + ) { return false; } + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { return true; } + @Override public final boolean isPrimitive() { return true; } @Override public final Value toPrimitive(Environment env) { return this; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/UserValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/UserValue.java index 5c426d3..49eb6f8 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/UserValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/UserValue.java @@ -4,12 +4,14 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Optional; 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.Value; +import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; @@ -23,8 +25,17 @@ public final class UserValue extends Value { @Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; } @Override public String toString(Environment ext) { return "[user value]"; } - @Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; } - @Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; } + @Override public boolean defineOwnField( + Environment env, KeyCache key, Value val, + Boolean writable, Boolean enumerable, Boolean configurable + ) { return false; } + @Override + public boolean defineOwnProperty( + Environment env, KeyCache key, Optional get, Optional set, + Boolean enumerable, Boolean configurable + ) { return false; } + @Override public boolean deleteOwnMember(Environment env, KeyCache key) { return true; } + @Override public final boolean isPrimitive() { return false; } @Override public final Value toPrimitive(Environment env) { return NumberValue.NAN; }