Xc++ Macro Preprocessor

The front end Xcpp includes a powerful preprocessor which is applied to the source code before subsequent compilation by a standard C/C++ compiler.

The Xc++ preprocessor directives begin with the @ character.

To illustrate the preprocessor self contained code examples are shown before and after translation. Red syntax colouring is used for the directives and blue for the standard C++ keywords.

Each directive has a defined translation - i.e. what it becomes in the result of applying the preprocessor.

Macros can be regarded as variables at preprocessor time.

Macros can be defined recursively.

The grammar of the preprocessor language is defined in EBNF.

Directives always having an empty translation

Directive Description
@def Macro definition
@nakeddef Macro definition where no local namespace is pushed onto the namespace stack when the macro is invoked
@print Write to stdout during compilation
@assert Assertion of a given expression at pre-processor time
@assertfails Verifies that macro expansion of its argument generates a macro expansion error. Used to unit test the xcpp preprocessor
@fail Aborts the xcpp preprocessor displaying an error message
@runpython Macro expands its parameter then runs it under the python interpreter
@defpython Defines a macro in terms of a python expression
@$ $variable definitions
@@ Expands into nothing, acts as a delimiter for the lexical scanner
@let Redefines the value of a macro to allow macros to be used like variables at preprocessor time.

Directives which may have a non-empty translation

Directive Description
@if-@else Conditional macro expansion
@scope Defines a local scope for macro definitions, for limiting the scope of macros, to avoid accidental invocation outside their intended usage
@str Expands into a double quoted string obtained by first macro expanding its argument then converting it to a double quoted string
@strx Same as @str except that linefeeds cause the string to be broken up into separate strings
@unstr Removes the quotes on the macro expansion of its string parameter
@[] Outputs the result of macro expanding the result of macro expanding its parameter
@quote Macro expands literally to its argument (i.e. without macro expanding it)
@() Outputs the evaluation of the expression which is the macro expansion of its parameter
@import Translates to a #include directive
@relativepath Calculates a relative path between two given absolute paths
@while Allows for loops
@for Allows for simple loops. The body of the directive is repeately macro expanded for different values of the loop variable(s), taken in sequence from the iteration list which is a comma separate list of values enclosed in square brackets.

Aborting the preprocessor

There are many different error conditions that can cause the xcpp preprocessor to abort with some failure indication. Error messages are displayed in a similar form to the Microsoft Visual C++ compiler.

The directives @assert, @assertfails and @fail are specifically aimed at aborting the xcpp preprocessor.

String conversion directives

Sometimes a macro has bound to some text, and we need to put it in double quotes so it looks like a C/C++ string literal. At other times it can be useful to remove the quotes. @str and @strx allow for adding the quotes, and @unstr allows for removing them.

Scope

The @scope directive defines a local scope for macro definitions.

Namespace stack

Consider a procedural execution model of the xcpp preprocessor. A file is typically translated by processing its text from start to finish. Typically text is processed by copying it verbatim from input to output.

C/C++ comments are copied verbatim without further processing. It follows that @def directives can be commented out easily. E.g

Before translationAfter translation
@def m = 1
//@def m = 2
int f() { return m; }


//@def m = 2
int f() { return 1; }

Note that the comment appeared in the output. To strip a comment from the output, precede the C/C++ comment with @. i.e. use @//... or @/*...*/. For example:

Before translationAfter translation
@// This comment is stripped from the output
@def m = 1
int f() { return m; }



int f() { return 1; }

The xcpp preprocessor uses a stack of namespaces to record macro definitions. Macro names are looked up by searching each namespace in turn, starting from the top of the stack. Therefore names in an inner scope hide names in an outer scope. When translation first begins at the top of the file, the stack is initialised with a single entry, which is the global namespace for macro definitions. Note therefore that macros at the outermost scope are recorded in the global namespace.

When a @def directive is processed the macro is recorded in the namespace at the top of the stack. The body of a macro is skipped over without being processed. Therefore, at this time the nested @def directives are ignored. Processing the body of a macro only occurs when the macro is invoked (if ever). When a macro is invoked a new namespace is pushed onto the top of the stack in preparation for processing the body of the macro. It is popped when the processing of the body is completed. The effect is that the execution of the macro creates a local namespace for nested @def directives.

Macros are expanded in the context of the caller

A macro is expanded in the context of the stack of namespaces defined at the point of invocation, not the point of definition. In the following example, m is invoked in a context where n = 2. The fact that n = 1 at the point of definition of m is irrelevant.

Before translationAfter translation
@def n = 1
@def m = n
@def n = 2
int f() { return m; }
@def n = 3
int g() { return m; }




int f() { return 2; }
int g() { return 3; }

This concept of expanding a macro in the context of the caller (and not the callee) makes it possible to write very flexible macros without the need to explicitly parameterise with large numbers of formal arguments.

Local scope in directives

Most directives introduce private namespaces for local macros. For example, local macros can be defined in either the boolean condition or the body of an @if directive, or within a @(...) directive:

Before translationAfter translation
@def int a = 1
@if
(
    @def a = true
    a
)
{
    @def b = 2
    int c = 10;
    int f() { return @(@def c = {3} a+b+c); }
}










int c = 10;
int f() { return 6; }

Python directives

The directives @runpython and @defpython provide access to a Python interpreter. The intention is for complex macros to be defined using Python, which represents a well supported and well documented language, allowing the xcpp preprocessor to be simpler.

Enabling and disabling macro expansion

The directives @@, @[] and @quote can be used to prevent or force macro expansion.

Expressions

The xcpp preprocessor allows for evaluation of expressions in a similar manner to the C/C++ compiler.