OEP

119

Title

Pre-processing support

Summary

Provide a C-style preprocessor for the occam compiler.

Owner

Fred Barnes <F.R.M.Barnes@kent.ac.uk>

Status

Accepted

Date-Accepted

2003-03-16

Keywords

language preprocessor

A limited support for pre-processing has now been added to occam. Most usefully, it adds a mechanism for conditional compilation, using pre-processor constants. Given the issues relating to the combination of conditional compilation and occam's indentation, the pre-processor is implemented as part of the compiler, with a special directive for limited control of indentation.

For example:

#DEFINE USE.INT.OUT

PROC write.int (VAL INT x, CHAN BYTE out!)
  #IF DEFINED (USE.INT.OUT)
  out.int (x, 0, out!)
  #ELSE
  out.number (x, 0, out!)
  #ENDIF
:

This would cause the use of "out.int" inside "write.int", instead of "out.number". Removing the "#DEFINE USE.OUT.INT" (or commenting it out) would reverse this.

The above example demonstrates the obvious problem with indentation -- it would be nice if the contents of conditional compilation blocks were indented, but it would be somewhat extreme to always enforce this (temporary "#IF FALSE"ing of code, for example). Therefore, a special "#RELAX" directive is provided, that "relaxes" the indentation inside a conditional compilation block.

Re-writing the above, for example:

#DEFINE USE.INT.OUT

PROC write.int (VAL INT x, CHAN BYTE out!)
  #IF DEFINED (USE.INT.OUT)
    #RELAX
    out.int (x, 0, out!)
  #ELSE
    #RELAX
    out.number (x, 0, out!)
  #ENDIF
:

In addition to simply defining pre-processor constants, they may be defined (and re-defined) with values. The values used may either be integers or strings. Anything else (besides nothing) is invalid. When referring to pre-processor constant values in code, the name must be prefixed with "##". Such substitutions are always literal.

The compiler, before parsing of a file starts, defines various pre-processor constants. Some of these depend on compiler build-time or run-time options. These are:

<table> <tr><th>Name</th><th>Type</th><th>Description</th></tr> <tr><td><code>PROCESS.PRIORITY</code></td><td>integer</td><td>if defined, the number of process priority levels supported</td></tr> <tr><td><code>OCCAM2.5</code></td><td>none</td><td>if defined, indicates that support for user-defined types and other occam 2.1 features is available</td></tr> <tr><td><code>USER.DEFINED.OPERATORS</code></td><td>none</td><td>defined if user-defined operators are supported</td></tr> <tr><td><code>INITIAL.DECL</code></td><td>none</td><td>defined if <code>INITIAL</code> declarations are supported</td></tr> <tr><td><code>MOBILES</code></td><td>none</td><td>defined if <code>MOBILE</code>s (all types) are supported</td></tr> <tr><td><code>BLOCKING.SYSCALLS</code></td><td>none</td><td>defined if blocking system-calls are supported</td></tr> <tr><td><code>VERSION</code></td><td>string</td><td>compiler version string</td></tr> <tr><td><code>NEED.QUAD.ALIGNMENT</code></td><td>none</td><td>defined if the target architecture requires 64-bit alignment of data</td></tr> <tr><td><code>TARGET.CANONICAL</code></td><td>string</td><td>canonical compiler target name, i.e. the host type that "<code>occ21</code>" was compiled for</td></tr> <tr><td><code>TARGET.CPU</code></td><td>string</td><td>target CPU -- the CPU type that the compiler runs on</td></tr> <tr><td><code>TARGET.OS</code></td><td>string</td><td>target OS -- the OS that the compiler runs on</td></tr> <tr><td><code>TARGET.VENDOR</code></td><td>string</td><td>target vendor -- the hardware in use (e.g. "pc")</td></tr> </table>

There is nothing to prevent a file re-defining or un-defining these. In general, it is not a good idea, however. Two additional built-ins are also maintained by the compiler, "FILE" and "LINE". These refer to the current file-name and line-number respectively. For example:

PROC message (VAL []BYTE file, VAL INT line, CHAN BYTE scr!)
  SEQ
    out.string ("Hello from ", 0, scr!)
    out.string (file, 0, scr!)
    out.string (", line ", 0, scr!)
    out.int (line, 0, scr!)
    scr ! '*n'
:

PROC main (CHAN BYTE kyb?, scr!, err!)
  SEQ
    ...  do stuff
    message (##FILE, ##LINE, scr!)
    ...  do more stuff
:

The mechanism for conditional compilation is formed using "#IF", "#ELIF", "#ELSE" and "#ENDIF". The expressions used in "#IF" and "#ELIF" are similar to occam expressions, but should not be mistaken for them. Such expressions must be boolean and fully bracketed. The values of pre-processor constants may be referred to simply by name (omitting the "##"), and compared with constants and each other using a limited range of boolean operators.

The three basic tests/expressions are "TRUE", "FALSE" and "`DEFINED ('''name''')`", that tests whether or not a particular name is a pre-processor define. For comparing strings are the operators "<>" and "=". For comparing integers, "<>", "=", "<=", "<", ">" and ">=". For combining/modifying the results of sub-expressions are "NOT", "AND" and "OR". Evaluation of boolean tests is done in strict lazy order, so that one can write, for example:

#IF (NOT DEFINED (PROCESS.PRIORITY)) OR (PROCESS.PRIORITY < 32)
  #RELAX
  #ERROR not enough or no priority!
#ENDIF

The two special pre-processor directives "#ERROR" and "#WARNING" are used to emit errors and warnings respectively. An error will abort compilation. Substitutions of pre-processor values in the message are done using the "##" prefix.

A more interesting example is where conditional compilation alters the logic of an occam program (alteration beyond clarity is not recommeded, however). For example:

WHILE TRUE
  #IF DEFINED (USE.PRIALT)
  PRI ALT
  #ELSE
  ALT
  #ENDIF
    ...  alt guards

OEP/119 (last edited 2007-09-27 00:29:10 by ats1)