nesting metatables in lua - lua

In the code below it's my attempt to nest metatables on __index, but it's not working. What I want to do is if the value is t1 or t2 then return the associated value, otherwise call the function on the innermost __index. Is this possible?
so in the below x["hello"] can I return a value. I know I can just use a function on the outermost __index, but it seems I should be able to do this somehow using nested metatables.
tia.
x = { val = 3 } -- our object
mt = {
__index = {
t1 = 2,
t2 = 3,
__index = function (table, key)
print("here"..key)
return table.val
end
}
}
setmetatable(x, mt)
print(x["t1"]) -- prints 2
print(x.t2) -- prints 3
print(x["hello"]) -- prints nil???
This works but it seems like I could do it with metatables
x = { val = 3 } -- our object
mt1 = {
t1 = 2,
t2 = 3
}
mt = {
__index = function (table, key)
local a = mt1[key]
if a == nil then
print("here"..key)
a = "test"
end
return a
end
}
setmetatable(x, mt)
print(x["t1"])
print(x.t2)
print(x["hello"])

..and for anyone following along at home, here it is with nested metatables inline. Thanks for the tip Alexander that makes it a lot cleaner.
x = setmetatable(
{ val = 3 },
{
__index = setmetatable({
t1 = 2,
t2 = 3
},
{
__index = function (table, key)
print("here"..key)
return key
end
}
)
}
)
print(x["t1"])
print(x.t2)
print(x["hello"])

This works, but can I do it without declaring mt2?
x = { val = 3 } -- our object
mt2 = {
__index = function (table, key)
print("here"..key)
return key
end
}
mt = {
__index = {
t1 = 2,
t2 = 3
}
}
setmetatable(mt.__index, mt2)
setmetatable(x, mt)
print(x["t1"])
print(x.t2)
print(x["hello"])

Related

Table not working as attribute in my class

I have a situation like this:
TestClass = { param = { n = 5 } }
function TestClass:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function TestClass:update(n)
n = n or 1
self.param.n = self.param.n + n
end
The issue is that when I instantiate an object like: obj = TestClass:new() the update method doesn't update the value stored in the obj table but instead it changes the value inside the default table: TestClass
Is there a way to fix this? I already looked up the tutorial by the creators of lua but they say nothing about tables as attributes.
I think you should do it like this then:
function TestClass:new(o)
o = o or { param = { n = 5 } }
setmetatable(o, self)
self.__index = self
return o
end
I solved this and the problem was that tables are stored by reference, not value so the solution was to create the table inside the o object at the time of instancing (the TestClass:new() method).
To do this automatically and only for params that are not passed in I wrote this little function:
function setDefaults(o, def)
for k,v in pairs(def) do
o[k] = o[k] or v
end
end
And it is simply used like this:
function TestClass:new(o)
o = o or {}
setDefaults(o, {
param = { n = 5 }
})
setmetatable(o, self)
self.__index = self
return o
end
I also wanted to thank a lot u/JackMacWindowsLinux and Ivo Beckers for helping me find this solution.

How to call a library function by its name and setting it's parameters

