lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On 21/10/2009 13:00, Patrick Donnelly wrote:
Hi List,

I thought I would share a debugger I created for tracing the path lpeg
takes in matching a pattern using a large/complex grammar. I've gone
through a lot of headache debugging a parser I made for Lua so I
thought I would share this bit of code which made it much easier.

The code changes the grammar like so:

(1) Changes each rule in the grammar to have a prefix pattern that is
always true and run time captures to print "ENTER<rule>"
(2) Changes each rule in the grammar to have a suffix pattern that is
composed of a pattern always true that run time captures to print
"LEAVE<rule>". The end of the suffix pattern is always false so it
does not change the actual pattern when it fails.
(3) If the rule in the grammar matches, (2) does not happen and
instead the current position and subject up to the position is
printed.

For my Lua parser, it looks something like this:

ENTER   1
ENTER   space
ENTER   comment
LEAVE   comment
---     space   ---
2

ENTER   chunk
ENTER   space
ENTER   comment
LEAVE   comment
---     space   ---
2

ENTER   stat
ENTER   varlist
ENTER   var
ENTER   prefix
ENTER   Name

The actual code to make this happen is quite simple:

for k, p in pairs(grammar) do
   local enter = lpeg.Cmt(lpeg.P(true), function(s, p, ...)
print("ENTER", k) return p end);
   local leave = lpeg.Cmt(lpeg.P(true), function(s, p, ...)
print("LEAVE", k) return p end) * (lpeg.P("k") - lpeg.P "k");
   grammar[k] = lpeg.Cmt(enter * p + leave, function(s, p, ...)
print("---", k, "---") print(p, s:sub(1, p-1)) return p end)
end

(Notice that there is a trick in leave, we can't use lpeg.P(false)
because LPeg optimizes it out.)

Hope someone out there finds it useful!

I do!
The idea is smart and once I understood how it worked, I found the implementation brilliant.

For some reasons, I had trouble making it to work with the latest version of LPeg (0.10.2), but perhaps it was just my failure. Anyway, I slightly adapted the code to use the latest features of LPeg, and to fit my taste in formatting the result.

Here is my version:

  for k, r in pairs(grammar) do
    if type(k) == "number" then
      if type(r) ~= "pattern" and type(r) ~= "userdata" then
        print("Initial rule: " .. r)
      end
    else
      local enter = lpeg.P(function (s, p, ...) print("ENTER", k) return true end)
      local leave = lpeg.P(function (s, p, ...) print("LEAVE", k) return false end)
      grammar[k] = lpeg.Cmt(enter * r + leave,
        function (s, p, ...)
          print("MATCH", k, p, "'" .. s:sub(1, p-1) .. "'")
          return true
        end)
    end
  end

It choked on the initial rule, so I made a (clumsy) detection to skip it.

Thanks for sharing.

--
Philippe Lhoste
--  (near) Paris -- France
--  http://Phi.Lho.free.fr
--  --  --  --  --  --  --  --  --  --  --  --  --  --