lib/std: improve doc of the class `Path`
[nit.git] / lib / standard / file.nit
index 3bad3f3..03a671e 100644 (file)
@@ -16,8 +16,8 @@
 module file
 
 intrude import stream
-intrude import ropes
-import string_search
+intrude import text::ropes
+import text
 import time
 import gc
 
@@ -77,9 +77,10 @@ abstract class FileStream
        # 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
+       #
+       # * `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
@@ -184,7 +185,7 @@ class FileWriter
                        last_error = new IOError("cannot write to non-writable stream")
                        return
                end
-               write_native(s.items, s.length)
+               write_native(s.items, 0, s.length)
        end
 
        redef fun write(s)
@@ -194,7 +195,7 @@ class FileWriter
                        last_error = new IOError("cannot write to non-writable stream")
                        return
                end
-               for i in s.substrings do write_native(i.to_cstring, i.length)
+               s.write_native_to(self)
        end
 
        redef fun write_byte(value)
@@ -225,7 +226,7 @@ class FileWriter
        redef var is_writable = false
 
        # Write `len` bytes from `native`.
-       private fun write_native(native: NativeString, len: Int)
+       private fun write_native(native: NativeString, from, len: Int)
        do
                if last_error != null then return
                if not _is_writable then
@@ -237,7 +238,7 @@ class FileWriter
                        _is_writable = false
                        return
                end
-               var err = _file.io_write(native, len)
+               var err = _file.io_write(native, from, len)
                if err != len then
                        # Big problem
                        last_error = new IOError("Problem in writing : {err} {len} \n")
@@ -272,7 +273,9 @@ 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"
+       private fun fd_to_stream(mode: NativeString): NativeFile `{
+               return fdopen(self, mode);
+       `}
 end
 
 # Constant for read-only file streams
@@ -299,7 +302,15 @@ class Stdin
                prepare_buffer(1)
        end
 
-       redef fun poll_in is extern "file_stdin_poll_in"
+       redef fun poll_in `{
+               struct pollfd fd = {0, POLLIN, 0};
+               int res = poll(&fd, 1, 0);
+               if (res == -1) {
+                       perror("Error poll stdin");
+                       exit(EXIT_FAILURE);
+               }
+               return res > 0;
+       `}
 end
 
 # Standard output stream.
@@ -339,9 +350,13 @@ redef class Writable
        end
 end
 
-# Utility class to access file system services
+# Utility class to access file system services.
 #
 # Usually created with `Text::to_path`.
+#
+# `Path` objects does not necessarily represent existing files in a file system.
+# They are sate-less objects that efficiently represent path information.
+# They also provide an easy to use API on file-system services and are used to store their error status (see `last_error`)
 class Path
 
        private var path: String
@@ -349,13 +364,25 @@ class Path
        # Path to this file
        redef fun to_s do return path
 
-       # Name of the file name at `to_s`
+       # Short name of the file at `to_s`
        #
        # ~~~
        # var path = "/tmp/somefile".to_path
        # assert path.filename == "somefile"
        # ~~~
-       var filename: String = path.basename("") is lazy
+       var filename: String = path.basename is lazy
+
+       # Last error produced by I/O operations.
+       #
+       # ~~~
+       # var path = "/does/not/exists".to_path
+       # assert path.last_error == null
+       # path.read_all
+       # assert path.last_error != null
+       # ~~~
+       #
+       # Since `Path` objects are stateless, `last_error` is reset on most operations and reflect its status.
+       var last_error: nullable IOError = null is writable
 
        # Does the file at `path` exists?
        fun exists: Bool do return stat != null
@@ -393,14 +420,25 @@ class Path
                return new FileStat(stat)
        end
 
-       # Delete a file from the file system, return `true` on success
-       fun delete: Bool do return path.to_cstring.file_delete
+       # Delete a file from the file system.
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       fun delete
+       do
+               var res = path.to_cstring.file_delete
+               if not res then
+                       last_error = new IOError("Cannot delete `{path}`: {sys.errno.strerror}")
+               else
+                       last_error = null
+               end
+       end
 
