Lua userdata array access and methods
Asked Answered
B

1

6

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;
};

This is the code that does that,

int luaopen_perm(lua_State *L) {

  luaL_newmetatable(L, PERM_MT);
  luaL_setfuncs(L, permL_methods, 0);
  luaL_setfuncs(L, permL_functions, 0);
  lua_pop(L, 1);

  luaL_newlib(L, permL_functions);

  return 1;
};

where permL_methods is

static const struct luaL_Reg permL_methods[] = {
  { "__index",      permL_index           },
  { "__eq",         permL_equal           },
  { "__tostring",   permL_tostring        },
  { "__gc",         permL_destroy         },
  [...]
  { NULL,           NULL                  }
};

and permL_functions is

static const struct luaL_Reg permL_functions[] = {
  { "inverse",      permL_new_inverse     },
  { "product",      permL_new_product     },
  { "composition",  permL_new_composition },
  [...]
  { NULL,           NULL                  }
};
Brassy answered 17/11, 2014 at 10:15 Comment(5)
That seems like a reasonable approach to me.Mitsue
Programming in Lua has an extensive tutorial for making an array type in C that includes adding the meta methods in C. It is written by one of the designers of Lua and is freely available.Taal
Thanks @rpattiso, I am aware. However, it does not deal with my problem AFAICT.Brassy
Are you sure? this page adds __index and __newindex methods to a user defined C array type. They add these as a second option for a:get(10) and a:set(10, 3.4) so it will be the same as a[10] and a[10]=3.4. What am I not understanding from your question?Taal
@rpattiso: They replace the get() and set() methods by the __index and __newindex notation. After they have done this a:get(10) results in mt.__index(a,"get")(10). I want both methods (like a:sort()) and index notation.Brassy
B
6

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
> 
Borlow answered 17/11, 2014 at 11:59 Comment(8)
Thanks! This is the kind of answer I was hoping for. But: I just implemented this in C and the problem I have now is that u[k] now calls op_mt.__index(mt, k) rather then op_mt.__index(u, k). Thus my C function registered as op_mt.__index doesn't know which userdata to act on...Brassy
You are right: I did not try to access the original table in my answer, so I did not see this problem. I guess the original suggestion is the way to go then.Borlow
I added an example of how to do this using a function for the __index metamethod.Borlow
Thanks, I wrote the C analog and it works as intended!Brassy
@1k5: Sorry I'm late to the party, but I'm having trouble getting this working in C -- could you share your experience here? #39089803 Thanks.Ferdinand
@Leo: I found my old code. My solution is really just a direct solution to C of Gilles Lua code.Brassy
@1k5: Very straightforward, thanks. I guess the part that causes me problems is registering the metatable in C, "PERM_MT" in your case. Did you just register a luaL_Reg with all your meta-events and methods, except for __index? That doesn't work for me.Ferdinand
@Leo: I added the code. permL_methods is a luaL_Reg containing __index among others. Then other functions are in permL_functions like e.g. composition and are registered twice. This is so I can do both composition(p, i, q) as well as p:composition(i,q) IIRC (unrelated to the question). The important bit is all functions including __index go to the metatable. Then __index checks first to see if there is a function with that name and if not assumes userdata access (in my case some array-like object).Brassy

© 2022 - 2024 — McMap. All rights reserved.