Lua 是一门扩展式程序设计语言,被设计成支持通用过程式编程,并有相关数据描述设施。 同时对面向对象编程、函数式编程和数据驱动式编程也提供了良好的支持。 它作为一个强大、轻量的嵌入式脚本语言,可供任何需要的程序使用。 Lua 由 clean C(标准 C 和 C++ 间共通的子集) 实现成一个库。 作为一门扩展式语言,Lua 没有 "main" 程序的概念: 它只能 嵌入 一个宿主程序中工作, 该宿主程序被称为 被嵌入程序 或者简称 宿主 。 宿主程序可以调用函数执行一小段 Lua 代码,可以读写 Lua 变量,可以注册 C 函数让 Lua 代码调用。 依靠 C 函数,Lua 可以共享相同的语法框架来定制编程语言,从而适用不同的领域。 Lua 的官方发布版包含一个叫做 lua 的宿主程序示例, 它是一个利用 Lua 库实现的完整独立的 Lua 解释器,可用于交互式应用或批处理。 Lua 是一个自由软件,其使用许可证决定了它的使用过程无需任何担保。 本手册所描述的实现可以在 Lua 的官方网站 www.lua.org 找到。 与其它的许多参考手册一样,这份文档有些地方比较枯燥。 关于 Lua 背后的设计思想, 可以看看 Lua 网站上提供的技术论文。 至于用 Lua 编程的细节介绍, 请参阅 Roberto 的书, Programming in Lua

2 – 基本概念

本章描述了语言的基本概念。

2.1 – 值与类型

Lua 是一门 动态类型语言 。 这意味着变量没有类型;只有值才有类型。 语言中不设类型定义。 所有的值携带自己的类型。 Lua 中所有的值都是 一等公民 。 这意味着所有的值均可保存在变量中、 当作参数传递给其它函数、以及作为返回值。 Lua 中有八种基本类型: nil boolean number string function userdata thread table Nil 是值 nil 的类型, 其主要特征就是和其它值区别开;通常用来表示一个有意义的值不存在时的状态。 Boolean false true 两个值的类型。 nil false 都会导致条件判断为假; 而其它任何值都表示为真。 Number 代表了整数和实数(浮点数)。 String 表示一个不可变的字节序列。 Lua 对 8 位是友好的: 字符串可以容纳任意 8 位值, 其中包含零 (' \0 ') 。 Lua 的字符串与编码无关; 它不关心字符串中具体内容。 number 类型有两种内部表现方式, 整数 浮点数 。 对于何时使用哪种内部形式,Lua 有明确的规则, 但它也按需(参见 §3.4.3 )作自动转换。 因此,程序员多数情况下可以选择忽略整数与浮点数之间的差异或者假设完全控制每个数字的内部表现方式。 标准 Lua 使用 64 位整数和双精度(64 位)浮点数, 但你也可以把 Lua 编译成使用 32 位整数和单精度(32 位)浮点数。 以 32 位表示数字对小型机器以及嵌入式系统特别合适。 (参见 luaconf.h 文件中的宏 LUA_32BITS 。) Lua 可以调用(以及操作)用 Lua 或 C (参见 §3.4.10 )编写的函数。 这两种函数有统一类型 function userdata 类型允许将 C 中的数据保存在 Lua 变量中。 用户数据类型的值是一个内存块, 有两种用户数据: 完全用户数据 ,指一块由 Lua 管理的内存对应的对象; 轻量用户数据 ,则指一个简单的 C 指针。 用户数据在 Lua 中除了赋值与相等性判断之外没有其他预定义的操作。 通过使用 元表 ,程序员可以给完全用户数据定义一系列的操作 (参见 §2.4 )。 你只能通过 C API 而无法在 Lua 代码中创建或者修改用户数据的值, 这保证了数据仅被宿主程序所控制。 thread 类型表示了一个独立的执行序列,被用于实现协程 (参见 §2.6 )。 Lua 的线程与操作系统的线程毫无关系。 Lua 为所有的系统,包括那些不支持原生线程的系统,提供了协程支持。 table 是一个关联数组, 也就是说,这个数组不仅仅以数字做索引,除了 nil 和 NaN 之外的所有 Lua 值 都可以做索引。 ( Not a Number 是一个特殊的数字,它用于表示未定义或表示不了的运算结果,比如 0/0 。) 表可以是 异构 的; 也就是说,表内可以包含任何类型的值( nil 除外)。 任何键的值若为 nil 就不会被记入表结构内部。 换言之,对于表内不存在的键,都对应着值 nil 。 表是 Lua 中唯一的数据结构, 它可被用于表示普通数组、序列、符号表、集合、记录、图、树等等。 对于记录,Lua 使用域名作为索引。 语言提供了 a.name 这样的语法糖来替代 a["name"] 这种写法以方便记录这种结构的使用。 在 Lua 中有多种便利的方式创建表(参见 §3.4.9 )。 我们使用 序列 这个术语来表示一个用 {1.. n } 的正整数集做索引的表。 这里的非负整数 n 被称为该序列的长度(参见 §3.4.7 )。 和索引一样,表中每个域的值也可以是任何类型。 需要特别指出的是:既然函数是一等公民,那么表的域也可以是函数。 这样,表就可以携带 方法 了。 (参见 §3.4.11 )。 索引一张表的原则遵循语言中的直接比较规则。 当且仅当 i j 直接比较相等时 (即不通过元方法的比较), 表达式 a[i] a[j] 表示了表中相同的元素。 特别指出:一个可以完全表示为整数的浮点数和对应的整数相等 (例如: 1.0 == 1 )。 为了消除歧义,当一个可以完全表示为整数的浮点数做为键值时, 都会被转换为对应的整数储存。 例如,当你写 a[2.0] = true 时, 实际被插入表中的键是整数 2 。 (另一方面,2 与 " 2 " 是两个不同的 Lua 值, 故而它们可以是同一张表中的不同项。) 表、函数、线程、以及完全用户数据在 Lua 中被称为 对象 : 变量并不真的 持有 它们的值,而仅保存了对这些对象的 引用 。 赋值、参数传递、函数返回,都是针对引用而不是针对值的操作, 这些操作均不会做任何形式的隐式拷贝。 库函数 type 用于以字符串形式返回给定值的类型。 (参见 §6.1 )。 (因此,它们也被称为 全局变量 )。 此外,所有的标准库都被加载入全局环境,一些函数也针对这个环境做操作。 你可以用 load (或 loadfile )加载代码块,并赋予它们不同的环境。 (在 C 里,当你加载一个代码块后,可以通过改变它的第一个上值来改变它的环境。) 由于 Lua 是一门嵌入式扩展语言,其所有行为均源于宿主程序中 C 代码对某个 Lua 库函数的调用。 (单独使用 Lua 时, lua 程序就是宿主程序。) 所以,在编译或运行 Lua 代码块的过程中,无论何时发生错误, 控制权都返回给宿主,由宿主负责采取恰当的措施(比如打印错误消息)。 可以在 Lua 代码中调用 error 函数来显式地抛出一个错误。 如果你需要在 Lua 中捕获这些错误, 可以使用 pcall xpcall 保护模式 下调用一个函数。 无论何时出现错误,都会抛出一个携带错误信息的 错误对象 错误消息 )。 Lua 本身只会为错误生成字符串类型的错误对象, 但你的程序可以为错误生成任何类型的错误对象, 这就看你的 Lua 程序或宿主程序如何处理这些错误对象。 使用 xpcall lua_pcall 时, 你应该提供一个 消息处理函数 用于错误抛出时调用。 该函数需接收原始的错误消息,并返回一个新的错误消息。 它在错误发生后栈尚未展开时调用, 因此可以利用栈来收集更多的信息, 比如通过探知栈来创建一组栈回溯信息。 同时,该处理函数也处于保护模式下,所以该函数内发生的错误会再次触发它(递归)。 如果递归太深,Lua 会终止调用并返回一个合适的消息。

2.4 – 元表及元方法

