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
25 # Any kind of error that could be produced by an operation on Streams
30 # Any kind of stream to read/write/both to or from a source
32 # Error produced by the file stream
34 # var ifs = new FileReader.open("donotmakethisfile.binx")
37 # assert ifs.last_error != null
38 var last_error
: nullable IOError = null
45 # Used to inform `self` that operations will start.
46 # Specific streams can use this to prepare some resources.
48 # Is automatically invoked at the beginning of `with` structures.
50 # Do nothing by default.
55 # Used to inform `self` that the operations are over.
56 # Specific streams can use this to free some resources.
58 # Is automatically invoked at the end of `woth` structures.
60 # call `close` by default.
64 # A `Stream` that can be read from
68 # Decoder used to transform input bytes to UTF-8
69 var decoder
: Codec = utf8_codec
is writable
71 # Reads a character. Returns `null` on EOF or timeout
72 fun read_char
: nullable Char is abstract
74 # Reads a byte. Returns `null` on EOF or timeout
75 fun read_byte
: nullable Byte is abstract
77 # Reads a String of at most `i` length
78 fun read
(i
: Int): String do return read_bytes
(i
).to_s
80 # Read at most i bytes
81 fun read_bytes
(i
: Int): Bytes
83 if last_error
!= null then return new Bytes.empty
84 var s
= new CString(i
)
85 var buf
= new Bytes(s
, 0, 0)
86 while i
> 0 and not eof
do
96 # Read a string until the end of the line.
98 # The line terminator '\n' and '\r\n', if any, is removed in each line.
101 # var txt = "Hello\n\nWorld\n"
102 # var i = new StringReader(txt)
103 # assert i.read_line == "Hello"
104 # assert i.read_line == ""
105 # assert i.read_line == "World"
109 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
110 # the end or file (EOF) is considered to delimit the end of lines.
111 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
114 # var txt2 = "Hello\r\n\n\rWorld"
115 # var i2 = new StringReader(txt2)
116 # assert i2.read_line == "Hello"
117 # assert i2.read_line == ""
118 # assert i2.read_line == "\rWorld"
122 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
123 fun read_line
: String
125 if last_error
!= null then return ""
126 if eof
then return ""
127 var s
= new FlatBuffer
132 # Read all the lines until the eof.
134 # The line terminator '\n' and `\r\n` is removed in each line,
137 # var txt = "Hello\n\nWorld\n"
138 # var i = new StringReader(txt)
139 # assert i.read_lines == ["Hello", "", "World"]
142 # This method is more efficient that splitting
143 # the result of `read_all`.
145 # NOTE: SEE `read_line` for details.
146 fun read_lines
: Array[String]
148 var res
= new Array[String]
155 # Return an iterator that read each line.
157 # The line terminator '\n' and `\r\n` is removed in each line,
158 # The line are read with `read_line`. See this method for details.
161 # var txt = "Hello\n\nWorld\n"
162 # var i = new StringReader(txt)
163 # assert i.each_line.to_a == ["Hello", "", "World"]
166 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
167 # Therefore, the stream should no be closed until the end of the stream.
170 # i = new StringReader(txt)
171 # var el = i.each_line
173 # assert el.item == "Hello"
175 # assert el.item == ""
180 # assert not el.is_ok
181 # # closed before "world" is read
183 fun each_line
: LineIterator do return new LineIterator(self)
185 # Read all the stream until the eof.
187 # The content of the file is returned as a String.
190 # var txt = "Hello\n\nWorld\n"
191 # var i = new StringReader(txt)
192 # assert i.read_all == txt
194 fun read_all
: String do
195 var s
= read_all_bytes
197 if slen
== 0 then return ""
200 var str
= s
.items
.clean_utf8
(slen
)
201 slen
= str
.byte_length
205 # The 129 size was decided more or less arbitrarily
206 # It will require some more benchmarking to compute
207 # if this is the best size or not
209 if chunksz
> remsp
then
210 rets
+= new FlatString.with_infos
(sits
, remsp
, pos
)
213 var st
= sits
.find_beginning_of_char_at
(pos
+ chunksz
- 1)
214 var byte_length
= st
- pos
215 rets
+= new FlatString.with_infos
(sits
, byte_length
, pos
)
219 if rets
isa Concat then return rets
.balance
223 # Read all the stream until the eof.
225 # The content of the file is returned verbatim.
226 fun read_all_bytes
: Bytes
228 if last_error
!= null then return new Bytes.empty
229 var s
= new Bytes.empty
232 if c
!= null then s
.add
(c
)
237 # Read a string until the end of the line and append it to `s`.
239 # Unlike `read_line` and other related methods,
240 # the line terminator '\n', if any, is preserved in each line.
241 # Use the method `Text::chomp` to safely remove it.
244 # var txt = "Hello\n\nWorld\n"
245 # var i = new StringReader(txt)
246 # var b = new FlatBuffer
247 # i.append_line_to(b)
248 # assert b == "Hello\n"
249 # i.append_line_to(b)
250 # assert b == "Hello\n\n"
251 # i.append_line_to(b)
256 # If `\n` is not present at the end of the result, it means that
257 # a non-eol terminated last line was returned.
260 # var i2 = new StringReader("hello")
262 # var b2 = new FlatBuffer
263 # i2.append_line_to(b2)
264 # assert b2 == "hello"
268 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
269 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
270 fun append_line_to
(s
: Buffer)
272 if last_error
!= null then return
279 if x
== '\n' then return
284 # Is there something to read.
285 # This function returns 'false' if there is something to read.
286 fun eof
: Bool is abstract
288 # Read the next sequence of non whitespace characters.
290 # Leading whitespace characters are skipped.
291 # The first whitespace character that follows the result is consumed.
293 # An empty string is returned if the end of the file or an error is encounter.
296 # var w = new StringReader(" Hello, \n\t World!")
297 # assert w.read_word == "Hello,"
298 # assert w.read_char == '\n'
299 # assert w.read_word == "World!"
300 # assert w.read_word == ""
303 # `Char::is_whitespace` determines what is a whitespace.
304 fun read_word
: String
306 var buf
= new FlatBuffer
307 var c
= read_nonwhitespace
312 if c
== null then break
313 if c
.is_whitespace
then break
321 # Skip whitespace characters (if any) then return the following non-whitespace character.
323 # Returns the code point of the character.
324 # Returns `null` on end of file or error.
326 # In fact, this method works like `read_char` except it skips whitespace.
329 # var w = new StringReader(" \nab\tc")
330 # assert w.read_nonwhitespace == 'a'
331 # assert w.read_nonwhitespace == 'b'
332 # assert w.read_nonwhitespace == 'c'
333 # assert w.read_nonwhitespace == null
336 # `Char::is_whitespace` determines what is a whitespace.
337 fun read_nonwhitespace
: nullable Char
339 var c
: nullable Char = null
342 if c
== null or not c
.is_whitespace
then break
348 # Iterator returned by `Reader::each_line`.
349 # See the aforementioned method for details.
351 super Iterator[String]
353 # The original stream
358 var res
= not stream
.eof
359 if not res
and close_on_finish
then stream
.close
367 line
= stream
.read_line
373 # The last line read (cache)
374 private var line
: nullable String = null
379 if line
== null then item
384 # Close the stream when the stream is at the EOF.
387 var close_on_finish
= false is writable
391 if close_on_finish
then stream
.close
395 # `Reader` capable of declaring if readable without blocking
396 abstract class PollableReader
399 # Is there something to read? (without blocking)
400 fun poll_in
: Bool is abstract
404 # A `Stream` that can be written to
405 abstract class Writer
408 # The coder from a nit UTF-8 String to the output file
409 var coder
: Codec = utf8_codec
is writable
411 # Writes bytes from `s`
412 fun write_bytes
(s
: Bytes) is abstract
415 fun write
(s
: Text) is abstract
417 # Write a single byte
418 fun write_byte
(value
: Byte) is abstract
420 # Writes a single char
421 fun write_char
(c
: Char) do write
(c
.to_s
)
423 # Can the stream be used to write
424 fun is_writable
: Bool is abstract
427 # Things that can be efficienlty written to a `Writer`
429 # The point of this interface is to allow the instance to be efficiently
430 # written into a `Writer`.
432 # Ready-to-save documents usually provide this interface.
434 # Write itself to a `stream`
435 # The specific logic it let to the concrete subclasses
436 fun write_to
(stream
: Writer) is abstract
438 # Like `write_to` but return a new String (may be quite large)
440 # This funtionality is anectodical, since the point
441 # of streamable object to to be efficienlty written to a
442 # stream without having to allocate and concatenate strings
443 fun write_to_string
: String
445 var stream
= new StringWriter
453 redef fun write_to
(s
) do s
.write_bytes
(self)
455 redef fun write_to_string
do return to_s
460 redef fun write_to
(stream
) do stream
.write
(self)
463 # Input streams with a buffered input for efficiency purposes
464 abstract class BufferedReader
468 if last_error
!= null then return null
470 last_error
= new IOError("Stream has reached eof")
473 # TODO: Fix when supporting UTF-8
474 var c
= _buffer
[_buffer_pos
].to_i
.code_point
481 if last_error
!= null then return null
483 last_error
= new IOError("Stream has reached eof")
486 var c
= _buffer
[_buffer_pos
]
491 # Resets the internal buffer
497 # Peeks up to `n` bytes in the buffer
499 # The operation does not consume the buffer
502 # var x = new FileReader.open("File.txt")
503 # assert x.peek(5) == x.read(5)
505 fun peek
(i
: Int): Bytes do
506 if eof
then return new Bytes.empty
507 var remsp
= _buffer_length
- _buffer_pos
509 var bf
= new Bytes.with_capacity
(i
)
510 bf
.append_ns_from
(_buffer
, i
, _buffer_pos
)
513 var bf
= new Bytes.with_capacity
(i
)
514 bf
.append_ns_from
(_buffer
, remsp
, _buffer_pos
)
515 _buffer_pos
= _buffer_length
516 read_intern
(i
- bf
.length
, bf
)
517 remsp
= _buffer_length
- _buffer_pos
518 var full_len
= bf
.length
+ remsp
519 if full_len
> _buffer_capacity
then
520 var c
= _buffer_capacity
521 while c
< full_len
do c
= c
* 2 + 2
524 var nns
= new CString(_buffer_capacity
)
525 bf
.items
.copy_to
(nns
, bf
.length
, 0, 0)
526 _buffer
.copy_to
(nns
, remsp
, _buffer_pos
, bf
.length
)
529 _buffer_length
= full_len
533 redef fun read_bytes
(i
)
535 if last_error
!= null then return new Bytes.empty
536 var buf
= new Bytes.with_capacity
(i
)
541 # Fills `buf` with at most `i` bytes read from `self`
542 private fun read_intern
(i
: Int, buf
: Bytes): Int do
545 var bufsp
= _buffer_length
- p
548 buf
.append_ns_from
(_buffer
, i
, p
)
551 _buffer_pos
= _buffer_length
552 var readln
= _buffer_length
- p
553 buf
.append_ns_from
(_buffer
, readln
, p
)
554 var rd
= read_intern
(i
- readln
, buf
)
558 redef fun read_all_bytes
560 if last_error
!= null then return new Bytes.empty
561 var s
= new Bytes.with_capacity
(10)
565 var k
= _buffer_length
567 s
.append_ns_from
(b
, rd_sz
, j
)
574 redef fun append_line_to
(s
)
576 var lb
= new Bytes.with_capacity
(10)
578 # First phase: look for a '\n'
580 while i
< _buffer_length
and _buffer
[i
] != 0xAu
8 do
585 if i
< _buffer_length
then
586 assert _buffer
[i
] == 0xAu
8
593 # if there is something to append
594 if i
> _buffer_pos
then
595 # Copy from the buffer to the string
625 if _buffer_pos
< _buffer_length
then return false
626 if end_reached
then return true
628 return _buffer_pos
>= _buffer_length
and end_reached
632 private var buffer
: CString = new CString(0)
634 # The current position in the buffer
635 private var buffer_pos
= 0
637 # Length of the current buffer (i.e. nuber of bytes in the buffer)
638 private var buffer_length
= 0
640 # Capacity of the buffer
641 private var buffer_capacity
= 0
644 protected fun fill_buffer
is abstract
646 # Has the last fill_buffer reached the end
647 protected fun end_reached
: Bool is abstract
649 # Allocate a `_buffer` for a given `capacity`.
650 protected fun prepare_buffer
(capacity
: Int)
652 _buffer
= new CString(capacity
)
653 _buffer_pos
= 0 # need to read
655 _buffer_capacity
= capacity
659 # A `Stream` that can be written to and read from
660 abstract class Duplex
665 # `Stream` that can be used to write to a `String`
667 # Mainly used for compatibility with Writer type and tests.
671 private var content
= new Array[String]
672 redef fun to_s
do return content
.plain_to_s
673 redef fun is_writable
do return not closed
675 redef fun write_bytes
(b
) do
682 content
.add
(str
.to_s
)
685 # Is the stream closed?
686 protected var closed
= false
688 redef fun close
do closed
= true
691 # `Stream` used to read from a `String`
693 # Mainly used for compatibility with Reader type and tests.
697 # The string to read from.
700 # The current position in the string (bytewise).
701 private var cursor
: Int = 0
703 redef fun read_char
do
704 if cursor
< source
.length
then
705 # Fix when supporting UTF-8
706 var c
= source
[cursor
]
714 redef fun read_byte
do
715 if cursor
< source
.length
then
716 var c
= source
.bytes
[cursor
]
728 redef fun read_all_bytes
do
729 var nslen
= source
.length
- cursor
730 var nns
= new CString(nslen
)
731 source
.copy_to_native
(nns
, nslen
, cursor
, 0)
732 return new Bytes(nns
, nslen
, nslen
)
735 redef fun eof
do return cursor
>= source
.byte_length