說明:Lua
Lua是在萌娘百科命名空間為「模塊」中所支持的一種腳本語言,可以提供相比模板更為強大的功能和解析效率。在MediaWiki軟件中,通過擴展Scribunto實現。
模塊是一種腳本處理流程,常用做各種複雜的函數庫或數據庫,目前在萌百上支持的Lua是5.1版本(但支持通過元表修改pairs和ipairs函數的行為)。與Widget(使用JavaScript)不同的是,模塊(使用Lua)中的邏輯是在服務端處理,用戶拿到的是最終結果。
基本語法
注釋
-- 單行注釋 --[[ 多行注釋 ]] --[=[ 通過在多行注釋頭尾的方括號間插入等量的等號(=), 來避免注釋內部的「[[ ]]」與多行注釋語法衝突。 ]=] --[==[ 可以按需調整等號的數量以避免衝突。 例如,這條注釋可以包含「[[ ]]」、「[=[ ]=]」、「[===[ ]===]」等等。 ]==]
關鍵字
Lua具有幾個保留關鍵字,保留關鍵字不能作為常量或變量或其他用戶自定義標識符:
and | break | do | else |
elseif | end | false | for |
function | if | in | local |
nil | not | or | repeat |
return | then | true | until |
while | goto |
一般地,以下劃線(_)開頭連接大寫字母的(比如 _VERSION、_MOEGIRL)被保留用於 Lua 內部全局變量。
22個關鍵字相比很多語言來說,已經非常簡單了。
標識符
標識符的意思就是用來聲明一個變量、函數的名字的東西,標識符可以是一個字母A到Z或a到z或下劃線_開頭後加上若干字母,下劃線,數字(0到9),Lua 不允許使用特殊字符如 @, $, 和 % 來定義標識符,也不能以數字開頭命名。
數據類型
Lua是一種動態類型語言,變量不要定義類型,只要賦值就好了。
數據類型 | 描述 |
---|---|
nil | 只有「值」nil屬於該類,表示一個無效值(在條件表達式中相當於false)。 |
boolean | 包含兩個值:false和true。 |
number | 表示雙精度類型的實浮點數 |
string | 字符串,使用一對雙引號""、一對單引號''或兩對方括號[[]],Lua中使用\作為轉義字符。 |
function | 函數,使用關鍵字function來定義的 |
userdata | 表示任意存儲在變量中的C數據結構(在Scribunto中不存在) |
thread | 表示執行的獨立線路,用於執行協同程序(在Scribunto中不存在) |
table | 表(table)是Lua中最複雜的類型,由多個鍵值對組成。表可以使用{}創建,t[field]或者t.fieldName可以訪問字段(且會受元表影響)。 |
使用type(object)
可以查看類型,注意type查看到的結果是字符串而不是類型對象。
type(nil) -- "nil" type(true) -- "boolean" type(3) -- "number" type("hello world") -- "string" type(function() return 0 end) -- "function" type({[1]='123'}) -- "table" type(abc) -- "nil",如果abc是不存在的變量,會返回nil type(type(5)) -- "string",證明type的結果是字符串
易錯提示:和其它語言不同,Lua的類型關鍵字並不是類型轉換函數,例如string(2)
將會報錯。
運算符
符號 | 描述 | 用法 | 假設A=6,B=3 |
---|---|---|---|
+ | 加號 | A+B | 6+3=9 |
- | 減號 | A-B | 6-3=3 |
* | 乘號 | A*B | 6*3=18 |
/ | 除號 | A/B | 6/3=2 |
% | 取模 | A%B | 6/3=2沒有餘數,因此:6%3=0 6/4=1……2餘數為2,因此6%4=2 |
^ | 乘冪 | A^B | B個A連乘:6^3=6*6*6=216 |
邏輯運算符 | |||
> | 大於 | A>B | A>B true |
< | 小於 | A<B | A<B false |
>= | 大於等於 | A>=B | A>=B true |
<= | 小於等於 | A<=B | A<=B false |
== | 等於 | A==B | A==B false |
~= | 不等於 | A~=B | A~=B true |
其他運算符 | |||
.. | 字符串連接 | A..B | 'Hello '..'World'將輸出Hello World |
# | 取字符串或表的長度 | #A | #'Hello'將輸出5 #{1,2,3,4}將輸出4 |
易錯提示
- Lua存在小部分弱類型現象。
- 算數運算符除了應用於數字之外,也可以用於能轉化為數字的字符串,例如
"4"/2
,"4"/"2"
,4/"2"
均可以算得正確結果2。 - 與JS不同,
==
要求兩邊的數據類型相同(受元表影響的除外),"5"==5
會得到false。對函數和表而言,==
要求雙方為同一對象(受元表影響的除外)。此外,數字nan與自己不相等,即0/0 == 0/0
會得到false。 - 兩個數字字符串之間使用大小比較將比較它們的ASCII值,例如
"72">"8"
會得到false,一個數字字符串與一個數字比較則會報錯。 - 只有string和number類型可以使用字符串連接
..
,且number類型只可以連接到其它字符串後方(在前方會被識別為格式錯誤),試圖連接其它類型均會報錯。例如"4"..2
會得到42,4.."2"
、""..true
等則會報錯。 - 取表長
#
實際上是從數字索引1開始統計,到中斷的地方結束,例如#{[0]=0, [1]=1,[2]=2,[4]=4}
會得到2,#{[4]=4, [5]=5,[6]=6}
會得到0,#{[1]=1, ["a"]=2}
會得到1。
布爾運算
- boolean類型進行布爾運算,在Lua中,只有nil和false視為false,其它所有值都是true,包括空字符串""、數值0,均視為true。
- Lua使用not、and、or三種布爾運算符,其中not優先級較高。
- not總是返回boolean類型。
not 0 -- false not "" -- false not nil -- true not false -- true
- and和or進行與、或的邏輯運算,但又不完全如此。
- and運算只有在符號的兩邊都為boolean類型的true時才輸出後面的那個true。
1 and 2 -- 2
- or運算只有在符號的兩邊都為boolean類型的false時輸出false,boolean類型的true時才輸出前面的那個true。
nil or false -- false 3 or 4 -- 3
- 但是,由於lua的獨特機制,使得它能運算不是boolean類型的類型,除了nil和false之外,都認為是true,也就是說lua的布爾運算實際上集成了空值判斷的功能,很多時候是不需要判斷空值的。這就意味着:
if(type(obj)=='nil')then return false end
,是沒有必要的,直接if not obj then return false end
就可以了。 - 因此,絕大多數情況下(在b不為false時),
a and b or c
可以認為是三目表達式(a? b : c)。 - 因此,如果要保證b一定為true,只要套上個花括號變成表,在最後的結果中獲得表中的第一個元素即可。
- 所以可以使用
(a and {b} or {c})[1]
來模擬三目表達式。
- 所以可以使用
- 邏輯運算符包括:>(大於)、<(小於)、>=(大於等於)、<=(小於等於)、==(等於)、~=(不等於)。
- >(大於)、<(小於)、>=(大於等於)、<=(小於等於)只能用來比較數字和字符串,並且只能在同類型之間進行比較。
- ==(等於)、~=(不等於)在比較非數值和非字符串時,比較的是對象。nil參與比較時,它只與nil相等。
賦值
- Lua使用=來進行賦值。
moegirl = 233 -- 全局變量可以直接賦值'''(在模塊內不應該賦值全局變量,包括函數)'''。 local moegirl = 233 -- 局部變量前面加local再賦值。 local abc = true -- 對局部變量abc進行賦值,值是boolean類型。 local aaa = 3 -- 對局部變量aaa進行賦值,值是number類型。 local aab = [[ 萌娘百科 Help:Lua ]] -- 多行字符串賦值。 local bbb['c'] = 'dd' -- 對一個局部變量bbb表中的『c』進行賦值,值是一個string類型。 local ccc[dd] = {'ee'} -- 對一個局部變量ccc表中,dd所代表的值所指向的表內元素進行賦值,值是table類型,只包含一個值是'ee'的string類型。 local xxx[callfunc(i)] = true -- 對一個局部變量xxx表中,使用一個函數返回值進行賦值,調用的函數是callfunc並傳入參數i,賦值的類型是boolean類型。 local func = function() return 0 end --[[ 對局部變量func進行賦值,值是一個匿名函數,效果類似於閉包。 你可以通過使用func()來調用這個函數,這個函數將返回函數體中return語句所返回的值,也就是0。 --]] local sets = {['init']='123'} -- 對局部變量sets進行賦值,值是table類型 local united = {['func']=function(flag) if flag then return 0 else return false end end} -- 當你學會了這些之後,就可以套娃了,請自己體會。 local aktable = {['atk']=100,['def']="yibai",[99]=true,[id]="mingzi"} -- 對局部變量aktable進行初始化賦值,要注意有無引號的區別。
- 特殊賦值
a = nil -- 清理掉對象a的值,即從內存中刪除變量a。就像它從沒出現過。(當且僅當一個變量不等於nil時,這個變量存在) a,b = b,a -- 把a和b兩個值交換。 a,b = 1,2 -- 分別給a、b傳入數值1、2。 a,b,c = 1,2 -- c沒有傳入值,因此為nil。 a,b,c = 1,2,3,4 -- 4沒有可接受的對象,因此會被忽略。 function() return 1,2,3 end -- 因為函數可以有多個返回值,所以你可以寫成這樣…… a,b,c = (function() return 1,2,3 end)() --(function() return 0 end)() -- 這是一個立即執行函數體,聲明好的同時會立即執行
類型轉換
- 轉換為字符串:tostring(number/boolean),把布爾類型和數值類型轉換為字符串類型
local str = tostring(true); -- 返回"true"。 local str = tostring(13); -- 返回"13"。 local str = tostring({}); -- 返回"table",Scibunto抹去了內存地址。
- 轉化為數字:
tonumber(string,number)
,接受2個參數,第一個為數字字符串(不能包含任何額外內容,否則計算會失敗),第二個可選參數,選擇要轉換前的進制,默認是十進制。
local num = tonumber("8"); -- 返回8。 local num = tonumber("AF",16); -- 從十六進制數返回十進制數175 。 local num = tonumber("0xA"); -- 返回十進制數10。 local num = tonumber("56.9"); -- 返回56.9。 local num = tonumber("00131"); -- 返回131。 local num = tonumber("123xxx"); -- 返回nil。 local num = tonumber("red"); -- 返回nil。 local num = tonumber("true"); -- 返回nil
條件判斷與循環
在Lua中條件判斷包括了if、else與elseif,循環包括了while、repeat...until、for。
if else elseif
語法:
if <condition1> then -- <statements1> elseif <condition2> then -- <statements2> elseif <condition3> then -- <statements3> ... elseif <condition-n> then -- <statements-n> else -- <statements> end
中間的elseif以及else均可省略,必須保存的是if ... then ... end
。
其中<condition(n)>為布爾值,為true時執行<statements(n)>然後直接跳到end,為false時跳到下一個elseif判斷<condition(n+1)>,如果所有if與elseif均為false,最終執行else下的<statements>。
while
語法:
while <condition> do --<statements> end
其中:<condition>為布爾值,為true時執行<statements>,不斷循環執行直到<condition>為false。
repeat...until
語法:
repeat statements until( condition )
參數與while
相同,但兩者不同的是,while
先判斷再循環,repeat...until
先循環再判斷。
for
數值for循環
語法:
for varName = start, stop[, step] do statements end
其中:
varName = start
初始化了局部變量,varName
自定,start
接受數字類型。此指令在循環過程中僅執行一次。初始化後第一次循環statements
。step
接受數字類型,可選,缺省值為1。步長,從第二次循環開始,每次執行就令varName = varName + step
。stop
接受數字類型,執行了varName = varName + n3
後,若varName ~= n2
則執行statements
。- ps:上列三個參數均「一次性執行」,即
start,stop,step
不會被statements
改變。
泛型for循環
語法:
array = {"a", "b", [4]="d", [3]="c", [6]=44,["str"]=1} for i,v in ipairs(array) do -- 循環語句 end -- i, v 會順序變為1 a, 2 b, 3 c, 4 d,會忽略中斷了的其它索引
array = {"a", "b", [4]="d", [3]="c", [6]=44,["str"]=1} for k, v in pairs(array) do -- 循環語句 end -- k, v 會以隨機順序變為 1 a, 2 b, 3 c, 4 d, 6 44, "str" 1, 各一次,包括了所有索引
i與k是表索引,v是對應索引的元素值。ipairs
/pairs
是Lua提供的迭代器函數,ipairs用來迭代數組(只考慮從1開始的連續正整數索引),pairs用來迭代字典,兩個迭代器均返回包含了鍵值對的表。ipairs會有序排列,而pairs是無序的。
創建和調用模塊
在萌百,所有的模塊都放置在「模块:XXXX
這樣的形式進行命名。
最基本的模塊
你可以在模塊:Sandbox的子頁面創建自己的模塊沙盒,最好建成模块:Sandbox/<用户名>/<子页名>
的形式,並且讓子頁名符合它的用途,例如模塊:Sandbox/示例用戶/hello1。(這個頁面已經有示例代碼了)
在你建好的沙盒頁面中加入如下代碼:
local p = {} function p.main( frame ) -- 模塊中被調用的函數名,被#invoke直接調用的函數可以有一個參數,接收框架對象 return "Hello, world!" -- 模塊函數輸出 end return p
之後在需要調用的頁面(通常是模板)使用:{{#invoke:<模块名称>|<函数名>}}
(模塊名稱不需要「模塊」二字作前綴),在剛才的例子中就是{{#invoke:Sandbox/示例用户/hello1|main}}
,則將會顯示:Hello, world!。
對這個模塊的說明如下:
local p = {}
:p是一個table類,用來充當包( ),其中存放需要被#invoke:
直接調用的函數。包一般都在開頭定義。封包是個好習慣。function p.main( frame )
:用於定義需要被調用的函數,其中frame是可選的框架對象。return "Hello, world!"
:直接調用的函數必須有返回值作為模塊內容。return p
:返回你的包,讓#invoke能夠識別裡面的函數。
接收來自模塊調用的參數
上方我們已經講到了,被#invoke直接調用的函數可以獲得一個框架對象。框架對象有很多用途,例如,可以獲得來自模塊調用的參數。
獲取參數的方法是frame.args[<参数名>]
,如果想獲取的參數不存在,則會返回nil
。
如果你想讓Hello world更花里胡哨一點,你可以把模塊代碼改成這樣:
local p = {} function p.main( frame ) -- 模塊中被調用的函數名,被#invoke直接調用的函數可以有一個參數,接收框架對象 return frame.args["name"] .. "say: Hello, world!" ..frame.args[1] -- 將參數name拼接到前面,參數1拼接到輸出後面 end return p
然後添加代碼{{#invoke:Sandbox/示例用户/hello2|main|" Hi!"|name=示例用户}}
,則將會顯示:示例用戶say: Hello, world!" Hi!"。
同樣地,你也可以使用{{#invoke:模块名称|函数名|位置参数1|位置参数2...|参数名a=名字参数a|...}}
調用其他的模塊,就像用模板一樣使用模塊。當然,和模塊的問題一樣,位置參數不能包含等號「=」否則會被當成名字參數,這時你也可以類似模塊地使用1=内容
來解決這個問題。
易錯提示:如果你的模塊需要包含參數,記得在模板文檔里寫出來,因為如果他人不傳入參數,你在調用這一參數時就會得到nil,而nil不能使用字符串連接。如果是可選的參數,請使用if語句判斷可選的參數是否存在。
通過預處理解決wikitext渲染問題
你可能想通過下面的模塊代碼來顯示一個沙盒模板:
local p = {} function p.main( frame ) return "[[User:示例用戶]]{{用戶_沙盒}}" end return p
但是,實際上,這一模塊代碼運行的結果是:User:示例用户{{用户_沙盒}}
,因為返回的結果可以含有wikitext但不會被預處理,因此不會展開模板、解析器函數等特殊語法。
還記得上面說過的框架嗎?除了傳參,frame還提供了很多有用的函數,例如wikitext預處理函數,frame:preprocess(<text>)
,這個函數會返回將text整個進行wikitext預處理後的結果,加載出模板等wiki要素。
那麼,代碼可以改成下面這樣:
local p = {} function p.main( frame ) return frame:preprocess("[[User:示例用戶]]{{用戶_沙盒}}") end return p
運行,得到了預期的結果。
當然,這裡只是一個例子,適用於生成內容比較多和複雜,每次加載容易導致代碼膨脹的情況。如果明確知道哪裡會使用模板,也可以使用展開模板的預處理函數,frame:expandTemplate(<tem>)
。於是,上面代碼的第四行可以寫成:
return "[[User:示例用戶]]" .. frame:expandTemplate {title = '用戶_沙盒'}
|
另請參閱
- Lua參考手冊:擁有比較全面的中文語法參考,強烈建議閱讀或留作參考手冊。
- Lua教程(英文)
- Scribunto的Lua參考手冊:Scribunto中的Lua語法、函數與類庫參考。