Lua1.1 Lua 的設計和實現 (一)

說明:
這個文檔是 Lua1.1 的 doc 目錄裏的 lua.ps 文件。
同時這個文檔能夠這裏找到:http://www.lua.org/semish94.html
原文版權歸原做者全部,這篇翻譯只是做爲學習之用。若是翻譯有不當之處,請參考原文。
--------------------如下是正文------------------
應用程序擴展語言的設計和實現

摘要。咱們描述 Lua 的設計和實現,一個簡單而強大的應用程序擴展語言。儘管Lua是一種程序語言,它有數據描述能力,並已普遍應用於幾個生產任務,包括用戶配置,通用的數據輸入,用戶界面的描述,描述應用程序對象和存儲結構化圖形元文件。

--------------------------------------
簡介
--------------------------------------
有愈來愈多的定製應用程序的需求。隨着應用程序變得更加複雜,用簡單參數來定製變得不太可能:用戶如今想要在程序運行時決定採用哪一種配置;用戶還想本身寫宏和腳本,以提升生產率(Ryan 1990)。所以,現在規模大點兒的應用程序幾乎老是爲終端用戶提供配置或腳本語言以作擴展之用。這些語言一般是簡單的,但每一種都有它本身的語法。所以,用戶必須爲每種應該程序學會一門新的語言(開發人員也不得不爲每種應用程序設計、實現和調試一門新的語言)。

咱們第一個專有腳本語言經驗來自於一個數據輸入應用程序,咱們爲這個程序設計了一個很簡單的聲明式語言(Figueiredo–Souza–Gattass–Coelho 1992)。(數據輸入領域對於用戶自定義的動做有迫切須要,由於事先編碼的確認測試不可能覆蓋全部應用程序)。當用戶要求爲這門語言添加愈來愈多功能的時候,咱們認定須要一個更通用的語言並開始設計一個通用的嵌入式語言。與此同時,另外一個聲明式語言被添加到一個不一樣的應用程序中以作數據描述之用。所以,咱們決定將這兩種語言合二爲一,併爲 Lua 程序語言添加了數據描述功能。Lua 已經超越了本來的目的並被用於其餘工業項目。

本文檔介紹 Lua 的設計決策及實現細節。

--------------------------------------
擴展語言
--------------------------------------
如今認爲應用程序擴展語言的使用是一個重要的設計技術:它使應用程序的設計變得更清晰,併爲用戶提供個性化配置。因爲大多數擴展語言是簡單,專門針對某個任務的,他們被稱爲「小語言「(Bentley 1986; Valdés 1991)。與之對應的是編寫應用程序的"大"語言,主流語言。現在這種區別並不明顯,由於實際上多個應用程序的主要部分就是由擴展語言寫成的。擴展語言有幾種:
配置語言:參數選擇,一般實現爲命令行參數列表或從配置文件中讀取鍵值對(例如:DOS 的 config.sys, MS-Windows 的 .ini文件, X11 的資源文件, Motif 的 UIL 文件);
腳本語言:自動化任務,有限的流程控制,如用於 DOS 的批處理文件或各類 Unix shell;
宏語言:也爲自動化任務,但一般只做爲一系列順序執行的基本操做,沒有流程控制;
嵌入式語言:基於應用程序提供的接口,用戶能夠自已定製函數來擴展應用程序。這些語言一般是被簡化的主流編程語言如 LISP 和 C的變體,具備很強大的功能。

嵌入式語言不一樣於獨立的語言之處是嵌入式語言只能嵌入到宿主才能工做,由於被稱爲嵌入式編程。此外,宿主程序一般爲嵌入式語言提供某特定領域的擴展,經過提供更高層次的抽象,建立一個特定版本的嵌入式語言。爲此,一個嵌入式語言既有它本身的編程語法又有與宿主交互的 API。所以,不像那樣用來給宿主提供參數值或一系列順序操做的簡單擴展語言,嵌入式語言和宿主程序之間有一個雙向的通訊。注意,應用程序員與嵌入式語言交互使用的是編寫宿主程序的主流編程語言,而終端用戶與與應用程序交互則只使用嵌入式語言。

LISP 一般是擴展語言的一個受歡迎的選擇,因其簡單,容易解析的語法和內置的可擴展性(Beckman 1991; Nahaboo)。舉例來講,Emacs 的主要部分事實上就是由它本身的 LISP 變體寫成;其餘文本編輯器遵循一樣的選擇。然而,在用於定製時,LISP 不能稱爲對用戶友好。C 和 shell 也一樣不能稱爲對用戶友好,後者的語言甚至更復雜和不常見。

設計 Lua 時的一個基本觀點 是它應該有清楚但常見的語法:咱們很快爲它選擇了一個簡化了的類 Pascal 語法。 咱們避免選擇基於 LISP 或者 C 的語法,由於它可能使非程序員用戶望而卻步。所以,Lua 首先是一種程序語言。然而,如前所述,Lua 所具備的數據描述能力增長了它的表達能力。

