1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
11 # Input and output streams of characters
24 # Any kind of error that could be produced by an operation on Streams
29 # Any kind of stream to read/write/both to or from a source
31 # Error produced by the file stream
33 # var ifs = new FileReader.open("donotmakethisfile.binx")
36 # assert ifs.last_error != null
37 var last_error
: nullable IOError = null
43 # A `Stream` that can be read from
46 # Reads a character. Returns `null` on EOF or timeout
47 fun read_char
: nullable Char is abstract
49 # Reads a byte. Returns `null` on EOF or timeout
50 fun read_byte
: nullable Int is abstract
52 # Reads a String of at most `i` length
53 fun read
(i
: Int): String do return read_bytes
(i
).to_s
55 # Read at most i bytes
56 fun read_bytes
(i
: Int): Bytes
58 if last_error
!= null then return new Bytes.empty
59 var s
= new NativeString(i
)
60 var buf
= new Bytes(s
, 0, 0)
61 while i
> 0 and not eof
do
71 # Read a string until the end of the line.
73 # The line terminator '\n' and '\r\n', if any, is removed in each line.
76 # var txt = "Hello\n\nWorld\n"
77 # var i = new StringReader(txt)
78 # assert i.read_line == "Hello"
79 # assert i.read_line == ""
80 # assert i.read_line == "World"
84 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
85 # the end or file (EOF) is considered to delimit the end of lines.
86 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
89 # var txt2 = "Hello\r\n\n\rWorld"
90 # var i2 = new StringReader(txt2)
91 # assert i2.read_line == "Hello"
92 # assert i2.read_line == ""
93 # assert i2.read_line == "\rWorld"
97 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
100 if last_error
!= null then return ""
101 if eof
then return ""
102 var s
= new FlatBuffer
107 # Read all the lines until the eof.
109 # The line terminator '\n' and `\r\n` is removed in each line,
112 # var txt = "Hello\n\nWorld\n"
113 # var i = new StringReader(txt)
114 # assert i.read_lines == ["Hello", "", "World"]
117 # This method is more efficient that splitting
118 # the result of `read_all`.
120 # NOTE: SEE `read_line` for details.
121 fun read_lines
: Array[String]
123 var res
= new Array[String]
130 # Return an iterator that read each line.
132 # The line terminator '\n' and `\r\n` is removed in each line,
133 # The line are read with `read_line`. See this method for details.
136 # var txt = "Hello\n\nWorld\n"
137 # var i = new StringReader(txt)
138 # assert i.each_line.to_a == ["Hello", "", "World"]
141 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
142 # Therefore, the stream should no be closed until the end of the stream.
145 # i = new StringReader(txt)
146 # var el = i.each_line
148 # assert el.item == "Hello"
150 # assert el.item == ""
155 # assert not el.is_ok
156 # # closed before "world" is read
158 fun each_line
: LineIterator do return new LineIterator(self)
160 # Read all the stream until the eof.
162 # The content of the file is returned as a String.
165 # var txt = "Hello\n\nWorld\n"
166 # var i = new StringReader(txt)
167 # assert i.read_all == txt
169 fun read_all
: String do return read_all_bytes
.to_s
171 # Read all the stream until the eof.
173 # The content of the file is returned verbatim.
174 fun read_all_bytes
: Bytes
176 if last_error
!= null then return new Bytes.empty
177 var s
= new Bytes.empty
180 if c
!= null then s
.add
(c
)
185 # Read a string until the end of the line and append it to `s`.
187 # Unlike `read_line` and other related methods,
188 # the line terminator '\n', if any, is preserved in each line.
189 # Use the method `Text::chomp` to safely remove it.
192 # var txt = "Hello\n\nWorld\n"
193 # var i = new StringReader(txt)
194 # var b = new FlatBuffer
195 # i.append_line_to(b)
196 # assert b == "Hello\n"
197 # i.append_line_to(b)
198 # assert b == "Hello\n\n"
199 # i.append_line_to(b)
204 # If `\n` is not present at the end of the result, it means that
205 # a non-eol terminated last line was returned.
208 # var i2 = new StringReader("hello")
210 # var b2 = new FlatBuffer
211 # i2.append_line_to(b2)
212 # assert b2 == "hello"
216 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
217 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
218 fun append_line_to
(s
: Buffer)
220 if last_error
!= null then return
227 if x
== '\n' then return
232 # Is there something to read.
233 # This function returns 'false' if there is something to read.
234 fun eof
: Bool is abstract
236 # Read the next sequence of non whitespace characters.
238 # Leading whitespace characters are skipped.
239 # The first whitespace character that follows the result is consumed.
241 # An empty string is returned if the end of the file or an error is encounter.
244 # var w = new StringReader(" Hello, \n\t World!")
245 # assert w.read_word == "Hello,"
246 # assert w.read_char == '\n'.ascii
247 # assert w.read_word == "World!"
248 # assert w.read_word == ""
251 # `Char::is_whitespace` determines what is a whitespace.
252 fun read_word
: String
254 var buf
= new FlatBuffer
255 var c
= read_nonwhitespace
260 if c
== null then break
261 if c
.is_whitespace
then break
269 # Skip whitespace characters (if any) then return the following non-whitespace character.
271 # Returns the code point of the character.
272 # Returns `null` on end of file or error.
274 # In fact, this method works like `read_char` except it skips whitespace.
277 # var w = new StringReader(" \nab\tc")
278 # assert w.read_nonwhitespace == 'a'
279 # assert w.read_nonwhitespace == 'b'
280 # assert w.read_nonwhitespace == 'c'
281 # assert w.read_nonwhitespace == null
284 # `Char::is_whitespace` determines what is a whitespace.
285 fun read_nonwhitespace
: nullable Char
287 var c
: nullable Char = null
290 if c
== null or not c
.is_whitespace
then break
296 # Iterator returned by `Reader::each_line`.
297 # See the aforementioned method for details.
299 super Iterator[String]
301 # The original stream
306 var res
= not stream
.eof
307 if not res
and close_on_finish
then stream
.close
315 line
= stream
.read_line
321 # The last line read (cache)
322 private var line
: nullable String = null
327 if line
== null then item
332 # Close the stream when the stream is at the EOF.
335 var close_on_finish
= false is writable
339 if close_on_finish
then stream
.close
343 # `Reader` capable of declaring if readable without blocking
344 abstract class PollableReader
347 # Is there something to read? (without blocking)
348 fun poll_in
: Bool is abstract
352 # A `Stream` that can be written to
353 abstract class Writer
356 # Writes bytes from `s`
357 fun write_bytes
(s
: Bytes) is abstract
360 fun write
(s
: Text) is abstract
362 # Write a single byte
363 fun write_byte
(value
: Int) is abstract
365 # Can the stream be used to write
366 fun is_writable
: Bool is abstract
369 # Things that can be efficienlty written to a `Writer`
371 # The point of this interface is to allow the instance to be efficiently
372 # written into a `Writer`.
374 # Ready-to-save documents usually provide this interface.
376 # Write itself to a `stream`
377 # The specific logic it let to the concrete subclasses
378 fun write_to
(stream
: Writer) is abstract
380 # Like `write_to` but return a new String (may be quite large)
382 # This funtionality is anectodical, since the point
383 # of streamable object to to be efficienlty written to a
384 # stream without having to allocate and concatenate strings
385 fun write_to_string
: String
387 var stream
= new StringWriter
395 redef fun write_to
(stream
) do stream
.write
(self)
398 # Input streams with a buffered input for efficiency purposes
399 abstract class BufferedReader
403 if last_error
!= null then return null
405 last_error
= new IOError("Stream has reached eof")
408 var c
= _buffer
[_buffer_pos
]
415 if last_error
!= null then return null
417 last_error
= new IOError("Stream has reached eof")
420 var c
= _buffer
[_buffer_pos
].ascii
430 # Peeks up to `n` bytes in the buffer
432 # The operation does not consume the buffer
435 # var x = new FileReader.open("File.txt")
436 # assert x.peek(5) == x.read(5)
438 fun peek
(i
: Int): Bytes do
439 if eof
then return new Bytes.empty
440 var remsp
= _buffer_length
- _buffer_pos
442 var bf
= new Bytes.with_capacity
(i
)
443 bf
.append_ns_from
(_buffer
, i
, _buffer_pos
)
446 var bf
= new Bytes.with_capacity
(i
)
447 bf
.append_ns_from
(_buffer
, remsp
, _buffer_pos
)
448 _buffer_pos
= _buffer_length
449 read_intern
(i
- bf
.length
, bf
)
450 remsp
= _buffer_length
- _buffer_pos
451 var full_len
= bf
.length
+ remsp
452 if full_len
> _buffer_capacity
then
453 var c
= _buffer_capacity
454 while c
< full_len
do c
= c
* 2 + 2
457 var nns
= new NativeString(_buffer_capacity
)
458 bf
.items
.copy_to
(nns
, bf
.length
, 0, 0)
459 _buffer
.copy_to
(nns
, remsp
, _buffer_pos
, bf
.length
)
462 _buffer_length
= full_len
466 redef fun read_bytes
(i
)
468 if last_error
!= null then return new Bytes.empty
469 var buf
= new Bytes.with_capacity
(i
)
474 # Fills `buf` with at most `i` bytes read from `self`
475 private fun read_intern
(i
: Int, buf
: Bytes): Int do
478 var bufsp
= _buffer_length
- p
481 buf
.append_ns_from
(_buffer
, i
, p
)
484 _buffer_pos
= _buffer_length
485 var readln
= _buffer_length
- p
486 buf
.append_ns_from
(_buffer
, readln
, p
)
487 var rd
= read_intern
(i
- readln
, buf
)
491 redef fun read_all_bytes
493 if last_error
!= null then return new Bytes.empty
494 var s
= new Bytes.with_capacity
(10)
497 var k
= _buffer_length
499 s
.add
(_buffer
[j
].ascii
)
508 redef fun append_line_to
(s
)
511 # First phase: look for a '\n'
513 while i
< _buffer_length
and _buffer
[i
] != '\n' do
518 if i
< _buffer_length
then
519 assert _buffer
[i
] == '\n'
526 # if there is something to append
527 if i
> _buffer_pos
then
528 # Enlarge the string (if needed)
529 s
.enlarge
(s
.length
+ i
- _buffer_pos
)
531 # Copy from the buffer to the string
548 if end_reached
then return
556 if _buffer_pos
< _buffer_length
then return false
557 if end_reached
then return true
559 return _buffer_pos
>= _buffer_length
and end_reached
563 private var buffer
: NativeString = new NativeString(0)
565 # The current position in the buffer
566 private var buffer_pos
= 0
568 # Length of the current buffer (i.e. nuber of bytes in the buffer)
569 private var buffer_length
= 0
571 # Capacity of the buffer
572 private var buffer_capacity
= 0
575 protected fun fill_buffer
is abstract
577 # Has the last fill_buffer reached the end
578 protected fun end_reached
: Bool is abstract
580 # Allocate a `_buffer` for a given `capacity`.
581 protected fun prepare_buffer
(capacity
: Int)
583 _buffer
= new NativeString(capacity
)
584 _buffer_pos
= 0 # need to read
586 _buffer_capacity
= capacity
590 # A `Stream` that can be written to and read from
591 abstract class Duplex
596 # `Stream` that can be used to write to a `String`
598 # Mainly used for compatibility with Writer type and tests.
602 private var content
= new Array[String]
603 redef fun to_s
do return content
.to_s
604 redef fun is_writable
do return not closed
606 redef fun write_bytes
(b
) do
613 content
.add
(str
.to_s
)
616 # Is the stream closed?
617 protected var closed
= false
619 redef fun close
do closed
= true
622 # `Stream` used to read from a `String`
624 # Mainly used for compatibility with Reader type and tests.
628 # The string to read from.
631 # The current position in the string.
632 private var cursor
: Int = 0
634 redef fun read_char
do
635 if cursor
< source
.length
then
636 var c
= source
[cursor
]
645 redef fun read_byte
do
646 if cursor
< source
.length
then
647 var c
= source
[cursor
]
660 redef fun read_all_bytes
do
661 var nslen
= source
.length
- cursor
662 var nns
= new NativeString(nslen
)
663 source
.copy_to_native
(nns
, nslen
, cursor
, 0)
664 return new Bytes(nns
, nslen
, nslen
)
667 redef fun eof
do return cursor
>= source
.length