Skip to content

Commit

Permalink
Convert participle.Error to an interface.
Browse files Browse the repository at this point in the history
This simplifies things quite a bit, as the parser can now just return
any error (including lexer.Error) without having to try and mash it
into a participle.parseError.
  • Loading branch information
alecthomas committed Dec 12, 2019
1 parent c5d862a commit 4658f9a
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 62 deletions.
7 changes: 7 additions & 0 deletions _examples/precedenceclimbing/main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
// Package main shows an example of how to add precedence climbing to a Participle parser.
//
// Precedence climbing is an approach to parsing expressions that efficiently
// produces compact parse trees.
//
// In contrast, naive recursive descent expression parsers produce parse trees proportional in
// complexity to the number of operators supported. This impacts both readability and
// performance.
//
// It is based on https://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing
package main

Expand Down
65 changes: 22 additions & 43 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,65 +9,44 @@ import (
// Error represents an error while parsing.
//
// The error will contain positional information if available.
type Error struct {
Message string
Pos lexer.Position
type Error interface {
error
Position() lexer.Position
}

// autoError attempts to determine position from an existing error.
func autoError(err error) error {
switch err := err.(type) {
case *lexer.Error:
return (*Error)(err)
// UnexpectedTokenError is returned by Parse when an unexpected token is encountered.
//
// This is useful for composing parsers in order to detect when a sub-parser has terminated.
type UnexpectedTokenError struct{ lexer.Token }

case *Error:
return err
func (u UnexpectedTokenError) Error() string {
return lexer.FormatError(u.Pos, fmt.Sprintf("unexpected token %q", u.Value))
}

case nil:
return nil
func (u UnexpectedTokenError) Position() lexer.Position { return u.Pos } // nolint: golint

default:
return &Error{Message: err.Error()}
}
type parseError struct {
Message string
Pos lexer.Position
}

func (p *parseError) Position() lexer.Position { return p.Pos }

// AnnotateError wraps an existing error with a position.
//
// If the existing error is a lexer.Error or participle.Error it will be returned unmodified.
func AnnotateError(pos lexer.Position, err error) error {
switch err := err.(type) {
case *lexer.Error:
return (*Error)(err)

case *Error:
return err

case nil:
return nil

default:
return &Error{Message: err.Error(), Pos: pos}
if perr, ok := err.(Error); ok {
return perr
}
return &parseError{Message: err.Error(), Pos: pos}
}

// Errorf creats a new Error at the given position.
func Errorf(pos lexer.Position, format string, args ...interface{}) error {
return &Error{Message: fmt.Sprintf(format, args...), Pos: pos}
return &parseError{Message: fmt.Sprintf(format, args...), Pos: pos}
}

// Error formats an error in the form "[<filename>:][<line>:<pos>:] <message>"
func (e *Error) Error() string {
msg := ""
if e.Pos.Filename != "" {
msg += e.Pos.Filename + ":"
}
if e.Pos.Line != 0 || e.Pos.Column != 0 {
msg += fmt.Sprintf("%d:%d:", e.Pos.Line, e.Pos.Column)
}
if msg != "" {
msg += " " + e.Message
} else {
msg = e.Message
}
return msg
func (p *parseError) Error() string {
return lexer.FormatError(p.Pos, p.Message)
}
23 changes: 19 additions & 4 deletions lexer/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,26 @@ func Errorf(pos Position, format string, args ...interface{}) *Error {
}
}

func (e *Error) Position() Position { return e.Pos } // nolint: golint

// Error complies with the error interface and reports the position of an error.
func (e *Error) Error() string {
filename := e.Pos.Filename
if filename == "" {
filename = "<source>"
return FormatError(e.Pos, e.Message)
}

// FormatError formats an error in the form "[<filename>:][<line>:<pos>:] <message>"
func FormatError(pos Position, message string) string {
msg := ""
if pos.Filename != "" {
msg += pos.Filename + ":"
}
if pos.Line != 0 || pos.Column != 0 {
msg += fmt.Sprintf("%d:%d:", pos.Line, pos.Column)
}
if msg != "" {
msg += " " + message
} else {
msg = message
}
return fmt.Sprintf("%s:%d:%d: %s", filename, e.Pos.Line, e.Pos.Column, e.Message)
return msg
}
8 changes: 4 additions & 4 deletions nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ func decorate(err *error, name func() string) {
}
switch realError := (*err).(type) {
case *lexer.Error:
*err = &Error{Message: name() + ": " + realError.Message, Pos: realError.Pos}
case *Error:
*err = &Error{Message: name() + ": " + realError.Message, Pos: realError.Pos}
*err = &parseError{Message: name() + ": " + realError.Message, Pos: realError.Pos}
case *parseError:
*err = &parseError{Message: name() + ": " + realError.Message, Pos: realError.Pos}
default:
*err = &Error{Message: fmt.Sprintf("%s: %s", name(), realError)}
*err = &parseError{Message: fmt.Sprintf("%s: %s", name(), realError)}
}
}

Expand Down
22 changes: 11 additions & 11 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ func Build(grammar interface{}, options ...Option) (parser *Parser, err error) {
func (p *Parser) Lex(r io.Reader) ([]lexer.Token, error) {
lex, err := p.lex.Lex(r)
if err != nil {
return nil, autoError(err)
return nil, err
}
tokens, err := lexer.ConsumeAll(lex)
return tokens, autoError(err)
return tokens, err
}

// Parse from lex into grammar v which must be of the same type as the grammar passed to
// ParseLexer into grammar v which must be of the same type as the grammar passed to
// participle.Build().
//
// This may return a participle.Error.
Expand Down Expand Up @@ -138,16 +138,16 @@ func (p *Parser) ParseLexer(lex lexer.Lexer, v interface{}) error {
}
ctx, err := newParseContext(lexer, p.useLookahead, caseInsensitive)
if err != nil {
return autoError(err)
return err
}
// If the grammar implements Parseable, use it.
if parseable, ok := v.(Parseable); ok {
return autoError(p.rootParseable(ctx, parseable))
return p.rootParseable(ctx, parseable)
}
if stream.IsValid() {
return autoError(p.parseStreaming(ctx, stream))
return p.parseStreaming(ctx, stream)
}
return autoError(p.parseOne(ctx, rv))
return p.parseOne(ctx, rv)
}

// Parse from r into grammar v which must be of the same type as the grammar passed to
Expand All @@ -157,7 +157,7 @@ func (p *Parser) ParseLexer(lex lexer.Lexer, v interface{}) error {
func (p *Parser) Parse(r io.Reader, v interface{}) (err error) {
lex, err := p.lex.Lex(r)
if err != nil {
return autoError(err)
return err
}
return p.ParseLexer(lex, v)
}
Expand Down Expand Up @@ -186,7 +186,7 @@ func (p *Parser) parseOne(ctx *parseContext, rv reflect.Value) error {
if err != nil {
return err
} else if !token.EOF() {
return lexer.Errorf(token.Pos, "unexpected token %q", token)
return UnexpectedTokenError{token}
}
return nil
}
Expand All @@ -204,7 +204,7 @@ func (p *Parser) parseInto(ctx *parseContext, rv reflect.Value) error {
}
if pv == nil {
token, _ := ctx.Peek(0)
return lexer.Errorf(token.Pos, "invalid syntax")
return Errorf(token.Pos, "invalid syntax")
}
return nil
}
Expand All @@ -223,7 +223,7 @@ func (p *Parser) rootParseable(lex lexer.PeekingLexer, parseable Parseable) erro
return err
}
if !peek.EOF() {
return lexer.Errorf(peek.Pos, "unexpected token %q", peek)
return UnexpectedTokenError{peek}
}
return err
}
Expand Down

0 comments on commit 4658f9a

Please sign in to comment.