I have a library functions defined like that in my C code :
static const struct luaL_reg SelSurfaceLib [] = {
{"CapabilityConst", CapabilityConst},
{"create", createsurface},
{NULL, NULL}
};
static const struct luaL_reg SelSurfaceM [] = {
{"Release", SurfaceRelease},
{"GetPosition", SurfaceGetPosition},
{"clone", SurfaceClone},
{"restore", SurfaceRestore},
{NULL, NULL}
};
void _include_SelSurface( lua_State *L ){
luaL_newmetatable(L, "SelSurface");
lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3); /* metatable.__index = metatable */
luaL_register(L, NULL, SelSurfaceM);
luaL_register(L,"SelSurface", SelSurfaceLib);
}
And I can use it with this Lua code :
local sub = SelSurface.create()
local x,y = sub:GetPosition()
...
Now, my difficult issue : I'm using follwing code
function HLSubSurface(parent_surface, x,y,sx,sy )
local self = {}
-- fields
local srf = parent_surface:SubSurface( x,y, sx,sy )
-- methods
local meta = {
__index = function (t,k)
local tbl = getmetatable(srf)
return tbl[k]
end
}
setmetatable( self, meta )
return self
end
and my main code is :
sub = HLSubSurface( parent, 0,0, 160,320 )
x,y = sub.GetPosition()
but it's failing
./HDB/80_LeftBar.lua:19: bad argument #1 to 'SetFont' (SelSurface expected, got userdata)
It's because I need to provide srf as 1st argument to GetPosition() function ... but I strictly duno how to do that :(
I don't want to do it when calling GetPosition(),
x,y = sub.GetPosition()
but I'm looking for a way to do it transparently by setting it in meta's function.
In other words, I would like to have HLSubSurface object to inherit methods from SubSurface.
Any idea ?
Thanks.
Laurent
function HLSubSurface(parent_surface, x, y, sx, sy)
local srf = parent_surface:SubSurface(x, y, sx, sy)
local self = {
-- fields
....
}
setmetatable(self, {__index =
function (obj, key)
local parent_field
local parent_fields = getmetatable(srf).__index
if type(parent_fields) == "function" then
parent_field = parent_fields(key)
elseif parent_fields then
parent_field = parent_fields[key]
end
if type(parent_field) == "function" then
return
function(o, ...)
if o == obj then
return parent_field(srf, ...)
else
return parent_field(o, ...)
end
end
else
return parent_field
end
end
})
return self
end
And your main code would be:
sub = HLSubSurface( parent, 0,0, 160,320 )
x,y = sub:GetPosition()

Point table to another table in Lua

