Lua（，葡萄牙語 月亮）是一個簡潔、輕量、可擴充套件的指令碼語言。Lua 有著相對簡單的 C 語言 API 而很容易嵌入應用中。很多應用程式使用 Lua 作為自己的嵌入式指令碼語言，以此來實現可配置性、可擴充套件性。 歷史 Lua 是在 1993 年由羅伯託耶魯薩林斯希、Luiz Henrique de Figueiredo 和 Waldemar Celes 建立的，他們當時是巴西的里約熱內盧天主教大學的計算機圖形技術組（Tecgraf）成員。Lua 的先驅是資料描述 配置語言 SOL（簡單物件語言）和 DEL（資料錄入語言）。。他們於 1992 年1993 年在 Tecgraf 獨立開發了需要增加靈活性的兩個不同專案（都是用於工程應用的互動式圖形程式）。在 SOL 和 DEL 中缺乏很多控制流結構，需要向它們增加完全的程式設計能力。 在《The Evolution of Lua》中，這門語言的作者寫道： Lua 主要受到了下列前輩語言的影響： Modula2：從中引入了大部份控制結構，if、while、repeatuntil。 CLU：多賦值和從函式呼叫的多個返回值，這是對引用引數或顯式指標的更簡單的替代。 C：允許區域性變數只在需要的地方宣告的靈巧想法。 SNOBOL 和 AWK：關聯陣列。 LISP 和 Scheme：在發表於《》的文章中，Lua 的創立者還聲稱，有著單一且無所不在的資料結構機制（列表）的 LISP 和 Scheme，對他們決定將表格開發為 Lua 的主要資料結構起到了主要影響。Lua 的語義久而久之日趨受到 Scheme 的影響，特別是介入了匿名函式和完全的詞法作用域。 Lua 在版本 50 之前在類似 BSD 許可證之下發行。自從版本 50 以來，Lua 採用了 MIT 許可證。 特性 Lua 是一種輕量語言，它的官方版本只包括一個精簡的核心和最基本的庫。這使得 Lua 體積小、啟動速度快。它用 ANSI C 語言編寫，並以原始碼形式開放，編譯後的完整參考直譯器只有大約 247kB，到 543 版本，該體積變成 283kB（Linuxamd64），依然非常小巧，可以很方便的嵌入別的程式裡。和許多 大而全 的語言不一樣，網路通訊、圖形介面等都沒有預設提供。但是 Lua 可以很容易地被擴充套件：由宿主語言（通常是 C 或 C）提供這些功能，Lua 可以使用它們，就像是本來就內建的功能一樣。事實上，現在已經有很多成熟的擴充套件模組可供選用。 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 1 for i 2 n do x x i end return x end 控制流 Lua 有一種型別的條件測試：if then end，它具有可選的 else 和 elseif then 執行控制構造。 通用的 if then end 語句需要三個關鍵字 if condition then statement body end 可以增加 else 關鍵字來控制執行，它隨同著在 if 條件求值為 false 之時執行的語句塊： if condition then statement body else statement body end 還可以使用 elseif then 關鍵字依據多個條件來控制執行： if condition then statement body elseif condition then statement body else optional optional default statement body end Lua 有四種類型的迴圈：while 迴圈、repeat 迴圈（類似於 do while 迴圈）、數值 for 迴圈和通用 for 迴圈。 condition true while condition do statements end repeat statements until condition for i first last delta do delta 可以是負數，允許計數增加或減少的迴圈 statements example print i end 通用 for 迴圈： for key value in pairs _G do print key value end 將在表格_G 上使用標準迭代器函式 pairs 進行迭代，直到它返回 nil。 可以有巢狀的迴圈，就是在其他迴圈中的迴圈。 local grid 11 12 13 21 22 23 31 32 33 for y row in ipairs grid do for x value in ipairs row do print x y grid yx end end 函式 Lua 將函式處理為頭等值，在下例子中用 print 函式的表現可以修改來展示： do local oldprint print 儲存當前的 print 函式為 oldprint function print s 重新定義 print 函式。新函式只有一個實際引數。 平常的 print 函式仍可以透過 oldprint 使用。 oldprint s foo and bar or s end end 任何對 print 的進一步呼叫都要經由新函式，並且由於 Lua 的詞法作用域，這個舊的 print 函式將只能被這個新的修改了的 print 訪問到。 Lua 還支援閉包，展示如下： function addto x 返回一個把實際引數加到 x 上 return function y 在我們引用變數 x 的時候，它在當前作用域的外部， 它的生命期會比這個匿名函式短，Lua 建立一個閉包。 return x y end end fourplus addto 4 print fourplus 3 列印 7 這也可以透過如下方式呼叫這個函式來完成： print addto 43 這是因為這直接用視件引數 3 呼叫了從 addto 4 返回的函式。 如果經常這麼呼叫的話會減少資料消耗並提升效能。 每次呼叫的時候為變數 x 建立新的閉包，所以返回的每個新匿名函式都訪問它自己的 x 引數。閉包由 Lua 的垃圾收集器來管理，如同任何其他物件一樣。 function create_a_counter local count 0 return function count count 1 return count end end create_a_counter 會返回一個匿名函式，這個匿名函式會把 count 加 1 後再回傳。在匿名函式中的變數 count 的值會一直被儲存在匿名函式中。因此呼叫 create_a_counter 時產生一個記數器函式，每次呼叫記數器函式，都會得到一個比上次大 1 的值。 變數型別 Lua 是一種動態型別語言，因此語言中沒有型別的定義，不需要宣告變數型別，每個變數自己儲存了型別。 有 8 種基本型別：nil、布林值（boolean）、數 字型（number）、字串型（string）、使用者自定義型別（userdata）、函式（function）、執行緒（thread）和表（table）。 print type nil 輸出 nil print type 997129 輸出 number print type true 輸出 boolean print type Hello Wikipedia 輸出 string print type print 輸出 function print type 1 2 test test 輸出 table 表格 表格（table）是 Lua 中最重要的資料結構（並且是設計中唯一內建的複合資料型別），並且是所有使用者建立型別的基礎。它們是增加了自動數值鍵和特殊語法的關聯陣列。 表格是鍵和資料的有序對的蒐集，其中的資料用鍵來引用；換句話說，它是雜湊異構關聯陣列。 表格使用 構造器語法來建立。 a_table 建立一個新的空表格 表格總是用引用來傳遞的（參見傳共享呼叫）。 鍵（索引）可以是除了 nil 和 NaN 的任何值，包括函式。 a_table x 10 建立一個新表格，具有對映 x 到數 10 的一個表項。 print a_table x 列印於這個字串關聯的值，這裡是 10。 b_table a_table b_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 列印 10 print pointx 同上一行完全相同含義。易讀的點只是語法糖。 透過使用表格來儲存有關函式，它可以充當名字空間。 Point Pointnew function x y return x x y y return x x y y end Pointset_x function point x pointx x point x x end 表格被自動的賦予了數值鍵，使得它們可以被用作。第一個自動索引是 1 而非 0，因為很多其他程式語言都這樣（儘管顯式的索引 0 是允許的）。 數值鍵 1 不同於字串鍵 1。 array a b c d 索引被自動賦予。 print array 2 列印 b。在 Lua 中自動索引開始於 1。 print array 列印 4。 是表格和字串的長度算符。 array 0 z 0 是合法索引。 print array 仍列印 4，因為 Lua 陣列是基於 1 的。 表格的長度 t 被定義為任何整數索引 n，使得 t n 不是 nil 而 t n1 是 nil；而且如果 t 1 是 nil， n 可以是 0。對於一個正規表格，具有非 nil 值從 1 到給定 n，它的長度就精確的是 n，它的最後的值的索引。如果這個陣列有 洞（就是說在其他非 nil 值之間的 nil 值），則 t 可以是直接前導於 nil 值的任何一個索引（就是說可以把任何這種 nil 值當作陣列的結束）。 ExampleTable 1 2 3 4 5 6 7 8 print ExampleTable 13 列印 3 print ExampleTable 24 列印 8 表格可以是物件的陣列。 function Point x y Point 物件構造器 return x x y y 建立並返回新物件（表格） end array 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 2y 列印 40 使用雜湊對映來模擬陣列通常比使用真正陣列要慢；但 Lua 表格為用作陣列而做了最佳化來避免這個問題。 元表格 可擴充套件的語義是 Lua 的關鍵特徵，而 元表格 概念允許以強力方式來定製 Lua 的表格。下列例子展示了一個 無限 表格。對於任何 n，fibs n 會給出第 n 個斐波那契數，使用了動態規劃和記憶化。 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。建立新物件要麼透過工廠方法（從頭構造一個新物件），要麼透過複製現存的物件。 建立一個基本的向量物件： local Vector Vector__index Vector function Vectornew x y z 構造器 return setmetatable x x y y z z Vector end function Vectormagnitude 另一個方法 使用 self 引用隱蔽的物件 return mathsqrt selfx2 selfy2 selfz2 end local vec Vectornew 0 1 0 建立一個向量 print vecmagnitude 呼叫一個方法 輸出 1 print vecx 訪問一個成員變數 輸出 0 Lua 為便利物件定向提供了一些語法糖。要宣告在原型表格內的成員函式，可以使用，它等價於。呼叫類方法也使用冒號，等價於。 下面是使用語法糖的對應的類： local Vector Vector__index Vector function Vectornew x y z 構造子 因為函式定義使用冒號， 其第一個實際引數是 self 它引用到 Vector return setmetatable x x y y z z self end function Vectormagnitude 另一個方法 使用 self 引用隱含的物件 return mathsqrt selfx2 selfy2 selfz2 end local vec Vectornew 0 1 0 建立一個向量 print vecmagnitude 呼叫方法（輸出：1） print vecx 訪問成員變數（輸出：0） 繼承 Lua 支援使用元表格來使得 Lua 具有類繼承。在這個例子中，我們允許向量在派生的類中將它們的值乘以一個常量： local Vector Vector__index Vector function Vectornew x y z 構造子 這裡的 self 引用到我們呼叫 new 方法的任何類 在派生的類中，self 將是派生的類； 在 Vector 類中，self 將是 Vector。 return setmetatable x x y y z z self end function Vectormagnitude 另一個方法 使用 self 引用隱含的物件 return mathsqrt selfx2 selfy2 selfz2 end 類繼承的例子 local VectorMult VectorMult__index VectorMult setmetatable VectorMult Vector 使得 VectorMult 成為 Vector 的孩子 function VectorMultmultiply value selfx selfx value selfy selfy value selfz selfz value return self end local vec VectorMultnew 0 1 0 建立一個向量 print vecmagnitude 呼叫一個方法（輸出：1） print vecy 訪問一個成員變數（輸出：1） vecmultiply 2 將向量的所有分量乘以 2 print vecy 再次訪問成員變數（輸出：2） Lua 還支援多重繼承，可以要麼是一個函式要麼是一個表格。也支援運算子過載，Lua 元表格透過設定元素比如、等來重寫表的運算子操作行為。 實現 Lua 程式不是從文字式的 Lua 檔案直接解釋的，而是編譯成位元組碼，接著把它執行在 Lua 虛擬機器上。編譯過程典型的對於使用者是不可見並且是在執行時間進行的，但是它可以離線完成用來增加裝載效能或透過排除編譯器來減少對宿主環境的記憶體佔用。Lua 位元組碼還可以在 Lua 之內產生和執行，使用來自字串庫的 dump 函式和 loadloadstringloadfile 函式。Lua 版本 534 是用大約 24000 行 C 程式碼實現的。 像大多數 CPU，而不像多數虛擬機器（它們是基於堆疊的），Lua VM 是基於暫存器的，因此更加類似真實的硬體設計。暫存器架構既避免了過多的值複製又減少了每函式的指令的總數。Lua 5 的虛擬機器是第一個廣泛使用的基於暫存器的純 VM。Parrot 和 Android 的 Dalvik 是另外兩個周知的基於暫存器的 VM。PCScheme 的 VM 也是基於暫存器的。 下面的例子列出上面定義的階乘函式的位元組碼（透過 luac 51 編譯器來展示）： function factoriallua17 9 instructions 36 bytes at 0x8063c60 1 param 6 slots 0 upvalues 6 locals 2 constants 0 functions 1 2 LOADK 1 1 1 2 3 LOADK 2 2 2 3 3 MOVE 3 0 4 3 LOADK 4 1 1 5 3 FORPREP 2 1 to 7 6 4 MUL 1 1 5 7 3 FORLOOP 2 2 to 6 8 6 RETURN 1 2 9 7 RETURN 0 1 C API Lua 意圖被嵌入到其他應用之中，為了這個目的而提供了 C API。API 被分成兩部份：Lua 核心庫和輔助庫。Lua API 的設計消除了對用 C 程式碼的手動引用管理的需要，不同於 Python 的 API。API 就像語言本身一樣是極簡主義的。高階功能透過輔助庫來提供，它們很大程度上構成自預處理器宏，用以幫助做複雜的表格操作。 Lua C API 是基於堆疊的。Lua 提供壓入和彈出最簡單 C 資料型別（整數、浮點數等）進入和離開堆疊的函式，還有透過堆疊操作表格的函式。Lua 堆疊稍微不同於傳統堆疊，例如堆疊可以直接的被索引。負數索引指示從棧頂開始往下的偏移量。例如1 是在頂部的（最新進壓入的值），而整數索引指示從底部（最舊的值）往上的偏移量。在 C 和 Lua 函式之間集結資料也使用堆疊完成。要呼叫一個 Lua 函式，把實際引數壓入堆疊，並接著使用 lua_call 來呼叫實際的函式。在寫從 Lua 直接呼叫的 C 函式的時候，實際引數讀取自堆疊。 下面是從 C 呼叫 Lua 函式的例子 include stdioh include luah Lua 主要庫 lua_ include lauxlibh Lua 輔助庫 luaL_ int main void 建立一個 Lua 狀態 lua_State L luaL_newstate 裝載並執行一個字串 if luaL_dostring L function foo xy return xy end lua_close L return 1 壓入全域性 foo（上面定義的函式）的值 到堆疊，跟隨著整數 5 和 3 lua_getglobal L foo lua_pushinteger L 5 lua_pushinteger L 3 lua_call L 2 1 呼叫有二個實際引數和一個返回值的函式 printf Result dn lua_tointeger L 1 列印在棧頂的專案的整數值 lua_pop L 1 返回堆疊至最初狀態 lua_close L 關閉 Lua 狀態 return 0 如下這樣執行這個例子： cc o example examplec llua example Result 8 C API 還提供一些特殊表格，位於各種 Lua 堆疊中的 偽索引 之上。Lua 52 之前，在 LUA_GLOBALSINDEX 之上是全域性表格；_G 來自 Lua 內部，它是主名字空間。還有一個登錄檔位於 LUA_REGISTRYINDEX，在這裡 C 程式可以儲存 Lua 值用於以後檢索。 還可以使用 Lua API 寫擴充套件模組。擴充套件模組是透過向 Lua 指令碼提供本地設施，用來擴充套件直譯器的功能的共享物件。從 Lua 方面看來，這種模組出現為一個名字空間表格，它持有自己的函式和變數。Lua 指令碼可以使用 require 裝載擴充套件模組，就像用 Lua 自身寫的模組一樣。一組仍在增長中的叫做 rock 的模組可以透過叫做的軟體包管理器獲取到，類似於 CPAN、RubyGems 和。對大多數流行的程式語言包括其他指令碼語言，都存在預先寫好的 Lua 繫結。對於 C，有許多基於模板的方式和一些自動繫結生成器。 應用 在影片遊戲開發中，Lua 被程式設計師廣泛的用作指令碼語言，主要由於它的可感知到的易於嵌入、快速執行，和短學習曲線。 在 2003 年，GameDevnet 組織的一次投票，說明了 Lua 是遊戲程式設計的最流行指令碼語言。在 2012 年 1 月 12 日，Lua 被《》宣佈為程式設計工具範疇的 Front Line 獎 2011 年度獲獎者。 大量非遊戲應用也出於可擴充套件性而使用 Lua，比如 TeX 排版設定語言實現 LuaTeX、鍵 值資料庫 Redis、文字編輯器 Neovim 和 web 伺服器 Nginx。 透過 Scribunto 擴充套件，Lua 可獲得為 MediaWiki 軟體中的伺服器端指令碼語言，Wikipedia 和其他 wiki 都基於了它。它的應用包括允許從 Wikidata 整合資料到文章中，和助力於。 Lua 可以用於嵌入式硬體，不僅可以嵌入其他程式語言，而且可以嵌入微處理器中，例如 NodeMCU 開源硬體專案將 Lua 嵌入到 WiFi SoC 中，另外，遊戲平臺 Roblox 所有遊戲都是利用 Lua 進行程式設計的。另外，還有中國大陸發起的開源 LuatOS 專案，也是將 Lua 應用到嵌入式領域，但不僅限於 Wifi，它包含 2G4GNBIOT 等通訊模組，也包含 stm32w800 等嵌入式 mcu，同時也支援在 windowslinux 平臺上執行的模擬器。 派生語言 編譯成 Lua 的語言 以 Lua 作為編譯目標輸出的程式語言： Moonscript，是動態、空白敏感的指令碼語言，它受到 CoffeeScript 的啟發，編譯成 Lua。 Haxe，支援編譯成 Lua 目標，支援 Lua 5153 還有 LuaJIT 20 和 21。 Fennel，目標為 Lua 的 Lisp 方言。 Urn，建造在 Lua 之上的 Lisp 方言。 Amulet，類 ML 的函式式語言，其編譯器輸出 Lua 檔案。 LunarML，產生 LuaJavaScript 程式碼的 Standard ML 編譯器。 方言 ，Lua 51 的即時編譯器。 ，由 Roblox 公司開發，派生自 Lua 51，具有，補充特徵並關注於效能。 Ravi，啟用了 JIT 的 Lua 53 語言並具有可選的靜態型別。JIT 由型別資訊來指導。 Shine，既有很多擴充套件的 LuaJIT 分叉，包括了模組系統和宏系統。 Glua，嵌入到遊戲蓋瑞模組中作為其指令碼語言的一個 Lua 修改版本。 Teal，用 Lua 編寫的靜態型別的 Lua 方言。 此外，Lua 使用者社群在參考 C 語言實現之上提供了一些 能力補丁。 引用 延伸閱讀 The 1st ed is available online Chapters 6 and 7 are dedicated to Lua while others look at software in Brazil more broadly Interview with Roberto Ierusalimschy How the embeddability of Lua impacted its design Lua papers and theses 外部連結 Lua Users 使用者社群 Awesome Lua Category 程式設計語言 Category 基於原型的程式語言 Category 使用 MIT 許可證的軟體 CategoryC 軟體 Category 用 C 程式設計的自由軟體 Category 巴西發明 Category1993 年建立的程式語言 Category 自由編譯器與直譯器