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