# You are allowed to redistribute it and sell it, alone or is a part of
# another product.
-# This module handle abstract input and output streams
-package stream
+# Input and output streams of characters
+module stream
-import string
+intrude import ropes
+
+in "C" `{
+ #include <unistd.h>
+ #include <poll.h>
+ #include <errno.h>
+ #include <string.h>
+ #include <signal.h>
+`}
# Abstract stream class
-class IOS
+interface IOS
# close the stream
- meth close is abstract
+ fun close is abstract
end
# Abstract input streams
-class IStream
-special IOS
+interface IStream
+ super IOS
# Read a character. Return its ASCII value, -1 on EOF or timeout
- meth read_char: Int is abstract
+ fun read_char: Int is abstract
# Read at most i bytes
- meth read(i: Int): String
+ fun read(i: Int): String
do
- var s = new String.with_capacity(i)
+ var s = new FlatBuffer.with_capacity(i)
while i > 0 and not eof do
var c = read_char
if c >= 0 then
i -= 1
end
end
- return s
+ return s.to_s
end
# Read a string until the end of the line.
- meth read_line: String
+ fun read_line: String
do
assert not eof
- var s = new String
+ var s = new FlatBuffer
append_line_to(s)
- return s
+ return s.to_s
end
# Read all the stream until the eof.
- meth read_all: String
+ fun read_all: String
do
- var s = ""
+ var s = new FlatBuffer
while not eof do
var c = read_char
if c >= 0 then s.add(c.ascii)
end
- return s
+ return s.to_s
end
- # Read a string until the end of the line and append it to `s'.
- meth append_line_to(s: String)
+ # Read a string until the end of the line and append it to `s`.
+ fun append_line_to(s: Buffer)
do
- while true do
+ loop
var x = read_char
if x == -1 then
if eof then return
else
var c = x.ascii
- s.push(c)
+ s.chars.push(c)
if c == '\n' then return
end
end
end
# Is there something to read.
- meth eof: Bool is abstract
+ # This function returns 'false' if there is something to read.
+ fun eof: Bool is abstract
+end
+
+# IStream capable of declaring if readable without blocking
+interface PollableIStream
+ super IStream
+
+ # Is there something to read? (without blocking)
+ fun poll_in: Bool is abstract
+
end
# Abstract output stream
-class OStream
-special IOS
+interface OStream
+ super IOS
# write a string
- meth write(s: String) is abstract
+ fun write(s: Text) is abstract
# Can the stream be used to write
- meth is_writable: Bool is abstract
+ fun is_writable: Bool is abstract
+end
+
+# Things that can be efficienlty writen to a OStream
+#
+# 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
+#
+# ready-to-save documents usually provide this interface.
+interface Streamable
+ # Write itself to a `stream`
+ # The specific logic it let to the concrete subclasses
+ fun write_to(stream: OStream) is abstract
+
+ # Like `write_to` but return a new String (may be quite large)
+ #
+ # This funtionnality is anectodical, since the point
+ # of streamable object to to be efficienlty written to a
+ # stream without having to allocate and concatenate strings
+ fun write_to_string: String
+ do
+ var stream = new StringOStream
+ write_to(stream)
+ return stream.to_s
+ end
+end
+
+redef class Text
+ super Streamable
+ redef fun write_to(stream) do stream.write(self)
+end
+
+redef class RopeNode
+ super Streamable
+end
+
+redef class Leaf
+
+ redef fun write_to(s) do s.write(str)
+end
+
+redef class Concat
+
+ redef fun write_to(s)
+ do
+ if left != null then left.write_to(s)
+ if right != null then right.write_to(s)
+ end
+end
+
+redef class RopeString
+
+ redef fun write_to(s) do root.write_to(s)
end
# Input streams with a buffer
-class BufferedIStream
-special IStream
- redef meth read_char
+abstract class BufferedIStream
+ super IStream
+ redef fun read_char
do
assert not eof
if _buffer_pos >= _buffer.length then
if _buffer_pos >= _buffer.length then
return -1
end
- var c = _buffer[_buffer_pos]
+ var c = _buffer.chars[_buffer_pos]
_buffer_pos += 1
return c.ascii
end
- redef meth read(i)
+ redef fun read(i)
do
- var s = new String.with_capacity(i)
- var j = _buffer_pos
- var k = _buffer.length
- while i > 0 do
- if j >= k then
+ if _buffer.length == _buffer_pos then
+ if not eof then
fill_buffer
- if eof then return s
- j = _buffer_pos
- k = _buffer.length
- end
- while j < k and i > 0 do
- s.add(_buffer[j])
- j += 1
- i -= 1
+ 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
end
- _buffer_pos = j
- return s
+ _buffer_pos += i
+ return _buffer.substring(_buffer_pos - i, i).to_s
end
- redef meth read_all
+ redef fun read_all
do
- var s = ""
+ var s = new FlatBuffer
while not eof do
var j = _buffer_pos
var k = _buffer.length
_buffer_pos = j
fill_buffer
end
- return s
+ return s.to_s
end
- redef meth append_line_to(s)
+ redef fun append_line_to(s)
do
- while true do
+ loop
# First phase: look for a '\n'
var i = _buffer_pos
- while i < _buffer.length and _buffer[i] != '\n' do i += 1
+ while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1
# if there is something to append
if i > _buffer_pos then
# Copy from the buffer to the string
var j = _buffer_pos
while j < i do
- s.add(_buffer[j])
+ s.add(_buffer.chars[j])
j += 1
end
end
end
end
- redef meth eof do return _buffer_pos >= _buffer.length and end_reached
+ redef fun eof do return _buffer_pos >= _buffer.length and end_reached
# The buffer
- attr _buffer: String = null
+ private var buffer: nullable FlatBuffer = null
# The current position in the buffer
- attr _buffer_pos: Int = 0
+ private var buffer_pos: Int = 0
# Fill the buffer
- protected meth fill_buffer is abstract
+ protected fun fill_buffer is abstract
# Is the last fill_buffer reach the end
- protected meth end_reached: Bool is abstract
+ protected fun end_reached: Bool is abstract
- # Allocate a `_buffer' for a given `capacity'.
- protected meth prepare_buffer(capacity: Int)
+ # Allocate a `_buffer` for a given `capacity`.
+ protected fun prepare_buffer(capacity: Int)
do
- _buffer = new String.with_capacity(capacity)
+ _buffer = new FlatBuffer.with_capacity(capacity)
_buffer_pos = 0 # need to read
end
end
-class IOStream
-special IStream
-special OStream
+interface IOStream
+ super IStream
+ super OStream
end
##############################################################"
-class FDStream
-special IOS
+abstract class FDStream
+ super IOS
# File description
- attr _fd: Int
-
- redef meth close do native_close(_fd)
+ var fd: Int
- private meth native_close(i: Int): Int is extern "stream_FDStream_FDStream_native_close_1"
- private meth native_read_char(i: Int): Int is extern "stream_FDStream_FDStream_native_read_char_1"
- private meth native_read(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_read_3"
- private meth native_write(i: Int, buf: NativeString, len: Int): Int is extern "stream_FDStream_FDStream_native_write_3"
+ redef fun close do native_close(fd)
- init(fd: Int) do _fd = 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
class FDIStream
-special FDStream
-special IStream
- redef readable attr _eof: Bool = false
+ super FDStream
+ super IStream
+ redef var eof: Bool = false
- redef meth read_char
+ redef fun read_char
do
- var nb = native_read_char(_fd)
- if nb == -1 then _eof = true
+ var nb = native_read_char(fd)
+ if nb == -1 then eof = true
return nb
end
-
- init(fd: Int) do end
end
class FDOStream
-special FDStream
-special OStream
- redef readable attr _is_writable: Bool
+ super FDStream
+ super OStream
+ redef var is_writable = true
- redef meth write(s)
+ redef fun write(s)
do
- var nb = native_write(_fd, s.to_cstring, s.length)
- if nb < s.length then _is_writable = false
+ var nb = native_write(fd, s.to_cstring, s.length)
+ if nb < s.length then is_writable = false
end
+end
- init(fd: Int)
+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
- _is_writable = true
+ 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<in_len; i ++ ) {
+ int fd;
+ fd = Array_of_Int__index( in_fds, i );
+
+ c_fds[i].fd = fd;
+ c_fds[i].events = POLLIN;
+ }
+
+ /* output streams */
+ for ( i=0; i<out_len; i ++ ) {
+ int fd;
+ fd = Array_of_Int__index( out_fds, i );
+
+ c_fds[i].fd = fd;
+ c_fds[i].events = POLLOUT;
+ }
+
+ /* poll all fds, unlimited timeout */
+ result = poll( c_fds, total_len, -1 );
+
+ if ( result > 0 ) {
+ /* analyse results */
+ for ( i=0; i<total_len; i++ )
+ if ( c_fds[i].revents & c_fds[i].events || /* awaited event */
+ c_fds[i].revents & POLLHUP ) /* closed */
+ {
+ first_polled_fd = c_fds[i].fd;
+ break;
+ }
+
+ return Int_as_nullable( first_polled_fd );
+ }
+ else if ( result < 0 )
+ fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
+
+ return null_Int();
+ `}
end
-class FDIOStream
-special FDIStream
-special FDOStream
-special IOStream
- init(fd: Int)
+# Stream to a String. Mainly used for compatibility with OStream type and tests.
+class StringOStream
+ super OStream
+
+ private var content = new Array[String]
+ redef fun to_s do return content.to_s
+ redef fun is_writable do return not closed
+ redef fun write(str)
do
- _fd = fd
- _is_writable = true
+ assert not closed
+ content.add(str.to_s)
end
+
+ protected var closed = false
+ redef fun close do closed = true
end