在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
作者: Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes Copyright © 2003 Tecgraf, PUC-Rio. All rights reserved. 译者:ShiningRay Nicholas @ NirvanaStudio 给予支持Lua是一种为支持有数据描述机制的一般过程式编程语言而设计的扩展编程语言。它同样可以对面向对象语言、函数式程序设计(Functional Programming,如Lisp)以及数据驱动编程(data-driven programming)提供很好的支持。它的目标是被用作一种强大的、轻型的配置语言。Lua目前已经被实现为一个扩展库,是用clean C (ANSI C/C++的一个通用子集)编写的。 作为一个扩展语言,Lua没有"Main"函数的概念:它仅仅是嵌入一个宿主程序进行工作,可以称之为 嵌入式编程 或者简单的说是 宿主编程。这个宿主程序可以调用函数来执行Lua的代码片断,可以设置和读取Lua的变量,可以注册C函数让Lua代码调用。Lua的能力可以扩展到更大范围,在不同的领域内,这样就在同样的语法框架下创建了你自定义的编程语言。 Lua的发行版包括一个独立的嵌入式程序, Lua是自由软件,通常不提供任何担保,如它的版权说明中叙述的那样。 手册中描述的实现在Lua的官方网站可以找到, 如果需要知道Lua设计背后的一些决定和讨论,可以参考以下论文,它们都可以在Lua的网站上找到。
Lua在葡萄牙语中的意思是“月亮”,发音是 LOO-ah。 2 - 语言这一章将描述Lua的词法、语法和语义结构。换句话说,这一章会讲什么标记是合法的,他们是如何组合的,以及他们的组合是什么含义。 语言结构会使用常用的扩展BNF范式来解释,如{a} 表示0或多个a, [a] 表示a是可选的(0个或1个)。非终端字体(不能显示的)用 斜体表示,关键字是粗体,其他终端符号用 2.1 - 词法约定Lua中的标识符(Identifiers)可以是任意的数字、字符和下划线“_”,但不能以数字开头。这条规则符合大多数编程语言中的标识符的定义。(字符的具体定义要根据系统的地区设置:任何区域设置可以认同的字母表中的字母都可以用在标识符中。) 下面的关键字(keywords)为保留关键字不可以作为标识符出现: and break do else elseif end false for function if in local nil not or repeat return then true until while Lua对大小写敏感: 下面的字符(串)是其他的一些标记: + - * / ^ = ~= <= >= < > == ( ) { } [ ] ; : , . .. ... 字符串(Literal strings) 以单引号或者双引号定界,同时可以包含以下C语言风格的转义字符:
另外,一个 ` 字符串也可以用双方括号来定界 (1) "alo\n123\"" (2) '\97lo\10\04923"' (3) [[alo 123"]] (4) [[ alo 123"]] 数值常量(Numerical constants) 可以有一个可选的底数部分和一个可选的指数部分。以下是有效的数值常量: 3 3.0 3.1416 314.16e-2 0.31416E1 注释(Comments) 可以在任何地方出现,必须在最前面加上双减号 ( 为了方便起见,文件的第一行如果是以 2.2 - 值和类型Lua是一种 动态类型语言(dynamically typed language)。这意味着变量是没有类型的;只有值才有。语言中没有类型定义。所有的值都包含他自身的类型。 Lua中有八种基本类型:nil, boolean, number, string, function, userdata, thread 和 table。 Nil 空类型只对应 nil值,他的属性和其他任何值都有区别;通常它代表没有有效的值。 Boolean 布尔类型有两种不同的值 false and true。在Lua中, nil and false 代表成假条件;其他任何值都代表成真条件。 Number 数字类型表示实数(双精度浮点数)。(构建Lua解释器时也可以很容易地用其他内部的表示方式表示数字,如单精度浮点数或者长整型)。 String 字符串类型表示一个字符的序列。Lua 字符串可以包含8位字符,包括嵌入的 ( 函数是Lua中的 第一类值(first-class values)。也就是说函数可以保存在变量中,当作参数传递给其他函数,或者被当作结果返回。Lua可以调用(和处理)Lua写的函数和C写的函数 (见 2.5.7)。 用户数据类型(userdata) 提供了让任意C数据储存在Lua变量中的功能。这种类型直接对应着一块内存,Lua中也没有任何预先定义的操作,除了赋值和一致性比较。然而,通过使用 元表(metatables),程序员可以定义处理userdata的操作。(见 2.8)。 Userdata 值不能在Lua中建立或者修改,只能通过 C API。这保证了宿主程序的数据完整性。 线程(thread) 类型代表了相互独立的执行线程,用来实现同步程序。 表(table) 类型实现了联合数组,也就是说,数组不仅可以使用数字,还能使用其他的值(除了 nil)。 而且,tables 可以是 互异的(heterogeneous),他们可以保存任何类型的值(除了 nil)。 Tables 是Lua中唯一的数据结构机制;他们可以用来表示一般数组,特征表,集合,记录,图,树等等。如果要表示记录,Lua使用字段名作为索引。语言支持 就像索引一样,表字段的值也可以是任何类型(除了 nil)。特别需要注意地是,由于函数是第一型的值,表字段也可以包含函数。这样表也可以支持 方法(methods) (见 2.5.8)。 表,函数,和用户数据类型的值都是 对象(objects):变量不会包含他们的实际值,只是一个他们的引用(references)。 赋值,参数传递和函数返回只是操作这些值的引用,这些操作不会暗含任何拷贝。 库函数 2.2.1 - 类型转换Lua提供运行时的数字和字符串值得自动转换。任何对字符串的算术操作都会现尝试把字符串转换成数字,使用一般规则转换。反过来,当一个数值用在需要字符串的地方时,数字会自动转换成字符串,遵循一种合理的格式。如果要指定数值如何转换成字符串,请使用字符串库中的 2.3 - 变量变量是储存值的地方。Lua中有三种不同的变量:全局变量,局部变量和表字段。 一个名称可以表示全局变量或局部变量(或者一个函数的正式参数,一种局部变量的特殊形式): var ::= Name Lua假设变量是全局变量,除非明确地用local进行声明 (见 2.4.7)。局部变量有 词义范围(lexically scoped):局部变量可以被在它们范围内的函数自由访问 (见 2.6)。 在变量第一次赋值之前,它的值是 nil。 方括号用于对表进行检索: var ::= prefixexp `[´ exp `]´ 第一个表达式 (prefixexp)结果必须是表;第二个表达式 (exp) 识别表中一个特定条目。给出表的表达式有一个限制语法;详细见 2.5。
var ::= prefixexp `.´ Name 访问全局变量和表字段的实质可以通过元表进行改变。对索引变量 所有的全局变量存在一个普?ǖ腖ua表中,称之为 环境变量表(environment tables) 或简称 环境(environments)。由C写的并导入到Lua中的函数 (C 函数) 全部共享一个通用 全局环境(global environment)。Lua写的每个函数 (a Lua 函数) 都有一个它自己的环境的引用,这样这个函数中的所有的全局变量都会指向这个环境变量表。当新创建一个函数时,它会继承创建它的函数的环境。要改变或者获得Lua函数的环境表,可以调用 访问全局变量 gettable_event(_env, "x")
2.4 - 语句Lua支持一种很通俗的语句集,和Pascal或者C中的很相似。他包括赋值,控制结构,过程调用,表构造和变量声明。 2.4.1 - 语句段Lua执行的最小单元称之为一个 段(chunk)。一段语句就是简单的语句的序列,以顺序执行。每一个语句后面都可以加上一个分号(可选): chunk ::= {stat [`;´]} Lua将语句段作为一个匿名函数 (见 2.5.8) 的本体进行处理。这样,语句段可以定义局部变量或者返回值。 一段语句可以储存在文件内或者宿主程序的一个字符串中。当语句段被执行时,他首先被预编译成虚拟机使用的字节码,然后虚拟机用一个解释器执行被编译的代码。 语句段也可以被预编译为二进制代码;详情参看 2.4.2 - 语句块一个语句块是一系列语句;从语句构成上来看,语句块等同于语句段: block ::= chunk 一个语句块可以明确定界来替换单个语句: stat ::= do block end 显式语句块可以很好地控制变量的声明范围。显示语句块有时也常会在另一个语句块的中间添加 return 或 break 语句 (见 2.4.4)。 2.4.3 - 赋值Lua允许多重赋值。因此,赋值的语法定义为:等号左边是一个变量表,右边是一个表达式表。两边的表中的元素都用逗号分隔开来: stat ::= varlist1 `=´ explist1 varlist1 ::= var {`,´ var} explist1 ::= exp {`,´ exp} 在赋值之前,值的表长度会被 调整 为和变量的表一样。如果值比需要的多,多出的值就会被扔掉。如果值的数量不够,就会用足够多的 nil 来填充表直到满足数量要求。如果表达式表以一个函数调用结束,那么在赋值之前,函数返回的所有的值都会添加到值的表中(除非把函数调用放在括号里面;见 2.5)。 赋值语句首先计算出所有的表达式,然后才会执行赋值,所以代码: i = 3 i, a[i] = i+1, 20 设置 x, y = y, x 可以交换 对全局变量和表字段的赋值可以看作是通过元表进行的。对一个索引变量的赋值 对全局变量的赋值 settable_event(_env, "x", val)
2.4.4 - 控制结构控制结构 if, while 和 repeat 具有通用的含义和类似的语法: stat ::= while exp do block end stat ::= repeat block until exp stat ::= if exp then block {elseif exp then block} [else block] end 控制结构的条件表达式 exp 可以返回任意值。false 和 nil 都表示假。所有其他的值都认为是真(特别要说明的:数字0和空字符串也表示真)。 语句 return 用来从函数或者是语句段中返回一个值。函数和语句段都可以返回多个值,所以 return 语句的语法为: stat ::= return [explist1] break 语句可以用来终止while, repeat 或者 for 循环的执行,直接跳到循环后面的语句。 stat ::= break break 结束最里面的一个循环。 由于语法的原因, return 和 break 语句只能作为语句块的 最后一个 语句。如果确实需要在语句块的中间使用 return 或者 break,需要使用一个显示语句块: ` 2.4.5 - For 语句for 语句有两种形式:数值形式和一般形式。 数值形式的 for 循环根据一个控制变量用算术过程重复一语句块。语法如下: stat ::= for Name `=´ exp `,´ exp [`,´ exp] do block end block 语句块根据 name 以第一个 exp 的值开始,直到他以第三个 exp 为步长达到了第二个 exp。一个这样的 for 语句: for var = e1, e2, e3 do block end 等价于一下代码: do local var, _limit, _step = tonumber(e1), tonumber(e2), tonumber(e3) if not (var and _limit and _step) then error() end while (_step>0 and var<=_limit) or (_step<=0 and var>=_limit) do block var = var + _step end end 注意:
for 的语句的一般形式是操作于函数之上的,称之为迭代器(iterators)。每一个迭代过程,它调用迭代函数来产生新的值,直到新的值是 nil 。一般形式 for 循环有如下语法: stat ::= for Name {`,´ Name} in explist1 do block end 一个这样的 for 语句 for var_1, ..., var_n in explist do block end 等同于以下代码: do local _f, _s, var_1 = explist local var_2, ... , var_n while true do var_1, ..., var_n = _f(_s, var_1) if var_1 == nil then break end block end end 注意:
2.4.6 - 语句式函数调用如果要忽略可能的影响,函数调用可以按照语句执行: stat ::= functioncall I在这里,所有的返回值都会被忽略。函数调用将在 2.5.7 详细解释。 2.4.7 - 局部变量声明局部变量可以在语句块中任何地方声明。声明时也可以添加一个初始赋值: stat ::= local namelist [`=´ explist1] namelist ::= Name {`,´ Name} 如果出现初始赋值,他的语法和多重赋值语句一样(见 2.4.3)。否则,所有的变量都会初始化为 nil。 一个语句段也是一个语句块(见 2.4.1),所以语句段之内的任何显式语句块之外也可以声明局部变量。这种局部变量在语句段结束就会销毁。 2.5 - 表达式Lua中有以下几种基本表达式: exp ::= prefixexp exp ::= nil | false | true exp ::= Number exp ::= Literal exp ::= function exp ::= tableconstructor prefixexp ::= var | functioncall | `(´ exp `)´ 数字和字符串已经在 2.1 中解释;变量在 2.3 中解释;函数定义在 2.5.8;函数调用在 2.5.7;表构造器在 2.5.6。 一个用括号括起的表达式只会返回一个值。这样, 表达式也可以使用各种算术运算符,关系运算符和逻辑运算符,下面几节就会讲到。 2.5.1 - 算术运算符Lua支持常见的几种运算符:二元 2.5.2 - 关系运算符Lua中的关系运算符有 == ~= < > <= >= 这些运算只会产生 false 或 true值。 等于 ( 你可以用"eq"元方法改变Lua比较表的方式(见 2.8)。 2.2.1 的转换规则 不适用 于相等比较。这样," 而操作符 T操作符的执行顺序如下。如果两个参数都是数字,那么它们就直接进行比较。如果,两个参数都是字符串,那么它们的值会根据当前的区域设置进行比较。否则,Lua尝试调用"lt"或者 "le" 元方法(见 2.8)。 2.5.3 - 逻辑运算符Lua中的逻辑运算符是: and or not 和控制结构一样(见 2.4.4),所有的逻辑操作符认为 false 和 nil 都?羌伲渌闹刀际钦妗? not 操作符总是返回 false 或 true。 合取运算 and 如果第一个参数是 false 或者 nil 则返回第一个参数;否则 and 返回第二个参数。析取运算 or 如果第一个参数不是 nil 或 false 则返回第一个参数,否则 or 返回第二个参数。 and 和 or 都使用截取计算,也就是,只有有必要的情况下才计算第二个参数。例如: 10 or error() -> 10 nil or "a" -> "a" nil and 10 -> nil false and error() -> false false and nil -> false false or nil -> nil 10 and 20 -> 20 2.5.4 - 串联接在Lua中字符串连接操作符是两个点 (` 2.5.5 - 优先级Lua中的操作符的优先级如下表所示,从低到高优先级: or and < > <= >= ~= == .. + - * / not - (unary) ^ 表达式中,你可以使用括号来改变优先顺序。串联接符 (` 2.5.6 - 表构造器表构造器是创建表的表达式。当计算构造器的时候,就会创建一个新的表。构造器可以用来创建空的表,或者创建表并初始化一些字段。一般的语法如下: tableconstructor ::= `{´ [fieldlist] `}´ fieldlist ::= field {fieldsep field} [fieldsep] field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp fieldsep ::= `,´ | `;´
a = {[f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45} 等同于: do local temp = {} temp[f(1)] = g temp[1] = "x" -- 1st exp temp[2] = "y" -- 2nd exp temp.x = 1 -- temp["x"] = 1 temp[3] = f(x) -- 3rd exp temp[30] = 23 temp[4] = 45 -- 4th exp a = temp end 如果列表中最后一个字段的形式是 字段列表可以有一个结尾的分隔符,这个对由机器生成的列表十分方便。 2.5.7 - 函数调用Lua中的一个函数调用有如下语法: functioncall ::= prefixexp args 在函数调用中,首先会计算 prefixexp 和 args 。如果 prefixexp 的值是 function 类型,那么那个函数就会被调用,同时使用给出的参数。否则,他的 "call" 元方法就会被调用,第一个参数是 prefixexp 的值,接下来是原来的调用参数(见 2.8)。 形式 functioncall ::= prefixexp `:´ Name args 可以用来调用“方法”("methods")。调用 参数可以有以下几种语法: args ::= `(´ [explist1] `)´ args ::= tableconstructor args ::= Literal 所有的参数表达式都会在实际调用之前进行计算。 因为函数可以返回任意个结果(见 2.4.4),结果的数量必须在使用它们前进行调整。如果函数按照语句进行调用(见 2.4.6),那么它的返回列表就会被调整为零个元素,这样就舍弃了所有的返回值。如果调用函数时,他是一个表达式列表的最后一个元素,那么不会做调整(除非调用时加了括号)。 以下是一些例子: f() -- 调整为0个结果 g(f(), x) -- f() 被调整成1个结果 g(x, f()) -- g 获得 x 加上f()返回的所有值 a,b,c = f(), x -- f() 被调整成1个结果(此时c获得nil值) a,b,c = x, f() -- f() 被调整为两个结果 a,b,c = f() -- f() 被调整为3个结果 return f() -- 返回所有 f() 返回的值 return x,y,f() -- 建立一个表包含所有 f() 返回的值 {f()} -- creates a list with all values returned by f() {f(), nil} -- f() 被调整为一个结果 如果你用括号括起调用的函数,那么它就会被调整为返回一个值。 return x,y,(f()) -- returns x, y, and the first value from f() {(f())} -- creates a table with exactly one element 作为Lua语法自由格式的一个例外,你不能在函数调用的 ` a = f (g).x(a) Lua会读作
return (f(x)) -- results adjusted to 1 return 2 * f(x) return x, f(x) -- additional results f(x); return -- results discarded return x or f(x) -- results adjusted to 1 2.5.8 - 函数定义函数定义的语法是: function ::= function funcbody funcbody ::= `(´ [parlist1] `)´ block end 下面较好的语法简化了函数定义: stat ::= function funcname funcbody stat ::= localfunction Name funcbody funcname ::= Name {`.´ Name} [`:´ Name] 语句 function f () ... end 会被翻译为 f = function () ... end 语句 function t.a.b.c.f () ... end 会被翻译为 t.a.b.c.f = function () ... end 语句 local function f () ... end 会被翻译为 local f; f = function () ... end 一个函数定义是一个可执行的表达式,他的类型为 函数(function) 。当Lua预编译语句段的时候,他的函数体也会被预编译。这样,当Lua执行函数定义的时候,函数被 实例化 (封装 closed)。这个函数实例(或闭包 closure)是表达式的最终结果。同一个函数的不同的实例可以引用不同的外部局部变量也可以有不同的环境表。 形式参数(代表参数的变量,简称形参)就像用实际参数值(简称实参)初始化的局部变量一样。 parlist1 ::= namelist [`,´ `...´] parlist1 ::= `...´ 当调用一个函数时,实参表会调整为和形参一样的长度,除非函数是 variadic 或者 变长参数函数(vararg function)。变长参数函数在其参数列表最后有三个点 (` 请思考以下函数定义的例子: function f(a, b) end function g(a, b, ...) end function r() return 1,2,3 end 然后,我们有以下实参到形参的对应关系: CALL PARAMETERS f(3) a=3, b=nil f(3, 4) a=3, b=4 f(3, 4, 5) a=3, b=4 f(r(), 10) a=1, b=10 f(r()) a=1, b=2 g(3) a=3, b=nil, arg={n=0} g(3, 4) a=3, b=4, arg={n=0} g(3, 4, 5, 8) a=3, b=4, arg={5, 8; n=2} g(5, r()) a=5, b=1, arg={2, 3; n=2} 结果使用 return 语句返回(见 2.4.4)。如果控制到达了函数尾部而没有遇到 return 语句,那么函数没有返回值。 冒号(:) 语法是用来定义 methods 的,也就是,函数有一个隐含的额外参数 function t.a.b.c:f (...) ... end 相对以下是较好的形式: t.a.b.c.f = function (self, ...) ... end 2.6 - 可见性规则Lua是一个有词法范围的语言。变量的范围从声明语句后的第一个语句开始到包含声明的最内部的语句块为止。例如: x = 10 -- global variable do -- new block local x = x -- new `x', with value 10 print(x) --> 10 x = x+1 do -- another block local x = x+1 -- another `x' print(x) --> 12 end print(x) --> 11 end print(x) --> 10 (the global one) 注意:在类似 由于词法范围的规则,在局部变量的范围内定义的函数可以任意访问这些变量。例如: local counter = 0 function inc (x) counter = counter + x return counter end 内部函数使用的局部变量在函数内部称之为 上值(upvalue),或者 外局部变量(external local variable)。 注意每个 local 语句执行时会定义一个新的局部变量。看以下例子: a = {} local x = 20 for i=1,10 do local y = 0 a[i] = function () y=y+1; return x+y end end 循环产生了十个闭包(也就是,十个匿名函数的实例)。每个闭包使用不同的 2.7 - 错误处理因为Lua是一个扩展语言,所有的Lua动作都是从宿主程序中调用Lua库中函数的C代码开始的(见 3.15)。无论错误发生在Lua编译过程时或执行时,控制返回C,然后可以做相应的处理(比如打印一个错误)。 Lua代码可以通过调用error函数来产生一个错误(见 5.1)。如果你要在Lua中捕获错误,你可以使用 2.8 - 元表 (Metatables)Lua中的每一个表和用户数据都可以拥有一个 元表(metatable)。这个 元表 是一个普通的Lua表,定义了在特定操作下原始表和用户数据的行为。你可以通过设置一个对象的元表中的特定字段来更改它某些方面的行为。例如,当一个对象是一个加法的操作数时,Lua检查它的元表中的 我们称元表中的键(字段名,key)为 事件(events) ,值为 元方法(metamethods)。在上一个例子中, 你可以通过 元表可以控制对象在算术操作、比较、串连接、索引取值中如何运行。元表也可以定义一个函数当收集内存垃圾时调用。每一个操作这里Lua都用一个特定的键关联,称之为事件。当Lua对一个表或是一个用户数据执行上面中的一个操作时,它先检查元表控制的操作已经罗列在下面。每个操作有一个相应的名称,代表了他的含义。他们在元表中的键是由名称前加上两条下划线;如,操作 "add" 的键是 这里给出的Lua代码仅仅是说明性的;真正的行为是硬编码在解释器中的,比下面的的模拟的效率要高很多。描述中用到的函数 ( metatable(obj)[event] 这个要读作: rawget(metatable(obj) or {}, event) 也就是,访问元方法时不会调用其它元方法,同时调用没有元表的对象不会出错(它返回一个 nil值)。
2.9 - 垃圾收集Lua 会自动进行内存管理。这意味着你不需要担心新对象的内存分配问题,也不需要释放不用的对象。Lua 通过不断地运行 垃圾收集器 收集 dead objects (也就是那些Lua中无法访问的对象)来自动管理内存。Lua中所有的对象都是自动管理的目标:表,用户数据,函数,线程,和字符串。Lua使用两个数字控制垃圾收集循环。一个数字表示Lua使用的动态内存的字节数,另一个是阀值。当内存字节数到达阀值时,Lua就运行垃圾收集器,来释放死对象的空间。一旦字节计数器被调整,那么阀值就会被设为字节计数器新值的两倍。 通过C API,你可以查询和更改阀值(见 3.7)。将阀值设为零时会强制立刻进行垃圾收集,同时把他设为足够大就可以停止垃圾收集。仅使用Lua代码中的 2.9.1 - 垃圾收集元 |
请发表评论