使用一個(gè)模式
這個(gè)例子顯示了一個(gè)建立和使用模式的程序,它非常簡(jiǎn)單但很完整:
復(fù)制代碼 代碼如下:
local lpeg = require "lpeg"
-- matches a word followed by end-of-string
p = lpeg.R"az"^1 * -1
print(p:match("hello")) --> 6
print(lpeg.match(p, "hello")) --> 6
print(p:match("1 hello")) --> nil
模式是簡(jiǎn)單的一個(gè)或多個(gè)小寫字符并在尾端以(-1)結(jié)束的序列。該程序調(diào)用match來(lái)當(dāng)作一個(gè)方法和函數(shù)。在以上成功案例,匹配函數(shù)返回成功 匹配的第一個(gè)字符的索引,為其字符串長(zhǎng)度加1。
復(fù)制代碼 代碼如下:
Name-value lists
這個(gè)例子解析一個(gè)名稱 - 值配對(duì)的列表,并返回那些配對(duì)的表:
復(fù)制代碼 代碼如下:
lpeg.locale(lpeg) -- adds locale entries into 'lpeg' table
local space = lpeg.space^0
local name = lpeg.C(lpeg.alpha^1) * space
local sep = lpeg.S(",;") * space
local pair = lpeg.Cg(name * "=" * space * name) * sep^-1
local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset)
t = list:match("a=b, c = hi; next = pi") --> { a = "b", c = "hi", next = "pi" }
每一配對(duì)都有 formatname =namefollowed 的一個(gè)可選的分離器(用逗號(hào)或分號(hào))。 配對(duì)模式(Thepairpattern)在一個(gè)組模式里形成閉包,那么那些名稱就可以成為單個(gè)捕獲的值。 列表模式 (Thelistpattern)然后折疊這些捕獲。 它以空列表開始,通過(guò)創(chuàng)建列表捕獲匹配一個(gè)空字符串,然后為每個(gè)捕獲(一名稱對(duì))appliesrawsetover累加器(表)和捕捉值(對(duì)名稱)。rawsetreturns((未初始化的集合)返回表本身,所以累加器總是表中執(zhí)行。
以下代碼創(chuàng)建了一個(gè)模式,該模式使用給定的分隔模式sep作為分隔器來(lái)來(lái)拆分字符串:
復(fù)制代碼 代碼如下:
function split (s, sep)
sep = lpeg.P(sep)
local elem = lpeg.C((1 - sep)^0)
local p = elem * (sep * elem)^0
return lpeg.match(p, s)
end
首先,該函數(shù)確保sep一個(gè)合適的模式。只要沒有匹配分隔器,該模式的elem 是重復(fù)的零個(gè)或多個(gè)任意字符。它還捕捉其匹配值。模式p匹配由sep拆分的一組元素.
如果拆分產(chǎn)生的結(jié)果值太多,可能會(huì)溢出由一個(gè)Lua函數(shù)返回的最大數(shù)目的值。在這種情況下,我們可以將這些值放到一個(gè)表中:
復(fù)制代碼 代碼如下:
function split (s, sep)
sep = lpeg.P(sep)
local elem = lpeg.C((1 - sep)^0)
local p = lpeg.Ct(elem * (sep * elem)^0) -- make a table capture
return lpeg.match(p, s)
end
模式搜索
基本的匹配僅僅工作在錨定模式下。如果我們打算查找匹配字符串中任何地方的模式,那么我們必須寫一個(gè)匹配任何地方的模式。
因?yàn)槟J绞强梢跃帉懙?,所以我們可以編寫一個(gè)函數(shù),它給定一個(gè)任意的模式p,返回一個(gè)搜索p的新模式,以匹配字符串的任何位置。執(zhí)行這種搜索有幾種方法。一種方法如下:
復(fù)制代碼 代碼如下:
function anywhere (p)
return lpeg.P{ p + 1 * lpeg.V(1) }
end
這個(gè)語(yǔ)法的直接解讀:匹配p或者跳過(guò)一個(gè)字符,然后試圖再次匹配。
如果我們想知道這個(gè)模式在字符串的所有匹配位置(而不僅僅知道它在字符串的某個(gè)位置),那么我們可以給這個(gè)模式添加位置捕捉:
復(fù)制代碼 代碼如下:
local I = lpeg.Cp()
function anywhere (p)
return lpeg.P{ I * p * I + 1 * lpeg.V(1) }
end
print(anywhere("world"):match("hello world!")) -> 7 12
這種搜索的另一個(gè)方法如下:
復(fù)制代碼 代碼如下:
local I = lpeg.Cp()
function anywhere (p)
return (1 - lpeg.P(p))^0 * I * p * I
end
再次說(shuō)明,這個(gè)模式的直接解讀:當(dāng)不匹配p時(shí),它跳過(guò)盡可能多的字符,然后對(duì)p進(jìn)行匹配(外加正確的位置捕捉)。
如果我們打算查找僅僅匹配單詞邊界的模式的話,那么我們可以使用以下轉(zhuǎn)換:
復(fù)制代碼 代碼如下:
local t = lpeg.locale()
function atwordboundary (p)
return lpeg.P{
[1] = p + t.alpha^0 * (1 - t.alpha)^1 * lpeg.V(1)
}
end
平衡的括號(hào)
以下模式只匹配帶有平衡括號(hào)的字符串::
復(fù)制代碼 代碼如下:
b = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" }
閱讀第一個(gè)(也是唯一的)所給語(yǔ)法規(guī)則,所謂平衡字符串,就是一個(gè)開括號(hào),后跟零個(gè)或多個(gè)非括號(hào)字符或者平衡字符串(LPFG.V(1)),最后跟著與開括號(hào)能夠閉合的結(jié)束括號(hào)。
全局替換
下面的例子和tostring.gsub所做工作類似。它接收一個(gè)母串和一個(gè)模式以及一個(gè)替換值,然后替代所傳入的母串中所有與指定模式匹配的子串為指定的替換值::
復(fù)制代碼 代碼如下:
function gsub (s, patt, repl)
patt = lpeg.P(patt)
patt = lpeg.Cs((patt / repl + 1)^0)
return lpeg.match(patt, s)
end
作為instring.gsub,替換值可以是一個(gè)字串、函數(shù),或者一個(gè)表.
逗號(hào)分隔值(CSV)
下面的例子將字符串轉(zhuǎn)換成逗號(hào)分隔的值,并返回所有的字段:
復(fù)制代碼 代碼如下:
local field = '"' * lpeg.Cs(((lpeg.P(1) - '"') + lpeg.P'""' / '"')^0) * '"' +
lpeg.C((1 - lpeg.S',\n"')^0)
local record = field * (',' * field)^0 * (lpeg.P'\n' + -1)
function csv (s)
return lpeg.match(record, s)
end
一個(gè)字段或是一個(gè)引用的字段(一族可能包含任何字符除單引號(hào),或雙引號(hào))或是一個(gè)未被引用的字段(不包含逗號(hào),換行符或引號(hào))。一個(gè)記錄就是一個(gè)用逗號(hào)分隔的字段列表(以換行符或以字符串結(jié)尾)。
就像這樣,前面的匹配返回的每個(gè)字段都是獨(dú)立返回的。若我們添加一個(gè)列表截取定義的記錄。返回的將不再是一個(gè)獨(dú)立的包含所有字段的列表。
復(fù)制代碼 代碼如下:
local record = lpeg.Ct(field * (',' * field)^0) * (lpeg.P'\n' + -1)
UTF-8 和 Latin 1
使用LPeg來(lái)將一字符串從UTF-8編碼轉(zhuǎn)換成Latin 1(ISO 88590-1),這并不困難:
復(fù)制代碼 代碼如下:
-- convert a two-byte UTF-8 sequence to a Latin 1 character
local function f2 (s)
local c1, c2 = string.byte(s, 1, 2)
return string.char(c1 * 64 + c2 - 12416)
end
local utf8 = lpeg.R("\0\127")
+ lpeg.R("\194\195") * lpeg.R("\128\191") / f2
local decode_pattern = lpeg.Cs(utf8^0) * -1
這些代碼中,UTF-8定義已經(jīng)為L(zhǎng)atin 1的編碼范圍(從0到255)。所有不在該范圍內(nèi)的編碼(以及任何無(wú)效的編碼)將不匹配該模式。
正如decode_pattern所要求的,這個(gè)模式匹配所有的輸入(因?yàn)?1在它的結(jié)尾處),任何無(wú)效字符串會(huì)匹配失敗,而無(wú)任何關(guān)于此問題的有用信息。我們可以通過(guò)重定義如下decode_pattern來(lái)改善這種情況:
復(fù)制代碼 代碼如下:
local function er (_, i) error("invalid encoding at position " .. i) end
local decode_pattern = lpeg.Cs(utf8^0) * (-1 + lpeg.P(er))
現(xiàn)在,如果模式utf8^0 在字符串尾部之前停止,會(huì)調(diào)用一個(gè)適用的出錯(cuò)函數(shù)。
UTF-8 和 Unicode
我們可以擴(kuò)展以前的模式來(lái)處理所有的Unicdoe代碼片段,當(dāng)然, 我們不能把它們翻譯阿拉伯?dāng)?shù)字1或其他任何一個(gè)字節(jié)編碼。相反,我們的翻譯序列結(jié)果中的數(shù)字表示的代碼片段。這里完整的代碼:
復(fù)制代碼 代碼如下:
-- decode a two-byte UTF-8 sequence
local function f2 (s)
local c1, c2 = string.byte(s, 1, 2)
return c1 * 64 + c2 - 12416
end
-- decode a three-byte UTF-8 sequence
local function f3 (s)
local c1, c2, c3 = string.byte(s, 1, 3)
return (c1 * 64 + c2) * 64 + c3 - 925824
end
-- decode a four-byte UTF-8 sequence
local function f4 (s)
local c1, c2, c3, c4 = string.byte(s, 1, 4)
return ((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168
end
local cont = lpeg.R("\128\191") -- continuation byte
local utf8 = lpeg.R("\0\127") / string.byte
+ lpeg.R("\194\223") * cont / f2
-- decode a two-byte UTF-8 sequence
local function f2 (s)
local c1, c2 = string.byte(s, 1, 2)
return c1 * 64 + c2 - 12416
end
-- decode a three-byte UTF-8 sequence
local function f3 (s)
local c1, c2, c3 = string.byte(s, 1, 3)
return (c1 * 64 + c2) * 64 + c3 - 925824
end
-- decode a four-byte UTF-8 sequence
local function f4 (s)
local c1, c2, c3, c4 = string.byte(s, 1, 4)
return ((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168
end
local cont = lpeg.R("\128\191") -- continuation byte
local utf8 = lpeg.R("\0\127") / string.byte
+ lpeg.R("\194\223") * cont / f2
Lua的長(zhǎng)字符串
Lua中的長(zhǎng)字符串由模式 [= *[ 開始,到第一次出現(xiàn)的帶有完全相同數(shù)量的等號(hào)的 ] =*] 結(jié)束。如果開括號(hào)后跟一個(gè)換行符,換行符將被丟棄(即,不會(huì)把它當(dāng)作字符串的一部分)。
在Lua中想要匹配一個(gè)長(zhǎng)字符串,模式必須捕捉第一次重復(fù)的等號(hào),然后,只需找到有關(guān)閉字符串的候選項(xiàng),檢查其是否具有相同數(shù)量的等號(hào)。
復(fù)制代碼 代碼如下:
equals = lpeg.P"="^0
open = "[" * lpeg.Cg(equals, "init") * "[" * lpeg.P"\n"^-1
close = "]" * lpeg.C(equals) * "]"
closeeq = lpeg.Cmt(close * lpeg.Cb("init"), function (s, i, a, b) return a == b end)
string = open * lpeg.C((lpeg.P(1) - closeeq)^0) * close / 1
open 模式匹配 [=*[,在一個(gè)名為 init 的組中捕獲重復(fù)的等號(hào);它也會(huì)丟棄一個(gè)可選的換行符(如果它存在的話)。close 模式匹配 ]= *],也是捕捉重復(fù)的等號(hào)。closeeq模式首先匹配 close,然后它采用逆向捕捉來(lái)恢復(fù)先前由 open 捕捉并命名為 init 的內(nèi)容,最后,用 match-time 捕捉來(lái)檢查兩個(gè)捕獲是否相同。字符串模式從 open 開始之后,它會(huì)一直包含到匹配了 closeeq 為止,然后匹配最終的 close。最后的數(shù)字捕獲簡(jiǎn)單地丟棄由 close 產(chǎn)生的捕獲。
算術(shù)表達(dá)式
本例對(duì)簡(jiǎn)單的算術(shù)表達(dá)式進(jìn)行完整的解析和求值。而且我們用兩種風(fēng)格來(lái)書寫。
第一種途徑首先建立一個(gè)語(yǔ)法樹,然后遍歷這棵樹來(lái)計(jì)算表達(dá)式的值:
復(fù)制代碼 代碼如下:
-- 詞典元素
[code]local Space = lpeg.S(" \n\t")^0
local Number = lpeg.C(lpeg.P"-"^-1 * lpeg.R("09")^1) * Space
local TermOp = lpeg.C(lpeg.S("+-")) * Space
local FactorOp = lpeg.C(lpeg.S("*/")) * Space
local Open = "(" * Space
local Close = ")" * Space
-- 語(yǔ)法
local Exp, Term, Factor = lpeg.V"Exp", lpeg.V"Term", lpeg.V"Factor"
G = lpeg.P{ Exp,
Exp = lpeg.Ct(Term * (TermOp * Term)^0);
Term = lpeg.Ct(Factor * (FactorOp * Factor)^0);
Factor = Number + Open * Exp * Close;
}
G = Space * G * -1
-- 求值器
function eval (x)
if type(x) == "string" then
return tonumber(x)
else
local op1 = eval(x[1])
for i = 2, #x, 2 do
local op = x[i]
local op2 = eval(x[i + 1])
if (op == "+") then op1 = op1 + op2
elseif (op == "-") then op1 = op1 - op2
elseif (op == "*") then op1 = op1 * op2
elseif (op == "/") then op1 = op1 / op2
end
end
return op1
end
end
-- 解析/求值
function evalExp (s)
local t = lpeg.match(G, s)
if not t then error("syntax error", 2) end
return eval(t)
end
-- 使用例子
print(evalExp"3 + 5*9 / (1+1) - 12") --> 13.5
第二種風(fēng)格不用建立語(yǔ)法樹,直接求值。下面代碼就按此種途徑(假定和上面有相同的詞典元素):
復(fù)制代碼 代碼如下:
-- 輔助函數(shù)
function eval (v1, op, v2)
if (op == "+") then return v1 + v2
elseif (op == "-") then return v1 - v2
elseif (op == "*") then return v1 * v2
elseif (op == "/") then return v1 / v2
end
end
-- 語(yǔ)法
local V = lpeg.V
G = lpeg.P{ "Exp",
Exp = lpeg.Cf(V"Term" * lpeg.Cg(TermOp * V"Term")^0, eval);
Term = lpeg.Cf(V"Factor" * lpeg.Cg(FactorOp * V"Factor")^0, eval);
Factor = Number / tonumber + Open * V"Exp" * Close;
}
-- 使用例子
print(lpeg.match(G, "3 + 5*9 / (1+1) - 12")) --> 13.5
注意 fold (收集器) 捕獲的用法。 要計(jì)算一個(gè)表達(dá)式的值,收集器從第一個(gè)術(shù)語(yǔ)的值開始,為每個(gè)副本應(yīng)用進(jìn)化收集器,操作符,和新術(shù)語(yǔ)。
您可能感興趣的文章:- 詳解Lua中的數(shù)據(jù)類型
- Lua判斷數(shù)據(jù)類型的方法
- 實(shí)現(xiàn)Lua中數(shù)據(jù)類型的源碼分享
- Lua數(shù)據(jù)類型介紹
- Lua中的基本數(shù)據(jù)類型詳細(xì)介紹
- Lua學(xué)習(xí)筆記之?dāng)?shù)據(jù)類型
- Lua教程(三):表達(dá)式和語(yǔ)句
- Lua學(xué)習(xí)筆記之表達(dá)式
- Lua表達(dá)式和控制結(jié)構(gòu)學(xué)習(xí)筆記
- Lua中的操作符和表達(dá)式總結(jié)
- Lua基礎(chǔ)教程之賦值語(yǔ)句、表達(dá)式、流程控制、函數(shù)學(xué)習(xí)筆記
- Lua學(xué)習(xí)筆記之運(yùn)算符和表達(dá)式
- Lua中基本的數(shù)據(jù)類型、表達(dá)式與流程控制語(yǔ)句講解