现在的位置: 首页 > 综合 > 正文

Lua-编译-运行-调试

2019年01月08日 ⁄ 综合 ⁄ 共 6487字 ⁄ 字号 评论关闭
文章目录

虽然我们把Lua当作解释型语言,但是Lua会首先把代码预编译成中间码然后再执行(很多解释型语言都是这么做的)。在解释型语言中存在编译阶段听起来不合适,然而,解释型语言的特征不在于他们是否被编译,而是编译器是语言运行时的一部分,所以,执行编译产生的中间码速度会更快。

dofile(file_name)

加载并执行文件,相当于loadfile()之后,返回一个函数,再调用这个函数。
  1. dofile实际上是一个辅助的函数。真正完成功能的函数是loadfile;
  2. 若出错,dofile会抛出异常,loadfile会返回nil和错误信息
  3. dofile每次都要编译。

--我们可以这样定义dofile:
function dofile (filename)
    local f = assert(loadfile(filename))
    return f()
end
--完成简单的功能dofile比较方便,他读入文件编译并且执行。

loadfile(file_name)

加载文件,这个函数相当于从文件里读出string,然后再调用loadstring(file_text)实现加载功能

  1. 与dofile不同的是loadfile编译代码成中间码并且返回编译后的chunk作为一个函数,而不执行代码
  2. 在发生错误的情况下,loadfile返回nil和错误信息,这样我们就可以自定义错误处理。
  3. 如果我们运行一个文件多次的话,loadfile只需要编译一次,但可多次运行

loadstring(str)

加载字符串,只是加载解析,并没有执行.

loadstring与loadfile相似,只不过它不是从文件里读入chunk,而是从一个串中读入。例如:

f = loadstring("i = i + 1")
f将是一个函数,调用时执行i=i+1。
i = 0
f(); print(i)     --> 1
f(); print(i)     --> 2

loadstring函数功能强大,但使用时需多加小心。确认没有其它简单的解决问题的方法再使用。

Lua把每一个chunk都作为一个匿名函数处理。例如:chunk "a = 1",loadstring返回与其等价的function () a = 1 end
与其他函数一样,chunks可以定义局部变量也可以返回值: 
f = loadstring("local a = 10; return a + 20") 
print(f()) --> 30 
loadfile和loadstring都不会抛出错误,如果发生错误他们将返回nil加上错误信息: 
print(loadstring("i i")) 
--> nil [string "i i"]:1: '=' expected near 'i' 

另外,loadfileloadstring都不会有边界效应产生,他们仅仅编译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 

如果你想快捷的调用dostring(比如加载并运行):可以这样loadstring(s)() 

调用loadstring返回的结果,然而如果加载的内容存在语法错误的话,loadstring返回nil和错误信息(attempt to call a nil value);为了返回更清楚的错误信息可以使用assert: assert(loadstring(s))() 

还有一个重要区别:loadstring编译的时候不关心词法范围,该函数总是在全局环境中编译它的字符串,因此它将无法访问文件局部变量,而是只能访问全局变量,如:

i = 322 
local i = 0
f = loadstring("i = i + 1; print(i)")
g = function() i = i + 1; print(i) end
f()   --f函数中的i为全局变量i,因此输出33
g()   --g函数中的i为局部变量i,因此输出1

对于loadstring返回的函数,如果需要对一个表达式求值,则必须在其之前添加return,这样才能构成一条语句,返回表达式的值,如:

i = 32
f = loadstring("i = i + 1; return i * 2")
print(f()) --输出66
print(f()) --输出68。由于loadstring返回的就是正规的函数,因此可以被反复调用。

require函数

这个函数是通过调用dofile()来实现的。不同的是,每次加载执行一个文件时,require()都会记录,保存在table
package.loaded中
,避免重复加载。另外,如果给定的路径找不到文件,
require()会到指定的路径下去找输到加载的文件。文件名称可以省去.lua后缀。

Lua提供高级的require函数来加载运行库。粗略的说require和dofile完成同样的功能但有两点不同

  1. require会搜索目录加载文件
  2. require会判断是否文件已经加载避免重复加载同一文件。由于上述特征,require在Lua中是加载库的更好的函数。

require使用的路径和普通我们看到的路径还有些区别,我们一般见到的路径都是一个目录列表。require的路径是一个模式列表每一个模式指明一种由虚文件名(require的参数)转成实文件名的方法。更明确地说,每一个模式是一个包含可选的问号的文件名。匹配的时候Lua会首先将问号用虚文件名替换,然后看是否有这样的文件存在。如果不存在继续用同样的方法用第二个模式匹配。例如:?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
     
调用require "lili"时会试着打开这些文件:

lili
lili.lua
c:\windows\lili
/usr/local/lua/lili/lili.lua

require是如何加载的呢?

  1. 先判断package.loaded这个table中有没有对应模块的信息;
  2. 如果有,就直接返回对应的模块,不再进行第二次加载;
  3. 如果没有,就加载,返回加载后的模块。

