X-Git-Url: http://nitlanguage.org diff --git a/lib/standard/stream.nit b/lib/standard/stream.nit index 3c2c292..7c4e606 100644 --- a/lib/standard/stream.nit +++ b/lib/standard/stream.nit @@ -12,35 +12,51 @@ module stream intrude import ropes +import error in "C" `{ #include - #include - #include #include #include `} -# Abstract stream class -interface IOS +# Any kind of error that could be produced by an operation on Streams +class IOError + super Error +end + +# Any kind of stream to read/write/both to or from a source +abstract class Stream + # Error produced by the file stream + # + # var ifs = new FileReader.open("donotmakethisfile.binx") + # ifs.read_all + # ifs.close + # assert ifs.last_error != null + var last_error: nullable IOError = null + # close the stream fun close is abstract end -# Abstract input streams -interface IStream - super IOS - # Read a character. Return its ASCII value, -1 on EOF or timeout - fun read_char: Int is abstract +# A `Stream` that can be read from +abstract class Reader + super Stream + # Reads a character. Returns `null` on EOF or timeout + fun read_char: nullable Char is abstract + + # Reads a byte. Returns `null` on EOF or timeout + fun read_byte: nullable Int is abstract # Read at most i bytes fun read(i: Int): String do + if last_error != null then return "" var s = new FlatBuffer.with_capacity(i) while i > 0 and not eof do var c = read_char - if c >= 0 then - s.add(c.ascii) + if c != null then + s.add(c) i -= 1 end end @@ -53,7 +69,7 @@ interface IStream # # ~~~ # var txt = "Hello\n\nWorld\n" - # var i = new StringIStream(txt) + # var i = new StringReader(txt) # assert i.read_line == "Hello" # assert i.read_line == "" # assert i.read_line == "World" @@ -66,7 +82,7 @@ interface IStream # # ~~~ # var txt2 = "Hello\r\n\n\rWorld" - # var i2 = new StringIStream(txt2) + # var i2 = new StringReader(txt2) # assert i2.read_line == "Hello" # assert i2.read_line == "" # assert i2.read_line == "\rWorld" @@ -76,6 +92,7 @@ interface IStream # NOTE: Use `append_line_to` if the line terminator needs to be preserved. fun read_line: String do + if last_error != null then return "" if eof then return "" var s = new FlatBuffer append_line_to(s) @@ -88,7 +105,7 @@ interface IStream # # ~~~ # var txt = "Hello\n\nWorld\n" - # var i = new StringIStream(txt) + # var i = new StringReader(txt) # assert i.read_lines == ["Hello", "", "World"] # ~~~ # @@ -105,21 +122,52 @@ interface IStream return res end + # Return an iterator that read each line. + # + # The line terminator '\n' and `\r\n` is removed in each line, + # The line are read with `read_line`. See this method for details. + # + # ~~~ + # var txt = "Hello\n\nWorld\n" + # var i = new StringReader(txt) + # assert i.each_line.to_a == ["Hello", "", "World"] + # ~~~ + # + # Unlike `read_lines` that read all lines at the call, `each_line` is lazy. + # Therefore, the stream should no be closed until the end of the stream. + # + # ~~~ + # i = new StringReader(txt) + # var el = i.each_line + # + # assert el.item == "Hello" + # el.next + # assert el.item == "" + # el.next + # + # i.close + # + # assert not el.is_ok + # # closed before "world" is read + # ~~~ + fun each_line: LineIterator do return new LineIterator(self) + # Read all the stream until the eof. # # The content of the file is returned verbatim. # # ~~~ # var txt = "Hello\n\nWorld\n" - # var i = new StringIStream(txt) + # var i = new StringReader(txt) # assert i.read_all == txt # ~~~ fun read_all: String do + if last_error != null then return "" var s = new FlatBuffer while not eof do var c = read_char - if c >= 0 then s.add(c.ascii) + if c != null then s.add(c) end return s.to_s end @@ -132,7 +180,7 @@ interface IStream # # ~~~ # var txt = "Hello\n\nWorld\n" - # var i = new StringIStream(txt) + # var i = new StringReader(txt) # var b = new FlatBuffer # i.append_line_to(b) # assert b == "Hello\n" @@ -147,7 +195,7 @@ interface IStream # a non-eol terminated last line was returned. # # ~~~ - # var i2 = new StringIStream("hello") + # var i2 = new StringReader("hello") # assert not i2.eof # var b2 = new FlatBuffer # i2.append_line_to(b2) @@ -159,14 +207,14 @@ interface IStream # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized. fun append_line_to(s: Buffer) do + if last_error != null then return loop var x = read_char - if x == -1 then + if x == null then if eof then return else - var c = x.ascii - s.chars.push(c) - if c == '\n' then return + s.chars.push(x) + if x == '\n' then return end end end @@ -174,37 +222,146 @@ interface IStream # Is there something to read. # This function returns 'false' if there is something to read. fun eof: Bool is abstract + + # Read the next sequence of non whitespace characters. + # + # Leading whitespace characters are skipped. + # The first whitespace character that follows the result is consumed. + # + # An empty string is returned if the end of the file or an error is encounter. + # + # ~~~ + # var w = new StringReader(" Hello, \n\t World!") + # assert w.read_word == "Hello," + # assert w.read_char == '\n'.ascii + # assert w.read_word == "World!" + # assert w.read_word == "" + # ~~~ + # + # `Char::is_whitespace` determines what is a whitespace. + fun read_word: String + do + var buf = new FlatBuffer + var c = read_nonwhitespace + if c != null then + buf.add(c) + while not eof do + c = read_char + if c == null then break + if c.is_whitespace then break + buf.add(c) + end + end + var res = buf.to_s + return res + end + + # Skip whitespace characters (if any) then return the following non-whitespace character. + # + # Returns the code point of the character. + # Returns `null` on end of file or error. + # + # In fact, this method works like `read_char` except it skips whitespace. + # + # ~~~ + # var w = new StringReader(" \nab\tc") + # assert w.read_nonwhitespace == 'a' + # assert w.read_nonwhitespace == 'b' + # assert w.read_nonwhitespace == 'c' + # assert w.read_nonwhitespace == null + # ~~~ + # + # `Char::is_whitespace` determines what is a whitespace. + fun read_nonwhitespace: nullable Char + do + var c: nullable Char = null + while not eof do + c = read_char + if c == null or not c.is_whitespace then break + end + return c + end +end + +# Iterator returned by `Reader::each_line`. +# See the aforementioned method for details. +class LineIterator + super Iterator[String] + + # The original stream + var stream: Reader + + redef fun is_ok + do + var res = not stream.eof + if not res and close_on_finish then stream.close + return res + end + + redef fun item + do + var line = self.line + if line == null then + line = stream.read_line + end + self.line = line + return line + end + + # The last line read (cache) + private var line: nullable String = null + + redef fun next + do + # force the read + if line == null then item + # drop the line + line = null + end + + # Close the stream when the stream is at the EOF. + # + # Default is false. + var close_on_finish = false is writable + + redef fun finish + do + if close_on_finish then stream.close + end end -# IStream capable of declaring if readable without blocking -interface PollableIStream - super IStream +# `Reader` capable of declaring if readable without blocking +abstract class PollableReader + super Reader # Is there something to read? (without blocking) fun poll_in: Bool is abstract end -# Abstract output stream -interface OStream - super IOS +# A `Stream` that can be written to +abstract class Writer + super Stream # write a string fun write(s: Text) is abstract + # Write a single byte + fun write_byte(value: Int) is abstract + # Can the stream be used to write fun is_writable: Bool is abstract end -# Things that can be efficienlty writen to a OStream +# Things that can be efficienlty written to a `Writer` # -# The point of this interface it to allow is instance to be efficenty -# writen into a OStream without having to allocate a big String object +# The point of this interface is to allow the instance to be efficiently +# written into a `Writer`. # -# ready-to-save documents usually provide this interface. -interface Streamable +# Ready-to-save documents usually provide this interface. +interface Writable # Write itself to a `stream` # The specific logic it let to the concrete subclasses - fun write_to(stream: OStream) is abstract + fun write_to(stream: Writer) is abstract # Like `write_to` but return a new String (may be quite large) # @@ -213,52 +370,92 @@ interface Streamable # stream without having to allocate and concatenate strings fun write_to_string: String do - var stream = new StringOStream + var stream = new StringWriter write_to(stream) return stream.to_s end end redef class Text - super Streamable + super Writable redef fun write_to(stream) do stream.write(self) end -# Input streams with a buffer -abstract class BufferedIStream - super IStream +# Input streams with a buffered input for efficiency purposes +abstract class BufferedReader + super Reader redef fun read_char do - if _buffer_pos >= _buffer.length then - fill_buffer + if last_error != null then return null + if eof then + last_error = new IOError("Stream has reached eof") + return null end - if _buffer_pos >= _buffer.length then - return -1 + var c = _buffer[_buffer_pos] + _buffer_pos += 1 + return c + end + + redef fun read_byte + do + if last_error != null then return null + if eof then + last_error = new IOError("Stream has reached eof") + return null end - var c = _buffer.chars[_buffer_pos] + var c = _buffer[_buffer_pos].ascii _buffer_pos += 1 - return c.ascii + return c + end + + # Peeks up to `n` bytes in the buffer, returns an empty string on EOF + # + # The operation does not consume the buffer + # + # ~~~nitish + # var x = new FileReader("File.txt") + # assert x.peek(5) == x.read(5) + # ~~~ + fun peek(i: Int): String do + if eof then return "" + var b = new FlatBuffer.with_capacity(i) + while i > 0 and not eof do + b.add _buffer[_buffer_pos] + _buffer_pos += 1 + i -= 1 + end + var nbuflen = b.length + (_buffer.length - _buffer_pos) + var nbuf = new FlatBuffer.with_capacity(nbuflen) + nbuf.append(b) + while _buffer_pos < _buffer.length do + nbuf.add(_buffer[_buffer_pos]) + _buffer_pos += 1 + end + _buffer_pos = 0 + _buffer = nbuf + return b.to_s end redef fun read(i) do - if _buffer.length == _buffer_pos then - if not eof then - return read(i) - end - return "" - end - if _buffer_pos + i >= _buffer.length then - var from = _buffer_pos - _buffer_pos = _buffer.length - return _buffer.substring_from(from).to_s + if last_error != null then return "" + if eof then return "" + var p = _buffer_pos + var bufsp = _buffer.length - p + if bufsp >= i then + _buffer_pos += i + return _buffer.substring(p, i).to_s end - _buffer_pos += i - return _buffer.substring(_buffer_pos - i, i).to_s + _buffer_pos = _buffer.length + var readln = _buffer.length - p + var s = _buffer.substring(p, readln).to_s + fill_buffer + return s + read(i - readln) end redef fun read_all do + if last_error != null then return "" var s = new FlatBuffer while not eof do var j = _buffer_pos @@ -278,11 +475,11 @@ abstract class BufferedIStream loop # First phase: look for a '\n' var i = _buffer_pos - while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1 + while i < _buffer.length and _buffer[i] != '\n' do i += 1 var eol if i < _buffer.length then - assert _buffer.chars[i] == '\n' + assert _buffer[i] == '\n' i += 1 eol = true else @@ -297,7 +494,7 @@ abstract class BufferedIStream # Copy from the buffer to the string var j = _buffer_pos while j < i do - s.add(_buffer.chars[j]) + s.add(_buffer[j]) j += 1 end _buffer_pos = i @@ -345,146 +542,17 @@ abstract class BufferedIStream end end -# An Input/Output Stream -interface IOStream - super IStream - super OStream +# A `Stream` that can be written to and read from +abstract class Duplex + super Reader + super Writer end -##############################################################" - -# A File Descriptor Stream. -abstract class FDStream - super IOS - # File description - var fd: Int - - redef fun close do native_close(fd) - - private fun native_close(i: Int): Int is extern "stream_FDStream_FDStream_native_close_1" - private fun native_read_char(i: Int): Int is extern "stream_FDStream_FDStream_native_read_char_1" - private fun native_read(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_read_3" - private fun native_write(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_write_3" - private fun native_write_char(i: Int, c: Char): Int is extern "stream_FDStream_FDStream_native_write_char_2" -end - -# An Input File Descriptor Stream. -class FDIStream - super FDStream - super IStream - redef var eof: Bool = false - - redef fun read_char - do - var nb = native_read_char(fd) - if nb == -1 then eof = true - return nb - end -end - -# An Output File Descriptor Stream. -class FDOStream - super FDStream - super OStream - redef var is_writable = true - - redef fun write(s) - do - var nb = native_write(fd, s.to_cstring, s.length) - if nb < s.length then is_writable = false - end -end - -# An Input/Output File Descriptor Stream. -class FDIOStream - super FDIStream - super FDOStream - super IOStream -end - -redef interface Object - # returns first available stream to read or write to - # return null on interruption (possibly a signal) - protected fun poll( streams : Sequence[FDStream] ) : nullable FDStream - do - var in_fds = new Array[Int] - var out_fds = new Array[Int] - var fd_to_stream = new HashMap[Int,FDStream] - for s in streams do - var fd = s.fd - if s isa FDIStream then in_fds.add( fd ) - if s isa FDOStream then out_fds.add( fd ) - - fd_to_stream[fd] = s - end - - var polled_fd = intern_poll( in_fds, out_fds ) - - if polled_fd == null then - return null - else - return fd_to_stream[polled_fd] - end - end - - 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) `{ - int in_len, out_len, total_len; - struct pollfd *c_fds; - sigset_t sigmask; - int i; - int first_polled_fd = -1; - int result; - - in_len = Array_of_Int_length( in_fds ); - out_len = Array_of_Int_length( out_fds ); - total_len = in_len + out_len; - c_fds = malloc( sizeof(struct pollfd) * total_len ); - - /* input streams */ - for ( i=0; i 0 ) { - /* analyse results */ - for ( i=0; i= source.length end