Lua 5.1 参考手册(三)

云风几年前译的Lua 5.1文档,这里直接转载过来,方便记录。原地址

2.6 - 可视规则

Lua 是一个有词法作用范围的语言。 变量的作用范围开始于声明它们之后的第一个语句段, 结束于包含这个声明的最内层语句块的结束点。 看下面这些例子:

x = 10                -- 全局变量
do                    -- 新的语句块
  local x = x         -- 新的一个 'x', 它的值现在是 10
  print(x)            --> 10
  x = x+1
  do                  -- 另一个语句块
    local x = x+1     -- 又一个 'x'
    print(x)          --> 12
  end
  print(x)            --> 11
end
print(x)              --> 10  (取到的是全局的那一个)

注意这里,类似 local x = x 这样的声明, 新的 x 正在被声明,但是还没有进入它的作用范围, 所以第二个 x 指向的是外面一层的变量。

因为有这样一个词法作用范围的规则, 所以可以在函数内部自由的定义局部变量并使用它们。 当一个局部变量被更内层的函数中使用的时候, 它被内层函数称作 upvalue(上值),或是 外部局部变量。

注意,每次执行到一个 local 语句都会定义出一个新的局部变量。 看看这样一个例子:

a = {}
local x = 20
for i=1,10 do
  local y = 0
  a[i] = function () y=y+1; return x+y end
end

这个循环创建了十个 closure(这指十个匿名函数的实例)。 这些 closure 中的每一个都使用了不同的 y 变量, 而它们又共享了同一份 x。

2.7 - 错误处理

因为 Lua 是一个嵌入式的扩展语言, 所有的 Lua 动作都是从宿主程序的 C 代码调用 Lua 库 (参见 lua_pcall)中的一个函数开始的。 在 Lua 编译或运行的任何时候发生了错误,控制权都会交还给 C , 而 C 可以来做一些恰当的措施(比如打印出一条错误信息)。

Lua 代码可以显式的调用 error 函数来产生一条错误。 如果你需要在 Lua 中捕获发生的错误, 你可以使用 pcall 函数。

2.8 - Metatable(元表)

Lua 中的每个值都可以用一个 metatable。 这个 metatable 就是一个原始的 Lua table , 它用来定义原始值在特定操作下的行为。 你可以通过在 metatable 中的特定域设一些值来改变拥有这个 metatable 的值 的指定操作之行为。 举例来说,当一个非数字的值作加法操作的时候, Lua 会检查它的 metatable 中 “__add” 域中的是否有一个函数。 如果有这么一个函数的话,Lua 调用这个函数来执行一次加法。

我们叫 metatable 中的键名为 事件 (event) ,把其中的值叫作 元方法 (metamethod)。 在上个例子中,事件是 “add” 而元方法就是那个执行加法操作的函数。

你可以通过 getmetatable 函数来查询到任何一个值的 metatable。

你可以通过 setmetatable 函数来替换掉 table 的 metatable 。 你不能从 Lua 中改变其它任何类型的值的 metatable (使用 debug 库例外); 要这样做的话必须使用 C API 。

每个 table 和 userdata 拥有独立的 metatable (当然多个 table 和 userdata 可以共享一个相同的表作它们的 metatable); 其它所有类型的值,每种类型都分别共享唯一的一个 metatable。 因此,所有的数字一起只有一个 metatable ,所有的字符串也是,等等。

一个 metatable 可以控制一个对象做数学运算操作、比较操作、连接操作、取长度操作、取下标操作时的行为, metatable 中还可以定义一个函数,让 userdata 作垃圾收集时调用它。 对于这些操作,Lua 都将其关联上一个被称作事件的指定健。 当 Lua 需要对一个值发起这些操作中的一个时, 它会去检查值中 metatable 中是否有对应事件。 如果有的话,键名对应的值(元方法)将控制 Lua 怎样做这个操作。

metatable 可以控制的操作已在下面列出来。 每个操作都用相应的名字区分。 每个操作的键名都是用操作名字加上两个下划线 ‘’ 前缀的字符串; 举例来说,”add” 操作的键名就是字符串 “add”。 这些操作的语义用一个 Lua 函数来描述解释器如何执行更为恰当。

这里展示的用 Lua 写的代码仅作解说用; 实际的行为已经硬编码在解释器中,其执行效率要远高于这些模拟代码。 这些用于描述的的代码中用到的函数 ( rawget , tonumber ,等等。) 都可以在 §5.1 中找到。 特别注意,我们使用这样一个表达式来从给定对象中提取元方法

metatable(obj)[event]

这个应该被解读作

rawget(getmetatable(obj) or {}, event)

这就是说,访问一个元方法不再会触发任何的元方法, 而且访问一个没有 metatable 的对象也不会失败(而只是简单返回 nil)。

