Chain Lua metatables - lua

I am have a situation where two libraries L,M, are trying to set a metatable for _G (named mL, mM respectively). The only thing in the metatables are __index.
How can I chain these two metatables so that if the __index in one fails it calls the index in the other?

Have one metatable that stores both mL and mM, and if one returns nil, check the other:
local metatbl = {}
metatbl.tbls = {mL, mM};
function metatbl.__index(intbl, key)
for i, mtbl in ipairs(metatbl.tbls) do
local mmethod = mtbl.__index
if(type(mmethod) == "function") then
local ret = mmethod(table, key)
if ret then return ret end
else
if mmethod[key] then return mmethod[key] end
end
return nil
end
end
setmetatable(_G,metatbl)

Assuming there's a point where your code can fiddle with _G's metatable itself, after the libraries have mucked about, to fix what L and M did, you can just stick in your own metatable that does the combined search, e.g.:
combined_metatable = {
__index = function (t, k)
return mL.__index (t, k) or mM.__index (t, k)
end
}
setmetatable (_G, combined_metatable)
That has the advantage of not fiddling with mL or mM themselves.
If you don't have the opportunity to correct things after the fact, you could just modify the __index entries of the library metatables to do the combined search:
local original_mM_index = mM.__index
local original_mL_index = mL.__index
local function L_then_M_index (t, k)
return original_mL_index (t, k) or original_mM_index (t, k)
end
mL.__index = L_then_M_index
mM.__index = L_then_M_index
[Note that as both library metatables are modified, this will work whichever gets installed last ("winning" the competition).]

Use __metatable to give them a table that isn't actually the metatable or give the library a different setmetatable: that way they can't change your _G metatable.
getmetatable(getfenv()).__metatable = function ( o ) return { } end
OR
local orig_setmetatable = setmetatable
function setmetatable ( ob , mt )
if ob == getfenv() or ob == _G then
return ob
else
return orig_setmetatable(ob,mt)
end
end
(depending on how the library does things)
If you still want to track the things it does to the metatable; look through mt before the return ob (and if you actually wanted to chain __index lookups; add to a table):
local env_indexes = {}
setmetatable(_G,{__index=function(t,k) for i,m in ipairs(env_indexes) do local v=m[k]; if v then return v end end return nil end } )
local orig_setmetatable = setmetatable
function setmetatable ( ob , mt )
if ob == _G then
table.insert ( env_indexes , mt.__index )
return ob
else
return orig_setmetatable(ob,mt)
end
end
Otherwise this is very bad practice for libraries to be doing; tell the author not to!

Related

OOP Help - How do I get this code block to recognize one of its arguments?

I'm learning Lua and how to implement OOP. Trying out a test example of an object seems to return one of the argument of the object as 'null' despite being assigned one.
function Character(Name, Level, Class) --Constructor
return {GetName = T.GetName, GetLevel = T.GetLevel, GetClass = T.GetClass}
end
end
-- Snippets
Player = Character("Bob", 1, "Novice")
When I try printing Player.GetName() it returns null instead of Bob. Where have I gone wrong?
Here is the full code.
OOP in Lua takes a bit more than what you have done there, you will want to make use of metatables and upvalues.
-- How you could define your character structure.
local Character = {}
function Character.GetName(self)
return self.name
end
function Character.new(Name, Level, Class)
local _meta = {}
local _private = {}
_private.name = Name
_private.level = Level
_private.class = Class
_meta.__index = function(t, k) -- This allows access to _private
return rawget(_private, k) or rawget(Character, k)
end
_meta.__newindex = function(t, k, v) -- This prevents the value from being shaded
if rawget(_private, k) or rawget(Character, k) then
error("this field is protected")
else
rawset(t, k, v)
end
end
return setmetatable({}, _meta) --return an empty table with our meta methods implemented
end
This creates a local table _private when you create a new instance of a Character. That local table is an upvalue to the _meta.__index and it cannot be accessed outside the scope of the Character.new function. _private can be accessed when __index is called because it is an upvalue.
-- How to use the character structure
player = Character.new("Bob", 10, "Novice")
npc = Character.new("Alice", 11, "Novice")
print(player:GetName())
I use player:GetName(), but in all honesty you can just do player.name as well.
Resources for more on this topic:
http://tutorialspoint.com/lua/lua_metatables.htm
http://lua-users.org/wiki/ObjectOrientationTutorial

Check If Metatable Is Read Only

