[Tutorial] PAWN Pre-Processor - Macro issues and spaces - Part 7/7
#1

Contents

Part 1 - Covers an introduction to the pre-processor and covers some important things when writing function-like macros.
Part 2 - Explains exactly what the compiler searches for and looks at some common macro uses.
Part 3 - Describes the other directives available (beside "#define") and looks at definitions with no replacement value.
Part 4 - How to use strings with the pre-processor.
Part 5 - Alternatives to the pre-processor, multiple symbols and recursion.
Part 6 - Case Study (y_remote).
Part 7 - Macro issues and spaces.

Additional

Utilising tagof - By g_aSlice.
Future-proof string concatenation
String bug
Macros that do multiple things
Advanced "tag" macros

Macro issues

In this part we will detail problems that can stem from using the pre-processor, errors and other issues that can arise. Most of these come from the fact that the pre-processor is a pure text replacement system, not a function system.
  • Recursion
Part 5 already looked in some depth at recursion, but let's just see what happens when it goes wrong:

Code:
MyFunc(a, b)
{
}

#define MyFunc(%0) MyFunc(%0, %0 + 42)
Now the general intention of this code (not that it makes much sense) is to take one parameter and instead pass two parameters, the second being 42 greater than the first:

Code:
main()
{
    MyFunc(12);
}
Becomes:

Code:
main()
{
    MyFunc(12, 12 + 42);
}
Unfortunately, after the pre-processor replaces some text it re-runs its match against the result so we get:

Code:
main()
{
    MyFunc(12, 12 + 42 + 42);
}
Which still matches and we get:

Code:
main()
{
    MyFunc(12, 12 + 42 + 42 + 42);
}
And so on forever - thus getting the compiler stuck in a loop until it crashes! The problem is that the compiler can't detect the endless loop - we've already shown that loops can sometimes be useful and will eventually end.
  • Parameters
When you are using macro parameters you have to be VERY careful if they are used multiple times. The simplest example of this is a "square" macro:

Code:
#define square(%0) ((%0) * (%0))
Looks simple enough, until someone decides to be clever:

Code:
new
    i = 0;
while (i != 10)
{
    printf("%d squared = %d", square(i++));
}
Now, after the text-based replacement of the pre-processor, you compile:

Code:
new
    i = 0;
while (i != 10)
{
    printf("%d squared = %d", ((i++) * (i++)));
}
I don't even know if that will print the right answer, but I DO know that it will skip every other number in the loop. In cases like this you should really just use a function as their parameters will only be evaluated once:

Code:
square(n) return n * n;
  • "if"
This is another tricky issue in macros - the use of "if" within them:

Code:
#define TEST(%0) if ((%0) >= 0) printf("%d is positive.", (%0));
Just to support the previous argument, try run this code:

Code:
main()
{
    new
        a = 0;
    TEST(a--)
}
Moving on from that though, using "if" can cause problems of its own when used with other "if2 statements:

Code:
new
    a = -7;
if (a <= 10)
    TEST(a)
else
    printf("%d is greater than 10.", a);
If you run that code you will get:

Code:
-7 is greater than 10.
Err, what?

Let's look at the expansion of this code:

Code:
new
    a = -7;
if (a <= 10)
    if ((a) >= 0) printf("%d is positive.", (a));
else
    printf("%d is greater than 10.", a);
That still looks OK until we remember that "else" is associated with the previous "if", not the one at the same level. In short, this code compiles as:

Code:
new
    a = -7;
if (a <= 10)
{
    if ((a) >= 0) printf("%d is positive.", (a));
    else printf("%d is greater than 10.", a);
}
Which is NOT what is wanted, and is almost invisible if you don't know that "TEST" is a macro. The best way to avoid this is to always use braces after "if" statements. The other way (which is less good) is to do this:

Code:
#define TEST(%0) if ((%0) < 0) {} else printf("%d is positive.", (%0));
That will compile as equivalent to:

Code:
new
    a = -7;
if (a <= 10)
{
    if ((a) < 0) {}
    else printf("%d is positive.", (a));
}
else
{
    printf("%d is greater than 10.", a);
}
Spaces

It is documented in pawn-lang.pdf that should you ever need to detect a space you could use "\32;" instead but I could never get it to work! Doing this:

Code:
#define TEST1\32;(%1) TEST1(%1)
Will NOT detect spaces before the brackets and remove them. Similarly this will not work:

Code:
#define TEST2(\32;%1) %1
That will NOT detect a call like "TEST2( 42)" - I'm still not sure why or what the exact rules governing how the macros work are, but this will work:

Code:
#define TEST3(%0\32;%1) %1
In that case, even just a leading space like in "TEST3( 42)" can be detected and removed. This lack of space detection has been the bane of my code for literally YEARS (if you don't believe me, that's what the point of this topic was). It is why this works:

Code:
foreach (new i : Player)
But this gives multiple cryptic errors:

Code:
foreach ( new i : Player )
I knew the documentation claimed it could be done, but my repeated failed attempts led me to believe that maybe the SA:MP compiler version couldn't until I started really digging about in its code. Anyway, i am very happy about this and will be retrofitting certain bits of code to find any errors caused by bad spacing that I can now fix! As Slice points out this can also be used to detect the end of a line using "\10;":

Code:
#define EAT_LINE:%0\10;%1 <%0>
#define foo%0\10; foo,
#define bar%0\10; bar,

EAT_LINE:boop boop
foo
bar

// Output: <boop boop>foo,bar,
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)