Programming in Lua
Table of Contents
- Programming in Lua
- Lua Reference Manual
- Usage
- 参考资料
lua note.
<!– more –>
Programming in Lua
Lua Reference Manual
Basic Concpts
Type and Values
bool
lua 在进行条件测试中,nil 和 false 为 false,任何其他值都为 true。
function PrintBoolCheck(var)
if var then
print(tostring(var) .. " is true")
else
print(tostring(var) .. " is false")
end
end
PrintBoolCheck(nil)
PrintBoolCheck(false)
PrintBoolCheck({})
--output
-- nil is false
-- false is false
-- table: 0xed7f00 is true
Metatable and Metamethod
metatable 是 lua 中修改 table 默认行为的一种机制。通过给一个 table 指定 metatable,并给该 metatable 添加一些特定的方法,就可以改变 table 的行为。
例如:
给 metatable 添加__index 元方法,可以改变索引 table 不存在字段时的行为。
给 metatable 添加__newindex 元方法,可以改变为 table 不存在字段赋值时的行为。
metatable-metamethod 实现支持继承的 class
实现 class 关键要实现以下几点:
- 类支持构造对象,构造出的对象共用类的方法
可以 new 对象,new 对象时调用类的构造函数 - 子类对象可以访问父类的方法
- 构造对象时,先调用父类构造函数,后调用子类构造函数
通常父类的构造函数调用不封装在 class 的实现中,而是放在定义类的时候,这样可以让上层控制,父类构造函数的调用顺序,以及是否调用。 - 子类对象拥有父类对象的属性
Tips:
在元方法__index 中取属性时,需要使用 rawget,否则会自己调用自己陷入死循环
具体实现
在该实现中,我将父类的构造函数调用也封装到 class 实现中了。
function class(clsName, super)
if clsName==nil then
print("Error: className is nil")
end
if _G[clsName] then
print("Error: class eixst. className = " .. clsName)
end
function invoke_ctor(class, obj, ...)
if class~=nil then
invoke_ctor(rawget(class,"super"), obj, ...)
if type(rawget(class,"ctor"))=="function" then
class.ctor(obj, ...)
end
end
end
function get_func(class, key)
if class~=nil then
if type(rawget(class,key))=="function" then
return class[key]
else
return get_func(rawget(class,"super"), key)
end
end
print("Error: not exist member func -> " .. key)
return nil
end
local cls = {}
cls.super = super
cls.__index = function (tbl, key)
return get_func(cls, key)
end
setmetatable(cls,cls)
cls.new = function (...)
local obj = {}
setmetatable(obj, cls)
invoke_ctor(cls, obj, ...)
return obj
end
return cls
end
Tips:
- pairs 不会列出元表中__index 的字段
- 判断 tabel 是否含有某个字段
-- 错误的写法:
-- 当 svrData.IsLeader == false 而 role.data.IsLeader == true 时,下面的代码不会更新 role.data.IsLeader 的值为 false
if rawget(svrData,"IsLeader") then
role.data.IsLeader = svrData.IsLeader
end
-- 正确的写法:
if rawget(svrData,"IsLeader")~=nil then
role.data.IsLeader = svrData.IsLeader
end
-- 最好的写法:
-- 这样即使 svrData[k] == nil 也可以对 role.data[k]进行赋值
for k,v in pairs(svrData) do
role.data[k] = v
end
The Language
The Application Program Interface
The Auxiliary Library
The Standard Libraries
String Manipulation
format
-- 前/后填充 string.format(%10s, str) --不够 10 个字符前面填充相应空格 string.format(%-10s, str) --不够 10 个字符后面填充相应空格
Lua Standalone
Incompatibilities with the Previous Version
The Complete Syntax of Lua
Usage
开发环境
lua
# windows pacman -S mingw-w64-x86_64-lua
luarocks 包管理
# 安装 luarocks pacman -S mingw-w64-x86_64-lua-luarocks # 在D:\Applications\msys64\mingw64\bin\ 目录下创建 luarocks.bat 文件,将下面内容填充进去: @echo off echo input parameters are: %* lua D:\Applications\msys64\mingw64\bin\luarocks %* pause # 下面命令在 PowerShell中执行 # 查看luarocks 环境, 会显示 Options Commands Variables Configuration 这几个方面的信息 luarocks.bat # 配置 lua dir (默认配置应该是正确的,这一步可以省略) luarocks.bat config lua_dir D:\Applications\msys64\mingw64\bin # 配置 lua include dir luarocks.bat config variables.LUA_INCDIR D:\Applications\msys64\mingw64\include # 配置 lua lib dir luarocks.bat config variables.LUA_LIBDIR D:\Applications\msys64\mingw64\lib # luarocks 创建工程 cd-test luarocks.bat init lua_test_proj # install lpeg (TODO 当前还没有成功, 所以还是直接使用 ZeroBrane 算了) luarocks.bat install lpeg
ERROR
IDE
VSCode
安装如下插件
- Code Runner https://github.com/formulahendry/vscode-code-runner
- LuaHelper https://github.com/Tencent/LuaHelper
ZeroBrane
ZeroBrane 自带 lua 环境,不需要额外配置。
- ZeroBrane https://studio.zerobrane.com/
Basic
打印代码和对应的执行结果
function locals(env_pos)
env_pos = env_pos or 3
local variables = {}
local idx = 1
while true do
local ln, lv = debug.getlocal(env_pos, idx)
if ln ~= nil then
variables[ln] = lv
else
break
end
idx = 1 + idx
end
return variables
end
function eval(equation, variables, env_pos)
if(type(equation) == "string") then
local eval = loadstring("return "..equation);
if(type(eval) == "function") then
variables = variables or locals(env_pos)
setfenv(eval, variables)
return eval();
end
end
end
function log_exp(exp, str_fmt_count, env_pos)
str_fmt_count = str_fmt_count or 20
env_pos = env_pos or 4
local result = eval(exp, nil, env_pos)
local fmt_str = '%-' .. tostring(str_fmt_count) .. 's'
print(string.format(fmt_str, exp), " -- ", result)
end
log_exp("hi")
local lpeg = require "lpeg"
local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
log_exp("lpeg.match(P'a', 'abc')")
打印代码执行时间的方法
下面打印出的时间单位为秒
local codeTimer = os.clock()
-- ... 此处省略若干上帝代码
print("delta time = " .. tostring(os.clock()-codeTimer))
-- delta time = 4.928
Lpeg
lpeg
Introduction
下表列出了创建 patterns 的基础操作:
| Operator | Description | 描述 |
| lpeg.P(string) | Matches string literally | 匹配字符串 |
| lpeg.P(n) | Matches exactly n characters | 准确匹配 n 个字符 |
| lpeg.S(string) | Matches any character in string (Set) | 匹配字符串中的任何字符 |
| lpeg.R("xy") | Matches any character between x and y (Range) | 匹配 x 和 y 之间的任何字符(范围) |
| patt^n | Matches at least n repetitions of patt | 匹配至少 n 个重复的 patt |
| patt^-n | Matches at most n repetitions of patt | 匹配至多 n 个重复的 patt |
| patt1 * patt2 | Matches patt1 followed by patt2 | 匹配 patt1 紧跟着 patt2 |
| patt1 + patt2 | Matches patt1 or patt2 (ordered choice) | 匹配 patt1 或 patt2 |
| patt1 - patt2 | Matches patt1 if patt2 does not match | 不匹配 patt2,匹配 patt1 |
| -patt | Equivalent to ("" - patt) | 等价于 "" - patt, 即:匹配 patt 失败则匹配"" |
| #patt | Matches patt but consumes no input | 匹配 patt,但不消耗任何输入 |
| lpeg.B(patt) | Matches patt behind the current position, consuming no input | 匹配当前位置后的 patt,不消耗任何输入 |
-- 下面例子中,都需要包含这里的 包引入 和 名称定义 local lpeg = require "lpeg" local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
Functions
lpeg.match(pattern, subject, [,init])
匹配函数。它试图将给定的模式与目标字符串进行匹配。如果匹配成功,返回匹配后第一个字符在目标字符串中的索引,或者捕获的值(如果模式捕获了任何值)。
init 为可选的整数型参数,用于设定进行匹配的开始位置。像 Lua 库中的惯例一样,负值表示从末尾开始计算。
与典型的模式匹配函数不同,match 只在 anchored 模式下工作;也就是说,它试图将模式与给定目标字符串的前缀(从 init 位置开始)相匹配,而不是与目标字符串的任意子串相匹配。因此,如果我们想在一个字符串的任何地方找到一个模式,我们必须在 Lua 中写一个循环,或者写一个可以在任何地方匹配的模式。第二种方法更简单,高效。
lpeg.type(value)
如果给定的 value 是一个 pattern,则返回字符串 "pattern",否则返回 nil
lpeg.version()
返回 LPeg 的版本号字符串
lpeg.setmaxstack(max)
设置 backtrack stack 的尺寸,LPeg 会使用 backtrack stack 更正调用和选择。默认值为 400
Basic Constructions
lpeg.P(value)
用下面的规则将一个给定的值转换成一个合适的 pattern:
如果参数是一个 pattern,则返回参数 pattern。
如果参数是一个 string,则返回匹配这个字符串的 pattern。
如果参数是一个非负整数 n, 则返回一个匹配正好是 n 个字符的字符串的 pattern。
如果参数是一个负整数 -n, 则只有在输入的字符串还剩下不到 n 个字符才会成。 lpeg.P(-n) 等同于 -lpeg.P(n) (see the unary minus operation).
如果参数是一个 boolean, value 为 true 返回总是匹配成功的 pattern,value 为 false 返回总是匹配失败的 pattern,不消耗任何输入。
如果参数是一个 table, 则被解读为一个 grammar (see Grammars)。
如果参数是一个 function, 则返回一个 pattern,等价于一个 match-time capture 用一个空字符串匹配.
local lpeg = require "lpeg" local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- P匹配字符串 从头开始找,返回第一次找到的位置+1 print(lpeg.match(P'a', 'abc')) -- 2 print(lpeg.match(P'b', 'abc')) -- nil print(lpeg.match(P'ab', 'abc')) -- 3 print(lpeg.match(P'a', 'aaa')) -- 2 print(lpeg.match(P'ab', 'abab')) -- 3
lpeg.B(patt)
lpeg.R({range})
local lpeg = require "lpeg" local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- R匹配范围,09代表0~9,AZ同理,也是从头开始找 print(lpeg.match(R'az', 'abc')) -- 2 print(lpeg.match(R'aZ', 'abc')) -- nil print(lpeg.match(R'AZ', 'abc')) -- nil print(lpeg.match(R'Az', 'abc')) -- 2 print(lpeg.match(R'az', '0abc')) -- nil
lpeg.S(string)
local lpeg = require "lpeg" local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- S集合匹配 从头开始 若字符串包含集合内元素,匹配成功(ps:那岂不是匹配成功就一定是2,后面再看) print(lpeg.match(S'abc', 'abc')) -- 2 print(lpeg.match(S'bc', 'abc')) -- nil print(lpeg.match(S'1a', 'abc')) -- 2
lpeg.V(v)
该操作为 grammar 创建一个非终结变量。非终结变量指的是在闭包语法中由 v 所索引的规则。
This operation creates a non-terminal (a variable) for a grammar. The created non-terminal refers to the rule indexed by v in the enclosing grammar.
lpeg.locale([table])
返回一个表格,其中包含匹配各类字符的模式,这些模式会考虑当前语言环境。该 table 有如下字段 alnum, alpha, cntrl, digit, graph, lower, print, punct, space, upper, 和 xdigit ,每个字段都包含一个相应的模式。每个模式都匹配属于其类别的任何单个字符。
如果调用时有一个参数 table,那么它就在给定的 table 内创建这些字段,并返回该 table。
patt1+patt2
local lpeg = require "lpeg" local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- +代表or(或) -- P'a' + P'b'^1 代表开头是a 或 开头至少1个b local a_1b = P'a' + P'b'^1 print(match(a_1b, 'ab')) --2 print(match(a_1b, 'bb')) --3 print(match(a_1b, 'bc')) --2
patt1-patt2
-- 不匹配patt2,匹配patt1 lpeg.match(S'abcd'-S'ab', 'cd') -- 2 lpeg.match(S'abcd'-S'ab', 'ab') -- nil
-patt
-- 等价于 "" - patt lpeg.match(-S'ab', 'ab') -- nil lpeg.match(-S'ab', 'cd') -- 1
#patt
-- 匹配patt,匹配成功不消耗输入 lpeg.match(#P'ab', 'ab') -- 1 lpeg.match(#P'ab', 'cd') -- nil
patt1*patt2
local lpeg = require "lpeg" local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- *代表 连接 -- P'a'*P'b'^0代表开头是a且后面跟至少0个b print(match(P'a'*P'b'^0, 'aab')) --2 print(match(P'a'*P'b'^1, 'aab')) --nil print(match(P'a'*P'b'^1, 'abab'))--3 print(match(P'a'^0*P'b', 'aab')) --4 print(match(P'a'^1*P'b', 'aab')) --4
patt^n
local lpeg = require "lpeg" local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- ^n代表至少匹配n次 ^-n代表至多匹配n次 print(lpeg.match(P'a'^1, 'aaaa')) --5 print(lpeg.match(P'a'^4, 'aaaa')) --5 print(lpeg.match(P'a'^5, 'aaaa')) --nil print(lpeg.match(P'aa'^2, 'aaaa')) --5 print(lpeg.match(P'ab'^2, 'ababc')) --5 print(lpeg.match(P'ab'^-1, 'ababc')) --3
Grammars
通过使用 Lua 变量,可以逐步定义模式,每个新模式都使用以前定义的模式。然而,这种技术并不允许定义递归模式。对于递归模式,我们需要真正的语法。
LPeg 用表格表示语法,每个条目都是一个规则。
调用 lpeg.V(v)可以创建一个模式,用来表示语法中索引为 v 的非终结符(或变量)。当这个函数被求解时,语法还不存在,所以返回的是一个对该规则的开放引用(open reference)。
当一个表被转换为一个模式时(通过调用 lpeg.P 或者需要模式的地方使用该表),它就被固定了。然后由 lpeg.V(v)创建的每一个开放引用都被纠正为指向规则,该规则在表中由 v 索引。
当一个表被固定时,其结果是一个符合其初始规则的模式。表中索引为 1 的条目定义了其初始规则。如果该条目是一个字符串,则假定它是初始规则的名称。否则,LPeg 假定条目 1 本身就是初始规则。
例如,下面的语法匹配具有相同数量的 a 和 b 的字符串:
equalcount = lpeg.P{
"S"; -- initial rule name
S = "a" * lpeg.V"B" + "b" * lpeg.V"A" + "",
A = "a" * lpeg.V"S" + "b" * lpeg.V"A" * lpeg.V"A",
B = "b" * lpeg.V"S" + "a" * lpeg.V"B" * lpeg.V"B",
} * -1
Captures
Cature 是一个 pattern,capture 可以依据其匹配的内容生成值()。
| Operation | What it Produces |
| lpeg.C(patt) | the match for patt plus all captures made by patt |
| lpeg.Carg(n) | the value of the nth extra argument to lpeg.match (matches the empty string) |
| lpeg.Cb(name) | the values produced by the previous group capture named name (matches the empty string) |
| lpeg.Cc(values) | the given values (matches the empty string) |
| lpeg.Cf(patt, func) | a folding of the captures from patt |
| lpeg.Cg(patt [, name]) | the values produced by patt, optionally tagged with name |
| lpeg.Cp() | the current position (matches the empty string) |
| lpeg.Cs(patt) | the match for patt with the values from nested captures replacing their matches |
| lpeg.Ct(patt) | a table with all captures from patt |
| patt / string | string, with some marks replaced by captures of patt |
| patt / number | the n-th value captured by patt, or no value when number is zero. |
| patt / table | table[c], where c is the (first) capture of patt |
| patt / function | the returns of function applied to the captures of patt |
| lpeg.Cmt(patt, function) | the returns of function applied to the captures of patt; the application is done at match time |
lpeg.C (patt)
lpeg.Carg (n)
lpeg.Cb (name)
lpeg.Cc ([value, …])
lpeg.Cf (patt, func)
lpeg.Cg (patt [, name])
lpeg.Cp ()
lpeg.Cs (patt)
lpeg.Ct (patt)
patt / string
patt / number
patt / table
patt / function
lpeg.Cmt(patt, function)
Some Examples
使用 Pattern
local lpeg = require "lpeg" lpeg.match(R'az'^1 * -1, 'hello ') -- nil lpeg.match(R'az'^1 * -1, 'hello') -- 6 lpeg.match(R'az'^1 * -P(1), 'hello') -- 6
匹配换行
--开头是\r\n 或 \r 或 \n P'\r\n' + S'\r\n'
匹配末尾
lpeg.match(S'helloworld'^1 * -1, 'hello')) -- 6 lpeg.match(S'helloworld'^1 * -1, 'helloX')) -- nil lpeg.match(S'helloworld'^1 * -P(1), 'Xhello')) -- nil lpeg.match(S'helloworld'^1 * -P(1), 'Xhello', 2)) -- 7
匹配数字
digit = R'09' -- 一个数字 digits = digit^1 -- 任意整数
匹配浮点数
-- maybe(p) 可能有p function maybe(p) return p^-1 end digits = R'09'^1 sign = maybe(S'+-') dot = '.' exp = S'eE' -- 浮点数 = 正负号 + 整数 + 小数 + 科学计数法 float = sign * digits * maybe(dot*digits) * maybe(exp*mpm*digits) print(match(C(float),'3.1415926')) --3.1415926 print(match(C(float),'3.14.15')) --3.14 print(match(C(float),'3.14e-3')) --3.14e-3
Simple mathematical expression parser and evaluator in Lua with LPEG
local lpeg = require"lpeg"
local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
local white = S(" \t") ^ 0
local integer = white * C(R("09") ^ 1) * white / tonumber
local power = white * C(P("^")) * white
local muldiv = white * C(S("/*%")) * white
local addsub = white * C(S("+-")) * white
local open = white * P("(") * white
local close = white * P(")") * white
local bfunc = {
["+"] = function(a,b) return a + b end,
["-"] = function(a,b) return a - b end,
["*"] = function(a,b) return a * b end,
["/"] = function(a,b) return math.floor(a / b) end,
["%"] = function(a,b) return a % b end,
["^"] = function(a,b) return a ^ b end
}
-- Insert binary node into AST
local function binary(rule)
local function recurse(left,op,right,...)
if op then
return recurse(bfunc[op](left,right),...)
else
return left
end
end
return rule / recurse
end
-- Insert unary node into AST
local function unary(rule)
return rule / function(op,right)
return bfunc[op](0,right)
end
end
local grammar = P({
"input",
input = V("expression") * -1,
expression = binary( V("term") * ( addsub * V("term") )^0 ),
term = binary( V("factor") * ( muldiv * V("factor"))^0 ),
factor = binary( V("primary") * ( power * V("factor"))^0 ),
primary = integer
+ open * V("expression") * close
+ unary( addsub * V("primary") )
})
print(grammar:match("6%4"), 6%4)
print(grammar:match("3*(5-7^8)"), 3*(5-7^8))
print(grammar:match("5 + 2*3 - 1 + 7 * 8"), 5 + 2*3 - 1 + 7 * 8)
print(grammar:match("67 + 2 * 3 - 67 + 2/1 - 7"), 67 + 2 * 3 - 67 + 2/1 - 7)
print(grammar:match("(5*7/5) + (23) - 5 * (98-4)/(6*7-42)"), (5*7/5) + (23) - 5 * (98-4)/(6*7-42))
print(grammar:match("(2) + (17*2-30) * (5)+2 - (8/2)*4"), (2) + (17*2-30) * (5)+2 - (8/2)*4)
print(grammar:match("2*3*4/8 - 5/2*4 + 6 + 0/3"), math.floor(2*3*4/8) - math.floor(5/2)*4 + 6 + 0/3)
print(grammar:match("2*3 - 4*5 + 6/3"), 2*3 - 4*5 + 6/3)
参考资料
- lua manual https://www.lua.org/manual/5.4/
- lua online https://www.lua.org/cgi-bin/demo
- LPEG and regular expressions - comparison and tutorial http://www.gammon.com.au/lpeg