下面这个 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作。 首先,Lua 尝试第一个操作数。 如果这个东西的类型没有定义这个操作的处理器,然后 Lua 会尝试第二个操作数。

function getbinhandler (op1, op2, event)
	return metatable(op1)[event] or metatable(op2)[event]
end

通过这个函数, op1 + op2 的行为就是

function add_event (op1, op2)
  local o1, o2 = tonumber(op1), tonumber(op2)
  if o1 and o2 then  -- 两个操作数都是数字?
    return o1 + o2   -- 这里的 '+' 是原生的 'add'
  else  -- 至少一个操作数不是数字时
    local h = getbinhandler(op1, op2, "__add")
    if h then
      -- 以两个操作数来调用处理器
      return h(op1, op2)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end
function unm_event (op)
  local o = tonumber(op)
  if o then  -- 操作数是数字?
    return -o  -- 这里的 '-' 是一个原生的 'unm'
  else  -- 操作数不是数字。
    -- 尝试从操作数中得到处理器
    local h = metatable(op).__unm
    if h then
      -- 以操作数为参数调用处理器
      return h(op)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end
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  -- 原生字符串连接
  else
    local h = getbinhandler(op1, op2, "__concat")
    if h then
      return h(op1, op2)
    else
      error(···)
    end
  end
end
function len_event (op)
  if type(op) == "string" then
    return strlen(op)         -- 原生的取字符串长度
  elseif type(op) == "table" then
    return #op                -- 原生的取 table 长度
  else
    local h = metatable(op).__len
    if h then
      -- 调用操作数的处理器
      return h(op)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

关于 table 的长度参见 §2.5.5 。

function getcomphandler (op1, op2, event)
  if type(op1) ~= type(op2) then return nil end
  local mm1 = metatable(op1)[event]
  local mm2 = metatable(op2)[event]
  if mm1 == mm2 then return mm1 else return nil end
end

“eq” 事件按如下方式定义:

function eq_event (op1, op2)
  if type(op1) ~= type(op2) then  -- 不同的类型?
    return false   -- 不同的对象
  end
  if op1 == op2 then   -- 原生的相等比较结果?
    return true   -- 对象相等
  end
  -- 尝试使用元方法
  local h = getcomphandler(op1, op2, "__eq")
  if h then
    return h(op1, op2)
  else
    return false
  end
end

a ~= b 等价于 not (a == b) 。

function lt_event (op1, op2)
  if type(op1) == "number" and type(op2) == "number" then
    return op1 < op2   -- 数字比较
  elseif type(op1) == "string" and type(op2) == "string" then
    return op1 < op2   -- 字符串按逐字符比较
  else
    local h = getcomphandler(op1, op2, "__lt")
    if h then
      return h(op1, op2)
    else
      error(···);
    end
  end
end

a > b 等价于 b < a.

function le_event (op1, op2)
  if type(op1) == "number" and type(op2) == "number" then
    return op1 <= op2   -- 数字比较
  elseif type(op1) == "string" and type(op2) == "string" then
    return op1 <= op2   -- 字符串按逐字符比较
  else
    local h = getcomphandler(op1, op2, "__le")
    if h then
      return h(op1, op2)
    else
      h = getcomphandler(op1, op2, "__lt")
      if h then
        return not h(op2, op1)
      else
        error(···);
      end
    end
  end
end

a >= b 等价于 b <= a 。 注意,如果元方法 “le” 没有提供,Lua 就尝试 “lt” , 它假定 a <= b 等价于 not (b < a) 。

function gettable_event (table, key)
   local h
   if type(table) == "table" then
     local v = rawget(table, key)
     if v ~= nil then return v end
     h = metatable(table).__index
     if h == nil then return nil end
   else
     h = metatable(table).__index
     if h == nil then
       error(···);
     end
   end
   if type(h) == "function" then
     return h(table, key)      -- 调用处理器
   else return h[key]          -- 或是重复上述操作
   end
 end
 function settable_event (table, key, value)
  local h
  if type(table) == "table" then
    local v = rawget(table, key)
    if v ~= nil then rawset(table, key, value); return end
    h = metatable(table).__newindex
    if h == nil then rawset(table, key, value); return end
  else
    h = metatable(table).__newindex
    if h == nil then
      error(···);
    end
  end
  if type(h) == "function" then
    return h(table, key,value)    -- 调用处理器
  else h[key] = value             -- 或是重复上述操作
  end
end
function function_event (func, ...)
   if type(func) == "function" then
     return func(...)   -- 原生的调用
   else
     local h = metatable(func).__call
     if h then
       return h(func, ...)
     else
       error(···)
     end
   end
 end