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