Programming in Lua, 2nd edition - Chapter 8: Compilation, Execution, and Errors

 

 

 

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 代劳了那些复杂的操作。像dofileloadfile 从一个文件加载一个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())

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值