--------------------------------------
概念
--------------------------------------
Lua 是一種通用的嵌入式編程語言,支持過程式編程與數據描述功能。作爲一個嵌入式語言,Lua 沒有 "main" 函數的概念;它只能嵌入到宿主(Lua 作爲 C 函數庫提供,與宿主應用程序連接)運行。宿主能夠執行一段 Lua 代碼,能夠讀寫 Lua 變量,能夠註冊被 Lua 代碼調用的 C 函數。經過註冊 C 函數,Lua 能夠擴展本身以應對不一樣的領域,從而建立可定製、共享語法框架的編程語言(Beckman 1991)。

本節包含一個 Lua 主要概念的簡介。包括一些實際的例子代碼,以領略該語言的風格。語言的精肯定義可見語言參考手冊(Ierusalimschy–Figueiredo–Celes 1994)。

-------------------
語法
-------------------
如前所述,咱們明確設計的 Lua 有一個簡單,常見的語法。Lua 用隱式但明確結束的塊結構支持一組常見的語句。簡單的語句包括簡單的賦值;控制結構如 while-do-end, repeat-until, if-then-elseif-else-end;函數調用。很是見的語句包括多重賦值;局部變量聲明,局部變量能夠放在塊內的任何地方;表構造函數,能夠包括用戶自定義的驗證函數(見下文)。此外,Lua 函數能夠有數量可變的參數,能夠返回多個值。這就避免了當須要返回多個結果時經過引用傳遞參數。

-------------------
環境和模塊
-------------------
Lua 中全部的語句都在一個全局環境中執行。這個全局環境持有全部的全局變量和函數,在嵌入語言一開始執行時進行初始化,並持續到結束。這個全局環境能夠用 Lua 代碼或者嵌入程序來管理,能夠經過 Lua 的實現庫來讀寫全局變量。

Lua 的執行單元叫作模塊。一個模塊能夠包含語句和函數定義,能夠在一個文件中或者在一個宿主程序的字符串中。當執行一個模塊,首先它全部的函數和語句被編譯,函數被添加到全局環境,而後語句按順序執行。模塊對於全局環境的全部修改是持久的,這些修改在模塊結束後依然可見。修改包括全局變量的修改和新函數的定義(一個函數的定義事實上就是對於一個全局變量的賦值。見下文)

-------------------
數據類型和變量
-------------------
Lua 是動態類型語言:變量沒有類型;只有值有類型。全部值含有本身的類型。全部,Lua 語言中沒有類型定義。沒有變量類型,看起來沒有什麼,事實上是簡化語言的一個重要因素;不少有類型的語言在做爲擴展語言進行裁剪時,也常常會把它作爲一個重要特性。此外,Lua 有垃圾回收。它跟蹤哪些值被使用並丟棄那些不被使用的。這避免了顯式內存分配的須要,編程錯誤的主要來源。Lua 中有七種基本的數據類型的:
    nil: 單個值類型 nil;
    number: 浮點數;
    string: 字符數組;
    function: 用戶定義的函數;
    Cfunction: 宿主程序提供的函數;
    userdata: 宿主數據指針;
    table: 關聯數組.

Lua 提供了一些自動類型轉換。 若是可能的話,一個字符串參與數值運算時被轉換爲數值型。相反,當一個數值被用於本該是字符串的地方的時候,數值被轉換爲字符串。這是轉換是有用的,由於它簡化編程而且避免了顯式轉換函數的須要。

全局變量不須要聲明,局部變量才須要。任何變量被假定爲是全局的除非顯式聲明爲 local 局部變量。局部變量聲明能夠放在一個塊內的任何地方。由於只有局部變量須要聲明,能夠把變量的聲明放在接近它被使用的地方。所以一般但是簡單的判斷出一個給定的變量是局部的或全局的。

在第一次賦值以前,變量的值爲 nil。所以,Lua 中沒有未初始化的變量,編程錯誤的另外一個主要來源。然而,nil 惟一有效的操做是賦值和相等測試(nil 的主要屬性是不一樣於任何其餘值)。所以,在本該使用一個「實際」的值的時候使用了「未初始化" 的變量(例如,一個算術表達式)會致使執行錯誤,提醒程序員沒有正確地初始化變量。所以,自動地初始化變量爲 nil 的目的不是爲了鼓勵程序員在使用變量前不對它進行初始化,其實是爲了能讓 Lua 指示出使用了未被初始化的變量。

Lua 中函數是第一類值(first-class values):他們能夠存儲在變量中,作爲參數傳遞給其餘函數或者作爲結果返回。當函數被定義,它的函數體被編譯並保存在一個給定名稱的全局變量。Lua 能夠調用(和操做)寫在 Lua 或 C 中的函數;後者的類型是 Cfunction。

userdata 類型容許 Lua 變量保存任意的 C 指針(void*);在 Lua 中對它有效的操做是分配和相等測試。

table 類型實現爲關聯數組,便可以用數字和字符串索引的數組。所以,該類型不只可用於表示普通數組,也能夠用於表示符號表,集合,記錄等。爲表示一個記錄,Lua 使用字段名爲下標。語言經過提供 a.name 這種表示做爲 a["name"] 的語法糖。

