lua: how can i get the raw string in __tostring metamethod? - lua

code below:
local t = {}
setmetatable(t, {__tostring = function(self) return 'MyTable is: '..tostring(self) end})
print(t)
running the code will cause error: "C stack overflow". Because in __tostring metamethod, tostring(self) will invoke the __tostring metamethod, that's a dead loop.
Is there a way to get the raw string of the value "t"?

To do what you're trying to do from Lua, you basically have to unset the metatable from the main table, then call tostring on it, then set the metatable back. Like this:
setmetatable(t, {__tostring = function(self)
local temp = getmetatable(self)
setmetatable(self, nil)
local ret = 'MyTable is: ' .. tostring(self)
setmetatable(self, temp)
return ret
end,
})
Also, note that the __tostring metafunction is supposed to return the string, not merely print it.

Related

Why doesn't the __call metamethod work in this Lua class?

in the code below, i set up a __call metafunction which, in theory, should allow me to call the table as a function and invoke the constructor, instead of using test.new()
test = {}
function test:new()
self = {}
setmetatable(self, self)
--- private properties
local str = "hello world"
-- public properties
self.__index = self
self.__call = function (cls, ...) print("Constructor called!") return cls.new(...) end
self.__tostring = function() return("__tostring: "..str) end
self.tostring = function() return("self:tstring(): "..str) end
return self
end
local t = test:new()
print(t) -- __tostring overload works
print(tostring(t)) -- again, __tostring working as expected
print(t:tostring()) -- a public call, which works
t = test() -- the __call metamethod should invoke the constructor test:new()
output:
> __tostring: hello world
> __tostring: hello world
> self.tostring(): hello world
> error: attempt to call global `test` (a table value) (x1)
(i'm using metatable(self, self) because i read somewhere it produces less overhead when creating new instances of the class. also it's quite clean-looking. it may also be where i'm getting unstuck).
You're setting the __call metamethod on the wrong table - the self table - rather than the test table. The fix is trivial:
test.__call = function(cls, ...) print("Constructor called!") return cls.new(...) end
setmetatable(test, test)
After this, test(...) will be equivalent to test.new(...).
That said, your current code needs a refactoring / rewrite; you overwrite the implicit self parameter in test:new, build the metatable on each constructor call, and don't even use test as a metatable! I suggest moving methods like tostring to test and setting the metatable of self to a metatable that has __index = test. I'd also suggest separating metatables and tables in general. I'd get rid of upvalue-based private variables for now as they require you to use closures, which practically gets rid of the metatable benefit of not having to duplicate the functions per object. This is how I'd simplify your code:
local test = setmetatable({}, {__call = function (cls, ...) return cls.new(...) end})
local test_metatable = {__index = test}
function test.new()
local self = setmetatable({}, test_metatable)
self._str = "hello world" -- private by convention
return self
end
function test_metatable:__tostring()
return "__tostring: " .. self._str
end
function test:tostring()
return "self:tstring(): " .. self._str
end
If you like, you can merge test and test_metatable; I prefer to keep them separated however.

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.

How can I change every index into a table using a metatable?

I'm trying to write a metatable so that all indexes into the table are shifted up one position (i.e. t[i] should return t[i+1]). I need to do this because the table is defined using index 1 as the first element, but I have to interface with a program that uses index 0 as the first element. Since reading Programming in Lua, I think that I can accomplish what I want with a proxy table, but I can't seem to get it working. So far, I have this:
t = {"foo", "bar"}
local _t = t
t = {}
local mt = {
__index = function(t, i)
return _t[i+1]
end
}
setmetatable(t, mt)
However, this does not create the expected result. In fact, it doesn't return any values at all (every lookup is nil). Is there a better way to do this, or am I just missing something?
t = {"foo", "bar"}
local _t = t
t = {}
local mt = {
__index = function(t, i)
return _t[i+1]
end
}
setmetatable(t, mt)
print(t[0])
outputs "foo" for me when run here: http://www.lua.org/cgi-bin/demo

Resources