Lua 5.3 拾遗——语言定义

对照: Lua 5.3 参考手册 、Lua 5.3参考手册(英文原文)

上一篇:Lua 5.3 拾遗——基本概念

红色表示关键点

绿色表示个人注解

------------------------------------------------------------------------

一. 词法约定

        Lua 会忽略语法元素间的空格和注释, 仅作为分割符。NRatel表示,还是要有良好的代码风格啊。

        标识符用来对变量、表中字段(field)、标签 等命名。以非数字开头,由字母、下划线和数字组成,对大小写敏感(不同大小写代表不同标识符),不能使用Lua关键字。避免使用下划线+大写字母开头(一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量)。

        Lua 的关键字有:and、break、do、else、elseif、end、false、for、function、goto、if、in、local、nil、not、or、repeat、return、then、true、until、while。

        单行字符串 由单引号或双引号括起。 可以包含下列 C 风格的转义串: '\a' (响铃), '\b' (退格), '\f' (换页), '\n' (换行), '\r' (回车), '\t' (横项制表), '\v' (纵向制表), '\\' (反斜杠), '\"' (双引号), '\'' (单引号),'\z' (忽略其后的一系列空白符,包括换行。实测没起作用),。 

        在反斜杠后跟一个真正的换行等价于 '\n' 。

扫描二维码关注公众号,回复: 4147379 查看本文章

        字符串中的单个字符 可以用数字值来表示。 两种方式。1),转义串 \xXX \x + 两位16 进制数 )。 2),转义串 \ddd  ( \ + 三位10进制数)。 如果在转义符后恰好是一个数字,那么字符的数字值应写满三个数字,不足三位的在前面补0。对于用 UTF-8 编码的 Unicode 字符,用 转义符 \u{XXX} 表示 (( \u +{三位16 进制数} ),注意这里必须有一对花括号)。

        多行字符串由N级长括号(两个方括号间插入N个等号, 如 0级:[[字符串内容]]、1级: [=[字符串内容]=] ) 括起。可以选用任何一级的长括号,但必须前后匹配。多级的设计应该不是为了进行嵌套,字符串嵌套没有实际意义。 这种方式不受分行限制(可以直接换行),不处理任何转义符,并且忽略掉任何不同级的长括号(同级但不是结束的长括号也被忽略,都被当做了普通字符串内容)。 字符串内的的回车、换行、回车加换行、换行加回车,会被转换为单个换行符。紧接左长括号的第一个直接换行,不会被放在字符串内。

        Lua的 数字(numeral) ,可以是10进制或16进制。16进制以0x 或 0X 开头。16 进制也支持小数加指数的形式。以10为底的指数用 'e' 或 'E' 标记;以2位底的指数用'p' 或 'P' 标记(实际运行不一定支持)。包含小数点或指数的数是浮点数,否则是整数。

        单行注释使用双横线 (--) 开头。

        多行注释使用双横线 (--) 加上 N级左长括号(如 [=[) 开头,与之匹配的N级右长括号结尾 (如 ]=])。

二. 变量

        变量用于存值。一个标识符即代表一个变量。变量有三种:全局变量、局部变量和表中的字段(field)。

        没有使用 local 显式声明为局部变量的变量均是全局变量。局部变量有其作用域。

        变量被声明后的默认值是 nil。

        方括号被用来对表作索引,也可使用小数点进行索引(只是语法糖)。 例如: table["name"] 和 table.name。

        对表的字段进行索引访问的含义是可以通过元表进行干预的(就是定义__index元方法,重载索引操作)。

三. 语句

        3-1)语句块(blocks)

        语句块是一个按次序执行语句序列。

        Lua 使用 ;(分号)来分割语句。支持空语句,所以可以在语句块最开始写一个分号(认为第一条语句为空); 允许连写两个分号(两个分号间是空语句)。

        Lua 可以省略语句末尾的分号。但在有些情况下,必须以分号分割,避免歧义。如下:

--第二条语句的小括号被第一条语句当做了函数调用。--会报错
a = b + c   
(print("sth"))   

--方法 f 中的第三条语句被当成 return 的结果。--不会报错
local function f()
    print("sth1")
    return
    print("sth2")
