模擬字串中的非局部變數 |
|
預設情況下,之前使用 loadfile
和 loadstring
分別編譯並之後直接執行或使用 pcall
執行的檔案或字串會在全域環境中運作。它們對呼叫者的影響僅能透過全域表格 _G
和對應的 return var_list
。以下我們使用字串情況進行示範。
因此,呼叫函式或區塊的局部和非局部變數並未由字串繼承。此外,字串不存在非局部變數(我們也無法使用字串作為閉包函式)。
然而,我們可能希望在字串內使用和/或修改一些非局部變數。唯一的方法是透過設計一個函式,準備一個新環境,其中一個表格(現在是字串中的全域表格)作為代理,並包含所需的非局部變數。這些變數必須在字串中像全域變數般使用,但有一個字首。我們選擇 _U
。在定義為字串的函式中,它們將是全域表格 _U
的欄位。我們也需要 debug
函式庫的一些函式(這是必要的缺點),以便修改回傳值中的向上值。
因此,非局部變數的使用是模擬的,但這麼做是有作用的。
我們需要一個函式 callstring
,其參數如下
proc
,loadstring
或 loadfile
編譯的結果。
_U
,一個包含所需非局部變數的表格;如果為 nil
,則假設為 {}。
func
,呼叫 callstring
的呼叫者;僅當 callstring
的呼叫在尾端回傳時才需要此參數;否則,它可以為 nil
。
proc
的參數。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
在這裡,c
是 example
中的非局部變數,因為它在 example
內部被使用(如果不是,Lua 目前會「忘記」它)。另一方面,d
是 example
中的局部變數。兩者在 proc
中的行為就像向上值:它們可以使用名稱 _U.c
和 _U.d
存取和修改。更重要的是,修改後的值會傳輸到 example
。
關鍵在於表格 _U
將會有兩個欄位 _U.c
和 _U.d
,分別初始化為 c
和 d
的值。此外,透過在 callstring
的呼叫中使用 c
和 d
,我們確保這兩個變數在 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,像是前面呈現的那樣。