Repository navigation
Expand file tree
/
Copy pathbuffered_reader.v
More file actions
242 lines (219 loc) · 5.62 KB
/
Copy pathbuffered_reader.v
File metadata and controls
242 lines (219 loc) · 5.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
module io
// BufferedReader provides a buffered interface for a reader.
pub struct BufferedReader {
mut:
reader Reader
buf []u8
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
total_read int // total number of bytes read
}
// BufferedReaderConfig are options that can be given to a buffered reader.
pub struct BufferedReaderConfig {
pub:
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
}
// BufferedReadLineConfig are options that can be given to the read_line() function.
@[params]
pub struct BufferedReadLineConfig {
pub:
delim u8 = `\n` // line delimiter; default `\n` mode strips both `\n` and `\r\n`
}
// new_buffered_reader creates a 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: []u8{len: o.cap, cap: o.cap}
offset: 0
mfails: o.retries
}
return r
}
fn (mut r BufferedReader) copy_unread(mut buf []u8) !int {
read := copy(mut buf, r.buf[r.offset..r.len])
if read == 0 {
return NotExpected{
cause: 'invalid copy of buffer'
code: -1
}
}
r.offset += read
r.total_read += read
return read
}
// read fufills the Reader interface.
pub fn (mut r BufferedReader) read(mut buf []u8) !int {
if buf.len == 0 {
return 0
}
if r.end_of_stream {
if r.offset < r.len {
return r.copy_unread(mut buf)
}
return Eof{}
}
// read data out of the buffer if we dont have any
if r.needs_fill() {
if !r.fill_buffer() {
// end of stream
return Eof{}
}
}
return r.copy_unread(mut buf)
}
// fill buffer but keep unread data
fn (mut r BufferedReader) fill_buffer_keep_unread() ! {
if r.end_of_stream {
return Eof{}
}
// shift unread bytes to front
r.len = r.len - r.offset
for i := 0; i < r.len; i++ {
r.buf[i] = r.buf[r.offset + i]
}
r.offset = 0
for r.len < r.buf.len {
read := r.reader.read(mut r.buf[r.len..]) or {
r.end_of_stream = true
return Eof{}
}
if read == 0 {
r.fails++
if r.fails >= r.mfails {
r.end_of_stream = true
return Eof{}
}
} else {
r.fails = 0
r.len += read
}
}
}
// peek reads n bytes without advancing the cursor.
// returns Eof if reached the end of the stream.
// returns an error if n < 0.
// returns an array with less than n bytes if n > capacity.
pub fn (mut r BufferedReader) peek(n int) ![]u8 {
if n < 0 {
return error('cannot peek a negative number of bytes')
}
if r.needs_fill() { // has no unread bytes
if r.end_of_stream {
return Eof{}
}
if !r.fill_buffer() {
return Eof{}
}
}
// enough data in buffer
if n <= r.len - r.offset {
return r.buf[r.offset..r.offset + n].clone()
}
r.fill_buffer_keep_unread() or { return r.buf[r.offset..r.len].clone() }
// asking for more bytes than buffer contains after refill
if n > r.len {
return r.buf[r.offset..r.len].clone()
}
return r.buf[r.offset..r.offset + n].clone()
}
// free deallocates the memory for a buffered reader's internal buffer.
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 false
}
for {
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 = 0
return true
}
r.fails++
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
}
}
return false
}
// 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 reads until it finds the specified delimiter or the end of stream.
// The returned string does not include the delimiter.
// With the default delimiter `\n`, both `\n` and `\r\n` line endings are
// accepted, and neither terminator byte is included in the returned string.
pub fn (mut r BufferedReader) read_line(config BufferedReadLineConfig) !string {
if r.end_of_stream && r.offset == r.len {
return Eof{}
}
mut line := []u8{}
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 Eof{}
}
return line.bytestr()
}
}
// try and find a newline character
mut i := r.offset
for ; i < r.len; i++ {
r.total_read++
c := r.buf[i]
if c == config.delim {
// great, we hit something
mut end := i
if config.delim == `\n` {
if i > r.offset && r.buf[i - 1] == `\r` {
end--
} else if i == r.offset && line.len > 0 && line[line.len - 1] == `\r` {
line.delete_last()
}
}
line << r.buf[r.offset..end]
r.offset = i + 1
return line.bytestr()
}
}
line << r.buf[r.offset..i]
r.offset = i
}
return Eof{}
}