(接上篇)
--------------------------------------
4 语言
--------------------------------------
这节介绍 Lua 的词法,语法和语义。
-------------------
4.1 词法约定
-------------------
Lua 是区别大小写的语言。标识符可以是任何字母,数字,下划线组成的字符串,且首字母不可为数字。下面这些是保留的关键字,不可用做标识符:
and do else elseif end
function if local nil not
or repeat return until then while
下面的字符串留做它用:
~= <= >= < > == = .. + - * /
% ( ) { } [ ] ; , .
字符串常量可以由成对的单引号或双引号界定,并且可以包括 C 语言风格的转义序列 '\n', '\t' 和 '\r'。字符串常量也可以由成对的 [[ ... ]] 界定。 后一种形式的字面量可以跨行,可以包含嵌套的 [[ ... ]],并且不解释转义序列。
注释可在字符串外面的任何地方用两个连字符(--)开始,直到行尾。
数值常量可以由可选的小数部分,可选的指数部分写成。下面是一些有效的数值常量示例:
4 4. .4 4.57e-3 .3e12
-------------------
4.2 约定
-------------------
Lua 提供了一些自动转化。在字符串上的算术运算会试图把字符串转化为数值,遵从通常的规则。相反的,当一个数值参与字符串操作时,数值会被转化为字符串,按照以下规则:如果数值是一个整数,没有指数或小数点,则直接转化;否则,它采用和标准 C 函数 printf 用 "%g" 一样的格式转化。
-------------------
4.3 调整
-------------------
Lua 中的函数可以返回多个值。因为没有类型声明,系统不知道函数会返回多少值,或者它需要多少个参数。所以,有时候,值列表必须在运行时调整到给定长度。如果实际值多于所需,那么多余的值会被丢掉;如果需要的值多于实际的,根据需要在列表中进行 nil 扩展。调整也发生多重赋值和函数调用中。
-------------------
4.4 语句
-------------------
Lua 支持几乎所有常规的语句。常规的命令包括:赋值,控制结构和过程调用。非常规的命令包括 4.5.7 节中描述的表的构造函数,和局部变量的声明。
---------
4.4.1 块
---------
一个块(block)就是一个顺序执行的语句列表。任何语句都可以可选的后跟一个分号。
block --> { stat sc } [ret sc]
sc --> [ ';' ]
由于语义分析的原因, return 语句只能作为一个块里的最后一句。这个约束同时也避免了“语句不可达”的错误。
---------
4.4.2 赋值
---------
Lua 支持多重赋值。所以,语法定义了赋值的左边是一个变量列表,右边是一个表达式的列表。两个列表元素都以逗号分割。
stat --> varlist1 '=' explist1
varlist1 --> var { ',' var }
这个语句首先求出所有右边的值,再排列左边的变量,最后对其赋值。所以,可以这样交换两个变量的值,如下所示:
x, y = y, x
赋值前,值的列表被调整到和变量列表的长度相等(参见 4.3 节)。
var --> name
一个名字可以指示一个全局变量或局部变量或形式参数。
var --> var '[' exp1 ']'
方括号用来索引 table。如果 var 的结果是一个 table,由表达式值索引的字段获得所赋的值;否则,回退函数(fallback) settable 会被调用,调用时有三个参数:var 的值,表达式的值,将要被赋的值;参见 4.7 节。
var --> var '.' name
var.NAME 仅仅是 var['NAME'] 的语法糖。
---------
4.4.3 控制结构
---------
控制结构的条件表达式可以返回任何值。所有不是 nil 的值都被认为是真,nil 被认为是假。if, while 和 repeat 和通常的意思一样。
stat --> while exp1 do block end
stat --> repeat block until exp1
stat --> if exp1 then block { elseif } [else block] end
elseif --> elseif exp1 then block
return 用于从函数中返回值。因为一个函数可以返回多个值,return 语句的语法是:
ret --> return explist
---------
4.4.4 函数调用做为语句
---------
由于可能的副作用(side-effects),函数调用可以作为语句执行。
stat --> functioncall
最后返回的值被丢弃。函数调用在 4.5.8 节解释。
---------
4.4.5 局部声明
---------
局部变量可以在块中的任何位置声明。它的作用域从声明的地方开始,直到块结束。声明可以包含赋初始值。
stat --> local declist [init]
declist --> name { ',' name }
init --> '=' explist1
如果有赋初值操作,那么他和多重赋值有同样的语义。否则,所有的变量被初始化为 nil。
-------------------
4.5 表达式
-------------------
---------
4.5.1 简单表达式
---------
简单表达式是:
exp --> '(' exp ')'
exp --> nil
exp --> 'number'
exp --> 'literal'
exp --> var
数值(数值常量)和字符串常量在 4.1 节解释过了。变量在 4.4.2 节解释过了。
---------
4.5.2 算术运算符
---------
Lua 支持常见的算术运算符。这些运算符是二元操作符 +, -, *, / 和 ^(幂),一元操作符 -。如果操作数是数值,或者是可以根据 4.2 节中给出的规则转化为数值的字符串,所有的操作(除了幂操作)具有通常意义。否则,回退函数 "arith" 将会被调用,参见 4.7 节。幂操作将一直调用这个回退函数。标准的数学库以预想的意义重定义了幂操作的回退,参见 6.3 节。
---------
4.5.3 关系运算符
---------
Lua 提供了以下的关系运算符:
< > <= >= ~= ==
他们返回 nil 做为假,非 nil (事实是数值 1 ) 做为真。
相等首先比较两个操作数的类型。如果不同,结果为 nil。否则,比较它们的值。数值或字符串以常见的方式比较。表, Cfuntion 和函数按引用比较,也就是说,两个比较的表只有是同一个的时候才被认为是相等的。不等运算 ~= 和相等运算 (==) 具有完全相反的结果。
其它的操作符是这么工作的:如果两个参数都是数值,他们就以数值比较。如果两个参数都可以转化为字符串,它们将以字典序比较。否则的话, 回退函数 "order" 将被调用;参见 4.7 节。
---------
4.5.4 逻辑运算符
---------
所有的逻辑运算符,和控制结构一样,认为 nil 为假而其它的都为真。逻辑运算符是:
and or not
and 和 or 是短路求值,也就是说,第二个操作数只在需要的时候才被求值。
---------
4.5.5 连接
---------
Lua 提供了一个字符串连接操作符“..",如果操作数是字符串或者数字,他们按 4.2 节的规则转化为字符串。否则,回退函数 "concat" 将被调用;参见 4.7 节。
---------
4.5.6 优先级
---------
操作符的优先级如下表所示,从低到高排列:
and or
< > <= >= ~= ==
..
+ -
* /
not - (unary)
^
二元操作符具体左结合性,^除外,幂操作具有右结合性。
---------
4.5.7 表的构造函数
---------
Table 的构造函数是创建表的表达式。当对表的构造函数求值的时候,会生成一个新的表。构造函数可以用来新建一个空表,或者新建一个表并初始化一些字段。
构造函数的语法如下:
tableconstructor --> '{' fieldlist '}'
fieldlist --> lfieldlist | ffieldlist | lfieldlist ';' ffieldlist
lfieldlist --> [lfieldlist1]
ffieldlist --> [ffieldlist1]
lfieldlist1 被用来初始化列表。
lfieldlist1 --> exp {',' exp} [',']
列表中的表达式被赋值给一个连续的数值索引,索引从 1 开始。例如:
a = {"v1", "v2", 23}
等同于:
temp = {}
temp[1] = "v1"
temp[2] = "v2"
temp[3] = 34
a = temp
另一种形式初始化表中的具名字段:
ffieldlist1 --> ffield {',' ffield} [',']
field --> name '=' exp
例如:
a = {x = 1, y = 3}
等同于:
temp = {}
temp.x = 1
temp.y = 3
a = temp
---------
4.5.8 函数调用
---------
函数调用有如下语法:
functioncall --> var realParams
这里,var 可以是任何变量(全局的,局部的,下标索引的等),如果它的类型为 function 或 Cfunction,这个函数就被调用. 否则,回退函数 "function" 被调用,第一个参数为 var 的值,之后是原来的调用参数。
形如:
functioncall --> var ':' name realParams
可被用来调用 "methods"。var:name(...) 调用是 var.name(var, ...) 的语法糖。除了 var 只被求值一次。
realParams --> '(' [explist1] ')'
realParams --> tableconstructor
explist1 --> exp1 {',' exp1}
所有参数表达式在函数调用前被求值;然后实参列表被调整到和形参列表一样大小(参见 4.3 节);最后,这个列表被赋值给形参。f{...} 调用是 f({...}) 的语法糖,即,参数列表是一个 新建的 table。
因为一个函数可以返回任意多个值(参见 4.4.3 节),返回值的个数在使用之前必须进行调整。如果一个函数作为语句使用(参见 4.4.4 节),它的返回结果会被调整到 0 个(也就是全部丢弃)。如果一个函数在需要一个值(语法中被表示为非终结的 exp1)的地方调用,它的返回结果会被调整到 1 个。如果一个函数在需要多个值的地方调用(语法上表示为非终结的 exp),不对返回结果进行调整。
-------------------
4.6 函数定义
-------------------
函数可以在模块中的任何全局层面定义;函数定义的语法是:
function --> function var '(' [parlist1 ] ')' block end
当 Lua 预编译一个模块,它所有的函数体也被预编译了。然后,当 Lua “执行”函数定义,它的函数体被保存在变量 var,类型为 function。
参数和局部变量的表现一样,由参数值进行初始化。
parlist1 --> 'name' { ',' name }
结果由 return 语句返回(见 4.4.3节)。如果执行到函数最后也没有 return 指令的话,函数不返回值。
函数定义有一个特殊的语法,即,函数有一个额外的参数 self。
function --> function var ':' name '(' [parlist1] ')' block end
一个声明如下:
function v:f (...)
...
end
等同于
function v.f (self, ...)
...
end
也就是说,函数额外获得一个名为 self 的的形参。注意变量 v 必须预先被初始化为 table。
-------------------
4.7 回退
-------------------
(Fallbacks 不知道该翻译成什么,回退?预设?陷入?这里统一叫为回退,不过应该叫做预设应该更适当一点吧。知道写做回退的地方指的是 fallback 就行了,早知道就不翻译它,直接用英文更好些。)
Lua 提供一个强大的机制去扩展它的语义,叫做回退 (fallbacks)。基本上,一个回退就是一个程序员定义的函数,当 Lua 不知道如何继续执行时将调用它。
Lua 支持下列回退,由给定的字符串识别:
"arith" 一个算术运算应用于非数值型操作数,或者幂运算 ^ 被调用时。它接受三个参数:两个操作数(如果是单目减运算时第二个为 nil)和一个下列描述出错运算符的字符串之一:
add sub mul div pow unm
它的返回值是算术运算的最终结果。默认函数引发一个错误。
"order" 一个比较运算应用于非数值型或非字符串型操作数的时候。它接受三个参数:两个操作数和一个下列描述出错运算符的字符串之一:
lt gt le ge
它的返回值是比较运算的最终结果。默认函数引发一个错误。
"concat" 一个连接运算应用于非字符串型操作数。它接受两个操作数作为参数。它的返回值是连接运算的最终结果。默认函数引发一个错误。
"index" Lua 试图取得不在 table 中的 index 的值的时候。它接受 table 和 index 作为参数。它的返回值是索引运算的最终结果。默认函数返回 nil。
"getglobal" Lua 试图取得一个值为 nil 的全局变量的时候(或者全局变量还没有初始化的时候)。它接受变量名作为参数。它的返回值是表达式的最终结果。默认函数返回 nil。
"gettable" Lua 试图索引一个非 table 值的时候。它接受非 table 值和索引 index 作为参数。它的返回值是索引操作的最终结果。默认函数引发一个错误。
"settable" Lua 试图给一个非 table 值某个索引赋值的时候。它接受非 table 值,索引 index 和所赋的值作为参数。默认函数引发一个错误。
"function" Lua 试图调用一个非函数值时。它接受非函数值和原来调用的参数做为参数。它的返回值是调用操作的最终结果。默认函数引发一个错误。
"gc" 垃圾回收的时候。它接受将要被垃圾回收的 table 作为参数。垃圾收集器每次调用之后会调用它,参数为 nil。因为这个函数在垃圾回收中运行,使用它时必须小心谨慎,程序员应该避免在其中新建对象(表或者字符串)。默认函数什么也不做。
"error" 一个错误发生的时候。它接受一个描述错误的字符串做为参数。默认函数打印那个出错消息到标准错误输出。
函数 setfallback 被用来改变一个回退动作。它的第一个参数是一个描述 fallback 的字符串,第二个参数是新的回退函数。它返回相应的老的回退函数。
8.6 节展示了一个使用回退的例子。
-------------------
4.8 错误处理
-------------------
由于 Lua 是一个扩展语言,所有的 Lua 动作从 C 代码调用 Lua 库中的一个函数开始。当一个错误在 Lua 编译或执行时发生,一个错误 fallback 函数被调用,然后相应的库中的函数 (lua_dofile, lua_dostring, lua_call, 和 lua_callfunction) 被终止并返回一个错误状态。
错误回退函数的唯一参数是一个描述错误的字符串。标准的 I/O 库为了打印一些像调用堆栈之类的额外信息,使用调试机制(参见 7 节)重定义了这个回退。为了得到一些关于错误的更多的信息,Lua 程序可以包含编译指示选项 $debug。这个编译指示必须独占一行。当一个错误发生在有该编译指示选项时,查错程序可以打印引起出错的调用(和错误)的行。如果需要的话,可以修改错误处理回退程序。参见 4.7 节。
Lua 代码可能通过调用函数 error 生成一个错误。它的可选参数是一个被用做错误消息的字符串。
(未完待续)