模拟字符串中的非局部变量

lua-users home
wiki

在编译文件或字符串中模拟非局部变量

默认情况下,之前使用loadfileloadstring分别编译的文件或字符串,以及之后直接执行或使用pcall执行的文件或字符串,都在全局环境中工作。它们对调用者的影响只能通过全局表_G和相应的return var_list实现。在以下内容中,我们将使用字符串的情况。

因此,调用函数或代码块的局部变量和非局部变量不会被字符串继承。此外,字符串不存在非局部变量(我们不能将字符串用作闭包)。

但是,我们可能希望在字符串内部使用和/或修改一些非局部变量。唯一的方法是设计一个函数,该函数使用一个表(现在是字符串内部的全局表)作为代理来准备一个新的环境,其中包含所需的非局部变量。这些变量必须在字符串内部使用,就好像它们是全局变量一样,但要使用一些前缀。我们选择了_U。在定义为字符串的函数内部,它们将是全局表_U的字段。我们还需要一些debug库的函数(这是一个必要的缺点),目的是修改返回值中的上值。

因此,非局部变量的使用是模拟的,但它有效。

我们需要一个函数callstring,它具有以下参数:

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

在这个例子中,cexample中的一个非局部变量,因为它在example内部使用(如果不是,Lua 会在此时“忘记”它)。另一方面,dexample中的一个局部变量。两者在proc内部都表现得像上值:它们可以使用_U.c_U.d的名字访问和修改。更重要的是,修改后的值会传递到example

关键在于表_U将有两个字段_U.c_U.d,分别初始化为cd的值。此外,通过在调用callstring时使用cd,我们确保这两个变量在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,例如上面介绍的构造。

-- JulioFernandez

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2007 年 3 月 23 日上午 6:26 GMT (差异)