diff options
Diffstat (limited to 'v_windows/v/vlib/io/buffered_reader.v')
-rw-r--r-- | v_windows/v/vlib/io/buffered_reader.v | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/v_windows/v/vlib/io/buffered_reader.v b/v_windows/v/vlib/io/buffered_reader.v new file mode 100644 index 0000000..f336e2d --- /dev/null +++ b/v_windows/v/vlib/io/buffered_reader.v @@ -0,0 +1,145 @@ +module io + +// BufferedReader provides a buffered interface for a reader +struct BufferedReader { +mut: + reader Reader + buf []byte + offset int // current offset in the buffer + len int + fails int // how many times fill_buffer has read 0 bytes in a row + mfails int // maximum fails, after which we can assume that the stream has ended +pub mut: + end_of_stream bool // whether we reached the end of the upstream reader +} + +// BufferedReaderConfig are options that can be given to a reader +pub struct BufferedReaderConfig { + reader Reader + cap int = 128 * 1024 // large for fast reading of big(ish) files + retries int = 2 // how many times to retry before assuming the stream ended +} + +// new_buffered_reader creates new BufferedReader +pub fn new_buffered_reader(o BufferedReaderConfig) &BufferedReader { + if o.cap <= 0 { + panic('new_buffered_reader should be called with a positive `cap`') + } + // create + r := &BufferedReader{ + reader: o.reader + buf: []byte{len: o.cap, cap: o.cap} + offset: 0 + mfails: o.retries + } + return r +} + +// read fufills the Reader interface +pub fn (mut r BufferedReader) read(mut buf []byte) ?int { + if r.end_of_stream { + return none + } + // read data out of the buffer if we dont have any + if r.needs_fill() { + if !r.fill_buffer() { + // end of stream + return none + } + } + read := copy(buf, r.buf[r.offset..r.len]) + if read == 0 { + return none + } + r.offset += read + return read +} + +pub fn (mut r BufferedReader) free() { + unsafe { + r.buf.free() + } +} + +// fill_buffer attempts to refill the internal buffer +// and returns whether it got any data +fn (mut r BufferedReader) fill_buffer() bool { + if r.end_of_stream { + // we know we have already reached the end of stream + // so return early + return true + } + r.offset = 0 + r.len = 0 + r.len = r.reader.read(mut r.buf) or { + // end of stream was reached + r.end_of_stream = true + return false + } + if r.len == 0 { + r.fails++ + } else { + r.fails = 0 + } + if r.fails >= r.mfails { + // When reading 0 bytes several times in a row, assume the stream has ended. + // This prevents infinite loops ¯\_(ツ)_/¯ ... + r.end_of_stream = true + return false + } + // we got some data + return true +} + +// needs_fill returns whether the buffer needs refilling +fn (r BufferedReader) needs_fill() bool { + return r.offset >= r.len +} + +// end_of_stream returns whether the end of the stream was reached +pub fn (r BufferedReader) end_of_stream() bool { + return r.end_of_stream +} + +// read_line attempts to read a line from the buffered reader +// it will read until it finds a new line character (\n) or +// the end of stream +pub fn (mut r BufferedReader) read_line() ?string { + if r.end_of_stream { + return none + } + mut line := []byte{} + for { + if r.needs_fill() { + // go fetch some new data + if !r.fill_buffer() { + // We are at the end of the stream + if line.len == 0 { + // we had nothing so return nothing + return none + } + return line.bytestr() + } + } + // try and find a newline character + mut i := r.offset + for ; i < r.len; i++ { + c := r.buf[i] + if c == `\n` { + // great, we hit something + // do some checking for whether we hit \r\n or just \n + if i != 0 && r.buf[i - 1] == `\r` { + x := i - 1 + line << r.buf[r.offset..x] + } else { + line << r.buf[r.offset..i] + } + r.offset = i + 1 + return line.bytestr() + } + } + line << r.buf[r.offset..i] + r.offset = i + } + return none +} |