128 lines
3.8 KiB
Go
128 lines
3.8 KiB
Go
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) *ParseAPIOnAction {
|
|
p.panicWhenStoppedOrInError()
|
|
p.checkForLoops()
|
|
|
|
// Perform the matching operation.
|
|
m := &TokenAPI{p: p}
|
|
if tokenHandler == nil {
|
|
panic("ParseHandler bug: 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 &ParseAPIOnAction{
|
|
p: p,
|
|
ok: ok,
|
|
input: m.input,
|
|
output: m.output,
|
|
inputPos: p.inputPos + m.inputOffset,
|
|
}
|
|
}
|
|
|
|
// ParseAPIOnAction is a struct that is used for building the On()-method chain.
|
|
// The On() method will return an initialized struct of this type.
|
|
type ParseAPIOnAction 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 *ParseAPIOnAction) 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 *ParseAPIOnAction) 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 *ParseAPIOnAction) 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 *ParseAPIOnAction) advanceCursor() {
|
|
if a.p.inputPos == a.inputPos {
|
|
return
|
|
}
|
|
a.p.loopCheck = map[string]bool{}
|
|
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'
|
|
}
|
|
}
|