3.3 语句

Lua 支持所有与 Pascal 或是 C 类似的常见形式的语句,这个集合包括赋值,控制结构,函数调用,还有变量声明。

3.3.1 语句块

语句块是一个语句序列,它们会按次序执行:

  1. block ::= {stat}

Lua 支持 空语句,你可以用分号分割语句,也可以以分号开始一个语句块,或是连着写两个分号:

  1. stat ::= ;

函数调用和赋值语句都可能以一个小括号打头,这可能让 Lua 的语法产生歧义。我们来看看下面的代码片断:

  1. a = b + c
  2. (print or io.write)('done')

从语法上说,可能有两种解释方式:

  1. a = b + c(print or io.write)('done')
  2.  
  3. a = b + c; (print or io.write)('done')

当前的解析器总是用第一种结构来解析,它会将开括号看成函数调用的参数传递开始处。为了避免这种二义性,在一条语句以小括号开头时,前面放一个分号是个好习惯:

  1. ;(print or io.write)('done')

一个语句块可以被显式的定界为单条语句:

  1. stat ::= do block end

显式的对一个块定界通常用来控制内部变量声明的作用域。有时,显式定界也用于在一个语句块中间插入return (参见 §3.3.4)。

3.3.2 代码块

Lua 的一个编译单元被称为一个 代码块。从句法构成上讲,一个代码块就是一个语句块。

  1. chunk ::= block

Lua 把一个代码块当作一个拥有不定参数的匿名函数(参见§3.4.11)来处理。正是这样,代码块内可以定义局部变量,它可以接收参数,返回若干值。此外,这个匿名函数在编译时还为它的作用域绑定了一个外部局部变量_ENV (参见 §2.2)。该函数总是把 _ENV 作为它唯一的一个上值,即使这个函数不使用这个变量,它也存在。

代码块可以被保存在文件中,也可以作为宿主程序内部的一个字符串。要执行一个代码块,首先要让 Lua 加载 它,将代码块中的代码预编译成虚拟机中的指令,而后,Lua 用虚拟机解释器来运行编译后的代码。

代码块可以被预编译为二进制形式;参见程序 luac 以及函数 string.dump 可获得更多细节。用源码表示的程序和编译后的形式可自由替换;Lua 会自动检测文件格式做相应的处理(参见 load)。

3.3.3 赋值

Lua 允许多重赋值。因此,赋值的语法定义是等号左边放一个变量列表, 而等号右边放一个表达式列表。两边的列表中的元素都用逗号间开:

  1. stat ::= varlist = explist
  2. varlist ::= var {‘, var}
  3. explist ::= exp {‘, exp}

表达式放在 §3.4 中讨论。

在作赋值操作之前,那值列表会被 调整 为左边变量列表的个数。如果值比需要的更多的话,多余的值就被扔掉。 如果值的数量不够需求,将会按所需扩展若干个 nil。如果表达式列表以一个函数调用结束,这个函数所返回的所有值都会在调整操作之前被置入值列表中(除非这个函数调用被用括号括了起来;参见 §3.4)。

赋值语句首先让所有的表达式完成运算,之后再做赋值操作。因此,下面这段代码

  1. i = 3
  2. i, a[i] = i+1, 20

会把 a[3] 设置为 20,而不会影响到 a[4] 。这是因为 a[i] 中的 i 在被赋值为 4 之前就被计算出来了(当时是 3 )。 简单说 ,这样一行

  1. x, y = y, x

会交换 xy 的值,及

  1. x, y, z = y, z, x

会轮换 xyz 的值。

对全局变量以及表的域的赋值操作的含义可以通过元表来改变。对 t[i] = val 这样的变量索引赋值,等价于 settable_event(t,i,val)。(关于函数 settable_event 的详细说明,参见§2.4。这个函数并没有在 Lua 中定义出来,也不可以被调用。这里我们列出来,仅仅出于方便解释的目的。)

对于全局变量 x = val 的赋值等价于_ENV.x = val(参见 §2.2)。

3.3.4 控制结构

if, while, and repeat这些控制结构符合通常的意义,而且也有类似的语法:

  1. stat ::= while exp do block end
  2. stat ::= repeat block until exp
  3. stat ::= if exp then block {elseif exp then block} [else block] end

Lua 也有一个 for 语句,它有两种形式(参见 §3.3.5)。

控制结构中的条件表达式可以返回任何值。falsenil 两者都被认为是假。所有不同于 nilfalse 的其它值都被认为是真(特别需要注意的是,数字 0 和空字符串也被认为是真)。

