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                  }
};
5
  • That seems like a reasonable approach to me. Commented Nov 17, 2014 at 10:46
  • 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. Commented Nov 17, 2014 at 12:11
  • 1
    Thanks @rpattiso, I am aware. However, it does not deal with my problem AFAICT. Commented Nov 17, 2014 at 12:27
  • 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? Commented Nov 17, 2014 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. Commented Nov 17, 2014 at 12:41

1 Answer 1

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
> 
Sign up to request clarification or add additional context in comments.

8 Comments

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...
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.
I added an example of how to do this using a function for the __index metamethod.
@Leo: I found my old code. My solution is really just a direct solution to C of Gilles Lua code.
@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).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.