146 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			V
		
	
	
| 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
 | |
| }
 |