Lua解析神器Lpeg

Lua Lpeg

  • 用基本匹配函數,組合匹配表達式
  • 所有匹配函數返回userdata類型,是一個匹配模式(以下用pattern代替),可相互組合

lpeg.P

lpeg.P(value)

將給定的value,根據規則返回適當的pattern,規則如下:

  • value是pattern,原封不動的返回這個pattern
  • value是string, 返回整個該字符串的pattern
  • value是非負整數,只有在輸入的字符串長度大于等于value個字符串時返回成功。
  • value是負整數,只有在輸入的字符串長度還剩下不到value個字符才返回成功,lpeg.P(-n)等價于-lpeg.P(n)
  • value是boolean,總是返回成功或者失敗的pattern
  • value是table,將它理解為一個語法(Grammar)
  • value是function,返回pattern,它等價于一個match-time capture用一個空字符串的匹配模式(lpeg.P(function)等價于lpeg.Cmt("", function))。

示例:

  • 都是從第一個字符串開始匹配,若沒匹配到就返回nil
  • 如果匹配到pattern,則返回匹配到的字符串長度加1
local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P

-- value是string的情況
local ps = P'ab'
print(match(ps, "a"))  ---> nil
print(match(ps, "abcd"))  ---> 3  注:只匹配"ab"

-- value是非負整數
-- 注:完全匹配任意value個字符
local pn1 = P(3)  
-- local pn1 = P(3.0)  -- 這樣也是可以的
print(match(pn1, "12"))  ---> nil  注:只有2個字符
print(match(pn1, "abcd"))  ---> 4

-- 注:value不能是小數,但是小數部分為0是可以的
-- 如果是小數,總是返回1
local pn2 = P(3.1)
print(match(pn2, ""))  ---> 1

-- value是負整數
-- 被匹配的字符串長度要小于value
local pn3 = P(-3)  -- 等價于  local pn3 = - P(3)
print(match(pn3, "abcd"))  ---> nil  注:字符串長度>=3了
print(match(pn3, "ab"))  ---> 1
print(match(pn3, ""))  ---> 1

-- value是boolean
local pb1 = P(true)
local str1 = "aaa"  -- str1為任意字符串
print(match(pb1, str1))  ---> 1  注:當value為true時,不管str1是什么字符串,始終返回1

local pb2 = P(false)
local str2 = "aaa"  -- str2為任意字符串
print(match(pb2, str2))  ---> nil  注:當value為false時,不管str2是什么字符串,始終返回nil

-- value是table
local m1 = Cs((P(1) / { a = 1, b = 2, c = 3 }) ^ 0)
print(match(m1, "abc"))  ---> 123

-- value是function
-- 請跳到match-time capture(lpeg.Cmt)

lpeg.B

  • 返回一個匹配模式,只有在輸入的字符串在當前位置是patt的謂語時,才被匹配到。
  • patt在有調整長度的字符串中匹配,并且不包含捕獲

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local B = lpeg.B

local m1 = B"a"
local m2 = 1 * m1
local m3 = 1 * B"b"

print(match(m1, "a"))  ---> nil
print(match(m2, "a"))  ---> 2
print(match(m3, "a"))  ---> nil

-- 注:這里的m3和m2的區別就是一個是a,一個是b,但得到的結果不一樣

local m4 = B(1)
local m5 = 1 * m4

print(match(m4, "a"))  ---> nil
print(match(m5, "a"))  ---> 2
print(match(-m4, "a"))  ---> nil

local m6 = B(250)
local m7 = 250 * m6

print(match(m6, string.rep("a", 250)))  ---> nil
print(match(m7, string.rep("a", 250)))  ---> 251

lpeg.R

  • 給定一個2字符的字符串(可以傳多個這樣的字符串),返回兩個字符之間范圍的pattern
  • 字符可以為任意字符編碼(閉區間)

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local R = lpeg.R

local r1 = R"09" -- 匹配任意數字
print(match(r1, "12345"))  ---> 2 注:只要匹配到1個字符就返回

local r2 = R("az", "AZ")  -- 可以傳多個2個字符組成的字符串
print(match(r2, "abcd"))  ---> 2
print(match(r2, "Abcd"))  ---> 2

-- 特別注意
local r3 = R()
local str = "aaa"
print(match(r3, str))  ---> nil
-- 當R沒有參數時,不管str為什么字符串,總返回nil

lpeg.S

  • 給定一個字符串(字符串等價于字符的集合(set)),返回一個匹配該字符串里任意一個字符的pattern

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local S = lpeg.S