end

        一个语句块可以用 do ... end 显式地定界为单条语句。通常以此来限定内部变量的作用域(使只在do...end中可见)。也用于在一个语句块中间插入 return Lua中,return 通常只能是语句块的最后一条语句。上面第二例中不会报错,就是因为实际上 return 是最后一条语句)。

        3-2)代码块(chunks)

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

        Lua 把一个代码块当作一个拥有不定参数的匿名函数来处理。 正是这样,代码块内可以定义局部变量、接收参数、返回若干值。此外,这个匿名函数在编译时,它的作用域会被绑定一个外部局部变量 _ENV。该函数总是把 _ENV 作为它唯一的一个upvalue, 即使它不使用该变量。什么是 外部局部变量upvalue? 如下:

local v = 0            //变量v 是 函数f 的 外部局部变量upvalue
local function f ()
    return "sth"
end

        代码块的执行过程是:首先,文件中 或 宿主程序中字符串形式保存的代码块被Lua 加载;然后,代码块中的代码预编译成虚拟机中的指令;最后,Lua 用虚拟机解释器来运行编译后的代码。

        代码块可以被预编译为二进制形式,并可与用源码表示的程序自由替换(编译为二进制形式可以隐藏明文的源代码,提高安全性)。Lua 会自动检测文件格式做相应的处理。

        3-3)赋值

        Lua 允许多重赋值。 规则:等号两边的 变量列表和表达式列表分别以逗号隔开。赋值前,会将右边列表中表达式可能产生多个值)的数量调整为与左边变量数量相同,多出的舍弃,不够的以 nil 补全。如下:

local x, y = 1+2, 3, 4 
local a, b = 5

print(x, y)        --舍弃多余右值,输出结果 3, 3        
print(a, b)        --补充默认右值,输出结果 5, nil

        当一个函数调用表达式出现在表达式列表最后时,它的返回值都会在调整前放入值列表。没有出现在表达式列表最后的,只会使用函数调用表达式返回的第一个值。被括号括起来的表达式永远被当作一个值。如下:

local function f()
  return 8, 9
end

local x, y, z = 1, f()
local a, b, c = f(), 2
local i, j, k = 1, (f())

print(x, y, z)    --表达式是函数调用,且出现在表达式列表最后,返回值均在调整前加入右值列表。输出结果:1, 8, 9
print(a, b, c)    --表达式是函数调用,没有出现在表达式列表最后,返回值只取第一个值,输出结果:8, 2, nil
print(i, j, k)    --表达式是函数调用,且出现在表达式列表最后,但被括号括起,返回值只取第一个值,。输出结果:1, 8, nil

        表达式运算的优先级高于赋值语句。

        多重赋值中,各个赋值运算的执行结果不相互影响(可认为是同时执行)。

local x, y = 1, 2
x, y = y, x

print(x, y) --交换了x, y的值,而不是都等于某个x或y的值,x=y 和y=x 的两个赋值运算的结果不相互影响。输出结果:2, 1。

        再次提及,对表的字段进行索引访问的含义是可以通过元表进行干预的(就是定义__index元方法,重载索引操作)。

        对于全局变量 x = val 的赋值等价于 _ENV.x = val 。(因为_ENV在生成时会将_G作为默认值

        3-4)控制结构

        控制语句包括: if 语句、while do-end 语句、repeat-until 语句、for语句、goto语句等。

        在控制语句的条件判断中,false与nil都是假,其他都是真(注意,数字 0和空字符串 "" 也是真)。

        repeat-until 语句中,repeat后语句块中定义的局部变量,对util后的条件表达式可见可用(不过一般很少很少这么定义吧)。

        goto语句将程序的控制点转移到一个标签处。Lua标签用双冒号包括起来(如 ::label::),由于句法原因也被认为是语句。(在windows下的Lua Development中,goto语句不可用。在其他语言中goto不建议使用。所以lua中也最好别用

        break被用来跳出while、 repeat、或 for 循环,只跳出最内层循环。Lua不支持 continue。

        return被用来从函数或是代码块(最终也是一个函数) 中返回值。可以返回多个值。只能被写在语句块的最后一句(除非写在 do-end中)。

        3-5)For语句

        for 有两种形式:一种是数字形式,另一种是通用形式。For循环可等价替换为while循环,可看做while循环的语法糖

        数字形式:需要指定var, limit, step(可选)三个表达式,表达式先于循环执行且结果必须都是数字。步长step正负均可,默认值是1。循环变量v仅在循环内部可见。

