Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
2.5 KiB
117 lines
2.5 KiB
// 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 |
|
}
|
|
|