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