lua快速入门 —— 编译执行与错误

本文最后更新于:2020年1月31日 晚上

概览

参照书籍:《Lua程序设计(第二版)》

Lua版本:Lua 5.3.5

编译

Lua是一种解释型语言,但Lua允许在运行源代码前,先将源代码预编译为一种中间形式。

区别解释型语言的主要特征并不在于是否能编译它们,而是在于编译器是否是语言运行时库的一部分,即是否有能力(并轻易地)执行动态生成的代码

Lua中的 dofile函数,用于运行Lua代码块

1
2
-- 在Lua的命令行中 执行下述语句可以得到结果
dofile("D:\\Code\\Lua\\new.lua")

但是实际上,loadfile函数才是做了真正核心的工作。loadfile从一个文件加载Lua代码块,但它并不运行代码块,而是只编译代码,然后将编译结果作为一个函数返回。

1
2
3
-- 在Lua的命令行中
f = loadfile("D:\\Code\\Lua\\new.lua")
f() -- 执行代码

与dofile不同的还有loadfile不会引发错误,它只是返回错误值并不处理错误。

1
2
3
4
5
-- 一般dofile可以这样来定义
function dofile(filename)
local f = assert(loadfile(filename))
return f()
end

上述代码中,如果loadfile失败,那么assert就会引发一个错误。

dofile在一次调用中做完了所有的事,而loadfile更加的灵活,比如需要多次运行一个文件,只需要多次使用loadfile的返回结果就可以了。

除此之外还有一个类似的loadstring函数,用于去执行外部代码。不过 loadstring在lua5.2中已经被弃用了

动态链接机制

Lua通常不会包含任何无法通过ANSI C来实现的机制,但是动态链接有些意外,因为ANSI C标准没有动态链接。

可以将动态链接视为其他机制的母机制,只要拥有它,就可以动态加载任何其他不在Lua中的机制了。

要检测某一平台是否支持动态链接机制,只需要在Lua命令行中运行print(package.loadlib("a","b")) ,然后观察执行结果,如果报告不存在指定文件,就说明该平台具有动态链接机制。否则会有错误消息提示或未安装此机制。

Lua提供了所有的动态连接的功能都在一个叫package.loadlib的函数内。这个函数有两个参数:动态库的完整路径和一个函数名称。所以典型的调用的例子如下:

1
2
local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket")

loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用他。如果加载动态库或者查找初始化函数时出错,loadlib将返回nil和错误信息。

通常使用require来加载C程序库,这个函数会搜索指定的库,然后用loadlib来加载库,并返回初始化函数。这个初始化函数应将库中提供的函数注册到Lua中,就好像一段Lua代码定义了其他的函数一样。

错误

Lua经常作为扩展语言嵌入在别的应用中,所以不能当错误发生时简单的崩溃或者退出。相反,当错误发生时Lua结束当前的chunk并返回到应用中。

Lua提供了内建的assert函数来处理错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print "please enter a number"
n = assert(io.read("*number"),"invalid input")
print ("You enter the num is "..n)

--[[
> dofile("D:\\Code\\Lua\\new.lua")
please enter a number
a
D:\Code\Lua\new.lua:261: invalid input
stack traceback:
[C]: in function 'assert'
D:\Code\Lu\new126.lua:261: in main chunk
[C]: in function 'dofile'
stdin:1: in main chunk
[C]: in ?
> nil
--]]

assert函数检查其第一个参数是否为true,若为true,则简单的返回该参数,否则(即false或者nil)就会引发一个错误。它的第二个参数是一个可选的信息字符串,当第一个参数为false时,assert会以第二个参数为错误信息抛出。

另:assert总会先处理两个参数(即如果参数需要计算就会进行计算),然后才调用函数。

当一个函数遭遇了一种未预期的状况(即异常时),它可以采取两种基本的行为:返回错误代码(通常时nil)**或者引发一个错误(调用error)**。

在这两种选择之间并没有固定的法则,通常的原则是:易于避免的异常应引发一个错误,否则应返回错误代码。

对于程序逻辑上能够避免的异常,以抛出错误的方式处理之,否则返回错误代码。——《Lua程序设计第一版》

Lua进行错误处理

如果想要处理错误,必须使用函数pcall来包装需要执行的代码。

第一步:将这段代码封装在一个函数内

1
2
3
4
5
6
7
function foo ()
...
if unexpected_condition then error() end
...
print(a[i]) -- potential error: `a' may not be a table
...
end

第二步:使用pcall调用这个函数

1
2
3
4
5
6
7
if pcall(foo) then
-- no errors while running `foo'
...
else
-- `foo' raised an error: take appropriate actions
...
end

当然也可以用匿名函数的方式调用pcall:

1
2
3
4
5
if pcall(function () <代码> ) then
<常规代码>
else
<错误代码处理>
end

pcall函数会以一种“保护模式(protected mode)”来调用它得第一个参数,因此pcall可以捕获函数执行中的错误。如果没有发生错误,那么pcall会返回true以及函数调用的返回值,否则返回false以及错误消息。

错误信息不一定仅为字符串(下面的例子是一个table),传递给error的任何信息都会被pcall返回:

1
2
3
local status, err = pcall(function () error({code=121}) end)
print(err.code) --> 121
-- 但是在本地的命令行无法运行…… 使用文件的方式可以使用

这种机制提供了强大的能力,足以应付Lua中的各种异常和错误情况。我们通过error抛出异常,然后通过pcall捕获之,而错误消息则可以标识出错误的类型或内容。

错误消息

通常情况下我们使用字符串来描述错误信息,如果遇到内部错误(比如对一个非table的值使用索引下标访问)Lua将自己产生错误信息,否则Lua使用传递给error函数的参数作为错误信息。

而error函数还有第二个附加参数level,来指明是调用层级中的哪一层的error()函数来报告当前错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function foo(num)
if type(num) ~= "number" then
error("number expected",2) -- 让error报告错误发生在第二级(自己的函数是第一级)
-- 好吧,没搞懂 书 P71
end
print(str..123)
end

n = io.read()
foo(n)
--[[
> dofile("D:\\Code\\Lua\\new126.lua")
asds(人为输入的值)
D:\Code\Lua\new126.lua:307: number expected (错误给出了文件名、行号、以及自定义的错误信息)
stack traceback:
[C]: in function 'error'
D:\Code\Lua\new126.lua:301: in function 'foo'
D:\Code\Lua\new126.lua:307: in main chunk
[C]: in function 'dofile'
stdin:1: in main chunk
[C]: in ?
>
--]]

追溯(traceback)

当错误发生的时候,我们常常希望了解详细的信息,而不仅是错误发生的位置。若能了解到“错误发生时的栈信息”就好了,但pcall返回错误信息时,已经释放了保存错误发生情况的栈信息。

因此,若想得到tracebacks,我们必须在pcall返回以前获取。Lua提供了xpcall来实现这个功能,xpcall接受两个参数:调用函数、错误处理函数。当错误发生时,Lua会在栈释放以前调用错误处理函数,因此可以使用debug库收集错误相关信息。有两个常用的debug处理函数:debug.debug和debug.traceback,前者给出Lua的提示符,你可以自己动手察看错误发生时的情况;后者通过traceback创建更多的错误信息,也是控制台解释器用来构建错误信息的函数。你可以在任何时候调用debug.traceback获取当前运行的traceback信息:

1
print(debug.traceback())

else …


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!