(接上篇)
--------------------------------------
4 语言
--------------------------------------
这节描述 Lua 的词法,语法和语义。
-------------------
4.1 词法约定
-------------------
Lua 中的标识符可以是任何字母,数字,下划线组成的字符串,且首字母不可为数字。这符合大多数语言中标识符的定义,除了字母的字义依赖于当前的区域设置:所有的在当前的区域设置中被认为是字母的字符可以被用在标识符里。下面这些是保留的关键字,不可用做标识符:
and break do else elseif
end for function if in
local nil not or repeat
return then until while
Lua 是区分大小写的语言:and 是保留字,但是 And 和 αnd (如果区域设置允许的话)是不同的,有效的标识符。作为一个约定,由下划线开始后跟大写字母的标识符(比如 _INPUT)保留作为内部变量。
以下字符串表示其它标记:
~= <= >= < > == = + - * /
( ) { } [ ] ; , . .. ...
字符串常量可以由成对的单引号或双引号界定,并且可以包含 C 语言风格的转义序列 `\a‘ (响铃), `\b‘ (退格), `\f‘ (换页), `\n‘ (换行), `\r‘ (回车), `\t‘ (水平制表符), `\v‘ (垂直制表符), `\\‘ (反斜杠), `\"‘ (双绰号), `\‘‘ (单引号), and `\newline‘ (就是说,一个反斜杠后跟一个 newline,这会在字符串中生成一个新行)。字符串中的字符可以由它的数值来指定,通过转义序列`\ddd‘,这里的 ddd 是一个三位数字。Lua 中的字符串可以包含任意的 8 位值,包含内嵌的 0 ,它可以由 `\000‘ 来指定。
字符串常量也可以由成对的 [[ ... ]] 界定。 这一种形式的字面量可以跨行,可以包含嵌套的 [[ ... ]],并且不解释转义序列。这种形式特别适用于编写包含程序块或其他引用字符串的字符串。例如,在一个使用 ASCII 码的系统中,下面的三个常量是等价的:
1) "alo\n123\""
2) ‘\97lo\10\04923"‘
3) [[alo
123"]]
注释可在字符串外面的任何地方用两个连字符(--)开始,直到行尾。而且,块的第一行会被跳过如果它由 # 开始。这种机制是为了允许把 Lua 作为一种 Unix 系统中的脚本解释器(参见 8 节)。
数值常量可以由可选的小数部分,可选的指数部分写成。下面是一些有效的数值常量示例:
3 3.0 3.1416 314.16e-2 0.31416E1
-------------------
4.2 强制转换
-------------------
Lua 在运行时提供了一些在值之间的自动转换。在字符串上的算术运算会试图把字符串转换为数值,遵从通常的规则。相反的,当一个数值参与字符串操作时,数值会被转换为字符串,以一种合理的格式。转换格式要求一个数值转换到字符串后,能再被转换回原始的数值。所以,对于一个数值,转换并不需要生成一个好看的文本格式。为完全控制数值到字符串的转换,使用 format 函数(参见 6.2 节)。
-------------------
4.3 调整
-------------------
Lua 中的函数可以返回多个值。因为没有类型声明,当一个函数被调用时系统不知道函数会返回多少值,或者它需要多少个参数。所以,有时候,值列表必须在运行时调整到给定长度。如果实际值多于所需,那么多余的值会被丢掉;如果需要的值多于实际的,根据需要在列表中进行 nil 扩展。调整也发生多重赋值(参见 4.4.2 节)和函数调用(参见 4.5.8 节)中。
-------------------
4.4 语句
-------------------
Lua 支持几乎所有常规的语句,和 Pascal 和 C 类似。常规的命令包括:赋值,控制结构和过程调用。非常规的命令包括表的构造函数(参见 4.5.7 节),和局部变量的声明(参见 4.4.6 节)。
---------
4.4.1 块
---------
一个块(block)就是一个语句列表。在语法中,一个 block 等同于一个 chunk:
block ::= chunk
一个 block 也可以显式的界定:
stat ::= do block end
显式的 block 在控制局部变量的作用域(参见 4.4.6 节)时很有用。显式的 block 有时也被用于在另一个块中添加一个 return 或者 break 语句(参见 4.4.3 节)。
---------
4.4.2 赋值
---------
Lua 支持多重赋值。所以,语法定义了赋值的左边是一个变量列表,右边是一个表达式的列表。两个列表元素都以逗号分割。
stat ::= varlist1 `=‘ explist1
varlist1 ::= var {`,‘ var}
这个语句首先求出所有右边的值,再排列左边的变量,最后对其赋值。所以,代码:
i = 3
i, a[i] = 4, 20
把 a[3] 设置为 20,但是并不影响 a[4] 因为 i 在 a[i] 里被赋值为 4 之前就被求值(evaluated)了。
多重赋值可以用来交换两个变量的值,如下所示:
x, y = y, x
赋值的两边的列表可能长度不同。在赋值前,值的列表被调整到和变量列表的长度相等(参见 4.3 节)。
一个名字可以指示一个全局变量或局部变量或形式参数:
var ::= name
方括号用来索引 table:
var ::= varorfunc `[‘ exp1 `]‘
varorfunc ::= var | functioncall
varorfunc 的结果是一个 table,由表达式 exp1 的值索引的字段获得所赋的值。
var.NAME 仅仅是 var["NAME"] 的语法糖。
var ::= varorfunc `.‘ name
全局变量和下标变量的赋值和评估(evaluations )的意义可以被 tag method 修改(参见 4.8 节)。事实上,一个 x=val 的赋值,这里 x 是一个全局变量,等价于调用 setglobal("x", val);并且 t[i] = val 等价于 settable_event(t,i,val)。这些函数的完整描述参见 4.8 节(setglobal 在基础的库中,settable_event 仅用于解释目的)。
---------
4.4.3 控制结构
---------
控制结构 if, while 和 repeat 有通常意义和常见的语法
stat ::= while exp1 do block end
stat ::= repeat block until exp1
stat ::= if exp1 then block {elseif exp1 then block} [else block] end
一个控制结构的条件表达式 exp1 可以返回任何值。所有不是 nil 的值都被认为是真,只有 nil 被认为是假。
return 语句用于从函数或者块中返回值。因为函数或块可以返回多个值,return 语句的语法是:
stat ::= return [explist1]
break 语句可以被用来终结一个循环的执行,跳到循环之后的下一条语句:
stat ::= break
break 结束包含它的最内层的循环(while, repeat 或 for)。
由于语法原因,return 和 break 语句只能写在一个 block 的最后一句。如果确实需要在一个块的中间 return 或 break,可以使用一个显式的 block,如同在语句 `do return end‘ 中,因为这时 return 是内层 block 的最后一个语句。
---------
4.4.4 For 语句
---------
for 语句有两种格式,一种用于数值,一种用于表。数值型的 for 循环有如下语法:
stat ::= for name `=‘ exp1 `,‘ exp1 [`,‘ exp1] do block end
一个 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
注意如下:
> _limit 和 _step 是不可见的变量。这里的名字仅用于解释目的。
> 如果在 block 内部对 var 赋值,行为未定义。
> 如果没有第三个表达式(步长),则步长为 1 。
> 循环结束(limit)和步长只被求值一次,在循环开始之前。
> 变量 var 是对于语句来说是局部的;在 for 结束后不可以使用它的值。
> 你可以使用 break 退出一个 for 循环。如果你需要索引的值,在退出前把它赋给另一个变量。
表的 for 循环语句遍历给定表的所有键值对(index, value)。它有如下语法:
stat ::= for name `,‘ name in exp1 do block end
一个 for 语句如同
for index, value in exp do block end
等价于下面的代码
do
local _t = exp
local index, value = next(t, nil)
while index do
block
index, value = next(t, index)
end
end
注意如下:
> _t 是不可见变量,这里的名字仅用于解释目的。
> 如果在 block 内部对 index 赋值,行为未定义。
> 如果在遍历时改变表 _t ,行为未定义。
> 变量 index 和 value 是对于语句来说是局部的;在 for 结束后不可以使用它们的值。
> 你可以使用 break 退出一个 for 循环。如果你需要 index 或 value 的值,在退出前把它们赋给另外的变量。
> 表的元素遍历的顺序是未定义的,即使是数字索引。如果你想以数字顺序遍历索引,使用数值型 for 。
---------
4.4.5 函数调用做为语句
---------
由于可能的副作用(side-effects),函数调用可以作为语句执行。
stat ::= functioncall
在这种情况下,所有的返回值都被丢弃。函数调用在 4.5.8 节解释。
---------
4.4.6 局部声明
---------
局部变量可以在块中的任何位置声明。声明可以包含赋初始值。
stat ::= local declist [init]
declist ::= name {`,‘ name}
init ::= `=‘ explist1
如果有赋初值操作,那么他和多重赋值有同样的语义。否则,所有的变量被初始化为 nil。
一个 chunk 也是一个 block,所以局部变量可以在任何显式 block 的外部声明。
局部变量的作用域从声明的地方开始,直到 block 结束。所以,代码 local print=print 新建一个名为 print 的局部变量,该局部变量的初始值为同名全局变量的值。
(未完待续)