Skip to content

atom-l/lua5.4-manual-zh

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

1 - 简介

Lua是一门强大、高效、轻量、可嵌入的脚本语言。它支持多种编程架构:过程编程、面向对象编程(OOP)、函数式编程、数据驱动编程及数据描述。

Lua结合了简洁的过程语法和强大的数据描述结构(基于关联数组和可扩展语义)。Lua使用动态类型,运行时内部通过寄存器式虚拟机(register-based VM)来执行解释字节码,有自动内存管理和分代GC机制,是配置、编写脚本和原型设计的理想选择。

Lua是作为一个C语言编写的库实现的,其被标准C/C++所兼容。Lua的发行版本中包括一个独立程序(就叫 lua),它就是由Lua库所实现,作为一个完整独立的编译/解释器,也可以用于交互式使用或批量执行。Lua志在成为一门强大轻量可嵌入在任何需要的地方的脚本语言,并且是强力但轻便高效的独立语言。

作为扩展语言,Lua没有 主程序 的概念,它通常是 嵌入 到宿主程序中使用,宿主程序也被称为 被嵌入程序 或简称 宿主 (通常宿主程序指上边说到的独立程序lua)。宿主程序可以调用函数来执行一段Lua代码,可以写入或读取Lua变量,也可以注册C函数供Lua代码调用。通过使用C函数,可以在相同的语法框架下来自定义编程语言,从而使得Lua能够应用于广泛的领域。

Lua是自由软件,如许可证所述,其日常分发无任何担保。本手册所言可以在Lua的官方网站www.lua.org上找到。

就像其他的参考手册一样,本文有些地方也很枯燥。关于Lua背后的设计决策及相关讨论,可以看看Lua官网上的技术论文。至于Lua编程的细节,可以去看Roberto的书 《Programming in Lua》。

2 - 基本概念

本章论述了Lua语言的基本概念。

2.1 - 值和类型

首先,Lua是动态类型语言。这意味着变量没有类型,只有值才有。语言中没有类型定义。所有的值保管着自己的类型。

Lua中所有的值都是“一等公民( first-class )“,即所有的值都可以被保存在变量中,可以作为函数参数以及被作为结果返回。

一等公民 first-class 的概念由 Christopher Strachey 提出,其内容可参考《Programming Language Pragmatics》中的一段:

In general, a value in a programming language is said to have first-class status if it can be passed as a parameter, returned from a subroutine, or assigned into a variable.

Lua有八种基本类型:nilbooleanstringfunctionuserdatathreadtablenil 类型是一个单独的值 nil ,主要特质就是不同于其他任何值,通常表示一个没有用的值。 boolean 类型有两种值—— falsetruenilfalse 都可以使得条件为假,他们统称为假值( false values )。其他值都使得条件为真。尽管他们都叫假值, false 也常常用来替代 nil ,但是关键的区别在于: false 在表(table)中是个常规值,而 nil 表示一个缺失的键。

number 类型使用两种子类型—— integerfloat ,以表示整数和浮点数。标准Lua使用64位整数和双精度浮点数,但也可以编译出使用32位整数和单精度浮点数的版本。32位整数和浮点数的选项对于小型机和嵌入式系统来说很有吸引力。(参见 luaconf.h 文件中的 LUA_32BITS 宏定义)

若非另有说明,任何在整数操作时的溢出,都遵循一般的双补码算术规则。(换言之,其结果都是单一可表示整数,即2n的算术模,这里的n为整数类型的位数)

Lua对于每个子类型的使用都有明确的规则,但是也会按需在它们之间做自动转换(参见3.4.3)。因此,编程人员可以选择忽略整数和浮点数的差异亦或者完全控制每个数的表示形式。

string 类型表示不可变的字节序。Lua字符串是纯8位形式——可以存储任意8位值,包括零值“\0”。Lua字符串也是编码无关的——不关心字符串的内容。字符串的长度必须是个Lua整数。

Lua可以同时调用由Lua或C编写(参见3.4.10)的函数,它们都表示为 function 类型。

userdata 类型提供将任意C数据存储在Lua变量中的能力。一个userdata值表示一块原始内存。由两种userdata: full userdata ——表示一块对象的内存并由Lua管理,以及 light userdata ——一个简单的C指针。userdata在Lua中除了赋值与相等判断外没有任何其他的预定义的操作。编程人员可以使用元表 metatables 来给 full userdata 来定义操作(参见2.4)。userdata值不可以在Lua中被创建或更改,只能用C接口操作。这保证了宿主程序和C库的数据只为其所有。

thread 类型表示一个独立执行的线程,其用于实现协程(参见2.6)。Lua线程与操作系统的线程没关系。Lua在所有系统上都支持协程,即便是那些本身不支持线程的平台。

table 类型实现了关联数组,即除了 nil 和NaN( Not a Number 是一个特殊的浮点值,被用于IEEE 754标准中,表示一个未定义的数值结果,例如除零运算)之外的值都可以作为索引,而不只是数字。表可以是 异构(heterogeneous) 的,即可以存储所有类型的值(除了 nil )。任何关联了nil值的键都被视为不存在于表中。换句话说,对于表中任意不存在的键,其值都是 nil

表是Lua中唯一的组织数据结构的机制,它可以用于表示一般数组、列表、符号表、集合、记录、图、树……等等。对于表示记录,Lua使用属性名来作为索引。语言支持用 a.name 来替代表示 a["name"] 的语法糖。同时也有些创建表的方法(参见3.4.9)。

和索引一样,表的值也可以是任意类型。尤其是function,因其也是一等公民(first-class) 的值,所以也可以被表包含。因此表同样可以保管函数(参见3.4.11)。

表、函数、线程、以及(full)userdata都是对象,因此变量其实并不是 包含(contain) 它们的值,而只是 引用(reference) 了它们。赋值、传递参数和函数返回都是在操作这些值的引用,这些操作不涉及任何复制。

可使用库函数type来获得给定值的类型描述(参见type)。

2.2 - 环境和全局环境

正如我们将在3.23.3.3所述,对一个自由名称(即一个未绑定任何声明的名称)的任意引用“var”都会在句法上被转换为“_ENV.var”。此外,每个代码块都被编译在有着一个名为_ENV的外部局部变量的空间中(参见3.3.2),所以_ENV本身在块中从来都不是一个自由名称。

尽管有外部变量_ENV以及自由名称的转换,但是_ENV完全是个合规名称。所以你可以给这个名称定义一个新变量和参数。程序中每个自由名称的引用都对于_ENV的点可见,符合Lua的可见性规则(参见3.5)。

任何作为_ENV值的表都称为环境(environment)

Lua保存着一个叫做 全局环境(global enviroment) 的特定环境,它的值被保存在C注册表(参见4.3)中的一个特殊索引上。在Lua中,全局变量_G被初始化为之前所保存的值(_G未被内部使用,所以更改_G的值只会影响你自己的代码)。

