Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"reflect"
"strings"
"sync"

"github.com/alecthomas/participle/v2/lexer"
)
Expand All @@ -29,6 +30,14 @@ type parseContext struct {
allowTrailing bool
}

var contextFieldSetPool = sync.Pool{
New: func() any { return &contextFieldSet{} },
}

var parseContextPool = sync.Pool{
New: func() any { return &parseContext{} },
}

func newParseContext(lex *lexer.PeekingLexer, lookahead int, caseInsensitive map[lexer.TokenType]bool) parseContext {
return parseContext{
PeekingLexer: *lex,
Expand All @@ -49,7 +58,9 @@ func (p *parseContext) DeepestError(err error) error {

// Defer adds a function to be applied once a branch has been picked.
func (p *parseContext) Defer(tokens []lexer.Token, strct reflect.Value, field structLexerField, fieldValue []reflect.Value) {
p.apply = append(p.apply, &contextFieldSet{tokens, strct, field, fieldValue})
set := contextFieldSetPool.Get().(*contextFieldSet)
*set = contextFieldSet{tokens, strct, field, fieldValue}
p.apply = append(p.apply, set)
}

// Apply deferred functions.
Expand All @@ -58,11 +69,28 @@ func (p *parseContext) Apply() error {
if err := setField(apply.tokens, apply.strct, apply.field, apply.fieldValue); err != nil {
return err
}
*apply = contextFieldSet{}
contextFieldSetPool.Put(apply)
}
p.apply = nil
return nil
}

func (p *parseContext) recycle(keep bool) {
if p == nil {
return
}
if !keep {
for _, apply := range p.apply {
*apply = contextFieldSet{}
contextFieldSetPool.Put(apply)
}
}
p.apply = nil
*p = parseContext{}
parseContextPool.Put(p)
}

// Branch accepts the branch as the correct branch.
func (p *parseContext) Accept(branch *parseContext) {
p.apply = append(p.apply, branch.apply...)
Expand All @@ -71,11 +99,12 @@ func (p *parseContext) Accept(branch *parseContext) {
p.deepestErrorDepth = branch.deepestErrorDepth
p.deepestError = branch.deepestError
}
branch.recycle(true)
}

// Branch starts a new lookahead branch.
func (p *parseContext) Branch() *parseContext {
branch := &parseContext{}
branch := parseContextPool.Get().(*parseContext)
*branch = *p
branch.apply = nil
return branch
Expand Down
16 changes: 11 additions & 5 deletions nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ func (g *group) Parse(ctx *parseContext, parent reflect.Value) (out []reflect.Va
out = append(out, v...) // Try to return as much of the parse tree as possible
return out, err
}
branch.recycle(false)
break
}
out = append(out, v...)
Expand Down Expand Up @@ -306,6 +307,7 @@ func (l *lookaheadGroup) Parse(ctx *parseContext, parent reflect.Value) (out []r
defer ctx.printTrace(l)()
// Create a branch to avoid advancing the parser as any match will be discarded
branch := ctx.Branch()
defer branch.recycle(false)
out, err = l.expr.Parse(branch, parent)
matchedLookahead := err == nil && out != nil
expectingMatch := !l.negative
Expand Down Expand Up @@ -337,12 +339,13 @@ func (d *disjunction) Parse(ctx *parseContext, parent reflect.Value) (out []refl
if ctx.Stop(err, branch) {
return value, err
}
cursor := branch.Cursor()
// Show the closest error returned. The idea here is that the further the parser progresses
// without error, the more difficult it is to trace the error back to its root.
if branch.Cursor() >= deepestError {
if cursor >= deepestError {
firstError = err
firstValues = value
deepestError = branch.Cursor()
deepestError = cursor
}
} else if value != nil {
bt := branch.RawPeek()
Expand All @@ -353,6 +356,7 @@ func (d *disjunction) Parse(ctx *parseContext, parent reflect.Value) (out []refl
ctx.Accept(branch)
return value, nil
}
branch.recycle(false)
}
if firstError != nil {
ctx.MaybeUpdateError(firstError)
Expand Down Expand Up @@ -481,15 +485,17 @@ 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()
notEOF := ctx.Peek()
if notEOF.EOF() {
// EOF cannot match a negation, which expects something
return nil, nil
}

// 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()
defer branch.recycle(false)

out, err = n.node.Parse(branch, parent)
if out != nil && err == nil {
// out being non-nil means that what we don't want is actually here, so we report nomatch
Expand Down