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 }