编译原理之学习 lua 3.0 (六) tag方法和可变数量参数

自 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 虚拟机
的代码改动较小.

你可能感兴趣的:(lua,源码学习)