go-toml/parsekit/internals.go

89 lines
2.6 KiB
Go

package parsekit
import (
"unicode/utf8"
)
// next returns the next rune from the input and a boolean indicating if
// reading the input was successful.
// When the end of input is reached, or an invalid UTF8 character is
// read, then false is returned. Both are considered error cases,
// and for that reason these automatically emit an error to the client.
func (p *P) next() (rune, bool) {
r, w, ok := p.peek()
if ok {
p.advanceCursor(r, w)
return r, true
}
if r == utf8.RuneError && w == 0 {
p.EmitError("unexpected end of file")
} else {
p.EmitError("invalid UTF8 character")
}
return r, false
}
// peek returns but does not advance the cursor to the next rune(s) in the input.
// Returns the rune, its width in bytes and a boolean.
// The boolean will be false in case no upcoming rune can be peeked
// (end of data or invalid UTF8 character).
func (p *P) peek() (rune, int, bool) {
peeked, width := utf8.DecodeRuneInString(p.input[p.pos:])
return peeked, width, peeked != utf8.RuneError
}
// peekMulti takes a peek at multiple upcoming runes in the input.
// Returns a slice of runes, a slice containing their respective
// widths in bytes and a boolean.
// The boolean will be false in case less runes can be peeked than
// the requested amount (end of data or invalid UTF8 character).
func (p *P) peekMulti(amount int) ([]rune, []int, bool) {
var runes []rune
var widths []int
offset := 0
for i := 0; i < amount; i++ {
r, w := utf8.DecodeRuneInString(p.input[p.pos+offset:])
switch {
case r == utf8.RuneError:
return runes, widths, false
default:
offset += w
runes = append(runes, r)
widths = append(widths, w)
}
}
return runes, widths, true
}
// progress moves the cursor forward in the input, returning one rune
// for every specified pattern. The cursor will only be moved forward when
// all requested patterns can be satisfied.
// Returns true when all patterns were satisfied and the cursor was
// moved forward, false otherwise.
// A callback function can be provided to specify what to do with
// the runes that are encountered in the input.
func (p *P) progress(callback func(rune), patterns ...string) bool {
if runes, widths, ok := p.Match(patterns...); ok {
for i, r := range runes {
callback(r)
p.advanceCursor(r, widths[i])
}
return true
}
return false
}
// 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'
}