Category : Files from Magazines
Archive   : CL-JUN89.ZIP
Filename : MUNOZ

 
Output of file : MUNOZ contained in archive : CL-JUN89.ZIP

{bt
A Language Independent Conditional Compilation Tool
(LICC)



Jose L. Munoz, Ph.D.
Naval Underwater Systems Center
Fort Trumbull
New London, CT 06320


Introduction

Situations often arise that require selection of segments of code to be considered (or not
considered) for processing by a compiler, that is conditional compilation. The C programming
language contains a pre-processor that supports this type of processing and is built-in as part of the
language environment. This paper presents a language-independent conditional
compilation tool, i.e. a pre-processor that may be used
independent of the actual compilation language.

Conditional compilation is most often used to insert code for testing or debugging purposes. In
this manner production code would not contain these statements and thereby save (1) the additional
time (in testing whether or not a particular condition is true, in real-time) and space incurred by the
generation of the additional code supporting these statements and, (2) the run time "dead code" as a
result of inserted code that is not executed. Conditional compilation can also be used to create
variants of the "same" program. For example, to insert (or remove) code specific to a particular
implementation (terminal types, operating systems, programming environment features, etc.) or to
insert (or remove) code for language features that might exist on some implementations but not on
others. As an example, the Simscript II.5 programming language, used for development of
simulations, supports the animation of a simulation. However, the animation is only supported on
certain graphical workstations while the set of machines on which the language is supported is
much greater. By using a tool such as LICC it would be possible to make the simulation more
portable by "turning off" the graphical code. In addition, the removal of graphical code also
supports simulation analysis that does not require any animation support and avoids having to
maintain multiple versions of what should be the same program.

This paper presents a tool that could be used to provide the desired characteristics described above.
It has features and capabilities (and syntax) similar to that provided as part of the C environment.
Its principal benefits are: (a) its relatively low cost (with respect to additional time to process the
stream of information), (b) by using AWK, the tool is readily available to Unix users but more
importantly there exist public domain versions of AWK or AWK-like tools available and, (c) the
various syntactic constructs required may be embedded in the particular compiler's comment
syntax thereby making these syntactic constructs "invisible" to the compiler (hence the phrase
"language independent").

The LICC tool is a pre-processor taking incoming text, processing that text using the syntax and
semantics described below, and finally outputting a "processed" version of the incoming text. The
processed version would be either the complete input text or the input text with selected portions
removed. A typical application of LICC would be found in a Unix command line such as:

awk -f licc input_file | pc


if the LICC output were intended for the pc compiler. The AWK "-f" switch is used to specify that
AWK should use file licc for obtaining its pattern/action description (see below).

LICC Syntax

As previously stated the syntactic constructs for the LICC was freely adapted from the C
programming language. Specifically, #define, #undef, #ifdef, #ifndef, #endif and, #else for
"define", "un-define", "ifÊddefined", "if not defined", "end of if condition" and, "if alternative",
respectively. The goal is to copy text from the input stream to the output stream under control of
these statements. The default mode is to copy a line of text. That is, in the absence of any of these
constructs, simply echo the line. In the examples shown, text in italics will not appear in the
output stream.

The above constructs have the following syntax and semantics:

#define identifier
The specified identifier is made known to the LICC tool for later conditional testing.
eg. #define debug

#undef identifier
The specified identifier is removed from the set of recognized LICC identifiers. If the identifier
given was not previously #defined an error message is generated.
eg. #undef debug

#ifdef identifier
If the specified identifier is recognized (i.e. was previously presented to LICC via a #define) the
text following this statement will be copied to the output stream. Lines will be copied to the output
stream until an #else or #endif statement is encountered (#else and #endif ar described
subsequently).
eg.
#define debug
#ifdef debug
Now we are testing the 'debug' symbol previously defined...
at this point it should be defined. Because it is defined this
segment of text should appear in the output stream.
#endif

#ifndef identifier
If the specified identifier is not currently defined (i.e. has not been previously defined via an
#define or was explicitly undefined via an #undef) the text following this statement will be copied
to the output stream. Lines will be copied until an #else or #endif statement is encountered.
eg.
#ifndef debug
Here the 'debug' symbol is not defined, but since this is an
'ifndef' conditional this segment of text should appear in the output. Initially all
symbols are "undefined".
#endif

#endif
Terminates the scope of an #ifdef or #ifndef statement. If not currently in the scope of an #ifdef or
an #ifndef an error message is generated.


#else
Provides an alternate path for the #ifdef and #ifndef statements. If the #ifdef or #ifndef conditional
failed (that is the condition was found to be false) then the text following this statement will be
copied to the output stream. Lines will be copied until an #endif statement is encountered. If not
currently in the scope of an #ifdef or an #ifndef an error message is generated.

#define debug
#ifdef licctest
The 'licctest' syymbol is not currently defined...so this segment of
text should not appear in the output stream.
#else
However, this segment of text follows an 'else' clause and
therefore should appear in the output as the first conditional test
failed. The 'endif' is, of course, still required.
#endif
#ifndef debug
Yet another 'else' test but this time using the 'ifndef' statement.
Since 'debug' is defined...this test will fail...but
#else
this section of text should be printed as the first conditional failed
and this segment follows an 'else' clause.
#endif
#ifdef debug
One final test with 'else'... demonstrating that if the test is
successful then the text following the 'else' clause will not be printed
#else
This text appears following an else clause for what should be
a successful test...therefore, this text should not appear in
the output stream.
#endif


The syntax/semantics is, as can be readily observed, very simple and intuitively consistent. The
tool supports nesting of the various constructs using conventional scoping rules (that is, inner-
most #else and #endif are satisfied first and are associated with the conditional most recently
preceding them). While LICC does support this feature, this author has not found nesting to be
often utilized in conditional compilation.

#define debug
#ifndef licctest
The 'licctest' symbol is not defined so this segment of text should
appear in the output stream.
#ifdef debug
The 'licctest' symbol is not defined, but the 'debug' symbol is...so you should see this
segment of text in the output. Note that two 'endif's will be required.
#endif
#endif

The reader should recall that the default mode is to copy text, the conditionals are used to control
this process (i.e. copy/or not copy a segment of text). It should also be noted that an additional
LICC goal is the ability to "hide" the LICC syntax from the compiler by embedding the constructs
inside the target language's conventional comment syntax. This is demonstrated in the following
code segment submitted to LICC for processing.


One of the convenient features of LICC is that it can be used on
different programming languages simply by embedding the LICC syntax
inside the programming language comment syntax...thereby making
it transparent to the particular compiler. Such as:

{#ifdef debug } Let's try a Pascal comment
Here we are utilizing the conventional Pascal programming language
comment construct...the curly braces.
{#endif}

/*#ifdef debug */
Here is yet another version of a popular Pascal syntax for comments.
This also represents the comment syntax used by the C programming
language.
However, it would not make sense to use this tool in C because C
has its own pre-processor and in fact was used to model the
pre-processor being demo'd here.
It also represents the comment construct used by the Prolog programming
language, used for AI applications.
/*#endif */

''#ifdef debug
Now we are demonstrating the comment constructs used in Simscript 2.5
These lines should appear in the output stream.
''#endif

C#ifdef debug
This is now using the FORTRAN comment constructs...
FFORTRAN comments are recognized by a 'C' in column 1.
C#endif

--#ifdef debug
Finally, the DoD standard programming language... Ada ...
Ada syntax uses a double hyphen '--' to signify to the compiler that
the remaining text on this line is a comment.
--#endif


AWK Implementation

Readers familiar with the AWK programming language (available under Unix) will no doubt find
an implementation of LICC straight forward (indeed, no doubt the implementation provided here
could be made even "tighter" by AWK aficionados). The implementation provided herein
represents a straight forward approach (hacker model?).

For readers not familiar with AWK, a full tutorial would be outside the scope of this paper,
however the AWK paradigm is: if a given pattern is encountered, execute the specified action for
that pattern, i.e.

pattern { action }

where the pattern is represented using the usual regular expression syntax found in Unix and the
action statements look (and behave) very much like C code.

The full AWK implementation of LICC is given below:

#
# LICC: Language Independent Conditional Compilation syntax (written in AWK)
# for selectingg parts of code to be (or not too be) compiled.
# Compiler independent (i.e. C, Pascal, Ada, Simscript, etc.)
# use the comment structure for your particular language to
# 'hide' the LICC syntactic constructs from the compiler.
#
# The output from the tool can then be piped directly to the
# compiler as in:
# awk -f licc file|pc
#
BEGIN {iprint[[1] = 1; ptr = 1; ifs = 0;} # iprint[] is used as a stack
/#define/ { defs[$2] = 1;
if (!iprint[ptr]) print $0; } # defs[] is used as a symbol table
/#undef/ { if (defs[$2]) { defs[$2] = 0; if(!iprint[ptr]) print $0; }
else print "<<< LICC UNDEF ERROR >>>"; }
/#ifdef/ { ifs++; if (ddefs[$2]) iprint[++ptr] = 1; else
{ iprint[++ptr] = 0; print $0;} }
/#ifndef/ { ifs++; if (!defs[$2]) iprint[++ptr] = 1; else
{ iprint[++ptr] = 0; print $0;} }
/#endif/ { if (!ifs) print "<<< LICC IF ERROR >>>"; else
{ --ifs; if (!iprint[--ptr]) print $0;} }
/#else/ { if (ifs) if(iprint[ptr]) iprint[ptr] = 0; else
iprint[ptr] = 1; else
print "<<< LICC ELSE ERROR >>>";
if (!ipriint[ptr]) print $0; }
{ if (iprint[ptr]) print $0; # copy lines to output stream
if (!ptr) print "<<< LICC POINTER ERROR >>>"}

