欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Lua 编译-执行-错误

程序员文章站 2024-03-17 23:13:58
...

note目录

  • 编译及常用的几个函数
  • require函数
  • C代码
  • 错误
  • 异常和错误处理
  • 错误信息和跟踪

1:编译及常用的几个函数

Lua称为解释型语言,但Lua确实允许在运行源代码之前,先将源代码编译为一种中间形式。听上去“编译”似乎不应在一种解释语言的范畴之列。
区别解释型语言的主要特征并不是在于是否能编译它们,而是在于编译器是否是语言运行时库的一部分,即是否有能力(并且轻易地)执行动态生成的代码。
正是因为存在了诸如dofile这样的函数,才可以将Lua称为解释型语言。

【dofile】

它是一种内置操作,用于运行代码块。
一般dofile可以这样实现:

function dofile(filename)
    local f = assert(loadfile(filename))
    return f()
end    

(1)dofile 实际上是一个辅助函数,loadfile才是真正的核心。

(2)类似dofileloadfile只会从一个文件中加载Lua代码块,但不会运行代码,只是编译代码,然后将编译结果作为一个函数返回。

(3)与dofile不同的还有loadfile不会引发错误,它只是返回错误值并不处理错误。 若出错,dofile会抛出异常,loadfile会返回nil和错误信息

(4)dofile每次都要编译。
如果需要多次运行一个文件,那么只需要调用一次loadfile后,多次调用它的返回结果就可以了。
相对于多次调用dofile来说,由于只需要编译一次文件,开销就小得多。

【loadstring】

loadstring声明,定义

loasstringloadfile类似,不同之处在于它是从一个字符串中读取代码,而非从文件中读取。

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

f就变成了一个函数,每次调用是就执行i = i + 1

i = 0
f()
print(i)               --> 1
f()
print(i)               --> 2

loadstring的功能是非常强大的,使用时需谨慎。确认没有更简单的解决问题的方法在使用它。

loadstring取变量的作用域

loadstring在编译时不涉及词法域,所以下面的2段代码是不等价的。

frag1:

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

frag2:

f = function()
     i = i + 1
    end 

测试代码:

i = 32
local i = 0
f = loadstring("i = i + 1;print(i)")
g = function() 
        i = i+ 1
    end

f()               --> 33
g()               --> 1

函数g如期的操作了局部变量i ,但是f操作的却是全局的i,这是因为loadstring总是在全局环境中编译它的字符串。

引入loadstring的目的

loadstring最典型的用处是执行外部代码,也就是那些位于程序之外的代码。
例如,让用户来参与一个函数的调用,那么这是就需要一用户输入函数的代码了,然后调用loadstring来对其求值。
注意loadstring的期望输入是一个程序块,如果需要对一个表达式求值,则必须在其之前添加return,这样才能构成一条语句,返回表达式的值。

example:

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

另外,loadfile和loadstring,都不会有边界效应产生,他们仅仅编译*chunk成为自己内部实现的一个匿名函数通常对他们的误解是他们定义了函数。Lua中的函数定义是发生在运行时的赋值不是发生在编译时*。假如我们有一个文件foo.lua:

-- file `foo.lua'
function foo (x)
print(x)
end
当我们执行命令f = loadfile("foo.lua")后,foo被编译了但还没有被定义,如果要定义他必须运行chunk:
f() -- defines `foo'
foo("ok") --> ok

2:require函数

2.1 require函数的作用

要加载一个模块,只需要简单地调用require<模块名>,该调用会返回一个有模块函数组成的table,并且还定义一个包含该table的全局变量。

在使用Lua的标准库的时候,可以不用显示的调用require,因为Lua已经预加载了他们。

Lua提供的高层函数来加载运行库,和dofile有类似的功能但有2点不同:

(1)require会搜索目录加载

(2)require会判断文件文件是否加载过,避免重复加载同一文件。

require使用的路径和其他语言的路径不一样。其他语言的目录一般是全路径格式(目录列表形式)。
require使用的是一种模式列表,每一个模式指明一种虚文件名(require的参数),转成实文件名的方法。

匹配的时候Lua会首先将问号?用虚文件名(require的参数)替换,这样就构成了一个完整的路径。然后看一下是否存在这个文件,如果不存在的话则继续用同样的方法用第二个模式匹配。

Lua中默认的匹配路径有一下4个路径(以分号;隔开)。
?;?.lua;c:/windows/?;/usr/local/lua/?/?.lua

example:

require("filename")

(1)?
当前脚本的同级目录下的filename文件(注意没有后缀)

(2)?.lua
当前脚本的同级目录下的filename.lua文件(注意.lua有后缀)

(3)c:windows/?
一般为lua的配置环境变量的路径,LUA_PATH在系统里面配的路径
c:windows?filename(注意没有后缀)

(4)/usr/local/lua/?/?.lua
一般为luac的配置环境变量的路径,LUAC_PATH在系统里面配的路径
/usr/local/lua/filename/filename.lua(注意.lua有后缀)

2.2 如何设定require的模式匹配字符串

Lua将require搜索的模式字符串?;?.lua;c:/windows/?;/usr/local/lua/?/?.lua放在变量package.path中。这个模式字符串我们自己可以添加自定义的字符串来匹配找到对应的Lua文件。

在lua中有3种设定require的模式匹配字符串

(1)当Lua启动后,便以环境变量LUA_PATH的值来初始化这个变量。
当我们的开发工程放在Lua配置的环境变量的目录下(一般我们的开发工程是不会放在这个目录下,不宜多个项目的开发)的时候就不需要额外为Lua的package添加新的路径,默认的路径就可以搜索到。在我们的开发工程里面跨文件夹require("fileName")也是可以找得到fileName.lua文件的,并加载进来。

