2023年政策修订增补工作正在进行中,欢迎参与!
  • Moegirl.ICU:萌娘百科流亡社群 581077156(QQ),欢迎对萌娘百科运营感到失望的编辑者加入
  • Moegirl.ICU:账号认领正在试运行,有意者请参照账号认领流程
本頁使用了標題或全文手工轉換

說明:Lua

萌娘百科,萬物皆可萌的百科全書!轉載請標註來源頁面的網頁連結,並聲明引自萌娘百科。內容不可商用。
跳至導覽 跳至搜尋
Commons-emblem-notice.svg
這個頁面「Help:Lua」是萌娘百科的幫助文件
  • 本文用於介紹萌娘百科中一些特定功能的操作方法;
  • 本文僅是一篇論述,不屬於方針或指引。如果本指南與相關方針或指引發生衝突或存在不一致的情況,請以方針或指引的條文為準。

Lua是在萌娘百科命名空間為「模組」中所支持的一種手稿語言,可以提供相比模板更為強大的功能和解析效率。在MediaWiki軟件中,通過擴充Scribunto實現。

模組是一種指令碼處理流程,常用做各種複雜的函式庫或資料庫,目前在萌百上支持的Lua是5.1版本(但支持通過元表修改pairs和ipairs函式的行為)。與Widget(使用JavaScript)不同的是,模組(使用Lua)中的邏輯是在伺服端處理,用戶拿到的是最終結果。

基本語法

Icon-info.png
您可使用Lua在線直譯器或者在模組沙盒下新建子頁面以在線練習、測試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是無序的。

建立和呼叫模組

在萌百,所有的模組都放置在「模組Module命名空間下。也就是說,建立的模組應當以模块: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類,用來充當package,其中存放需要被#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 = '用戶_沙盒'}


另請參閱

註釋