The following segments of text represent actual input and generated output of text submitted to
LICC for processing. Again, text appearing in italics form should not appearr in the final
generated output of LICC.


INPUT submitted to LICC:

When no tokens are defined LICC works in a pass thru mode
...thus any input lines are simple copied to the output
#ifdef debug
At this point nothing has been defined so any conditional testing
of any symbol will fail...such as this test on the 'debug' symbol
as a result this segment of text will not be copied to the output
#endif
#ifndef debug
Here the 'debug' symbol is still not defined, but since this is an
'ifndef' conditional this segment of text should appear in the output.
#endif
#define licctest Test 1
#ifdef licctest
This segment of code should appear in the output as the 'licctest'
symbol should be recognized having been presented to LICC via a
previous 'define' statment.
#endif licctest Test 1
#define debug Defining a second symbol for subsequent testing
These lines of text are outside the scope of any conditional tests
As a result they should be printed...text outside the scope
of any 'ifdef', 'ifndef' or 'else' should be outputted.
#ifndef licctest Test 2
This piece of text is within the scope of an 'ifndef' conditional test.
Since the 'licctest' symbol IS DEFINED...this segment of text
should not appear in the output.
#endif End Test 2
#ifdef debug Test 3
Now we are testing the 'debug' symbol previously defined...
at this point it should be defined. Both 'licctest' and 'debug'
should be recognized.
#endif
#undef licctest Test 4 (un-defining the 'licctest' symbol)
#ifndef licctest Test 5
Now the 'licctest' symbol is not defined...so this segment of text
should appear in the output.
#endif End Test 5
#ifdef licctest Test 6
The 'licctest' symbol was previously undefined...therefore this
segment of text SHOULD NOT appear in the output stream.
#endif End Test 6
#ifdef debug Test 7
While the 'graphics' symbol has been undefined...the 'debug' symbol
should should still be recognized...this text should appear in output.
#endif
#ifndef licctest (Demonstrate nesting of conditionals)
The 'licctest' symbol is not defined so this segment of text should
appear in the output stream.
#ifdef debug (Nested conditional)
The 'licctest' symbol is not defined,
but the 'debug' symbol is...so you should see this segment
of text in the output. Note that two 'endif's will be required.
#endif (Terminate the 'debug' test)
#endif (Terminate the 'licctest' test)