(2)如果没有找到该环境变量LUA_PATH,则使用一个编译时定义的默认路径来初始化。
一般我们的开发工程都不会在lua的配置环境变量的目录下的。我们都会自定义我们的工程目录的。这样一来lua的默认的4个搜索的字符串就匹配不到了。这个时候我们就要自己把我们自己的开发工程的路径添加到这个模式匹配的串的后面,或使用一个编译时定义的默认路径来初始化,在最开始启动Lua程序的时候我们可以这样做,把自己的开发工程路径加进来:
假设开发路径,Lua的脚本路径为:
E:\work\untiyLuaProject\Assets\Lua

package.path = package.path .. ";E:\\work\\unityLuaProject\\Assets\\Lua";

【注意】
a:最前面加上分号”;”
b:加上转义字符’\’

(3)如果require无法找到与模块名相符的Lua文件,就会找C程序库。C程序库的搜索模式存放在变量package.cpath中。而这个变量则是通过环境变量LUA_CPATH来初始化的。

2.3 require避免同一文件加载2次

lua中会保留一张已经加载过的文件列表(package.loaded是一个table)。如果一个文件在表中已经加载过,则require简单返回。
注意package.loaded表中使用文件的虚名(不是全路径只是require里面的那个参数)来作为key的,而不是实文件名。所以使用不同的虚文件名require同一个Lua文件的2次话(如:第一个和第二个模式字符串),将会加载该文件2次。

example:

require("foo")
require("foo.lua")

模式字符串为:

"?"
"?.lua"

这样”foo”和”foo.lua”是不一样的”key”,会加载2次foo.lua文件。

同样我们也可以使用一点小技巧让require加载一个文件两次。比如,require “foo”之后package.loaded[“foo”]将不为nil,我们可以将其赋值为nil,require “foo.lua”将会再次加载该文件。

2.4 一个路径中的模式也可以不包含问号而只是一个固定的路径

这个通用的设置就是上面2.2中的第2条讲到的,我们可以自定义自己的模式匹配字符串,自定lua去找我们开发工程下的lua文件。


3:C代码

Lua提供的所有关于动态链接的功能都聚集在一个函数中,即package.loadlib
该函数有2个字符串参数:动态库的完整路径和一个函数名称。

example:

local path = "user/local/lib/5.1/socket.so"
local f = package.loadlib(path,"luaopen_socket")

loadlib函数加载指定的库,并将其链接如Lua,不过,它并没有调用库中的任何方法。相反,它将一个C函数作为Lua函数返回。
如果在加载库或查找初始化函数时发生错误,loadlib返回nil及一条消息。

loadlib函数是一个非常底层的函数。必须提供完整路径及正确的函数名称。
通常使用require来加载C程序库,这个函数会搜索指定的库,然后用loadlib来加载库,并返回初始化函数。
这个初始化函数应将库中提供的库函数注册到Lua中,就好像一段Lua代码定义了其他的函数一样。


4:错误

Lua作为一种扩展语言。通常嵌在其他应用程序中,因此在发生错误时,它不能简单地奔溃或退出。相反,只要发生一个错误,Lua应当结束当前程序块并返回应用程序。

在Lua中通过error函数来获取到错误消息。
error的参数是要抛出的错误的消息。

print("enter a number")
n = io.read("*number")
if not n then
    error("invalid input")
end    

【assert函数的由来】

由于像if not<condition> then error end这样的组合是非常通用的代码。所以Lua提供了一个内建(build-in)函数assert来完成此类工作。

函数声明

assert(exprision,[info])

assert有2个参数,
检查第一个参数是否为true,若为true,则简单的返回该参数;
否则(为falsenil)就引发一个错误。它的第二个参数是一个可选的信息字符串。

example:

print("enter a number")
n = assert(io.read("*number"),"invalid input")

5:异常和错误处理

如果需要在Lua中处理错误,则必须要使用pcall(protected call :受保护的调用)来包装需要执行的代码。如果该函数执行失败,pcall返回false和错误信息,否则返回true和函数调用的返回值。

function foo()
    local a = 10
    print(a[2])
end
r,msg = pcall(foo)
if r then
    print("This is ok.")
else
    print("This is error.")
    print(msg)
end
--This is error.
--E:\Lua\CB1.lua:21: attempt to index local 'a' (a number value)

错误消息不一定是字符串(也可以是一张变table),但事实上任何类型的Lua值都可以作为错误消息传递给error函数,并且这些值也会成为pcall的返回值。

status, err = pcall(function() error({code = 121}) end)
print(err.code)

output:
121

6:错误信息和跟踪

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

print(debug.traceback())

通常在错误发生时,希望得到更多的调试信息,而不是只有发生错误的位置。至少等追溯到发生错误时和函数调用情况,显示一个完整的函数调用栈轨迹。要完成这一功能,我们需要使用Lua提供的另外一个内置函数xpcall。该函数除了接受一个需要被调用的函数之外,还接受第二个参数,即错误处理函数。当发生错误时,Lua会在调用栈展开前调用错误处理函数。这样,我们就可以在这个函数中使用debug库的debug.traceback函数,它会根据调用栈来构建一个扩展的错误消息

function errorFunc()
    local a = 20
    print(a[10])
end
function errorHandle()
    print(debug.traceback())
end
if xpcall(errorFunc,errorHandle) then
    print("This is OK.")
else
    print("This is error.")
end
--输出结果为:
--[[stack traceback:
        d:/test.lua:7: in function <d:/test.lua:6>
        d:/test.lua:3: in function <d:/test.lua:1>
        [C]: in function 'xpcall'
        d:/test.lua:10: in main chunk
        [C]: ?
This is error.
--]]