Gobo Eiffel Preprocessor PreviousNext

When writing portable Eiffel classes, two major kinds of problems may be experienced. The first interoperability problem has already been addressed in the adaptation by inheritance and the client/supplier adaptation techniques. It is the result of incompatibilities between kernel classes and features provided by different Eiffel compilers. NICE (Nonprofit International Consortium for Eiffel) has made the first step towards a better interoperability in this area by adopting ELKS '95 as the kernel library standard. But this standard is not perfect yet.

The other problem encountered when writing portable code is incompatibility between the different language dialects provided by the Eiffel compilers. The definition of the Eiffel language is controlled by the Language Committee of NICE, after the specification given in Eiffel: The Language, second printing. However no Eiffel compiler will realistically implement the standard Eiffel language. Some compilers will implement only part of it, failing to support expanded types or exception handling. Some compilers will add extensions to the language to support concurrency or wide characters. And some compilers will have bugs in their implementation. Workarounds usually exist for these bugs, but these workarounds are very often not supported by the other Eiffel compilers. A solution could be to use only the subset of the Eiffel language supported by all Eiffel compilers. This would mean that a given feature of the Eiffel language could not be used in a portable class just because one compiler cannot deal with it. Each Eiffel compiler has its strengths and weaknesses. It would be a pain to be stopped by the weaknesses of all Eiffel compilers without even being able to take advantage of their strengths. This would probably be acceptable for a "Hello World!" program, but certainly not for more realistic large-scale library classes. The solution adopted here is to get help from a preprocessor.

Description

Gobo Eiffel Preprocessor is a simple filter program which has been developed in Eiffel using gelex and geyacc. It takes a text file as input, interprets the processing instructions contained in that file, and returns the resulting file as output. The command-line usage message for gepp is as follows:

gepp [--lines][-Dname ...][filename | -][filename | -]

where the first filename is the name of the input file or the second filename is the name of the output file. If the input filename is missing, or if - is specified instead, the gepp reads its input from the standard input. Likewise, if the output filename is missing or if - is specified instead, then the standard output file is assumed. By convention, filenames for Eiffel classes containing preprocessing instructions have the suffix .ge instead of just .e. Zero or more name definitions can also be declared in the command-line using the notation -Dname where name is the name being defined.

When the command-line option --lines (or its short cut -l) is specified, empty lines are inserted in the output file in place of the lines of the input file which have been ignored by the preprocessor. Although the output file formatting may look ugly, it is a nice way to preserve the line numbering between the input and output files, which is quite useful when compilers report errors with their line number in the files generated by gepp.

A Unix-like usage message of gepp can be invoked using one of the following commands:

gepp --help
gepp -h
gepp -?

and one can get gepp's version number with either:

gepp --version
gepp -V

Gepp behaves much like the well-known C-preprocessor, but only a small subset of the preprocessing instructions is actually supported. Keywords making up gepp instructions typically start with the character # at the beginning of the line. The preprocessor instructions supported by gepp are as follows:

Name definition

#define name

where name is the name to be defined. This is slightly different from the macro definition provided by C-preprocessors. Here, the name is just defined but no value is given. The name definition is only used as conditions in the #ifdef and #ifndef instructions. In particular, there is no macro substitution in gepp. name should be made of one or more letters (a-z), digits (0-9), underscores (_), dots (.) or minus sign (-). Also note that name is case-sensitive. Defining name is not equivalent to defining NaMe.

Name undefinition

#undefine name

where name is the name to be undefined.

Positive conditional

#ifdef Condition
...
#else
...
#endif

where Condition is either:

name
Condition is true is name is defined, false otherwise.
( Condition1 )
Condition has the same value as Condition1.
Condition1 || Condition2
Condition is true if either Condition1 or Condition2 is true, false otherwise.
Condition1 && Condition2
Condition is true if both Condition1 and Condition2 are true, false otherwise.
! Condition1
Condition is true if Condition1 is false, false otherwise.

If Condition is evaluated to true, then the lines between #else and #endif in the input file will be ignored by gepp. Otherwise gepp ignores lines between #ifdef and #else.

The #else part is optional. The following conditional instruction with its #else part missing:

#ifdef Condition
...
#endif

is equivalent to the same conditional instruction with an empty #else part:

#ifdef Condition
...
#else
#endif

Negative conditional

#ifndef Condition
...
#else
...
#endif

