使用Lua編寫可嵌入式指令碼

tengrid發表於2009-05-18

使用lua編寫可嵌入式指令碼 lua提供了高階抽象,卻又沒失去與硬體的關聯
將此頁作為電子郵件傳送
); //--&gt 未顯示需要javascript的文件選項


級別:初級
martinstreicher (),首席編輯,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 --see~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=""},然後執行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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章