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