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