# 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
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
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
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
# 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`.
# 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
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
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
# 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
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