当Lua加载了一个代码块,其_ENV变量的默认值为全局环境(参见load)。因此,一般来说Lua中的自由名称是对全局环境中成员的引用,所以它们也称作 全局变量(global variables) 。此外,所有的独立库都被加载到全局环境中且有些函数会操作它们的环境。你可以使用load(或者loadfile)来将代码块加载到不同的环境中。(对于C,你可以在直接加载代码块并改变第一个upvalue的值;参见lua_setupvalue

2.3 - 错误处理

在Lua中有些操作会 抛出(raise) 错误。错误会打断程序的正常流程,可以通过 捕获(catching) 错误来继续。

Lua代码可以通过调用error函数来显式地抛出异常。(此函数永远不返回。)

对于在Lua中捕获异常,你可以使用pcall(或者xpcall)来发起一个 保护调用(protected call)pcall函数将在 保护模式(protected mode) 下调用给定函数。任何错误的产生都会停止执行函数,控制流直接返回到pcall调用处,并返回其状态码。

因为Lua是一个被嵌入的扩展语言,Lua代码的启动执行是由宿主程序中的C代码调用的。(当你独立使用Lua的时候,宿主程序就是那个lua应用程序。)通常此调用都是被保护的;所以当一个其他错误发生在Lua块的编译或执行中时,控制流会回到宿主处,宿主程序就可以采取合适的措施,例如打印错误消息。

每当有错误的时候,带着关于其信息的一个错误对象会被生成出来。Lua本身只生成其错误对象为字符串的错误,但是程序将任何类型作为其生成错误的错误对象。这些错误对象有Lua程序或宿主来对其做处理。因为一些历史原因,错误对象通常被称之为 错误信息(error message) , 尽管它不一定非得是个字符串。

当你使用xpcall(或者C接口lua_pcall)时,你可以给定一个 消息处理方法( message handler) 用于错误处理中。这个方法由原始的错误对象调用并返回一个新的错误对象。它于错误出现时调用栈展开前被调用,所以它可以收集更多有关于错误的信息,例如调查栈并创建一个栈的回溯信息。这个消息处理方法仍然处于保护模式下,所以消息处理方法中的错误会再次出发消息处理方法。如果这个循环持续得太长了,Lua会打断并返回一个合适的消息。这个消息处理方法只会用于合规的运行时错误,它不会因为内存分配错误而被调用,也不会因为运行结束器或其他消息处理方法而被调用。

Lua还提供了系统警告 warnings (参见warn)。与错误不同,警告不会以任何方式干扰程序执行。它通常只是生成一个消息给用户,尽管此行为可以用C改变(参见lua_setwarnf)。

2.4 - 元表和元函数

每个值都可以有 元表(metatable)元表 是定义了原始数据在某些事件下行为的一个普通Lua表。你可以通过设置其元表的某些特定属性来改变某个值的某些行为。举个例子,一个非数字值进行加法操作时,Lua会在这个值的元表中查找__add属性函数,找到了的情况下,Lua就会调用这个函数来执行加法操作。

元表中的每个事件对应的键都是一个字符串,内容是以两个下划线做前缀的事件名,其相应的值被称为 元值(metavalue)。对于大部分事件,其元值必须是一个称为 元函数(metamethod) 的方法。在上边说的例子里,键值是“_add”字符串且元函数是一个用来做加法操作的方法。若非另有说明,元函数实际上可以是任意可调用的值,它要么是个函数,要么是个带有元方法“__call”的值。

你可以使用getmetatable方法来查询任何值的元表。Lua使用原始访问(参见rawget)来查询元表中的元函数。

你可以使用setmetatable方法来替换表的元表。你不能从Lua代码中改变其他类型的元表,除非使用调试库(参见6.10)。

表和full userdata有单独的元表,尽管多个表和userdata之间可以共享它们的元表。其他类型的值共享每个类型的单独元表;即,存在一个单独的元表给所有数字使用,一个单独的元表给所有的字符串使用,等等。默认情况下,值没有元表,但是字符串库给字符串类型设置了一个元表(参见6.4)。

下面给出了关于元表控制的操作的详细列表。每种事件由对应的键标识。按约定,所有的元表键由两个下划线后跟小写拉丁字母组合而成。

  • __add: 加法(+)操作。如果任何一个加法操作的操作数不是一个数字,Lua将尝试调用元函数。它从第一个操作数开始检查(即使它是数字),如果它没有为__add定义元函数,Lua将继续检查第二个操作数。如果Lua可以找到了元函数,那么Lua将两个操作数为参数来调用元函数,且将调用结果(调整为单个值)作为作为操作的结果。反之,如果没有找到元函数,Lua会抛出一个错误。
  • __sub: 减法(-)操作。行为类似于加法操作。
  • __mul: 乘法(*)操作。行为类似于加法操作。
  • __div: 除法(/)操作。行为类似于加法操作。
  • __pow: 幂(^)操作。行为类似于加法操作。
  • __unm: 取负(一元 -)操作。行为类似于加法操作。
  • __idiv: 整除(//)操作。行为类似于加法操作。
  • __band: 按位与(&)操作。行为类似于加法操作,不同之处在于当操作数既不是整数也不是可强转到整数的浮点数时(参见3.4.3),Lua将尝试调用元函数。
  • __bor: 按位或(|)操作。行为类似于按位与操作。
  • __bxor: 按位异或(~)操作。行为类似于按位与操作。
  • __bnot: 按位取反(一元 ~)操作。行为类似于按位与操作。
  • __shl: 位左移(<<)操作。行为类似于按位与操作。
  • __shr: 位右移(>>)操作。行为类似于按位与操作。
  • __concat: 连接(..)操作。行为类似于加法操作,不同之处在于当操作数既不是字符串也不是数字时(数字定能被转换为一个字符串),Lua将尝试调用元函数。
  • __len: 取长(#)操作。如果对象不是一个字符串,Lua将尝试调用其元函数。如果元函数存在,则调用将对象作为参数调用元函数,并将调用结果(通常调整为单个值)作为操作结果。如果元表不存在但是对象是table,Lua使用表的取长操作(参见3.4.7)。否则,Lua抛出将会抛出错误。
  • __eq: 判断相等(==)操作。行为类似于加法操作,不同之处在于当这些值都是表或都是full userdata且它们底层不相等时,Lua将会尝试调用元函数。其结果总是会被转换为一个布尔值。
  • __lt: 判断小于(<)操作。行为类似于加法操作,不同之处在于当这些值既不是都是数字也不都是字符串时,Lua将尝试调用元函数。另外,其结果总是会被转换为一个布尔值。
  • __le: 判断小于等于(<=)操作。行为类似于判断小于操作。
  • __index: 访问索引(table[key])操作。这个操作发生在table不是一个表或者key不存在于table中的情况下。此时将会在table的元表中查找其元值。
    此事件的元值可以是一个方法、一个表、或者任何带有__index元值的值。如果是方法,它会将tablekey作为参数来调用,调用结果(调整为单值)作为操作结果。否则,最终的结果是其元值索引key的结果。此索引是常规索引,而非直接索引,所以可以出发其他的__index元值。
  • __newindex: 赋值索引(table[key])操作。像index事件一样,在table不是一个表或者key不存在于table中时,将会在table的元表中查找其元值。
    与索引类似,此元值可以是方法、表、或者任何带有__newindex元值的值。如果是方法,它会将tablekeyvalue作为参数来调用。否则,Lua将再次对这个元值做索引赋值。这里的赋值流程是常规赋值,而不是直接的赋值,所以它可能会触发其他地方的__newindex元值。
    无论何时,当__newindex元值被调用,Lua不会执行任何更多的赋值操作。如果需要,元函数自身可以调用rawset来做赋值。
  • __call: 调用方法func(args)操作。此事件发生在Lua尝试调用一个non-function值(即,func不是个方法)的时候。将在func中寻找此元函数。如果存在,会将func作为第一个参数,再在后边加上其原本调用的参数列表来调用此元函数。所有此操作的结果都将作为其调用结果。这是唯一一个允许多个返回结果的元函数。

除了上述列表外,解释器还遵循了以下元表中的键:__gc(参见2.5.3),__close(参见3.3.8),__mode(参见2.5.4),以及__name。(tostring和错误消息中可能会用到包含字符串的__name。)

对于一元操作(取负、取长度和按位取否)而,元函数是使用虚拟的第二个操作数来计算和调用的,其等于第一个操作数。这个多余的操作数只是为了简化Lua的内部结构(使得这些操作和二元操作做相似的行为),并且在未来的版本中可能会删掉这些。对于大部分用途,这个多余的操作数都是无所谓的。

因为元表其实是常规的表,所以它们可以包含任意属性,而不只是定义上边提到的事件名。有些标准库中的函数就为了其自己的目的而使用了元表上的其他属性。

在一个好的实现中,会在给一些对象设置元表前把所有需要的元函数都加到一个表上;特别是__gc元函数,它只能用这种方式才能起效(参见2.5.3)。在一个对象被创建后立刻设置元表也是个好的实现。

2.5 - 垃圾回收(Garbage Collection)

Lua使用自动内存管理。这意味着你不用操心给新对象分配内存或释放不再需要的对象的事。Lua调用GC(garbage collector)清理所有的死亡对象以自动管理内存。Lua所有使用的内存都由其自动管理:例如字符串、表、userdata、函数、协程、内部结构体……等等。

当收集器确定某个对象在正常的执行或程序中不再会被访问时,这个对象就被认为已经死亡了。(这里的“正常的执行”不包括终结器,其可以复活死亡对象;也不包括执行调试库的操作。)注意收集器认为一个对象死亡的时机可能与编程人员的认知有根本上的不同。它只保证在程序的正常执行中可能会被访问的对象不被收集,并最终会在其不可访问时回收对象。(这里说的“不可访问”在Lua中的意思是指某个对象没有被任何值或活跃对象引用。)因为Lua对C代码中的情况一无所知,所以它永远不会收集可以在全局环境(参见2.2)中通过注册(参见4.3)来访问的对象。

Lua中的垃圾收集器(GC)有两种工作模式:步进模式和代际模式。

默认参数设置下的默认GC对大多数用户来说已经足够了。然而,有大量浪费在分配和释放内存的耗时程序可以从其他的设置中收益。请记住对于跨平台和跨Lua版本的GC行为是不可移植(non-portable)的,因此优化设置也是不可移植的。

你可以使用不同的参数在C中调用lua_gc或在Lua中调用collectgarbage来改变GC模式。你也可以使用这些函数来直接控制收集器(例如停止或重启它)。

2.5.1 - 步进GC(Incremental Garbage Collection)

在步进模式中,每个GC循环都是一小步一小步地与程序一起交错执行标记-扫描清理。这个模式下的收集器使用三个数字来控制GC循环:GC停步(garbage-collector pause)GC步进乘数(garbage-collector step multiplier),以及GC步数(garbage-collector step size)

GC停步用以控制什么时候收集器才开始一个新的GC循环。收集器会在使用了上次GC时n%的内存后开始新一轮的GC。其值越大则收集器的影响越小。值小于等于100意味着收集器将不会等待而直接开始新一轮循环。值为200则意味着收集器将在只用之前整整两倍内存后才开始一轮新的循环。其默认值是100,最大为1000。

GC步进乘数用以控制相对于内存分配的收集速度,即每分配1KB的内存就标记并扫描多少个元素。值越大则收集器的影响越大,但也会增加步进的大小。你不应当使用小于100的值,因为这会使得收集器太慢以至于收集器永远无法完成一个循环。其默认值是100,最大为1000。

GC步数用以控制每次步进的的大小,具体说来就是在步进执行之前解释可分配的内存数量。这是个对数参数:其值为n表示解释器会在步进之间分配2n字节并在步进时做相同大小的清理工作。较大的值(例如60)会使收集器变为一个“世界暂停(stop-the-world)”的(即,非步进的)收集器。其默认值为13,意味着步长约为8KB左右。

2.5.2 - 代际GC(Generational Garbage Collection)

在代际模式中,收集器频繁进行*次代(minor)收集,其仅遍历最近创建的对象。如果在进行了次代收集后内存仍然超出了限制,那么收集器会做一次“世界暂停(stop-the-world)”的主(major)*收集,其会遍历所有的对象。代际模式使用两个参数:次代乘数(minor multiplier)以及主乘数(major multiplier)

次代乘数用以控制次代回收的频率。对于一个次代乘数值x,会在内存相对于上次主收集后增长超过x%时开始一轮新的次代收集。例如乘数为20时,收集器将会在内存增长超过上次主收集时的20%时执行一轮次代收集。其默认值是20,最大为200。

主乘数用以控制主会后的频率。对于一个主乘数值x,会在内存相对于上次主收集后增长超过x%时开始一轮新的主收集。例如乘数为100时,收集器将会在内存使用量超过上次的两倍时执行一轮主收集。其默认值是100,最大为1000。

2.5.3 - GC元函数(Garbage-Collection Metamethods)

你可以直接对表、或通过C API对 full userdata 来设置GC元函数(参见2.4)。此元函数被称为终结器(finalizers),于垃圾收集器发现相应的表或 userdata 已死时被调用。终结器允许你将垃圾收集器与外部资源管理协调起来,例如关闭文件、网络或数据库连接,或者释放你自己的内存。

对于收集时要终结的对象(表或者 userdata ),你需要把它标记为可触发终结(finalization)。如果你想将标记一个对象,你需要设置一个对象的元表并且此元表要有元函数__gc。注意,当你设置元表时没有__gc属性,而是之后再于元表上创建这个属性的话,这个对象将不会被标记。

当一个被标记的对象死亡时,其并不会立刻被垃圾收集器收集起来。相反,Lua会将其放到一个列表中。Lua在收集完成后遍历这个列表。对于列表中的每个对象都会查找其是否有元函数__gc:如果有,Lua将这个对象作为单一参数来调用此函数。

在垃圾收集周期的最后,周期内被收集的对象中,终结器会以对象被标记为可触发终结的倒序来调用终结器;即,第一个调用的终结器是在程序中最后被标记为可触发终结的对象所关联的终结器。终结器的调用可能发生在常规代码执行时的任意时刻。

因为被回收的对象仍然会被终结器使用,所以其一定会被Lua复原(包括被其唯一关联的其他对象)。通常,这个复原是暂时的,而且其内存会在下一次GC周期内被释放。然而,如果终结器将对象保存到了某些全局位置(例如全局变量),那么这个复原就是持续的。此外,如果终结器将一个正在被终结的对象再次标记为可触发终结,那么终结器会在下个GC周期时的对象死亡的地方被再次调用。在任意情况下,没有被标记为可触发终结并已经死亡的对象,其内存只可能在GC周期内被释放。

当你关闭一个状态机(参见lua_close)时,Lua会调用所有被标记为可触发终结的对象的终结器,调用顺序与它们被标记的顺序相反。如果此步骤内的某些终结器再次标记了对象,那么这时的标记是无效的。

终结器不会让出或运行垃圾收集器。它们只能在不可预知的时间运行,所以在好的实现里会在必要的最低限度下正确释放与其关联的资源。

终结器运行时产生的错误会生成一个警告,此错误不会被传播。

2.5.4 - 弱表(Weak Tables)

弱表(Weak Tables) 就是一个其元素为弱引用的表。弱引用会被GC忽略。换言之,一个仅被弱引用指向的对象会被GC回收。

一个弱表可以同时或任一拥有弱键和弱值。拥有弱值的表允许收集其值,但是会阻止收集其键。一个同时拥有弱值和弱键的表的键和值都允许收集。任意情况下,当键或值被收集,其键值对都会从表中移除。表的“弱性”由元表中的__mode属性来控制。此元值如果存在,则必须为给出的字符串之一:"k"——表示拥有弱键的表,"v"表示拥有弱值的表,"kv"表示同时拥有弱键和弱值的表。

拥有弱键和强值的表也被称为临时表(ephemeron table)。在临时表中,键可达是值可达的前提。具体来说,某个键只被其值引用时,这个键值对将被删除。

任何对表的“弱性”做的修改都会在下一次GC周期生效。比如,当你将“弱性”改成一个更强的模式时,Lua仍然会在改动生效之前回收一些东西。

只有显式构建的对象才会从弱表中移除。纯值,例如数字和轻量C函数,其不受GC的约束,因此它们不会从弱表中移除(除了收集它们所关联的值)。尽管字符串受制于GC,但其没有显式的结构且与纯值平等,比起对象其行为更类似于值。因此其也不会从弱表中移除。

复原的对象(即,正被执行终结的对象,或仅被正在终结的对象所引用的对象)在弱表中的行为比较特殊。当对象明确被释放时,于弱值中的将会在执行终结之前被移除,而在弱键中的则只会在执行终结后的下一次收集中被移除。这是为了允许终结器通过弱表来访问与其对象所关联的属性。

如果弱表在一个收集周期中被复原的值里,那么在下个循环之前可能都没有得到适当的清理。

2.6 - 协程(Coroutines)

Lua支持协程(Coroutines),其也被称为协同式线程(collaborative multithreading)。Lua中的协程表示一个独立执行线程。然而不同于多线程系统中的线程,协程只能通过显示调用让出函数来挂起自身的执行。

通过调用coroutine.create你可以创建一个协程。此函数的唯一参数是用作协程主函数的方法。create函数只是创建一个新协程并返回其句柄(一个thread类型的对象),并不会启动协程。

你可以调用coroutine.resume来执行协程。当你首次调用coroutine.resume,需要将coroutine.create返回的结果作为第一个参数传递,协程将会通过调用其主函数来启动执行。其余传递给coroutine.resume的参数会被传递到这个函数中。启动协程将会一直运行,直到它结束或让出(yields)

协程可以通过两种方式来结束运行:通常情况下是其主函数返回(显式或隐式,在最后一条指令后);特别情况是产生了未在保护下的错误。正常结束的情况下,coroutine.resume返回true,后跟协程的主函数返回的任意结果值。在发生错误时,coroutine.resume返回false,后边跟着错误对象。这种情况下,协程不会展开堆栈,所以在错误发生后可以通过调试API来调查。

协程通过调用coroutine.yield来让出。当协程让出时,相应的coroutine.resume会立刻返回,即使让出发生在内嵌的函数调用中(即不在主函数中,而是在主函数直接或间接调用的函数中)。当在让出的情况下,coroutine.resume也是返回true,后跟由coroutine.yield传递而来的值。当你下次重启同一个协程时,其将会在之前让出的地方继续执行,调用coroutine.yield的地方会返回由coroutine.resume传递过来的额外参数。

coroutine.create一样,coroutine.wrap函数也可以创建协程,但不返回协程本身,而是返回一个用来启动协程的函数。任何传递到这个函数的参数都会作为coroutine.resume的额外参数传入。coroutine.warp返回所有coroutine.resume的值,除了第一个(那个布尔错误码)。与coroutine.resume不同,由coroutine.wrap创建的函数不会传播任何错误给用户。这种情况下,此函数还会关闭协程(参见coroutine.close)。

作为一个展示协程如何工作的例子,请考量以下代码:

function foo (a)
  print("foo", a)
  return coroutine.yield(2*a)
end

co = coroutine.create(function (a,b)
      print("co-body", a, b)
      local r = foo(a+1)
      print("co-body", r)
      local r, s = coroutine.yield(a+b, a-b)
      print("co-body", r, s)
      return b, "end"
end)

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_newthreadlua_resumelua_yield函数。

3 - 语言

本章将描述Lua的词法、语法结构及语义。换言之,本章将描述有哪些起效的语法标记(tokens),它们如何组合,以及其组合的意义。

这里将使用常见的BNF扩展表示来解释语言结构:其中{a}表示0个或多个a,[a]表示一个可选的a。可分解的非终结符号会表示为non-terminal,关键词会像kword表示,其他不可分解的终结符会像‘=’一样表示。在本手册的最后(参见9)中可以找到Lua的完整语法。

3.1 - 词法约定

Lua是一门格式自由的语言。除了两个语法标记之间的分隔符,其余的空白字符以及在语法标记间的注释会被忽略。在源码中,Lua将ASCII标准下的空格字符、制页符、换行符、回车、垂直制表位和水平制表位都识别为空白字符。

Lua中的名称(也称作标识)可以是由非数字开头、非保留字开头的,任意小写拉丁字母、阿拉伯数字及下划线组合而成的字符串。标识被用作变量、表属性和标签的命名。

下面是被保留的关键词,它们不能作为名称使用:

and       break     do        else      elseif    end
false     for       function  goto      if        in
local     nil       not       or        repeat    return
then      true      until     while

Lua是大小写敏感的语言:and是保留词,但是AndAND 就是两个不同的有效名称了。在惯例下,程序应当避免创建以下划线开头、后跟一个或多个大写字母的名称(例如_VERSION)。

以下是一些其他标识:

+     -     *     /     %     ^     #
&     ~     |     <<    >>    //
==    ~=    <=    >=    <     >     =
(     )     {     }     [     ]     ::
;     :     ,     .     ..    ...

文本字符串(literal string) 可以通过单引号或双引号分割,且可以包含C形式的转移符:'\a'(响铃)、'\b'(退格)、'\f'(换页)、'\n'(换行)、'\r'(回车)、'\t'(水平制表)、'\v'(垂直制表)、'\\'(反斜杠)、'\"'(双引号)以及'\''单引号。反斜杠后跟输入一个换行会在字符串中新起一行。转义符'\z'会跳过后边一系列的空白字符,包括换行符。这在以下情况特别有用,当你想将一个比较长的文本字符串分为多行写入时,同时又不希望插入任何换行或空白字符。短文本字符串不可以包含原始未转义的换行符或转义未完成的字符序列。

我们可以通过字节数值在短文本字符串中指定任意字节数据,包括内嵌的0。我们可以用转义序列\xXX来表示XX这个两位的十六进制数,或者用转义序列\ddd来表示ddd这个三位的十进制数。(注意如果十进制转义序列后要紧跟一个数字的话,那么这里的转义序列必须使用完整的三位数字形式。)

UTF-8编码的字符可以用转义序列\u {XXX} (此处的大括号不可省略)插入到文本字符串中,此处的XXX为一位或多位十六进制数表示的字符码点。码点可以是任意小于231的值。(此处Lua使用原始的UTF-8规范,并不完全符合Unicode的有效码点。)

也可以使用一种由长括号括起来的长格式来定义文本字符串。我们将一个开方括号后跟n个等号后再跟一个开方括号的组合定义为n级开长括号。所以0级开口长括号写作[[,1级开口长括号写作[=[,以此类推。闭长括号的定义与之类似;例如,4级闭长括号写作]====]。一段长文本开始于任意级别的开长括号并结束于同级别的闭长括号。其可以包含除同级闭长括号之外的任意字符。此括号形式内的文本字符可以是多行文本,解释器将不会做任何转义操作,并忽略任意其他级别的长括号。各种行末序列(指回车、换行、换行后跟回车或回车后跟换行)会被转换为简单的换行。换行后紧跟开长括号的情况下,此换行不会包含在字符串里。

举个例子,在使用ASCII码(在这个标准下,'a'的码点为97,换行的码点为10,'1'的码点为49)的系统中,以下5种文本字符串都表示相同的字符串:

a = 'alo\n123"'
a = "alo\n123\""
a = '\97lo\10\04923"'
a = [[alo
123"]]
a = [==[
alo
123"]==]

除了符合上述规则的之外,其余任意字节在文本字符串中都表示为它本身。然而,Lua会在文本模式下打开文件来解析,而且系统的文件方法可能有些控制字符的问题。所以,传达非文本字符的安全做法是把其二进制数据作为用显式转义表示的引用文本。

一个常量数字(或者叫数值)写成一个可选的小数部分和一个可选的十为底的指数部分一起的形式,指数部分由'e'或'E'标记。Lua也接受以0x或0X开头的十六进制数常量。十六进制数常量也接受一个可选的小鼠部分加上一个可选的二为底的指数部分,标记为小写的'p'或'P'并将其写到指数中。(例如,0x1.fp10表示为1984,由0x1f除以16再乘以210得来。)

可以由小数点或指数组成的数字常量表示浮点数;如果其值可表示为整数或者是十六进制常量,则为整数;否则即溢出的十进制整数),则为浮点数。既没有小数点也没有指数表示的常量数字为整数,如果这个值溢出,则回绕到合适的整数值。

合法的整数常量的示例:

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)。

除非显式声明为局部变量,否则任何变量名都被认为是全局的。局部变量是相对于词法作用域(lexically scoped) 下的:局部变量可以被其定义范围下的函数访问(参见3.5)。

在第一次给变量赋值之前,其值为nil

方括号被用于索引一个表:

var ::= prefixexp ‘[’ exp ‘]’

可以通过元表来改变访问表属性的意义(参见2.4)。

var.Name语法只是var["Name"]的语法糖。

var ::= prefixexp ‘.’ Name

访问全局变量x相当于_ENV.x。由于代码块的编译方式,变量_ENV本身永远不会是全局的(参见2.2)。

3.3 - 语句

Lua支持着一组通俗易见的语句集,就像其他常见语言中的一样。其中包括块、赋值、控制结构、函数调用、以及变量声明。

3.3.1 - 语句块(Blocks)

语句块(Block) 是一个依序执行的语句列表:

block ::= {stat}

可以使用分号来分隔语句以开始一个新语句块,或者连续使用两个分号产生一个空语句

stat ::= ‘;’

函数调用和赋值都可以用左圆括号开头。但在Lua的语法中这可能会导致歧义。考量以下片段:

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

语法有两种方式看待此片段:

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

当前版本的解析器通常用第一种方式看待这个结构,将左圆括号解释为函数调用的开始。为了避免这种歧义,在良好的实现中通常会在圆括号为始的语句的开头加上分号:

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

可以显式分割语句块以生成单独的语句:

stat ::= do block end

显式的语句块对于控制变量声明的范围十分有用。显式的语句块有时候也用于在其他的语句块中添加return语句。

3.3.2 - 代码块(Chunks)

Lua的编译单元称为一个代码块(Chunk)。从语法上说,一个代码块就是一个简单的语句块:

chunk ::= block

Lua将一个代码块作为具有可变参数的匿名函数体来处理(参见3.4.11)。因此,代码块可以定义局部变量、接收参数以及返回值。另外,此匿名函数是在一个称作_ENV的外部局部变量的范围下编译的(参见2.2)。这使得函数通常都拥有_ENV作为其唯一的外部局部变量,即使本身没有使用变量。

代码块可以被存储在文件或宿主程序的字符串中。当执行一个代码块,Lua首先会加载它,将代码块中的代码预编译为虚拟机的指令,然后Lua通过虚拟机的解释器来执行编译后的代码。

代码块被预编译为二进制形式;细节请参见程序luac和string.dump函数。程序中的代码形式和编译后形式是可以互换的;Lua会自动检测文件类型并采取相应行动(参见load)。

3.3.3 - 赋值

Lua允许多重赋值。因此,Lua的赋值语法定义成左边是变量列表而右边是表达式列表。其两边的列表都以逗号分隔元素:

stat ::= varlist ‘=’ explist
varlist ::= var {‘,’ var}
explist ::= exp {‘,’ exp}

表达式参见3.4

在做赋值操作之前,右侧值列表会被调整至与左侧变量列表长度相同(参见3.4.12)。

如果某个变量在多重赋值中同时被读取,那么Lua会确保所有值的读取操作处在赋值之前。

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

因此这段代码会将a[3]设为20,而不会影响a[4]的值,因为a[i]中的的i值在其被赋值为4之前就已经决定了。

x, y = y, x

同理,这行代码会交换x和y的值。

x, y, z = y, z, x

而这行代码会回绕交换x、y、z的值。

要注意这里的访问语义保证只被涵盖在赋值语句中。如果在赋值期间有方法或元函数改变了变量的值,Lua将不会保证访问顺序。

全局名称的赋值 x = val 等效于 _ENV.x = val(参见2.2)。

这意味这可以通过元表来改变表属性和全局变量(实际上也是表属性)的赋值(参见2.4)。

3.3.4 - 控制结构

控制结构ifwhilerepeat的含义都很常见而且有相似的语法:

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)。

控制结构中的条件表达式可以返回任何值。falsenil均表示假值。所有不同于nilfalse的值都表示真值。尤其数字0和空字符串也表示真值。

repeat-until循环中,其里边的语法块不是以until关键词为结束,而是在条件语句的后面。所以其条件表达式可以引用循环语法块中声明的局部变量。

goto语句用以改变程序的控制流到一个标签处。出于语法的原因,Lua中的标签也被视为语句:

stat ::= goto Name
stat ::= label
label ::= ‘::’ Name ‘::

标签在定义它的整个语法块中都是可见的,除了其内嵌的方法。goto可以跳转到任何可见的标签处,只要它没有进入另一个局部变量的作用域。不应当重复声明一个已存在的同名标签,即使这个此标签已经声明在完成的语法块中。

break语句会打断whilerepeatfor循环的执行,然后跳转到循环的下一条语句:

stat ::= break

break只跳出最内层的循环。

return语句被用来从方法或代码块(会被当成匿名函数处理)中返回值。方法可以返回多个值,所以return语句的语法为:

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

return语句只能作为最后一条语句被写在语法块中。如果需要在语法块的中间return,你可以显式使用一个语句块,例如短句 do return end,这样现在return在(内部)语法块中就是最后一句了。

3.3.5 - for语句

for语句有两种形式:数字形式和通用形式。

数字形式的for语句

数字形式的for语句是以一个控制变量遍历算术过程来循环重复一个代码块。其有以下语法:

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

给出的标识符(名称)定义了控制变量,其为循环体(语法块)中一个新的局部变量。

循环首先计算一次三个控制表达式的值。这些值分别被称作初始化变量界限以及步长。如果步长被省略了,其默认值为1。

如果初始化变量和步长都是整数,那么循环是由整数结束的;要注意界限可以不是整数。否则,三个值会被转换为浮点数且循环由浮点数结束。这种情况下要注意浮点精度的问题。

在初始化完成后,循环体会通过控制变量的遍历算术过程来重复,其由初始值为始,由给出的步长计算差值。负数的步长会产生一个递减的序列;步长等于零时会抛出异常。循环一直持续到值小于等于(对于正数步长则是大于等于)界限值为止。如果初始值已经小于(或者大于,当步长为正数)界限值时,循环体不会被执行。

对于整数循环,控制变量永远不会回绕;反之,溢出会结束循环。

你不应当在循环期间改变控制变量的值。如果你在循环之后还需要这个值,你可以在退出循环前将其赋值给另一个变量。

通用形式的for语句

通用形式的for语句通过一个被称为迭代器的方法来工作。在每个迭代器中,其迭代方法被调用以产生一个新的值,当这个新值为nil时循环结束。通用形式的**for*循环有以下语法:

stat ::= for namelist in explist do block end
namelist ::= Name {‘,’ Name}

一条for语句就像这样:

for var_1, ···, var_n in explist do body end

var_i被声明为循环体的局部变量。其中的第一个变量就是控制变量

循环首先计算explist以生成四个值:迭代器方法状态、控制结构的初始化变量以及关闭值

然后每次迭代时,Lua会分别将状态和控制变量作为两个参数来调用迭代方法。调用结果在之后被复制到循环变量中,流程遵循多重赋值的规则(参见3.3.3)。控制变量变为nil时,循环终止。否则,将执行循环体并循环到下次迭代。

关闭值的行为和待关闭变量(参见3.3.8)相似,可以用于循环结束时释放资源。否则它不会影响循环。

你不应当在循环期间改变控制变量的值。

3.3.6 - 作为语句的函数调用

考虑到副作用,函数调用可以作为语句执行:

stat ::= functioncall

其中,所有的返回值都被丢弃。函数调用的解释可以参见3.4.10

3.3.7 - 局部声明

局部变量可以声明在语法块中的任意位置。声明可以同时做初始化:

stat ::= local attnamelist [‘=’ explist]
attnamelist ::=  Name attrib {‘,’ Name attrib}

如果有初始化赋值,则其语法与多重赋值一致(参见3.3.3)。否则,所有的变量都初始化为nil

每个变量名后可以跟一个属性(一个在尖括号中的名称):

attrib ::= [‘<’ Name ‘>’]

属性有两种可能的值:const——声明一个常量;即一个不能在初始化后再次赋值的变量。close——声明一个待关闭变量(参见3.3.8)。

一个代码块也时一个语法块(参见3.3.2),因此也可以在显式语法块之外的代码块中声明局部变量。

局部变量的可见性规则解释可以参见3.5

3.3.8 - 待关闭变量

待关闭变量的行为类似局部常量,不同在于变量超出作用域后其值会被关闭,包括常规语法块的结束、使用break/goto/return退出语法块、或者因为错误而退出。

在这里,关闭一个值的意思是指调用__close元函数。当调用这个元函数时,这个值本身会作为第一个参数传递,导致其退出的错误对象(如果有的话)会作为第二个参数传递;如果没有错误,那么第二个参数为nil

被待关闭值所赋值的值必须有__close元函数或者是个假值(nilfalse会被被待关闭值忽略)。

如果在调用关闭函数时出现了错误,此错误将会被当作在其变量定义处的一段常规代码所抛出的错误处理。在错误结束后,其他待定的关闭函数仍然会被调用。

如果协程让出或再也不重启了,一些变量可能永远不会超出作用域,因此它们将永远不会关闭。(这些变量在协程内部创建的,且在协程让出点之前的范围内。)类似的,如果协程因为错误而退出了,其不会展开其堆栈,因此也不会关闭任何变量。在这些情况下,你可以使用终结器或者调用coroutine.close来关闭变量。然而,如果协程是用coroutine.wrap创建的,那么其相应的函数要在发生错误的情况下关闭协程。

3.4 - 表达式

以下是Lua中基本的表示式:

exp ::= prefixexp
exp ::= nil | false | true
exp ::= Numeral
exp ::= LiteralString
exp ::= functiondef
exp ::= tableconstructor
exp ::= ‘...’
exp ::= exp binop exp
exp ::= unop exp
prefixexp ::= var | functioncall | ‘(’ exp ‘)’

数字和文本字符串参见3.1;变量相关参见3.2;方法定义参见3.4.11;方法调用参见3.4.10;表的构造参见3.4.9。可变参数表达式,其表示为三个点('...'),只能直接在可变参数方法中使用;相关解释请参见3.4.11

二元操作包含算术操作(参见3.4.1)、位操作(参见3.4.2)、关系操作(参见3.4.4)、逻辑操作(参见3.4.5)以及连接操作(参见3.4.6)。一元操作包含取负操作(参见3.4.1)、按位否操作(参见3.4.2)、逻辑操作(参见3.4.5)以及取长操作(参见3.4.7)。

3.4.1 - 算术操作

Lua支持以下算术操作:

  • +:加法
  • -:减法
  • *:乘法
  • /:浮点除法
  • //:整除
  • %:模
  • ^:幂
  • -:一元运算,取负

除了幂和浮点除法之外,算术操作的工作流程如下:如果操作数都是整数,那么以整数操作且结果为整数。否则,如果操作数都是数字,那么以机器浮点算术规则(通常是IEEE 754标准)的流程操作,且结果是浮点数。(在算术运算中string库将字符串强制转换到数字;参见3.4.3。)

幂和浮点除法(/)通常会将它们的操作数转换到浮点数且结果也是浮点数。幂运算使用ISO标准C函数pow,所以它也适用于非整数幂。

整除(//)是指对操作数做除法后取整。

模的定义是指对操作数整除后取余。

对于整数算术操作出现溢出的情况,所有的操作都会回绕

3.4.2 - 位操作

Lua支持以下位操作:

  • &:按位与
  • |:按位或
  • ~: 按位异或
  • >>:右移
  • <<:左移
  • ~:一元运算,按位取反

所有的位操作都将操作数转换到整数(参见3.4.3),在这些整数的所有位上进行操作,其结果为整数。

左移和右移都使用零填补空位。负位移向另一个方向移动;位移的绝对值如果等于或高于整数数的位数,则其结果为零(因为所有的位都被移出了)。

3.4.3 - 转换及强制转换

Lua在运行时中提供了对一些类型和表达的自动转换。位操作总是会将浮点操作数转换为整数。幂运算和浮点除法总是将整数操作数转换为浮点数。用于混合数字(整数和浮点数)的其他算术运算都是将整数操作数转换为浮点数。C API会根据需要将整数转换到浮点数或将浮点数转换到整数。此外,除了字符串,数字也可以作为字符串连接操作的参数。

从整数转换到浮点数的过程中,如果整数值有一个确切的浮点表示,那么结果就是这个表示的浮点数。否则,转换会得到一个与之最接近的值。这种转换永远不会失败。

从浮点数到整数的转换中会检查浮点数是否有确切的整数表示(即此浮点数有处于整数表示范围内的整数值)。如果检查成功,则这个整数表示就是其结果。否则,转换失败。

出于需要,Lua中的有些地方会将字符串强制转换到数字。尤其string库会设置元函数以尝试在算术操作中将字符串转化为数字。如果转化失败,此库会调用另一个操作数的元函数(如果存在的话)或者抛出错误。注意位操作不会执行此转换。

通常在良好实现中不会依赖字符串到数字的隐式转换,因为它们并不是一直适用的;比如 "1" == 1 为假,而 "1" < 1 会抛出错误(参见3.4.4)。这些强制转换主要是因为兼容性,并且其可能在语言的未来版本中删除。

字符串到数字或浮点数的转换遵循语法和Lua的词法解析器的规则。字符串的前后也可能有空格或其他字符。所有从字符串到数字的转换都接收点或者当前局部标记作为基数字符。(然而Lua的词法解析器只接受点。)如果字符串不是一个有效的数字表示,则转换失败。如果有必要,第一步的结果将按照之前的整数与浮点数的转换规则来转换到特定子类型。

数字到字符串的转换会使用常见的人类可读的格式。如果要用特殊的方式来将数字转换到字符串,可以使用string.format方法。

3.4.4 - 关系操作

Lua支持以下关系操作:

  • ==:相等
  • ~=:不等
  • <:小于
  • >: 大于
  • <=:小于等于
  • >=:大于等于

这些关系操作的结果始终都是falsetrue

相等(==)首先会比较操作数的类型。如果类型不同,那么结果就是false。否则将比较操作数的值。字符串在其拥有相同的字节内容时相等。数字在其拥有相同的算术值时相等。

表、 userdata 和协程都是比较其引用:两个对象只在同时引用同一个对象的情况下相等。每当你创建一个新对象(表、 userdata 或协程),这个新对象和之前存在的所有对象都是不同的。方法总是和它自身相等。有任何可检测的差异(不同的行为或不同的定义)的方法总是不同的。不同时间创建但没有可检测差异的方法之间被归类于相等或不等都是有可能的(这取决于内部的缓存细节)。

你可以通过使用__eq元函数(参见2.4)来改变Lua表和 userdata 的比较方式。

比较是否相等时不会将字符串转换到数字,反之亦然。因此,"0"==0等于false,且t[0]和t["0"]表示在表中表示不同的字段。

不等操作(~=)与相等操作(==)相反。

顺序操作以如下流程工作:如果参数都是数字,则通过它们的算术值比较,无论它们的子类型如何。另外,如果参数都是字符串,则通过当前所设置的方式来比较。否则,Lua会尝试调用__lt或__le元函数(参见2.4)。a > b会被转化为b < a,而a >= b会被转换为b <= a,从而进行比较。

在IEEE 754标准中,特殊值NaN被认为不小于、不等于、也不大于任何值,包括它自己。

3.4.5 - 逻辑操作

Lua中的逻辑操作就是andornot。与控制结构(参见3.3.4)类似,所有的逻辑操作将falsenil都视为假值而其他则视为真值。

取反操作not总是返回falsetrue。逻辑与操作and中,当第一个参数为falsenil时会将其返回;否则and返回第二个参数。逻辑或操作or中,当第一个参数的值不同于falsenil时会将其返回,否则or返回第二个参数。andor都使用短路规则,第二个操作数只有在必要的时候才会被评估。这里是一些例子:

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

3.4.6 - 连接

Lua中的字符串连接操作表示为两个点('..')。如果操作数都是字符串或数字,那么数字会被转换到非特定格式的字符串(参见3.4.3)。否则,元函数__concat将会被调用(参见2.4)。

3.4.7 - 取长操作符

取长操作符表示为一元前缀 #

这里字符串的长度就是其字节数量。(通常每个字符为单字节时意味着字符串真实长度。)

表的取长操作返回表的边长(border)。表t边长是一个满足以下条件的非负整数:

(border == 0 or t[border] ~= nil) and (t[border + 1] == nil or border == math.maxinteger)

简单来说,边长是表的一个正整数索引,它的下个索引刚好在表中不存在,然后再加上两条限制:当索引1不存在时,边长为0;以及所存在的索引值最大为整数值的最大值。注意表中存在的非正整数键不会影响其边长。

一个只有边长的表被称为序列(sequence)。例如,表 {10, 20, 30, 40, 50} 就是一个序列,因为它只有边长(5)以内的键。当表t并不是序列时,#t 也只会返回其边长。(确切地说它取决于表的内部表示,说回来又取决于表的填充方式和非数字键的内存地址。)

表长度计算的时间复杂度为O(log n),其中的n为表中的最大整数键。

在程序中可以通过__len元函数来更改除字符串之外的任意类型的取长操作。

3.4.8 - 优先级

Lua中操作符的优先级(从低到高)如下所示:

or
and
<     >     <=    >=    ~=    ==
|
~
&
<<    >>
..
+     -
*     /     //    %
unary operators (not   #     -     ~)

通常,你可以通过使用圆括号来改变表达式中的优先级。连接符('..')和幂运算('^')都是随右边的。所有其他的二元操作符都是随左边的。

3.4.9 - 表的构造

表构造即创建表的表达式。每当构造语句生效,都会创建一个新表。构造语句可以用来创建空表或创建并初始化一个表。构造语句的通用语法为:

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 }

等效于:

do
  local t = {}
  t[f(1)] = g
  t[1] = "x"         -- 第一个exp
  t[2] = "y"         -- 第二个exp
  t.x = 1            -- t["x"] = 1
  t[3] = f(x)        -- 第三个exp
  t[30] = 23
  t[4] = 45          -- 第四个exp
  a = t
end

关于构造语句中的赋值顺序则是未定义的。(这里指对于重复的键而言的顺序。)

如果构造列表中的最后一个属性是exp形式且是个多值复合表达式,那么其产生的所有值则依次进入到构造列表中。(参见3.4.12

构造列表的属性间的分隔符后缀是可选可,以方便生成机器码。

3.4.10 - 函数调用

函数调用的语法如下:

functioncall ::= prefixexp args

在函数调用中,首先会计算打头的前缀表达式(prefixexp)和后边的参数(args)。如果前缀表达式的值是function类型的,那么这个值对应的函数就通过所给参数来调用。否则,如果前缀表达式其值有__call元函数的话,会这样调用此元函数:第一个参数是前缀表达式的值,后跟原本的调用参数(参见2.4)。

functioncall ::= prefixexp ‘:’ Name args

这种形式被用来调用成员函数。v:name(args) 其实是 v.name(v, args) 的语法糖,不同之处在于v只被计算一次。

调用参数的语法如下:

args ::= ‘(’ [explist] ‘)’
args ::= tableconstructor
args ::= LiteralString

所有参数表达式会在调用之前被计算。f{fields}调用形式其实就是f({fields})的语法糖;即此参数列表是一个表。 f'string'(或者 f"string" 有或者 f[[string]])调用形式其实就是 f('string')的语法糖;即此参数列表是一个字符串。

所有在非待关闭变量的作用域下的 return functioncall 调用形式被称为尾调用(tail call)。Lua实现了尾调用优化 proper tail calls(或称为尾递归优化 proper tail recursion):在尾部调用中的调用函数会重复使用调用栈。因此程序可以执行无限制的内嵌尾调用。然而,尾调用会擦除所有其调用函数的调试信息。注意尾调用只发生于特定的语法中:return语句只有一个函数调用作为其参数,且这个函数在任何待关闭变量的作用域之外。此语法使得调用函数时直接返回其函数的返回值,其中没有任何多余动作。所以,以下都不是尾调用:

return (f(x))        -- 返回值后还有一步操作,非直接的函数调用
return 2 * f(x)      -- 返回值后有两步操作,也是非直接的函数调用
return x, f(x)       -- 多个返回值
f(x); return         -- 返回值被全局丢弃
return x or f(x)     -- 计算返回值前还有一步操作,非直接的函数调用

3.4.11 - 函数定义

函数定义的语法为:

functiondef ::= function funcbody
funcbody ::= ‘(’ [parlist] ‘)’ 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

(当函数体内包含f的引用的情况下时,才会凸显此处的差别。)


函数定义是一个可执行的表达式,其值的类型是function。当Lua预编译代码块时,所有的函数体也会被预编译,但是它们还没有被创建。在之后的过程中,当Lua执行到函数定义时,这个函数定义才实例化 instantiated(或者说关闭 closed 了)。此函数实例,或称作闭包(closure),就是这个表达式的最终值。

形参是被作为由参数值进行初始化的局部变量:

parlist ::= namelist [‘,’ ‘...’] | ‘...’

当Lua中的函数被调用时,传入的实参列表都将会被调整到函数形参列表的长度,除非是可变参数函数(variadic function)——参数列表的末尾由三个点('...')表示。一个可变参数函数不会调整其传入的参数列表;相应的,他会收取所有的额外参数并通过*可变参数表达式(vararg expression)*填充到函数中,可变参数表达式也写作三个点('...')。这个表达式的值是一系列额外参数的值,类似于一个具有多个返回值的函数。

作为示例,请考量以下代码:

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语句都没遇到,那么函数将不会返回任何结果。

函数可以返回多少个值与系统相关限制有关,但Lua保证大于1000。


可以用冒号语法模拟定义成员方法,会将一个隐式的额外参数self添加到函数中。因此,语句

function t.a.b.c:f (params) body end

t.a.b.c.f = function (self, params) body end

的语法糖。

3.4.12 - 表达式列表、多重返回以及相应调整

函数调用和可变参数表达式的结果都可以是多个值。这些表达式被称为多重表达式(multires expressions)

当一个多重表达式被用作表达式列表的最后一个元素时,其所有的结果值都被添加到由表达式列表生成的值列表中。注意在表达式列表中的单一表达式也视为列表中的最后一个表达式(即单一列表)。

这些是Lua视为表达式列表的地方:

  • return语句,例如 return e1, e2, e3(参见3.3.4)。
  • 表的构造语句,例如 {e1, e2, e3}(参见3.4.9)。
  • 函数调用的形参,例如 foo(e1, e2, e3)(参见3.4.10)。
  • 多重赋值语句,例如 a , b, c = e1, e2, e3(参见3.3.3)。
  • 局部声明,例如 local a , b, c = e1, e2, e3(参见3.3.7)。
  • for循环通用形式的初始化变量,例如 for k in e1, e2, e3 do ... end(参见3.3.5)。

在后四种情况中,表达式列表的值列表已经会被调整到合适的长度:非可变参数函数的参数个数(参见3.4.11)、多重赋值和局部声明的变量个数、以及上述for循环通用形式中的那几个值的个数4。这里的调整遵循这些规则:如果存在多于所需要的值,那么额外的将被抛弃;如果所有的值比所需的少,那么会使用nil来扩展列表。当表达式列表最后也是个多重表达式时,此多重表达式的所有值将会在调整之前进入值列表。

当一个多重表达式在表达式列表中并不是最后一个元素时,或在一个被语法视为单一表达式的位置中,Lua会将表达式的结果列表调整为单个元素。举个特殊的例子,语法将单一表达式视作其在一对括号内;因此,在多重表达式外加上括号会直接强制生成单个结果。

我们很少在一个被语法视为单一表达式的地方使用多重表达式。(通常更简单的方式是在可变部分之前添加并使用常规参数。)当有这样的需求时,我们推荐将多重表达式赋值给一个单独的变量然后在此处使用。

这里是一些使用多重表达式的例子。无论如何,当构造语句中需要“第n个结果”然而并没有时,都使用nil

print(x, f())      -- 打印x和f()的所有结果
print(x, (f()))    -- 打印x和f()的第一个结果
print(f(), x)      -- 打印f()的第一个结果和x
print(1 + f())     -- 打印f()的第一个结果加1
local x = ...      -- x为可变参数中的第一个值

x,y = ...          -- x为可变参数中的第一个值
                   -- y为可变参数中的第二个值

x,y,z = w, f()     -- x为可变参数中的第一个值
                   -- y为f()的第一个结果
                   -- z为f()的第二个结果

x,y,z = f()        -- x为f()的第一个结果
                   -- y为f()的第二个结果
                   -- z为f()的第三个结果

x,y,z = f(), g()   -- x为f()的第一个结果
                   -- y为g()的第一个结果
                   -- z为g()的第二个结果

x,y,z = (f())      -- x为f()的第一个结果,y和z都为nil
return f()         -- 返回f()的所有结果
return x, ...      -- 返回x和所接收到的所有可变参数
return x,y,f()     -- 返回x、y、以及f()的所有结果
{f()}              -- 创建一个列表,其中是f()的所有结果
{...}              -- 创建一个列表,其中是接收到的所有可变参数
{f(), 5}           -- 创建一个列表,其中是f()的第一个结果和数字5

3.5 - 可见性规则

Lua语言在词法上是有作用域的。局部变量的作用域从它声明后的第一条语句开始,一直到它声明处最后的非空语句,如果最后一条非空语句是包含嵌套的,那么就到最后的最深处的语句为止。(空语句就是单纯的标签和空白语句。)请看下面的代码:

x = 10                -- 全局变量
do                    -- 新的语句块
  local x = x         -- 新的变量x,值为10
  print(x)            --> 打印 10
  x = x+1
  do                  -- 另一个块
    local x = x+1     -- 另一个变量‘x’
    print(x)          --> 打印 12
  end
  print(x)            --> 打印 11
end
print(x)              --> 打印 10 (那个全局的x值)

注意,在 local x = x 这样的声明中,新的x这时还没有在作用域内真的被声明出来,所以右侧的x引用的是外部变量。

因为作用域规则,局部变量在它的作用域下可以被其下所定义的函数任意地访问。被内部函数使用的局部变量被称为上值 upvalue(或者叫外部局部变量,又简称外部变量)。


要注意,每条local语句的执行都会定义一个新的局部变量。请看以下代码:

a = {}
local x = 20
for i = 1, 10 do
  local y = 0
  a[i] = function () y = y + 1; return x + y end
end

这个循环创建了十个闭包(即十个匿名函数实例)。其中每个闭包都使用不同的y变量,却共用同一个x。

4 - 应用编程接口

本章讲述Lua的C API,即可以让宿主程序和Lua通信的C函数集合。所有API函数和相关类型及常量都在头文件lua.h中被定义。

即使我们说的是术语“函数”,但其实API中的某些工具可能是一个宏。除非另有说明,这些宏内部只使用一次它的参数(除了第一个参数,通常都是Lua状态机对象),因此不会产生任何隐藏的副作用。

大部分的C库中,Lua的API函数都不会检查参数的有效性和一致性。然而,你可以通过定义Lua的编译宏 LUA_USE_APICHECK 宏来改变此行为。

Lua库是完全可重入的——它没有全局变量。它把所有需要的信息都保存到一个叫做 Lua状态机(Lua state) 的动态结构中。

每个Lua状态机都有一个或多个Lua线程(就是协程),它们相互独立,却又共同执行。lua_State类型(尽管叫这么个名字,很容易让人误会好吗)就引用了单个Lua线程。(Lua线程也间接引用了相关联的Lua状态机。)

库中每个函数的第一个参数都必须由指向Lua线程的指针传递,除了lua_newstate,此函数会从头创建一个Lua状态机,然后返回一个指向新状态机里的Lua主线程的指针。

4.1 - 栈

Lua使用虚拟栈来与C代码相互传递值。栈中的每个元素都表示一个Lua值(nil、数字、字符串……等等)。API中的函数可以通过接收到的状态机参数来访问栈。

当Lua调用C代码时,所调用的函数会得到一个新的栈,其独立于之前的和仍在活跃的C函数的栈。这个堆栈最初包含C函数的所需参数,C函数可以在这里存储临时Lua值,并且必须将结果都压入栈中以返回给调用方(参见lua_CFunction)。

为了方便,API中的大多数查询操作都不必严格遵循的栈规则。相反,它们可以使用索引来访问栈中的任意元素:正数索引表示栈中的绝对位置,以栈底1开始增长;负数索引表示相对于栈顶的位置。具体来说,如果栈有n个元素,那么索引1表示其第一个元素(即这个元素是第一个被压入栈的)而索引n表示最后一个元素;索引-1也表示最后一个元素(即栈顶的元素)而索引-n则表示第一个元素。

4.1.1 - 栈的大小

当你与Lua API交互,你负责确保一致性。特别说明,你有责任控制栈溢出。当你调用任意API函数时,你必须确保栈有足够的空间以容纳结果。

在上述规则中有个例外:当你调用一个结果数量不固定的Lua函数时(参见lua_call),Lua会确保栈有足够的空间以容纳所有结果。然而,其并不保证有任何额外的空间。所以在调用这样的函数之后,你应该在压栈前使用lua_checkstack检查栈空间。

当Lua调用C代码时,其确保栈中至少有 LUA_MINSTACK 宏定义大小的额外空间;即,你可以安全地压入 LUA_MINSTACK 个值到栈中。LUA_MINSTACK 的定义是20,所以通常你不必担心栈空间除非你在循环中压栈。如有必要,你可以使用lua_checkstack函数来确保栈中有足够的空间来压入值。

4.1.2 - 有效与可接受的索引

API中的任何函数都只接收有效索引可接受索引

有效索引指的是引用位置存储的是可更改的Lua值。它包括从1到栈顶(1 ≤ abs(index) ≤ top)的栈索引以及伪索引 pseudo-indice,伪索引表示一些C代码可以访问但是不在栈中的位置。伪索引被用来访问注册表(参见4.3)和索引C函数的上值(参见4.2)。

可使用可接受索引来调用一个只需要传入值的函数(比如查询函数),而无需传入某个可变变量的所处位置。可接受索引可以是任何有效的索引,也可以是一个在为栈分配的空间内、在栈顶之后的任何存在的位置,即最高能索引栈大小的位置。(注意0绝对是不可接受的索引。)对于当前C函数的上值索引而言(参见4.2),哪怕这个索引的上值实际上大于上值数量,此时这个索引也是可接受的(但是无效)。除非另有说明,API中的函数使用可接受索引。

在查询栈的时候,可接受索引有助于避免对栈顶做额外的测试。例如,某个C函数可以查询其第三个参数却不用检查第三个参数是否真的存在,即无需查询索引3是否有效。

对于可以使用可接受索引调用的函数,任何无效的索引会被当做包含了虚拟类型LUA_TNONE的值来处理,其的行为和nil值类似。

4.1.3 - 字符串指针

API中的有些函数会在栈中返回一个指向Lua字符串的指针(const char*)。(参见辅助库中的lua_pushfstringlua_pushlstringlua_pushstringlua_tolstring。)

通常来说,Lua的GC可以释放或移动内部内存和已失效的内部字符串指针。为了安全地使用这些指针,只要字符串值的索引没有从栈中移除,那么API就会保证在所有栈内索引此字符串的指针有效。(尽管它可以移动到另一个索引上。)当索引是一个伪索引(引用的是一个上值)时,只要相关调用仍然活跃且相关上值没有被改动,其指针就是有效的。

调试接口中有些函数也可以返回字符串指针,它们分别是lua_getlocallua_getupvalue以及lua_setupvalue。对于这些函数,只要这个函数调用仍在活跃且所给(如果有)闭包还在栈中,那么其指针保证有效。

在这些保证之外的情况下,GC可以自由决定内部字符串的指针是否有效。

4.2 - C闭包

当C函数被创建,它可能要关联上一些值,以创建一个C闭包(参见lua_pushcclosure);这些值被称为上值 upvalues,在调用函数的时候可以访问它们。

当C函数被调用,其上值处于特殊的伪索引上。这些伪索引由lua_upvalueindex宏创建。函数关联的第一个上值在lua_upvalueindex(1)索引上,以此类推。任意对lua_upvalueindex(n)的访问中,如果n大于当前函数的上值数量(但是不能大于256——闭包上值的最大数量加一),会产生一个可接受但无效的索引。

C闭包也可以更改其相关上值的值。

4.3 - 注册表

Lua提供了注册表,是一个在C代码中用来存储所需Lua值的预定义表。这个注册表通常使用伪索引 LUA_REGISTRYINDEX 来访问。所有C库都可以在这个表中存储数据,但是必须注意所选择的键要与其他库区分,以避免碰撞。通常你应该使用包含库名的字符串,或者使用一个带有你代码中C对象地址的 light userdata ,亦或使用你的代码所创建的Lua对象来当作注册表的键。与变量名的约定一样,注册表中的以下划线开头后跟大写字母的字符串Lua的保留键。

注册表中的数字键被用于引用机制(参见luaL_ref)和一些预定义值。因此,注册表中的数字键绝对不能用于其他目的。

当你创建一个新的Lua状态机,其注册表会附带一些预定义的值。这些预定义值的索引使用的整数键定义在lua.h中。定义的常量有:

  • LUA_RIDX_MAINTHREAD:状态机的Lua主线程在注册表中的索引位置。(在整个状态机中主线程一共只创建一次。)
  • LUA_RIDX_GLOBALS:全局环境在注册表中的索引位置。

4.4 - C代码中的错误处理

在内部,Lua使用C方法longjump来处理错误。(如果你将Lua作为C++代码编译,那么将使用C++异常;相关细节请在源码中搜索LUAI_THROW。)当Lua面对错误时,例如内存分配错误或类型错误,它会抛出错误;即执行一次longjump。保护模式下的环境使用setjmp设置一个恢复点;任何错误都会跳转到最近的活跃恢复点。

在C函数内部你可以显式调用lua_error来抛出错误。

API中的大部分函数都可以抛出错误,例如内存分配错误。本文中对于每个函数都会说明是否可能抛出异常。

如果在保护模式环境的外部发生了错误,Lua会调用panic函数(see lua_atpanic)然后调用abort,以此来退出宿主程序。你的panic函数可以永不返回以避免程序退出(例如做一次Lua外部的long jump)。

panic函数,其顾名思义(panic有陷入恐慌的含义)就是最后的挽救机制,应该在程序中避免。在通用的规则下,当Lua在一个状态机中调用了一个C函数,这个函数对Lua状态机中做的任意操作都应该处于保护模式下。然而当C代码对其他的Lua状态机操作时(例如作为一个是参数传进函数的Lua状态机,另一个是在注册表中存储的Lua状态机,或者还有lua_newthread返回的结果),其应当使用那些不会抛出错误的API调用。

paninc函数运行方式类似于(错误)消息处理(参见2.3);尤其是错误对象会在栈顶上。然而这里的栈空间没有任何保证。所以panic函数中应当在把一些东西压栈前首先检查其中的可用空间(参见4.1.1)。

4.4.1 - 状态码

一些可能发生错误的API函数会使用以下错误码来表明不同种类的错误或者其他含义:

  • LUA_OK (0):无错误。
  • LUA_ERRRUN:运行时错误。
  • LUA_ERRMEM:内存分配错误。对于这类错误,Lua不会调用任何(错误)消息处理。
  • LUA_ERRERR:运行(错误)消息处理时又引发了错误。
  • LUA_ERRSYNTAX:预编译时的语法错误。
  • LUA_YIELD:Lua线程(协程)让出。
  • LUA_ERRFILE:文件相关的错误;例如不能打开或读取文件。

这些常量定义在lua.h文件中。

4.5 - 在C代码中处理让出

在内部,Lua使用C库函数longjump来让出协程。因此,如果有一个C函数foo调用了一个API函数且这个API函数让出了(直接或间接地由其他函数发起的让出),那么Lua不会再从foo中返回了,因为longjmp在C栈上移除了其栈帧。

为了避免此类问题,每当尝试通过API调用来让出时都会抛出错误,除非是这三个函数:lua_yieldklua_callklua_pcallk。这些函数会接收一个延续函数 continuation function(作为参数名k)以在让出后继续执行。

我们需要说到一些术语以解释延续。我们把从Lua调用的C函数称为源函数 original function。这个源函数中调用的上述这三种C API中的函数,我们称为被调用方函数 callee function,它们会让出当前Lua线程。这种情况会在callee函数为lua_yieldk时发生,或者callee函数是lua_callklua_pcallk且它们发生了让出时。

假设运行的Lua线程执行callee函数时让出了,那么在Lua线程重入后,其最终源函数应当完成运行。然而callee函数并不能返回源函数,因为其C堆栈上的栈帧已经因为让出而被销毁了。作为代替,Lua会调用延续函数,其通过callee函数的参数来给出。顾名思义,延续函数应当继续完成源函数的工作。

作为示例,请考量以下函数:

int original_function (lua_State *L) {
  /*... code 1 */
  status = lua_pcall(L, n, m, h);  /* calls Lua */
  /*... code 2 */
}

现在我们想让Lua代码可以在lua_pcall中运行时让出。首先我们可以重写我们的函数,像这样:

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代码被某些方式中断执行(错误或者让出)后应该调用k,所以我们需要重写代码,用lua_pcallk替换lua_pcall,像这样:

int original_function (lua_State *L) {
  /* ... code 1 */
  return k(L, lua_pcallk(L, n, m, h, ctx2, k), ctx1);
}

注意外层显式调用的延续函数k:Lua只会在需要的情况下调用延续函数,即因为错误或让出后的时候。如果被调用的函数并没有让出而是正常返回,lua_pcallk(以及lua_callk)也将会正常返回。(当然,这种情况下与其调用延续函数k,还不如把等效的工作直接放到源函数中完成。)

除了Lua状态机,延续函数还有两个其他参数:调用的最终状态码和上下文(ctx),其由原来的lua_pcallk传递而来。Lua并不使用这个上下文,它只是从源函数中传递给延续函数。对于lua_pcallk,状态应当和lua_pcallk本身的返回相同,除了执行让出后返回LUA_YIELD(而不是LUA_OK)。对于lua_yieldklua_callk,当Lua调用延续函数时它们的状态码永远是LUA_YIELD。(对于这两个函数,Lua将不会因为错误而调用延续函数,因为它们根本就不会处理错误。)同样,当使用lua_callk时,你应当将LUA_OK当作状态码来调用延续函数。(对于lua_yieldk,这里直接显式调用延续函数并没有太多意义,因为lua_yieldk通常不返回。)

Lua将延续函数和源函数作相同看待。延续函数同样接收来自源函数的Lua栈,如果callee函数已经返回了,那么还是在相同的Lua状态机内。(例如,在lua_callk之后,函数和其参数都从栈上移除了,取而代之的是调用结果。)其拥有同样的上值。无论如何,此返回都会被Lua作和源函数返回一样的处理。

4.6 - 相关函数及类型

在这里我们列出了C API中所有的函数和类型,其按照字母顺序排列。每个函数都有一个像这样的标注:[-o, +p, x]

其中第一个属性o表示这个函数会从栈上弹出多少个元素。第二个属性p表示这个函数会把多少个元素压入栈。(任何函数都会在弹出其参数后再把其结果压入栈中。)x|y的形式的属性表示会压入(弹出)x个或者y个元素;其依情况为定;一个问号?表示不知道这个函数会压入(弹出)多少个元素,这取决于它们的参数。(例如依赖了栈内的什么值。)第三个属性x会告知这个函数有没有可能抛出错误:'-'意味着函数不会抛出任何错误;'m'意味着可能会抛出内存溢出的错误;'v'意味着这个函数可能会抛出在下文中解释的错误;'e'意味着这个函数可以运行任意Lua代码,无论是直接执行还是通过调用元函数,因此可能会抛出任何错误。

lua_absindex

[-o, +0, -]

int lua_absindex (lua_State *L, int idx);

将一个可接收索引idx转换到一个等效的绝对索引(即不依赖栈大小来表示)。

lua_Alloc

typedef void * (*lua_Alloc) (void *ud,
                             void *ptr,
                             size_t osize,
                             size_t nsize);

由Lua状态机使用的内存分配函数类型。这个分配函数必须提供像realloc类似的功能,但不用完全一致。参数ud,为lua_newstate所传递而来的一个不透明指针;参数ptr,为需要被分配/再分配/释放的内存;参数osize,为之前分配的内存块的原本大小;以及参数nsize,为新的内存块所需的大小。

当ptr不为NULL时,osize就是ptr所指向的内存块大小,即之前分配或再分配时所给的大小。

当ptr为NULL时,osize为Lua需要分配的对象种类编码。osize的可能为这些值:LUA_TSTRINGLUA_TTABLELUA_TFUNCTIONLUA_TUSERDATA或者LUA_TTHREAD;这些值就是Lua正在创建的新对象的类型。当osize是其他值时,表明Lua在分配其他的东西。

Lua假定所给的分配函数有以下的行为:

当nsize为零时,分配函数必须有类似free的行为且返回NULL。

当nsize非零时,分配函数的行为必须类似realloc。尤其是当其不能满足分配请求时应当返回NULL。

这里是分配函数的一个简单实现。它被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;
  }
  else
    return realloc(ptr, nsize);
}

注意ISO标准下的C会确保free(NULL)是没有影响的且realloc(NULL, size)等效于malloc(size)。

lua_arith

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

void lua_arith (lua_State *L, int op);

对处在栈顶上的两个值(或一个,例如取负操作)做算术或位运算,其中顶部的值是第二个参数,操作会弹出这两个值然后再将结果压入到栈中。函数遵循Lua操作符的相关语法(即有可能会调用元函数)。

参数op的值必须为以下情况之一:

  • LUA_OPADD: 加法
  • LUA_OPSUB: 减法
  • LUA_OPMUL: 乘法
  • LUA_OPDIV: 小数除法
  • LUA_OPIDIV: 整除
  • LUA_OPMOD: 取余
  • LUA_OPPOW: 幂运算(^)
  • LUA_OPUNM: 取负(一元操作 -)
  • LUA_OPBNOT: 按位否(~)
  • LUA_OPBAND: 按位与(&)
  • LUA_OPBOR: 按位或 (|)
  • LUA_OPBXOR: 按位异或 (~)
  • LUA_OPSHL: 左移(<<)
  • LUA_OPSHR: 右移(>>)

lua_atpanic

[-0, +0, e]

lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf);

返回旧的并设置新的panic函数。

lua_call

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

void lua_call (lua_State *L, int nargs, int nresults);

调用函数。和一般的Lua调用异常,lua_call也遵循__call元方法。所以这里“函数”一词意思是任何可被调用的值。

你必须通过这样的协议来完成调用:首先将被调用函数压入栈;接着按序压入调用参数,即第一个参数首先被压入栈。最终你调用了lua_call;nargs为你压入栈的参数数量。当函数返回时,之前所有被压入栈的函数以及参数列表都被弹出了,所有的调用结果都被压入栈中。结果数量取决于nresults,除非nresults为LUA_MULTRET。在这种情况下,来自函数的所有结果都被压入栈;Lua会保证栈空间足以容纳返回值,但不会确保任何额外的栈空间。函数结果也是按序压入栈中的(第一个结果会首先入栈),所以在调用完成后最后一个结果会在栈顶上。

调用及运行过程中发生的任何错误都会向上传播(通过longjmp)。

下面的示例展示了在宿主程序中如何得到这段Lua代码的效果:

a = f("how", t.x, 14)

这是C代码:

lua_getglobal(L, "f");      /* 要调用的函数 */
lua_pushliteral(L, "how");  /* 第一个调用参数 */
lua_getglobal(L, "t");      /* 索引这个表 */
lua_getfield(L, -1, "x");   /* 压入't.x'的结果并作为第二个调用参数 */
lua_remove(L, -2);          /* 将't'移出栈 */
lua_pushinteger(L, 14);     /*第三个调用参数 */
lua_call(L, 3, 1);          /* 调用'f',其中包含三个参数和一个结果值 */
lua_setglobal(L, "a");      /* 设为全局变量'a' */

注意以上代码是平衡的 balanced:即在执行的最后,栈回到了一开始的状态。这被认为是一种良好的编程实现。

lua_callk

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

void lua_callk (lua_State *L,
                int nargs,
                int nresults,
                lua_KContext ctx,
                lua_KFunction k);

此函数的行为完全类似于lua_call,但是允许被调用的函数让出(参见4.5)。

lua_CFunction

typedef int (*lua_CFunction) (lua_State *L);

C函数的类型。

为了和Lua合理交流,C函数必须遵循一些约定,其必须定义传递参数和结果的方法:C函数按序从Lua的栈中接收参数(第一个参数首先入栈)。所以当函数开始时,会通过lua_gettop(L)函数来接收一定量的参数。第一个参数(如果有的话)位于索引1且最后一个参数位于lua_gettop(L)。为了给Lua返回值,C函数只需要把它们按序压入栈(第一个结果首先压入),然后在C代码中返回结果的数量。在栈中其他位于结果下边的值都会被Lua直接抛弃。和Lua函数类似,由Lua调用的C函数也可以反返回多个结果。

作为示例,以下函数接收一定量的数字参数并且返回它们的平均值与和。

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_checkstack

[-0, +0, -]

int lua_checkstack (lua_State *L, int n);

确保栈中最少有n个额外的空间,即你可以安全地压入n个值。如果不能满足要求它会返回false,要么是因为其所要求的大小大于一个固定的最大值(通常至少允许有几千个元素),要么是因为它不能为额外的空间分配内存了。这个函数永远不会收缩栈;如果栈已经有足够的空间容纳额外元素了,那么它会保持不变。

lua_close

[-0, +0, -]

void lua_close (lua_State *L);

在Lua主线程中关闭所有待关闭变量,在给定的Lua状态机上释放所有变量(如果有的话,调用相关的GC元函数),然后释放由这个状态所使用的所有动态内存。

在一些平台上,你也许不需要调用此函数,因为在宿主程序结束的时候会自然释放所有的资源。另一方面,而创建了多个状态机的长时间运行的程序,例如守护精华曾或者Web服务,可能要在不需要的时候晶块关闭状态机。

lua_closeslot

[-0, +0, e]

void lua_closeslot (lua_State *L, int index);

关闭所给索引处的待关闭插槽并设置其值为nil。索引必须是之前被标记为要关闭(参见lua_toclose)的最后一个,但其仍然存活(即尚未关闭)。

通过此函数调用时的__close元函数不可以让出。

(此函数于版本5.4.3引入。)

lua_closethread

[-0, +?, -]

int lua_closethread (lua_State *L, lua_State *from);

重置一个Lua线程,清理器调用栈并关闭所有的待关闭变量。最后返回一个状态码:LUA_OK表示在这个Lua线程中没有错误(要么是因为线程已经停止了或者在关闭函数中本身发生的错误),或者一个其他的错误状态码。在发生错误的情况下,会将错误对象保留在栈顶。

参数from表示正在重置L的协程。如果没有这个协程,此参数可以为NULL。

(此函数于版本5.4.6引入。)

lua_compare

[-0, +0, e]

int lua_compare (lua_State *L, int index1, int index2, int op);

比较两个Lua值。处于index1的值和index2的值比较后能满足op的话就会返回1,否则返回0;并且遵循Lua操作符的相关语法约定(即可能会调用元表)。任何无效索引也会导致返回0。

op值必须为以下常量:

  • LUA_OPEQ:比较是否相等(==)
  • LUA_OPLT:比较是否小于(<)
  • LUA_OPLE:比较是否小于等于(<=)

lua_concat

[-n, +1, e]

void lua_concat (lua_State *L, int n);

连接在栈顶上的n个值,弹出这些值并将结果放到栈顶上。如果n为1,则结果就是在栈中的单一值(即此函数什么都不会干);如果n为0,则结果为空字符串。连接操作遵循之前提到的一般Lua语法(参见3.4.6)。

lua_copy

[-0, +0, -]

void lua_copy (lua_State *L, int fromidx, int toidx);

拷贝fromidx索引处的值到有效索引toidx处的值,并替换掉这个位置上的值。其他位置上的值不受影响。

lua_createtable

[-0, +1, m]

void lua_createtable (lua_State *L, int narr, int nrec);

创建一个空表并压入栈中。参数narr指出表将会是个序列且有多少个元素;参数nrec指出此表将会有多少个元素。Lua可能会使用这些指示值来提前分配内存给新表。当你提前知道表中会有多少个元素时,提前分配内存有助于性能提升。否则你可以使用lua_newtable函数。

lua_dump

[-0, +0, -]

int lua_dump (lua_State *L,
                        lua_Writer writer,
                        void *data,
                        int strip);

将Lua函数转存为一个二进制块。从栈顶上接收一个函数并产生其二进制块,如果之后加载这个块,那么会产生一个转存的函数相等效的函数。每产出一部分二进制块,lua_dump都会调用writer(参见lua_Writer)并使用所给的data参数指向的缓冲块来写入。

如果strip为true,则表示二进制块可能不会包含所有与函数相关的调试信息,以节省存储空间。

返回值时最后一次调用writer所返回的错误码;0表示无错误。

此接口不会弹出在栈上的Lua函数。

lua_error

[-1, +0, v]

int lua_error (lua_State *L);

抛出一个Lua错误,使用栈顶上的值作为错误对象。此函数会发生long jump,因此永不返回(参见luaL_error)。

lua_gc

[-0, +0, -]

int lua_gc (lua_State *L, int what, ...);

GC行为控制。

此函数会执行一些任务,任务通过参数what的值来指定。下面列出了各选项以及对应所需的额外函数。

  • LUA_GCCOLLECT:执行一次完整的GC循环。
  • LUA_GCSTOP:停止GC。
  • LUA_GCRESTART:重启GC。
  • LUA_GCCOUNT:返回Lua当前使用的内存量(单位Kbytes)。
  • LUA_GCSTEP(int stepsize)::执行步进GC的一小步,步长为stepsize(Kbytes)。
  • LUA_GCISRUNNING:返回一个布尔值以表明收集器是否在运行中。
  • LUA_GCINC (int pause, int stepmul, stepsize):根据所给参数将GC变为步进模式(参见2.5.1)。并返回之前使用的模式(LUA_GCGEN 或是 LUA_GCINC)。
  • LUA_GCGEN (int minormul, int majormul):根据所给参数将GC变为代际模式(参见2.5.1)。并返回之前使用的模式(LUA_GCGEN 或是 LUA_GCINC)。

更多细节请参见collectgarbage

此函数不应当在终结器中调用。

lua_getallocf

[-0, +0, -]

lua_Alloc lua_getallocf (lua_State *L, void **ud);

返回所给状态机使用的内存分配函数。当ud不为NULL时,Lua会将对应状态机所使用的内存分配函数存到*ud中。

lua_getfield

[-0, +1, e]

int lua_getfield (lua_State *L, int index, const char *k);

将t[k]的值压入栈中,此处t为给出的索引所处的值。和在Lua代码中一样,此函数可能会触发"index"事件的元函数(参见2.4)。

返回值为压入栈中的值的类型。

lua_getextraspace

[-0, +0, -]

void *lua_getextraspace (lua_State *L);

返回指向所给Lua状态机中一块原始内存区域的指针。程序将这块区域用于任何目的,Lua不会将其用在任何事上。

每个Lua线程都有这块区域,其由Lua主线程的区域拷贝而来。

默认情况下,这块内存的大小为void指针的大小,不过你可以使用不同的大小来重新编译Lua(参见 luaconf.h 中的 LUA_EXTRASPACE)。

lua_getglobal

[-0, +1, e]

int lua_getglobal (lua_State *L, const char *name);

将名为name的全局变量的值压入栈中。返回值为这个全局变量的类型。

lua_geti

[-0, +1, e]

int lua_geti (lua_State *L, int index, lua_Integer i);

将t[i]的值压入栈中,这里的t为给出索引的值。和在Lua代码中一样,此函数可能会触发"index"事件的元函数(参见2.4)。

lua_getmetatable

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

int lua_getmetatable (lua_State *L, int index);

如果给出索引的值中含有元表,那么此函数会将其元表压入栈中然后返回1。否则,此函数返回且不会向栈中压入任何值。

lua_gettable

[-1, +1, e]

int lua_gettable (lua_State *L, int index);

将t[k]的值压入栈中,这里的t为给出索引的值,k为栈顶上的值。

此函数从栈中弹出键,再将结果压入同样的位置。和在Lua中一样,此函数可能会触发"index"事件的元函数(参见2.4)。

lua_gettop

[-0, +0, -]

int lua_gettop (lua_State *L);

返回栈顶元素的索引。因为索引是从1开始的,所以这里的结果就是栈中元素的数量;特殊情况下是0,表明栈是空的。

lua_getiuservalue

[-0, +1, -]

int lua_getiuservalue (lua_State *L, int index, int n);

将给出索引处的 full userdata 关联的第n个user值压入栈中,并且返回此值的类型。

如果这个 userdata 没有这个值,则压入nil并且返回LUA_TNONE

lua_insert

[-1, +1, -]

void lua_insert (lua_State *L, int index);

将栈顶元素移动到有效的index处,上移其上边的元素到空出来的位置。此函数不可以使用虚拟索引,因为虚拟索引并不真实存在于栈上。

lua_Integer

typedef ... lua_Integer;

Lua中的整数类型。

此类型默认为 long long,(通常是一个64位整数),但是其可以改为 long 或者 int (通常是一个32位整数)。(参见 luaconf.h 中的 LUA_INT_TYPE 。)

Lua还定义了常量 LUA_MININTEGER 和 LUA_MAXINTEGER,用于表示此类型的最大值和最小值。

lua_isboolean

[-0, +0, -]

int lua_isboolean (lua_State *L, int index);

当给出索引的值为布尔类型时则返回1,否则返回0。

lua_iscfunction

[-0, +0, -]

int lua_iscfunction (lua_State *L, int index);

当给出索引的值为C函数类型时则返回1,否则返回0。

lua_isinteger

[-0, +0, -]

int lua_isinteger (lua_State *L, int index);

当给出索引的值为整数类型时则返回1,否则返回0。

lua_islightuserdata

[-0, +0, -]

int lua_islightuserdata (lua_State *L, int index);

当给出索引的值为 light userdata 时则返回1,否则返回0。

lua_isnil

[-0, +0, -]

int lua_isnil (lua_State *L, int index);

当给出索引的值为nil时则返回1,否则返回0。

lua_isnone

[-0, +0, -]

int lua_isnone (lua_State *L, int index);

当给出索引的值无效时则返回1,否则返回0。

lua_isnoneornil

[-0, +0, -]

int lua_isnoneornil (lua_State *L, int index);

当给出索引的值无效或为nil时则返回1,否则返回0。

lua_isnumber

[-0, +0, -]

int lua_isnumber (lua_State *L, int index);

当给出索引的值为数字类型或是可转换为数字的字符串类型时则返回1,否则返回0。

lua_isstring

[-0, +0, -]

int lua_isstring (lua_State *L, int index);

当给出索引的值为字符串类型或数字类型(数字必然可以转换为字符串)时则返回1,否则返回0。

lua_istable

[-0, +0, -]

int lua_istable (lua_State *L, int index);

当给出索引的值为表类型时则返回1,否则返回0。

lua_isthread

[-0, +0, -]

int lua_isthread (lua_State *L, int index);

当给出索引的值为Lua线程(协程)类型时则返回1,否则返回0。

lua_isuserdata

[-0, +0, -]

int lua_isuserdata (lua_State *L, int index);

当给出索引的值为 (full/light) user data 类型时则返回1,否则返回0。

lua_isyieldable

[-0, +0, -]

int lua_isyieldable (lua_State *L);

当所给的协程(即传入的lua_State类型)可以让出时则返回1,否则返回0。

lua_KContext

typedef ... lua_KContext;

延续函数的上下文类型。其必须是一个数字类型。当 intptr_t 可用时,此类型被定义为 intptr_t 类型,所以它也可以存指针。否则,其被定义为 ptrdiff_t 类型。

lua_KFunction

typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx);

延续函数的类型(参见4.5)。

lua_len

[-0, +1, e]

void lua_len (lua_State *L, int index);

返回给出索引的值的长度。其等效于Lua代码中的"#"操作(参见3.4.7),可能会触发"length"事件的元函数(参见2.4)。结果会被压入到栈中。

lua_load

[-0, +1, -]

int lua_load (lua_State *L,
              lua_Reader reader,
              void *data,
              const char *chunkname,
              const char *mode);

加载Lua代码块但不运行。如果没有错误发生,lua_load会将编译后的块作为一个Lua函数压入栈中。否则,它将压入错误对象。

lua_load函数通过用户提供的reader函数来读取代码块(参见lua_Reader)。data参数是一个要传递给reader的不透明值。

参数chunkname是所给代码块的名称,会被用在错误消息和调试信息中(参见4.7)。

lua_load会自动检测传入的代码块是文本还是二进制格式然后用合适的方式加载它(参见 luac 程序)。字符串参数mode的工作方式和load函数中的相同,另有不同的是,当mode值为NULL时等效于字符串"bt"。

lua_load内部也使用了栈,所以reader函数在返回是必须使得栈保持不变。

lua_load可能返回 LUA_OK、 LUA_ERRSYNTAX 或是 LUA_ERRMEM (。此函数也可能返回与read函数所产生错误相关的值。参见4.4.1

如果加载得到的函数有上值,那么第一个上值就会设在全局环境中的值中,全局环境被存在注册表中的 LUA_RIDX_GLOBALS 索引处(参见4.3)。加载主代码块时,这个上值会是变量 _ENV(参见2.2)。其他上值会被初始化为nil

lua_newstate

[-0, +0, -]

lua_State *lua_newstate (lua_Alloc f, void *ud);

创建一个独立的Lua状态机并返回其Lua主协程。如果不能创建状态机(可能是因为内存不足)时返回 NULL 。参数 f 为内存分配函数;Lua在这个状态机中的所有内存分配都将使用这个函数(参见lua_Alloc)。第二个参数 ud 是一个不透明指针,Lua每次调用内存分配时都会将其传入。

lua_newtable

[-0, +1, m]

void lua_newtable (lua_State *L);

创建一个表并将其压入栈中。等效于 lua_createtable(L, 0, 0) 。

lua_newthread

[-0, +1, m]

lua_State *lua_newthread (lua_State *L);

创建一个Lua线程,并将其压入栈中,然后返回一个代表这个新Lua线程的lua_State指针。所返回的新Lua线程与传递进来的原线程共享同一个全局环境,但是有独立执行栈。

Lua线程和其他Lua对象一样,也受GC的管理。

lua_newuserdatauv

[-0, +1, m]

void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue);

此函数会创建一个新的 full userdata 并将其压入栈中。其引用了 nuvalue 个Lua值,称为 user value,加上引用了一块 size 字节的原始内存。(这些 user value 可以通过lua_setiuservaluelua_getiuservalue函数来设置或读取。)

此函数返回关联内存块的地址。Lua会确保在 userdata 活跃期间其关联的内存块不会失效(参见2.5)。此外,如果 userdata 被标记为可终结的(参见2.5.3),那么这个地址会被保留到调用终结器为止。

lua_next

[-0, +(2|0), v]

int lua_next (lua_State *L, int index);

从栈中弹出一个键,然后从给出索引处的table中取出一对键值压入栈中,这个键值对就在之前弹出的键后边的位置。如果表中没有更多的元素,那么lua_next返回0并且不会压栈。

通常表的遍历会像这样:

/* 表在栈中't'索引的位置 */
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混淆在一起。

当给的键既不是nil也不存在于表中时,此函数可能会抛出错误。与在遍历过程中更改表相关的注意事项请参见next函数。

lua_Number

typedef ... lua_Number;

Lua中的浮点数类型。

默认为 double 类型,但是你可以改为 float 或 long double 。(参见 luaconf.h 中的 LUA_FLOAT_TYPE 。)

lua_numbertointeger

int lua_numbertointeger (lua_Number n, lua_Integer *p);

常见将Lua浮点数转换为Lua整数;浮点数 n 一定有整数值。如果这个整数值在Lua整数的取值范围内,那么会将此浮点数转换为一个整数并赋值给 *p 。这个宏的结果是个布尔值,以表示转换是否成功。(要知道,由于四舍五入,没有这个宏很难正确执行范围测试。)

此宏可能会多次计算其参数。

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_OKLUA_ERRRUNLUA_ERRMEM或者LUA_ERRERR

lua_pcallk

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

int lua_pcallk (lua_State *L,
                int nargs,
                int nresults,
                int msgh,
                lua_KContext ctx,
                lua_KFunction k);

此函数的行为类似于lua_pcall,不同在于它允许调用的函数让出(参见4.5)。

lua_pop

[-n, +0, e]

void lua_pop (lua_State *L, int n);

从栈上弹出 n 个元素。其实现为一个用宏包装起来的 lua_settop 调用。

lua_pushboolean

[-0, +1, -]

void lua_pushboolean (lua_State *L, int b);

将一个布尔值 b 压入栈中。

lua_pushcclosure

[-n, +1, m]

void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);

将一个C闭包压入栈中。此函数接收一个指向C函数的指针并将其作为 function 类型的Lua值压入栈中,当这个值被调用的时候会触发相关的C函数。参数 n 表示这个函数将会有多少个上值(参见4.2)。

任何可以被Lua调用的函数都必须正确遵循传参和返回的协议(参见lua_CFunction)。

当一个C函数被创建,它可能会关联一些值,所以被称为上值 upvalues ;这些上值可以在调用期间被函数访问。这种关联关系被称为C闭包(参见4.2)。要创建一个C闭包,首先就必须将上值的初始值压入栈中。(当存在多个上值得时候,第一个上值首先压入,以此类推。)然后调用lua_pushcclosure来创建C函数并压入栈中,其中参数 n 指出了函数有多少个关联的值。lua_pushcclosure会将这些值从栈上弹出。

n 的最大值为255。

当 n 为零时,此函数创建的是一个轻量级C函数 light C function ,其只是一个C函数指针。这种情况下永远不会抛出内存错误。

lua_pushcfunction

[-0, +1, -]

void lua_pushcfunction (lua_State *L, lua_CFunction f);

将一个C函数压入栈中。此函数等效于不带上值的lua_pushcclosure调用。

lua_pushfstring

[-0, +1, v]

const char *lua_pushfstring (lua_State *L, const char *fmt, ...);

将一个格式化字符串压入到栈中并返回这个字符串的指针(参见4.1.3)。其和ISO标准下的C函数 sprintf 类似,但是有两点不同之处。第一点,你无需为结果分配空间;这个结果是一个Lua字符串所以会由Lua来管理它的内存分配(以及通过GC释放内存)。第二点,其转义符有很多限制。不支持标志、宽度或是精度。转义符只支持'%%'(插入一个百分号字符)、'%s'(插入一个C风格字符串,即以0为结尾,不限制大小)、'%f'(插入一个lua_Number)、'%I'(插入一个lua_Integer)、'%p'(插入一个指针值)、'%d'(插入一个 int )、'%c'(插入一个由 int 值表示编码值的字符)、以及'%U'(插入一个 long int 作为一个UTF-8的字节序)。

此函数会在内存溢出或者发生了无效转义的时候抛出错误。

lua_pushglobaltable

[-0, +1, -]

void lua_pushglobaltable (lua_State *L);

将全局环境压入栈中。

lua_pushinteger

[-0, +1, -]

void lua_pushinteger (lua_State *L, lua_Integer n);

压入一个值为 n 的整数到栈中。

lua_pushlightuserdata

[-0, +1, -]

void lua_pushlightuserdata (lua_State *L, void *p);

压入一个 light userdata 到栈中。

userdata 在Lua中表示一个C值。一个 light userdata 表示一个 *void 类型的指针。它是个值(和 number 一样):你不需要专门创建它,它没有独立的元表并且不会被GC清理(因为它没有经历创建的过程)。任何 light userdata 之间,只要它们指向相同的C地址,那么它们就是相等的。

lua_pushliteral

[-0, +1, m]

const char *lua_pushliteral (lua_State *L, const char *s);

这个宏等效于lua_pushstring,但只应该在 s 是一个字面量时使用。

lua_pushlstring

[-0, +1, m]

const char *lua_pushlstring (lua_State *L, const char *s, size_t len);

将一个指向长度为 len 的字符串的指针 s 压入到栈中。Lua将新建或重用所给字符串的拷贝,所以 s 所指向的内存可以在函数返回后立刻释放或者继续复用。这个字符串可以包含任何二进制数据,包括嵌入的零值。

返回一个指向此字符串的内部拷贝的指针(参见4.1.3)。

lua_pushnil

[-0, +1, -]

void lua_pushnil (lua_State *L);

将压入一个nil值到栈中。

lua_pushnumber

[-0, +1, -]

void lua_pushnumber (lua_State *L, lua_Number n);

压入一个值为 n 的浮点数到栈中。

lua_pushstring

[-0, +1, m]

const char *lua_pushstring (lua_State *L, const char *s);

将一个以零为终止符的字符串指针 s 压入到栈中。Lua将新建或重用所给字符串的拷贝,所以 s 所指向的内存可以在函数返回后立刻释放或者继续复用。

返回一个指向此字符串的内部拷贝的指针(参见4.13)。

如果 s 为 NULL ,则会将nil压入栈中并返回 NULL 。

lua_pushthread

[-0, +1, -]

int lua_pushthread (lua_State *L);

将 L 所表示的Lua线程压入到栈中。如果这个状态机的Lua线程是主线程则会返回1。

lua_pushvalue

[-0, +1, -]

void lua_pushvalue (lua_State *L, int index);

拷贝给出索引处的值,并将其压入栈中。

lua_pushvfstring

[-0, +1, v]

const char *lua_pushvfstring (lua_State *L,
                              const char *fmt,
                              va_list argp);

等效于lua_pushfstring,不同在于它最后一个参数类型是 va_list 而不是可变参。

lua_rawequal

[-0, +0, -]

int lua_rawequal (lua_State *L, int index1, int index2);

当两个值直接相等时(即用不着调用元函数 __eq 来判断)会返回1。否则返回0。如果两者之间有任何的非法索引也会返回0。

lua_rawget

[-1, +1, -]

int lua_rawget (lua_State *L, int index);

lua_gettable类似,但是执行的是直接访问(即不通过元函数访问)。索引处的值必须是个表。

lua_rawgeti

[-0, +1, -]

int lua_rawgeti (lua_State *L, int index, lua_Integer n);

将 t[n] 的值压入栈中,这里的 t 为给出索引处的表。这也是直接访问的,即不使用元值 ___index 。

返回值为这个值的类型。

lua_rawgetp

[-0, +1, -]

int lua_rawgetp (lua_State *L, int index, const void *p);

将 t[k] 的值压入栈中,这里的 t 为给出索引处的表,且 k 为表示指针 p 的一个 light userdata 。这也是直接访问的,即不使用元值 ___index 。

返回值为这个值的类型。

lua_rawlen

[-0, +0, -]

lua_Unsigned lua_rawlen (lua_State *L, int index);

返回索引处值的“直接长度”:对于字符串,就是其字符串长度;对于表,就是执行取长操作符('#')的结果,但是不会调用元函数;对于 userdata,就是 userdata 关联的内存块大小。对于其他的值,此调用都返回0。

lua_rawset

[-2, +0, m]

void lua_rawset (lua_State *L, int index);

类似于lua_settable,但执行的是直接赋值(即不调用元函数)。索引处的值必须是一个表。

lua_rawseti

[-1, +0, m]

void lua_rawseti (lua_State *L, int index, lua_Integer i);

等效于执行 t[i] = v ,这里的 t 是索引处的表,v 是放在栈顶上的值。

此函数将值从栈上弹出。此处是直接赋值,即不会调用元值 __newindex 。

lua_rawsetp

[-1, +0, m]

void lua_rawsetp (lua_State *L, int index, const void *p);

等效于执行 t[p] = v ,这里的 t 是索引处的表,p 是转换后的 light userdata,且 v 是处于栈顶上的值。

此函数将值从栈上弹出。此处是直接赋值,即不会调用元值 __newindex 。

lua_Reader

typedef const char * (*lua_Reader) (lua_State *L,
                                    void *data,
                                    size_t *size);

用于lua_load的 reader 函数。每当lua_load需要代码块中的下一小块时,其会调用 reader,并接着传递参数 data 。这个 reader 必须返回指向代码块的新部分的指针,并且将 *size 设为这块内存的大小。这块内存必须直到下一次调用 reader 前都存在。为了表示代码块结束了,reader 必须返回 NULL 并将 *size 设为零。reader函数可以返回任意长度大于0的块。

lua_register

[-0, +0, e]

void lua_register (lua_State *L, const char *name, lua_CFunction f);

将全局变量 name 的值设为给出的C函数。这个宏是这样定义的:

#define lua_register(L,n,f) \
        (lua_pushcfunction(L, f), lua_setglobal(L, n))

lua_remove

[-1, +0, -]

void lua_remove (lua_State *L, int index);

从栈上移除给出的有效索引的元素,空出来的位置由上边的元素依次下移填充。此函数不可以用虚拟索引调用,因为虚拟索引并不实际存在于栈上。

lua_replace

[-1, +0, -]

void lua_replace (lua_State *L, int index);

将栈顶的值移动到给出索引的位置(原文为 moves,但其实这个栈顶的元素还在),但是不下移任何元素(于是就替换了索引处的值),然后弹出栈顶的值。

lua_resetthread

[-0, +?, -]

int lua_resetthread (lua_State *L);

此函数已废弃;其等效于 from 参数为 NULL 的lua_closethread调用。

lua_resume

[-?, +?, -]

int lua_resume (lua_State *L, lua_State *from, int nargs,
                          int *nresults);

开始或重启给定的Lua线程所指的协程 L 。(本质上是一回事)

如需启动一个协程,你必须将主函数和其所有参数都压入到这个Lua线程的空栈中。然后你再调用lua_resume,其参数 nargs 为主函数的参数数量。此调用会在协程执行完成或者被挂起时返回。当它返回时,由lua_yield或主函数返回的值会被放到栈顶上,*nresult 会变更为这些返回值的数量。当协程让出时lua_resume会返回LUA_YIELD,如果协程执行完成并未发生任何错误则返回LUA_OK。在发生错误的情况下,错误对象会被放在栈顶中。

如需重启一个协程,你必须把在栈上的 *nresults 个让出返回值移除,将结果值压入栈中以传递给让出的地方,然后再调用lua_resume

参数 from 表示执行重启的协程 L 的主体,其也是一个协程。如果不存在这样的协程,此参数可以是 NULL。

lua_rotate

[-0, +0, -]

void lua_rotate (lua_State *L, int idx, int n);

旋转/回绕给定索引处到栈顶之间的元素。当 n 是正数时,这些元素会向顶部方向旋转 n 个位置;如果是一个负值 -n ,那么会向栈底方向旋转 n 个位置。n 的绝对值不能大于需要旋转的元素数量。不可以使用虚拟索引来调用这个函数,因为虚拟函数实际上并不存在于栈上。

lua_setallocf

[-0, +0, -]

void lua_setallocf (lua_State *L, lua_Alloc f, void *ud);

将内存分配函数改为 f 并带上用户数据 ud。

lua_setfield

[-1, +0, e]

void lua_setfield (lua_State *L, int index, const char *k);

等效于执行 t[k] = v,这里的 t 为给定索引处的表,v 为栈顶上的值。

此函数会从栈上弹出这个值。和在Lua代码中一样,此函数可能会触发"newindex"事件对应的元函数(参见2.4)。

lua_setglobal

[-1, +0, e]

void lua_setglobal (lua_State *L, const char *name);

从栈上弹出一个值并将其赋值到全局变量 name 中。

lua_seti

[-1, +0, e]

void lua_seti (lua_State *L, int index, lua_Integer n);

等效于执行 t[n] = v,这里的 t 为给定索引处的表,v 为栈顶上的值。

此函数会从栈上弹出这个值。和在Lua代码中一样,此函数可能会触发"newindex"事件对应的元函数(参见2.4)。

lua_setiuservalue

[-1, +0, -]

int lua_setiuservalue (lua_State *L, int index, int n);

从栈上弹出一个值,将这个值设为给出索引处的 full userdata 的第 n 个 user value。当 userdata 中没有这个值时会返回0。

lua_setmetatable

[-1, +0, -]

int lua_setmetatable (lua_State *L, int index);

从栈上弹出一个表或者nil,并将其设置为给出索引处的值的新元表。(nil意味着没有元表。)

(因为一些历史原因,此函数返回值类型为 int ,现在返回值已经无任何指示意义,总是会返回1。)

lua_settable

[-2, +0, e]

void lua_settable (lua_State *L, int index);

等效于执行 t[k] = v ,这里的 t 为给出索引处的值,v 是栈顶上的值,k 是紧跟在栈顶下边位置上的值。

此函数会从栈上弹出键和键值。和Lua代码中一样,此函数可能会触发 "newindex" 事件对应的元函数。

lua_settop

[-?, +?, e]

void lua_settop (lua_State *L, int index);

接受一个任意索引或者0,然后将栈顶增长或下降到这个索引的位置,即扩张或收缩栈的大小。如果新的栈顶位置大于原来的,那么新的元素都使用nil值填充。如果给出索引为0,那么所有栈元素都被移除。

此函数在移除的索引处为待关闭变量时可能会运行任意的代码(因为可能触发 __close 元函数)。

lua_setwarnf

[-0, +0, -]

void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud);

设置供Lua发出警告时使用的警告函数(参见lua_WarnFunction)。参数 ud 为每次调用此函数时需要传递给警告函数的特定数据。

lua_State

typedef struct lua_State lua_State;

该结构体并不透明,它指向一个Lua线程及(通过Lua线程)间接指向Lua解释器的一个完整的状态机。Lua库是完全可重入的,就是因为这个结构体没有使用全局变量。所有关于状态机的信息都可以通过这个结构体访问到。

库中的每个函数都需要将一个指向这个结构体的指针作为第一个参数传入,除了lua_newstate,它是从头创建一个Lua状态机。

lua_status

[-0, +0, -]

int lua_status (lua_State *L);

返回给出的Lua线程 L 的状态。

这个状态可以是表示正常的LUA_OK、由lua_resume唤醒的Lua线程在执行时发生错误后的错误码、或是表示Lua线程已经被挂起的LUA_YIELD

你只能在LUA_OK状态中的线程中调用这个函数。你可以唤醒处在LUA_OK状态(会启动新协程)或LUA_YIELD状态(会唤醒协程)下的线程。

lua_stringtonumber

[-0, +1, -]

size_t lua_stringtonumber (lua_State *L, const char *s);

将以零为终止符的字符串 s 转换为一个 number ,并将其压入到栈中,然后返回字符串的完整大小——即长度加一。根据Lua中的词法转换,此转换的结果可能是整数或浮点数(参见3.1)。字符串前边或后边可以有空白字符或符号。如果字符串内容不是一个有效的数字,此函数会返回0且不会做压栈操作。(注意此返回结果可以作为布尔值使用,转换成功即为true。)

lua_toboolean

[-0, +0, -]

int lua_toboolean (lua_State *L, int index);

将给定索引处的Lua值转换为一个C布尔值(0或1)。和Lua中所有的真假值测试类似,lua_toboolean对于任何不同于falsenil的Lua值都返回true;否则返回false。(如果你只想接收Lua布尔值,可以使用lua_isboolean来探测类型。)

lua_tocfunction

[-0, +0, -]

lua_CFunction lua_tocfunction (lua_State *L, int index);

将给定索引处的Lua值转换为一个C函数,此值必须本就是C函数,否则返回NULL。

lua_toclose

[-0, +0, m]

void lua_toclose (lua_State *L, int index);

将在栈中的给定索引标记为待关闭槽位(参见3.3.8)。和Lua中的待关闭变量类似,栈上在该槽中的值将会在其超出作用域后被关闭。在这里的C函数上下文中,超出作用域指的是函数执行返回到Lua中了、或者发生了错误、或者通过lua_settoplua_pop将该槽位从栈上移除了、或者调用了lua_closeslot。被标记位待关闭的槽位不应当使用除了lua_settoplua_pop之外的任何API函数将其移除,除非提前通过lua_closeslot使之都失活了。

传入此函数的索引不应当等于或低于其他的待关闭槽位(原文没有解释原因,从源码看应当是为了保证析构的顺序)。

注意当 __close 元函数运行时,其无论是发生了错误还是正常返回,C调用栈都已经展开了,所以在调用函数时自动声明的C变量(例如某个缓冲区)都将超出作用域。

lua_tointeger

[-0, +0, -]

lua_Integer lua_tointeger (lua_State *L, int index);

等效于 isnum 参数为NULL调用lua_tointegerx

lua_tointegerx

[-0, +0, -]

lua_Integer lua_tointegerx (lua_State *L, int index, int *isnum);

将给定索引处的Lua值转换为有符号整数类型lua_Integer。给出的Lua值必须是整数、或是一个 number 、或是一个内容为可转换为整数的数字字符串(参见3.4.3);否则lua_tointegerx返回0。

如果参数 isnum 不为NULL,则会被赋值为一个布尔值以表明转换是否成功。

lua_tolstring

[-0, +0, m]

const char *lua_tolstring (lua_State *L, int index, size_t *len);

将给定索引处的字符串转换为C字符串。如果 len 为NULL,其会将 *len 设为字符串长度。给出索引处的值必须是字符串或 number ,否则此函数返回NULL。如果是 number ,lua_tolstring将栈上的实际值改为字符串。(通过lua_next遍历表时对键使用lua_tolstring时会因为这里的改变而混淆遍历。)

lua_tolstring返回值为在Lua状态机内部的一个字符串指针。此字符串总是以零('\0')结尾(即C风格字符串),但是可以在其正文中包含其他的零。

lua_tonumber

[-0, +0, -]

lua_Number lua_tonumber (lua_State *L, int index);

等效于 isnum 参数为NULL调用lua_tonumberx

lua_tonumberx

[-0, +0, -]

lua_Number lua_tonumberx (lua_State *L, int index, int *isnum);

将给定索引处的Lua值转换为C类型lua_Number。给出的Lua值必须为一个 number 或一个可转换为number的字符串(参见3.4.3);否则lua_tonumberx返回0。

如果参数 isnum 不为NULL,则会被赋值为一个布尔值以表明操作是否成功。

lua_topointer

[-0, +0, -]

const void *lua_topointer (lua_State *L, int index);

将给定索引处的Lua值转换为一个通用C指针(void*)。给出的值必须为 userdata、表、Lua线程、字符串或函数中的一种;否则lua_topointer将返回 NULL。不同的对象会得到不同的指针。并且无法将指针转换为原始值。

通常此函数只用于哈希和调试信息中。

lua_tostring

[-0, +0, m]

const char *lua_tostring (lua_State *L, int index);

等效于 len 参数为NULL时调用lua_tolstring

lua_tothread

[-0, +0, -]

lua_State *lua_tothread (lua_State *L, int index);

将给定索引处的值转换为Lua线程(用 lua_State* 类型表示)。给出的值必须为一个Lua线程;否则此函数返回NULL。

lua_touserdata

[-0, +0, -]

void *lua_touserdata (lua_State *L, int index);

如果给出索引处的值为一个 full userdata,则返回其内存块的地址。如果这个值为一个 light userdata,则直接返回值(light userdata 本就是个指针)。否则返回NULL。

lua_type

[-0, +0, -]

int lua_type (lua_State *L, int index);

返回给定索引处值的类型,或者返回 LUA_TNONE 表示无效但可接受的索引。由lua_type返回的类型在源代码 lua.h 文件中定义:LUA_TNIL、LUA_TNUMBER、LUA_TBOOLEAN、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA、LUA_TTHREAD、以及 LUA_TLIGHTUSERDATA 。

lua_typename

[-0, +0, -]

const char *lua_typename (lua_State *L, int tp);

返回类型值 tp 对应的类型名,其必须是可以由lua_type返回的值。

lua_Unsigned

typedef ... lua_Unsigned;

lua_Integer的无符号版本。

lua_upvalueindex

[-0, +0, -]

int lua_upvalueindex (int i);

返回此刻在运行的函数的第 i 个上值的虚拟索引(参见4.2)。i 必须在区间[1, 256]内。

lua_version

[-0, +0, -]

lua_Number lua_version (lua_State *L);

返回Lua版本。

lua_WarnFunction

typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont);

警告函数的类型,由Lua发出警告时调用。第一个参数是由lua_setwarnf设置的一个不透明指针。第二个参数是警告消息。第三个参数是个布尔值,用以表示是否会在下一次调用时继续重复这个警告消息。

关于警告的更多细节请参见warn

lua_Writer

typedef int (*lua_Writer) (lua_State *L,
                           const void* p,
                           size_t sz,
                           void* ud);

lua_dump使用的写入函数。每次lua_dump需要读到代码块中的下一部分时都会调用它,调用时传入写入缓冲区(p)、大小(sz)以及提供给lua_dump的参数(ud)。

此函数会返回一个错误码:0意味着没有错误;其他的值意味着有错误且在下次调用前停止lua_dump

lua_xmove

[-?, +?, -]

void lua_xmove (lua_State *from, lua_State *to, int n);

在同一个状态机中的不同Lua线程间交换值。

此函数会从 from 的栈上弹出n个值,并将其压入到 to 的栈中。

lua_yield

[-?, +?, v]

int lua_yield (lua_State *L, int nresults);

此函数等效于lua_yieldk,但是它没有延续函数(参见4.5)。因此,当唤醒Lua线程时,其会从之前调用[lua_yield]的地方开始执行。为避免意外,此函数应当在尾调用中使用。

lua_yieldk

[-?, +?, v]

int lua_yieldk (lua_State *L,
                int nresults,
                lua_KContext ctx,
                lua_KFunction k);

让出一个协程(Lua线程)。

当C函数中调用lua_yieldk时,正在运行的协程会被挂起,通过调用lua_resume可以启动这个协程并继续返回。参数 nresults 表示当使用 lua_resume唤醒是将会从栈上返回多少个值。

当协程被再次唤醒,Lua会调用给出的延续函数 k 以在C函数中让出后继续执行(参见4.5)。这个延续函数会接收和之前的函数相同的栈,并将 n 个结果替换传递给 lua_resume。此外,延续函数还会接收由lua_yieldk传递的值 ctx 。

通常这个函数不会返回;当协程最终被恢复时,它会继续执行延续函数。然而有一种特殊情况:当其在行内或计数 hook 中被调用时(参见4.7)。在这种情况下,lua_yieldk应当不使用延续函数(索性使用lua_yield)且无结果返回,hook 也应当在此函数调用后立刻返回。Lua会让出,并当其再次唤醒时将继续执行并触发 hook 的函数。

当其由一个Lua线程中的一个挂起的C调用且没有延续函数时(被称为C调用边界),此函数可能会抛出错误,或者在一个无法在运行时挂起的Lua线程中调用时(通常是Lua主线程)。

4.7 - 调试接口

Lua没有内置的调试工具。取而代之的是提供特殊的接口函数和钩子 hooks。这些接口可用于构建不同的调试器、监控、以及其他需要使用解释器“内部信息”的工具。

lua_Debug

typedef struct lua_Debug {
  int event;
  const char *name;           /* (n) */
  const char *namewhat;       /* (n) */
  const char *what;           /* (S) */
  const char *source;         /* (S) */
  size_t srclen;              /* (S) */
  int currentline;            /* (l) */
  int linedefined;            /* (S) */
  int lastlinedefined;        /* (S) */
  unsigned char nups;         /* (u) 上值的数量 */
  unsigned char nparams;      /* (u) 参数数量 */
  char isvararg;              /* (u) */
  char istailcall;            /* (t) */
  unsigned short ftransfer;   /* (r) 被转移的第一个值的索引 */
  unsigned short ntransfer;   /* (r) 被转移的值的数量 */
  char short_src[LUA_IDSIZE]; /* (S) */
  /* 私有部分 */
  other fields
} lua_Debug;

此结构体用于记录各种有关于函数或运行记录的信息部分。lua_getstack在最近的一次调用中只会填充此结构体的私有部分。如果需要填充lua_Debug的其他字段以得到有用的信息,那么你必须使用一个合适的参数来调用lua_getinfo函数。(具体说来,如果要获取某个字段,你必须要将上边代码块中对应字段后注释中用括号包围的字母传入到lua_getinfo的 what 参数中。)

lua_Debug中的各字段的含义如下:

  • source: 所创建的函数的代码块源码。当 source 由字符 '@' 为首时,则意味着函数定义在 '@' 后跟的文件中。当 source 由字符 '=' 为首时,其后边的内容中对源码的描述是依赖于用户的。否则,该函数就是定义在 source 表示的字符串中的。
  • srclen: 字符串 source 的长度。
  • short_src: 一个“可打印”版本的 source ,用于错误信息中。
  • linedefined: 函数定义起始对应的行号。
  • lastlinedefined: 函数定义末尾对应的行号。
  • what: 当这个字符串内容为"Lua"时表示该函数是个Lua函数,为"C"时则是一个C函数,为"main"时则表示代码块的主体部分。
  • currentline: 表示给定函数的执行到哪一行了。当提供不了有效行数信息的时候,currentline 会被置为 -1。
  • name: 给定函数的合理名称。因为函数在Lua中是一等公民值,所以其没有固定的名称:有些函数是多个全局变量共有的值,其他的可能只是表中的字段。lua_getinfo函数会检查函数的调用方式以找到一个合适的名字。如果没有找到,那么 name 会被置为NULL。
  • namewhat: 用于解释字段 namenamewhat 可以是"global"、"local"、"method"、"field"、"upvalue"、或者""(空字符串),取决于该函数的调用方式。(在似乎没有合适的选择时,Lua会使用空字符串。)
  • istailcall: 如果该函数是由尾调用形式唤起的则为true。在这种情况下,这一层的调用者不在栈中。
  • nups: 该函数的上值数量。
  • nparams: 该函数的参数数量(C函数中始终是0)。
  • isvararg: 该函数是否为可变参数函数(对于C函数来说始终为 true )。
  • ftransfer: 被“转移”的第一个值在栈中的索引,即调用中的参数或返回语句中的返回值。(其他的值在第一个值其后边的连续索引中。)通过这个索引,你就可以使用lua_getlocallua_setlocal来访问或更改这些值。该字段只在调用 hook 期间标记第一个参数、或是返回 hook 中标记第一个返回的值中有意义。(对于调用 hook,该值始终为 1。)
  • ntransfer: 被“转移”(参见上一条)的值的数量。(对于Lua函数的调用,这个值总是等于 nparams。)

lua_gethook

[-0, +0, -]

lua_Hook lua_gethook (lua_State *L);

返回当前函数的 hook。

lua_gethookcount

[-0, +0, -]

int lua_gethookcount (lua_State *L);

返回当前函数的 hook 数量。

lua_gethookmask

[-0, +0, -]

int lua_gethookmask (lua_State *L);

返回当前 hook 的掩码。

lua_getinfo

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

int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);

