使用Lua编写可嵌入式脚本

http://www.voosay.com/detail/226963

使用lua编写可嵌入式脚本 lua提供了高级抽象,却又没失去与硬件的关联
将此页作为电子邮件发送
); //--&gt 未显示需要javascript的文档选项


级别:初级
martinstreicher (martin.streicher@linux-mag.com),首席编辑,linuxmagazine

2006年6月12日
虽然编译性编程语言和脚本语言各自具有自己独特的优点,但是如果我们使用这两种类型的语言来编写大型的应用程序会是什么样子呢?lua是一种嵌入式脚本语言,它非常小,速度很快,功能却非常强大。在创建其他配置文件或资源格式(以及与之对应的解析器)之前,请尝试一下lua。 尽管诸如perl、python、php和ruby之类的解释性编程语言日益被web应用程序广泛地采纳——它们已经长期用来实现自动化系统管理任务——但是诸如c、c++之类的编译性编程语言依然是必需的。编译性编程语言的性能是脚本语言所无法企及的(只有手工调优的汇编程序的性能才能超过它),有些软件——包括操作系统和设备驱动程序——只能使用编译代码来高效地实现。实际上,当软件和硬件需要进行无缝地连接操作时,程序员本能地就会选择c编译器:c非常基础,距离“原始金属材料非常近”——即可以操作硬件的很多特性——并且c的表现力非常强大,可以提供高级编程结构,例如结构、循环、命名变量和作用域。
然而,脚本语言也有自己独特的优点。例如,当某种语言的解释器被成功移植到一种平台上以后,使用这种语言编写的大量脚本就可以不加任何修改在这种新平台上运行——它们没有诸如系统特定的函数库之类的依赖限制。(我们可以考虑一下microsoft?windows?操作系统上的许多dll文件和unix?及linux?上的很多libcs)。另外,脚本语言通常都还会提供高级编程构造和便利的操作,程序员可以使用这些功能来提高生产效率和灵活性。另外,使用解释语言来编程的程序员工作的速度更快,因为这不需要编译和链接的步骤。c及其类似语言中的“编码、编译、链接、运行”周期缩减成了更为紧凑的“编写脚本、运行”。
lua新特性
与其他脚本语言一样,lua也有自己的一些特性:
lua类型。在lua中,值可以有类型,但是变量的类型都是动态决定的。nil、布尔型、数字和字符串类型的工作方式与我们期望的一样。 nil是值为nil的一种特殊类型,用来表示没有值。布尔型的值可以是true和false常量。(nil也可以表示false,任何非nil的值都表示true。) lua中所有的数字都是双精度的(不过我们可以非常简便地编写一些代码来实现其他数字类型)。字符串是定长字符数组。(因此,要在一个字符串后面附加上字符,必须对其进行拷贝。) 表、函数和线程类型都是引用。每个都可以赋值给一个变量,作为参数传递,或作为返回值从函数中返回。例如,下面是一个存储函数的例子:

--exampleofananonymousfunction --returnedasavalue --seehttp://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf functionadd(x) returnfunction(y)return(x+y)end end f=add(2) print(type(f),f(10)) function12
lua线程。线程是通过调用内嵌函数coroutine.create(f)创建的一个协同例程(co-routine),其中f是一个lua函数。线程不会在创建时启动;相反,它是在创建之后使用coroutine.resume(t)启动的,其中t就是一个线程。每个协同例程都必须使用coroutine.yield()偶尔获得其他协同例程的处理器。 赋值语句。lua允许使用多种赋值语句,可以先对表达式进行求值,然后再进行赋值。例如,下面的语句:

