go-parsekit/parser.go

65 lines
2.3 KiB
Go

package parsekit
import (
"fmt"
"runtime"
"strings"
)
// Parser is the top-level struct that holds the configuration for a parser.
// The Parser can be instantiated using the parsekit.NewParser() method.
type Parser struct {
startHandler ParseHandler // the function that handles the very first state
}
// ParseHandler defines the type of function that must be implemented to handle
// a parsing state in a Parser state machine.
//
// A ParseHandler function gets a ParseAPI struct as its input. This struct holds
// all the internal state for the parsing state machine and provides the
// interface that the ParseHandler uses to interact with the parser.
type ParseHandler func(*ParseAPI)
// NewParser instantiates a new Parser.
//
// The Parser is a state machine-style recursive descent parser, in which
// ParseHandler functions are used to move the state machine forward during
// parsing. This style of parser is typically used for parsing programming
// languages and structured data formats (like json, xml, toml, etc.)
//
// To parse input data, use the method Parser.Execute().
func NewParser(startHandler ParseHandler) *Parser {
if startHandler == nil {
_, filepos := getCaller(1)
panic(fmt.Sprintf("parsekit.NewParser(): NewParser() called with nil input at %s", filepos))
}
return &Parser{startHandler: startHandler}
}
// Execute starts the parser for the provided input.
// When an error occurs during parsing, then this error is returned. Nil otherwise.
func (p *Parser) Execute(input string) *Error {
api := &ParseAPI{
tokenAPI: NewTokenAPI(strings.NewReader(input)),
loopCheck: map[string]bool{},
}
if api.Handle(p.startHandler) {
// Handle indicated that parsing could still continue, meaning that there
// was no error and that the parsing has not actively been Stop()-ed.
// However, at this point, the parsing really should have stopped.
// We'll see what happens when we tell the parser that EOF was expected.
// This might work if we're indeed at EOF. Otherwise, an error will be
// generated.
api.ExpectEndOfFile()
}
return api.err
}
func getCaller(depth int) (string, string) {
// No error handling, because we call this method ourselves with safe depth values.
pc, file, line, _ := runtime.Caller(depth + 1)
filepos := fmt.Sprintf("%s:%d", file, line)
caller := runtime.FuncForPC(pc)
return caller.Name(), filepos
}