邓际锋<soloist.deng#gmail.com>
本文发表于2006年6月《程序员》杂志,谢绝转载
Lua语言简介
1993年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro in Brazil)诞生了一门编程语言,发明者是该校的三位研究人员,他们给这门语言取了个浪漫的名字——Lua,在葡萄牙语里代表美丽的月亮。事实证明她没有糟蹋这个优美的单词,Lua语言正如它名字所预示的那样成长为一门简洁、优雅且富有乐趣的语言。
Lua从一开始就是作为一门方便嵌入(其它应用程序)并可扩展的轻量级脚本语言来设计的,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用ANSI C编写,能以C程序库的形式嵌入到宿主程序中。Lua的每个版本都保持着开放源码的传统,不过各版采用的许可协议并不相同,自5.0版(最新版是5.1)开始她采用的是著名的MIT许可协议。正由于上述特点,所以Lua在游戏开发、机器人控制、分布式应用、图像处理、生物信息学等各种各样的领域中得到了越来越广泛的应用。其中尤以游戏开发为最,许多著名的游戏,比如Escape from Monkey Island、World of Warcraft、大话西游,都采用了Lua来配合引擎完成数据描述、配置管理和逻辑控制等任务。
作为一门过程型动态语言,Lua有着如下的特性:
1、变量名没有类型,值才有类型,变量名在运行时可与任何类型的值绑定;
2、语言只提供唯一一种数据结构,称为表(table),它类似key-value关联数组,可以用任何类型的值作为key和value。提供了一致且富有表达力的表构造语法,使得Lua很适合描述复杂的数据;
3、函数是一等类型,支持匿名函数和正则尾递归(proper tail recursion);
4、支持词法定界(lexical scoping)和闭包(closure);
5、提供thread类型和结构化的协程(coroutine)机制,在此基础上可方便实现协作式多任务;
6、运行期能编译字符串形式的程序文本并载入虚拟机执行;
7、通过元表(Metatable)和元方法(Metamethod)提供动态元机制(dynamic Meta-mechanism),从而允许程序运行时根据需要改变或扩充语法设施的内定语义;
8、能方便地利用表和动态元机制实现基于原型(prototype-based)的面向对象模型;
9、从5.1版开始提供了完善的模块机制,从而更好地支持开发大型的应用程序;
Lua的语法类似PASCAL和Modula但更加简洁,所有的语法产生式规则(EBNF)不过才60几个。熟悉C和PASCAL的程序员一般只需半个小时便可将其完全掌握。而在语义上Lua则与Scheme极为相似,她们完全共享上述的1、3、4、6点特性, Scheme的continuation与协程也基本相同只是自由度更高。最引人注目的是,两种语言都只提供唯一一种数据结构:Lua的表和Scheme的列表(list)。正因为如此,有人甚至称Lua为“只用表的Scheme”。
库和工具
相比Java、Python、Perl,Lua的开源工具和库可能并不算多,但其中不乏优秀之作。以下介绍的资源均可在http://lua-users.org/wiki/LuaAddons上找到,而且绝大多数都遵循着与Lua相同的许可协议。
一、Kepler
Kepler是一个简单且轻量的Web开发平台(但这并不意味着只能用它来开发简单的应用),支持用Lua撰写Web程序,因此相当易学易用,并且能较方便地应用在一些资源受限的系统中。由于使用ANSI C和Lua进行开发,所以它能移植到任何支持ANSI C的平台上。
Kepler由多个Lua扩展库组成,包括CGILua、LuaSocket、LuaFileSystem、Copas、Luasql、LuaLDAP、LuaExpat、LuaXMLRPC、LuaSOAP、LuaZip、Xavante等,它们可大致分为核心库和功能支撑库两部分。其中核心是CGILua和LuaSocket,后者负责TCP/UDP sockets的操作,前者则可以创建动态页面并处理web表单上的输入数据。Kepler通过CGILua起动器(launcher)使得Web服务器能执行CGILua和Web程序并与之通信。目前的版本已经包括适合CGI、FastCGI、Apache、IIS、Tomcat、Zope的CGILua起动器,因此用Lua开发的Web程序可以在这些种类的服务器中自由迁移,只要同时安装上对应的CGILua起动器。
LuaFileSystem是对标准Lua库中文件读写功能的补充,它提供了一种可移植的方法来访问系统的目录结构和文件属性。Copas则是一个基于协程的服务调度器。Xavante是一个用Lua开发的支持HTTP 1.1的Web服务器,它直接支持CGILua而无需起动器。
其它的组件提供了sql数据库访问、XML解析、LDAP、SOAP、XMLRPC、ZIP文件操作等功能,用户如果只需要其中的某些功能,可以抽出相关组件(及其所依赖的组件)来使用。
二、wxLua
GUI是开发人员花费气力比较大的一个领域,因此简化GUI程序的编写一直是广大程序员的努力方向。随着脚本语言的兴起,将动态、灵活、易用的脚本语言引入到GUI开发中是一种非常有价值的尝试。由于复杂的GUI布局需要大量的描述信息,所以比起其它脚本来,既适合编程又适合描述数据的Lua语言在构建GUI上就具有独特的优势。
wxWidgets是一个著名的跨平台C++ GUI库,wxLua在Lua与wxWidgets之间架起了一座桥梁,通过它Lua代码几乎可以调用wxWidgets的所有功能。wxLua基本将wxWidgets的类体系映射到了Lua(基于原型)的对象模型中,这使得程序员能以基于对象或面向对象的风格来开发wxLua程序。一个Lua脚本的撰写、运行、测试和修改可以非常快速,这无疑大大提高了GUI程序的开发效率,因此wxLua非常适合快速原型的构造。另外,Lua本身以及wxWidgets良好的可移植性使得相应的Lua GUI程序在许多平台上都能顺畅地运行。
三、Pluto
虽然Lua中的表能通过表构造器以Lua代码的形式保存到文件中从而实现持久化,但当数据之间有着复杂的引用关系,并且存在循环引用、共享引用等特殊情况时,这个任务就变得相当困难与繁琐了。Pluto持久化库能够为用户解决这个难题。在它的帮助下程序员可以将任意复杂的表数据保存到特殊格式的二进制文件中以待将来恢复,库会自动处理循环引用之类的情况。
除表之外,Pluto还支持函数(确切地说是闭包)、thread的持久化,这种能力非常有意义。大家都知道程序调试中的一个基本动作就是复现bug,但很多时候bug产生的条件是非常复杂的,依赖很多因素,开发者很难精确地构建出完全一致的运行环境。而利用Pluto对函数和thread的持久化能力,我们可以把bug发生时程序的完整运行环境保存下来,今后就可凭此方便地复现bug。另外一个重要应用是游戏进度的保存。实现游戏逻辑的Lua脚本的运行状态能随时写入到文件中留待将来恢复,这使得在任何时间点保存游戏成了一件非常容易的事情。
四、LuaCOM
LuaCOM是一个支持Lua与符合COM规范的组件对象(确切一点说是自动化对象)进行交互的扩展库。所谓交互包括了两个方面,首先是允许Lua程序使用COM对象。LuaCOM支持注册在系统注册表中的COM对象的动态实例化,也支持动态访问运行中的对象。在LuaCOM的帮助下,调用COM对象方法就象调用普通Lua函数一样,存取属性也与存取表的字段类似,同时它还负责Automation数据类型与Lua数据类型的自动转换。有了这些特性,Lua程序操作COM对象就变得容易多了,再加上Lua天生的动态性,这无疑使其成了一门非常灵活的组件装配语言。
交互的另外一个方面就是支持用Lua来实现(自动化)组件对象并提供给外部客户使用。LuaCOM同时支持进程内和进程外组件,它提供了一些辅助函数来处理注册、对象实例化这类事情,从而简化了相关工作。由于LuaCOM实际上是根据Lua的表来构造一个COM对象,所以我们可以做一些非常有趣的事情:在userdata数据类型(代表不属于Lua世界的数据结构)和动态元机制的支持下,Lua能通过表访问各种各样的外部数据,包括C++对象、C结构或者CORBA对象等等;LuaCOM可以很方便地将代表这些数据的表包装成一个COM对象给外部使用,从而使得那些老迈的应用程序和库无需太多的努力便能跻身于COM世界。
五、tolua
直接用C实现某些功能,然后将相应的函数导入到Lua中是很常见的做法。不过尽管Lua提供了与C语言交互的API,但用户必需手工进行繁琐的Lua栈(用于与C交换数据)操作,而且还需注意两种语言数据类型的正确转换。难怪有人说使用Lua的C API就象在使用汇编语言一样。为了减轻程序员的负担,一些C/C++ Wrapper应运而生。
tolua本身不是一个Wrapper,但它是一个Wrapper代码自动生成器。它使用一种称为包(package)的文件来描述要导入到Lua环境中的常量、变量、函数、类和方法,这种文件按照简化了的C++头文件格式编写。使用时首先让tolua解析包文件以生成含有相应胶水代码的C/C++源文件。然后将生成的源文件编译并与那些具体实现功能的目标模块链接起来。
tolua虽然自动产生胶水代码,但需另外撰写描述文件,所以仍然不够方便。其它一些Wrapper库则利用C++模板元编程技术来自动生成合适的连接代码,从而避免了额外的描述文件,比如使用boost库的luabind。
六、LuaJIT
Lua非常高效,它运行得比许多其它脚本(如Perl、Python、Ruby)都快,这点在第三方的独立测评中得到了证实。尽管如此,仍然会有人不满足,他们总觉得“嗯,还不够快!”。LuaJIT就是一个为了再榨出一点速度的尝试,它利用JIT编译技术把Lua代码编译成本地机器码后交由cpu直接执行。LuaJIT测评报告表明,在浮点运算、循环和协程的切换等方面它的加速效果比较显著,但如果程序大量依赖C编写的函数,那么运行速度便不会有什么改进。目前LuaJIT只支持X86 cpu。
LuaJIT中包括一个名为Coco的库,用户可以单独使用它。Coco为C函数提供了真正的协程能力,用户能在C函数内部的任何一点将协程挂起,然后在将来用协程恢复操作返回到那一点。在标准Lua中,协程的挂起与恢复是不允许跨越C函数调用边界的。Coco使用了一些依赖于特定系统的特性,因此在移植程序时要特别注意。
七、ChunkSpy
Lua的虚拟机字节码指令集并非语言定义的一部分,因此官方没有提供相应的文档。用户当然可以通过查看相关源代码来获取信息,但这毕竟不方便。
ChunkSpy是一个Lua虚拟机字节码反汇编器,可以将一个二进制Lua代码块输出为非常易读的各种格式(详细或简略,带或不带源程序)的字节码汇编文件。而且它还支持交互式的反汇编,用户在敲入一行代码后立刻就能看到对应的字节码指令。ChunkSpy的作者写过一篇详细的介绍Lua5虚拟机指令的文章,名为《A No-Frills Introduction to Lua 5 VM Instructions》,你在项目主页上能找到它。这篇文章现在已经针对最新的Lua5.1做了更正。另外,他还是Yueliang项目的开发者,这个项目采用Lua语言本身来实现Lua。从项目名来看,作者应该是个华人。
八、其它
另外一些库与工具还包括LuaEDIT、LuaEclipse、VS’05LuaLangPack(它们都是IDE或IDE插件),LuaWrapper、CaLua、CPB、CppLua(Wrapper库),LuaProfiler(性能测量工具)等,读者可以在lua-user.org与luaforge.net网站上找到它们以及其它有用资源。