获取某个函数或某个函数调用的信息。

如需获取某个函数调用的信息,那么参数 ar 必须为一个有效的活动记录,其可以是由在此之前调用[lua_getstack]所填充的,或是作为 hook 的参数给出的(参见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 中的某些特定的字段被设置,或是将某个值压入到栈中。(这些字符在结构体lua_Debug中各字段后的注释中标明了,在其中是由括号包围起来的字符。)各字符选项的含义如下所示:

  • 'f': 将运行在给定层级中的函数压入栈中。
  • 'l': 填充 currentline 字段。
  • 'n': 填充 name、namewhat 字段。
  • 'r': 填充 ftransfer、ntransfer 字段。
  • 'S': 填充 source、short_src、linedefined、lastlinedefined、what 字段。
  • 't': 填充 istailcall 字段。
  • 'u': 填充 nups、nparams、isvararg 字段。
  • 'L': 将一个表压入栈中,该表的索引是在函数上关联的源代码行号,即那些可以打断点的行(不包括空行和注释)。如果该选项与选项 'f' 一起使用,那么再选项 'f' 压完栈之后再将这个表入栈。这是唯一可能抛出内存错误的选项。

该函数返回0则表示 what 中有无效的字符选项,但此时其他的有效字符选项仍然会被处理。

lua_getlocal

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

const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);