repeatuntil 循环中,内部语句块的结束点不是在 until 这个关键字处,它还包括了其后的条件表达式。因此,条件表达式中可以使用循环内部语句块中的定义的局部变量。

goto 语句将程序的控制点转移到一个标签处。由于句法上的原因,Lua 里的标签也被认为是语句:

  1. stat ::= goto Name
  2. stat ::= label
  3. label ::= :: Name ::

除了在内嵌函数中,以及在内嵌语句块中定义了同名标签,的情况外,标签对于它定义所在的整个语句块可见。只要 goto 没有进入一个新的局部变量的作用域,它可以跳转到任意可见标签处。

标签和没有内容的语句被称为空语句,它们不做任何操作。

break 被用来结束whilerepeat、或 for 循环,它将跳到循环外接着之后的语句运行:

  1. stat ::= break

break 跳出最内层的循环。

return 被用于从函数或是代码块(其实它就是一个函数)中返回值。函数可以返回不止一个值,所以 return 的语法为

  1. stat ::= return [explist] [‘;’]

return 只能被写在一个语句块的最后一句。如果你真的需要从语句块的中间 return,你可以使用显式的定义一个内部语句块,一般写作 do return end。可以这样写是因为现在 return 成了(内部)语句块的最后一句了。

3.3.5 For 语句

for 有两种形式:一种是数字形式,另一种是通用形式。

数字形式的 for 循环,通过一个数学运算不断地运行内部的代码块。下面是它的语法:

  1. stat ::= for Name = exp , exp [‘, exp] do block end

block 将把 name 作循环变量。从第一个 exp 开始起,直到第二个 exp 的值为止,其步长为第三个 exp 。更确切的说,一个 for 循环看起来是这个样子

  1. for v = e1, e2, e3 do block end

这等价于代码:

  1. do
  2. local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
  3. if not (var and limit and step) then error() end
  4. var = var - step
  5. while true do
  6. var = var + step
  7. if (step >= 0 and var > limit) or (step < 0 and var < limit) then
  8. break
  9. end
  10. local v = var
  11. block
  12. end
  13. end

注意下面这几点:

  • 所有三个控制表达式都只被运算一次,表达式的计算在循环开始之前。 这些表达式的结果必须是数字。
  • varlimit,以及 step都是一些不可见的变量。 这里给它们起的名字都仅仅用于解释方便。
  • 如果第三个表达式(步长)没有给出,会把步长设为 1 。
  • 你可以用 breakgoto 来退出 for 循环。
  • 循环变量 v 是一个循环内部的局部变量;如果你需要在循环结束后使用这个值,在退出循环前把它赋给另一个变量。 通用形式的 for 通过一个叫作 迭代器 的函数工作。每次迭代,迭代器函数都会被调用以产生一个新的值,当这个值为 nil 时,循环停止。 通用形式的 for 循环的语法如下:
  1. stat ::= for namelist in explist do block end
  2. namelist ::= Name {‘, Name}

这样的 for 语句

  1. for var_1, ···, var_n in explist do block end

它等价于这样一段代码:

  1. do
  2. local f, s, var = explist
  3. while true do
  4. local var_1, ···, var_n = f(s, var)
  5. if var_1 == nil then break end
  6. var = var_1
  7. block
  8. end
  9. end

注意以下几点:

  • explist 只会被计算一次。它返回三个值, 一个 迭代器 函数,一个 状态,一个 迭代器的初始值
  • fs,与 var都是不可见的变量。这里给它们起的名字都只是为了解说方便。
  • 你可以使用 break 来跳出 for 循环。
  • 环变量 var_i 对于循环来说是一个局部变量;你不可以在 for 循环结束后继续使用。如果你需要保留这些值,那么就在循环跳出或结束前赋值到别的变量里去。

3.3.6 函数调用语句

为了允许使用函数的副作用,函数调用可以被作为一个语句执行:

  1. stat ::= functioncall

在这种情况下,所有的返回值都被舍弃。函数调用在 §3.4.10 中解释。

3.3.7 局部声明

局部变量可以在语句块中任何地方声明。 声明可以包含一个初始化赋值操作:

  1. stat ::= local namelist [‘= explist]

如果有初始化值的话,初始化赋值操作的语法和赋值操作一致(参见 §3.3.3 )。若没有初始化值,所有的变量都被初始化为 nil

一个代码块同时也是一个语句块(参见 §3.3.2),所以局部变量可以放在代码块中那些显式注明的语句块之外。

局部变量的可见性规则在 §3.5 中解释。