使用 Lua 编写可嵌入式脚本
使用 Lua 编写可嵌入式脚本 Martin Streicher (martin.streicher@linux-mag.com),首席编辑,Linux Magazine
虽然编译性编程语言和脚本语言各自具有自己独特的优点,但是如果我们使用这两种类型的语言来编写大型的应用程序会是什么样子呢?Lua 是一种嵌入式脚本语言,它非常小,速度很快,功能却非常强大。在创建其他配置文件或资源格式(以及与之对应的解析器)之前,请尝试一下 Lua。 然而,脚本语言也有自己独特的优点。例如,当某种语言的解释器被成功移植到一种平台上以后,使用这种语言编写的大量脚本就可以不加任何修改在这种新平台上运行 —— 它们没有诸如系统特定的函数库之类的依赖限制。(我们可以考虑一下 Microsoft? Windows? 操作系统上的许多 DLL 文件和 UNIX? 及 Linux? 上的很多 libcs)。另外,脚本语言通常都还会提供高级编程构造和便利的操作,程序员可以使用这些功能来提高生产效率和灵活性。另外,使用解释语言来编程的程序员工作的速度更快,因为这不需要编译和链接的步骤。C 及其类似语言中的 “编码、编译、链接、运行” 周期缩减成了更为紧凑的 “编写脚本、运行”。 Lua 新特性 与其他脚本语言一样,Lua 也有自己的一些特性: Lua 类型。在 Lua 中,值可以有类型,但是变量的类型都是动态决定的。nil、布尔型、数字 和 字符串 类型的工作方式与我们期望的一样。 -- example of an anonymous function
i = 3 会生成 4 7 5 nil nil。如果变量列表的个数大于值列表的个数,那么多出的变量都被赋值为 nil;因此,b 就是 nil。如果值的个数多于变量的个数,那么多出的值部分就会简单地丢弃。在 Lua 中,变量名是大小写敏感的,这可以解释为什么 I 的值是 nil。 在所有的工程任务中,要在编译性语言和解释性语言之间作出选择,就意味着要在这种环境中对每种语言的优缺点、权重和折中进行评测,并接受所带来的风险。 ?
在两个世界之间最好地进行混合 如果您希望充分利用这两个世界的优点,应该怎样办呢,是选择最好的性能还是选择高级强大的抽象?更进一步说,如果我们希望对处理器密集且依赖于系统的算法和函数以及与系统无关且很容易根据需要而进行修改的单独逻辑进行优化,那又当如何呢? 对高性能代码和高级编程的需要进行平衡是 Lua(一种可嵌入式脚本语言)要解决的问题。在需要时我们可以使用编译后的代码来实现底层的功能,然后调用 Lua 脚本来操作复杂的数据。由于 Lua 脚本是与编译代码独立的,因此我们可以单独修改这些脚本。使用 Lua,开发周期就非常类似于 “编码、编译、运行、编写脚本、编写脚本、编写脚本 ...”。 例如,Lua Web 站点 “使用” 页面(请参见 参考资料)列出了主流市场上的几个计算机游戏,包括 World of Warcraft 和(家用版的)Defender,它们集成 Lua 来实现很多东西,从用户界面到敌人的人工智能都可以。Lua 的其他应用程序包括流行的 Linux 软件更新工具 apt-rpm 的扩展机制,还有 “Crazy Ivan” Robocup 2000 冠军联赛的控制逻辑。这个页面上的很多推荐感言都对 Lua 的小巧与杰出性能赞不绝口。 ?
开始使用 Lua Lua 5.0.2 版本是撰写本文时的最新版本,不过最近刚刚发布了 5.1 版本。您可以从 lua.org 上下载 Lua 的源代码,在 Lua-users wiki(链接请参见 参考资料)上可以找到预先编译好的二进制文件。完整的 Lua 5.0.2 核心文件中包括了标准库和 Lua 编译器,不过只有 200KB 大小。 如果您使用的是 Debian Linux,那么可以以超级用户的身份运行下面的命令来快速安装 Lua 5.0: # apt-get install lua50
在系统上安装好 Lua 之后,我们可以首先来试用一下单独的 Lua 解释器。(所有的 Lua 应用程序必须要嵌入到宿主应用程序中。解释器只是一种特殊类型的宿主,对于开发和调试工作来说非常有用。)创建一个名为 factorial.lua 的文件,然后输入下面的代码: -- defines a factorial function print("enter a number:")
$ lua factorial.lua
$ (echo '#! /usr/bin/lua'; cat factorial.lua) > factorial ?
Lua 语言 Lua 具有现代脚本语言中的很多便利:作用域,控制结构,迭代器,以及一组用来处理字符串、产生及收集数据和执行数学计算操作的标准库。在 Lua 5.0 Reference Manual 中有对 Lua 语言的完整介绍(请参见 参考资料)。 在 Lua 中,只有值 具有类型,而变量的类型是动态决定的。Lua 中的基本类型(值)有 8 种: nil,布尔型,数字,字符串,函数,线程,表 以及 用户数据。前 6 种类型基本上是自描述的(例外情况请参见上面的 Lua 特性 一节);最后两个需要一点解释。 Lua 表 在 Lua 中,表是用来保存所有数据的结构。实际上,表是 Lua 中惟一的 数据结构。我们可以将表作为数组、字典(也称为散列 或联合数组)、树、记录,等等。 与其他编程语言不同,Lua 表的概念不需要是异构的:表可以包含任何类型的组合,也可以包含类数组元素和类字典元素的混合体。另外,任何 Lua 值 —— 包括函数或其他表 —— 都可以用作字典元素的键值。 要对表进行浏览,请启动 Lua 解释器,并输入清单 1 中的黑体显示的代码。
$ lua > -- more commonly,create the table and define elements > -- make a reference and a string > -- indices can be any Lua value > -- the phrase table.string is the same as table["string"] > -- indices can also be "multi-dimensional" > -- i points to the same table as t3 > -- non-existent indices return nil values >? -- even a function can be a key
> table.foreachi(t1,print)
> table.foreach(t2,print)
现在我们可以创建一个表 t,其元素是 {2,4,6,language="Lua",version="5",8,10,12,web="www.lua.org"},然后运行 table.foreach(t,print) 和 table.foreachi(t,print)。 用户数据 由于 Lua 是为了嵌入到使用另外一种语言(例如 C 或 C++)编写的宿主应用程序中,并与宿主应用程序协同工作,因此数据可以在 C 环境和 Lua 之间进行共享。正如 Lua 5.0 Reference Manual 所说,userdata 类型允许我们在 Lua 变量中保存任意的 C 数据。我们可以认为 userdata 就是一个字节数组 —— 字节可以表示指针、结构或宿主应用程序中的文件。 用户数据的内容源自于 C,因此在 Lua 中不能对其进行修改。当然,由于用户数据源自于 C,因此在 Lua 中也没有对用户数据预定义操作。不过我们可以使用另外一种 Lua 机制来创建对 userdata 进行处理的操作,这种机制称为 元表(metatable)。 元表 由于表和用户数据都非常灵活,因此 Lua 允许我们重载这两种类型的数据的操作(不能重载其他 6 种类型)。元表 是一个(普通的)Lua 表,它将标准操作映射成我们提供的函数。元表的键值称为事件;值(换而言之就是函数)称为元方法。 函数 setmetatable() 和 getmetatable() 分别对对象的元表进行修改和查询。每个表和 userdada 对象都可以具有自己的元表。 例如,添加操作对应的事件是 __add。我们可以推断这段代码所做的事情么? -- Overload the add operation function String(string) -- The first operand is a String table s = String('Hello')
Hello There World!
__index 是另外一个事件。__index 的元方法每当表中不存在键值时就会被调用。下面是一个例子,它记住 (memoize) 函数的值: -- code courtesy of Rici Lake,rici@ricilake.net COLORS = {"red","blue","green","yellow","black"}
这段代码接收一个键值 node,查找 node 指定的颜色。如果这种颜色不存在,代码就会给 node 赋一个新的随机选择的颜色。否则,就返回赋给 node 的颜色。在前一种情况中,__index 元方法被执行一次以分配一个颜色。后一种情况比较简单,所执行的是快速散列查找。 Lua 语言提供了很多其他功能强大的特性,所有这些特性都有很好的文档进行介绍。在碰到问题或希望与专家进行交谈时,请连接 Lua Users Chat Room IRC Channel(请参见 参考资料)获得非常热心的支持。 ?
嵌入和扩展 除了语法简单并且具有功能强大的表结构之外,Lua 的强大功能使其可以与宿主语言混合使用。由于 Lua 与宿主语言的关系非常密切,因此 Lua 脚本可以对宿主语言的功能进行扩充。但是这种融合是双赢的:宿主语言同时也可以对 Lua 进行扩充。举例来说,C 函数可以调用 Lua 函数,反之亦然。 Lua 与宿主语言之间的这种共生关系的核心是宿主语言是一个虚拟堆栈。虚拟堆栈与实际堆栈类似,是一种后进先出(LIFO)的数据结构,可以用来临时存储函数参数和函数结果。要从 Lua 中调用宿主语言的函数(反之亦然),调用者会将一些值压入堆栈中,并调用目标函数;被调用的函数会弹出这些参数(当然要对类型和每个参数的值进行验证),对数据进行处理,然后将结果放入堆栈中。当控制返回给调用程序时,调用程序就可以从堆栈中提取出返回值。 实际上在 Lua 中使用的所有的 C 应用程序编程接口(API)都是通过堆栈来进行操作的。堆栈可以保存 Lua 的值,不过值的类型必须是调用程序和被调用者都知道的,特别是向堆栈中压入的值和从堆栈中弹出的值更是如此(例如 lua_pushnil() 和 lua_pushnumber()。 清单 2 给出了一个简单的 C 程序(节选自 参考资料 中 Programming in Lua 一书的第 24 章),它实现了一个很小但却功能完善的 Lua 解释器。
?1 #include <stdio.h>
第 17 行和 luaL_loadbuffer() 会从 stdin 中以块的形式接收输入,并对其进行编译,然后将其放入虚拟堆栈中。第 18 行从堆栈中弹出数据并执行之。如果在执行时出现了错误,就向堆栈中压入一个 Lua 字符串。第 20 行访问栈顶(栈顶的索引为 -1)作为 Lua 字符串,打印消息,然后从堆栈中删除该值。 使用 C API,我们的应用程序也可以进入 Lua 状态来提取信息。下面的代码片段从 Lua 状态中提取两个全局变量: .. lua_getglobal(L,"width");
反之,在 Lua 中调用 C 函数也与之类似。如果您的操作系统支持动态加载,那么 Lua 可以根据需要来动态加载并调用函数。(在必须使用静态加载的操作系统中,可以对 Lua 引擎进行扩充,此时调用 C 函数时需要重新编译 Lua。) ?
结束语 Lua 是一种学习起来容易得难以置信的语言,但是它简单的语法却掩饰不了其强大的功能:这种语言支持对象(这与 Perl 类似),元表使表类型具有相当程度的可伸展性,C API 允许我们在脚本和宿主语言之间进行更好的集成和扩充。Lua 可以在 C、C++、C#、Java? 和 Python 语言中使用。 在创建另外一个配置文件或资源格式(以及相应的处理程序)之前,请尝试一下 Lua。Lua 语言及其社区非常健壮,具有创新精神,随时准备好提供帮助。
学习 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
获得产品和技术 下载 Lua 5.0.2 或 Lua 5.1 源代码,从头开始编译 Lua。
讨论 通过参与 developerWorks blogs 加入 developerWorks 社区。 关于作者 ? ? Martin Streicher 是 Linux Magazine 的首席编辑。他在普渡大学获得了计算机硕士学位,自 1982 年以来,就一直在使用 Pascal、C、Perl、Java 以及(最近使用的)Ruby 编程语言编写类 Unix 系统。??? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |