Simulated Non Local In Strings |
|
By default the files or strings previously compiled by using loadfile
and loadstring
, respectively, and executed afterwards directly or by using pcall
, work in the global environment. Their effects in the caller are only possible through the global table _G
and the corresponding return var_list
. In the following we work with the string case.
Therefore, the local and non-local variables of the caller function or chunk are not inherited by the string. Besides, non-local variables do not exist for the string (and we cannot use the string as a closure).
However we could wish to use and/or modify some non-local variable inside the string. The only way of doing it is through the design of a function that prepares a new environment with a table (now a global one inside the string) as a proxy with the required non-local variables. These must be used inside the string as if they were global, but with some prefix. We chose _U
. Inside the function defined as a string they will be the fields of a global table _U
. We also need some functions of the debug
library (this is a necessary drawback) with the aim of modify the upvalues in the return.
Therefore, the use of non-local variables is simulated, but it works.
We need a function, callstring
, with arguments:
proc
, the compiled result of loadstring
or loadfile
.
_U
, a table with the desired non-local variables; if nil
then {} is supposed.
func
, the caller to callstring
; this is needed only if the invocation of callstring
is in a tail return; otherwise it can be nil
.
proc
.
The return of callstring
is a status
with nil
in case of error or true
if all is correct, and the return of proc
.
Let put a simple example:
s = [[ _U.c = 11; _U.d = 22; return ... ]] proc = loadstring(s) local c = 1 function example (proc) local d = 2 local result = {callstring(proc, {c = c, d = d}}, nil, "a", "b")}} print(d) -- possibly modified inside proc return result -- a table with the status and the results of proc end example(proc) print(c) -- possibly modified inside proc
In this, c
is a non-local variable in example
because it is used inside example
(if not Lua 'forgot' it for the moment). On the other hand, d
is a local variable in example
. Both behave like upvalues inside proc
: they can be accessed and modified with the names _U.c
and _U.d
. Even more, the modified values are transferred to example
.
The key is that the table _U
will have two fields _U.c
and _U.d
, initialised with the values of c
and d
, respectively. Besides, by using c
and d
in the invocation of callstring
we assure that both variables work like upvalues inside proc
.
Other fields passed in _U
to callstring
are treated as local variables inside proc
. In the calling:
callstring(proc, {c = c, d = d, x = "xx"}}, nil, "a", "b")
x
is not an upvalue, because it does not exist in some place in the caller closure. In this case _U.x
takes the value "xx"
in the string, and it behaves like a local variable inside the string.
It is time to present callstring
.
-- Calling a function obtained from a string or file -- with simulated upvalues -- -- Arguments: -- proc loaded string or file -- _U is a new local and non-local table; -- if it is nil then we suppose it {}} -- func is the function from we invoked 'callstring'; -- can be nil if the invocation is not a tail return -- ... the arguments to string calling function callstring (proc, _U, func, ...) _U = _U or {}} if type(_U) ~= "table" then return nil, "Second argument of callstring must be a table or nil" end -- determine the calling function func = func or debug.getinfo(2, "f").func -- count _U fields local nt = 0 for _, _ in pairs(_U) do nt = nt+1 end if func == nil and nt ~= 0 then return nil, "Callstring invoked in a tail call cannot evaluate string" end -- both _G and _U are passed as global in the new environment -- the direct indexing of _G is permitted for accessing (not modifying) local newgt = {_U = _U}} setmetatable(newgt, {__index = _G}}) setfenv(proc, newgt) -- proc is executed with arguments local result = {proc(...)}} -- when _U is {}} (no upvalues) the return is always possible -- (even in a tail call) if nt == 0 then return true, unpack(result) end -- modify local and non-local variables of the calling routine -- (adapted from PIL2 chapter 23) for n, v in pairs(_U) do local found = false -- non-local variable for i = 1, math.huge do local m, _ = debug.getupvalue(func, i) if not m then break end if m == n then found = true debug.setupvalue(func, i, v) break end end if not found then -- local variable local ipos for i = 1, math.huge do local m, _ = debug.getlocal(2, i) if not m then break end if m == n then ipos = i end -- the last found is the correct end if ipos then debug.setlocal(2, ipos, v) end end end return true, unpack(result) end
A more elaborate example of calling could be:
local c = 1 local h = 2 g = 3 -- global local s = [[ local f = 99 -- local inside the string _U.c = 11 -- non-local of something _U.h = 22 -- internal (not passed in table) _U.z = 4 -- internal (not passed in table) _U.d = 77 -- local of something _G.zzz = table.concat({...}}, "+") -- global of new creation print("_U.x = ", _U.x) -- internal (because in table does not -- correspond to any local/non-local) _G.g = 33 -- previously existent global _G.v = f -- global of new creation return 10*g, 10*v -- return ]] -- loading the string local proc, msg = loadstring(s) function something (proc) local d = 3 if proc == nil then error(msg) end local r = {callstring(proc, {c = c, d = d, x = "xx"}}, nil, "a", "b")}} -- printing a local modified inside the string as an upvalue print("d = ", d) return unpack(r) end local r = {something(proc)}} if not r[1] then print("error in callstring: " .. r[2]) else k1 = r[2] k2 = r[3] end print(c, h, g, v, z) print(k1, k2) print(zzz)
The output of this program is:
_U.x = xx --< internal variable in the string d = 77 --< local variable in something 11 2 33 99 nil --< local, local, global, global, global 330 990 --< return of string a+b --< global zzz modified inside the string
As a conclusion, it will be good if Lua executed compiled strings and files as other 'normal' function. But, for the moment we have to 'circumvent' Lua with some constructions like the one presented.