获取给定的活动记录或函数中的局部变量或临时值的相关信息。

如果是从一个活动记录中获取信息,那么参数 ar 必须是之前使用lua_getstack填充或是作为 hook 的参数给出(参见lua_Hook)的有效活动记录。索引 n 决定了要查看哪个局部变量;关于局部变量索引和名称的细节,请参见debug.getlocal

lua_getlocal 会将变量的值压入栈中并返回其名称。

如果是从一个函数中获取信息,那么参数 ar 必须为NULL并且将要观察的函数放在栈顶上。这种情况下,只有Lua函数中的参数可见(因为没有关于活动变量的信息)并且不会将任何值压入栈中。

lua_getstack

[-0, +0, -]

int lua_getstack (lua_State *L, int level, lua_Debug *ar);

获取解释器运行时栈的相关信息。

此函数会根据某个活动记录 activation record 的标识来填充结构体lua_Debug的部分字段,这个活动记录来自于执行到给定层级 level 处的函数。当前运行的函数为第0层,而第 n+1 层的函数就是某个通过层层调用,调用了n层才到当前函数(尾调用不计数,其不算在调用栈层数中)。当传入的 level 参数大于当前调用栈的深度时,lua_getstack会返回0;否则返回1。

lua_getupvalue

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

const char *lua_getupvalue (lua_State *L, int funcindex, int n);

