编译原理之学习 lua 2.1 (五) 新增加的 fallback 和 oo 等支持

Lua 1.1 我比较关心的部分叙述得差不多了, 还有些东西可能还要简要的记一下, 免得忘记:
  1. lua 1.1 的回收是简单的标记-清扫算法, 当前只有字符串和 array 需要标记和回收.
  2. 比起 lua 1.0, 还支持对 cfunction 的调用, 这属于和宿主语言接口的设计问题, 其中 lua 的设计方式
      值得借鉴.

下一个lua 的主版本是 lua 2.1, 按照其 README 所述, 有如下几个主要改进:
  1. 面向对象的支持.
  2. fallbacks (不知道如何翻译合适) 
  3. tables(array) 语法简化了
  4. 许多的内部改进.

这里有一个 lua 历史简述的文章, 可以参考:
http://blog.sina.com.cn/s/blog_4be3060201008j8l.html

 

对于 3. tables 的语法简化, 是在其构造器上丢弃了@,而统一使用了单一的括号来表示记录和
列表. 在 lua.stx 文法文件中, 可以对比这一改变. (我猜不需要对支持创建表的指令做很大改变)

对于 4. 内部改进, 我略略的看了一下代码, 常量表改为用二叉树表示了, 内存分配单独分出一个
c 文件及其 luaI_ 系列的分配函数, 栈,全局变量等表格现在可以动态扩充了(1.1 中用满了就会
报错退出)... 等等小的改进, 理论上这些小的改进积累起来, 可能会产生大的变化.

下面主要关心 1, 2 方面的改进.

1. 面向对象的支持. 例子:

o = {name = 'Tom', age = 25}  -- 定义一个对象
function o:hello(word) -- 定义对象 o 里面的方法 hello.
    print (word .. self.name)  -- 打印 'xxx Tom'
end
o:hello('Hello ')  --- 以面向对象的方式调用, 自动传动 o 作为 self 给函数hello, 打印出 'Hello Tom'
o.hello(o, 'Good bye ') --- 显式传递o 参数做 self, 打印出 'Good bye Tom'

在这个例子中, 函数 hello() 的声明中名字前面的 o: 表明该函数是定义在对象 o 里面的.
调用方式 o:hello(...) 将 o 作为第一个参数 self 传递给函数 hello(), 而用形式 o.hello(...)
调用的则必须显式地以 o 作为第一个参数, 也即 self. 实现方式如下.

在文法文件中, 函数声明多了几个相关产生式:
   functionlist -> functionlist method | ...  
   method -> FUNCTION NAME1 ':' NAME2 {代码块1} body {代码块2}
                    $1             $2        $3 $4        $5         $6      $7
在 method 的这个产生式中, 解析 o:hello() 这种形式的面向对象的方法定义.
其中代码块1 中: 初始化函数对象名为 $4 (即NAME2), 并给其添加第一个未列出的参数 self.
代码块2 中, 产生指令
   PUSH $2 (即示例的对象 o)
   PUSHSTRING $4(NAME2)
   PUSHFUNCTION $6 (即创建出的函数对象), 此时堆栈中为 [o, 'hello', func  )
   STOREINDEXED0  -- 执行 o['hello'] = func

这样, 为对象 o 创建/设置了一个名为 hello 的函数.

在调用处, 相关产生式新增的有:
   functioncall -> funcvalue funcParams
   funcvalue -> varexp ':' NAME {代码块1} | ...
在代码块1 中, 产生指令 PUSHSELF NAME, 如 PUSHSELF 'hello'; 注意 varexp 计算的值
压入了堆栈中.

下面看虚拟机中新增的指令 PUSHSELF 'hello' 如何执行:
   case PUSHSELF:
     在执行的时候, 堆栈为 [o  ), 即对象 o 已经在栈中了.
     将字符串 'hello' 压入堆栈中, 此时堆栈为 [o, 'hello'  )
     执行 pushsubscript(), 此函数类似 1.1 中的 pushindexed(), 即或 o['hello'] 的值
       此时栈中结果为 [o.hello  ), 其应该是一个函数对象
     压栈 o, 当做 self 参数, 此时堆栈中为 [o.hello, o  )

