Lua 5.3手册解读 基本概念(二)

  1. 语法

其实个人感觉这块没啥好翻译的,除非要研究Lua编译器,否则语法上用一段时间Lua就熟了,Lua的语法很简单,很自由的。

3.1 词法规定

Lua关键字列表:

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

3.2 变量

3.3 语句

3.3.1 代码块

代码块就是一系列顺序执行的语句。

3.3.2 Chunks

Lua编译的单位叫Chunk,语法上就是一个代码块。Lua把chunk当作是一个可变参数列表的匿名函数来处理。Chunk编译时永远都有个upvalue_ENV,不管这个Chunk是否用了_ENV。chunk可以来自文件或者字符串,分别对应不同的加载(编译)函数。Lua执行要给Chunk的流程是:先加载,然后预编译成Lua虚拟机可识别的指令,最后Lua解释器会执行这些编译好的字节码(byte code)。

Chunk的预编译结果也可以是二进制形式,Lua官方提供了一个Luac,另外还有string.dump函数也可以做到。

3.3.3 赋值

Lua支持多重赋值,本着“多退少补”的原则。

3.3.4 控制结构

stat ::= while exp do block end
stat ::= repeat block until exp
stat ::= if exp then block {elseif exp then block} [else block] end
stat ::= goto Name
stat ::= break
stat ::= return [explist] [‘;’]

3.3.5 For语句

for v = e1, e2, e3 do block end

展开是

do
   local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
   if not (var and limit and step) then error() end
   var = var - step
   while true do
     var = var + step
     if (step >= 0 and var > limit) or (step < 0 and var < limit) then
       break
     end
     local v = var
     block
   end
 end

3.3.6 函数调用语句

3.3.7 局部变量声明

stat ::= local namelist [‘=’ explist]

3.4 表达式

省略大量简单内容,有一个东西挺有意思的,之前没注意过。 就是下边两个函数定义是有细微区别的:

local function f1() f1 = 1 end

local f2 = function() f2 = 2 end

因为第一种定义方式实际上等于:

local f1; f1 = function() f1 = 2 end

区别就是第一种是先定义了一个外部的f1,然后定义了一个函数,注意这个函数是有f1这个上层变量的,然后把这个函数赋值给外部的f1变量。所以f1()之后,外部的local变量f1就不再引用原来的函数了,而是变成了1。第二种则没有这个问题,第二种等于是将全局变量f2赋值为2。

3.5 可见性规则

基本同C一样,不赘述。

4 API接口

4.1 栈

Lua和C的交互是通过一个虚拟的栈来实现的,栈里的每个元素表示一个Lua的值。

Lua调用C函数会创建一个新的栈,这个栈以Lua传给C函数的参数初始化,C函数的返回值也是通过这个栈返回给Lua。Lua栈的索引可正可负,正数就是从底往上数,负数是从上往底数,所以-1代表栈顶第一个元素,也就是最后一个入栈的元素。

4.2 栈大小

使用Lua API的时候使用者要负责栈的安全,保证不要栈溢出。lua_checkstack函数可以检查栈是否有足够的空间。Lua调用C函数的栈的默认空间至少是LUA_MINSTACK,这个值是20。

4.3 合法和可接受索引

所有API接受的栈索引只能是合法索引。合法索引的范围即1-n(栈元素数量)外加预定义索引。预定义索引是那些可以被C访问的但是不在栈里的“位置”。一般用来访问Lua注册表和C函数的upvalue。可接受索引是不需要检查是否在栈顶范围之内的。

4.4 C闭包

创建一个C函数的时候,可能有关联一些值,这个函数体和这些值共同构成闭包,这种情况下这些值叫做upvalue。这些upvalue放在预定义索引的位置上,通过宏lua_upvalueindex产生这些预定义索引。第n个upvalue对应的预定义索引就是lua_upvalueindex(n)(n<=256),这种索引是可接受但非法索引。

4.5 注册表

