tests: add test_keep_going.nit
[nit.git] / lib / standard / stream.nit
index 6079d95..7c4e606 100644 (file)
@@ -42,8 +42,11 @@ end
 # A `Stream` that can be read from
 abstract class Reader
        super Stream
-       # Read a character. Return its ASCII value, -1 on EOF or timeout
-       fun read_char: Int is abstract
+       # 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
@@ -52,8 +55,8 @@ abstract class Reader
                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
@@ -164,7 +167,7 @@ abstract class Reader
                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
@@ -207,12 +210,11 @@ abstract class Reader
                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
@@ -220,6 +222,65 @@ abstract class Reader
        # 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`.
@@ -284,6 +345,9 @@ abstract class Writer
        # 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
@@ -322,33 +386,71 @@ abstract class BufferedReader
        super Reader
        redef fun read_char
        do
-               if last_error != null then return -1
+               if last_error != null then return null
+               if eof then
+                       last_error = new IOError("Stream has reached eof")
+                       return null
+               end
+               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 -1
+                       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 last_error != null then return ""
-               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
-                       if from == 0 then return _buffer.to_s
-                       return _buffer.substring_from(from).to_s
+               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
@@ -373,11 +475,11 @@ abstract class BufferedReader
                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
@@ -392,7 +494,7 @@ abstract class BufferedReader
                                # 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
@@ -481,12 +583,23 @@ class StringReader
 
        redef fun read_char do
                if cursor < source.length then
-                       var c = source[cursor].ascii
+                       var c = source[cursor]
 
                        cursor += 1
                        return c
                else
-                       return -1
+                       return null
+               end
+       end
+
+       redef fun read_byte do
+               if cursor < source.length then
+                       var c = source[cursor]
+
+                       cursor += 1
+                       return c.ascii
+               else
+                       return null
                end
        end