share/libgc: add a feature to script to use a local version of the source pkgs
[nit.git] / lib / standard / 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 intrude import ropes
15 import error
16
17 in "C" `{
18 #include <unistd.h>
19 #include <string.h>
20 #include <signal.h>
21 `}
22
23 # Any kind of error that could be produced by an operation on Streams
24 class IOError
25 super Error
26 end
27
28 # Any kind of stream to read/write/both to or from a source
29 abstract class Stream
30 # Error produced by the file stream
31 #
32 # var ifs = new FileReader.open("donotmakethisfile.binx")
33 # ifs.read_all
34 # ifs.close
35 # assert ifs.last_error != null
36 var last_error: nullable IOError = null
37
38 # close the stream
39 fun close is abstract
40 end
41
42 # A `Stream` that can be read from
43 abstract class Reader
44 super Stream
45 # Reads a character. Returns `null` on EOF or timeout
46 fun read_char: nullable Char is abstract
47
48 # Reads a byte. Returns `null` on EOF or timeout
49 fun read_byte: nullable Int is abstract
50
51 # Read at most i bytes
52 fun read(i: Int): String
53 do
54 if last_error != null then return ""
55 var s = new FlatBuffer.with_capacity(i)
56 while i > 0 and not eof do
57 var c = read_char
58 if c != null then
59 s.add(c)
60 i -= 1
61 end
62 end
63 return s.to_s
64 end
65
66 # Read a string until the end of the line.
67 #
68 # The line terminator '\n' and '\r\n', if any, is removed in each line.
69 #
70 # ~~~
71 # var txt = "Hello\n\nWorld\n"
72 # var i = new StringReader(txt)
73 # assert i.read_line == "Hello"
74 # assert i.read_line == ""
75 # assert i.read_line == "World"
76 # assert i.eof
77 # ~~~
78 #
79 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
80 # the end or file (EOF) is considered to delimit the end of lines.
81 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
82 #
83 # ~~~
84 # var txt2 = "Hello\r\n\n\rWorld"
85 # var i2 = new StringReader(txt2)
86 # assert i2.read_line == "Hello"
87 # assert i2.read_line == ""
88 # assert i2.read_line == "\rWorld"
89 # assert i2.eof
90 # ~~~
91 #
92 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
93 fun read_line: String
94 do
95 if last_error != null then return ""
96 if eof then return ""
97 var s = new FlatBuffer
98 append_line_to(s)
99 return s.to_s.chomp
100 end
101
102 # Read all the lines until the eof.
103 #
104 # The line terminator '\n' and `\r\n` is removed in each line,
105 #
106 # ~~~
107 # var txt = "Hello\n\nWorld\n"
108 # var i = new StringReader(txt)
109 # assert i.read_lines == ["Hello", "", "World"]
110 # ~~~
111 #
112 # This method is more efficient that splitting
113 # the result of `read_all`.
114 #
115 # NOTE: SEE `read_line` for details.
116 fun read_lines: Array[String]
117 do
118 var res = new Array[String]
119 while not eof do
120 res.add read_line
121 end
122 return res
123 end
124
125 # Return an iterator that read each line.
126 #
127 # The line terminator '\n' and `\r\n` is removed in each line,
128 # The line are read with `read_line`. See this method for details.
129 #
130 # ~~~
131 # var txt = "Hello\n\nWorld\n"
132 # var i = new StringReader(txt)
133 # assert i.each_line.to_a == ["Hello", "", "World"]
134 # ~~~
135 #
136 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
137 # Therefore, the stream should no be closed until the end of the stream.
138 #
139 # ~~~
140 # i = new StringReader(txt)
141 # var el = i.each_line
142 #
143 # assert el.item == "Hello"
144 # el.next
145 # assert el.item == ""
146 # el.next
147 #
148 # i.close
149 #
150 # assert not el.is_ok
151 # # closed before "world" is read
152 # ~~~
153 fun each_line: LineIterator do return new LineIterator(self)
154
155 # Read all the stream until the eof.
156 #
157 # The content of the file is returned verbatim.
158 #
159 # ~~~
160 # var txt = "Hello\n\nWorld\n"
161 # var i = new StringReader(txt)
162 # assert i.read_all == txt
163 # ~~~
164 fun read_all: String
165 do
166 if last_error != null then return ""
167 var s = new FlatBuffer
168 while not eof do
169 var c = read_char
170 if c != null then s.add(c)
171 end
172 return s.to_s
173 end
174
175 # Read a string until the end of the line and append it to `s`.
176 #
177 # Unlike `read_line` and other related methods,
178 # the line terminator '\n', if any, is preserved in each line.
179 # Use the method `Text::chomp` to safely remove it.
180 #
181 # ~~~
182 # var txt = "Hello\n\nWorld\n"
183 # var i = new StringReader(txt)
184 # var b = new FlatBuffer
185 # i.append_line_to(b)
186 # assert b == "Hello\n"
187 # i.append_line_to(b)
188 # assert b == "Hello\n\n"
189 # i.append_line_to(b)
190 # assert b == txt
191 # assert i.eof
192 # ~~~
193 #
194 # If `\n` is not present at the end of the result, it means that
195 # a non-eol terminated last line was returned.
196 #
197 # ~~~
198 # var i2 = new StringReader("hello")
199 # assert not i2.eof
200 # var b2 = new FlatBuffer
201 # i2.append_line_to(b2)
202 # assert b2 == "hello"
203 # assert i2.eof
204 # ~~~
205 #
206 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
207 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
208 fun append_line_to(s: Buffer)
209 do
210 if last_error != null then return
211 loop
212 var x = read_char
213 if x == null then
214 if eof then return
215 else
216 s.chars.push(x)
217 if x == '\n' then return
218 end
219 end
220 end
221
222 # Is there something to read.
223 # This function returns 'false' if there is something to read.
224 fun eof: Bool is abstract
225
226 # Read the next sequence of non whitespace characters.
227 #
228 # Leading whitespace characters are skipped.
229 # The first whitespace character that follows the result is consumed.
230 #
231 # An empty string is returned if the end of the file or an error is encounter.
232 #
233 # ~~~
234 # var w = new StringReader(" Hello, \n\t World!")
235 # assert w.read_word == "Hello,"
236 # assert w.read_char == '\n'.ascii
237 # assert w.read_word == "World!"
238 # assert w.read_word == ""
239 # ~~~
240 #
241 # `Char::is_whitespace` determines what is a whitespace.
242 fun read_word: String
243 do
244 var buf = new FlatBuffer
245 var c = read_nonwhitespace
246 if c != null then
247 buf.add(c)
248 while not eof do
249 c = read_char
250 if c == null then break
251 if c.is_whitespace then break
252 buf.add(c)
253 end
254 end
255 var res = buf.to_s
256 return res
257 end
258
259 # Skip whitespace characters (if any) then return the following non-whitespace character.
260 #
261 # Returns the code point of the character.
262 # Returns `null` on end of file or error.
263 #
264 # In fact, this method works like `read_char` except it skips whitespace.
265 #
266 # ~~~
267 # var w = new StringReader(" \nab\tc")
268 # assert w.read_nonwhitespace == 'a'
269 # assert w.read_nonwhitespace == 'b'
270 # assert w.read_nonwhitespace == 'c'
271 # assert w.read_nonwhitespace == null
272 # ~~~
273 #
274 # `Char::is_whitespace` determines what is a whitespace.
275 fun read_nonwhitespace: nullable Char
276 do
277 var c: nullable Char = null
278 while not eof do
279 c = read_char
280 if c == null or not c.is_whitespace then break
281 end
282 return c
283 end
284 end
285
286 # Iterator returned by `Reader::each_line`.
287 # See the aforementioned method for details.
288 class LineIterator
289 super Iterator[String]
290
291 # The original stream
292 var stream: Reader
293
294 redef fun is_ok
295 do
296 var res = not stream.eof
297 if not res and close_on_finish then stream.close
298 return res
299 end
300
301 redef fun item
302 do
303 var line = self.line
304 if line == null then
305 line = stream.read_line
306 end
307 self.line = line
308 return line
309 end
310
311 # The last line read (cache)
312 private var line: nullable String = null
313
314 redef fun next
315 do
316 # force the read
317 if line == null then item
318 # drop the line
319 line = null
320 end
321
322 # Close the stream when the stream is at the EOF.
323 #
324 # Default is false.
325 var close_on_finish = false is writable
326
327 redef fun finish
328 do
329 if close_on_finish then stream.close
330 end
331 end
332
333 # `Reader` capable of declaring if readable without blocking
334 abstract class PollableReader
335 super Reader
336
337 # Is there something to read? (without blocking)
338 fun poll_in: Bool is abstract
339
340 end
341
342 # A `Stream` that can be written to
343 abstract class Writer
344 super Stream
345 # write a string
346 fun write(s: Text) is abstract
347
348 # Can the stream be used to write
349 fun is_writable: Bool is abstract
350 end
351
352 # Things that can be efficienlty written to a `Writer`
353 #
354 # The point of this interface is to allow the instance to be efficiently
355 # written into a `Writer`.
356 #
357 # Ready-to-save documents usually provide this interface.
358 interface Writable
359 # Write itself to a `stream`
360 # The specific logic it let to the concrete subclasses
361 fun write_to(stream: Writer) is abstract
362
363 # Like `write_to` but return a new String (may be quite large)
364 #
365 # This funtionnality is anectodical, since the point
366 # of streamable object to to be efficienlty written to a
367 # stream without having to allocate and concatenate strings
368 fun write_to_string: String
369 do
370 var stream = new StringWriter
371 write_to(stream)
372 return stream.to_s
373 end
374 end
375
376 redef class Text
377 super Writable
378 redef fun write_to(stream) do stream.write(self)
379 end
380
381 # Input streams with a buffered input for efficiency purposes
382 abstract class BufferedReader
383 super Reader
384 redef fun read_char
385 do
386 if last_error != null then return null
387 if eof then
388 last_error = new IOError("Stream has reached eof")
389 return null
390 end
391 var c = _buffer[_buffer_pos]
392 _buffer_pos += 1
393 return c
394 end
395
396 redef fun read_byte
397 do
398 if last_error != null then return null
399 if eof then
400 last_error = new IOError("Stream has reached eof")
401 return null
402 end
403 var c = _buffer[_buffer_pos].ascii
404 _buffer_pos += 1
405 return c
406 end
407
408 # Peeks up to `n` bytes in the buffer, returns an empty string on EOF
409 #
410 # The operation does not consume the buffer
411 #
412 # ~~~nitish
413 # var x = new FileReader("File.txt")
414 # assert x.peek(5) == x.read(5)
415 # ~~~
416 fun peek(i: Int): String do
417 if eof then return ""
418 var b = new FlatBuffer.with_capacity(i)
419 while i > 0 and not eof do
420 b.add _buffer[_buffer_pos]
421 _buffer_pos += 1
422 i -= 1
423 end
424 var nbuflen = b.length + (_buffer.length - _buffer_pos)
425 var nbuf = new FlatBuffer.with_capacity(nbuflen)
426 nbuf.append(b)
427 while _buffer_pos < _buffer.length do
428 nbuf.add(_buffer[_buffer_pos])
429 _buffer_pos += 1
430 end
431 _buffer_pos = 0
432 _buffer = nbuf
433 return b.to_s
434 end
435
436 redef fun read(i)
437 do
438 if last_error != null then return ""
439 if eof then return ""
440 var p = _buffer_pos
441 var bufsp = _buffer.length - p
442 if bufsp >= i then
443 _buffer_pos += i
444 return _buffer.substring(p, i).to_s
445 end
446 _buffer_pos = _buffer.length
447 var readln = _buffer.length - p
448 var s = _buffer.substring(p, readln).to_s
449 fill_buffer
450 return s + read(i - readln)
451 end
452
453 redef fun read_all
454 do
455 if last_error != null then return ""
456 var s = new FlatBuffer
457 while not eof do
458 var j = _buffer_pos
459 var k = _buffer.length
460 while j < k do
461 s.add(_buffer[j])
462 j += 1
463 end
464 _buffer_pos = j
465 fill_buffer
466 end
467 return s.to_s
468 end
469
470 redef fun append_line_to(s)
471 do
472 loop
473 # First phase: look for a '\n'
474 var i = _buffer_pos
475 while i < _buffer.length and _buffer[i] != '\n' do i += 1
476
477 var eol
478 if i < _buffer.length then
479 assert _buffer[i] == '\n'
480 i += 1
481 eol = true
482 else
483 eol = false
484 end
485
486 # if there is something to append
487 if i > _buffer_pos then
488 # Enlarge the string (if needed)
489 s.enlarge(s.length + i - _buffer_pos)
490
491 # Copy from the buffer to the string
492 var j = _buffer_pos
493 while j < i do
494 s.add(_buffer[j])
495 j += 1
496 end
497 _buffer_pos = i
498 else
499 assert end_reached
500 return
501 end
502
503 if eol then
504 # so \n is found
505 return
506 else
507 # so \n is not found
508 if end_reached then return
509 fill_buffer
510 end
511 end
512 end
513
514 redef fun eof
515 do
516 if _buffer_pos < _buffer.length then return false
517 if end_reached then return true
518 fill_buffer
519 return _buffer_pos >= _buffer.length and end_reached
520 end
521
522 # The buffer
523 private var buffer: nullable FlatBuffer = null
524
525 # The current position in the buffer
526 private var buffer_pos: Int = 0
527
528 # Fill the buffer
529 protected fun fill_buffer is abstract
530
531 # Is the last fill_buffer reach the end
532 protected fun end_reached: Bool is abstract
533
534 # Allocate a `_buffer` for a given `capacity`.
535 protected fun prepare_buffer(capacity: Int)
536 do
537 _buffer = new FlatBuffer.with_capacity(capacity)
538 _buffer_pos = 0 # need to read
539 end
540 end
541
542 # A `Stream` that can be written to and read from
543 abstract class Duplex
544 super Reader
545 super Writer
546 end
547
548 # `Stream` that can be used to write to a `String`
549 #
550 # Mainly used for compatibility with Writer type and tests.
551 class StringWriter
552 super Writer
553
554 private var content = new Array[String]
555 redef fun to_s do return content.to_s
556 redef fun is_writable do return not closed
557 redef fun write(str)
558 do
559 assert not closed
560 content.add(str.to_s)
561 end
562
563 # Is the stream closed?
564 protected var closed = false
565
566 redef fun close do closed = true
567 end
568
569 # `Stream` used to read from a `String`
570 #
571 # Mainly used for compatibility with Reader type and tests.
572 class StringReader
573 super Reader
574
575 # The string to read from.
576 var source: String
577
578 # The current position in the string.
579 private var cursor: Int = 0
580
581 redef fun read_char do
582 if cursor < source.length then
583 var c = source[cursor]
584
585 cursor += 1
586 return c
587 else
588 return null
589 end
590 end
591
592 redef fun read_byte do
593 if cursor < source.length then
594 var c = source[cursor]
595
596 cursor += 1
597 return c.ascii
598 else
599 return null
600 end
601 end
602
603 redef fun close do
604 source = ""
605 end
606
607 redef fun read_all do
608 var c = cursor
609 cursor = source.length
610 if c == 0 then return source
611 return source.substring_from(c)
612 end
613
614 redef fun eof do return cursor >= source.length
615 end