關聯數組是一個功能強大的語言結構;許多算法得以簡化,由於用於搜索他們的所需的數據結構和算法被語言提供(Aho–Kerninghan–Weinberger 1988; Bentley 1988)。例如,一個記錄單詞在文本中出現次數的程序的核心能夠被寫爲
table[word] = table[word] + 1
而無需搜索詞語的列表。 (然而,按字母順序排列的報告須要一些實實在在的工做,由於 Lua 表中的索引是任意排序的。)

表能夠以多種方式來建立。最簡單的方法對應於普通數組:
t = @(100)

這樣的表達式會生成一個新的的空表。表的尺寸(在上述例子中是100)是可選的,而且能夠給初始表的大小一個提示。Lua 中的表能夠根據須要進行動態擴展不管初始大小是多大。所以,對 t[200] 和 t["day"] 的引用都是徹底有效的。

有兩種建立表的語法 : 一個用於列表(@[]),一個用於記錄(@{})。舉例來講,很容易經過提供表的的元素來建立它,如
        t = @["red", "green", "blue", 3]
這個代碼與下面的代碼等價
        t = @()
        t[1] = "red"
        t[2] = "green"
        t[3] = "blue"
        t[4] = 3

此外,能夠在建立列表或記錄時提供用戶自定義函數,如
        t = @colors["red", "green", "blue", "yellow"]
        t = @employee{name="john smith", age=34}
Thus, the code for the employee record is equivalent to:
在這裏,colors 和 employee 都會在建立表後被自動調用。這樣的函數能夠被用來檢查字段值,建立默認字段,或用於任何其餘的有反作用的操做。所以,employee 操做記錄的代碼等價於:
        t = @()
        t.name = "john smith"
        t.age = 34
        employee(t)
須要注意的是,雖然 Lua 中沒有類型聲明,可是因爲它有表建立後自動調用函數的功能,使 Lua 有了用戶可控的類型構造函數。這種不常見的結構是一個很是強大的功能,在使用 Lua 進行聲明式編程時能夠這樣如此表示。

-------------------
API
-------------------
實現 Lua 的 C 函數庫中有一些使 Lua 和宿主進行交互的 API(大約有30個這樣的函數)。這些函數使 Lua 做爲嵌入式語言,能夠處理下列任務:執行包含在一個文件或字符串中的 Lua 代碼; 轉換 C 和 Lua 中的值;讀寫的全局變量中的 Lua 對象;調用Lua 函數;註冊可在 Lua 中調用的 C 函數,包括錯誤處理程序。一個簡單的 Lua 解釋能夠寫成以下:
        #include "lua.h"
        int main(void)
        {
         char s[1000];
         while (gets(s))
           lua_dostring(s);
         return 0;
        }

這個簡單的解釋器能夠增長用C語言編寫的特定領域函數,並可使用函數 lua_register 註冊給 Lua 使用。擴展函數遵循特定協議來接收和返回 Lua 中的值。


-------------------
預約義的函數和庫
-------------------
Lua 的一組預約義函數雖少但功能強大。他們中大多數提供的功能讓語言有必定程度的自反性。這些功能不能經過語言的其它部分模擬也不能經過標準的 API 模擬。預約義函數能處理如下任務:執行包含在一個文件或字符串中的 Lua 模塊;遍歷一個表的全部字段;枚舉全部的全局變量;類型查詢和轉換。

庫,在另外一方面,提供了一種經過標準 API 實現的有用的程序。所以,它們並不是語言必須的部分,而且做爲單獨的 C 模塊被提供,它能夠根據須要被鏈接到應用程序。目前,有字符串處理庫,數學函數庫,輸入輸出庫。

-------------------
持久化
-------------------
枚舉函數能夠用來實現 Lua 全局環境的持久化,它能夠寫 Lua 代碼,在執行時恢復全部全局變量的值。咱們如今展現一些方法來存儲和恢復 Lua 中的值,用 Lua 寫成的文本文件做爲存儲媒介。用這種辦法保存的值也能夠很方便的恢復回來。

保存一個鍵值對,用下面的代碼就能夠了:
        function store(name, value)
          write(name .. '=')
          write_value(value)
        end
在這裏,「..」是字符串鏈接操做,write 是用來輸出的庫函數。函數 write_value 根據 value 的類型輸出一個合適的格式,value 的類型能夠利用預先定義的函數 type 得到:
        function write_value(value)
          local t = type(value)
              if t = 'nil' then write('nil')
          elseif t = 'number' then write(value)
          elseif t = 'string' then write('"' .. value .. '"')
          end
        end
存儲表有點複雜。首先,write_value 中再加上
          elseif t = 'table' then write_record(value)
假設表被用做記錄(即沒有循環引用,全部下標均爲標識符),表的值能夠用表的構造函數寫成:
        function write_record(t)
          local i, v = next(t, nil) -- "next" enumerates the fields of t
          write('@{') -- starts constructor
          while i do
            store(i,v)
            i, v = next(t, i)
            if i then write(', ') end
          end
          write('}') -- closes constructor
        end
(未完待續)
html

相關文章
相關標籤/搜索