diff --git a/lexer/reader.go b/lexer/reader.go index 75ea32a..7c0aaa3 100644 --- a/lexer/reader.go +++ b/lexer/reader.go @@ -2,6 +2,7 @@ package lexer import ( "bytes" + "errors" "io" "strings" "unicode/utf8" @@ -18,7 +19,14 @@ const ( // implemented interfaces var ( - _ io.RuneReader = (*Reader)(nil) + _ io.RuneReader = (*Reader)(nil) + _ io.RuneScanner = (*Reader)(nil) +) + +var ( + // ErrInvalidUnreadRune indicates UnreadRune() was calls after an + // action other than a successful ReadRune() + ErrInvalidUnreadRune = errors.New("invalid UnreadRune() call") ) // Reader is a RuneReader aimed at implementing text parsers @@ -28,6 +36,8 @@ type Reader struct { buf []byte off int cursor int + + lastRuneSize int } // String returns what's already Read but not yet emitted or discarded @@ -54,6 +64,9 @@ func (b *Reader) Discard() { // step b.off = b.cursor } + + // and prevent UnreadRune() + b.lastRuneSize = -1 } // ready tells how many bytes are ready to decode @@ -139,6 +152,8 @@ func (b *Reader) ReadRune() (rune, int, error) { for { err := b.needsBytes(count) if err != nil { + b.lastRuneSize = -1 + return 0, 0, err } @@ -155,10 +170,34 @@ func (b *Reader) ReadRune() (rune, int, error) { r, l := utf8.DecodeRune(b.buf[b.cursor:]) // step over b.cursor += l + // and remember for UnreadRune() + b.lastRuneSize = l return r, l, nil } +// UnreadRune moves the cursor where it was before the last call to ReadRune +func (b *Reader) UnreadRune() error { + if b.lastRuneSize > 0 { + b.cursor -= b.lastRuneSize + b.lastRuneSize = -1 + return nil + } + + return ErrInvalidUnreadRune +} + +// PeekRune returns information about the next rune without moving the +// cursor +func (b *Reader) PeekRune() (rune, int, error) { + r, l, err := b.ReadRune() + if err != nil { + return r, l, err + } + err = b.UnreadRune() + return r, l, err +} + // NewReader creates a new runes [Reader] using the given [io.Reader] func NewReader(r io.Reader) *Reader { if r == nil {