(接上篇)
-------------------
4.6 可見性和 Upvalue
-------------------
一個函數體能夠引用它本身的局部變量(包括它的參數)和全局變量,只要它們沒有被函數中同名的局部變量所隱藏(shadowed )。一個不能夠使用包含它的函數的局部變量,由於這樣的變量可能在函數調用的時候已經不存在了。然而,一個函數可經過 upvalue 使用包含它的函數中的局部變量。upvalue 的語法以下:
upvalue ::= `%' name
一個 upvalue 多少有點像是一個變量表達式,可是它的值是凍結的(frozen)當使用它的函數實例化時。upvalue 中使用的名字能夠是任何變量的名字,只要函數定義的時候該變量是可見的,也就是說,直接包含它的函數中的全局變量和局部變量。注意,當 upvalue 是一個表時,只有表的引用(也就是 upvalue 的值)是凍結的。表的內容是能夠任意修改的。使用表的值做爲 upvalue 可讓函數有可寫的可是私有的狀態。
下面是一些例子:
程序員
a,b,c = 1,2,3 -- global variables local d function f (x) local b = {} -- x and b are local to f; b shadows the global b local g = function (a) local y -- a and y are local to g p = a -- OK, access local `a' p = c -- OK, access global `c' p = b -- ERROR: cannot access a variable in outer scope p = %b -- OK, access frozen value of `b' (local to `f') %b = 3 -- ERROR: cannot change an upvalue %b.x = 3 -- OK, change the table contents p = %c -- OK, access frozen value of global `c' p = %y -- ERROR: `y' is not visible where `g' is defined p = %d -- ERROR: `d' is not visible where `g' is defined end -- g end -- f
-------------------
4.7 錯誤處理
-------------------
因爲 Lua 是一個擴展語言,全部的 Lua 動做從宿主程序中的 C 代碼調用 Lua 庫中的一個函數開始。每當一個錯誤在 Lua 編譯或執行時發生,函數 _ERRORMESSAGE 將被調用(若是它不是 nil 的話),而後相應的庫中的函數 (lua_dofile, lua_dostring, lua_dobuffer 和 lua_call) 被終止,並返回一個錯誤狀態。
內存分配錯誤是上面規則的一個例外。當內存分配失敗,Lua 也許不能執行 _ERRORMESSAGE 函數。所以,對於這種錯誤,Lua 不調用 _ERRORMESSAGE 函數,而是,庫中相應的函數當即帶一個特別的錯誤碼(ERRMEM)返回。這個和其它的錯誤碼定義在 lua.h 中,參見 5.8 節。
_ERRORMESSAGE 惟一的參數是一個描述錯誤的字符串。這個函數的默認定義叫作 _ALERT,它打印信息到 stderr (參見 6.1 節)。標準 I/O 庫重定義了 _ERRORMESSAGE 而且使用調試機制(參見 7 節)去打印一些額外的信息,好比調用堆棧回溯。
Lua 代碼可能經過顯式調用函數 error (參見 6.1 節)生成一個錯誤。Lua 代碼能夠使用函數 call (參見 6.1 節)捕獲一個錯誤。
-------------------
4.8 標籤方法
-------------------
Lua 提供一個強大的機制去擴展它的語義,叫作標籤方法 (tag method)。一個標籤方法是一個程序員定義的在 Lua 程序執行的特定關鍵點調用的函數,它容許程序員在這些關鍵點上改變標準的 Lua 行爲。每個這樣的點叫作一個事件。
特定事件的標籤方法根據事件中的所涉及值的標籤被調用(參見 3 節)。函數 settagmethod 改變給定對(tag,event)關聯的標籤方法。它的第一個參數是標籤,第二個參數是事件的名字(一個字符串,參見下面),第三個參數是新的方法(一個函數),或者 nil 用來恢復對(標籤事件對)的默認行爲。settagmethod 函數返回標籤事件對以前的標籤方法。一個於之對應的函數 gettagmethod 接收一個標籤和一個事件名並返回於之關聯的當前方法。
標籤方法在下面的事件中被調用,由給定名字區分。標籤方法的語義能夠由 Lua 函數描述解釋器在每一個事件的行爲來更好的解釋。這個函數不只展現何時會調用標籤方法,也展現它的參數,返回值和默認的行爲。這裏展現的代碼僅用於說明目的;解釋器中真正的行爲是硬編碼的,而且它比這個模擬更加的高效。這些解釋(rawget, tonumber, call, etc.)中使用的全部的函數在 6.1 節中描述。
``add'':
當 + 運算被應用於非數值型的操做數時會調用到它。
下面的函數 getbinmethod 定義了 Lua 如何爲一個二元運算選擇一個標籤方法。首先,Lua 嘗試第一個操做數,若是它的標籤沒有爲操做定義標籤方法;那麼 Lua 將嘗試第二個操做數,若是它依然失敗,那麼將從標籤 0 得到一個標籤方法。
express
function getbinmethod (op1, op2, event) return gettagmethod(tag(op1), event) or gettagmethod(tag(op2), event) or gettagmethod(0, event) end
使用這個函數, ``add'' 事件的標籤方法是:
函數
function add_event (op1, op2) local o1, o2 = tonumber(op1), tonumber(op2) if o1 and o2 then -- both operands are numeric return o1+o2 -- '+' here is the primitive 'add' else -- at least one of the operands is not numeric local tm = getbinmethod(op1, op2, "add") if tm then -- call the method with both operands and an extra -- argument with the event name return tm(op1, op2, "add") else -- no tag method available: default behavior error("unexpected type at arithmetic operation") end end end
``sub'':
當 - 運算被應用於非數值型的操做數時會調用到它。 它的行爲相似於 ``add'' 事件。
``mul'':
當 * 運算被應用於非數值型的操做數時會調用到它。 它的行爲相似於 ``add'' 事件。
``div'':
當 / 運算被應用於非數值型的操做數時會調用到它。 它的行爲相似於 ``add'' 事件。
``pow'':
當 ^ (冪)運算調用時,即便對於數值型操做數。
編碼
function pow_event (op1, op2) local tm = getbinmethod(op1, op2, "pow") if tm then -- call the method with both operands and an extra -- argument with the event name return tm(op1, op2, "pow") else -- no tag method available: default behavior error("unexpected type at arithmetic operation") end end
``unm'':
當一元運算 - 被應用於非數值型的操做數時會調用到它。
lua
function unm_event (op) local o = tonumber(op) if o then -- operand is numeric return -o -- '-' here is the primitive 'unm' else -- the operand is not numeric. -- Try to get a tag method from the operand; -- if it does not have one, try a "global" one (tag 0) local tm = gettagmethod(tag(op), "unm") or gettagmethod(0, "unm") if tm then -- call the method with the operand, nil, and an extra -- argument with the event name return tm(op, nil, "unm") else -- no tag method available: default behavior error("unexpected type at arithmetic operation") end end end
``lt'':
當比較運算被應用於非數值型或非字符串型的操做數時會調用到它。它至關於 < 操做符。
調試
function lt_event (op1, op2) if type(op1) == "number" and type(op2) == "number" then return op1 < op2 -- numeric comparison elseif type(op1) == "string" and type(op2) == "string" then return op1 < op2 -- lexicographic comparison else local tm = getbinmethod(op1, op2, "lt") if tm then return tm(op1, op2, "lt") else error("unexpected type at comparison"); end end end
其它的比較運算符使用這個標籤方法根據常見的等值性:
a>b <=> b<a
a<=b <=> not (b<a)
a>=b <=> not (a<b)
``concat'':
當連結運算被應用於非字符串型的操做數時會調用到它。
code
function concat_event (op1, op2) if (type(op1) == "string" or type(op1) == "number") and (type(op2) == "string" or type(op2) == "number") then return op1..op2 -- primitive string concatenation else local tm = getbinmethod(op1, op2, "concat") if tm then return tm(op1, op2, "concat") else error("unexpected type for concatenation") end end end
``index'':
當 Lua 試圖返回一個索引不在表中的值時會調用到它。語義參見 ``gettable'' 事件。
``getglobal'':
當 Lua 須要一個全局變量的值時會調用到它。這個方法能夠只爲 nil 設置,且只爲由 newtag 新建的標籤設置。注意標籤是全局變量的當前值。
索引
function getglobal (varname) -- access the table of globals local value = rawget(globals(), varname) local tm = gettagmethod(tag(value), "getglobal") if not tm then return value else return tm(varname, value) end end
函數 getglobal 在基本庫中被定義(參見 6.1 節)。
``setglobal'':
當 Lua 給一個全局變量賦值時會調用到它。對於數值,字符串,表和有默認標籤的 userdata 不能夠設置這個方法。
事件
function setglobal (varname, newvalue) local oldvalue = rawget(globals(), varname) local tm = gettagmethod(tag(oldvalue), "setglobal") if not tm then rawset(globals(), varname, newvalue) else tm(varname, oldvalue, newvalue) end end
函數 setglobal 在基本庫中被定義(參見 6.1 節)。
``gettable'':
當 Lua 調用一個索引變量時會調用到它。對於有默認標籤的表不能夠設置這個方法。
內存
function gettable_event (table, index) local tm = gettagmethod(tag(table), "gettable") if tm then return tm(table, index) elseif type(table) ~= "table" then error("indexed expression not a table"); else local v = rawget(table, index) tm = gettagmethod(tag(table), "index") if v == nil and tm then return tm(table, index) else return v end end end
``settable'':
當 Lua 設置一個索引變量時會調用到它。對於有默認標籤的表不能夠設置這個方法。
function settable_event (table, index, value) local tm = gettagmethod(tag(table), "settable") if tm then tm(table, index, value) elseif type(table) ~= "table" then error("indexed expression not a table") else rawset(table, index, value) end end
``function'':
當 Lua 試圖調用一個不是函數的值時會調用到它。
function function_event (func, ...) if type(func) == "function" then return call(func, arg) else local tm = gettagmethod(tag(func), "function") if tm then for i=arg.n,1,-1 do arg[i+1] = arg[i] end arg.n = arg.n+1 arg[1] = func return call(tm, arg) else error("call expression not a function") end end end
``gc'':
當 Lua 垃圾回收一個 userdata 時會調用到它。這個標籤方法只能夠在 C 中設置,而且不能夠設置給有默認標籤的 userdata。對於每一個被垃圾回收的 userdata, Lua 做和下面函數等價的操做:
function gc_event (obj) local tm = gettagmethod(tag(obj), "gc") if tm then tm(obj) end end
在一個垃圾回收週期中,userdata 的標籤方法被以標籤建立的逆序調用,也就是說,被調用的第一個標籤方法是關聯於程序中建立的最後一個標籤。並且,在週期結束時,Lua 作等價於 gc_event(nil) 調用的事情。(未完待續)