136 lines
3.2 KiB
Go
136 lines
3.2 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 StateFn, 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
|
|
}
|
|
|
|
// AtEndOfFile returns true when there is no more data available in the input.
|
|
func (p *P) AtEndOfFile() bool {
|
|
return p.pos >= p.len
|
|
}
|
|
|
|
// AtEndOfLine returns true when the cursor is either at the end of the line
|
|
// or at the end of the file. The cursor is not moved to a new position
|
|
// by this method.
|
|
func (p *P) AtEndOfLine() bool {
|
|
return p.AtEndOfFile() ||
|
|
p.On(C.String("\r\n")).Stay() ||
|
|
p.On(C.Rune('\n')).Stay()
|
|
}
|
|
|
|
// SkipEndOfLine returns true when the cursor is either at the end of the line
|
|
// or at the end of the file. Additionally, when not at the end of the file,
|
|
// the cursor is moved forward to beyond the newline.
|
|
func (p *P) SkipEndOfLine() bool {
|
|
return p.AtEndOfFile() ||
|
|
p.On(C.String("\r\n")).Skip() ||
|
|
p.On(C.Rune('\n')).Skip()
|
|
}
|
|
|
|
// AcceptEndOfLine returns true when the cursor is either at the end of the line
|
|
// or at the end of the file. When not at the end of the file, a normalized
|
|
// newline (only a '\n' character, even with '\r\n' on the input)
|
|
// is added to the string buffer.
|
|
func (p *P) AcceptEndOfLine() bool {
|
|
if p.AtEndOfFile() {
|
|
return true
|
|
}
|
|
if p.SkipEndOfLine() {
|
|
p.buffer.writeRune('\n')
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *P) On(m Matcher) *action {
|
|
runes, widths, ok := p.Match(m)
|
|
return &action{
|
|
p: p,
|
|
runes: runes,
|
|
widths: widths,
|
|
ok: ok,
|
|
}
|
|
}
|
|
|
|
func (p *P) Match(matcher Matcher) ([]rune, []int, bool) {
|
|
return p.match(0, matcher)
|
|
}
|
|
|
|
func (p *P) match(offset int, matcher Matcher) ([]rune, []int, bool) {
|
|
m := &MatchDialog{p: p}
|
|
ok := matcher.Match(m)
|
|
return m.runes, m.widths, ok
|
|
}
|
|
|
|
type action struct {
|
|
p *P
|
|
runes []rune
|
|
widths []int
|
|
ok bool
|
|
}
|
|
|
|
func (a *action) 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
|
|
}
|
|
|
|
func (a *action) 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
|
|
}
|
|
|
|
func (a *action) 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 (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'
|
|
}
|
|
|
|
func (a *action) RouteTo(state StateFn) bool {
|
|
if a.ok {
|
|
a.p.RouteTo(state)
|
|
}
|
|
return a.ok
|
|
}
|
|
|
|
func (a *action) RouteReturn() bool {
|
|
if a.ok {
|
|
a.p.RouteReturn()
|
|
}
|
|
return a.ok
|
|
}
|