Setting __index of current environment in Roblox - lua

In Roblox Studio, I have a ModuleScript object that implements an analogous class to the one shown in chapter 16 of the 1st edition of Programming In Lua, as shown below:
local h4x0r = { }
local function setCurrentEnvironment( t, env )
if ( not getmetatable( t ) ) then
setmetatable( t, { __index = getfenv( 0 ) } )
end
setfenv( 0, t )
end
do
setCurrentEnvironment( h4x0r );
do
h4x0r.Account = { };
setCurrentEnvironment( h4x0r.Account );
__index = h4x0r.Account;
function withdraw( self, v )
self.balance = self.balance - v;
return self.balance;
end
function deposit( self, v )
self.balance = self.balance + v;
return self.balance;
end
function new( )
return setmetatable( { balance = 0 }, h4x0r.Account )
end
setCurrentEnvironment( h4x0r );
end
end
return h4x0r
I then attempted to use the following script to access the Account class, assuming that all of the members of the 2nd do-end block would be assigned to h4x0r.Account:
h4x0r = require( game.Workspace.h4x0r );
Account = h4x0r.Account;
account = Account.new( );
print( account:withdraw( 100 ) );
The above script fails with the error Workspace.Script:5: attempt to call method 'withdraw' (a nil value), so it must be an issue regarding the line where I set the __index field of h4x0r.Account.
Can someone please explain to me where I went wrong?

Try using getfenv(2) and setfenv(2, t) instead of getfenv(0) and setfenv(0, t). You essentially want to change the environment of the encapsulating function, which would be stack level 2.
0 is a special argument that would instead get or set the environment of the thread, which is used as a default environment in some cases, but that does not affect the individual closures that have already been instantiated in the thread, hence it doesn't work in this case.

Related

attempt to call method 'func' (a nil value)

No matter how I approach Lua, I run into this error all the time, so I must not understand something inherit to the language:
attempt to call method 'func' (a nil value)
I've seen the error here a few times as well but the problem doesn't seem clear to me.
Here's my module:
actor.lua
Actor = {
x = 0,
mt = {},
new = function()
local new_actor = {}
new_actor.x = Actor.x
new_actor.mt = Actor.mt
return new_actor
end,
test = function(self, a, b)
print(a, b)
end
}
I'm using Löve.
main.lua
require "game/actor"
local a = Actor:new() --works fine
function love.load()
a.x = 10
print(a.x) --output: 10
a:test(11, 12) --error: attempt to call method 'test' (a nil value)
end
I'm also not sure when it's appropriate to use the previous styling over this in a module.
Actor = {
x = 0
}
Actor.mt = {}
function Actor.new()
print(42)
end
I'm honestly not sure what is more correct than the other but considering I run into a simple error either way, there's probably something I'm missing entirely?
It looks like you're trying to instance a kind of class made of metatables. You basically need to assign new_actor's metatable with Actor.mt. (Resuming the problem: when you're indexing new_actor you're not indexing Actor in this case)
setmetatable(new_actor, Actor.mt);
Even if the metatable is being added, it won't work until you put the meta "__index" event to index a table containing your class methods/values, in this case:
Actor.mt = {
__index = Actor
};
I'd suggest moving your class methods/values into a new table, like Actor.prototype, Actor.fn, etc... avoiding conflicts:
Actor.fn = {
test = function(self, a, b)
print(a, b)
end
};
Actor.mt = {
__index = Actor.fn
};
More about metatables in Lua 5.3 manual.

quoting troubles in Lua

I am trying to make a quote function in lua, so i can use the arguments as strings but without quotes or access them in some environment. Much like in the second comment on this question
w = print
function test()
local function _ix( _ , k )
w( " _ix \\ " , _ , k )
local v = rawget( _G , k )
w( " <-- " , k )
return k
end
local _ = setmetatable( {} , { __index = _ix } )
local function q( _ ) return _ end
q = setfenv( q , _ )
return q
end
So, when I run it:
q = test()
w( "q( uno )" , q( uno ) )
It doesn't even call the __index metamethod:
---------- Capture Output ----------
q( uno ) nil
> Terminated with exit code 0.
So, what I'm doing wrong?
If I'm understanding correctly, then what you're trying to do doesn't make much sense. uno will be looked up in the environment that q is called in, not with. In your example it's like calling q(nil). The example from the other question works because they're working in the same, global environment.
You can use write a helper function to intercept your current environments nil-lookups, but it must be called preemptively in any environment you want to use these nil-to-string lookups.
local function intercept (tab)
setfenv(2, setmetatable(tab or {}, {
__index = function (_, key)
return key
end
}))
end
And you'll need an environment cloning function, unless you want to manually create your sandboxes every time, else you'll probably mess up parent environments (e.g., _G). You could move this logic inside of intercept for a cleaner function call, but with less flexibility.
local function clone_current_env ()
local env = {}
for key, value in pairs(getfenv(2)) do
env[key] = value
end
return env
end
Using them together, you can cause nil lookups in whichever environment you're in to become strings.
intercept(clone_current_env())
print(type(foo), type(bar)) --> string string
This is some ugly metaprogramming, and I don't really know why you'd want to write code like this, except as a proof of concept.
A full example.
DEMO
local function clone (tab)
local new = {}
for key, value in pairs(tab) do
new[key] = value
end
return new
end
local function enable_nil_strings ()
setfenv(2, setmetatable(clone(getfenv(2)), {
__index = function (env, key)
return key
end
}))
end
local function disable_nil_strings()
setmetatable(getfenv(2), nil)
end
-----------------------------------------------------
print(type(foo), type(bar)) --> nil nil
enable_nil_strings()
print(type(foo), type(bar)) --> string string
disable_nil_strings()
print(type(foo), type(bar)) --> nil nil
Finally, arguably the best way to implement this would be to simply wrap around an execution context:
local function with_nil_strings (context, ...)
local env = {}
for key, value in pairs(getfenv(2)) do
env[key] = value
end
setfenv(
context,
setmetatable(env, {
__index = function (_, key) return key end
})
)
context(...)
end
print(type(foo)) --> nil
with_nil_strings(function ()
print(type(foo)) --> string
end)
print(type(foo)) --> nil

