lua metatable registration from c question - lua

Hello I have the following bit of code which seems to work, but I'm not sure why - I've built a testclass as follows
class testclass {
int ivalue;
public:
int getivalue();
void setivalue(int &v);
};
and then registered the testclass (bits left out for the actual functions but they're pretty basic). It's the registration of the metatables I'm not following. (etivalue and setivalue are c functions that call the class functions of the same name)
static const struct luaL_Reg arraylib_f [] = {
{"new", new_testclass},
{NULL, NULL}
};
static const struct luaL_Reg arraylib_m [] = {
{"set", setivalue},
{"get", getivalue},
{NULL, NULL}
};
int luaopen_testclass (lua_State *L) {
luaL_newmetatable(L, "LuaBook.testclass");
lua_pushvalue(L, -1); /* duplicates the metatable */
lua_setfield(L, -2, "__index");
luaL_register(L, NULL, arraylib_m);
luaL_register(L, "testclass", arraylib_f);
return 1;
}
The bit I don't understand is I'm adding the functions to the __index for the metatable but
when I run
a = testclass.new()
a:set(10)
print(a:get())
Then it works as expected. The bit I don't understand is why the set is being called when I think I've loaded it in the __index metatable? Is that what I've done or something else?
tia

int luaopen_testclass (lua_State *L) {
luaL_newmetatable(L, "LuaBook.testclass"); //leaves new metatable on the stack
lua_pushvalue(L, -1); // there are two 'copies' of the metatable on the stack
lua_setfield(L, -2, "__index"); // pop one of those copies and assign it to
// __index field od the 1st metatable
luaL_register(L, NULL, arraylib_m); // register functions in the metatable
luaL_register(L, "testclass", arraylib_f);
return 1;
}
That code is equivalent to the example Lua code:
metatable = {}
metatable.__index = metatable
metatable.set = function() --[[ stuff --]] end
metatable.get = function() --[[ stuff --]] end
I assume that 'new_testclass' C function sets the metatable "LuaBook.testclass" for the returned table.
In your code you dont add functions to the metatable __index field. You assign pointer to metatable to that metatable's field named __index, and you register set and get functions to it.
Now, if you set that metatable to the value returned from 'new_testclass' function (which I assume you do) - lets call that value 'foo', and you call foo:set(10), than Lua:
checks that there is no such field as 'set' in 'foo'
sees that 'foo' has a metatable
looks at that metatable's __index field - sees it's a table
checks if that table assigned to __index field has a field 'set' and it's value is a function
calls 'set' method passing 'foo' as self parameter
I hope that this will help you figure out whats going on here.

If I understand your question, you are asking how the set() get() get invoked through the __index metamethod.
The code can be expressed in pure lua:
local o = {}
function o.get(self)
return self.ivalue
end
function o.set(self, val)
self.ivalue = val
end
a = {}
mt = {
__index = function(t, n)
return o[n]
end
}
setmetatable(a, mt)
print(a:get())
a:set(10)
print(a:get())
results:
nil
10
In this example the mt table is set as the a table's metatable. The __index metamethod is invoked for both get and set since neither get or set currently exist in table a.
If this example is changed instead to this:
local o = {}
function o.get(self)
return self.ivalue
end
function o.set(self, val)
self.ivalue = val
end
a = {}
function a.get(self)
print('here')
return self.ivalue
end
mt = {
__index = function(t, n)
return o[n]
end
}
setmetatable(a, mt)
print(a:get())
a:set(10)
print(a:get())
results:
here
nil
here
10
In this case the __index metamethod is NOT invoked for get() since a get index already exists in the a table.
Many interesting constructs can be created using metamethods, once you understand how they work. I suggest reading 13.4.1 - The __index Metamethod in PiL and work through a few more examples. All of the above can also be done from the c api.

Related

Lua Get/Set Metatable

local ents = {
GetLocalPlayer = function()
local tbl = {
localplayer = {"Ava", "1", {213,234,234}},
GetIndex = function(self)
return self.localplayer[2]
end,
}
setmetatable(tbl, getmetatable(tbl.localplayer))
return tbl
end
}
local function main()
print(ents.GetLocalPlayer()[2])
end
main() print returns nil. If I was to do ents.GetLocalPlayer():GetIndex() however, it returns 1.
The idea is to have the default return value to be localplayer if I don't do things such as GetIndex()
A table has no default metatable, which is why your getmetatable call returns nil. In order to do anything, the second argument to setmetatable must be a table that has at least one metamethod. (__index is the most common metamethod.)
The solution is to change getmetatable(tbl.localplayer) to {__index = tbl.localplayer}.

Lua: Workaround for boolean conversion of a class variable when enclosed in parentheses

In the below code, can anyone explain why does t1:print() works but (t1):print fails. I am attempting to make something like (t1 * 3):print() work without using an intermediate variable.
function classTestTable(members)
members = members or {}
local mt = {
__metatable = members;
__index = members;
}
function mt.print(self)
print("something")
end
return mt
end
TestTable = {}
TestTable_mt = ClassTestTable(TestTable)
function TestTable:new()
return setmetatable({targ1 = 1}, TestTable_mt )
end
TestTable t1 = TestTable:new()
t1:print() -- works fine.
(t1):print() -- fails with error "attempt to call a boolean value"
Lua expressions can extend over multiple lines.
print
(3)
Will print 3
So
t1:print()
(t1):print()
actually is equivalent to
t1:print()(t1):print()
or
local a = t1:print()
local b = a(t1)
b:print()
So you're calling the return value of t1:print()
To avoid that follow Egors advice and separate both statements with a semicolon.
t1:print();(t1):print()

Lua/Luajit: Indexing and named method at the same time?

The Lua PIL and Luajit FFI tutorial gave two usages of __index in the metatable.
One is for indexing like obj[123], e.g.,
__index = function (self, k) return self._data+(k-self._lower)
The other usage is to define named methods, as given in the tutorial,
__index = { area = function(a) return a.x*a.x + a.y*a.y end, },
We can then make function call like obj:area().
Can I do both at the same time, e.g., direct indexing and named methods?
The answer, as is usual for extra-interesting code in Lua, is more metatables.
When your __index metamethod is actually a table, Lua simply does a standard table access on the given table. This means you can set a metatable on your metatable. Then you can set an __index metamethod on this "meta-metatable".
foo = function()
print("foo")
end
bar = function(_, key)
return function()
print(string.format("bar: %s", key))
end
end
mmt = { __index = bar }
mti = { foo = foo }
mt = { __index = mti }
t = {}
setmetatable(mti, mmt)
setmetatable(t, mt)
t.foo() -- prints: "foo"
t.bar() -- prints: "bar: bar"
t.baz() -- prints: "bar: baz"
With this, when you try to access a field which is absent in both tables, lua will first try to access the top-level table which will access the first metatable which will then call your metamethod in the second metatable.
There is also another, possibly more straight forward, answer: Use your __index metamethod to check another table for named fields:
foo = function()
print("foo")
end
f = { foo = foo }
bar = function(_, key)
if f[key] then return f[key] end
return function()
print(string.format("bar: %s", key))
end
end
mt = { __index = bar }
t = {}
setmetatable(t, mt)
t.foo() -- prints: "foo"
t.bar() -- prints: "bar: bar"
t.baz() -- prints: "bar: baz"
Tested on Lua 5.3.

Equality operator on mixed types in Lua

In chapter 13.2 of Programming in Lua it's stated that
Unlike arithmetic metamethods, relational metamethods do not support mixed types.
and at the same time
Lua calls the equality metamethod only when the two objects being compared share this metamethod
So I'm implementing my library in C and want to be able to support behavior like
a = A()
b = B()
a == b
by providing
static const struct luaL_Reg mylib_A[] =
{
{ "__eq", my_equal }
, <more stuff>
, { NULL, NULL }
};
and
static const struct luaL_Reg mylib_B[] =
{
{ "__eq", my_equal }
, <more stuff>
, { NULL, NULL }
};
Which doesn't seem to work, is there a workaround for this?
Note: my_equal is able to handle both userdata of type A and type B in any of it's arguments
UPDATE:
Metatables registration:
luaL_newmetatable(lua, "B");
lua_pushvalue(lua, -1);
lua_setfield(lua, -2, "__index");
luaL_register(lua, NULL, mylib_B);
luaL_newmetatable(lua, "A");
lua_pushvalue(lua, -1);
lua_setfield(lua, -2, "__index");
luaL_register(lua, NULL, mylib_A);
luaL_register(lua, "mylib", mylib); -- where mylib is a bunch of static functions
Application code:
require 'mylib'
a = mylib.new_A()
b = mylib.new_B()
a == b -- __eq is not called
EDIT: Also see whoever's answer which has a particular caveat with regards to implementing __eq in the C API.
The __eq metamethod belongs in your metatable, not in the __index table.
In lua:
function my_equal(x,y)
return x.value == y.value
end
A = {} -- luaL_newmetatable(lua, "A");
A.__eq = my_equal
function new_A(value)
local a = { value = value }
return setmetatable(a, A)
end
B = {} -- luaL_newmetatable(lua, "B");
B.__eq = my_equal
function new_B(value)
local b = { value = value }
return setmetatable(b, B)
end
a = new_A()
b = new_B()
print(a == b) -- __eq is called, result is true
a.value = 5
print(a == b) -- __eq is called, result is false
What you have done is this:
myLib_A = {}
myLib_A.__eq = my_equal
A = {} -- luaL_newmetatable(lua, "A");
A.__index = myLib_A
Note that __eq is not in A's metatable, it's on a totally separate table that you just be happen to be using in a different, unrelated metamethod (__index). Lua is not going to look there when trying to resolve the equality operator for a.
The Lua manual explains this in detail:
"eq": the == operation. The function getcomphandler defines how Lua chooses a metamethod for comparison operators. A metamethod only is selected when both objects being compared have the same type and the same metamethod for the selected operation.
function getcomphandler (op1, op2, event)
if type(op1) ~= type(op2) then return nil end
local mm1 = metatable(op1)[event]
local mm2 = metatable(op2)[event]
if mm1 == mm2 then return mm1 else return nil end
end
The "eq" event is defined as follows:
function eq_event (op1, op2)
if type(op1) ~= type(op2) then -- different types?
return false -- different objects
end
if op1 == op2 then -- primitive equal?
return true -- objects are equal
end
-- try metamethod
local h = getcomphandler(op1, op2, "__eq")
if h then
return (h(op1, op2))
else
return false
end
end
So when Lua encounters result = a == b, it's going to do the following (this is done in C, Lua used as pseudocode here):
-- Are the operands are the same type? In our case they are both tables:
if type(a) ~= type(b) then
return false
end
-- Are the operands the same object? This comparison is done in C code, so
-- it's not going to reinvoke the equality operator.
if a ~= b then
return false
end
-- Do the operands have the same `__eq` metamethod?
local mm1 = getmetatable(a).__eq
local mm2 = getmetatable(b).__eq
if mm1 ~= mm2 then
return false
end
-- Call the `__eq` metamethod for the left operand (same as the right, doesn't really matter)
return mm1(a,b)
You can see there's no path here that results in resolve a.__eq, which would resolve to myLib_A through your __index metamethod.
For all others who would be facing the same problem:
This was the only way how I made Lua aware of my_equal being the exact same function from the point of Lua in both cases and consequently returning correct operator from getcomphandler. Registering it in any other way (including separate luaL_Reg) doesn't work due to my_equal being saved under different closures upon luaL_register, which I here avoid by creating the closure only once.
// we'll copy it further to ensure lua knows that it's the same function
lua_pushcfunction(lua, my_equal);
luaL_newmetatable(lua, "B");
// removed __index for clarity
luaL_register(lua, NULL, mylib_B);
// Now we register __eq separately
lua_pushstring(lua, "__eq");
lua_pushvalue(lua, -3); // Copy my_equal on top
lua_settable(lua, -3); // Register it under B metatable
lua_pop(lua, 1);
luaL_newmetatable(lua, "A");
// removed __index for clarity
luaL_register(lua, NULL, mylib_A);
lua_pushstring(lua, "__eq");
lua_pushvalue(lua, -3); // Copy my_equal on top
lua_settable(lua, -3); // Register it under A metatable
luaL_register(lua, "mylib", mylib); // where mylib is a bunch of static functions

Hiding a Lua metatable and only exposing an object's attributes

How do you create a Lua object that only exposes its attributes and not its methods? For example:
local obj = {
attr1 = 1,
attr2 = 2,
print = function(...)
print("obj print: ", ...)
end,
}
Produces:
> for k,v in pairs(obj) do print(k, v) end
attr1 1
attr2 2
print function: 0x7ffe1240a310
Also, is it possible to not use the colon syntax for OOP in Lua? I don't need inheritance, polymorphism, only encapsulation and privacy.
I started out with the above question and after chasing down the rabbit hole, I was surprised by the limited number of examples, lack of examples for the various metamethods (i.e. __ipairs, __pairs, __len), and how few Lua 5.2 resources there were on the subject.
Lua can do OOP, but IMO the way that OOP is prescribed is a disservice to the language and community (i.e. in such a way as to support polymorphism, multiple inheritance, etc). There are very few reasons to use most of Lua's OOP features for most problems. It doesn't necessarily mean there's a fork in the road either (e.g. in order to support polymorphism there's nothing that says you have to use the colon syntax - you can fold the literature's described techniques in to the closure-based OOP method).
I appreciate that there are lots of ways to do OOP in Lua, but it's irritating to have there be different syntax for object attributes versus object methods (e.g. obj.attr1 vs obj:getAttr() vs obj.method() vs obj:method()). I want a single, unified API to communicate internally and externally. To that end, PiL 16.4's section on Privacy is a fantastic start, but it's an incomplete example that I hope to remedy with this answer.
The following example code:
emulates a class's namespace MyObject = {} and saves the object constructor as MyObject.new()
hides all of the details of the objects inner workings so that a user of an object only sees a pure table (see setmetatable() and __metatable)
uses closures for information hiding (see Lua Pil 16.4 and Object Benchmark Tests)
prevents modification of the object (see __newindex)
allows for methods to be intercepted (see __index)
lets you get a list of all of the functions and attributes (see the 'key' attribute in __index)
looks, acts, walks, and talks like a normal Lua table (see __pairs, __len, __ipairs)
looks like a string when it needs to (see __tostring)
works with Lua 5.2
Here's the code to construct a new MyObject (this could be a standalone function, it doesn't need to be stored in the MyObject table - there is absolutely nothing that ties obj once its created back to MyObject.new(), this is only done for familiarity and out of convention):
MyObject = {}
MyObject.new = function(name)
local objectName = name
-- A table of the attributes we want exposed
local attrs = {
attr1 = 123,
}
-- A table of the object's methods (note the comma on "end,")
local methods = {
method1 = function()
print("\tmethod1")
end,
print = function(...)
print("MyObject.print(): ", ...)
end,
-- Support the less than desirable colon syntax
printOOP = function(self, ...)
print("MyObject:printOOP(): ", ...)
end,
}
-- Another style for adding methods to the object (I prefer the former
-- because it's easier to copy/paste function()'s around)
function methods.addAttr(k, v)
attrs[k] = v
print("\taddAttr: adding a new attr: " .. k .. "=\"" .. v .. "\"")
end
-- The metatable used to customize the behavior of the table returned by new()
local mt = {
-- Look up nonexistent keys in the attrs table. Create a special case for the 'keys' index
__index = function(t, k)
v = rawget(attrs, k)
if v then
print("INFO: Successfully found a value for key \"" .. k .. "\"")
return v
end
-- 'keys' is a union of the methods and attrs
if k == 'keys' then
local ks = {}
for k,v in next, attrs, nil do
ks[k] = 'attr'
end
for k,v in next, methods, nil do
ks[k] = 'func'
end
return ks
else
print("WARN: Looking up nonexistant key \"" .. k .. "\"")
end
end,
__ipairs = function()
local function iter(a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
return iter, attrs, 0
end,
__len = function(t)
local count = 0
for _ in pairs(attrs) do count = count + 1 end
return count
end,
__metatable = {},
__newindex = function(t, k, v)
if rawget(attrs, k) then
print("INFO: Successfully set " .. k .. "=\"" .. v .. "\"")
rawset(attrs, k, v)
else
print("ERROR: Ignoring new key/value pair " .. k .. "=\"" .. v .. "\"")
end
end,
__pairs = function(t, k, v) return next, attrs, nil end,
__tostring = function(t) return objectName .. "[" .. tostring(#t) .. "]" end,
}
setmetatable(methods, mt)
return methods
end
And now the usage:
-- Create the object
local obj = MyObject.new("my object's name")
print("Iterating over all indexes in obj:")
for k,v in pairs(obj) do print('', k, v) end
print()
print("obj has a visibly empty metatable because of the empty __metatable:")
for k,v in pairs(getmetatable(obj)) do print('', k, v) end
print()
print("Accessing a valid attribute")
obj.print(obj.attr1)
obj.attr1 = 72
obj.print(obj.attr1)
print()
print("Accessing and setting unknown indexes:")
print(obj.asdf)
obj.qwer = 123
print(obj.qwer)
print()
print("Use the print and printOOP methods:")
obj.print("Length: " .. #obj)
obj:printOOP("Length: " .. #obj) -- Despite being a PITA, this nasty calling convention is still supported
print("Iterate over all 'keys':")
for k,v in pairs(obj.keys) do print('', k, v) end
print()
print("Number of attributes: " .. #obj)
obj.addAttr("goosfraba", "Satoshi Nakamoto")
print("Number of attributes: " .. #obj)
print()
print("Iterate over all keys a second time:")
for k,v in pairs(obj.keys) do print('', k, v) end
print()
obj.addAttr(1, "value 1 for ipairs to iterate over")
obj.addAttr(2, "value 2 for ipairs to iterate over")
obj.addAttr(3, "value 3 for ipairs to iterate over")
obj.print("ipairs:")
for k,v in ipairs(obj) do print(k, v) end
print("Number of attributes: " .. #obj)
print("The object as a string:", obj)
Which produces the expected - and poorly formatted - output:
Iterating over all indexes in obj:
attr1 123
obj has a visibly empty metatable because of the empty __metatable:
Accessing a valid attribute
INFO: Successfully found a value for key "attr1"
MyObject.print(): 123
INFO: Successfully set attr1="72"
INFO: Successfully found a value for key "attr1"
MyObject.print(): 72
Accessing and setting unknown indexes:
WARN: Looking up nonexistant key "asdf"
nil
ERROR: Ignoring new key/value pair qwer="123"
WARN: Looking up nonexistant key "qwer"
nil
Use the print and printOOP methods:
MyObject.print(): Length: 1
MyObject.printOOP(): Length: 1
Iterate over all 'keys':
addAttr func
method1 func
print func
attr1 attr
printOOP func
Number of attributes: 1
addAttr: adding a new attr: goosfraba="Satoshi Nakamoto"
Number of attributes: 2
Iterate over all keys a second time:
addAttr func
method1 func
print func
printOOP func
goosfraba attr
attr1 attr
addAttr: adding a new attr: 1="value 1 for ipairs to iterate over"
addAttr: adding a new attr: 2="value 2 for ipairs to iterate over"
addAttr: adding a new attr: 3="value 3 for ipairs to iterate over"
MyObject.print(): ipairs:
1 value 1 for ipairs to iterate over
2 value 2 for ipairs to iterate over
3 value 3 for ipairs to iterate over
Number of attributes: 5
The object as a string: my object's name[5]
Using OOP + closures is very convenient when embedding Lua as a facade or documenting an API.
Lua OOP can also be very, very clean and elegant (this is subjective, but there aren't any rules with this style - you always use a . to access either an attribute or a method)
Having an object behave exactly like a table is VERY, VERY useful for scripting and interrogating the state of a program
Is extremely useful when operating in a sandbox
This style does consume slightly more memory per object, but for most situations this isn't a concern. Factoring out the metatable for reuse would address this, though the example code above doesn't.
A final thought. Lua OOP is actually very nice once you dismiss most of the examples in the literature. I'm not saying the literature is bad, btw (that couldn't be further from the truth!), but the set of sample examples in PiL and other online resources lead you to using only the colon syntax (i.e. the first argument to all functions is self instead of using a closure or upvalue to retain a reference to self).
Hopefully this is a useful, more complete example.
Update (2013-10-08): There is one notable drawback to the closure-based OOP style detailed above (I still think the style is worth the overhead, but I digress): each instance must have its own closure. While this is obvious in the above lua version, this becomes slightly problematic when dealing with things on the C-side.
Assume we're talking about the above closure style from the C-side from here on out. The common case on the C side is to create a userdata via lua_newuserdata() object and attach a metatable to the userdata via lua_setmetatable(). On face value this doesn't appear like a problem until you realize that methods in your metatable require an upvalue of the userdata.
using FuncArray = std::vector<const ::luaL_Reg>;
static const FuncArray funcs = {
{ "__tostring", LI_MyType__tostring },
};
int LC_MyType_newInstance(lua_State* L) {
auto userdata = static_cast<MyType*>(lua_newuserdata(L, sizeof(MyType)));
new(userdata) MyType();
// Create the metatable
lua_createtable(L, 0, funcs.size()); // |userdata|table|
lua_pushvalue(L, -2); // |userdata|table|userdata|
luaL_setfuncs(L, funcs.data(), 1); // |userdata|table|
lua_setmetatable(L, -2); // |userdata|
return 1;
}
int LI_MyType__tostring(lua_State* L) {
// NOTE: Blindly assume that upvalue 1 is my userdata
const auto n = lua_upvalueindex(1);
lua_pushvalue(L, n); // |userdata|
auto myTypeInst = static_cast<MyType*>(lua_touserdata(L, -1));
lua_pushstring(L, myTypeInst->str()); // |userdata|string|
return 1; // |userdata|string|
}
Note how the table created with lua_createtable() didn't get associated with a metatable name the same as if you would have registered the metatable with luaL_getmetatable()? This is 100% a-okay because these values are completely inaccessible outside of the closure, but it does mean that luaL_getmetatable() can't be used to look up a particular userdata's type. Similarly, this also means that luaL_checkudata() and luaL_testudata() are also off limits.
The bottom line is that upvalues (such as userdata above) are associated with function calls (e.g. LI_MyType__tostring) and are not associated with the userdata itself. As of now, I'm not aware of a way in which you can associate an upvalue with a value such that it becomes possible to share a metatable across instances.
UPDATE (2013-10-14) I'm including a small example below that uses a registered metatable (luaL_newmetatable()) and also lua_setuservalue()/lua_getuservalue() for a userdata's "attributes and methods". Also adding random comments that have been the source of bugs/hotness that I've had to hunt down in the past. Also threw in a C++11 trick to help with __index.
namespace {
using FuncArray = std::vector<const ::luaL_Reg>;
static const std::string MYTYPE_INSTANCE_METAMETHODS{"goozfraba"}; // I use a UUID here
static const FuncArray MyType_Instnace_Metamethods = {
{ "__tostring", MyType_InstanceMethod__tostring },
{ "__index", MyType_InstanceMethod__index },
{ nullptr, nullptr }, // reserve space for __metatable
{ nullptr, nullptr } // sentinel
};
static const FuncArray MyType_Instnace_methods = {
{ "fooAttr", MyType_InstanceMethod_fooAttr },
{ "barMethod", MyType_InstanceMethod_barMethod },
{ nullptr, nullptr } // sentinel
};
// Must be kept alpha sorted
static const std::vector<const std::string> MyType_Instance___attrWhitelist = {
"fooAttr",
};
static int MyType_ClassMethod_newInstance(lua_State* L) {
// You can also use an empty allocation as a placeholder userdata object
// (e.g. lua_newuserdata(L, 0);)
auto userdata = static_cast<MyType*>(lua_newuserdata(L, sizeof(MyType)));
new(userdata) MyType(); // Placement new() FTW
// Use luaL_newmetatable() since all metamethods receive userdata as 1st arg
if (luaL_newmetatable(L, MYTYPE_INSTANCE_METAMETHODS.c_str())) { // |userdata|metatable|
luaL_setfuncs(L, MyType_Instnace_Metamethods.data(), 0); // |userdata|metatable|
// Prevent examining the object: getmetatable(MyType.new()) == empty table
lua_pushliteral(L, "__metatable"); // |userdata|metatable|literal|
lua_createtable(L, 0, 0); // |userdata|metatable|literal|table|
lua_rawset(L, -3); // |userdata|metatable|
}
lua_setmetatable(L, -2); // |userdata|
// Create the attribute/method table and populate with one upvalue, the userdata
lua_createtable(L, 0, funcs.size()); // |userdata|table|
lua_pushvalue(L, -2); // |userdata|table|userdata|
luaL_setfuncs(L, funcs.data(), 1); // |userdata|table|
// Set an attribute that can only be accessed via object's fooAttr, stored in key "fooAttribute"
lua_pushliteral(L, "foo's value is hidden in the attribute table"); // |userdata|table|literal|
lua_setfield(L, -2, "fooAttribute"); // |userdata|table|
// Make the attribute table the uservalue for the userdata
lua_setuserdata(L, -2); // |userdata|
return 1;
}
static int MyType_InstanceMethod__tostring(lua_State* L) {
// Since we're using closures, we can assume userdata is the first value on the stack.
// You can't make this assumption when using metatables, only closures.
luaL_checkudata(L, 1, MYTYPE_INSTANCE_METAMETHODS.c_str()); // Test anyway
auto myTypeInst = static_cast<MyType*>(lua_touserdata(L, 1));
lua_pushstring(L, myTypeInst->str()); // |userdata|string|
return 1; // |userdata|string|
}
static int MyType_InstanceMethod__index(lua_State* L) {
lua_getuservalue(L, -2); // |userdata|key|attrTable|
lua_pushvalue(L, -2); // |userdata|key|attrTable|key|
lua_rawget(L, -2); // |userdata|key|attrTable|value|
if (lua_isnil(L, -1)) { // |userdata|key|attrTable|value?|
return 1; // |userdata|key|attrTable|nil|
}
// Call cfunctions when whitelisted, otherwise the caller has to call the
// function.
if (lua_type(L, -1) == LUA_TFUNCTION) {
std::size_t keyLen = 0;
const char* keyCp = ::lua_tolstring(L, -3, &keyLen);
std::string key(keyCp, keyLen);
if (std::binary_search(MyType_Instance___attrWhitelist.cbegin(),
MyType_Instance___attrWhitelist.cend(), key))
{
lua_call(L, 0, 1);
}
}
return 1;
}
static int MyType_InstanceMethod_fooAttr(lua_State* L) {
// Push the uservalue on to the stack from fooAttr's closure (upvalue 1)
lua_pushvalue(L, lua_upvalueindex(1)); // |userdata|
lua_getuservalue(L, -1); // |userdata|attrTable|
// I haven't benchmarked whether lua_pushliteral() + lua_rawget()
// is faster than lua_getfield() - (two lua interpreter locks vs one lock + test for
// metamethods).
lua_pushliteral(L, "fooAttribute"); // |userdata|attrTable|literal|
lua_rawget(L, -2); // |userdata|attrTable|value|
return 1;
}
static int MyType_InstanceMethod_barMethod(lua_State* L) {
// Push the uservalue on to the stack from barMethod's closure (upvalue 1)
lua_pushvalue(L, lua_upvalueindex(1)); // |userdata|
lua_getuservalue(L, -1); // |userdata|attrTable|
// Push a string to finish the example, not using userdata or attrTable this time
lua_pushliteral(L, "bar() was called!"); // |userdata|attrTable|literal|
return 1;
}
} // unnamed-namespace
The lua script side of things looks something like:
t = MyType.new()
print(typue(t)) --> "userdata"
print(t.foo) --> "foo's value is hidden in the attribute table"
print(t.bar) --> "function: 0x7fb560c07df0"
print(t.bar()) --> "bar() was called!"
how do you create a lua object that only exposes its attributes and not its methods?
If you don't expose methods in any way, you can't call them, right? Judging from your example, it sounds like what you really want is a way to iterate through the attributes of an object without seeing methods, which is fair.
The simplest approach is just to use a metatable, which puts the methods in a separate table:
-- create Point class
Point = {}
Point.__index = Point
function Point:report() print(self.x, self.y) end
-- create instance of Point
pt = setmetatable({x=10, y=20}, Point)
-- call method
pt:report() --> 10 20
-- iterate attributes
for k,v in pairs(pt) do print(k,v) end --> x 10 y 20
is it possible to not use the colon syntax for OOP in Lua?
You can use closures instead, but then pairs is going to see your methods.
function Point(x, y)
local self = { x=x, y=y}
function pt.report() print(self.x, self.y) end
return self
end
pt = Point(10,20)
pt.report() --> 10 20
for k,v in pairs(pt) do print(k,v) end --> x 10 y 20 report function: 7772112
You can fix the latter problem by just writing an iterator that shows only attributes:
function nextattribute(t, k)
local v
repeat
k,v = next(t, k)
if type(v) ~= 'function' then return k,v end
until k == nil
end
function attributes (t)
return nextattribute, t, nil
end
for k,v in attributes(pt) do print(k,v) end --> x 10 y 20
I don't need inheritance, polymorphism
You get polymorphism for free in Lua, without or without classes. If your zoo has a Lion, Zebra, Giraffe each of which can Eat() and want to pass them to the same Feed(animal) routine, in a statically typed OO languages you'd need to put Eat() in a common base class (e.g. Animal). Lua is dynamically typed and your Feed routine can be passed any object at all. All that matters is that the object you pass it has an Eat method.
This is sometimes called "duck typing": if it quacks like a duck and swims like a duck, it's a duck. As far as our Feed(animal) routine is concerned, if it Eats like an animal, it's an animal.
only encapsulation and privacy.
Then I think exposing data members while hiding methods is the opposite of what you want to do.

Resources