| 1 | module io |
| 2 | |
| 3 | // BufferedReader provides a buffered interface for a reader. |
| 4 | pub struct BufferedReader { |
| 5 | mut: |
| 6 | reader Reader |
| 7 | buf []u8 |
| 8 | offset int // current offset in the buffer |
| 9 | len int |
| 10 | fails int // how many times fill_buffer has read 0 bytes in a row |
| 11 | mfails int // maximum fails, after which we can assume that the stream has ended |
| 12 | pub mut: |
| 13 | end_of_stream bool // whether we reached the end of the upstream reader |
| 14 | total_read int // total number of bytes read |
| 15 | } |
| 16 | |
| 17 | // BufferedReaderConfig are options that can be given to a buffered reader. |
| 18 | pub struct BufferedReaderConfig { |
| 19 | pub: |
| 20 | reader Reader |
| 21 | cap int = 128 * 1024 // large for fast reading of big(ish) files |
| 22 | retries int = 2 // how many times to retry before assuming the stream ended |
| 23 | } |
| 24 | |
| 25 | // BufferedReadLineConfig are options that can be given to the read_line() function. |
| 26 | @[params] |
| 27 | pub struct BufferedReadLineConfig { |
| 28 | pub: |
| 29 | delim u8 = `\n` // line delimiter; default `\n` mode strips both `\n` and `\r\n` |
| 30 | } |
| 31 | |
| 32 | // new_buffered_reader creates a new BufferedReader. |
| 33 | pub fn new_buffered_reader(o BufferedReaderConfig) &BufferedReader { |
| 34 | if o.cap <= 0 { |
| 35 | panic('new_buffered_reader should be called with a positive `cap`') |
| 36 | } |
| 37 | // create |
| 38 | r := &BufferedReader{ |
| 39 | reader: o.reader |
| 40 | buf: []u8{len: o.cap, cap: o.cap} |
| 41 | offset: 0 |
| 42 | mfails: o.retries |
| 43 | } |
| 44 | return r |
| 45 | } |
| 46 | |
| 47 | // read fufills the Reader interface. |
| 48 | pub fn (mut r BufferedReader) read(mut buf []u8) !int { |
| 49 | if buf.len == 0 { |
| 50 | return 0 |
| 51 | } |
| 52 | if r.end_of_stream { |
| 53 | return Eof{} |
| 54 | } |
| 55 | // read data out of the buffer if we dont have any |
| 56 | if r.needs_fill() { |
| 57 | if !r.fill_buffer() { |
| 58 | // end of stream |
| 59 | return Eof{} |
| 60 | } |
| 61 | } |
| 62 | read := copy(mut buf, r.buf[r.offset..r.len]) |
| 63 | if read == 0 { |
| 64 | return NotExpected{ |
| 65 | cause: 'invalid copy of buffer' |
| 66 | code: -1 |
| 67 | } |
| 68 | } |
| 69 | r.offset += read |
| 70 | r.total_read += read |
| 71 | return read |
| 72 | } |
| 73 | |
| 74 | // free deallocates the memory for a buffered reader's internal buffer. |
| 75 | pub fn (mut r BufferedReader) free() { |
| 76 | unsafe { |
| 77 | r.buf.free() |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | // fill_buffer attempts to refill the internal buffer |
| 82 | // and returns whether it got any data. |
| 83 | fn (mut r BufferedReader) fill_buffer() bool { |
| 84 | if r.end_of_stream { |
| 85 | // we know we have already reached the end of stream |
| 86 | // so return early |
| 87 | return false |
| 88 | } |
| 89 | for { |
| 90 | r.offset = 0 |
| 91 | r.len = 0 |
| 92 | r.len = r.reader.read(mut r.buf) or { |
| 93 | // end of stream was reached |
| 94 | r.end_of_stream = true |
| 95 | return false |
| 96 | } |
| 97 | if r.len > 0 { |
| 98 | r.fails = 0 |
| 99 | return true |
| 100 | } |
| 101 | r.fails++ |
| 102 | if r.fails >= r.mfails { |
| 103 | // When reading 0 bytes several times in a row, assume the stream has ended. |
| 104 | // This prevents infinite loops ¯\_(ツ)_/¯ ... |
| 105 | r.end_of_stream = true |
| 106 | return false |
| 107 | } |
| 108 | } |
| 109 | return false |
| 110 | } |
| 111 | |
| 112 | // needs_fill returns whether the buffer needs refilling. |
| 113 | fn (r BufferedReader) needs_fill() bool { |
| 114 | return r.offset >= r.len |
| 115 | } |
| 116 | |
| 117 | // end_of_stream returns whether the end of the stream was reached. |
| 118 | pub fn (r BufferedReader) end_of_stream() bool { |
| 119 | return r.end_of_stream |
| 120 | } |
| 121 | |
| 122 | // read_line attempts to read a line from the buffered reader. |
| 123 | // It reads until it finds the specified delimiter or the end of stream. |
| 124 | // The returned string does not include the delimiter. |
| 125 | // With the default delimiter `\n`, both `\n` and `\r\n` line endings are |
| 126 | // accepted, and neither terminator byte is included in the returned string. |
| 127 | pub fn (mut r BufferedReader) read_line(config BufferedReadLineConfig) !string { |
| 128 | if r.end_of_stream { |
| 129 | return Eof{} |
| 130 | } |
| 131 | mut line := []u8{} |
| 132 | for { |
| 133 | if r.needs_fill() { |
| 134 | // go fetch some new data |
| 135 | if !r.fill_buffer() { |
| 136 | // We are at the end of the stream |
| 137 | if line.len == 0 { |
| 138 | // we had nothing so return nothing |
| 139 | return Eof{} |
| 140 | } |
| 141 | return line.bytestr() |
| 142 | } |
| 143 | } |
| 144 | // try and find a newline character |
| 145 | mut i := r.offset |
| 146 | for ; i < r.len; i++ { |
| 147 | r.total_read++ |
| 148 | c := r.buf[i] |
| 149 | if c == config.delim { |
| 150 | // great, we hit something |
| 151 | mut end := i |
| 152 | if config.delim == `\n` { |
| 153 | if i > r.offset && r.buf[i - 1] == `\r` { |
| 154 | end-- |
| 155 | } else if i == r.offset && line.len > 0 && line[line.len - 1] == `\r` { |
| 156 | line.delete_last() |
| 157 | } |
| 158 | } |
| 159 | line << r.buf[r.offset..end] |
| 160 | r.offset = i + 1 |
| 161 | return line.bytestr() |
| 162 | } |
| 163 | } |
| 164 | line << r.buf[r.offset..i] |
| 165 | r.offset = i |
| 166 | } |
| 167 | return Eof{} |
| 168 | } |
| 169 | |