local s1 = S"+-*/"
print(match(s1, "+"))  ---> 2
print(match(s1, "-"))  ---> 2
print(match(s1, "*"))  ---> 2
print(match(s1, "/"))  ---> 2

-- 特別注意
local s2 = S""
local str = "aaa"
print(match(s2, str))  ---> nil
-- 注:當S的參數為空字符串的時候,不管str是什么字符串,總返回nil

lpeg.V

  • 這個操作為語法(Grammar)生成一個變量(non-terminal),變量規則是閉包語法中的v
  • 個人理解:因為語法是通過table來定義的,調用這個函數告訴lpeg在table里定義了哪些變量名
  • 剩下的后面說哇~~

lpeg.locale

  • 返回一個table,table里面的元素是lpeg提供的常用pattern,有以下匹配模式:alnum,alpha,cntrl,digit,graph,lower,print,punct,space,upper,xdigit
  • 如果參數是table,則會把這些pattern放到參數的table里面

示例:

local lpeg = require "lpeg"

local lpegHelp = {}
lpeg.locale(lpegHelp)

local help = lpeg.locale()

Lpeg操作符

#patt

  • 返回一個pattern。如果輸入的字符串被patt匹配到,這個pattern才會被匹配。
  • 這個pattern被叫做,and謂語,等價于原生PEG中的&patt
  • 這個pattern從不產生任何捕獲。

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P
local C = lpeg.C
local V= lpeg.V

local p1 = P"aa"
local p2 = #p1

print(match(p1, "a"))  ---> nil
print(match(p2, "a"))  ---> nil

print(match(p1, "aaaa"))  ---> 3
print(match(p2, "aaaa"))  ---> 1

-- 進階
local pat = P{
    "S";
    S1 = C("abc") + 3,
    S = #V("S1")     -- 這里捕獲到了結果,但是#必須忽略這個結果
}

print(match(pat, "abc"))  ---> 1

-- 調整長度(fixed length)
local m1 = #("a" * (P"bd" + "cd"))
local m2 = C(m1 * 2)
local m3 = C(m1 * 3)
local m4 = C(m1 * 4)
print(match(m1, "acd"))  ---> 1
print(match(m2, "acd"))  ---> ac
print(match(m3, "acd"))  ---> acd
print(match(m4, "acd"))  ---> nil

-- 解析:
-- 1. #patt后面跟著 * number時,是對捕獲到的值的長度進行調整
-- 2. number的值不能超過捕獲到的值的長度

-patt

  • 返回一個pattern,只有patt沒有被匹配到的時候始終返回1,否則返回nil
  • 這個pattern從不產生任何捕獲
  • 這個pattern等價于patt的補集
  • 個人理解:這里始終返回1是根據結果猜的,如果有其他情況后面再補上

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P

local p1 = P"aa"
local p2 = - p1

print(match(p1, "aa"))  ---> 3
print(match(p2, "aa"))  ---> nil

print(match(p1, "a"))  ---> nil
print(match(p2, "a"))  ---> 1

patt1 + patt2

  • 返回一個pattern,匹配patt1或者patt2
  • 如果patt1和patt2都是字符集合,這個操作,返回結果的并集。

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P

local p1 = P"aaa"
local p2 = P"aa"
local p3 = p1 + p2

print(match(p3, "a"))  ---> nil
print(match(p3, "aa"))  ---> 3
print(match(p3, "aaa"))  ---> 4

patt1 - patt2

  • 返回一個pattern,不捕獲patt2并且捕獲到patt1
  • 當捕獲成功后,這個pattern產生patt1的所有捕獲,并且從不捕獲patt2
  • 如果patt1和patt2都是字符集合,這個操作相當于兩個集合的差集。- patt2 = "" - patt2 = 0 - patt2。(如果patt2是字符集合,那么1 - patt2是補集)
local lpeg = require "lpeg"

local match = lpeg.match
local S = lpeg.S

local s1 = S"\1\0\2"
local s2 = S"\0"
local s3 = s1 - s2

print(match(s1, "\0"))  ---> 2
print(match(s2, "\0"))  ---> 2
print(match(s3, "\0"))  ---> 0
print(match(s3, "\1"))  ---> 2

-- 注:這里的s3是s1與s2的差集,也就是不配s2并且匹配s1

patt1 * patt2

  • 返回一個pattern,它會先匹配patt1,如果匹配成功繼續匹配patt2

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P

local p1 = P"aa"
local p2 = P"bb"
local p3 = p1 * p2

