Lua

Table of Contents

1. Lua 简介

Lua 是一个小巧的脚本语言,它是巴西里约热内卢天主教大学里的一个由 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 三人所组成的研究小组于 1993 年开发的。Lua 中不用关心内存的分配和释放,有 GC 机制。

1.1. Hello World 程序

Lua 版本的 Hello World 程序如下:

print("Hello World")

假定你把上面这句保存在 hello.lua 文件中,你在命令行只需要:

$ lua hello.lua

2. Lua 基本介绍

2.1. 注释

两个连字符 -- 是单行注释,如:

-- 这是单行注释

多行注释以 --[[ 开始,以 --]] 结束,如下面代码中 print(10) 不会输出任何内容:

--[[
print(10)           -- no action (commented out)
--]]

如果我们想临时取消多行注释,让被注释掉的代码重新生效,有一个技巧就是在多行注释的开始位置多加一个连字符(即 3 个连字符),如:

---[[
print(10)           --> 10
--]]

这是因为, ---[[ 表示一个普通的单行注释,而接下来的 --]] 也是单行注释。

2.2. 标示符和保留字

Lua 标示符用于定义一个变量,函数或者其他用户定义的项。

下面这些关键字是保留字(不能作为标示符使用):

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

作为约定,以下划线开头后面根大写字母的名字(比如 _VERSION )被保留用于 Lua 内部全局变量。

2.3. Chunks

Chunk 就是一个代码块(Block),它由一个或多个语句组成。比如使用 Lua 解释器执行的一个完整 Lua 源码文件就是一个 Chunk。在交互模式下,Lua 通常把每一个行当作一个 Chunk,但如果 Lua 一行不是一个完整的 Chunk 时,他会等待继续输入直到得到一个完整的 Chunk。

The unit of compilation of Lua is called a chunk.

A chunk can be stored in a file or in a string inside the host program. To execute a chunk, Lua first loads it, precompiling the chunk's code into instructions for a virtual machine, and then Lua executes the compiled code with an interpreter for the virtual machine.

Chunks can also be precompiled into binary form; see program luac and function string.dump for details.

2.4. 全局变量

全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是: nil.

print(b)    --> nil
b = 10
print(b)    --> 10

如果你想删除一个全局变量,只需要将变量赋值为 nil.

b = nil
print(b)    --> nil

2.5. 局部变量

默认地,变量是全局变量。使用 local 可以创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。如:

x = 10
local i = 1       -- local to the chunk

while i<=x do
  local x = i*2   -- local to the while body
  print(x)        --> 2, 4, 6, 8, ...
  i = i + 1
end

2.6. 赋值语句

Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。

a, b = 10, 2*x           -- 相当于 a=10; b=2*x

遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:

x, y = y, x              -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i]  -- swap 'a[i]' for 'a[i]'

当变量个数和值的个数不一致时,Lua 会一直以变量个数为基础采取以下策略:
1、“变量个数”大于“值的个数”时,按变量个数补足 nil
2、“变量个数”小于“值的个数”时,多余的值会被忽略。

变量个数和值的个数不一致时的例子:

a, b, c = 0, 1
print(a,b,c)             --> 0 1 nil

a, b = a+1, b+1, b+2     --> value of b+2 is ignored
print(a,b)               --> 1 2

2.7. Metatables and Metamethods

Every value in Lua can have a metatable. This metatable is an ordinary Lua table that defines the behavior of the original value under certain special operations. You can change several aspects of the behavior of operations over a value by setting specific fields in its metatable. For instance, when a non-numeric value is the operand of an addition, Lua checks for a function in the field "__add" of the value's metatable. If it finds one, Lua calls this function to perform the addition.

The key for each event in a metatable is a string with the event name prefixed by two underscores; the corresponding values are called metamethods. In the previous example, the key is "__add" and the metamethod is the function that performs the addition. Unless stated otherwise, metamethods should be function values.

3. 类型和值

Lua 是动态类型语言,这意味着变量没有类型,只有值有类型。

Lua 中所有的值都是“一等公民”。 这意味着所有的值均可保存在变量中、当作参数传递给其它函数、以及作为返回值。

Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table,如表 1 所示。

Table 1: Lua 的 8 个基本类型
数据类型 描述
nil 只有值 nil 属于该类,表示一个无效值(在条件表达式中相当于 false)。全局变量没有被赋值前默认值为 nil
boolean 包含两个值:false 和 true。Lua 中所有值都可以作为条件, 除了 falsenil 为假,其它值都是真(注:数字 0 和空字符串也是真)
number 整数或浮点数(默认为 64 位整数或双精度浮点数)
string 字符串由一对双引号或单引号来表示
function 由 C 或 Lua 编写的函数
userdata 这是宿主程序中的数据,Lua 中不能创建和修改 userdata,只能通过 C API 操作它们
thread Lua 中的线程,用于实现 coroutines。Lua 线程不同于操作系统的线程,在不支持线程的操作系统也有 Lua coroutines.
table Lua 中的 table 其实是一个“关联数组”,数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过“构造表达式”来完成,最简单构造表达式是 {} ,用来创建一个空表。

