模拟字符串中的非局部变量 |
|
默认情况下,之前使用loadfile
和loadstring
分别编译的文件或字符串,以及之后直接执行或使用pcall
执行的文件或字符串,都在全局环境中工作。它们对调用者的影响只能通过全局表_G
和相应的return var_list
实现。在以下内容中,我们将使用字符串的情况。
因此,调用函数或代码块的局部变量和非局部变量不会被字符串继承。此外,字符串不存在非局部变量(我们不能将字符串用作闭包)。
但是,我们可能希望在字符串内部使用和/或修改一些非局部变量。唯一的方法是设计一个函数,该函数使用一个表(现在是字符串内部的全局表)作为代理来准备一个新的环境,其中包含所需的非局部变量。这些变量必须在字符串内部使用,就好像它们是全局变量一样,但要使用一些前缀。我们选择了_U
。在定义为字符串的函数内部,它们将是全局表_U
的字段。我们还需要一些debug
库的函数(这是一个必要的缺点),目的是修改返回值中的上值。
因此,非局部变量的使用是模拟的,但它有效。
我们需要一个函数callstring
,它具有以下参数:
proc
,loadstring
或loadfile
的编译结果。
_U
,包含所需非局部变量的表;如果为nil
,则假定为{}。
func
,调用callstring
的函数;仅当callstring
的调用在尾部返回时才需要此参数;否则可以为nil
。
proc
的参数。
callstring
的返回值是一个status
,如果发生错误,则为nil
,如果一切正常,则为true
,以及proc
的返回值。
让我们举一个简单的例子
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
在这个例子中,c
是example
中的一个非局部变量,因为它在example
内部使用(如果不是,Lua 会在此时“忘记”它)。另一方面,d
是example
中的一个局部变量。两者在proc
内部都表现得像上值:它们可以使用_U.c
和_U.d
的名字访问和修改。更重要的是,修改后的值会传递到example
。
关键在于表_U
将有两个字段_U.c
和_U.d
,分别初始化为c
和d
的值。此外,通过在调用callstring
时使用c
和d
,我们确保这两个变量在proc
内部像上值一样工作。
传递给callstring
的_U
中的其他字段在proc
内部被视为局部变量。在调用
callstring(proc, {c = c, d = d, x = "xx"}}, nil, "a", "b")
x
不是上值,因为它在调用者闭包中的某个地方不存在。在这种情况下,_U.x
在字符串中取值为"xx"
,它在字符串内部的行为类似于局部变量。
现在是介绍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
调用更详细的例子可能是
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)
该程序的输出为
_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
总之,如果 Lua 像其他“正常”函数一样执行编译后的字符串和文件,那就太好了。但是,目前我们必须使用一些构造来“规避” Lua,例如上面介绍的构造。