Lua 中的每个值都可以有一个 元表 。 这个 元表 就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。 如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。 例如,当你对非数字值做加操作时, Lua 会检查该值的元表中的 " __add " 域下的函数。 如果能找到,Lua 则调用这个函数来完成加这个操作。 元表中的键对应着不同的 事件 名; 键关联的那些值被称为 元方法 。 在上面那个例子中引用的事件为 "add" , 完成加操作的那个函数就是元方法。 你可以用 getmetatable 函数 来获取任何值的元表。 使用 setmetatable 来替换一张表的元表。在 Lua 中,你不可以改变表以外其它类型的值的元表 (除非你使用调试库(参见 §6.10 )); 若想改变这些非表类型的值的元表,请使用 C API。 表和完全用户数据有独立的元表 (当然,多个表和用户数据可以共享同一个元表)。 其它类型的值按类型共享元表; 也就是说所有的数字都共享同一个元表, 所有的字符串共享另一个元表等等。 默认情况下,值是没有元表的, 但字符串库在初始化的时候为字符串类型设置了元表 (参见 §6.4 )。 元表决定了一个对象在数学运算、位运算、比较、连接、 取长度、调用、索引时的行为。 元表还可以定义一个函数,当表对象或用户数据对象在垃圾回收 (参见 §2.5 )时调用它。 接下来会给出一张元表可以控制的事件的完整列表。 每个操作都用对应的事件名来区分。 每个事件的键名用加有 ' __ ' 前缀的字符串来表示; 例如 "add" 操作的键名为字符串 " __add "。 注意、Lua 从元表中直接获取元方法; 访问元表中的元方法永远不会触发另一次元方法。 下面的代码模拟了 Lua 从一个对象 obj 中获取一个元方法的过程: rawget(getmetatable(obj) or {}, "__" .. event_name) 对于一元操作符(取负、求长度、位反), 元方法调用的时候,第二个参数是个哑元,其值等于第一个参数。 这样处理仅仅是为了简化 Lua 的内部实现 (这样处理可以让所有的操作都和二元操作一致), 这个行为有可能在将来的版本中移除。 (使用这个额外参数的行为都是不确定的。) 小于等于操作可能用到两个不同的事件。 首先,像 "lt" 操作的行为那样,Lua 在两个操作数中查找 " __le " 元方法。 如果一个元方法都找不到,就会再次查找 " __lt " 事件, 它会假设 a <= b 等价于 not (b < a) 。 而其它比较操作符类似,其结果会被转换为布尔量。
  • "index": 索引 table[key] 。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。 此时,会读出 table 相应的元方法。 尽管名字取成这样, 这个事件的元方法其实可以是一个函数也可以是一张表。 如果它是一个函数,则以 table key 作为参数调用它。 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。 (这个索引过程是走常规的流程,而不是直接索引, 所以这次索引有可能引发另一次元方法。)
  • "newindex": 索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。 同索引过程那样, 这个事件的元方法即可以是函数,也可以是一张表。 如果是一个函数, 则以 table key 、以及 value 为参数传入。 如果是一张表, Lua 对这张表做索引赋值操作。 (这个索引过程是走常规的流程,而不是直接索引赋值, 所以这次索引赋值有可能引发另一次元方法。) 一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)
  • "call": 函数调用操作 func(args) 。 当 Lua 尝试调用一个非函数的值的时候会触发这个事件 (即 func 不是一个函数)。 查找 func 的元方法, 如果找得到,就调用这个元方法, func 作为第一个参数传入,原来调用的参数( args )后依次排在后面。 Lua 运行了一个 垃圾收集器 来收集所有 死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。 Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率 垃圾收集器步进倍率 。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。 垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。 垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的“两倍”速工作。 如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。 (例如:放在一个全局变量里),这次复活就持续生效了。 此外,如果在终结器中对一个正进入终结流程的对象再次做一次标记让它触发终结器, 只要这个对象在下个循环中依旧不可达,它的终结函数还会再调用一次。 无论是哪种情况, 对象所属内存仅在垃圾收集循环中该对象不可达且 没有被标记成需要触发终结器才会被释放。 当你关闭一个状态机(参见 lua_close ), Lua 将调用所有被标记了需要触发终结器对象的终结过程, 其次序为标记次序的逆序。 在这个过程中,任何终结器再次标记对象的行为都不会生效。 print("main", coroutine.resume(co, 1, 10)) print("main", coroutine.resume(co, "r")) print("main", coroutine.resume(co, "x", "y")) print("main", coroutine.resume(co, "x", "y")) 当你运行它,将产生下列输出: co-body 1 10 foo 2 main true 4 co-body r main true 11 -9 co-body x y main true 10 end main false cannot resume dead coroutine 你也可以通过 C API 来创建及操作协程: lua_newthread lua_resume , 以及 lua_yield 。 and break do else elseif end false for function goto if in local nil not or repeat return then true until while Lua 语言对大小写敏感: and 是一个保留字,但 And AND 则是两个不同的有效名字。 作为一个约定,程序应避免创建以下划线加一个或多个大写字母构成的名字 (例如 _VERSION )。 下列字符串是另外一些符记: + - * / % ^ # & ~ | << >> // == ~= <= >= < > = ( ) { } [ ] :: ; : , . .. ... 字面串 可以用单引号或双引号括起。 字面串内部可以包含下列 C 风格的转义串: ' \a ' (响铃), ' \b ' (退格), ' \f ' (换页), ' \n ' (换行), ' \r ' (回车), ' \t ' (横项制表), ' \v ' (纵向制表), ' \\ ' (反斜杠), ' \" ' (双引号), 以及 ' \' ' (单引号)。 在反斜杠后跟一个真正的换行等价于在字符串中写一个换行符。 转义串 ' \z ' 会忽略其后的一系列空白符,包括换行; 它在你需要对一个很长的字符串常量断行为多行并希望在每个新行保持缩进时非常有用。 字面串还可以用一种 长括号 括起来的方式定义。 我们把两个正的方括号间插入 n 个等号定义为 n 级开长括号 。 就是说,0 级开的长括号写作 [[ , 一级开长括号写作 [=[ , 如此等等。 闭长括号 也作类似定义; 举个例子,4 级反的长括号写作 ]====] 。 一个 长字面串 可以由任何一级的开长括号开始,而由第一个碰到的同级的闭长括号结束。 这种方式描述的字符串可以包含任何东西,当然特定级别的反长括号除外。 整个词法分析过程将不受分行限制,不处理任何转义符,并且忽略掉任何不同级别的长括号。 其中碰到的任何形式的换行串(回车、换行、回车加换行、换行加回车),都会被转换为单个换行符。 字面串中的每个不被上述规则影响的字节都呈现为本身。 然而,Lua 是用文本模式打开源文件解析的, 一些系统的文件操作函数对某些控制字符的处理可能有问题。 因此,对于非文本数据,用引号括起来并显式按转义符规则来表述更安全。 为了方便起见, 当一个开长括号后紧接一个换行符时, 这个换行符不会放在字符串内。 举个例子,假设一个系统使用 ASCII 码 (此时 ' a ' 编码为 97 , 换行编码为 10 ,' 1 ' 编码为 49 ), 下面五种方式描述了完全相同的字符串: a = 'alo\n123"' a = "alo\n123\"" a = '\97lo\10\04923"' a = [[alo 123"]] a = [==[ 123"]==] 数字常量 (或称为 数字量 ) 可以由可选的小数部分和可选的十为底的指数部分构成, 指数部分用字符 ' e ' 或 ' E ' 来标记。 Lua 也接受以 0x 0X 开头的 16 进制常量。 16 进制常量也接受小数加指数部分的形式,指数部分是以二为底, 用字符 ' p ' 或 ' P ' 来标记。 数字常量中包含小数点或指数部分时,被认为是一个浮点数; 否则被认为是一个整数。 下面有一些合法的整数常量的例子: 3 345 0xff 0xBEBADA 以下为合法的浮点常量: 3.0 3.1416 314.16e-2 0.31416E1 34e1 0x0.1E 0xA23p-4 0X1.921FB54442D18P+1 在字符串外的任何地方出现以双横线 ( -- ) 开头的部分是 注释 。 如果 -- 后没有紧跟着一个开大括号, 该注释为 短注释 , 注释到当前行末截至。 否则,这是一段 长注释 , 注释区一直维持到对应的闭长括号。 长注释通常用于临时屏蔽掉一大段代码。

    3.2 – 变量

    变量是储存值的地方。 Lua 中有三种变量: 全局变量、局部变量和表的域。 单个名字可以指代一个全局变量也可以指代一个局部变量 (或者是一个函数的形参,这是一种特殊形式的局部变量)。 var ::= Name 名字指 §3.1 中定义的标识符。 所有没有显式声明为局部变量(参见 §3.3.7 ) 的变量名都被当做全局变量。 局部变量有其 作用范围 : 局部变量可以被定义在它作用范围中的函数自由使用(参见 §3.5 )。 在变量的首次赋值之前,变量的值均为 nil 。 方括号被用来对表作索引: var ::= prefixexp ‘ [ ’ exp ‘ ] ’ 对全局变量以及表的域之访问的含义可以通过元表来改变。 以索引方式访问一个变量 t[i] 等价于 调用 gettable_event(t,i) 。 (参见 §2.4 ,有一份完整的关于 gettable_event 函数的说明。 这个函数并没有在 lua 中定义出来,也不能在 lua 中调用。这里我们把提到它只是方便说明问题。) var.Name 这种语法只是一个语法糖,用来表示 var["Name"] : var ::= prefixexp ‘ . ’ Name 对全局变量 x 的操作等价于操作 _ENV.x 。 由于代码块编译的方式, _ENV 永远也不可能是一个全局名字 (参见 §2.2 )。 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 语句,它有两种形式 (参见 §3.3.5 )。 控制结构中的条件表达式可以返回任何值。 false nil 两者都被认为是假。 所有不同于 nil false 的其它值都被认为是真 (特别需要注意的是,数字 0 和空字符串也被认为是真)。 在 repeat until 循环中, 内部语句块的结束点不是在 until 这个关键字处, 它还包括了其后的条件表达式。 因此,条件表达式中可以使用循环内部语句块中的定义的局部变量。 goto 语句将程序的控制点转移到一个标签处。 由于句法上的原因, Lua 里的标签也被认为是语句: local var , limit , step = tonumber( e1 ), tonumber( e2 ), tonumber( e3 ) if not ( var and limit and step ) then error() end var = var - step while true do var = var + step if ( step >= 0 and var > limit ) or ( step < 0 and var < limit ) then break local v = var block 注意下面这几点: f() -- 调整为 0 个结果 g(f(), x) -- f() 会被调整为一个结果 g(x, f()) -- g 收到 x 以及 f() 返回的所有结果 a,b,c = f(), x -- f() 被调整为 1 个结果 (c 收到 nil) a,b = ... -- a 收到可变参数列表的第一个参数, -- b 收到第二个参数(如果可变参数列表中 -- 没有实际的参数,a 和 b 都会收到 nil) a,b,c = x, f() -- f() 被调整为 2 个结果 a,b,c = f() -- f() 被调整为 3 个结果 return f() -- 返回 f() 的所有返回结果 return ... -- 返回从可变参数列表中接收到的所有参数parameters return x,y,f() -- 返回 x, y, 以及 f() 的所有返回值 {f()} -- 用 f() 的所有返回值创建一个列表 {...} -- 用可变参数中的所有值创建一个列表 {f(), nil} -- f() 被调整为一个结果 被括号括起来的表达式永远被当作一个值。 (f(x,y,z)) 即使 f 返回多个值, 这个表达式永远是一个单一值。 ( (f(x,y,z)) 的值是 f 返回的第一个值。 如果 f 不返回值的话,那么它的值就是 nil 。)

    3.4.1 – 数学运算操作符

    Lua 支持下列数学运算操作符:

  • + : 加法
  • - : 减法
  • * : 乘法
  • / : 浮点除法
  • // : 向下取整除法
  • % : 取模
  • ^ : 乘方
  • - : 取负
  • 除了乘方和浮点除法运算, 数学运算按如下方式工作: 如果两个操作数都是整数, 该操作以整数方式操作且结果也将是一个整数。 否则,当两个操作数都是数字或可以被转换为数字的字符串 (参见 §3.4.3 )时, 操作数会被转换成两个浮点数, 操作按通常的浮点规则(一般遵循 IEEE 754 标准) 来进行,结果也是一个浮点数。 乘方和浮点除法 ( / ) 总是把操作数转换成浮点数进行,其结果总是浮点数。 乘方使用 ISO C 函数 pow , 因此它也可以接受非整数的指数。 向下取整的除法 ( // ) 指做一次除法,并将商圆整到靠近负无穷的一侧, 即对操作数做除法后取 floor 。 取模被定义成除法的余数,其商被圆整到靠近负无穷的一侧(向下取整的除法)。 对于整数数学运算的溢出问题, 这些操作采取的策略是按通常遵循的以 2 为补码的数学运算的 环绕 规则。 (换句话说,它们返回其运算的数学结果对 2 64 取模后的数字。)

    3.4.2 – 位操作符

    Lua 支持下列位操作符:

  • & : 按位与
  • | : 按位或
  • ~ : 按位异或
  • ~ : 按位非
  • 所有的位操作都将操作数先转换为整数 (参见 §3.4.3 ), 然后按位操作,其结果是一个整数。 对于右移和左移,均用零来填补空位。 移动的位数若为负,则向反方向位移; 若移动的位数的绝对值大于等于 整数本身的位数,其结果为零 (所有位都被移出)。 tableconstructor ::= ‘ { ’ [fieldlist] ‘ } ’ fieldlist ::= field {fieldsep field} [fieldsep] field ::= ‘ [ ’ exp ‘ ] ’ ‘ = ’ exp | Name ‘ = ’ exp | exp fieldsep ::= ‘ , ’ | ‘ ; ’ 每个形如 [exp1] = exp2 的域向表中增加新的一项, 其键为 exp1 而值为 exp2 。 形如 name = exp 的域等价于 ["name"] = exp 。 最后,形如 exp 的域等价于 [i] = exp , 这里的 i 是一个从 1 开始不断增长的数字。 这这个格式中的其它域不会破坏其记数。 举个例子: a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 } 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 构造子中赋值的次序未定义。 (次序问题只会对那些键重复时的情况有影响。) 如果表单中最后一个域的形式是 exp , 而且其表达式是一个函数调用或者是一个可变参数, 那么这个表达式所有的返回值将依次进入列表 (参见 §3.4.10 )。 初始化域表可以在最后多一个分割符, 这样设计可以方便由机器生成代码。 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 (这个差别只在函数体内需要引用 f 时才有。) 一个函数定义是一个可执行的表达式, 执行结果是一个类型为 function 的值。 当 Lua 预编译一个代码块时, 代码块作为一个函数,整个函数体也就被预编译了。 那么,无论何时 Lua 执行了函数定义, 这个函数本身就进行了 实例化 (或者说是 关闭 了)。 这个函数的实例(或者说是 闭包 )是表达式的最终值。 形参被看作是一些局部变量, 它们将由实参的值来初始化: parlist ::= namelist [‘ , ’ ‘ ... ’] | ‘ ... ’ 当一个函数被调用, 如果函数并非一个 可变参数函数 , 即在形参列表的末尾注明三个点 (' ... '), 那么实参列表就会被调整到形参列表的长度。 变长参数函数不会调整实参列表; 取而代之的是,它将把所有额外的参数放在一起通过 变长参数表达式 传递给函数, 其写法依旧是三个点。 这个表达式的值是一串实参值的列表, 看起来就跟一个可以返回多个结果的函数一样。 如果一个变长参数表达式放在另一个表达式中使用, 或是放在另一串表达式的中间, 那么它的返回值就会被调整为单个值。 若这个表达式放在了一系列表达式的最后一个, 就不会做调整了 (除非这最后一个参数被括号给括了起来)。 我们先做如下定义,然后再来看一个例子: 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, ... --> (nothing) g(3, 4) a=3, b=4, ... --> (nothing) g(3, 4, 5, 8) a=3, b=4, ... --> 5 8 g(5, r()) a=5, b=1, ... --> 2 3 结果由 return 来返回(参见 §3.3.4 )。 如果执行到函数末尾依旧没有遇到任何 return 语句, 函数就不会返回任何结果。 关于函数可返回值的数量限制和系统有关。 这个限制一定大于 1000 。 冒号 语法可以用来定义 方法 , 就是说,函数可以有一个隐式的形参 self 。 因此,如下语句 function t.a.b.c:f ( params ) body end 是这样一种写法的语法糖 t.a.b.c.f = function (self, params ) body end x = 10 -- 全局变量 do -- 新的语句块 local x = x -- 新的一个 'x', 它的值现在是 10 print(x) --> 10 x = x+1 do -- 另一个语句块 local x = x+1 -- 又一个 'x' print(x) --> 12 print(x) --> 11 print(x) --> 10 (取到的是全局的那一个) 注意这里,类似 local x = x 这样的声明, 新的 x 正在被声明,但是还没有进入它的作用范围, 所以第二个 x 指向的是外面一层的变量。 因为有这样一个词法作用范围的规则, 局部变量可以被在它的作用范围内定义的函数自由使用。 当一个局部变量被内层的函数中使用的时候, 它被内层函数称作 上值 ,或是 外部局部变量 。 注意,每次执行到一个 local 语句都会定义出一个新的局部变量。 看看这样一个例子: a = {} local x = 20 for i=1,10 do local y = 0 a[i] = function () y=y+1; return x+y end 这个循环创建了十个闭包(这指十个匿名函数的实例)。 这些闭包中的每一个都使用了不同的 y 变量, 而它们又共享了同一份 x 。 int original_function (lua_State *L) { ... /* code 1 */ status = lua_pcall(L, n, m, h); /* calls Lua */ ... /* code 2 */ 现在我们想允许被 lua_pcall 运行的 Lua 代码让出。 首先,我们把函数改写成这个样子: int k (lua_State *L, int status, lua_KContext ctx) { ... /* code 2 */ int original_function (lua_State *L) { ... /* code 1 */ return k(L, lua_pcall(L, n, m, h), ctx); 上面的代码中,新函数 k 就是一个 延续函数 (函数类型为 lua_KFunction )。 它的工作就是原函数中调用 lua_pcall 之后做的那些事情。 现在我们必须通知 Lua 说,你必须在被 lua_pcall 执行的 Lua 代码发生过中断(错误或让出)后, 还得继续调用 k 。 所以我们还得继续改写这段代码,把 lua_pcall 替换成 lua_pcallk : int original_function (lua_State *L) { ... /* code 1 */ return k(L, lua_pcallk(L, n, m, h, ctx2, k), ctx1); 注意这里那个额外的显式的对延续函数的调用: Lua 仅在需要时,这可能是由错误导致的也可能是发生了让出而需要继续运行,才会调用延续函数。 如果没有发生过任何让出,调用的函数正常返回, 那么 lua_pcallk (以及 lua_callk )也会正常返回。 (当然,这个例子中你也可以不在之后调用延续函数, 而是在原函数的调用后直接写上需要做的工作。) 除了 Lua 状态,延续函数还有两个参数: 一个是调用最后的状态码,另一个一开始由 lua_pcallk 传入的上下文 ( ctx )。 (Lua 本身不使用这个值;它仅仅从原函数转发这个值给延续函数。) 对于 lua_pcallk 而言, 状态码和 lua_pcallk 本应返回值相同,区别仅在于发生过让出后才执行完时,状态码为 LUA_YIELD (而不是 LUA_OK )。 对于 lua_yieldk lua_callk 而言, 调用延续函数传入的状态码一定是 LUA_YIELD 。 (对这两个函数,Lua 不会因任何错误而调用延续函数。 因为它们并不处理错误。) 同样,当你使用 lua_callk 时, 你应该用 LUA_OK 作为状态码来调用延续函数。 (对于 lua_yieldk , 几乎没有什么地方需要直接调用延续函数, 因为 lua_yieldk 本身并不会返回。) Lua 会把延续函数看作原函数。 延续函数将接收到和原函数相同的 Lua 栈,其接收到的 lua 状态也和 被调函数若返回后应该有的状态一致。 lua_callk 调用之后, 栈中之前压入的函数和调用参数都被调用产生的返回值所替代。) 这时也有相同的上值。 等到它返回的时候,Lua 会将其看待成原函数的返回去操作。 ud ,一个由 lua_newstate 传给它的指针; ptr ,一个指向已分配出来/将被重新分配/要释放的内存块指针; osize ,内存块原来的尺寸或是关于什么将被分配出来的代码; nsize ,新内存块的尺寸。 如果 ptr 不是 NULL osize ptr 指向的内存块的尺寸, 即这个内存块当初被分配或重分配的尺寸。 如果 ptr NULL osize 是 Lua 即将分配对象类型的编码。 当(且仅当)Lua 创建一个对应类型的新对象时, osize LUA_TSTRING LUA_TTABLE LUA_TFUNCTION LUA_TUSERDATA ,或 LUA_TTHREAD 中的一个。 若 osize 是其它类型,Lua 将为其它东西分配内存。 Lua 假定分配器函数会遵循以下行为: 当 nsize 是零时, 分配器必须和 free 行为类似并返回 NULL 。 当 nsize 不是零时, 分配器必须和 realloc 行为类似。 如果分配器无法完成请求,返回 NULL 。 Lua 假定在 osize >= nsize 成立的条件下, 分配器绝不会失败。 这里有一个简单的分配器函数的实现。 这个实现被放在补充库中,供 luaL_newstate 使用。 static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; /* not used */ if (nsize == 0) { free(ptr); return NULL; return realloc(ptr, nsize); 注意,标准 C 能确保 free(NULL) 没有副作用, 且 realloc(NULL,size) 等价于 malloc(size) 。 这段代码假定 realloc 在缩小块长度时不会失败。 (虽然标准 C 没有对此行为做出保证,但这看起来是一个安全的假定。)


    lua_call

    [-(nargs+1), +nresults, e ]

    void lua_call (lua_State *L, int nargs, int nresults);
    调用一个函数。 要调用一个函数请遵循以下协议: 首先,要调用的函数应该被压入栈; 接着,把需要传递给这个函数的参数按正序压栈; 这是指第一个参数首先压栈。 最后调用一下 lua_call nargs 是你压入栈的参数个数。 当函数调用完毕后,所有的参数以及函数本身都会出栈。 而函数的返回值这时则被压栈。 返回值的个数将被调整为 nresults 个, 除非 nresults 被设置成 LUA_MULTRET 。 在这种情况下,所有的返回值都被压入堆栈中。 Lua 会保证返回值都放入栈空间中。 函数返回值将按正序压栈(第一个返回值首先压栈), 因此在调用结束后,最后一个返回值将被放在栈顶。 被调用函数内发生的错误将(通过 longjmp )一直上抛。 下面的例子中,这行 Lua 代码等价于在宿主程序中用 C 代码做一些工作: a = f("how", t.x, 14) 这里是 C 里的代码: lua_getglobal(L, "f"); /* function to be called */ lua_pushliteral(L, "how"); /* 1st argument */ lua_getglobal(L, "t"); /* table to be indexed */ lua_getfield(L, -1, "x"); /* push result of t.x (2nd arg) */ lua_remove(L, -2); /* remove 't' from the stack */ lua_pushinteger(L, 14); /* 3rd argument */ lua_call(L, 3, 1); /* call 'f' with 3 arguments and 1 result */ lua_setglobal(L, "a"); /* set global 'a' */ 注意上面这段代码是 平衡 的: 到了最后,堆栈恢复成原有的配置。 这是一种良好的编程习惯。 static int foo (lua_State *L) { int n = lua_gettop(L); /* 参数的个数 */ lua_Number sum = 0.0; int i; for (i = 1; i <= n; i++) { if (!lua_isnumber(L, i)) { lua_pushliteral(L, "incorrect argument"); lua_error(L); sum += lua_tonumber(L, i); lua_pushnumber(L, sum/n); /* 第一个返回值 */ lua_pushnumber(L, sum); /* 第二个返回值 */ return 2; /* 返回值的个数 */ lua_pushnil(L); /* 第一个键 */ while (lua_next(L, t) != 0) { /* 使用 '键' (在索引 -2 处) 和 '值' (在索引 -1 处)*/ printf("%s - %s\n", lua_typename(L, lua_type(L, -2)), lua_typename(L, lua_type(L, -1))); /* 移除 '值' ;保留 '键' 做下一次迭代 */ lua_pop(L, 1); 在遍历一张表的时候, 不要直接对键调用 lua_tolstring , 除非你知道这个键一定是一个字符串。 调用 lua_tolstring 有可能改变给定索引位置的值; 这会对下一次调用 lua_next 造成影响。 关于迭代过程中修改被迭代的表的注意事项参见 next 函数。


    lua_Number

    typedef double lua_Number;
    Lua 中浮点数的类型。 Lua 中数字的类型。缺省是 double ,但是你可以改成 float 。 (参见 luaconf.h 中的 LUA_REAL 。)


    lua_pcall

    [-(nargs + 1), +(nresults|1), –]

    int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
    以保护模式调用一个函数。 nargs nresults 的含义与 lua_call 中的相同。 如果在调用过程中没有发生错误, lua_pcall 的行为和 lua_call 完全一致。 但是,如果有错误发生的话, lua_pcall 会捕获它, 然后把唯一的值(错误消息)压栈,然后返回错误码。 同 lua_call 一样, lua_pcall 总是把函数本身和它的参数从栈上移除。 如果 msgh 是 0 , 返回在栈顶的错误消息就和原始错误消息完全一致。 否则, msgh 就被当成是 错误处理函数 在栈上的索引位置。 (在当前的实现里,这个索引不能是伪索引。) 在发生运行时错误时, 这个函数会被调用而参数就是错误消息。 错误处理函数的返回值将被 lua_pcall 作为错误消息返回在堆栈上。 典型的用法中,错误处理函数被用来给错误消息加上更多的调试信息, 比如栈跟踪信息。 这些信息在 lua_pcall 返回后, 由于栈已经展开,所以收集不到了。 lua_pcall 函数会返回下列常数 (定义在 lua.h 内)中的一个:
  • LUA_OK (0):
  • LUA_ERRRUN : 运行时错误。
  • LUA_ERRMEM : 内存分配错误。对于这种错,Lua 不会调用错误处理函数。
  • LUA_ERRERR : 在运行错误处理函数时发生的错误。
  • LUA_ERRGCMM : 在运行 __gc 元方法时发生的错误。 (这个错误和被调用的函数无关。)


    lua_pushcclosure

    [-n, +1, e ]

    void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
    把一个新的 C 闭包压栈。 当创建了一个 C 函数后, 你可以给它关联一些值, 这就是在创建一个 C 闭包(参见 §4.4 ); 接下来无论函数何时被调用,这些值都可以被这个函数访问到。 为了将一些值关联到一个 C 函数上, 首先这些值需要先被压入堆栈(如果有多个值,第一个先压)。 接下来调用 lua_pushcclosure 来创建出闭包并把这个 C 函数压到栈上。 参数 n 告之函数有多少个值需要关联到函数上。 lua_pushcclosure 也会把这些值从栈上弹出。 n 的最大值是 255 。 当 n 为零时, 这个函数将创建出一个 轻量 C 函数 , 它就是一个指向 C 函数的指针。 这种情况下,不可能抛出内存错误。


    lua_pushcfunction

    [-0, +1, –]

    void lua_pushcfunction (lua_State *L, lua_CFunction f);
    将一个 C 函数压栈。 这个函数接收一个 C 函数指针, 并将一个类型为 function 的 Lua 值压栈。 当这个栈顶的值被调用时,将触发对应的 C 函数。 注册到 Lua 中的任何函数都必须遵循正确的协议来接收参数和返回值 (参见 lua_CFunction )。 lua_pushcfunction 是作为一个宏定义出现的: #define lua_pushcfunction(L,f) lua_pushcclosure(L,f,0)

    lua_pushfstring

    [-0, +1, e ]

    const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
    把一个格式化过的字符串压栈, 然后返回这个字符串的指针。 它和 C 函数 sprintf 比较像, 不过有一些重要的区别:


    lua_pushlstring

    [-0, +1, e ]

    const char *lua_pushlstring (lua_State *L, const char *s, size_t len);
    把指针 s 指向的长度为 len 的字符串压栈。 Lua 对这个字符串做一个内部副本(或是复用一个副本), 因此 s 处的内存在函数返回后,可以释放掉或是立刻重用于其它用途。 字符串内可以是任意二进制数据,包括零字符。


    lua_register

    [-0, +0, e ]

    void lua_register (lua_State *L, const char *name, lua_CFunction f);
    把 C 函数 f 设到全局变量 name 中。 它通过一个宏定义: #define lua_register(L,n,f) \ (lua_pushcfunction(L, f), lua_setglobal(L, n))


    lua_tointegerx

    [-0, +0, –]

    lua_Integer lua_tointegerx (lua_State *L, int index, int *isnum);
    将给定索引处的 Lua 值转换为带符号的整数类型 lua_Integer 。 这个 Lua 值必须是一个整数,或是一个可以被转换为整数 (参见 §3.4.3 )的数字或字符串; 否则, lua_tointegerx 返回 0 。 如果 isnum 不是 NULL *isnum 会被设为操作是否成功。


    lua_tolstring

    [-0, +0, e ]

    const char *lua_tolstring (lua_State *L, int index, size_t *len);
    把给定索引处的 Lua 值转换为一个 C 字符串。 如果 len 不为 NULL , 它还把字符串长度设到 *len 中。 这个 Lua 值必须是一个字符串或是一个数字; 否则返回返回 NULL 。 如果值是一个数字, lua_tolstring 还会 把堆栈中的那个值的实际类型转换为一个字符串 。 (当遍历一张表的时候, 若把 lua_tolstring 作用在键上, 这个转换有可能导致 lua_next 弄错。) lua_tolstring 返回一个已对齐指针 指向 Lua 状态机中的字符串。 这个字符串总能保证 ( C 要求的)最后一个字符为零 ('\0') , 而且它允许在字符串内包含多个这样的零。 因为 Lua 中可能发生垃圾收集, 所以不保证 lua_tolstring 返回的指针, 在对应的值从堆栈中移除后依然有效。


    lua_tonumber

    [-0, +0, –]

    lua_Number lua_tonumber (lua_State *L, int index);
    等价于调用 lua_tonumberx , 其参数 isnum NULL 。 const char *name; /* (n) */ const char *namewhat; /* (n) */ const char *what; /* (S) */ const char *source; /* (S) */ int currentline; /* (l) */ int linedefined; /* (S) */ int lastlinedefined; /* (S) */ unsigned char nups; /* (u) 上值的数量 */ unsigned char nparams; /* (u) 参数的数量 */ char isvararg; /* (u) */ char istailcall; /* (t) */ char short_src[LUA_IDSIZE]; /* (S) */ /* 私有部分 */ } lua_Debug; 这是一个携带有有关函数或活动记录的各种信息的结构。 lua_getstack 只会填充结构的私有部分供后面使用。 调用 lua_getinfo 可以在 lua_Debug 中填充那些可被使用的信息域。 下面对 lua_Debug 的各个域做一个说明:
  • source : 创建这个函数的代码块的名字。 如果 source 以 ' @ ' 打头, 指这个函数定义在一个文件中,而 ' @ ' 之后的部分就是文件名。 若 source 以 ' = ' 打头, 剩余的部分由用户行为来决定如何表示源码。 其它的情况下,这个函数定义在一个字符串中, 而 source 正是那个字符串。
  • short_src : 一个“可打印版本”的 source ,用于出错信息。
  • linedefined : 函数定义开始处的行号。
  • lastlinedefined : 函数定义结束处的行号。
  • what : 如果函数是一个 Lua 函数,则为一个字符串 "Lua" ; 如果是一个 C 函数,则为 "C" ; 如果它是一个代码块的主体部分,则为 "main"
  • currentline : 给定函数正在执行的那一行。 当提供不了行号信息的时候, currentline 被设为 -1 。
  • name : 给定函数的一个合理的名字。 因为 Lua 中的函数是一等公民, 所以它们没有固定的名字: 一些函数可能是全局复合变量的值, 另一些可能仅仅只是被保存在一张表的某个域中。 lua_getinfo 函数会检查函数是怎样被调用的, 以此来找到一个适合的名字。 如果它找不到名字, name 就被设置为 NULL
  • namewhat : 用于解释 name 域。 namewhat 的值可以是 "global" , "local" , "method" , "field" , "upvalue" , 或是 "" (空串)。 这取决于函数怎样被调用。 (Lua 用空串表示其它选项都不符合。)
  • istailcall : 如果函数以尾调用形式调用,这个值就为真。 在这种情况下,当层的调用者不在栈中。
  • nups : 函数的上值个数。
  • nparams : 函数固定形参个数 (对于 C 函数永远是 0 )。
  • isvararg : 如果函数是一个可变参数函数则为真 (对于 C 函数永远为真)。


    lua_getinfo

    [-(0|1), +(0|1|2), e ]

    int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);
    返回一个指定的函数或函数调用的信息。 当用于取得一次函数调用的信息时, 参数 ar 必须是一个有效的活动的记录。 这条记录可以是前一次调用 lua_getstack 得到的, 或是一个钩子 (参见 lua_Hook )得到的参数。 用于获取一个函数的信息时, 可以把这个函数压入堆栈, 然后把 what 字符串以字符 ' > ' 起头。 (这会让 lua_getinfo 从栈顶上弹出函数。) 例如,想知道函数 f 是在哪一行定义的, 你可以使用下列代码: lua_Debug ar; lua_getglobal(L, "f"); /* 取得全局变量 'f' */ lua_getinfo(L, ">S", &ar); printf("%d\n", ar.linedefined); what 字符串中的每个字符都筛选出结构 ar 结构中一些域用于填充, 或是把一个值压入堆栈:
  • ' n ': 填充 name namewhat 域;
  • ' S ': 填充 source short_src linedefined lastlinedefined ,以及 what 域;
  • ' l ': 填充 currentline 域;
  • ' t ': 填充 istailcall 域;
  • ' u ': 填充 nups nparams ,及 isvararg 域;
  • ' f ': 把正在运行中指定层次处函数压栈;
  • ' L ': 将一张表压栈,这张表中的整数索引用于描述函数中哪些行是有效行。 ( 有效行 指有实际代码的行,即你可以置入断点的行。 无效行包括空行和只有注释的行。) 如果这个选项和选项 ' f ' 同时使用, 这张表在函数之后压栈。


    lua_getlocal

    [-0, +(0|1), –]

    const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);
    从给定活动记录或从一个函数中获取一个局部变量的信息。 对于第一种情况, 参数 ar 必须是一个有效的活动的记录。 这条记录可以是前一次调用 lua_getstack 得到的, 或是一个钩子 (参见 lua_Hook )的参数。 索引 n 用于选择要检阅哪个局部变量; 参见 debug.getlocal 中关于变量的索引和名字的介绍。 lua_getlocal 将变量的值压栈,并返回其名字。 对于第二种情况, ar 必须填 NULL 。 需要探知的函数必须放在栈顶。 对于这种情况,只有 Lua 函数的形参是可见的 (没有关于还有哪些活动变量的信息) 也不会有任何值压栈。 当索引大于活动的局部变量的数量, 返回 NULL (无任何压栈)


    lua_getupvalue

    [-0, +(0|1), –]

    const char *lua_getupvalue (lua_State *L, int funcindex, int n);
    获取一个闭包的上值信息。 (对于 Lua 函数,上值是函数需要使用的外部局部变量, 因此这些变量被包含在闭包中。) lua_getupvalue 获取第 n 个上值, 把这个上值的值压栈, 并且返回它的名字。 funcindex 指向闭包在栈上的位置。 ( 因为上值在整个函数中都有效,所以它们没有特别的次序。 因此,它们以字母次序来编号。) 当索引号比上值数量大的时候, 返回 NULL (而且不会压入任何东西)。 对于 C 函数,所有上值的名字都是空串 ""


    lua_Hook

    typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
    用于调试的钩子函数类型。 无论何时钩子被调用,它的参数 ar 中的 event 域都被设为触发钩子的事件。 Lua 把这些事件定义为以下常量: LUA_HOOKCALL LUA_HOOKRET LUA_HOOKTAILCALL LUA_HOOKLINE LUA_HOOKCOUNT 。 除此之外,对于 line 事件, currentline 域也被设置。 要想获得 ar 中的其他域, 钩子必须调用 lua_getinfo 。 对于 call 事件, event 可以是 LUA_HOOKCALL 这个通常值, 或是 LUA_HOOKTAILCALL 表示尾调用; 后一种情况,没有对应的返回事件。 当 Lua 运行在一个钩子内部时, 它将屏蔽掉其它对钩子的调用。 也就是说,如果一个钩子函数内再调回 Lua 来执行一个函数或是一个代码块 , 这个执行操作不会触发任何的钩子。 钩子函数不能有延续点, 即不能用一个非空的 k 调用 lua_yieldk lua_pcallk ,或 lua_callk 。 钩子函数可以在满足下列条件时让出: 只有行计数事件可以让出,且不能在让出时传出任何值; 从钩子里让出必须用 lua_yield 来结束钩子的运行,且 nresults 必须为零。


    lua_sethook

    [-0, +0, –]

    void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);
    设置一个调试用钩子函数。 参数 f 是钩子函数。 mask 指定在哪些事件时会调用: 它由下列一组位常量构成 LUA_MASKCALL LUA_MASKRET LUA_MASKLINE LUA_MASKCOUNT 。 参数 count 只在掩码中包含有 LUA_MASKCOUNT 才有意义。 对于每个事件,钩子被调用的情况解释如下:
  • call hook: 在解释器调用一个函数时被调用。 钩子将于 Lua 进入一个新函数后, 函数获取参数前被调用。
  • return hook: 在解释器从一个函数中返回时调用。 钩子将于 Lua 离开函数之前的那一刻被调用。 没有标准方法来访问被函数返回的那些值。
  • line hook: 在解释器准备开始执行新的一行代码时, 或是跳转到这行代码中时(即使在同一行内跳转)被调用。 (这个事件仅仅在 Lua 执行一个 Lua 函数时发生。)
  • count hook: 在解释器每执行 count 条指令后被调用。 (这个事件仅仅在 Lua 执行一个 Lua 函数时发生。)


    lua_setlocal

    [-(0|1), +0, –]

    const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);
    设置给定活动记录中的局部变量的值。 参数 ar n lua_getlocal 中的一样 (参见 lua_getlocal )。 lua_setlocal 把栈顶的值赋给变量然后返回变量的名字。 它会将值从栈顶弹出。


    lua_setupvalue

    [-(0|1), +0, –]

    const char *lua_setupvalue (lua_State *L, int funcindex, int n);
    设置闭包上值的值。 它把栈顶的值弹出并赋于上值并返回上值的名字。 参数 funcindex n lua_getupvalue 中的一样 (参见 lua_getupvalue )。 当索引大于上值的数量时,返回 NULL (什么也不弹出)。 [-0, +0, –]

    void lua_upvaluejoin (lua_State *L, int funcindex1, int n1,
                                        int funcindex2, int n2);
    让索引 funcindex1 处的 Lua 闭包的第 n1 个上值 引用索引 funcindex2 处的 Lua 闭包的第 n2 个上值。


    luaL_addlstring

    [-?, +?, e ]

    void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l);
    向缓存 B (参见 luaL_Buffer ) 添加一个长度为 l 的字符串 s 。 这个字符串可以包含零。


    luaL_argerror

    [-0, +0, v ]

    int luaL_argerror (lua_State *L, int arg, const char *extramsg);
    抛出一个错误报告调用的 C 函数的第 arg 个参数的问题。 它使用下列标准信息并包含了一段 extramsg 作为注解: bad argument # arg to ' funcname ' ( extramsg ) 这个函数永远不会返回。 因此,在使用缓存中,你不能假定目前栈顶在哪。 在对缓存操作的函数调用间,你都可以使用栈,只需要保证栈平衡即可; 即,在你做一次缓存操作调用时,当时的栈位置和上次调用缓存操作后的位置相同。 (对于 luaL_addvalue 是个唯一的例外。) 在调用完 luaL_pushresult 后, 栈会恢复到缓存初始化时的位置上,并在顶部压入最终的字符串。


    luaL_buffinitsize

    [-?, +?, e ]

    char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz);
    等价于调用序列 luaL_buffinit luaL_prepbuffsize


    luaL_checklstring

    [-0, +0, v ]

    const char *luaL_checklstring (lua_State *L, int arg, size_t *l);
    检查函数的第 arg 个参数是否是一个 字符串,并返回该字符串; 如果 l 不为 NULL , 将字符串的长度填入 *l 。 这个函数使用 lua_tolstring 来获取结果。 所以该函数有可能引发的转换都同样有效。


    luaL_checkudata

    [-0, +0, v ]

    void *luaL_checkudata (lua_State *L, int arg, const char *tname);
    检查函数的第 arg 个参数是否是一个类型为 tname 的用户数据 (参见 luaL_newmetatable )。 它会返回该用户数据的地址 (参见 lua_touserdata )。


    luaL_fileresult

    [-0, +(1|3), e ]

    int luaL_fileresult (lua_State *L, int stat, const char *fname);
    这个函数用于生成标准库中和文件相关的函数的返回值。 (指 ( io.open os.rename file:seek ,等。)。


    luaL_getmetafield

    [-0, +(0|1), e ]

    int luaL_getmetafield (lua_State *L, int obj, const char *e);
    将索引 obj 处对象的元表中 e 域的值压栈。 如果该对象没有元表,或是该元表没有相关域, 此函数什么也不会压栈并返回 LUA_TNIL


    luaL_getsubtable

    [-0, +1, e ]

    int luaL_getsubtable (lua_State *L, int idx, const char *fname);
    确保 t[fname] 是一张表,并将这张表压栈。 这里的 t 指索引 idx 处的值。 如果它原来就是一张表,返回真; 否则为它创建一张新表,返回假。


    luaL_optnumber

    [-0, +0, v ]

    lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number d);
    如果函数的第 arg 个参数是一个 数字,返回该数字。 若该参数不存在或是 nil , 返回 d 。 除此之外的情况,抛出错误。 [-0, +1, e ]

    void luaL_requiref (lua_State *L, const char *modname,
                        lua_CFunction openf, int glb);
    如果 modname 不在 package.loaded 中, 则调用函数 openf ,并传入字符串 modname 。 将其返回值置入 package.loaded[modname] 。 这个行为好似该函数通过 require 调用过一样。 如果 glb 为真, 同时也讲模块设到全局变量 modname 里。 在栈上留下该模块的副本。


    luaL_setfuncs

    [-nup, +0, e ]

    void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);
    把数组 l 中的所有函数 (参见 luaL_Reg ) 注册到栈顶的表中(该表在可选的上值之下,见下面的解说)。 若 nup 不为零, 所有的函数都共享 nup 个上值。 这些值必须在调用之前,压在表之上。 这些值在注册完毕后都会从栈弹出。


    luaL_testudata

    [-0, +0, e ]

    void *luaL_testudata (lua_State *L, int arg, const char *tname);
    此函数和 luaL_checkudata 类似。 但它在测试失败时会返回 NULL 而不是抛出错误。


    luaL_tolstring

    [-0, +1, e ]

    const char *luaL_tolstring (lua_State *L, int idx, size_t *len);
    将给定索引处的 Lua 值转换为一个相应格式的 C 字符串。 结果串不仅会压栈,还会由函数返回。 如果 len 不为 NULL , 它还把字符串长度设到 *len 中。 如果该值有一个带 "__tostring" 域的元表, luaL_tolstring 会以该值为参数去调用对应的元方法, 并将其返回值作为结果。


    luaL_traceback

    [-0, +1, e ]

    void luaL_traceback (lua_State *L, lua_State *L1, const char *msg,
                         int level);
    将栈 L1 的栈回溯信息压栈。 如果 msg 不为 NULL ,它会附加到栈回溯信息之前。 level 参数指明从第几层开始做栈回溯。 (例如, type getmetatable ); 另一些提供和“外部”打交道的服务(例如 I/O ); 还有些本可以用 Lua 本身来实现,但在 C 中实现可以满足关键点上的性能需求 (例如 table.sort )。 所有的库都是直接用 C API 实现的,并以分离的 C 模块形式提供。 目前,Lua 有下列标准库:
  • 基础库 ( §6.1 );
  • 协程库 ( §6.2 );
  • 包管理库 ( §6.3 );
  • 字符串控制 ( §6.4 );
  • 基础 UTF-8 支持 ( §6.5 );
  • 表控制 ( §6.6 );
  • 数学函数 ( §6.7 ) (sin ,log 等);
  • 输入输出 ( §6.8 );
  • 操作系统库 ( §6.9 );
  • 调试库 ( §6.10 ).
  • 除了基础库和包管理库, 其它库都把自己的函数放在一张全局表的域中, 或是以对象方法的形式提供。 要使用这些库, C 的宿主程序需要先调用一下 luaL_openlibs 这个函数, 这样就能打开所有的标准库。 或者宿主程序也可以用 luaL_requiref 分别打开这些库: luaopen_base (基础库), luaopen_package (包管理库), luaopen_coroutine (协程库), luaopen_string (字符串库), luaopen_utf8 (UTF8 库), luaopen_table (表处理库), luaopen_math (数学库), luaopen_io (I/O 库), luaopen_os (操作系统库), luaopen_debug (调试库)。 这些函数都定义在 lualib.h 中。

    6.1 – 基础函数

    基础库提供了 Lua 核心函数。 如果你不将这个库包含在你的程序中, 你就需要小心检查程序是否需要自己提供其中一些特性的实现。


    assert (v [, message])

    如果其参数 v 的值为假( nil false ), 它就调用 error ; 否则,返回所有的参数。 在错误情况时, message 指那个错误对象; 如果不提供这个参数,参数默认为 " assertion failed! " 。 当 message 是一个字符串时,通常 error 会把一些有关出错位置的信息附加在消息的前头。 level 参数指明了怎样获得出错位置。 对于 level 1 (默认值),出错位置指 error 函数调用的位置。 Level 2 将出错位置指向调用 error 的函数的函数;以此类推。 传入 level 0 可以避免在消息前添加出错位置信息。 进制可以是 2 到 36 (包含 2 和 36)之间的任何整数。 大于 10 进制时,字母 ' A ' (大小写均可)表示 10 , ' B ' 表示 11,依次到 ' Z ' 表示 35 。 如果字符串 e 不是该进制下的合法数字, 函数返回 nil 。 以字符串形式返回协程 co 的状态: 当协程正在运行(它就是调用 status 的那个) ,返回 "running" ; 如果协程调用 yield 挂起或是还没有开始运行,返回 "suspended" ; 如果协程是活动的,都并不在运行(即它正在延续其它协程),返回 "normal" ; 如果协程运行完主体函数或因错误停止,返回 "dead" 。 这个函数首先查找 package.loaded 表, 检测 modname 是否被加载过。 如果被加载过, require 返回 package.loaded[modname] 中保存的值。 否则,它试着为模块寻找 加载器 require 遵循 package.searchers 序列的指引来查找加载器。 如果改变这个序列,我们可以改变 require 如何查找一个模块。 下列说明基于 package.searchers 的默认配置。 首先 require 查找 package.preload[modname] 。 如果这里有一个值,这个值(必须是一个函数)就是那个加载器。 否则 require 使用 Lua 加载器去查找 package.path 的路径。 如果查找失败,接着使用 C 加载器去查找 package.cpath 的路径。 如果都失败了,再尝试 一体化 加载器 (参见 package.searchers )。 每次找到一个加载器, require 都用两个参数调用加载器: modname 和一个在获取加载器过程中得到的参数。 (如果通过查找文件得到的加载器,这个额外参数是文件名。) 如果加载器返回非空值, require 将这个值赋给 package.loaded[modname] 。 如果加载器没能返回一个非空值用于赋给 package.loaded[modname] require 会在那里设入 true 。 无论是什么情况, require 都会返回 package.loaded[modname] 的最终值。 如果在加载或运行模块时有错误, 或是无法为模块找到加载器, require 都会抛出错误。 它仅仅连接该库,让库中的符号都导出给其它动态链接库使用。 否则,它查找库中的函数 funcname ,以 C 函数的形式返回这个函数。 因此, funcname 必须遵循原型 lua_CFunction (参见 lua_CFunction )。 这是一个低阶函数。 它完全绕过了包模块系统。 和 require 不同, 它不会做任何路径查询,也不会自动加扩展名。 libname 必须是一个 C 库需要的完整的文件名,如果有必要,需要提供路径和扩展名。 funcname 必须是 C 库需要的准确名字 (这取决于使用的 C 编译器和链接器)。 这个函数在标准 C 中不支持。 因此,它只在部分平台有效 ( Windows ,Linux ,Mac OS X, Solaris, BSD, 加上支持 dlfcn 标准的 Unix 系统)。 require 按次序调用这些查找器, 并传入模块名( require 的参数)作为唯一的一个参数。 此函数可以返回另一个函数(模块的 加载器 )加上另一个将传递给这个加载器的参数。 或是返回一个描述为何没有找到这个模块的字符串 (或是返回 nil 什么也不想说)。 Lua 用四个查找器函数初始化这张表。 第一个查找器就是简单的在 package.preload 表中查找加载器。 第二个查找器用于查找 Lua 库的加载库。 它使用储存在 package.path 中的路径来做查找工作。 查找过程和函数 package.searchpath 描述的一致。 第三个查找器用于查找 C 库的加载库。 它使用储存在 package.cpath 中的路径来做查找工作。 查找过程和函数 package.searchpath 描述的一致。 例如,如果 C 路径是这样一个字符串 "./?.so;./?.dll;/usr/local/?/init.so" 查找器查找模块 foo 会依次尝试打开文件 ./foo.so ./foo.dll , 以及 /usr/local/foo/init.so 。 一旦它找到一个 C 库, 查找器首先使用动态链接机制连接该库。 然后尝试在该库中找到可以用作加载器的 C 函数。 这个 C 函数的名字是 " luaopen_ " 紧接模块名的字符串, 其中字符串中所有的下划线都会被替换成点。 此外,如果模块名中有横线, 横线后面的部分(包括横线)都被去掉。 例如,如果模块名为 a.b.c-v2.1 , 函数名就是 luaopen_a_b_c 。 第四个搜索器是 一体化加载器 。 它从 C 路径中查找指定模块的根名字。 例如,当请求 a.b.c 时, 它将查找 a 这个 C 库。 如果找得到,它会在里面找子模块的加载函数。 在我们的例子中,就是找 luaopen_a_b_c 。 利用这个机制,可以把若干 C 子模块打包进单个库。 每个子模块都可以有原本的加载函数名。 除了第一个(预加载)搜索器外,每个搜索器都会返回 它找到的模块的文件名。 这和 package.searchpath 的返回值一样。 第一个搜索器没有返回值。 --> x="hello hello world" x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") --> x="world hello Lua from" x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv) --> x="home = /home/roberto, user = roberto" x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) return load(s)() --> x="4+5 = 9" local t = {name="lua", version="5.3"} x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) --> x="lua-5.3.tar.gz"
  • s[ n ] : 长度加内容的字符串,其长度编码为一个 n 字节(默认是个 size_t ) 长的无符号整数。
  • x : 一个字节的填充
  • X op : 按选项 op 的方式对齐(忽略它的其它方面)的一个空条目
  • ' ': (空格)忽略
  • ( " [ n ] " 表示一个可选的整数。) 除填充、空格、配置项(选项 " xX <=>! ")外, 每个选项都关联一个参数(对于 string.pack ) 或结果(对于 string.unpack )。 对于选项 " ! n ", " s n ", " i n ", " I n ", n 可以是 1 到 16 间的整数。 所有的整数选项都将做溢出检查; string.pack 检查提供的值是否能用指定的字长表示; string.unpack 检查读出的值能否置入 Lua 整数中。 任何格式串都假设有一个 " !1= " 前缀, 即最大对齐为 1 (无对齐)且采用本地大小端设置。 对齐行为按如下规则工作: 对每个选项,格式化时都会填充一些字节直到数据从一个特定偏移处开始, 这个位置是该选项的大小和最大对齐数中较小的那个数的倍数; 这个较小值必须是 2 个整数次方。 选项 " c " 及 " z " 不做对齐处理; 选项 " s " 对对齐遵循其开头的整数。 string.pack 用零去填充 ( string.unpack 则忽略它)。


    utf8.charpattern

    用于精确匹配到一个 UTF-8 字节序列的模式(是一个字符串,并非函数)" [\0-\x7F\xC2-\xF4][\x80-\xBF]* " (参见 §6.4.1 )。 它假定处理的对象是一个合法的 UTF-8 字符串。 在 list 的位置 pos 处插入元素 value , 并后移元素 list[pos], list[pos+1], ···, list[#list] pos 的默认值为 #list+1 , 因此调用 table.insert(t,x) 会将 x 插在列表 t 的末尾。 移除 list pos 位置上的元素,并返回这个被移除的值。 当 pos 是在 1 到 #list 之间的整数时, 它向前移动元素 list[pos+1], list[pos+2], ···, list[#list] 并删除元素 list[#list] ; 索引 pos 可以是 #list + 1 ,或在 #list 为 0 时可以是 0 ; 在这些情况下,函数删除元素 list[pos] pos 默认为 #list , 因此调用 table.remove(l) 将移除表 l 的最后一个元素。 它必须是一个可以接收两个列表内元素为参数的函数。 当第一个元素需要排在第二个元素之前时,返回真 (因此 not comp(list[i+1],list[i]) 在排序结束后将为真)。 如果没有提供 comp , 将使用标准 Lua 操作 < 作为替代品。 排序算法并不稳定; 即当两个元素次序相等时,它们在排序后的相对位置可能会改变。 注解有 " integer/float " 的函数会对整数参数返回整数结果, 对浮点(或混合)参数返回浮点结果。 圆整函数( math.ceil , math.floor , math.modf ) 在结果在整数范围内时返回整数,否则返回浮点数。


    math.abs (x)

    返回 x 的绝对值。(integer/float) 检查 obj 是否是合法的文件句柄。 如果 obj 它是一个打开的文件句柄,返回字符串 "file" 。 如果 obj 是一个关闭的文件句柄,返回字符串 "closed file" 。 如果 obj 不是文件句柄,返回 nil year (四位数字), month (1–12), day (1–31), hour (0–23), min (0–59), sec (0–61), wday (星期几,星期天为 1 ), yday (当年的第几天), 以及 isdst (夏令时标记,一个布尔量)。 对于最后一个域,如果该信息不提供的话就不存在。 如果 format 并非 " *t ", date 以字符串形式返回, 格式化方法遵循 ISO C 函数 strftime 的规则。 如果不传参数调用, date 返回一个合理的日期时间串, 格式取决于宿主程序以及当前的区域设置 (即, os.date() 等价于 os.date("%c") )。 在非 POSIX 系统上, 由于这个函数依赖 C 函数 gmtime localtime , 它可能并非线程安全的。 category 是一个描述有改变哪个分类的可选字符串: "all" "collate" "ctype" "monetary" "numeric" , 或 "time" ; 默认的分类为 "all" 。 此函数返回新区域的名字。 如果请求未被获准,返回 nil 。 当 locale 是一个空串, 当前区域被设置为一个在实现中定义好的本地区域。 当 locale 为字符串 " C ", 当前区域被设置为标准 C 区域。 当第一个参数为 nil 时, 此函数仅返回当前区域指定分类的名字。 由于这个函数依赖 C 函数 setlocale , 它可能并非线程安全的。 这个函数会以此文件名创建一个文件以回避安全风险。 (别人可能未经允许在获取到这个文件名到创建该文件之间的时刻创建此文件。) 你依旧需要在使用它的时候先打开,并最后删除(即使你没使用到)。 只有有可能,你更应该使用 io.tmpfile , 因为该文件可以在程序结束时自动删除。 这里我们列出了把程序从 Lua 5.2 迁移到 Lua 5.3 会碰到的不兼容的地方。 你可以在编译 Lua 时定义一些恰当的选项(参见文件 luaconf.h ), 来回避一些不兼容性。 然而,这些兼容选项以后会移除。 Lua 的版本更替总是会修改一些 C API 并涉及源代码的改变。 例如一些常量的数字值,用宏来实现一些函数。 因此,你不能假设在不同的 Lua 版本间可以做到二进制兼容。 当你使用新版时,一定要将使用了 Lua API 的客户程序重新编译。 同样,Lua 版本更替还会改变预编译代码块的内部呈现方式; 在不同的 Lua 版本间,预编译代码块不兼容。 官方发布版的标准路径也可能随版本变化。

    8.1 – 语言的变更

    Lua 5.2 到 Lua 5.3 最大的变化是引入了数字的整数子类型。 虽然这个变化不会影响“一般”计算, 但一些计算 (主要是涉及溢出的) 会得到不同的结果。 你可以通过把数字都强制转换为浮点数来消除差异 (在 Lua 5.2 中,所有的数字都是浮点数)。 比如你可以将所有的常量都以 .0 结尾, 或是使用 x = x + 0.0 来转换一个变量。 (这条建议仅用于偶尔快速解决一些不兼容问题; 这不是一条好的编程准则。 好好写程序的话,你应该在需要使用浮点数的地方用浮点数, 需要整数的地方用整数。) 把浮点数转为字符串的地方,现在都对等于整数的浮点数加了 .0 后缀。 (例如,浮点数 2.0 会被打印成 2.0 , 而不是 2 。) 如果你需要定制数字的格式,就必须显式的格式化它们。 (准确说这个不是兼容性问题, 因为 Lua 并没有规定数字如何格式化成字符串, 但一些程序假定遵循某种特别的格式。) 分代垃圾收集器没有了。 (它是 Lua 5.2 中的一个试验性特性。) frexp , 以及 ldexp 。 你可以用 x^y 替换 math.pow(x,y); 你可以用 math.atan 替换 math.atan2 ,前者现在可以接收一或两个参数; 你可以用 x * 2.0^exp 替换 math.ldexp(x,exp) 。 若用到其它操作,你可以写一个扩展库,或在 Lua 中实现它们。 require 在搜索 C 加载器时处理版本号的方式有所变化。 现在,版本号应该跟在模块名后(其它大多数工具都是这样干的)。 出于兼容性考虑,如果使用新格式找不到加载器的话,搜索器依然会尝试旧格式。 (Lua 5.2 已经是这样处理了,但是并没有写在文档里。) 用于传入传出无符号整数的函数 ( lua_pushunsigned lua_tounsigned lua_tounsignedx luaL_checkunsigned luaL_optunsigned ) 都废弃了。 直接从有符号版做类型转换。 处理输入非默认整数类型的宏 ( luaL_checkint luaL_optint luaL_checklong luaL_optlong ) 废弃掉了。 直接使用 lua_Integer 加一个类型转换就可以替代 (或是只要有可能,就在你的代码中使用 lua_Integer )。 while exp do block end | repeat block until exp | if exp then block { elseif exp then block} [ else block] end | for Name ‘ = ’ exp ‘ , ’ exp [‘ , ’ exp] do block end | for namelist in explist do block end | function funcname funcbody | local function Name funcbody | local namelist [‘ = ’ explist] retstat ::= return [explist] [‘ ; ’] label ::= ‘ :: ’ Name ‘ :: ’ funcname ::= Name {‘ . ’ Name} [‘ : ’ Name] varlist ::= var {‘ , ’ var} var ::= Name | prefixexp ‘ [ ’ exp ‘ ] ’ | prefixexp ‘ . ’ Name namelist ::= Name {‘ , ’ Name} explist ::= exp {‘ , ’ exp} exp ::= nil | false | true | Numeral | LiteralString | ‘ ... ’ | functiondef | prefixexp | tableconstructor | exp binop exp | unop exp prefixexp ::= var | functioncall | ‘ ( ’ exp ‘ ) ’ functioncall ::= prefixexp args | prefixexp ‘ : ’ Name args args ::= ‘ ( ’ [explist] ‘ ) ’ | tableconstructor | LiteralString functiondef ::= function funcbody funcbody ::= ‘ ( ’ [parlist] ‘ ) ’ block end parlist ::= namelist [‘ , ’ ‘ ... ’] | ‘ ... ’ tableconstructor ::= ‘ { ’ [fieldlist] ‘ } ’ fieldlist ::= field {fieldsep field} [fieldsep] field ::= ‘ [ ’ exp ‘ ] ’ ‘ = ’ exp | Name ‘ = ’ exp | exp fieldsep ::= ‘ , ’ | ‘ ; ’ binop ::= ‘ + ’ | ‘ - ’ | ‘ * ’ | ‘ / ’ | ‘ // ’ | ‘ ^ ’ | ‘ % ’ | ‘ & ’ | ‘ ~ ’ | ‘ | ’ | ‘ >> ’ | ‘ << ’ | ‘ .. ’ | ‘ < ’ | ‘ <= ’ | ‘ > ’ | ‘ >= ’ | ‘ == ’ | ‘ ~= ’ | and | or unop ::= ‘ - ’ | not | ‘ # ’ | ‘ ~