Help:Lua

来自滚动的天空Wiki

Lua是轻量的动态类型语言。MediaWiki的Scribunto扩展允许页面调用Lua代码,其中Lua代码放置在“Module:”命名空间下。MediaWiki使用的Lua版本为5.1,但进行了一些修改,并移植了后面的版本的一些功能。

本帮助页面将简单地介绍Lua的语法、用法及特点等,但不会特别细致,建议有一定编程基础的人阅读。请注意,本页提及了一些MediaWiki及其扩展提供的库,这些在MediaWiki之外的标准运行环境中是不直接具备的。本页还会提及本Wiki中的(包括搬运的)部分模块,不一定适用于其他基于MediaWiki的网站。零基础的用户如需学习Lua语言,可在网上自行搜索入门教程。

变量[编辑]

Lua的变量分为全局变量局部变量。局部变量应使用local关键字进行声明。同其他语言一样,我们使用等号进行赋值。在没有声明局部变量的情况下进行赋值,则默认赋值给全局变量。例如,

a = 1 -- 声明一个全局变量
do -- 一个作用域
    local b = 1 -- 声明一个局部变量
    b = "2" -- 修改一个局部变量
end
print(b) -- 局部变量超出作用域会失效,所以为nil

注意:

  1. 上面这个例子中,只是标准的开发环境,因此使用了局部变量,并使用print函数以将值输出到控制台。MediaWiki不同于标准的开发环境,不应当修改局部变量,也不应该使用print函数。
  2. 在MediaWiki的模块中,不应该创建或修改任何全局变量,而应该定义局部变量,并在模块末尾使用return语句返回你需要返回的值。

局部变量也可以先声明再赋值。没有赋值的变量,以及不存在的变量,其值都为nil,使用这些变量并不会抛出错误,但仍需留意。

变量可以多重赋值,例如a,b = 1,2是有效的,但不支持a=b=1这样的语法。等号右边也可以是返回多个值的函数(如unpack或string.find)。如果等号右边的值的个数超过左边,则多余的会被忽略;如果少于左边的,则缺少的会赋值为nil。注意Lua没有元组(tuple)。

变量名称不能以数字开头,不能为保留字,也不可以有中文。

数据类型[编辑]

Lua是动态类型语言,变量没有类型,只有值有。声明局部变量、函数参数等,均无需声明类型。

基本类型介绍[编辑]

在Scribunto扩展中,Lua只有6个基本的类型:

nil[编辑]

空值(nil)表示这个值不存在。尝试访问不存在的变量或表中不存在的字段的值时,会得到nil。函数调用中未提供的参数的值都会被视为nil。很多函数也会在失败时返回nil而不是抛出错误,例如tonumber函数在无法转换成数字时会返回nil。

boolean[编辑]

布尔值(boolean)只有true或false。将其他数据类型转化为布尔值时,只有false和nil视为false,其他的一律视为true。

string[编辑]

字符串(string)为一系列的8比特字节;这取决于应用程序以哪种特定的编码来解析。字符串使用单引号或双引号表示,单引号和双引号无区别,也不会区分“字符”与“字符串”。字符串可以转义。

字符串也可以使用长括号来定义。长括号之间可以夹杂着0个或更多个等号(两边的等号要等量)。例如,"abc"'abc'[[abc]][==[abc]==]都是等价的。注意:由长括号定义的字符串可以直接换行,且不会处理转义序列

Lua标准库中的string库可用于处理字符串,MediaWiki库中的mw.textmw.ustring则提供了更多的处理方法。

number[编辑]

Lua中的数字(number)均为双精度浮点数,-9007199254740992到9007199254740992之间的整数都会准确表达,更大的数和带有小数部分的数将会受到舍入的误差。

数字可以用点(.)来表示小数,例如123456.78。数字也可以用不带空格的科学计数法,例如1.23e-10、123.45e20或者1.23E5。也可以用16进制表示整数,方法就是以0x开头,例如0x3A。

1/0会得到inf,0/0会得到nan。inf和nan都可以正确地储存,但是并无直接的文字表示方法(无穷大可以使用math.huge表示)。使用时,应当留意inf和nan可能带来的潜在错误。nan不等于它自己,也就是说,nan==nan会返回false

Lua标准库中的math库可用于处理数字。

table[编辑]

表(table)是复合数据类型,是Lua中最复杂的一种数据类型,很像JavaScript中的对象(object)或者Python中的字典(dict)。在Lua中,表同时充当了映射、列表、集合的功能。

