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
23 # Any kind of error that could be produced by an operation on Streams
28 # Any kind of stream to read/write/both to or from a source
30 # Error produced by the file stream
32 # var ifs = new FileReader.open("donotmakethisfile.binx")
35 # assert ifs.last_error != null
36 var last_error
: nullable IOError = null
42 # A `Stream` that can be read from
45 # Reads a character. Returns `null` on EOF or timeout
46 fun read_char
: nullable Char is abstract
48 # Reads a byte. Returns `null` on EOF or timeout
49 fun read_byte
: nullable Int is abstract
51 # Read at most i bytes
52 fun read
(i
: Int): String
54 if last_error
!= null then return ""
55 var s
= new FlatBuffer.with_capacity
(i
)
56 while i
> 0 and not eof
do
66 # Read a string until the end of the line.
68 # The line terminator '\n' and '\r\n', if any, is removed in each line.
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"
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.
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"
92 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
95 if last_error
!= null then return ""
97 var s
= new FlatBuffer
102 # Read all the lines until the eof.
104 # The line terminator '\n' and `\r\n` is removed in each line,
107 # var txt = "Hello\n\nWorld\n"
108 # var i = new StringReader(txt)
109 # assert i.read_lines == ["Hello", "", "World"]
112 # This method is more efficient that splitting
113 # the result of `read_all`.
115 # NOTE: SEE `read_line` for details.
116 fun read_lines
: Array[String]
118 var res
= new Array[String]
125 # Return an iterator that read each line.
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.
131 # var txt = "Hello\n\nWorld\n"
132 # var i = new StringReader(txt)
133 # assert i.each_line.to_a == ["Hello", "", "World"]
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.
140 # i = new StringReader(txt)
141 # var el = i.each_line
143 # assert el.item == "Hello"
145 # assert el.item == ""
150 # assert not el.is_ok
151 # # closed before "world" is read
153 fun each_line
: LineIterator do return new LineIterator(self)
155 # Read all the stream until the eof.
157 # The content of the file is returned verbatim.
160 # var txt = "Hello\n\nWorld\n"
161 # var i = new StringReader(txt)
162 # assert i.read_all == txt
166 if last_error
!= null then return ""
167 var s
= new FlatBuffer
170 if c
!= null then s
.add
(c
)
175 # Read a string until the end of the line and append it to `s`.
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.
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)
194 # If `\n` is not present at the end of the result, it means that
195 # a non-eol terminated last line was returned.
198 # var i2 = new StringReader("hello")
200 # var b2 = new FlatBuffer
201 # i2.append_line_to(b2)
202 # assert b2 == "hello"
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)
210 if last_error
!= null then return
217 if x
== '\n' then return
222 # Is there something to read.
223 # This function returns 'false' if there is something to read.
224 fun eof
: Bool is abstract
226 # Read the next sequence of non whitespace characters.
228 # Leading whitespace characters are skipped.
229 # The first whitespace character that follows the result is consumed.
231 # An empty string is returned if the end of the file or an error is encounter.
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 == ""
241 # `Char::is_whitespace` determines what is a whitespace.
242 fun read_word
: String
244 var buf
= new FlatBuffer
245 var c
= read_nonwhitespace
250 if c
== null then break
251 if c
.is_whitespace
then break
259 # Skip whitespace characters (if any) then return the following non-whitespace character.
261 # Returns the code point of the character.
262 # Returns `null` on end of file or error.
264 # In fact, this method works like `read_char` except it skips whitespace.
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
274 # `Char::is_whitespace` determines what is a whitespace.
275 fun read_nonwhitespace
: nullable Char
277 var c
: nullable Char = null
280 if c
== null or not c
.is_whitespace
then break
286 # Iterator returned by `Reader::each_line`.
287 # See the aforementioned method for details.
289 super Iterator[String]
291 # The original stream
296 var res
= not stream
.eof
297 if not res
and close_on_finish
then stream
.close
305 line
= stream
.read_line
311 # The last line read (cache)
312 private var line
: nullable String = null
317 if line
== null then item
322 # Close the stream when the stream is at the EOF.
325 var close_on_finish
= false is writable
329 if close_on_finish
then stream
.close
333 # `Reader` capable of declaring if readable without blocking
334 abstract class PollableReader
337 # Is there something to read? (without blocking)
338 fun poll_in
: Bool is abstract
342 # A `Stream` that can be written to
343 abstract class Writer
346 fun write
(s
: Text) is abstract
348 # Write a single byte
349 fun write_byte
(value
: Int) is abstract
351 # Can the stream be used to write
352 fun is_writable
: Bool is abstract
355 # Things that can be efficienlty written to a `Writer`
357 # The point of this interface is to allow the instance to be efficiently
358 # written into a `Writer`.
360 # Ready-to-save documents usually provide this interface.
362 # Write itself to a `stream`
363 # The specific logic it let to the concrete subclasses
364 fun write_to
(stream
: Writer) is abstract
366 # Like `write_to` but return a new String (may be quite large)
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
373 var stream
= new StringWriter
381 redef fun write_to
(stream
) do stream
.write
(self)
384 # Input streams with a buffered input for efficiency purposes
385 abstract class BufferedReader
389 if last_error
!= null then return null
391 last_error
= new IOError("Stream has reached eof")
394 var c
= _buffer
[_buffer_pos
]
401 if last_error
!= null then return null
403 last_error
= new IOError("Stream has reached eof")
406 var c
= _buffer
[_buffer_pos
].ascii
411 # Peeks up to `n` bytes in the buffer, returns an empty string on EOF
413 # The operation does not consume the buffer
416 # var x = new FileReader("File.txt")
417 # assert x.peek(5) == x.read(5)
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
]
427 var nbuflen
= b
.length
+ (_buffer
.length
- _buffer_pos
)
428 var nbuf
= new FlatBuffer.with_capacity
(nbuflen
)
430 while _buffer_pos
< _buffer
.length
do
431 nbuf
.add
(_buffer
[_buffer_pos
])
441 if last_error
!= null then return ""
442 if eof
then return ""
444 var bufsp
= _buffer
.length
- p
447 return _buffer
.substring
(p
, i
).to_s
449 _buffer_pos
= _buffer
.length
450 var readln
= _buffer
.length
- p
451 var s
= _buffer
.substring
(p
, readln
).to_s
453 return s
+ read
(i
- readln
)
458 if last_error
!= null then return ""
459 var s
= new FlatBuffer
462 var k
= _buffer
.length
473 redef fun append_line_to
(s
)
476 # First phase: look for a '\n'
478 while i
< _buffer
.length
and _buffer
[i
] != '\n' do i
+= 1
481 if i
< _buffer
.length
then
482 assert _buffer
[i
] == '\n'
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
)
494 # Copy from the buffer to the string
511 if end_reached
then return
519 if _buffer_pos
< _buffer
.length
then return false
520 if end_reached
then return true
522 return _buffer_pos
>= _buffer
.length
and end_reached
526 private var buffer
: nullable FlatBuffer = null
528 # The current position in the buffer
529 private var buffer_pos
: Int = 0
532 protected fun fill_buffer
is abstract
534 # Is the last fill_buffer reach the end
535 protected fun end_reached
: Bool is abstract
537 # Allocate a `_buffer` for a given `capacity`.
538 protected fun prepare_buffer
(capacity
: Int)
540 _buffer
= new FlatBuffer.with_capacity
(capacity
)
541 _buffer_pos
= 0 # need to read
545 # A `Stream` that can be written to and read from
546 abstract class Duplex
551 # `Stream` that can be used to write to a `String`
553 # Mainly used for compatibility with Writer type and tests.
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
563 content
.add
(str
.to_s
)
566 # Is the stream closed?
567 protected var closed
= false
569 redef fun close
do closed
= true
572 # `Stream` used to read from a `String`
574 # Mainly used for compatibility with Reader type and tests.
578 # The string to read from.
581 # The current position in the string.
582 private var cursor
: Int = 0
584 redef fun read_char
do
585 if cursor
< source
.length
then
586 var c
= source
[cursor
]
595 redef fun read_byte
do
596 if cursor
< source
.length
then
597 var c
= source
[cursor
]
610 redef fun read_all
do
612 cursor
= source
.length
613 if c
== 0 then return source
614 return source
.substring_from
(c
)
617 redef fun eof
do return cursor
>= source
.length