YetAnotherForum
Welcome Guest Search | Active Topics | Log In | Register

Metamethods for +=, -=, *=
cue
#1 Posted : Sunday, November 4, 2012 8:14:33 PM(UTC)
Rank: Advanced Member

Groups: Registered
Joined: 1/3/2011(UTC)
Posts: 60
Man

Thanks: 0 times
Was thanked: 4 time(s) in 4 post(s)
Hi fagiano, is it possible to add more metamethods, specifically for +=, -= and *= ?

At the moment it's possible to write

Code:

a += b;


which is expanded to

Code:

a = a + b;


and finally to

Code:

a = a._add(b);


but this involves the creation of a new object. For applications that involve math for example, it would be favourable to expand the above expression to

Code:

a._addAssign(b);


or something like this, because the creation of a new object would be avoided.


More available metamethods, like for << and >> would also be nice.


If you don't want to add those metamethods, how difficult would it be to add them myself?
cmwelsh
#2 Posted : Friday, December 21, 2012 11:14:19 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 12/21/2012(UTC)
Posts: 4

Thanks: 0 times
Was thanked: 0 time(s) in 0 post(s)
Why not do:

Code:

foo <- {
    _add = function(b) {
        this.counter += b
        return this
    }
}

foo += b
Guest
#3 Posted : Saturday, December 22, 2012 7:18:33 AM(UTC)
Rank: Guest

Groups:

Thanks: 0 times
Was thanked: 1 time(s) in 1 post(s)
Message was deleted by Moderator.
cue
#4 Posted : Saturday, December 22, 2012 7:14:20 PM(UTC)
Rank: Advanced Member

Groups: Registered
Joined: 1/3/2011(UTC)
Posts: 60
Man

Thanks: 0 times
Was thanked: 4 time(s) in 4 post(s)
cmwelsh wrote:
Why not do:

Code:

foo <- {
    _add = function(b) {
        this.counter += b
        return this
    }
}

foo += b


This doesn't really help. First, My vector/matrix classes are C++ classes exposed to Squirrel, so the objects don't have "true" slots like "this.counter" in your example. And second, I would expect an expression like "a + b" to return a new object, which wouldn't be true anymore with your method.
cmwelsh
#5 Posted : Saturday, December 22, 2012 7:28:29 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 12/21/2012(UTC)
Posts: 4

Thanks: 0 times
Was thanked: 0 time(s) in 0 post(s)
cue wrote:

This doesn't really help. First, My vector/matrix classes are C++ classes exposed to Squirrel, so the objects don't have "true" slots like "this.counter" in your example. And second, I would expect an expression like "a + b" to return a new object, which wouldn't be true anymore with your method.


Ah sorry, you're right.
Dwachs
#6 Posted : Tuesday, October 6, 2015 6:45:41 PM(UTC)
Rank: Member

Groups: Registered
Joined: 11/2/2011(UTC)
Posts: 20

Thanks: 5 times
Was thanked: 2 time(s) in 2 post(s)
I tried to implement this. Here is a first patch:
Code:

diff --git a/squirrel/sqobject.h b/squirrel/sqobject.h
index 49efb96..8f0cb30 100644
--- a/squirrel/sqobject.h
+++ b/squirrel/sqobject.h
@@ -35,7 +35,12 @@ enum SQMetaMethod{
    MT_TOSTRING=15,
    MT_NEWMEMBER=16,
    MT_INHERITED=17,
-    MT_LAST = 18
+    MT_ADDASGN=18,
+    MT_SUBASGN=19,
+    MT_MULASGN=20,
+    MT_DIVASGN=21,
+    MT_MODULOASGN=22,
+    MT_LAST =23
};

