Модуль:Lpegdebug

Материал из свободной русской энциклопедии «Традиция»
Перейти к навигации Перейти к поиску

This is the original README.md file from https://github.com/pkulchenko/PegDebug, converted into wiki syntax.

Project Description

PegDebug is a trace debugger for LPeg rules and captures.

Features

PegDebug allows to trace the processing of LPeg rules and to visualize how patterns are explored and matched as well as when captures are made.

PegDebug is based on a prototype by Patrick Donnelly, which has been improved to provide formatted output, handle folding captures, and report captures (with some #Limitations).

Usage

lua
local grammar = require('pegdebug').trace(lpegGrammar, traceOptions)
lpeg.match(lpeg.P(grammar),"subject string")

Example

The following example demonstrates the usage of PegDebug with default options:

local lpeg = require('lpeg')
local grammar = require('pegdebug').trace({ "List";
  NonNumber = lpeg.R('az');
  Number = lpeg.R"09"^1 / tonumber;
  List = lpeg.V("NonNumber") + lpeg.V("Number") * ("," * lpeg.V("Number"))^0;
})
print(lpeg.match(lpeg.P(grammar),"10,30,43"))

This will generate the following output:

+	List	1	"1"
 +	NonNumber	1	"1"
 -	NonNumber	1
 +	Number	1	"1"
 =	Number	1-2	"10"
 +	Number	4	"3"
 =	Number	4-5	"30"
 +	Number	7	"4"
 =	Number	7-8	"43"
=	List	1-8	"10,30,43"
/	Number	1	1	10
/	Number	4	1	30
/	Number	7	1	43
/	List	1	3	10, 30, 43
10	30	43

As you can see, the output shows all the patterns that LPeg explored ('+'), matched ('='), and failed to match ('-'). The output includes the name of the pattern, the range of positions and the content (in case of a match). The output also includes all captures when they are produced.

Options

  • `out` (table) -- provide a table to store results instead of `printing` them;
  • `only` (table) -- provide a table to specify which rules to include in the output;
  • `serializer` (function) -- provide an alternative mechanism to serialize captured values;
  • `'/'`, `'+'`, `'-'`, `'='` (boolean) -- disable handling of specific events;

for example, `['/'] = false` will disable showing captures in the output.

Installation

Make `src/pegdebug.lua` available to your script.

Limitations

PegDebug may return incorrect captures when folding captures are used; for example, the following fragment will return `10`, instead of expected `83`:

lua
local lpeg = require('lpeg')
local grammar = require('pegdebug').trace({ "Sum";
  NonNumber = lpeg.R('az');
  Number = lpeg.R"09"^1 / tonumber;
  List = lpeg.V("NonNumber") + lpeg.V("Number") * ("," * lpeg.V("Number"))^0;
  Sum = lpeg.Cf(lpeg.V("List"),
    function(acc, newvalue) return newvalue and acc + newvalue end);
})
print(lpeg.match(lpeg.P(grammar),"10,30,43"))

This is because the function capture used to report captures returns only one capture, which breaks the folding capture (which expects a list of captures).

You may disable capture reporting by using {['/'] = false} or disable capture reporting specifically for the rules that generate captures used in folding: using {['/'] = {List = false}} will 'fix' this example.

Author

Paul Kulchenko ([email protected])

License

See LICENSE file


--
-- PegDebug -- A debugger for LPeg expressions and processing
-- Copyright 2014 Paul Kulchenko
--

local lpeg = lpeg --require "lpeg"

local Cmt = lpeg.Cmt
local Cp  = lpeg.Cp
local P   = lpeg.P

local pegdebug = {
  _NAME = "pegdebug",
  _VERSION = 0.41,
  _COPYRIGHT = "Paul Kulchenko",
  _DESCRIPTION = "Debugger for LPeg expressions and processing",
}

function pegdebug.trace(grammar, opts)
  opts = opts or {}
  local serpent = opts.serializer
    or pcall(require, "serpent") and require("serpent").line
    or pcall(require, "mobdebug") and require("mobdebug").line
    or nil
  local function line(s) return (string.format("%q", s):gsub("\\\n", "\\n")) end
  local function pretty(...)
    if serpent then return serpent({...}, {comment = false}):sub(2,-2) end
    local res = {}
    for i = 1, select('#', ...) do
      local v = select(i, ...)
      local tv = type(v)
      res[i] = tv == 'number' and v or tv == 'string' and line(v) or tostring(v)
    end
    return table.concat(res, ", ")
  end
  local level = 0
  local start = {}
  local print = print
  if type(opts.out) == 'table' then
    print = function(...) table.insert(opts.out, table.concat({...}, "\t")) end
  end
  for k, p in pairs(grammar) do
    local enter = Cmt(P(true), function(s, p, ...)
        start[level] = p
        if opts['+'] ~= false then
          print((" "):rep(level).."+", k, p, line(s:sub(p,p)))
        end
        level = level + 1
        return true
      end)
    local leave = Cmt(P(true), function(s, p, ...)
        level = level - 1
        if opts['-'] ~= false then
          print((" "):rep(level).."-", k, p)
        end
        return true
      end) * (P(1) - P(1))
    local eq = Cmt(P(true), function(s, p, ...)
        level = level - 1
        if opts['='] ~= false then
          print((" "):rep(level).."=", k, start[level]..'-'..(p-1), line(s:sub(start[level],p-1)))
        end
        return true
      end)
    if k ~= 1 and (not opts.only or opts.only[k]) then
      if opts['/'] ~= false
      and (type(opts['/']) ~= 'table' or opts['/'][k] ~= false) then
        -- Cp() is needed to only get captures (and not the whole match)
        p = Cp() * p / function(pos, ...)
            print((" "):rep(level).."/", k, pos, select('#', ...), pretty(...))
            return ...
          end
      end
      grammar[k] = enter * p * eq + leave
    end
  end
  return grammar
end

return pegdebug