package parsekit // On checks if the input at the current cursor position matches the provided // TokenHandler. On must be chained with another method that tells the parser // what action to perform when a match was found: // // 1) On(...).Skip() - Only move cursor forward, ignore the matched runes. // // 2) On(...).Accept() - Move cursor forward, add runes to parsers's string buffer. // // 3) On(...).Stay() - Do nothing, the cursor stays at the same position. // // So an example chain could look like this: // // p.On(parsekit.A.Whitespace).Skip() // // The chain as a whole returns a boolean that indicates whether or not at match // was found. When no match was found, false is returned and Skip() and Accept() // will have no effect. Because of this, typical use of an On() chain is as // expression for a conditional expression (if, switch/case, for). E.g.: // // // Skip multiple exclamation marks. // for p.On(parsekit.A.Excl).Skip() { } // // // Fork a route based on the input. // switch { // case p.On(parsekit.A.Excl).Stay() // p.RouteTo(stateHandlerA) // case p.On(parsekit.A.Colon).Stay(): // p.RouteTo(stateHandlerB) // default: // p.RouteTo(stateHandlerC) // } // // // When there's a "hi" on input, emit a parser item for it. // if p.On(parsekit.C.Str("hi")).Accept() { // p.Emit(SomeItemType, p.BufLiteral()) // } func (p *ParseAPI) On(tokenHandler TokenHandler) *MatchAction { p.panicWhenStoppedOrInError() // Perform the matching operation. m := &TokenAPI{p: p} if tokenHandler == nil { panic("internal parser error: tokenHandler argument for On() is nil") } ok := tokenHandler(m) // Keep track of the last match, to allow parser implementations // to access it in an easy way. Typical use would be something like: // // if p.On(somethingBad).End() { // p.Errorf("This was bad: %s", p.LastMatch) // } p.LastMatch = string(m.input) return &MatchAction{ p: p, ok: ok, input: m.input, output: m.output, inputPos: p.inputPos + m.inputOffset, } } // MatchAction is a struct that is used for building the On()-method chain. // The On() method will return an initialized struct of this type. type MatchAction struct { p *ParseAPI ok bool input []rune output []rune inputPos int } // Accept tells the parser to move the cursor past a match that was found, // and to store the input that matched in the parser's string buffer. // When no match was found, then no action is taken. // // Returns true in case a match was found. // When no match was found, then no action is taken and false is returned. func (a *MatchAction) Accept() bool { if a.ok { a.p.buffer.writeString(string(a.output)) a.advanceCursor() } return a.ok } // Skip tells the parser to move the cursor past a match that was found, // without storing the actual match in the parser's string buffer. // // Returns true in case a match was found. // When no match was found, then no action is taken and false is returned. func (a *MatchAction) Skip() bool { if a.ok { a.advanceCursor() } return a.ok } // Stay tells the parser to not move the cursor after finding a match. // Returns true in case a match was found, false otherwise. func (a *MatchAction) Stay() bool { return a.ok } // advanceCursor advances the input position in the input data. // While doing so, it keeps tracks of newlines that are encountered, so we // can report on line + column positions on error. func (a *MatchAction) advanceCursor() { a.p.inputPos = a.inputPos for _, r := range a.input { if a.p.newline { a.p.cursorLine++ a.p.cursorColumn = 1 } else { a.p.cursorColumn++ } a.p.newline = r == '\n' } }