#define MM_ADD        _SC("_add")
@@ -63,6 +68,12 @@ enum SQMetaMethod{
#define MM_DIVASGN        _SC("_divasgn")
#define MM_MODULOASGN    _SC("_moduloasgn")

+#define MM_ADDASGN        _SC("_addasgn")
+#define MM_SUBASGN        _SC("_subasgn")
+#define MM_MULASGN        _SC("_mulasgn")
+#define MM_DIVASGN        _SC("_divasgn")
+#define MM_MODULOASGN    _SC("_moduloasgn")
+

#define _CONSTRUCT_VECTOR(type,size,ptr) { \
    for(SQInteger n = 0; n < ((SQInteger)size); n++) { \
diff --git a/squirrel/sqstate.cpp b/squirrel/sqstate.cpp
index 1c2b4f7..6f07f88 100644
--- a/squirrel/sqstate.cpp
+++ b/squirrel/sqstate.cpp
@@ -144,6 +144,11 @@ void SQSharedState::Init()
    newmetamethod(MM_TOSTRING);
    newmetamethod(MM_NEWMEMBER);
    newmetamethod(MM_INHERITED);
+    newmetamethod(MM_ADDASGN);
+    newmetamethod(MM_SUBASGN);
+    newmetamethod(MM_MULASGN);
+    newmetamethod(MM_DIVASGN);
+    newmetamethod(MM_MODULOASGN);

    _constructoridx = SQString::Create(this,_SC("constructor"));
    _registry = SQTable::Create(this,0);
diff --git a/squirrel/sqvm.cpp b/squirrel/sqvm.cpp
index 7d5c1a2..904c8fa 100644
--- a/squirrel/sqvm.cpp
+++ b/squirrel/sqvm.cpp
@@ -59,7 +59,7 @@ bool SQVM::BW_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,con
    } \
}