This is equivalent to:

#ifdef ! Condition
...
#else
...
#endif

Include files

#include "filename"

Gepp will replace the above line by the contents of filename and will then preprocess it recursively. The maximum number of nested include files has been set to 10 to avoid infinite loops when dealing with cyclic definitions.

Examples

Gepp can be used in three different kinds of cases when writing portable Eiffel classes. The first case is when the adaptation by inheritance and the client/supplier adaptation techniques are not applicable. This can happen due to a language, rather than a kernel class, incompatibility between Eiffel compilers. For example, in a early release of Visual Eiffel, there was a bug in the select clause. In class FOO sketched below, the compiler was reporting a validity error unless two select clauses were specified, one in NUMERIC and one in HASHABLE. However, according to Eiffel: The Language and all other compilers, the class is valid if and only if one and only one select clause is specified for is_equal, either in NUMERIC or in HASHABLE, but definitely not both. The only solution to solve this incompatibility was to use the preprocessor as follows:

class FOO

inherit

    NUMERIC
        redefine
            is_equal
        select
            is_equal
        end

    COMPARABLE
        rename
            is_equal as comp_is_equal
        end

    HASHABLE
        undefine
            is_equal
#ifdef VE
        select
            is_equal
#endif
        end

feature -- Comparison

    ...

end -- class FOO

The names used for each supported Eiffel compiler are, in alphabetical order:

Halstenbach HACT
ISE Eiffel ISE
SmallEiffel SE
Visual Eiffel VE

and gepp should then be invoked as follows:

Halstenbach gepp -DHACT foo.ge foo.e
ISE Eiffel gepp -DISE foo.ge foo.e
SmallEiffel gepp -DSE foo.ge foo.e
Visual Eiffel gepp -DVE foo.ge foo.e

Gepp can also be used even when the adaptation techniques using inheritance or client/supplier relationship would otherwise work. However the main use of gepp was actually to support these techniques by automatically generating the extra "adapted" kernel clusters associated with each Eiffel compiler from a set of "template" classes such as:

class KL_INTEGER_ROUTINES

inherit

    PLATFORM

feature -- Conversion

    to_character (an_int: INTEGER): CHARACTER is
            -- Character whose ASCII code is an_int
        require
            a_int_large_enough: an_int >= Minimum_character_code
            a_int_small_enough: an_int <= Maximum_character_code
        do
#ifdef VE
            Result.from_integer (an_int)
#else
#ifdef ISE || HACT
            Result := '%U' + an_int
#else
            Result := an_int.to_character
#endif
#endif
        ensure
            valid_character_code: Result.code = an_int
        end

end -- class KL_INTEGER_ROUTINES

The corresponding class KL_INTEGER_ROUTINES for Visual Eiffel, generated with the following command-line:

gepp -DVE kl_integer_routines.ge kl_integer_routines.e

will then be:

class KL_INTEGER_ROUTINES

inherit

    PLATFORM

feature -- Conversion

    to_character (an_int: INTEGER): CHARACTER is
            -- Character whose ASCII code is an_int
        require
            a_int_large_enough: an_int >= Minimum_character_code
            a_int_small_enough: an_int <= Maximum_character_code
        do
            Result.from_integer (an_int)
        ensure
            valid_character_code: Result.code = an_int
        end

end -- class KL_INTEGER_ROUTINES

Finally, preprocessing can also be used even when no portability issue is involved. For example, let's consider that besides the portable way to implement a particular feature, one of the Eiffel compilers provides a much more optimized mechanism, although not portable. It would be a shame to have to discard this optimization just because the code being written had to be portable. On the other hand, gepp would make it possible to take advantage of this optimized feature using #ifdef instructions as in the examples above.

Caveats

The only way to write portable code in C is by taking advantage of the C-preprocessor. Gepp could be used in pretty much the same way in Eiffel. However it is against the Eiffel philosophy to use such mechanism and none of the existing Eiffel compilers provide support for such preprocessing. Eiffel is by nature an elegant and very readable language which should not be soiled with ugly preprocessing instructions. As a consequence, this technique has only been used in very rare cases where no other solutions were satisfactory.


Copyright © 1998-2001, Eric Bezault
mailto:
ericb@gobosoft.com
http:
//www.gobosoft.com
Last Updated: 25 October 2001

HomeTocPreviousNext