(接上篇)
-------------------
4.5 表达式
-------------------
---------
4.5.1 基本表达式
---------
Lua 中基本表达式是:
exp ::= `(‘ exp `)‘
exp ::= nil
exp ::= number
exp ::= literal
exp ::= var
exp ::= upvalue
exp ::= function
exp ::= functioncall
exp ::= tableconstructor
数值(数值常量)和字符串常量在 4.1 节解释过了。变量在 4.4.2 节解释过了。upvalue 在 4.6 节解释。函数定义在 4.5.9 节解释。函数调用在 4.5.8 节解释。表的构造函数在 4.5.7 节解释。
一个全局变量 x 的调用等价于调用 getglobal("x"),一个下标变量 t[i] 等价于 gettable_event(t,i) 。这些函数的解释参见 4.8 节(getglobal 在基础的库中,gettable_event 仅用于解释目的)。
非终结的 exp1 被用来指示一个表达式的返回值必须被调整为一个值:
exp1 ::= exp
---------
4.5.2 算术运算符
---------
Lua 支持常见的算术运算符。这些运算符是二元操作符 +(加), -(减), *(乘), /(除) 和 ^(幂),一元操作符 -(负)。如果操作数是数值,或者是可以转化为数值的字符串(根据 4.2 节中给出的规则),所有的操作(除了幂操作)具有通常意义。否则,一个适用的标签函数(参见 4.8 节)将会被调用。幂操作将一直调用一个标签函数。标准的数学库以预想的意义重定义了幂操作(参见 6.3 节)。
---------
4.5.3 关系运算符
---------
Lua 中的关系运算符是:
== ~= < > <= >=
他们返回 nil 做为假,非 nil 做为真。
相等(==)首先比较两个操作数的标签。如果不同,结果为 nil。否则,比较它们的值。数值或字符串以常见的方式比较。表, userdata 和函数按引用比较,也就是说,两个比较的表只有是同一个的时候才被认为是相等的。不等运算 ~= 和相等运算 (==) 具有完全相反的结果。
4.2 节的转换规则不适用于相等比较。所以,"0"==0 的求值结果为 false,t[0] 和 t["0"] 表示表中不同的元素。
顺序比较操作符是这么工作的:如果两个参数都是数值,他们就以数值比较。如果两个参数都可以转化为字符串,它们将以字典序比较。否则的话, 标签方法 "lt" 将被调用(参见 4.8 节)。
---------
4.5.4 逻辑运算符
---------
Lua 中的逻辑运算符是:
and or not
和控制结构一样,所有的逻辑运算符认为 nil 为假而其它的都为真。
合取运算符 and 返回 nil 如果它的第一个参数为 nil;否则,它返回它的第二个参数。析取运算符 or 返回它的第一个参数如果它不同于 nil;否则,它返回它的第第二个参数。and 和 or 是短路求值,也就是说,第二个操作数只在需要的时候才被求值。
有两个使用逻辑运算符的习惯用法。第一个是:
x = x or v
它等价于
if x == nil then x = v end
该用法当 x 未设置时给 x 设置一个默认值。
第二个习惯用法是:
x = a and b or c
它应该被读为 x = (a and b) or c. 它等价于
if a then x = b else x = c end
条件是 b 不为 nil。
---------
4.5.5 连接
---------
Lua 中的字符串连接操作符由两点“.." 表示。如果两个操作数是字符串或者数字,他们按 4.2 节的规则转化为字符串。否则,标签方法 "concat" 将被调用(参见 4.8 节)。
---------
4.5.6 优先级
---------
运算符的优先级如下表所示,从低到高排列:
and or
< > <= >= ~= ==
..
+ -
* /
not - (unary)
^
所有的二元操作符具体左结合性,^除外,幂操作具有右结合性。预编译可能重新排列相关运算符(比如 .. 和 +)的求值顺序,只要优化不改变正常的结果。反而,这些优化可能改变一些结果,如果你为这些运算符定义了一些不相关的标签方法。
---------
4.5.7 表的构造函数
---------
Table 的构造函数是创建表的表达式。当对构造函数求值的时候,会生成一个新的表。构造函数可以用来新建一个空表,或者新建一个表并初始化一些字段。
构造函数的语法如下:
tableconstructor ::= `{‘ fieldlist `}‘
fieldlist ::= lfieldlist | ffieldlist | lfieldlist `;‘ ffieldlist | ffieldlist `;‘ lfieldlist
lfieldlist ::= [lfieldlist1]
ffieldlist ::= [ffieldlist1]
lfieldlist1 被用来初始化列表。
lfieldlist1 ::= exp {`,‘ exp} [`,‘]
列表中的表达式被赋值给一个连续的数值索引,索引从 1 开始。例如:
a = {"v1", "v2", 34}
等同于:
do
local temp = {}
temp[1] = "v1"
temp[2] = "v2"
temp[3] = 34
a = temp
end
ffieldlist1 初始化表中的其它字段:
ffieldlist1 ::= ffield {`,‘ ffield} [`,‘]
ffield ::= `[‘ exp `]‘ `=‘ exp | name `=‘ exp
例如:
a = {[f(k)] = g(y), x = 1, y = 3, [0] = b+c}
等同于:
do
local temp = {}
temp[f(k)] = g(y)
temp.x = 1 -- or temp["x"] = 1
temp.y = 3 -- or temp["y"] = 3
temp[0] = b+c
a = temp
end
事实上, {x = 1, y = 4} 这样的表达式是表达式 {["x"] = 1, ["y"] = 4} 的语法糖。
两种形式都可以有一个可选的结尾逗号,并且在同样的构造中可以用分号分割。例如,下面的形式都是对的。
x = {;}
x = {"a", "b",}
x = {type="list"; "a", "b"}
x = {f(0), f(1), f(2),; n=3,}
---------
4.5.8 函数调用
---------
Lua 中的函数调用有如下语法:
functioncall ::= varorfunc args
首先,varorfunc 被求值。如果它的值类型为 function,这个函数就用给定的参数被调用。 否则,标签方法 "function" 被调用,第一个参数为 varorfunc 的值,之后是原来的调用参数(参见 4.8 节)。
形如:
functioncall ::= varorfunc `:‘ name args
可被用来调用 "methods"。v:name(...) 调用是 v.name(v, ...) 的语法糖,除了 v 只被求值一次。
参数的语法如下:
args ::= `(‘ [explist1] `)‘
args ::= tableconstructor
args ::= literal
explist1 ::= {exp1 `,‘} exp
所有参数表达式在函数调用前被求值。f{...} 调用是 f({...}) 的语法糖,即,参数列表是一个新建的 table。f‘...‘ (或者 f"..." 或者 f[[...]])调用是 f(‘...‘) 的语法糖,即,参数列表是一个字符串常量。
因为一个函数可以返回任意多个值(参见 4.4.3 节),返回值的个数在使用之前必须进行调整(参见 4.3 节)。如果一个函数调用作为语句使用(参见 4.4.5 节),它的返回结果会被调整到 0 个,因而返回值被全部丢弃。如果一个函数在需要一个值(语法中被表示为非终结的 exp1)的地方调用,它的返回结果会被调整到 1 个,因而除了第一个返回值其它的都被丢弃。如果一个函数在需要多个值的地方调用(语法上表示为非终结的 exp),不对返回结果个数进行调整。唯一可以保持多个值的地方是赋值的最后(或唯一)一个表达式,参数列表中,或者在 return 语句中。这里是一些例子:
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() 调整到 2 个返回值
a,b,c = f() -- f() 调整到 3 个返回值
return f() -- 返回所有的 f() 的返回值
return x,y,f() -- 返回 a, b, 和所有的 f() 的返回值
-------------------
4.5.9 函数定义
-------------------
函数定义的语法是:
function ::= function `(‘ [parlist1] `)‘ block end
stat ::= function funcname `(‘ [parlist1] `)‘ block end
funcname ::= name | name `.‘ name | name `:‘ name
语句
function f () ... end
是
f = function () ... end
的语法糖。
语句
function v.f () ... end
是
v.f = function () ... end
的语法糖。
一个函数定义是一个可执行的表达式,它的值类型为 function。当 Lua 预编译一个 chunk,它所有的函数体也都会被预编译。然后,当 Lua 执行函数定义,它的 upvalue 就是确定的了(参见 4.6 节),函数也就实例化(或者闭合)了。这个函数实例(或者闭包)就是表达式的最终值。同样函数的不同实例可能有不同的 upvalue 。
参数和局部变量的表现一样,由参数值进行初始化。
parlist1 ::= `...‘
parlist1 ::= name {`,‘ name} [`,‘ `...‘]
当一个函数被调用时,实参的个数被调整为和形参一样(参见 4.3 节),除非函数是可变参数函数(vararg function),也就是参数列表的最后是三个点(‘...‘)。一个可变参数函数不需要调整它的实参列表;而是把所有额外的实参收集到名为 arg 的隐含参数中。arg 的值是一个表,它的一个字段 n 表示额外参数的个数,并且额外的参数位于 1, 2, ..., n 。
作为一个例子,考虑下面的定义:
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 语句返回(见 4.4.3节)。如果执行到函数最后也没有 return 指令的话,函数不返回值。
语法
funcname ::= name `:‘ name
用来定义函数,函数有一个隐含的参数 self。
语句
function v:f (...) ... end
只是
v.f = function (self, ...) ... end
的语法糖。
注意函数获得一个额外的名为 self 的形式参数。
(未完待续)