获取给定索引处的闭包中的第 n 个上值。此函数会将对应上值的值压入栈中并返回它的名称。当索引 n 超出了实际上的上值数量时会返回 NULL(并且不会压栈)。

更多关于上值的细节请参见debug.getupvalue

lua_Hook

typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);

调试函数 hook 的类型。

每当一个 hook 被调用,其参数 ar 的 event 字段都会被置为触发 hook 的特定事件。Lua使用这些常量来标识各种事件:LUA_HOOKCALL、LUA_HOOKRET、LUA_HOOKTAILCALL、LUA_HOOKLINE、LUA_HOOKCOUNT 。此外,在 line 事件中也会设置 currentline 字段。如要获取 ar 中的其他字段值,则必须调用lua_getinfo

对于 call 事件, event 可以是 LUA_HOOKCALL 表示常规调用;或是 LUA_HOOKTAILCALL 表示尾调用,此时不会有相应的 return 事件。

当Lua运行了一个 hook 时,它将屏蔽掉其他 hook 的调用。因此,当一个 hook 调用回了Lua并执行了某个函数或代码块,此时也不会触发其他的 hook 调用。

hook 函数没有延续函数,即不能在调用 lua_yieldklua_pcallklua_callk 使用非空的参数 k 。

hook 函数可以在这些条件下让出:只有 count 事件和 line 事件中可以让出;为了可以让出,hook 函数必须调用lua_yield完成执行,且参数 nresults 应当等于零(即没有返回值)。

lua_sethook

[-0, +0, -]

void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);

设置调试用的 hook 函数。

参数 f 就是 hook 函数。mask 表示那些事件会触发 hook :其格式为各事件常量的按位或的结果,常量有 LUA_MASKCALL、LUA_MASKRET、LUA_MASKLINE、LUA_MASKCOUNT 。参数 count 只会在 mask 包括 LUA_MASKCOUNT 时才有意义。对于每个事件的 hook 调用的解释如下:

  • call hook: 当解释器调用一个函数时触发。这个 hook 只在Lua进入一个新函数后调用。
  • return hook: 当解释器从一个函数中返回时触发。这个 hook 只在Lua离开函数后调用。
  • line hook: 当解释器开始执行到代码新的一行时触发,或在其跳转回代码中时也会触发(即使是跳转到相同地方的代码)。这个事件只会在执行函数中触发。
  • count hook: 解释器每当执行了 count 条指令后触发。这个事件只会在执行函数中触发。

可以将 mask 置零以禁用 hook 。

lua_setlocal

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

const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);

设置给定的活动记录中的局部变量的值。此函数会将栈顶上的值赋给这个局部变量并返回变量名。同时也会将栈顶上的值弹出。

当给出的索引大于实际上正活跃的变量数量时会返回NULL(而且不会将任何值从栈上弹出)。

参数 ar 和 n 和在lua_getlocal中的相同。

lua_setupvalue

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

const char *lua_setupvalue (lua_State *L, int funcindex, int n);

设置一个闭包的上值。此函数会将栈顶上的值赋给这个上值并返回其名称。同时也会将栈顶上的值弹出。

当给出的索引大于上值数量时会返回NULL(而且不会将任何值从栈上弹出)。

参数 funcindex 和 n 和在lua_getupvalue中的相同。

lua_upvalueid

[-0, +0, -]

void *lua_upvalueid (lua_State *L, int funcindex, int n);

返回给定索引 funcindex 处的闭包中的第 n 个上值的唯一标识。

此唯一标识可以用于区分不同的闭包之间是否共享了同一个上值。对于共享了上值的闭包(即访问了同样的外部局部变量),其返回共享上值的唯一标识也是相同的。

参数 funcindex 和 n 和在lua_getupvalue中的相同,但是 n 不可以大于上值的数量。

lua_upvaluejoin

[-0, +0, -]

void lua_upvaluejoin (lua_State *L, int funcindex1, int n1,
                                    int funcindex2, int n2);

使索引 funcindex1 处的闭包中第 n1 个上值引用索引 funcindex2 处的闭包中第 n2 个上值。即之前提到的闭包之间共享上值。

5 - 辅助库

辅助库 auxiliary library 中提供了一系列方便C代码与Lua交互的函数。虽然基础API已经为C代码和LUA交互行为提供了所有的基础函数,但是辅助库可以为组合任务提供更高级的函数。

辅助库中所有的函数和类型都定义在头文件 lauxlib.h 中,并且名称中都带有前缀“luaL_”。

所有辅助库中的函数都是基于基础API封装的,因此它无法提供API以外的功能。尽管如此,使用辅助库仍然会使得你的代码更加健壮。

辅助库中的一些函数会使用栈中的额外位置。当辅助库某个函数使用的位置少于五个,它将不会检查栈大小;它只是简单地假设栈空间足够。

辅助库中有一些函数会用于检查C函数的参数。因为其错误信息是由参数格式化而来(例如:"bad argument #1"),所以你不应该对其他栈值使用这些函数。

luaL_check* 形式的函数总会在不满足检查时抛出错误。

5.1 - 函数和类型

这里按字符序列出了辅助库中的所有函数和类型。

luaL_addchar

[-?, +?, m]

void luaL_addchar (luaL_Buffer *B, char c);

将字节 c 添加到缓冲区 B 中(参见luaL_Buffer)。

luaL_addgsub

[-?, +?, m]

const void luaL_addgsub (luaL_Buffer *B, const char *s,
                         const char *p, const char *r);

将字符串 s 拷贝到缓冲区 B (参见luaL_Buffer)中,并将其中遇到的字符串 p 替换为 字符串 r 。

luaL_addlstring

[-?, +?, m]

void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l);

将 s 所指向的长度为 l 的字符串添加到缓冲区 B (参见luaL_Buffer) 中。该字符串可以包含嵌入的零值。

luaL_addsize

[-?, +?, -]

void luaL_addsize (luaL_Buffer *B, size_t n);

将之前已经复制到缓冲区 B 中的长度为 n 的字符串添加到其中,即之前已经拷贝到缓冲区的内存了,此时是将大小也合并进去(参见luaL_prepbuffer)。

luaL_addstring

[-?, +?, m]

void luaL_addstring (luaL_Buffer *B, const char *s);

将 s 所指向的一个零为终止符的字符串添加到缓冲区B中(参见luaL_Buffer)。

luaL_addvalue

[-?, +?, m]

void luaL_addvalue (luaL_Buffer *B);

将栈顶上的值添加到缓冲区B中(参见luaL_Buffer),然后弹出这个值。

对于字符串缓冲区,这是唯一会额外使用(而且必须使用)栈上元素的函数,这个元素的值会被添加到缓冲区中。

luaL_argcheck

[-0, +0, v]

void luaL_argcheck (lua_State *L,
                    int cond,
                    int arg,
                    const char *extramsg);

检查 cond 是否为真。如果不是,抛出一个关于第 arg 参数的带标准信息的错误(参见luaL_argerror)。

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_argexpected

[-0, +0, v]

void luaL_argexpected (lua_State *L,
                       int cond,
                       int arg,
                       const char *tname);

检查 cond 是否为真。如果不是,抛出一个关于第 arg 参数的类型的带标准信息的错误(参见luaL_typeerror)。

luaL_Buffer

typedef struct luaL_Buffer luaL_Buffer;

字符串缓冲区 string buffer 的类型。

字符串缓冲区允许在C代码中分步构建Lua字符串。其按照如下模式使用:

  • 首先声明一个luaL_Buffer类型的变量 b 。
  • 然后使用 luaL_buffinit(L, &b) 初始化。
  • 使用 luaL_add* 类函数来将不同的字符串片段添加到其中。
  • 最后调用 luaL_pushresult(&b) 。其会将最终的字符串放在栈顶上。

如果你提前就知道最终的字符串能有多大,那么你可以像这样使用:

  • 先声明一个luaL_Buffer类型的变量 b 。
  • 然后使用 luaL_buffinitsize(L, &b, sz) 来提前分配 sz 大小的空间并初始化。
  • 再和之前一样在分配的空间里构建字符串。
  • 最后调用 luaL_pushresultsize(&b, sz) ,其中 sz 是在缓冲区空间中的最终字符串的长度(其实可能会小于或等于之前分配的大小)。

一般在以上的操作过程中,字符串缓冲区会使用到不定数目的栈空间。所以当使用缓冲区时,你不可以假设栈顶是已知的。你可以在连续的缓冲区操作之间使用栈,只要能够平衡地去使用它;意思是,当你调用一个缓冲区操作时,栈的层级和上次缓冲区操作后的相同。(这个规则的唯一例外是luaL_addvalue。)在调用luaL_pushresult后,栈应该回到缓冲区初始化的状态,然后将最终的字符串加到栈顶上。

luaL_buffaddr

[-0, +0, -]

char *luaL_buffaddr (luaL_Buffer *B);

返回缓冲区 B(参见luaL_Buffer)当前内容的地址。注意任何对缓冲区的添加操作都可能使这个地址失效(可能会在内部空间不够的时候重新分配内存)。

luaL_buffinit

[-0, +?, -]

void luaL_buffinit (lua_State *L, luaL_Buffer *B);

初始化缓冲区 B(参见luaL_Buffer。此函数不会分配任何空间;此缓冲区必须声明为变量。

luaL_bufflen

[-0, +0, -]

size_t luaL_bufflen (luaL_Buffer *B);

返回缓冲区 B(参见luaL_Buffer)当前内容的长度。

luaL_buffinitsize

[-?, +?, m]

char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz);

等效于先后调用luaL_buffinitluaL_prepbuffsize

luaL_buffsub

[-?, +?, -]

void luaL_buffsub (luaL_Buffer *B, int n);

从缓冲区 B(参见luaL_Buffer)中移除 n 个字节。缓冲区中必须有足够的字节。

luaL_callmeta

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

int luaL_callmeta (lua_State *L, int obj, const char *e);

调用某个元函数。

如果处于索引 obj 处的对象拥有元表并且其中包含字段 e ,那么此函数就会将该对象作为唯一参数调用对应字段。这种情况下此函数会返回 true 并将调用结果压入栈中。如果没有找到元表或元函数,此函数会返回 false 并且不会压栈。

luaL_checkany

[-0, +0, v]

void luaL_checkany (lua_State *L, int arg);

检查函数的参数列表中是否有第 arg 个的任何类型(包括nil)参数。

luaL_checkinteger

[-0, +0, v]

lua_Integer luaL_checkinteger (lua_State *L, int arg);

检查函数的第 n 个参数的类型是否为整数(或者可以转换到整数)并返回其整数值。

luaL_checklstring

[-0, +0, v]

const char *luaL_checklstring (lua_State *L, int arg, size_t *l);

检查函数的第 n 个参数的类型是否为字符串并返回这个字符串;参数 l 不为NULL时则会被设为该字符串的长度。

此函数使用lua_tolstring获取结果,因此所有相关的转换都可能会生效。

luaL_checknumber

[-0, +0, v]

lua_Number luaL_checknumber (lua_State *L, int arg);

检查函数的第 n 个参数的类型是否为 number ,然后将该值转换成 lua_Number 并返回。

luaL_checkoption

[-0, +0, v]

int luaL_checkoption (lua_State *L,
                      int arg,
                      const char *def,
                      const char *const lst[]);

检查函数的第 arg 个参数类型是否为字符串,并在数组 lst (必须由NULL作为终止标记)中查找字符串。如果找到了就返回在数组中对应的索引下标。如果参数不是个字符串或者查找无果则会抛出错误。

如果 def 不为 NULL,那么此函数会在没有参数或参数为nil时作为其默认值。

此函数通常被用来匹配C中的枚举值。(在Lua库通常会使用字符串而不是数字作为可选项。)

luaL_checkstack

[-0, +0, v]

void luaL_checkstack (lua_State *L, int sz, const char *msg);

扩充栈空间到 top + sz 个元素。如果无法完成扩充则会抛出错误。参数 msg 指向的文本会被额外添加到错误信息中(或者使用 NULL 表示不添加任何文本)。

luaL_checkstring

[-0, +0, v]

const char *luaL_checkstring (lua_State *L, int arg);

检查函数的第 arg 个参数类型是否为字符串并返回这个字符串。

此函数使用lua_tolstring获取结果,因此所有相关的转换都可能被触发。

luaL_checktype

[-0, +0, v]

void luaL_checktype (lua_State *L, int arg, int t);

检查函数的第 arg 个参数类型是否为 t 。关于 t 所对应的的各类型编码请参见lua_type

luaL_checkudata

[-0, +0, v]

void *luaL_checkudata (lua_State *L, int arg, const char *tname);

检查函数的第 arg 个参数类型是否为类型名为 tname 的 userdata(参见luaL_newmetatable)并返回其内存块的地址(参见lua_touserdata)。

luaL_checkversion

[-0, +0, v]

void luaL_checkversion (lua_State *L);

检查正在调用的代码和正在调用的Lua库所使用的Lua版本是否相同,以及相同的数值类型。

luaL_dofile

[-0, +?, m]

int luaL_dofile (lua_State *L, const char *filename);

加载并运行指定的文件。是一个如下定义的宏:

(luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))

如果没有错误发生会返回0(LUA_OK),否则返回1。

luaL_dostring

[-0, +?, -]

int luaL_dostring (lua_State *L, const char *str);

加载并运行指定的字符串。是一个如下定义的宏:

(luaL_loadstring(L, str) || lua_pcall(L, 0, LUA_MULTRET, 0))

如果没有错误发生会返回0(LUA_OK),否则返回1。

luaL_error

[-0, +0, v]

int luaL_error (lua_State *L, const char *fmt, ...);

抛出一个错误。其错误信息由 fmt 加上额外的参数格式化而来,遵循和lua_pushfstring相同的规则。其也会将遇到错误的文件名和行号加到此错误信息的前面,如果可以找到这些信息的话。

此函数永不返回,但是可以使用C函数的独有写法 return luaL_error(args) 。

luaL_execresult

[-0, +3, m]

int luaL_execresult (lua_State *L, int stat);

此函数用于生成标准库中和线程相关的函数(os.executeio.close)的返回值。

luaL_fileresult

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

int luaL_fileresult (lua_State *L, int stat, const char *fname);

此函数用于生成标准库中文件相关的函数(io.openos.renamefile:seek 等)的返回值。

luaL_getmetafield

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

int luaL_getmetafield (lua_State *L, int obj, const char *e);

将索引 obj 处的对象的元表中的字段 e 压入栈中,并返回其类型。如果对象没有元表或元表中没有该字段,则不会压栈且返回 LUA_TNIL 。

luaL_getmetatable

[-0, +1, m]

int luaL_getmetatable (lua_State *L, const char *tname);

将注册表中 tname 的元表压入栈中,或者当不存在相关元表时压入nil。返回值为压入栈中的值的类型。

luaL_getsubtable

[-0, +1, e]

int luaL_getsubtable (lua_State *L, int idx, const char *fname);

确保 t[fname] 的值是一个表并将这个表压入栈中,这里的 t 是索引 idx 处的一个表。当找到这个表时则返回true,否则返回false并创建一个新的表。

luaL_gsub

[-0, +1, m]

const char *luaL_gsub (lua_State *L,
                       const char *s,
                       const char *p,
                       const char *r);

创建一个字符串 s 的拷贝,遇到字符串 p 的地方都使用字符串 r 替换。将最终的字符串压入栈中同时并返回。

luaL_len

[-0, +0, e]

lua_Integer luaL_len (lua_State *L, int index);

将给出索引处的值的“长度”作为一个lua_Integer返回;其等效于Lua代码中的"#"操作。如果其结果不是一个整数则会抛出错误(这种情况只会发生在元函数中)。

luaL_loadbuffer

[-0, +1, -]

int luaL_loadbuffer (lua_State *L,
                     const char *buff,
                     size_t sz,
                     const char *name);

等效于参数 mode 传NULL以调用luaL_loadbufferx

luaL_loadbufferx

[-0, +1, -]

int luaL_loadbufferx (lua_State *L,
                      const char *buff,
                      size_t sz,
                      const char *name,
                      const char *mode);

将一块缓冲区加载为Lua代码块。此函数使用lua_load来加载 buff 所指向的长度为 sz 的代码块。

此函数的返回值同lua_load。参数 name 是该代码块的名称,用于调试信息和错误消息。字符串参数 mode 用法同lua_load

luaL_loadfile

[-0, +1, m]

int luaL_loadfile (lua_State *L, const char *filename);

等效于参数 mode 传NULL以调用luaL_loadfilex

luaL_loadfilex

[-0, +1, m]

int luaL_loadfilex (lua_State *L, const char *filename,
                                            const char *mode);

将一个文件加载为Lua代码块。此函数使用lua_load来加载名为 filename 的文件。当 filename 为NULL时会从标准输入中加载。文件中以 '#' 开头的第一行会被忽略。

字符串参数 mode 用法同lua_load

此函数的返回值同lua_load或者在发生与文件无关的错误时返回LUA_ERRFILE

lua_load一样,此函数只是加载代码块而不运行。

luaL_loadstring

[-0, +1, -]

int luaL_loadstring (lua_State *L, const char *s);

将一个字符串加载为Lua代码块。此函数使用lua_load来加载以零为终止符的字符串 s 。

此函数的返回值同lua_load

lua_load一样,此函数只是加载代码块而不运行。

luaL_newlib

[-0, +1, m]

void luaL_newlib (lua_State *L, const luaL_Reg l[]);

创建一个表并将包含在列表 l 中的函数注册在其中。

其实现为一个如下的宏:

(luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))

l 必须传入数组本身,而不是一个指针。

luaL_newlibtable

[-0, +1, m]

void luaL_newlibtable (lua_State *L, const luaL_Reg l[]);

创建一个足以容纳数组 l 中所有条目的表(但不会真的把它们存进去)。其主要是和luaL_setfuncs组合使用(参见luaL_newlib)。

luaL_newmetatable

[-0, +1, m]

int luaL_newmetatable (lua_State *L, const char *tname);

如果注册表中已经存在键 tname ,则直接返回0。否则,创建一个新表用于作为 userdata 的元表,在这个表中添加键值对 __name = tname,再将键值对 [tname] = new tbale 添加到注册表中,并返回1。

luaL_newstate

[-0, +0, -]

lua_State *luaL_newstate (void);

创建一个状态机。其会使用一个基于ISO标准C的内存分配函数调用lua_newstate,然后设置警告函数和 panic 函数(参见4.4),使之调用时会打印消息到标准错误输出。