i=3 a={1,3,5,7,9} i,a,a[i+1],b=i+1,a[i+1],a print(i,a[3],a[4],b,i)
会生成475nilnil。如果变量列表的个数大于值列表的个数,那么多出的变量都被赋值为nil;因此,b就是nil。如果值的个数多于变量的个数,那么多出的值部分就会简单地丢弃。在lua中,变量名是大小写敏感的,这可以解释为什么i的值是nil。 块(chunk)。 块可以是任何lua语句序列。块可以保存到文件中,或者保存到lua程序中的字符串中。每个块都是作为一个匿名函数体来执行的。因此,块可以定义局部变量和返回值。 更酷的东西。lua具有一个标记-清理垃圾收集器。在lua5.1中,垃圾收集器是以增量方式工作的。lua具有完整的词法闭包(这与scheme类似,而与python不同)。lua具有可靠的尾部调用语义(同样,这也与scheme类似,而与python不同)。 在programminginlua和lua-userswiki(链接请参见后面的参考资料部分)中可以找到更多lua代码的例子。
在所有的工程任务中,要在编译性语言和解释性语言之间作出选择,就意味着要在这种环境中对每种语言的优缺点、权重和折中进行评测,并接受所带来的风险。




回页首

在两个世界之间最好地进行混合
如果您希望充分利用这两个世界的优点,应该怎样办呢,是选择最好的性能还是选择高级强大的抽象?更进一步说,如果我们希望对处理器密集且依赖于系统的算法和函数以及与系统无关且很容易根据需要而进行修改的单独逻辑进行优化,那又当如何呢?
对高性能代码和高级编程的需要进行平衡是lua(一种可嵌入式脚本语言)要解决的问题。在需要时我们可以使用编译后的代码来实现底层的功能,然后调用lua脚本来操作复杂的数据。由于lua脚本是与编译代码独立的,因此我们可以单独修改这些脚本。使用lua,开发周期就非常类似于“编码、编译、运行、编写脚本、编写脚本、编写脚本...”。
例如,luaweb站点“使用”页面(请参见参考资料)列出了主流市场上的几个计算机游戏,包括worldofwarcraft和(家用版的)defender,它们集成lua来实现很多东西,从用户界面到敌人的人工智能都可以。lua的其他应用程序包括流行的linux软件更新工具apt-rpm的扩展机制,还有“crazyivan”robocup2000冠军联赛的控制逻辑。这个页面上的很多推荐感言都对lua的小巧与杰出性能赞不绝口。




回页首

