I am writing a Lua (5.4) extension for a C library I wrote. One thing I would like to do is to define an object-like table and use direct key access to get data for some functions that don't need any arguments other than the object table itself.
I would like to do this using a generic getter function instead of repeating the same logic for multiple attributes. The generic getter should be triggered only if the key accessed is not set as a normal Lua value, and return nil
if the key is not registered as a getter function.
E.g., if I wrote this in pure Lua, I would do something along these lines:
-- Getters registry
local getters = {
name = function(t) return "NAME: " .. math.random(1, 10) end,
description = function(t) return "DESC: " .. math.random(11, 20) end,
}
mt = {["__index"] = function(t, k)
if getters[k] then return getters[k](t) end
end
}
a = {}
setmetatable (a, mt)
The random function is obviously contrived, it would be replaced in C by something that looks for userdata.
In the C API it looks like I can set functions with luaL_setfuncs()
and static values with lua_setfield()
, but if I push a getter function with either method, I would get the function reference, not the result of the function call.
How to write a generic getter in the C API?
Generally, I wouldn't use a for loop to compare strings one after the other at run time. Even if it's in C, it's eventually going to be slow for nothing if the attributes are accessed frequently. Why don't reuse Lua tables for something they are already very good at ?
int yourAttrGetter (lua_State* L) {
// Error checking removed for brievity
const char* name = lua_tostring(L, 2); // key accessed
// If the key exists in metatable, return it directly, we assume it's a method or default value
if (luaL_getmetafield(L, 1, name)) return 1;
// Otherwise, build the name of a getter method and if it exists, call it
char buf[64] = { 0 };
snprintf(buf, 63, "get%s", name);
if (luaL_getmetafield(L, 1, buf)) {
lua_pushvalue(L, 1); // push self
lua_call(L, 1, 1); // call the getter
return 1; // result of the getter
}
return 0; // nil
}
int luaopen_yourlib (lua_State* L) {
luaL_newmetatable(L, "yourlib");
lua_pushcfunction(L, &yourAttrGetter);
lua_setfield(L, -2, "__index");
// register your other getters here using lua_pushcfunction and lua_setfield
return 1;
}
EDIT: I updated the code to give a possible solution supporting both methods and attributes. It works as follows when writing object.attr in lua:
object:attr(...)
The advantage of my approach is that you can access attributes both in a direct style (òbject.attr) and by a getter method style (
object:getattr()`).
You can of course do the same for setters in __newindex, and/or use a more sophisticated approach to find a getter by trying different names (get_attr, getAttr, isattr or isAttr, etc.).
However, it's probably not the most optimized solution. I suggest you to have a look at well known binders/wrappers implementation, such as SOL2 or LUAA (they are both in C++, not C), to see how they do it, certainly better than me.