clean up member logic

This commit is contained in:
TopchetoEU 2024-09-14 13:53:58 +03:00
parent 3e6816cb2c
commit 1f42263051
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
12 changed files with 201 additions and 132 deletions

View File

@ -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<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
var res = new LinkedHashSet<String>();
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<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
// var res = new LinkedHashSet<String>();
// res.addAll(super.getOwnMembers(env, onlyEnumerable));
// for (var i = 0; i < stackPtr; i++) res.add(i + "");
// return res;
// }
};
}

View File

@ -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++;

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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 (configurable()) {
// We will overlay the getters and setters of the new member
enumerable = prop.enumerable;
configurable = prop.configurable;
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;
if (prop.getter == getter) return true;
if (prop.setter == setter) return true;
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;
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);

View File

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

View File

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

View File

@ -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<String, Member> members = new LinkedHashMap<>();
public LinkedHashMap<SymbolValue, Member> 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<ObjectValue> key) {
if (!extensible) return false;
if (!getState().extendable) return false;
prototype = env -> env.get(key);
return true;
}

View File

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

View File

@ -19,4 +19,10 @@ public abstract class PrimitiveValue extends Value {
@Override public Member getOwnMember(Environment env, KeyCache key) { return null; }
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) { return Set.of(); }
@Override public Set<SymbolValue> 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() {}
}

View File

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