开始使用lua
lua5.0.2版本是撰写本文时的最新版本,不过最近刚刚发布了5.1版本。您可以从lua.org上下载lua的源代码,在lua-userswiki(链接请参见参考资料)上可以找到预先编译好的二进制文件。完整的lua5.0.2核心文件中包括了标准库和lua编译器,不过只有200kb大小。
如果您使用的是debianlinux,那么可以以超级用户的身份运行下面的命令来快速安装lua5.0:
#apt-getinstalllua50
本文中给出的例子都是在debianlinuxsarge上运行的,使用的是lua5.0.2和2.4.27-2-686版本的linux内核。
在系统上安装好lua之后,我们可以首先来试用一下单独的lua解释器。(所有的lua应用程序必须要嵌入到宿主应用程序中。解释器只是一种特殊类型的宿主,对于开发和调试工作来说非常有用。)创建一个名为factorial.lua的文件,然后输入下面的代码:
--definesafactorialfunction functionfact(n) ifn==0then return1 else returnn*fact(n-1) end end print("enteranumber:") a=io.read("*number") print(fact(a))
factorial.lua中的代码——更确切地说是任何lua语句序列——都称为一个块,这在上面的lua特性中已经进行了介绍。要执行刚才创建的代码块,请运行命令luafactorial.lua:
$luafactorial.lua enteranumber: 10 3628800
或者像在其他解释性语言中一样,我们可以在代码顶部添加一行“标识符”(#!),使这个脚本变成可执行的,然后像单独命令一样来运行这个文件:
$(echo#!/usr/bin/lua;catfactorial.lua)>factorial $chmodu+xfactorial $./factorial enteranumber: 4 24




回页首

lua语言
lua具有现代脚本语言中的很多便利:作用域,控制结构,迭代器,以及一组用来处理字符串、产生及收集数据和执行数学计算操作的标准库。在lua5.0referencemanual中有对lua语言的完整介绍(请参见参考资料)。
在lua中,只有值具有类型,而变量的类型是动态决定的。lua中的基本类型(值)有8种:nil,布尔型,数字,字符串,函数,线程,表以及用户数据。前6种类型基本上是自描述的(例外情况请参见上面的lua特性一节);最后两个需要一点解释。
lua表
在lua中,表是用来保存所有数据的结构。实际上,表是lua中惟一的数据结构。我们可以将表作为数组、字典(也称为散列或联合数组)、树、记录,等等。
与其他编程语言不同,lua表的概念不需要是异构的:表可以包含任何类型的组合,也可以包含类数组元素和类字典元素的混合体。另外,任何lua值——包括函数或其他表——都可以用作字典元素的键值。
要对表进行浏览,请启动lua解释器,并输入清单1中的黑体显示的代码。

清单1.体验lua表
$lua >--createanemptytableandaddsomeelements >t1={} >t1[1]="moustache" >t1[2]=3 >t1["brothers"]=true >--morecommonly,createthetableanddefineelements >allatonce >t2={[1]="groucho",[3]="chico",[5]="harpo"} >t3={[t1[1]]=t2[1],accent=t2[3],horn=t2[5]} >t4={} >t4[t3]="themarxbrothers" >t5={characters=t2,marks=t3} >t6={["anightattheopera"]="classic"} >--makeareferenceandastring >i=t3 >s="anightattheopera" >--indicescanbeanyluavalue >print(t1[1],t4[t3],t6) moustachethemarxbrothersclassic >--thephrasetable.stringisthesameastable["string"] >print(t3.horn,t3["horn"]) harpoharpo >--indicescanalsobe"multi-dimensional" >print(t5["marks"]["horn"],t5.marks.horn) harpoharpo >--ipointstothesametableast3 >=t4 themarxbrothers >--non-existentindicesreturnnilvalues >print(t1[2],t2[2],t5.films) nilnilnil >--evenafunctioncanbeakey >t={} >functiont.add(i,j) >>return(i+j) >>end >print(t.add(1,2)) 3 >print(t[add](1,2)) 3 >--andanothervariationofafunctionasakey >t={} >functionv(x) >>print(x) >>end >t[v]="thebigstore" >forkey,valueintdokey(value)end thebigstore
正如我们可能期望的一样,lua还提供了很多迭代器函数来对表进行处理。全局变量table提供了这些函数(是的,lua包就是表)。有些函数,例如table.foreachi(),会期望一个从1(数字1)开始的连续整数范围:
>table.foreachi(t1,print) 1moustache 23
另外一些函数,例如table.foreach(),会对整个表进行迭代:
>table.foreach(t2,print) 1groucho 3chico 5harpo >table.foreach(t1,print) 1moustache 23 brotherstrue
尽管有些迭代器对整数索引进行了优化,但是所有迭代器都只简单地处理(key,value)对。
现在我们可以创建一个表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之间进行共享。正如lua5.0referencemanual所说,userdata类型允许我们在lua变量中保存任意的c数据。我们可以认为userdata就是一个字节数组——字节可以表示指针、结构或宿主应用程序中的文件。
用户数据的内容源自于c,因此在lua中不能对其进行修改。当然,由于用户数据源自于c,因此在lua中也没有对用户数据预定义操作。不过我们可以使用另外一种lua机制来创建对userdata进行处理的操作,这种机制称为元表(metatable)。
元表
由于表和用户数据都非常灵活,因此lua允许我们重载这两种类型的数据的操作(不能重载其他6种类型)。元表是一个(普通的)lua表,它将标准操作映射成我们提供的函数。元表的键值称为事件;值(换而言之就是函数)称为元方法。
函数setmetatable()和getmetatable()分别对对象的元表进行修改和查询。每个表和userdada对象都可以具有自己的元表。
例如,添加操作对应的事件是__add。我们可以推断这段代码所做的事情么?
--overloadtheaddoperation --todostringconcatenation -- mt={} functionstring(string) returnsetmetatable({value=stringor},mt) end --thefirstoperandisastringtable --thesecondoperandisastring --..istheluaconcatenateoperator -- functionmt.__add(a,b) returnstring(a.value..b) end s=string(hello) print((s+there+world!).value)
这段代码会产生下面的文本:
hellothereworld!
函数string()接收一个字符串string,将其封装到一个表({value=sor})中,并将元表mt赋值给这个表。函数mt.__add()是一个元方法,它将字符串b添加到在a.value中找到的字符串后面b次。这行代码print((s+there+world!).value)调用这个元方法两次。
__index是另外一个事件。__index的元方法每当表中不存在键值时就会被调用。下面是一个例子,它记住(memoize)函数的值:
--codecourtesyofricilake,rici@ricilake.net functionmemoize(func,t) returnsetmetatable( tor{}, {__index= function(t,k) localv=func(k); t[k]=v; returnv; end } ) end colors={"red","blue","green","yellow","black"} color=memoize( function(node) returncolors[math.random(1,table.getn(colors))] end )
将这段代码放到lua解释器中,然后输入print(color[1],color[2],color[1])。您将会看到类似于blueblackblue的内容。
这段代码接收一个键值node,查找node指定的颜色。如果这种颜色不存在,代码就会给node赋一个新的随机选择的颜色。否则,就返回赋给node的颜色。在前一种情况中,__index元方法被执行一次以分配一个颜色。后一种情况比较简单,所执行的是快速散列查找。
lua语言提供了很多其他功能强大的特性,所有这些特性都有很好的文档进行介绍。在碰到问题或希望与专家进行交谈时,请连接luauserschatroomircchannel(请参见参考资料)获得非常热心的支持。




回页首

嵌入和扩展
除了语法简单并且具有功能强大的表结构之外,lua的强大功能使其可以与宿主语言混合使用。由于lua与宿主语言的关系非常密切,因此lua脚本可以对宿主语言的功能进行扩充。但是这种融合是双赢的:宿主语言同时也可以对lua进行扩充。举例来说,c函数可以调用lua函数,反之亦然。
lua与宿主语言之间的这种共生关系的核心是宿主语言是一个虚拟堆栈。虚拟堆栈与实际堆栈类似,是一种后进先出(lifo)的数据结构,可以用来临时存储函数参数和函数结果。要从lua中调用宿主语言的函数(反之亦然),调用者会将一些值压入堆栈中,并调用目标函数;被调用的函数会弹出这些参数(当然要对类型和每个参数的值进行验证),对数据进行处理,然后将结果放入堆栈中。当控制返回给调用程序时,调用程序就可以从堆栈中提取出返回值。
实际上在lua中使用的所有的c应用程序编程接口(api)都是通过堆栈来进行操作的。堆栈可以保存lua的值,不过值的类型必须是调用程序和被调用者都知道的,特别是向堆栈中压入的值和从堆栈中弹出的值更是如此(例如lua_pushnil()和lua_pushnumber()。
清单2给出了一个简单的c程序(节选自参考资料中programminginlua一书的第24章),它实现了一个很小但却功能完善的lua解释器。

清单2.一个简单的lua解释器
1#include 2#include 3#include 4#include 5 6intmain(void){ 7charbuff[256]; 8interror; 9lua_state*l=lua_open();/*openslua*/ 10luaopen_base(l);/*opensthebasiclibrary*/ 11luaopen_table(l);/*opensthetablelibrary*/ 12luaopen_io(l);/*opensthei/olibrary*/ 13luaopen_string(l);/*opensthestringlib.*/ 14luaopen_math(l);/*opensthemathlib.*/ 15 16while(fgets(buff,sizeof(buff),stdin)!=null){ 17error=lual_loadbuffer(l,buff,strlen(buff),"line")|| 18lua_pcall(l,0,0,0); 19if(error){ 20fprintf(stderr,"%s",lua_tostring(l,-1)); 21lua_pop(l,1);/*poperrormessagefromthestack*/ 22} 23} 24 25lua_close(l); 26return0; 27}
第2行到第4行包括了lua的标准函数,几个在所有lua库中都会使用的方便函数以及用来打开库的函数。第9行创建了一个lua状态。所有的状态最初都是空的;我们可以使用luaopen_...()将函数库添加到状态中,如第10行到第14行所示。
第17行和lual_loadbuffer()会从stdin中以块的形式接收输入,并对其进行编译,然后将其放入虚拟堆栈中。第18行从堆栈中弹出数据并执行之。如果在执行时出现了错误,就向堆栈中压入一个lua字符串。第20行访问栈顶(栈顶的索引为-1)作为lua字符串,打印消息,然后从堆栈中删除该值。
使用capi,我们的应用程序也可以进入lua状态来提取信息。下面的代码片段从lua状态中提取两个全局变量:
.. if(lual_loadfile(l,filename)||lua_pcall(l,0,0,0)) error(l,"cannotrunconfigurationfile:%s",lua_tostring(l,-1)); lua_getglobal(l,"width"); lua_getglobal(l,"height"); .. width=(int)lua_tonumber(l,-2); height=(int)lua_tonumber(l,-1); ..
请再次注意传输是通过堆栈进行的。从c中调用任何lua函数与这段代码类似:使用lua_getglobal()来获得函数,将参数压入堆栈,调用lua_pcall(),然后处理结果。如果lua函数返回n个值,那么第一个值的位置在堆栈的-n处,最后一个值在堆栈中的位置是-1。
反之,在lua中调用c函数也与之类似。如果您的操作系统支持动态加载,那么lua可以根据需要来动态加载并调用函数。(在必须使用静态加载的操作系统中,可以对lua引擎进行扩充,此时调用c函数时需要重新编译lua。)




回页首

结束语
lua是一种学习起来容易得难以置信的语言,但是它简单的语法却掩饰不了其强大的功能:这种语言支持对象(这与perl类似),元表使表类型具有相当程度的可伸展性,capi允许我们在脚本和宿主语言之间进行更好的集成和扩充。lua可以在c、c++、c#、java?和python语言中使用。
在创建另外一个配置文件或资源格式(以及相应的处理程序)之前,请尝试一下lua。lua语言及其社区非常健壮,具有创新精神,随时准备好提供帮助。




回页首

参考资料
学习
您可以参阅本文在developerworks全球站点上的英文原文。

referencemanualforlua5.0 介绍了lua语言的完整知识。

robertoierusalimschy所著的programminginlua(robertoierusalimschy,2003年)是学习如何使用lua有效地进行编码的非常有用的资源。

请参阅lua-userswiki中的libraryoflua教程,学习如何使用lua的特性。

请参阅使用lua的项目列表。

在developerworkslinux专区可以找到为linux开发人员准备的更多资源。

随时关注developerworks技术事件和网络广播。
获得产品和技术
下载lua5.0.2或lua5.1源代码,从头开始编译lua。

在lua-userswiki上,浏览很多预先构建的、可安装的lua二进制文件。

在luaforge上可以找到大量的代码库,包括很多语言绑定和专门的计算库。

订购免费的sekforlinux ,这有两张dvd,包括最新的ibmforlinux试用软件,包括db2?、lotus?、rational?、tivoli?和websphere?。

在您的下一个开发项目中采用ibm试用软件,这可以从developerworks直接下载。


讨论
通过参与developerworksblogs加入developerworks社区。







关于作者

martinstreicher是linuxmagazine的首席编辑。他在普渡大学获得了计算机硕士学位,自1982年以来,就一直在使用pascal、c、perl、java以及(最近使用的)ruby编程语言编写类unix系统。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/94384/viewspace-600350/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/94384/viewspace-600350/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值