返回值为一个新的状态机,或者在发生内存分配错误时返回NULL。

luaL_openlibs

[-0, +0, e]

void luaL_openlibs (lua_State *L);

打开给出的状态机中的所有标准库。

luaL_opt

[-0, +0, -]

T luaL_opt (L, func, arg, dflt);

这个宏的定义如下:

(lua_isnoneornil(L,(arg)) ? (dflt) : func(L,(arg)))

换言之。如果参数 arg 为nil或者缺失,这个宏的结果都是默认的 dflt 。否则,其结果为使用状态机 L 和 arg 作参数的 func 调用结果。注意表达式 dflt 只会在需要的时候被计算。

luaL_optinteger

[-0, +0, v]

lua_Integer luaL_optinteger (lua_State *L,
                             int arg,
                             lua_Integer d);

如果函数的第 arg 个参数是一个整数(或可转换为整数),则返回这个整数值。如果参数缺失或为nil,则返回 d 。其他情况则会抛出错误。

luaL_optlstring

[-0, +0, v]

const char *luaL_optlstring (lua_State *L,
                             int arg,
                             const char *d,
                             size_t *l);

如果函数的第 arg 个参数是一个字符串,则返回这个字符串。如果参数缺失或为nil,则返回 d 。其他情况则会抛出错误。

如果 l 不为NULL,则会被置为结果的长度。如果结果是NULL(只可能是返回的 d 且 d == NULL),其长度会被视为0。

此函数使用lua_tolstring获取结果,因此所有相关的转换都会被触发。

luaL_optnumber

[-0, +0, v]

lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number d);

如果函数的第 arg 个参数是一个 number ,则将其作为 lua_Number 返回。如果参数缺失或为nil,则返回 d 。其他情况则会抛出错误。

luaL_optstring

[-0, +0, v]

const char *luaL_optstring (lua_State *L,
                            int arg,
                            const char *d);

如果函数的第 arg 个参数是一个字符串,则返回这个字符串。如果参数缺失或为nil,则返回 d 。其他情况则会抛出错误。

luaL_prepbuffer

[-?, +?, m]

char *luaL_prepbuffer (luaL_Buffer *B);

等效于使用预定义的 LUAL_BUFFERSIZE 调用luaL_prepbuffsize

luaL_prepbuffsize

[-?, +?, m]

char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz);

返回一个大小为 sz 的内存空间地址以供之后向缓冲区 B(参加luaL_Buffer)中复制数据,即提前分配一定的内存而不是在添加时遇到空间不够后再分配。在往其中复制完字符串后你必须调用luaL_addsize来真正将其添加到缓冲区中。

luaL_pushfail

[-0, +1, -]

void luaL_pushfail (lua_State *L);

fail值压入栈中(参见6)。

luaL_pushresult

[-?, +1, m]

void luaL_pushresult (luaL_Buffer *B);

结束对缓冲区 B 的使用并将最终的字符串放到栈顶上。

luaL_pushresultsize

[-?, +1, m]

void luaL_pushresultsize (luaL_Buffer *B, size_t sz);

等效于先后执行luaL_addsizeluaL_pushresult

luaL_ref

[-1, +0, m]

int luaL_ref (lua_State *L, int t);

在索引 t 处的表中创建并返回一个引用 reference ,其引用的是于栈顶处的对象(并且会弹出栈顶对象)。

引用是一个唯一的整数键。只要你没有手动给表添加整数键 t ,luaL_ref就会保证其返回的键是唯一的。你可以通过调用 lua_rawgeti(L, t, r) 来取回引用 r 所引用的对象。可以使用[luaL_unref]来释放引用。

如果栈顶的对象是nil,那么luaL_ref会返回常量 LUA_REFNIL 。常量 LUA_NOREF 也绝对不同于luaL_ref返回的引用值。

luaL_Reg

typedef struct luaL_Reg {
  const char *name;
  lua_CFunction func;
} luaL_Reg;

用于luaL_setfuncs的注册函数的数组的元素类型。字段 name 是函数的名称,func 是对应的函数指针。任何luaL_Reg数组都必须以一个name 和 func 字段都是NULL的哨兵条目结尾。

luaL_requiref

[-0, +1, e]

void luaL_requiref (lua_State *L, const char *modname,
                    lua_CFunction openf, int glb);

如果 package.loaded[modname] 结果不为 true 时,会使用字符串 modname 作为函数 openf 的参数传入并调用,并将调用结果放到 package.loaded[modname] 中,就好像这个函数由require调用的一样。

如果参数 glb 为true,则将导入的模块也存到与 modname 同名的全局变量中。

最后会将模块的拷贝到栈顶上。

luaL_setfuncs

[-nup, +0, m]

void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);

将数组 l(参见luaL_Reg)中的所有函数注册到栈顶的表中(也可能会在上值下边,见下文)。

当 nup 不为零时,所有的函数创建时都会带上 nup 个上值,这些上值使用之前压入栈顶的 nup 个值的拷贝来初始化,在栈中这些值的下边才是注册目标的表。这些值都会在注册完之后从栈中弹出。

当某个 func 的值为NULL时,其表示只占位,在表中注册时会由false作为键值填充,即 t[name] = false 。

luaL_setmetatable

[-0, +0, -]

void luaL_setmetatable (lua_State *L, const char *tname);

将栈顶上对象的元表设置为在注册表中名为 tname 所关联的表(参见luaL_newmetatable)。

luaL_Stream

typedef struct luaL_Stream {
  FILE *f;
  lua_CFunction closef;
} luaL_Stream;

用于表示标准I/O库中文件句柄的结构。

文件句柄是作为一个 full userdata 实现的,其带有一个称为 LUA_FILEHANDLE(一个由宏定义的元表名称)的元表。这个元表是由I/O库创建的(参见luaL_newmetatable)。

其实现的 userdata 中必须由 luaL_Stream结构作为起始,要包含的其他数据应该放在初始结构的后面。字段 f 指向了其对应的 C stream(或者可以为NULL以表示未完全创建的句柄)。字段 closef 指向了一个当句柄被关闭或回收时用来关闭这个流的函数;此函数接收对应的文件句柄作为唯一的参数,成功时必须返回 true,或折失败的时候返回false加上一个错误消息。一旦Lua调用了这个字段,其就会将该字段置为NULL以表示句柄被关闭了。

luaL_testudata

[-0, +0, m]

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字符串。将结果字符串压入栈中并将其作为该函数的返回值(参见4.1.3)。如果 len 不为NULL,此函数也会就将 *len 置为该字符串的长度。

如果给出索引处的Lua值有元表并其中包含了 __tostring 字段,那么luaL_tolstring会将此值作为参数来调用相应的元函数,最后将调用结果作为此函数的结果。

luaL_traceback

[-0, +1, m]

void luaL_traceback (lua_State *L, lua_State *L1, const char *msg,
                     int level);

新建回溯信息并将其压入到L1的栈中。如果参数 msg 不为 NULL,那么会将其添加到回溯信息的开头。参数 level 指出了从哪一级开始回溯。

luaL_typeerror

[-0, +0, v]

int luaL_typeerror (lua_State *L, int arg, const char *tname);

抛出一个标准消息格式的类型错误,其和正在调用的C函数的第 arg 个参数相关;参数 tname 是函数需要的类型。此函数永不返回。

luaL_typename

[-0, +0, -]

const char *luaL_typename (lua_State *L, int index);

返回给定索引处值的类型名。

luaL_unref

[-0, +0, -]

void luaL_unref (lua_State *L, int t, int ref);

释放索引 index 处表的引用 ref(参见luaL_ref)。因为该条目从表中移除了,所以引用的对象可以被回收。引用 ref 的值释放后也可以再次使用。

当 ref 值为LUA_NOREFLUA_REFNIL时,luaL_unref什么都不会做。

luaL_where

[-0, +1, m]

void luaL_where (lua_State *L, int lvl);

将一个表示当前控制流相对于调用栈的 lvl 层的所在位置的字符串压入栈中。通常这个字符串有以下格式:

chunkname:currentline:

第 0 层为当前运行的函数,第 1 层为调用这个函数的函数,以此类推。

此函数一般用于构建错误信息的前缀。

6 - 标准库

Lua标准标准库提供了一些有用的函数,这些函数是通过 C API 实现在C代码中的。其中有些函数为语言提供了基础服务(如typegetmetatable);有些函数提供了外部服务(例如 I/O 相关);有些函数其实也可以Lua代码中实现,但是因为各种原因其更适合在C中实现(例如table.sort)。

所有的库函数都是通过Lua官方的 C API 实现的,并以独立的 C 模块形式提供。一般情况下,库中的函数是不会将参数的数量调整到其预期的参数列表的。在文档中形如 foo(arg) 的函数在使用的时候就不应当缺省参数。

符号fail的意为一个表示某种错误的假值。(目前fail还是等于nil,但是在未来版本中可能发生变更。推荐在判断它时使用 (not status) 的形式而不是 (status == nil) 。)

到目前为止,Lua中有这些标准库:

  • 基础库(参见6.1);
  • 协程库(参见6.2);
  • 包(参见6.3);
  • 字符串操作库(参见6.4);
  • 基本的 UTF-8 支持(参见6.5);
  • 表操作库(参见6.6);
  • 数学库(参见6.6)(例如 sin、log 等);
  • I/O(参见6.8);
  • OS(参见6.9);
  • 调试工具(参见6.10);

除了基础库和包相关的库,每个库都是以某个全局表或其对象的成员函数的形式提供的。

为了访问这些库,宿主程序中应当调用luaL_openlibs函数以打开所有的标准库。或者可以使用luaL_requiref单独打开某个库,其中可以传入 luaopen_base(基础库)、luaopen_package (包相关库)、luaopen_coroutine (协程库)、luaopen_string (字符串库)、luaopen_utf8 (UTF-8 库)、luaopen_table (表相关库)、luaopen_math (数学库)、luaopen_io (I/O库)、luaopen_os (OS相关库)、以及 luaopen_debug (调试库)。这些函数声明在头文件 lualib.h 中。

6.1 - 基础库

基础库提供了Lua中的核心函数。如果你不想让程序中包含这个库,你应当仔细确认你是否需要自己提供这些工具的实现。

assert (v [, message])

当表达式 v 的值为假(即为nilfalse)时抛出异常;否则返回其所有的参数。在发生错误时, 参数 message 会作为错误对象;如果该参数缺失,则使用默认值 "assertion failed!" 。

collectgarbage ([opt [, arg]])

此函数是GC的通用接口。其根据第一个参数 opt 来执行不同的方法:

  • "collect": 默认选项,执行一轮完整GC。
  • "stop": 停止GC的自动运行。收集器将只在显式触发时运行,直到调用 restart 。
  • "restart": 重启GC的自动执行。
  • "count": 返回Lua中正在使用的所有内存的大小(KB)。该值有小数部分,因此可以乘以1024得出Lua所使用内存的确切字节数。
  • "step": 单步运行GC。步长由 arg 参数控制。步长为零时,收集器将执行一步基础(不可分割)行为。步长不为零时,收集器将在Lua分配到了步长大小(KB)的内存后工作。如果这一步完成了一轮GC会返回true
  • "isrunning": 返回一个布尔值以表示收集器是否正在运行(即没有被停止)。
  • "incremental": 将GC改为步进模式。该选项可以跟三个数值参数:GC停步(garbage-collector pause),GC步进乘数(garbage-collector step multiplier),以及GC步数(garbage-collector step size)(参见2.5.1),传零则表示不更改对应的设置值。
  • "generational": 将GC改为代际模式。该选项可以跟两个数值参数:次代乘数(minor multiplier)以及主乘数(major multiplier)(参见2.5.2),传零则表示不更改对应的设置值。

更多关于GC和以上选项的细节请参见2.5

此函数不应当在终结器中调用。

dofile ([filename])

打开指定的文件并将其内容作为Lua代码块执行。不带参数调用时,dofile 会执行从标准输入获取的内容。会将代码块中所有的返回值原样返回。遇到错误时,dofile 会将错误传播给调用者(即 dofile 没有在保护模式下运行)。

error (message [, level])

抛出一个错误(参见2.3),错误对象为参数 message 。此函数永不返回。

通常,如果 message 是字符串的话,error 会在此之前添加一些关于错误位置的信息。参数 level 指定了如何获取错误的位置。当 level 为1(默认值),错误位置就是调用 error 函数的地方;当level 为2指的是调用 error 函数的地方的上层调用者;以此类推。当 level 为0时将不会添加错误位置的信息。

_G

持有全局环境(参见2.2)的一个全局变量。Lua自身并不使用它,改变这个变量的值并不会影响任何环境,反之亦然。

getmetatable (object)

如果 object 没有元表,则返回nil。或者如果 object 的元表中有 __metatable 字段,则返回这个字段相应的值。否则返回给出的 object 的元表。

ipairs (t)

返回三个值(迭代函数、表 t 、以及 0),因此这样的形式:

for i,v in ipairs(t) do body end

将会按序遍历 t 表中的键值对(1,t[1]),(2,t[2]),...,直到遇到缺失的索引。

load (chunk [, chunkname [, mode [, env]]])

加载代码块。

如果参数 chunk 是一个字符串,那么该字符串直接作为代码块加载。如果是一个函数,load 会反复调用该函数以获得代码块,其每次调用都必须返回紧跟在上一次结果后的字符串,返回空字符串或nil或无返回则表示代码块结束。

如果没有发生语法错误,load 会将代码块作为一个函数返回;否则返回fail加上其错误消息。

当你加载主代码块时,其返回的函数将额外带有一个上值—— _ENV 变量(参见2.2)。然而,当你加载一个由某个函数创建而来的二进制块时(参见string.dump),最后的结果函数可能要带上一些其原本的上值,而且其中的第一个上值不一定是 _ENV 变量。(一个非主函数的上值中甚至可能没有 _ENV 。)

但无论如何,如果最后的函数包含上值,那么第一个上值将被设为给出参数 env 的值,如果该参数缺省则使用全局环境的值。其他的上值则使用nil初始化。这里所有的上值都是崭新的,即它们不会和其他函数共享。

chunkname 作为代码块的名称以供错误或调试信息使用(参见4.7)。该参数缺省的情况下,chunk 为字符串时,则同该字符串,否则为 "=(load)" 。

字符串参数 mode 表示代码块是文本还是二进制数据(即预编译的代码块)。其可以是字符串 "b"(表示只可能是二进制块),或者字符串 "t"(表示只可能是文本),或是字符串 "bt"(二者皆可)。其默认值为 "bt" 。

加载不合规的二进制块并不危险;因为 load 会发出适当的错误。然而,Lua并不会检查二进制块中的代码一致性,因此运行恶意制作的字节码可能会导致解释器崩溃。

loadfile ([filename [, mode [, env]]])

load类似,但是会从 filename 文件中获取代码块,如果没有给出文件名的话则会从标准输入中获取。

next (table [, index])

允许代码中遍历表的所有字段。第一个参数是一个表,第二个参数为表中的一个索引。调用 next 会返回表中下一个索引以及对应的值。当第二个参数为nil时,next 会返回起始索引和其对应的值。当第二个参数为表中最后的索引,或者是一个空表中的nil索引是,next 会返回nil。如果第二个参数缺省,则其会被当作nil处理。因此,你可以使用 next(t) 来检查是否为空表。

其中并没有对于每个索引的顺序做定义,即使是数字索引。(如果需要按照数值顺序遍历表,那么请使用数字形式的for语句。)

你不应当在遍历过程中给表中分配原本没有的字段,然而可以修改已存在的字段,甚至你可以将已存在的字段设为nil

pairs (t)

如果 t 拥有元函数 __pairs ,则将 t 作为参数调用该元方法并返回调用结果的前三个。

否则,返回三个值:next函数、表 t 、以及nil,所以这样的代码:

for k,v in pairs(t) do body end

会遍历表 t 中的所有键值对。

关于遍历过程中对表的修改行为请参考next函数。

pcall (f [, arg1, ···])

保护模式下使用给出的参数列表来调用函数 f 。意思是 f 中的任何错误都不会传播开;而是会由 pcall 捕获错误并返回状态码。其第一个返回值是一个状态码(布尔类型),没有错误则为true。这种情况下,pcall 会将所有的调用结果也放在第一个返回值后一并返回。在发生错误的情况下,pcall 会返回false加上错误对象。注意捕获的错误不会触发错误处理。

print (···)

接收任意数量的参数并将其输出到标准输出 stdout 中,其中会将每个参数转换为字符串,转换规则同tostring

print 函数不适用于格式化输出,而是一个展示值的快捷方法,例如在调试的时候使用。如需完全控制输出的内容,请使用string.formatio.write

rawequal (v1, v2)

检查 v1 和 v2 是否相等,并不会触发元函数 __eq 。返回值为布尔类型。

rawget (table, index)

直接获取 table[index] 的值,并不会触发元值 __index 。参数 table 必须是一个表;参数 index 可以是任何值。

rawlen (v)

返回对象 v 的长度,其必须是个表或者字符串,并不会触发元函数 __len 。返回值为一个整数。

rawset (table, index, value)

直接将 table[index] 设为 value ,不会使用元值 __newindex。参数 table 必须是一个表,参数 index 可以是除nil和 NaN 的任何值,参数 value 可以是任意Lua值。

此函数会将 table 返回

select (index, ···)

如果 index 是一个 number (或者可以转换为一个 number),那么返回后边参数列表第 index 个之后的所有参数;值为负数时则从后边开始数起(-1就是最后一个参数)。否则 index 必须为字符串 "#",此时 select 会返回后边的参数列表中的参数数量。

setmetatable (table, metatable)

给一个表设置元表。如果 metatable 为nil,则删除表上的元表。如果原本的元表中含有 __metatable 字符,则抛出错误。

此函数会将传进来的 table 返回。

如需改变其他类型的元表,你必须使用调试库(参见6.10)。

tonumber (e [, base])

如果参数 base 缺省,tonumber 会尝试将参数 e 转换为一个 number 。其如果已经是一个 number 或者是一个可以转换为 number 的字符串,那么 tonumber 就将这个 number 值返回;否则返回fail

字符串可以转换为整数或浮点数,由Lua的词法转换规则决定(参见3.1)。字符串的开头和结尾可以含有空格或单个符号。

当 base 不缺省时,那么 e 必须是一个可以转换为对应基数下整数的字符串。参数 base 可以是包括2到36之间的任意整数值。对于10以上的基数,字母 'A'(小写也可以)表示10,'B'表示11,以此类推,一直到 'Z' 表示35。如果字符串 e 不是一个有效的对应基数的数字,那么则返回fail

tostring (v)

接收一个任意类型的值并将其转换为人类可读的格式的字符串。

如果 v 的元表中含有 __tostring 字段,那么 tostring 会将 v 作为参数调用对应的值,然后将调用结果返回。如果 v 的元表中含有 __name 字段,并且其值是一个字符串,那么 tostring 可能会将其作为最后的结果。

如需完全控制如何转换数字,可以使用string.format

type (v)

只返回第一个参数的类型所对应的字符串。此函数的结果可能为 "nil"(是字符串,不是nil值)、"number"、"string"、"boolean"、"table"、"function"、"thread"、或者 "userdata" 。

_VERSION

一个包含了Lua版本的全局字符串变量。当前版本中此变量的值为 "Lua 5.4" 。

warn (msg1, ···)

发出一个警告,警告消息由传进来的参数连接而成(这些参数应该用字符串)。

根据约定,一块由 '@' 开头的警告信息为控制消息 control message ,是给警告系统本身的消息。特别是Lua中的标准警告函数可以识别的控制信息 "@off" ,其会停止发出警告,以及与之对应的 "@on" ,会(重新)开启警告;其会忽略不可识别的控制信息。

xpcall (f, msgh [, arg1, ···])

此函数类似于pcall,不同在于其可以新设置一个错误消息处理函数 msgh 。

6.2 - 处理协程

协程库包含了处理协程的相关操作,其都包括在表 coroutine 中。关于协程的描述请参见2.6

coroutine.close (co)

关闭协程 co ,即关闭所有的待关闭变量并将该协程对应的状态机置为死亡状态。所给协程必须处于死亡或挂起状态中。发生错误的情况下(要么是协程的停止过程本身有错误,要么是某些关闭函数发生了错误)返回false加上对应的错误对象;否则返回true

coroutine.create (f)

将 f 作为主体创建协程,参数 f 必须是一个函数。返回新创建的协程 —— 一个类型为 "thread" 的对象。

coroutine.isyieldable ([co])

如果协程 co 可以让出则返回true。参数 co 的默认值时当前正在运行的协程。

当一个协程不是Lua主线程并且不在一个不可让出的C函数中时,该协程才可以让出。

coroutine.resume (co [, val1, ···])

开始或继续执行协程 co 。当你首次对协程执行此函数,将会开始运行其主体。其中 val1, ··· 等参数是传递给主体函数的参数。如果之前协程让出了,resume 将会重新启动它,并传入 val1, ··· 等参数作为之前让出的返回值。

如果协程的运行没有发生错误,resume 将会返回true加上让出时传递的值(在协程让出时)或者由主体函数返回的值(当函数执行完毕时)。如果发生了任何错误,resume 会返回false加上错误信息。

coroutine.running ()

返回当前运行的协程加上一个布尔值,为true时表示此协程是主协程。

coroutine.status (co)

返回表示协程 co 所处状态的字符串:"running" 表示正在运行;"suspended" 表示挂起状态,要么是让出了,要么是还未开始运行;"normal" 表示协程仍然活跃但是没有在运行(即该协程 resume 了另一个协程);"dead" 表示协程已经执行完了主体函数,或者因为错误而停止了。

coroutine.wrap (f)

使用 f 做主体函数来创建协程;参数 f 必须是个函数。返回另一个函数,此每次调用此函数都会 resume 协程。所有传递给这个函数的参数都会作为 resume 的扩展参数传入。此函数除了第一个返回值之外,其余的返回值和 resume 相同。在有错误的情况下,此函数会关闭协程并发出一个错误。

coroutine.yield (···)

挂起当前运行的协程。任何传入 yield 的参数都会作为之前 resume 的调用结果返回。

6.3 – 模块

包相关库提供了Lua中加载模块相关的基础工具。其直接将一个函数require导出到了全局环境中,其他都导出到了表 package 中。

require (modname)

加载给定的模块。此函数一开始会在表package.loaded中确认名为参数 modname 模块是否已经加载。如果是,那么 require 会返回存在 package.loaded[modname] 中的值。(这种情况下是没有第二个返回值的,以表示此次调用没有加载任何模块。)否则将尝试查找模块的加载器 loader

package.searchers会引导查找加载器。该表中的每一个元素都是一个查找函数,通过其各自的方式查找模块。所以通过改变该表我们就可以改变 require 查找模块的方式。下边解释了默认配置下的 package.searchers 。

首先 require 会查询 package.preload[modname]。如果找到了,那么其对应的值(一定是个函数)就是加载器。否则 require 会使用另一个加载器,这个加载器使用存在 package.path 的路径查找模块。如果又失败了,则会使用一个C加载器,其会在 package.cpath 中寻找。如果还是失败了,则会尝试使用 all-in-one 加载器(参见package.searchers)。

对于每个找到的加载器, require 都会使用两个参数调用它:modname 和一个扩展值(是一个 加载器数据 loader data,也是由查找器返回的) 。这个加载器数据是模块所用到的一个任意值;对于默认的查找器,它是加载器被查找到的位置。(例如,如果是一个来自文件中的加载器,这个值就是文件路径。)如果加载器返回了任何非空的值,require 会将其赋值给 package.loaded[modname] 。如果加载器没有返回有效值并没有任何 package.loaded[modname] 的赋值,那么 require 会将该条目置为true。任何情况下,require 都会返回 package.loaded[modname] 最终的值。除这个值以外,require 还会将查找器返回的加载器数据作为第二个结果返回,其表明了 require 是如何找到的模块。

如果在加载或运行模块的过程中发生了任何错误,或者没有找到任何加载器,那么 require 都会抛出一个错误。

package.config

该字符串描述了关于包的一些编译时设置。此字符串由这些行构成:

  • 第一行是目录的分割符。Windows下默认是 '' ,其他系统默认是 '/' 。
  • 第二行是各个路径的分割符。默认为 ';' 。
  • 第三行是标记匹配替换的符号。默认为 '?' 。
  • 第四行是表示在Windows中会被替换为可执行文件所在的目录的符号。默认为 '!' 。
  • 第五行是一个标记,会使得在构建 luaopen_ 函数名时忽略其后边的文本。默认为 '-' 。

package.cpath

该字符串是用于reqire中查找 C 加载器的路径。

Lua初始化package.cpath的方式和package.path是一样的,使用环境变量 LUA_CPATH_5_4 ,或使用环境变量 LUA_CPATH,或者使用定义在 luaconf.h 文件中的默认路径。

package.loaded

用于require的一个表,可以用来确认哪些模块已经加载了。当你需要一个名为 modname 的模块时,如果 package.loaded[modname] 存在,那么require就会之间返回存在此处的值。

这个全局变量只是真正的 loaded 表的引用;给这个变量赋值并不会改变require的行为。真正的 loaded 表是存在C注册表中的(参见4.3),索引为 LUA_LOADED_TABLE, 一个预定义的字符串。

package.loadlib (libname, funcname)

将一个名为 libname 的C库动态链接到宿主程序中。

如果 funcname 为 "*" ,那么只会链接这个库,将其符号导出给其他的动态链接库。否则,会在这个库中查找名为 funcname 的函数并找到的函数作为Lua定义的C函数形式返回。所以, funcname 函数必须符合lua_CFunction的约定类型。

这是一个底层接口。其完全绕开了包和模块系统。与require不同,其不会执行任何路径查找和紫红添加扩展名。参数 libname 必须是C库的完整文件名,其包括包括必要的路径和扩展名。参数 funcname 必须是C库中已经导出的确切名称(可能取决于所使用的C编译器和链接器)。

ISO标准C并不支持此函数。因为函数的功能只在部分支持动态链接的操作系统上可用(Windows、Linux、Mac OS X、Solaris、BSD以及其他支持 dlfcn 的类Unix系统)。

此函数本质上是不安全的,因为其允许Lua在系统中的任何可读动态库中调用任何函数。(Lua说的函数都是假设符合约定类型并遵守调用约定的,可以参见lua_CFunction。因此在任意库中调用任意函数通常会引起访问冲突。)

package.path

该字符串变量存储了供require查找加载器的路径。

在启动时Lua会初始化这个变量的值,其值来自于环境变量 LUA_PATH_5_4 或是 LUA_PATH ,如果没有找到这些环境变量,则使用定义在 luaconf.h 文件中的默认路径。环境变量中的 ";;" 会被替换为默认路径。

package.preload

存储了一些特殊模块的加载器的表(参见require)。

该变量只是引用了真正的表;给这个变量赋值不会改变require的行为。真正的表是存在C注册表中的(参见4.3),索引是一个预定义的字符串 LUA_LOADED_TABLE 。

package.searchers

控制了require如何查找模块的表。

该表中的每个条目都是一个查找函数。每当查找一个模块,require会先后调用其中的每个查找函数,并将模块名(由传进require的参数而来)作为唯一参数传入。查找器如果找到了模块,则返回另一个函数,即该模块的加载器,以及一个额外的值,即加载数据,将会传入加载器并作为require的第二个参数。如果没找到模块,会返回一个字符串以解释原因(或是返回nil以表示无话可说)。

Lua会使用四个函数来初始化这个表。

第一个函数会直接在package.preload表中查找加载器。

第二个函数会使用package.path中的路径来查找有没有相应的Lua库。搜索方式可以参照package.searchpath中的描述。

第三个函数会使用package.cpath中所给出的路径来查找有没有相应的C库。同样,其搜索方式可以参照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 。

