go-toml/parsekit/matching.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
}