69 lines
2.2 KiB
Go
69 lines
2.2 KiB
Go
package parsekit
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// ParseAPI holds the internal state of a parse run and provides an API to
|
|
// ParseHandler methods to communicate with the parser.
|
|
type ParseAPI struct {
|
|
tokenAPI *TokenAPI // the input reader
|
|
loopCheck map[string]bool // used for parser loop detection
|
|
expecting string // a description of what the current state expects to find (see Expects())
|
|
result *Result // TokenHandler result, as received from On(...).Accept()
|
|
err *Error // error during parsing, retrieved by Error(), further ParseAPI calls are ignored
|
|
stopped bool // a boolean set to true by Stop(), further ParseAPI calls are ignored
|
|
}
|
|
|
|
// panicWhenStoppedOrInError will panic when the parser has produced an error
|
|
// or when it has been stopped. It is used from the ParseAPI methods, to
|
|
// prevent further calls to the ParseAPI on these occasions.
|
|
//
|
|
// Basically, this guard ensures proper coding of parsers, making sure
|
|
// that clean routes are followed. You can consider this check a runtime
|
|
// unit test.
|
|
func (p *ParseAPI) panicWhenStoppedOrInError() {
|
|
if !p.isStoppedOrInError() {
|
|
return
|
|
}
|
|
|
|
called, _ := p.getCaller(1)
|
|
parts := strings.Split(called, ".")
|
|
calledShort := parts[len(parts)-1]
|
|
caller, filepos := p.getCaller(2)
|
|
|
|
after := "Error()"
|
|
if p.stopped {
|
|
after = "Stop()"
|
|
}
|
|
|
|
panic(fmt.Sprintf("Illegal call to ParseAPI.%s() from %s at %s: no calls allowed after ParseAPI.%s", calledShort, caller, filepos, after))
|
|
}
|
|
|
|
func (p *ParseAPI) isStoppedOrInError() bool {
|
|
return p.stopped || p.err != nil
|
|
}
|
|
|
|
func (p *ParseAPI) initLoopCheck() {
|
|
p.loopCheck = map[string]bool{}
|
|
}
|
|
|
|
func (p *ParseAPI) checkForLoops() {
|
|
caller, filepos := p.getCaller(2)
|
|
if _, ok := p.loopCheck[filepos]; ok {
|
|
panic(fmt.Sprintf("Loop detected in parser in %s at %s", caller, filepos))
|
|
}
|
|
p.loopCheck[filepos] = true
|
|
}
|
|
|
|
// TODO delete this one
|
|
func (p *ParseAPI) 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
|
|
}
|