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
14 intrude import text
::ropes
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 Byte 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
170 var s
= read_all_bytes
172 if slen
== 0 then return ""
178 # The 129 size was decided more or less arbitrarily
179 # It will require some more benchmarking to compute
180 # if this is the best size or not
182 if chunksz
> remsp
then
183 rets
+= new FlatString.with_infos
(sits
, remsp
, pos
, pos
+ remsp
- 1)
186 var st
= sits
.find_beginning_of_char_at
(pos
+ chunksz
- 1)
187 var bytelen
= st
- pos
188 rets
+= new FlatString.with_infos
(sits
, bytelen
, pos
, st
- 1)
192 if rets
isa Concat then return rets
.balance
196 # Read all the stream until the eof.
198 # The content of the file is returned verbatim.
199 fun read_all_bytes
: Bytes
201 if last_error
!= null then return new Bytes.empty
202 var s
= new Bytes.empty
205 if c
!= null then s
.add
(c
)
210 # Read a string until the end of the line and append it to `s`.
212 # Unlike `read_line` and other related methods,
213 # the line terminator '\n', if any, is preserved in each line.
214 # Use the method `Text::chomp` to safely remove it.
217 # var txt = "Hello\n\nWorld\n"
218 # var i = new StringReader(txt)
219 # var b = new FlatBuffer
220 # i.append_line_to(b)
221 # assert b == "Hello\n"
222 # i.append_line_to(b)
223 # assert b == "Hello\n\n"
224 # i.append_line_to(b)
229 # If `\n` is not present at the end of the result, it means that
230 # a non-eol terminated last line was returned.
233 # var i2 = new StringReader("hello")
235 # var b2 = new FlatBuffer
236 # i2.append_line_to(b2)
237 # assert b2 == "hello"
241 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
242 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
243 fun append_line_to
(s
: Buffer)
245 if last_error
!= null then return
252 if x
== '\n' then return
257 # Is there something to read.
258 # This function returns 'false' if there is something to read.
259 fun eof
: Bool is abstract
261 # Read the next sequence of non whitespace characters.
263 # Leading whitespace characters are skipped.
264 # The first whitespace character that follows the result is consumed.
266 # An empty string is returned if the end of the file or an error is encounter.
269 # var w = new StringReader(" Hello, \n\t World!")
270 # assert w.read_word == "Hello,"
271 # assert w.read_char == '\n'.ascii
272 # assert w.read_word == "World!"
273 # assert w.read_word == ""
276 # `Char::is_whitespace` determines what is a whitespace.
277 fun read_word
: String
279 var buf
= new FlatBuffer
280 var c
= read_nonwhitespace
285 if c
== null then break
286 if c
.is_whitespace
then break
294 # Skip whitespace characters (if any) then return the following non-whitespace character.
296 # Returns the code point of the character.
297 # Returns `null` on end of file or error.
299 # In fact, this method works like `read_char` except it skips whitespace.
302 # var w = new StringReader(" \nab\tc")
303 # assert w.read_nonwhitespace == 'a'
304 # assert w.read_nonwhitespace == 'b'
305 # assert w.read_nonwhitespace == 'c'
306 # assert w.read_nonwhitespace == null
309 # `Char::is_whitespace` determines what is a whitespace.
310 fun read_nonwhitespace
: nullable Char
312 var c
: nullable Char = null
315 if c
== null or not c
.is_whitespace
then break
321 # Iterator returned by `Reader::each_line`.
322 # See the aforementioned method for details.
324 super Iterator[String]
326 # The original stream
331 var res
= not stream
.eof
332 if not res
and close_on_finish
then stream
.close
340 line
= stream
.read_line
346 # The last line read (cache)
347 private var line
: nullable String = null
352 if line
== null then item
357 # Close the stream when the stream is at the EOF.
360 var close_on_finish
= false is writable
364 if close_on_finish
then stream
.close
368 # `Reader` capable of declaring if readable without blocking
369 abstract class PollableReader
372 # Is there something to read? (without blocking)
373 fun poll_in
: Bool is abstract
377 # A `Stream` that can be written to
378 abstract class Writer
381 # Writes bytes from `s`
382 fun write_bytes
(s
: Bytes) is abstract
385 fun write
(s
: Text) is abstract
387 # Write a single byte
388 fun write_byte
(value
: Byte) is abstract
390 # Can the stream be used to write
391 fun is_writable
: Bool is abstract
394 # Things that can be efficienlty written to a `Writer`
396 # The point of this interface is to allow the instance to be efficiently
397 # written into a `Writer`.
399 # Ready-to-save documents usually provide this interface.
401 # Write itself to a `stream`
402 # The specific logic it let to the concrete subclasses
403 fun write_to
(stream
: Writer) is abstract
405 # Like `write_to` but return a new String (may be quite large)
407 # This funtionality is anectodical, since the point
408 # of streamable object to to be efficienlty written to a
409 # stream without having to allocate and concatenate strings
410 fun write_to_string
: String
412 var stream
= new StringWriter
420 redef fun write_to
(stream
) do stream
.write
(self)
423 # Input streams with a buffered input for efficiency purposes
424 abstract class BufferedReader
428 if last_error
!= null then return null
430 last_error
= new IOError("Stream has reached eof")
433 # TODO: Fix when supporting UTF-8
434 var c
= _buffer
[_buffer_pos
].to_i
.ascii
441 if last_error
!= null then return null
443 last_error
= new IOError("Stream has reached eof")
446 var c
= _buffer
[_buffer_pos
]
456 # Peeks up to `n` bytes in the buffer
458 # The operation does not consume the buffer
461 # var x = new FileReader.open("File.txt")
462 # assert x.peek(5) == x.read(5)
464 fun peek
(i
: Int): Bytes do
465 if eof
then return new Bytes.empty
466 var remsp
= _buffer_length
- _buffer_pos
468 var bf
= new Bytes.with_capacity
(i
)
469 bf
.append_ns_from
(_buffer
, i
, _buffer_pos
)
472 var bf
= new Bytes.with_capacity
(i
)
473 bf
.append_ns_from
(_buffer
, remsp
, _buffer_pos
)
474 _buffer_pos
= _buffer_length
475 read_intern
(i
- bf
.length
, bf
)
476 remsp
= _buffer_length
- _buffer_pos
477 var full_len
= bf
.length
+ remsp
478 if full_len
> _buffer_capacity
then
479 var c
= _buffer_capacity
480 while c
< full_len
do c
= c
* 2 + 2
483 var nns
= new NativeString(_buffer_capacity
)
484 bf
.items
.copy_to
(nns
, bf
.length
, 0, 0)
485 _buffer
.copy_to
(nns
, remsp
, _buffer_pos
, bf
.length
)
488 _buffer_length
= full_len
492 redef fun read_bytes
(i
)
494 if last_error
!= null then return new Bytes.empty
495 var buf
= new Bytes.with_capacity
(i
)
500 # Fills `buf` with at most `i` bytes read from `self`
501 private fun read_intern
(i
: Int, buf
: Bytes): Int do
504 var bufsp
= _buffer_length
- p
507 buf
.append_ns_from
(_buffer
, i
, p
)
510 _buffer_pos
= _buffer_length
511 var readln
= _buffer_length
- p
512 buf
.append_ns_from
(_buffer
, readln
, p
)
513 var rd
= read_intern
(i
- readln
, buf
)
517 redef fun read_all_bytes
519 if last_error
!= null then return new Bytes.empty
520 var s
= new Bytes.with_capacity
(10)
523 var k
= _buffer_length
534 redef fun append_line_to
(s
)
537 # First phase: look for a '\n'
539 while i
< _buffer_length
and _buffer
[i
] != 0xAu
8 do
544 if i
< _buffer_length
then
545 assert _buffer
[i
] == 0xAu
8
552 # if there is something to append
553 if i
> _buffer_pos
then
554 # Enlarge the string (if needed)
555 s
.enlarge
(s
.bytelen
+ i
- _buffer_pos
)
557 # Copy from the buffer to the string
560 s
.bytes
.add
(_buffer
[j
])
574 if end_reached
then return
582 if _buffer_pos
< _buffer_length
then return false
583 if end_reached
then return true
585 return _buffer_pos
>= _buffer_length
and end_reached
589 private var buffer
: NativeString = new NativeString(0)
591 # The current position in the buffer
592 private var buffer_pos
= 0
594 # Length of the current buffer (i.e. nuber of bytes in the buffer)
595 private var buffer_length
= 0
597 # Capacity of the buffer
598 private var buffer_capacity
= 0
601 protected fun fill_buffer
is abstract
603 # Has the last fill_buffer reached the end
604 protected fun end_reached
: Bool is abstract
606 # Allocate a `_buffer` for a given `capacity`.
607 protected fun prepare_buffer
(capacity
: Int)
609 _buffer
= new NativeString(capacity
)
610 _buffer_pos
= 0 # need to read
612 _buffer_capacity
= capacity
616 # A `Stream` that can be written to and read from
617 abstract class Duplex
622 # `Stream` that can be used to write to a `String`
624 # Mainly used for compatibility with Writer type and tests.
628 private var content
= new Array[String]
629 redef fun to_s
do return content
.plain_to_s
630 redef fun is_writable
do return not closed
632 redef fun write_bytes
(b
) do
639 content
.add
(str
.to_s
)
642 # Is the stream closed?
643 protected var closed
= false
645 redef fun close
do closed
= true
648 # `Stream` used to read from a `String`
650 # Mainly used for compatibility with Reader type and tests.
654 # The string to read from.
657 # The current position in the string (bytewise).
658 private var cursor
: Int = 0
660 redef fun read_char
do
661 if cursor
< source
.length
then
662 # Fix when supporting UTF-8
663 var c
= source
[cursor
]
671 redef fun read_byte
do
672 if cursor
< source
.length
then
673 var c
= source
.bytes
[cursor
]
685 redef fun read_all_bytes
do
686 var nslen
= source
.length
- cursor
687 var nns
= new NativeString(nslen
)
688 source
.copy_to_native
(nns
, nslen
, cursor
, 0)
689 return new Bytes(nns
, nslen
, nslen
)
692 redef fun eof
do return cursor
>= source
.bytelen