第四个函数会尝试使用 all-in-one 加载器。其搜索给定模块的根名称的C库。例如导入 a.b.c 库是,其将会搜索名为 a 的库。如果找到了,会在其中搜索子模块的 open 函数;在本例中,这个 open 函数会是 luaopen_a_b_c 。通过这种方式,一个包可以将多个C子模块打包到一个库中,其中每个子模块都有各自的 open 函数。

除了第一个查找函数之外,所有的查找函数都会返回一个记录了模块文件路径的额外字符串,由package.searchpath返回。第一个查找函数始终返回字符串 ":preload:" 。

查找函数应该不会抛出错误并且对Lua没有副作用。(可能会因为将库连接到了程序,从而对C代码产生副作用。)

package.searchpath (name, path [, sep [, rep]])

在给定路径 path 中查找名称 name 。

路径是一个包含了多个模板 templates 的字符串,各模板之间使用分号隔开。对于每个模板,此函数会将每个模板中的问号替换由参数 name 得来的文件路径,其中会将 name 中的每个遇到的 sep(默认是 '.')替换为 rep (默认为系统中的目录分隔符),然后再尝试打开最后得到的各个文件路径。

例如一个路径为如下的字符串:

"./?.lua;./?.lc;/usr/local/?/init.lua"

如果在其中查找名称 foo.a ,则会先后尝试打开这些文件: ./foo/a.lua、./foo/a.lc、以及 /usr/local/foo/a/init.lua 。

如果成功了,则返回第一个遇到的可以用读模式打开的文件名(在此之后会关闭文件)。否则返回fail加上一个错误消息。(这个错误消息列出了每个文件打不开的原因。)

6.4 - 字符串操作

此库提供了字符串操作的通用函数,例如查找和提取子串,或是模式匹配。在Lua中对字符串使用索引时,第一个字符的位置是1(而不是像C代码中的0)。索引可以是负数,以表示从末尾开始倒数的下标。例如最后一个字符的位置是-1,以此类推。

字符串库所提供的函数都放在名为 string 的表中。其同时会设置字符串值的元表,其中 __index 字段指向了 string 表。因此你可以使用面向对象的风格来调用字符串函数。例如 string.byte(s,i) 可以写成 s:byte(i) 的形式。

字符串库中,字符是假定为单字节编码的。

string.byte (s [, i [, j]])

返回字符串中索引 i 到索引 j 之间的(s[i], s[i+1], ..., s[j]) 的各个字符的数值。参数 i 的默认值为1,j 的默认值为 i 的值。此处遵循和string.sub中一样的索引规则。

其由字符转换而来的数值在各平台之间不一定都相同。

string.char (···)

接收零个或多个整数参数。返回一个字符串,其长度为传入的参数数量,其中每个字符依次为各参数在内部字符编码中所对应的字符。

其中的编码数值在各平台之间不一定都相同。

string.dump (function [, strip])

返回包含所给函数的二进制表示(即二进制块 binary chunk )的字符串,之后对该字符串调用load会返回该函数的拷贝(但是其上值都是新创建的)。如果参数 strip 为真值,那么其二进制块中将不会把该函数所有的调试信息都包含进去,由此可以节省空间。

对于带有上值的函数,二进制块只会存储其上值的数量。被(再次)加载后,这些上值都会是新实例。(相关细节请参见load函数。必要时你可以使用调试库来序列化或重新加载这些函数的上值。)

string.find (s, pattern [, init [, plain]])

在字符串 s 中查找 pattern 参数的第一个匹配(参见6.4.1)。如果找到了,那么 find 函数会分别返回匹配位置的开始和结束索引;否则返回fail。第三个可选参数 init 表明从哪里开始搜索,其默认值为1并且可以是负数。第四个参数 plain 为treu时会关闭模式匹配机制,从而做直接“查找子串”的操作,参数 pattern 中内容都视为无特殊含义的字符。

如果匹配式 pattern 捕获到了,则表示匹配成功并且会在两个索引后一并返回捕捉到的值。

string.format (formatstring, ···)

第一个参数必须为字符串,后边跟不定数量的参数,返回所组成的格式化字符串。这里的字符串格式化规则与ISO标准C函数 sprintf 相同。但是不支持转义符 F、n、*、h、L、和 l ,并且多出一个额外的转义符 q 。如果有宽度和精度,则限制为两位数。

转义符对布尔、nil、number和字符串的转移结果都是一个定义在Lua源码中的有效常量值。布尔和 nil 的结果都是直接写在代码中的(true、false、nil)。浮点数结果为一个十六进制数,以保证完整精度。字符串的转换结果会被放在双引号之间,会在必要的时候使用合适的转义符,此结果可以在之后供Lua解释器成功读入。例如这样的调用:

string.format('%q', 'a string with "quotes" and \n new line')

会生成这样的字符串:

"a string with \"quotes\" and \
new line"

该转义符不支持修饰符(如标志、宽度、精度)。

转义符 A、a、E、e、f、G、以及 g 都需要一个数字作为参数。转义符 c、d、i、o、u、X、以及 x 需要的是整数。当Lua是由C89标准编译出时,转义符 A 和 a (十六进制浮点数) 都不支持修饰符。

转义符 s 需要一个字符串作为参数,如果传入的参数不是个字符串,那么其将遵循和tostring相同的规则转换为一个字符串。如果该转义符有任何修饰符,则对应的字符串参数不应当包含任何嵌入的零。

转义符 p 会使用lua_topointer来格式化指针。其会给出一个可以唯一标识表、userdata 、Lua线程、字符串和函数的字符串。对于其他类型的值(number、nil、布尔值),转义符将返回表示空指针的字符串。

string.gmatch (s, pattern [, init])

返回一个迭代器函数,每次调用该迭代器函数都会返回下一个在字符串 s 中对 pattern 的捕获(参见6.4.1)。如果 pattern 没有指定的捕获,那么每次调用就匹配整个 pattern 内容。第三个参数 init 是可选的数字,其表明匹配的起始位置,默认为1且可以传负数。

举个例子,以下这个循环将迭代字符串中的每个单词,并分行打印出来:

s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do
  print(w)
end

下边的例子会将给定字符串中的每个键值对收集起来:

t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
  t[k] = v
end

对于此函数,pattern 开头中的锚点 '^' 将不会生效,因为其会阻止迭代。

string.gsub (s, pattern, repl [, n])

拷贝整个字符串 s (或者前 n 个,如果传了此参数的话),将其中每个遇到的 pattern 匹配和捕获(参见6.4.1)根据 repl 来做替换,并将最终的字符串返回。参数 repl 可以是字符串、表、或者函数。同时 gsub 会将遇到的匹配次数作为第二个返回值返回。函数名 gsub 出自全局替换 Global SUBstitution

如果参数 repl 是一个字符串,那么其值将被用来做替换。"%" 符为转义符:任何包含在 repl 中的 %d 都被认为是第 d 个捕获项,其中 d 的值可以是1到9,%0 表示整个捕获项,%% 表示单个 "%" 符。

如果 repl 是个表,则每次匹配时都会使用捕获中的第一个捕获项作为键来对该表作查询。

如果 repl 是个函数,则每次匹配时都会将所有的捕获项先后作为参数传入来调用该函数。

如果从 repl 表或函数中得到的返回是一个字符串或 number ,那么其将会被作为替换用的字符串。否则如果返回的是nil或者false,那么将不会做替换(即原匹配维持原样)。

以下是部分示例:

x = string.gsub("hello world", "(%w+)", "%1 %1")
--> x="hello hello world world"

x = string.gsub("hello world", "%w+", "%0 %0", 1)
--> 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)()
    end)
--> x="4+5 = 9"

local t = {name="lua", version="5.4"}
x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
--> x="lua-5.4.tar.gz"

string.len (s)

接收一个字符串参数并返回其长度。空串 "" 的长度为0。嵌入的零值也会被计数,所以 "a\000bc\000" 的长度为5。

string.lower (s)

接收一个字符串参数并拷贝它(不会改动原有的字符串),将其中每个大写的字符都替换为对应的小写形式,将最终的字符串返回。其他的字符都不会被更改。对于大写字母的定义取决于当前的区域设置。

string.match (s, pattern [, init])

在字符串 s 中查找 pattern 的第一个匹配(参见6.4.1)。如果找到了,match 会将 pattern 的捕获返回;否则返回 fail。如果 pattern 不含有捕获内容,那么将返回整个 pattern 的匹配。第三个参数 init 是一个可选数字参数,其表明搜索的起始位置;默认值为1且可以为负值。

string.pack (fmt, v1, v2, ···)

返回一个包含了 v1、v2等值的二进制字符串,该字符串是通过格式化字符串 fmt 序列化而来的(参见6.4.2)。

string.packsize (fmt)

返回string.pack的结果的长度。该格式化字符串不可以包含变长的选项 's' 或 'z' (参见6.4.2)。

string.rep (s, n [, sep])

返回一个字符串,其由字符串 s 的 n 个拷贝组成,每个拷贝之前用 sep 间隔开。参数 sep 的默认值为空串(即没有间隔)。如果参数 n 不为正数则返回空串。

(要注意该函数很容易耗尽你的内存。)

string.reverse (s)

返回所给字符串 s 的翻转。

string.sub (s, i [, j])

截取字符串 s 从第 i 个到第 j 个字符并返回;其中 i 和 j 可以是负数。如果 j 缺省,则会被当作 -1 处理(即字符串末尾)。例如,调用 string.sub(s,1,j) 会返回 s 中前 j 个字符,而 string.sub(s, -i) (i为整数)则返回 s 中后 j 个字符。

在转换完负数索引后,如果 i 小于1,则置为1,如果 j 大于字符串长度,则置为字符串长度。在此之后,如果 i 大于 j ,该函数会返回一个空串。

string.unpack (fmt, s [, pos])

返回以格式化字符串 fmt 打包(参见string.pack)而来的二进制字符串 s 中的值。可选参数 pos 标记了在 s 中读取的起始位置。读取完这些值后,该函数同时还返回在 s 中第一个不可读字节的位置。

string.upper (s)

接收一个字符串参数并拷贝它(不会改动原有的字符串),将其中每个小写的字符都替换为对应的大写形式,将最终的字符串返回。其他的字符都不会被更改。对于小写字母的定义取决于当前的区域设置。

6.4.1 - 模式匹配

模式 pattern 在Lua中由常规字符串描述,其被解释为模式以用于模式匹配函数 string.findstring.gmatchstring.gsub、以及string.match中。本节会讲述相关语法以及这些字符串的含义。

字符类:

用以表示一类字符。描述各类字符时,可以使用以下字符的组合:

  • X (此处的 x 不是魔法字符 magic characters ^$()%.[]*+-? 中的任意一个)表示字符 x 本身。
  • .: 表示任意字符。
  • %a: 表示任意字母。
  • %c: 表示任意控制字符。
  • %d: 表示任意单个数字(0-9)。
  • %g: 表示出了空白符以外的任意可打印字符。
  • %l: 表示任意小写字母。
  • %p: 表示任意的标点符号。
  • %s: 表示任意的空白字符。
  • %u: 表示任意大写字母
  • %w: 表示任意的字母或数字
  • %x: 表示十六进制数字符号。
  • %x (此处的 x 为任意非字母、非数字的字符)表示字符 x 。这是转义魔法字符 magic characters 的标准方式。所有非字母、非数字的字符 (包括所有标点,也包括非魔法字符) 都可以在匹配模式串中使用 '%' 前缀以表示自身。
  • [set]: 表示包含在 set 中的字符集。可以使用按照字符值升序排列的两个字符来表示一个范围,两个字符使用 '-' 连接。上边说的 %x 形式表示的特殊字符也可以用在这里。其他在 set 中的字符则表示它们本身。例如,[%w_] (或者 [_%w]) 表示一个字母或数字加上一个下划线。[0-7] 表示0到7之间的数字,[0-7%l%-] 则表示一个0到7之间的数字加上一个小写字母以及一个连字符。
    方括号可以直接放在 set 中的第一个位置,连字符可以直接放在开始或结束的位置,就可以表示它们自身(当然使用转义也可以)。
    将范围表示和类表示混合使用的行为是未定义的,l类似 [%a-z] 或者 [a-%%] 的模式串是没有意义的。
  • [^set]: 表示 set 的补集,关于 set 可以参见上边的说明。

对于所有表示单个字母的字符类表示(例如 %a、%c 等),其对应的大写形式表示了它们的补集。例如,%S 表示任意非空字符。

对于字母、空格以及其他字符组的定义取决于当前的区域设置。例如 [a-z] 可能并不等效于 %l 。

模式项:

一个模式项 pattern item 可以是:

  • 单个字符类,将匹配该类中的任何单个字符。
  • 单个字符类后跟一个 '*',将匹配该类中的零个或多个字符。该重复项始终会匹配尽可能长的串。
  • 单个字符类后跟一个 '+',将匹配该类中的一个或多个字符。该重复项始终会匹配尽可能长的串。
  • 单个字符类后跟一个 '-',也将匹配该类中的零个或多个字符。但不同于 '*' 的是,其始终匹配尽可能短的串。
  • 单个字符类后跟一个 '?',将匹配零个或一个遇到该类中的字符。它尽可能地只匹配一个。
  • %n ,此处 n 为1到9之间的字符;该项匹配一个等于第 n 个匹配项的子串(见下文)。
  • %bxy ,此处的 x 和 y 分别是两个确实的字符,会匹配以 x 开始、以 y 为结尾的项,并且这里的 xy平衡的。意思是从左往右读取字符串,如果遇到 x 就计数+1,遇到 y 就计数-1,末尾的 y 就是计数到0的那个位置的 y 。例如:%b() 模式项会匹配成对的括号。
  • %f[set] ,前向匹配的模式;将匹配位于 set 中的某个字符之前的空串,并且其之前的字符不在 set 之中。这里的 set 集合的含义同之前的描述。所匹配到的空串的开始和结束处应当做 '\0' 处理。

模式:

一个模式 pattern 由一系列模式项组成。在模式开始处的 '^' 符会锚定在子串开始处匹配。在模式末尾处的 '$' 符会锚定在子串末尾处匹配。其他位置处的 '^' 和 '$' 符没有什么特殊含义而只是表示它们本身。

捕获:

模式中可以包含由括号包围起来的子模式;该子模式被称为捕获 captures 。每次成功匹配时,匹配到的子串中对应子模式的部分会被保存(即捕获到了)下来以供之后使用。多个捕获项之间是依据最左括号的顺序排列编号的。例如对于模式 "(a*(.)%w(%s*))" ,首先其最外层(最左边)括号中的部分 "a*(.)%w(%s*)" 被作为第一个捕获项,并且其编号为1;然后再匹配下一个括号中的 "." 作为2号捕获项;最后的括号中的 "%s*" 作为3号捕获项。

有个特例的捕获,() 会捕获当前字符串所在的位置(是个 number)(原文的描述不准确,应当是每个空括号紧跟的字符所在的位置,末尾的空括号就表示匹配到的子串后边的那个字符的位置)。例如,如果我们对字符串 "flaaap" 做捕获 "()aa()" ,那么其会产生两个捕获项:3和5(第一个 a 在3号位置,匹配到的 "aa" 下一个字符位置是5,虽然下一个还是 a ,但是无关紧要,此处举的例子很有误导性)。

多重匹配:

函数string.gsub和迭代器string.gmatch会在子串中多次匹配所出的模式。对于这些函数,每个新有效匹配的末尾至少会在之前匹配的末尾位置后隔一个字节。换言之,模式匹配机制永远不会接受空字符串作为紧跟着另一个匹配的下一个匹配(原文中的“immediately after”可以指紧接着的下一个或者是末尾处跟着的,还是不准确。此处应当是指模式匹配机制中一个匹配项的下一个匹配不可能是在其末尾同位置的空字符串)。可以观察以下代码示例:

string.gsub("abc", "()a*()", print);
--> 1   2
--> 3   3
--> 4   4

第二个和第三个结果可以看出,虽然都是匹配的空串,但是其位置仍然不会和上一个匹配的末尾位置重合,只有这样迭代器才会有尽头,而不会是死循环。

6.4.2 - 打包与解包中的格式化字符串

string.packstring.packsize、以及string.unpack中的第一个参数是个格式化字符串,其描述了要创建或读取的结构的布局。

格式化字符串由一系列的转换项组成。这些转换项如下所示:

  • <: 设为小端编码
  • >: 设为大端编码
  • =: 设为原生大小端编码,即跟随本地环境设置。
  • ![n]: 将最大对齐设为 n (默认跟随本地环境设置)。
  • b: 一个有符号字节(char)。
  • B: 一个无符号字节(char)。
  • h: 一个有符号 short(取决于本地环境大小)。
  • H: 一个无符号 short(取决于本地环境大小)。
  • l: 一个有符号的 long(取决于本地环境大小)。
  • L: 一个无符号的 long(取决于本地环境大小)。
  • j: 一个lua_Integer
  • J: 一个lua_Unsigned
  • T: 一个 size_t(取决于本地环境大小)。
  • i[n]: 一个大小为 n 个字节的有符号 int(默认为本地环境大小)。
  • I[n]: 一个大小为 n 个字节的无符号 int(默认为本地环境大小)。
  • f: 一个 float(取决于本地环境大小)。
  • d: 一个 double(取决于本地环境大小)。
  • n: 一个lua_Number
  • cn: 一个固定长度为 n 字节的字符串。
  • z: 一个以零为终止符的字符串。
  • s[n]: 一个由表示长度的 n 字节大小整数(默认是 size_t)打头的字符串。
  • x: 填充一个字节
  • Xop: 一个根据转换项 op (其他的会被忽略)来对齐的空对象。
  • ' ': (空格)无意义,会被忽略。可以用来隔开各个选项以提升可读性。

("[n]"的意思是指一个可选的整数。)除了填充、空格以及设置项("xX <=>!" 这些选项)外,每个选项都需要对应一个在string.pack参数或是string.unpack的结果。

对于 "!n"、"sn"、"in"、以及 "In" 选项,其中的 n 可以是一个1到16之间的整数。所有的整数选项都会检查移除;string.pack会检查所给的值和大小是否合适;string.unpack会检查读取的值是否符合Lua中的整数。对于无符号的选项,Lua解释器也会做类似的无符号数检查。

任何以前缀 "!1=" 开始的格式化字符串的意思是,最大对齐为1字节(即不对齐)并且大小端设置跟随本地环境。

本地的大小端设置假设整个系统要么是大端要么是小端。打包函数无法处理混合大小端模式的行为。

对齐机制如下:对于每个转换项,格式化时会做填充使得数据发生偏移,其偏移量为转换项大小和最大偏移量的最小乘数;其最小乘数必须是2的幂,转换项 "c" 和 "z" 不需要对齐;转换项 "s" 的对齐大小与其开头的整数的大小一致。

string.pack使用零值来做填充并且在string.unpack中会被忽略。

6.5 - UTF-8支持

本库对UTF-8编码提供了一些基础支持。所有提供的函数都放在表 utf8 中。本库对于其他的 Unicode 编码没有提供任何支持。所有需要使用字符含义的操作,例如字符分类,都不在讨论的范围之内。

除非另有说明,对于所有需要字节位置作参数的函数,都假设这个给出的位置要么是从字节序列的开头数起,要么始于字符串的长度加一数起。就像字符串库中一样。负数索引是从字符串末尾开始数的。

创建UTF-8字节序的函数可以接收 0x7FFFFFFF 以内的值,其定义于UTF-8原始规范;这意味着单个字节序最多为6个字节。

用于解释UTF-8字节序的函数只接受合法的字节序(格式正规并且不会过长)。默认情况下,其只接受结果值为Unicode码点中的字节序,拒绝大于 10FFF 的值和增补集。对于布尔参数 lax ,当它打开的时候,会解除检查,以接收 0x7FFFFFFF 之内的值。

utf8.char (···)

接受零个或多个整数值,将其转化为对应的UTF-8字节序,并返回包含了这些字节序的字符串。

utf8.charpattern

模式串(是个字符串,不是函数)"[\0-\x7F\xC2-\xFD][\x80-\xBF]*" ,其会匹配一个UTF-8字节序,它假定其是一个UTF-8编码的字符串。

utf8.codes (s [, lax])

用法如下:

for p, c in utf8.codes(s) do body end

其会迭代字符串 s 中所有的的UTF-8编码字符,返回值 p 为该字符的位置(以字节为单位计数),c 为对应字符的码点值。当遇到无效的字节序时会抛出错误。

utf8.codepoint (s [, i [, j [, lax]]])

返回字符串 s 中从第 i 个字节直到第 j 个字节(包括 i 和 j)之间的所有字符的对应码点。参数 i 默认为1、参数 j 默认等于 i 。当遇到任何非法的字节序时会抛出错误。

utf8.len (s [, i [, j [, lax]]])

返回字符串 s 中从第 i 个字节直到第 j 个字节(包括 i 和 j)之间所包含的UTF-8字符的数量。参数 i 默认为1、参数 j 默认等于-1。当遇到任何非法的字节序时会返回fail加上遇到的第一个非法字节的位置。

utf8.offset (s, n [, i])

返回在字符串 s 中第 n 个字符编码的起始位置(字节位置,从第 i 个字节开始算)。当 n 为负数时则从位置 i 开始往前数。当参数 n 非负且不超过 s 的长度时,参数 i 默认为1,因此 utf8.offset(s, -n) 会获取字符串末尾开始倒数第 n 个字符的字节位置。如果指定的字符不在字符串中或者超出了末尾的范围,那么会返回fail

有个特殊的例子是,当 n 为0时,该函数会返回包含了在字符串 s 中第 i 个字节的字符的编码起始位置。

该函数的前提是 s 为一个合法的UTF-8编码的字符串。

6.6 - 表操作

本库提供了对表进行操作的通用函数,所有提供的函数都放在名为 table 的表中。

请记住,对于任何需要表长度的操作,之前说的取长操作符相关的规则都适用(参见3.4.7)。所有的函数都会忽略给出的表中的非数字键。

table.concat (list [, sep [, i [, j]]])

给定一个列表,其所有的元素都是字符串或数字,返回等效于 list[i]..sep..list[i+1] ··· sep..list[j] 的结果。参数 sep 默认为空字符串,i 默认为1,j 默认等于 #list 。如果 i 大于 j ,则返回空字符串。

table.insert (list, [pos,] value)

