| Portability Issues |
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 |