Skip to content

Commit

Permalink
lib.maxpc: fix backtracking “zero or more” combinator match.all
Browse files Browse the repository at this point in the history
Removes the broken match.range and replaces match.all with a simple
implementation by means of match.plus.

In lib.xsd_regexp implement backtracking match.range combinator by means of
match.all.

Simplify variable argument variants of match.plus and match.alternate along the
way.
  • Loading branch information
eugeneia committed Apr 9, 2018
1 parent 03daccb commit be6c657
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 49 deletions.
96 changes: 48 additions & 48 deletions src/lib/maxpc.lua
Expand Up @@ -318,55 +318,41 @@ function match.optional (parser)
return match.alternate(parser, match.seq())
end

function match.range (parser, min, max)
return function (s)
local rests = {}
while s and (not max or #rests <= max) do
table.insert(rests, s)
s = parser(s)
end
local more
more = function ()
local rest = table.remove(rests)
if rest and (not min or #rests >= min) then
return rest, nil, nil, more
end
end
return more()
end
end

function match.all (parser)
return match.range(parser, 0)
end

function match.one_or_more (parser)
return match.plus(parser, match.all(parser))
return match.optional(
match.plus(parser, function (s) return match.all(parser)(s) end)
)
end

local function make_reducer (combinator, sentinel)
local reduce
reduce = function (parsers)
if #parsers == 0 then
return sentinel
elseif #parsers == 1 then
return parsers[1]
else
local head = table.remove(parsers, 1)
local tail = reduce(parsers)
return combinator(head, tail)
end
end
return function (...)
return reduce({...})
local function reduce (fun, tab)
local acc
for _, val in ipairs(tab) do
if not acc then acc = val
else acc = fun(acc, val) end
end
return acc
end

local function identity (...) return ... end
match.path = make_reducer(match.plus, identity)

local function constantly_nil () end
match.either = make_reducer(match.alternate, constantly_nil)

function match.path (...)
local parsers = {...}
if #parsers > 0 then
return reduce(match.plus, parsers)
else
return identity
end
end

function match.either (...)
local parsers = {...}
if #parsers > 0 then
return reduce(match.alternate, parsers)
else
return constantly_nil
end
end


-- tests
Expand Down Expand Up @@ -531,13 +517,6 @@ function selftest ()
assert(result == 1234) assert(matched) assert(not eof)

-- backtracking
local result, matched, eof = parse(
"0aaaaaaaa1",
match.path(match.equal("0"),
match.all(match.satisfies(is_alphanumeric)),
match.equal("1"))
)
assert(not result) assert(matched) assert(eof)
local result, matched, eof =
parse("a", match.either(match.equal("a"), match.equal("b")))
assert(not result) assert(matched) assert(eof)
Expand All @@ -548,6 +527,27 @@ function selftest ()
assert(not result) assert(matched)
local result, matched, eof = parse("", match.optional(match.equal(".")))
assert(not result) assert(matched) assert(eof)
local result, matched, eof = parse(
"0aaaaaaaa1",
match.path(match.equal("0"),
match.all(match.satisfies(is_alphanumeric)),
match.equal("1"))
)
assert(not result) assert(matched) assert(eof)
local result, matched, eof = parse(
"aaac",
match.path(
match.all(
match.either(
match.seq(match.equal("a"), match.equal("a")),
match.seq(match.equal("a"), match.equal("a"), match.equal("a")),
match.equal("c")
)
),
match.eof()
)
)
assert(not result) assert(matched) assert(eof)
local domain_like = match.either(
match.path(
match.path(
Expand Down
36 changes: 35 additions & 1 deletion src/lib/xsd_regexp.lua
Expand Up @@ -348,13 +348,43 @@ function compile_quantifier (quantifier)
end
elseif quantifier.exactly then
return function (parser)
return match.range(parser, quantifier.exactly, quantifier.exactly)
return match.exactly_n(parser, quantifier.exactly)
end
else
error("Invalid quantifier")
end
end

function match.one_or_more (parser)
return match.path(parser, match.all(parser))
end

function match.exactly_n (parser, n)
local ps = {}
for i = 1, n do table.insert(ps, parser) end
return match.seq(unpack(ps))
end

function match.upto_n (parser, n)
local p = match.seq()
for i = 1, n do p = match.optional(match.plus(parser, p)) end
return p
end

function match.range (parser, min, max)
if min and max then
assert(min <= max, "Invalid quanitity: "..min.."-"..max)
return match.path(match.exactly_n(parser, min),
match.upto_n(parser, max - min))
elseif min then
return match.path(match.exactly_n(parser, min), match.all(parser))
elseif max then
return match.upto_n(parser, max)
else
return match.all(parser)
end
end

function compile_atom (atom)
local function is_special_escape (s)
return member(s, "\\|.-^?*+{}()[]")
Expand Down Expand Up @@ -620,6 +650,10 @@ function selftest ()
accept={"0","12", "123", "192","168","178",},
reject={"a.a.a.", ""}}

test{regexp="(aa|aaa|bb)*",
accept={"", "aa", "aaa", "aaaa", "aabb", "aaabb", "bb"},
reject={"a", "b", "bbb", "aaaab"}}

local ipv4_address =
"(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}"
.. "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
Expand Down

0 comments on commit be6c657

Please sign in to comment.