320e9b6057
ci-via-github-actions
117 lines
2.5 KiB
Go
117 lines
2.5 KiB
Go
// Package buffruneio is a wrapper around bufio to provide buffered runes access with unlimited unreads.
|
|
package buffruneio
|
|
|
|
import (
|
|
"bufio"
|
|
"container/list"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// Rune to indicate end of file.
|
|
const (
|
|
EOF = -(iota + 1)
|
|
)
|
|
|
|
// ErrNoRuneToUnread is returned by UnreadRune() when the read index is already at the beginning of the buffer.
|
|
var ErrNoRuneToUnread = errors.New("no rune to unwind")
|
|
|
|
// Reader implements runes buffering for an io.Reader object.
|
|
type Reader struct {
|
|
buffer *list.List
|
|
current *list.Element
|
|
input *bufio.Reader
|
|
}
|
|
|
|
// NewReader returns a new Reader.
|
|
func NewReader(rd io.Reader) *Reader {
|
|
return &Reader{
|
|
buffer: list.New(),
|
|
input: bufio.NewReader(rd),
|
|
}
|
|
}
|
|
|
|
type runeWithSize struct {
|
|
r rune
|
|
size int
|
|
}
|
|
|
|
func (rd *Reader) feedBuffer() error {
|
|
r, size, err := rd.input.ReadRune()
|
|
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
return err
|
|
}
|
|
r = EOF
|
|
}
|
|
|
|
newRuneWithSize := runeWithSize{r, size}
|
|
|
|
rd.buffer.PushBack(newRuneWithSize)
|
|
if rd.current == nil {
|
|
rd.current = rd.buffer.Back()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadRune reads the next rune from buffer, or from the underlying reader if needed.
|
|
func (rd *Reader) ReadRune() (rune, int, error) {
|
|
if rd.current == rd.buffer.Back() || rd.current == nil {
|
|
err := rd.feedBuffer()
|
|
if err != nil {
|
|
return EOF, 0, err
|
|
}
|
|
}
|
|
|
|
runeWithSize := rd.current.Value.(runeWithSize)
|
|
rd.current = rd.current.Next()
|
|
return runeWithSize.r, runeWithSize.size, nil
|
|
}
|
|
|
|
// UnreadRune pushes back the previously read rune in the buffer, extending it if needed.
|
|
func (rd *Reader) UnreadRune() error {
|
|
if rd.current == rd.buffer.Front() {
|
|
return ErrNoRuneToUnread
|
|
}
|
|
if rd.current == nil {
|
|
rd.current = rd.buffer.Back()
|
|
} else {
|
|
rd.current = rd.current.Prev()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Forget removes runes stored before the current stream position index.
|
|
func (rd *Reader) Forget() {
|
|
if rd.current == nil {
|
|
rd.current = rd.buffer.Back()
|
|
}
|
|
for ; rd.current != rd.buffer.Front(); rd.buffer.Remove(rd.current.Prev()) {
|
|
}
|
|
}
|
|
|
|
// PeekRune returns at most the next n runes, reading from the uderlying source if
|
|
// needed. Does not move the current index. It includes EOF if reached.
|
|
func (rd *Reader) PeekRunes(n int) []rune {
|
|
res := make([]rune, 0, n)
|
|
cursor := rd.current
|
|
for i := 0; i < n; i++ {
|
|
if cursor == nil {
|
|
err := rd.feedBuffer()
|
|
if err != nil {
|
|
return res
|
|
}
|
|
cursor = rd.buffer.Back()
|
|
}
|
|
if cursor != nil {
|
|
r := cursor.Value.(runeWithSize).r
|
|
res = append(res, r)
|
|
if r == EOF {
|
|
return res
|
|
}
|
|
cursor = cursor.Next()
|
|
}
|
|
}
|
|
return res
|
|
}
|