(接上篇)
-------------------
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 获得一个标签方法。
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‘‘:
当一元运算 - 被应用于非数值型的操作数时会调用到它。
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‘‘:
当连结运算被应用于非字符串型的操作数时会调用到它。
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) 调用的事情。
(未完待续)