自 lua 2.1 之后又发布/发展了 2.2, 2.3, 2.4, 2.5, 到 3.0 版本.
下面简单列出这些版本一些改变的地方:
Change since version 2.1
+ functions now may be declared with any "lvalue" as a name
函数名字可以使用"左值"作为名字(语法上的改进)
+ garbage collection of functions
可以回收函数对象 (函数作为 Value 中的一种了)
+ support for pipes (略)
Change since version 2.2
+ external compiler creates portable binary files that can be loaded faster
提供了 luac 编译器, 可创建可移植的二进制文件(字节码)
+ interface for debugging and profiling
调试和 profile 接口.
+ new "getglobal" fallback
新增了 getglobal 类型的 fallback.
+ new functions for handling references to Lua objects
?? 提供新的 C API 给宿主?
+ new functions in standard lib
库的改进.
+ only one copy of each string is stored
字符串只保存一份(优化内部结构, 如用二叉树存储字符串)
+ expanded documentation, with more examples
增加了文档, 提供更多例子
* Changes since version 2.4
+ io and string libraries are now based on pattern matching;
the old libraries are still available for compatibility
库的改进.
+ dofile and dostring can now return values (via return statement)
执行一个 lua 文件或字符串 可以像函数一样返回值.
+ better support for 16- and 64-bit machines (略)
+ expanded documentation, with more examples (略)
* Changes since version 2.5 (no versions between 2.5 and 3.0)
+ NEW CONCEPT: "tag methods".
Tag methods replace fallbacks as the meta-mechanism for extending the
semantics of Lua. Whereas fallbacks had a global nature, tag methods
work on objects having the same tag (e.g., groups of tables).
Existing code that uses fallbacks should work without change.
新概念: tag 方法. 它代替 fallback 机制, 成为扩展 lua 语义的元机制.
+ new, general syntax for constructors {[exp] = exp, ... }.
语法改进.
+ support for handling variable number of arguments in functions (varargs).
支持函数可变数量参数.
+ support for conditional compilation ($if ... $else ... $end).
支持条件编译. (语法改进)
+ cleaner semantics in API simplifies host code.
接口 API 清晰化.
+ better support for writing libraries (auxlib.h).
对写库提供更好的支持.
+ better type checking and error messages in the standard library.
标准库中更好的类型检查和错误消息.
+ luac can now also undump. (略)
如上所列改进, 大部分我们了解有即可, 下面主要关心两个改进的具体实现:
1. tag 方法的机制.
2. 函数的可变数量参数.
问题1: tag 方法(method)的机制.
tag 方法是对 fallback 的扩充. 在 2.1 版本中引入的 fallback 机制, 当
访问某个对象不支持的算符(或事件, 如 +,-,[] 等)的时候, 会为每种算符提供
一个 fallback 处理函数的槽, 用户从而可以为每种算符设置一个处理函数.
而 tag 为每一种数据类型的每一个算符, 提供了一个 fallback 函数处理的槽.
具体在 fallback.c 实现中的 luaI_IMtable[][] 即是存放此种函数的二维表.
原来 fallback 是一维的, 现在则是二维(数据类型, 算符)的了. 所以称方法
(tag method) 可以指加在一类对象上的函数, 而 fallback 是函数不加在特定
类型对象上.
例如, 在虚拟机 lua_execute() 中, 指令 ADDOP(相当于算符 +):
1. 正常数字执行加法运算; 非数字的调用入 call_arith(IM_ADD); 其中 IM_ADD
即表示 tag 方法的算符维度(lua 中统称 event, 用算符容易理解一些).
2. 在 call_arith() 函数中, 调用入 call_binTM(), bin 指二元运算符, TM 指
tag method.
3. 在 call_binTM() 函数中, 获得操作数(bin 是二元操作意思, 其先尝试左操作数,
然后尝试找右操作数)的值的类型, 即 tag (基本上是 TObject 中的 ttype 字段,
历史上在 lua 1.1 版本中该字段叫 tag).
然后从 luaI_IMtable[tag][event] 二维表中找到系统预定义或用户设置的处理
函数(对 tag 类型的对象可称为方法).
然后调用该函数: callIM(), 即调用入该 tag method.
其它如 PUSHGLOBAL 指令, 可能触发 event='getglobal' 类型的 tag method.
又如 EQOP, LTOP 等指令, 产生 comparison() 调用, 其可能触发 event='less'
等类型的 tag method.
总结: tag method 是对 fallback 的扩充; 在指令执行层次调用 tag method;
tag method 使用二维表存放, get/settagmethod() 本质即维护该二维表.
问题2: 函数的可变数量参数.
在前述研究 lua 1.1 的时候, 我们知道函数调用如果有多余的参数, 会被函数
入口代码将多余参数裁剪掉. 当实参数量少于函数声明的形参数量时, 只要简单
补上 NIL, 就不会发生问题. 问题是, 实参数量多于形参数量时怎么处理的?
举一个例子: function f(a, b, ...) -- 三个点是新增的语法元素, 表示更多参数
print (a, b, arg) --- arg 是表示 '...' 参数的
end
f(1,2,3,4) -- 则 a=1,b=2, arg={3,5; n=2}
查看 lua.stx 文法文件中相关产生式:
1. par -> DOTS {$$=1} -- 如果遇到 '...', 则返回综合属性 $$=1
2. parlist1 -> parlist1 ',' par {代码1}
在关联代码块1中, 调用 close_parlist($1), 当最后一个 par 是 '...'
时, $1=1, 其它情况 $1=0.
在 函数 close_parlist(dots=1) 中, 如果 dots=1, 则产生代码:
VARARGS nlocalvar -- 表示此函数接收可变数量参数.
add_localvar("arg") -- 为此函数添加一个局部变量 arg.
下面查看虚拟机中 VARARGS 指令(新增的指令)的执行:
调用 adjust_varargs(...), 进入该函数查看, 大致几十行代码,
创建一个 array 对象, 产生属性 arg.n 为可变参数数量, arg[1~n]
是这些可变参数, 然后将该对象存入参数 "arg" 的位置.
可以与 js 相比, js 不需要声明有 '...' 参数, 直接使用内置对象
arguments 就能访问到所有参数. 相比我觉得 lua 这样实现反而较
复杂的. 也许实现为这种方式, 是因为这样做对原有 lua 虚拟机
的代码改动较小.