lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Have you considered metatable trickery?

Here's some code:

function X(t)
return setmetatable({},{
__index = function(w,k,v)
return X(t and t[k])
end;
__call = function(w,default)
if t == nil then return default end
return t
end;
})

end

print(X(test).foo.bar.doo("otherwise"))
test = {foo = {bar = { doo = "yes"}}}
print(X(test).foo.bar.doo("otherwise"))

--- Output:
otherwise
yes
--- 

So it's of course terrible for the GC and performance, but if both is acceptable, it's working...

2013/3/1 Sven Olsen <sven2718@gmail.com>
Hey list,

I've been playing around with another parser hack, and it's starting to seem like it may be worth a list post. 

Like most Lua programmers, I'm in the habit of using 'or' to specify a list of possible fallback values.  For example:

   color = object.icon.color or object.color or white

Sometimes I'll want to check for fallback values stored inside tables that may themselves be undefined.  If not all objects have icons, the example above gets a bit more complex, becoming something like:

   color = (object.icon and object.icon.color) or object.color or white 

And if the hierarchy of optional subtables is several levels deep, then properly defining color becomes a real chore.  (For example, consider writing a function that has color default to object.icon.glow.color, but doesn't crash if any of the referenced tables are missing.)

Thus my latest excursion into lparser.c.  The idea is to allow ~ to denote a "conditional field reference".  When evaluating 'table~key', the result is 'table.key' provided that 'table' exists. But if 'table' doesn't exist, then 'table~key' evaluates to 'table'.  I fall back on the nonexistent case when 'table' equals nil or false, so the semantics match the behavior of more standard 'and/or' tricks.

By chaining ~, I can write statements like the following, which are safe even when 'object' or any of it's subtables have a chance of being nil:

  color = object~icon~glow~color or white

Extending the sugar so that table~[key] becomes a conditional version of table[key] is easy enough to do, and makes the hack useful in a wider range of situations.

Like the "stringification" hack, my current feeling is that this is a piece of sugar that's useful enough to justify its obscurity -- though I'm sure some members of the list will consider it a lollipop :)  

Once again, I'll ask if any of the more knowledgeable members of the list know what this kind of shorthand ought to be called.  "Conditional fields" is the best name I can come up with, but, if there's a standard term out there, it would be good to know it.

-Sven

PS:  For those of you who enjoy messing around with parser hacks, the entry point's a new case in lparser:primaryexp()

    switch (ls->t.token) {
    case '~': 
        luaX_next(ls);
        conditional_field(ls,v);
        break;

The implementation is messier than I'd like.  I'd hoped to simply piggyback on OP_AND, but couldn't figure out how to do so without requiring multiple evaluations of the table _expression_.  The implementation I ended up with takes a lower level approach.  It seems to be pretty safe -- though I haven't made a close enough study of Lua's bytecode generation to be entirely confident in it.  I'm also doing some stack maintenance that's probably unnecessarily paranoid.

static void conditional_field(LexState *ls, expdesc *v) {
    FuncState *fs = ls->fs;
    luaK_exp2nextreg(fs, v);
    luaK_codeABC(fs,OP_TEST, v->u.info, NO_REG, 0 );
    {
    int old_free=fs->freereg;             
    int vreg=v->u.info;
    int j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP);
    expdesc key;
    switch(ls->t.token) {
    case '[':
        yindex(ls, &key);
        luaK_indexed(fs, v, &key);
        luaK_exp2nextreg(fs, v);
        break;        
    default:
        checkname(ls, &key);
        luaK_indexed(fs, v, &key);
    }
    luaK_exp2nextreg(fs, v);
    fs->freereg=old_free;
    /* i think this next check is unnecessary, as any complex key
      expressions should be courteous enough to leave the top of
      the stack where they found it. */
    if(v->u.info!=vreg) {
        luaK_codeABC(fs,OP_MOVE, vreg, v->u.info, 0 );
        v->u.info=vreg;
    }
    SETARG_sBx(fs->f->code[j], fs->pc-j-1);
    }
}