How to implement a trace function for table to functions? - lua

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)

Related

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).

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

Is the following Lua iterator stateless?

I'm confused with the concepts of stateless iterators. As an exercise I write an iterator to print all non-empty substrings of a given string. The code is as follows.
local function iter(state, i)
local str = state.str
if type(str) ~= "string" or str == "" then return nil end
if state.ending > #str then return nil end
local start = state.start
local ending = state.ending
if start == ending then
state.ending = ending + 1
state.start = 1
else
state.start = start + 1
end
return string.sub(str, start, ending)
end
function allSubstrings(str)
return iter, { str = str, start = 1, ending = 1 }, nil
end
for substr in allSubstrings("abcd123") do
print(substr)
end
I use a table { str = str, start = 1, ending = 1 } as the so-called invariant state, but I have to change the fields in this table in the iter local function. So is this iterator stateless or with complex states? If it's not stateless, is there a way to implement this functionality with a stateless iterator?
Thank you.
The PIL-chapter about stateless iterators states:
As the name implies, a stateless iterator is an iterator that does not keep any state by itself. Therefore, we may use the same stateless iterator in multiple loops, avoiding the cost of creating new closures.
In code that means that both for loops in:
f, s, var = pairs( t )
for k,v in f, s, var do print( k, v ) end
for k,v in f, s, var do print( k, v ) end
should produce the same output. This only works if neither the invariant state s nor any upvalues of the iterator function f change during the iteration, or else the second for loop would have different starting conditions than the first loop.
So, no, your iterator is not a stateless iterator because you change the invariant state during iteration.
There is a way to make your iterator stateless (and the popular Lua library luafun uses this technique extensively): keep all mutable state in the loop control variable var (even if you need to allocate a fresh table in each allocation step):
local function iter( str, var )
if type( str ) ~= "string" or str == "" then return nil end
if var[ 2 ] > #str then return nil end
local start, ending = var[ 1 ], var[ 2 ]
if start == ending then
return { 1, ending+1 }, string.sub( str, start, ending )
else
return { start+1, ending }, string.sub( str, start, ending )
end
end
function allSubstrings2( str )
return iter, str, { 1, 1 }
end
for _,substr in allSubstrings2( "abcd123" ) do
print( substr )
end
Drawbacks are that the first iteration variable var (the loop control variable) has no useful meaning for the user of the iterator, and since you have to allocate a table for each iteration step, "avoiding the cost of creating new closures" for another loop doesn't matter much.
luafun does it anyway, because it does not have the ability to recreate an iterator tuple (it is passed via function arguments from outside code), and for some algorithms you absolutely need to run the same iteration multiple times.
An alternative to this approach would be to concentrate all mutable state in the "invariant state" s (all upvalues to f must be immutable), and provide a way to copy/clone this state for later iterations:
f, s, var = allSubstrings("abcd123")
s2 = clonestate( s )
for str in f, s, var do print( str ) end
for str in f, s2, var do print( str ) end
This is still not a stateless iterator, but it is more memory-efficient than the stateless iterator with heap-allocated loop control variable, and it allows you to restart an iteration.
This is not a stateless iterator, and is indeed an iterator with complex state.
In stateless iterators there is only one control variable, and it should be treated as a pure value throughout the iterator.
You could consider using closures to implement this. Not quite stateless, but does use upvalues over table lookups, which should be more efficient.
local function allSubstrings (str)
if type(str) ~= "string" or str == "" then
return function () end -- NOP out on first iteration
end
local length = #str
local start, ending = 1, 1
return function ()
if ending > length then return nil end
local st, ed = start, ending
if start == ending then
ending = ending + 1
start = 1
else
start = start + 1
end
return string.sub(str, st, ed)
end
end
for substr in allSubstrings("abcd123") do
print(substr)
end
PIL book calls that 'Iterators with Complex State'
http://www.lua.org/pil/7.4.html

Setting __index of current environment in Roblox

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.

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