Lua编译与运行

编译·运行

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

前面我们介绍过 dofile,把它当作 Lua 运行代码的 chunk 的一种原始的操作。dofile实际上是一个辅助的函数。真正完成功能的函数是 loadfile;与 dofile 不同的是 loadfile编译代码成中间码并且返回编译后的 chunk 作为一个函数,而不执行代码;另外 loadfile不会抛出错误信息而是返回错误代。.我们可以这样定义 dofile:

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

如果 loadfile 失败 assert 会抛出错误。

完成简单的功能 dofile 比较方便,他读入文件编译并且执行。然而 loadfile 更加灵活。在发生错误的情况下,loadfile 返回 nil 和错误信息,这样我们就可以自定义错误处理。另外,如果我们运行一个文件多次的话,loadfile 只需要编译一次,但可多次运行。dofile
却每次都要编译。

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") 
Programming in Lua 48
Copyright ® 2005, Translation Team, www.luachina.net 
print(f()) --> 30 

loadfile 和 loadstring 都不会抛出错误,如果发生错误他们将返回 nil 加上错误信息:

print(loadstring("i i"))  --> nil [string "i i"]:1: '=' expected near 'i' 

另外,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 

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

loadstring(s)() 

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

扫描二维码关注公众号,回复: 9185080 查看本文章
assert(loadstring(s))() 

通常使用 loadstring 加载一个字串没什么意义,例如:

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

大概与 f = function () i = i + 1 end 等价,但是第二段代码速度更快因为它只需要编译一次,第一段代码每次调用 loadstring 都会重新编译,还有一个重要区别:loadstring 编译的时候不关心词法范围:

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

这个例子中,和想象的一样 g 使用局部变量 i,然而 f 使用全局变量 i;loadstring 总是在全局环境中编译他的串。
loadstring 通常用于运行程序外部的代码,比如运行用户自定义的代码。注意:
loadstring 期望一个 chunk,即语句。如果想要加载表达式,需要在表达式前加 return,那样将返回表达式的值。看例子:

print "enter your expression:"
local l = io.read() 
local func = assert(loadstring("return " .. l)) 
print("the value of your expression is " .. func()) 
loadstring 返回的函数和普通函数一样,可以多次被调用:
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())) 
end 

require 函数

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 关注的问题只有分号(模式之间的分隔符)和问号,其他的信息(目录分隔符,文件扩展名)在路径中定义。为了确定路径,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

Lua 和 C 是很容易结合的,使用 C 为 Lua 写包。与 Lua 中写包不同,C 包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制,然而动态连接库不是 ANSI C 的一部分,也就是说在标准 C 中实现动态连接是很困难的。

通常 Lua 不包含任何不能用标准 C 实现的机制,动态连接库是一个特例。我们可以将动态连接库机制视为其他机制之母:一旦我们拥有了动态连接机制,我们就可以动态的加载 Lua 中不存在的机制。所以,在这种特殊情况下,Lua 打破了他平台兼容的原则而通过条件编译的方式为一些平台实现了动态连接机制。标准的 Lua 为 windows、Linux、FreeBSD、Solaris 和其他一些 Unix 平台实现了这种机制,扩展其它平台支持这种机制也是不难的。在 Lua 提示符下运行 print(loadlib())看返回的结果,如果显示 bad arguments则说明你的发布版支持动态连接机制,否则说明动态连接机制不支持或者没有安装。

Lua 在一个叫 loadlib 的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:

local path = "/usr/local/lua/lib/libluasocket.so"
local f = 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 库了。

发布了252 篇原创文章 · 获赞 151 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39885372/article/details/104334251