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] 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); }