Getting Variables From Values |
|
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.