-       # Copy content of file at `path` to `dest`
+       # Copy content of file at `path` to `dest`.
        #
-       # Require: `exists`
+       # `last_error` is updated to contains the error information on error, and null on success.
        fun copy(dest: Path)
        do
+               last_error = null
                var input = open_ro
                var output = dest.open_wo
 
@@ -411,41 +449,75 @@ class Path
 
                input.close
                output.close
+               last_error = input.last_error or else output.last_error
        end
 
-       # Open this file for reading
+       # Open this file for reading.
+       #
+       # ~~~
+       # var file = "/etc/issue".to_path.open_ro
+       # print file.read_line
+       # file.close
+       # ~~~
        #
-       # Require: `exists and not link_stat.is_dir`
+       # Note that it is the user's responsibility to close the stream.
+       # Therefore, for simple use case, look at `read_all` or `each_line`.
+       #
+       # ENSURE `last_error == result.last_error`
        fun open_ro: FileReader
        do
-               # TODO manage streams error when they are merged
-               return new FileReader.open(path)
+               var res = new FileReader.open(path)
+               last_error = res.last_error
+               return res
        end
 
        # Open this file for writing
        #
-       # Require: `not exists or not stat.is_dir`
+       # ~~~
+       # var file = "bla.log".to_path.open_wo
+       # file.write "Blabla\n"
+       # file.close
+       # ~~~
+       #
+       # Note that it is the user's responsibility to close the stream.
+       # Therefore, for simple use case, look at `Writable::write_to_file`.
+       #
+       # ENSURE `last_error == result.last_error`
        fun open_wo: FileWriter
        do
-               # TODO manage streams error when they are merged
-               return new FileWriter.open(path)
+               var res = new FileWriter.open(path)
+               last_error = res.last_error
+               return res
        end
 
-       # Read all the content of the file
+       # Read all the content of the file as a string.
        #
        # ~~~
        # var content = "/etc/issue".to_path.read_all
        # print content
        # ~~~
        #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
+       #
        # See `Reader::read_all` for details.
        fun read_all: String do return read_all_bytes.to_s
 
+       # Read all the content on the file as a raw sequence of bytes.
+       #
+       # ~~~
+       # var content = "/etc/issue".to_path.read_all_bytes
+       # print content.to_s
+       # ~~~
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
        fun read_all_bytes: Bytes
        do
                var s = open_ro
                var res = s.read_all_bytes
                s.close
+               last_error = s.last_error
                return res
        end
 
@@ -462,12 +534,16 @@ class Path
        # end
        # ~~~
        #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
+       #
        # See `Reader::read_lines` for details.
        fun read_lines: Array[String]
        do
                var s = open_ro
                var res = s.read_lines
                s.close
+               last_error = s.last_error
                return res
        end
 
@@ -482,48 +558,93 @@ class Path
        #
        # Note: the stream is automatically closed at the end of the file (see `LineIterator::close_on_finish`)
        #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       #
        # See `Reader::each_line` for details.
        fun each_line: LineIterator
        do
                var s = open_ro
                var res = s.each_line
                res.close_on_finish = true
+               last_error = s.last_error
                return res
        end
 
 
-       # Lists the name of the files contained within the directory at `path`
+       # Lists the files contained within the directory at `path`.
        #
-       # Require: `exists and is_dir`
+       #     var files = "/etc".to_path.files
+       #     assert files.has("/etc/issue".to_path)
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
+       #
+       #     var path = "/etc/issue".to_path
+       #     files = path.files
+       #     assert files.is_empty
+       #     assert path.last_error != null
        fun files: Array[Path]
        do
