Lua 笔记

Table of Contents

1 简介1

Lua2是一个简洁、轻量、可扩展的脚本语言,它的官方版本只包括一个精简的核心和最基本的库。这使得 Lua 体积小、启动速度快。它用标准 C 语言编写并以源代码形式开放,编译后仅仅一百余 K,可以很方便的嵌入别的程序里。和许多 “大而全” 的语言不一样,网路通讯、图形界面等都没有默认提供。但是 Lua 可以很容易地被扩展:由宿主语言(通常是 C 或 C++)提供这些功能,Lua 可以使用它们,就像是本来就内置的功能一样。事实上,现在已经有很多成熟的扩展模块可供选用。

Lua 是一种多重编程范式的程序设计语言:它只提供了很小的一个特性集合来满足不同编程范式的需要,而不是为某种特定的编程范式提供繁杂的特性支持。例如,Lua 并不提供继承这个特性,但是你可以用元表来模拟它。诸如名字空间、类这些概念都没有在语言基本特性中实现,但是我们可以用表结构(Lua 唯一提供的复杂数据结构)轻易模拟。Lua 可以在运行时随时构造出一个函数,并把它看作一个对象(正是所谓的 first class function),这个特性可以很好的满足函数式编程的需要。正是提供了这些基本的元特性,我们可以任意的对语言进行自需的改造。

Lua 原生支持的数据类型 非常之少,它只提供了数字(缺省是双精度浮点数,可配置)、布尔量、字符串、表(table)、子程序(function)、协程(coroutine)以及用户自定义数据(userdata)这几种。但是其处理表和字符串的效率非常之高,加上元表(metatable)的支持,开发者可以高效的模拟出需要的复杂数据类型(比如集合、数组等)。

Lua 是一个动态弱类型语言,支持增量式垃圾收集策略。有内建的,与操作系统无关的协作式多线程(coroutine)支持。

2 实践3 , 4

2.1 安装

    sudo aptitude install lua5.3 liblua5.3-0

2.2 上手

学习一门语言,我已经习惯了要做的第一件事情就是让代码给我打印个 hello,world 出来。Lua 做这个只需要一行代码:

    print "hello,Lua"

先不管为什么要这么写,这是 Lua 的语法问题,后面会介绍。那么这段代码该怎么执行呢?

前面介绍了,Lua 是一种嵌入式语言,那怎么嵌入到别的语言中呢?以 C 语言举例,可以想象的是,肯定不应该在 C 代码里嵌套大段的 Lua 代码(为什么?)。Lua 代码必然应该作为一个单独的文件(比如 hello.lua)被 C 加载、解释、执行,然后返回执行结果。那么 C 代码是怎么完成这个过程的呢?

正如其他脚本语言一样,官方肯定提供了一个这样的交互式解析器,没错,那就是 lua.exe。Lua 在早期几乎都是被用来嵌入到其它系统中使用,所以源代码通常被编译成动态库或静态库被宿主系统加载或链接。但随着 Lua 的第三方库越来越丰富,人们开始倾向于把 Lua 作为一门独立语言来使用。Lua 的官方版本里也提供了一个简单的独立解析器,便是 lua.c 所实现的这个。并有 luac.c 实现了一个简单的字节码编译器,可以预编译文本的 Lua 源程序。5

先查看一下用法:这是一个好习惯。

    lua --help

我们可以先用 lua.exe 中通过 dofile()加载 hello.lua 运行一下试试:

    lua hello.lua

TODO:插入运行图片

没有问题。那么回到刚才的关键问题:c 代码如何执行 Lua 文件并获取结果的呢?这就要看一下 lua.c 文件了。

lua.c 代码只有 行,使用 gdb 调试或者看一下源码中基本上涉及到以下几行:

    static int pmain (lua_State *L) {
        int argc = (int)lua_tointeger(L, 1);
        char **argv = (char **)lua_touserdata(L, 2);
        /* 省略。。。。。 */
        luaL_openlibs(L);  /* open standard libraries */
        /* 省略。。。。。 */
        else if (script == argc && !(args & (has_e | has_v))) {  /* no arguments? */
            if (lua_stdin_is_tty()) {  /* running in interactive mode? */
                print_version();
                doREPL(L);  /* do read-eval-print loop */
            }
            else dofile(L, NULL);  /* executes stdin as a file */
        }
        lua_pushboolean(L, 1);  /* signal no errors */
        return 1;
    }


    int main (int argc, char **argv) {
        int status, result;
        lua_State *L = luaL_newstate();  /* create state */
        if (L == NULL) {
            l_message(argv[0], "cannot create state: not enough memory");
            return EXIT_FAILURE;
        }
        lua_pushcfunction(L, &pmain);  /* to call 'pmain' in protected mode */
        lua_pushinteger(L, argc);  /* 1st argument */
        lua_pushlightuserdata(L, argv); /* 2nd argument */
        status = lua_pcall(L, 2, 1, 0);  /* do the call */
        result = lua_toboolean(L, -1);  /* get result */
        report(L, status);
        lua_close(L);
        return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
    }

上面代码中几个函数 google 一下,就可以了解到 C 是通过哪些函数加载执行 lua 文件6,又是如何获取结果的。现在我们可以自己写一个简单的 c 函数来调用 lua 脚本:

    #include <stdio.h>
    #include <string.h>
    #include <assert.h>
    #include <lua5.3/lua.h>
    #include <lua5.3/lauxlib.h>
    #include <lua5.3/lualib.h>

    int main(int argc, char *argv[])
    {
        lua_State *luaEnv = luaL_newstate();
        luaL_openlibs(luaEnv);

        /* load lua file */
        ret = luaL_dofile(luaEnv, "hello.lua");
        assert(ret == LUA_OK);

        lua_close(luaEnv);
        return 0;
    }

执行一下,没有问题。

TODO:插入图片

现在已经能让 lua 脚本在 c 环境中跑起来了,下面的任务就是学习 lua 语言本身的一些知识来体会 lua 的简单与强大了。

2.3 类型和值3.5

Lua 是一门动态类型语言。这意味着 变量没有类型;只有值才拥有并携带自己类型,任何变量可以包含任何类型的值 。Lua 中有八种基本类型 nil(空) 、boolean(布尔)、number(数字)、string(字符串)、userdata(自定义类型)、function(函数)、thread(线程)、table(表) 。函数 type 可以根据一个值返回其类型名称:

    print(type(nil))                -->nil
    print(type(true))               -->boolean
    print(type(10.4*3))             -->number
    print(type("hello world"))      -->string
    print(type(print))              -->function

