Merge: Newstreams
[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 import error
16
17 in "C" `{
18 #include <unistd.h>
19 #include <string.h>
20 #include <signal.h>
21 `}
22
23 # Any kind of error that could be produced by an operation on Streams
24 class IOError
25 super Error
26 end
27
28 # Abstract stream class
29 abstract class IOS
30 # Error produced by the file stream
31 #
32 # var ifs = new IFStream.open("donotmakethisfile.binx")
33 # ifs.read_all
34 # ifs.close
35 # assert ifs.last_error != null
36 var last_error: nullable IOError = null
37
38 # close the stream
39 fun close is abstract
40 end
41
42 # Abstract input streams
43 abstract class IStream
44 super IOS
45 # Read a character. Return its ASCII value, -1 on EOF or timeout
46 fun read_char: Int is abstract
47
48 # Read at most i bytes
49 fun read(i: Int): String
50 do
51 if last_error != null then return ""
52 var s = new FlatBuffer.with_capacity(i)
53 while i > 0 and not eof do
54 var c = read_char
55 if c >= 0 then
56 s.add(c.ascii)
57 i -= 1
58 end
59 end
60 return s.to_s
61 end
62
63 # Read a string until the end of the line.
64 #
65 # The line terminator '\n', if any, is preserved in each line.
66 # Use the method `Text::chomp` to safely remove it.
67 #
68 # ~~~
69 # var txt = "Hello\n\nWorld\n"
70 # var i = new StringIStream(txt)
71 # assert i.read_line == "Hello\n"
72 # assert i.read_line == "\n"
73 # assert i.read_line == "World\n"
74 # assert i.eof
75 # ~~~
76 #
77 # If `\n` is not present at the end of the result, it means that
78 # a non-eol terminated last line was returned.
79 #
80 # ~~~
81 # var i2 = new StringIStream("hello")
82 # assert not i2.eof
83 # assert i2.read_line == "hello"
84 # assert i2.eof
85 # ~~~
86 #
87 # NOTE: Only LINE FEED (`\n`) is considered to delimit the end of lines.
88 fun read_line: String
89 do
90 if last_error != null then return ""
91 assert not eof
92 var s = new FlatBuffer
93 append_line_to(s)
94 return s.to_s
95 end
96
97 # Read all the lines until the eof.
98 #
99 # The line terminator '\n' is removed in each line,
100 #
101 # ~~~
102 # var txt = "Hello\n\nWorld\n"
103 # var i = new StringIStream(txt)
104 # assert i.read_lines == ["Hello", "", "World"]
105 # ~~~
106 #
107 # This method is more efficient that splitting
108 # the result of `read_all`.
109 #
110 # NOTE: Only LINE FEED (`\n`) is considered to delimit the end of lines.
111 fun read_lines: Array[String]
112 do
113 var res = new Array[String]
114 while not eof do
115 res.add read_line.chomp
116 end
117 return res
118 end
119
120 # Read all the stream until the eof.
121 fun read_all: String
122 do
123 if last_error != null then return ""
124 var s = new FlatBuffer
125 while not eof do
126 var c = read_char
127 if c >= 0 then s.add(c.ascii)
128 end
129 return s.to_s
130 end
131
132 # Read a string until the end of the line and append it to `s`.
133 #
134 # SEE: `read_line` for details.
135 fun append_line_to(s: Buffer)
136 do
137 if last_error != null then return
138 loop
139 var x = read_char
140 if x == -1 then
141 if eof then return
142 else
143 var c = x.ascii
144 s.chars.push(c)
145 if c == '\n' then return
146 end
147 end
148 end
149
150 # Is there something to read.
151 # This function returns 'false' if there is something to read.
152 fun eof: Bool is abstract
153 end
154
155 # IStream capable of declaring if readable without blocking
156 abstract class PollableIStream
157 super IStream
158
159 # Is there something to read? (without blocking)
160 fun poll_in: Bool is abstract
161
162 end
163
164 # Abstract output stream
165 abstract class OStream
166 super IOS
167 # write a string
168 fun write(s: Text) is abstract
169
170 # Can the stream be used to write
171 fun is_writable: Bool is abstract
172 end
173
174 # Things that can be efficienlty writen to a OStream
175 #
176 # The point of this interface it to allow is instance to be efficenty
177 # writen into a OStream without having to allocate a big String object
178 #
179 # ready-to-save documents usually provide this interface.
180 interface Streamable
181 # Write itself to a `stream`
182 # The specific logic it let to the concrete subclasses
183 fun write_to(stream: OStream) is abstract
184
185 # Like `write_to` but return a new String (may be quite large)
186 #
187 # This funtionnality is anectodical, since the point
188 # of streamable object to to be efficienlty written to a
189 # stream without having to allocate and concatenate strings
190 fun write_to_string: String
191 do
192 var stream = new StringOStream
193 write_to(stream)
194 return stream.to_s
195 end
196 end
197
198 redef class Text
199 super Streamable
200 redef fun write_to(stream) do stream.write(self)
201 end
202
203 # Input streams with a buffer
204 abstract class BufferedIStream
205 super IStream
206 redef fun read_char
207 do
208 if last_error != null then return 0
209 if eof then last_error = new IOError("Stream has reached eof")
210 if _buffer_pos >= _buffer.length then
211 fill_buffer
212 end
213 if _buffer_pos >= _buffer.length then
214 return -1
215 end
216 var c = _buffer.chars[_buffer_pos]
217 _buffer_pos += 1
218 return c.ascii
219 end
220
221 redef fun read(i)
222 do
223 if last_error != null then return ""
224 if _buffer.length == _buffer_pos then
225 if not eof then
226 fill_buffer
227 return read(i)
228 end
229 return ""
230 end
231 if _buffer_pos + i >= _buffer.length then
232 var from = _buffer_pos
233 _buffer_pos = _buffer.length
234 return _buffer.substring_from(from).to_s
235 end
236 _buffer_pos += i
237 return _buffer.substring(_buffer_pos - i, i).to_s
238 end
239
240 redef fun read_all
241 do
242 if last_error != null then return ""
243 var s = new FlatBuffer
244 while not eof do
245 var j = _buffer_pos
246 var k = _buffer.length
247 while j < k do
248 s.add(_buffer[j])
249 j += 1
250 end
251 _buffer_pos = j
252 fill_buffer
253 end
254 return s.to_s
255 end
256
257 redef fun append_line_to(s)
258 do
259 loop
260 # First phase: look for a '\n'
261 var i = _buffer_pos
262 while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1
263
264 # if there is something to append
265 if i > _buffer_pos then
266 # Enlarge the string (if needed)
267 s.enlarge(s.length + i - _buffer_pos)
268
269 # Copy from the buffer to the string
270 var j = _buffer_pos
271 while j < i do
272 s.add(_buffer.chars[j])
273 j += 1
274 end
275 end
276
277 if i < _buffer.length then
278 # so \n is in _buffer[i]
279 _buffer_pos = i + 1 # skip \n
280 return
281 else
282 # so \n is not found
283 _buffer_pos = i
284 if end_reached then
285 return
286 else
287 fill_buffer
288 end
289 end
290 end
291 end
292
293 redef fun eof do return _buffer_pos >= _buffer.length and end_reached
294
295 # The buffer
296 private var buffer: nullable FlatBuffer = null
297
298 # The current position in the buffer
299 private var buffer_pos: Int = 0
300
301 # Fill the buffer
302 protected fun fill_buffer is abstract
303
304 # Is the last fill_buffer reach the end
305 protected fun end_reached: Bool is abstract
306
307 # Allocate a `_buffer` for a given `capacity`.
308 protected fun prepare_buffer(capacity: Int)
309 do
310 _buffer = new FlatBuffer.with_capacity(capacity)
311 _buffer_pos = 0 # need to read
312 end
313 end
314
315 # An Input/Output Stream
316 abstract class IOStream
317 super IStream
318 super OStream
319 end
320
321 # Stream to a String.
322 #
323 # Mainly used for compatibility with OStream type and tests.
324 class StringOStream
325 super OStream
326
327 private var content = new Array[String]
328 redef fun to_s do return content.to_s
329 redef fun is_writable do return not closed
330 redef fun write(str)
331 do
332 assert not closed
333 content.add(str.to_s)
334 end
335
336 # Is the stream closed?
337 protected var closed = false
338
339 redef fun close do closed = true
340 end
341
342 # Stream from a String.
343 #
344 # Mainly used for compatibility with IStream type and tests.
345 class StringIStream
346 super IStream
347
348 # The string to read from.
349 var source: String
350
351 # The current position in the string.
352 private var cursor: Int = 0
353
354 redef fun read_char do
355 if cursor < source.length then
356 var c = source[cursor].ascii
357
358 cursor += 1
359 return c
360 else
361 return -1
362 end
363 end
364
365 redef fun close do
366 source = ""
367 end
368
369 redef fun eof do return cursor >= source.length
370 end