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 }