To use the preprocessor
The compiler bic6.2
for 3GL program scripts has a built in preprocessor which has these
functions:
- Include files
- Define macros
- Variable Macro Arguments
- Token Pasting
- Set an identification in the object
- Pragma codes
- Conditional compiling
All preprocessor directives starts with a #
. A preprocessor directive must be the first word on a source line,
possibly with leading spaces or tabs.
Include Files
To include files this statement is used: #include
filename
or #include <filename>
Files included between < and > are searched in the $BSE/include<rel.number>
directory. This directory is reserved for system headers and cannot be
used for applications.
Normally the filename between quotes is searched by using the
standard file redirection method. Suppose this entry occurs in $BSE/lib/fd.6.2<package_comb>
:
ippmmm:/usr/bse/standard6.2
and there is an include statement like:
#include ippmmmheader
The file /usr/bse/standard6.2/ippmmm/immmheader0
is included.
If a / occurs in the filename, the file is searched using the specified path name.
When a file is included twice, the second include is ignored.
Note that the preprocessor only works during compilation of a
3GL source because the standard generator std_gen6.2
does not have a preprocessor pass. So you cannot use 4GL events in an
included file.
Macro Definition
To define macros this statement can be used:
#define macroname[(arguments)] macro definition
macro definition
To undefine macros the following statement can be used:
#undef
macroname[(arguments)]
The macro names in the source are expanded to the macro definition. If a macro definition does not fit on one line, you can continue the definition on the next line(s) using the caret symbol ^ at the beginning of the next line. You can use arguments in the macro.
Example:
#define a(x, y, z) for i = x to y
^ z()
^ and for | The definition
a(1, 100, func) | The invocation
Macro definitions with the same name but with different number of arguments are different, as in this example:
#define x definition without arguments
#define x() definition with 0 arguments
#define x(arg1) definition with 1 argument arg1
#define x(arg1,arg2) definition with 2 arguments
#define x(...) definition with ellipsis ... (see below)
Note that there is a difference between a macro without arguments x and a macro with zero arguments x(). In the first case you must use x in the program script and in the second case you must use x() in the program script to call the macro.
The #undef
statement causes the macro definition to be forgotten. The number of
arguments in the #undef
call must match the number of arguments in the definition.
#define MAXLENGTH 1000
#define INCR 1
#define INCR(i) i=i+INCR
.....
#undef MAXLENGTH
#define INCR 2 | Redefines INCR
.....
#undef INCR(i)
If you apply #undef
to an unknown macro, an error occurs. To be sure that the macro is
defined, use this construction:
#ifdef INCR | without arguments
#undef INCR(i)
#endif | of #ifdef / #endif calls
Variable Macro Arguments
To define a macro, you can also use the ellipsis notation ( , ... ) to give the macro a varying number of arguments.
Example
#define fillbuf(buf1, format, ...) buf1 = sprintf$(format,...)
| macro call string buffer(100) long l_val double d_val fillbuf(buffer,
%d. %s = %d %5.2f, 1, Value, l_val, d_val)
The macro definition can contain a number of arguments, but the
ellipsis notation must be the last argument. This notation can be used with
functions that also use the ellipsis notation, such as the sprintf()
, message()
and mess()
functions.
Token Pasting
In the macro you can enter a part of a variable as macro argument. The symbol ## is used to distinguish macro arguments and the rest of the macro, but is omitted in the macro call. You can paste more macro arguments together as one identifier. This principle is called token pasting.
Example
#define p(x) message("%d", var##x)
#define q(x,y) message("%d", x##y)
#define r(x,y,z) message("%d", x##y##z)
long var1a, var1b
p(1a) | becomes: message("%d", var1a)
p(1b) | becomes: message("%d", var1b)
q(var, 1a) | becomes: message("%d", var1a)
q(var, 1b) | becomes: message("%d", var1b)
r(var, 1, a) | becomes: message("%d", var1a)
r(var, 1, b) | becomes: message("%d", var1b)
This method is used to reuse parts of source code, where using functions is impossible or difficult to use functions. See this example.
Example
#define VRC(table,v,r,c)
^ tt##table##.vers = v
^ tt##table##.rele = r
^ tt##table##.cust = c
VRC(adv100, "6.2", "a", "")
VRC(adv200, "6.2", "a", "")
VRC(adv300, "6.2", "a", "")
Object Identifications
To set an identification in the object, use this statement:
#ident "@(#)Identification of object"
Example
#ident "@(#)<progname>, YY/MM/DD [HH/MM], version,
author"
A default identification is always placed in the object by the compiler with these contents:
#ident "@(#)<source name>, YY/MM/DD, [HH/MM],
From ${logname}"
The UNIX
command writes all object lines beginning with '@(#)' on standard output.Besides this default identification the programmer can use his own identification which is also placed in the object.
Pragma Codes
Pragma codes are a kind of compiler options. These pragma codes can be used:
#pragma nodebug | Do not show the source while debugging. |
#pragma debug | Show the source while debugging. |
#pragma nowarnings | Do not give warnings about the source. |
#pragma warnings | Give warnings about the source. |
#pragma notransactions | The source only contains read actions. There are no transactions in the source. So, it is sufficient to start one database server. |
#pragma warning <text> | The programmer generates his own warning. (Warning level 15). See example. |
#pragma fatal <text> | The programmer generates his own error. |
#pragma sticky | Do not remove the object out of memory when the process ends. |
#pragma used <component> <code> | See below. |
Example
#pragma warning This is not a fine solution !
| After compilation the following warning appears:
| <Source(line)>: Warning(15): This is not a fine solution !
In some cases the where-used list will not be updated automatically. For example, when a session code is entered but not expected.
For example,
message("ttadv2130s000") or bms.send("command",
event, "ttaad3100s000", pr.id)
To put this session code into the where-used list, enter this command line:
#pragma used
session ttadv2130s000
Following is an overall picture of the pragma codes to update the where-used list:
#pragma used include | <file> |
#pragma used table | <table code> |
#pragma used field | <field code> |
#pragma used domain | <domain code> |
#pragma used message | <message code> |
#pragma used question | <question code> |
#pragma used session | <session code> |
#pragma used menu | <menu code> |
#pragma used dll | <dll objectname> |
#pragma used chart | <chart code> |
Usually the where-used list will be updated. In case of functions, the where-used list is updated if the function call contains the string value. (see example).
Example
mess("ttadvs0000", 1) | Where-used list will be updated
str = "ttadvs0000"
mess(str, 1) | Where-used list will not be updated
#pragma used message ttadvs0000
Pragma codes can be placed everywhere in the source.
For instance, you have set this pragma on top of the source:
#pragma
nowarnings
After a number of warnings, you can enter this pragma:
#pragma warnings
Conditional Compiling
#ifdef <macro>
The source after #ifdef up to #else/#elif/#endif will be compiled if
<macro> is defined. Otherwise this source is ignored.
#ifndef <macro>
The source after #ifndef up to #else/#elif/#endif will be
compiled if <macro> is not defined. Otherwise this source is ignored.
#if <constant expression>
The source after #if up to #else/#elif/#endif will
be compiled if <constant expression> is TRUE. Otherwise this source is
ignored.
#else
If the condition belonging to #if/#ifdef/#ifndef/#elif is FALSE,
the source after #else up to #endif will be compiled. If the condition
belonging to #if/#ifdef/#ifndef/#elif is TRUE, the source after #else up to
#endif will be ignored.
#elif <constant expression>
#elif is a
combination of #else and #if.
Example
#if <constant expression>
...
#else
#if <constant expression>
...
#else
...
#endif
#endif
Is equal to:
#if
<constant expression>
...
#elif <constant expression>
...
#else
...
#endif
#endif
To finish a part of the source started with #ifdef/#ifndef/#if
#undef <macro>
To delete a macro definition, which means that this macro
is not known on a next #ifdef call.
The <constant expression> in a #if, #elif is a numeric expression. In this expression you can only use macros of long type and operators such as +, -, *, /, =, <, >, <=, >=, <>, and, or, not, ?:, ().
You can use nested #if structures.
You can define a macro while starting the compiler by using the -D
option.
bic6.2 -D<macro> | no value means default 1
bic6.2 -D<macro>=<value>
bic6.2 -D<macro>='any token string'
These macros can also be used in the #if conditions as in these examples:
bic6.2 -DDEBUG -DMYTEST <source>
|source
#if DEBUG and MYTEST
message(Some debug information)
...
#endif
bic6.2 -DCUSTOMER_X -DCUSTOMER_Y <source>
bic6.2 -DSTANDARD <source>
#if STANDARD
....
#elif ( CUSTOMER_X and (not CUSTOMER_Y) )
....
#endif
You can inactivate a file with #ifdef:
Example:
#ifdef OLD
....
....
....
#endif
The preprocessor only works during compilation of a 3GL source because the standard generator
std_gen6.2
does not have a preprocessor pass. You cannot use 4GL
events in a #if, #ifdef or #ifndef.
You cannot use a #ifdef statement in an embedded SQL query. This formula is not allowed:
select *
#ifdef STANDARD
...
from x
#else
...
from y
#endif
where ...
selectdo
...
endselect
But this construction is possible:
#ifdef STANDARD
select * from x where ...
selectdo
...
endselect
#else select * from y where ...
selectdo
...
endselect
#endif
Two keywords are implemented to give debug information in 3GL sources:
__FILE__ : contains the name of the current source
__LINE__ : contains the line number of the current source
Example
message(This is at line %d in the source %s, __LINE__, __FILE__)