-bool SQVM::ARITH_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2)
+bool SQVM::ARITH_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2, bool assign)
{
    SQInteger tmask = type(o1)|type(o2);
    switch(tmask) {
@@ -96,7 +96,7 @@ bool SQVM::ARITH_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,
            if(op == '+' &&    (tmask & _RT_STRING)){
                if(!StringCat(o1, o2, trg)) return false;
            }
-            else if(!ArithMetaMethod(op,o1,o2,trg)) {
+            else if(!ArithMetaMethod(op,o1,o2,trg, assign)) {
                return false;
            }
    }
@@ -145,7 +145,10 @@ SQVM::~SQVM()
    REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
}

-bool SQVM::ArithMetaMethod(SQInteger op,const SQObjectPtr &o1,const SQObjectPtr &o2,SQObjectPtr &dest)
+#define _META_ASGN(mt) \
+    case MT_ ## mt: mmasgn = MT_ ## mt ## ASGN; break
+
+bool SQVM::ArithMetaMethod(SQInteger op,const SQObjectPtr &o1,const SQObjectPtr &o2,SQObjectPtr &dest, bool assign)
{
    SQMetaMethod mm;
    switch(op){
@@ -157,8 +160,28 @@ bool SQVM::ArithMetaMethod(SQInteger op,const SQObjectPtr &o1,const SQObjectPtr
        default: mm = MT_ADD; assert(0); break; //shutup compiler
    }
    if(is_delegable(o1) && _delegable(o1)->_delegate) {
-
+        // arithmetic assign
        SQObjectPtr closure;
+        if (assign) {
+            SQMetaMethod mmasgn;
+            switch(mm) {
+                _META_ASGN(ADD);
+                _META_ASGN(SUB);
+                _META_ASGN(DIV);
+                _META_ASGN(MUL);
+                _META_ASGN(MODULO);
+                default: assert(0); break;
+            }
+            if (_delegable(o1)->GetMetaMethod(this, mmasgn, closure)) {
+                SQObjectPtr ignore;
+                Push(o1);Push(o2);
+                if (CallMetaMethod(closure,mmasgn,2,ignore)) {
+                    dest = o1;
+                    return true;
+                }
+                return false;
+            }
+        }
        if(_delegable(o1)->GetMetaMethod(this, mm, closure)) {
            Push(o1);Push(o2);
            return CallMetaMethod(closure,mm,2,dest);
@@ -478,11 +501,13 @@ bool SQVM::PLOCAL_INC(SQInteger op,SQObjectPtr &target, SQObjectPtr &a, SQObject
    return true;
}

-bool SQVM::DerefInc(SQInteger op,SQObjectPtr &target, SQObjectPtr &self, SQObjectPtr &key, SQObjectPtr &incr, bool postfix,SQInteger selfidx)
+
+bool SQVM::DerefInc(SQInteger op,SQObjectPtr &target, SQObjectPtr &self, SQObjectPtr &key, SQObjectPtr &incr,
+              bool postfix,SQInteger selfidx)
{
    SQObjectPtr tmp, tself = self, tkey = key;
    if (!Get(tself, tkey, tmp, false, selfidx)) { return false; }
-    _RET_ON_FAIL(ARITH_OP( op , target, tmp, incr))
+    _RET_ON_FAIL(ARITH_OP( op , target, tmp, incr, true))
    if (!Set(tself, tkey, target,selfidx)) { return false; }
    if (postfix) target = tmp;
    return true;
diff --git a/squirrel/sqvm.h b/squirrel/sqvm.h
index b51d6f0..0444cb4 100644
--- a/squirrel/sqvm.h
+++ b/squirrel/sqvm.h
@@ -94,10 +94,10 @@ public:

    bool TypeOf(const SQObjectPtr &obj1, SQObjectPtr &dest);
    bool CallMetaMethod(SQObjectPtr &closure, SQMetaMethod mm, SQInteger nparams, SQObjectPtr &outres);
-    bool ArithMetaMethod(SQInteger op, const SQObjectPtr &o1, const SQObjectPtr &o2, SQObjectPtr &dest);
+    bool ArithMetaMethod(SQInteger op, const SQObjectPtr &o1, const SQObjectPtr &o2, SQObjectPtr &dest, bool assign = false);
    bool Return(SQInteger _arg0, SQInteger _arg1, SQObjectPtr &retval);
    //new stuff
-    _INLINE bool ARITH_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2);
+    _INLINE bool ARITH_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2, bool assign = false);
    _INLINE bool BW_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2);
    _INLINE bool NEG_OP(SQObjectPtr &trg,const SQObjectPtr &o1);
    _INLINE bool CMP_OP(CmpOP op, const SQObjectPtr &o1,const SQObjectPtr &o2,SQObjectPtr &res);
--

This adds the possibility to use metamethods like _addasgn for += etc. However, it works only for non-local variables. Hacking the compiler to implement it is way beyond my (maybe poor) understanding of the squirrel core. To be honest, I am not sure whether the patch is really bug free.

I tried to even go a step further. I noticed that the compiler compiles code like
Code:

a = b+c+d

like
Code:

local tmp = b+c; tmp = tmp + d; a = tmp

where a temporary value is instantiated twice and cleared once. It would be more efficient to generate code that looks like
Code:

tmp = b+c; tmp +=d; a= tmp

Due to the difficulties to hack the compiler to make arithemtic assignment possible, I went for a hack of the virtual machine:
Code:

diff --git a/squirrel/sqvm.cpp b/squirrel/sqvm.cpp
index 904c8fa..1df7f97 100644
--- a/squirrel/sqvm.cpp
+++ b/squirrel/sqvm.cpp
@@ -37,14 +37,14 @@ bool SQVM::BW_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,con
    return true;
}

-#define _ARITH_(op,trg,o1,o2) \
+#define _ARITH_(op,trg,o1,o2,assign) \
{ \
    SQInteger tmask = type(o1)|type(o2); \
    switch(tmask) { \
        case OT_INTEGER: trg = _integer(o1) op _integer(o2);break; \
        case (OT_FLOAT|OT_INTEGER): \
        case (OT_FLOAT): trg = tofloat(o1) op tofloat(o2); break;\
-        default: _GUARD(ARITH_OP((#op)[0],trg,o1,o2)); break;\
+        default: _GUARD(ARITH_OP((#op)[0],trg,o1,o2, assign && is_delegable(o1) && (o1._unVal.pRefCounted->_uiRef==1))); break;\
    } \
}

@@ -856,9 +856,9 @@ exception_restore:
                if(!IsEqual(STK(arg2),COND_LITERAL,res)) { SQ_THROW(); }
                TARGET = (!res)?true:false;
                } continue;
-            case _OP_ADD: _ARITH_(+,TARGET,STK(arg2),STK(arg1)); continue;
-            case _OP_SUB: _ARITH_(-,TARGET,STK(arg2),STK(arg1)); continue;
-            case _OP_MUL: _ARITH_(*,TARGET,STK(arg2),STK(arg1)); continue;
+            case _OP_ADD: _ARITH_(+,TARGET,STK(arg2),STK(arg1), arg0==arg2); continue;
+            case _OP_SUB: _ARITH_(-,TARGET,STK(arg2),STK(arg1), arg0==arg2); continue;
+            case _OP_MUL: _ARITH_(*,TARGET,STK(arg2),STK(arg1), arg0==arg2); continue;
            case _OP_DIV: _ARITH_NOZERO(/,TARGET,STK(arg2),STK(arg1),_SC("division by zero")); continue;
            case _OP_MOD: ARITH_OP('%',TARGET,STK(arg2),STK(arg1)); continue;
            case _OP_BITW:    _GUARD(BW_OP( arg3,TARGET,STK(arg2),STK(arg1))); continue;
@@ -949,7 +949,7 @@ exception_restore:
                }
                else {
                    SQObjectPtr o(sarg3); //_GUARD(LOCAL_INC('+',TARGET, STK(arg1), o));
-                    _ARITH_(+,a,a,o);
+                    _ARITH_(+,a,a,o,true);
                }
                           } continue;
            case _OP_PINC: {SQObjectPtr o(sarg3); _GUARD(DerefInc('+',TARGET, STK(arg1), STK(arg2), o, true, arg1));} continue;
--

The idea is that the vm invokes the arithmetic assignement expressions if it comes across a statement like
Code:

a = b + c

where a and b are variables on the same location on the stack, and the reference counter of a (and b) is equal to 1. Then the statement a=a+b would instantiate a+b then destroy a. This is optimized away by the vm to a+=b.

I would appreciate any comments. There is still the possibility that these patches rely on flawed assumptions on the code.
absence
#7 Posted : Wednesday, October 7, 2015 5:28:19 PM(UTC)
Rank: Advanced Member

Groups: Registered
Joined: 8/23/2014(UTC)
Posts: 109
Man
Location: Northern Germany & Lincolnshire, U.K.

Thanks: 1 times
Was thanked: 10 time(s) in 10 post(s)
Not that I'm totally against this, there might be use cases. I just want to add to consideration:
a+=b notation is syntactic sugar only, and by definition IS equal to a=a+b.
Squirrel will call arithmetic metamethods just properly in both cases, as in both notations the result is expected to be the exact same.
(that's why the compiler expands them and there are no separate opcodes)
Introducing _different_ metamethods adds footprint and in your case also degrades performance to allow to programmatically break the semantics of the language, because a+=b and a=a+b then can do different things. If you want to do different things, you're better off using for example functions/members so the code reads properly - instead of abusing settled operator syntax.

(And if so, you really should add additional op codes to compiler and vm for performance reasons. In that case, you could as well define additional syntax for these operators, why not a #+ b or something the like)

Just my 2 cents.




Users browsing this topic
Guest
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.

Clean Slate theme by Jaben Cargman (Tiny Gecko)
Powered by YAF 1.9.4 | YAF © 2003-2010, Yet Another Forum.NET
This page was generated in 0.108 seconds.