帮助: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语法、函数与类库参考。