is there any way to point table to another table? for example:
local a = {}
local b = {}
a.name = "Josh"
print(a.name) -- Prints 'Josh'
print(b.name) -- Prints 'Josh' aswell
a.name = "I don't have a name"
print(a.name) -- Print 'I don't have a name'
print(b.name) -- Prints 'I don't have a name' aswell
I hope you get my point.. thanks
EDIT:
Okay, so here is the idea:
I am making a dinamic function that is like this
local table = { 1, 2, "hey" }
function drawimage(name, posx, posy referencetable)
_tabledata[name] = { posx = posx, posy = posy, reference = {}}
setmetatable(_tabledata[name].reference, { __index = referencetable })
end
drawimage("Header", 0, 50, table)
All good and fine, values work and we are all happy.. the problem occurs when the reference table changes it's value in this way
local data = { 123123, 545454, "heyou" } -- Data is sent from another script via a trigger
table = data
Since I am not updating it by an index (ie: table[1] = 9999) reference variable is 'unsynced' with the real one, I hope you understand :)
EDIT2:
Okay here is a self working example of my main problem
local maintable = { "Stack", "Overflow" }
local maintablecopy = {}
maintablecopy = maintable
print("maintable[1] = " ..maintable[1]) -- Prints Stack
print("maintable[2] = " ..maintable[2]) -- Prints Overflow
print("")
print("maintablecopy[1] = " ..maintablecopy[1]) -- Prints Stack
print("maintablecopy[2] = " ..maintablecopy[2]) -- Prints Overflow
print("")
print("Changing values..")
local newdata = { "Hello", "World" }
maintable = newdata
print("")
print("maintable[1] = " ..maintable[1]) -- Prints Hello
print("maintable[2] = " ..maintable[2]) -- Prints World
print("")
print("maintablecopy[1] = " ..maintablecopy[1]) -- Prints Stack -- PROBLEM
print("maintablecopy[2] = " ..maintablecopy[2]) -- Prints Overflow -- PROBLEM
print("Using setmetatable..")
maintable = { "Stack", "Overflow" }
maintablecopy = {}
setmetatable(maintablecopy, { __index = maintable })
print("maintable[1] = " ..maintable[1]) -- Prints Stack
print("maintable[2] = " ..maintable[2]) -- Prints Overflow
print("")
print("maintablecopy[1] = " ..maintablecopy[1]) -- Prints Stack
print("maintablecopy[2] = " ..maintablecopy[2]) -- Prints Overflow
print("")
print("Changing values..")
local newdata = { "Hello", "World" }
maintable = newdata
print("")
print("maintable[1] = " ..maintable[1]) -- Prints Hello
print("maintable[2] = " ..maintable[2]) -- Prints World
print("")
print("maintablecopy[1] = " ..maintablecopy[1]) -- Prints Stack -- PROBLEM
print("maintablecopy[2] = " ..maintablecopy[2]) -- Prints Overflow -- PROBLEM
Why I cannot directly point it to the table when the variable updates? becouse I have 20 tables to update, it would be easier to do this
local _dynamics = {}
local tbl1 = { "Hey", 8787 }
local tbl2 = { 123, "There" }
local tbl3 = { "You", 1111 }
function dynamicFunction(name, posx, posy, textsize, reference)
_dynamics[name] = { posx = posx, posy = posy, textsize = textsize, reference = reference }
end
dynamicFunction("first", 0, 0, 5, tbl1)
dynamicFunction("second", 0, 0, 5, tbl2)
dynamicFunction("third", 0, 0, 5, tbl3)
for key in pairs(_dynamics) do
local inf = _dynamics[key]
for i = 1, #inf.reference do
print(inf.reference[i])
if i == #inf.reference then
print("")
end
end
end
print("")
print("")
tbl1 = { "aaaaa", "bbbbbbbbbb" }
tbl2 = { "ccccccccccc", "ttttttttttt" }
tbl3 = { "rrrrrrr", "yyyyyyyyyyy" }
for key in pairs(_dynamics) do
local inf = _dynamics[key]
for i = 1, #inf.reference do
print(inf.reference[i])
if i == #inf.reference then
print("")
end
end
end
print("Values should get updated on the reference variable, but it doesn't.. this would save me to do a check for every single variable")
You can run it on http://www.compileonline.com/execute_lua_online.php to see yourself what I mean.
Sorry if it's a mess but my english is not the best :D
You want the __index metamethod:
local a = { name="Josh" }
local b = {}
print(a.name) --> Josh
print(b.name) --> nil
setmetatable(b,{__index=a})
print(b.name) --> Josh
a.name = "Gilligan"
print(a.name) --> Gilligan
print(b.name) --> Gilligan
-- but note! the shadow
b.name = "overridden"
print(a.name) --> Gilligan
print(b.name) --> overridden
b.name = nil
print(a.name) --> Gilligan
print(b.name) --> Gilligan
For more details, I offer up this article of mine:
http://phrogz.net/lua/LearningLua_ValuesAndMetatables.html
Response to Edit2:
Let me summarize the problems with some of your code:
local maintablecopy = {}
maintablecopy = maintable
With the above code you create one table, set maintablecopy to reference that table, and then you completely abandon it when you set maintablecopy to instead reference a different table. This demonstrates a lack of understanding of how variables work.
local newdata = { "Hello", "World" }
maintable = newdata
Again, you are not "copying" newdata into maintable, you are changing the variable to reference the same table here.
maintable = { "Stack", "Overflow" }
maintablecopy = {}
setmetatable(maintablecopy, { __index = maintable })
-- …
local newdata = { "Hello", "World" }
maintable = newdata
Again, same problem. Here are some ways to change your code:
Replace a Table's Contents
Instead of maintable = newdata you could do this:
function copytable(from,to_table)
-- erase all old keys
for k,_ in pairs(to_table) do to_table[k] = nil end
-- copy the new ones over
for k,v in pairs(from) do to_table[k] = v end
end
local a = { name="Foo" }
local b = {}
copytable(a,b)
print(a.name == b.name) --> true
local c = { name="NEW" }
copytable(c,b)
print(c.name == b.name) --> true
However, doing this will not cause b to update if c changes.
c.name = "EVEN NEWER"
print(c.name == b.name) --> false
Update the __index
local a = { name="Foo" }
local b = setmetatable({},{__index=a})
print(a.name == b.name) --> true
-- cause b to follow c now instead of a
local c = { name="NEW" }
getmetatable(b).__index = c
print(c.name == b.name) --> true
c.name = "EVEN NEWER"
print(c.name == b.name) --> true
In general, you need to step back and describe the original problem you are trying to solve, instead of this XY problem.

Division of metatable

