fix: make member algorithms correct

This commit is contained in:
TopchetoEU 2024-12-13 02:26:12 +02:00
parent ff4aa3dcfd
commit 00aeef5321
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
10 changed files with 288 additions and 155 deletions

View File

@ -2,13 +2,12 @@ package me.topchetoeu.jscript.runtime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Optional;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; 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.Value;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
@ -60,8 +59,8 @@ public class InstructionRunner {
else if (val instanceof FunctionValue func) accessor = func; else if (val instanceof FunctionValue func) accessor = func;
else throw EngineException.ofType("Getter must be a function or undefined"); 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)); if ((boolean)instr.get(0)) obj.defineOwnProperty(env, key, null, Optional.of(accessor), true, true);
else obj.defineOwnMember(env, key, new PropertyMember(obj, accessor, null, true, true)); else obj.defineOwnProperty(env, key, Optional.of(accessor), null, true, true);
frame.codePtr++; frame.codePtr++;
return null; return null;
@ -71,7 +70,7 @@ public class InstructionRunner {
var key = frame.pop(); var key = frame.pop();
var obj = 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++; frame.codePtr++;
return null; return null;
@ -86,7 +85,7 @@ public class InstructionRunner {
for (var el : members) { for (var el : members) {
var obj = new ObjectValue(); var obj = new ObjectValue();
obj.defineOwnMember(env, "value", StringValue.of(el)); obj.defineOwnField(env, "value", StringValue.of(el));
frame.push(obj); frame.push(obj);
} }
@ -405,13 +404,13 @@ public class InstructionRunner {
break; break;
case SHIFT_LEFT: case SHIFT_LEFT:
res = Value.shiftLeft(env, stack[ptr], stack[ptr]); res = Value.shiftLeft(env, stack[ptr - 1], stack[ptr]);
break; break;
case SHIFT_RIGHT: case SHIFT_RIGHT:
res = Value.shiftRight(env, stack[ptr], stack[ptr]); res = Value.shiftRight(env, stack[ptr - 1], stack[ptr]);
break; break;
case USHIFT_RIGHT: case USHIFT_RIGHT:
res = Value.unsignedShiftRight(env, stack[ptr], stack[ptr]); res = Value.unsignedShiftRight(env, stack[ptr - 1], stack[ptr]);
break; break;
case IN: case IN:
@ -433,7 +432,7 @@ public class InstructionRunner {
var name = (String)instr.get(0); var name = (String)instr.get(0);
if (!Value.global(env).hasMember(env, name, false)) { 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++; frame.codePtr++;

View File

@ -97,8 +97,8 @@ public class EngineException extends RuntimeException {
if (msg == null) msg = ""; if (msg == null) msg = "";
if (name != null) res.defineOwnMember(Environment.empty(), "name", StringValue.of(name)); if (name != null) res.defineOwnField(Environment.empty(), "name", StringValue.of(name));
res.defineOwnMember(Environment.empty(), "message", StringValue.of(msg)); res.defineOwnField(Environment.empty(), "message", StringValue.of(msg));
return res; return res;
} }

View File

@ -1,5 +1,7 @@
package me.topchetoeu.jscript.runtime.values; package me.topchetoeu.jscript.runtime.values;
import java.util.Optional;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; 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 configurable() { return configurable && self.getState().configurable; }
@Override public boolean enumerable() { return enumerable; } @Override public boolean enumerable() { return enumerable; }
@Override public boolean redefine(Environment env, Member newMember, Value self) { public boolean reconfigure(
// If the given member isn't a property, we can't redefine Environment env, Value self,
if (!(newMember instanceof PropertyMember prop)) return false; Optional<FunctionValue> get, Optional<FunctionValue> set,
Boolean enumerable, Boolean configurable
if (configurable()) { ) {
if (this.configurable) {
// We will overlay the getters and setters of the new member // We will overlay the getters and setters of the new member
enumerable = prop.enumerable; if (enumerable != null) this.enumerable = enumerable;
configurable = prop.configurable; if (configurable != null) this.configurable = configurable;
if (prop.getter != null) getter = prop.getter; if (get != null) this.getter = get.orElse(null);
if (prop.setter != null) setter = prop.setter; if (set != null) this.setter = set.orElse(null);
return true; return true;
} }
else { else {
// We will pretend that a redefinition has occurred if the two members match exactly // We will pretend that a redefinition has occurred if the two members match exactly
if (prop.configurable() != configurable()) return false; if (configurable != null && this.configurable != configurable) return false;
if (prop.enumerable != enumerable) return false; if (enumerable != null && this.enumerable != enumerable) return false;
if (prop.getter != getter || prop.setter != setter) return false; if (get != null && get.orElse(null) != getter) return false;
if (set != null && set.orElse(null) != setter) return false;
return true; return true;
} }
@ -55,14 +59,14 @@ public interface Member {
// Don't touch the ordering, as it's emulating V8 // Don't touch the ordering, as it's emulating V8
if (getter == null) res.defineOwnMember(env, "getter", Value.UNDEFINED); if (getter == null) res.defineOwnField(env, "getter", Value.UNDEFINED);
else res.defineOwnMember(env, "getter", getter); else res.defineOwnField(env, "getter", getter);
if (setter == null) res.defineOwnMember(env, "setter", Value.UNDEFINED); if (setter == null) res.defineOwnField(env, "setter", Value.UNDEFINED);
else res.defineOwnMember(env, "setter", setter); else res.defineOwnField(env, "setter", setter);
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable)); res.defineOwnField(env, "enumerable", BoolValue.of(enumerable));
res.defineOwnMember(env, "configurable", BoolValue.of(configurable)); res.defineOwnField(env, "configurable", BoolValue.of(configurable));
return res; return res;
} }
@ -73,6 +77,13 @@ public interface Member {
this.configurable = configurable; this.configurable = configurable;
this.enumerable = enumerable; this.enumerable = enumerable;
} }
public PropertyMember(Value self, Optional<FunctionValue> getter, Optional<FunctionValue> 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 { public static abstract class FieldMember implements Member {
@ -85,8 +96,10 @@ public interface Member {
value = val; value = val;
return true; 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); super(self, configurable, enumerable, writable);
if (value == null) value = Value.UNDEFINED;
this.value = value; this.value = value;
} }
} }
@ -100,37 +113,39 @@ public interface Member {
@Override public final boolean enumerable() { return enumerable; } @Override public final boolean enumerable() { return enumerable; }
public final boolean writable() { return writable && self.getState().writable; } public final boolean writable() { return writable && self.getState().writable; }
@Override public final boolean redefine(Environment env, Member newMember, Value self) { public final boolean reconfigure(
// If the given member isn't a field, we can't redefine Environment env, Value self, Value val,
if (!(newMember instanceof FieldMember field)) return false; Boolean writable, Boolean enumerable, Boolean configurable
) {
if (configurable()) { if (this.configurable) {
configurable = field.configurable; if (writable != null) this.writable = writable;
enumerable = field.enumerable; if (enumerable != null) this.enumerable = enumerable;
writable = field.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 // 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 // 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; if (!set(env, val, self)) writable = false;
}
return true; return true;
} }
else { else {
// New field settings must be an exact match // New field settings must be an exact match
if (configurable() != field.configurable()) return false; if (configurable != null && this.configurable != configurable) return false;
if (enumerable() != field.enumerable()) 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 the field isn't writable, the redefinition should be an exact match
if (field.writable()) return false; if (writable != null && writable) return false;
if (field.get(env, self).equals(this.get(env, self))) return false; if (val != null && val.equals(this.get(env, self))) return false;
return true; return true;
} }
else { else {
// Writable non-configurable fields may be made readonly or their values may be changed // 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; return true;
} }
} }
@ -138,14 +153,18 @@ public interface Member {
@Override public ObjectValue descriptor(Environment env, Value self) { @Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue(); var res = new ObjectValue();
res.defineOwnMember(env, "value", get(env, self)); res.defineOwnField(env, "value", get(env, self));
res.defineOwnMember(env, "writable", BoolValue.of(writable)); res.defineOwnField(env, "writable", BoolValue.of(writable));
res.defineOwnMember(env, "enumerable", BoolValue.of(enumerable)); res.defineOwnField(env, "enumerable", BoolValue.of(enumerable));
res.defineOwnMember(env, "configurable", BoolValue.of(configurable)); res.defineOwnField(env, "configurable", BoolValue.of(configurable));
return res; 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.self = self;
this.configurable = configurable; this.configurable = configurable;
this.enumerable = enumerable; this.enumerable = enumerable;
@ -155,17 +174,16 @@ public interface Member {
public static FieldMember of(Value self, Value value) { public static FieldMember of(Value self, Value value) {
return new SimpleFieldMember(self, value, true, true, true); 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); 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); return new SimpleFieldMember(self, value, configurable, enumerable, writable);
} }
} }
public boolean configurable(); public boolean configurable();
public boolean enumerable(); public boolean enumerable();
public boolean redefine(Environment env, Member newMember, Value self);
public ObjectValue descriptor(Environment env, Value self); public ObjectValue descriptor(Environment env, Value self);
public Value get(Environment env, Value self); public Value get(Environment env, Value self);

