Help:Lua

出自滚动的天空Wiki

Lua是輕量的動態型別語言。MediaWiki的Scribunto擴充允許頁面呼叫Lua代碼,其中Lua代碼放置在「Module:」命名空間下。MediaWiki使用的Lua版本為5.1,但進行了一些修改,並移植了之後的版本的一些功能。

本幫助頁面將簡單地介紹Lua的語法、用法及特點等,但不會特別細緻,建議有一定編程基礎的使用者閱讀。請注意,本頁提及了一些MediaWiki及其擴充提供的庫,這些在MediaWiki之外的標準執行環境中是不直接具備的。本頁還會提及本wiki中的(包括搬運的)部分模組,不一定適用於其他基於MediaWiki的網站。零基礎的使用者如需學習Lua語言,可在網上自行搜尋入門教程。

變數[編輯原始碼]

Lua的變數分為全域變數局部變數。局部變數應使用local關鍵字進行聲明。同其他語言一樣,我們使用等號進行賦值。在沒有聲明局部變數的情況下進行賦值,則預設賦值給全域變數。例如,

a = 1 -- 声明一个全局变量
do -- 一个作用域
    local b = 1 -- 声明一个局部变量
    b = "2" -- 修改一个局部变量
end
print(b) -- 局部变量超出作用域会失效,所以为nil

注意:

  1. 上面這個例子中,只是標準的開發環境,因此使用了全域變數,並使用print函式以將值輸出到控制台。MediaWiki不同於標準的開發環境,不應當修改全域變數,也不應該使用print函式。
  2. 在MediaWiki的模組中,不應該建立或修改任何全域變數,而應該定義局部變數,並在模組末尾使用return語句返回你需要返回的值。

局部變數也可以先聲明再賦值。沒有賦值的變數,以及不存在的變數,其值都為nil,使用這些變數並不會丟擲錯誤,但仍需留意。

變數可以多重賦值,例如a, b = 1, 2是有效的,但不支持a = b = 1這樣的語法。等號右邊也可以是返回多個值的函式(如unpack或string.find)。如果等號右邊的值的個數超過左邊,則多餘的會被忽略;如果少於左邊的,則缺少的會賦值為nil。注意Lua沒有元組(tuple)。

變數名稱不能以數字開頭,不能為保留字,也不可以有中文。

資料類型[編輯原始碼]

Lua是動態型別語言,變數沒有類型,只有值有。聲明局部變數、函式參數等,均無需聲明類型。

基本類型介紹[編輯原始碼]

在Scribunto擴充中,Lua只有6個基本的類型:

nil[編輯原始碼]

空值(nil)表示這個值不存在。嘗試存取不存在的變數或表中不存在的欄位的值時,會得到nil。函式呼叫中未提供的參數的值都會被視為nil。很多函式也會在失敗時返回nil而不是丟擲錯誤,例如tonumber函式在無法轉換成數字時會返回nil。

boolean[編輯原始碼]

布林值(boolean)只有true或false。將其他資料類型轉化為布林值時,只有false和nil視為false,其他的一律視為true。

string[編輯原始碼]

字串(string)為一系列的8位元位元組;這取決於應用程式以哪種特定的編碼來解析。字串使用單引號或雙引號表示,單引號和雙引號無區別,也不會區分「字元」與「字串」。字串可以跳脫。

字串也可以使用長括號來定義。長括號之間可以夾雜著0個或更多個等號(兩邊的等號要等量)。例如,"abc"'abc'[[abc]][==[abc]==]都是等價的。注意:由長括號定義的字串可以直接換行,且不會處理跳脫序列

Lua標準庫中的string庫可用於處理字串,MediaWiki庫中的mw.textmw.ustring則提供了更多的處理方法。

number[編輯原始碼]

Lua中的數字(number)均為雙精度浮點數,-9007199254740992到9007199254740992之間的整數都會準確表達,更大的數和帶有小數部分的數將會受到捨入的誤差。

數字可以用點(.)來表示小數,例如123456.78。數字也可以用不帶空格的科學計數法,例如1.23e-10、123.45e20或者1.23E5。也可以用16進位表示整數,方法就是以0x開頭,例如0x3A。