got some problem with metatable. This is my simple metatable:
local mt = {}
function mt:add(n)
return setmetatable({n = n}, {__index = mt})
end
function mt:get() return self.n end
Now I want to add some division like:
mt.math
mt.effect
Which each one has some own methods like:
mt.math:floor() return math.floor(self:get()) end
mt.effect:show(args) onMapShowEffect(self:get(), {x = x + (args[1] ~= nil or 0), ...) end
mt.effect:get() return getCurrentPos() end
Any ideas?
OK, trying make all details to share my problem.
Player = {}
function Player:add(this)
return setmetatable({this = this}, {__index = Player})
end
Player:get() return self.this end
Above code works perfectly on this example
function enterToGame(player1, player2)
local p1 = Player:add(player1)
local p2 = Player:add(player2)
print(p1:get()) -- ID1
print(p2:get()) -- ID2
Now I want to create some helpfully methods(functions) for table Player. I want to make it more flexible, so I want divide it for classes. Example:
Player.info = {
id = function() return Player:get() end,
}
Player.pos = {
get = function() return getPosition(Player:get()) end,
set = function(args) setPosition(Player:get(), args) end,
}
Player.speed = {
get = function() return getSpeed(Player:get()) end,
set = function(value) setSpeed(value) end,
improve = function(value) setSpeed(Player.speed.get() + value) end,
}
But its not work exactly what I want:
function enterToGame(player1, player2)
local p1 = Player:add(player1)
local p2 = Player:add(player2)
print(p1:get()) -- ID1
print(p2:get()) -- ID2
print(p1.info.id()) -- ID2 instead of ID1
print(p2.info.id()) -- ID2
When I put Player:get() in my methods its return last object declaration.
Based on what you state, if you do
mt.math = mt:add(123)
You don't need themt:get() because mt is the metatable for mt.math. Then
mt.math.floor = function(self) return math.floor(self.n) end
will work as expected. For example,
print(mt.math:floor())
prints 123.
EDIT 1: So now that I have a better understanding of what you are trying to do: normally you would do
p1:id()
p1:getPos()
p1:setPos()
p1:getSpeed()
p1:improveSpeed()
Note the colon, this is important, so that each method gets a "self" as first parameter, thereby given them the table instance to operate on (p1, in the above example). Instead you want to group methods so
p1.info:id()
p1.pos:get()
p1.pos:set()
p1.speed:improve()
p1.speed:get()
These methods will get a self that points to p1.info, p1.pos, etc. But those sub-tables have no knowledge of the container table (p1). The info and pos tables are in the Player class: they are shared by all instances of Player (p1, p2 etc). You have to make the info and pos tables non-shared:
function Player:add(player)
local pN= setmetatable( {n = player, info={}, pos={}}, {__index = Player})
pN.info.id = function() return pN.n end
pN.pos.set = function(x) return setPosition(pN, x) end
return pN
end
Then you get
> p1=mt:add(player1)
> p2=mt:add(player2)
> print(player1)
table: 0024D390
> print(p1.info.id())
table: 0024D390
> print(player2)
table: 0024D250
> print(p2.info.id())
table: 0024D250
All that said, I don't really like the idea of having to use closures like this, perhaps there are gotchas since not everything will be in Player.

Lua hierarchy string to table

Is there a way that I can convert a hierarchy string into table form?
Suppose the input is A.B.C.D
ouput should be a table which traverses above input:
A = {}
A.B = {}
A.B.C = {}
A.B.C.D = {}
Thanks.
The obvious solution would be to parse the string up and construct the hierarchy table from that. But a more clever solution is to let lua do it for you. With a bit of metamagic and function environment manipulation this can be done:
dump = require 'pl.pretty'.dump -- convenient table dumper from penlight
function createtable(str)
local env_mt = {}
env_mt.__index = function(t, k)
rawset(t, k, setmetatable({}, env_mt))
return rawget(t, k)
end
local env = setmetatable({}, env_mt)
local f = loadstring("return "..str)
setfenv(f, env)
f()
return env
end
dump( createtable "A.B.C.D" )
this outputs:
{
A = {
B = {
C = {
D = {
}
}
}
}
}
#greatwolf's answer is right but I prefer the more straightforward approach of "parsing" the string and constructing the table. Less magic, and you do not execute a function loaded from a (possibly) user-defined string, which would be a security issue.
local createtable = function(str)
local top = {}
local cur = top
for i in str:gmatch("[^.]+") do
cur[i] = {}
cur = cur[i]
end
return top
end
(require "pl.pretty").dump(createtable("A.B.C.D"))

Resources