189 lines
5.7 KiB
Go
189 lines
5.7 KiB
Go
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}
|
|
}
|