-               var files = new Array[Path]
-               for filename in path.files do
-                       files.add new Path(path / filename)
+               last_error = null
+               var res = new Array[Path]
+               var d = new NativeDir.opendir(path.to_cstring)
+               if d.address_is_null then
+                       last_error = new IOError("Cannot list directory `{path}`: {sys.errno.strerror}")
+                       return res
+               end
+
+               loop
+                       var de = d.readdir
+                       if de.address_is_null then
+                               # readdir cannot fail, so null means end of list
+                               break
+                       end
+                       var name = de.to_s_with_copy
+                       if name == "." or name == ".." then continue
+                       res.add new Path(path / name)
                end
-               return files
+               d.closedir
+
+               return res
        end
 
-       # Delete a directory and all of its content, return `true` on success
+       # Delete a directory and all of its content
        #
        # Does not go through symbolic links and may get stuck in a cycle if there
        # is a cycle in the file system.
-       fun rmdir: Bool
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # The method does not stop on the first error and try to remove most file and directories.
+       #
+       # ~~~
+       # var path = "/does/not/exists/".to_path
+       # path.rmdir
+       # assert path.last_error != null
+       # ~~~
+       fun rmdir
        do
-               var ok = true
+               last_error = null
                for file in self.files do
                        var stat = file.link_stat
+                       if stat == null then
+                               last_error = file.last_error
+                               continue
+                       end
                        if stat.is_dir then
-                               ok = file.rmdir and ok
+                               # Recursively rmdir
+                               file.rmdir
                        else
-                               ok = file.delete and ok
+                               file.delete
                        end
+                       if last_error == null then last_error = file.last_error
                end
 
-               # Delete the directory itself
-               if ok then ok = path.to_cstring.rmdir and ok
-
-               return ok
+               # Delete the directory itself if things are fine
+               if last_error == null then
+                       if path.to_cstring.rmdir then
+                               last_error = new IOError("Cannot remove `{self}`: {sys.errno.strerror}")
+                       end
+               end
        end
 
        redef fun ==(other) do return other isa Path and path.simplify_path == other.path.simplify_path
@@ -665,6 +786,11 @@ end
 redef class Text
        # Access file system related services on the path at `self`
        fun to_path: Path do return new Path(to_s)
+
+       private fun write_native_to(s: FileWriter)
+       do
+               for i in substrings do s.write_native(i.to_cstring, 0, i.length)
+       end
 end
 
 redef class String
@@ -693,36 +819,51 @@ redef class String
        # Copy content of file at `self` to `dest`
        fun file_copy_to(dest: String) do to_path.copy(dest.to_path)
 
-       # Remove the trailing extension `ext`.
+       # Remove the trailing `extension`.
+       #
+       # `extension` usually starts with a dot but could be anything.
        #
-       # `ext` usually starts with a dot but could be anything.
+       #     assert "file.txt".strip_extension(".txt")   == "file"
+       #     assert "file.txt".strip_extension("le.txt") == "fi"
+       #     assert "file.txt".strip_extension("xt")     == "file.t"
        #
-       #     assert "file.txt".strip_extension(".txt")  == "file"
-       #     assert "file.txt".strip_extension("le.txt")  == "fi"
-       #     assert "file.txt".strip_extension("xt")  == "file.t"
+       # If `extension == null`, the rightmost extension is stripped, including the last dot.
        #
-       # if `ext` is not present, `self` is returned unmodified.
+       #     assert "file.txt".strip_extension           == "file"
+       #
+       # If `extension` is not present, `self` is returned unmodified.
        #
        #     assert "file.txt".strip_extension(".tar.gz")  == "file.txt"
-       fun strip_extension(ext: String): String
+       fun strip_extension(extension: nullable String): String
        do
-               if has_suffix(ext) then
-                       return substring(0, length - ext.length)
+               if extension == null then
+                       extension = file_extension
+                       if extension == null then
+                               return self
+                       else extension = ".{extension}"
+               end
+
+               if has_suffix(extension) then
+                       return substring(0, length - extension.length)
                end
                return self
        end
 
-       # Extract the basename of a path and remove the extension
+       # Extract the basename of a path and strip the `extension`
+       #
+       # The extension is stripped only if `extension != null`.
        #
