local m = require 'lpeglabel' local p, r, l, s, poserr local function checklabeq (x, ...) y = { ... } assert(type(x) == "table") assert(#x == #y) if x[2] == 0 then -- 0 -> 'fail' x[2] = 'fail' end for i = 1, 3 do assert(x[i] == y[i]) end end local function checkeq (x, y, p) if p then print(x,y) end if type(x) ~= "table" then assert(x == y) else for k,v in pairs(x) do checkeq(v, y[k], p) end for k,v in pairs(y) do checkeq(v, x[k], p) end end end -- tests related to reporting the farthest failure position -- when a label is not thrown p = m.P"a"^0 * m.P"b" + m.P"c" checklabeq({4, nil, nil}, p:match("aabk")) checklabeq({2, nil, nil}, p:match("ck")) checklabeq({nil, 0, 1}, p:match("dk")) checklabeq({nil, 0, 3}, p:match("aak")) p = (m.P"a" + m.P"c")^0 * m.P"b" + m.P"c" checklabeq({4, nil, nil}, p:match("aabk")) checklabeq({2, nil, nil}, p:match("ck")) checklabeq({nil, 0, 1}, p:match("dk")) checklabeq({nil, 0, 3}, p:match("aak")) p = m.P"a"^0 * m.P"b" + m.P(1)^0 * m.P(1) checklabeq({4, nil, nil}, p:match("aabk")) checklabeq({nil, 0, 3}, p:match("ck")) checklabeq({nil, 0, 4}, p:match("aak")) p = m.P(1) * m.P"a" + m.P"c" checklabeq({3, nil, nil}, p:match("bac")) checklabeq({2, nil, nil}, p:match("c")) checklabeq({nil, 0, 2}, p:match("x")) checklabeq({nil, 0, 2}, p:match("kx")) p = m.P"a"^0 * m.P(1) * m.P(1) + m.P"a"^0 * m.P"c" checklabeq({5, nil, nil}, p:match("aabc")) checklabeq({4, nil, nil}, p:match("aac")) checklabeq({nil, 0, 4}, p:match("aak")) checklabeq({nil, 0, 2}, p:match("x")) p = m.P"a"^0 * m.P(1) * m.P(1) + m.P"a"^0 * m.P"c" checklabeq({5, nil, nil}, p:match("aabc")) checklabeq({4, nil, nil}, p:match("aac")) checklabeq({nil, 0, 4}, p:match("aak")) checklabeq({nil, 0, 2}, p:match("x")) p = m.Cmt(m.P"a"^0, function() return nil end) + m.P"x" checklabeq({2, nil, nil}, p:match("xabc")) checklabeq({nil, 0, 3}, p:match("aac")) checklabeq({nil, 0, 1}, p:match("kx")) p = m.P"b" * -m.P"a" + m.P"c" checklabeq({nil, 0, 2}, p:match("ba")) checklabeq({nil, 0, 1}, p:match("kx")) p = (m.P"c" + m.P"a") * m.P("b" + m.P"d") + m.P"xxx" checklabeq({nil, 0, 1}, p:match("kk")) checklabeq({nil, 0, 2}, p:match("ak")) checklabeq({nil, 0, 3}, p:match("xxy")) checklabeq({nil, 0, 2}, p:match("xyz")) print"+" -- throws a label p = m.T(1) s = "abc" r, l, poserr = p:match(s) assert(r == nil and l == 1 and poserr == 1) -- throws a label, choice does not catch labels p = m.T(1) + m.P"a" r, l, poserr = p:match(s) assert(r == nil and l == 1 and poserr == 1) -- again throws a label that is not caught by choice local g = m.P{ "S", S = m.V"A" + m.V"B", A = m.T(1), B = m.P"a" } r, l, poserr = g:match(s) assert(r == nil and l == 1 and poserr == 1) -- throws a label in a position that is not the farthest one -- but it is the position that should be reported p = m.P(1) * m.P"a" + m.T(11) checklabeq({3, nil, nil}, p:match("bac")) checklabeq({nil, 11, 1}, p:match("c")) checklabeq({nil, 11, 1}, p:match("x")) checklabeq({nil, 11, 1}, p:match("kx")) -- throws a label without a corresponding recovery rule p = m.P{ "S", S = m.T("bola"), bolada = m.P"a" } r, l, poserr = p:match("abc") assert(r == nil and l == 'bola' and poserr == 1) -- throws a label without a corresponding recovery rule -- T(2) indexes key ["2"] p = m.P{ "S", S = m.T(2), [2] = m.P"a" } r, l, poserr = p:match("abc") assert(r == nil and l == 2 and poserr == 1) -- throws a label with a corresponding recovery rule p = m.P{ "S", S = m.T("bola"), bola = m.P"a" } r, l, poserr = p:match("abc") assert(r == 2) -- throws a label with a corresponding recovery rule -- T(2) indexes key ["2"] p = m.P{ "S", S = m.T(2), ["2"] = m.P"a"^0 } r, l, poserr = p:match("aaabc") assert(r == 4) -- regular failure after the recovery p = m.P{ "S", S = m.T(2), ["2"] = m.P"a"^0 * m.P"c" } r, l, poserr = p:match("aaabc") assert(r == nil and l == 'fail' and poserr == 4) s = "bola" r, l, poserr = p:match("bola") assert(r == nil and l == 'fail' and poserr == 1) -- Recovery rules do not catch "fail" by default p = m.P{ "S", S = m.P"b" * m.T(2), ["2"] = m.P"a"^0 } r, l, poserr = p:match("c") assert(r == nil and l == 'fail' and poserr == 1) assert(p:match("baa") == 4) -- recovery rules for "2" or "3" -- when we use V instead of T, a recovery -- rule becomes a regular grammar rule p = m.P{ "S", S = (m.P"a" + m.T(2)) * m.T(3), ["3"] = m.V"2", ["2"] = m.P"a" + m.P"b", } assert(p:match("aac") == 3) assert(p:match("abc") == 3) r, l, poserr = p:match("acc") assert(r == nil and l == 'fail' and poserr == 2) --throws 2, recovery rule matches 'b', throw 3, and rec rule matches 'a' assert(p:match("bac") == 3) r, l, poserr = p:match("cab") assert(r == nil and l == 'fail' and poserr == 1) -- tests related to predicates p = #m.T(1) r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 1) p = #m.T(1) + m.P"a" r, l, poserr = p:match("abc") assert(r == 2) p = #m.T(1) * m.P"a" r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 1) p = ##m.T(1) + m.P"a" r, l, poserr = p:match("abc") assert(r == 2) p = -#m.T(1) + m.P"a" r, l, poserr = p:match("abc") assert(r == 1) p = -m.T(1) * m.P"a" r, l, poserr = p:match("abc") assert(r == 2) p = -m.T(1) r, l, poserr = p:match("bbc") assert(r == 1) p = -#m.T(1) r, l, poserr = p:match("bbc") assert(r == 1) p = -(-m.P'a') r, l, poserr = p:match("abc") assert(r == 1) p = -(-m.P'a') r, l, poserr = p:match("bbc") assert(r == nil and l == 'fail' and poserr == 1) p = #(m.P"a" * m.T(1)) r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 2) p = #(m.P"a" * m.P'a') r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 2) p = m.P'a' * #m.P('a') r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 2) p = m.P'a' * #m.P('ba') r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 3) p = m.P'a' * -m.P('b') r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 2) p = m.P'a' * -m.P('bc') r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 4) p = m.P'a' * #m.T('a') r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 2) p = m.P'a' * #(m.P'b' * m.T('a')) r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 3) p = m.P'a' * (m.P'b' * m.T('a')) r, l, poserr = p:match("abc") assert(r == nil and l == 'a' and poserr == 3) p = -(-m.T(1)) r, l, poserr = p:match("bbc") assert(r == nil and l == 'fail' and poserr == 1) p = m.P{ "S", S = -m.T(22), ["22"] = m.P"a" } r, l, poserr = p:match("abc") assert(r == 1) assert(p:match("bbc") == 1) p = m.P{ "S", S = #m.T(22), ["22"] = m.P"a" } r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 1) p = m.P{ "S", S = m.T(22), ["22"] = #m.P"a" } assert(p:match("abc") == 1) p = m.P{ "S", S = #m.T(22), ["22"] = m.P"a" } r, l, poserr = p:match("bbc") assert(r == nil and l == 'fail' and poserr == 1) p = m.P{ "S", S = #m.P"a" * m.T(22), ["22"] = m.T(15) } r, l, poserr = p:match("abc") assert(r == nil and l == 15 and poserr == 1) p = m.P{ "S", S = #(m.P"a" * m.T(22)), ["22"] = m.T(15) } r, l, poserr = p:match("abc") assert(r == nil and l == 'fail' and poserr == 2) -- tests related to repetition p = m.T(1)^0 r, l, poserr = p:match("ab") assert(r == nil and l == 1 and poserr == 1) p = (m.P"a" + m.T(1))^0 r, l, poserr = p:match("aa") assert(r == nil and l == 1 and poserr == 3) -- Bug reported by Matthew Allen -- some optmizations performed by LPeg should not be -- applied in case of labels -- recovery operator p = m.P{ "S", S = m.P"A" + m.P"B", ["2"] = m.P(true) } assert(p:match("B") == 2) p = m.P{ "S", S = m.P"A" + m.P"B", ["2"] = m.P(false) } assert(p:match("B") == 2) --[[ S -> A //{1} 'a' A -> B B -> %1 ]] g = m.P{ "S", S = m.V"A", A = m.V"B", B = m.T(2), ["2"] = m.P'a' } assert(g:match("ab") == 2) r, l, poserr = g:match("bc") assert(r == nil and l == 'fail' and poserr == 1) --[[ S -> A A -> (B (';' / %{2}))* B -> 'a' ]] g = m.P{ "S", S = m.V"A", A = m.P(m.V"B" * (";" + m.T(2)))^0, B = m.P'a', } assert(g:match("a;a;") == 5) r, l, poserr = g:match("a;a") assert(r == nil and l == 2 and poserr == 4) p = m.P{ "S", S = m.T'A', A = m.T'B', B = m.P'a' } assert(p:match("abc") == 2) r, l, poserr = p:match("") assert(r == nil and l == 'fail' and poserr == 1) -- Infinte Loop TODO: check the semantics p = m.P{ "S", S = m.T"A", A = m.T"S", } --r, l, poserr = p:match("ab") --assert(r == nil and l == 1 and poserr == "ab") p = m.P{ "S", S = m.T"A", A = m.P'a' * (-m.P(1) + m.T"A"), } r, l, poserr = p:match("ab") assert(r == nil and l == 'fail' and poserr == 2) r, l, poserr = p:match("cd") assert(r == nil and l == 'fail' and poserr == 1) p = m.P{ "S", S = m.T"A", A = m.P(1) * (-m.P(1) + m.T"A"), } assert(p:match("abc") == 4) print("+") --[[ grammar based on Figure 8 of paper submitted to SCP S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error S0 -> S1 / S2 / &'int' %3 S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4 S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3 ]] local sp = m.S" \t\n"^0 local eq = sp * m.P"=" g = m.P{ "S", S = m.V"S0" + m.T'L5', -- error S0 = m.V"S1" + m.V"S2" + #m.V"I" * m.T'L3', S1 = #(m.V"ID" * eq) * m.T'L2' + sp * #(m.V"ID" * -m.P(1)) * m.T'L1' + #m.V"ID" * m.T'L4', S2 = #(m.V"U"^1 * m.V"ID") * m.T'L4' + #(m.V"U"^1 * m.V"I") * m.T'L3', ID = sp * m.P"a", U = sp * m.P"unsigned", I = sp * m.P"int", Exp = sp * m.P"E", L1 = m.V"ID", L2 = m.V"ID" * eq * m.V"Exp", L3 = m.V"U"^0 * m.V"I" * m.V"ID", L4 = m.V"U"^0 * m.V"ID" * m.V"ID", } local s = "a" assert(g:match(s) == #s + 1) --1 s = "a = E" assert(g:match(s) == #s + 1) --2 s = "int a" assert(g:match(s) == #s + 1) --3 s = "unsigned int a" assert(g:match(s) == #s + 1) --3 s = "unsigned a a" assert(g:match(s) == #s + 1) --4 s = "b" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) s = "unsigned" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) s = "unsigned a" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) s = "unsigned int" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) print("+") local re = require 'relabel' g = re.compile[['a' / %{l1} ]] assert(g:match("a") == 2) r, l, poserr = g:match("b") assert(r == nil and l == 'l1' and poserr == 1) g = re.compile[['a'^l1 ]] assert(g:match("a") == 2) r, l, poserr = g:match("b") assert(r == nil and l == 'l1' and poserr == 1) g = re.compile[[A <- 'a'^B B <- [a-f]^C C <- [a-z] ]] assert(g:match("a") == 2) assert(g:match("a") == 2) assert(g:match("f") == 2) assert(g:match("g") == 2) assert(g:match("z") == 2) r, l, poserr = g:match("A") assert(r == nil and l == 'fail' and poserr == 1) g = re.compile[[A <- %{C} B <- [a-z] ]] r, l, poserr = g:match("a") assert(r == nil and l == 'C' and poserr == 1) g = re.compile[[A <- %{B} B <- [a-z] ]] r, l, poserr = g:match("a") assert(g:match("a") == 2) r, l, poserr = g:match("U") assert(r == nil and l == 'fail' and poserr == 1) g = re.compile[[A <- [a-f] %{B} B <- [a-c] %{C} C <- [a-z] ]] r, l, poserr = g:match("a") assert(r == nil and l == 'fail' and poserr == 2) r, l, poserr = g:match("aa") assert(r == nil and l == 'fail' and poserr == 3) assert(g:match("aaa") == 4) r, l, poserr = g:match("ad") assert(r == nil and l == 'fail' and poserr == 2) r, l, poserr = g:match("g") assert(r == nil and l == 'fail' and poserr == 1) --[[ grammar based on Figure 8 of paper submitted to SCP (using the recovery operator) S -> S0 //{1} ID //{2} ID '=' Exp //{3} 'unsigned'* 'int' ID //{4} 'unsigned'* ID ID / %error S0 -> S1 / S2 / &'int' %3 S1 -> &(ID '=') %2 / &(ID !.) %1 / &ID %4 S2 -> &('unsigned'+ ID) %4 / & ('unsigned'+ 'int') %3 ]] g = re.compile([[ S <- S0 / %{L5} S0 <- S1 / S2 / &Int %{L3} S1 <- &(ID %s* '=') %{L2} / &(ID !.) %{L1} / &ID %{L4} S2 <- &(U+ ID) %{L4} / &(U+ Int) %{L3} ID <- %s* 'a' U <- %s* 'unsigned' Int <- %s* 'int' Exp <- %s* 'E' L1 <- ID L2 <- ID %s* '=' Exp L3 <- U* Int ID L4 <- U ID ID ]]) local s = "a" assert(g:match(s) == #s + 1) --1 s = "a = E" assert(g:match(s) == #s + 1) --2 s = "int a" assert(g:match(s) == #s + 1) --3 s = "unsigned int a" assert(g:match(s) == #s + 1) --3 s = "unsigned a a" assert(g:match(s) == #s + 1) --4 s = "b" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) s = "unsigned" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) s = "unsigned a" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) s = "unsigned int" r, l, poserr = g:match(s) assert(r == nil and l == 'L5' and poserr == 1) local terror = { cmdSeq = "Missing ';' in CmdSeq", ifExp = "Error in expresion of 'if'", ifThen = "Error matching 'then' keyword", ifThenCmdSeq = "Error matching CmdSeq of 'then' branch", ifElseCmdSeq = "Error matching CmdSeq of 'else' branch", ifEnd = "Error matching 'end' keyword of 'if'", repeatCmdSeq = "Error matching CmdSeq of 'repeat'", repeatUntil = "Error matching 'until' keyword", repeatExp = "Error matching expression of 'until'", assignOp = "Error matching ':='", assignExp = "Error matching expression of assignment", readName = "Error matching 'NAME' after 'read'", writeExp = "Error matching expression after 'write'", simpleExp = "Error matching 'SimpleExp'", term = "Error matching 'Term'", factor = "Error matching 'Factor'", openParExp = "Error matching expression after '('", closePar = "Error matching ')'", undefined = "Undefined Error"} g = re.compile([[ Tiny <- CmdSeq CmdSeq <- (Cmd SEMICOLON^cmdSeq) (Cmd SEMICOLON^cmdSeq)* Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd IfCmd <- IF Exp^ifExp THEN^ifThen CmdSeq^ifThenCmdSeq (ELSE CmdSeq^ifElseCmdSeq / '') END^ifEnd RepeatCmd <- REPEAT CmdSeq^repeatCmdSeq UNTIL^repeatUntil Exp^repeatExp AssignCmd <- NAME ASSIGNMENT^assignOp Exp^assignExp ReadCmd <- READ NAME^readName WriteCmd <- WRITE Exp^writeExp Exp <- SimpleExp ((LESS / EQUAL) SimpleExp^simpleExp / '') SimpleExp <- Term ((ADD / SUB) Term^term)* Term <- Factor ((MUL / DIV) Factor^factor)* Factor <- OPENPAR Exp^openParExp CLOSEPAR^closePar / NUMBER / NAME ADD <- Sp '+' ASSIGNMENT <- Sp ':=' CLOSEPAR <- Sp ')' DIV <- Sp '/' IF <- Sp 'if' ELSE <- Sp 'else' END <- Sp 'end' EQUAL <- Sp '=' LESS <- Sp '<' MUL <- Sp '*' NAME <- !RESERVED Sp [a-z]+ NUMBER <- Sp [0-9]+ OPENPAR <- Sp '(' READ <- Sp 'read' REPEAT <- Sp 'repeat' SEMICOLON <- Sp ';' SUB <- Sp '-' THEN <- Sp 'then' UNTIL <- Sp 'until' WRITE <- Sp 'write' RESERVED <- (IF / ELSE / END / READ / REPEAT / THEN / UNTIL / WRITE) ![a-z]+ Sp <- (%s / %nl)* ]], terror) s = [[ n := 5;]] assert(g:match(s) == #s + 1) s = [[ n := 5; f := 1; repeat f := f * n; n := n - 1; until (n < 1); write f;]] assert(g:match(s) == #s + 1) -- a ';' is missing in 'read a' s = [[ read a]] r, l, poserr = g:match(s) assert(l == 'cmdSeq') -- a ';' is missing in 'n := n - 1' s = [[ n := 5; f := 1; repeat f := f * n; n := n - 1 until (n < 1); write f;]] r, l, poserr = g:match(s) assert(l == 'cmdSeq') -- IF expression s = [[ if a then a := a + 1; end;]] assert(g:match(s) == #s + 1) -- IF expression s = [[ if a then a := a + 1; else write 2; end;]] assert(g:match(s) == #s + 1) -- Error in expression of 'if'. 'A' is not a valida name s = [[ if A then a := a + 1; else write 2; end;]] r, l, poserr = g:match(s) assert(l == 'ifExp') -- Error matching the 'then' keyword s = [[ if a a := a + 1; else write 2; end;]] r, l, poserr = g:match(s) assert(l == 'ifThen') -- Error matching the CmdSeq inside of 'then' branch s = [[ if a then 3 := 2; else write 2; end;]] r, l, poserr = g:match(s) assert(l == 'ifThenCmdSeq') -- Error matching the CmdSeq inside of 'else' branch s = [[ if a then b := 2; else A := 2; end;]] r, l, poserr = g:match(s) assert(l == 'ifElseCmdSeq') -- Error matching 'end' of 'if' s = [[ if a then b := 2; else a := 2; 77;]] r, l, poserr = g:match(s) assert(l == 'ifEnd') -- Error matching the CmdSeq of 'repeat' s = [[repeat F := f * n; n := n - 1; until (n < 1);]] r, l, poserr = g:match(s) assert(l == 'repeatCmdSeq') -- Error matching 'until' s = [[repeat f := f * n; n := n - 1; 88 (n < 1);]] r, l, poserr = g:match(s) assert(l == 'repeatUntil') -- Error matching expression of 'until' s = [[repeat f := f * n; n := n - 1; until ; (n < 1);]] r, l, poserr = g:match(s) assert(l == 'repeatExp') -- Error matching ':=' s = [[ f = f * n;]] r, l, poserr = g:match(s) assert(l == 'assignOp') -- Error matching expression of assignment s = [[ f := A * n;]] r, l, poserr = g:match(s) assert(l == 'assignExp') -- Error matching 'name' s = [[ read 2;]] r, l, poserr = g:match(s) assert(l == 'readName') -- Error matching expression after 'write' s = [[ write [a] := 2;]] r, l, poserr = g:match(s) assert(l == 'writeExp') -- Error matching 'SimpleExp' s = [[ a := a < A;]] r, l, poserr = g:match(s) assert(l == 'simpleExp') -- Error matching 'Term' s = [[ a := a + A;]] r, l, poserr = g:match(s) assert(l == 'term') -- Error matching 'Factor' s = [[ a := a * A;]] r, l, poserr = g:match(s) assert(l == 'factor') -- Error matching expression after '(' s = [[ a := (A);]] r, l, poserr = g:match(s) assert(l == 'openParExp') -- Error matching ')' s = [[ a := (a];]] r, l, poserr = g:match(s) assert(l == 'closePar') -- Error undefined s = [[ A := a;]] r, l, poserr = g:match(s) assert(l == 'fail') print("+") p = m.P{ "A", A = m.P"a", B = m.P"b" } assert(p:match("a") == 2) checklabeq({nil, 0, 1}, p:match("b")) checklabeq({nil, 0, 1}, p:match("c")) p = m.P{ "A", A = m.T"B", B = m.P"b" } checklabeq({nil, 'fail', 1}, p:match("a")) assert(p:match("b") == 2) --[[ S -> (A //{128} (!c .)*) C A -> a*b / %128 C -> c+ ]] g = m.P{ "S", S = m.V"A" * m.V"C", A = m.P"a"^0 * "b" + m.T'Rec', C = m.P"c"^1, Rec = (-m.P"c" * m.P(1))^0 } assert(g:match("abc") == 4) assert(g:match("aabc") == 5) assert(g:match("aadc") == 5) assert(g:match("dc") == 3) checklabeq({nil, 0, 2}, g:match("bbc")) assert(g:match("xxc") == 4) assert(g:match("c") == 2) checklabeq({nil, 0, 5}, g:match("fail")) checklabeq({nil, 0, 5}, g:match("aaxx")) --[[ S -> (A //{99} (!c .)*) C A -> a+ (b / ^99) C -> c+ ]] g = m.P{ "S", S = m.V"A" * m.V"C", A = m.P"a"^1 * ("b" + m.T'Rec'), C = m.P"c"^1, Rec = (-m.P"c" * m.P(1))^0 } assert(g:match("abc") == 4) assert(g:match("aabc") == 5) assert(g:match("aadc") == 5) checklabeq({nil, 0, 1}, g:match("bc")) checklabeq({nil, 0, 1}, g:match("bbc")) checklabeq({nil, 0, 3}, g:match("abb")) checklabeq({nil, 0, 4}, g:match("axx")) assert(g:match("accc") == 5) assert(g:match("axxc") == 5) checklabeq({nil, 0, 1}, g:match("c")) checklabeq({nil, 0, 1}, g:match("fail")) -- Matthew's recovery example lpeg = m local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt local T = lpeg.T local labels = { NoExp = "no expression found", Extra = "extra characters found after the expression", ExpTerm = "expected a term after the operator", ExpExp = "expected an expression after the parenthesis", MisClose = "missing a closing ')' after the expression", } local errors = {} local function expect(patt, labname) function recorderror(input, pos) table.insert(errors, {labname, pos}) return true end return patt + Cmt("", recorderror) * T(labname) end local num = R("09")^1 / tonumber local op = S("+-*/") local function compute(tokens) local result = tokens[1] for i = 2, #tokens, 2 do if tokens[i] == '+' then result = result + tokens[i+1] elseif tokens[i] == '-' then result = result - tokens[i+1] elseif tokens[i] == '*' then result = result * tokens[i+1] elseif tokens[i] == '/' then result = result / tokens[i+1] else error('unknown operation: ' .. tokens[i]) end end return result end local g = P { "Exp", Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute; Operand = expect(V"Term", "ExpTerm"); Term = num + V"Group"; Group = "(" * V"InnerExp" * expect(")", "MisClose"); InnerExp = expect(V"Exp", "ExpExp"); ExpTerm = Cc(0); MisClose = P""; ExpExp = (P(1) - ")")^0 * Cc(0); } g = expect(g, "NoExp") * expect(-P(1), "Extra") local function eval(input) local result, label, suffix = g:match(input) if #errors == 0 then return result else local out = {} for i, err in ipairs(errors) do local pos = err[2] local msg = labels[err[1]] table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") end errors = {} return nil, table.concat(out, "\n") end end assert(eval "98-76*(54/32)" == 37.125) --> 37.125 local e, msg = eval "(1+1-1*2/2" assert(e == nil and msg == "syntax error: missing a closing ')' after the expression (at index 11)") e, msg = eval "(1+)-1*(2/2)" assert(e == nil and msg == "syntax error: expected a term after the operator (at index 4)") e, msg = eval "(1+1)-1*(/2)" assert(e == nil and msg == "syntax error: expected an expression after the parenthesis (at index 10)") e, msg = eval "1+(1-(1*2))/2x" assert(e == nil and msg == "syntax error: extra characters found after the expression (at index 14)") e, msg = eval "-1+(1-(1*2))/2" assert(e == nil and msg == "syntax error: no expression found (at index 1)") e, msg = eval "(1+1-1*(2/2+)-():" assert(e == nil and msg == "syntax error: expected a term after the operator (at index 13)\n" .. "syntax error: expected an expression after the parenthesis (at index 16)\n" .. "syntax error: missing a closing ')' after the expression (at index 17)\n" .. "syntax error: extra characters found after the expression (at index 17)") print("+") local g1 = { "S1", S1 = V"End" + V'A' * V'S1', A = P'a' + T'Ea', End = P"." * (-P(1) + T'Eend'), } local p1 = m.P(g1) assert(p1:match("a.") == 3) assert(p1:match("aa.") == 4) assert(p1:match(".") == 2) checklabeq({nil, 'Ea', 1}, p1:match("ba.")) checklabeq({nil, 'Ea', 2}, p1:match("aba.")) checklabeq({nil, 'Ea', 1}, p1:match("cba.")) checklabeq({nil, 'Eend', 3}, p1:match("a.a")) local g2 = { "S2", S2 = m.V"S1", Ea = P'b'^1 + T'Eb', } g1[1] = nil -- start rule for k, v in pairs(g1) do g2[k] = v end local p2 = m.P(g2) assert(p2:match("a.") == 3) assert(p2:match("aa.") == 4) assert(p2:match(".") == 2) assert(p2:match("ba.") == 4) assert(p2:match("aba.") == 5) checklabeq({nil, 'Eb', 1}, p2:match("cba.")) checklabeq({nil, 'Eend', 3}, p2:match("a.a")) local g3 = { "S3", S3 = V'S2', Eb = P'c'^1 + T'Ec', Eend = V'Eb', } g2[1] = nil -- start rule for k, v in pairs(g2) do g3[k] = v end local p3 = m.P(g3) assert(p3:match("a.") == 3) assert(p3:match("aa.") == 4) assert(p3:match(".") == 2) assert(p3:match("ba.") == 4) assert(p3:match("aba.") == 5) assert(p3:match("cba.") == 5) checklabeq({nil, 'Ec', 3}, p3:match("a.a")) checklabeq({nil, 'Ec', 1}, p3:match("dc")) checklabeq({nil, 'Ec', 2}, p3:match(".d")) -- testing more captures local g = re.compile[[ S <- ( %s* &. {A} )* A <- [0-9]+ / %{5} ]] checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")}) checkeq({nil, 5, 4}, {g:match("44 a 123")}) local g2 = re.compile[[ S <- ( %s* &. {A} )* A <- [0-9]+ / %{Rec} Rec <- ((![0-9] .)*) -> "58" ]] checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")}) checkeq({"44", "a ", "58", "123"}, {g2:match("44 a 123")}) local g = re.compile[[ S <- ( %s* &. A )* A <- {[0-9]+} / %{5} ]] checkeq({"523", "624", "346", "888"} , {g:match("523 624 346\n888")}) checkeq({nil, 5, 4}, {g:match("44 a 123")}) local g2 = re.compile[[ S <- ( %s* &. A )* A <- {[0-9]+} / %{Rec} Rec <- ((![0-9] .)*) -> "58" ]] checkeq({"523", "624", "346", "888"} , {g2:match("523 624 346\n888")}) checkeq({"44", "58", "123"}, {g2:match("44 a 123")}) local R, S, P, V = lpeg.R, lpeg.S, lpeg.P, lpeg.V local C, Cc, Ct, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cmt local T = lpeg.T local labels = { {"NoExp", "no expression found"}, {"Extra", "extra characters found after the expression"}, {"ExpTerm", "expected a term after the operator"}, {"ExpExp", "expected an expression after the parenthesis"}, {"MisClose", "missing a closing ')' after the expression"}, } local errors = {} local function expect(patt, labname, recpatt) function recorderror(input, pos) table.insert(errors, {labname, pos}) return true end if not recpatt then recpatt = P"" end --return Rec(patt, Cmt("", recorderror) * recpatt) return patt + T(labname) end local num = R("09")^1 / tonumber local op = S("+-*/") local function compute(tokens) local result = tokens[1] for i = 2, #tokens, 2 do if tokens[i] == '+' then result = result + tokens[i+1] elseif tokens[i] == '-' then result = result - tokens[i+1] elseif tokens[i] == '*' then result = result * tokens[i+1] elseif tokens[i] == '/' then result = result / tokens[i+1] else error('unknown operation: ' .. tokens[i]) end end return result end local g = P { "Exp", Exp = Ct(V"Term" * (C(op) * V"Operand")^0) / compute, Operand = expect(V"Term", "ExpTerm"), Term = num, ExpTerm = Cc(3) } local function eval(input) local result, label, suffix = g:match(input) if #errors == 0 then return result else local out = {} for i, err in ipairs(errors) do local pos = err[2] local msg = labels[err[1]] table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")") end errors = {} return nil, table.concat(out, "\n") end end assert(eval("98-76*54/32") == 37.125) --> 37.125 assert(eval("1+") == 4) --> syntax error: expected a term after the operator (at index 3) print("OK")