置顶公告:【置顶】关于临时开启评论区所有功能的公告(2022.10.22) | 【置顶】关于本站Widget恢复使用的公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。
本頁使用了標題或全文手工轉換

幫助: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 = '用戶_沙盒'}


另請參閱

注釋