Gobo Eiffel Preprocessor |

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 ECMA TC39-TG4 committee for the standardization of the Eiffel Language, 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 and 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 `-D` `name` 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

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

> ISE Eiffel | `ISE`
> SmartEiffel | `SE`
> Visual Eiffel | `VE`

(Note that `ISE`, `SE`, `VE` should be all in uppercase because gepp is case-sensitive.)

gepp should then be invoked as follows:

> ISE Eiffel | `gepp -D` `ISE``` `foo.ge foo.e`
> SmartEiffel | `gepp -D` `SE``` `foo.ge foo.e`
> Visual Eiffel | `gepp -D` `VE``` `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
>                 Result := '%U' + an_int
>     #else
>                 Result := an_int.to_character
>     #endif
>     #endif
>             ensure
>                 valid_character_code: Result.code = an_int
>             end

>     end

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

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.