for var=start, limit, step do
    block
end

如:

for v=1, 10, 2 do
    print(v)
end

        通用形式:需要指定一个名称列表namelist 和 一个表达式列表explist 。explist 只会被计算一次,它的结果是一个迭代函数 f、一个状态常量s(实际从 pairs() 和 ipairs() 函数的返回值来看,应该是一个table) 和一个用于首次迭代的初始值。循环通过迭代函数f 工作,每次迭代,迭代函数f 被调用以产生一个新值, 直到值为 nil 时循环停止。名称列表namelist中的值仅在循环内部可见。

for namelist in explist
    block
end

如:

for k, v in pairs(table) do
    print(k, v)
end

        3-6)函数调用语句

        函数调用可以直接被当做一条语句执行,这种情况下,函数的返回值将被忽略和丢弃。

        3-7)局部声明

        局部变量可以在语句块中任何地方声明。 可以在声明局部变量时赋初始值。没有赋初值时,默认为 nil

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

四.表达式

        表达式包括:前缀表达式(prefixexp)、nil、boolean值(true/false)、数字(Numeral)、字符串(LiteralString)、函数定义(functiondef)、表构造(tableconstructor)、可变长参数('...')、双目运算式(exp binop exp)、单目运算(unop exp)、变量(var)、函数调用(functioncall)、括号括起来的运算式('('exp')')等。

        函数调用表达式和可变参数表达式都可导致多个值(都可用于多重返回值和多参数)。

        函数调用表达式被当做一条语句执行时,函数的返回值将被忽略和丢弃。

        函数调用表达式被用于表达式列表最后一个(或唯一)元素时,返回值/参数数量不会被调整,被括号括起除外(被括号括起来的表达式永远被当作一个值),其他情况下,Lua会把结果调整为一个元素置入表达式列表中, 即保留第一个结果而忽略之后的所有值,没有结果时以 nil 补足 。

        4-1)数学运算操作符

        Lua支持的数学运算操作符有:+加法、-减法、*乘法、/浮点除法、//向下取整除法、%取模、^乘方、-取负。

        运算规则:除了乘方(结果可能变成指数表示形式,指数形式也是浮点数)和除法(结果可能为小数),如果两个操作数都是整数, 结果也是整数。否则,只要有一个操作数为浮点数(或可被隐式转换为浮点数的字符串),结果即为浮点数。

        乘方和浮点除法 (/) 总是把操作数转换成浮点数进行,其结果总是浮点数。

        乘方本质使用标准 C 函数 pow(), 因此它也可以接受非整数的指数。

        向下取整的除法 (//) 指做一次除法,并将商近似取整到靠近负无穷的一侧(// 运算符不一定在实际运行环境中支持)(注意,向下取整不是简单的舍弃小数部分,结果总是小于等于原值,负值的向下取整勿错。),即对操作数做除法后进行 floor () 。

        对于整数数学运算的溢出问题, 这些操作采取的策略是按通常遵循的 二补数 运算规则。 (即返回结果对 2^64 取模后的数字。)(未理解

        4-2)位操作符

        Lua支持的数学运算操作符有:&按位与、|按位或、~按位异或、>>右移、<<左移、~按位非。

        所有的位操作都将操作数先转换为整数, 然后按位操作,其结果是一个整数

        对于右移和左移,均用零来填补空位。 移动位数若为负值,则向反方向位移。若最终向右移位,且位数大于等于整数本身的位数,则其结果为零 (因为所有位都被移出)。

        4-3)强制转换

        隐式转换:位操作总是将浮点操作数隐式转换为整数;乘方和浮点除法总是将整数转换为浮点数;含浮点数的非乘方和非除法操作将整数转换为浮点数;字符串连接操作将数字隐式转为字符串;数字操作将字符串隐式转为数字(前提是可转)。

        整数转为浮点数时,若整数值恰好可以表示为一个浮点数,那就取那个浮点数。否则,转换会取最接近的较大值或较小值来表示这个数。 这种转换不会失败。

        浮点数转为整数时,会检查浮点数能否被准确的表达为一个整数(即,浮点数是一个整数值且在整数可以表达的区间)。 如果可以,结果就是那个数,否则转换失败。(失败的情况如:较大的指数式浮点数装换为整数会溢出

        字符串转为数字时: Lua会分析词法,先转为笼统的数字类型,然后再转为具体所需的类型(浮点或整数)。字符串可以有前置或后置的空格以及一个符号(指的是正负号吧,实测其他符号不行)。。

        数字转为字符串时,使用非指定的人可读的格式(未理解)。使用字符串库的 format() 函数可控制数字到字符串的转换过程。

        4-4)比较操作符

        Lua支持的比较操作符有:==等于、~=不等于、<小于、>大于、<=小于等于、>=大于等于。

        这些操作的结果不是 false 就是 true

        等于操作 (==)先比较操作数的类型。 如果类型不同,结果就是 false。 否则,继续比较值。 字符串按一般的方式比较。 数字遵循二元操作的规则: 如果两个操作数都是整数,它们按整数比较; 否则,它们先转换为浮点数,然后再做比较。

        Tables, userdata, threads都按引用比较: 只有两者引用同一个对象时才认为它们相等。Lua中的对象都是唯一的。闭包总是等于它自己。有任何可察觉的差异(不同的行为,不同的定义)一定不等。

        可以通过使用 "__eq" 元方法改变 Lua 比较表和用户数据时的方式。(即:重载==运算符

        等于操作不会将字符串转换为数字,反之亦然。 即,"0"==0 结果为 false, 且 t[0] 与 t["0"] 指代着表中的不同项。

        ~= 操作完全等价于 (==) 操作的反值。

        比较大小时: 数字比较按数字大小。字符串比较由当前语言环境决定(实际应该是逐字符按ASCII码比较)。不能直接进行比较的,Lua 会调用 "lt" 或是 "le" 元方法 。 a > b 的比较被转译为 b < a, a >= b 被转译为 b <= a

        4-5)逻辑操作符

        Lua支持的逻辑操作符有: and:与, or:或,以及 not:非

        逻辑操作符把 false 和 nil 作为假, 其它的一切都作为真(和控制结构一样)。

        与操作符(and 在第一个参数为假时返回这第一个参数,否则返回第二个参数。(可以这么理解:如果第一个参数为假,那结果必定为假,因为第一个参数是假,所以就返回第一个参数。如果第一个参数为真,那整体的结果全由第二个参数决定。

        或操作符(or 在第一个参数为真时返回这第一个参数,否则返回第二个参数。(可以这么理解:如果第一个参数为真,那结果必定为真,因为第一个参数是真,所以就返回第一个参数。如果第一个参数为假,那整体的结果全由第二个参数决定。

        and 和 or 都遵循短路规则,即,第二个操作数只在需要的时候去求值。(第一个参数已经能够决定结果,所以不对第二个参数进行计算或执行。

        4-6)字符串连接

        Lua 中字符串的连接操作符写作两个点('..')。

        字符串连接操作会先将数字隐式转换为字符串。 操作数不能转为字符串时,调用"__concat"元方法(即:重载 '..' 操作符

        4-7)取长度操作符

        取长度操作符 写作 一元前置符 #

        #取字符串的长度时,结果是字符串的字节数(一个字符占一个字节,utf-8编码的中文占两个)。

        #应用于表上时,返回表的边界。表的边界值应使下式结果为真。(即:如果表的某个非nil值后紧跟一个nil值。那么这个非nil值对应的索引就是表的边界)。

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

         只有一个边界的表称为 “序列”(sequence),空表{} 是一个边界为0的序列。非自然键不会干扰表是否是序列(未理解)。当表 t 是序列时,#t 返回它的唯一边界(即直观上的表长度),否则可能返回它的任何边界,这取决于表内部表示的详细信息,而它又取决于表的填充方式和非数字键的内存地址。(总之,不要对非序列的表取长度,无直观意义)。

         表长度的计算保证最坏的时间是O(log n),其中n是表中最大的自然键。

         可以通过 "__len" 元方法修改取长度操作行为。(即:重载 '#' 操作符

         4-8)优先级

         由低到高: 逻辑运算符 < 比较运算符 < 位运算 < 连接操作符 ('..')<  算数运算(除乘方外) < 单目运算  < 乘方运算('^')

         括号可以用来改变运算次序。

         连接操作符 ('..') 和乘方操作 ('^') 是从右至左的。 其它所有的操作都是从左至右。

         4-9)表构造

         表构造指创建表的表达式。

         形如 [exp1]=exp2 的显式键值对(key是明确的)为表种增加一项, name=exp 与 ["name"] = exp 等价。exp 与 [i]=exp 等价,也可以为表增加一项, i从1自然增长,显式键值对不会破坏 i 的计数(忽略掉显式的键值对)。(总之,表中的项最终都会被转化为键值对形式。)举例:

local t = {[f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45}

等价于:

local t = {[f(1)] = g, [1] = x", [2] = "y", ["x"] = 1, [3] = f(x), [30] = 23, [4] = 45}

         构造器中的赋值顺序没有定义,次序问题只会对重复键的情况有影响。(显而易见,不同键不管赋值顺序怎样,都是一样的。相同键时,赋值顺序影响了与该键对应的最终值。

         如果表的最后一个字段是一个函数调用表达式或一个可变参数表达式,那么这个表达式所有的返回值将依次进入列表。

         为了便于机器生成代码,字段列表可以有一个可选的尾随分隔符。(表的分隔符可以是分号或逗号,两者是等价的)。

         4-10)函数调用

         Lua 中的函数调用的语法如下:

f args。

         函数调用时,f 和 args 先被求值。如果 f 的值的类型是 Function,则函数f被调用,参数是args。 否则 f 的元方法 "__call" 被调用, 参数是 f 和 args。

          Lua 中的“方法”(指表中的函数,“方法”和面向对象有关,函数和面向对象无关。)的调用的语法是: t:f(args) 。它是一种语法糖,会被解释成 t.f(t, args)。(实际上,点号操作也是一种语法糖,最终被解释成 t["f"](t, args)。

          所有参数的表达式求值都在函数调用之前。

          当函数的参数只有一个表或一个字符串时,可以省略小括号(是个语法糖,但不一定所有环境都支持,NRatel认为最好别用)。

          return functioncall 这样的调用形式将触发一次 尾调用。 仅当 return 只有单一函数调用作为参数时才发生尾调用; 这种语法使得调用函数的所有结果可以完整地返回。 

         4-11)函数定义

          函数定义的语法如下:

函数定义原语法: f = function (parms) body end

语法糖: function f (parms) body end

表中方法定义:
local t = {} 
t.f = function (parms) body end  
或:   function t.f (parms) body end   
或:   function t:f (parms) body end    --是上式的语法糖,相当于上式额外传入第一个参数 self

          函数定义是可执行的表达式, 执行结果是类型为 Function 的值(如上边的 f)。 

          形参被看作是局部变量, 它们将由实参的值来初始化,多个形参以逗号分隔。

          当函数被调用时,实参列表会被调整到形参列表的长度,除非函数是一个可变长参数函数。可变长参数函数:形参列表以可变长参数表达式 ('...')结束。

          可变长参数函数不会调整实参列表,而是将所有额外的参数放在一起通过可变长参数表达式 ('...')传递给函数。可变长参数表达式的值是一串实参值的列表。

          如果可变长参数表达式不在形参列表的末尾或在另一个表达式之中,它就会被调整为只能接收一个实参值。

          reutrn 用来返回函数结果,如果没有return语句,就不返回任何结果。返回值有数量限制,和系统有关,这个限制一定大于1000。

五. 可见性规则

          Lua中变量的作用范围开始于变量声明之后的第一条语句,结束于包含这个声明的 最内层语句块的 最后一条非空语句。

          局部变量可以被在它的作用范围内定义的函数自由使用。

          当一个局部变量被内层的函数中使用的时候,它被内层函数称作 上值(upvalue)或 外部局部变量。

          注意:(1)形如 local x = x 的声明,新的 x 正在被声明,但是还没有进入它的作用范围,所以第二个 x 指向的是之前定义的x的值。(2)循环的每次执行都创建一个闭包,这些闭包内的 local 语句将创建不同的局部变量。

猜你喜欢

转载自blog.csdn.net/NRatel/article/details/83624752