Lua

Lua是一個簡潔、輕量、可擴充套件的指令碼語言。Lua有著相對簡單的C語言API而很容易嵌入應用中。很多應用程式使用Lua作為自己的嵌入式指令碼語言，以此來實現可配置性、可擴充套件性。歷史.羅伯託·耶魯薩林斯希，Lua語言的開發者Lua是在1993年由羅伯託·耶魯薩林斯希、Luiz Henrique de Figueiredo和Waldemar Celes建立的，他們當時是巴西的里約熱內盧天主教大學的計算機圖形技術組。在SOL和DEL中缺乏很多控制流結構，需要向它們增加完全的程式設計能力。在《The Evolution of Lua》中，這門語言的作者寫道：在1993年，唯一真正的競爭者是Tcl，它已經明確的設計用於嵌入到應用之中。但是，Tcl有著不熟知的語法，未對資料描述提供良好的支援，並且只在Unix平臺上執行。我們不考慮LISP或Scheme，因為它們有著不友好的語法。Python仍處在幼年期。在Tecgraf的自由的自力更生氛圍下，我們非常自然的嘗試開發自己的指令碼語言 ... 由於這門語言的很多潛在使用者不是專業程式設計者，語言應當避免神秘的語法和語義。新語言的實現應當是高度可移植的，因為Tecgraf的客戶有著非常多樣的各種計算機平臺。最後，由於我們預期Tecgraf的其他產品也需要嵌入指令碼語言，新語言應當追隨SOL的例子並提供為帶有C API的庫。Lua主要受到了下列前輩語言的影響：Lua在版本5.0之前在類似BSD許可證之下發行。自從版本5.0以來，Lua採用了MIT許可證。特性.Lua是一種輕量語言，它的官方版本只包括一個精簡的核心和最基本的庫。這使得Lua體積小、啟動速度快。它用ANSI C語言編寫，並以原始碼形式開放，編譯後的完整參考直譯器只有大約247kB，到5.4.3版本，該體積變成283kB提供這些功能，Lua可以使用它們，就像是本來就內建的功能一樣。事實上，現在已經有很多成熟的擴充套件模組可供選用。Lua是一個動態型別語言，支援增量式垃圾收集策略。有內建的，與作業系統無關的協作式多執行緒支援。Lua原生支援的資料型別很少，只提供了數值。語法和語義.Lua是一種多重程式設計範型的程式設計語言：它只提供了很小的一個特性集合來滿足不同程式設計範型的需要，而不是為某種特定的程式設計範型提供繁雜的特性支援。例如，Lua並不提供繼承這個特性，但是你可以用元表格來模擬它。諸如名稱空間、類這些概念都沒有在語言基本特性中實現，但是我們可以用表格結構輕易模擬。正是提供了這些基本的元特性，我們可以任意的對語言進行自需的改造。Lua實現了少量的高階特徵比如頭等函式、垃圾回收、閉包、正當尾呼叫、強制和動態模組裝載。詞法.經典的Hello World!程式可以寫為如下：print("Hello World!")或者如下：print 'Hello World'print Hello World在Lua中註釋可以於雙連字元並行至此行的結束，類似於Ada、Eiffel、Haskell、SQL和VHDL。多行字串和註釋用雙方括號來裝飾。下例中實現了一個階乘函式：function factorial(n)local x = 1for i = 2, n dox = x * iendreturn xend控制流.Lua有四種類型的迴圈：codice_2迴圈，codice_3迴圈，數值codice_8迴圈，和通用codice_8迴圈，和條件語句codice_10。--condition = truewhile condition do--statementsendrepeat--statementsuntil conditionfor i = first, last, delta do --delta可以是負數，允許計數增加或減少的迴圈--statements--example: print(i)end通用codice_8迴圈：for key, value in pairs(_G) doprint(key, value)end將在表格codice_12上使用標準迭代器函式codice_13進行迭代，直到它返回codice_14。可以有巢狀的迴圈，就是在其他迴圈中的迴圈。local grid = {{ 11, 12, 13 },{ 21, 22, 23 },for y, row in ipairs(grid) dofor x, value in ipairs(row) doprint(x, y, grid[y][x])endend函式.Lua將函式處理為頭等值，在下例子中用print函式的表現可以修改來展示：dolocal oldprint = print-- 儲存當前的print函式為oldprintfunction print(s)--重新定義print函式。新函式只有一個實際引數。平常的print函式仍可以透過oldprint使用。oldprint(s == "foo" and "bar" or s)endend任何對codice_15的進一步呼叫都要經由新函式，並且由於Lua的詞法作用域，這個舊的print函式將只能被這個新的修改了的print訪問到。Lua還支援閉包，展示如下：function addto(x)-- 返回一個把實際引數加到x上return function(y)--[=[ 在我們引用變數x的時候，它在當前作用域的外部，它的生命期會比這個匿名函式短，Lua建立一個閉包。]=]return x + yendendfourplus = addto(4)print(fourplus(3)) -- 列印7--這也可以透過如下方式呼叫這個函式來完成：print(addto(4)(3))--這是因為這直接用視件引數3呼叫了從addto(4)返回的函式。如果經常這麼呼叫的話會減少資料消耗並提升效能。每次呼叫的時候為變數codice_16建立新的閉包，所以返回的每個新匿名函式都訪問它自己的codice_16引數。閉包由Lua的垃圾收集器來管理，如同任何其他物件一樣。function create_a_counter()local count = 0return function()count = count + 1return countendendcreate_a_counter()會返回一個匿名函式，這個匿名函式會把count加1後再回傳。在匿名函式中的變數count的值會一直被儲存在匿名函式中。因此呼叫create_a_counter()時產生一個記數器函式，每次呼叫記數器函式，都會得到一個比上次大1的值。變數型別.Lua是一種動態型別語言，因此語言中沒有型別的定義，不需要宣告變數型別，每個變數自己儲存了型別。有8種基本型別：nil、布林值。print(type(nil)) -- 輸出 nilprint(type(99.7+12*9)) -- 輸出 numberprint(type(true)) -- 輸出 booleanprint(type("Hello Wikipedia")) -- 輸出 stringprint(type(print)) -- 輸出 functionprint(type({1, 2, test = "test"})) -- 輸出 table表格.表格，並且是所有使用者建立型別的基礎。它們是增加了自動數值鍵和特殊語法的關聯陣列。表格是鍵和資料的有序對的蒐集，其中的資料用鍵來引用；換句話說，它是雜湊異構關聯陣列。表格使用codice_18構造器語法來建立。a_table = {} -- 建立一個新的空表格表格總是用引用來傳遞的。鍵可以是除了codice_14和NaN的任何值，包括函式。a_table = {x = 10} -- 建立一個新表格，具有對映"x"到數10的一個表項。print(a_table["x"]) -- 列印於這個字串關聯的值，這裡是10。b_table = a_tableb_table["x"] = 20 -- 在表格中的值變更為20。print(b_table["x"]) -- 列印20。print(a_table["x"]) -- 還是列印20，因為a_table和b_table都引用相同的表格。透過使用字串作為鍵，表格經常用作。由於這種用法太常見，Lua為訪問這種欄位提供了特殊語法。point = { x = 10, y = 20 } -- 建立一個新表格print(point["x"]) -- 列印10print(point.x) -- 同上一行完全相同含義。易讀的點只是語法糖。透過使用表格來儲存有關函式，它可以充當名字空間。Point.new = function(x, y)endPoint.set_x = function(point, x)point.x = x -- point["x"] = x;end表格被自動的賦予了數值鍵，使得它們可以被用作。第一個自動索引是1而非0，因為很多其他程式語言都這樣。數值鍵codice_20不同於字串鍵codice_21。array = { "a", "b", "c", "d" } -- 索引被自動賦予。print(array[2]) -- 列印"b"。在Lua中自動索引開始於1。print(#array) -- 列印4。#是表格和字串的長度算符。array[0] = "z" -- 0是合法索引。print(#array) -- 仍列印4，因為Lua陣列是基於1的。表格的長度codice_22被定義為任何整數索引codice_23，使得codice_24不是codice_14而codice_26是codice_14；而且如果codice_28是codice_14， codice_23可以是0。對於一個正規表格，具有非nil值從1到給定codice_23，它的長度就精確的是codice_23，它的最後的值的索引。如果這個陣列有“洞”。ExampleTable ={1, 2, 3, 4},print(ExampleTable[1][3]) -- 列印"3"print(ExampleTable[2][4]) -- 列印"8"表格可以是物件的陣列。function Point(x, y) -- "Point"物件構造器return { x = x, y = y } -- 建立並返回新物件endarray = { Point(10, 20), Point(30, 40), Point(50, 60) } -- 建立point的陣列-- array = { { x = 10, y = 20 }, { x = 30, y = 40 }, { x = 50, y = 60 } };print(array[2].y) -- 列印40使用雜湊對映來模擬陣列通常比使用真正陣列要慢；但Lua表格為用作陣列而做了最佳化來避免這個問題。元表格.可擴充套件的語義是Lua的關鍵特徵，而“元表格”概念允許以強力方式來定製Lua的表格。下列例子展示了一個“無限”表格。對於任何codice_23，codice_36會給出第codice_23個斐波那契數，使用了動態規劃和記憶化。fibs = { 1, 1 } -- 給fibs[1]和fibs[2]初始值。setmetatable(fibs, {__index = function(values, n) --__index是Lua預定義的函式，如果"n"不存在則呼叫它。values[n] = values[n - 1] + values[n - 2] -- 計算並記憶化fibs[n]。return values[n]end面向物件程式設計.儘管Lua沒有內建的類的概念，可以用過兩個語言特徵實現面向物件程式設計：頭等函式和表格。透過放置函式和有關資料入表格，形成一個物件。繼承可以透過使用元表格機制來實現，告訴這個物件在哪些父物件中查詢不存在的方法。對於這些技術不採用“類”的概念，而是採用原型，類似於Self或JavaScript。建立新物件要麼透過工廠方法，要麼透過複製現存的物件。建立一個基本的向量物件：Vector.__index = Vectorfunction Vector:new(x, y, z) -- 構造器return setmetatable({x = x, y = y, z = z}, Vector)endfunction Vector:magnitude() -- 另一個方法-- 使用self引用隱蔽的物件return math.sqrt(self.x^2 + self.y^2 + self.z^2)endlocal vec = Vector:new(0, 1, 0) -- 建立一個向量print(vec:magnitude()) -- 呼叫一個方法 (輸出: 1)print(vec.x) -- 訪問一個成員變數 (輸出: 0)Lua為便利物件定向提供了一些語法糖。要宣告在原型表格內的成員函式，可以使用，它等價於。呼叫類方法也使用冒號，等價於。下面是使用語法糖的對應的類：Vector.__index = Vectorfunction Vector:new(x, y, z) -- 構造子-- 因為函式定義使用冒號，-- 其第一個實際引數是"self"-- 它引用到"Vector"return setmetatable({x = x, y = y, z = z}, self)endfunction Vector:magnitude() -- 另一個方法-- 使用self引用隱含的物件return math.sqrt(self.x^2 + self.y^2 + self.z^2)endlocal vec = Vector:new(0, 1, 0) -- 建立一個向量print(vec:magnitude()) -- 呼叫方法print(vec.x) -- 訪問成員變數繼承.Lua支援使用元表格來使得Lua具有類繼承。在這個例子中，我們允許向量在派生的類中將它們的值乘以一個常量：Vector.__index = Vectorfunction Vector:new(x, y, z) -- 構造子-- 這裡的self引用到我們呼叫"new"方法的任何類-- 在派生的類中，self將是派生的類；-- 在Vector類中，self將是Vector。return setmetatable({x = x, y = y, z = z}, self)endfunction Vector:magnitude() -- 另一個方法-- 使用self引用隱含的物件return math.sqrt(self.x^2 + self.y^2 + self.z^2)end-- 類繼承的例子VectorMult.__index = VectorMultsetmetatable(VectorMult, Vector) -- 使得VectorMult成為Vector的孩子function VectorMult:multiply(value)self.x = self.x * valueself.y = self.y * valueself.z = self.z * valuereturn selfendlocal vec = VectorMult:new(0, 1, 0) -- 建立一個向量print(vec:magnitude()) -- 呼叫一個方法print(vec.y) -- 訪問一個成員變數vec:multiply(2) -- 將向量的所有分量乘以2print(vec.y) -- 再次訪問成員變數Lua還支援多重繼承，可以要麼是一個函式要麼是一個表格。運算子過載也可以進行，Lua元表格擁有元素比如、，諸如此類。實現.Lua程式不是從文字式的Lua檔案直接解釋的，而是編譯成位元組碼，接著把它執行在Lua虛擬機器上。編譯過程典型的對於使用者是不可見並且是在執行時間進行的，但是它可以離線完成用來增加裝載效能或透過排除編譯器來減少對宿主環境的記憶體佔用。Lua位元組碼還可以在Lua之內產生和執行，使用來自字串庫的codice_38函式和codice_39函式。Lua版本5.3.4是用大約24,000行C程式碼實現的。像大多數CPU，而不像多數虛擬機器，Lua VM是基於暫存器的，因此更加類似真實的硬體設計。暫存器架構既避免了過多的值複製又減少了每函式的指令的總數。Lua 5的虛擬機器是第一個廣泛使用的基於暫存器的純VM。Parrot和Android的Dalvik是另外兩個周知的基於暫存器的VM。PCScheme的VM也是基於暫存器的。下面的例子列出上面定義的階乘函式的位元組碼：function  (9 instructions, 36 bytes at 0x8063c60)1 param, 6 slots, 0 upvalues, 6 locals, 2 constants, 0 functions1 [2] LOADK 1 -1 ; 12 [3] LOADK 2 -2 ; 23 [3] MOVE 3 04 [3] LOADK 4 -1 ; 15 [3] FORPREP 2 1 ; to 76 [4] MUL 1 1 57 [3] FORLOOP 2 -2 ; to 68 [6] RETURN 1 29 [7] RETURN 0 1C API.Lua意圖被嵌入到其他應用之中，為了這個目的而提供了C API。API被分成兩部份：Lua核心庫和輔助庫。Lua API的設計消除了對用C程式碼的手動引用管理的需要，不同於Python的API。API就像語言本身一樣是極簡主義的。高階功能透過輔助庫來提供，它們很大程度上構成自預處理器宏，用以幫助做複雜的表格操作。Lua C API是基於堆疊的。Lua提供壓入和彈出最簡單C資料型別往上的偏移量。在C和Lua函式之間集結資料也使用堆疊完成。要呼叫一個Lua函式，把實際引數壓入堆疊，並接著使用codice_41來呼叫實際的函式。在寫從Lua直接呼叫的C函式的時候，實際引數讀取自堆疊。下面是從C呼叫Lua函式的例子:int main(void)// 建立一個Lua狀態lua_State *L = luaL_newstate();// 裝載並執行一個字串if (luaL_dostring(L, "function foo (x,y) return x+y end")) {lua_close(L);return -1;// 壓入全域性"foo"的值// 到堆疊，跟隨著整數5和3lua_getglobal(L, "foo");lua_pushinteger(L, 5);lua_pushinteger(L, 3);lua_call(L, 2, 1); // 呼叫有二個實際引數和一個返回值的函式printf("Result: %d\n", lua_tointeger(L, -1)); // 列印在棧頂的專案的整數值lua_pop(L, 1); // 返回堆疊至最初狀態lua_close(L); // 關閉Lua狀態return 0;如下這樣執行這個例子：C API還提供一些特殊表格，位於各種Lua堆疊中的“偽索引”之上。Lua 5.2之前，在codice_42之上是全域性表格；codice_12來自Lua內部，它是主名字空間。還有一個登錄檔位於codice_44，在這裡C程式可以儲存Lua值用於以後檢索。還可以使用Lua API寫擴充套件模組。擴充套件模組是透過向Lua指令碼提供本地設施，用來擴充套件直譯器的功能的共享物件。從Lua方面看來，這種模組出現為一個名字空間表格，它持有自己的函式和變數。Lua指令碼可以使用codice_45裝載擴充套件模組，就像用Lua自身寫的模組一樣。一組仍在增長中的叫做“rock”的模組可以透過叫做的軟體包管理器獲取到，類似於CPAN、RubyGems和。對大多數流行的程式語言包括其他指令碼語言，都存在預先寫好的Lua繫結。對於C++，有許多基於模板的方式和一些自動繫結生成器。應用.在影片遊戲開發中，Lua被程式設計師廣泛的用作指令碼語言，主要由於它的可感知到的易於嵌入、快速執行，和短學習曲線。在2003年，GameDev.net組織的一次投票，說明了Lua是遊戲程式設計的最流行指令碼語言。在2012年1月12日，Lua被《》宣佈為程式設計工具範疇的Front Line獎2011年度獲獎者。大量非遊戲應用也出於可擴充套件性而使用Lua，比如TeX排版設定語言實現LuaTeX、鍵-值資料庫Redis、文字編輯器Neovim和web伺服器Nginx。透過Scribunto擴充套件，Lua可獲得為MediaWiki軟體中的伺服器端指令碼語言，Wikipedia和其他wiki都基於了它。它的應用包括允許從Wikidata整合資料到文章中，和助力於。Lua可以用於嵌入式硬體，不僅可以嵌入其他程式語言，而且可以嵌入微處理器中，例如NodeMCU開源硬體專案將Lua嵌入到Wi-Fi SoC中，另外，遊戲平臺Roblox所有遊戲都是利用Lua進行程式設計的。另外, 還有中國大陸發起的開源LuatOS專案，也是將Lua應用到嵌入式領域，但不僅限於Wifi，它包含2G/4G/NBIOT等通訊模組，也包含stm32/w800等嵌入式mcu，同時也支援在windows/linux平臺上執行的模擬器。