View File

@ -11,6 +11,7 @@ import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import me.topchetoeu.jscript.common.SyntaxException; 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.common.environment.Key;
import me.topchetoeu.jscript.runtime.EventLoop; import me.topchetoeu.jscript.runtime.EventLoop;
import me.topchetoeu.jscript.runtime.exceptions.EngineException; 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.Member.PropertyMember;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; 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 Member getOwnMember(Environment env, KeyCache key);
public abstract Set<String> getOwnMembers(Environment env, boolean onlyEnumerable); public abstract Set<String> getOwnMembers(Environment env, boolean onlyEnumerable);
public abstract Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable); public abstract Set<SymbolValue> 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<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable);
public abstract boolean deleteOwnMember(Environment env, KeyCache key); public abstract boolean deleteOwnMember(Environment env, KeyCache key);
public abstract ObjectValue getPrototype(Environment env); public abstract ObjectValue getPrototype(Environment env);
@ -135,33 +136,46 @@ public abstract class Value {
return getOwnMember(env, new KeyCache(key)); return getOwnMember(env, new KeyCache(key));
} }
public final boolean defineOwnMember(Environment env, Value key, Member member) { public final boolean defineOwnProperty(Environment env, Value key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, new KeyCache(key), member); return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
} }
public final boolean defineOwnMember(Environment env, String key, Member member) { public final boolean defineOwnProperty(Environment env, String key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, new KeyCache(key), member); return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
} }
public final boolean defineOwnMember(Environment env, int key, Member member) { public final boolean defineOwnProperty(Environment env, int key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, new KeyCache(key), member); return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
} }
public final boolean defineOwnMember(Environment env, double key, Member member) { public final boolean defineOwnProperty(Environment env, double key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, new KeyCache(key), member); return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
} }
public final boolean defineOwnMember(Environment env, KeyCache key, Value val) { public final boolean defineOwnField(Environment env, Value key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, key, FieldMember.of(this, val)); return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable);
} }
public final boolean defineOwnMember(Environment env, Value key, Value val) { public final boolean defineOwnField(Environment env, String key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, new KeyCache(key), val); return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable);
} }
public final boolean defineOwnMember(Environment env, String key, Value val) { public final boolean defineOwnField(Environment env, int key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, new KeyCache(key), val); return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable);
} }
public final boolean defineOwnMember(Environment env, int key, Value val) { public final boolean defineOwnField(Environment env, double key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnMember(env, new KeyCache(key), val); 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) { public final boolean deleteOwnMember(Environment env, Value key) {
@ -221,14 +235,14 @@ public abstract class Value {
var member = obj.getOwnMember(env, key); var member = obj.getOwnMember(env, key);
if (member != null && (member instanceof PropertyMember || obj == this)) { if (member != null && (member instanceof PropertyMember || obj == this)) {
if (member.set(env, val, 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; return true;
} }
else return false; else return false;
} }
} }
if (defineOwnMember(env, key, val)) { if (defineOwnField(env, key, val)) {
if (val instanceof FunctionValue func) { if (val instanceof FunctionValue func) {
if (key.isSymbol()) func.setName(key.toSymbol().toString()); if (key.isSymbol()) func.setName(key.toSymbol().toString());
else func.setName(key.toString(env)); else func.setName(key.toString(env));
@ -255,7 +269,7 @@ public abstract class Value {
var member = obj.getOwnMember(env, key); var member = obj.getOwnMember(env, key);
if (member != null) { if (member != null) {
if (member.set(env, val, obj)) { 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; return true;
} }
else return false; else return false;
@ -434,8 +448,8 @@ public abstract class Value {
return new NativeFunction("", args -> { return new NativeFunction("", args -> {
var obj = new ObjectValue(); var obj = new ObjectValue();
if (!it.hasNext()) obj.defineOwnMember(args.env, "done", BoolValue.TRUE); if (!it.hasNext()) obj.defineOwnField(args.env, "done", BoolValue.TRUE);
else obj.defineOwnMember(args.env, "value", it.next()); else obj.defineOwnField(args.env, "value", it.next());
return obj; return obj;
}); });

View File

@ -66,15 +66,16 @@ public abstract class FunctionValue extends ObjectValue {
} }
@Override public Member getOwnMember(Environment env, KeyCache key) { @Override public Member getOwnMember(Environment env, KeyCache key) {
switch (key.toString(env)) { if (!key.isSymbol()) switch (key.toString(env)) {
case "length": return lengthField; case "length": return lengthField;
case "name": return nameField; case "name": return nameField;
case "prototype": return prototypeField; case "prototype": return prototypeField;
default: return super.getOwnMember(env, key);
} }
return super.getOwnMember(env, key);
} }
@Override public boolean deleteOwnMember(Environment env, KeyCache key) { @Override public boolean deleteOwnMember(Environment env, KeyCache key) {
switch (key.toString(env)) { if (!key.isSymbol()) switch (key.toString(env)) {
case "length": case "length":
length = 0; length = 0;
return true; return true;
@ -83,8 +84,8 @@ public abstract class FunctionValue extends ObjectValue {
return true; return true;
case "prototype": case "prototype":
return false; return false;
default: return super.deleteOwnMember(env, key);
} }
return super.deleteOwnMember(env, key);
} }
@Override public StringValue type() { return StringValue.of("function"); } @Override public StringValue type() { return StringValue.of("function"); }
@ -114,7 +115,7 @@ public abstract class FunctionValue extends ObjectValue {
this.length = length; this.length = length;
this.name = name; this.name = name;
prototype.defineOwnMember(null, "constructor", this); prototype.defineOwnField(null, "constructor", this);
} }
} }

View File

@ -65,11 +65,13 @@ public abstract class ArrayLikeValue extends ObjectValue {
var i = key.toInt(env); var i = key.toInt(env);
if (i == num && i >= 0 && i < size() && has(i)) return new IndexField(i, this); if (i == num && i >= 0 && i < size() && has(i)) return new IndexField(i, this);
else if (key.toString(env).equals("length")) return lengthField; else if (!key.isSymbol() && key.toString(env).equals("length")) return lengthField;
else return null; else return null;
} }
@Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) { @Override public boolean defineOwnField(
if (!(member instanceof FieldMember) || super.getOwnMember(env, key) != null) return super.defineOwnMember(env, key, member); Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) {
if (!getState().writable) return false; if (!getState().writable) return false;
if (!key.isSymbol()) { if (!key.isSymbol()) {
@ -77,12 +79,18 @@ public abstract class ArrayLikeValue extends ObjectValue {
var i = key.toInt(env); var i = key.toInt(env);
if (i == num) { if (i == num) {
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 (!getState().extendable && !has(i)) return false;
if (set(env, i, ((FieldMember)member).get(env, this))) return true; 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) { @Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!super.deleteOwnMember(env, key)) return false; if (!super.deleteOwnMember(env, key)) return false;

View File

@ -1,11 +1,13 @@
package me.topchetoeu.jscript.runtime.values.objects; package me.topchetoeu.jscript.runtime.values.objects;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import me.topchetoeu.jscript.common.environment.Environment; 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.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value; 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.Member.PropertyMember;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
@ -37,8 +40,13 @@ public class ObjectValue extends Value {
protected PrototypeProvider prototype; protected PrototypeProvider prototype;
public LinkedHashMap<String, Member> members = new LinkedHashMap<>(); private HashMap<String, FieldMember> fields = new HashMap<>();
public LinkedHashMap<SymbolValue, Member> symbolMembers = new LinkedHashMap<>(); private HashMap<SymbolValue, FieldMember> symbolFields = new HashMap<>();
private HashMap<String, PropertyMember> properties = new HashMap<>();
private HashMap<SymbolValue, PropertyMember> symbolProperties = new HashMap<>();
private LinkedHashMap<String, Boolean> keys = new LinkedHashMap<>();
private LinkedHashMap<SymbolValue, Boolean> symbols = new LinkedHashMap<>();
@Override public boolean isPrimitive() { return false; } @Override public boolean isPrimitive() { return false; }
@Override public Value toPrimitive(Environment env) { @Override public Value toPrimitive(Environment env) {
@ -78,57 +86,154 @@ public class ObjectValue extends Value {
@Override public Member getOwnMember(Environment env, KeyCache key) { @Override public Member getOwnMember(Environment env, KeyCache key) {
if (key.isSymbol()) { if (key.isSymbol()) {
if (symbolMembers.size() > 0) return symbolMembers.get(key.toSymbol()); 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 return null; else return null;
} }
else if (members.size() > 0) return members.get(key.toString(env)); @Override public boolean defineOwnField(
else return null; Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) {
if (key.isSymbol()) {
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);
} }
@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;
if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member); symbols.put(key.toSymbol(), false);
else members.put(key.toString(env), member); 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<FunctionValue> get, Optional<FunctionValue> 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; return true;
} }
@Override public boolean deleteOwnMember(Environment env, KeyCache key) { @Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!getState().extendable) return false; if (!getState().extendable) return false;
var member = getOwnMember(env, key); if (key.isSymbol()) {
if (member == null) return true; if (!symbols.containsKey(key.toSymbol())) return true;
if (!member.configurable()) return false;
if (key.isSymbol()) symbolMembers.remove(key.toSymbol()); if (symbols.get(key.toSymbol())) {
else members.remove(key.toString(env)); if (!symbolProperties.get(key.toSymbol()).configurable) return false;
symbolProperties.remove(key.toSymbol());
symbols.remove(key.toSymbol());
return true; 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<String> getOwnMembers(Environment env, boolean onlyEnumerable) { @Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
if (onlyEnumerable) { if (onlyEnumerable) {
var res = new LinkedHashSet<String>(); var res = new LinkedHashSet<String>();
for (var el : members.entrySet()) { for (var el : keys.entrySet()) {
if (el.getValue().enumerable()) res.add(el.getKey()); 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; return res;
} }
else return members.keySet(); else return keys.keySet();
} }
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { @Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
if (onlyEnumerable) { if (onlyEnumerable) {
var res = new LinkedHashSet<SymbolValue>(); var res = new LinkedHashSet<SymbolValue>();
for (var el : symbolMembers.entrySet()) { for (var el : symbols.entrySet()) {
if (el.getValue().enumerable()) res.add(el.getKey()); 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; return res;
} }
else return symbolMembers.keySet(); else return symbols.keySet();
} }
@Override public ObjectValue getPrototype(Environment env) { @Override public ObjectValue getPrototype(Environment env) {

View File

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

View File

@ -1,17 +1,28 @@
package me.topchetoeu.jscript.runtime.values.primitives; package me.topchetoeu.jscript.runtime.values.primitives;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value; 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.objects.ObjectValue;
public abstract class PrimitiveValue extends Value { public abstract class PrimitiveValue extends Value {
@Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; } @Override public boolean defineOwnField(
@Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; } Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) { return false; }
@Override
public boolean defineOwnProperty(
Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> 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 boolean isPrimitive() { return true; }
@Override public final Value toPrimitive(Environment env) { return this; } @Override public final Value toPrimitive(Environment env) { return this; }

View File

@ -4,12 +4,14 @@ import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.KeyCache;
import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Member;
import me.topchetoeu.jscript.runtime.values.Value; 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.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue;
@ -23,8 +25,17 @@ public final class UserValue<T> extends Value {
@Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; } @Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; }
@Override public String toString(Environment ext) { return "[user value]"; } @Override public String toString(Environment ext) { return "[user value]"; }
@Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; } @Override public boolean defineOwnField(
@Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; } Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) { return false; }
@Override
public boolean defineOwnProperty(
Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> 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 boolean isPrimitive() { return false; }
@Override public final Value toPrimitive(Environment env) { return NumberValue.NAN; } @Override public final Value toPrimitive(Environment env) { return NumberValue.NAN; }