print(match(p3, "aabb"))  ---> 5
print(match(p3, "aaaa"))  ---> nil

patt^n

  • 如果n是非負整數,這個pattern將匹配n個或n個以上patt
  • 如果n是負整數,那么這個pattern講最多匹配n個patt,也就是最少匹配0個,最多匹配n個
  • patt ^ 0類似于lua里面正則表達式的*
  • patt ^ 1類似于lua里面正則表達式的+
  • patt ^ -1類似于lua里面正則表達式的?
  • 在所有情況下,此pattern的結果是沒有回溯的貪婪模式,也就是說匹配最長的可能匹配到的序列
local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P

local p1 = P"aa"
local p2 = p1 ^ 0
local p3 = p1 ^ 3
local p4 = p1 ^ -1

print(match(p1, "aaaa"))  ---> 3
print(match(p2, "aaaa"))  ---> 5
print(match(p3, "aaaa"))  ---> nil
print(match(p3, "aaaaaa"))  ---> 7
print(match(p4, "aaaa"))  ---> 3
print(match(p4, "a"))  ---> 1  注:這里沒有匹配到也返回1

Lpeg語法(Grammars)

  • 在lua的環境下,可以自定義一些patterns,讓新定義的pattern可以使用已經定義過的舊的pattern。然而,這種技術不允許定義遞歸匹配模式。對于遞歸匹配模式,我們需要真正的語法。
  • Lpeg用tables來描述語法,每個條目就是一個語法規則。
  • 通過調用lpeg.V(v)來創建一個pattren,它表示在語法中的一個引索V。
  • 當table被轉換成了pattern(通過調用lpeg.P或者在一個pattern中使用),它被固定了。
  • 當table已經固定,結果是一個匹配它初始規則的pattern。第一個引索的條目就是她初始規則。如果這個條目是字符串,那么它被認為是初始規則的名字。否則lpeg假定第一個條目就是table的初始規則。

捕獲(Captures)

  • 捕獲是一種模式,該模式會根據匹配到的數據返回值(語義信息)。
  • lpeg有幾種捕獲方式,產生的值會基于匹配,并且組合這個值產生新的值。
  • 每個捕獲可能產生0個或多個值。

lpeg.C (patt)

  • 創建一個簡單的捕獲,捕獲匹配到patt的子字符串。
  • 捕獲的值是一個字符串。
  • 如果patt有其他捕獲,他們的值將在這個值之后返回

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P
local R = lpeg.R
local S = lpeg.S

local C = lpeg.C

local p1 = P"aa" ^ 3
local cp1 = C(p1)

print(match(cp1, "aaaaaa"))  ---> aaaaaa
print(match(cp1, "aa"))  ---> nil

local r1 = R"09" ^ 0
local cr1 = C(r1)

