Merge branch 'dump_rta'
[nit.git] / lib / standard / stream.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2004-2008 Jean Privat <jean@pryen.org>
4 #
5 # This file is free software, which comes along with NIT. This software is
6 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
7 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
8 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
9 # is kept unaltered, and a notification of the changes is added.
10 # You are allowed to redistribute it and sell it, alone or is a part of
11 # another product.
12
13 # Input and output streams of characters
14 module stream
15
16 import string
17
18 in "C" `{
19 #include <unistd.h>
20 #include <poll.h>
21 #include <errno.h>
22 #include <string.h>
23 `}
24
25 # Abstract stream class
26 interface IOS
27 # close the stream
28 fun close is abstract
29 end
30
31 # Abstract input streams
32 interface IStream
33 super IOS
34 # Read a character. Return its ASCII value, -1 on EOF or timeout
35 fun read_char: Int is abstract
36
37 # Read at most i bytes
38 fun read(i: Int): String
39 do
40 var s = new Buffer.with_capacity(i)
41 while i > 0 and not eof do
42 var c = read_char
43 if c >= 0 then
44 s.add(c.ascii)
45 i -= 1
46 end
47 end
48 return s.to_s
49 end
50
51 # Read a string until the end of the line.
52 fun read_line: String
53 do
54 assert not eof
55 var s = new Buffer
56 append_line_to(s)
57 return s.to_s
58 end
59
60 # Read all the stream until the eof.
61 fun read_all: String
62 do
63 var s = new Buffer
64 while not eof do
65 var c = read_char
66 if c >= 0 then s.add(c.ascii)
67 end
68 return s.to_s
69 end
70
71 # Read a string until the end of the line and append it to `s`.
72 fun append_line_to(s: Buffer)
73 do
74 loop
75 var x = read_char
76 if x == -1 then
77 if eof then return
78 else
79 var c = x.ascii
80 s.chars.push(c)
81 if c == '\n' then return
82 end
83 end
84 end
85
86 # Is there something to read.
87 # This function returns 'false' if there is something to read.
88 fun eof: Bool is abstract
89 end
90
91 # Abstract output stream
92 interface OStream
93 super IOS
94 # write a string
95 fun write(s: String) is abstract
96
97 # Can the stream be used to write
98 fun is_writable: Bool is abstract
99 end
100
101 # Things that can be efficienlty writen to a OStream
102 #
103 # The point of this interface it to allow is instance to be efficenty
104 # writen into a OStream without having to allocate a big String object
105 interface Streamable
106 # Write itself to a `stream`
107 # The specific logic it let to the concrete subclasses
108 fun write_to(stream: OStream) is abstract
109 end
110
111 redef class String
112 super Streamable
113 redef fun write_to(stream) do stream.write(self)
114 end
115
116 # Input streams with a buffer
117 abstract class BufferedIStream
118 super IStream
119 redef fun read_char
120 do
121 assert not eof
122 if _buffer_pos >= _buffer.length then
123 fill_buffer
124 end
125 if _buffer_pos >= _buffer.length then
126 return -1
127 end
128 var c = _buffer.chars[_buffer_pos]
129 _buffer_pos += 1
130 return c.ascii
131 end
132
133 redef fun read(i)
134 do
135 var s = new Buffer.with_capacity(i)
136 var j = _buffer_pos
137 var k = _buffer.length
138 while i > 0 do
139 if j >= k then
140 fill_buffer
141 if eof then return s.to_s
142 j = _buffer_pos
143 k = _buffer.length
144 end
145 while j < k and i > 0 do
146 s.add(_buffer.chars[j])
147 j += 1
148 i -= 1
149 end
150 end
151 _buffer_pos = j
152 return s.to_s
153 end
154
155 redef fun read_all
156 do
157 var s = new Buffer
158 while not eof do
159 var j = _buffer_pos
160 var k = _buffer.length
161 while j < k do
162 s.add(_buffer.chars[j])
163 j += 1
164 end
165 _buffer_pos = j
166 fill_buffer
167 end
168 return s.to_s
169 end
170
171 redef fun append_line_to(s)
172 do
173 loop
174 # First phase: look for a '\n'
175 var i = _buffer_pos
176 while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1
177
178 # if there is something to append
179 if i > _buffer_pos then
180 # Enlarge the string (if needed)
181 s.enlarge(s.length + i - _buffer_pos)
182
183 # Copy from the buffer to the string
184 var j = _buffer_pos
185 while j < i do
186 s.add(_buffer.chars[j])
187 j += 1
188 end
189 end
190
191 if i < _buffer.length then
192 # so \n is in _buffer[i]
193 _buffer_pos = i + 1 # skip \n
194 return
195 else
196 # so \n is not found
197 _buffer_pos = i
198 if end_reached then
199 return
200 else
201 fill_buffer
202 end
203 end
204 end
205 end
206
207 redef fun eof do return _buffer_pos >= _buffer.length and end_reached
208
209 # The buffer
210 var _buffer: nullable Buffer = null
211
212 # The current position in the buffer
213 var _buffer_pos: Int = 0
214
215 # Fill the buffer
216 protected fun fill_buffer is abstract
217
218 # Is the last fill_buffer reach the end
219 protected fun end_reached: Bool is abstract
220
221 # Allocate a `_buffer` for a given `capacity`.
222 protected fun prepare_buffer(capacity: Int)
223 do
224 _buffer = new Buffer.with_capacity(capacity)
225 _buffer_pos = 0 # need to read
226 end
227 end
228
229 interface IOStream
230 super IStream
231 super OStream
232 end
233
234 ##############################################################"
235
236 abstract class FDStream
237 super IOS
238 # File description
239 var fd: Int
240
241 redef fun close do native_close(fd)
242
243 private fun native_close(i: Int): Int is extern "stream_FDStream_FDStream_native_close_1"
244 private fun native_read_char(i: Int): Int is extern "stream_FDStream_FDStream_native_read_char_1"
245 private fun native_read(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_read_3"
246 private fun native_write(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_write_3"
247 private fun native_write_char(i: Int, c: Char): Int is extern "stream_FDStream_FDStream_native_write_char_2"
248
249 init(fd: Int) do self.fd = fd
250 end
251
252 class FDIStream
253 super FDStream
254 super IStream
255 redef var eof: Bool = false
256
257 redef fun read_char
258 do
259 var nb = native_read_char(fd)
260 if nb == -1 then eof = true
261 return nb
262 end
263
264 init(fd: Int) do end
265 end
266
267 class FDOStream
268 super FDStream
269 super OStream
270 redef var is_writable: Bool
271
272 redef fun write(s)
273 do
274 var nb = native_write(fd, s.to_cstring, s.length)
275 if nb < s.length then is_writable = false
276 end
277
278 init(fd: Int)
279 do
280 is_writable = true
281 end
282 end
283
284 class FDIOStream
285 super FDIStream
286 super FDOStream
287 super IOStream
288 init(fd: Int)
289 do
290 self.fd = fd
291 is_writable = true
292 end
293 end
294
295 redef interface Object
296 # returns first available stream to read or write to
297 # return null on interruption (possibly a signal)
298 protected fun poll( streams : Sequence[FDStream] ) : nullable FDStream
299 do
300 var in_fds = new Array[Int]
301 var out_fds = new Array[Int]
302 var fd_to_stream = new HashMap[Int,FDStream]
303 for s in streams do
304 var fd = s.fd
305 if s isa FDIStream then in_fds.add( fd )
306 if s isa FDOStream then out_fds.add( fd )
307
308 fd_to_stream[fd] = s
309 end
310
311 var polled_fd = intern_poll( in_fds, out_fds )
312
313 if polled_fd == null then
314 return null
315 else
316 return fd_to_stream[polled_fd]
317 end
318 end
319
320 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) `{
321 int in_len, out_len, total_len;
322 struct pollfd *c_fds;
323 sigset_t sigmask;
324 int i;
325 int first_polled_fd = -1;
326 int result;
327
328 in_len = Array_of_Int_length( in_fds );
329 out_len = Array_of_Int_length( out_fds );
330 total_len = in_len + out_len;
331 c_fds = malloc( sizeof(struct pollfd) * total_len );
332
333 /* input streams */
334 for ( i=0; i<in_len; i ++ ) {
335 int fd;
336 fd = Array_of_Int__index( in_fds, i );
337
338 c_fds[i].fd = fd;
339 c_fds[i].events = POLLIN;
340 }
341
342 /* output streams */
343 for ( i=0; i<out_len; i ++ ) {
344 int fd;
345 fd = Array_of_Int__index( out_fds, i );
346
347 c_fds[i].fd = fd;
348 c_fds[i].events = POLLOUT;
349 }
350
351 /* poll all fds, unlimited timeout */
352 result = poll( c_fds, total_len, -1 );
353
354 if ( result > 0 ) {
355 /* analyse results */
356 for ( i=0; i<total_len; i++ )
357 if ( c_fds[i].revents & c_fds[i].events || /* awaited event */
358 c_fds[i].revents & POLLHUP ) /* closed */
359 {
360 first_polled_fd = c_fds[i].fd;
361 break;
362 }
363
364 return Int_as_nullable( first_polled_fd );
365 }
366 else if ( result < 0 )
367 fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
368
369 return null_Int();
370 `}
371 end
372
373 # Stream to a String. Mainly used for compatibility with OStream type and tests.
374 class StringOStream
375 super OStream
376
377 private var content = new Array[String]
378 redef fun to_s do return content.to_s
379 redef fun is_writable do return true
380 redef fun write(str) do content.add(str)
381 end