diff --git a/jsvar.d b/jsvar.d index edb56df..fbb1dc8 100644 --- a/jsvar.d +++ b/jsvar.d @@ -808,7 +808,14 @@ struct var { public var opOpAssign(string op, T)(T t) { if(payloadType() == Type.Object) { - if(this._payload._object !is null) { + if(auto pt = cast(PropertyPrototype) this._payload._object) { + auto propValue = pt.get; + auto result = propValue.opOpAssign!(op)(t); + + pt.set(result); + + return result; + } else if(this._payload._object !is null) { var* operator = this._payload._object._peekMember("opOpAssign", true); if(operator !is null && operator._type == Type.Function) return operator.call(this, op, t); @@ -1375,7 +1382,7 @@ struct var { else return *(new var); } - return from._getMember(name, true, false, file, line); + return from._getMember(name, true, false, false, file, line); } public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { @@ -1823,12 +1830,14 @@ class PrototypeObject { } // FIXME: maybe throw something else - /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) { + /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, bool returnRawProperty = false, string file = __FILE__, size_t line = __LINE__) { var* mem = _peekMember(name, recurse); if(mem !is null) { // If it is a property, we need to call the getter on it if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { + if(returnRawProperty) + return *mem; auto prop = cast(PropertyPrototype) (*mem)._payload._object; return prop.get; } @@ -1854,14 +1863,25 @@ class PrototypeObject { /*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) { var* mem = _peekMember(name, recurse); + bool onParent = false; + + if(mem is null && !recurse) { + mem = _peekMember(name, true); // properties need the check anyway as a setter might be on a prototype + onParent = true; + } + if(mem !is null) { // Property check - the setter should be proxied over to it if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { auto prop = cast(PropertyPrototype) (*mem)._payload._object; return prop.set(t); } - *mem = t; - return *mem; + if(!onParent) { + *mem = t; + return *mem; + } else { + mem = null; + } } if(!suppressOverloading) { @@ -2369,6 +2389,36 @@ unittest { ); } +version(with_arsd_script) +unittest { + import arsd.script; + static class Test { + @scriptable int a; + } + + Test test = new Test; + + test.a = 15; + + var globals = var.emptyObject; + globals.test = test; + globals.Test = subclassable!Test; + + interpret(q{ + test.a = 4; + var wtf = test.a += 5; + assert(wtf == 9); + assert(test.a == 9); + var test2 = new Test; + test2.a = 2; + test2.a += 5; + assert(test2.a == 7); + }, globals); + + assert(test.a == 9); + assert(globals.test2.get!Test.a == 7); +} + // just a base class we can reference when looking for native objects class WrappedNativeObject : PrototypeObject { TypeInfo wrappedType; diff --git a/script.d b/script.d index 7764c82..13d7305 100644 --- a/script.d +++ b/script.d @@ -1503,7 +1503,7 @@ class OpAssignExpression : Expression { var n; foreach(ctOp; CtList!("+=", "-=", "*=", "/=", "~=", "&=", "|=", "^=", "%=")) if(ctOp[0..1] == op) - n = mixin("v.getVar(sc) "~ctOp~" right"); + n = mixin("v.getVar(sc, true, true) "~ctOp~" right"); // FIXME: ensure the variable is updated in scope too @@ -1579,9 +1579,9 @@ class VariableExpression : Expression { return getVar(sc).get!string; } - ref var getVar(PrototypeObject sc, bool recurse = true) { + ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { try { - return sc._getMember(identifier, true /* FIXME: recurse?? */, true); + return sc._getMember(identifier, true /* FIXME: recurse?? */, true, returnRawProperty); } catch(DynamicTypeException dte) { dte.callStack ~= loc; throw dte; @@ -1592,7 +1592,12 @@ class VariableExpression : Expression { return sc._setMember(identifier, t, true /* FIXME: recurse?? */, true, suppressOverloading); } - ref var getVarFrom(PrototypeObject sc, ref var v) { + ref var getVarFrom(PrototypeObject sc, ref var v, bool returnRawProperty) { + if(returnRawProperty) { + if(v.payloadType == var.Type.Object) + return v._payload._object._getMember(identifier, true, false, returnRawProperty); + } + return v[identifier]; } @@ -1658,7 +1663,7 @@ class DotVarExpression : VariableExpression { return e1.toString() ~ "." ~ e2.toString(); } - override ref var getVar(PrototypeObject sc, bool recurse = true) { + override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { if(!this.recurse) { // this is a special hack... if(auto ve = cast(VariableExpression) e1) { @@ -1676,7 +1681,7 @@ class DotVarExpression : VariableExpression { } if(auto ve = cast(VariableExpression) e1) { - return this.getVarFrom(sc, ve.getVar(sc, recurse)); + return this.getVarFrom(sc, ve.getVar(sc, recurse), returnRawProperty); } else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") { auto se = cast(StringLiteralExpression) e1; var* functor = new var; @@ -1690,7 +1695,7 @@ class DotVarExpression : VariableExpression { // make a temporary for the lhs auto v = new var(); *v = e1.interpret(sc).value; - return this.getVarFrom(sc, *v); + return this.getVarFrom(sc, *v, returnRawProperty); } } @@ -1702,8 +1707,8 @@ class DotVarExpression : VariableExpression { } - override ref var getVarFrom(PrototypeObject sc, ref var v) { - return e2.getVarFrom(sc, v); + override ref var getVarFrom(PrototypeObject sc, ref var v, bool returnRawProperty) { + return e2.getVarFrom(sc, v, returnRawProperty); } override string toInterpretedString(PrototypeObject sc) { @@ -1725,13 +1730,13 @@ class IndexExpression : VariableExpression { return e1.toString() ~ "[" ~ e2.toString() ~ "]"; } - override ref var getVar(PrototypeObject sc, bool recurse = true) { + override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { if(auto ve = cast(VariableExpression) e1) - return ve.getVar(sc, recurse)[e2.interpret(sc).value]; + return ve.getVar(sc, recurse, returnRawProperty)[e2.interpret(sc).value]; else { auto v = new var(); *v = e1.interpret(sc).value; - return this.getVarFrom(sc, *v); + return this.getVarFrom(sc, *v, returnRawProperty); } }