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 Matcher: m := &MatchDialog{p: p} if pattern.Match(m) { return m.runes, m.widths, true } else { return m.runes, m.widths, false } 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 } 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) 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. // TODO not needed anymore // func (p *P) AcceptMatching(patterns ...interface{}) bool { // return p.progress(func(r rune) { p.buffer.writeRune(r) }, patterns...) // } // 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 }