I'm trying to find a way to check if a Metatable is readonly or not
for example
local mt = metatable(game)
if mt == "readonly" do
print("Attempt to modify Metatables")
end
I hope there is a way to do this for Roblox, so I can prevent GUI tampering
You can use getmetatable() to see if the contents of a metatable are protected.
Example:
local mt = getmetatable(game)
if mt ~= nil and type(mt) ~= "table" then -- not perfect, as __metatable could be set to {}
print("This metatable is protected!")
end
Alternatively if you are looking to see if the table itself is read-only, you will need to check two behaviors
What happens when you attempt to add a value to the table
What happens when you attempt to change a value in the table.
Example of read-only table:
local protected_table = {'1', '2', '3'}
local table = setmetatable({}, {-- create a dumby table with a metatable
__index = function(_, k)
return protected_table[k]-- access protected table
end,
__newindex = function()
error('This value is read-only.')
end,
__pairs = function(_)
return function(_, k)
return next(protected_table, k)
end
end,
__metatable = false,
})
Examples of possible interactions:
table[4] = "4" -- read-only error will be generated by _newindex setting
table[1] = "0" -- read-only error will be generated by _newindex setting
first = table[1] -- will retrieve first value of protected_table

table.insert doesn't trigger __index?

I made a custom table using metatables that automatically tracks sizing when elements are added. It works very well and removes the need for the # operator or the getn function.
However it has a problem. If you call table.insert, the function apparently never calls __index or __newindex. Thus, my table cannot know when elements are removed this way. I assume the problem is the same with table.remove as well.
How can I either:
Capture the event of insert and use my own function to do so
Throw an error if insert is called on my table.
Thanks
function Table_new()
local public = { }
local tbl = { }
local size = 0
function public.size()
return size
end
return setmetatable(public, {
__newindex = function(t, k, v)
local previous_v = tbl[k]
rawset(tbl, k, v)
if previous_v ~= nil then
if v == nil then
size = size - 1
end
elseif v ~= nil then
size = size + 1
end
end,
__index = tbl
})
end
local t = Table_new()
t[5] = "hi"
t[17] = "hello"
t[2] = "yo"
t[17] = nil
print(t.size()) -- prints 2
local z = Table_new()
table.insert(z, "hey")
table.insert(z, "hello")
table.insert(z, "yo")
print(z.size()) -- prints 1 -- why?
If you print k,v in __newindex, you'll see that k is always 1. This is because table.insert asks for the size of table to find where to insert the value. By default, it's at the end. You should add a __len metamethod. But perhaps this defeats your purposes (which are obscure to me).

Accessing the key in the value (right side) of a table key affectation in Lua

The goal is to match a key of a table with a value depending of the key.
example = { ["dummy"] = this .. " example" }
print example.dummy -- print "dummy example"
Where this is the keyword to refer to the key. Is there any way to do that in Lua?
There is no way to do this directly.
You could do some preprocessing:
example = { ["dummy"] = "{THIS} example" }
for k,v in pairs(example) do
example[k]=v:gsub("{THIS}",k)
end
print(example.dummy)
It's not possible to have as clean an expression as:
t = { foo = this .. ' bar' }
because this would always be expressed without relation to the key or the table. That is, you can't capture an expression as the value of a table entry.
What is possible is implementing some level of indirection using metatables and functions, but it's hardly pretty. Here we do fetch-time evaluation. You could also recompute the results.
local function indirect_table ()
local uptable = {}
return setmetatable({}, {
__index = function (self, key)
local value = uptable[key]
return type(value) == 'function' and uptable[key](key) or value
end,
__newindex = function (self, key, value)
uptable[key] = value
--[[precompute, with no need for an uptable, or __index:
`rawset(self, key, value(key)`]]
end
})
end
local tab = indirect_table()
tab.foo = function (key) return key .. 'bar' end
print(tab.foo) --> 'foobar'
Note: this example uses a closure, but you can implement this kind of pattern using getmetatable as well.
Personally, I'd abstract this into an indirection pattern that allows arbitrary keys and values, and their actions to be specified. I figure this kind of pattern would mostly be used programatically, rather than by hand, where the results of key values are dependent on the inputs received. Again, not pretty, but a little more robust (optional actions).
local function I (_, value) return value end
local K = setmetatable({
__call = function (actor, key)
return actor.action(key, actor.value)
end
}, {
__call = function (K, value, action)
return setmetatable({ value = value, action = action or I }, K)
end
})
local T = setmetatable({
__newindex = function (self, key, value)
if getmetatable(value) == K then
value = value(key)
end
rawset(self, key, value)
end
}, {
__call = function (T, o)
return setmetatable(o or {}, T)
end
})
Simple use:
local function concat (left, right) return left .. right end
local t = T {}
t.foo = K('bar', concat) -- with a common action
t.zar = K({}, unknown_action) -- without action (`nil`)
t.qux = 'qaz' -- standard
print(t.foo, t.zar, t.qux)
This is strange metaprogramming. I'd double-check the reasoning for needing such an approach. Perhaps you're falling into an XY Problem trap? Really feels like a solution to a problem that doesn't need to exist in the first place.

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.

Resources