120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
package parsekit
|
|
|
|
// Expects is used to let a state function describe what input it is expecting.
|
|
// This expectation is used in error messages to make them more descriptive.
|
|
//
|
|
// Also, when defining an expectation inside a StateHandler, you do not need
|
|
// to handle unexpected input yourself. When the end of the function is
|
|
// reached without setting the next state, an automatic error will be
|
|
// emitted. This error differentiates between issues:
|
|
// * there is valid data on input, but it was not accepted by the function
|
|
// * there is an invalid UTF8 character on input
|
|
// * the end of the file was reached.
|
|
func (p *P) Expects(description string) {
|
|
p.expecting = description
|
|
}
|
|
|
|
// On checks if the current input matches the provided Matcher.
|
|
// It returns a MatchAction struct, which provides methods that
|
|
// can be used to tell the parser what to do with a match.
|
|
//
|
|
// The intended way to use this, is by chaining some methods,
|
|
// for example: p.On(...).Accept()
|
|
// The chained methods will as a whole return a boolean value,
|
|
// indicating whether or not a match was found and processed.
|
|
func (p *P) On(m Matcher) *MatchAction {
|
|
runes, widths, ok := p.match(m)
|
|
p.LastMatch = string(runes)
|
|
return &MatchAction{
|
|
p: p,
|
|
runes: runes,
|
|
widths: widths,
|
|
ok: ok,
|
|
}
|
|
}
|
|
|
|
// Match checks if the provided Matcher matches the current input.
|
|
// Returns a slice of matching runes, a slice of their respective
|
|
// byte widths and a boolean.
|
|
// The boolean will be false and the slices will be empty in case
|
|
// the input did not match.
|
|
func (p *P) match(matcher Matcher) ([]rune, []int, bool) {
|
|
m := &MatchDialog{p: p}
|
|
ok := matcher.Match(m)
|
|
return m.runes, m.widths, ok
|
|
}
|
|
|
|
type MatchAction struct {
|
|
p *P
|
|
runes []rune
|
|
widths []int
|
|
ok bool
|
|
}
|
|
|
|
// 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.
|
|
// 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 {
|
|
for i, r := range a.runes {
|
|
a.p.buffer.writeRune(r)
|
|
a.p.advanceCursor(r, a.widths[i])
|
|
}
|
|
}
|
|
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 {
|
|
for i, r := range a.runes {
|
|
type C struct {
|
|
Rune MatchRune
|
|
}
|
|
|
|
a.p.advanceCursor(r, a.widths[i])
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
// RouteTo is a shortcut for p.On(...).Stay() + p.RouteTo(...).
|
|
func (a *MatchAction) RouteTo(state StateHandler) bool {
|
|
if a.ok {
|
|
a.p.RouteTo(state)
|
|
}
|
|
return a.ok
|
|
}
|
|
|
|
// RouteReturn is a shortcut for p.On(...).Stay() + p.RouteReturn().
|
|
func (a *MatchAction) RouteReturn() bool {
|
|
if a.ok {
|
|
a.p.RouteReturn()
|
|
}
|
|
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 (p *P) advanceCursor(r rune, w int) {
|
|
p.pos += w
|
|
if p.newline {
|
|
p.cursorColumn = 0
|
|
p.cursorRow++
|
|
} else {
|
|
p.cursorColumn++
|
|
}
|
|
p.newline = r == '\n'
|
|
}
|