Portability Issues PreviousNext

The following non-exhaustive list of portability issues encountered during the development of this package, along with the corresponding solutions which have been adopted, will help to understand some of the design and implementation decisions which have been made. Hopefully this list will also be useful to those of you who want to develop portable Eiffel class libraries. Feel free to contact me if you want to discuss some of the items from this list or if you experienced other interoperability problems.

[Note that this list is out-of-date and some of the problems described below might not exist anymore.]


Problem: Eiffel is a case-insensitive language. However SmallEiffel is case-sensitive!

Solution: A new command-line option -case_insensitive has been introduced in SmallEiffel -0.83 to make the compiler case-insensitive.


Problem: In order to avoid classname clashes between different libraries, some Eiffel compilers support class renaming in the Ace file or equivalent. But some don't.

Solution: The name of the classes have been systematically prefixed with a two-letter library code. For example the classes from the Gobo Eiffel Structure Library have been prefixed by DS (which stands for Data Structures), as in DS_LINKED_LIST, whereas classes from the Gobo Eiffel Lexical Library have been prefixed by LX, as in LX_SCANNER.


Problem: There is no Data Structure library standard. Although each Eiffel compiler provides its own Data Structure library, none of them is portable.

Solution: Although portable Data Structure libraries, such as Pylon, have been made public, none of these libraries were available when this project has been started. Therefore a (yet another) Data Structure library (the Gobo Eiffel Structure Library) has been developed as a foundation for the other libraries of this package.


Problem: According to ELKS '95, a class has to inherit from HASHABLE to supply feature hash_code in its interface. However SmallEiffel does not support class HASHABLE but provides a built-in feature hash_code in class GENERAL instead.

This problem has been fixed in SmallEiffel -0.82. The solution which was adopted before the release of SmallEiffel -0.82 is provided below for the interest of the reader only.

Solution: After several attempts, it was impossible to make classes using hash tables portable using either the inheritance or client/supplier adaptation techniques. Even creating a dummy class HASHABLE in SmallEiffel didn't work mainly because of the fact that hash_code was built-in in GENERAL.The only alternative left was to use gepp preprocessor as follows:

#ifdef SE
class DS_HASH_TABLE [G, K]
#else
class DS_HASH_TABLE [G, K -> HASHABLE]
#endif

inherit

    DS_TABLE [G, K]
...
end

or as in:

class FOO

inherit

    BAR
#ifdef SE
        redefine
            hash_code
        end
#endif

#ifndef SE
    HASHABLE
#endif
...
feature -- Access

    hash_code: INTEGER
            -- Hash code value
...
end

Problem: Some Eiffel compilers do not handle properly inheritance from classes ARRAY or STRING. This is mainly because of some built-in features hard-coded for optimization purposes but which cannot even be renamed in descendant classes without breaking the run-time system (try to rename feature count from class ARRAY in a descendant class with SmallEiffel to see for yourself). As a consequence, classes inheriting from ARRAY for implementation purposes (such as DS_ARRAYED_LIST for example) will not work as expected with the faulty compilers.

Solution: Instead of using implementation by inheritance, classes such as DS_ARRAYED_LIST have a hidden attribute of type ARRAY and implement their functionalities by delegation.


Problem: Class FILE is specified in ELKS '95 with routines to read from (read_*) and to write to (put_*) files. However each Eiffel compiler differs from the other on that matter. ISE Eiffel and Halstenbach provide an abstract class IO_MEDIUM as ancestor for files, sockets, etc. As opposed to ELKS, class FILE is deferred, one possible effective descendant being PLAIN_TEXT_FILE. TowerEiffel and Visual Eiffel support class FILE from ELKS, but TowerEiffel also provides class TEXT_STREAM as an ancestor of FILE (similar to IO_MEDIUM above). Finally, SmallEiffel does not support FILE at all, but instead has the notion of INPUT_STREAM and OUTPUT_STREAM, with two effective descendants STD_FILE_READ and STD_FILE_WRITE. This is a real portability nightmare. The obvious solution which is to write a class KL_FILE, implemented as a descendant of the various classes above provided by each compiler, is not satisfactory. For example, let's have a routine which takes a file as argument. Since the standard input and output files provided in class STD_FILES from ELKS are not of type KL_FILE, they cannot be passed as argument to this routine, making the routine rather useless. The ideal solution would be to take advantage of each compiler implementation choices, transparently allowing the use of IO_MEDIUM or TEXT_STREAM without breaking too much portability constraints.

