v2 / vlib / io / buffered_reader.v
168 lines · 158 sloc · 4.13 KB · ccef9ab6b17ad5e014522971012c92f5f7bddb4e
Raw
1module io
2
3// BufferedReader provides a buffered interface for a reader.
4pub struct BufferedReader {
5mut:
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
12pub 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.
18pub struct BufferedReaderConfig {
19pub:
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]
27pub struct BufferedReadLineConfig {
28pub:
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.
33pub 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.
48pub 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.
75pub 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.
83fn (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.
113fn (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.
118pub 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.
127pub 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