3.1. 字符串

字符串由一对双引号(支持转义)或单引号(不支持转义)来表示。

Lua 中字符串是不可变的, 我们不能改变字符串中的某个字符。

a = "one string"
b = string.gsub(a, "one", "another") -- change string parts
print(a)                             --> one string
print(b)                             --> another string

使用 # 符号可以得到字符串的字节长度(不是某种编码下字符的数量)。如:

a = "hello"
print(#a)                --> 5
print(#"good bye")       --> 8
print(#"中文")           --> 6
print(utf8.len("中文"))  --> 2 (函数 utf8.len 可以得到utf8字符串的字符数量)

使用 .. 可以连接两个字符串(如果是数字,则会先转换为字符串),得到新字符串,如:

print("Hello " .. "World")       --> Hello World
print("result is " .. 3)         --> result is 3

还可以使用 [[...]] 表示字符串。这种形式的字符串可以包含多行,且不会解释转义序列,如果 [[ 后的第一个字符是换行符会被自动忽略掉。如:

page = [[
<html>
<head>
  <title>An HTML Page</title>
</head>
<body>
  <a href="http://www.lua.org">Lua</a>
</body>
</html>
]]
print(page)

3.2. Table(关联数组)

Table 是 Lua 中主要的数据结构,它是关联数组。

可以使用 {} 创建 Table,如:

a = {}
a["x"] = 10
a.y = "hello"     -- 相当于 a["y"] = "hello"

print(a.x)        -- 输出 10
print(a["x"])     -- 同上,输出 10
print(a.y)        -- 输出 "hello"
print(a.z)        -- 访问不存在的下标,也不会报错,会输出 nil

使用 {} 也可以创建数组,如:

days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
-- 相当于 days = {[1]="Sunday", [2]="Monday", [3]="Tuesday", [4]="Wednesday", [5]="Thursday", [6]="Friday", "Saturday"}

print(days[1])    -- 输出Sunday,lua中数组默认从下标1(而不是0)开始

3.2.1. pairs 遍历 Table

使用 pairs 可以遍历 Table 中的 key-value 对(遍历顺序不固定),如:

t = {10, x = 12, k = "hi"}
for k, v in pairs(t) do
    print(k, v)
end

上面程序可能输出:

1	10
x	12
k	hi

特别说明,多次运行上面程序,可能得到不同结果。如:

1	10
k	hi
x	12

3.2.2. ipairs 遍历 Table

使用 ipairs 可以遍历数组(遍历顺序是固定的),如:

t = {10, 12, "hi"}
for k, v in ipairs(t) do
    print(k, v)
end

上面程序会输出:

1	10
2	12
3	hi

长度操作符 # 可以得到数组长度,所有也可以采用下面方式来遍历数组,如:

t = {10, 12, "hi"}
for k = 1, #t do
    print(k, t[k])
end

上面程序会输出:

1	10
2	12
3	hi

4. 运算符

Lua 支持下面算术运算符:
+: addition
-: subtraction
*: multiplication
/: float division
//: floor division
%: modulo
^: exponentiation
-: unary minus

Lua 支持下面位运算符:
&: bitwise AND
|: bitwise OR
~: bitwise exclusive OR
>>: right shift
<<: left shift
~: unary bitwise NOT

Lua 支持下面关系运算符:
==: equality
~=: inequality
<: less than
>: greater than
<=: less or equal
>=: greater or equal

Lua 支持 3 个逻辑运算符: and, or, not

参考:https://www.lua.org/manual/5.3/manual.html#3.4

5. 控制结构

5.1. if 语句

if 语句有 3 种形式,分别如下:

if conditions then
    then-part
end

if conditions then
    then-part
else
    else-part
end

if conditions then
    then-part
elseif conditions then
    then-part
......                ---> 多个elseif
else
    else-part
end

如:

a = 10

if a < 20 then
    print("a < 20")
else
    print("a >= 20")
end

5.2. while 循环

下面是 while 循环的例子:

a=1
while a < 5
do
   print(a)         -- 输出1到4
   a = a+1
end

5.3. for 循环

for 循环有两种形式,第一种形式如下:

for var=exp1, exp2[, exp3] do
    your code block
end

var 从 exp1 变化到 exp2,每次变化以 exp3 为步长(exp3 是可选的,如果不指定,默认为 1)递增 var,并执行一次 block。如:

for i=1, 5 do        -- 省略了步长,默认为1
    print(i)         -- 从1输出到5
end

for i=5, 1, -1 do    -- 步长是 -1,从5输出到1
    print(i)
end

for 循环的第二种形式为(类似于 Java 中的 foreach):

for namelist in explist do
    your code block
end

下面是 for 循环的第二种形式的一个实例:

a = {"one", "two", "three"}

for i, v in ipairs(a) do       -- ipairs是Lua的一个迭代器函数,用来迭代数组
    print(i, v)
end

上面代码将输出:

1	one
2	two
3	three

5.4. repeat...until 循环

repeat...until 循环的条件语句在当前循环结束后判断,所以被包围的代码块至少会执行一次。如:

a=1
repeat
   print(a)          -- 输出1到4
   a = a+1
until a >= 5

5.5. break 和 return 语句

break 语句用来退出当前循环(即在 for, repeat, while 语句中使用),在循环外部不可以使用。注:Lua 中没有 continue 语句。

return 用来从函数返回结果,当一个函数自然结束结尾会有一个默认的 return。

Lua 语法要求 break 和 return 只能出现在 block 的结尾一句(也就是说:或者在 end 之前,或者 else 前,或者 until 前) ,如:

local i = 1
while a[i] do
  if a[i] == v then
    break
  end
  i = i + 1
end

有时候为了调试方便或者其他目的需要在 block 的中间使用 return 或者 break, 可以显式的使用 do ... end 来实现, 如:

function foo ()
  return --<< SYNTAX ERROR
  do return end -- OK
  ... -- statements not reached
end

5.6. goto 语句

goto 语句将程序的控制点转移到一个标签处,标签名由 :: 开始和结束。如下面程序将输出 1 到 10 中的奇数:

for i=1,10 do
  if i % 2 == 0 then
    goto labal1          -- 这个goto实现了continue的效果
  end
  print(i)
  ::labal1::
end

6. 函数

Lua 中函数以关键字 function 开始,如:

function max(num1, num2)
   if num1 > num2 then
      return num1
   else
      return num2
   end
end

print(max(1, 3))

也可以写为形式:

max = function (num1, num2)
   if num1 > num2 then
      return num1
   else
      return num2
   end
end

6.1. 返回多个值

Lua 函数可以返回多个结果值,比如 string.find ,其返回匹配串“开始和结束的下标”(如果不存在匹配串则返回 nil)。

s, e = string.find("hello Lua users", "Lua")
print(s, e)                          --> 7 9

Lua 函数中,在 return 后列出要返回的值得列表即可返回多值,如:

function maximum (a)
  local mi = 1 -- maximum index
  local m = a[mi] -- maximum value
  for i,val in ipairs(a) do
    if val > m then
      mi = i
      m = val
    end
  end
  return m, mi
end

print(maximum({8,10,23,12,5}))   --> 23 3

Lua 总是调整函数返回值的个数去适用调用环境,当作为一个语句调用函数时,所有返回值被忽略。假设有如下三个函数:

function foo0 () end                  -- returns no results
function foo1 () return "a" end       -- returns 1 result
function foo2 () return "a", "b" end  -- returns 2 results

下面是调用实例:

x,y = foo0()        -- x=nil, y=nil
x,y = foo1()        -- x="a", y=nil
x,y,z = foo2()      -- x="a", y="b", z=nil
x = foo2()          -- x='a', 'b' is discarded

6.2. 可变参数

Lua 中,函数最后一个参数使用三点 ... 表示函数有可变的参数。如:

function add(...)
local s = 0
  for i, v in ipairs{...} do    --> {...} 表示一个由所有变长参数构成的数组
    s = s + v
  end
  return s
end

print(add(3,4,5,6,7))           --> 25

7. Lua 完整语法(Extended BNF)

Lua 的语法比较简洁,完整的 Extended BNF 如下:

	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 namelist ['=' explist]

	retstat ::= return [explist] [';']

	label ::= '::' Name '::'

	funcname ::= Name {'.' Name} [':' Name]

	varlist ::= var {',' var}

	var ::=  Name | prefixexp '[' exp ']' | prefixexp '.' Name

	namelist ::= Name {',' Name}

	explist ::= exp {',' exp}

	exp ::=  nil | false | true | Numeral | LiteralString | '...' | functiondef |
		 prefixexp | tableconstructor | exp binop exp | unop exp

	prefixexp ::= var | functioncall | '(' exp ')'

	functioncall ::=  prefixexp args | prefixexp ':' Name args

	args ::=  '(' [explist] ')' | tableconstructor | LiteralString

	functiondef ::= function funcbody

	funcbody ::= '(' [parlist] ')' block end

	parlist ::= namelist [',' '...'] | '...'

	tableconstructor ::= '{' [fieldlist] '}'

	fieldlist ::= field {fieldsep field} [fieldsep]

	field ::= '[' exp ']' '=' exp | Name '=' exp | exp

	fieldsep ::= ',' | ';'

	binop ::=  '+' | '-' | '*' | '/' | '//' | '^' | '%' |
		 '&' | '~' | '|' | '>>' | '<<' | '..' |
		 '<' | '<=' | '>' | '>=' | '==' | '~=' |
		 and | or

	unop ::= '-' | not | '#' | '~'

参考:https://www.lua.org/manual/5.3/manual.html#9

8. 参考

Lua 5.3 Reference Manual: https://www.lua.org/manual/5.3/manual.html
Programming in Lua, 4th Edition

Author: cig01

Created: <2019-01-05 Sat>

Last updated: <2019-06-07 Fri>

Creator: Emacs 27.1 (Org mode 9.4)