模擬字串中的非局部變數

lua-users home
wiki

模擬編譯檔案或字串中的非局部變數

預設情況下,之前使用 loadfileloadstring 分別編譯並之後直接執行或使用 pcall 執行的檔案或字串會在全域環境中運作。它們對呼叫者的影響僅能透過全域表格 _G 和對應的 return var_list。以下我們使用字串情況進行示範。

因此,呼叫函式或區塊的局部和非局部變數並未由字串繼承。此外,字串不存在非局部變數(我們也無法使用字串作為閉包函式)。

然而,我們可能希望在字串內使用和/或修改一些非局部變數。唯一的方法是透過設計一個函式,準備一個新環境,其中一個表格(現在是字串中的全域表格)作為代理,並包含所需的非局部變數。這些變數必須在字串中像全域變數般使用,但有一個字首。我們選擇 _U。在定義為字串的函式中,它們將是全域表格 _U 的欄位。我們也需要 debug 函式庫的一些函式(這是必要的缺點),以便修改回傳值中的向上值。

因此,非局部變數的使用是模擬的,但這麼做是有作用的。

我們需要一個函式 callstring,其參數如下

callstring 的回傳值是狀態,如果發生錯誤則為 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 中會像向上值般運作。

傳入 _U 的其他欄位至 callstring,在 proc 內會被當成區域變數處理。在呼叫中

callstring(proc, {c = c, d = d, x = "xx"}}, nil, "a", "b")

x 並非 upvalue,因為它不位於呼叫封包中的某個地方。此處 _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

另請參閱


RecentChanges · preferences
edit · history
最後編輯時間為 2007 年 3 月 23 日上午 6:26 (GMT) (diff)