1k5 1k5 - 3 months ago 28
C Question

Lua userdata array access and methods

I am writing in C a userdata type for use in Lua. It has some array-type properties and various methods aswell. Right now if u is of this type, I use

u:set(k,v)
resp.
u:get(k)
to access data and e.g.
u:sort()
as method. For this I set
__index
to a table containing these methods. Now if I want to access the data using
u[k] = v
or
u[k]
, I need to set
__newindex
and
__index
to
set
resp
get
. But then the other methods are no longer accessible...

What's the best way to deal with this in C? I am guessing I need to write a function in C to register as
__index
and somehow deal with it there. Maybe check if key belongs to a Lua table of methods and if so call it.

Any help/hints would be appreciated. I did not find examples like this, although it seems a very natural thing to do (to me.)

edit: Added my C version of the solution in Lua posted in the answer below. This is more or less a direct translation, so all credit goes to @gilles-gregoire .

The following C function is registered as __index metamethod.

static int permL_index(lua_State *L) {
struct perm **pp = luaL_checkudata(L, 1, PERM_MT);
int i;

luaL_getmetatable(L, PERM_MT);
lua_pushvalue(L, 2);
lua_rawget(L, -2);

if ( lua_isnil(L, -1) ) {
/* found no method, so get value from userdata. */
i = luaL_checkint(L, 2);
luaL_argcheck(L, 1 <= i && i <= (*pp)->n, 2, "index out of range");

lua_pushinteger(L, (*pp)->v[i-1]);
};

return 1;
};

Answer

This looks like a problem which can be solved with nested metatables. You need one metatable for the methods (like your sort() method), and a second one for index operations. That second metatable is actually the metatable of the methods metatable.

Let me write this as lua code. You need 3 tables:

-- the userdata object. I'm using a table here,
-- but it will work the same with a C userdata
u = {}

-- the "methods" metatable:
mt = {sort = function() print('sorting...') end}

-- the "operators" metatable:
op_mt = {__index = function() print('get') end}

Now, the tricky part is here: lua will first lookup u when you will call a method. If it does not find it, it will lookup in the table pointed by the __index field of u's metatable... And Lua will repeat the process for that table!

-- first level metatable
mt.__index = mt
setmetatable(u, mt)

-- second level metatable
setmetatable(mt, op_mt)

You can now use your u like this:

> u:sort()
sorting...
> = u[1]
get
nil

EDIT: a better solution by using a function for the __index metamethod

Using a function for the __index metamethod is probably the right way to this:

u = {}
mt = {sort = function() print('sorting...') end}
setmetatable(u, mt)
mt.__index = function(t, key)
    -- use rawget to avoid recursion
    local mt_val = rawget(mt, key)
    if mt_val ~=nil then
        return mt_val
    else
        print('this is a get on object', t)
    end
end

Usage:

> print(u)
table: 0x7fb1eb601c30
> u:sort()
sorting...
> = u[1]
this is a get on object    table: 0x7fb1eb601c30
nil
>