表由多个(或者零个)字段组成,每个字段都有键(key)值(value),其中键可以是除nil或nan外的任何值。不存在的字段可视为值为nil。键是唯一的,值不是唯一的。若键为表或函数,则判断其唯一性是根据其指针。

声明一个表的方法是:在一组花括号中,使用“[键]=值”的方式表示一个字段,多个字段使用逗号或分号隔开。如果键是字符串的字面量,且符合变量名称要求,则可以省略引号并使用“键=值”的方式表示。例如,{a=1,b="2"}中,"a"和"b"都是键,1和"2"是对应的值,它也可以表示为{["a"]=1,["b"]=2}

声明一个表时,键是可以省略的。例如,{"a","b","c","d","e"}等价于{[1]="a",[2]="b",[3]="c",[4]="d",[5]="e"},这样的表称为数组(array)序列(sequence)

对于表t和数字b,如果满足(border == 0 or t[border] ~= nil) and t[border + 1] == nil,则称b为表t的一个边界(border)。数组只有一个边界,有多个边界的表不是严格的数组,称为稀疏数组(sparse array),对其进行取长运算会返回其中一个边界,存在不确定性。例如,{nil, 2, 3, nil, nil, 6}是个稀疏数组,其边界为0、3、6,因此其长度可能是0、3、6中的某一个值。

也可以将表用作集合(set),如{a=true, b=true, c=true},因为键是不能重复的,所以可以通过判断键对应的值是否为nil来判断集合是否包含某个元素。

表的字段访问:可以通过“变量名称[键]”或“变量名称.键”的方式来访问表中的键对应的值,也就是说,找到一个键为指定的值(表、函数等需要是相同指针,字符串、数字等只需要值相等即可)的字段,并返回其值。例如,如果t是一个表,则t[true]表示布尔值键true对应的值,t.abct["abc"]表示字符串键"abc"对应的值。如果这个值不存在,显然会返回nil。

