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.
108 lines
2.4 KiB
108 lines
2.4 KiB
6 years ago
|
package jstream
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
"sync/atomic"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
chunk = 4095 // ~4k
|
||
|
maxUint = ^uint(0)
|
||
|
maxInt = int64(maxUint >> 1)
|
||
|
)
|
||
|
|
||
|
type scanner struct {
|
||
|
pos int64 // position in reader
|
||
|
ipos int64 // internal buffer position
|
||
|
ifill int64 // internal buffer fill
|
||
|
end int64
|
||
|
buf [chunk + 1]byte // internal buffer (with a lookback size of 1)
|
||
|
nbuf [chunk]byte // next internal buffer
|
||
|
fillReq chan struct{}
|
||
|
fillReady chan int64
|
||
|
}
|
||
|
|
||
|
func newScanner(r io.Reader) *scanner {
|
||
|
sr := &scanner{
|
||
|
end: maxInt,
|
||
|
fillReq: make(chan struct{}),
|
||
|
fillReady: make(chan int64),
|
||
|
}
|
||
|
|
||
|
go func() {
|
||
|
var rpos int64 // total bytes read into buffer
|
||
|
|
||
|
for _ = range sr.fillReq {
|
||
|
scan:
|
||
|
n, err := r.Read(sr.nbuf[:])
|
||
|
|
||
|
if n == 0 {
|
||
|
switch err {
|
||
|
case io.EOF: // reader is exhausted
|
||
|
atomic.StoreInt64(&sr.end, rpos)
|
||
|
close(sr.fillReady)
|
||
|
return
|
||
|
case nil: // no data and no error, retry fill
|
||
|
goto scan
|
||
|
default:
|
||
|
panic(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rpos += int64(n)
|
||
|
sr.fillReady <- int64(n)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
sr.fillReq <- struct{}{} // initial fill
|
||
|
|
||
|
return sr
|
||
|
}
|
||
|
|
||
|
// remaining returns the number of unread bytes
|
||
|
// if EOF for the underlying reader has not yet been found,
|
||
|
// maximum possible integer value will be returned
|
||
|
func (s *scanner) remaining() int64 {
|
||
|
if atomic.LoadInt64(&s.end) == maxInt {
|
||
|
return maxInt
|
||
|
}
|
||
|
return atomic.LoadInt64(&s.end) - s.pos
|
||
|
}
|
||
|
|
||
|
// read byte at current position (without advancing)
|
||
|
func (s *scanner) cur() byte { return s.buf[s.ipos] }
|
||
|
|
||
|
// read next byte
|
||
|
func (s *scanner) next() byte {
|
||
|
if s.pos >= atomic.LoadInt64(&s.end) {
|
||
|
return byte(0)
|
||
|
}
|
||
|
s.ipos++
|
||
|
|
||
|
if s.ipos > s.ifill { // internal buffer is exhausted
|
||
|
s.ifill = <-s.fillReady
|
||
|
s.buf[0] = s.buf[len(s.buf)-1] // copy current last item to guarantee lookback
|
||
|
copy(s.buf[1:], s.nbuf[:]) // copy contents of pre-filled next buffer
|
||
|
s.ipos = 1 // move to beginning of internal buffer
|
||
|
|
||
|
// request next fill to be prepared
|
||
|
if s.end == maxInt {
|
||
|
s.fillReq <- struct{}{}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
s.pos++
|
||
|
return s.buf[s.ipos]
|
||
|
}
|
||
|
|
||
|
// back undoes a previous call to next(), moving backward one byte in the internal buffer.
|
||
|
// as we only guarantee a lookback buffer size of one, any subsequent calls to back()
|
||
|
// before calling next() may panic
|
||
|
func (s *scanner) back() {
|
||
|
if s.ipos <= 0 {
|
||
|
panic("back buffer exhausted")
|
||
|
}
|
||
|
s.ipos--
|
||
|
s.pos--
|
||
|
}
|