第一版是针对 Lua 5.0 编写的。虽然在很大程度上仍然适用于后续版本,但有一些不同之处。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目


8 - 编译、执行和错误

尽管我们称 Lua 为解释型语言,但 Lua 在运行之前始终会将源代码预编译为中间形式。(这不是什么大问题:大多数解释型语言都会这样做。)在 Lua 这样的解释型语言中,编译阶段的存在听起来似乎有些格格不入。然而,解释型语言的显著特征并不是它们没有编译,而是任何编译器都是语言运行时的一部分,因此可以(并且很容易)执行动态生成的代码。我们可以说,像 dofile 这样的函数的存在使得 Lua 可以被称为解释型语言。

之前,我们介绍了 dofile,它是一种运行 Lua 代码块的原始操作。dofile 函数实际上是一个辅助函数;loadfile 负责繁重的工作。与 dofile 一样,loadfile 也从文件中加载一个 Lua 块,但它不会运行该块。相反,它只编译该块,并将编译后的块作为函数返回。此外,与 dofile 不同,loadfile 不会引发错误,而是返回错误代码,以便我们可以处理错误。我们可以将 dofile 定义如下

    function dofile (filename)
      local f = assert(loadfile(filename))
      return f()
    end
请注意,如果 loadfile 失败,则使用 assert 引发错误。

对于简单的任务,dofile 很方便,因为它可以在一次调用中完成整个工作。但是,loadfile 更灵活。如果发生错误,loadfile 会返回 nil 和错误消息,这使我们可以以自定义方式处理错误。此外,如果我们需要多次运行一个文件,我们可以调用 loadfile 一次,然后多次调用其结果。这比多次调用 dofile 要便宜得多,因为程序只编译一次文件。

loadstring 函数类似于 loadfile,不同之处在于它从字符串中读取其块,而不是从文件中读取。例如,在代码之后

    f = loadstring("i = i + 1")
f 将是一个函数,当调用时,它会执行 i = i + 1
    i = 0
    f(); print(i)   --> 1
    f(); print(i)   --> 2
loadstring 函数功能强大;必须小心使用。它也是一个昂贵的函数(与它的替代函数相比),并且可能导致代码难以理解。在使用它之前,请确保没有更简单的方法来解决手头的问题。

Lua 将任何独立块视为匿名函数的主体。例如,对于块 "a = 1"loadstring 返回等效于

    function () a = 1 end
与任何其他函数一样,块可以声明局部变量和返回值
    f = loadstring("local a = 10; return a + 20")
    print(f())          --> 30

loadstringloadfile 都不会引发错误。在出现任何类型的错误的情况下,这两个函数都会返回 nil 加上错误消息

    print(loadstring("i i"))
      --> nil     [string "i i"]:1: `=' expected near `i'
此外,这两个函数都不会产生任何副作用。它们仅将块编译为内部表示形式,并以匿名函数的形式返回结果。一个常见的错误是假设 loadfile(或 loadstring)定义函数。在 Lua 中,函数定义是赋值;因此,它们是在运行时进行的,而不是在编译时进行的。例如,假设我们有一个文件 foo.lua,如下所示
    -- file `foo.lua'
    function foo (x)
      print(x)
    end
然后我们运行命令
    f = loadfile("foo.lua")
此命令后,foo 已编译,但尚未定义。要定义它,您必须运行块
    f()           -- defines `foo'
    foo("ok")     --> ok

如果您想快速而粗略地执行 dostring(即加载并运行一个块),则可以直接调用 loadstring 的结果

    loadstring(s)()
但是,如果存在任何语法错误,loadstring 将返回 nil,最终错误消息将是 "attempt to call a nil value"。要获得更清晰的错误消息,请使用 assert
    assert(loadstring(s))()

通常,对文字字符串使用 loadstring 没有意义。例如,代码

    f = loadstring("i = i + 1")
大致相当于
    f = function () i = i + 1 end
但第二个代码要快得多,因为它仅在块编译时编译一次。在第一个代码中,每次调用 loadstring 都涉及一个新的编译。但是,这两个代码并不完全等效,因为 loadstring 不使用词法作用域进行编译。为了了解差异,让我们稍微更改一下前面的示例
    local i = 0
    f = loadstring("i = i + 1")
    g = function () i = i + 1 end
g 函数按预期操作局部 i,但 f 操作全局 i,因为 loadstring 始终在其全局环境中编译其字符串。

loadstring 最典型的用法是运行外部代码,即来自程序外部的代码片段。例如,您可能想绘制用户定义的函数;用户输入函数代码,然后您使用 loadstring 对其进行评估。请注意,loadstring 需要一个块,即语句。如果您想评估一个表达式,您必须在前面加上 return,以便获得一个返回给定表达式值的语句。请参见示例

    print "enter your expression:"
    local l = io.read()
    local func = assert(loadstring("return " .. l))
    print("the value of your expression is " .. func())

loadstring 返回的函数是一个常规函数,因此您可以多次调用它

    print "enter function to be plotted (with variable `x'):"
    local l = io.read()
    local f = assert(loadstring("return " .. l))
    for i=1,20 do
      x = i   -- global `x' (to be visible from the chunk)
      print(string.rep("*", f()))
    end

在需要运行外部代码的生产级程序中,您应该处理 loadstring 报告的任何错误。此外,如果代码不可信,您可能希望在受保护的环境中运行新的块,以避免在运行代码时产生不愉快的副作用。