The first phase of compiling a pawn source file to the executable P-code is
“preprocessing”: a general purpose text filter that modifies/cleans up the text before it is fed into the parser. The preprocessing phase removes com- ments, strips out “conditionally compiled” blocks, processes the compiler directives and performs find-&-replace operations on the text of the source file. The compiler directives are summarized onpage 116 and the text sub- stitution (“find-&-replace”) is the topic of this chapter.
The preprocessor is a process that is invoked on all source lines immediately after they are read. No syntax checking is performed during the text substi- tutions. While the preprocessor allows powerful tricks in thepawnlanguage,
it is also easy to shoot yourself in the foot with it.
In this chapter, I will refer to the C/C++ language on several occasions becausepawn’s preprocessor is similar to the one in C/C++. That said, the pawn preprocessor is incompatible with the C/C++ preprocessor.
The#define directive defines the preprocessor macros. Simple macros are:
#define maxsprites 25
#define CopyRightString "(c) Copyright 2004 by me"
In thepawn script, you can then use them as you would use constants. For
example:
#define maxsprites 25
#define CopyRightString "(c) Copyright 2004 by me" main()
{
print( Copyright ) new sprites[maxsprites] }
By the way, for these simple macros there are equivalent pawnconstructs: const maxsprites = 25
stock const CopyRightString[] = "(c) Copyright 2004 by me"
These constant declarations have the advantage of better error checking and the ability to create tagged constants. The syntax for a string constant is an array variable that is declared both “const” and “stock”. The const
attribute prohibits any change to the string and the stockattribute makes the declaration “disappear” if it is never referred to.
Substitution macros can take up to 10 parameters. A typical use for macros with parameters is to simulate tiny functions:
Listing: the “min” macro
#define min(%1,%2) ((%1) < (%2) ? (%1) : (%2))
If you know C/C++, you will recognize the habit of enclosing each argument and the whole substitution expression in parentheses.
If you use the above macro in a script in the following way:
Listing: bad usage of the “min” macro new a = 1, b = 4
new min = min(++a,b)
the preprocessor translates it to:
new a = 1, b = 4
new min = ((++a) < (b) ? (++a) : (b))
which causes “a” to possibly be incremented twice. This is one of the traps that you can trip into when using substitution macros (this particular prob- lem is well known to C/C++ programmers). Therefore, it may be a good
idea to use a naming convention to distinguish macros from functions. In C/C++it is common practice to write preprocessor macros in all upper case.
To show why enclosing macro arguments in parentheses is a good idea, con- sider the macro:
#define ceil_div(%1,%2) (%1 + %2 - 1) / %2
This macro divides the first argument by the second argument, but rounding upwards to the nearest integer (the divide operator, “/”, rounds downwards). If you use it as follows:
new a = 5
new b = ceil_div(8, a - 2)
the second line expands to “new b = (8 + a - 2 - 1) / a - 2”, which,
Operator prece- dence:109
considering the precedence levels of thepawn operators, leads to “b” being
set to zero (if “a” is 5). What you would have expected from looking at the macro invocation is eight divided by three (“a - 2”), rounded upwards — hence, that “b” would be set to the value 3. Changing the macro to enclose each parameter in parentheses solves the problem. For similar reasons, it is also advised to enclose the complete replacement text in parentheses. Below is the ceil_divmacro modified accordingly:
#define ceil_div(%1,%2) ( ((%1) + (%2) - 1) / (%2) )
The pattern matching is subtler than matching strings that look like function calls. The pattern matches text literally, but accepts arbitrary text where the pattern specifies one or more parameter(s). You can create patterns like:
Listing: macro that translates a syntax for variable assignment to a function call #define Field.%1=%2; SetField(%1,%2)
When the expansion of a macro contains text that matches other macros, the expansion is performed at invocation time, not at definition time. Thus the code:
#define a(%1) (1+b(%1)) #define b(%1) (2*(%1)) new c = a(8)
will evaluate to “new c = (1+(2*(8)))”, even though the macro “b” was not defined at the time of the definition of “a”.
If an argument in the replacement text has a “#” immediately in front of it, the argument is converted to a packed string constant —meaning that double quotes are tagged at the beginning and the end. For example, if you have the definition:
#define log(%1) "ERR: " ... #%1 ... "\n"
then the expressionlog(test) will result in"ERR: " . . . "test" . . . "\n". The See page100for
concatenating literal strings with the “...” operator
“#” operator is also called the “stringize” operator, as it converts arguments to (packed) strings.
In the preceding examples, the pattern and the substitution text fit on a Directives:116
single line, as is the case for all directives. For macros where this is incon- venient, an alternative syntax is to enclose the substitution text between square brackets. The substitution text must still start at the same line as the pattern, but it may be split over multiple lines.
The pattern matching is constrained to the following rules:
⋄ There may be no square brackets and no space characters in the pattern. If you must match a space, you need to use the “\32;” escape sequence. The substitution text, on the other hand, may contain space characters and brackets. Due to the matching rules of the macro pattern (explained below), matching a space character is rarely needed.
⋄ Escape sequences may appear in the pattern, and can be used, for example, to match a literal “%” character.
⋄ The pattern may not end with a parameter; a pattern like “set:%1=%2” is illegal. If you wish to match with the end of a statement, you can add a semicolon at the end of the pattern. If semicolons are optional at the end of each statement, the semicolon will also match a newline in the source.
⋄ The pattern must start with a letter, an underscore, or an “@” character The first part of the pattern that consists of alphanumeric characters (plus the “_” and/“@”) is the “name” or the “prefix” of the macro. On the
defined operator and the#undefdirective, you specify the macro prefix.
⋄ When matching a pattern, the preprocessor ignores white space between non-alphanumeric symbols and white space between an alphanumeric sym- bol and a non-alphanumeric one, with one exception: between two identi- cal symbols, white space is not ignored. Therefore:
the patternabc(+-) matches “abc ( + - )”
the patternabc(--) matches “abc ( -- )” but does not match “abc(- -)”
⋄ There are up to 10 parameters, denoted with a “%” and a single digit (1 to 9 and 0). The order of the parameters in a pattern is not important.
⋄ The #define symbol is a parser directive. As with all parser directives,
Directives: 116
the pattern definition must fit on a single line. You can circumvent this with a “\” on the end of the line. The text to match must also fit on a single line.
Note that in the presence of (parametrized) macros, lines of source code may not be what they appear: what looks like an array access may be “preprocessed” to a function call, and vice versa.
A host application that embeds the pawn parser may provide an option
to let you check the result of text substitution through macros. If you are using the standard pawn toolset, you will find instructions of how to use
the compiler and run-time in the companion booklet “Thepawnbooklet —