How to implement a trace function for table to functions?

I have a table like this
local ftable = {
getPinId = app.getPinId
}
ftable is passed to another function which exports it as a RPC interface.
This works but now I want to add function call tracing to a log file.
The simple approach is
local ftable = {
getPinId = function(...) print("getPinId") app.getPinId(...) end
}
But, this is not particularly nice.
I'd like to put something like:
local trace = function(func, ...)
return function(...) print(func) func(...) end
end
local ftable = {
getPinId = trace(app.getPinId)
}
But this doesn't produce quite the desired result. The parameters are not being passed through.
One other option is to use a metatable like this:
local ftable = {}
setmetatable(ftable, {
__index = function(_, k)
printf("Call: app.%s\n", k) return app[k] end
})
Which works. But I'd also like to be able to print the parameters that are passed if possible.
Any suggestions?
I'm exclusively using luajit if that makes any difference.
Wrapping a function call is easy in Lua:
local function wrap( f )
local function after( ... )
-- code to execute *after* function call to f
print( "return values:", ... )
return ...
end
return function( ... )
-- code to execute *before* function call to f
print( "arguments:", ... )
return after( f( ... ) )
end
end
local function f( a, b, c )
return a+b, c-a
end
local f_wrapped = wrap( f )
f_wrapped( 1, 2, 3 )
Output is:
arguments: 1 2 3
return values: 3 2
One problem for logging/tracing is that Lua values (including functions) don't have names themselves. The debug library tries to find suitable names for functions by inspecting how they are called or where they are stored, but if you want to make sure, you'll have to supply a name yourself. However, if your functions are stored in (nested) tables (as indicated in a comment), you could write a function that iterates the nested tables, and wraps all functions it finds using the table keys as names:
local function trace( name, value )
local t = type( value )
if t == "function" then -- do the wrapping
local function after( ... )
print( name.." returns:", ... )
return ...
end
return function( ... )
print( "calling "..name..":", ... )
return after( value( ... ) )
end
elseif t == "table" then -- recurse into subtables
local copy = nil
for k,v in pairs( value ) do
local nv = trace( name.."."..tostring( k ), v )
if nv ~= v then
copy = copy or setmetatable( {}, { __index = value } )
copy[ k ] = nv
end
end
return copy or value
else -- other values are ignored (returned as is)
return value
end
end
local ftable = {
getPinId = function( ... ) return "x", ... end,
nested = {
getPinId = function( ... ) return "y", ... end
}
}
local ftableTraced = trace( "ftable", ftable )
ftableTraced.getPinId( 1, 2, 3 )
ftableTraced.nested.getPinId( 2, 3, 4 )
Output is:
calling ftable.getPinId: 1 2 3
ftable.getPinId returns: x 1 2 3
calling ftable.nested.getPinId: 2 3 4
ftable.nested.getPinId returns: y 2 3 4
Some things to be aware of:
Table keys can be arbitrary Lua values, not just short strings entirely consisting of printable characters.
Tables can contain cyclic references. If they do, the naive implementation above will die with a stack overflow.
Use the __call metamethod instead:
M = { __call =
function (t,...) print("calling ",t.name,...) return t.func(...) end
}
trace = function(func,name)
return setmetatable({func=func,name=name},M)
end
function f(...)
print("in f",...)
end
g=trace(f,"f")
g(10,20,30)

Metatables, attempt to call method 'rename' (a nil value)

This is my first time using metatables, I did a simple script to test in Lua demo but it always give me "attempt to call method 'rename' (a nil value)", why?
peds = {}
function peds.new ( name )
local tb = { name = name }
setmetatable ( tb, { __index = peds } )
return tb
end
function peds.rename ( name )
self.name = name
return self.name == name
end
local ped = peds.new ( "max" )
ped:rename ( "randomname" )
There's two (possible) problems in your code, depending on how you are setting things up.
If you are just typing the above into a REPL, then when you declare local ped = ... it immediately goes out of scope and becomes inaccessible. So the expression ped:rename is invalid, although it should report "ped is nil" not "rename is nil".
If you are saving the above to a script and loading it using load_file or something, you will still get a problem, because this function signature isn't right:
function peds.rename ( name )
should be:
function peds.rename ( self, name )
Similar to how it works in C++, in lua, when you make an object method, you have to take the hidden self parameter first, and when you call ped:rename( "random name" ) that's just syntactic sugar for ped.rename(ped, "random_name"). If the self parameter doesn't exist then it's not going to work, or may even say "function not found / rename is nil" because the signatures don't match up.

Chain Lua metatables

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!

Resources