Lua提供了一个预定义的注册表,可以让C代码存储任何想存的Lua的值。注册表可以通过预定义索引LUA_REGISTRYINDEX访问到,C代码可以往注册表里存值,但是为了避免冲突,必须要小心选择key,通常可以用一个字符串或者一个lightuserdata。不要使用整数的key。每当创建一个Lua state后,都会自动创建一个main thread,自动创建一个注册表,而且这个注册表会有一些预定义的值,可以在lua.h里查到。

4.6 C中的错误处理

Lua内部使用C的longjump机制来处理错误。如果Lua遇到一个错误,比如内存申请失败或者什么运行期错误之类的,Lua就会执行一个longjmp。所谓的保护模式运行就是事先用setjmp设置好了一个恢复点。如果在保护模式外发生了一个错误,那么Lua会调用一个panic函数然后终止程序,这个panic函数是可定制。

4.7 C中的yield

Lua内部使用longjmp机制来实现挂起一个协程,所以如果一个C函数foo调用了一个API函数,然后这个函数调用过程中挂起了,那么Lua回不到foo了,因为栈帧被longjmp删除了。为了处理这种情况,Lua引入了一个“持续函数”的概念。

4.8 函数和类型

这里是所有C API的函数列表,右上角有个标示[-o,+p,x]。o表示这个函数会从Lua栈弹出多少个元素,p表示这个函数会压入栈多少个元素,x表示这个函数是否会抛出错误,-表示不会,e表示有可能会,v表示这个函数可能估计产生一个错误。

具体列表这里就不罗列了,这部分就是API手册,有些常用的用多了就熟了。

4.9 调试接口

Lua没有内置的调试工具,而是提供了一些接口和hook机制,利用这些可以构建出各种调试器,性能分析工具等。

5 辅助库

Lua中以luaL_开头的都是这类辅助API,主要是利用已有API封装组合了一些经常使用的方便的API。

6 标准库

标准库提供了一些直接通过C的API实现的语言内置的函数,对于日常使用Lua来说非常好用,和其他语言的标准库类似,Lua标准库大概有以下几类:

  • 基础库
  • 协程库
  • 模块库
  • 字符串库
  • UTF-8支持相关
  • 表相关
  • 输入输出库
  • 操作系统相关
  • 调试库

具体某个库的相关API和功能介绍请查阅官网manual,这里不赘述。

7 Lua独立程序

尽管一般Lua是嵌入到C宿主程序中运行,但有时候也会独立使用。标准发行版里就提供了一个解释器。用法如下:

lua [options] [script [args]]

options有:

  • -e stat:执行stat字符串
  • -l mod:require mod
  • -i:执行完脚本后进入交互模式
  • -v:打印版本信息
  • -E:忽略环境变量
  • --:不处理options
  • -:从file读取stdin,不处理options

解释器先处理options然后运行脚本,默认options是-v -i。

8 与上个版本的兼容性

8.1 语言上的变化

  • Lua 5.2和5.3主要的变化是number上的类型,5.3引进了子类型integer,在5.2所有的number都是float类型,在5.3则不一定。
  • float转string的时候现在会加一个.0后缀,如果这个float看起来会和integer混淆的话。比如float 2.0 print出来会是2.0,而不是2。
  • 分代gc模式移除了,在lua5.2中它也是个实验性的功能。

8.2 库函数的变化

  • bit32库废弃了。
  • table库现在关心设置和获取元素的元方法了。
  • ipairs迭代器现在关心元方法了,而且__ipairs元方法废弃了。
  • io.read方法的"*"选项没用了。
  • 数学库去掉了一些方法。
  • require对待版本号的查找方法变化了。
  • collectgarbage("count")现在只有一个返回值了。

8.3 API的变化

  • 协程函数现在接受一个参数用来做luagetctx,所以luagetctx废弃了。
  • lua_dump多了一个额外的参数,不需要设置的话请使用0。
  • 关于unsigned integer的注入和导出函数都废弃了。
  • luaL_checkint,luaL_checklong,luaL_optint,luaL_optlong都废弃了。