go-parsekit/statehandler_on.go

117 lines
3.5 KiB
Go

package parsekit
// On checks if the input at the current cursor position matches the provided Matcher.
// On must be chained with another method, which 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 matched runes to the 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, which 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 it.
// if p.On(parsekit.C.Str("hi")).Accept() {
// p.Emit(SomeItemType, p.BufLiteral())
// }
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{
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.
type matchAction struct {
p *P
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 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() 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 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 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'
}
}