require关注的问题只有分号(模式之间的分隔符)和问号其他的信息(目录分隔符,文件扩展名)在路径中定义。

为了确定路径,Lua首先检查全局变量LUA_PATH是否为一个字符串,如果是则认为这个串就是路径;否则require检查环境变量LUA_PATH的值,如果两个都失败require使用固定的路径(典型的"?;?.lua")

require的另一个功能是避免重复加载同一个文件两次。Lua保留一张所有已经加载的文件的列表(使用table保存)。如果一个加载的文件在表中存在require简单的返回;表中保留加载的文件的虚名,而不是实文件名所以如果你使用不同的虚文件名require同一个文件两次,将会加载两次该文件。比如require "foo"和require "foo.lua",路径为"?;?.lua"将会加载foo.lua两次。我们也可以通过全局变量_LOADED访问文件名列表,这样我们就可以判断文件是否被加载过;同样我们也可以使用一点小技巧让require加载一个文件两次。比如,require "foo"之后_LOADED["foo"]将不为nil,我们可以将其赋值为nil,require "foo.lua"将会再次加载该文件。

一个路径中的模式也可以不包含问号而只是一个固定的路径,比如:?;?.lua;/usr/local/default.lua

这种情况下,require没有匹配的时候就会使用这个固定的文件(当然这个固定的路径必须放在模式列表的最后才有意义)。在require运行一个chunk以前,它定义了一个全局变量_REQUIREDNAME用来保存被required的虚文件的文件名。我们可以通过使用这个技巧扩展require的功能。举个极端的例子,我们可以把路径设为"/usr/local/lua/newrequire.lua",这样以后每次调用require都会运行newrequire.lua,这种情况下可以通过使用_REQUIREDNAME的值去实际加载required的文件。

C Packages--C包

Lua和C是很容易结合的,使用C为Lua写包。与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制,然而动态连接库不是ANSI C的一部分,也就是说在标准C中实现动态连接是很困难的。
通常Lua不包含任何不能用标准C实现的机制,动态连接库是一个特例。我们可以将动态连接库机制视为其他机制之母:一旦我们拥有了动态连接机制,我们就可以动态的加载Lua中不存在的机制。所以,在这种特殊情况下,Lua打破了他平台兼容的原则而通过条件编译的方式为一些平台实现了动态连接机制。标准的Lua为windows、Linux、FreeBSD、Solaris和其他一些Unix平台实现了这种机制,扩展其它平台支持这种机制也是不难的。在Lua提示符下运行print(package.loadlib())看返回的结果,如果显示bad
arguments则说明你的发布版支持动态连接机制,否则说明动态连接机制不支持或者没有安装。
Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:
local path = "/usr/local/lua/lib/libluasocket.so"  --由于loadlib是非常底层的函数,因为在调用时必须提供完整的路径名和函数名称
local f = package.loadlib(path, "luaopen_socket")

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

local path = "/usr/local/lua/lib/libluasocket.so"
-- or path = "C:\\windows\\luasocket.dll"
local f = assert(loadlib(path, "luaopen_socket"))
f()  -- actually open the library

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的stub文件,安装二进制库的时候可以随便放在某个目录,只需要修改stub文件对应二进制库的实际路径即可。将stub文件所在的目录加入到LUA_PATH,这样设定后就可以使用require函数加载C库了。

错误

Lua作为一种嵌入式脚本语言,在发生错误时,不应该只是简单的退出或崩溃。相反,一旦有错误发生,Lua就应该结束当前程序块并返回到应用程序。在Lua中我们可以通过error()函数获取错误消息error的参数是要抛出的错误信息,如:

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

上面代码中的最后一行我们可以通过Lua提供的另外一个内置函数assert类辅助完成,如:

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

assert函数将检查其第一个参数是否为true,如果是,则简单的返回该参数,否则就引发一个错误第二个参数是可选字符串。

对于所有的编程语言而言,错误处理都是一个非常重要的环节。在实际的开发中,没有统一的指导原则,只能是在遇到问题后,经过缜密的分析在结合当时的应用场景,最后结合自己的经验再给出错误的具体处理方式。在有些情况下,我们可以直接返回错误码,而在另外一些情况下,则需要直接抛出错误(error,assert方式,在lua中),让开发者能够快速定位导致错误的代码源。

异常和错误处理

Lua提供了错误处理函数pcall,该函数的第一个参数为需要“保护执行”的函数,如果该函数执行失败,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:\workplace\CB1.lua:21: attempt to index local 'a' (a number value)

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

r, msg = pcall(function() error({code = 121}) end)
if r then
    print("This is ok.")
else
    print("This is error.")
    print(msg.code)
end
--This is error.
--121

错误信息和回跟踪

当错误发生的时候,我们常常希望了解详细的信息,而不仅是错误发生的位置。若能了解到“错误发生时的栈信息”就好了,但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.
--]]

【上篇】
【下篇】

抱歉!评论已关闭.