diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 4e440e4..2c2a46d 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -22,6 +22,8 @@ import me.topchetoeu.jscript.lib.EnvironmentLib; import me.topchetoeu.jscript.mapping.SourceMap; public class Context implements Extensions { + public static final Context NULL = new Context(null); + public final Context parent; public final Environment environment; public final CodeFrame frame; diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index db7252c..a106d63 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -71,7 +71,7 @@ public class Environment implements Extensions { return ext.init(COMPILE_FUNC, new NativeFunction("compile", args -> { var source = args.getString(0); var filename = args.getString(1); - var env = Values.wrapper(args.convert(2, ObjectValue.class).getMember(args.ctx, Symbol.get("env")), Environment.class); + var env = Values.wrapper(Values.getMember(args.ctx, args.get(2), Symbol.get("env")), Environment.class); var isDebug = DebugContext.enabled(args.ctx); var res = new ObjectValue(); diff --git a/src/me/topchetoeu/jscript/engine/ExtensionStack.java b/src/me/topchetoeu/jscript/engine/ExtensionStack.java deleted file mode 100644 index 336e903..0000000 --- a/src/me/topchetoeu/jscript/engine/ExtensionStack.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.topchetoeu.jscript.engine; - -import me.topchetoeu.jscript.engine.values.Symbol; - -public abstract class ExtensionStack implements Extensions { - protected abstract Extensions[] extensionStack(); - - @Override public void add(Symbol key, T obj) { - for (var el : extensionStack()) { - if (el != null) { - el.add(key, obj); - return; - } - } - } - @Override public T get(Symbol key) { - for (var el : extensionStack()) { - if (el != null && el.has(key)) return el.get(key); - } - - return null; - } - @Override public boolean has(Symbol key) { - for (var el : extensionStack()) { - if (el != null && el.has(key)) return true; - } - - return false; - } - @Override public boolean remove(Symbol key) { - var anyRemoved = false; - - for (var el : extensionStack()) { - if (el != null) anyRemoved &= el.remove(key); - } - - return anyRemoved; - } -} diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index f3e860d..dd39e78 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -124,8 +124,7 @@ public class SimpleDebugger implements Debugger { this.global = frame.function.environment.global.obj; this.local = frame.getLocalScope(true); this.capture = frame.getCaptureScope(true); - this.local.setPrototype(frame.ctx, capture); - this.capture.setPrototype(frame.ctx, global); + Values.makePrototypeChain(frame.ctx, global, capture, local); this.valstack = frame.getValStackScope(); this.serialized = new JSONMap() @@ -841,7 +840,7 @@ public class SimpleDebugger implements Debugger { } else { propDesc.set("name", Values.toString(ctx, key)); - propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); + propDesc.set("value", serializeObj(ctx, Values.getMember(ctx, obj, key))); propDesc.set("writable", obj.memberWritable(key)); propDesc.set("enumerable", obj.memberEnumerable(key)); propDesc.set("configurable", obj.memberConfigurable(key)); @@ -850,7 +849,7 @@ public class SimpleDebugger implements Debugger { } } - var proto = obj.getPrototype(ctx); + var proto = Values.getPrototype(ctx, obj); var protoDesc = new JSONMap(); protoDesc.set("name", "__proto__"); diff --git a/src/me/topchetoeu/jscript/engine/frame/InstructionResult.java b/src/me/topchetoeu/jscript/engine/frame/InstructionResult.java deleted file mode 100644 index c6d0be5..0000000 --- a/src/me/topchetoeu/jscript/engine/frame/InstructionResult.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.topchetoeu.jscript.engine.frame; - -public class InstructionResult { - public final Object value; - - public InstructionResult(Object value) { - this.value = value; - } -} diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index 4d78e3a..b792b1a 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -7,13 +7,14 @@ import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; public class GlobalScope implements ScopeRecord { public final ObjectValue obj; public boolean has(Context ctx, String name) { - return obj.hasMember(ctx, name, false); + return Values.hasMember(null, obj, name, false); } public Object getKey(String name) { return name; @@ -21,7 +22,7 @@ public class GlobalScope implements ScopeRecord { public GlobalScope globalChild() { var obj = new ObjectValue(); - obj.setPrototype(null, this.obj); + Values.setPrototype(null, obj, this.obj); return new GlobalScope(obj); } public LocalScopeRecord child() { @@ -29,12 +30,12 @@ public class GlobalScope implements ScopeRecord { } public Object define(String name) { - if (obj.hasMember(null, name, true)) return name; - obj.defineProperty(null, name, null); + if (Values.hasMember(Context.NULL, obj, name, false)) return name; + obj.defineProperty(Context.NULL, name, null); return name; } public void define(String name, Variable val) { - obj.defineProperty(null, name, + obj.defineProperty(Context.NULL, name, new NativeFunction("get " + name, args -> val.get(args.ctx)), new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }), true, true @@ -51,12 +52,12 @@ public class GlobalScope implements ScopeRecord { } public Object get(Context ctx, String name) { - if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); - else return obj.getMember(ctx, name); + if (!Values.hasMember(ctx, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); + else return Values.getMember(ctx, obj, name); } public void set(Context ctx, String name, Object val) { - if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); - if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); + if (!Values.hasMember(ctx, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); + if (!Values.setMember(ctx, obj, name, val)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); } public Set keys() { diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 176078a..20ffac6 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -54,6 +54,13 @@ public class ObjectValue { public LinkedHashSet nonConfigurableSet = new LinkedHashSet<>(); public LinkedHashSet nonEnumerableSet = new LinkedHashSet<>(); + private Property getProperty(Context ctx, Object key) { + if (properties.containsKey(key)) return properties.get(key); + var proto = getPrototype(ctx); + if (proto != null) return proto.getProperty(ctx, key); + else return null; + } + public final boolean memberWritable(Object key) { if (state == State.FROZEN) return false; return !values.containsKey(key) || !nonWritableSet.contains(key); @@ -159,6 +166,113 @@ public class ObjectValue { return (ObjectValue)prototype; } + public final boolean setPrototype(PlaceholderProto val) { + if (!extensible()) return false; + switch (val) { + case OBJECT: prototype = OBJ_PROTO; break; + case FUNCTION: prototype = FUNC_PROTO; break; + case ARRAY: prototype = ARR_PROTO; break; + case ERROR: prototype = ERR_PROTO; break; + case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break; + case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break; + case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break; + case NONE: prototype = null; break; + } + return true; + } + + /** + * A method, used to get the value of a field. If a property is bound to + * this key, but not a field, this method should return null. + */ + protected Object getField(Context ctx, Object key) { + if (values.containsKey(key)) return values.get(key); + var proto = getPrototype(ctx); + if (proto != null) return proto.getField(ctx, key); + else return null; + } + /** + * Changes the value of a field, that is bound to the given key. If no field is + * bound to this key, a new field should be created with the given value + * @return Whether or not the operation was successful + */ + protected boolean setField(Context ctx, Object key, Object val) { + if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { + ((FunctionValue)val).name = Values.toString(ctx, key); + } + + values.put(key, val); + return true; + } + /** + * Deletes the field bound to the given key. + */ + protected void deleteField(Context ctx, Object key) { + values.remove(key); + } + /** + * Returns whether or not there is a field bound to the given key. + * This must ignore properties + */ + protected boolean hasField(Context ctx, Object key) { + return values.containsKey(key); + } + + public final Object getMember(Context ctx, Object key, Object thisArg) { + key = Values.normalize(ctx, key); + + if ("__proto__".equals(key)) { + var res = getPrototype(ctx); + return res == null ? Values.NULL : res; + } + + var prop = getProperty(ctx, key); + + if (prop != null) { + if (prop.getter == null) return null; + else return prop.getter.call(ctx, Values.normalize(ctx, thisArg)); + } + else return getField(ctx, key); + } + public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) { + key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); + + var prop = getProperty(ctx, key); + if (prop != null) { + if (prop.setter == null) return false; + prop.setter.call(ctx, Values.normalize(ctx, thisArg), val); + return true; + } + else if (onlyProps) return false; + else if (!extensible() && !values.containsKey(key)) return false; + else if (key == null) { + values.put(key, val); + return true; + } + else if ("__proto__".equals(key)) return setPrototype(ctx, val); + else if (nonWritableSet.contains(key)) return false; + else return setField(ctx, key, val); + } + public final boolean hasMember(Context ctx, Object key, boolean own) { + key = Values.normalize(ctx, key); + + if (key != null && "__proto__".equals(key)) return true; + if (hasField(ctx, key)) return true; + if (properties.containsKey(key)) return true; + if (own) return false; + var proto = getPrototype(ctx); + return proto != null && proto.hasMember(ctx, key, own); + } + public final boolean deleteMember(Context ctx, Object key) { + key = Values.normalize(ctx, key); + + if (!memberConfigurable(key)) return false; + properties.remove(key); + nonWritableSet.remove(key); + nonEnumerableSet.remove(key); + deleteField(ctx, key); + return true; + } public final boolean setPrototype(Context ctx, Object val) { val = Values.normalize(ctx, val); @@ -186,111 +300,6 @@ public class ObjectValue { } return false; } - public final boolean setPrototype(PlaceholderProto val) { - if (!extensible()) return false; - switch (val) { - case OBJECT: prototype = OBJ_PROTO; break; - case FUNCTION: prototype = FUNC_PROTO; break; - case ARRAY: prototype = ARR_PROTO; break; - case ERROR: prototype = ERR_PROTO; break; - case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break; - case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break; - case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break; - case NONE: prototype = null; break; - } - return true; - } - - protected Property getProperty(Context ctx, Object key) { - if (properties.containsKey(key)) return properties.get(key); - var proto = getPrototype(ctx); - if (proto != null) return proto.getProperty(ctx, key); - else return null; - } - protected Object getField(Context ctx, Object key) { - if (values.containsKey(key)) return values.get(key); - var proto = getPrototype(ctx); - if (proto != null) return proto.getField(ctx, key); - else return null; - } - protected boolean setField(Context ctx, Object key, Object val) { - if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { - ((FunctionValue)val).name = Values.toString(ctx, key); - } - - values.put(key, val); - return true; - } - protected void deleteField(Context ctx, Object key) { - values.remove(key); - } - protected boolean hasField(Context ctx, Object key) { - return values.containsKey(key); - } - - public final Object getMember(Context ctx, Object key, Object thisArg) { - key = Values.normalize(ctx, key); - - if ("__proto__".equals(key)) { - var res = getPrototype(ctx); - return res == null ? Values.NULL : res; - } - - var prop = getProperty(ctx, key); - - if (prop != null) { - if (prop.getter == null) return null; - else return prop.getter.call(ctx, Values.normalize(ctx, thisArg)); - } - else return getField(ctx, key); - } - public final Object getMember(Context ctx, Object key) { - return getMember(ctx, key, this); - } - - public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) { - key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); - - var prop = getProperty(ctx, key); - if (prop != null) { - if (prop.setter == null) return false; - prop.setter.call(ctx, Values.normalize(ctx, thisArg), val); - return true; - } - else if (onlyProps) return false; - else if (!extensible() && !values.containsKey(key)) return false; - else if (key == null) { - values.put(key, val); - return true; - } - else if ("__proto__".equals(key)) return setPrototype(ctx, val); - else if (nonWritableSet.contains(key)) return false; - else return setField(ctx, key, val); - } - public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) { - return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps); - } - - public final boolean hasMember(Context ctx, Object key, boolean own) { - key = Values.normalize(ctx, key); - - if (key != null && "__proto__".equals(key)) return true; - if (hasField(ctx, key)) return true; - if (properties.containsKey(key)) return true; - if (own) return false; - var proto = getPrototype(ctx); - return proto != null && proto.hasMember(ctx, key, own); - } - public final boolean deleteMember(Context ctx, Object key) { - key = Values.normalize(ctx, key); - - if (!memberConfigurable(key)) return false; - properties.remove(key); - nonWritableSet.remove(key); - nonEnumerableSet.remove(key); - deleteField(ctx, key); - return true; - } public final ObjectValue getMemberDescriptor(Context ctx, Object key) { key = Values.normalize(ctx, key); diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 5b06a04..b53a400 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -281,7 +281,7 @@ public class Values { obj = normalize(ctx, obj); key = normalize(ctx, key); if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); - if (obj instanceof ObjectValue) return object(obj).getMember(ctx, key); + if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMember(ctx, key, obj); if (obj instanceof String && key instanceof Number) { var i = number(key); @@ -307,7 +307,7 @@ public class Values { if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val); - if (obj instanceof ObjectValue) return object(obj).setMember(ctx, key, val, false); + if (obj instanceof ObjectValue) return ((ObjectValue)obj).setMember(ctx, key, val, obj, false); var proto = getPrototype(ctx, obj); return proto.setMember(ctx, key, val, obj, true); @@ -340,7 +340,7 @@ public class Values { public static ObjectValue getPrototype(Context ctx, Object obj) { if (obj == null || obj == NULL) return null; obj = normalize(ctx, obj); - if (obj instanceof ObjectValue) return object(obj).getPrototype(ctx); + if (obj instanceof ObjectValue) return ((ObjectValue)obj).getPrototype(ctx); if (ctx == null) return null; if (obj instanceof String) return ctx.get(Environment.STRING_PROTO); @@ -352,7 +352,12 @@ public class Values { } public static boolean setPrototype(Context ctx, Object obj, Object proto) { obj = normalize(ctx, obj); - return obj instanceof ObjectValue && object(obj).setPrototype(ctx, proto); + return obj instanceof ObjectValue && ((ObjectValue)obj).setPrototype(ctx, proto); + } + public static void makePrototypeChain(Context ctx, Object... chain) { + for(var i = 1; i < chain.length; i++) { + setPrototype(ctx, chain[i], chain[i - 1]); + } } public static List getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) { List res = new ArrayList<>(); @@ -367,7 +372,7 @@ public class Values { while (proto != null) { res.addAll(proto.keys(includeNonEnumerable)); - proto = proto.getPrototype(ctx); + proto = getPrototype(ctx, proto); } } @@ -400,10 +405,10 @@ public class Values { var res = new ObjectValue(); try { var proto = Values.getMember(ctx, func, "prototype"); - res.setPrototype(ctx, proto); - + setPrototype(ctx, res, proto); + var ret = call(ctx, func, res, args); - + if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret; return res; } @@ -569,11 +574,11 @@ public class Values { private void loadNext() { if (next == null) value = null; else if (consumed) { - var curr = object(next.call(ctx, iterator)); + var curr = next.call(ctx, iterator); if (curr == null) { next = null; value = null; } - if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; } + if (toBoolean(Values.getMember(ctx, curr, "done"))) { next = null; value = null; } else { - this.value = curr.getMember(ctx, "value"); + this.value = Values.getMember(ctx, curr, "value"); consumed = false; } } diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 96c04ed..e1b5984 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -298,8 +298,8 @@ public class NativeWrapperProvider implements WrappersProvider { var parentProto = getProto(parent); var parentConstr = getConstr(parent); - if (parentProto != null) proto.setPrototype(null, parentProto); - if (parentConstr != null) constr.setPrototype(null, parentConstr); + if (parentProto != null) Values.setPrototype(Context.NULL, proto, parentProto); + if (parentConstr != null) Values.setPrototype(Context.NULL, constr, parentConstr); } public ObjectValue getProto(Class clazz) { diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 5f9c685..4309dbb 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -58,8 +58,8 @@ public class JSON { var res = new JSONMap(); - for (var el : ((ObjectValue)val).keys(false)) { - var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev); + for (var el : Values.getMembers(ctx, val, false, false)) { + var jsonEl = fromJs(ctx, Values.getMember(ctx, val, el), prev); if (jsonEl == null) continue; if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); } diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 88a220b..18a3b7a 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.HashMap; import me.topchetoeu.jscript.Reading; +import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.FunctionValue; @@ -208,7 +209,7 @@ public class Internals { env.add(Environment.TYPE_ERR_PROTO, wp.getProto(TypeErrorLib.class)); env.add(Environment.RANGE_ERR_PROTO, wp.getProto(RangeErrorLib.class)); - wp.getProto(ObjectLib.class).setPrototype(null, null); + Values.setPrototype(Context.NULL, wp.getProto(ObjectLib.class), null); env.add(Environment.REGEX_CONSTR, wp.getConstr(RegExpLib.class)); return env; diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 112da5a..622f4fa 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -26,7 +26,7 @@ public class ObjectLib { @Expose(target = ExposeTarget.STATIC) public static ObjectValue __create(Arguments args) { var obj = new ObjectValue(); - obj.setPrototype(args.ctx, args.get(0)); + Values.setPrototype(args.ctx, obj, args.get(0)); if (args.n() >= 1) { var newArgs = new Object[args.n()]; @@ -53,7 +53,7 @@ public class ObjectLib { if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property."); if (!obj.defineProperty( args.ctx, key, - attrib.getMember(args.ctx, "value"), + Values.getMember(args.ctx, attrib, "value"), Values.toBoolean(Values.getMember(args.ctx, attrib, "writable")), Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")), Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable"))