-       #     assert "/path/to/a_file.ext".basename(".ext")         == "a_file"
-       #     assert "path/to/a_file.ext".basename(".ext")          == "a_file"
-       #     assert "path/to".basename(".ext")                     == "to"
-       #     assert "path/to/".basename(".ext")                    == "to"
+       #     assert "/path/to/a_file.ext".basename(".ext")     == "a_file"
+       #     assert "path/to/a_file.ext".basename(".ext")      == "a_file"
+       #     assert "path/to/a_file.ext".basename              == "a_file.ext"
+       #     assert "path/to".basename(".ext")                 == "to"
+       #     assert "path/to/".basename(".ext")                == "to"
+       #     assert "path/to".basename                         == "to"
        #     assert "path".basename("")                        == "path"
        #     assert "/path".basename("")                       == "path"
        #     assert "/".basename("")                           == "/"
        #     assert "".basename("")                            == ""
-       fun basename(ext: String): String
+       fun basename(extension: nullable String): String
        do
                var l = length - 1 # Index of the last char
                while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
@@ -732,7 +873,10 @@ redef class String
                if pos >= 0 then
                        n = substring(pos+1, l-pos)
                end
-               return n.strip_extension(ext)
+
+               if extension != null then
+                       return n.strip_extension(extension)
+               else return n
        end
 
        # Extract the dirname of a path
@@ -767,11 +911,12 @@ redef class String
                return res
        end
 
-       # Simplify a file path by remove useless ".", removing "//", and resolving ".."
+       # Simplify a file path by remove useless `.`, removing `//`, and resolving `..`
        #
-       # * ".." are not resolved if they start the path
-       # * starting "/" is not removed
-       # * trailing "/" is removed
+       # * `..` are not resolved if they start the path
+       # * starting `.` is simplified unless the path is empty
+       # * starting `/` is not removed
+       # * trailing `/` is removed
        #
        # Note that the method only work on the string:
        #
@@ -785,17 +930,29 @@ redef class String
        # assert "dir/..".simplify_path            ==  "."
        # assert "//absolute//path/".simplify_path ==  "/absolute/path"
        # assert "//absolute//../".simplify_path   ==  "/"
+       # assert "/".simplify_path                 == "/"
+       # assert "../".simplify_path               == ".."
+       # assert "./".simplify_path                == "."
+       # assert "././././././".simplify_path      == "."
+       # assert "./../dir".simplify_path                  == "../dir"
+       # assert "./dir".simplify_path                     == "dir"
        # ~~~
        fun simplify_path: String
        do
                var a = self.split_with("/")
                var a2 = new Array[String]
                for x in a do
-                       if x == "." then continue
-                       if x == "" and not a2.is_empty then continue
+                       if x == "." and not a2.is_empty then continue # skip `././`
+                       if x == "" and not a2.is_empty then continue # skip `//`
                        if x == ".." and not a2.is_empty and a2.last != ".." then
-                               a2.pop
-                               continue
+                               if a2.last == "." then # do not skip `./../`
+                                       a2.pop # reduce `./../` in `../`
+                               else # reduce `dir/../` in `/`
+                                       a2.pop
+                                       continue
+                               end
+                       else if not a2.is_empty and a2.last == "." then
+                               a2.pop # reduce `./dir` in `dir`
                        end
                        a2.push(x)
                end
@@ -878,7 +1035,7 @@ redef class String
        # In other cases, parts of the current directory may be exhibited:
        #
        #     var p = "../foo/bar".relpath("baz")
-       #     var c = getcwd.basename("")
+       #     var c = getcwd.basename
        #     assert p == "../../{c}/baz"
        #
        # For path resolution independent of the current directory (eg. for paths in URL),
@@ -962,13 +1119,12 @@ redef class String
        #
        # Return an error object in case of error.
        #
-       #    assert "/fail/does not/exist".rmdir != null
+       #     assert "/fail/does not/exist".rmdir != null
        fun rmdir: nullable Error
        do
-               var res = to_path.rmdir
-               if res then return null
-               var error = new IOError("Cannot change remove `{self}`: {sys.errno.strerror}")
-               return error
+               var p = to_path
+               p.rmdir
+               return p.last_error
        end
 
        # Change the current working directory
@@ -1049,9 +1205,32 @@ redef class String
        end
 end
 
+redef class FlatString
+       redef fun write_native_to(s)
+       do
+               s.write_native(items, index_from, length)
+       end
+end
+
 redef class NativeString
-       private fun file_exists: Bool is extern "string_NativeString_NativeString_file_exists_0"
-       private fun file_stat: NativeFileStat is extern "string_NativeString_NativeString_file_stat_0"
+       private fun file_exists: Bool `{
+               FILE *hdl = fopen(self,"r");
+               if(hdl != NULL){
+                       fclose(hdl);
+               }
+               return hdl != NULL;
+       `}
+
+       private fun file_stat: NativeFileStat `{
+               struct stat buff;
+               if(stat(self, &buff) != -1) {
+                       struct stat* stat_element;
+                       stat_element = malloc(sizeof(struct stat));
+                       return memcpy(stat_element, &buff, sizeof(struct stat));
+               }
+               return 0;
+       `}
+
        private fun file_lstat: NativeFileStat `{
                struct stat* stat_element;
                int res;
@@ -1060,63 +1239,106 @@ redef class NativeString
                if (res == -1) return NULL;
                return stat_element;
        `}
-       private fun file_mkdir: Bool is extern "string_NativeString_NativeString_file_mkdir_0"
+
+       private fun file_mkdir: Bool `{ return !mkdir(self, 0777); `}
+
        private fun rmdir: Bool `{ return !rmdir(self); `}
-       private fun file_delete: Bool is extern "string_NativeString_NativeString_file_delete_0"
-       private fun file_chdir: Bool is extern "string_NativeString_NativeString_file_chdir_0"
-       private fun file_realpath: NativeString is extern "file_NativeString_realpath"
+
+       private fun file_delete: Bool `{
+               return remove(self) == 0;
+       `}
+
+       private fun file_chdir: Bool `{ return !chdir(self); `}
+
+       private fun file_realpath: NativeString `{ return realpath(self, NULL); `}
 end
 
 # This class is system dependent ... must reify the vfs
 private extern class NativeFileStat `{ struct stat * `}
+
        # Returns the permission bits of file
-       fun mode: Int is extern "file_FileStat_FileStat_mode_0"
+       fun mode: Int `{ return self->st_mode; `}
+
        # Returns the last access time
-       fun atime: Int is extern "file_FileStat_FileStat_atime_0"
+       fun atime: Int `{ return self->st_atime; `}
+
        # Returns the last status change time
-       fun ctime: Int is extern "file_FileStat_FileStat_ctime_0"
+       fun ctime: Int `{ return self->st_ctime; `}
+
        # Returns the last modification time
-       fun mtime: Int is extern "file_FileStat_FileStat_mtime_0"
+       fun mtime: Int `{ return self->st_mtime; `}
+
        # Returns the size
-       fun size: Int is extern "file_FileStat_FileStat_size_0"
+       fun size: Int `{ return self->st_size; `}
 
        # Returns true if it is a regular file (not a device file, pipe, sockect, ...)
        fun is_reg: Bool `{ return S_ISREG(self->st_mode); `}
+
        # Returns true if it is a directory
        fun is_dir: Bool `{ return S_ISDIR(self->st_mode); `}
+
        # Returns true if it is a character device
        fun is_chr: Bool `{ return S_ISCHR(self->st_mode); `}
+
        # Returns true if it is a block device
        fun is_blk: Bool `{ return S_ISBLK(self->st_mode); `}
+
        # Returns true if the type is fifo
        fun is_fifo: Bool `{ return S_ISFIFO(self->st_mode); `}
