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 # Codec used to transform raw data to text
33 # Note: defaults to UTF-8
34 var codec
: Codec = utf8_codec
is protected writable(set_codec
)
36 # Lookahead buffer for codecs
38 # Since some codecs are multibyte, a lookahead may be required
39 # to store the next bytes and consume them only if a valid character
41 protected var lookahead
: CString is noinit
43 # Capacity of the lookahead
44 protected var lookahead_capacity
= 0
46 # Current occupation of the lookahead
47 protected var lookahead_length
= 0
49 # Buffer for writing data to a stream
50 protected var write_buffer
: CString is noinit
53 var lcap
= codec
.max_lookahead
54 lookahead
= new CString(lcap
)
55 write_buffer
= new CString(lcap
)
57 lookahead_capacity
= lcap
60 # Change the codec for this stream.
61 fun codec
=(c
: Codec) do
62 if c
.max_lookahead
> lookahead_capacity
then
63 var lcap
= codec
.max_lookahead
64 var lk
= new CString(lcap
)
65 var llen
= lookahead_length
67 lookahead
.copy_to
(lk
, llen
, 0, 0)
70 lookahead_capacity
= lcap
71 write_buffer
= new CString(lcap
)
76 # Error produced by the file stream
78 # var ifs = new FileReader.open("donotmakethisfile.binx")
81 # assert ifs.last_error != null
82 var last_error
: nullable IOError = null
89 # Used to inform `self` that operations will start.
90 # Specific streams can use this to prepare some resources.
92 # Is automatically invoked at the beginning of `with` structures.
94 # Do nothing by default.
99 # Used to inform `self` that the operations are over.
100 # Specific streams can use this to free some resources.
102 # Is automatically invoked at the end of `with` structures.
104 # call `close` by default.
108 # A `Stream` that can be read from
109 abstract class Reader
112 # Reads a character. Returns `null` on EOF or timeout
113 fun read_char
: nullable Char is abstract
115 # Reads a byte. Returns `null` on EOF or timeout
116 fun read_byte
: nullable Byte is abstract
118 # Reads a String of at most `i` length
119 fun read
(i
: Int): String do return read_bytes
(i
).to_s
121 # Read at most i bytes
122 fun read_bytes
(i
: Int): Bytes
124 if last_error
!= null then return new Bytes.empty
125 var s
= new CString(i
)
126 var buf
= new Bytes(s
, 0, 0)
127 while i
> 0 and not eof
do
137 # Read a string until the end of the line.
139 # The line terminator '\n' and '\r\n', if any, is removed in each line.
142 # var txt = "Hello\n\nWorld\n"
143 # var i = new StringReader(txt)
144 # assert i.read_line == "Hello"
145 # assert i.read_line == ""
146 # assert i.read_line == "World"
150 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
151 # the end or file (EOF) is considered to delimit the end of lines.
152 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
155 # var txt2 = "Hello\r\n\n\rWorld"
156 # var i2 = new StringReader(txt2)
157 # assert i2.read_line == "Hello"
158 # assert i2.read_line == ""
159 # assert i2.read_line == "\rWorld"
163 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
164 fun read_line
: String
166 if last_error
!= null then return ""
167 if eof
then return ""
168 var s
= new FlatBuffer
173 # Read all the lines until the eof.
175 # The line terminator '\n' and `\r\n` is removed in each line,
178 # var txt = "Hello\n\nWorld\n"
179 # var i = new StringReader(txt)
180 # assert i.read_lines == ["Hello", "", "World"]
183 # This method is more efficient that splitting
184 # the result of `read_all`.
186 # NOTE: SEE `read_line` for details.
187 fun read_lines
: Array[String]
189 var res
= new Array[String]
196 # Return an iterator that read each line.
198 # The line terminator '\n' and `\r\n` is removed in each line,
199 # The line are read with `read_line`. See this method for details.
202 # var txt = "Hello\n\nWorld\n"
203 # var i = new StringReader(txt)
204 # assert i.each_line.to_a == ["Hello", "", "World"]
207 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
208 # Therefore, the stream should no be closed until the end of the stream.
211 # i = new StringReader(txt)
212 # var el = i.each_line
214 # assert el.item == "Hello"
216 # assert el.item == ""
221 # assert not el.is_ok
222 # # closed before "world" is read
224 fun each_line
: LineIterator do return new LineIterator(self)
226 # Read all the stream until the eof.
228 # The content of the file is returned as a String.
231 # var txt = "Hello\n\nWorld\n"
232 # var i = new StringReader(txt)
233 # assert i.read_all == txt
235 fun read_all
: String do
236 var s
= read_all_bytes
238 if slen
== 0 then return ""
239 return codec
.decode_string
(s
.items
, s
.length
)
242 # Read all the stream until the eof.
244 # The content of the file is returned verbatim.
245 fun read_all_bytes
: Bytes
247 if last_error
!= null then return new Bytes.empty
248 var s
= new Bytes.empty
251 if c
!= null then s
.add
(c
)
256 # Read a string until the end of the line and append it to `s`.
258 # Unlike `read_line` and other related methods,
259 # the line terminator '\n', if any, is preserved in each line.
260 # Use the method `Text::chomp` to safely remove it.
263 # var txt = "Hello\n\nWorld\n"
264 # var i = new StringReader(txt)
265 # var b = new FlatBuffer
266 # i.append_line_to(b)
267 # assert b == "Hello\n"
268 # i.append_line_to(b)
269 # assert b == "Hello\n\n"
270 # i.append_line_to(b)
275 # If `\n` is not present at the end of the result, it means that
276 # a non-eol terminated last line was returned.
279 # var i2 = new StringReader("hello")
281 # var b2 = new FlatBuffer
282 # i2.append_line_to(b2)
283 # assert b2 == "hello"
287 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
288 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
289 fun append_line_to
(s
: Buffer)
291 if last_error
!= null then return
298 if x
== '\n' then return
303 # Is there something to read.
304 # This function returns 'false' if there is something to read.
305 fun eof
: Bool is abstract
307 # Read the next sequence of non whitespace characters.
309 # Leading whitespace characters are skipped.
310 # The first whitespace character that follows the result is consumed.
312 # An empty string is returned if the end of the file or an error is encounter.
315 # var w = new StringReader(" Hello, \n\t World!")
316 # assert w.read_word == "Hello,"
317 # assert w.read_char == '\n'
318 # assert w.read_word == "World!"
319 # assert w.read_word == ""
322 # `Char::is_whitespace` determines what is a whitespace.
323 fun read_word
: String
325 var buf
= new FlatBuffer
326 var c
= read_nonwhitespace
331 if c
== null then break
332 if c
.is_whitespace
then break
340 # Skip whitespace characters (if any) then return the following non-whitespace character.
342 # Returns the code point of the character.
343 # Returns `null` on end of file or error.
345 # In fact, this method works like `read_char` except it skips whitespace.
348 # var w = new StringReader(" \nab\tc")
349 # assert w.read_nonwhitespace == 'a'
350 # assert w.read_nonwhitespace == 'b'
351 # assert w.read_nonwhitespace == 'c'
352 # assert w.read_nonwhitespace == null
355 # `Char::is_whitespace` determines what is a whitespace.
356 fun read_nonwhitespace
: nullable Char
358 var c
: nullable Char = null
361 if c
== null or not c
.is_whitespace
then break
367 # Iterator returned by `Reader::each_line`.
368 # See the aforementioned method for details.
370 super Iterator[String]
372 # The original stream
377 var res
= not stream
.eof
378 if not res
and close_on_finish
then stream
.close
386 line
= stream
.read_line
392 # The last line read (cache)
393 private var line
: nullable String = null
398 if line
== null then item
403 # Close the stream when the stream is at the EOF.
406 var close_on_finish
= false is writable
410 if close_on_finish
then stream
.close
414 # `Reader` capable of declaring if readable without blocking
415 abstract class PollableReader
418 # Is there something to read? (without blocking)
419 fun poll_in
: Bool is abstract
423 # A `Stream` that can be written to
424 abstract class Writer
427 # Writes bytes from `s`
428 fun write_bytes
(s
: Bytes) is abstract
431 fun write
(s
: Text) is abstract
433 # Write a single byte
434 fun write_byte
(value
: Byte) is abstract
436 # Writes a single char
437 fun write_char
(c
: Char) do write
(c
.to_s
)
439 # Can the stream be used to write
440 fun is_writable
: Bool is abstract
443 # Things that can be efficienlty written to a `Writer`
445 # The point of this interface is to allow the instance to be efficiently
446 # written into a `Writer`.
448 # Ready-to-save documents usually provide this interface.
450 # Write itself to a `stream`
451 # The specific logic it let to the concrete subclasses
452 fun write_to
(stream
: Writer) is abstract
454 # Like `write_to` but return a new String (may be quite large)
456 # This funtionality is anectodical, since the point
457 # of streamable object to to be efficienlty written to a
458 # stream without having to allocate and concatenate strings
459 fun write_to_string
: String
461 var stream
= new StringWriter
469 redef fun write_to
(s
) do s
.write_bytes
(self)
471 redef fun write_to_string
do return to_s
476 redef fun write_to
(stream
) do stream
.write
(self)
479 # Input streams with a buffered input for efficiency purposes
480 abstract class BufferedReader
484 if last_error
!= null then return null
486 last_error
= new IOError("Stream has reached eof")
489 # TODO: Fix when supporting UTF-8
490 var c
= _buffer
[_buffer_pos
].to_i
.code_point
497 if last_error
!= null then return null
499 last_error
= new IOError("Stream has reached eof")
502 var c
= _buffer
[_buffer_pos
]
507 # Resets the internal buffer
513 # Peeks up to `n` bytes in the buffer
515 # The operation does not consume the buffer
518 # var x = new FileReader.open("File.txt")
519 # assert x.peek(5) == x.read(5)
521 fun peek
(i
: Int): Bytes do
522 if eof
then return new Bytes.empty
523 var remsp
= _buffer_length
- _buffer_pos
525 var bf
= new Bytes.with_capacity
(i
)
526 bf
.append_ns_from
(_buffer
, i
, _buffer_pos
)
529 var bf
= new Bytes.with_capacity
(i
)
530 bf
.append_ns_from
(_buffer
, remsp
, _buffer_pos
)
531 _buffer_pos
= _buffer_length
532 read_intern
(i
- bf
.length
, bf
)
533 remsp
= _buffer_length
- _buffer_pos
534 var full_len
= bf
.length
+ remsp
535 if full_len
> _buffer_capacity
then
536 var c
= _buffer_capacity
537 while c
< full_len
do c
= c
* 2 + 2
540 var nns
= new CString(_buffer_capacity
)
541 bf
.items
.copy_to
(nns
, bf
.length
, 0, 0)
542 _buffer
.copy_to
(nns
, remsp
, _buffer_pos
, bf
.length
)
545 _buffer_length
= full_len
549 redef fun read_bytes
(i
)
551 if last_error
!= null then return new Bytes.empty
552 var buf
= new Bytes.with_capacity
(i
)
557 # Fills `buf` with at most `i` bytes read from `self`
558 private fun read_intern
(i
: Int, buf
: Bytes): Int do
561 var bufsp
= _buffer_length
- p
564 buf
.append_ns_from
(_buffer
, i
, p
)
567 _buffer_pos
= _buffer_length
568 var readln
= _buffer_length
- p
569 buf
.append_ns_from
(_buffer
, readln
, p
)
570 var rd
= read_intern
(i
- readln
, buf
)
574 redef fun read_all_bytes
576 if last_error
!= null then return new Bytes.empty
577 var s
= new Bytes.with_capacity
(10)
581 var k
= _buffer_length
583 s
.append_ns_from
(b
, rd_sz
, j
)
590 redef fun append_line_to
(s
)
592 var lb
= new Bytes.with_capacity
(10)
594 # First phase: look for a '\n'
596 while i
< _buffer_length
and _buffer
[i
] != 0xAu
8 do
601 if i
< _buffer_length
then
602 assert _buffer
[i
] == 0xAu
8
609 # if there is something to append
610 if i
> _buffer_pos
then
611 # Copy from the buffer to the string
641 if _buffer_pos
< _buffer_length
then return false
642 if end_reached
then return true
644 return _buffer_pos
>= _buffer_length
and end_reached
648 private var buffer
: CString = new CString(0)
650 # The current position in the buffer
651 private var buffer_pos
= 0
653 # Length of the current buffer (i.e. nuber of bytes in the buffer)
654 private var buffer_length
= 0
656 # Capacity of the buffer
657 private var buffer_capacity
= 0
660 protected fun fill_buffer
is abstract
662 # Has the last fill_buffer reached the end
663 protected fun end_reached
: Bool is abstract
665 # Allocate a `_buffer` for a given `capacity`.
666 protected fun prepare_buffer
(capacity
: Int)
668 _buffer
= new CString(capacity
)
669 _buffer_pos
= 0 # need to read
671 _buffer_capacity
= capacity
675 # A `Stream` that can be written to and read from
676 abstract class Duplex
681 # Write to `bytes` in memory
684 # var writer = new BytesWriter
686 # writer.write "Strings "
687 # writer.write_char '&'
688 # writer.write_byte 0x20u8
689 # writer.write_bytes "bytes".to_bytes
691 # assert writer.to_s == "\\x53\\x74\\x72\\x69\\x6E\\x67\\x73\\x20\\x26\\x20\\x62\\x79\\x74\\x65\\x73"
692 # assert writer.bytes.to_s == "Strings & bytes"
695 # As with any binary data, UTF-8 code points encoded on two bytes or more
696 # can be constructed byte by byte.
699 # writer = new BytesWriter
701 # # Write just the character first half
702 # writer.write_byte 0xC2u8
703 # assert writer.to_s == "\\xC2"
704 # assert writer.bytes.to_s == "�"
706 # # Complete the character
707 # writer.write_byte 0xA2u8
708 # assert writer.to_s == "\\xC2\\xA2"
709 # assert writer.bytes.to_s == "¢"
715 var bytes
= new Bytes.empty
717 redef fun to_s
do return bytes
.chexdigest
721 if closed
then return
722 str
.append_to_bytes bytes
725 redef fun write_char
(c
)
727 if closed
then return
731 redef fun write_byte
(value
)
733 if closed
then return
737 redef fun write_bytes
(b
)
739 if closed
then return
743 # Is the stream closed?
744 protected var closed
= false
746 redef fun close
do closed
= true
747 redef fun is_writable
do return not closed
750 # `Stream` writing to a `String`
752 # This class has the same behavior as `BytesWriter`
753 # except for `to_s` which decodes `bytes` to a string.
756 # var writer = new StringWriter
758 # writer.write "Strings "
759 # writer.write_char '&'
760 # writer.write_byte 0x20u8
761 # writer.write_bytes "bytes".to_bytes
763 # assert writer.to_s == "Strings & bytes"
768 redef fun to_s
do return bytes
.to_s
771 # Read from `bytes` in memory
774 # var reader = new BytesReader(b"a…b")
775 # assert reader.read_char == 'a'
776 # assert reader.read_byte == 0xE2u8 # 1st byte of '…'
777 # assert reader.read_byte == 0x80u8 # 2nd byte of '…'
778 # assert reader.read_char == '�' # Reads the last byte as an invalid char
779 # assert reader.read_all_bytes == b"b"
784 # Source data to read
787 # The current position in `bytes`
788 private var cursor
= 0
792 if cursor
>= bytes
.length
then return null
794 var len
= bytes
.items
.length_of_char_at
(cursor
)
795 var char
= bytes
.items
.char_at
(cursor
)
802 if cursor
>= bytes
.length
then return null
804 var c
= bytes
[cursor
]
809 redef fun close
do bytes
= new Bytes.empty
811 redef fun read_all_bytes
813 var res
= bytes
.slice_from
(cursor
)
814 cursor
= bytes
.length
818 redef fun eof
do return cursor
>= bytes
.length
821 # `Stream` reading from a `String` source
823 # This class has the same behavior as `BytesReader`
824 # except for its constructor accepting a `String`.
827 # var reader = new StringReader("a…b")
828 # assert reader.read_char == 'a'
829 # assert reader.read_byte == 0xE2u8 # 1st byte of '…'
830 # assert reader.read_byte == 0x80u8 # 2nd byte of '…'
831 # assert reader.read_char == '�' # Reads the last byte as an invalid char
832 # assert reader.read_all == "b"
839 # Source data to read
842 init do bytes
= source
.to_bytes