tests: update sav/nitserial_args1.res because change in RTA
[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 # Any kind of stream to read/write/both to or from a source
29 abstract class Stream
30 # Error produced by the file stream
31 #
32 # var ifs = new FileReader.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 # A `Stream` that can be read from
43 abstract class Reader
44 super Stream
45 # Reads a character. Returns `null` on EOF or timeout
46 fun read_char: nullable Char is abstract
47
48 # Reads a byte. Returns `null` on EOF or timeout
49 fun read_byte: nullable Int is abstract
50
51 # Read at most i bytes
52 fun read(i: Int): String
53 do
54 if last_error != null then return ""
55 var s = new FlatBuffer.with_capacity(i)
56 while i > 0 and not eof do
57 var c = read_char
58 if c != null then
59 s.add(c)
60 i -= 1
61 end
62 end
63 return s.to_s
64 end
65
66 # Read a string until the end of the line.
67 #
68 # The line terminator '\n' and '\r\n', if any, is removed in each line.
69 #
70 # ~~~
71 # var txt = "Hello\n\nWorld\n"
72 # var i = new StringReader(txt)
73 # assert i.read_line == "Hello"
74 # assert i.read_line == ""
75 # assert i.read_line == "World"
76 # assert i.eof
77 # ~~~
78 #
79 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
80 # the end or file (EOF) is considered to delimit the end of lines.
81 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
82 #
83 # ~~~
84 # var txt2 = "Hello\r\n\n\rWorld"
85 # var i2 = new StringReader(txt2)
86 # assert i2.read_line == "Hello"
87 # assert i2.read_line == ""
88 # assert i2.read_line == "\rWorld"
89 # assert i2.eof
90 # ~~~
91 #
92 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
93 fun read_line: String
94 do
95 if last_error != null then return ""
96 if eof then return ""
97 var s = new FlatBuffer
98 append_line_to(s)
99 return s.to_s.chomp
100 end
101
102 # Read all the lines until the eof.
103 #
104 # The line terminator '\n' and `\r\n` is removed in each line,
105 #
106 # ~~~
107 # var txt = "Hello\n\nWorld\n"
108 # var i = new StringReader(txt)
109 # assert i.read_lines == ["Hello", "", "World"]
110 # ~~~
111 #
112 # This method is more efficient that splitting
113 # the result of `read_all`.
114 #
115 # NOTE: SEE `read_line` for details.
116 fun read_lines: Array[String]
117 do
118 var res = new Array[String]
119 while not eof do
120 res.add read_line
121 end
122 return res
123 end
124
125 # Return an iterator that read each line.
126 #
127 # The line terminator '\n' and `\r\n` is removed in each line,
128 # The line are read with `read_line`. See this method for details.
129 #
130 # ~~~
131 # var txt = "Hello\n\nWorld\n"
132 # var i = new StringReader(txt)
133 # assert i.each_line.to_a == ["Hello", "", "World"]
134 # ~~~
135 #
136 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
137 # Therefore, the stream should no be closed until the end of the stream.
138 #
139 # ~~~
140 # i = new StringReader(txt)
141 # var el = i.each_line
142 #
143 # assert el.item == "Hello"
144 # el.next
145 # assert el.item == ""
146 # el.next
147 #
148 # i.close
149 #
150 # assert not el.is_ok
151 # # closed before "world" is read
152 # ~~~
153 fun each_line: LineIterator do return new LineIterator(self)
154
155 # Read all the stream until the eof.
156 #
157 # The content of the file is returned verbatim.
158 #
159 # ~~~
160 # var txt = "Hello\n\nWorld\n"
161 # var i = new StringReader(txt)
162 # assert i.read_all == txt
163 # ~~~
164 fun read_all: String
165 do
166 if last_error != null then return ""
167 var s = new FlatBuffer
168 while not eof do
169 var c = read_char
170 if c != null then s.add(c)
171 end
172 return s.to_s
173 end
174
175 # Read a string until the end of the line and append it to `s`.
176 #
177 # Unlike `read_line` and other related methods,
178 # the line terminator '\n', if any, is preserved in each line.
179 # Use the method `Text::chomp` to safely remove it.
180 #
181 # ~~~
182 # var txt = "Hello\n\nWorld\n"
183 # var i = new StringReader(txt)
184 # var b = new FlatBuffer
185 # i.append_line_to(b)
186 # assert b == "Hello\n"
187 # i.append_line_to(b)
188 # assert b == "Hello\n\n"
189 # i.append_line_to(b)
190 # assert b == txt
191 # assert i.eof
192 # ~~~
193 #
194 # If `\n` is not present at the end of the result, it means that
195 # a non-eol terminated last line was returned.
196 #
197 # ~~~
198 # var i2 = new StringReader("hello")
199 # assert not i2.eof
200 # var b2 = new FlatBuffer
201 # i2.append_line_to(b2)
202 # assert b2 == "hello"
203 # assert i2.eof
204 # ~~~
205 #
206 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
207 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
208 fun append_line_to(s: Buffer)
209 do
210 if last_error != null then return
211 loop
212 var x = read_char
213 if x == null then
214 if eof then return
215 else
216 s.chars.push(x)
217 if x == '\n' then return
218 end
219 end
220 end
221
222 # Is there something to read.
223 # This function returns 'false' if there is something to read.
224 fun eof: Bool is abstract
225
226 # Read the next sequence of non whitespace characters.
227 #
228 # Leading whitespace characters are skipped.
229 # The first whitespace character that follows the result is consumed.
230 #
231 # An empty string is returned if the end of the file or an error is encounter.
232 #
233 # ~~~
234 # var w = new StringReader(" Hello, \n\t World!")
235 # assert w.read_word == "Hello,"
236 # assert w.read_char == '\n'.ascii
237 # assert w.read_word == "World!"
238 # assert w.read_word == ""
239 # ~~~
240 #
241 # `Char::is_whitespace` determines what is a whitespace.
242 fun read_word: String
243 do
244 var buf = new FlatBuffer
245 var c = read_nonwhitespace
246 if c != null then
247 buf.add(c)
248 while not eof do
249 c = read_char
250 if c == null then break
251 if c.is_whitespace then break
252 buf.add(c)
253 end
254 end
255 var res = buf.to_s
256 return res
257 end
258
259 # Skip whitespace characters (if any) then return the following non-whitespace character.
260 #
261 # Returns the code point of the character.
262 # Returns `null` on end of file or error.
263 #
264 # In fact, this method works like `read_char` except it skips whitespace.
265 #
266 # ~~~
267 # var w = new StringReader(" \nab\tc")
268 # assert w.read_nonwhitespace == 'a'
269 # assert w.read_nonwhitespace == 'b'
270 # assert w.read_nonwhitespace == 'c'
271 # assert w.read_nonwhitespace == null
272 # ~~~
273 #
274 # `Char::is_whitespace` determines what is a whitespace.
275 fun read_nonwhitespace: nullable Char
276 do
277 var c: nullable Char = null
278 while not eof do
279 c = read_char
280 if c == null or not c.is_whitespace then break
281 end
282 return c
283 end
284 end
285
286 # Iterator returned by `Reader::each_line`.
287 # See the aforementioned method for details.
288 class LineIterator
289 super Iterator[String]
290
291 # The original stream
292 var stream: Reader
293
294 redef fun is_ok
295 do
296 var res = not stream.eof
297 if not res and close_on_finish then stream.close
298 return res
299 end
300
301 redef fun item
302 do
303 var line = self.line
304 if line == null then
305 line = stream.read_line
306 end
307 self.line = line
308 return line
309 end
310
311 # The last line read (cache)
312 private var line: nullable String = null
313
314 redef fun next
315 do
316 # force the read
317 if line == null then item
318 # drop the line
319 line = null
320 end
321
322 # Close the stream when the stream is at the EOF.
323 #
324 # Default is false.
325 var close_on_finish = false is writable
326
327 redef fun finish
328 do
329 if close_on_finish then stream.close
330 end
331 end
332
333 # `Reader` capable of declaring if readable without blocking
334 abstract class PollableReader
335 super Reader
336
337 # Is there something to read? (without blocking)
338 fun poll_in: Bool is abstract
339
340 end
341
342 # A `Stream` that can be written to
343 abstract class Writer
344 super Stream
345 # write a string
346 fun write(s: Text) is abstract
347
348 # Write a single byte
349 fun write_byte(value: Int) is abstract
350
351 # Can the stream be used to write
352 fun is_writable: Bool is abstract
353 end
354
355 # Things that can be efficienlty written to a `Writer`
356 #
357 # The point of this interface is to allow the instance to be efficiently
358 # written into a `Writer`.
359 #
360 # Ready-to-save documents usually provide this interface.
361 interface Writable
362 # Write itself to a `stream`
363 # The specific logic it let to the concrete subclasses
364 fun write_to(stream: Writer) is abstract
365
366 # Like `write_to` but return a new String (may be quite large)
367 #
368 # This funtionnality is anectodical, since the point
369 # of streamable object to to be efficienlty written to a
370 # stream without having to allocate and concatenate strings
371 fun write_to_string: String
372 do
373 var stream = new StringWriter
374 write_to(stream)
375 return stream.to_s
376 end
377 end
378
379 redef class Text
380 super Writable
381 redef fun write_to(stream) do stream.write(self)
382 end
383
384 # Input streams with a buffered input for efficiency purposes
385 abstract class BufferedReader
386 super Reader
387 redef fun read_char
388 do
389 if last_error != null then return null
390 if eof then
391 last_error = new IOError("Stream has reached eof")
392 return null
393 end
394 var c = _buffer[_buffer_pos]
395 _buffer_pos += 1
396 return c
397 end
398
399 redef fun read_byte
400 do
401 if last_error != null then return null
402 if eof then
403 last_error = new IOError("Stream has reached eof")
404 return null
405 end
406 var c = _buffer[_buffer_pos].ascii
407 _buffer_pos += 1
408 return c
409 end
410
411 # Peeks up to `n` bytes in the buffer, returns an empty string on EOF
412 #
413 # The operation does not consume the buffer
414 #
415 # ~~~nitish
416 # var x = new FileReader("File.txt")
417 # assert x.peek(5) == x.read(5)
418 # ~~~
419 fun peek(i: Int): String do
420 if eof then return ""
421 var b = new FlatBuffer.with_capacity(i)
422 while i > 0 and not eof do
423 b.add _buffer[_buffer_pos]
424 _buffer_pos += 1
425 i -= 1
426 end
427 var nbuflen = b.length + (_buffer.length - _buffer_pos)
428 var nbuf = new FlatBuffer.with_capacity(nbuflen)
429 nbuf.append(b)
430 while _buffer_pos < _buffer.length do
431 nbuf.add(_buffer[_buffer_pos])
432 _buffer_pos += 1
433 end
434 _buffer_pos = 0
435 _buffer = nbuf
436 return b.to_s
437 end
438
439 redef fun read(i)
440 do
441 if last_error != null then return ""
442 if eof then return ""
443 var p = _buffer_pos
444 var bufsp = _buffer.length - p
445 if bufsp >= i then
446 _buffer_pos += i
447 return _buffer.substring(p, i).to_s
448 end
449 _buffer_pos = _buffer.length
450 var readln = _buffer.length - p
451 var s = _buffer.substring(p, readln).to_s
452 fill_buffer
453 return s + read(i - readln)
454 end
455
456 redef fun read_all
457 do
458 if last_error != null then return ""
459 var s = new FlatBuffer
460 while not eof do
461 var j = _buffer_pos
462 var k = _buffer.length
463 while j < k do
464 s.add(_buffer[j])
465 j += 1
466 end
467 _buffer_pos = j
468 fill_buffer
469 end
470 return s.to_s
471 end
472
473 redef fun append_line_to(s)
474 do
475 loop
476 # First phase: look for a '\n'
477 var i = _buffer_pos
478 while i < _buffer.length and _buffer[i] != '\n' do i += 1
479
480 var eol
481 if i < _buffer.length then
482 assert _buffer[i] == '\n'
483 i += 1
484 eol = true
485 else
486 eol = false
487 end
488
489 # if there is something to append
490 if i > _buffer_pos then
491 # Enlarge the string (if needed)
492 s.enlarge(s.length + i - _buffer_pos)
493
494 # Copy from the buffer to the string
495 var j = _buffer_pos
496 while j < i do
497 s.add(_buffer[j])
498 j += 1
499 end
500 _buffer_pos = i
501 else
502 assert end_reached
503 return
504 end
505
506 if eol then
507 # so \n is found
508 return
509 else
510 # so \n is not found
511 if end_reached then return
512 fill_buffer
513 end
514 end
515 end
516
517 redef fun eof
518 do
519 if _buffer_pos < _buffer.length then return false
520 if end_reached then return true
521 fill_buffer
522 return _buffer_pos >= _buffer.length and end_reached
523 end
524
525 # The buffer
526 private var buffer: nullable FlatBuffer = null
527
528 # The current position in the buffer
529 private var buffer_pos: Int = 0
530
531 # Fill the buffer
532 protected fun fill_buffer is abstract
533
534 # Is the last fill_buffer reach the end
535 protected fun end_reached: Bool is abstract
536
537 # Allocate a `_buffer` for a given `capacity`.
538 protected fun prepare_buffer(capacity: Int)
539 do
540 _buffer = new FlatBuffer.with_capacity(capacity)
541 _buffer_pos = 0 # need to read
542 end
543 end
544
545 # A `Stream` that can be written to and read from
546 abstract class Duplex
547 super Reader
548 super Writer
549 end
550
551 # `Stream` that can be used to write to a `String`
552 #
553 # Mainly used for compatibility with Writer type and tests.
554 class StringWriter
555 super Writer
556
557 private var content = new Array[String]
558 redef fun to_s do return content.to_s
559 redef fun is_writable do return not closed
560 redef fun write(str)
561 do
562 assert not closed
563 content.add(str.to_s)
564 end
565
566 # Is the stream closed?
567 protected var closed = false
568
569 redef fun close do closed = true
570 end
571
572 # `Stream` used to read from a `String`
573 #
574 # Mainly used for compatibility with Reader type and tests.
575 class StringReader
576 super Reader
577
578 # The string to read from.
579 var source: String
580
581 # The current position in the string.
582 private var cursor: Int = 0
583
584 redef fun read_char do
585 if cursor < source.length then
586 var c = source[cursor]
587
588 cursor += 1
589 return c
590 else
591 return null
592 end
593 end
594
595 redef fun read_byte do
596 if cursor < source.length then
597 var c = source[cursor]
598
599 cursor += 1
600 return c.ascii
601 else
602 return null
603 end
604 end
605
606 redef fun close do
607 source = ""
608 end
609
610 redef fun read_all do
611 var c = cursor
612 cursor = source.length
613 if c == 0 then return source
614 return source.substring_from(c)
615 end
616
617 redef fun eof do return cursor >= source.length
618 end