Solution: From the description above, the adaptation by inheritance technique has naturally been discarded, adaptation using client/supplier relationship being a better choice when dealing with standard files as regular text files. To accommodate with SmallEiffel viewpoint, the file functionalities had to be split into two separate categories: input and output. Finally, using anchor types would ease the use of IO_MEDIUM and TEXT_STREAM while keeping portability in mind. Following are two classes taking care of input functionalities. The same kind of classes provides the output counterpart. The first class provides the anchor types to be used when requiring objects with file access facilities, and a once function giving access to adapted input features. This class should be used through inheritance to take advantage of the anchor technique.

class KL_IMPORTED_INPUT_STREAM_ROUTINES

feature -- Access

    INPUT_STREAM_: KL_INPUT_STREAM_ROUTINES
            -- Routines that ought to be in class INPUT_STREAM
        once
            create Result
        ensure
            input_stream_routines_not_void: Result /= Void
        end

feature -- Anchor types

#ifdef ISE 
    INPUT_STREAM_TYPE: IO_MEDIUM do end
#else
#ifdef SE
    INPUT_STREAM_TYPE: INPUT_STREAM do end
#else
#ifdef TOWER
    INPUT_STREAM_TYPE: TEXT_STREAM do end
#else
    INPUT_STREAM_TYPE: FILE do end
#endif
#endif
#endif
            -- Anchor type

end

Note that the name convention used for the once function is derived from the name of the class and suffixed by an underscore character (_) to try to avoid name clashes with user-defined feature names. The second class uses the client/supplier adaptation technique to provide portable input features. This class should only be used through the once function of the class above.

class KL_INPUT_STREAM_ROUTINES

inherit

    KL_IMPORTED_INPUT_STREAM_ROUTINES

feature -- Initialization

    make_file_open_read (a_filename: STRING): like INPUT_STREAM_TYPE
            -- Create a new file object with a_filename as
            -- file name and try to open it in read-only mode.
            -- is_open_read (Result) is set to True
            -- if operation was successful.
        require
            a_filename_not_void: a_filename /= Void
            a_filename_not_empty: not a_filename.empty
        local
            rescued: BOOLEAN
#ifdef ISE
            a_file: PLAIN_TEXT_FILE
#else
#ifdef SE
            a_file: STD_FILE_READ
#else
#ifdef TOWER
            a_file: FILE
#endif
#endif
#endif
        do
            if not rescued then
#ifdef ISE
                create a_file.make (a_filename)
                Result := a_file
                a_file.open_read
            elseif not a_file.is_closed then
                a_file.close
#else
#ifdef SE
                create a_file.make
                Result := a_file
                a_file.connect_to (a_filename)
            elseif a_file.is_connected then
                a_file.disconnect
#else
                create Result.make (a_filename)
                Result.open_read
            elseif not Result.is_closed then
                Result.close
#endif
#endif
            end
        ensure
            file_not_void: Result /= Void
        rescue
            if not rescued then
                rescued := True
                retry
            end
        end

feature -- Status report

    is_open_read (a_stream: like INPUT_STREAM_TYPE): BOOLEAN
            -- Is a_stream open in read mode?
        require
            a_stream_void: a_stream /= Void
        do
#ifdef SE
            Result := a_stream.is_connected
#else
            Result := a_stream.is_open_read
#endif
        end
...
end

Following is an example using portable file access:

class FOO

inherit

    KL_IMPORTED_INPUT_STREAM_ROUTINES

feature

    parse_from_file (a_file: like INPUT_STREAM_TYPE)
            -- Parse data from a_file.
        require
            a_file_not_void: a_file /= Void
            a_file_open_read: INPUT_STREAM_.is_open_read (a_file)
        do
            ...
        end

    execute
            -- Ask for a file name and parse it.
        local
            a_file: like INPUT_STREAM_TYPE
        do
            a_file := INPUT_STREAM_.make_file_open_read ("foo.txt")
            if INPUT_STREAM_.is_open_read (a_file) then
                parse_from_file (a_file)
                INPUT_STREAM_.close (a_file)
            else
                    -- Parse from standard input.
                parse_from_file (io.input)
            end
        end

end

Problem: According to ELKS '95, standard files are accessible as follows:

io.input
io.output
io.error

and are all of type FILE. However in SmallEiffel they appear as std_input, std_output and std_error in class GENERAL. Moreover, as sketched in the portability issue above with respect to FILE, they are declared of a different type across different compilers.

Solution: The solution adopted consists of two steps. First, to solve the typing problem, the anchor types technique described above in the portability issue about files. Then, two classes are introduced. The first class is more or less like an adaptation of the STD_FILES class from ELKS:

class KL_STANDARD_FILES

inherit

    KL_IMPORTED_INPUT_STREAM_ROUTINES
    KL_IMPORTED_OUTPUT_STREAM_ROUTINES

feature -- Access

#ifdef SE
    input: STD_INPUT
#else
#ifdef TOWER
    input: TEXT_STREAM
#else
    input: FILE
#endif
#endif
            -- Standard input file
        once
#ifdef SE
            Result := std_input
#else
            Result := io.input
#endif
        ensure
            file_not_void: Result /= Void
            file_open_read: INPUT_STREAM_.is_open_read (Result)
        end

... Same thing for output and error ...

end

Note that input could not be declared as like INPUT_STREAM_TYPE since it is a once function. The second class is used to access these standard files through a once function in the same way as io from GENERAL:

class KL_SHARED_STANDARD_FILES

feature -- Access

    std: KL_STANDARD_FILES
            -- Standard files
        once
            create Result
        ensure
            std_not_void: Result /= Void
        end

end

Now, wherever one would have used io.input, io.output or io.error, one can use std.input, std.output or std.error in a portable way as in the following example:

class FOO

inherit

    KL_SHARED_STANDARD_FILES

feature

    parse_from_file (a_file: like INPUT_STREAM_TYPE)
            -- Parse data from a_file.
        require
            a_file_not_void: a_file /= Void
            a_file_open_read: INPUT_STREAM_.is_open_read (a_file)
        do
            ...
        end

    execute
            -- Ask for a file name and parse it.
        local
            a_file: like INPUT_STREAM_TYPE
            a_name: STRING
        do
            std.output.put_string ("Enter a filename: ")
            std.input.read_line
            a_name := std.input.last_string
            a_file := INPUT_STREAM_.make_file_open_read (a_name)
            if INPUT_STREAM_.is_open_read (a_file) then
                parse_from_file (a_file)
                INPUT_STREAM_.close (a_file)
            else
                std.error.put_string ("Cannot open file ")
                std.error.put_string (a_name)
                    -- According to ELKS, the following line should
                    -- be written "std.error.put_new_line", however
                    -- this routine was named `new_line' in ISE Eiffel
                    -- 4.2 and Halstenbach 2.0. At least, the following
                    -- line is portable.
                std.error.put_character ('%N')
                    -- Parse from standard input instead.
                parse_from_file (std.input)
            end
        end

end

Problem: Some useful features are missing in ELKS '95. Most often these features are already provided by some compilers, but not by all compilers and probably under different names or signatures. For example, features such as is_integer in class STRING would be useful (specially as a precondition for to_integer).

Solution: The solution adopted is the same as when a feature specified in ELKS is not supported by some compilers, which is to use client/supplier adaptation. For the example above, class KL_STRING_ROUTINES will provide the following portable feature:

    is_integer (a_string: STRING): BOOLEAN
            -- Is a_string only made up of digits?
        require
            a_string_not_void: a_string /= Void
#ifdef VE || TOWER
        local
            i: INTEGER
            c: CHARACTER
#endif
        do
#ifdef VE || TOWER
            from
                i := a_string.count
                Result := True
            until
                not Result or i = 0
            loop
                c := a_string.item (i)
                Result := c >= '0' and c <= '9'
                i := i - 1
            end
#else
            Result := a_string.is_integer
#endif
        end

Note that a simple implementation had to be provided when missing.


Problem: Sometimes, creation procedures are not portable across compilers. This is for example the case with the creation procedure make from class STRING. ELKS says that make (n) allocates space for at least n characters, but keeps count null. However, Visual Eiffel sets count to n in that case.

Solution: The solution adopted is similar to the client/supplier adaptation of regular features. The only difference is that the adapted routine will be a factory function with the same arguments as the original creation procedure instead of a routine whose extra first argument is the target of the call. The class KL_STRING_ROUTINES will hence have the following function:

    make (n: INTEGER): STRING
            -- Create an empty string. Try to allocate space
            -- for at least n characters.
        require
            non_negative_n: n >= 0
        do
#ifdef VE
            create Result.make (0)
#else
            create Result.make (n)
#endif
        ensure
            string_not_void: Result /= Void
            empty_string: Result.count = 0
        end

and the usual string creation using create:

str: STRING
create str.make (10)

is replaced in portable code by:

str: STRING
str := STRING_.make (10)

Copyright © 1997-2005, Eric Bezault
mailto:
ericb@gobosoft.com
http:
//www.gobosoft.com
Last Updated: 21 February 2005

HomeTocPreviousNext