Moved examples to tofix because fixing them is besides the point right now.
1054 lines
27 KiB
Lua
1054 lines
27 KiB
Lua
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")
|