将 value 作为元素插入到列表 list 中的 pos 位置,顺移后边的元素 list[pos], list[pos+1], ···, list[#list] 。参数 pos 默认等于 #list+1 ,因此 table.insert(t,x) 会将 x 插入到列表 t 中。

table.move (a1, f, e, t [,a2])

将表 a1 中的元素移入到表 a2 中,等效于执行多重赋值:a2[t],··· = a1[f],···,a1[e] ,所以当 a1 和 a2 不是同一个表时,a1 是不会发生变动的。其中参数 a2 默认等于 a1 。目标范围可以和源范围重叠。要移动的下标的值在Lua中必须是个整数。

返回目标表 a2。

table.pack (···)

返回一个新表,其先后将所接收到的所有参数存储到数字键 1、2、3……等等中,并将参数数量作为字段 "n" 的值。 要注意,当有些参数值为nil时,其返回的表不一定是个序列。

table.remove (list [, pos])

移除列表 list 中位于 pos 的元素,返回被移除的元素的值。当 pos 的值处于1到 #list 之间时,会先后顺移后边的元素 list[pos+1], list[pos+2], ···, list[#list] 并且移除元素 list[#list];当 #list 为0时参数 pos 可以为0,也可以为 #list + 1 。

参数 pos 默认等于 #list ,因此调用 table.remove(l) 会移除列表 l 中的最后一个元素。

table.sort (list [, comp])

将列表中从 list[1] 到 list[#list] 的元素按照给出的顺序就地排序。如果给出了参数 comp ,其必须是一个函数,该接收两个列表元素,并判断两个元素的相对位置,如果需要第一个元素在第二个元素之前,则返回true,因此在排完序后,对于索引 i <= j ,comp(list[i], list[j]) 的结果为真。如果参数 comp 未给出,那么会使用Lua标准操作符 < 代替。

函数 comp 必须定义一个绝对的顺序;更正式的说法是,该函数必须排序稳定。(弱排序看似是个完整的排序,但它可能在比较中认为两个不同的元素相等。)

排序逻辑不稳定的意思是:两个在次序上相等的不同元素,在排序完成后可能会使得两个元素的相对位置发生改变。

table.unpack (list [, i [, j]])

依次返回给定列表中的元素。该函数等效于:

return list[i], list[i+1], ···, list[j]

参数 i 默认为,j 默认等于 #list 。

6.7 - 数学函数

该库提供了基础数学方法。其提供的函数和常量都放在表 math 中。说明中带有 "integer/float" 的函数中对于整数参数会给出整数结果而对于浮点数参数会给出浮点数结果。用于四舍五入的函数math.ceilmath.floor、以及math.modf会根据情况返回整数或浮点数。

math.abs (x)

返回 x 的绝对值。(integer/float)

math.acos (x)

返回 x 的反余弦(以弧度表示)。

math.asin (x)

返回 x 的反正弦(以弧度表示)。

math.atan (y [, x])

返回 y/x 的反正切(以弧度表示),使用两个参数的符号来查找结果所处的象限。x 为零时也会正确计算。

x 的默认值为1, 所以调用 math.atan(y) 会返回 y 的反正切值。

math.ceil (x)

对 x 向上取整。结果是个大于或等于 x 的整数。

math.cos (x)

返回 x 的余弦(假定参数是弧度)。

math.deg (x)

将弧度表示的角 x 转换为角度表示。

math.exp (x)

返回ex的值(这里的 e 是自然对数的底)。

math.floor (x)

对 x 向下取整。结果是个小于或等于 x 的整数。

math.fmod (x, y)

取余,结果是将 x 除以 y 的商向零圆整后的余数。(integer/float)

math.huge

浮点值 HUGE_VAL ,比任何数值都要大。

math.log (x [, base])

计算以 base 为底的 x 对数。其中 base 默认为 e (所以该函数默认返回 x 的自然对数)。

math.max (x, ···)

返回所给参数中的最大值,使用Lua操作符 < 计算。

math.maxinteger

整数最大值。

math.min (x, ···)

返回所给参数中的最小值,使用Lua操作符 < 计算。

math.mininteger

整数最小值。

math.modf (x)

返回 x 中的整数和小数部分。其第二个返回值始终是浮点数。

math.pi

π的值

math.rad (x)

将 x 度的角转换为弧度表示。

math.random ([m [, n]])

如果没有参数传入,则返回一个在 [0,1) 区间内的伪随机浮点数。如果将 m 和 n 两个参数传入整数,则 math.random 会返回一个处在 [m,n) 区间内的伪随机整数。当代码 math.random(n) 中的 n 为负数时,其等效于math.random(1,n) 。当传入零时, math.random(0) 的结果为一个整数,该整数中的每个位都是为随机的。

math.randomseed ([x [, y]])

如果调用时至少传入了一个参数,那么整数参数 x 和 y 将被放进一个128位(源码内部其实是个lua_Unsigned[4],即一个256位数,其中 x 和 y 相间放入其中,其他位由1或0填充,然后会在每次生成随机数之后变更数据,所以可以当作128位理解)的随机种子 seed 以重新初始化伪随机生成器;相同的随机种子会生成的一系列随机数也是相同的。参数 y 默认为0。

如果调用时省略所有参数,Lua将会简单随机生成一个种子(目前源码中的算法是将当前时间戳和当前Lua状态机的内存地址合并为一个128整数作为随机种子)。

该函数的返回值是两个整数,它们最后所设置的随机种子的值的组成部分(128位中的两个64位整数),再次设置相同的值会重复生成随机数列。

为了确保随机生成器的初始状态足够得随机(或者是相反的情况,例如在调试时,你需要固定随机),你应当显式地传入参数来调用math.randomseed

math.sin (x)

返回 x 的正弦(以弧度表示)。

math.sqrt (x)

返回 x 的平方根。(你也可以使用表达式 x ^ 0.5 来做等效的计算。)

math.tan (x)

返回 x 的正切(以弧度表示)。

math.tointeger (x)

如果 x 可以转换为一个整数值,那么返回该整数值。否则返回fail

math.type (x)

如果 x 是整数,则返回 "integer" 。如果是浮点数,则返回 "float"。如果 x 不是数字则返回fail

math.ult (m, n)

返回一个布尔值,表示将 m 和 n 做无符号整数比较,为true则表示 m 小于 n 。

6.8 - I/O操作

I/O库提供了两种不同风格的文件操作。第一种使用隐式的文件句柄;即有些操作可以设置默认的输入文件和输出文件,并且所有的I/O操作都会使用这些默认文件来执行。第二种就是使用显式的文件句柄了。

当使用隐式的文件句柄时,其所有的操作都由表 io 提供。使用显式的文件句柄时,io.open操作会返回一个文件句柄,该文件句柄中的方法提供了相关操作。

当文件句柄关闭时,会尝试调用其元表中的 __gc 和 __close 元方法。

io 表中还提供了三个预定义的文件句柄:io.stdin、io.stdout、和 io.stderr,其和C代码中的用法含义相同。I/O库永远不会关闭这几个文件。

除非另有说明,所有的I/O函数都会在失败的时候返回fail,再加上一个错误信息作为第二个返回值以及系统相关的错误码作为第三个返回值。在非POSIX系统中,在发生错误的时对错误消息和错误码的计算可能不是线程安全的,因为其由C代码中的全局变量 errno 而来。

io.close ([file])

等效于 file:close() 。如果参数 file 缺省,则会关闭默认的输出文件。

io.flush ()

等效于 io.output():flush() 。

io.input ([file])

当传入文件名来调用,会(以文本模式)打开对应的文件,并将该文件句柄作为默认输入文件。当传入文件句柄来调用时,会直接将其设为默认输入文件。不带参数调用则会返回当前的默认输入文件。

发生错误的情况下会抛出错误,而不是返回错误码。

io.lines ([filename, ···])

以读模式打开相应的文件,并返回一个迭代器函数,用以像 file:lines(···) 一样遍历文件内容。当这个迭代器函数读取失败,其会自动关闭该文件。除了返回迭代器函数,io.lines 还会额外返回三个值:两个作为占位符的nil值,加上所创建的文件句柄。因此,当将其放在for循环中时,循环被错误或break语句打断后,该文件也会关闭。

缺省文件名调用 io.lines() 等效于 io.input():lines("l") ;即迭代默认输入文件的行。在这种情况下,迭代器不会在循环结束时关闭文件。

打开文件错误的情况下,该函数会抛出错误,而不是返回错误码。

io.open (filename [, mode])

该函数会以字符串参数 mode 所指定的模式打开参数 filename 所对应的文件。成功的情况下,其会返回一个新的文件句柄。

参数 mode 可以是以下内容:

  • r: 读模式(默认模式)。
  • w: 写模式。
  • a: 追加模式。
  • r+: 更新模式,保留之前的数据。
  • w+: 更新模式,删除之前的数据。
  • a+: 追加更新模式,保留之前的数据,只允许在文件末尾写入。

字符串参数 mode 可以用一个 'b' 做结尾,在某些系统系统中以二进制模式打开文件是必须的。

io.output ([file])

类似于io.input,但操作的是默认输出文件。

io.popen (prog [, mode])

该函数依赖于系统,其不是在所有系统上都可用的。

在单独的进程中启动 prog 程序,并返回一个文件句柄,你可以使用该句柄来从这个程序中读取数据(如果参数 mode 为 "r" ,这也是默认值),或写入数据(如果参数 mode 为 "w")。

io.read (···)

等效于 io.input():read(···) 。

io.tmpfile ()

成功的情况下,会返回临时文件的句柄。该文件是以更新模式打开并且会在程序结束时自动删除掉。

io.type (obj)

检查对象 obj 是否为一个有效的文件句柄。如果 obj 是一个打开的文件句柄,则返回字符串 "file"。如果是一个关闭的文件句柄,则返回 "closed file" 。或者不是文件句柄则返回fail

io.write (···)

等效于 io.output():write(···) 。

file:close ()

关闭文件 file 。注意文件会在其句柄被GC后自动关闭,但其关闭的时刻是不确定的。

当关闭一个由io.popen创建的文件句柄时,file:close会使用os.execute的返回值。

file:flush ()

将数据保存到文件 file 中。

file:lines (···)

返回一个迭代器函数,其每次调用都会按照给出的格式来读文件。如果没有给出格式,则默认使用 "l" 。例如:

for c in file:lines(1) do body end

这样的代码将会从当前位置遍历文件的所有字符。与io.lines不同,该函数不会在循环结束后关闭文件。

file:read (···)

按照给出的格式读取文件 file 的指定内容。对于每种格式,该函数会返回所读取的字符串或数字,或者不能按照指定格式读取数据时返回fail。(在后边的情况中,该函数不会读取后续的格式。)当不传入参数调用时,会使用默认的格式来读取下一行(如下所示)。

可用的格式有:

  • "n": 读取一个数字并作为整数或浮点数返回,其遵循Lua的词法规则。(数字前边可能有空格和符号。)该模式总是读取尽可能长的数字序列;如果没有有效的数字前缀(如,空字符串、"0x"、或者"3.4e-")或者实在太长(超过200个字符),则会放弃读取并返回fail
  • "a": 从当前位置读取整个文件。如果当前位置在文件末尾,则会返回一个空字符串,该格式永远不会失败。
  • "l": 读取下一行并忽略换行符,到达文件末尾时会返回fail。这是默认的读取格式。
  • "L": 读取下一行并保留换行符(如果有的话),到达文件末尾时会返回fail
  • number 读取若干字节的字符串,到达文件末尾时会返回fail。如果 number 为零,则什么都不会读取并返回空字符串,或者刚好在文件末尾时返回fail

"l" 和 "L" 格式只能对文本文件使用。

file:seek ([whence [, offset]])

设置并获取文件位置,所返回的位置表示为相对于文件开头偏移,而给出的位置由基于字符串参数 whence 所指的的位置的偏移 offset ,whence 字符串内容可选:

  • "set": 基于零点位置(文件开头)。
  • "cur": 基于当前位置。
  • "end": 基于文件末尾。

成功的情况下,seek 会返回最终的文件位置,其表示为为相对于文件开头的字节偏移量。如果 seek 失败了,则返回fail,以及描述错误的字符串。

参数 whence 默认为 "cur",offset 默认为0。因此,执行 file:seek() 会返回当前的文件位置而不会改变它;执行 file:seek("set") 会将当前位置设置到文件开头(并返回0);执行 file:seek("end") 会将当前位置设置到文件末尾并返回文件的大小。

file:setvbuf (mode [, size])

设置文件的缓冲模式。有三种可用模式:

  • "no": 无缓冲。
  • "full": 全缓冲。
  • "line": 行缓冲。

对于后两种情况,size 用于指定缓冲区字节大小。其默认是个适当的大小(目前源码中是一个预定义的值,其为 16 X 所处平台的指针大小 X lua_Number大小,一般为1024,即1KB)。

每种模式所指的具体行为是不可移植的,更多细节请查看所处平台的ISO标准C中的 setvbuf 函数。

file:write (···)

将接收到的每个参数都写入到文件 file 中。所传参数必须是字符串或数字。

成功的情况下,该函数返回 file 本身。

6.9 - OS相关操作

本库的通过 os 表实现。

os.clock ()

返回当前程序所使用的CPU时间的近似秒数表示,底层使用的是ISO标准C函数 clock 。

os.date ([format [, time]])

返回包含了日期和时间的字符串或表,其按照所给字符串参数 format 来格式化的。

如果传入了参数 time ,则使用这个时间来格式化(关于该值的描述请参见os.time)。否则,date 会使用当前时间来格式化。

如果 format 参数由 '!' 开头,那么日期会使用协调世界时格式化。在该选项字符的后边,format 内容为 "*t" 时,date 会返回带有这些属性的表:year、month (1–12)、day (1–31)、hour (0–23)、min (0–59)、sec (0–61,因为有闰秒)、wday (星期几,1–7,周日为1)、yday (本年中的第多少天,1–366)、以及 isdst (布尔值,其为夏令时标志)。如果没有可用的相关信息,最后一个属性可能会缺省。

如果 format 参数不为 "*t",那么 date 会返回表示日期的字符串,格式化规则同ISO标准C函数 strftime 。

如果 format 缺省,则默认为 "%c",其会给出一个人类可读的日期,并且使用本地的表示方法。

在非POSIX系统上,该函数可能并不是线程安全的,因为其依赖于C函数 gmtime 和 localtime 。

os.difftime (t2, t1)

返回时间 t1 和 t2 秒数之间的差距(此处的时间为os.time所返回的结果)。在POSIX、Windows、以及其他的系统上,该值就是 t2-t1 。

os.execute ([command])

该函数等效于ISO标准C函数 system 。其通过操作系统的 shell 来执行传入的 command 参数。如果命令成功结束则第一个结果会返回true,否则返回false。在第一个返回值后该函数会返回一个字符串加上一个数字,如下所示:

  • "exit": 命令正常结束;后边跟一个表示命令退出状态码的数字。
  • "signal": 命令因为收到一个系统信号结束;后跟的数字是结束这个命令的信号。

如果缺省了 command 参数,那么 os.execute 则会返回一个布尔值,该布尔值为true时表示系统的 shell 可用。

os.exit ([code [, close]])

调用ISO标准C函数 exit 以结束宿主程序。如果 code 参数为true,则会返回状态码 EXIT_SUCCESS ;如果 code 参数为false,则会返回状态码 EXIT_FAILURE ;如果 code 参数是一个数字,则会将这个数字作为状态码返回。code 默认为 true

当可选参数 close 为真值是,该函数会在退出前关闭Lua状态机(参见 lua_close)。

os.getenv (varname)

获取当前进程中名为 vername 的环境变量,如果变量未定义则返回fail

os.remove (filename)

删除给定名称相应的文件(或者在POSIX系统中可以删除空目录)。如果函数执行失败,则返回fail加上一个描述错误的字符串以及错误码。否则返回true

os.rename (oldname, newname)

将名为 oldname 的目录或文件重命名为 newname 。如果函数执行失败,则返回fail加上一个描述错误的字符串以及错误码。否则返回true

os.setlocale (locale [, category])

当前程序的本地设置。参数 locale 是一个描述了具体设置的与系统相关的字符串;参数 category 是一个字符串选项,用以描述改变哪个本地项,各项为: "all"、"collate"、"ctype"、"monetary"、"numeric"、或是 "time";默认值为 "all"。该函数会返回新的本地项名称,或者在修改申请不能通过时则会返回fail

如果 locale 参数是一个空字符串,则当前的本地项会被设为一个提前实现定义好的原生本地项。例如当 locale 是字符串 "C",则当前的本地项被设置标准C设置。

当第一个参数为nil时,该函数仅仅返回当前本地项中所给类的名称。

该函数可能不是线程安全的,因为其依赖于C函数 setlocale 。

os.time ([table])

不带参数调用该函数时会返回当前时间。或者可以根据给定的参数数来使得返回表示当地日期和时间。此处作为参数的表必须年月日含有字段—— year、month 和 day,或者可选包含字段 hour(默认为12)、min(默认为0)、sec(默认为0)、以及 isdst(默认为nil)。其他的字段会被忽略。对于这些字段的相关描述,请参见os.date函数。

调用函数时,这些字段的值不用必须在其有效范围之内。例如,当 sec 为 -10 时,其表示提前于其他字段所表示的时间10秒;如果 hour 为1000,则表示提前于其他字段所表示的时间1000个小时。

返回值是一个数字,其含义取决于你的系统。在 POSIX、Windows 以及一些其他的系统上,这个数字表示从某些给定时间("epoch")开始的秒数。在其他系统上,其没有用以表示的含义,该数字只是由 time 函数所返回,可用于作为os.dateos.difftime的参数。

当传入一个表来调用该函数时, os.time 中使用的所有字段也遵循和os.date函数中所描述中的规范,所以它们都表示在调用前相同的时间,但是取值于各自的有效范围。

os.tmpname ()

返回一个字符串,其可以用作临时文件的文件名。该文件必须在使用前显式地打开,并且当再也不需要时显式地删除。

在POSIX系统中,该函数还会创建同名文件,以避免冲突的风险。(也许同时有其他地方在创建这个文件。)你仍然需要打开这个文件并删除它(直到你不在用它为止)。

如果可以的话,你也许更喜欢用io.tmpfile,其会在程序结束时自动删除文件。

6.10 - 调试库

该库提供了关于Lua程序的调试接口函数。你应当在使用该库时额外小心。这些函数破坏了Lua代码的基本假设(例如,某个函数内的局部变量不可以在外部访问;userdata 的元表不可以在Lua代码中改变;Lua程序不会crash等)并且它们会破坏其他的安全代码。此外,该库中的一些函数可能很慢。

该库中的所有函数都提供在 debug 表中。所有对Lua线程做操作的函数中的第一个参数都是需要操作的Lua线程。其默认为当前线程。

debug.debug ()

进入可交互模式,运行用户输入的每一个字符串。使用简单的命令和其他调试工具,用户可以查看全局或本地变量、改变它们的值、计算表达式等等。一行只包含单词 cont 的输入会结束这个函数,所以调用者代码会在此处之后继续执行。

注意 debug.debug 的命令在词法上没有嵌入到任何函数中,所以你无法通过本地变量直接访问。

debug.gethook ([thread])

返回当前设置在给出Lua线程中的 hook ,有三个值:当前 hook 的函数、当前 hook 的掩码、当前 hook 的数量,同debug.sethook函数。

如果没有活跃的 hook 则返回fail

debug.getinfo ([thread,] f [, what])

返回带有某个函数的相关信息的表。你可以直接给定一个函数,或者你可以给定一个数字作为参数 f ,意思是运行在给定Lua线程的调用栈的第 f 层的函数:0层为当前函数(getinfo 函数自身);1层为调用 getinfo 的函数(尾调用除外,其不在调用栈计数中);以此类推。如果 f 是一个大于活跃数量的数字,则 geinfo 返回fail

这里返回的表中所能包含的字段同lua_getinfo,字符串参数 what 描述了那些要填充的字段。参数 what 默认获取所有可用的信息,除了有效行的表。如果有选项 'f' 则会将该函数自身添加到一个名为 func 的字段上。如果有选项 'L' 则会将有效行的表添加到名为 activelines 的字段上。

例如,表达式 debug.getinfo(1,"n").name 会返回当前函数的名称,如果可以找到合理的名称的话。表达式 debug.getinfo(print) 则会返回带有print函数所有相关信息的表。

debug.getlocal ([thread,] f, local)

返回在某个函数中 local 索引处的局部变量的名称和值,该函数位于调用栈上的第 f 层。该函数不但可以访问本地变量,也可以访问闯入的参数和临时变量。

第一个参数或本地变量的索引是1,以此类推,往后的顺序都依据代码中的声明先后而来,此处只计数函数的当前作用域中的活跃变量。编译时的常量可能并没有加在声明列表中,其会被编译器优化掉。负数索引引用了可变参数:-1表示第一个可变参数。当给出索引处没有变量时该函数会返回fail,并在超出调用层数时抛出一个错误。(你可以调用debug.getinfo来检查给定的层数是否有效。)

由 '(' (开圆括号)打头的变量名表示变量的名称未知(例如像循环控制变量之类的内部变量,以及代码块中没有调试信息的变量)。

参数 f 也可以是个函数。在这种情况下,getlocal 只返回该函数中各个参数的名称。

debug.getmetatable (value)

返回给定 value 的元表,没有元表则返回nil

debug.getregistry ()

返回注册表(参见4.3)。

debug.getupvalue (f, up)

返回在函数 f 中索引为 up 的 upvalue 的名称和值。不存在相应 upvalue 则返回fail

(对于Lua函数,upvalue 是在该函数中使用的外部局部变量,随之包含在该闭包中。)

对于C函数,该函数使用空字符串 "" 作为所有 upvalue 的名称。

变量名 '?'(问号)表示该变量的名称未知(代码块中不带调试信息的变量)。

debug.getuservalue (u, n)

返回给出的 userdata 参数 u 对应的第 n 个 user value ,以及一个布尔值,如果没有相应的 user value 时,这个布尔值为false

debug.sethook ([thread,] hook, mask [, count])

设置调试 hook 。字符串参数 mask 和数字参数 count 表明了触发 hook 的时机。mask 可以由以下字符组合而成,其各含义为:

  • 'c': 每次发生函数调用时触发 hook 。
  • 'r': 每次发生函数返回时触发 hook 。
  • 'c': 每次执行到下一行代码时触发 hook 。

此外,当 count 不为零时,该 hook 会在每执行 count 条指令后触发。

不带参数调用该函数时,会关闭 hook 。

每当 hook 被触发,其第一个传入的参数为描述了触发事件的字符串:"call"、"tail call"、"return"、"line"、和 "count"。对于行事件,还会将新行的行数作为 hook 的第二个参数传入。在 hook 内部,你可以对第 2 层调用 getinfo 来获取关于当前函数更多的信息。(第0层是 getinfo 函数本身,第一层是这个 hook 函数。)

debug.setlocal ([thread,] level, local, value)

将处于调用栈的第 level 层的函数中索引为 local 的局部变量赋值为 value 。如果没有相应的局部变量,该函数会返回fail,并且如果当 level 超出了调用层数,则会另外抛出一个错误。(你可以调用 getinfo 以确认层数是否有效。)否则,其会返回局部变量的名称。

更多关于变量索引和名称的信息请参考debug.getlocal

debug.setmetatable (value, table)

将给定的 value 的元表设置为给定的 table(可以是个nil)。并返回 value 本身。

debug.setupvalue (f, up, value)

该函数会将函数 f 中索引为 up 的 upvalue 设置为给定值 value 。当不存在相应的 upvalue 时会返回fail。否则会返回上值的名称。

更多关于上值的信息请参考debug.getupvalue

debug.setuservalue (udata, value, n)

将给定的 udata 中的第 n 个 user value 设为 value 值。参数 udata 必须是一个 full userdata 。

返回 usdata,或者当不存在相应的 userdata 和 user value 时返回fail

debug.traceback ([thread,] [message [, level]])

如果 message 未缺省并且既不是个字符串或nil,该函数不会做进一步处理并直接返回 message 。否则会返回一个带上了这个 message 的调用栈回溯信息。可选字符串参数 message 回被加到回溯信息的开头。可选数字参数 level 制定了从调用栈的哪一层开始回溯(默认为1,是调用 traceback 的函数)。

debug.upvalueid (f, n)

返回给定函数中的第 n 个上值的唯一索引(是个 light userdata)。

这个唯一索引使得程序可以分辨不同的闭包是否共享了某个上值。对于Lua闭包所共享的某个上值(即访问同名的外部局部变量)将返回相同的索引。

debug.upvaluejoin (f1, n1, f2, n2)

使得Lua闭包 f1 中的第 n1 个上值引用Lua闭包 f2 中的第 n2 个上值。

7 - 单独使用的Lua

尽管Lua被设计为一个扩展语言,用以嵌入在宿主C程序中,但其也经常当作单独的语言使用。Lua的解释器就是一个单独的语言,这里简称为 lua ,其随着标准发行版本一起提供。这个单独的解释器包含了所有标准库。其用法为:

lua [options] [script [args]]

选项如下:

  • -e stat 执行字符串 stat
  • -i: 在运行脚本后进入交互模式。
  • -l mod "require" mod 模块并将结果赋值到全局变量 mod 中。
  • -l g=mod "require" mod 模块并将结果赋值到全局变量 g 中。
  • -v: 打印版本信息。
  • -E: 忽略环境变量。
  • -W: 打开警告。
  • --: 停止处理后边的选项。
  • -: 将 stdin 作为文件来执行并停止处理后边的选项。

(-l g=mod 选项从5.4.4版本才引入的。)

在处理其选项后, lua 会运行给定的脚本 scipt 。如果不带任何参数来运行 lua ,那么当其标注输入(stdin)是终端时等效于运行 lua -v -i ,否则等效于运行 lua - 。

如果没有传入选项 -E ,那么解释器会在运行其他参数前检查环境变量 LUA_INIT_5_4 (或者在没有定义版本名时检查 LUA_INIT )。如果环境变量的内容是 @filename 形式的,那么 lua 会执行相应文件。否则,lua 只执行其本身的内容。

如果传入了选项 -E ,那么Lua不会查询任何环境变量。在这种特殊情况下,对于package.pathpackage.cpath会置为定义在 luaconf.h 中的值。

选项 -e、-l、以及 -W 会被按序处理。例如,像这样的命令:

$ lua -e 'a=1' -llib1 script.lua

会先将 a 设为 1,然后再导入库 lib1 ,最后再不带任何参数地运行脚本 script.lua 。(这里的 $ 是 shell 的提示符,你的 shell 中也许会不一样。)

在运行代码之前,lua 会将所有的命令行参数收集到一个名为 arg 的全局表中。脚本名在表中的索引0处,第一个参数在索引1处,以此类推。任何在脚本文件名之前的命令行参数会是负索引。例如:

$ lua -la b.lua t1 t2

中的 arg 表会像这样:

arg = { [-2] = "lua", [-1] = "-la",
        [0] = "b.lua",
        [1] = "t1", [2] = "t2" }

如果命令中没有脚本名,则解释器的名字会放到索引0处,后边再跟其他的参数。例如:

$ lua -e "print(arg[1])"

将会打印 "-e" 。如果有脚本,那么这个脚本会带上参数 arg[1], ···, arg[#arg] 被调用。和所有Lua中的代码块一样,脚本会被编译为一个可变参数函数。

在交互模式中,Lua会反复打印和等待行输入。在读取了一行后,Lua首先尝试将其翻译为表达式。如果成功了,则打印表达式的值。否则,将其翻译为语句,如果你写了一个不完整的语句,那么解释器会用另一个提示符号来提示你将其完成。

如果全局变量 _PROMPT 包含一个字符串,那么这个字符串值会被用以作为提示符。类似的,如果全局变量 _PROMPT2 包含一个和字符串,其会被作为第二提示符(提示未完成语句)。

脚本中发生了不在保护模式下的错误时,解释器会将错误报告到标准错误输出流。如果错误对象不是个字符串但是有元函数 __tostring,那么解释器会调用这个元函数来产生最终的作物信息。否则,解释器会将错误对象转换为一个字符串并加上一个栈回溯信息。当警告打开时,只会简单的打印到标准错误输出中。

当正常结束时,解释器会关闭Lua主状态机(参见lua_close)。脚本可以通过调用os.exit来结束,以避开这个步骤。

在Unix系统中为了使得Lua可以作为一个脚本解释器,Lua会跳过文件代码块中以 # 号开头的第一行。因此,Lua脚本可以通过使用 chmod +x 命令和将 #! 作为开头,以使得其可以作为一个可直接执行的程序:

#!/usr/local/bin/lua

当然,在你的机器中,Lua解释器也许不在当前这个位置。如果 lua 在你的 PATH 中的话,那么:

 #!/usr/bin/env lua

是一种更通用的解决方式。

8 - 版本兼容性相关

我们在这里列出了从Lua 5.3迁移到Lua 5.4的过程中你可能会遇到的兼容性问题。

你可以通过使用适当的选项来编译Lua以避免一些兼容性问题(参见 luaconf.h 文件)。然而,所有的这些兼容选项在将来都会被移除。通常情况下,这些选项被移除后就会出现兼容性问题了。所以在任何时候,你只要有机会,就应当尝试使用关闭了所有兼容选项编译某个版本的Lua中测试你的代码。这将会比较容易的迁移到新版本Lua。

Lua版本变化时通常会改变 C API 的内部实现方式,例如修改其常量定义或者是宏方法的实现。因此,你永远不该假设二进制文件在两个不同的Lua版本中是兼容的。使用新的版本时通常应该重新编译 Lua API 的客户端程序。

类似的,Lua版本变化会改动预编译代码块的内部表示;预编译代码块在不同的Lua版本中也是不兼容的。

不同版本之间的官方发行版本中标准路径可能也会变化。

8.1 - 语言中的兼容性问题

  • 算术运算和位运算中对字符串到数字的强制转换已经从语言核心特性中移除。对于算术运算(位运算没有)字符串库中可以使用字符串元函数来完成相似的任务。然而,与之前的版本不同,新版实现中保留了字符串中的隐式数字类型。例如,"1" + "2" 的结果是个整数,而不是浮点数。
  • 溢出的字面十进制整数常量会被作为浮点数读取,而不是做环绕。如果你想要产生之前的行为(环绕),你可以使用十六进制来表示这种常量。
  • 用 __lt 元方法来逼近 __le 的做法已经被溢出。需要用到该元函数时,该元函数必须显式地定义。
  • 数字形式的for循环的语义有一些变化。特别是控制变量不会再做环绕了。
  • 不可以在相同的可见范围内声明重名的goto标签,即时另一个标签是在封闭的语句块中声明的。
  • 终结某个对象的时候,Lua不会忽略 __gc 元函数并非时个函数的情况。任何值都将被调用,只要它存在。(和其他调用终结器时发生的错误一样,不可调用的值将会产生一个警告。)

8.2 - 库中的兼容性问题

  • print函数不会调用tostring来格式化其参数,而是有硬实现的方法。你应当使用 __tostring 来改变某个值的打印方式。
  • math.random中使用的伪随机数生成器现在会从一个有些随机的随机种子开始。此外,其使用了不同的算法。
  • 默认情况下,utf8库中的解码函数不在接受UTF-16中的代理项作为有效码点。如果需要宽松些的码点判断,可以使用相关函数的额外参数(lax)。
  • collectgarbage函数中的 "setpause" 和 "setstepmul" 选项已经被弃用。你应当使用新选项 "incremental" 来设置它们。
  • io.lines函数现在会返回四个值,而不是一个。将该函数的返回值作为另一个函数的唯一参数使用时会有问题,例如 load(io.lines(filename, "L")) 。为了修复这样的错误,你可以将其用括号括起来,以确保其只会传出一个结果。

8.3 - API中的兼容性问题

  • full userdat 现在可以有任意数量的 user value 。因此,lua_newuserdata 、lua_setuservalue、以及 lua_getuservalue 函数都被替换为lua_newuserdatauvlua_setiuservalue、以及lua_getiuservalue,这些函数都有额外参数。
  • lua_resume函数有了额外参数。由协程所让出或返回的值可以作为若干个参数传入进去。
  • lua_version函数会返回版本数字,而不是版本数字的地址。Lua核心应该能够正确地与使用相同核心的静态副本的库一起工作,因此无需检查它们是否使用相同的地址空间。
  • 常量 LUA_ERRGCMM 被移除。在终结器中发生的错误永远不会传播,而是只会生成一个警告。
  • lua_gc函数的 LUA_GCSETPAUSE 和 LUA_GCSETSTEPMUL 选项被弃用了。你应该使用新选项 LUA_GCINC 来设置它们。

9 - Lua完整语法

以下是用BNF扩展形式来展示的Lua完整语法。和常见的BNF扩展形式一样,{A} 意为0个或多个A、[A]意为一个可选的A。(关于运算符优先级,请参见3.4.8;关于名称、数字、以及字面量字符串的描述,请参见3.1。)

chunk ::= block

block ::= {stat} [retstat]

stat ::=  ‘;’ | 
	 varlist ‘=’ explist | 
	 functioncall | 
	 label | 
	 break | 
	 goto Name | 
	 do block end | 
	 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 attnamelist [‘=’ explist] 

attnamelist ::=  Name attrib {‘,’ Name attrib}

attrib ::= [‘<’ Name ‘>’]

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 | ‘#’ | ‘~

Releases

No releases published

Packages

No packages published