Getting Variables From Values


If we have a value, can we obtain its variable? For example, can a function return the names of the variables passed to it?

f(x,y) --> 'x', 'y'

Or can Lua support "pass-by-reference" (or maybe even "pass-by-name") calling semantics?

local a = 10; local b = 11
print(a,b) --> 10,11
swap(a,b)
print(a,b) --> 11,10

The answer is not normally. However, with some byte code inspection (using the lbci library [1]) and Lua debug library hackery, it may be possible...

Note first the bytecode in a typical function call:

$ echo -e 'local x = {2,3};\n local y = 4;\n f(x,y,z)' | luac -p -l -
main <stdin:0,0> (11 instructions, 44 bytes at 0x6d0ea8)
0+ params, 6 slots, 0 upvalues, 2 locals, 5 constants, 0 functions
 1 [1] NEWTABLE 0 2 0
 2 [1] LOADK 1 -1 ; 2
 3 [1] LOADK 2 -2 ; 3
 4 [1] SETLIST 0 2 1 ; 1
 5 [2] LOADK 1 -3 ; 4
 6 [3] GETGLOBAL 2 -4 ; f
 7 [3] MOVE 3 0
 8 [3] MOVE 4 1
 9 [3] GETGLOBAL 5 -5 ; z
 10 [3] CALL 2 4 1
 11 [3] RETURN 0 1

The instructions prior to the CALL move the variables into place. Here's how we might make use of that:

-- D.Manura, 2009-10. Public domain.
require "bci"
-- Returns list of variables passed to function at given
-- stack level number `level`. `level` defaults to 1, the calling
-- function, if omitted. If confused, returns nothing.
-- See code for format of variables.
-- WARNING: This code is experimental. Not intended for production use.
local mt
local function getargvariables(level)
 mt = mt or {__tostring = function(t)
 local s = '{'
 for i=1,#t do
 s = s .. (i==1 and '' or ',') .. tostring(t[i])
 end
 s = s .. '}'
 return s
 end}
 -- Get function info.
 level = (level or 1) + 2
 local f = debug.getinfo(level,'f').func
 if not f then return end -- could be a tail call
 local currentline = debug.getinfo(level,'l').currentline
 local F = inspector.getheader(f)
 -- Get instruction pointer of call.
 -- Unfortunately, we only have the line number from which to infer the
 -- instruction pointer. So, we can only do this unambiguously when the
 -- call is the only call on its line. Perhaps bci or debug.getinfo
 -- could be patched to return the exact instruction pointer.
 local count = 0
 local currentip
 for i=1,F.instructions do
 local line, opcode, a, b, c = inspector.getinstruction(f,i)
 if line == currentline and opcode == "CALL" then
 currentip = i
 count = count + 1
 end
 end
 if count ~= 1 then return end -- ambiguous, return nothing
 -- Get CALL opcode data
 local _,_,idxfunc,nparamsp,_ = inspector.getinstruction(f,currentip)
 -- Get arguments.
 local names = {}
 for i=1,nparamsp-1 do
 local _,opcode,a,b,c = inspector.getinstruction(f,currentip-i)
 if opcode == 'MOVE' and a == idxfunc + nparamsp - i then -- local
 local varname,_,_ = inspector.getlocal(f,b+1)
 names[nparamsp - i] = setmetatable({'local', varname, b+1}, mt)
 elseif opcode == 'GETGLOBAL' and b < 0 then -- global
 local varname = inspector.getconstant(f,-b)
 names[nparamsp - i] = setmetatable({'global', varname}, mt)
 else
 return -- other possibilities not currently implemented
 end
 end
 return unpack(names, 1, nparamsp-1)
end
-- Set variable `var` (as returned by `getargvariables`) in context of
-- stack level number `level` to value `value`.
local function setvariable(level, var, value)
 level = (level or 1) + 1
 if var[1] == 'local' then
 local varname, idx = var[2], var[3]
 debug.setlocal(level, idx, value)
 elseif var[1] == 'global' then
 local varname = var[2]
 getfenv(2)[varname] = value
 else
 assert(false)
 end
end
-- TESTS
local function f(a,b)
 print(getargvariables()) --> {local,y,5} {local,z,6} {global,w}
end
do
 local y=1
 local z=2
 f(y,z,w)
end
local function swap(x,y)
 local xvar, yvar = getargvariables()
 assert(xvar and yvar)
 setvariable(2, xvar, y)
 setvariable(2, yvar, x)
end
a = 10
local b = 11
print(a,b) --> 10,11
swap(a,b)
print(a,b) --> 11,10
print 'DONE'

As is, the above is not intended for production use. The above could be generalized further. Please do so if you are so inclined.

--DavidManura

See Also


RecentChanges · preferences
edit · history
Last edited October 8, 2009 11:56 pm GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /