Merge: Newstreams
authorJean Privat <jean@pryen.org>
Sat, 13 Dec 2014 07:49:28 +0000 (02:49 -0500)
committerJean Privat <jean@pryen.org>
Sat, 13 Dec 2014 07:49:28 +0000 (02:49 -0500)
Small refactoring of Streams/Files:
- Added a simple error management system
- Removed FDStreams, reified under FStream

Next PR to come: Proposition for renaming of the classes of the stream hierarchy (as requested in #466)

Pull-Request: #932
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Jean Privat <jean@pryen.org>

1  2 
lib/pnacl.nit
lib/standard/exec.nit
lib/standard/file.nit
lib/standard/stream.nit
src/interpreter/naive_interpreter.nit

diff --combined lib/pnacl.nit
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 -#
 -# Targets the PNaCl platform
 +
 +# Provides PNaCl support for Nit.
  #
  # To use this module and compile for PNaCl, you must install the
  # NaCl SDK (This file is based on Pepper 33).
  # If NACL_SDK_ROOT is not set in your PATH, you have to work in
  # 'nacl_sdk/pepper_your_pepper_version/getting_started/your_project_folder'.
 -#
 -# Provides PNaCl support for Nit.
  module pnacl is platform
  
  import standard
@@@ -44,6 -46,7 +44,7 @@@ in "C Header" `
        #include <string.h>
        #include <stdlib.h>
        #include <pthread.h>
+       #include <poll.h>
  
        #define MAX_DICTIONARY_QUEUE_SIZE 200
        #define MAX_MESSAGE_QUEUE_SIZE 10
        }
  
        /* Hack in order to avoid the problem with file. */
-       int poll(void *fds, int nfds, int timeout) { return 0; }
+       int poll(struct pollfd* fds, nfds_t nfds, int timeout) { return 0; }
  `}
  
  # Nit class representing a Pepper C API PP_Var typed as a Dictionary.
diff --combined lib/standard/exec.nit
@@@ -15,7 -15,7 +15,7 @@@
  # Standard input and output can be handled through streams.
  module exec
  
- import stream
+ import file
  
  # Simple sub-process
  class Process
@@@ -48,7 -48,8 +48,7 @@@
        var arguments: nullable Array[String]
  
        # Launch a command with some arguments
 -      init(command: String, arguments: String...)
 -      do
 +      init(command: String, arguments: String...) is old_style_init do
                self.command = command
                self.arguments = arguments
                execute
@@@ -91,9 -92,7 +91,9 @@@ en
  class IProcess
        super Process
        super IStream
 +
 +      # File Descriptor used for the input.
-       var stream_in: FDIStream is noinit
+       var stream_in: IFStream is noinit
  
        redef fun close do stream_in.close
  
        redef fun execute
        do
                super
-               stream_in = new FDIStream(data.out_fd)
+               stream_in = new IFStream.from_fd(data.out_fd)
        end
  end
  
  class OProcess
        super Process
        super OStream
 +
 +      # File Descriptor used for the output.
        var stream_out: OStream is noinit
  
        redef fun close do stream_out.close
        redef fun execute
        do
                super
-               stream_out = new FDOStream(data.in_fd)
+               stream_out = new OFStream.from_fd(data.in_fd)
        end
  end
  
@@@ -162,9 -159,6 +162,9 @@@ redef class Sy
  end
  
  redef class NativeString
 +      # Execute self as a shell command.
 +      #
 +      # See the posix function system(3).
        fun system: Int is extern "string_NativeString_NativeString_system_0"
  end
  
diff --combined lib/standard/file.nit
@@@ -27,6 -27,8 +27,8 @@@ in "C Header" `
        #include <sys/stat.h>
        #include <unistd.h>
        #include <stdio.h>
+       #include <poll.h>
+       #include <errno.h>
  `}
  
  # File Abstract Stream
@@@ -38,11 -40,25 +40,26 @@@ abstract class FStrea
        # The FILE *.
        private var file: nullable NativeFile = null
  
 +      # The status of a file. see POSIX stat(2).
        fun file_stat: FileStat do return _file.file_stat
  
        # File descriptor of this file
        fun fd: Int do return _file.fileno
+       # Sets the buffering mode for the current FStream
+       #
+       # If the buf_size is <= 0, its value will be 512 by default
+       #
+       # The mode is any of the buffer_mode enumeration in `Sys`:
+       #       - buffer_mode_full
+       #       - buffer_mode_line
+       #       - buffer_mode_none
+       fun set_buffering_mode(buf_size, mode: Int) do
+               if buf_size <= 0 then buf_size = 512
+               if _file.set_buffering_type(buf_size, mode) != 0 then
+                       last_error = new IOError("Error while changing buffering type for FStream, returned error {sys.errno.strerror}")
+               end
+       end
  end
  
  # File input stream
@@@ -56,8 -72,14 +73,14 @@@ class IFStrea
        # The original path is reused, therefore the reopened file can be a different file.
        fun reopen
        do
-               if not eof then close
+               if not eof and not _file.address_is_null then close
+               last_error = null
                _file = new NativeFile.io_open_read(path.to_cstring)
+               if _file.address_is_null then
+                       last_error = new IOError("Error: Opening file at '{path.as(not null)}' failed with '{sys.errno.strerror}'")
+                       end_reached = true
+                       return
+               end
                end_reached = false
                _buffer_pos = 0
                _buffer.clear
@@@ -65,7 -87,8 +88,8 @@@
  
        redef fun close
        do
-               _file.io_close
+               if _file.address_is_null then return
+               var i = _file.io_close
                _buffer.clear
                end_reached = true
        end
                _buffer.length = nb
                _buffer_pos = 0
        end
-       
        # End of file?
        redef var end_reached: Bool = false
  
                self.path = path
                prepare_buffer(10)
                _file = new NativeFile.io_open_read(path.to_cstring)
-               assert not _file.address_is_null else
-                       print "Error: Opening file at '{path}' failed with '{sys.errno.strerror}'"
+               if _file.address_is_null then
+                       last_error = new IOError("Error: Opening file at '{path}' failed with '{sys.errno.strerror}'")
+                       end_reached = true
                end
        end
  
+       init from_fd(fd: Int) do
+               self.path = ""
+               prepare_buffer(1)
+               _file = fd.fd_to_stream(read_only)
+               if _file.address_is_null then
+                       last_error = new IOError("Error: Converting fd {fd} to stream failed with '{sys.errno.strerror}'")
+                       end_reached = true
+               end
+       end
  end
  
  # File output stream
@@@ -104,30 -137,52 +138,52 @@@ class OFStrea
        
        redef fun write(s)
        do
-               assert _is_writable
+               if last_error != null then return
+               if not _is_writable then
+                       last_error = new IOError("Cannot write to non-writable stream")
+                       return
+               end
                if s isa FlatText then
                        write_native(s.to_cstring, s.length)
                else
                        for i in s.substrings do write_native(i.to_cstring, i.length)
                end
+               _file.flush
        end
  
        redef fun close
        do
-               _file.io_close
+               if _file.address_is_null then
+                       if last_error != null then return
+                       last_error = new IOError("Cannot close unopened write stream")
+                       _is_writable = false
+                       return
+               end
+               var i = _file.io_close
+               if i != 0 then
+                       last_error = new IOError("Close failed due to error {sys.errno.strerror}")
+               end
                _is_writable = false
        end
        redef var is_writable = false
        
        # Write `len` bytes from `native`.
        private fun write_native(native: NativeString, len: Int)
        do
-               assert _is_writable
+               if last_error != null then return
+               if not _is_writable then
+                       last_error = new IOError("Cannot write to non-writable stream")
+                       return
+               end
+               if _file.address_is_null then
+                       last_error = new IOError("Writing on a null stream")
+                       _is_writable = false
+                       return
+               end
                var err = _file.io_write(native, len)
                if err != len then
                        # Big problem
-                       printn("Problem in writing : ", err, " ", len, "\n")
+                       last_error = new IOError("Problem in writing : {err} {len} \n")
                end
        end
        
        init open(path: String)
        do
                _file = new NativeFile.io_open_write(path.to_cstring)
-               assert not _file.address_is_null else
-                       print "Error: Opening file at '{path}' failed with '{sys.errno.strerror}'"
-               end
                self.path = path
                _is_writable = true
+               if _file.address_is_null then
+                       last_error = new IOError("Error: Opening file at '{path}' failed with '{sys.errno.strerror}'")
+                       is_writable = false
+               end
+       end
+       # Creates a new File stream from a file descriptor
+       init from_fd(fd: Int) do
+               self.path = ""
+               _file = fd.fd_to_stream(wipe_write)
+               _is_writable = true
+                if _file.address_is_null then
+                        last_error = new IOError("Error: Opening stream from file descriptor {fd} failed with '{sys.errno.strerror}'")
+                       _is_writable = false
+               end
        end
  end
  
+ redef class Int
+       # Creates a file stream from a file descriptor `fd` using the file access `mode`.
+       #
+       # NOTE: The `mode` specified must be compatible with the one used in the file descriptor.
+       private fun fd_to_stream(mode: NativeString): NativeFile is extern "file_int_fdtostream"
+ end
+ # Constant for read-only file streams
+ private fun read_only: NativeString do return "r".to_cstring
+ # Constant for write-only file streams
+ #
+ # If a stream is opened on a file with this method,
+ # it will wipe the previous file if any.
+ # Else, it will create the file.
+ private fun wipe_write: NativeString do return "w".to_cstring
  ###############################################################################
  
 +# Standard input stream.
  class Stdin
        super IFStream
  
        redef fun poll_in: Bool is extern "file_stdin_poll_in"
  end
  
 +# Standard output stream.
  class Stdout
        super OFStream
        init do
        end
  end
  
 +# Standard error stream.
  class Stderr
        super OFStream
        init do
@@@ -531,7 -612,7 +616,7 @@@ redef class Strin
        end
  
        # returns files contained within the directory represented by self
 -      fun files : Set[ String ] is extern import HashSet[String], HashSet[String].add, NativeString.to_s, String.to_cstring, HashSet[String].as(Set[String]) `{
 +      fun files: Array[String] is extern import Array[String], Array[String].add, NativeString.to_s, String.to_cstring `{
                char *dir_path;
                DIR *dir;
  
                }
                else
                {
 -                      HashSet_of_String results;
 +                      Array_of_String results;
                        String file_name;
                        struct dirent *de;
  
 -                      results = new_HashSet_of_String();
 +                      results = new_Array_of_String();
  
                        while ( ( de = readdir( dir ) ) != NULL )
                                if ( strcmp( de->d_name, ".." ) != 0 &&
                                        strcmp( de->d_name, "." ) != 0 )
                                {
                                        file_name = NativeString_to_s( strdup( de->d_name ) );
 -                                      HashSet_of_String_add( results, file_name );
 +                                      Array_of_String_add( results, file_name );
                                }
  
                        closedir( dir );
 -                      return HashSet_of_String_as_Set_of_String( results );
 +                      return results;
                }
        `}
  end
@@@ -617,6 -698,10 +702,10 @@@ private extern class NativeFile `{ FILE
        fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
        fun file_stat: FileStat is extern "file_NativeFile_NativeFile_file_stat_0"
        fun fileno: Int `{ return fileno(recv); `}