1/0會得到inf,0/0會得到nan。inf和nan都可以正確地儲存,但是並無直接的文字表示方法(無窮大可以使用math.huge表示)。使用時,應當留意inf和nan可能帶來的潛在錯誤。nan不等於它自己,也就是說,nan == nan會返回false

Lua標準庫中的math庫可用於處理數字。

table[編輯原始碼]

表(table)是複合資料類型,是Lua中最複雜的一種資料類型,很像JavaScript中的對象(object)或者Python中的字典(dict)。在Lua中,表同時充當了對映、列表、集合的功能。

表由多個(或者零個)欄位組成,每個欄位都有鍵(key)值(value),其中鍵可以是除nil或nan外的任何值。不存在的欄位可視為其值為nil。鍵是唯一的,值不是唯一的。若鍵為表或函式,則判斷其唯一性是根據其是否為同一個對象。

聲明一個表的方法是:在一組花括號中,使用「[鍵] = 值」的方式表示一個欄位,多個欄位使用逗號或分號隔開。如果鍵是字串的字面量,且該字串符合變數名稱要求,則可以省略引號並使用「鍵 = 值」的方式表示。例如,{a = 1, b = "2"}中,"a""b"都是鍵,1"2"是對應的值,也可以表示為{["a"] = 1, ["b"] = 2}

聲明一個表時,鍵是可以省略的。例如,{"a", "b", "c", "d", "e"}等價於{[1]="a", [2]="b",[3]="c", [4]="d", [5]="e"},這樣的表稱為陣列(array)序列(sequence),其特點就是鍵是連續不間斷的正整數。空表也可以視為陣列

對於表t和數字b,如果滿足(t == 0 or t[b] ~= nil) and t[b + 1] == nil,則稱b為表t的一個邊界(border)。陣列只有一個邊界。有多個邊界的表不是嚴格的陣列,稱為稀疏陣列(sparse array),對其進行取長運算會返回其中一個邊界,存在不確定性。例如,{nil, 2, 3, nil, nil, 6}是個稀疏陣列,其邊界為0、3、6,因此其長度可能是0、3、6中的某一個值。

表也可以用作集合(set),如{a = true, b = true, c = true},因為鍵是不能重複的,所以可以通過判斷鍵對應的值是否存在來判斷集合是否包含某個元素。

表的欄位存取:可以通過「變數名稱[鍵]」或「變數名稱.鍵」(該方法的鍵必須是可作識別碼的字串)的方式來存取表中的鍵對應的值,也就是說,找到一個鍵為指定的值(表、函式等需要是同一對象,字串、數字等只需要值相等即可)的欄位,並返回其值。例如,如果t是一個表,則t[true]表示布林值鍵true對應的值,t.abct["abc"]表示字串鍵"abc"對應的值。如果這個值不存在,返回nil。

