Back to Contents
The Preprocessor
Summary:
See also: Programs, Tools
The preprocessor is used to transform your sources before compilation. It allows you to include other files
and to define macros that will be expanded when used in the source. It behaves
similar to the
C Preprocessor, with some differences.
The preprocessor transforms files as follows:
- The source file is read and split into lines.
- Continued lines are merged into one long line if it is part of a preprocessor definition.
- Comments are not removed unless they appear in a macro definition.
- Each line is split into a list of lexical tokens.
The preprocessor implements the following features :
- File inclusion
- Conditional compilation
- Macro definition and expansion
There are two kind of macros:
- Simple macros
- Function macros
where function macros look like function calls.
Preprocessor options can be used with fglcomp
and fglform compilers.
File inclusion path:
-I path
The -I option defines a path used to search files included by the &include
directives.
Macro definition:
-D identifier
The -D option defines a macro with the value 1, so that it can be used
conditional directives like &ifdef.
Preprocessing only:
-E
By using the -E option, only the pre-processing phase is done by the compilers.
Result is dumped in standard output.
Preprocessing options:
-p [nopp|fglpp]
By default, the preprocessor is activated using '&' as the preprocessor symbol.
The option -p fglpp enables the compatibility mode using '#' as the preprocessor symbol.
And -p nopp disables the preprocessor usage.
Examples:
fglcomp -E -D DEBUG -I /usr/sources/headers program.4gl
fglcomp -E -p fglpp -I /usr/sources/headers program.4gl
fglcomp -E -p nopp -I /usr/sources/headers program.4gl
Purpose:
The &include directive instructs the preprocessor to include a file. The
included file will be scanned and processed before continuing with the rest of
the current file.
Syntax:
&include "filename"
Notes:
- filename is searched first in the directory containing the current file, then
in the directories listed in the include path. (-I option).
The file name can be followed by spaces and comments.
Example:
File A |
01 First line
02 &include "B"
03 Third line
|
File B |
01 Second line
|
Output |
01 & 1 "A"
02 First line
03 & 1 "B"
04 Second line
05 & 3 "A"
06 Third line
|
These preprocessor directives inform the compiler of its current location
with special preprocessor comments, so the compiler can provide the right error message
when a syntax error occurs.
The preprocessor-generated comments use the following format:
& number "filename"
where:
- number is the current line in the preprocessed file
- filename is the current file name
Recursive inclusions
Recursive inclusions are not allowed. Doing so will fail and output
an error message.
The following example is incorrect:
File A |
01 &include "B"
|
File B |
01 HELLO
02 &include "A"
|
Output |
01 & 1 "A"
02 & 1 "B"
03 HELLO
04 A:0: Multiple inclusion of the file 'A'.
05 included from B:2
06 included from A:1
|
But including the same file several times is allowed:
File A |
01 &include "B"
02 &include "B" -- correct
|
File B |
01 HELLO
|
Output |
01 & 1 "A"
02 & 1 "B"
03 HELLO
04 & 2 "A"
05 & 1 "B"
06 HELLO
|
A macro is identified by its name and body. As the preprocessor scans the
text, it substitutes the macro body for the name identifier.
Syntax:
&define identifier body
Notes:
- identifier is the name of the macro. Any valid identifier can be used.
- body is any sequence of tokens until the end of the line.
After substitution, the macro definition is replaced with blank lines.
Examples:
The following example show macro substitution with 2 simple macros:
File A |
01 &define MAX_TEST 12
02 &define HW "Hello world"
03
04 MAIN
05 DEFINE i INTEGER
06 FOR i=1 TO MAX_TEST
07 DISPLAY HW
08 END FOR
09 END MAIN
|
Output |
01 & 1 "A"
02
03
04
05 MAIN
06 DEFINE i INTEGER
07 FOR i=1 TO 12
08 DISPLAY "Hello world"
09 END FOR
10 END MAIN
|
The macro definition can be continued on multiple lines, but when the macro is
expanded, it is joined to a single line as follows:
File A |
01 &define TABLE_VALUES 1, \
02 2, \
03 3
04 DISPLAY TABLE_VALUES
|
Output |
01 & 1 "A"
02
03
04
05 DISPLAY 1, 2, 3
|
The source file is processed sequentially, so a macro takes effect at the
place it has been written:
File A |
01 DISPLAY X
02 &define X "Hello"
03 DISPLAY X
|
Output |
01 & 1 "A"
02 DISPLAY X
03
04 DISPLAY "Hello"
|
The macro body is expanded only when the macro is applied :
File A |
01 &define AA BB
02 &define BB 12
03 DISPLAY AA
|
Output |
01 & 1 "A"
02
03
04 DISPLAY 12
|
- AA is first expanded to BB.
- The text is rescanned and BB is expanded to 12.
- When the macro AA is defined, BB is not known yet; but it is known
when the macro AA is used.
In order to prevent infinite recursion, a macro cannot be expanded recursively.
File A |
01 &define A B
02 &define B A
03 &define C C
04 A C
|
Output |
01 & 1 "A"
02
03
04
05 A C
|
- A is first expanded to B.
- B is expanded to A.
- A is not expanded again as it appears in its own expansion.
- C expands to C and can not be expanded further.
Function macros are macros which can take arguments.
Syntax:
&define identifier( arglist ) body
Notes:
- identifier is the name of the macro. Any valid identifier can be used.
- body is any sequence of tokens until the end of the line.
- arglist is a list of identifiers separated with commas and
optionally whitespace.
- There must be NO space or comment between the macro name and the opening parenthesis.
Otherwise the macro is not a function macro, but a simple macro.
Example:
File A |
01 &define function_macro(a,b) a + b
02 &define simple_macro (a,b) a + b
03 function_macro( 4 , 5 )
04 simple_macro (1,2)
|
Output |
01 & 1 "A"
02
03
04 4 + 5
05 (a,b) a + b (1,2)
|
A function macro can have an empty argument list. In this case, parenthesis
are required for the macro to be expanded.
As we can see in the next example, line 03 is not expanded because it there is no '()' after
foo. The function macro cannot be applied even if it has no arguments.
File A |
01 &define foo() yes
02 foo()
03 foo
|
Output |
01 & 1 "A"
02
03 yes
04 foo
|
The comma separates arguments. Macro parameters containing a comma can be used
with parenthesis. In the following example Line 02 has been substituted,
but line 03 produced an error, because the number of parameters is incorrect.
The error message is produced on standard error.
File A |
01 &define one_parameter(a) a
02 one_parameter((a,b))
03 one_parameter(a,b)
|
Output |
01 & 1 "A"
02
03 (a,b)
04 one_parameter
05 t:3: Invalid number of parameters for macro one_parameter.
|
Macro arguments are completely expanded and substituted before the function macro expansion.
A macro argument can be left empty.
File A |
01 &define two_args(a,b) a b
02 two_args(,b)
03 two_args(,)
04 two_args()
05 two_args(,,)
|
Output |
01 & 1 "A"
02
03 b
04
05 A:4: Invalid number of parameters for macro two_args.
06 two_args
07 A:5: Invalid number of parameters for macro two_args.
08 two_args
|
Macro arguments appearing inside strings are not expanded.
File A |
01 &define foo(x) "x"
02 foo(toto)
|
Output |
01 & 1 "A"
02
03 "x"
|
The stringification transformation allows you to create a string using a macro parameter.
When a macro parameter is used with a preceding '#', it is replaced by a
string containing the literal text of the argument. The argument is not macro
expanded before the substitution.
Example:
File A |
01 &define test(x) IF x THEN \
02 DISPLAY "Condition "||#x||" is true." \
03 ELSE \
04 DISPLAY "Condition "||#x||" is false." \
05 END IF
06 test(1=2)
|
Output |
01 & 1 "A"
02
03
04
05
06
07 IF 1=2 THEN DISPLAY "Condition "||"1=2"||" is true."
ELSE DISPLAY "Condition "||"1=2"||" is false."
END IF
|
Line 07 has been split on multiple lines for readability. The preprocessor output is merged on one line.
The operator '##' can be used to merge two tokens while expanding a macro. The
two tokens on either side of each '##' are combined to create a single token.
All tokens can not be merged. Usually these tokens are identifiers, or
numbers. The concatenation result produces an identifier.
Example:
File A |
01 &define COMMAND(NAME) #NAME, NAME ## _command
02 COMMAND(quit)
|
Output |
01 & 1 "A"
02
03 "quit", quit_command
|
The preprocessor predefines 2 macros:
__LINE__
expands to the current line number. Its definition
changes with each new line of the code.
__FILE__
expands to the name of the current file as a string
constant. Ex : "subdir/file.inc"
These macros are often used to generate error messages.
An &include directive changes the values of __FILE__ and __LINE__ to correspond
to the included file.
You are allowed to un-define a macro and then redefine it with a new body.
Syntax:
&undef identifier
Usage:
If a macro is redefined without having been undefined previously, the
preprocessor issues a warning and replaces the existing definition with
the new one.
Example:
File A |
01 &define HELLO "hello"
02 DISPLAY HELLO
03 &undef HELLO
04 DISPLAY HELLO
|
Output |
01 & 1 "A"
02
03 DISPLAY "hello"
04 DISPLAY HELLO
|
Conditional processing is supported with the &ifdef and &ifndef directives.
Syntax 1:
&ifdef identifier
...
[&else
...]
&endif
Syntax 2:
&ifndef identifier
...
[&else
...]
&endif
Notes:
- The comment following &endif is not required. However, it can help to match the
corresponding &ifdef or &ifndef.
- Even if the condition is evaluated to false, the content of the &ifdef block
is still scanned and tokenized. Therefore, it must be lexically correct.
- Sometimes it is useful to use some code if a macro is not defined. You can use
&ifndef, that evaluates to true if the macro is not defined.
Example:
File A |
01 &define IS_DEFINED
02 &ifdef IS_DEFINED
03 DISPLAY "The macro is defined"
04 &endif /* IS_DEFINE */
|
Output |
01 & 1 "A"
02
03
04 DISPLAY "The macro is defined"
05
|