+       # Flushes the buffer, forcing the write operation
+       fun flush: Int is extern "fflush"
+       # Used to specify how the buffering will be handled for the current stream.
+       fun set_buffering_type(buf_length: Int, mode: Int): Int is extern "file_NativeFile_NativeFile_set_buffering_type_0"
  
        new io_open_read(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_read_1"
        new io_open_write(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_write_1"
@@@ -627,6 -712,10 +716,10 @@@ en
  
  redef class Sys
  
+       init do
+               if stdout isa FStream then stdout.as(FStream).set_buffering_mode(256, buffer_mode_line)
+       end
        # Standard input
        var stdin: PollableIStream = new Stdin is protected writable
  
        # Standard output for errors
        var stderr: OStream = new Stderr is protected writable
  
+       # Enumeration for buffer mode full (flushes when buffer is full)
+       fun buffer_mode_full: Int is extern "file_Sys_Sys_buffer_mode_full_0"
+       # Enumeration for buffer mode line (flushes when a `\n` is encountered)
+       fun buffer_mode_line: Int is extern "file_Sys_Sys_buffer_mode_line_0"
+       # Enumeration for buffer mode none (flushes ASAP when something is written)
+       fun buffer_mode_none: Int is extern "file_Sys_Sys_buffer_mode_none_0"
+       # returns first available stream to read or write to
+       # return null on interruption (possibly a signal)
+       protected fun poll( streams : Sequence[FStream] ) : nullable FStream
+       do
+               var in_fds = new Array[Int]
+               var out_fds = new Array[Int]
+               var fd_to_stream = new HashMap[Int,FStream]
+               for s in streams do
+                       var fd = s.fd
+                       if s isa IFStream then in_fds.add( fd )
+                       if s isa OFStream 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
  
  # Print `objects` on the standard output (`stdout`).
diff --combined lib/standard/stream.nit
  module stream
  
  intrude import ropes
+ import error
  
  in "C" `{
        #include <unistd.h>
-       #include <poll.h>
-       #include <errno.h>
        #include <string.h>
        #include <signal.h>
  `}
  
+ # Any kind of error that could be produced by an operation on Streams
+ class IOError
+       super Error
+ end
  # Abstract stream class
- interface IOS
+ abstract class IOS
+       # Error produced by the file stream
+       #
+       #     var ifs = new IFStream.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
+ abstract class IStream
        super IOS
        # Read a character. Return its ASCII value, -1 on EOF or timeout
        fun read_char: Int is abstract
@@@ -36,6 -48,7 +48,7 @@@
        # 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
        end
  
        # Read a string until the end of the line.
 +      #
 +      # The line terminator '\n', if any, is preserved in each line.
 +      # Use the method `Text::chomp` to safely remove it.
 +      #
 +      # ~~~
 +      # var txt = "Hello\n\nWorld\n"
 +      # var i = new StringIStream(txt)
 +      # assert i.read_line == "Hello\n"
 +      # assert i.read_line == "\n"
 +      # assert i.read_line == "World\n"
 +      # assert i.eof
 +      # ~~~
 +      #
 +      # If `\n` is not present at the end of the result, it means that
 +      # a non-eol terminated last line was returned.
 +      #
 +      # ~~~
 +      # var i2 = new StringIStream("hello")
 +      # assert not i2.eof
 +      # assert i2.read_line == "hello"
 +      # assert i2.eof
 +      # ~~~
 +      #
 +      # NOTE: Only LINE FEED (`\n`) is considered to delimit the end of lines.
        fun read_line: String
        do
+               if last_error != null then return ""
                assert not eof
                var s = new FlatBuffer
                append_line_to(s)
                return s.to_s
        end
  
 +      # Read all the lines until the eof.
 +      #
 +      # The line terminator '\n' is removed in each line,
 +      #
 +      # ~~~
 +      # var txt = "Hello\n\nWorld\n"
 +      # var i = new StringIStream(txt)
 +      # assert i.read_lines == ["Hello", "", "World"]
 +      # ~~~
 +      #
 +      # This method is more efficient that splitting
 +      # the result of `read_all`.
 +      #
 +      # NOTE: Only LINE FEED (`\n`) is considered to delimit the end of lines.
 +      fun read_lines: Array[String]
 +      do
 +              var res = new Array[String]
 +              while not eof do
 +                      res.add read_line.chomp
 +              end
 +              return res
 +      end
 +
        # Read all the stream until the eof.
        fun read_all: String
        do
+               if last_error != null then return ""
                var s = new FlatBuffer
                while not eof do
                        var c = read_char
        end
  
        # Read a string until the end of the line and append it to `s`.
 +      #
 +      # SEE: `read_line` for details.
        fun append_line_to(s: Buffer)
        do
+               if last_error != null then return
                loop
                        var x = read_char
                        if x == -1 then
  end
  
  # IStream capable of declaring if readable without blocking
- interface PollableIStream
+ abstract class PollableIStream
        super IStream
  
        # Is there something to read? (without blocking)
  end
  
  # Abstract output stream
- interface OStream
+ abstract class OStream
        super IOS
        # write a string
        fun write(s: Text) is abstract
@@@ -189,7 -156,8 +205,8 @@@ abstract class BufferedIStrea
        super IStream
        redef fun read_char
        do
-               assert not eof
+               if last_error != null then return 0
+               if eof then last_error = new IOError("Stream has reached eof")
                if _buffer_pos >= _buffer.length then
                        fill_buffer
                end
  
        redef fun read(i)
        do
+               if last_error != null then return ""
                if _buffer.length == _buffer_pos then
                        if not eof then
                                fill_buffer
  
        redef fun read_all
        do
+               if last_error != null then return ""
                var s = new FlatBuffer
                while not eof do
                        var j = _buffer_pos
        end
  end
  
 +# An Input/Output Stream
- interface IOStream
+ abstract class IOStream
        super IStream
        super OStream
  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<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
  # Stream to a String.
  #
  # Mainly used for compatibility with OStream type and tests.
@@@ -443,9 -283,7 +333,9 @@@ class StringOStrea
                content.add(str.to_s)
        end
  
 +      # Is the stream closed?
        protected var closed = false
 +
        redef fun close do closed = true
  end
  
@@@ -21,6 -21,7 +21,7 @@@ import litera
  import semantize
  private import parser::tables
  import mixin
+ import primitive_types
  
  redef class ToolContext
        # --discover-call-trace
@@@ -75,7 -76,7 +76,7 @@@ class NaiveInterprete
                init_instance_primitive(self.true_instance)
                self.false_instance = new PrimitiveInstance[Bool](mainmodule.bool_type, false)
                init_instance_primitive(self.false_instance)
 -              self.null_instance = new MutableInstance(mainmodule.model.null_type)
 +              self.null_instance = new PrimitiveInstance[nullable Object](mainmodule.model.null_type, null)
        end
  
        # Starts the interpreter on the main module of a program
        # Subtype test in the context of the mainmodule
        fun is_subtype(sub, sup: MType): Bool
        do
 -              return sub.is_subtype(self.mainmodule, self.frame.arguments.first.mtype.as(MClassType), sup)
 +              return sub.is_subtype(self.mainmodule, current_receiver_class, sup)
        end
  
 +      # Get a primitive method in the context of the main module
        fun force_get_primitive_method(name: String, recv: MType): MMethod
        do
                assert recv isa MClassType
 -              return self.modelbuilder.force_get_primitive_method(self.frame.current_node, name, recv.mclass, self.mainmodule)
 +              return self.modelbuilder.force_get_primitive_method(current_node, name, recv.mclass, self.mainmodule)
        end
  
        # Is a return executed?
                return res
        end
  
 +      # Return a instance associated to a primitive class
 +      # Current primitive classes are `Int`, `Bool`, and `String`
        fun value_instance(object: Object): Instance
        do
                if object isa Int then
                return b.to_s
        end
  
 +      # The current node, used to print errors, debug and stack-traces
 +      fun current_node: nullable ANode
 +      do
 +              if frames.is_empty then return null
 +              return frames.first.current_node
 +      end
 +
 +      # The dynamic type of the current `self`
 +      fun current_receiver_class: MClassType
 +      do
 +              return frames.first.arguments.first.mtype.as(MClassType)
 +      end
 +
        # Exit the program with a message
        fun fatal(message: String)
        do
 -              if frames.is_empty then
 +              var node = current_node
 +              if node == null then
                        print message
                else
 -                      self.frame.current_node.fatal(self, message)
 +                      node.fatal(self, message)
                end
                exit(1)
        end
        # Debug on the current node
        fun debug(message: String)
        do
 -              if frames.is_empty then
 +              var node = current_node
 +              if node == null then
                        print message
                else
 -                      self.frame.current_node.debug(message)
 +                      node.debug(message)
                end
        end
  
                assert args.length == mpropdef.msignature.arity + 1 else debug("Invalid arity for {mpropdef}. {args.length} arguments given.")
  
                # Look for the AST node that implements the property
 -              var mproperty = mpropdef.mproperty
                var val = mpropdef.constant_value
 -              if self.modelbuilder.mpropdef2npropdef.has_key(mpropdef) then
 -                      var npropdef = self.modelbuilder.mpropdef2npropdef[mpropdef]
 -                      self.parameter_check(npropdef, mpropdef, args)
 -                      return npropdef.call(self, mpropdef, args)
 -              else if mproperty.is_root_init then
 -                      var nclassdef = self.modelbuilder.mclassdef2nclassdef[mpropdef.mclassdef]
 -                      self.parameter_check(nclassdef, mpropdef, args)
 -                      return nclassdef.call(self, mpropdef, args)
 +
 +              var node = modelbuilder.mpropdef2node(mpropdef)
 +              if node isa APropdef then
 +                      self.parameter_check(node, mpropdef, args)
 +                      return node.call(self, mpropdef, args)
 +              else if node isa AClassdef then
 +                      self.parameter_check(node, mpropdef, args)
 +                      return node.call(self, mpropdef, args)
 +              else if node != null then
 +                      fatal("Fatal Error: method {mpropdef} associated to unexpected AST node {node.location}")
 +                      abort
                else if val != null then
                        return value_instance(val)
                else
                end
        end
  
 -      # Generate type checks in the C code to check covariant parameters
 +      # Execute type checks of covariant parameters
        fun parameter_check(node: ANode, mpropdef: MMethodDef, args: Array[Instance])
        do
                var msignature = mpropdef.msignature
                        var origmtype =  mpropdef.mproperty.intro.msignature.mparameters[i].mtype
                        if not origmtype.need_anchor then continue
  
 +                      #print "{mpropdef}: {mpropdef.mproperty.intro.msignature.mparameters[i]}"
 +
                        # get the parameter type
                        var mtype = msignature.mparameters[i].mtype
                        var anchor = args.first.mtype.as(MClassType)
                                        self.send(p, args)
                                else if p isa MAttribute then
                                        assert recv isa MutableInstance
 -                                      recv.attributes[p] = arguments[i]
 +                                      write_attribute(p, recv, arguments[i])
                                        i += 1
                                else abort
                        end
                var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
                self.mainmodule.linearize_mclassdefs(cds)
                for cd in cds do
 -                      if not self.modelbuilder.mclassdef2nclassdef.has_key(cd) then continue
 -                      var n = self.modelbuilder.mclassdef2nclassdef[cd]
 -                      for npropdef in n.n_propdefs do
 -                              if npropdef isa AAttrPropdef then
 -                                      res.add(npropdef)
 -                              end
 -                      end
 +                      res.add_all(modelbuilder.collect_attr_propdef(cd))
                end
  
                cache[mtype] = res
                return mainmodule.get_primitive_class(name)
        end
  
 -      # This function determine the correct type according the reciever of the current definition (self).
 +      # This function determines the correct type according to the receiver of the current propdef (self).
        fun unanchor_type(mtype: MType): MType
        do
 -              return mtype.anchor_to(self.mainmodule, self.frame.arguments.first.mtype.as(MClassType))
 +              return mtype.anchor_to(self.mainmodule, current_receiver_class)
        end
  
        # Placebo instance used to mark internal error result when `null` already have a meaning.
@@@ -604,7 -589,7 +605,7 @@@ abstract class Instanc
  
        # The real value encapsulated if the instance is primitive.
        # Else aborts.
 -      fun val: Object do abort
 +      fun val: nullable Object do abort
  end
  
  # A instance with attribute (standards objects)
@@@ -617,7 -602,7 +618,7 @@@ en
  
  # Special instance to handle primitives values (int, bool, etc.)
  # The trick it just to encapsulate the <<real>> value
 -class PrimitiveInstance[E: Object]
 +class PrimitiveInstance[E]
        super Instance
  
        # The real value encapsulated
  
        redef fun ==(o)
        do
 -              if not o isa PrimitiveInstance[Object] then return false
 +              if not o isa PrimitiveInstance[nullable Object] then return false
                return self.val == o.val
        end
  
        redef fun eq_is(o)
        do
 -              if not o isa PrimitiveInstance[Object] then return false
 +              if not o isa PrimitiveInstance[nullable Object] then return false
                return self.val.is_same_instance(o.val)
        end
  
 -      redef fun to_s do return "{mtype}#{val.object_id}({val})"
 +      redef fun to_s do return "{mtype}#{val.object_id}({val or else "null"})"
  
        redef fun to_i do return val.as(Int)
  
@@@ -796,6 -781,12 +797,12 @@@ redef class AMethPropde
                else if pname == "exit" then
                        exit(args[1].to_i)
                        abort
+               else if pname == "buffer_mode_full" then
+                       return v.int_instance(sys.buffer_mode_full)
+               else if pname == "buffer_mode_line" then
+                       return v.int_instance(sys.buffer_mode_line)
+               else if pname == "buffer_mode_none" then
+                       return v.int_instance(sys.buffer_mode_none)
                else if pname == "sys" then
                        return v.mainobj
                else if cname == "Int" then
                                return v.bool_instance(args[0].to_f.is_nan)
                        else if pname == "is_inf_extern" then
                                return v.bool_instance(args[0].to_f.is_inf != 0)
 +                      else if pname == "round" then
 +                              return v.float_instance(args[0].to_f.round)
                        end
                else if cname == "NativeString" then
                        if pname == "new" then
                        else if pname == "atof" then
                                return v.float_instance(recvval.to_f)
                        end
 +              else if cname == "String" then
 +                      var cs = v.send(v.force_get_primitive_method("to_cstring", args.first.mtype), [args.first])
 +                      var str = cs.val.to_s
 +                      if pname == "files" then
 +                              var res = new Array[Instance]
 +                              for f in str.files do res.add v.string_instance(f)
 +                              return v.array_instance(res, v.get_primitive_class("String").mclass_type)
 +                      end
                else if pname == "calloc_string" then
                        return v.native_string_instance("!" * args[1].to_i)
                else if cname == "NativeArray" then
                        else if pname == "length" then
                                return v.int_instance(recvval.length)
                        else if pname == "copy_to" then
 -                              recvval.copy(0, args[2].to_i, args[1].val.as(Array[Instance]), 0)
 +                              recvval.copy_to(0, args[2].to_i, args[1].val.as(Array[Instance]), 0)
                                return null
                        end
                else if cname == "NativeFile" then
                        if pname == "native_stdout" then
-                               var instance = new PrimitiveInstance[OStream](mpropdef.mclassdef.mclass.mclass_type, sys.stdout)
+                               var inst = new PrimitiveNativeFile.native_stdout
+                               var instance = new PrimitiveInstance[PrimitiveNativeFile](mpropdef.mclassdef.mclass.mclass_type, inst)
                                v.init_instance_primitive(instance)
                                return instance
                        else if pname == "native_stdin" then
-                               var instance = new PrimitiveInstance[IStream](mpropdef.mclassdef.mclass.mclass_type, sys.stdin)
+                               var inst = new PrimitiveNativeFile.native_stdin
+                               var instance = new PrimitiveInstance[PrimitiveNativeFile](mpropdef.mclassdef.mclass.mclass_type, inst)
                                v.init_instance_primitive(instance)
                                return instance
                        else if pname == "native_stderr" then
-                               var instance = new PrimitiveInstance[OStream](mpropdef.mclassdef.mclass.mclass_type, sys.stderr)
+                               var inst = new PrimitiveNativeFile.native_stderr
+                               var instance = new PrimitiveInstance[PrimitiveNativeFile](mpropdef.mclassdef.mclass.mclass_type, inst)
                                v.init_instance_primitive(instance)
                                return instance
                        else if pname == "io_open_read" then
                                var a1 = args[1].val.as(Buffer)
-                               var instance = new PrimitiveInstance[IStream](mpropdef.mclassdef.mclass.mclass_type, new IFStream.open(a1.to_s))
+                               var inst = new PrimitiveNativeFile.io_open_read(a1.to_s)
+                               var instance = new PrimitiveInstance[PrimitiveNativeFile](mpropdef.mclassdef.mclass.mclass_type, inst)
                                v.init_instance_primitive(instance)
                                return instance
                        else if pname == "io_open_write" then
                                var a1 = args[1].val.as(Buffer)
-                               var instance = new PrimitiveInstance[OStream](mpropdef.mclassdef.mclass.mclass_type, new OFStream.open(a1.to_s))
+                               var inst = new PrimitiveNativeFile.io_open_write(a1.to_s)
+                               var instance = new PrimitiveInstance[PrimitiveNativeFile](mpropdef.mclassdef.mclass.mclass_type, inst)
                                v.init_instance_primitive(instance)
                                return instance
                        end
                        var recvval = args.first.val
                        if pname == "io_write" then
                                var a1 = args[1].val.as(Buffer)
-                               recvval.as(OStream).write(a1.substring(0, args[2].to_i).to_s)
-                               return args[2]
+                               return v.int_instance(recvval.as(PrimitiveNativeFile).io_write(a1.to_cstring, args[2].to_i))
                        else if pname == "io_read" then
-                               var str = recvval.as(IStream).read(args[2].to_i)
                                var a1 = args[1].val.as(Buffer)
-                               new FlatBuffer.from(str).copy(0, str.length, a1.as(FlatBuffer), 0)
-                               return v.int_instance(str.length)
+                               var ns = new NativeString(a1.length)
+                               var len = recvval.as(PrimitiveNativeFile).io_read(ns, args[2].to_i)
+                               a1.clear
+                               a1.append(ns.to_s_with_length(len))
+                               return v.int_instance(len)
+                       else if pname == "flush" then
+                               recvval.as(PrimitiveNativeFile).flush
+                               return null
                        else if pname == "io_close" then
-                               recvval.as(IOS).close
-                               return v.int_instance(0)
-                       else if pname == "address_is_null" then
-                               return v.false_instance
+                               return v.int_instance(recvval.as(PrimitiveNativeFile).io_close)
+                       else if pname == "set_buffering_type" then
+                               return v.int_instance(recvval.as(PrimitiveNativeFile).set_buffering_type(args[1].to_i, args[2].to_i))
                        end
                else if pname == "calloc_array" then
                        var recvtype = args.first.mtype.as(MClassType)
                else if pname == "errno" then
                        return v.int_instance(sys.errno)
                else if pname == "address_is_null" then
+                       var recv = args[0]
+                       if recv isa PrimitiveInstance[PrimitiveNativeFile] then
+                               return v.bool_instance(recv.val.address_is_null)
+                       end
                        return v.false_instance
                end
                return v.error_instance
        end
  end
  
 -redef class AbstractArray[E]
 -      fun copy(start: Int, len: Int, dest: AbstractArray[E], new_start: Int)
 -      do
 -              self.copy_to(start, len, dest, new_start)
 -      end
 -end
 -
  redef class AAttrPropdef
        redef fun call(v, mpropdef, args)
        do