github.com/grailbio/base@v0.0.11/morebufio/peekback.go (about)

     1  package morebufio
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/grailbio/base/ioctx"
     7  )
     8  
     9  // PeekBackReader is a Reader augmented with a function to "peek" backwards at the data that
    10  // was already passed. Peeking does not change the current stream position (that is, PeekBack has
    11  // no effect on the next Read).
    12  type PeekBackReader interface {
    13  	ioctx.Reader
    14  	// PeekBack returns a fixed "window" of the data that Read has already returned, ending at
    15  	// the current Read position. It may be smaller at the start until enough data has been read,
    16  	// but after that it's constant size. PeekBack is allowed after Read returns EOF.
    17  	// The returned slice aliases the internal buffer and is invalidated by the next Read.
    18  	PeekBack() []byte
    19  }
    20  
    21  type peekBackReader struct {
    22  	r   ioctx.Reader
    23  	buf []byte
    24  }
    25  
    26  // NewPeekBackReader returns a PeekBackReader. It doesn't have a "forward" buffer so small
    27  // PeekBackReader.Read operations cause small r.Read operations.
    28  func NewPeekBackReader(r ioctx.Reader, peekBackSize int) PeekBackReader {
    29  	return &peekBackReader{r, make([]byte, 0, peekBackSize)}
    30  }
    31  
    32  func (p *peekBackReader) Read(ctx context.Context, dst []byte) (int, error) {
    33  	nRead, err := p.r.Read(ctx, dst)
    34  	dst = dst[:nRead]
    35  	// First, grow the peek buf until cap, since it starts empty.
    36  	if grow := cap(p.buf) - len(p.buf); grow > 0 {
    37  		if len(dst) < grow {
    38  			grow = len(dst)
    39  		}
    40  		p.buf = append(p.buf, dst[:grow]...)
    41  		dst = dst[grow:]
    42  	}
    43  	if len(dst) == 0 {
    44  		return nRead, err
    45  	}
    46  	// Shift data if any part of the peek buf is still valid.
    47  	updateTail := p.buf
    48  	if len(dst) < len(p.buf) {
    49  		n := copy(p.buf, p.buf[len(dst):])
    50  		updateTail = p.buf[n:]
    51  	}
    52  	_ = copy(updateTail, dst[len(dst)-len(updateTail):])
    53  	return nRead, err
    54  }
    55  
    56  func (p *peekBackReader) PeekBack() []byte { return p.buf }