2.3.1 nil(空)

nil 是一种类型,其只有一个值 nil,它的主要功能是用于区别其它任何值。典型的应用是:一个全局变量在第一次赋值前默认值就是 nil,将 nil 赋予一个全局变量就等同于删除它。Lua 将 nil 用于表示一种无效值(non-value)的情况,即没有任何有效值的情况。

2.3.2 boolean(布尔)

boolean 类型有两个可选值:false 和 true。 Lua 将值 false 和 nil 视为假,而将除此之外的其它值视为真。 需要注意的是:Lua 在条件测试中,将数字零和空字符串也都视为真,这点不同于某些脚本或者语言。

2.3.3 number(数字)

number 类型用于表示实数即双精度浮点数。Lua 没有整型类型,而用数字来表示 32 位整数。通过重新编译 Lua 也可以非常方便的使用其它类型来表示数字,比如:长整型 long 或单精度浮点数 float,这点详见发行版中的 luaconf.h 文件。

2.3.4 string(字符串)

Lua 中的字符串通常表示一个字符序列。 Lua 完全采用 8 位编码,Lua 字符串中的字符可以具有任何数值编码,包括数值 0 。也就是说,可以将任意二进制数据存储到一个字符串中。Lua 的字符串是不可变的值(immutable values),不能像 C 语言中那样直接修改字符串的某个字符,而是应该根据修改要求来创建一个新的字符串,比如:

    a = "one string"
    b = string.gsub(a,"one","another") -- 修改字符串的一部分
    print(a)                           --one string
    print(b)                           --another string

Lua 的字符串与其它 Lua 对象(比如:table 或函数等)一样,都是自动内存管理机制所管理的对象,即无需担心字符串的分配和释放而由 Lua 全权处理。一个字符串可以小到只包含一个字母,也可以大到包含整本书,Lua 能高效地处理长字符串,在 Lua 程序中操作 100K 或 1M 的字符串是很常见的事情。

字符串用一对匹配的单引号或双引号来界定,建议在程序中使用相同类型的引号,除非字符串本身包含引号,那么可以使用另一种引号来界定字符串,或者使用反斜杠对引号进行转义,Lua 的转义序列类似于 C 语言。

还可以 用一对匹配的双方括号来界定一个字母字符串 ,就像写块注释那样。 以这种形式书写的字符串可以延伸多行,且 Lua 不会解释其中的转义序列, 如果字符串的第一个字符是换行符,则 Lua 会忽略它,这种写法对那种含有程序代码的字符串尤为有用,比如:

    page = [[
    <html>
    <head>
    <title>An HTML Page</title>
    </head>
    <body>
    <a href="http://www.lua.org">lua</a>
    </body>
    </html>
    ]]

有时候字符串中可能包含这样的内容: a=b[c[i]] 或者 包含已经被注释掉的代码。为了解决这种问题,需要在两个方括号之间加上任意数量的等号, 如: [===[ ,这样字符串只有遇到内嵌有相同数量等号的双右括号时才会结束,即 ]===] ,否则 Lua 会忽略它。通过这种方式,就可以不用对此进行转义了。同理,注释块时也可以采用这种机制,即 --[===[ 开始, ]===] 结束。

Lua 提供了运行时的数字与字符串的自动转换,即在一个字符串上应用算术操作时,Lua 会尝试将这个字符串转换成一个数字,同样,在 Lua 期望一个字符串但却得到一个数字时,也会将数字转换为字符串:

    print("10" + 1)                 -->11
    print(10 .. 20)                 -->1020