表的字段赋值:可以通过赋值语句修改表中的键对应的值。可以给表添加字段、修改现有字段的值或者清除一个字段。例如,t[true] = "q"表示将键true对应的值修改为字符串"q",t["abc"] = nilt.abc = nil表示将键"abc"对应的值修改为nil(相当于删除这个字段,如果其原来的值不是nil的话)。注意:键可以是除nil或nan外的任意值,可以是函数、表,甚至是表自身,也就是说t[print] = 1t[t] = t这样的用法都是有效的,而t[0/0] = 1则是无效的(0/0相当于nan,参考#number)。

表不会复制。例如如果t是一个表,则赋值语句s = t不会将t的内容复制到新的表s中,而是会让变量s和t的指针都指向同一个表,对表s的修改和对t的修改是同步、等价的。

Lua中,判断两个表是否相等的标准是两个表是否指向同一个指针,而不是内容是否一致。例如,在上面这一段的例子中,s == t就会返回true,而{} == {}会返回false。

Lua标准库中的table库可用于处理表,其中大部分函数都是用于处理数组(表中的数组部分)的。此外,本Wiki也搬运了元模块TableTools,可用于对表进行一些高级的处理。

此外,表的索引、赋值、判断相等等操作都可以受到#元表影响。

function[编辑]

Lua中的函数(function)闭包,这意味着它们维护对它们声明的作用域的引用,并可以访问和操作该作用域中的变量。

定义一个函数的基本语法是:

function 函数名称(参数列表)
    语句……
end

函数名称可以是表的字段,例如t.abc。或者,你也可在function前面加上local关键字,表示该函数是个局部变量。参数是可选的,且视为局部变量。定义函数时,可以使用...表示不定数量的参数(参见select)。

函数内可以使用return语句,表示函数返回一个值,return语句后面应当紧跟代码块的结束。如果没有执行return语句,函数不会返回值,可以认为返回了nil。

匿名函数的表示方法为:

function (参数列表)
    语句……
end

匿名函数与普通的函数无区别,例如,下面两个语句是等价的:

  • local function f(x) return x+1 end
  • local f = function (x) return x+1 end

注意:通过匿名函数赋值的方式定义的函数,如果需要在函数内调用函数自身,应当先声明再赋值。例如:

  • 正确:local function f(x) return x<5 and f(x+1) or x end
  • 错误:local f = function (x) return x<5 and f(x+1) or x end(这里的f(x+1)中的f视为全局变量)
  • 正确:local f; f = function (x) return x<5 and f(x+1) or x end

调用函数的语法为:

函数名称(参数列表)

函数的调用可以单独作为语句,也可以作为表达式。此外,如果调用函数时(无论作为语句还是表达式),只有一个参数,且该参数为字符串或表的字面量,则括号可以省略,例如:

  • print "abc"等价于print("abc")
  • print{1,2,3}等价于print({1,2,3})

Lua的函数不支持方法重载或多重派发,也不能够直接给参数设置默认值。调用函数时,缺失的参数视为nil[1],多余的参数会被忽略。一种常见的间接设置默认值的方式是,检查某个参数的值是否为nil,如果为nil则赋值为默认值。

方法[编辑]

函数作为表的字段时可以作为方法(method)调用,会自动将表自身作为第一个参数。方法是面向对象的一种体现。

Lua并没有严格意义上的方法,但Lua提供了下面两种语法糖:

  • 声明方法:function table:method(args)等价于function table.method(self,args)
  • 调用方法:table:method("foo")等价于table.method(table,"foo")

此外,字符串也可以面向对象使用(参见#元表)。

类型转换[编辑]

Lua的数据类型可以相互转换。

tonumber函数可以将字符串转化为数字,例如tonumber("123")会返回123。不能转化为数字的,则会转化为nil。对字符串进行算数运算时会尝试自动转换成数字。

tostring函数可以将任意值转化为字符串。表会转化为"table"(受#元表影响的除外),函数会转化为"function"。数字与字符串连接、数字之间连接时会自动转换成字符串。

type函数可以返回值的数据类型,用字符串表示。例如type(1/0)会返回"string"type(true)会返回"boolean"

运算[编辑]

和大多数语言一样,Lua支持算术运算。其中,除以零不会出错,会得到inf或nan。模运算的定义为a % b == a - math.floor( a / b ) * b,例如-4%3会返回2而非-1。幂运算的符号为^。对字符串进行算数运算,会尝试使用tonumber函数转化为数字,如无法转换则会抛出错误。

关系运算包括==~=<><=>=,运算结果一定是布尔值。对于==,不同类型的数据比较为false。注意表或函数的比较会比较指针,当两个值指向同一个表或函数时才会返回true,除非受元表影响。

逻辑运算包括andornot。注意:逻辑运算的结果不一定是布尔值,且进行逻辑运算时,并不会提前把所有值算出来,例如3 or error()的值为3,不会抛出错误。可以使用逻辑运算来组成“三元运算”,例如a and b or c相当于JavaScript、Julia或Java中的a ? b : c或Python中的b if a else c,前提是b不能为false。

对两个字符串进行连接运算的方式是a .. b,而非a + ba * b。可以对数字进行连接,连接时会尝试转化为字符串。注意:Lua的字符串是不可变的(immutable),而且Lua不提供任何类型的“字符串构造器(string builder)”,所以反复进行a = a .. b会多次创建新的字符串,并最终将旧字符串作为垃圾收集。如果许多字符串都需要连接,则应使用string.format,或将所有的字符串添加到一个数组然后最后使用table.concat()连接。

#a可以获取字符串或者数组a的长度。对于字符串,会获取其字节数,类似于string.len,获取字符数可以使用函数mw.ustring.len。对于表,只会获取其数组部分的长度,其他部分的字段一律忽略(对于{1, 2, nil, 4}这样的稀疏数组,其长度是其边界中的某一个,例如可能为2或4[2]),如需获取表中所有字段的个数,应使用一次for循环,或导入模块:TableTools并使用其中的size函数。

对于表,上述所有运算都可以通过#元表控制。

优先级[编辑]

Lua中,各运算的优先级如下:

  1. ^
  2. not # - (负号)
  3. * / %
  4. + - (减号)
  5. ..
  6. < > <= >= ~= ==
  7. and
  8. or

此外,对于同级运算,a/b/c相当于(a/b)/c,a^b^c相当于a^(b^c)。

流程控制[编辑]

Lua和大多数语言一样,具有条件语句和循环语句,没有switch——case语句。

条件语句[编辑]

条件语句的基本语法是:

if 条件1 then
    语句块1
elseif 条件2 then
    语句块2
else
    语句块3
end

当条件1成立时,执行语句块1,否则当条件2成立时,执行语句块2,否则执行语句块3。elseif和else的结构都是可选的,当然elseif可以有多个。

循环语句[编辑]

while循环的基本语法:

while 条件 do
    语句块
end

repeat循环的基本语法:

repeat
    语句块
until 条件

在repeat循环中,语句块至少执行一次。条件可以使用语句块中的局部变量。

for循环的基本语法:

for 名称 = 表达式1, 表达式2, 表达式3 do
    语句块
end

这种循环会声明一个局部变量,由“表达式1”的值开始,以“表达式3”的值为步长,循环到“表达式2”的值。表达式3可以省略,默认为1,但不能为false或nil。

for语句除了用来循环之外,还可以进行迭代。语法为:

for 变量 in 表达式 do
    语句块
end

变量、表达式可以有多个。通常表达式是返回3个值的一个函数调用。如果迭代器函数可以被写入,那么只取决于传入的参数,这会更加有效。否则,在Lua中编程建议返回闭包比返回表更好,因为静态变量在每次迭代更新其成员。

在上述for、repeat和两种for循环中,均可使用break关键字来立即跳出循环,break后面内容不再执行。Lua没有continue语句[3],但可以调用包含循环代码块的函数,用return来代替。

常用的迭代函数有pairsipairsstring.gmatchmw.text.gsplit等。例如,对于一个普通的表t,我们可以使用这样的方式迭代其所有的键和值:

for k,v in pairs(t) do
    含有k和v的语句块
end

元表[编辑]

每个表都可以设置一个元表(metatable),用来修改特定的行为。setmetatable(t, m)可以用来将表t的元表设为m(m也可以为nil,表示取消元表设置)[4],该函数自身也会返回修改后的表;getmetatable(t)可以用来获取表t的元表。

如果元表具有__index字段,则当通过指定的键访问表的字段的时候,如果该字段不存在,则会使用元表__index字段。如果__index是表,则会访问这个表的字段。如果__index是函数,则会调用这个函数。例如,访问t[k]时,如果表本身的这个字段不存在,则会尝试访问__index[k](如果__index是表)或__index(t, k)(如果__index是函数)。rawget函数可绕过这个元方法。

__newindex字段则用于修改给表的字段赋值的行为。赋值给表的一个不存在的键时,如果__newindex字段的值是表,则会在该表中重复赋值。如果__newindex的值是函数,则会调用这个函数。例如,执行t[k] = v时,表本身不存在这个字段,则会执行__newindex[k] = v(如果__newindex是表)或__newindex(t, k, v)(如果__newindex是函数)。rawset函数可绕过这个元方法。

__call字段可以让表能像函数那样调用,值必须是函数。如果这个元表字段存在,则调用表t(...)时,会像__call(t, ...)这样调用,而不是抛出无法调用表的错误。

__metatable字段用于保护元表,防止其被访问或修改。如果该字段存在,当对表进行getmetatable时,不会返回真正的元表,而是返回该字段的值[5],如果进行setmetatable,则会抛出错误[4]

__mode字段用于使表保持弱引用,详见原版参考手册。

元表的一些其他字段用法如下,其值一般都是函数,函数的第一个参数是表自身。例如,如果元表__add字段存在,则对表t计算t + a时,则会调用__add(t, a),而不是抛出无法对表进行算术运算的错误。

字段名称和用法 对应操作
__add(self, v) self + v
__sub(self, v) self - v
__mul(self, v) self * v
__div(self, v) self / v
__mod(self, v) self % v
__pow(self, v) self ^ v
__unm(self) -self
__concat(self, v) self .. v
__len(self) #self
__eq(self, v) self == v
__lt(self, v) self < v
__le(self, v) self <= v
__pairs(self)[6] pairs(self)
__ipairs(self)[6] ipairs(self)
__tostring(self) tostring(self)

所有字符串共用同一个元表,该元表的__index字段等同于string库的一个副本,以使得字符串能够作为对象使用。比如,string.match("label123","^label%d")等价于("label123"):match("^label%d")。注意这个元表无法通过setmetatable函数获得[7]

注释[编辑]

  1. 如果函数有不定数量的参数,则这些参数的数量是准确的;此外一些Lua内置函数也不会补充参数,例如tostring()会抛出错误,而不是理解为tostring(nil)
  2. 需要留意这种情况,因为会导致一些不确定性。
  3. Lua 5.2以上的版本具有标签功能,可达到类似于continue的效果,但MediaWiki使用的Lua 5.1,是不支持标签的。
  4. 4.0 4.1 原版Lua中,debug.setmetatable函数可以强制设置一个值(不限于表)的元表,且不受__metatable元方法影响。MediaWiki删除了此函数。
  5. 原版Lua的debug.getmetatable函数可以强制获取任意值的元表,不受__metatable元方法影响。MediaWiki删除了此函数。
  6. 6.0 6.1 原版Lua 5.1没有__pairs和__ipairs的功能(是在后面的版本才加入的),不过Scribunto扩展在Lua中移植了该功能,因此你可以在MediaWiki中使用此元表。
  7. 原版Lua可以获取任意值的元表,只要元表存在。