<<<<<<<<<<<<<<<< 'ELSE' clause Demonstrations >>>>>>>>>>>>>>>>

#ifdef licctest (Demonstrate 'else' LICC clause processing)
The 'licctest' symbol is not currently ddefined...so this segment of
text should not appear in the output stream.
#else
However, this segment of text follows an 'else' clause and
therefore should appear in the output as the first conditional test
failed. The 'endif' is, of course, still required.
#endif
#ifndef debug
Yet another 'else' test but this time using the 'ifndef' statement.
Sinnce 'debug' is defined...this test will fail...but
#else
this section of text should be printed as the first conditional failed
and this segment follows an 'else' clause.
#endif

#ifdef debug
One final test with 'else'... confirming that if the test is
successful then the text following the 'else' clause will not be printed
#else
This text appears following an else clause for what should be
a successful test...therefore, this text should not appear in
the output stream.
#endif



LICC Generated OUTPUT

When no tokens are defined LICC works in a pass thru mode
...thus any input lines are simple copied to the output
#ifdef debug
#endif
#ifndef debug
Here the 'debug' symbol is still not defined, but since this is an
'ifndef' conditional this segment of text should appear in the output.
#endif
#define licctest Test 1
#ifdef licctest
This segment of code should appear in the output as the 'licctest'
symbol should be recognized having been presented to LICC via a
previous 'define' statment.
#endif licctest Test 1
#define debug Defining a second symbol for subsequent testing
These lines of text are outside the scope of any conditional tests
As a result they should be printed...text outside the scope
of any 'ifdef', 'ifndef' or 'else' should be outputted.
#ifndef licctest Test 2
#endif End Test 2
#ifdef debug Test 3
Now we are testing the 'debug' symbol previously defined...
at this point it should be defined. Both 'licctest' and 'debug'
sshould be recognized.
#endif
#undef licctest Test 4 (un-defining the 'licctest' symbol)
#ifndef licctest Test 5
Now the 'licctest' symbol is not defined...so this segment of text
should appear in the output.
#endif End Test 5
#ifdef licctest Test 6
#endif End Test 6
#ifdef debug Test 7
While the 'grpahics' symbol has been undefined...the 'debug' symbol
should should still be recognized...this text should appear in output.
#endif
#ifndef licctest (Demonstrate nesting of conditionals)
The 'licctest' symbol is not defined so this seggment of text should
appear in the output stream.
#ifdef debug (Nested conditional)
The 'licctest' symbol is not defined,
but the 'debug' symbol is...so you should see this segment
of text in the output. Note that two 'endif's will be required.
#endif (Terminate the 'debug' test)
#endif (Terminate the 'licctest' test)

<<<<<<<<<<<<<<<< 'ELSE' clause Demonstrations >>>>>>>>>>>>>>>>

#ifdef licctest (Demonstrate 'else' LICC clause processing)
#else
However, this segment of text follows an 'else' clause and
therefore should appear in the output as the first conditional test
failed. The 'endif' is, of course, still required.
#endif
#ifndef debug
#else
this section of text should be print as the first conditional failed
and this segment follows an 'else' clause.
#endif
#ifdef debug
One final test with 'else'... confirming that if the test is
successful then the text following the 'else' clause will not be printed
#else
#endif

The reader should note that text may be inserted following the LICC syntactic constructs and that
these are just passed through by LICC, thus providing an ability to insert comments concerning the
specific condition being tested.


Summary

Using AWK, it was possible to generate an "inexpensive" language independent conditional
compilation tool that could be used to select (or de-select) segments of code for consideration by a
compiler. The only requirement is that the compiler have some sort of comment syntax (to support
hiding the LICC syntax from the compiler) and that AWK or an AWK-like language be available.
The total processing overhead required to support the various language syntax semantics was only
about 2.5 sec for 1000 lines of code (Sun 3/160).
{et