Chapter 8: Compilation, Execution, and Errors
决定一种语言是不是解释语言并不在于其是否需要编译,而是其编译器是语言运行时的一部份。Lua 源代码在运行前总是被编译成中间语言。
8.1 Compilation
lib1.lua
function norm (x, y)
return (x^2 + y^2)^0.5
end
function twice (x)
return 2*x
end
> dofile("lib1.lua") -- load your library
> n = norm(3.4, 1.0)
> print(twice(n)) --> 7.0880180586677
前面,我们引入dofile 作为一种加载Lua code chunks 的主要操作,但dofile 实际上是一个辅助函数,loadfile 代劳了那些复杂的操作。像dofile,loadfile 从一个文件加载一个Lua chunk,但它不运行这个chunk,只是编译它并且返回一个函数作为编译结果。loadfile 遇到错误时不会引发错误,而是返回错误码,所以我们能够处理它。
dofile 的实现
function dofile (filename)
local f = assert(loadfile(filename))
return f()
end
注意到使用了断言,当loadfile 失败时引发错误。
对简单任务,dofile 是便利的,因为只需一个调用就完成了工作。但是loadfile 更灵活。
loadfile 发生错误时会返回一个nil 和一个错误消息。
此外,如果我们需要多次运行文件,可以调用loadfile 一次并且调用其返回结果多次。这比多次调用dofile 开销小多了,因为文件只被编译一次。
loadstring 与loadfile 类似,除了它不是从文件而是从一个string 读chunk。
f = loadstring("i = i + 1")
i = 0
f(); print(i) --> 1
f(); print(i) --> 2
f 是一个函数,一调用就执行i=i+1
loadstring 要小心使用。其功能强,代价高,会产生易费解的代码结果。
loadstring 不是必不得以,不要使用。
loadstring的一种用法
assert(loadstring(s))()
f = loadstring("i = i + 1")
大体上相当于
f = function () i = i + 1 end
但第二个更快,
因为loadstring 不是以lexical scoping 编译,以上代码在以下例子是不一样的:
i = 32
local i = 0
f = loadstring("i = i + 1; print(i)")
g = function () i = i + 1; print(i) end
f() --> 33
g() --> 1
g 操作局部变量i,而f 却操作全局变量i。因为loadstring 总是在编译的时侯在其串中使用全局环境。
loadstring 的典型使用是运行外部代码,就是说从你的程序外部来的代码片段。例如,你希望一个函数让用户去定义。用户输入代码然后你用loadstring 执行它。要注意,loadstring 想要的是一个chunk,也就是语句。
用户输入表达式你的程序计算结果
print "enter your expression:"
local l = io.read()
local func = assert(loadstring("return " .. l))
print("the value of your expression is " .. func())
另一个例子
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())) -- '*' 重复n 次,连成串(这个n 由用户输入)
end
如果再深入下去,我们会发现真正的原始工作是由load 完成的,既不是loadfile 也不是loadstring。
Lua 将任何独立chunk 作为参数数量可变的匿名函数体对待。
例如,loadstring("a = 1") 相当于表达式function (...) a = 1 end
像其它任何函数一样,chunks 可以声明局部变量:
f = loadstring("local a = 10; print(a + 20)")
f() --> 30
利用这些特性,我们可以将前面的例子重写,避免使用全局变量x:
print "enter function to be plotted (with variable 'x'):"
local l = io.read()
local f = assert(loadstring("local x = ...; return " .. l))
for i=1,20 do
print(string.rep("*", f(i)))
end
x 有什么用?看起来要不要都一样。
一个见错误是,假定加载的chunk 定了了函数。Lua 中,函数的定义是赋值,而像loadstring这种特别情况下,赋值发生在运行时而不是编译时。
-------------------------------
-- foo.lua
function foo (x)
print(x)
end
------------------------------
f = loadfile("foo.lua")
print(foo) --> nil -- 函数未定义
f() -- defines 'foo' -- 运行chunk
foo("ok") --> ok -- 已定义
要定义函数,必须先运行chunk
8.2 C Code
和lua 不同,使用前C 代码需要与应用程序进行link(链接)。link 机制不是ANSI C 规范的一部分,link并没有一种可移值的实现方式。
Lua 通过单一函数实现所有动态链接功能,称为package.loadlib
package.loadlib 有两个string参数,库的完整路径和函数名。典型的调用如下所示:
local path = "/usr/local/lib/lua/5.1/socket.so"
local f = package.loadlib(path, "luaopen_socket")
loadlib 函数加载指定的库,并将lua 链接到其中。但是它并不调用这个函数,而是返回C 函数作为Lua 函数。
如果对loadlib 的调用失败,会返回nil 和错误消息。
loadlib 函数是非常底层的函数,我们必须提供库的完整路径和正确的函数名。
通常,我们使用require 函数加载C 库
require 函数搜索指定的库,并使用loadlib 加载。
参见15.1 节,更详细的讨论在26.2 节。
8.3 Errors
因为lua 通常是嵌入应用程序里的,当错误发生时lua 不能简单的崩溃或退出程序,而是终止当前chunk 并返回应用程序。
两种错误处理
简单的打印错误消息
print "enter a number:"
n = io.read("*number")
if not n then error("invalid input") end
用断言引发一个错误
print "enter a number:"
n = assert(io.read("*number"), "invalid input")
assert 中的错误消息参数是可选的。
assert 是一个语法糖函数
n = io.read()
assert(tonumber(n), "invalid input: " .. n .. " is not a number")
代码中不管n 能否转成数字,总是会执行串的连接操作。也许在这个例子中使用显示测试是英明决定。
file = assert(io.open("no-file", "r"))
--> stdin:1: no-file: No such file or directory
注意到open 的第二个返回值错误消息是如何传给assert 作为第二参数的。
8.4 Error Handling and Exceptions
许多应用程序中,Lua 不需要作任何错误处理;应用程序收到lua 返回的错误码会进行适当的处理。
如果要处理Lua 中的错误,必须调用pcall 函数。 (protected call)
我们使用error 函数抛出异常,使用pcall 捕获异常。
pcall 以保护模式调用传给它的第一个参数,并捕获函数运行期间的所有错误。 如果没有错误,pcall 返回true,加上函数的函回值;否则,返回false 加上错误码。
function foo ()
if true then error() end -- 引发错误
end
if pcall(foo) then
print("OK")
else
print("encounter some error!!")
end
--
function foo ()
if true then error({code=121}) end -- 引发错误,加上错误码
end
local status, err = pcall(foo)
print(err.code) --> 121
----------------------------------------------------------------
local status, err = pcall(function () error({code=121}) end)
print(err.code) --> 121
----------------------------------------------------------------
function foo()
a = 1
print(a[1])
end
local status,err = pcall(foo)
if (status) then
print 'ok'
else
print(err) -- 打印Lua 产生的错误消息
end
这种机制满足Lua 异常处理的所有需要。
8.5 Error Messages and Tracebacks
虽然你可以使用任意类型的一个值作为错误消息(比如表error({code=121})、数值error(10)),但通常错误消息都是string,它描述了是什么引发了错误。当引发了内部错误(例如试图对一个不是表的东西进行索引),Lua 生成错误消息;否则,错误消息是传给error 的值。
无论何时,当发生了错误而且错误消息是一个string 时,Lua 总是尝试在这个消息上添加一些别的信息,如文件名、错误所在行号。
local status, err = pcall(function () a = "a"+1 end)
print(err)
--> stdin:1: attempt to perform arithmetic on a string value
local status, err = pcall(function () error("my error") end)
print(err)
--> stdin:1: my error
error 函数还有额外的第二个参数
function foo (str)
if type(str) ~= "string" then
error("string expected") -- lua 会报告错误发生在这里
end
--<regular code>
end
foo({x=1})
-----------------------------------------------------------
function foo (str)
if type(str) ~= "string" then
error("string expected",2)
end
--<regular code>
end
foo({x=1}) -- 现在,lua 报告错误发生在这里
通常,当错误发生时我们想要更多的调试信息,而不只是错误发生的位置。至少,显示出引发错误的完整调用堆栈。当pcall 返回错误消息,它销毁了堆栈部分。因些,如果想要追踪堆栈信息必须在pcall 返回之前。Lua 提供了xpcall 可达此目的。
两个常用错误处理器是debug.debug 和debug.traceback。
你可以在任何时刻调用debug.traceback,得到当前执行的追踪(traceback)
print(debug.traceback())