print(match(cr1, "1234aaaa"))  ---> 1234
print(#match(cr1, "aaaa"))  ---> 0  注:這里返回的是空字符串,跟lpeg.P不同

local s1 = S"+-*/" ^ 0
local cs1 = C(s1)

print(match(cs1, "++++aaaa"))  ---> ++++
print(#match(cs1, "aaaa"))  ---> 0  注:這里返回的是空字符串,跟lpeg.P不同 

-- 進階
local m1 = { [1] = C(C(1) * V(1) + -1) }

print(match(m1, "abc"))  ---> "abc" "a" "bc" "c" "c" ""  注:這里的值添加了引號,最后一個是空字符串

-- 解析:
-- 過程:
-- ① (1)匹配到a,然后匹配V(1),由于C(1),產生結果a
-- ② (1)匹配到b,然后匹配V(1),由于C(1),產生結果b
-- ③ (1)匹配到c,然后匹配V(1),由于C(1),產生結果c
-- ④ (1)C(1) * V(1)沒有匹配到,匹配到-1,返回到③,由于-1,產生""
-- ③ (2)匹配到C(1) * V(1),返回到②,由于C(1) * V(1),產生c和④的組合,就是c
-- ② (2)匹配到C(1) * V(1),返回到①,由于C(1) * V(1),產生b和③的組合,就是bc
-- ① (2)匹配到C(1) * V(1),返回結果,由于C(1) * V(1),產生a和②的組合,就是abc
-- 這個過程是個遞歸的過程,看似有點復雜,根據結果,仔細分析一下就能得出結論啦

lpeg.Carg (n)

  • 創建一個參數捕獲。
  • 未完待續

lpeg.Cb (name)

  • 創建一個回退捕獲
  • 這個pattern匹配空字符串,并且通過最近的group cpature(lpeg.Cg)被命名的名字(名字可以是任何Lua類型),產生返回值
  • 最近的group cpature(lpeg.Cg)被命名的名字的意思是,最近一次完整的給定名字的組捕獲
  • 完整的捕獲意思是,整個模式對應的捕獲匹配。
  • 外層的意思是,這個捕獲不是在另一個完整的捕獲

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local Cg = lpeg.Cg
local Cb = lpeg.Cb

local t = {}
local foo = function(s)
    t[#t + 1] = s
    return s .. "x"
end

local m1 = Cg(C(2) / foo, "y") * Cb"y"
             * Cg(Cb"y" / foo, "y") * Cb"y"
             * Cg(Cb"y" / foo, "y") * Cb"y"

print(match(m1, "ab"))  ---> abx abxx abxxx
print(table.concat(t, " "))  ---> ab ab abx ab abx abxx

-- 解析:
-- ① C(2)捕獲到"ab",通過foo函數,向t插入"ab",并返回"abx",再給"abx"打上"y"的標記
-- ② 接下來的Cb"y",捕獲到打了"y"標記的"abx",這里產生了"abx"
-- ③ 第一個Cg(Cb"y" / foo, "y"),這里的Cb"y",從Cg(C(2) / foo, "y")開始,走①②流程
-- ④ 然后再匹配Cg(③的結果 / foo, "y") * Cb"y",向t插入"abx",此時t里面的內容是"ab" "ab" "abx",并通過foo函數返回"abxx",并打上"y"標記
-- ⑤ 接下來的Cb"y",捕獲到打了"y"標記的"abxx",這里產生了"abxx"
-- ⑥ 第二個Cg(Cb"y" / foo, "y"),重復③的流程

-- 最后m1捕獲到的結果是:"abx" "abxx" "abxxx"
-- t里面的結果是
-- {
--      "ab",                       -- 這個是Cg(C(2) / foo, "y") * Cb"y"的結果
--      "ab", "abx",                -- 這個是第一個* Cg(Cb"y" / foo, "y") * Cb"y"的結果
--      "ab", "abx", "abxx"         -- 這個是第二個* Cg(Cb"y" / foo, "y") * Cb"y"的結果
-- }

lpeg.Cg (patt [, name])

  • 創建一個組捕獲。這個組捕獲的所有返回值都是通過patt捕獲到的簡單返回值
  • 如果沒有給組取名字,那么這個組就是匿名的,或者這個組的名字是給定的名字(nil不能作為名字)
  • 在大多數情況下,一個被命名的組,是沒有任何捕獲值返回的,只有在back capture(lpeg.Cb)或者table capture(lpeg.Ct)才會有捕獲值返回

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local Cg = lpeg.Cg
local Cc = lpeg.Cc
local C = lpeg.C

local m1 = Cg(1)
print(match(m1, "x"))  ---> x

local m2 = Cg(Cg(Cg(1)))
print(match(m2, "x"))  ---> x

local m3 = Cg(Cg(Cg(C(1)) ^ 0) * Cg(Cc(1) * Cc(2)))
print(match(m3, "abc"))  ---> a b c 1 2

local m4 = Ct(Cg(Cc(10), "hi") * C(1) ^ 0 * Cg(Cc(20), "ho"))
print(match(m4, "abc"))  ---> { hi = 10, ho = 20, "a", "b", "c" }

-- 非字符串的組名
local print = print
local tab = {}
local m5 = Ct(Cg(1, print) * Cg(1, 23.5) * Cg(1, tab))

local result = match(m5, "abcdefghij")
print(result)  ---> { [function: 0x10322db70] = "a", [23.5] = "b", [table: 0x7faaa2408660] = "c" }

print(result[print])  ---> a
print(result[tab])  ---> c

lpeg.Cp ()

  • 創建一個位置捕獲。這個pattern匹配空字符串,并且返回捕獲的位置
  • 這個捕獲值是數字類型

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local R = lpeg.R
local Cp = lpeg.Cp

local letter = R"az" + R"AZ"

local m1 = letter ^ 1
local m2 = Cp() * m1
local m3 = m2 * Cp()
local m4 = C(m3)
local m5 = Cp() * letter ^ 5 * Cp()

print(match(m1, "abcd"))  ---> 5
print(match(m2, "abcd"))  ---> 1
print(match(m3, "abcd"))  ---> 1 5
print(match(m4, "abcd"))  ---> abcd 1 5
print(match(m5, "abcd"))  ---> nil

-- 解析:
-- 1. 通過m1和m2,推出:如果有Cp的pattern只返回Cp的結果
-- 2. 通過m3,推出:存在Cp時,并匹配成功,只返回Cp()相關結果
-- 3. 通過m4,推出:會優先捕獲其他pattern的結果,再返回Cp的結果

lpeg.Cc ([value, ...])

  • 創建一個常量捕獲,這個pattern匹配到一個空字符串并且產生所有給定的值作為捕獲的值

示例

local lpeg = require "lpeg"

local match = lpeg.match
local R = lpeg.R
local Cc = lpeg.Cc

local m1 = C(R"09" ^ 1 * Cc("a"))
local m2 = C(R"09" ^ 1 * Cc("a", "b"))
local m3 = Cc(nil)
local m4 = Cc()

print(match(m1, "123"))  ---> 123 a
print(match(m2, "123"))  ---> 123 a b
print(match(m3, "123"))  ---> nil
print(match(m4, "123"))  ---> 1

----- 分割線 -----

local m5 = Cc() * Cc() * Cc(1) * Cc(20, 30, 40) * "a" * Cp()
print(match(m5, "aaa"))  ---> 1 20 30 40 2

local m6 = Cc() * Cc() * Cc(nil) * Cc(1) * Cc() * Cc(20, 30, 40) * "a" * Cp()
local m7 = Cc(1) * Cc(2) * Cc(nil) * Cc(3) * Cc()
print(match(m6, "aaa"))  ---> nil 1 20 30 40 2
print(match(m7, "aaa"))  ---> 1 2 nil 3  注:這里只返回了4個結果
-- 根據結果,推出以下結論:
-- 1. Cc()單獨匹配時返回1,例如m4
-- 2. Cc()和其他太參數的Cc函數或其他pattern組合時,Cc()會被忽略,即Cc()什么都不會返回

lpeg.Cf (patt, func)

  • 創建一個迭代捕獲。
  • 如果這個patt產生了一個C1, C2, ..., Cn的捕獲列表,那么這個捕獲將產生一個值,這個值由func(...func(func(C1, C2), C3)..., Cn)產生
  • 也就是說,它會使用func對產生的捕獲值進行迭代
  • 這個patt至少捕獲到一個值(任何類型的值),這個值來作為累積器的初始值。至少要這個條件成立
  • 如果patt要一個特殊的初始值,應該在這個patt前面加一個constant capture(lpeg.Cc)
  • 對于后面的每一個捕獲,LPeg調用func,這個累積值作為func的第一個參數,這個捕獲產生的所有值作為這個func的額外參數,func的結果成為新的累積值。這個累積的最終值就是這個patt的結果

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local R = lpeg.R
local Cf = lpeg.Cf
local Cc = lpeg.Cc
local C = lpeg.C

local number = R"09" ^ 1 /tonumber
local list = number * ("," * number) ^ 0

function add(acc, cpatureNewValue)
    return acc + cpatureNewValue
end

local sum = Cf(list, add)
print(match(sum, "10,30,43"))  ---> 83

local f = function(x)
    return x + 1
end

local m1 = Cc(0) * C(1) ^ 0
local m2 = Cf(m1, f)
print(match(m1, "alo alo"))  ---> 0 "a" "l" "o" "" "a" "l" "o"
print(match(m2, "alo alo"))  ---> 7
-- 注:通過m1的結果,推出:Cc(0)是第一個捕獲值,最后迭代出7

local m3 = Cf(Cc(1, 2, 3), error)
print(match(m3, ""))  ---> 1  注:這里調用error直接返回1(個人理解)

local m4 = Ct(true) * Cg(C(R"az" ^ 1) * "=" * C(R"az" ^ 1) * ";") ^ 0
local m5 = Cf(m4, rawset)
print(match(m4, "a=b;c=du;xux=yuy;"))  ---> {} a b c du xux yuy
print(match(m5, "a=b;c=du;xux=yuy;"))  ---> { a= "b", c = "du", xux = "yuy" }
-- 注:這里的Ct(true)捕獲到一個空table,m5通過rawset向空table設置key-value

lpeg.Cs (patt)

  • 創建一個替換捕獲

lpeg.Ct (patt)

  • 創建一個table捕獲。這個捕獲返回一個table
  • 返回所有patt的匿名捕獲的值,key從1開始
  • 此外,每個被命名的捕獲組,第一個值是組名

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local R = lpeg.R
local Ct = lpeg.Ct
local C = lpeg.C
local Cc = lpeg.Cc

local letter = R"az" + R"AZ"

local m1 = letter ^ 1
local m2 = Ct(m1)

print(match(m1, "alo"))  ---> 4
print(match(m2, "alo"))  ---> {}   注:m1沒有返回捕獲的值

local m3 = C(m1)
local m4 = Ct(m3) * Cc("t")

print(match(m4, "alo"))  ---> { "alo" }  "t"

local m5 = Ct(C(C(letter) ^ 1))
print(match(m5, "alo"))  ---> { "alo", "a", "l", "o" }

local m6 = Ct((Cp() * letter * Cp()) ^ 1)
local m7 = Ct(m6)

print(match(m6, "alo"))  ---> { 1, 2, 2, 3, 3, 4 }
print(match(m7, "alo"))  ---> { { 1, 2, 2, 3, 3, 4 } }

patt / string

  • 創建一個字符串捕獲。這個操作會創建一個基于string的字符串捕獲
  • 返回的捕獲的值是string的一個副本,string中的%是轉義字符,如果要表示%,則用%%來表示
  • 字符串中的任何序列中有%n(n在1到9之間),在patt中匹配第n個捕獲。%0表示匹配到的整個序列

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P
local Cp = lpeg.Cp

local pi = "3.14159 26535 89793 23846 26433 83279 50288 41971 69399 37510"

local m1 = (P"1" / "a" + P"5" / "b" + P"9" / "c" + 1) ^ 0
print(match(m1, pi))  ---> a a b c b b c c c b a c a c c c b a
-- 注:這里返回的是捕獲到值后被替換的值

print(match(m1, "222"))  ---> 4  注:這里只匹配到了1,所以返回4

local m2 = Cp() * P(3) * Cp() / "%2%1%1 - %0"
print(match(m2, "abcde"))  ---> "411 - abc"
-- 解析:
-- 1. 先匹配Cp() * P(3) * Cp(),匹配到 1 4
-- 2. 然后通過 / "%2%1%1 - %0" 格式化結果
-- 3. 這里的%1對應1,%2對應4,%0對應"abc"

local m3 = C"a" / "%1%%%0"
print(match(m3, "a"))  ---> a%a

local m4 = P(1) / "%0"
print(match(m4, "abc"))  ---> a

local m5 = C(1) ^ 0 / "%2-%9-%0-%9"
print(match(m5, "0123456789"))  ---> "1-8-0123456789-8"

local m6 = C(1) ^ 0 / "9-%1-%0-%3"
print(match(m6, "12345678901234567890"))  ---> "9-1-12345678901234567890-3"
-- 注:%1 - %9 是所有捕獲的結果,%0 - %9 的值都只能是數字或者字符串,其他類型報錯

patt / number

  • 創建一個有編號的捕獲
  • 對于非0的number,返回的是被捕獲的第n個值
  • 當number為0時,不會返回捕獲的值

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local C = lpeg.C

local m1 = C(1)
local m2 = C(C(m1 * C(2)) * C(3))
local m3 = m2 / 3
local m4 = m2 / 0

print(match(m2, "aaabcdefgh"))  ---> aaabcd aaa a aa bcd
-- 注:這里返回值的順序是什么規律?按匹配的順序也不像哇~~~~

print(match(m3, "aaabcdefgh"))  ---> a
print(match(m4, "aaabcdefgh"))  ---> 7
-- 注:
-- 1. 如果是負數會報錯
-- 2. 如果number超出捕獲的長度,也會報錯

local m5 = m1 * (C(m1 * C(2)) * C(3) / 4) * m1
print(match(m5, "abcdefgh"))  ---> a efg h
-- 解析:
-- ① 匹配m1,捕獲到a
-- ② (1)先匹配m1,捕獲到b
-- ② (2)再匹配C(2),捕獲到cd
-- ② (3)再匹配m1 * C(2),捕獲到bcd
-- ② (4)再匹配C(3),捕獲到efg
-- ② (5)再取②捕獲到的值的第4個捕獲值,最終得到efg
-- ③ (6)最后匹配m1,捕獲到h

patt / table

  • 創建一個查詢捕獲。patt捕獲到的值作為table中的key,來引索table中的value
  • table中的值是捕獲的最終值。如果table沒有key,將不會產生捕獲

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P

local pi = "3.14159 26535 89793 23846 26433 83279 50288 41971 69399 37510"
local m1 = (P(1) / { ["1"] = "a", ["5"] = "b", ["9"] = "c" }) ^ 0

print(match(m1, pi))  ---> a a b c b b c c c b a c a c c c b a
print(match(m1, "222"))  ---> 4

patt / function

  • 創建一個函數捕獲
  • 這個pattern將調用給定的function,并把捕獲到的所有值當參數傳個這個function
  • function的返回值是這個捕獲的最終值,如果function沒有返回值,也就沒有捕獲值

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local C = lpeg.C
local Cg = lpeg.Cg
local Cb = lpeg.Cb

local foo = function(s)
    return s .. "0"
end

local m1 = Cg(C(3) / foo, "name") * Cb("name")
print(match(m1, "abc"))  ---> abc0

lpeg.Cmt(patt, function)

  • 創建一個匹配時的捕獲
  • 不像其他捕獲,當有一個匹配時,會立即執行(甚至在一個很復雜的匹配模式中失敗)
  • 它會強制執行所有嵌入的捕獲,并調用function
  • 整個待匹配的字符串,當前匹配到的坐標,還有任何捕獲到的值是這個function的參數
  • function返回的第一個值定義了匹配過程,如果返回的是一個數字,則表示匹配成功,返回的數字成為新的匹配位置
  • 假設一個被匹配的字符串s,和當前的匹配位置i,返回的數字必須在[i, len(s+1)]
  • 如果返回true,匹配成功時,不消耗任何輸入(所以返回true,等價于返回i)
  • 如果返回falsenil,或者無返回值,匹配失敗
  • function額外的返回值會成為捕獲的值

示例:

local lpeg = require "lpeg"

local match = lpeg.match

local S = lpeg.S
local P = lpeg.P
local R = lpeg.R
local V = lpeg.V
local C = lpeg.C

local Cs = lpeg.Cs
local Ct = lpeg.Ct
local Cg = lpeg.Cg
local Cmt = lpeg.Cmt

local space = S" \t\n" ^ 0

local id = function(s, i, ...)
    return true, ...
end

local m1 = P{
    "S",
    S = V"atom" * space + Cmt(Ct("(" * space * (Cmt(V"S" ^ 1, id) + P(true)) * ")" * space), id),
    atom = Cmt(C(R("AZ", "az", "09") ^ 1), id)
}

print(match(m1, "(a g () ((b) c) (d (e)))"))  ---> { "a", "g", {}, { { "b" }, "c" }, { "d", { "e" } } }

local m2 = Cmt(1, id) ^ 0
print(match(m2, string.rep("a", 5)))  ---> a a a a a

local id = function(s, i, x)
    if x == "a" then
        return i, 1, 3, 7
    end
    
    return nil, 2, 4, 6, 8
end

local m3 = (P(id) * 1 + Cmt(2, id) * 1 + Cmt(1, id) * 1) ^ 0
print(match(m3, "abababab"))  ---> 1 3 7 1 3 7 1 3 7 1 3 7
-- 問題:這里每個Cmt后面要 * 1呢?不加的話會報錯

local ref = function(s, i, x)
    -- print(s, i, x)
    return match(x, s, i - #x)
end

local m4 = Cmt(P(1) ^ 0)
local m5 = P(1) * m4
local m6 = P(1) * Cmt(C(1) ^ 0)

print(match(m4, "alo"))  ---> 4
print(match(m5, "alo"))  ---> 4
print(match(m6, "alo"))  ---> nil
-- 注:這里的m5和m6的區別,一個P,一個是C;
-- P后面跟了^0,所以要等P匹配完才產生捕獲;
-- 雖然C后面也跟了^0,但是C立馬產生捕獲,
-- 所以會立馬調用ref,就會匹配不到,然后直接返回nil

ref = function(s, i, a, b)
    if a == b then
        return i, a:upper()
    end
end

local any = P(1)

local m7 = Cmt(C(R"az" ^ 1) * "-" * C(R"az" ^ 1), ref)
local m8 = (any - m7) ^ 0 * m7 * any ^ 0 * -1

print(match(m8, "abbbc-bc ddaa"))  ---> BC
-- 解析:
-- ① (1)先匹配(any - m7) ^ 0,這里是不匹配m1,并且匹配any,匹配至少0次
-- ① (2)先匹配m7,這里會捕獲到"abbbc"和"bc"當做參數傳給ref,也就是ref的a和b兩個參數,然后"abbbc"不等于"bc",沒有返回值,所以也就沒有匹配到m7,且匹配到any
-- ② 重復①的過程,這次m7匹配到"bbbc"和"bc",也沒匹配到m7
-- ③ 重復①的過程,這次m7匹配到"bbc"和"bc",也沒匹配到m7
-- ④ 重復①的過程,這次m7匹配到"bc"和"bc",這回匹配到m7了,所以(any - m7) ^ 0的匹配終止,產生結果4
-- ⑤ 接著匹配m7,從位置4開始匹配,然后捕獲到"bc"和"bc",返回了一個捕獲值"BC"

local check = function(_, _, s1, s2)
    return s1 == s2
end

local m9 = "[" * Cg(P"=" ^ 0, "init") * "[" * {
    Cmt("]" * C(P"=" ^ 0) * "]" * Cb("init"), check) + 1 * V(1) 
} / 0
print(match(m9, "[==[]]====]]]]==]===[]"))  ---> 18
-- 解析:
-- ① 首先匹配"[" * Cg(P"=" ^ 0, "init") * "[",這里捕獲到"==",并命名為"init",返回位置5
-- ② (1)再匹配{ Cmt("]" * C(P"=" ^ 0) * "]" * Cb("init"), check) + 1 * V(1) } / 0,這里有兩個捕獲,一個是C(P"=" ^ 0),另一個是Cb("init")
-- ② (2)先匹配Cmt("]" * C(P"=" ^ 0) * "]" * Cb("init"), check),從位置5開始匹配,匹配到]],產生捕獲"",和前面捕獲的"=="比較,不相等,返回false,匹配失敗
-- ② (3)然后匹配1 * V(1),重復過程②(1)
-- ③ 直到匹配到]==]

