diff --git a/context.go b/context.go index 17484dd6..025fb3c7 100644 --- a/context.go +++ b/context.go @@ -1,7 +1,10 @@ package participle import ( + "fmt" + "io" "reflect" + "strings" "github.com/alecthomas/participle/v2/lexer" ) @@ -16,6 +19,8 @@ type contextFieldSet struct { // Context for a single parse. type parseContext struct { *lexer.PeekingLexer + depth int + trace io.Writer deepestError error deepestErrorDepth int lookahead int @@ -103,6 +108,16 @@ func (p *parseContext) Stop(err error, branch *parseContext) bool { func (p *parseContext) hasInfiniteLookahead() bool { return p.lookahead < 0 } +func (p *parseContext) printTrace(n node) func() { + if p.trace != nil { + tok := p.PeekingLexer.Peek() + fmt.Fprintf(p.trace, "%s%q %s\n", strings.Repeat(" ", p.depth*2), tok, n.GoString()) + p.depth += 1 + return func() { p.depth -= 1 } + } + return func() {} +} + func maxInt(a, b int) int { if a > b { return a diff --git a/ebnf.go b/ebnf.go index 2df523e0..0b127205 100644 --- a/ebnf.go +++ b/ebnf.go @@ -159,9 +159,6 @@ func buildEBNF(root bool, n node, seen map[node]bool, p *ebnfp, outp *[]*ebnfp) buildEBNF(true, n.expr, seen, p, outp) p.out += ")" - case *trace: - buildEBNF(root, n.node, seen, p, outp) - default: panic(fmt.Sprintf("unsupported node type %T", n)) } diff --git a/nodes.go b/nodes.go index a7f1a25b..d1b27eab 100644 --- a/nodes.go +++ b/nodes.go @@ -60,6 +60,7 @@ func (p *parseable) String() string { return ebnf(p) } func (p *parseable) GoString() string { return p.t.String() } func (p *parseable) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(p)() rv := reflect.New(p.t) v := rv.Interface().(Parseable) err = v.Parse(ctx.PeekingLexer) @@ -82,6 +83,7 @@ func (c *custom) String() string { return ebnf(c) } func (c *custom) GoString() string { return c.typ.Name() } func (c *custom) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(c)() results := c.parseFn.Call([]reflect.Value{reflect.ValueOf(ctx.PeekingLexer)}) if err, _ := results[1].Interface().(error); err != nil { if err == NextMatch { @@ -102,6 +104,7 @@ func (u *union) String() string { return ebnf(u) } func (u *union) GoString() string { return u.typ.Name() } func (u *union) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(u)() temp := disjunction{u.members} vals, err := temp.Parse(ctx, parent) if err != nil { @@ -145,6 +148,7 @@ func (s *strct) String() string { return ebnf(s) } func (s *strct) GoString() string { return s.typ.Name() } func (s *strct) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(s)() sv := reflect.New(s.typ).Elem() start := ctx.RawCursor() t := ctx.Peek() @@ -225,6 +229,7 @@ type group struct { func (g *group) String() string { return ebnf(g) } func (g *group) GoString() string { return fmt.Sprintf("group{%s}", g.mode) } func (g *group) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(g)() // Configure min/max matches. min := 1 max := 1 @@ -294,6 +299,7 @@ func (n *lookaheadGroup) String() string { return ebnf(n) } func (n *lookaheadGroup) GoString() string { return "lookaheadGroup{}" } func (n *lookaheadGroup) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(n)() // Create a branch to avoid advancing the parser as any match will be discarded branch := ctx.Branch() out, err = n.expr.Parse(branch, parent) @@ -315,6 +321,7 @@ func (d *disjunction) String() string { return ebnf(d) } func (d *disjunction) GoString() string { return "disjunction{}" } func (d *disjunction) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(d)() var ( deepestError = 0 firstError error @@ -362,6 +369,7 @@ func (s *sequence) String() string { return ebnf(s) } func (s *sequence) GoString() string { return "sequence{}" } func (s *sequence) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(s)() for n := s; n != nil; n = n.next { child, err := n.node.Parse(ctx, parent) out = append(out, child...) @@ -396,6 +404,7 @@ func (c *capture) String() string { return ebnf(c) } func (c *capture) GoString() string { return "capture{}" } func (c *capture) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(c)() start := ctx.RawCursor() v, err := c.node.Parse(ctx, parent) if v != nil { @@ -420,6 +429,7 @@ func (r *reference) String() string { return ebnf(r) } func (r *reference) GoString() string { return fmt.Sprintf("reference{%s}", r.identifier) } func (r *reference) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(r)() token, cursor := ctx.PeekAny(func(t lexer.Token) bool { return t.Type == r.typ }) @@ -439,6 +449,7 @@ func (o *optional) String() string { return ebnf(o) } func (o *optional) GoString() string { return "optional{}" } func (o *optional) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(o)() branch := ctx.Branch() out, err = o.node.Parse(branch, parent) if err != nil { @@ -466,6 +477,7 @@ func (r *repetition) GoString() string { return "repetition{}" } // Parse a repetition. Once a repetition is encountered it will always match, so grammars // should ensure that branches are differentiated prior to the repetition. func (r *repetition) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(r)() i := 0 for ; i < MaxIterations; i++ { branch := ctx.Branch() @@ -505,6 +517,7 @@ func (l *literal) String() string { return ebnf(l) } func (l *literal) GoString() string { return fmt.Sprintf("literal{%q, %q}", l.s, l.tt) } func (l *literal) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(l)() match := func(t lexer.Token) bool { var equal bool if ctx.caseInsensitive[t.Type] { @@ -530,6 +543,7 @@ func (n *negation) String() string { return ebnf(n) } func (n *negation) GoString() string { return "negation{}" } func (n *negation) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Value, err error) { + defer ctx.printTrace(n)() // Create a branch to avoid advancing the parser, but call neither Stop nor Accept on it // since we will discard a match. branch := ctx.Branch() diff --git a/options.go b/options.go index b453cb47..3c8dae7d 100644 --- a/options.go +++ b/options.go @@ -2,6 +2,7 @@ package participle import ( "fmt" + "io" "reflect" "github.com/alecthomas/participle/v2/lexer" @@ -111,6 +112,13 @@ func Union[T any](members ...T) Option { // ParseOption modifies how an individual parse is applied. type ParseOption func(p *parseContext) +// Trace the parse to "w". +func Trace(w io.Writer) ParseOption { + return func(p *parseContext) { + p.trace = w + } +} + // AllowTrailing tokens without erroring. // // That is, do not error if a full parse completes but additional tokens remain. diff --git a/parser.go b/parser.go index 0a1a71dd..39d34ec6 100644 --- a/parser.go +++ b/parser.go @@ -23,7 +23,6 @@ type customDef struct { // A Parser for a particular grammar and lexer. type Parser struct { root node - trace io.Writer lex lexer.Definition typ reflect.Type useLookahead int @@ -114,9 +113,6 @@ func Build(grammar interface{}, options ...Option) (parser *Parser, err error) { if err := validate(p.root); err != nil { return nil, err } - if p.trace != nil { - p.root = injectTrace(p.trace, 0, p.root) - } return p, nil } diff --git a/parser_test.go b/parser_test.go index dd631cd1..4d633c5d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1834,8 +1834,10 @@ func TestParserWithUnion(t *testing.T) { {`{ [ { [12] } ] }`, grammar{B: BMember2{AMember2{BMember2{AMember2{BMember1{12}}}}}}}, } { var actual grammar - require.NoError(t, parser.ParseString("", c.src, &actual)) + var trace strings.Builder + require.NoError(t, parser.ParseString("", c.src, &actual, participle.Trace(&trace))) require.Equal(t, c.expected, actual) + require.NotEqual(t, "", trace.String()) } require.Equal(t, strings.TrimSpace(` diff --git a/trace.go b/trace.go deleted file mode 100644 index 4f1d239e..00000000 --- a/trace.go +++ /dev/null @@ -1,62 +0,0 @@ -package participle - -import ( - "fmt" - "io" - "reflect" - "strings" -) - -// Trace the parse to "w". -func Trace(w io.Writer) Option { - return func(p *Parser) error { - p.trace = w - return nil - } -} - -type trace struct { - w io.Writer - indent int - node -} - -func (t *trace) Parse(ctx *parseContext, parent reflect.Value) ([]reflect.Value, error) { - tok := ctx.Peek() - fmt.Fprintf(t.w, "%s%q %s\n", strings.Repeat(" ", t.indent), tok, t.node.GoString()) - return t.node.Parse(ctx, parent) -} - -func injectTrace(w io.Writer, indent int, n node) node { - out := &trace{w, indent, n} - switch n := n.(type) { - case *disjunction: - for i, child := range n.nodes { - n.nodes[i] = injectTrace(w, indent+2, child) - } - case *union: - for i, child := range n.members { - n.members[i] = injectTrace(w, indent+2, child) - } - case *strct: - n.expr = injectTrace(w, indent+2, n.expr) - case *sequence: - n.node = injectTrace(w, indent+2, n.node) - // injectTrace(w, indent, n.next) - case *parseable: - case *custom: - case *capture: - n.node = injectTrace(w, indent+2, n.node) - case *reference: - case *optional: - n.node = injectTrace(w, indent+2, n.node) - case *repetition: - n.node = injectTrace(w, indent+2, n.node) - case *negation: - n.node = injectTrace(w, indent+2, n.node) - case *literal: - case *group: - n.expr = injectTrace(w, indent+2, n.expr) - } - return out -}