在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
云风 译 Copyright © 2006 Lua.org, PUC-Rio. All rights reserved. 1 - 介绍Lua 是一个扩展式程序设计语言,它被设计成支持通用的过程式编程,并有相关数据描述的设施。 Lua 也能对面向对象编程,函数式编程,数据驱动式编程提供很好的支持。 它可以作为一个强大、轻量的脚本语言,供任何需要的程序使用。 Lua 以一个用 clean C 写成的库形式提供。(所谓 Clean C ,指的 ANSI C 和 C++ 中共通的一个子集) 作为一个扩展式语言,Lua 没有 "main" 程序的概念:它只能 嵌入 一个宿主程序中工作,这个宿主程序被称作 embedding program 或简称为 host 。 宿主程序可以通过调用函数执行一小段 Lua 代码,可以读写 Lua 变量,可以注入 C 函数让 Lua 代码调用。 这些扩展的 C 函数,可以大大的扩展了 Lua 可以处理事务的领域,这样就可以订制出各种语言, 而它们共享一个统一的句法格式的框架。 Lua 的官方发布版就包含了一个叫做 Lua 是一个自由软件,它的使用许可决定了对它的使用过程一般没有任何保证。 这份手册中描述的东西的实现,可以在 Lua 的官方网站 跟其它的许多参考手册一样,这份文档有些地方比较枯燥。 关于 Lua 的设计想法的探讨,可以看看 Lua 网站上提供的技术论文。 有关用 Lua 编程的细节介绍,可以读一下 Roberto 的书,Programming in Lua (Second Edition) 。 2 - 语言这一节从词法、语法、句法上描述 Lua 。 换句话说,这一节描述了哪些 token (符记)是有效的,它们如何被组合起来,这些组合方式有什么含义。 关于语言的构成概念将用常见的扩展 BNF 表达式写出。也就是这个样子: {a} 意思是 0 或多个 a , [a] 意思是一个可选的 a 。 非最终的符号会保留原来的样子,关键字则看起来像这样 kword , 其它最终的符号则写成 `=´ 。 完整的 Lua 语法可以在本手册最后找到。 2.1 - 词法约定Lua 中用到的 名字(也称作 标识符)可以是任何非数字开头的字母、数字、下划线组成的字符串。 这符合几乎所有编程语言中关于名字的定义。 (字母的定义依赖于当前环境:系统环境中定义的字母表中的字母都可以被用于标识符。) 标识符用来命名变量,或作为表的域名。 下面的关键字是保留的,不能用作名字: and break do else elseif end false for function if in local nil not or repeat return then true until while Lua 是一个大小写敏感的语言: 下面这些是其它的 token : + - * / % ^ # == ~= <= >= < > = ( ) { } [ ] ; : , . .. ... 字符串既可以用一对单引号引起,也可以是双引号,里面还可以包含类似 C 的转义符: ' 只有在你需要把不同的引号、换行、反斜杠、或是零结束符这些字符置入字符串时, 你才必须使用转义符。别的任何字符都可以直接写在文本里。(一些控制符可以会影响文件系统造成某些问题, 但是不会引起 Lua 的任何问题。) 字符串还可以用一种长括号括起来的方式定义。 我们把两个正的方括号间插入 n 个等号定义为第 n 级正长括号。 就是说,0 级正的长括号写作 另一个约定是,当正的长括号后面立即跟了一个换行符, 这个换行符就不包含在这个字符串内。 举个例子,假设一个系统使用 ASCII 码 (这时,' a = 'alo\n123"' a = "alo\n123\"" a = '\97lo\10\04923"' a = [[alo 123"]] a = [==[ alo 123"]==] 数字常量可以分两部分写,十进制底数部分和十进制的指数部分。指数部分是可选的。 Lua 也支持十六进制整数常量,只需要在前面加上前缀 3 3.0 3.1416 314.16e-2 0.31416E1 0xff 0x56 注释可以在除字符串内的任何地方是以两横 ( 2.2 - 值与类型Lua 是一种 动态类型语言。 这意味着变量没有类型,只有值才有类型。 语言中不存在类型定义。而所有的值本身携带它们自己的类型信息。 Lua 中的所有值都是一致 (first-class) 的。 这意味着所有的值都可以被放在变量里,当作参数传递到另一个函数中,并被函数作为结果返回。 Lua 中有八种基本类型: nil, boolean, number, string, function, userdata, thread, and table. Nil 类型只有一种值 nil ,它的主要用途用于标表识和别的任何值的差异; 通常,当需要描述一个无意义的值时会用到它。 Boolean 类型只有两种值:false 和 true。nil 和 false 都能导致条件为假;而另外所有的值都被当作真。 Number 表示实数(双精度浮点数)。 (编译一个其它内部数字类型的 Lua 解释器是件很容易的事;比如把内部数字类型改作 单精度浮点数或长整型。参见文件 Lua 可以调用(和处理)用 Lua 写的函数以及用 C 写的函数(参见 §2.5.8). userdata 类型用来将任意 C 数据保存在 Lua 变量中。 这个类型相当于一块原生的内存,除了赋值和相同性判断,Lua 没有为之预定义任何操作。 然而,通过使用 metatable (元表) ,程序员可以为 userdata 自定义一组操作 (参见 §2.8)。 userdata 不能在 Lua 中创建出来,也不能在 Lua 中修改。这样的操作只能通过 C API。 这一点保证了宿主程序完全掌管其中的数据。 thread 类型用来区别独立的执行线程,它被用来实现 coroutine (协同例程)(参见 §2.11)。 不要把 Lua 线程跟操作系统的线程搞混。 Lua 可以在所有的系统上提供对 coroutine 的支持,即使系统并不支持线程。 table 类型实现了一个关联数组。也就是说, 数组可以用任何东西(除了nil)做索引,而不限于数字。 table 可以以不同类型的值构成;它可以包含所有的类型的值(除 nil 外)。 table 是 lua 中唯一的一种数据结构;它可以用来描述原始的数组、符号表、集合、 记录、图、树、等等。 用于表述记录时,lua 使用域名作为索引。 语言本身采用一种语法糖,支持以 跟索引一样, table 每个域中的值也可以是任何类型(除 nil外)。 特别的,因为函数本身也是值,所以 table 的域中也可以放函数。 这样 table 中就可以有一些 methods 了 (参见see §2.5.9)。 table, function ,thread ,和 (full) userdata 这些类型的值是所谓的对象: 变量本身并不会真正的存放它们的值,而只是放了一个对对象的引用。 赋值,参数传递,函数返回,都是对这些对象的引用进行操作; 这些操作不会做暗地里做任何性质的拷贝。 库函数 2.2.1 - 强制转换Lua 提供运行时字符串到数字的自动转换。 任何对字符串的数学运算操作都会尝试用一般的转换规则把这个字符串转换成一个数字。 相反,无论何时,一个数字需要作为字符串来使用时,数字都会以合理的格式转换为字符串。 需要完全控制数字怎样转换为字符串,可以使用字符串库中的 2.3 - 变量写上变量的地方意味着当以其保存的值来替代之。 Lua 中有三类变量:全局变量,局部变量,还有 table 的域。 一个单一的名字可以表示一个全局变量,也可以表示一个局部变量 (或者是一个函数的参数,这是一种特殊形式的局部变量): var ::= Name Name 就是 §2.1 中所定义的标识符。 任何变量都被假定为全局变量,除非显式的以 local 修饰定义 (参见 §2.4.7)。 局部变量有其作用范围: 局部变量可以被定义在它作用范围中的函数自由使用 (参见 §2.6)。 在变量的首次赋值之前,变量的值均为 nil。 方括号被用来对 table 作索引: var ::= prefixexp `[´ exp `]´ 对全局变量以及 table 域之访问的含义可以通过 metatable 来改变。 以取一个变量下标指向的量
var ::= prefixexp `.´ Name 所有的全局变量都是放在一个特定 lua table 的诸个域中,这个特定的 table 叫作 environment (环境)table 或者简称为 环境 (参见§2.9)。 每个函数都有对一个环境的引用, 所以一个函数中可见的所有全局变量都放在这个函数所引用的环境表(environment table)中。 当一个函数被创建出来,它会从创建它的函数中继承其环境,你可以调用 对一个全局变量 gettable_event(_env, "x") 这里, 2.4 - 语句段(Statement)Lua 支持惯例形式的语句段,它和 Pascal 或是 C 很相象。 这个集合包括赋值,控制结构,函数调用,还有变量声明。 2.4.1 - Chunk(语句组)Lua 的一个执行单元被称作 chunk。 一个 chunk 就是一串语句段,它们会被循序的执行。 每个语句段可以以一个分号结束: chunk ::= {stat [`;´]} 这儿不允许有空的语句段,所以 ' lua 把一个 chunk 当作一个拥有不定参数的匿名函数 (参见 §2.5.9)处理。 正是这样,chunk 内可以定义局部变量,接收参数,并且返回值。 chunk 可以被保存在一个文件中,也可以保存在宿主程序的一个字符串中。 当一个 chunk 被执行,首先它会被预编译成虚拟机中的指令序列, 然后被虚拟机解释运行这些指令。 chunk 也可以被预编译成二进制形式;细节参考程序 2.4.2 - 语句块语句块是一列语句段;从语法上来说,一个语句块跟一个 chunk 相同: block ::= chunk 一个语句块可以被显式的写成一个单独的语句段: stat ::= do block end 显式的语句块对于控制变量的作用范围很有用。 有时候,显式的语句块被用来在另一个语句块中插入 return 或是 break (参见§2.4.4)。 2.4.3 - 赋值Lua 允许多重赋值。 因此,赋值的语法定义是等号左边放一系列变量, 而等号右边放一系列的表达式。 两边的元素都用逗号间开: stat ::= varlist1 `=´ explist1 varlist1 ::= var {`,´ var} explist1 ::= exp {`,´ exp} 表达式放在 §2.5 里讨论。 在作赋值操作之前, 那一系列的右值会被对齐到左边变量需要的个数。 如果右值比需要的更多的话,多余的值就被扔掉。 如果右值的数量不够需求, 将会按所需扩展若干个 nil。 如果表达式列表以一个函数调用结束, 这个函数所返回的所有值都会在对齐操作之前被置入右值序列中。 (除非这个函数调用被用括号括了起来;参见 §2.5)。 赋值段首先会做运算完所有的表达式,然后仅仅做赋值操作。 因此,下面这段代码 i = 3 i, a[i] = i+1, 20 会把 x, y = y, x 可以用来交换 对全局变量以及 table 中的域的赋值操作的含义可以通过 metatable 来改变。 对变量下标指向的赋值,即 对于全局变量的赋值 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 Lua 也有一个 for 语句,它有两种形式(参见 §2.4.5)。 控制结构中的条件表达式可以返回任何值。 false 和 nil 两者都被认为是假条件。 所有不同于 nil 和 false 的其它值都被认为是真 (特别需要注意的是,数字 0 和空字符串也被认为是真)。 在 repeat–until 循环中, 内部语句块的结束点不是在 until 这个关键字处, 它还包括了其后的条件表达式。 因此,条件表达式中可以使用循环内部语句块中的定义的局部变量。 return 被用于从函数或是 chunk(其实它就是一个函数)中 返回值。 函数和 chunk 可以返回不只一个值, 所以 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 v = 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 local v = var block var = var + step end end 注意下面这几点:
一般形式的 for 通过一个叫作迭代器(iterators)的函数工作。 每次迭代,迭代器函数都会被调用以产生一个新的值, 当这个值为 nil时,循环停止。 一般形式的 for 循环的语法如下: stat ::= for namelist in explist1 do block end namelist ::= Name {`,´ Name} for 语句好似这样 for var_1, ···, var_n in explist do block end 它等价于这样一段代码: do local f, s, var = explist while true do local var_1, ···, var_n = f(s, var) var = var_1 if var == nil then break end block end end 注意以下几点:
2.4.6 - 把函数调用作为语句段为了允许使用可能的副作用, 函数调用可以被作为一个语句段执行: stat ::= functioncall 在这种情况下,所有的返回值都被舍弃。 函数调用在 §2.5.8 中解释。 2.4.7 - 局部变量声名局部变量可以在语句块中任何地方声名。 声名可以包含一个初始化赋值操作: stat ::= local namelist [`=´ explist1] 如果有的话,初始化赋值操作的行为等同于赋值操作(参见 §2.4.3)。 否则,所有的变量将被初始化为 nil。 一个 chunk 同时也是一个语句块(参见 §2.4.1), 所以局部变量可以放在 chunk 中那些显式注明的语句块之外。 这些局部变量的作用范围从声明起一直延伸到 chunk 末尾。 局部变量的可见规则在 §2.6 中解释。 2.5 - 表达式Lua 中有这些基本表达式: exp ::= prefixexp exp ::= nil | false | true exp ::= Number exp ::= String exp ::= function exp ::= tableconstructor exp ::= `...´ exp ::= exp binop exp exp ::= unop exp prefixexp ::= var | functioncall | `(´ exp `)´ 数字和字符串在 §2.1 中解释; 变量在 §2.3 中解释; 函数定义在 §2.5.9 中解释; 函数调用在 §2.5.8 中解释; table 的构造在§2.5.7 中解释; 可变参数的表达式写作三个点 (' 二元操作符包含有数学运算操作符(参见 §2.5.1), 比较操作符(参见 §2.5.2),逻辑操作符(参见 §2.5.3), 以及连接操作符(参见 §2.5.4)。 一元操作符包括负号(参见see §2.5.1), 取反 not(参见 §2.5.3), 和取长度操作符(参见 §2.5.5)。 函数调用和可变参数表达式都可以放在多重返回值中。 如果表达式作为一个独立语句段出现(参见 §2.4.6) (这只能是一个函数调用), 它们的返回列表将被对齐到零个元素,也就是忽略所有返回值。 如果表达式用于表达式列表的最后(或者是唯一)的元素, 就不会有任何的对齐操作(除非函数调用用括号括起来)。 在任何其它的情况下,Lua 将把表达式结果看成单一元素, 忽略除第一个之外的任何值。 这里有一些例子: f() -- 调整到 0 个结果 g(f(), x) -- f() 被调整到一个结果 g(x, f()) -- g 被传入 x 加上所有 f() 的返回值 a,b,c = f(), x -- f() 被调整到一个结果 ( c 在这里被赋为 nil ) a,b = ... -- a 被赋值为可变参数中的第一个, -- b 被赋值为第二个 (如果可变参数中并没有对应的值, -- 这里 a 和 b 都有可能被赋为 nil) a,b,c = x, f() -- f() 被调整为两个结果 a,b,c = f() -- f() 被调整为三个结果 return f() -- 返回 f() 返回的所有结果 return ... -- 返回所有从可变参数中接收来的值 return x,y,f() -- 返回 x, y, 以及所有 f() 的返回值 {f()} -- 用 f() 的所有返回值创建一个列表 {...} -- 用可变参数中的所有值创建一个列表 {f(), nil} -- f() 被调整为一个结果 被括号括起来的表达式永远被当作一个值。所以, 2.5.1 - 数学运算操作符Lua 支持常见的数学运算操作符: 二元操作 a % b == a - math.floor(a/b)*b 这就是说,其结果是商相对负无穷圆整后的余数。(译注:负数对正数取模的结果为正数) 2.5.2 - 比较操作符Lua 中的比较操作符有 == ~= < > <= >= 这些操作的结果不是 false 就是 true。 等于操作 ( 你可以改变 Lua 比较 table 和 userdata 的方式,这需要使用 "eq" 这个原方法 (参见 §2.8)。 §2.2.1 中提及的转换规则并不作用于比较操作。 所以, 操作符 大小比较操作以以下方式进行。 如果参数都是数字,那么就直接做数字比较。 否则,如果参数都是字符串,就用字符串比较的方式进行。 再则,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 时, 返回这第一个参数,否则返回第二个参数。 and 和 or 都遵循短路规则; 也就是说,第二个操作数只在需要的时候去求值。 这里有一些例子: 10 or 20 --> 10 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 - 取长度操作符取长度操作符写作一元操作 table 2.5.6 - 优先级Lua 中操作符的优先级写在下表中,从低到高优先级排序: or and < > <= >= ~= == .. + - * / % not # - (unary) ^ 通常,你可以用括号来改变运算次序。 连接操作符 (' 2.5.7 - Table 构造table 构造子是一个构造 table 的表达式。 每次构造子被执行,都会构造出一个新的 table 。 构造子可以被用来构造一个空的 table, 也可以用来构造一个 table 并初始化其中的一些域。 一般的构造子的语法如下 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 t = {} t[f(1)] = g t[1] = "x" -- 1st exp t[2] = "y" -- 2nd exp t.x = 1 -- t["x"] = 1 t[3] = f(x) -- 3rd exp t[30] = 23 t[4] = 45 -- 4th exp a = t end 如果表单中最后一个域的形式是 初始化域表可以在最后多一个分割符, 这样设计可以方便由机器生成代码。 2.5.8 - 函数调用Lua 中的函数调用的语法如下: functioncall ::= prefixexp args 函数调用时,第一步,prefixexp 和 args 先被求值。 如果 prefixexp 的值的类型是 function, 那么这个函数就被用给出的参数调用。 否则 prefixexp 的元方法 "call" 就被调用, 第一个参数就是 prefixexp 的值,跟下来的是原来的调用参数 (参见 §2.8)。 这样的形式 functioncall ::= prefixexp `:´ Name args 可以用来调用 "方法"。 这是 Lua 支持的一种语法糖。像 参数的语法如下: args ::= `(´ [explist1] `)´ args ::= tableconstructor args ::= String 所有参数的表达式求值都在函数调用之前。 这样的调用形式 因为表达式语法在 Lua 中比较自由, 所以你不能在函数调用的 ' a = f (g).x(a) Lua 将把它当作一个单一语句段, 这样一种调用形式: return (f(x)) -- 返回值被调整为一个 return 2 * f(x) return x, f(x) -- 最加若干返回值 f(x); return -- 无返回值 return x or f(x) -- 返回值被调整为一个 2.5.9 - 函数定义函数定义的语法如下: function ::= function funcbody funcbody ::= `(´ [parlist1] `)´ block end 另外定义了一些语法糖简化函数定义的写法: stat ::= function funcname funcbody stat ::= local function Name funcbody funcname ::= Name {`.´ Name} [`:´ Name] 这样的写法: function f () body end 被转换成 f = function () body end 这样的写法: function t.a.b.c.f () body end 被转换成 t.a.b.c.f = function () body end 这样的写法: local function f () body end 被转换成 local f; f = function () body end 注意,并不是转换成 local f = function () body end (这个差别只在函数体内需要引用 一个函数定义是一个可执行的表达式, 执行结果是一个类型为 function 的值。 当 Lua 预编译一个 chunk 的时候, chunk 作为一个函数,整个函数体也就被预编译了。 那么,无论何时 Lua 执行了函数定义, 这个函数本身就被实例化了(或者说是关闭了)。 这个函数的实例(或者说是 closure(闭包)) 是表达式的最终值。 相同函数的不同实例有可能引用不同的外部局部变量, 也可能拥有不同的环境表。 形参(函数定义需要的参数)是一些由实参(实际传入参数)的值初始化的局部变量: parlist1 ::= namelist [`,´ `...´] | `...´ 当一个函数被调用, 如果函数没有被定义为接收不定长参数,即在形参列表的末尾注明三个点 (' 我们先做如下定义,然后再来看一个例子: |
请发表评论