(接上篇)
程序員
--------------------------------------
實現
--------------------------------------
擴展語言老是由應用程序以某種方式解釋執行的。簡單的擴展語言能夠直接從源代碼進行解釋執行。另外一方面,嵌入式語言一般是強大的編程語言,具備複雜的語法和語義。一個更有效的嵌入式語言實現技術是設計適合語言需求的虛擬機,編譯擴展程序成虛擬機的字節碼,而後經過解釋執行字節碼來模擬虛擬機(Betz 1988, 1991; Franks 1991)。咱們選擇這種混合架構來實現Lua;和直接執行源代碼相比,它擁有以下優勢:
由於詞法和語法解析只進行一次,可能在實際嵌入以前使用外部解析器,識別簡單的早期錯誤,得到更短的開發週期和更快的執行速度;
若是使用一個外部編譯器時,能夠只提供字節碼形式的擴展程序,也就是預編譯,從而可使加載更快,環境更安全,運行時更小(不過,鏈接幾個預編譯的擴展程序多是一項艱鉅的任務)。
這種架構率先用於 Smalltalk(Goldberg–Robson 1983; Budd 1987)(字節碼就是從它那裏借來的術語),也成功用於基於P碼(Clark–Koehler 1982)的 UCSD 的 Pascal 系統。在這些系統中,字節碼虛擬機被用來減小複雜性並提升可移植性。這個方法也用於移植 BCPL 編譯器(Richards–Whitby-Strevens 1980)。
擴展程序的編譯器代碼可使用標準工具生成,如 lex 和 yacc (Levine–Mason–Brown 1992)。構造編譯器的好工具的存在,並在七十年代末被普遍使用是當時小語言的萌發的主要緣由,特別是在Unix環境裏。咱們實現 Lua 時使用 YACC 進行語法分析。最初,咱們使用的 lex 寫的詞法分析器。經過對生產程序進行性能分析,咱們發現,這個模塊佔用了差很少一半的加載和執行程序的時間。而後,咱們直接用 C 重寫了這個模塊;新的詞法分析器的速度超過舊的兩倍多。
-------------------
Lua 的虛擬機
-------------------
Lua 中使用的虛擬機是一個堆棧機。這意味着它不具備隨機存取存儲器:全部的臨時值和局部變量保存在棧裏。此外,它不具備通用寄存器,只有幾個特殊的控制寄存器來控制堆棧和程序的執行。這些寄存器棧底,棧頂和程序計數器(base of stack, top of stack and program counter)。
虛擬機的程序是指令序列,稱爲字節碼。程序的執行是經過解釋字節碼實現的,每一次指令操做都在棧頂進行。例如,語句
a = b + f(c)
被編譯爲:
PUSHGLOBAL "b"
PUSHGLOBAL "f"
PUSHMARK
PUSHGLOBAL "c"
CALLFUNC
ADJUST 2
ADD
STOREGLOBAL "a"
Lua的虛擬機有大約有 60 條指令;相應地,可以使用 8 位的字節碼進行表示。許多指令(例如,ADD)不須要額外的參數;這些指令直接在棧上運行,而且編譯後的代碼只佔用一個字節。其餘指令(例如,PUSHGLOBAL 和 STOREGLOBAL)須要額外的參數,須要超過一個字節的佔用。由於參數能夠採用一個,兩個或四個字節,這在某些體系架構上形成了字節對齊問題,不過能夠經過填充空(NOP)指令來解決邊界對齊的問題。
許多指令只是爲了優化而存在。例如,有一種 PUSH 指令,須要一個數字做爲參數並將其壓棧,但也有單字節優化版本用於經常使用值的壓棧,例如 0 和 1。所以,咱們有 PUSHNIL,PUSH0,PUSH2,PUSH3。這樣的優化同時減小了字節碼的空間佔用,和指令執行的時間佔用。
回想一下,Lua 支持多重賦值和多個返回值。因此,有時候,值列表必須在運行時調整到給定長度:若是實際值多於所需,那麼多餘的值會被扔掉;若是須要的值多於實際的,根據須要在列表中進行 nil 擴展。調整經過 ADJUST 指令在棧上完成。
儘管多重賦值和多重返回是 Lua 中的一個強大的功能,便它們同進也是編譯器和解釋器複雜度的一個重要來源。由於函數沒有類型聲明,編譯器不知道函數會返回多少值。所以,調整必須在運行時完成。一樣,編譯器不知道函數使用多少參數。由於這個數字在運行時可能會有所不一樣,在 PUSHMARK 和 CALLFUNC 指令之間參數列表中是相等的。
一種擴展 Lua 使用由主機提供的函數的方法是將每一個這樣的函數賦值給字節碼作爲指令(Betz 1988)。雖然這種策略將簡化解釋器,但它的缺點是隻有少於 200 個的外部函數能夠添加,由於 Lua 中只有 8 位字節碼,而且 Lua 本身已經使用了其中的 60 個作爲根本指令。因此咱們選擇了宿主顯式註冊外部函數而且
像對待原生的Lua函數同樣處理這些外部函數。所以,單一 CALLFUNC 指令就足夠了;解釋器根據被調用的函數類型決定該作什麼。
一個至關不一樣的策略由 Franks 提出(1991):宿主中的全部外部函數能夠被嵌入語言調用;不須要進行顯式註冊。這是經過閱讀和解釋由連接器生成的符號表來完成。該解決方案對應用程序員來講是很方便的,可是是不可移植的,依賴於符號表文件的格式和所使用操做系統的重定位策略(Franks 使用了一個特定的 DOS 編譯器)。
-------------------
內部數據結構
-------------------
正如前面所提到的,Lua 的變量沒有類型;值纔有。所以,值由擁有兩個字段的結構體(struct)實現:一個類型(type)和一個包含實際值的聯合體(union)。這些結構出如今棧和符號表中,符號表(symbol table)持有全部的全局符號。
數值直接存儲到聯合體中。字符串保存在一個數組中;字符串(string)類型的值是指向該數組指針。函數類型的值是指向字節碼數組的指針。類型 Cfunction 值是實際指向宿主程序中 C 函數的指針,用戶數據類型(userdata) 的值與之相似。
表(table)被實現爲哈希表,由單獨的連接處理哈希碰撞(這也就是爲何一個表中索引是任意的緣由)。若是在建立表時給出了它的尺寸(size),那麼該尺寸就被看成哈希表的大小來用。所以,經過給哈希表提供一個近似等於表中元素數目的尺寸,會減小一些哈希碰撞,從而獲得更高效的索引位置。此外,若是表做爲數組來用,也就是隻有數值下標,在建立表時選擇合適的尺寸能夠作主保證沒有哈希碰撞。
全部的 Lua 內部數據結構都是動態分配的數組。當這些數組中沒有更多的空位置(free slots)時,會自動執行垃圾回收,Lua 的垃圾回覆算法用的是標記-清除(mark-and-sweep)算法。若是沒有空間被回收(因爲全部的值都被引用中),則數組會從新分配,尺寸擴大一倍。
垃圾回收爲程序員提供了便利,由於它避免了顯式的內存管理。當 Lua 做爲一個獨立的語言(它常常是)來使用時,垃圾回收是頗有價值的。然而,當 Lua 嵌入到宿主程序(這是它的主要目的)中使用時,垃圾回收給與 Lua 進行交互的應用程序員帶來了新的煩惱:應注意不要把 Lua 中的表和字符串存儲到 C 語言變量中,由於這些值可能在垃圾回收過程當中被回收,若是在 Lua 中他們沒有其它引用的話。更確切地說,程序員必須在控制返回到 Lua 以前明確拷貝這些值到 C 變量中。雖然這是一個不一樣的模式,可是它至少不比使用標準 C 語言庫中的 malloc-free 協議的內存管理差。
--------------------------------------
結論
--------------------------------------
Lua 自 93 年中被普遍應用於生產中,執行如下任務:
應用程序中用戶的配置;
通用數據錄入,使用用戶定義的確認程序;
用戶界面的描述;
應用程序對象的編程說明;
存儲結構化的圖形圖元文件,用於圖形編輯器和應用程序之間的通訊。
此外,Lua 是目前正在考慮的可一個視化編程系統的基礎。
對用戶和開發人員來講,在運行時加載並執行 Lua 程序使配置變得很簡單。此外,通用的嵌入式語言的存在下降了語言的不兼容,並鼓勵更好的設計,把應用程序的配置問題和應用程序其它的主要問題清楚的分割開來。
本文中所描述的 Lua 實現能夠從匿名的 ftp 中下載:http://www.lua.org/ftp/lua-1.1.tar.gz
-------------------
致謝
-------------------
感謝在 ICAD 和 TeCGraf 工做的全體員工使用和測試 Lua。文中提到正在開發中的工業應用和 PETROBRAS (CENPES) 和 ELETROBRAS (CEPEL) 的研究中心是合做夥伴關係。
--------------------------------------
參考文獻
--------------------------------------
M. Abrash, D. Illowsky, "Roll your own minilanguages with mini-interpreters", Dr. Dobb's Journal 14 (9) (Sep 1989) 52–72.
A. V. Aho, B. W. Kerninghan, P. J. Weinberger, The AWK programming language, Addison-Wesley, 1988.
B. Beckman, "A Scheme for little languages in interactive graphics", Software, Practice & Experience 21 (1991) 187–207.
J. Bentley, "Programming pearls: little languages", Communications of the ACM 29 (1986) 711–721.
J. Bentley, More programming pearls, Addison-Wesley, 1988.
D. Betz, "Embedded languages", Byte 13 #12 (Nov 1988) 409–416.
D. Betz, "Your own tiny object-oriented language", Dr. Dobb's Journal 16 (9) (Sep 1991) 26–33.
T. Budd, A Little Smalltalk, Addison-Wesley, 1987.
R. Clark, S. Koehler, The UCSD Pascal handbook: a reference and guidebook for programmers, Prentice-Hall, 1982.
M. Cowlishaw, The REXX programming language, Prentice-Hall, 1990.
L. H. de Figueiredo, C. S. de Souza, M. Gattass, L. C. G. Coelho, "Geração de interfaces para captura de dados sobre desenhos", Anais do SIBGRAPI V (1992) 169–175 [in Portuguese].
N. Franks, "Adding an extension language to your software", Dr. Dobb's Journal 16 (9) (Sep 1991) 34–43.
A. Goldberg, D. Robson, Smalltalk-80: the language and its implementation, Addison-Wesley, 1983.
R. Ierusalimschy, L. H. de Figueiredo, W. Celes Filho, "Reference manual of the programming language Lua", Monografias em Ciência da Computação 4/94, Departamento de Informática, PUC-Rio, 1994.
J. R. Levine, T. Mason, D. Brown, Lex & Yacc, O'Reilly and Associates, 1992.
C. Nahaboo, A catalog of embedded languages, available from colas@indri.inria.fr.
M. Richards, C. Whitby-Strevens, BCPL: the language and its compiler, Cambridge University Press, 1980.
B. Ryan, "Scripts unbounded", Byte 15 (8) (Aug 1990) 235–240.
R. Valdés, "Little languages, big questions", Dr. Dobb's Journal 16 (9) (Sep 1991) 16–25.算法