198 lines
4.7 KiB
Go
198 lines
4.7 KiB
Go
package parsekit
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// 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.Upcoming("\r", "\n") ||
|
|
p.Upcoming("\n")
|
|
}
|
|
|
|
// 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.SkipMatching("\r", "\n") ||
|
|
p.SkipMatching("\n")
|
|
}
|
|
|
|
// 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) Match(patterns ...interface{}) ([]rune, []int, bool) {
|
|
return p.match(0, patterns...)
|
|
}
|
|
|
|
func (p *P) match(offset int, patterns ...interface{}) ([]rune, []int, bool) {
|
|
var runes []rune
|
|
var widths []int
|
|
|
|
addRune := func(r rune, w int) {
|
|
offset += w
|
|
runes = append(runes, r)
|
|
widths = append(widths, w)
|
|
}
|
|
|
|
for _, pattern := range patterns {
|
|
r, w := utf8.DecodeRuneInString(p.input[p.pos+offset:])
|
|
if r == utf8.RuneError {
|
|
return runes, widths, false
|
|
}
|
|
switch pattern := pattern.(type) {
|
|
case []interface{}:
|
|
rs, ws, matched := p.match(offset, pattern...)
|
|
for i, r := range rs {
|
|
addRune(r, ws[i])
|
|
}
|
|
if !matched {
|
|
return runes, widths, false
|
|
}
|
|
case string:
|
|
if strings.IndexRune(pattern, r) < 0 {
|
|
return runes, widths, false
|
|
}
|
|
addRune(r, w)
|
|
case rune:
|
|
if pattern != r {
|
|
return runes, widths, false
|
|
}
|
|
addRune(r, w)
|
|
default:
|
|
panic(fmt.Sprintf("Not rune matching implemented for pattern of type %T", pattern))
|
|
}
|
|
}
|
|
return runes, widths, true
|
|
}
|
|
|
|
// Upcoming checks if the upcoming runes satisfy all provided patterns.
|
|
// Returns true if all provided patterns are satisfied.
|
|
// This is basically the same as the Match method, but with only
|
|
// the boolean return parameter for programmer convenciency.
|
|
func (p *P) Upcoming(patterns ...interface{}) bool {
|
|
_, _, ok := p.Match(patterns...)
|
|
return ok
|
|
}
|
|
|
|
// AcceptAny adds the next rune from the input to the string buffer.
|
|
// If no rune could be read (end of file or invalid UTF8 data),
|
|
// then false is returned.
|
|
func (p *P) AcceptAny() bool {
|
|
if r, ok := p.next(); ok {
|
|
p.buffer.writeRune(r)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
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 {
|
|
a.p.advanceCursor(r, a.widths[i])
|
|
}
|
|
}
|
|
return a.ok
|
|
}
|
|
|
|
func (a *action) RouteTo(state StateFn) bool {
|
|
if a.ok {
|
|
a.p.RouteTo(state)
|
|
}
|
|
return a.ok
|
|
}
|
|
|
|
func (a *action) Stay() bool {
|
|
return a.ok
|
|
}
|
|
|
|
func (p *P) On(patterns ...interface{}) *action {
|
|
runes, widths, ok := p.Match(patterns...)
|
|
return &action{
|
|
p: p,
|
|
runes: runes,
|
|
widths: widths,
|
|
ok: ok,
|
|
}
|
|
}
|
|
|
|
// AcceptMatching adds the next runes to the string buffer, but only
|
|
// if the upcoming runes satisfy the provided patterns.
|
|
// When runes were added then true is returned, false otherwise.
|
|
func (p *P) AcceptMatching(patterns ...interface{}) bool {
|
|
return p.progress(func(r rune) { p.buffer.writeRune(r) }, patterns...)
|
|
}
|
|
|
|
// AcceptConsecutive adds consecutive runes from the input to the string
|
|
// buffer, as long as they exist in the pattern.
|
|
// If any runes were added then true is returned, false otherwise.
|
|
func (p *P) AcceptConsecutive(pattern string) bool {
|
|
accepted := false
|
|
for p.AcceptMatching(pattern) {
|
|
accepted = true
|
|
}
|
|
return accepted
|
|
}
|
|
|
|
// SkipMatching skips runes, but only when all provided patterns are satisfied.
|
|
// Returns true when one or more runes were skipped.
|
|
func (p *P) SkipMatching(patterns ...interface{}) bool {
|
|
if runes, widths, ok := p.Match(patterns...); ok {
|
|
for i, r := range runes {
|
|
p.advanceCursor(r, widths[i])
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SkipConsecutive skips consecutive runes from the provided pattern.
|
|
// Returns true when one or more runes were skipped.
|
|
func (p *P) SkipConsecutive(pattern string) bool {
|
|
didSkip := false
|
|
for p.SkipMatching(pattern) {
|
|
didSkip = true
|
|
}
|
|
return didSkip
|
|
}
|