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

Help: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 = '用户_沙盒'}


另请参阅

注释