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