+
        # Returns true if the type is a link
        fun is_lnk: Bool `{ return S_ISLNK(self->st_mode); `}
+
        # Returns true if the type is a socket
        fun is_sock: Bool `{ return S_ISSOCK(self->st_mode); `}
 end
 
 # Instance of this class are standard FILE * pointers
 private extern class NativeFile `{ FILE* `}
-       fun io_read(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_read_2"
-       fun io_write(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_write_2"
-       fun write_byte(value: Int): Int `{
+       fun io_read(buf: NativeString, len: Int): Int `{
+               return fread(buf, 1, len, self);
+       `}
+
+       fun io_write(buf: NativeString, from, len: Int): Int `{
+               return fwrite(buf+from, 1, len, self);
+       `}
+
+       fun write_byte(value: Byte): Int `{
                unsigned char b = (unsigned char)value;
                return fwrite(&b, 1, 1, self);
        `}
-       fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
-       fun file_stat: NativeFileStat is extern "file_NativeFile_NativeFile_file_stat_0"
+
+       fun io_close: Int `{ return fclose(self); `}
+
+       fun file_stat: NativeFileStat `{
+               struct stat buff;
+               if(fstat(fileno(self), &buff) != -1) {
+                       struct stat* stat_element;
+                       stat_element = malloc(sizeof(struct stat));
+                       return memcpy(stat_element, &buff, sizeof(struct stat));
+               }
+               return 0;
+       `}
+
        fun fileno: Int `{ return fileno(self); `}
+
        # Flushes the buffer, forcing the write operation
-       fun flush: Int is extern "fflush"
+       fun flush: Int `{ return fflush(self); `}
+
        # 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"
+       fun set_buffering_type(buf_length: Int, mode: Int): Int `{
+               return setvbuf(self, NULL, mode, buf_length);
+       `}
 
-       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"
-       new native_stdin is extern "file_NativeFileCapable_NativeFileCapable_native_stdin_0"
-       new native_stdout is extern "file_NativeFileCapable_NativeFileCapable_native_stdout_0"
-       new native_stderr is extern "file_NativeFileCapable_NativeFileCapable_native_stderr_0"
+       new io_open_read(path: NativeString) `{ return fopen(path, "r"); `}
+
+       new io_open_write(path: NativeString) `{ return fopen(path, "w"); `}
+
+       new native_stdin `{ return stdin; `}
+
+       new native_stdout `{ return stdout; `}
+
+       new native_stderr `{ return stderr; `}
 end
 
 # Standard `DIR*` pointer
@@ -1149,11 +1371,13 @@ redef class Sys
        var stderr: Writer = new Stderr is protected writable, lazy
 
        # Enumeration for buffer mode full (flushes when buffer is full)
-       fun buffer_mode_full: Int is extern "file_Sys_Sys_buffer_mode_full_0"
+       fun buffer_mode_full: Int `{ return _IOFBF; `}
+
        # 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"
+       fun buffer_mode_line: Int `{ return _IONBF; `}
+
        # 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"
+       fun buffer_mode_none: Int `{ return _IOLBF; `}
 
        # returns first available stream to read or write to
        # return null on interruption (possibly a signal)
@@ -1179,10 +1403,10 @@ redef class Sys
                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) `{
+       private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]): nullable Int
+       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;
@@ -1236,7 +1460,7 @@ end
 # Print `objects` on the standard output (`stdout`).
 fun printn(objects: Object...)
 do
-       sys.stdout.write(objects.to_s)
+       sys.stdout.write(objects.plain_to_s)
 end
 
 # Print an `object` on the standard output (`stdout`) and add a newline.
@@ -1268,5 +1492,6 @@ do
 end
 
 # Return the working (current) directory
-fun getcwd: String do return file_getcwd.to_s
-private fun file_getcwd: NativeString is extern "string_NativeString_NativeString_file_getcwd_0"
+fun getcwd: String do return native_getcwd.to_s
+
+private fun native_getcwd: NativeString `{ return getcwd(NULL, 0); `}