46ad73c596b0ef96178879337dbb6d781c73f92c
[nit.git] / lib / standard / stream.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
9 # another product.
10
11 # Input and output streams of characters
12 module stream
13
14 intrude import ropes
15 import error
16
17 in "C" `{
18 #include <unistd.h>
19 #include <string.h>
20 #include <signal.h>
21 `}
22
23 # Any kind of error that could be produced by an operation on Streams
24 class IOError
25 super Error
26 end
27
28 # Abstract stream class
29 abstract class IOS
30 # Error produced by the file stream
31 #
32 # var ifs = new IFStream.open("donotmakethisfile.binx")
33 # ifs.read_all
34 # ifs.close
35 # assert ifs.last_error != null
36 var last_error: nullable IOError = null
37
38 # close the stream
39 fun close is abstract
40 end
41
42 # Abstract input streams
43 abstract class IStream
44 super IOS
45 # Read a character. Return its ASCII value, -1 on EOF or timeout
46 fun read_char: Int is abstract
47
48 # Read at most i bytes
49 fun read(i: Int): String
50 do
51 if last_error != null then return ""
52 var s = new FlatBuffer.with_capacity(i)
53 while i > 0 and not eof do
54 var c = read_char
55 if c >= 0 then
56 s.add(c.ascii)
57 i -= 1
58 end
59 end
60 return s.to_s
61 end
62
63 # Read a string until the end of the line.
64 #
65 # The line terminator '\n' and '\r\n', if any, is removed in each line.
66 #
67 # ~~~
68 # var txt = "Hello\n\nWorld\n"
69 # var i = new StringIStream(txt)
70 # assert i.read_line == "Hello"
71 # assert i.read_line == ""
72 # assert i.read_line == "World"
73 # assert i.eof
74 # ~~~
75 #
76 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
77 # the end or file (EOF) is considered to delimit the end of lines.
78 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
79 #
80 # ~~~
81 # var txt2 = "Hello\r\n\n\rWorld"
82 # var i2 = new StringIStream(txt2)
83 # assert i2.read_line == "Hello"
84 # assert i2.read_line == ""
85 # assert i2.read_line == "\rWorld"
86 # assert i2.eof
87 # ~~~
88 #
89 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
90 fun read_line: String
91 do
92 if last_error != null then return ""
93 if eof then return ""
94 var s = new FlatBuffer
95 append_line_to(s)
96 return s.to_s.chomp
97 end
98
99 # Read all the lines until the eof.
100 #
101 # The line terminator '\n' and `\r\n` is removed in each line,
102 #
103 # ~~~
104 # var txt = "Hello\n\nWorld\n"
105 # var i = new StringIStream(txt)
106 # assert i.read_lines == ["Hello", "", "World"]
107 # ~~~
108 #
109 # This method is more efficient that splitting
110 # the result of `read_all`.
111 #
112 # NOTE: SEE `read_line` for details.
113 fun read_lines: Array[String]
114 do
115 var res = new Array[String]
116 while not eof do
117 res.add read_line
118 end
119 return res
120 end
121
122 # Return an iterator that read each line.
123 #
124 # The line terminator '\n' and `\r\n` is removed in each line,
125 # The line are read with `read_line`. See this method for details.
126 #
127 # ~~~
128 # var txt = "Hello\n\nWorld\n"
129 # var i = new StringIStream(txt)
130 # assert i.each_line.to_a == ["Hello", "", "World"]
131 # ~~~
132 #
133 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
134 # Therefore, the stream should no be closed until the end of the stream.
135 #
136 # ~~~
137 # i = new StringIStream(txt)
138 # var el = i.each_line
139 #
140 # assert el.item == "Hello"
141 # el.next
142 # assert el.item == ""
143 # el.next
144 #
145 # i.close
146 #
147 # assert not el.is_ok
148 # # closed before "world" is read
149 # ~~~
150 fun each_line: LineIterator do return new LineIterator(self)
151
152 # Read all the stream until the eof.
153 #
154 # The content of the file is returned verbatim.
155 #
156 # ~~~
157 # var txt = "Hello\n\nWorld\n"
158 # var i = new StringIStream(txt)
159 # assert i.read_all == txt
160 # ~~~
161 fun read_all: String
162 do
163 if last_error != null then return ""
164 var s = new FlatBuffer
165 while not eof do
166 var c = read_char
167 if c >= 0 then s.add(c.ascii)
168 end
169 return s.to_s
170 end
171
172 # Read a string until the end of the line and append it to `s`.
173 #
174 # Unlike `read_line` and other related methods,
175 # the line terminator '\n', if any, is preserved in each line.
176 # Use the method `Text::chomp` to safely remove it.
177 #
178 # ~~~
179 # var txt = "Hello\n\nWorld\n"
180 # var i = new StringIStream(txt)
181 # var b = new FlatBuffer
182 # i.append_line_to(b)
183 # assert b == "Hello\n"
184 # i.append_line_to(b)
185 # assert b == "Hello\n\n"
186 # i.append_line_to(b)
187 # assert b == txt
188 # assert i.eof
189 # ~~~
190 #
191 # If `\n` is not present at the end of the result, it means that
192 # a non-eol terminated last line was returned.
193 #
194 # ~~~
195 # var i2 = new StringIStream("hello")
196 # assert not i2.eof
197 # var b2 = new FlatBuffer
198 # i2.append_line_to(b2)
199 # assert b2 == "hello"
200 # assert i2.eof
201 # ~~~
202 #
203 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
204 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
205 fun append_line_to(s: Buffer)
206 do
207 if last_error != null then return
208 loop
209 var x = read_char
210 if x == -1 then
211 if eof then return
212 else
213 var c = x.ascii
214 s.chars.push(c)
215 if c == '\n' then return
216 end
217 end
218 end
219
220 # Is there something to read.
221 # This function returns 'false' if there is something to read.
222 fun eof: Bool is abstract
223 end
224
225 # Iterator returned by `IStream::each_line`.
226 # See the aforementioned method for details.
227 class LineIterator
228 super Iterator[String]
229
230 # The original stream
231 var stream: IStream
232
233 redef fun is_ok
234 do
235 var res = not stream.eof
236 if not res and close_on_finish then stream.close
237 return res
238 end
239
240 redef fun item
241 do
242 var line = self.line
243 if line == null then
244 line = stream.read_line
245 end
246 self.line = line
247 return line
248 end
249
250 # The last line read (cache)
251 private var line: nullable String = null
252
253 redef fun next
254 do
255 # force the read
256 if line == null then item
257 # drop the line
258 line = null
259 end
260
261 # Close the stream when the stream is at the EOF.
262 #
263 # Default is false.
264 var close_on_finish = false is writable
265
266 redef fun finish
267 do
268 if close_on_finish then stream.close
269 end
270 end
271
272 # IStream capable of declaring if readable without blocking
273 abstract class PollableIStream
274 super IStream
275
276 # Is there something to read? (without blocking)
277 fun poll_in: Bool is abstract
278
279 end
280
281 # Abstract output stream
282 abstract class OStream
283 super IOS
284 # write a string
285 fun write(s: Text) is abstract
286
287 # Can the stream be used to write
288 fun is_writable: Bool is abstract
289 end
290
291 # Things that can be efficienlty writen to a OStream
292 #
293 # The point of this interface it to allow is instance to be efficenty
294 # writen into a OStream without having to allocate a big String object
295 #
296 # ready-to-save documents usually provide this interface.
297 interface Streamable
298 # Write itself to a `stream`
299 # The specific logic it let to the concrete subclasses
300 fun write_to(stream: OStream) is abstract
301
302 # Like `write_to` but return a new String (may be quite large)
303 #
304 # This funtionnality is anectodical, since the point
305 # of streamable object to to be efficienlty written to a
306 # stream without having to allocate and concatenate strings
307 fun write_to_string: String
308 do
309 var stream = new StringOStream
310 write_to(stream)
311 return stream.to_s
312 end
313 end
314
315 redef class Text
316 super Streamable
317 redef fun write_to(stream) do stream.write(self)
318 end
319
320 # Input streams with a buffer
321 abstract class BufferedIStream
322 super IStream
323 redef fun read_char
324 do
325 if last_error != null then return -1
326 if eof then
327 last_error = new IOError("Stream has reached eof")
328 return -1
329 end
330 var c = _buffer.chars[_buffer_pos]
331 _buffer_pos += 1
332 return c.ascii
333 end
334
335 redef fun read(i)
336 do
337 if last_error != null then return ""
338 if _buffer.length == _buffer_pos then
339 if not eof then
340 return read(i)
341 end
342 return ""
343 end
344 if _buffer_pos + i >= _buffer.length then
345 var from = _buffer_pos
346 _buffer_pos = _buffer.length
347 return _buffer.substring_from(from).to_s
348 end
349 _buffer_pos += i
350 return _buffer.substring(_buffer_pos - i, i).to_s
351 end
352
353 redef fun read_all
354 do
355 if last_error != null then return ""
356 var s = new FlatBuffer
357 while not eof do
358 var j = _buffer_pos
359 var k = _buffer.length
360 while j < k do
361 s.add(_buffer[j])
362 j += 1
363 end
364 _buffer_pos = j
365 fill_buffer
366 end
367 return s.to_s
368 end
369
370 redef fun append_line_to(s)
371 do
372 loop
373 # First phase: look for a '\n'
374 var i = _buffer_pos
375 while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1
376
377 var eol
378 if i < _buffer.length then
379 assert _buffer.chars[i] == '\n'
380 i += 1
381 eol = true
382 else
383 eol = false
384 end
385
386 # if there is something to append
387 if i > _buffer_pos then
388 # Enlarge the string (if needed)
389 s.enlarge(s.length + i - _buffer_pos)
390
391 # Copy from the buffer to the string
392 var j = _buffer_pos
393 while j < i do
394 s.add(_buffer.chars[j])
395 j += 1
396 end
397 _buffer_pos = i
398 else
399 assert end_reached
400 return
401 end
402
403 if eol then
404 # so \n is found
405 return
406 else
407 # so \n is not found
408 if end_reached then return
409 fill_buffer
410 end
411 end
412 end
413
414 redef fun eof
415 do
416 if _buffer_pos < _buffer.length then return false
417 if end_reached then return true
418 fill_buffer
419 return _buffer_pos >= _buffer.length and end_reached
420 end
421
422 # The buffer
423 private var buffer: nullable FlatBuffer = null
424
425 # The current position in the buffer
426 private var buffer_pos: Int = 0
427
428 # Fill the buffer
429 protected fun fill_buffer is abstract
430
431 # Is the last fill_buffer reach the end
432 protected fun end_reached: Bool is abstract
433
434 # Allocate a `_buffer` for a given `capacity`.
435 protected fun prepare_buffer(capacity: Int)
436 do
437 _buffer = new FlatBuffer.with_capacity(capacity)
438 _buffer_pos = 0 # need to read
439 end
440 end
441
442 # An Input/Output Stream
443 abstract class IOStream
444 super IStream
445 super OStream
446 end
447
448 # Stream to a String.
449 #
450 # Mainly used for compatibility with OStream type and tests.
451 class StringOStream
452 super OStream
453
454 private var content = new Array[String]
455 redef fun to_s do return content.to_s
456 redef fun is_writable do return not closed
457 redef fun write(str)
458 do
459 assert not closed
460 content.add(str.to_s)
461 end
462
463 # Is the stream closed?
464 protected var closed = false
465
466 redef fun close do closed = true
467 end
468
469 # Stream from a String.
470 #
471 # Mainly used for compatibility with IStream type and tests.
472 class StringIStream
473 super IStream
474
475 # The string to read from.
476 var source: String
477
478 # The current position in the string.
479 private var cursor: Int = 0
480
481 redef fun read_char do
482 if cursor < source.length then
483 var c = source[cursor].ascii
484
485 cursor += 1
486 return c
487 else
488 return -1
489 end
490 end
491
492 redef fun close do
493 source = ""
494 end
495
496 redef fun eof do return cursor >= source.length
497 end