Lua 5.2 的细节改变

2012-09-29 18:47

Lua 5.2 的细节改变

by 云风

at 2012-09-29 10:47:44

original http://blog.codingnow.com/2012/09/lua_52_changes.html

最近想试一下, Lua JIT 2.0 能给我们的系统带来多大的提升。但可惜的是,我们一开始就在用 Lua 5.2 来构建系统,而 Lua JIT 2.0 只支持 Lua 5.1 的 API ,在可以看到的时间里,恐怕也不太会去支持 5.2 了。

所以,我只能想办法反向支持 Lua 5.1 。

语法层面最重大的改变是 Lua 5.2 取消了环境表这个概念,转而提供 _ENV 这个语法糖。

许多小细节是 C API 上的变化。这使得按 Lua 5.2 标准写的 C 库,无法在 Lua 5.1 环境下编译。我打算用 Lua 5.1 的 API 来模拟出来。

    <p>我并没有完全实现所有 Lua 5.2 新增的 API ,比如对 continuation function 的支持,几乎不可能在 Lua 5.1 已有的 API 上完成的。</p>

一些简单的 API 的实现我 放在这里了

luaL_checkversion 很有用,但很难完全实现其功能。无法检测出重复链接。

lua_arith 虽然可以实现,但是直接用 5.1 的 API 模拟出来,会比较繁琐,性能低下。而我也没用它,所以就放弃了。

lua_upvalueidlua_upvaluejoin 估计也不太会用的上。

luaL_Buffer 在 Lua 5.2 里结构变了,所以,新增加的 luaL_buffinitsizeluaL_prepbuffsize 是无法实现相同的语义的,不想改变 5.1 里的 auxlib 的行为,所以只能回避用它们了。


对于 lua 层面的 API ,改变最大的是 load 的语义。这是和 _ENV 一起改变的,完美支持会比较麻烦。我先模拟了一个最简单的

一开始,我想通过修改用户传入的代码,在前面增加一小段代码来模拟 _ENV 的行为。后来发现,我们自己用的库里有一部分是用 binary 格式来在 State 间传递 function 的。而二进制模式的代码,不能通过附加文本代码的方式来改变行为。这个难题下一步再去解决。

Lua 5.2 里的 string.format 中,%s 会默认调用对象的 __tostring 方法,而 5.1 则不会。我不打算更新 5.1 中的 format 方法,还是把 lua 代码中的 format 参数显式调用一下好了。那些地方多是用来输出 log 的,只要避免将 nil 传进入就可以了。


Lua 5.2 改进了 coroutine 的支持,好在 Lua JIT 2.0 也同样支持了。但是 Lua JIT 2 似乎对 __pairs 的支持(默认关闭)是有 bug 的。今天调试了很久终于确认并非我们自己代码的 bug 。

我构造了一个简单的 test case :

local tbl = {}

local a = { values=  { foo = 1 } }

local iter = function(value)
    local function loop(t,k)
        return next(t,k)
    end
    return loop, value
end

local mt = {
    __pairs = function(self)
        local values = rawget(self, "values")
        return iter(values)
    end
}

setmetatable(a,mt)

tbl.a = a

local function trav(k,t)
    if type(t) == "table" then
        print(k, t)
        for k,v in pairs(t) do
            trav(k,v)
        end
    end
end

trav("",tbl)

在 luajit 2.0 下运行,会输出两个 a :

        table: 0x00327fa8
a       table: 0x00321d00
a       table: 0x00321d00

a 这个字段被重复迭代了两次。


今天还发现,debug 库中的 getinfo 信息,lua 5.2 比 lua 5.1 更丰富。5.2 用 nparams 字段,可以取得函数的参数个数。而 5.1 里把参数和 local 变量一视同仁。

在旧代码里,我们试图这样去取得一个函数的参数名字(为了匹配通讯协议)。

    local nparam = debug.getinfo(f,"u").nparams
    local p = {}
    for i=1,nparam do
        local name = debug.getlocal(f,i)
        table.insert(p, name)
    end

在 Lua 5.1 里,是没有 nparams 字段的。而且 getlocal 不能传入一个函数,而必须是一个堆栈层次数。我只好使用了一个变通的方案来达到相同的功能。

local function gen_hook(p)
        return function()
                local i = 1
                while true do
                        local name = debug.getlocal(2,i)
                        if name == nil or string.byte(name) == 40 then -- '(' is 40
                                break
                        end
                        p[i] = name
                        i = i + 1
                end
                error()
        end
end

function debug.params(f)
        local p = {}
        local co
        local function probe()
                debug.sethook(co, gen_hook(p), "c")
                f()
        end

        co = coroutine.create(probe)
        coroutine.resume(co)
        return p
end