LPEG是一个供lua使用的基于 Parsing Expression Grammars 的模式匹配库,这篇文章只是讲其如何使用,并不涉及底层如何实现。
LPEG 的函数主要分为三类,第一类是创建Pattern的构造函数,第二类是 Capture 函数, 第三类则是 match 等函数。 Capture 就是指一个Pattern,当前匹配时会产生某些捕获的值。
Match 等函数
lpeg.match (pattern, subject [, init])
以上为 match 函数, 第一个参数为用来匹配的 pattern,第二个参数则为用来匹配的字符串, 可选参数 init指明 subject 开始匹配的位置, 负数指从subject 结尾处开始数。另外, match 函数中还可以传入一些额外参数, 供 capture 使用,这个后面涉及到的时候再讲。
match 函数只匹配给定subject(计算过 init参数之后的subject)字符串的前缀, 而不是匹配其任意位置。不过,我们可以通过一些方式改写 pattern, 使其匹配任意位置的subject。
match的返回值有两种情况,一种是返回 pattern 匹配时捕获的值, 如果没有捕获的值则返回pattern匹配后subject的第一个字符的index, 比如 pattern 为匹配任意一个字符, 则返回值为2。 这个在后面的例子里会再具体讲。 如果匹配 pattern失败,则返回 nil。
lpeg.type (value)
value 是 Pattern 时, 返回 “pattern”; 否则, 返回 nil。
lpeg.version()
返回 Lpeg 的版本
lpeg.setmaxstack (max)
这个函数是设置Lpeg解析过程中允许使用的栈的大小,默认400, 这里就不展开讲了。
创建 Pattern 的构造函数
lpeg.P (value)
将 value 转变为 patten。value 可以是以下值
- pattern, 返回 pattern
- string, 返回匹配该string固定值得pattern
- 非负数n, 返回一个匹配 n 个字符的 pattern
- 负数 -n, lpeg.P(-n) = -lpeg.P(n), 即匹配任意 不匹配 lpeg.P(n)的subject, 所以匹配少于n个字符的 subject, 注意的是,它不会消耗任何字符,比如 n = 3, subject = “ab”, match成功后返回的 index 是1, 就像没有匹配过一样。
- boolean , 返回一个 Pattern, 匹配总是 失败 或者 成功, 也不消耗任何字符
- function, 相当于lpeg.Cmt(”, function), 这个后面讲Capture时会讲到。
期待使用pattern时传入以上的值,也会通过lpeg.P 函数转换为pattern。
lpeg.B(patt)
在subject 的当前 index 之前匹配patt, 不消耗任何字符, 并且 patt只可以是那种不是匹配无限长度字符串的pattern。
> b = lpeg.B("str")
> print (lpeg.match(b, "istrim", 5))
5
> print (lpeg.match(b, "istrim", 6))
nil
以上实例为在位置5之前匹配str, 匹配成功返回当前 index 5 , 若改为从位置6开始匹配,则匹配失败,返回nil。
lpeg.R ({range})
返回一个pattern, 可以匹配range范围内的任一字符。
lpeg.R(“09”), 匹配 0-9.
lpeg.R(“az”), 匹配 a-z,
lpeg.R(“az”, “AZ”) 匹配 a-z, A-Z
lpeg.S (string)
匹配出现在string中的任意字符
lpeg.V (v)
根据index v 创造一个作为非终结符的变量.
在讲这个之前,我们要讲一下 Lpeg 中的Grammar, 通过各种函数来组合pattern,无法表达包含递归的Pattern,所以需要使用Grammar来构造。
Grammar 是使用 table 来表示的, 每一个元素都代表一个rule, index 为 1的代表 initial rule,如果 index 为1的是一个字符串,则代表initial rule的index。 通过 lpeg.P(table) 可以将table 转换为pattern,转换之后得到的pattern会匹配table中的initial rule。
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
相当于
S <- 'a' B / 'b' A / ''
A <- 'a' S / 'b' A A
B <- 'b' S / 'a' B B
48 function anywhere (p) --这个函数是指将p转换为可以匹配subject任意位置子字符串的p
49 return lpeg.P({ p + (1 * lpeg.V(1)) })
50 end
51
52 print(anywhere(lpeg.alpha^1):match(",,ab,,"))--匹配大于一个的字母,故返回5
53 print("l48:",anywhere(lpeg.alpha^1):match(",,,,"))--返回nil
lpeg.locale ([table])
在table中添加一些lpeg定义好的pattern, 比如 alnum, alpha, cntrl, digit, graph, lower, print, punct, space, upper, and xdigit.
lpeg.locale(t), 则 t.alpha 返回一个可以匹配字母的pattern
#patt
匹配patt,但不消耗任何字符,这在前面已经说明过。需要注意的是,patt中不会产生capture。
-patt
匹配不匹配patt的subject。不产生字符消耗。不产生任何capture。
patt1 + patt2
匹配 patt1 或者 patt2, 这是有序的,即优先匹配patt1, 这也是 PEG 与 CFG的最大区别。
patt1 - patt2
保证不匹配patt2, 然后匹配patt1
patt1 * patt2
先匹配 patt1, 再匹配 patt2
patt^n
若n为非负数,则匹配 n 或者更多的patt;若n为负数,则最多匹配n个patt。
Captures
一个capture pattern 在匹配成功的时候产生捕获的值。通常情况下,capture只会在整个匹配结束之后才求capture的值,在匹配期间,只会收集足够的信息来产生之后的capture的值。并且,绝大部分的capture值是不会影响到匹配的结果的。但是,lpeg.Cmt(patt,function)是一个例外,它强制在匹配时生成所有它嵌套的capture的值,然后调用相应的函数,该函数会决定匹配是否成功,并且产生相应的值。
lpeg.C(patt)
捕获subject中匹配patt的子字符串。如果patt有其他capture,则会跟在后面返回,即会返回多个变量。
lpeg.Carg(n)
匹配空字符串,返回lpeg.match()函数的第n个额外参数,我们将match的时候也提到过。
lpeg.Cb (name)
匹配空字符串,捕获之前最近的一个group capture 中名叫name的值。
Most recent means the last complete outermost group capture with the given name. A Complete capture means that the entire pattern corresponding to the capture has matched. An Outermost capture means that the capture is not inside another complete capture.
以上是对最近一个group capture的说明,为了避免词不达意,就贴上原文。
lpeg.Cc ([value, …])
匹配空字符串,将所有的value作为捕获的值。
lpeg.Cf (patt, func)
如果patt捕获的值为C1, C2,…, Cn, 该Capture则会返回 (…func(func(C1,C2),C3)…, Cn).
lpeg.Cg (patt [, name])
将patt捕获的值打包成一个capture,仍旧是多个变量的形式。group可以是匿名的或者是有名字的,匿名的是将多个capture打包成一个capture。有名字的则是为了back capture(lpeg.Cb (name)) 和 table captue(lpeg.Ct (patt))使用。
需要注意的是,有名字时其本身就不会返回捕获的值,而是返回匹配后字符的index。
lpeg.Cp ()
匹配空字符串,捕获subject当前位置。
lpeg.Cs (patt)
捕获patt匹配的子字符串。在patt中,会有一些捕获的值,最后,将子字符串在patt中匹配的值的子字符串,替换成其对应的捕获的值, 通常配合patt/string 来达到替换字符串的效果。
local rep = name/"name";--name匹配任意长度的字母
print(rep:match(“hi=ab”)) -- 匹配hi,返回捕获的值 “name”
print(lpeg.Cs((rep+1)^0):match("hi=ab")) --匹配成功,返回子字符串hi=ab,其中hi和ab都匹配rep,将其替换成rep捕获的值name,则返回“name=name”
lpeg.Ct (patt)
创建一个table,将patt所有匿名捕获的值从键值1开始存入table,如果是Cg中捕获的有名字的group,该group中的第一个值会被存入table中,group的名字就是它对应的键值。
patt / string
匹配patt,返回string。
如果string是%n,n为1-9,则返回patt中第n个捕获的值。%0 返回整个匹配的值.
%%代表字符%。
patt / number
返回patt中第n个捕获的值,如果number = 0, 没有捕获的值。
patt / table
将patt第一个捕获的值作为key,没有则将整个匹配的值作为key,table[key]就是最好捕获的值,如果没有,则没有捕获的值。
patt / function
将patt捕获的值全部作为参数传递给function,没有则将整个capture作为参数,function返回的值就是捕获的值,没有返回值,则没有捕获的值。
lpeg.Cmt(patt, function)
将整个subject,当前index,捕获的值作为参数传递给function。
function返回数字的话,匹配成功,并且将该数字作为subject新的index。如果返回true,则匹配成功,不消耗任何字符。如果返回false,nil,不返回任何字符,匹配失败。
该函数返回的额外值,作为该Capture捕获的值。