lang-bootstrap/06/lpeglabel/testlabel.lua

1055 lines
27 KiB
Lua
Raw Normal View History

local m = require 'lpeglabel'
local p, r, l, s, serror
local function checklabeq (x, ...)
y = { ... }
assert(type(x) == "table")
assert(#x == #y)
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, "dk"}, p:match("dk"))
checklabeq({nil, 0, "k"}, 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, "dk"}, p:match("dk"))
checklabeq({nil, 0, "k"}, 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, ""}, p:match("ck"))
checklabeq({nil, 0, ""}, 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, ""}, p:match("x"))
checklabeq({nil, 0, "x"}, 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, ""}, p:match("aak"))
checklabeq({nil, 0, ""}, 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, ""}, p:match("aak"))
checklabeq({nil, 0, ""}, 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, "c"}, p:match("aac"))
checklabeq({nil, 0, "kx"}, p:match("kx"))
p = m.P"b" * -m.P"a" + m.P"c"
checklabeq({nil, 0, "a"}, p:match("ba"))
checklabeq({nil, 0, "kx"}, p:match("kx"))
p = (m.P"c" + m.P"a") * m.P("b" + m.P"d") + m.P"xxx"
checklabeq({nil, 0, "kk"}, p:match("kk"))
checklabeq({nil, 0, "k"}, p:match("ak"))
checklabeq({nil, 0, "y"}, p:match("xxy"))
checklabeq({nil, 0, "yz"}, p:match("xyz"))
print"+"
-- throws a label
p = m.T(1)
s = "abc"
r, l, serror = p:match(s)
assert(r == nil and l == 1 and serror == "abc")
-- throws a label, choice does not catch labels
p = m.T(1) + m.P"a"
r, l, serror = p:match(s)
assert(r == nil and l == 1 and serror == "abc")
-- 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, serror = g:match(s)
assert(r == nil and l == 1 and serror == "abc")
-- 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, "c"}, p:match("c"))
checklabeq({nil, 11, "x"}, p:match("x"))
checklabeq({nil, 11, "kx"}, p:match("kx"))
-- throws a label that is not caught by the recovery operator
p = m.Rec(m.T(2), m.P"a", 1, 3)
r, l, serror = p:match(s)
assert(r == nil and l == 2 and serror == "abc")
-- wraps the previous pattern with a recovery that catches label "2"
p = m.Rec(p, m.P"a", 2)
assert(p:match(s) == 2)
-- throws a label that is caught by recovery
p = m.Rec(m.T(25), m.P"a", 25)
assert(p:match(s) == 2)
-- "fail" is label "0"
-- throws the "fail" label after the recovery
s = "bola"
r, l, serror = p:match("bola")
assert(r == nil and l == 0 and serror == "bola")
-- Recovery does not catch "fail" by default
p = m.Rec(m.P"b", m.P"a", 1)
r, l, serror = p:match("abc")
assert(r == nil and l == 0 and serror == "abc")
assert(p:match("bola") == 2)
-- recovery operator catches "1" or "3"
p = m.Rec((m.P"a" + m.T(1)) * m.T(3), (m.P"a" + m.P"b"), 1, 3)
assert(p:match("aac") == 3)
assert(p:match("abc") == 3)
r, l, serror = p:match("acc")
assert(r == nil and l == 0 and serror == "cc")
--throws 1, recovery pattern matches 'b', throw 3, and rec pat mathces 'a'
assert(p:match("bac") == 3)
r, l, serror = p:match("cab")
assert(r == nil and l == 0 and serror == "cab")
-- associativity
-- (p1 / %1) //{1} (p2 / %2) //{2} p3
-- left-associativity
-- ("a" //{1} "b") //{2} "c"
p = m.Rec(m.Rec(m.P"a" + m.T(1), m.P"b" + m.T(2), 1), m.P"c", 2)
assert(p:match("abc") == 2)
assert(p:match("bac") == 2)
assert(p:match("cab") == 2)
r, l, serror = p:match("dab")
assert(r == nil and l == 0 and serror == "dab")
-- righ-associativity
-- "a" //{1} ("b" //{2} "c")
p = m.Rec(m.P"a" + m.T(1), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1)
assert(p:match("abc") == 2)
assert(p:match("bac") == 2)
assert(p:match("cab") == 2)
r, l, serror = p:match("dab")
assert(r == nil and l == 0 and serror == "dab")
-- associativity -> in this case the error thrown by p1 is only
-- recovered when we have a left-associative operator
-- (p1 / %2) //{1} (p2 / %2) //{2} p3
-- left-associativity
-- ("a" //{1} "b") //{2} "c"
p = m.Rec(m.Rec(m.P"a" + m.T(2), m.P"b" + m.T(2), 1), m.P"c", 2)
assert(p:match("abc") == 2)
r, l, serror = p:match("bac")
assert(r == nil and l == 0 and serror == "bac")
assert(p:match("cab") == 2)
r, l, serror = p:match("dab")
assert(r == nil and l == 0 and serror == "dab")
-- righ-associativity
-- "a" //{1} ("b" //{2} "c")
p = m.Rec(m.P"a" + m.T(2), m.Rec(m.P"b" + m.T(2), m.P"c", 2), 1)
assert(p:match("abc") == 2)
r, l, serror = p:match("bac")
assert(r == nil and l == 2 and serror == "bac")
r, l, serror = p:match("cab")
assert(r == nil and l == 2 and serror == "cab")
r, l, serror = p:match("dab")
assert(r == nil and l == 2 and serror == "dab")
-- tests related to predicates
p = #m.T(1) + m.P"a"
r, l, serror = p:match("abc")
assert(r == nil and l == 1 and serror == "abc")
p = ##m.T(1) + m.P"a"
r, l, serror = p:match("abc")
assert(r == nil and l == 1 and serror == "abc")
p = -m.T(1) * m.P"a"
r, l, serror = p:match("abc")
assert(r == nil and l == 1 and serror == "abc")
p = -m.T(1) * m.P"a"
r, l, serror = p:match("bbc")
assert(r == nil and l == 1 and serror == "bbc")
p = -(-m.T(1)) * m.P"a"
r, l, serror = p:match("abc")
assert(r == nil and l == 1 and serror == "abc")
p = m.Rec(-m.T(22), m.P"a", 22)
r, l, serror = p:match("abc")
assert(r == nil and l == 0 and serror == "bc")
assert(p:match("bbc") == 1)
p = m.Rec(#m.T(22), m.P"a", 22)
assert(p:match("abc") == 1)
p = #m.Rec(m.T(22), m.P"a", 22)
assert(p:match("abc") == 1)
p = m.Rec(m.T(22), #m.P"a", 22)
assert(p:match("abc") == 1)
p = m.Rec(#m.T(22), m.P"a", 22)
r, l, serror = p:match("bbc")
assert(r == nil and l == 0 and serror == "bbc")
p = m.Rec(#m.P("a") * m.T(22), m.T(15), 22)
r, l, serror = p:match("abc")
assert(r == nil and l == 15 and serror == "abc")
p = m.Rec(#(m.P("a") * m.T(22)), m.T(15), 22)
r, l, serror = p:match("abc")
assert(r == nil and l == 15 and serror == "bc")
-- tests related to repetition
p = m.T(1)^0
r, l, serror = p:match("ab")
assert(r == nil and l == 1 and serror == "ab")
p = (m.P"a" + m.T(1))^0
r, l, serror = p:match("aa")
assert(r == nil and l == 1 and serror == "")
-- Bug reported by Matthew Allen
-- some optmizations performed by LPeg should not be
-- applied in case of labeled choices
p = m.Rec(m.P"A", m.P(true), 1) + m.P("B")
assert(p:match("B") == 2)
p = m.Rec(m.P"A", m.P(false), 1) + m.P("B")
assert(p:match("B") == 2)
--[[
S -> A //{1} 'a'
A -> B
B -> %1
]]
g = m.P{
"S",
S = m.Rec(m.V"A", m.P"a", 1),
A = m.V"B",
B = m.T(1),
}
assert(g:match("ab") == 2)
r, l, serror = g:match("bc")
assert(r == nil and l == 0 and serror == "bc")
--[[
S -> A
A -> (B (';' / %{1}))*
B -> 'a'
]]
g = m.P{
"S",
S = m.V"A",
A = m.P(m.V"B" * (";" + m.T(1)))^0,
B = m.P'a',
}
assert(g:match("a;a;") == 5)
r, l, serror = g:match("a;a")
assert(r == nil and l == 1 and serror == "")
-- %1 //{1,3} %2 //{2} 'a'
p = m.Rec(m.Rec(m.T(1), m.T(2), 1, 3), m.P"a", 2)
assert(p:match("abc") == 2)
r, l, serror = p:match("")
assert(r == nil and l == 0 and serror == "")
p = m.Rec(m.T(1), m.Rec(m.T(2), m.P"a", 2), 1, 3)
assert(p:match("abc") == 2)
r, l, serror = p:match("")
assert(r == nil and l == 0 and serror == "")
-- Infinte Loop TODO: check the semantics
-- %1 //{1} %1
p = m.Rec(m.T(1), m.T(1), 1)
--r, l, serror = p:match("ab")
--assert(r == nil and l == 1 and serror == "ab")
-- %1 //{1} 'a' (!. / %1)
p = m.Rec(m.T(1), m.P"a" * (-m.P(1) + m.T(1)), 1)
r, l, serror = p:match("ab")
assert(r == nil and l == 0 and serror == "b")
r, l, serror = p:match("cd")
assert(r == nil and l == 0 and serror == "cd")
-- %1 //{1} . (!. / %1)
p = m.Rec(m.T(1), m.P(1) * (-m.P(1) + m.T(1)), 1)
assert(p:match("abc") == 4)
-- testing the limit of labels
-- can only throw labels between 1 and 255
local r = pcall(m.Rec, m.P"b", m.P"a", 0)
assert(r == false)
local r = pcall(m.Rec, m.P"b", m.P"a", 256)
assert(r == false)
local r = pcall(m.Rec, m.P"b", m.P"a", -1)
assert(r == false)
local r = pcall(m.T, 0)
assert(r == false)
local r = pcall(m.T, 256)
assert(r == false)
local r = pcall(m.T, -1)
assert(r == false)
local r = m.Rec(m.P"b", m.P"a", 255)
assert(p:match("a") == 2)
p = m.T(255)
s = "abc"
r, l, serror = p:match(s)
assert(r == nil and l == 255 and serror == "abc")
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.Rec(
m.Rec(
m.Rec(
m.Rec(m.V"S0", m.V"ID", 1),
m.V"ID" * eq * m.V"Exp", 2
),
m.V"U"^0 * m.V"I" * m.V"ID", 3
),
m.V"U"^0 * m.V"ID" * m.V"ID", 4)
+ m.T(5), -- error
S0 = m.V"S1" + m.V"S2" + #m.V"I" * m.T(3),
S1 = #(m.V"ID" * eq) * m.T(2) + sp * #(m.V"ID" * -m.P(1)) * m.T(1) + #m.V"ID" * m.T(4),
S2 = #(m.V"U"^1 * m.V"ID") * m.T(4) + #(m.V"U"^1 * m.V"I") * m.T(3),
ID = sp * m.P"a",
U = sp * m.P"unsigned",
I = sp * m.P"int",
Exp = sp * m.P"E",
}
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, serror = g:match(s)
assert(r == nil and l == 5 and serror == "b")
s = "unsigned"
r, l, serror = g:match(s)
assert(r == nil and l == 5 and serror == s)
s = "unsigned a"
r, l, serror = g:match(s)
assert(r == nil and l == 5 and serror == s)
s = "unsigned int"
r, l, serror = g:match(s)
assert(r == nil and l == 5 and serror == s)
print("+")
local re = require 'relabel'
g = re.compile[['a' //{4,9} [a-z]
]]
assert(g:match("a") == 2)
r, l, serror = g:match("b")
assert(r == nil and l == 0 and serror == "b")
g = re.compile[['a' //{4,9} [a-f] //{5, 7} [a-z]
]]
assert(g:match("a") == 2)
r, l, serror = g:match("b")
assert(r == nil and l == 0 and serror == "b")
g = re.compile[[%{1} //{4,9} [a-z]
]]
r, l, serror = g:match("a")
assert(r == nil and l == 1 and serror == "a")
g = re.compile[[%{1} //{4,1} [a-f]
]]
assert(g:match("a") == 2)
r, l, serror = g:match("h")
assert(r == nil and l == 0 and serror == "h")
g = re.compile[[[a-f]%{9} //{4,9} [a-c]%{7} //{5, 7} [a-z] ]]
r, l, serror = g:match("a")
assert(r == nil and l == 0 and serror == "")
r, l, serror = g:match("aa")
assert(r == nil and l == 0 and serror == "")
assert(g:match("aaa") == 4)
r, l, serror = g:match("ad")
assert(r == nil and l == 0 and serror == "d")
r, l, serror = g:match("g")
assert(r == nil and l == 0 and serror == "g")
--[[ 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
]]
g = re.compile([[
S <- S0 //{1} ID //{2} ID %s* '=' Exp //{3} U* Int ID //{4} U ID ID / %{5}
S0 <- S1 / S2 / &Int %{3}
S1 <- &(ID %s* '=') %{2} / &(ID !.) %{1} / &ID %{4}
S2 <- &(U+ ID) %{4} / &(U+ Int) %{3}
ID <- %s* 'a'
U <- %s* 'unsigned'
Int <- %s* 'int'
Exp <- %s* 'E'
]])
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, serror = g:match(s)
assert(r == nil and l == 5 and serror == s)
s = "unsigned"
r, l, serror = g:match(s)
assert(r == nil and l == 5 and serror == s)
s = "unsigned a"
r, l, serror = g:match(s)
assert(r == nil and l == 5 and serror == s)
s = "unsigned int"
r, l, serror = g:match(s)
assert(r == nil and l == 5 and serror == s)
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 //{1} '' -> cmdSeq //{2} '' -> ifExp //{3} '' -> ifThen //{4} '' -> ifThenCmdSeq
//{5} '' -> ifElseCmdSeq //{6} '' -> ifEnd //{7} '' -> repeatCmdSeq
//{8} '' -> repeatUntil //{9} '' -> repeatExp //{10} '' -> assignOp
//{11} '' -> assignExp //{12} '' -> readName //{13} '' -> writeExp
//{14} '' -> simpleExp //{15} '' -> term //{16} '' -> factor
//{17} '' -> openParExp //{18} '' -> closePar / '' -> undefined
CmdSeq <- (Cmd (SEMICOLON / %{1})) (Cmd (SEMICOLON / %{1}))*
Cmd <- IfCmd / RepeatCmd / ReadCmd / WriteCmd / AssignCmd
IfCmd <- IF (Exp / %{2}) (THEN / %{3}) (CmdSeq / %{4}) (ELSE (CmdSeq / %{5}) / '') (END / %{6})
RepeatCmd <- REPEAT (CmdSeq / %{7}) (UNTIL / %{8}) (Exp / %{9})
AssignCmd <- NAME (ASSIGNMENT / %{10}) (Exp / %{11})
ReadCmd <- READ (NAME / %{12})
WriteCmd <- WRITE (Exp / %{13})
Exp <- SimpleExp ((LESS / EQUAL) (SimpleExp / %{14}) / '')
SimpleExp <- Term ((ADD / SUB) (Term / %{15}))*
Term <- Factor ((MUL / DIV) (Factor / %{16}))*
Factor <- OPENPAR (Exp / %{17}) (CLOSEPAR / %{18}) / 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]]
assert(g:match(s) == terror['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;]]
assert(g:match(s) == terror['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;]]
assert(g:match(s) == terror['ifExp'])
-- Error matching the 'then' keyword
s = [[
if a a := a + 1; else write 2; end;]]
assert(g:match(s) == terror['ifThen'])
-- Error matching the CmdSeq inside of 'then' branch
s = [[
if a then 3 := 2; else write 2; end;]]
assert(g:match(s) == terror['ifThenCmdSeq'])
-- Error matching the CmdSeq inside of 'else' branch
s = [[
if a then b := 2; else A := 2; end;]]
assert(g:match(s) == terror['ifElseCmdSeq'])
-- Error matching 'end' of 'if'
s = [[
if a then b := 2; else a := 2; 77;]]
assert(g:match(s) == terror['ifEnd'])
-- Error matching the CmdSeq of 'repeat'
s = [[repeat
F := f * n;
n := n - 1;
until (n < 1);]]
assert(g:match(s) == terror['repeatCmdSeq'])
-- Error matching 'until'
s = [[repeat
f := f * n;
n := n - 1;
88 (n < 1);]]
assert(g:match(s) == terror['repeatUntil'])
-- Error matching expression of 'until'
s = [[repeat
f := f * n;
n := n - 1;
until ; (n < 1);]]
assert(g:match(s) == terror['repeatExp'])
-- Error matching ':='
s = [[
f = f * n;]]
assert(g:match(s) == terror['assignOp'])
-- Error matching expression of assignment
s = [[
f := A * n;]]
assert(g:match(s) == terror['assignExp'])
-- Error matching 'name'
s = [[
read 2;]]
assert(g:match(s) == terror['readName'])
-- Error matching expression after 'write'
s = [[
write [a] := 2;]]
assert(g:match(s) == terror['writeExp'])
-- Error matching 'SimpleExp'
s = [[
a := a < A;]]
assert(g:match(s) == terror['simpleExp'])
-- Error matching 'Term'
s = [[
a := a + A;]]
assert(g:match(s) == terror['term'])
-- Error matching 'Factor'
s = [[
a := a * A;]]
assert(g:match(s) == terror['factor'])
-- Error matching expression after '('
s = [[
a := (A);]]
assert(g:match(s) == terror['openParExp'])
-- Error matching ')'
s = [[
a := (a];]]
assert(g:match(s) == terror['closePar'])
-- Error undefined
s = [[
A := a;]]
assert(g:match(s) == terror['undefined'])
print("+")
p = m.Rec("a", "b", 3)
assert(p:match("a") == 2)
checklabeq({nil, 0, "b"}, p:match("b"))
checklabeq({nil, 0, "c"}, p:match("c"))
p = m.Rec(m.T(3), "b", 1)
checklabeq({nil, 3, "a"}, p:match("a"))
checklabeq({nil, 3, "b"}, p:match("b"))
p = m.Rec(m.T(3), "b", 3)
checklabeq({nil, 0, "a"}, 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.Rec(m.V"A", (-m.P"c" * m.P(1))^0, 128) * m.V"C",
A = m.P"a"^0 * "b" + m.T(128),
C = m.P"c"^1,
}
assert(g:match("abc") == 4)
assert(g:match("aabc") == 5)
assert(g:match("aadc") == 5)
assert(g:match("dc") == 3)
checklabeq({nil, 0, "bc"}, g:match("bbc"))
assert(g:match("xxc") == 4)
assert(g:match("c") == 2)
checklabeq({nil, 0, ""}, g:match("fail"))
checklabeq({nil, 0, ""}, g:match("aaxx"))
--[[
S -> (A //{99} (!c .)*) C
A -> a+ (b / ^99)
C -> c+
]]
g = m.P{
"S",
S = m.Rec(m.V"A", (-m.P"c" * m.P(1))^0, 99) * m.V"C",
A = m.P"a"^1 * ("b" + m.T(99)),
C = m.P"c"^1,
}
assert(g:match("abc") == 4)
assert(g:match("aabc") == 5)
assert(g:match("aadc") == 5)
checklabeq({nil, 0, "bc"}, g:match("bc"))
checklabeq({nil, 0, "bbc"}, g:match("bbc"))
checklabeq({nil, 0, "b"}, g:match("abb"))
checklabeq({nil, 0, ""}, g:match("axx"))
assert(g:match("accc") == 5)
assert(g:match("axxc") == 5)
checklabeq({nil, 0, "c"}, g:match("c"))
checklabeq({nil, 0, "fail"}, 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, Lc = lpeg.T, lpeg.Lc
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 function labelindex(labname)
for i, elem in ipairs(labels) do
if elem[1] == labname then
return i
end
end
error("could not find label: " .. labname)
end
local errors = {}
local function expect(patt, labname)
local i = labelindex(labname)
function recorderror(input, pos)
table.insert(errors, {i, pos})
return true
end
return patt + Cmt("", recorderror) * T(i)
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"OpRecov")^0) / compute;
OpRecov = m.Rec(V"Operand", Cc(0), labelindex("ExpTerm"));
Operand = expect(V"Term", "ExpTerm");
Term = num + V"Group";
Group = "(" * V"InnerExp" * m.Rec(expect(")", "MisClose"), P"", labelindex("MisClose"));
InnerExp = m.Rec(expect(V"Exp", "ExpExp"), (P(1) - ")")^0 * Cc(0), labelindex("ExpExp"));
}
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]][2]
table.insert(out, "syntax error: " .. msg .. " (at index " .. pos .. ")")
end
errors = {}
return nil, table.concat(out, "\n")
end
end
print(eval "98-76*(54/32)")
--> 37.125
print(eval "(1+1-1*2/2")
--> syntax error: missing a closing ')' after the expression (at index 11)
print(eval "(1+)-1*(2/2)")
--> syntax error: expected a term after the operator (at index 4)
print(eval "(1+1)-1*(/2)")
--> syntax error: expected an expression after the parenthesis (at index 10)
print(eval "1+(1-(1*2))/2x")
--> syntax error: extra chracters found after the expression (at index 14)
print(eval "-1+(1-(1*2))/2")
--> syntax error: no expression found (at index 1)
print(eval "(1+1-1*(2/2+)-():")
--> syntax error: expected a term after the operator (at index 13)
--> syntax error: expected an expression after the parenthesis (at index 16)
--> syntax error: missing a closing ')' after the expression (at index 17)
--> syntax error: extra characters found after the expression (at index
print("+")
local g = m.P{
"S",
S = V"End" + V'A' * V'S',
A = P'a' + T(1),
End = P"." * (-P(1) + T(2)),
}
assert(g:match("a.") == 3)
assert(g:match("aa.") == 4)
assert(g:match(".") == 2)
checklabeq({nil, 1, "ba."}, g:match("ba."))
checklabeq({nil, 1, "ba."}, g:match("aba."))
checklabeq({nil, 1, "cba."}, g:match("cba."))
checklabeq({nil, 2, "a"}, g:match("a.a"))
local g2 = m.P{
"S",
S = m.Rec(g, V"B", 1),
B = P'b'^1 + T(3)
}
assert(g2:match("a.") == 3)
assert(g2:match("aa.") == 4)
assert(g2:match(".") == 2)
assert(g2:match("ba.") == 4)
assert(g2:match("aba.") == 5)
checklabeq({nil, 3, "cba."}, g2:match("cba."))
checklabeq({nil, 2, "a"}, g2:match("a.a"))
local g3 = m.P{
"S",
S = m.Rec(g2, V"C", 2, 3),
C = P'c'^1 + T(4)
}
assert(g3:match("a.") == 3)
assert(g3:match("aa.") == 4)
assert(g3:match(".") == 2)
assert(g3:match("ba.") == 4)
assert(g3:match("aba.") == 5)
assert(g3:match("cba.") == 5)
checklabeq({nil, 4, "a"}, g3:match("a.a"))
checklabeq({nil, 4, "dc"}, g3:match("dc"))
checklabeq({nil, 4, "d"}, g3: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, "a 123"}, {g:match("44 a 123")})
local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5)
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, "a 123"}, {g:match("44 a 123")})
local g2 = m.Rec(g, ((-m.R("09") * m.P(1))^0) / "58", 5)
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, Lc, Rec = lpeg.T, lpeg.Lc, lpeg.Rec
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 function labelindex(labname)
for i, elem in ipairs(labels) do
if elem[1] == labname then
return i
end
end
error("could not find label: " .. labname)
end
local errors = {}
local function expect(patt, labname, recpatt)
local i = labelindex(labname)
function recorderror(input, pos)
table.insert(errors, {i, pos})
return true
end
if not recpatt then recpatt = P"" end
--return Rec(patt, Cmt("", recorderror) * recpatt)
return patt + T(i)
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,
}
local rg = Rec(g, Cc(3), labelindex("ExpTerm"))
local function eval(input)
local result, label, suffix = rg: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]][2]
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")