表的欄位賦值:可以通過賦值語句修改表中的鍵對應的值。可以給表添加欄位、修改現有欄位的值或者清除一個欄位。例如,t[true] = "q"表示將鍵true對應的值修改為字串"q",t["abc"] = nilt.abc = nil(該方法的鍵必須是可作識別碼的字串)表示將鍵"abc"對應的值修改為nil(相當於刪除這個欄位,如果其原來的值不是nil的話)。注意:鍵可以是除nil或nan外的任意值,可以是函式、表,甚至是表自身,也就是說t[print] = 1t[t] = t這樣的用法都是有效的,而t[0/0] = 1則是無效的(0/0相當於nan,參考#number)。

表不會自動複製。例如如果t是一個表,則賦值語句s = t不會將t的內容複製到新的表s中,而是會讓變數s和t的指標都指向同一個記憶體空間,它們仍是同一個對象,對表s的修改和對t的修改是同步、等價的。

Lua中,判斷兩個表是否相等的標準是兩個表是否是同一個對象,而不是內容是否一致。例如,在上面這一段的例子中,s == t就會返回true,而{} == {}會返回false。一般來說,每對花括號都意味著建立了一個新的對象。

Lua標準庫中的table庫可用於處理表,其中大部分函式都是用於處理陣列(表中的陣列部分)的。此外,本wiki也搬運了元模組TableTools,可用於對表進行一些進階的處理。

此外,表的索引、賦值、判斷相等等操作都可以受到#元表影響。

function[編輯原始碼]

Lua中的函式(function)閉包,這意味著它們維護對它們聲明的作用域的引用,並可以存取和操作該作用域中的變數。

定義一個函式的基本語法是:

function 函数名称(参数列表)
    语句……
end

函式名稱可以是表的欄位,例如t.abc。或者,你也可在function前面加上local關鍵字,表示該函式是個局部變數。參數是可選的,且視為局部變數。定義函式時,可以使用...表示不定數量的參數(參見select)。

函式內可以使用return語句,表示函式返回一個值,return語句後面應當緊跟代碼塊的結束。如果沒有執行return語句,函式不會返回值,可以認為返回了nil。

匿名函式的表示方法為:

function (参数列表)
    语句……
end

匿名函式與普通的函式無區別,例如,下面兩個語句的效果是一樣的:

  • local function f(x) return x+1 end
  • local f = function (x) return x+1 end

注意:通過匿名函式賦值的方式定義的函式,如果需要在函式內呼叫函式自身,應當先聲明再賦值。例如:

  • 正確:local function f(x) return x < 5 and f(x + 1) or x end
  • 錯誤:local f = function (x) return x < 5 and f(x + 1) or x end(這裡的f(x+1)中的f視為全域變數)
  • 正確:local f; f = function (x) return x < 5 and f(x + 1) or x end

事實上,在Lua中,local function f(...)是一個語法糖,在位元組碼中,實際上是先聲明局部變數再賦值的。

呼叫函式的語法為:

函数名称(参数列表)

函式的呼叫可以單獨作為語句,也可以作為表達式。此外,如果呼叫函式時(無論作為語句還是表達式),只有一個參數,且該參數為字串或表的字面量,則括號可以省略,例如:

  • print "abc"等價於print("abc")
  • print{1, 2, 3}等價於print({1, 2, 3})

Lua的函式不支持方法多載或多重派發,也不能夠直接給偏好設定預設值。呼叫函式時,缺失的參數視為nil[1],多餘的參數會被忽略。一種常見的間接設定預設值的方式是,檢查某個參數的值是否存在,如果為nil則賦值為預設值,例如:

function f(name)
  name = name or 'default'
  ...
end
方法[編輯原始碼]

函式作為表的欄位時可以作為方法(method)呼叫,會自動將表自身作為第一個參數。方法是物件導向的一種體現。

Lua並沒有嚴格意義上的方法,但Lua提供了下面兩種語法糖:

  • 聲明方法:function table:method(args)等價於function table.method(self, args)
  • 呼叫方法:table:method("foo")等價於table.method(table, "foo")

此外,字串也可以物件導向使用(參見#元表)。

類型轉換[編輯原始碼]

Lua的資料類型可以相互轉換。

tonumber函式可以將字串轉化為數字,例如tonumber("123")會返回123。不能轉化為數字的,則會轉化為nil。對字串進行算數運算時會嘗試自動轉換成數字。

tostring函式可以將任意值轉化為字串。表會轉化為"table"(受#元表影響的除外),函式會轉化為"function"。數字與字串連接、數字之間連接時會自動轉換成字串。

type函式可以返回值的資料類型,用字串表示。例如type(1/0)會返回"string"type(true)會返回"boolean"

運算[編輯原始碼]

和大多數語言一樣,Lua支持算術運算。其中,除以零不會出錯,會得到inf或nan。模運算的定義為a % b == a - math.floor( a / b ) * b,例如-4%3會返回2而非-1。冪運算的符號為^。對字串進行算數運算,會嘗試使用tonumber函式轉化為數字,如無法轉換則會丟擲錯誤。

關係運算包括==~=<><=>=,運算結果一定是布林值。對於==,不同類型的資料比較為false。注意表或函式的比較會比較指標,當兩個值指向同一個表或函式時才會返回true,除非受元表影響。

邏輯運算包括andornot。注意:邏輯運算的結果不一定是布林值,且進行邏輯運算時,並不會提前把所有值算出來,例如3 or error()的值為3,不會丟擲錯誤。可以使用邏輯運算來組成「三元運算」,例如a and b or c相當於JavaScript、Julia或Java中的a ? b : c或Python中的b if a else c,前提是b不能為false。

對兩個字串進行連接運算的方式是a .. b,而非a + ba * b。可以對數字進行連接,連接時會嘗試轉化為字串。注意:Lua的字串是不可變的(immutable),而且Lua不提供任何類型的「字串構造器(string builder)」,所以反覆進行a = a .. b會多次建立新的字串,並最終將舊字串作為垃圾收集。如果許多字串都需要連接,則應使用string.format,或將所有的字串添加到一個陣列然後最後使用table.concat()連接。

#a可以取得字串或者陣列a的長度。對於字串,會取得其位元組數,類似於string.len,取得字元數可以使用函式mw.ustring.len。對於表,只會取得其陣列部分的長度,其他部分的欄位一律忽略(對於{1, 2, nil, 4}這樣的稀疏陣列,其長度是其邊界中的某一個,例如可能為2或4[2]),如需取得表中所有欄位的個數,應使用一次for迴圈,或匯入模組:TableTools並使用其中的size函式。

對於表,上述所有運算都可以通過#元表控制。

優先級[編輯原始碼]

Lua中,各運算的優先級如下:

  1. ^
  2. not # -(負號)
  3. * / %
  4. + -(減號)
  5. ..
  6. < > <= >= ~= ==
  7. and
  8. or

此外,對於同級運算,a/b/c相當於(a/b)/ca^b^c相當於a^(b^c)

流程控制[編輯原始碼]

Lua和大多數語言一樣,具有條件語句和迴圈語句,沒有switch——case語句。

條件語句[編輯原始碼]

條件語句的基本語法是:

if 条件1 then
    语句块1
elseif 条件2 then
    语句块2
else
    语句块3
end

當條件1成立時,執行語句塊1,否則當條件2成立時,執行語句塊2,否則執行語句塊3。elseif和else的結構都是可選的,當然elseif可以有多個。

迴圈語句[編輯原始碼]

while迴圈的基本語法:

while 条件 do
    语句块
end

repeat迴圈的基本語法:

repeat
    语句块
until 条件

在repeat迴圈中,語句塊至少執行一次。條件可以使用語句塊中的局部變數。

for迴圈的基本語法:

for 名称 = 表达式1, 表达式2, 表达式3 do
    语句块
end

這種迴圈會聲明一個局部變數,由「表達式1」的值開始,以「表達式3」的值為步長,迴圈到「表達式2」的值。表達式3可以省略,預設為1,但不能為false或nil。

for語句除了用來迴圈之外,還可以進行迭代。語法為:

for 变量 in 表达式 do
    语句块
end

變數、表達式可以有多個。通常表達式是返回3個值的一個函式呼叫。如果迭代器函式可以被寫入,那麼只取決於傳入的參數,這會更加有效。否則,在Lua中編程建議返回閉包比返回表更好,因為靜態變數在每次迭代更新其成員。

在上述for、repeat和兩種for迴圈中,均可使用break關鍵字來立即跳出迴圈,break後面內容不再執行。Lua沒有continue語句[3],但可以呼叫包含迴圈代碼塊的函式,用return來代替。

常用的迭代函式有pairsipairsstring.gmatchmw.text.gsplit等。例如,對於一個普通的表t,我們可以使用這樣的方式迭代其所有的鍵和值:

for k,v in pairs(t) do
    含有k和v的语句块
end

元表[編輯原始碼]

每個表都可以設定一個元表(metatable),用來修改特定的行為。setmetatable(t, m)可以用來將表t的元表設為m(m也可以為nil,表示取消元表設定)[4],該函式自身也會返回修改後的表;getmetatable(t)可以用來取得表t的元表。

如果元表具有__index欄位,則當通過指定的鍵存取表的欄位的時候,如果該欄位不存在,則會使用元表__index欄位。如果__index是表,則會存取這個表的欄位。如果__index是函式,則會呼叫這個函式。例如,存取t[k]時,如果表本身的這個欄位不存在,則會嘗試存取__index[k](如果__index是表)或__index(t, k)(如果__index是函式)。rawget函式可繞過這個元方法。

__newindex欄位則用於修改給表的欄位賦值的行為。賦值給表的一個不存在的鍵時,如果__newindex欄位的值是表,則會在該表中重複賦值。如果__newindex的值是函式,則會呼叫這個函式。例如,執行t[k] = v時,表本身不存在這個欄位,則會執行__newindex[k] = v(如果__newindex是表)或__newindex(t, k, v)(如果__newindex是函式)。rawset函式可繞過這個元方法。

__call欄位可以讓表能像函式那樣呼叫,值必須是函式。如果這個元表欄位存在,則呼叫表t(...)時,會像__call(t, ...)這樣呼叫,而不是丟擲無法呼叫表的錯誤。

__metatable欄位用於保護元表,防止其被存取或修改。如果該欄位存在,當對表進行getmetatable時,不會返回真正的元表,而是返回該欄位的值[5],如果進行setmetatable,則會丟擲錯誤[4]

__mode欄位用於使表保持弱引用。在Lua中,變數和表的欄位對對象(函式、表等)的引用是強引用,只要某個函式和表依然存在引用,例如儲存在某個全域變數或者沒有超出作用域的局部變數中,或作為鍵或者值儲存在未被清理掉的表中,那麼它就不會被垃圾收集機制清理掉,而是一直儲存在記憶體中。弱引用則表示其引用不會阻止該表中儲存的內容被清理。當__mode的值為k表示鍵保持弱引用,v表示值為弱引用,kv表示鍵和值均為弱引用。例如:

t = setmetatable({value = {1, 2, 3}}, {__mode = 'v'})
print(t.value)
collectgarbage()
print(t.value)

輸出的結果為:

table
nil

元表的一些其他欄位用法如下,其值一般都是函式,函式的第一個參數是表自身。例如,如果元表__add欄位存在,則對表t計算t + a時,則會呼叫__add(t, a),而不是丟擲無法對表進行算術運算的錯誤。

欄位名稱和用法 對應操作
__add(self, v) self + v
__sub(self, v) self - v
__mul(self, v) self * v
__div(self, v) self / v
__mod(self, v) self % v
__pow(self, v) self ^ v
__unm(self) -self
__concat(self, v) self .. v
__len(self) #self
__eq(self, v) self == v
__lt(self, v) self < v
__le(self, v) self <= v
__pairs(self)[6] pairs(self)
__ipairs(self)[6] ipairs(self)
__tostring(self) tostring(self)

所有字串共享同一個元表,該元表的__index欄位等同於string庫的一個副本,以使得字串能夠作為對象使用。比如,string.match("label123", "^label%d")等價於("label123"):match("^label%d")。注意這個元表無法通過getmetatable函式獲得[7]

注釋[編輯原始碼]

  1. 如果函式有不定數量的參數,則這些參數的數量是準確的;此外一些Lua內建函式也不會補充參數,例如tostring()會丟擲錯誤,而不是理解為tostring(nil)
  2. 需要留意這種情況,因為會導致一些不確定性。
  3. Lua 5.2以上的版本具有標籤功能,可達到類似於continue的效果,但MediaWiki使用的Lua 5.1,是不支持標籤的。
  4. 4.0 4.1 原版Lua中,debug.setmetatable函式可以強制設定一個值(不限於表)的元表,且不受__metatable元方法影響。MediaWiki刪除了此函式。
  5. 原版Lua的debug.getmetatable函式可以強制取得任意值的元表,不受__metatable元方法影響。MediaWiki刪除了此函式。
  6. 6.0 6.1 原版Lua 5.1沒有__pairs和__ipairs的功能(是在後面的版本才加入的),不過Scribunto擴充在Lua中移植了該功能,因此你可以在MediaWiki中使用此元表。
  7. 原版Lua可以取得任意值的元表,只要元表存在。