Lpeg 小技巧

示例:

local lpeg = require "lpeg"

local match = lpeg.match
local P = lpeg.P
local R = lpeg.R

local V = lpeg.V
local C = lpeg.C

print(match(3, "aaaa"))  ---> 4
print(match(P(3), "aaaa"))  ---> 4

-- lpeg 對lua的數據類型默認使用lpeg.P來處理

local m1 = R"09" * -1
local m2 = R"09" * P(-1)
local m3 = R"09" * -P(1)

-- 這里的m1、m2、m3等價

print(match(m1, "9"))  ---> 2
print(match(m1, "99"))  ---> nil
print(match(m2, "9"))  ---> 2
print(match(m2, "99"))  ---> nil
print(match(m3, "9"))  ---> 2
print(match(m3, "99"))  ---> nil

-- lpeg.V、#patt
local tab = {
    [1] = "(" * (((1 - S"()") + #P"(" * V(1)) ^ 0)
}

-- 這里的V(1)指的是tab[1]的規則
-- 這里的#P"("表示這條規則匹配到的"("的個數(個人結論,不太確定)

local digit = R"09"
local upper = R"AZ"
local lower = R"az"
local letter = S"" + upper + lower
local alpha = letter + digit + R()

local m1 = letter ^ 1
local m2 = m1:C()
local m3 = C(m1)  -- 注:這里的m3等價于m2
local m4 = P{ [1] = m2 + (1 * V(1)) }
local m5 = m4 ^ 0

print(match(m1, "   4achou123..."))  ---> nil
print(match(m4, "   4achou123..."))  ---> achou
print(match(m5, " two words, one more  "))  ---> two  words  one  more

-- 解析:
-- m1是匹配所有字母,通過m4的語法,就可以匹配不以字母開頭的字符串
-- 關鍵點是  1 * V(1)  中的 1 *,這里的1等價于P(1),也就是至少匹配任意一個字符
-- 匹配過程:
-- (1) 第一個字符是空白符,m2是肯定沒匹配到,那么匹配(1 * V(1))
-- (2) 匹配到1的時候,符合匹配條件,再匹配V(1),此時又回到過程(1)
-- 直到都不符合匹配。整個過程是個遞歸的過程,最后把匹配到的結果返回

-- 語法例子
local m6 = P{ P"x" * V(1) + P"y" }
-- 這里的m6可以改為 local m6 = P{ P"x" * V(1) + -1 } 看看是什么結果
local m7 = -m6
local m8 = C(m7)

print(match(m6, "xxx"))  ---> nil
print(match(m7, "xxx"))  ---> 1
print(match(m8, "xxx"))  ---> ""  注:這里是空字符串

-- 解析:
-- ① m6的匹配過程:一直匹配P"x" * V(1),知道最后一個x,然后匹配P"y",但是沒有沒匹配到,直接返回nil
-- ② m7根據①的返回結果,返回①的補集,這里nil的補集就是1
-- ③ m8根據②的返回結果,捕獲到一個空字符串
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容