在 Lua 中,.. 是字符串连接操作符,当直接在一个数字后面输入它的时候, 必须要用一个空格来分隔它们 ,否则,Lua 会将第一个点理解为一个小数点。除此,Lua 还提供了数字与字符串之间显示转换的函数,比如:tonumber 和 tostring。在 Lua 5.1 中,还可以在字符串前面放置操作符#来获得该字符串的长度:

    a = "hello"
    print(#a)        -->5

2.3.5 table(表)

table 类型实现了关联数组(associative array)。关联数组是一种具有特殊索引方式的数组,类似于 STL 中的 map。在这里,不仅可以通过整数来索引它,还可以使用字符串或其它类型的值(除了 nil)来索引它。table 没有固定大小,可以动态地添加任意数量的元素到一个 table 中。table 是 Lua 中主要的(事实上也是仅有的)数据结构机制,具有强大的功能。基于 table,可以以一种简单、统一和高效的方式来表示普通数组、符号表(symbol table)、集合、记录、队列和其它数据结构。Lua 也是通过 table 来表示模块(module)、包(package)和对象(object)的,比如:当输入 io.read 的时候,其含义是 io 模块中的 read 函数,对于 Lua 而言,这表示使用字符串 read 作为 key 来索引 table io。

在 Lua 中, table 既不是值也不是变量,而是对象 。可以将一个 table 想象成一种动态分配的对象,程序仅持有一个对它们的引用或指针,Lua 不会暗中产生 table 副本或创建新的 table。table 的创建是通过构造表达式完成的,最简单的构造表达式是 {}:

    a = {}              -- 创建一个 table,并将它的引用存储到 a

table 永远是匿名的 ,一个持有 table 的变量与 table 自身之间没有固定的关联性,当一个程序再也没有对一个 table 的引用时,Lua 的垃圾收集器(garbage collector)最终会删除该 table,并复用其内存:

    a["x"] = 10        -- 新条目,key="x", value=10
    a[20] = "hello"   -- 新条目,key=20, value="hello"
    b = a                --b 与 a 引用了同一个 table
    a = nil               -- 现在只有 b 还在引用 table
    b = nil               -- 再也没有对 table 的引用了,Lua 会回收其内存

与全局变量类似,当 table 的某个元素没有初始化时,其 value 为 nil,将 nil 赋予 table 的某个元素来删除该元素。这种相似性的缘由是因为 Lua 正是将全局变量存储在一个普通 table 中。

Lua 对于诸如 a["name"] 的写法提供了一种更简便的语法糖(syntactic sugar),可以直接输入 a.name。需要注意的是:a.x 和 a[x] 不是一回事,前者表示 a["x"] ,表示以字符串 "x" 来索引 table,而后者是以变量 x 的值来索引 table,比如:

    a = {}
    x = "y"
    a[x] = 10        -- 将 10 放入字段 “y”
    print(a[x])       -->10
    print(a.x)         -->nil

虽然可以用任何值作为一个 table 的索引,也可以用任何数字作为数组索引的起始值,但就 Lua 的习惯而言,数组通常以 1 作为索引的起始值,这不同于 C 语言的下标从 0 开始的习惯。

长度操作符#用于返回一个数组或线性表的最后一个索引值或其大小,比如:

    -- 打印 a 中所有元素
    for i=1, #a do
        print(a[i])
    end
    对于长度操作符 Lua 中的习惯用法:
    print(a[#a])        -- 打印列表 a 的最后一个值
    a[#a] = nil         -- 删除最后一个值
    a[#a+!] = v       -- 将 v 添加到列表末尾

对于下面数组大小是多少呢:

    a = {}
    a[10000] = 1

大小是 0 而不是 10000 ,这是因为数组也是一个 table,而 table 中所有未初始化的元素的索引结果都是 nil。Lua 将 nil 作为界定数组结尾的标志,当一个数组有空隙(Hole)时,即中间含有 nil 时,长度操作符会认为这些 nil 元素就是结尾标记,因此,应该避免对含有空隙的数组使用长度操作符。当然,可以使用函数 table.maxn 来返回一个 table 的最大正索引数:

    print(table.maxn[a])        -->10000

2.3.6 function(函数)

在 Lua 中,函数是作为第一类值来看待的,即函数可以存储在变量中,可以通过参数传递给其它函数,还可以作为其它函数的返回值。这种特性使语言具有了极大的灵活性。

Lua 既可以调用以自身 Lua 语言编写的函数,又可以调用以 C 语言编写的函数。Lua 所有的标准库都是用 C 语言写的,标准库中包括对字符串的操作、table 的操作、I/O、操作系统的功能调用、数学函数和调试函数。

2.3.7 userdata(自定义类型)

由于 userdata 类型可以将任意的 C 语言数据存储到 Lua 变量中,所以这种类型没有太多的预定义操作,只能进行赋值和相等性测试。userdata 用于表示一种由应用程序或 C 语言库所创建的新类型,例如标准的 I/O 库就用 userdata 来表示文件。

2.3.8 thread(线程)

后面系列会详细分析这种类型,并介绍协程(coroutine)。

2.4 表达式

算数运算符对整数来说,计算结果的符号永远与第二个参数相同。对实数来说,它有一些额外的用途。例如,x%1 表示 x 的小数部分,x-x%1 表示 x 的整数部分。类似的,x-x%0.01 将 x 精确到小数点后 2 位。

在表的一个构造式中还可以用分号代替逗号。通常会将分号用于分隔构造式中不同的成分,例如将列表部分预记录部分明显的区分开。

2.5 语句

C 中 continue 用法如下:

    int i = 10;
    do {
        if (i == 5) {
            continue;
        }
        printf("%d\n", i);
    } while(i--);

为什么 Lua 没有 continue?官方也有作出了解释。

This is a common complaint. The Lua authors felt that continue was only one of a number of possible new control flow mechanisms (the fact that it cannot work with the scope rules of repeat/until was a secondary factor.)

In Lua 5.2, there is a goto statement which can be easily used to do the same job.

在 Lua 中,repeat until 有点类似于 C() 的 do while,但在机制上有一点区别,在 Lua 的 until 的条件表达式中,表达式中的变量可以是 repeat until 代码块内声明的局部变量,但在 C() 中,while 的条件表达式中的变量不允许出现 do while 内部声明的临时变量,必须是 do while 外部声明的变量。

基于这个原因,我们假设 Lua 支持了 continue,考虑以下代码:

    local a = 1  -- outer
    repeat
        if f() then
            continue
        end
        local a = 1  -- inner
        ...
    until a == 0

由于 continue 会跳过后面 local a = 1 的声明,那么 until a == 0 到底是指内部的 local a 还是外部的 a 就会有歧义了!

那么如何用别的机制来代码 continue 呢?有一个比较简单的方法:在循环内套一个 repeat … until true,再将想用 continue 的地方写成 break。如:

    for i = 1, 10 do
        repeat
            if i == 5 then
                break
            end
            print(i)
        until true
    end

2.6 函数

2.6.1 多重返回

Lua 允许函数返回多个结果:只有当一个函数调用是一系列表达式中的最后一个元素时,才能获得它的全部返回值。那么这是如何实现的呢?3.6.1 unpack3.6.2函数是一个比较特殊的函数。它接受一个数组作为参数,并从下标 1 开始返回该数组的所有元素,它的一个重要用途体现在“泛型调用”机制中。泛型调用机制可以动态的以任何参数来调用任何函数,个人感觉类似 C++中的 bind 函数。

2.6.2 变长参数

如果变长参数中可能包含一些故意传入的 nil,那么此时就需要用函数 select 来访问变长参数了。 调用 select 时,必须传入一个固定的实参 selector 和一系列变长参数。如果 selector 为数字 n,那么 select 返回它的第 n 个可变实参;否则 selector 只能为字符串 “#”,这样 select 会返回变长参数的总数。

    for i =1 ,select("#", ...) do
      local arg = select(i, ...)  --->得到第 i 个变长参数对应的可变实参。
        <循环体>
    end

2.6.3 具名实参

Lua 函数是不支持默认参数的,为了达到这样的效果,出现了具名实参这个东西。传递给函数的是一个表,形参是表的索引,通常是给真正的函数外面包裹一层检查参数的函数来达到默认实参的效果。举例如创建一个新窗口可能会有很多参数是可选的,那么可以如下实现:

    function Window(option)
        -- 检查必要的参数
        if type(option.title) != "string" then
            error("no title")
        elseif type(options.width) ~= "number" then
            error("no width")
        elseif type(options.height) ~= "number" then
            error("no height")
        end
        -- 其它参数可选
        _Window(options.title,
                options.x or 0        -- 默认值
                options.y or 0        -- 默认值
                options.width,
                options.height,
                options.background or "white",        -- 默认值
                options.border                               -- 默认是为 false(nil)
        )
    end

Window 函数可以根据要求检查必填参数,或者为某些参数添加默认值,而真正的创建窗口的操作在函数内由被调用函数_Window 来完成,其要求所有参数以正确的次序传入,那么 Window 函数可以如此这般:

2.6.4 第一类值

函数作为第一类值是如何用 c 实现的?3.6.3

2.7 深入函数

2.7.1 闭包

简单地讲,一个 closure 就是一个函数加上该函数所需访问的所有“非局部的变量”。感觉类似 C++中的 lamda 捕获。

主要作用有两个:返回回调函数和定义沙盒函数。

closure 是如何实现的?3.6.4

2.7.2 非全局的函数

Lua 是将每个程序块(chunk)作为一个函数来处理的。

在定义递归函数的时候必须使用一个明确的前向声明。

2.7.3 尾调用

正确使用尾调用可以减少新的栈层的创建。

2.7.4 TODO 闭包(closure)

2.8 TODO 迭代器与泛型 for

2.9 编译、执行与错误

    f = loadstring("i = i + 1")
    f = function () i = i + 1 end

由于 loadstring 在编译时不涉及词法域,loadstring 总是在全局环境中编译它的代码,所以上述两段代码并不等价。

    i = 32
    local i = 0
    f = loadstring("print(i)")
    g = function () print(i) end
    f() -- 32
    g() -- 0

常见的误解是认为加载了一个程序块,也就是定义了其中的函数。其实在 Lua 中,函数定义是一种复制操作。也就是说它们是在运行时才完成的操作。

可以使用 xpcall 来在错误发生时显示完整的函数调用栈。该函数除了接受一个需要贝被调用的函数之外,还接受一个错误处理函数,当发生错误时,Lua 会在调用栈炸拿开前调用错误处理函数,于是就可以在这个函数中使用 debug 库来获取关于错误的额外信息了。

2.10 协同程序

协程与线程差不多,也就是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其他协程共享全局变量和其他大部分东西。

协程和线程的主要区别基本上就是非抢占式和抢占式的区别。现在的操作系统的 进程/线程 调度模型是抢占式的。在用户空间下无法完全自主控制 进程/线程 的切换。抢占又引发竞争。在高并发的情况下,由于强行切换导致的资源竞争冲突会经常性发生,严重影响性能。而协程是非抢占式的,程序逻辑可以保证不在锁没有释放的情况下切换,从而每个获得执行权的协程都可以有效的执行逻辑,避免资源竞争引起的并发冲突。非抢占式(协程)模型下,要求程序逻辑主动、及时的释放执行权。在最初的 windows3,windows95 系统是非抢占式的,一旦某个进程 / 线程不主动释放执行权,整个系统就会被卡死,所以现在的操作系统的进程 / 线程调度方式都是抢占式的。由于非抢占式(协程)模型在高并发下有其优势,很多程序使用这种模式调度其并发逻辑。7

2.10.1 协同程序状态测试

    local co  --试试不前置声明会怎么样
    co = coroutine.create(  function()
            print("before yield status: " .. coroutine.status(co))
            coroutine.yield()
            print("after yield: " .. coroutine.status(co))
    end)
    print("before start status: ",coroutine.status(co))
    coroutine.resume(co)
    print("after yeild status: ",coroutine.status(co))
    coroutine.resume(co)
    print("after end status: ",coroutine.status(co))

当创建一个协同程序时,它处于挂起状态。

resume 实在保护模式中运行的。因此,如果一个协同程序的执行中发生任何错误,Lua 是不会显示错误消息的,而是讲执行全返回给 resume 调用。

2.10.2 协程 yield resume 返回值测试

当一个协同程序调用 yield 时,它不是进入了一个新的函数,而是从一个悬而未决的调用中返回。同样的,对于 resume 的调用也不会启动一个新函数,而是从一次 yeild 调用中返回。 要深刻理解这一点,有利于分析协程的执行过程。同时也说明了协程和回调函数的区别。

    local co = coroutine.create(function (val,n)
            local loop_count = 0
            repeat
                for i=1,n do
                    print(val)
                end
                loop_count = loop_count + 1
                print(loop_count .. " loop_count")
            until (not coroutine.yield(loop_count))

            print("all finish")
    end)

    function test()
        local ret,count = coroutine.resume(co,"hello,lua",5)
        while ret do
            if count < 3 then
                ret,count = coroutine.resume(co,true)
            else
                ret,count = coroutine.resume(co,false)
                break
            end
        end
    end

    test()

2.10.3 协程应用

实现类似管道类型的生产者消费者模型。

生产者、消费者练习
    -- 消费者驱动 :tangle lua-analysis/producer-consumer.lua
    producer = coroutine.create(function(status)
            while status do
                local product = io.read()
                status = coroutine.yield(product)
            end
    end)
    function consumer()
        while true do
            local status,value = coroutine.resume(producer,true)
            if value ~= "quit" then  --判断是否要退出程序
                print(value)
            else
                coroutine.resume(producer,false)
                break
            end
        end
    end
    consumer()
添加过滤的生产者、消费者程序
    function prod()
        return coroutine.create(function (is_run)
                while is_run do
                    local str = io.read()
                    is_run = coroutine.yield(str)
                end
        end)
    end

    function filter(p)
        return coroutine.create(function (is_run)
                local i = 0
                while is_run do
                    local status,str = coroutine.resume(p,true)
                    if str ~= "quit" then
                        i = i + 1
                        str = string.format("%d ",i) .. str
                    end
                    is_run = coroutine.yield(str)
                end
                coroutine.resume(prod,false) --close producer
        end)
    end

    function consumer(f)
        while true do
            local status, data = coroutine.resume(f,true)
            if data ~= "quit"then
                print(data)
            else
                coroutine.resume(f,false) --close filter
                break
            end
        end
    end

    consumer(filter(prod()))
非抢先式多线程

协同程序预常规的多线程的不同之处在于,协同程序是非抢占式的。就是说当一个协同程序运行时,是无法从外部停止它的。

    require "socket"
    function Download(host, file)
        local c = assert(sokcet.connect(host, 80))
        local count = 0             -- 记录接受到的字节数
        c:send("GET" .. file .. "HTTP/1.0\r\n\r\n")
        while true do
            local s, status, partial = Receive(c)
            count = count + #(s or partial)
            if status == "closed" then
                break
            end
        end

        c:close()
        print(file,count)
    end

    function Receive(conncetion)
        connection::settimeout(0)   -- 此处不会阻塞,所以节省了时间
        local s,status,partial = connection:receive(2^10)
        if status == "timeout" then
            coroutine.yield(connection)
        end
        return s or partial, status
    end

    threads = {}

    function Get(host,file)
        local co = coroutine.create(function ()
                Download(host,file)
        end)
        table.insert(threads, co)
    end

    function Dispatch()
        local i = 1
        while true do
            if threads[i] == nil then -- 是否还有下载线程
                if threads[1] ==  nil then -- 下载列表是否为空
                    break
                end
                i = 1
            end
            local status, res = coroutine.resume(threads[i])
            if not res then
                table.remove(threads, i)
            else
                i = i + 1
            end
        end
    end

    local host = "www.w3.org"
    Get(host, "TR/htlm1401/htlm40.txt")
    Get(host, "TR/2002/REC-xhtml11-20020801/xhtlm1.pdf")

    Dispatch()

2.11 TODO 数据类型

2.12 TODO 数据文件与持久性

2.13 TODO 元表与元方法

2.14 TODO 环境

2.15 TODO 模块与包

2.16 TODO 面向对象编程

2.17 TODO 弱引用 table

2.18 TODO C API

lua 是一门嵌入式语言,也就是说 lua 不能单独运行,而是一个可以链接到其他程序的库。它需要与其他语言编写的程序进行交互,这种交互是双向的,以 C 为例,这样的交互过程中,C 代码扮演两种角色:一种是调用 lua 代码的应用代码,另一种是被 lua 调用的库代码。不管怎样,C 和 Lua 之间都是通过一组 C API 来通信的。通过这些 C API 可以读写 Lua 全局变量、运行 lua 代码、以及注册 C 代码供 Lua 使用。

   gVar = "old global value"
   gTable = {r=2, g=3, b=4}
   function LuaFunc(x,y)
       return x/y
   end

2.18.1 读写 Lua 全局变量

    #include <stdio.h>
    #include <string.h>
    #include <assert.h>
    #include <lua5.3/lua.h>
    #include <lua5.3/lauxlib.h>
    #include <lua5.3/lualib.h>

    int main(int argc, char *argv[])
    {
        lua_State *luaEnv = luaL_newstate();
        luaL_openlibs(luaEnv);

        /* load lua file */
        ret = luaL_dofile(luaEnv, "test.lua");
        assert(ret == LUA_OK);

        /* get lua global var  */
        ret = lua_getglobal(luaEnv, "gVar");
        assert(ret == LUA_TSTRING);
        const char* luaStr = lua_tostring(luaEnv, -1);
        assert(luaStr != NULL);
        printf("%s\n",luaStr);
        lua_pop(luaEnv,1);

        /* set lua global var */
        lua_pushstring(luaEnv, "new global value");
        lua_setglobal(luaEnv, "gVar");

        /* check global var new value */
        const char *luaCode = "print(gVar)";
        ret = luaL_dostring(luaEnv, luaCode);
        assert(ret == LUA_OK);

        lua_close(luaEnv);
        return 0;
    }

2.18.2 调用 Lua 函数

   #include <stdio.h>
   #include <string.h>
   #include <assert.h>
   #include <lua5.3/lua.h>
   #include <lua5.3/lauxlib.h>
   #include <lua5.3/lualib.h>

   int main(int argc, char *argv[])
   {
       lua_State *luaEnv = luaL_newstate();
       luaL_openlibs(luaEnv);

       int ret = luaL_dofile(luaEnv,"test.lua");
       assert(ret == LUA_OK);

       ret = lua_getglobal(luaEnv, "LuaFunc");
       assert(ret == LUA_TFUNCTION);

       lua_pushnumber(luaEnv, 1);
       lua_pushnumber(luaEnv, 2);
       ret = lua_pcall(luaEnv,2,1,0);
       assert(ret == LUA_OK);

       ret = lua_tonumber(luaEnv,-1);
       printf("lua func result: %d\n",ret);

       lua_close(luaEnv);
       return 0;
   }

2.18.3 注册 C 函数

当 Lua 调用 C 函数时,也使用了一个与 C 语言调用 Lua 时相同的栈。C 函数从栈中获取函数参数,并将结果压入栈中。为了在栈中将函数结果与其他值区分开,C 函数还应返回其压入栈中的结果数量。

栈不是一个全局性的结构,这是一个重要概念。每个函数都有自己的局部私有栈。当 Lua 调用一个 C 函数时,第一个参数总是这个局部栈的索引 1.即使这个函数调用了 Lua 代码,并且 Lua 代码有调用了相同的 C 函数,这些 C 函数调用只看到自己的私有栈,它们的第一个参数都是索引 1。

   #include <stdio.h>
   #include <lua5.3/lua.h>
   #include <lua5.3/lauxlib.h>
   #include <lua5.3/lualib.h>

   int MyDiv(lua_State* l)
   {
       //检查栈中的参数是否合法,1 表示 Lua 调用时的第一个参数(从左到右),依此类推。
       //如果 Lua 代码在调用时传递的参数不为 number,该函数将报错并终止程序的执行。
       int arg1 = luaL_checknumber(l, 1);
       int arg2 = luaL_checknumber(l, 2);
       //将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
       lua_pushnumber(l, arg1/arg2);
       //返回值用于提示该 C 函数的返回值数量,即压入栈中的返回值数量。
       return 1;
   }

   const char *LuaCode = "print(Div(4, 2))";

   int main(int argc, char **argv)
   {
       lua_State* luaEnv = luaL_newstate();
       luaL_openlibs(luaEnv);

       lua_register(luaEnv, "Div", MyDiv);
       luaL_dostring(luaEnv, LuaCode);

       lua_close(luaEnv);
       return 0;
   }

注意,Lua 规定了要注册的函数类型必须是以下形式:

   typedef int (*lua_CFunction) (lua_State *L);

2.18.4 注册 C 模块

还可以将 lib 中的代码注册到 lua 中

   #include <stdio.h>
   #include <lua5.3/lua.h>
   #include <lua5.3/lauxlib.h>
   #include <lua5.3/lualib.h>

   int MyDiv(lua_State* l)
   {
       int a = lua_tonumber(l, 1);
       int b = lua_tonumber(l, 2);
       lua_pushnumber(l, a/b);
       return 1;
   }

   static const luaL_Reg funcs[] = {
       {"Div", MyDiv},
       {NULL, NULL}
   };
   int luaopen_mylib(lua_State* l)
   {
       luaL_newlib(l, funcs);
       return 1;
   }

luaL_register 根据给定的名称(“mylib”)创建或复用一个 table,并用数组 funclib 中的信息填充这个 table。在 LuaL_register 返回时,会将这个 table 留在栈中。最后, luaopen_clib 函数返回 1,标识将这个 table 返回给 lua。

函数名必须为 luaopen_xxx ,其中 xxx 表示 library 名称。Lua 代码 require "xxx"需要与之对应。

          #include <stdio.h>
          #include <stdlib.h>
          #include <lua5.3/lua.h>
          #include <lua5.3/lualib.h>
          #include <lua5.3/lauxlib.h>

          /* const char *luaCode = "local mylib = require \"mylib\" print(mylib.Div(4, 2))"; */
          const char *luaCode = "print(package.path) package.loadlib('~/mylib.a','luaopen_mylib')  local mylib = require \"mylib\" print(mylib.Div(4, 2))";
          int main(int argc, char **argv)
          {
              lua_State* luaEnv = luaL_newstate();
              luaL_openlibs(luaEnv);
              int ret = luaL_dostring(luaEnv, luaCode);
              if (ret != LUA_OK) {
                  printf("%s\n", lua_tostring(luaEnv,-1));
              }

              lua_close(luaEnv);
              return 0;
          }

静态链接(TODO)

   gcc -g -c mylib.c -llua5.3
   ar -r mylilb.a mylib.o
   gcc -g lua-call-c-lib.c -llua5.3

动态链接

   gcc -fpic -c mylib.c
   gcc -shared -o mylib.so mylib.o
   gcc -g lua-call-c-lib.c -llua5.3

lua 和 c 语言通信的主要方式是一个无处不在的虚拟栈。几乎所有的 api 调用都会操作这个栈上的值。所有的数据交换,无论是 Lua 到 c 还是 c 到 Lua 都通过这个栈来完成。此外,还可以用这个栈来保存一些中间结果。栈可以解决 Lua 和 c 语言之间的两大差异,第一种差异是 Lua 使用垃圾收集,而 c 要求显示释放内存;第二种是 Lua 使用动态类型,而 c 使用静态类型。

2.18.5

元表(metatable)
   t = {sunday=7, monday=1}
   print(getmetatable(t))
   t1 = {}
   setmetatable(t,t1)
   print(getmetatable(t) == t1)

   --__index and __newindex
   Window = {}
   Window.prototype = {x=10, y=20, width=100, height=200}
   Window.mt = {}
   function Window.new(o)
       setmetatable(o, Window.mt)
       return o
   end
   -- Window.mt.__index = function(t,key)
   -- return Window.prototype[key]
   -- end
   Window.mt.__index = Window.prototype

   w = Window.new({x=30,y=30})
   print(w.x)  --30
   print(w.width)  --100
   w.width = 300
   print(Window.prototype.width) --100
   Window.mt.__newindex = Window.prototype
   w.height = 300
   print(Window.prototype.height) --300
  • 元表应用
    • 具有默认值的表
          function set_default(t,default)
              mt = {
                  __index = function() return default end
              }
              setmetatable(t,mt)
          end
      
          set_default(t,0)
          print(t[wensday]) --0
      
    • 跟踪表使用
          local _t = t
          t = {}
          local mt = {
              __index = function(t,k)
                  print("access to element " .. tostring(k))
                  return _t[k]
              end,
              __newindex = function(t,k,v)
                  print("update element " .. tostring(k) .. " to " .. tostring(v))
                  _t[k] = v
              end
          }
          setmetatable(t,mt)
      
          print(t.sunday)
          t.wensday = 3
      
    • 只读表
          function read_only(t)
              local proxy = {}
              local mt = {
                  __index = t,
                  __newindex = function(t,k,v) error("attempt to update a read-only table",2) end
              }
              setmetatable(proxy,mt)
              return proxy
          end
          r_t = read_only(t)
          print("sunday = " .. r_t.sunday)
      
          r_t.saturday = 6
      
    • 实现集合(set)
          --测试集合类
          Set = {}
          mt = {}
      
          function  Set.new(l)
              local set = {}
              setmetatable(set,mt)
              for i,v in ipairs(l) do
                  set[v] = true
              end
              return set
          end
          --并集
          function Set.union(a,b)
              local res = Set.new({})
              for k in pairs(a) do
                  res[k] = true
              end
      
              for k in pairs(b) do
                  res[k] = true
              end
              return res
          end
          --交集
          function Set.intersection(a,b)
              local res = Set.new({})
              for k in pairs(a) do
                  res[k] = b[k]
              end
              return res
          end
          --补集
          function Set.complementation(a,b)
              local res = Set.new({})
              for k in pairs(b) do
                  if not a[k] then
                      res[k] = true
                  end
              end
              return res
          end
      
          --集合相等
          function Set.equal(a,b)
              return Set.le(a,b) and Set.le(b,a)
          end
          --a<b
          function Set.lt(a,b)
              return Set.le(a,b) and not Set.le(b,a)
          end
          --a<=b
          function Set.le(a,b)
              for k,_ in pairs(a) do
                  if not b[k] then
                      return false
                  end
              end
              return true
          end
      
          --打印表
          function Set.tostring(set)
              local l = {}
              for k in pairs(set) do
                  l[#l + 1] = k
              end
              return "{" .. table.concat(l,",") .. "}"
          end
      
          --需要注意的是,必须要在 setmetatable 函数执行后,为 metatable 设置元方法才可以。
          --在 setmetatable 之前为 metatable 赋值都是无效的。
          mt.__add = Set.union
          mt.__sub = Set.complementation
          mt.__mul = Set.intersection
          mt.__eq  = Set.equal
          mt.__lt  = Set.lt
          mt.__le  = Set.le
          mt.__tostring = Set.tostring
          mt.__metatable = "do not change the set's metatable"
      
          set1 = Set.new({10,20,30,50})
          set2 = Set.new({10,40})
          set3 = set1 - set2
          --print(set3)
          --print(set1 >= set2)
          print(getmetatable(set3))
      
    • 实现类
      • PIL 实现方式一
          class_A = {
              value = 10,     -- 类变量
              func = function()   -- 类方法
                  print("function in base class A")
              end
          }
          function class_A:new(o) -- 类构造函数
              local o = o or {}
              setmetatable(o,class_A)
              self.__index = self     -- 为什么要这样设置呢?
              return o
          end
        
          a1 = class_A:new({x=10})
          print(string.format("a1.x=%d",a1.x))
          print(string.format("a1.value=%d",a1.value))
          a1.func()
        
      • PIL 实现方式二
         class_B =
             {
                 value = 10,
                 func = function ()
                     print("function in base class B")
                 end
             }
         function class_B:new(o)
             local o = o or {}
             setmetatable(o,{__index = class_B})   -- 这也就是原文中“创建一个额外的 table 作为账户对象的元表”,与上面相比这里多创建了一个表作为对象的元表
             return o
         end
        
         b1 = class_B:new({x=10})
         print(b1.x)
         print(b1.value_b)
         b1.func()
        
      • PIL 继承
         class_C = class_A:new()
         function class_C:new(o)
             local o =  o or {}
             setmetatable(o,class_C)
             self.__index = self
             return o
         end
        
         -- c1 = class_C.new({x=30})  -- 会报错
         c1 = class_C:new({x=30})
         print(string.format("c1.x=%d",c1.x))
         print(string.format("c1.value=%d",c1.value))
         c1.func()
        

        到这时,我们还缺少什么?

      • DONE 云风的实现方式8
          local _class = {}
          function class(super)
              local class_type = {}
              class_type.ctor = false
              class_type.super = super
        
              class_type.new = function(...)
                  local obj = {}
                  do
                      local create
                      create = function(c,...)
                          if c.super then
                              create(c.super,...)
                          end
                          if c.ctor then
                              c.ctor(obj,...)
                          end
                      end
                      create(class_type,...)
                  end
                  setmetatable(obj,{__index = _class[class_type]})
                  return obj
              end
              -- 注意要在 setmetatable 函数执行之后再添加元方法
              local vtbl = {}
              _class[class_type] = vtbl  -- 类似于 C++的虚表
              setmetatable(class_type,{__newindex = function(t,k,v)
                                           vtbl[k] = v
              end})
              if super then
                  setmetatable(class_type,{__index = function(k,v)
                                               local ret = _class[super][k]
                                               vtbl[k]    = ret
                                               return ret
                  end})
              end
              return class_type
          end
        
          base_type = class()
        
          function base_type:ctor()
              print("base_type ctor")
          end
        
          function base_type:print_f()
              print("base_type print function")
          end
        
        
          test = base_type:new()
        
          test.print_f()
        

2.19 TODO 用户自定义类型(userdata)

2.20 管理资源

2.21 线程和状态

2.22 内存管理

2.22.1 分配函数

2.22.2 垃圾回收

3 源码分析9, 10

3.1 概览11

Lua 作为一门动态语言,提供了一个虚拟机。Lua 代码最终都是是以字节码的形式由虚拟机解释执行的。把外部组织好的代码置入内存,让虚拟机解析运行,需要有一个源代码解释器,或是预编译字节码的加载器。

而只实现语言特性,几乎做不了什么事。所以 Lua 的官方版本12还提供了一些库,并提供一系列 C API,供第三方开发。这样,Lua 就可以借助这些外部库,做一些我们需要的工作。

下面,我们按这个划分来分析源码文件的结构。

3.1.1 源文件划分13

从官网下载到 Lua5.3 的源代码后,展开压缩包,会发现源代码文件全部放在 src 子目录下,模块结构如下:

实用功能:
  • ldebug.c - 调试接口。用于访问调试钩子的函数 (lua_sethook, lua_gethook, lua_gethookcount) , 访问运行时堆栈信息 (lua_getstack / lua_getlocal / lua_setlocal) , 检查字节码 (luaG_checkopenop / luaG_checkcode), 和发现错误 (luaG_typeerror / luaG_concaterror / luaG_aritherror / luaG_ordererror / luaG_errormsg / luaG_runerror)
  • lzio.c - 通用缓冲输入流接口。
  • lmem.c - 内存管理接口,实现用于包裹内存分配函数的 luaM_realloc / luaM_growaux_
  • lgc.c - 增量垃圾收集器(内存管理)。
基本数据类型。
  • lstate.c - 全局状态机,用于打开和关闭 Lua states (lua_newstate/lua_close) 和线程 (luaE_nEwthread / luaE_freethread) 的函数。
  • lobject.c - 不同的数据类型最终被统一定义为 LuaObject,这里定义了对象相关的操作,包括数据类型和字符串之间的转换, raw equality test ( luaO_rawequalObj ), and log base 2 (luaO_log2)
  • lstring.c - 字符串表(保存由 Lua 处理的所有字符串)。
  • lfunc.c - 操作原型和闭包的辅助函数。
  • ltable.c - Lua tables (hash)
代码解析和生成:

光有核心代码和一个虚拟机还无法让 Lua 程序运行起来。因为必须从外部输入将运行的 Lua 程序。Lua 的程序的人读形式是一种程序文本,需要经过解析得到内部的数据结构(常量和 opcode 的集合) 。这个过程是通过 parser:lparser.c(luaY14为前缀的 API)及词法分析 llex.c(luaX 为前缀的 API) 。

解析完文本代码,还需要最终生成虚拟机理解的数据,这个步骤在 lcode.c 中实现,其 API 以 luaK 为前缀。 为了满足某些需求,加快代码翻译的流程。还可以采用预编译的过程。把运行时编译的结果,生成为字节码。这个过程以及逆过程由 ldump.c 和 lundump.c 实现。其 API 以 luaU 为前缀。16

  • lcode.c - 由 lparser.c 使用的 Lua 代码生成器。
  • llex.c - 由 lparser.c 使用的此法分析器。
  • lparser.c - Lua 解析器。
  • lundump.c - 还原预编译的 Lua 代码块(chunks)。实现了用来加载预编译代码块的函数 luaU_undump,也实现了分析函数头的 luaU_header (由 luaU_undump 内部使用)。
  • ldump.c - 序列化预编译的 Lua 块。实现了用于将函数对象转储为预编译代码串的 luaU_dump
执行 Lua 字节码:

Lua 核心部分仅包括 Lua 虚拟机的运转。Lua 虚拟机的行为是由一组组 opcode 控制的。这些 opcode 定义在 lopcode.h 及 lopcode.c 中。而虚拟机对 opcode 的解析和运作在 lvm.c 中,其 API 以 luaV 为前缀。

Lua 虚拟机的外在数据形式是一个 lua_state 结构体,取名 State 大概意为 Lua 虚拟机的当前状态。全局 State 引用了整个虚拟机的所有数据。这个全局 State 的相关代码放在 lstate.c 中,API 使用 luaE 为前缀。

函数的运行流程:函数调用及返回则放在 ldo.c 中,相关 API 以 luaD17为前缀。

  • lopcodes.c - Lua 虚拟机的操作码。定了所有操作码的名字和信息(通过表 luaP_opnames and luaP_opmodes )。
  • lvm.c - Lua 虚拟机,用来执行 Lua 字节码 ( luaV_execute ). 也暴露了一些 lapi.c 使用的函数(比如 luaV_concat )。
  • ldo.c - Lua 函数调用及栈管理。处理函数调用 ( luaD_call / luaD_pcall ),堆栈增长,协程处理。。。。
  • ltm.c - 标记方法(tag methods)。实现了访问对象的元方法。
标准库:

作为嵌入式语言,其实完全可以不提供任何库及函数。全部由宿主系统注入到 State 中即可。也的确有许多系统是这么用的。但 Lua 的官方版本还是提供了少量必要的库。尤其是一些基础函数如 pairs、error、sermetatable、type 等等,完成了语言的一些基本特性,几乎很难不使用。 而 coroutine、string、table、math 等等库,也很常用。Lua 提供了一套简洁的方案,允许你自由加载你需要的部分,以控制最终执行文件的体积和内存的占用量。主动加载这些内建库进入 lua_State ,是由在 lualib.h 中的 API 实现的。18

在 Lua5.0 之前,Lua 并没有一个统一的模块管理机制。这是由早期 Lua 仅仅定位在嵌入式语言决定的。这些年,由更多的人倾向于把 Lua 作为一门独立编程语言来使用,那么统一的模块化管理就变得非常有必要。这样才能让丰富的第三方库可以协同工作。即使是当成嵌入式语言使用,随着代码编写规模的扩大,也需要合理的模块划分。

Lua 5.1 引入了一个官方推荐的模块管理机制。使用 require/module 来管理 Lua 模块,并允许从 C 语言编写的动态库中加载扩展模块。这个机制被作者认为有点过度设计了。在 Lua5.2 中又有所简化。我们可以在 loadlib.c 中找到实现。内建库的初始化 API 则在 linit.c 中可以找到。

其它基础库可以在那些以 lib.c 为后缀的源文件中,分别找到它们的实现。

  • lbaselib.c - (base functions)
  • lstrlib.c - string
  • ltablib.c - table
  • lmathlib.c - math
  • loslib.c - os
  • liolib.c - io
  • loadlib.c - package
  • ldblib.c - debug
  • lbitlib.c 位操作库
  • lcorolib.c 协程库
  • linit.c 内嵌库的初始化
  • loadlib.c 动态扩展库管理
C API:
  • lapi.c - Lua API. 实现了大部分的 Lua C API ( lua_* functions)。
  • lauxlib.c - 定义了辅助库提供的函数。它的所有定义都以 luaL_ 开头,辅助库一个使用 lua.h 中 API 编写出的一个较高的抽象层。Lua 所有标准库编写都用到了辅助库。注意,辅助库并没有直接访问 Lua 内部,它都是用官方的基础 API 来完成所有工作的。
  • linit.c - 实现了加载从 C 加载上述模块的 luaL_openlibs
  • lctype.c C 标准库中 ctype 相关实现
lua 和 luac 程序
  • lua.c - Lua 解释器。
  • print.c - 定义了打印函数内字节码的"PrintFunction?"函数,(由 luac.c 的"-l"选项调用)。
  • luac.c - Lua 编译器(保存字节码到文件;也可以列出字节码)。
src/Makefile

In src/Makefile (5.1.1), the mingw target 不寻常之处在于他只编译 lua(没有 luac),也可以在添加一个 mingw-cygwin target。结果查看 See mingw notes in BuildingLua 中的 mingw notes。

In src/luaconf.h (5.1.1), LUA_PATH_DEFAULT 是指 LUA_LDIRLUA_CDIR , 但 LUA_CPATH_DEFAULT 只是 LUA_CDIR 。RiciLake 暗示这可能是一个安全方面的决定,因为于 Lua 模块相比,C 模块需要更多信任。

/Makfile

首先浏览一下 doc/readme.html,了解编译和安装的信息。

 wget -P ~/Downloads/ http://www.lua.org/ftp/lua-5.3.1.tar.gz
 tar zxf ~/Downloads/lua-5.3.1.gz
 cd lua-5.3.1
 make linux install

因为想要查看 lua 的源码,所以编译的可执行文件和库文件要添加调试所需要的信息。

 make CFLAGS+=-g linux
 make local

所有生成的 bin、lib、include 文件都放在 include 文件中。

3.1.2 阅读源代码的次序

Lua 的源代码有着良好的设计,优美易读。其整体篇幅不大,仅两万行 C 代码左右19。但一开始入手阅读还是有些许难度的。

从易到难,理清作者编写代码的脉络非常重要。LuaJIT 的作者 Mike Pall 在回答“哪一个开源代码项目设计优美,值得阅读不容错过”这个问题时,推荐了一个阅读次序:20

  • 首先、阅读外围的库是如何实现功能扩展的,这样可以熟悉 Lua 公开 API。不必陷入功能细节。
  • 然后、阅读 API 的具体实现。Lua 对外暴露的 API 可以说是一个对内部模块的一层封装,这个层次尚未触及核心,但可以对核心代码有个初步的了解。
  • 之后、可以开始了解 LuaVM 的实现。
  • 接下来就是分别理解函数调用、返回,string、table、metatable 等如何实现。
  • debug 模块是一个额外的设施,但可以帮助你理解 Lua 内部细节。
  • 最后是 parser 等等编译相关的部分。
  • 垃圾收集将是最难的部分,可能会花掉最多的时间去理解细节。

3.2 内置库的实现

3.3 TODO Lua API 具体实现

3.4 TODO Lua VM 实现

3.5 TODO 数据结构21

3.5.1 TValue

3.5.2 TODO 字符串(string)

3.5.3 表(table)

3.6 TODO 函数调用实现

3.6.1 多返回值

3.6.2 unpack

3.6.3 函数 first class value

3.6.4 closure

3.7 TODO Lua parser 实现

3.8 TODO 垃圾收集(garbage-collect)

Footnotes:

5

笔者(云风)倾向于在服务器应用中使用独立的 Lua 解析器。这样会更加灵活,可以随时切换其它 Lua 实现(例如采用性能更高的 LuaJIT),并可以方便的使用第三方库。

11

云风所著《Lua 源码欣赏》

12

Lua 官方实现并不是对 Lua 语言的唯一实现。另外比较流行的 Lua 语言实现还有LuaJIT 。由于采用了 JIT 技术,运行性能更快。除此之外,还能在互联网上找到其它一些不同的实现。

13

在 Lua Wiki 上有一篇文章介绍了 Lua 源代码的结构:http://lua-users.org/wiki/LuaSource

14

Y 可能是取 yacc 的含义。因为 Lua 最早期版本的代码翻译是用 yacc 和 lex 这两个 Unix 工具实现的15 ,后来才改为手写解析器。

15

DEFINITION NOT FOUND: 4

16

极端情况下,我们还可以对 Lua 的源码做稍许改变,把 parser 从最终的发布版本中裁减掉,让虚拟机只能加载预编译好的字节码。这样可以减少执行代码的体积。Lua 的代码解析部分与核心部分之间非常独立,做到这一点所需修改极少。但这种做法并不提倡。

17

D 取 Do 之意。

18

如果你静态链接 Lua 库,还可以通过这些 API 控制最终链入执行文件的代码体积。

19

Lua 5.2.2 版本的源代码分布在 58 个文件中,共 20220 行 C 代码。

Author: lsl

Created: 2016-08-07 Sun 19:48

Validate