Merge: Nitgs optims
[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 FlatBuffer.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 FlatBuffer
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 FlatBuffer
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 #
106 # ready-to-save documents usually provide this interface.
107 interface Streamable
108 # Write itself to a `stream`
109 # The specific logic it let to the concrete subclasses
110 fun write_to(stream: OStream) is abstract
111
112 # Like `write_to` but return a new String (may be quite large)
113 #
114 # This funtionnality is anectodical, since the point
115 # of streamable object to to be efficienlty written to a
116 # stream without having to allocate and concatenate strings
117 fun write_to_string: String
118 do
119 var stream = new StringOStream
120 write_to(stream)
121 return stream.to_s
122 end
123 end
124
125 redef class String
126 super Streamable
127 redef fun write_to(stream) do stream.write(self)
128 end
129
130 # Input streams with a buffer
131 abstract class BufferedIStream
132 super IStream
133 redef fun read_char
134 do
135 assert not eof
136 if _buffer_pos >= _buffer.length then
137 fill_buffer
138 end
139 if _buffer_pos >= _buffer.length then
140 return -1
141 end
142 var c = _buffer.chars[_buffer_pos]
143 _buffer_pos += 1
144 return c.ascii
145 end
146
147 redef fun read(i)
148 do
149 var s = new FlatBuffer.with_capacity(i)
150 var j = _buffer_pos
151 var k = _buffer.length
152 while i > 0 do
153 if j >= k then
154 fill_buffer
155 if eof then return s.to_s
156 j = _buffer_pos
157 k = _buffer.length
158 end
159 while j < k and i > 0 do
160 s.add(_buffer.chars[j])
161 j += 1
162 i -= 1
163 end
164 end
165 _buffer_pos = j
166 return s.to_s
167 end
168
169 redef fun read_all
170 do
171 var s = new FlatBuffer
172 while not eof do
173 var j = _buffer_pos
174 var k = _buffer.length
175 while j < k do
176 s.add(_buffer.chars[j])
177 j += 1
178 end
179 _buffer_pos = j
180 fill_buffer
181 end
182 return s.to_s
183 end
184
185 redef fun append_line_to(s)
186 do
187 loop
188 # First phase: look for a '\n'
189 var i = _buffer_pos
190 while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1
191
192 # if there is something to append
193 if i > _buffer_pos then
194 # Enlarge the string (if needed)
195 s.enlarge(s.length + i - _buffer_pos)
196
197 # Copy from the buffer to the string
198 var j = _buffer_pos
199 while j < i do
200 s.add(_buffer.chars[j])
201 j += 1
202 end
203 end
204
205 if i < _buffer.length then
206 # so \n is in _buffer[i]
207 _buffer_pos = i + 1 # skip \n
208 return
209 else
210 # so \n is not found
211 _buffer_pos = i
212 if end_reached then
213 return
214 else
215 fill_buffer
216 end
217 end
218 end
219 end
220
221 redef fun eof do return _buffer_pos >= _buffer.length and end_reached
222
223 # The buffer
224 var _buffer: nullable FlatBuffer = null
225
226 # The current position in the buffer
227 var _buffer_pos: Int = 0
228
229 # Fill the buffer
230 protected fun fill_buffer is abstract
231
232 # Is the last fill_buffer reach the end
233 protected fun end_reached: Bool is abstract
234
235 # Allocate a `_buffer` for a given `capacity`.
236 protected fun prepare_buffer(capacity: Int)
237 do
238 _buffer = new FlatBuffer.with_capacity(capacity)
239 _buffer_pos = 0 # need to read
240 end
241 end
242
243 interface IOStream
244 super IStream
245 super OStream
246 end
247
248 ##############################################################"
249
250 abstract class FDStream
251 super IOS
252 # File description
253 var fd: Int
254
255 redef fun close do native_close(fd)
256
257 private fun native_close(i: Int): Int is extern "stream_FDStream_FDStream_native_close_1"
258 private fun native_read_char(i: Int): Int is extern "stream_FDStream_FDStream_native_read_char_1"
259 private fun native_read(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_read_3"
260 private fun native_write(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_write_3"
261 private fun native_write_char(i: Int, c: Char): Int is extern "stream_FDStream_FDStream_native_write_char_2"
262
263 init(fd: Int) do self.fd = fd
264 end
265
266 class FDIStream
267 super FDStream
268 super IStream
269 redef var eof: Bool = false
270
271 redef fun read_char
272 do
273 var nb = native_read_char(fd)
274 if nb == -1 then eof = true
275 return nb
276 end
277
278 init(fd: Int) do end
279 end
280
281 class FDOStream
282 super FDStream
283 super OStream
284 redef var is_writable: Bool
285
286 redef fun write(s)
287 do
288 var nb = native_write(fd, s.to_cstring, s.length)
289 if nb < s.length then is_writable = false
290 end
291
292 init(fd: Int)
293 do
294 is_writable = true
295 end
296 end
297
298 class FDIOStream
299 super FDIStream
300 super FDOStream
301 super IOStream
302 init(fd: Int)
303 do
304 self.fd = fd
305 is_writable = true
306 end
307 end
308
309 redef interface Object
310 # returns first available stream to read or write to
311 # return null on interruption (possibly a signal)
312 protected fun poll( streams : Sequence[FDStream] ) : nullable FDStream
313 do
314 var in_fds = new Array[Int]
315 var out_fds = new Array[Int]
316 var fd_to_stream = new HashMap[Int,FDStream]
317 for s in streams do
318 var fd = s.fd
319 if s isa FDIStream then in_fds.add( fd )
320 if s isa FDOStream then out_fds.add( fd )
321
322 fd_to_stream[fd] = s
323 end
324
325 var polled_fd = intern_poll( in_fds, out_fds )
326
327 if polled_fd == null then
328 return null
329 else
330 return fd_to_stream[polled_fd]
331 end
332 end
333
334 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) `{
335 int in_len, out_len, total_len;
336 struct pollfd *c_fds;
337 sigset_t sigmask;
338 int i;
339 int first_polled_fd = -1;
340 int result;
341
342 in_len = Array_of_Int_length( in_fds );
343 out_len = Array_of_Int_length( out_fds );
344 total_len = in_len + out_len;
345 c_fds = malloc( sizeof(struct pollfd) * total_len );
346
347 /* input streams */
348 for ( i=0; i<in_len; i ++ ) {
349 int fd;
350 fd = Array_of_Int__index( in_fds, i );
351
352 c_fds[i].fd = fd;
353 c_fds[i].events = POLLIN;
354 }
355
356 /* output streams */
357 for ( i=0; i<out_len; i ++ ) {
358 int fd;
359 fd = Array_of_Int__index( out_fds, i );
360
361 c_fds[i].fd = fd;
362 c_fds[i].events = POLLOUT;
363 }
364
365 /* poll all fds, unlimited timeout */
366 result = poll( c_fds, total_len, -1 );
367
368 if ( result > 0 ) {
369 /* analyse results */
370 for ( i=0; i<total_len; i++ )
371 if ( c_fds[i].revents & c_fds[i].events || /* awaited event */
372 c_fds[i].revents & POLLHUP ) /* closed */
373 {
374 first_polled_fd = c_fds[i].fd;
375 break;
376 }
377
378 return Int_as_nullable( first_polled_fd );
379 }
380 else if ( result < 0 )
381 fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
382
383 return null_Int();
384 `}
385 end
386
387 # Stream to a String. Mainly used for compatibility with OStream type and tests.
388 class StringOStream
389 super OStream
390
391 private var content = new Array[String]
392 redef fun to_s do return content.to_s
393 redef fun is_writable do return true
394 redef fun write(str) do content.add(str)
395 end