下一步产生对 [o.hello, o ) 调用的指令, 是必然的情形了, 与普通调用不同之处在于堆栈中自动多了一个
self 参数. 至此, 已经基本知道面向对象的支持的实现机理: 在语法解析上有改进, 以及增加相应指令支持.

 

对于 fallback, 为支持该功能, 似乎有较大的改动:
1. 虚拟机中对于函数调用和返回指令(CALLFUNC, RETCODE) 有了很大变化.
2. 可以使用 setfallback() 函数设置自己的 fallback 函数, 在 fallback.c 实现中使用表格存放
   所有种类的 fallback 函数, 并实现/初始化为缺省 fallback 函数.
3. 在多个指令执行中, 添加对 fallback 情况下的调用.

下面先给出 lua 2.1 自己的 fallback 例子:
  function my_fallback(receiver, params, operator)  -- 定义自己的一个 fallback 处理函数
     if type(receiver) == 'table' then  -- 如果参与操作的对象是表格...
        return receiver[operator](receiver, params) -- 调用该表格中定义的 operator 函数
     else
        return oldFallback(...) -- 否则传递给旧的(可能是缺省的) fallback 函数处理
     end
  end

  oldFallback = setfallback('arith', my_fallback) -- 设置在数学运算时, 调用 my_fallback

  o = {x=3, y=4}
  function o:add(params)  -- 当对 o 进行加法(add) 运算时, operator='add' 调用此函数
     print ('o:add is called', self, params)
  end
  test = o + 567 -- 对对象 o 没有加法语义, 故以 operator='add' 进入到 fallback 函数中处理.

这个例子给出了如何使用 fallback 的方法, 我个人觉得 fallback 可以对各类对象的各种操作
  都能处理, 似乎太过宽泛了... 但实在没有用过, 则最好还是不做评价, 只关心它是如何实现的.

首先 1, 在虚拟机实现的函数 lua_execute() 中, CALLFUNC 指令被改变为调用 do_call() 函数,
其中 do_call() 函数能够对函数(也含 cfunc)进行执行, 主要方式是递归调用 lua_execute() 函数,
以执行一个 lua 或 c 的函数. 这样, 对 fallback 函数的调用, 也是能够通过 do_call() 进行了.

对比 lua 前一个版本, 虚拟机的 CALLFUNC 指令仅在 lua_execute() 函数中改变 pc,base 地址
即可, 不需要重入/递归 lua_execute() 函数. 我认为改为 2.1 中的形式, 就是为了支持 fallback
而为主要原因.

RETCODE 也相应的有所变化, 从 lua_execute() 函数中返回到外层的 do_call() 调用, 进而返回
外层的 lua_execute(), 不再详述.

 

对于函数 setfallback(), 实现是很简单的, 即将用户定义的 fallback 函数设置到全局 fallback
函数的表格 luaI_fallBacks[] 里面即可.

 

在很多指令中, 会产生对 fallback 的调用. 下面举几个例子说明:
1. 例如 ADDOP 指令实现为:
   得到操作数 l, r (左操作数, 右操作数)
   if l,r 都是数字类型
      结果 = l+r 入栈
   else
      call_arith('add') -- 里面即调用 fallback, 以 operator='add' 为参数

其中函数 call_arith(op = 'add') 为例:
  PUSHSTRING 'add'  -- 将操作符名字入栈, 此时栈中为 [l, r, 'add'  ), 即左右操作数和运算符名
  do_call(FB_ARITH 类型的 fallback) -- 调用 arith 类型的 fallback 处理函数
共有数种类型的 fallback 可以注册于 lua 中, 指令根据自己的操作类型, 选择对应的 fallback 函数
进入.

2. 再举例如 STOREINDEXED 指令, 如果栈中 [obj, index, value  )  的 obj 不是 array, 则调用
   都 fallback 类型 FB_SETTABLE 的函数去处理.

3. 再例如 EQOP, LTOP 等比较运算符, 当不可比较时, 选择 fallback 类型 FB_ORDER 的函数处理.

 

似乎 lua 的后期版本又取消了 fallback 的特性, 那我们也许只是参观了一下历史遗迹, 并思考它能否
为未来提供什么启发.

 


 

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