lib/core: add blocking eof implementation
[nit.git] / lib / core / stream.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
9 # another product.
10
11 # Input and output streams of characters
12 module stream
13
14 import error
15 intrude import bytes
16 import codecs
17
18 in "C" `{
19 #include <unistd.h>
20 #include <string.h>
21 #include <signal.h>
22 `}
23
24 # Any kind of error that could be produced by an operation on Streams
25 class IOError
26 super Error
27 end
28
29 # Any kind of stream to read/write/both to or from a source
30 abstract class Stream
31 # Codec used to transform raw data to text
32 #
33 # Note: defaults to UTF-8
34 var codec: Codec = utf8_codec is protected writable(set_codec)
35
36 # Lookahead buffer for codecs
37 #
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
40 # is read.
41 protected var lookahead: CString is noinit
42
43 # Capacity of the lookahead
44 protected var lookahead_capacity = 0
45
46 # Current occupation of the lookahead
47 protected var lookahead_length = 0
48
49 # Buffer for writing data to a stream
50 protected var write_buffer: CString is noinit
51
52 init do
53 var lcap = codec.max_lookahead
54 lookahead = new CString(lcap)
55 write_buffer = new CString(lcap)
56 lookahead_length = 0
57 lookahead_capacity = lcap
58 end
59
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
66 if llen > 0 then
67 lookahead.copy_to(lk, llen, 0, 0)
68 end
69 lookahead = lk
70 lookahead_capacity = lcap
71 write_buffer = new CString(lcap)
72 end
73 set_codec(c)
74 end
75
76 # Error produced by the file stream
77 #
78 # var ifs = new FileReader.open("donotmakethisfile.binx")
79 # ifs.read_all
80 # ifs.close
81 # assert ifs.last_error != null
82 var last_error: nullable IOError = null
83
84 # close the stream
85 fun close is abstract
86
87 # Pre-work hook.
88 #
89 # Used to inform `self` that operations will start.
90 # Specific streams can use this to prepare some resources.
91 #
92 # Is automatically invoked at the beginning of `with` structures.
93 #
94 # Do nothing by default.
95 fun start do end
96
97 # Post-work hook.
98 #
99 # Used to inform `self` that the operations are over.
100 # Specific streams can use this to free some resources.
101 #
102 # Is automatically invoked at the end of `with` structures.
103 #
104 # call `close` by default.
105 fun finish do close
106 end
107
108 # A `Stream` that can be read from
109 abstract class Reader
110 super Stream
111
112 # Read a byte directly from the underlying stream, without
113 # considering any eventual buffer
114 protected fun raw_read_byte: Int is abstract
115
116 # Read at most `max` bytes from the underlying stream into `buf`,
117 # without considering any eventual buffer
118 #
119 # Returns how many bytes were read
120 protected fun raw_read_bytes(buf: CString, max: Int): Int do
121 var rd = 0
122 for i in [0 .. max[ do
123 var b = raw_read_byte
124 if b < 0 then break
125 buf[i] = b.to_b
126 rd += 1
127 end
128 return rd
129 end
130
131 # Reads a character. Returns `null` on EOF or timeout
132 #
133 # Returns unicode replacement character '�' if an
134 # invalid byte sequence is read.
135 #
136 # `read_char` may block if:
137 #
138 # * No byte could be read from the current buffer
139 # * An incomplete char is partially read, and more bytes are
140 # required for full decoding.
141 fun read_char: nullable Char do
142 if eof then return null
143 var cod = codec
144 var codet_sz = cod.codet_size
145 var lk = lookahead
146 var llen = lookahead_length
147 if llen < codet_sz then
148 llen += raw_read_bytes(lk.fast_cstring(llen), codet_sz - llen)
149 end
150 if llen < codet_sz then
151 lookahead_length = 0
152 return 0xFFFD.code_point
153 end
154 var ret = cod.is_valid_char(lk, codet_sz)
155 var max_llen = cod.max_lookahead
156 while ret == 1 and llen < max_llen do
157 var rd = raw_read_bytes(lk.fast_cstring(llen), codet_sz)
158 if rd < codet_sz then
159 llen -= codet_sz
160 if llen > 0 then
161 lookahead.lshift(codet_sz, llen, codet_sz)
162 end
163 lookahead_length = llen.max(0)
164 return 0xFFFD.code_point
165 end
166 llen += codet_sz
167 ret = cod.is_valid_char(lk, llen)
168 end
169 if ret == 0 then
170 var c = cod.decode_char(lk)
171 var clen = c.u8char_len
172 llen -= clen
173 if llen > 0 then
174 lookahead.lshift(clen, llen, clen)
175 end
176 lookahead_length = llen
177 return c
178 end
179 if ret == 2 or ret == 1 then
180 llen -= codet_sz
181 if llen > 0 then
182 lookahead.lshift(codet_sz, llen, codet_sz)
183 end
184 lookahead_length = llen
185 return 0xFFFD.code_point
186 end
187 # Should not happen if the decoder works properly
188 var arr = new Array[Object]
189 arr.push "Decoder error: could not decode nor recover from byte sequence ["
190 for i in [0 .. llen[ do
191 arr.push lk[i]
192 arr.push ", "
193 end
194 arr.push "]"
195 var err = new IOError(arr.plain_to_s)
196 err.cause = last_error
197 last_error = err
198 return 0xFFFD.code_point
199 end
200
201 # Reads a byte. Returns a negative value on error
202 fun read_byte: Int do
203 var llen = lookahead_length
204 if llen == 0 then return raw_read_byte
205 var lk = lookahead
206 var b = lk[0].to_i
207 if llen == 1 then
208 lookahead_length = 0
209 else
210 lk.lshift(1, llen - 1, 1)
211 lookahead_length -= 1
212 end
213 return b
214 end
215
216 # Reads a String of at most `i` length
217 fun read(i: Int): String do
218 var cs = new CString(i)
219 var rd = read_bytes_to_cstring(cs, i)
220 return codec.decode_string(cs, rd)
221 end
222
223 # Reads up to `max` bytes from source
224 fun read_bytes(max: Int): Bytes do
225 var cs = new CString(max)
226 var rd = read_bytes_to_cstring(cs, max)
227 return new Bytes(cs, rd, max)
228 end
229
230 # Reads up to `max` bytes from source and stores them in `bytes`
231 fun read_bytes_to_cstring(bytes: CString, max: Int): Int do
232 var llen = lookahead_length
233 if llen == 0 then return raw_read_bytes(bytes, max)
234 var rd = max.min(llen)
235 var lk = lookahead
236 lk.copy_to(bytes, rd, 0, 0)
237 if rd < llen then
238 lk.lshift(rd, llen - rd, rd)
239 lookahead_length -= rd
240 else
241 lookahead_length = 0
242 end
243 return rd + raw_read_bytes(bytes.fast_cstring(rd), max - rd)
244 end
245
246 # Read a string until the end of the line.
247 #
248 # The line terminator '\n' and '\r\n', if any, is removed in each line.
249 #
250 # ~~~
251 # var txt = "Hello\n\nWorld\n"
252 # var i = new StringReader(txt)
253 # assert i.read_line == "Hello"
254 # assert i.read_line == ""
255 # assert i.read_line == "World"
256 # assert i.eof
257 # ~~~
258 #
259 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
260 # the end or file (EOF) is considered to delimit the end of lines.
261 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
262 #
263 # ~~~
264 # var txt2 = "Hello\r\n\n\rWorld"
265 # var i2 = new StringReader(txt2)
266 # assert i2.read_line == "Hello"
267 # assert i2.read_line == ""
268 # assert i2.read_line == "\rWorld"
269 # assert i2.eof
270 # ~~~
271 #
272 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
273 fun read_line: String
274 do
275 if last_error != null then return ""
276 if eof then return ""
277 var s = new FlatBuffer
278 append_line_to(s)
279 return s.to_s.chomp
280 end
281
282 # Read all the lines until the eof.
283 #
284 # The line terminator '\n' and `\r\n` is removed in each line,
285 #
286 # ~~~
287 # var txt = "Hello\n\nWorld\n"
288 # var i = new StringReader(txt)
289 # assert i.read_lines == ["Hello", "", "World"]
290 # ~~~
291 #
292 # This method is more efficient that splitting
293 # the result of `read_all`.
294 #
295 # NOTE: SEE `read_line` for details.
296 fun read_lines: Array[String]
297 do
298 var res = new Array[String]
299 while not eof do
300 res.add read_line
301 end
302 return res
303 end
304
305 # Return an iterator that read each line.
306 #
307 # The line terminator '\n' and `\r\n` is removed in each line,
308 # The line are read with `read_line`. See this method for details.
309 #
310 # ~~~
311 # var txt = "Hello\n\nWorld\n"
312 # var i = new StringReader(txt)
313 # assert i.each_line.to_a == ["Hello", "", "World"]
314 # ~~~
315 #
316 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
317 # Therefore, the stream should no be closed until the end of the stream.
318 #
319 # ~~~
320 # i = new StringReader(txt)
321 # var el = i.each_line
322 #
323 # assert el.item == "Hello"
324 # el.next
325 # assert el.item == ""
326 # el.next
327 #
328 # i.close
329 #
330 # assert not el.is_ok
331 # # closed before "world" is read
332 # ~~~
333 fun each_line: LineIterator do return new LineIterator(self)
334
335 # Read all the stream until the eof.
336 #
337 # The content of the file is returned as a String.
338 #
339 # ~~~
340 # var txt = "Hello\n\nWorld\n"
341 # var i = new StringReader(txt)
342 # assert i.read_all == txt
343 # ~~~
344 fun read_all: String do
345 var s = read_all_bytes
346 var slen = s.length
347 if slen == 0 then return ""
348 return codec.decode_string(s.items, s.length)
349 end
350
351 # Read all the stream until the eof.
352 #
353 # The content of the file is returned verbatim.
354 fun read_all_bytes: Bytes
355 do
356 if last_error != null then return new Bytes.empty
357 var s = new Bytes.empty
358 var buf = new CString(4096)
359 while not eof do
360 var rd = read_bytes_to_cstring(buf, 4096)
361 s.append_ns(buf, rd)
362 end
363 return s
364 end
365
366 # Read a string until the end of the line and append it to `s`.
367 #
368 # Unlike `read_line` and other related methods,
369 # the line terminator '\n', if any, is preserved in each line.
370 # Use the method `Text::chomp` to safely remove it.
371 #
372 # ~~~
373 # var txt = "Hello\n\nWorld\n"
374 # var i = new StringReader(txt)
375 # var b = new FlatBuffer
376 # i.append_line_to(b)
377 # assert b == "Hello\n"
378 # i.append_line_to(b)
379 # assert b == "Hello\n\n"
380 # i.append_line_to(b)
381 # assert b == txt
382 # assert i.eof
383 # ~~~
384 #
385 # If `\n` is not present at the end of the result, it means that
386 # a non-eol terminated last line was returned.
387 #
388 # ~~~
389 # var i2 = new StringReader("hello")
390 # assert not i2.eof
391 # var b2 = new FlatBuffer
392 # i2.append_line_to(b2)
393 # assert b2 == "hello"
394 # assert i2.eof
395 # ~~~
396 #
397 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
398 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
399 fun append_line_to(s: Buffer)
400 do
401 if last_error != null then return
402 loop
403 var x = read_char
404 if x == null then
405 if eof then return
406 else
407 s.chars.push(x)
408 if x == '\n' then return
409 end
410 end
411 end
412
413 # Is there something to read.
414 # This function returns 'false' if there is something to read.
415 fun eof: Bool do
416 if lookahead_length > 0 then return false
417 lookahead_length = raw_read_bytes(lookahead, 1)
418 return lookahead_length <= 0
419 end
420
421 # Read the next sequence of non whitespace characters.
422 #
423 # Leading whitespace characters are skipped.
424 # The first whitespace character that follows the result is consumed.
425 #
426 # An empty string is returned if the end of the file or an error is encounter.
427 #
428 # ~~~
429 # var w = new StringReader(" Hello, \n\t World!")
430 # assert w.read_word == "Hello,"
431 # assert w.read_char == '\n'
432 # assert w.read_word == "World!"
433 # assert w.read_word == ""
434 # ~~~
435 #
436 # `Char::is_whitespace` determines what is a whitespace.
437 fun read_word: String
438 do
439 var buf = new FlatBuffer
440 var c = read_nonwhitespace
441 if c != null then
442 buf.add(c)
443 while not eof do
444 c = read_char
445 if c == null then break
446 if c.is_whitespace then break
447 buf.add(c)
448 end
449 end
450 var res = buf.to_s
451 return res
452 end
453
454 # Skip whitespace characters (if any) then return the following non-whitespace character.
455 #
456 # Returns the code point of the character.
457 # Returns `null` on end of file or error.
458 #
459 # In fact, this method works like `read_char` except it skips whitespace.
460 #
461 # ~~~
462 # var w = new StringReader(" \nab\tc")
463 # assert w.read_nonwhitespace == 'a'
464 # assert w.read_nonwhitespace == 'b'
465 # assert w.read_nonwhitespace == 'c'
466 # assert w.read_nonwhitespace == null
467 # ~~~
468 #
469 # `Char::is_whitespace` determines what is a whitespace.
470 fun read_nonwhitespace: nullable Char
471 do
472 var c: nullable Char = null
473 while not eof do
474 c = read_char
475 if c == null or not c.is_whitespace then break
476 end
477 return c
478 end
479 end
480
481 # Iterator returned by `Reader::each_line`.
482 # See the aforementioned method for details.
483 class LineIterator
484 super Iterator[String]
485
486 # The original stream
487 var stream: Reader
488
489 redef fun is_ok
490 do
491 var res = not stream.eof
492 if not res and close_on_finish then stream.close
493 return res
494 end
495
496 redef fun item
497 do
498 var line = self.line
499 if line == null then
500 line = stream.read_line
501 end
502 self.line = line
503 return line
504 end
505
506 # The last line read (cache)
507 private var line: nullable String = null
508
509 redef fun next
510 do
511 # force the read
512 if line == null then item
513 # drop the line
514 line = null
515 end
516
517 # Close the stream when the stream is at the EOF.
518 #
519 # Default is false.
520 var close_on_finish = false is writable
521
522 redef fun finish
523 do
524 if close_on_finish then stream.close
525 end
526 end
527
528 # `Reader` capable of declaring if readable without blocking
529 abstract class PollableReader
530 super Reader
531
532 # Is there something to read? (without blocking)
533 fun poll_in: Bool is abstract
534
535 end
536
537 # A `Stream` that can be written to
538 abstract class Writer
539 super Stream
540
541 # Write bytes from `s`
542 fun write_bytes(s: Bytes) do write_bytes_from_cstring(s.items, s.length)
543
544 # Write `len` bytes from `ns`
545 fun write_bytes_from_cstring(ns: CString, len: Int) is abstract
546
547 # Write a string
548 fun write(s: Text) is abstract
549
550 # Write a single byte
551 fun write_byte(value: Byte) is abstract
552
553 # Write a single char
554 fun write_char(c: Char) do
555 var ln = codec.add_char_to(c, write_buffer)
556 write_bytes_from_cstring(write_buffer, ln)
557 end
558
559 # Can the stream be used to write
560 fun is_writable: Bool is abstract
561 end
562
563 # Things that can be efficienlty written to a `Writer`
564 #
565 # The point of this interface is to allow the instance to be efficiently
566 # written into a `Writer`.
567 #
568 # Ready-to-save documents usually provide this interface.
569 interface Writable
570 # Write itself to a `stream`
571 # The specific logic it let to the concrete subclasses
572 fun write_to(stream: Writer) is abstract
573
574 # Like `write_to` but return a new String (may be quite large)
575 #
576 # This funtionality is anectodical, since the point
577 # of streamable object to to be efficienlty written to a
578 # stream without having to allocate and concatenate strings
579 fun write_to_string: String
580 do
581 var stream = new StringWriter
582 write_to(stream)
583 return stream.to_s
584 end
585 end
586
587 redef class Bytes
588 super Writable
589 redef fun write_to(s) do s.write_bytes(self)
590
591 redef fun write_to_string do return to_s
592 end
593
594 redef class Text
595 super Writable
596 redef fun write_to(stream) do stream.write(self)
597 end
598
599 # Input streams with a buffered input for efficiency purposes
600 abstract class BufferedReader
601 super Reader
602
603 redef fun raw_read_byte
604 do
605 if last_error != null then return -1
606 if eof then
607 last_error = new IOError("Stream has reached eof")
608 return -1
609 end
610 var c = _buffer[_buffer_pos]
611 _buffer_pos += 1
612 return c.to_i
613 end
614
615 # Resets the internal buffer
616 fun buffer_reset do
617 _buffer_length = 0
618 _buffer_pos = 0
619 end
620
621 # Peeks up to `n` bytes in the buffer
622 #
623 # The operation does not consume the buffer
624 #
625 # ~~~nitish
626 # var x = new FileReader.open("File.txt")
627 # assert x.peek(5) == x.read(5)
628 # ~~~
629 fun peek(i: Int): Bytes do
630 if eof then return new Bytes.empty
631 var remsp = _buffer_length - _buffer_pos
632 if i <= remsp then
633 var bf = new Bytes.with_capacity(i)
634 bf.append_ns_from(_buffer, i, _buffer_pos)
635 return bf
636 end
637 var bf = new Bytes.with_capacity(i)
638 bf.append_ns_from(_buffer, remsp, _buffer_pos)
639 _buffer_pos = _buffer_length
640 read_intern(i - bf.length, bf)
641 remsp = _buffer_length - _buffer_pos
642 var full_len = bf.length + remsp
643 if full_len > _buffer_capacity then
644 var c = _buffer_capacity
645 while c < full_len do c = c * 2 + 2
646 _buffer_capacity = c
647 end
648 var nns = new CString(_buffer_capacity)
649 bf.items.copy_to(nns, bf.length, 0, 0)
650 _buffer.copy_to(nns, remsp, _buffer_pos, bf.length)
651 _buffer = nns
652 _buffer_pos = 0
653 _buffer_length = full_len
654 return bf
655 end
656
657 redef fun read_bytes_to_cstring(buf, i)
658 do
659 if last_error != null then return 0
660 var bbf = new Bytes(buf, 0, i)
661 return read_intern(i, bbf)
662 end
663
664 # Fills `buf` with at most `i` bytes read from `self`
665 private fun read_intern(i: Int, buf: Bytes): Int do
666 if eof then return 0
667 var p = _buffer_pos
668 var bufsp = _buffer_length - p
669 if bufsp >= i then
670 _buffer_pos += i
671 buf.append_ns_from(_buffer, i, p)
672 return i
673 end
674 _buffer_pos = _buffer_length
675 var readln = _buffer_length - p
676 buf.append_ns_from(_buffer, readln, p)
677 var rd = read_intern(i - readln, buf)
678 return rd + readln
679 end
680
681 redef fun read_all_bytes
682 do
683 if last_error != null then return new Bytes.empty
684 var s = new Bytes.with_capacity(10)
685 var b = _buffer
686 while not eof do
687 var j = _buffer_pos
688 var k = _buffer_length
689 var rd_sz = k - j
690 s.append_ns_from(b, rd_sz, j)
691 _buffer_pos = k
692 fill_buffer
693 end
694 return s
695 end
696
697 redef fun append_line_to(s)
698 do
699 var lb = new Bytes.with_capacity(10)
700 loop
701 # First phase: look for a '\n'
702 var i = _buffer_pos
703 while i < _buffer_length and _buffer[i] != 0xAu8 do
704 i += 1
705 end
706
707 var eol
708 if i < _buffer_length then
709 assert _buffer[i] == 0xAu8
710 i += 1
711 eol = true
712 else
713 eol = false
714 end
715
716 # if there is something to append
717 if i > _buffer_pos then
718 # Copy from the buffer to the string
719 var j = _buffer_pos
720 while j < i do
721 lb.add(_buffer[j])
722 j += 1
723 end
724 _buffer_pos = i
725 else
726 assert end_reached
727 s.append lb.to_s
728 return
729 end
730
731 if eol then
732 # so \n is found
733 s.append lb.to_s
734 return
735 else
736 # so \n is not found
737 if end_reached then
738 s.append lb.to_s
739 return
740 end
741 fill_buffer
742 end
743 end
744 end
745
746 redef fun eof
747 do
748 if _buffer_pos < _buffer_length then return false
749 if end_reached then return true
750 fill_buffer
751 return _buffer_pos >= _buffer_length and end_reached
752 end
753
754 # The buffer
755 private var buffer: CString = new CString(0)
756
757 # The current position in the buffer
758 private var buffer_pos = 0
759
760 # Length of the current buffer (i.e. nuber of bytes in the buffer)
761 private var buffer_length = 0
762
763 # Capacity of the buffer
764 private var buffer_capacity = 0
765
766 # Fill the buffer
767 protected fun fill_buffer is abstract
768
769 # Has the last fill_buffer reached the end
770 protected fun end_reached: Bool is abstract
771
772 # Allocate a `_buffer` for a given `capacity`.
773 protected fun prepare_buffer(capacity: Int)
774 do
775 _buffer = new CString(capacity)
776 _buffer_pos = 0 # need to read
777 _buffer_length = 0
778 _buffer_capacity = capacity
779 end
780 end
781
782 # A `Stream` that can be written to and read from
783 abstract class Duplex
784 super Reader
785 super Writer
786 end
787
788 # Write to `bytes` in memory
789 #
790 # ~~~
791 # var writer = new BytesWriter
792 #
793 # writer.write "Strings "
794 # writer.write_char '&'
795 # writer.write_byte 0x20u8
796 # writer.write_bytes "bytes".to_bytes
797 #
798 # assert writer.to_s == "\\x53\\x74\\x72\\x69\\x6E\\x67\\x73\\x20\\x26\\x20\\x62\\x79\\x74\\x65\\x73"
799 # assert writer.bytes.to_s == "Strings & bytes"
800 # ~~~
801 #
802 # As with any binary data, UTF-8 code points encoded on two bytes or more
803 # can be constructed byte by byte.
804 #
805 # ~~~
806 # writer = new BytesWriter
807 #
808 # # Write just the character first half
809 # writer.write_byte 0xC2u8
810 # assert writer.to_s == "\\xC2"
811 # assert writer.bytes.to_s == "�"
812 #
813 # # Complete the character
814 # writer.write_byte 0xA2u8
815 # assert writer.to_s == "\\xC2\\xA2"
816 # assert writer.bytes.to_s == "¢"
817 # ~~~
818 class BytesWriter
819 super Writer
820
821 # Written memory
822 var bytes = new Bytes.empty
823
824 redef fun to_s do return bytes.chexdigest
825
826 redef fun write(str)
827 do
828 if closed then return
829 str.append_to_bytes bytes
830 end
831
832 redef fun write_char(c)
833 do
834 if closed then return
835 bytes.add_char c
836 end
837
838 redef fun write_byte(value)
839 do
840 if closed then return
841 bytes.add value
842 end
843
844 redef fun write_bytes_from_cstring(ns, len) do
845 if closed then return
846 bytes.append_ns(ns, len)
847 end
848
849 # Is the stream closed?
850 protected var closed = false
851
852 redef fun close do closed = true
853 redef fun is_writable do return not closed
854 end
855
856 # `Stream` writing to a `String`
857 #
858 # This class has the same behavior as `BytesWriter`
859 # except for `to_s` which decodes `bytes` to a string.
860 #
861 # ~~~
862 # var writer = new StringWriter
863 #
864 # writer.write "Strings "
865 # writer.write_char '&'
866 # writer.write_byte 0x20u8
867 # writer.write_bytes "bytes".to_bytes
868 #
869 # assert writer.to_s == "Strings & bytes"
870 # ~~~
871 class StringWriter
872 super BytesWriter
873
874 redef fun to_s do return bytes.to_s
875 end
876
877 # Read from `bytes` in memory
878 #
879 # ~~~
880 # var reader = new BytesReader(b"a…b")
881 # assert reader.read_char == 'a'
882 # assert reader.read_byte == 0xE2 # 1st byte of '…'
883 # assert reader.read_byte == 0x80 # 2nd byte of '…'
884 # assert reader.read_char == '�' # Reads the last byte as an invalid char
885 # assert reader.read_all_bytes == b"b"
886 # ~~~
887 class BytesReader
888 super Reader
889
890 # Source data to read
891 var bytes: Bytes
892
893 # The current position in `bytes`
894 private var cursor = 0
895
896 redef fun raw_read_byte
897 do
898 if cursor >= bytes.length then return -1
899
900 var c = bytes[cursor]
901 cursor += 1
902 return c.to_i
903 end
904
905 redef fun close do bytes = new Bytes.empty
906
907 redef fun read_all_bytes
908 do
909 var res = bytes.slice_from(cursor)
910 cursor = bytes.length
911 return res
912 end
913
914 redef fun raw_read_bytes(ns, max) do
915 if cursor >= bytes.length then return 0
916
917 var copy = max.min(bytes.length - cursor)
918 bytes.items.copy_to(ns, copy, cursor, 0)
919 cursor += copy
920 return copy
921 end
922
923 redef fun eof do return cursor >= bytes.length
924 end
925
926 # `Stream` reading from a `String` source
927 #
928 # This class has the same behavior as `BytesReader`
929 # except for its constructor accepting a `String`.
930 #
931 # ~~~
932 # var reader = new StringReader("a…b")
933 # assert reader.read_char == 'a'
934 # assert reader.read_byte == 0xE2 # 1st byte of '…'
935 # assert reader.read_byte == 0x80 # 2nd byte of '…'
936 # assert reader.read_char == '�' # Reads the last byte as an invalid char
937 # assert reader.read_all == "b"
938 # ~~~
939 class StringReader
940 super BytesReader
941
942 autoinit source
943
944 # Source data to read
945 var source: String
946
947 init do bytes = source.to_bytes
948
949 redef fun close
950 do
951 source = ""
952 super
953 end
954 end