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")
