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                  }
};
1k5
  • 342
  • 6
  • 15
  • That seems like a reasonable approach to me. – Etan Reisner Nov 17 '14 at 10:46
  • [Programming in Lua](http://www.lua.org/pil/28.2.html) 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. – ryanpattison Nov 17 '14 at 12:11
  • 1
    Thanks @rpattiso, I am aware. However, it does not deal with my problem AFAICT. – 1k5 Nov 17 '14 at 12:27
  • Are you sure? [this page](http://www.lua.org/pil/28.4.html) 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? – ryanpattison Nov 17 '14 at 12:33
  • @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. – 1k5 Nov 17 '14 at 12:41

1 Answers1

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
> 
Gilles Gregoire
  • 1,696
  • 1
  • 12
  • 14
  • 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... – 1k5 Nov 17 '14 at 12:30
  • 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. – Gilles Gregoire Nov 17 '14 at 12:53
  • 2
    I added an example of how to do this using a function for the __index metamethod. – Gilles Gregoire Nov 17 '14 at 13:03
  • Thanks, I wrote the C analog and it works as intended! – 1k5 Nov 17 '14 at 14:29
  • @1k5: Sorry I'm late to the party, but I'm having trouble getting this working in C -- could you share your experience here? http://stackoverflow.com/questions/39089303/lua-userdata-unable-to-have-simultaneous-array-access-and-methods?noredirect=1#comment65527716_39089303 Thanks. – Lars Aug 25 '16 at 16:25
  • 1
    @Leo: I found my old code. My solution is really just a direct solution to C of Gilles Lua code. – 1k5 Aug 25 '16 at 19:16
  • @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. – Lars Aug 25 '16 at 22:46
  • 1
    @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). – 1k5 Aug 26 '16 at 06:10