lib/stream: document `read_all`
[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
16 in "C" `{
17 #include <unistd.h>
18 #include <poll.h>
19 #include <errno.h>
20 #include <string.h>
21 #include <signal.h>
22 `}
23
24 # Abstract stream class
25 interface IOS
26 # close the stream
27 fun close is abstract
28 end
29
30 # Abstract input streams
31 interface IStream
32 super IOS
33 # Read a character. Return its ASCII value, -1 on EOF or timeout
34 fun read_char: Int is abstract
35
36 # Read at most i bytes
37 fun read(i: Int): String
38 do
39 var s = new FlatBuffer.with_capacity(i)
40 while i > 0 and not eof do
41 var c = read_char
42 if c >= 0 then
43 s.add(c.ascii)
44 i -= 1
45 end
46 end
47 return s.to_s
48 end
49
50 # Read a string until the end of the line.
51 #
52 # The line terminator '\n' and '\r\n', if any, is removed in each line.
53 #
54 # ~~~
55 # var txt = "Hello\n\nWorld\n"
56 # var i = new StringIStream(txt)
57 # assert i.read_line == "Hello"
58 # assert i.read_line == ""
59 # assert i.read_line == "World"
60 # assert i.eof
61 # ~~~
62 #
63 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
64 # the end or file (EOF) is considered to delimit the end of lines.
65 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
66 #
67 # ~~~
68 # var txt2 = "Hello\r\n\n\rWorld"
69 # var i2 = new StringIStream(txt2)
70 # assert i2.read_line == "Hello"
71 # assert i2.read_line == ""
72 # assert i2.read_line == "\rWorld"
73 # assert i2.eof
74 # ~~~
75 #
76 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
77 fun read_line: String
78 do
79 if eof then return ""
80 var s = new FlatBuffer
81 append_line_to(s)
82 return s.to_s.chomp
83 end
84
85 # Read all the lines until the eof.
86 #
87 # The line terminator '\n' and `\r\n` is removed in each line,
88 #
89 # ~~~
90 # var txt = "Hello\n\nWorld\n"
91 # var i = new StringIStream(txt)
92 # assert i.read_lines == ["Hello", "", "World"]
93 # ~~~
94 #
95 # This method is more efficient that splitting
96 # the result of `read_all`.
97 #
98 # NOTE: SEE `read_line` for details.
99 fun read_lines: Array[String]
100 do
101 var res = new Array[String]
102 while not eof do
103 res.add read_line
104 end
105 return res
106 end
107
108 # Read all the stream until the eof.
109 #
110 # The content of the file is returned verbatim.
111 #
112 # ~~~
113 # var txt = "Hello\n\nWorld\n"
114 # var i = new StringIStream(txt)
115 # assert i.read_all == txt
116 # ~~~
117 fun read_all: String
118 do
119 var s = new FlatBuffer
120 while not eof do
121 var c = read_char
122 if c >= 0 then s.add(c.ascii)
123 end
124 return s.to_s
125 end
126
127 # Read a string until the end of the line and append it to `s`.
128 #
129 # Unlike `read_line` and other related methods,
130 # the line terminator '\n', if any, is preserved in each line.
131 # Use the method `Text::chomp` to safely remove it.
132 #
133 # ~~~
134 # var txt = "Hello\n\nWorld\n"
135 # var i = new StringIStream(txt)
136 # var b = new FlatBuffer
137 # i.append_line_to(b)
138 # assert b == "Hello\n"
139 # i.append_line_to(b)
140 # assert b == "Hello\n\n"
141 # i.append_line_to(b)
142 # assert b == txt
143 # assert i.eof
144 # ~~~
145 #
146 # If `\n` is not present at the end of the result, it means that
147 # a non-eol terminated last line was returned.
148 #
149 # ~~~
150 # var i2 = new StringIStream("hello")
151 # assert not i2.eof
152 # var b2 = new FlatBuffer
153 # i2.append_line_to(b2)
154 # assert b2 == "hello"
155 # assert i2.eof
156 # ~~~
157 #
158 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
159 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
160 fun append_line_to(s: Buffer)
161 do
162 loop
163 var x = read_char
164 if x == -1 then
165 if eof then return
166 else
167 var c = x.ascii
168 s.chars.push(c)
169 if c == '\n' then return
170 end
171 end
172 end
173
174 # Is there something to read.
175 # This function returns 'false' if there is something to read.
176 fun eof: Bool is abstract
177 end
178
179 # IStream capable of declaring if readable without blocking
180 interface PollableIStream
181 super IStream
182
183 # Is there something to read? (without blocking)
184 fun poll_in: Bool is abstract
185
186 end
187
188 # Abstract output stream
189 interface OStream
190 super IOS
191 # write a string
192 fun write(s: Text) is abstract
193
194 # Can the stream be used to write
195 fun is_writable: Bool is abstract
196 end
197
198 # Things that can be efficienlty writen to a OStream
199 #
200 # The point of this interface it to allow is instance to be efficenty
201 # writen into a OStream without having to allocate a big String object
202 #
203 # ready-to-save documents usually provide this interface.
204 interface Streamable
205 # Write itself to a `stream`
206 # The specific logic it let to the concrete subclasses
207 fun write_to(stream: OStream) is abstract
208
209 # Like `write_to` but return a new String (may be quite large)
210 #
211 # This funtionnality is anectodical, since the point
212 # of streamable object to to be efficienlty written to a
213 # stream without having to allocate and concatenate strings
214 fun write_to_string: String
215 do
216 var stream = new StringOStream
217 write_to(stream)
218 return stream.to_s
219 end
220 end
221
222 redef class Text
223 super Streamable
224 redef fun write_to(stream) do stream.write(self)
225 end
226
227 # Input streams with a buffer
228 abstract class BufferedIStream
229 super IStream
230 redef fun read_char
231 do
232 if _buffer_pos >= _buffer.length then
233 fill_buffer
234 end
235 if _buffer_pos >= _buffer.length then
236 return -1
237 end
238 var c = _buffer.chars[_buffer_pos]
239 _buffer_pos += 1
240 return c.ascii
241 end
242
243 redef fun read(i)
244 do
245 if _buffer.length == _buffer_pos then
246 if not eof then
247 return read(i)
248 end
249 return ""
250 end
251 if _buffer_pos + i >= _buffer.length then
252 var from = _buffer_pos
253 _buffer_pos = _buffer.length
254 return _buffer.substring_from(from).to_s
255 end
256 _buffer_pos += i
257 return _buffer.substring(_buffer_pos - i, i).to_s
258 end
259
260 redef fun read_all
261 do
262 var s = new FlatBuffer
263 while not eof do
264 var j = _buffer_pos
265 var k = _buffer.length
266 while j < k do
267 s.add(_buffer[j])
268 j += 1
269 end
270 _buffer_pos = j
271 fill_buffer
272 end
273 return s.to_s
274 end
275
276 redef fun append_line_to(s)
277 do
278 loop
279 # First phase: look for a '\n'
280 var i = _buffer_pos
281 while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1
282
283 var eol
284 if i < _buffer.length then
285 assert _buffer.chars[i] == '\n'
286 i += 1
287 eol = true
288 else
289 eol = false
290 end
291
292 # if there is something to append
293 if i > _buffer_pos then
294 # Enlarge the string (if needed)
295 s.enlarge(s.length + i - _buffer_pos)
296
297 # Copy from the buffer to the string
298 var j = _buffer_pos
299 while j < i do
300 s.add(_buffer.chars[j])
301 j += 1
302 end
303 _buffer_pos = i
304 else
305 assert end_reached
306 return
307 end
308
309 if eol then
310 # so \n is found
311 return
312 else
313 # so \n is not found
314 if end_reached then return
315 fill_buffer
316 end
317 end
318 end
319
320 redef fun eof
321 do
322 if _buffer_pos < _buffer.length then return false
323 if end_reached then return true
324 fill_buffer
325 return _buffer_pos >= _buffer.length and end_reached
326 end
327
328 # The buffer
329 private var buffer: nullable FlatBuffer = null
330
331 # The current position in the buffer
332 private var buffer_pos: Int = 0
333
334 # Fill the buffer
335 protected fun fill_buffer is abstract
336
337 # Is the last fill_buffer reach the end
338 protected fun end_reached: Bool is abstract
339
340 # Allocate a `_buffer` for a given `capacity`.
341 protected fun prepare_buffer(capacity: Int)
342 do
343 _buffer = new FlatBuffer.with_capacity(capacity)
344 _buffer_pos = 0 # need to read
345 end
346 end
347
348 # An Input/Output Stream
349 interface IOStream
350 super IStream
351 super OStream
352 end
353
354 ##############################################################"
355
356 # A File Descriptor Stream.
357 abstract class FDStream
358 super IOS
359 # File description
360 var fd: Int
361
362 redef fun close do native_close(fd)
363
364 private fun native_close(i: Int): Int is extern "stream_FDStream_FDStream_native_close_1"
365 private fun native_read_char(i: Int): Int is extern "stream_FDStream_FDStream_native_read_char_1"
366 private fun native_read(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_read_3"
367 private fun native_write(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_write_3"
368 private fun native_write_char(i: Int, c: Char): Int is extern "stream_FDStream_FDStream_native_write_char_2"
369 end
370
371 # An Input File Descriptor Stream.
372 class FDIStream
373 super FDStream
374 super IStream
375 redef var eof: Bool = false
376
377 redef fun read_char
378 do
379 var nb = native_read_char(fd)
380 if nb == -1 then eof = true
381 return nb
382 end
383 end
384
385 # An Output File Descriptor Stream.
386 class FDOStream
387 super FDStream
388 super OStream
389 redef var is_writable = true
390
391 redef fun write(s)
392 do
393 var nb = native_write(fd, s.to_cstring, s.length)
394 if nb < s.length then is_writable = false
395 end
396 end
397
398 # An Input/Output File Descriptor Stream.
399 class FDIOStream
400 super FDIStream
401 super FDOStream
402 super IOStream
403 end
404
405 redef interface Object
406 # returns first available stream to read or write to
407 # return null on interruption (possibly a signal)
408 protected fun poll( streams : Sequence[FDStream] ) : nullable FDStream
409 do
410 var in_fds = new Array[Int]
411 var out_fds = new Array[Int]
412 var fd_to_stream = new HashMap[Int,FDStream]
413 for s in streams do
414 var fd = s.fd
415 if s isa FDIStream then in_fds.add( fd )
416 if s isa FDOStream then out_fds.add( fd )
417
418 fd_to_stream[fd] = s
419 end
420
421 var polled_fd = intern_poll( in_fds, out_fds )
422
423 if polled_fd == null then
424 return null
425 else
426 return fd_to_stream[polled_fd]
427 end
428 end
429
430 private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]) : nullable Int is extern import Array[Int].length, Array[Int].[], Int.as(nullable Int) `{
431 int in_len, out_len, total_len;
432 struct pollfd *c_fds;
433 sigset_t sigmask;
434 int i;
435 int first_polled_fd = -1;
436 int result;
437
438 in_len = Array_of_Int_length( in_fds );
439 out_len = Array_of_Int_length( out_fds );
440 total_len = in_len + out_len;
441 c_fds = malloc( sizeof(struct pollfd) * total_len );
442
443 /* input streams */
444 for ( i=0; i<in_len; i ++ ) {
445 int fd;
446 fd = Array_of_Int__index( in_fds, i );
447
448 c_fds[i].fd = fd;
449 c_fds[i].events = POLLIN;
450 }
451
452 /* output streams */
453 for ( i=0; i<out_len; i ++ ) {
454 int fd;
455 fd = Array_of_Int__index( out_fds, i );
456
457 c_fds[i].fd = fd;
458 c_fds[i].events = POLLOUT;
459 }
460
461 /* poll all fds, unlimited timeout */
462 result = poll( c_fds, total_len, -1 );
463
464 if ( result > 0 ) {
465 /* analyse results */
466 for ( i=0; i<total_len; i++ )
467 if ( c_fds[i].revents & c_fds[i].events || /* awaited event */
468 c_fds[i].revents & POLLHUP ) /* closed */
469 {
470 first_polled_fd = c_fds[i].fd;
471 break;
472 }
473
474 return Int_as_nullable( first_polled_fd );
475 }
476 else if ( result < 0 )
477 fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
478
479 return null_Int();
480 `}
481 end
482
483 # Stream to a String.
484 #
485 # Mainly used for compatibility with OStream type and tests.
486 class StringOStream
487 super OStream
488
489 private var content = new Array[String]
490 redef fun to_s do return content.to_s
491 redef fun is_writable do return not closed
492 redef fun write(str)
493 do
494 assert not closed
495 content.add(str.to_s)
496 end
497
498 # Is the stream closed?
499 protected var closed = false
500
501 redef fun close do closed = true
502 end
503
504 # Stream from a String.
505 #
506 # Mainly used for compatibility with IStream type and tests.
507 class StringIStream
508 super IStream
509
510 # The string to read from.
511 var source: String
512
513 # The current position in the string.
514 private var cursor: Int = 0
515
516 redef fun read_char do
517 if cursor < source.length then
518 var c = source[cursor].ascii
519
520 cursor += 1
521 return c
522 else
523 return -1
524 end
525 end
526
527 redef fun close do
528 source = ""
529 end
530
531 redef fun eof do return cursor >= source.length
532 end