package parsekit // On checks if the current input matches the provided Matcher. // // This method is the start of a chain method in which multiple things can // be arranged in one go: // // 1) Checking whether or not there is a match (this is what On does) // // 2) Deciding what to do with the match (Stay(): do nothing, Skip(): only move // the cursor forward, Accept(): move cursor forward and add the match in // the parser string buffer) // // 3) Dedicing where to route to (e.g. using RouteTo() to route to a // StateHandler by name) // // 4) Followup routing after that, when applicable (.e.g using something like // RouteTo(...).ThenTo(...)) // // For every step of this chain, you can end the chain using the // End() method. This will return a boolean value, indicating whether or // not the initial On() method found a match in the input. // End() is not mandatory. It is merely provided as a means to use // a chain as an expression for a switch/case or if statement (since those // require a boolean expression). // // You can omit "what to do with the match" and go straight into a routing // method, e.g. // // On(...).RouteTo(...) // // This is functionally the same as using // // On(...).Stay().RouteTo(...). // // Here's a complete example chain: // // p.On(something).Accept().RouteTo(stateB).ThenTo(stateC).End() func (p *P) On(matcher Matcher) *matchAction { m := &MatchDialog{p: p} if matcher == nil { panic("internal parser error: matcher argument for On() is nil") } ok := matcher(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{ routeAction: routeAction{chainAction{p, ok}}, input: m.input, output: m.output, inputPos: p.inputPos + m.inputOffset, } } // chainAction is used for building method chains for the On() method. // Every element of the method chain embeds this struct. type chainAction struct { p *P ok bool } // End ends the method chain and returns a boolean indicating whether // or not a match was found in the input. func (a *chainAction) End() bool { return a.ok } // matchAction is a struct that is used for building On()-method chains. // // It embeds the routeAction struct, to make it possible to go right into // a route action, which is basically a simple way of aliasing a chain // like p.On(...).Stay().RouteTo(...) into p.On(...).RouteTo(...). type matchAction struct { routeAction 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 string buffer. // When no match was found, then no action is taken. // It returns a routeAction struct, which provides methods that can be used // to tell the parser what state to go to next. func (a *matchAction) Accept() *routeAction { if a.ok { a.p.buffer.writeString(string(a.output)) a.advanceCursor() } return &routeAction{chainAction: chainAction{a.p, a.ok}} } // Skip tells the parser to move the cursor past a match that was found, // without storing the actual match in the 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() *routeAction { if a.ok { a.advanceCursor() } return &routeAction{chainAction: chainAction{a.p, a.ok}} } // advanceCursor advances the rune cursor one position in the input data. // While doing so, it keeps tracks of newlines, so we can report on // row + 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' } } // 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() *routeAction { return &routeAction{chainAction: chainAction{a.p, a.ok}} } // routeAction is a struct that is used for building On() method chains. type routeAction struct { chainAction } // RouteRepeat indicates that on the next parsing cycle, // the current StateHandler must be reinvoked. func (a *routeAction) RouteRepeat() *chainAction { if a.ok { return a.p.RouteRepeat() } return &chainAction{nil, false} } // RouteTo tells the parser what StateHandler function to invoke // in the next parsing cycle. func (a *routeAction) RouteTo(state StateHandler) *routeFollowupAction { if a.ok { return a.p.RouteTo(state) } return &routeFollowupAction{chainAction: chainAction{nil, false}} } // RouteReturn tells the parser that on the next cycle the next scheduled // route must be invoked. func (a *routeAction) RouteReturn() *chainAction { if a.ok { return a.p.RouteReturn() } return &chainAction{nil, false} } // routeFollowupAction chains parsing routes. // It allows for routing code like p.RouteTo(handlerA).ThenTo(handlerB). type routeFollowupAction struct { chainAction } // ThenTo schedules a StateHandler that must be invoked after the RouteTo // StateHandler has been completed. // For example: p.RouteTo(handlerA).ThenTo(handlerB) func (a *routeFollowupAction) ThenTo(state StateHandler) *chainAction { if a.ok { a.p.pushRoute(state) } return &chainAction{nil, a.ok} } // ThenReturnHere schedules the current StateHandler to be invoked after // the RouteTo StateHandler has been completed. // For example: p.RouteTo(handlerA).ThenReturnHere() func (a *routeFollowupAction) ThenReturnHere() *chainAction { if a.ok { a.p.pushRoute(a.p.state) } return &chainAction{nil, a.ok} }