[Tutorial] PAWN Pre-Processor - Search patterns - Part 2/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

Search patterns

The easiest way to explain all macro search patterns generically is by explaining EXACTLY what the compiler searches for. It is also very important to realise that macros are text replacements - all of the following are valid:

pawn Code:
// Definition
#define MY_DEF                          IsPlayerConnected
// Use
if (MY_DEF(playerid))
// Output
if (IsPlayerConnected(playerid))
pawn Code:
// Definition
#define MY_DEF                          IsPlayerConnected(
// Use
if (MY_DEF playerid))
// Output
if (IsPlayerConnected(playerid))
pawn Code:
// Definition
#define MY_DEF                          IsPlayerConnected(playerid
// Use
if (MY_DEF))
// Output
if (IsPlayerConnected(playerid))
pawn Code:
// Definition
#define MY_DEF                          IsPlayerConnected(playerid))
// Use
if (MY_DEF
// Output
if (IsPlayerConnected(playerid))
pawn Code:
// Definition
#define MY_DEF                          if (IsPlayerConnected(playerid))
// Use
MY_DEF
// Output
if (IsPlayerConnected(playerid))
pawn Code:
// Definition
#define MY_DEF(%0)                      if (IsPlayerConnected((%0)))
// Use
MY_DEF(playerid)
// Output
if (IsPlayerConnected(playerid))
It doesn't matter if the code is not valid syntax BEFORE macro replacements - just as long as it's valid AFTER.
  • Definitions
A definition only has text in the search pattern - for example "MAX_PLAYERS". The examples above give a number of examples of this in various situations and with all sorts of replacement patterns. This is the name of the macro. Macros can START with any of the following characters:

Code: a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ @ And after the start can contain any of the following characters:

Code: a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 _ @
  • Macros
A macro search pattern with parameters has the very relaxed following syntax with three parts:

pawn Code:
#define <part 1><part 2><part 3>
Part 1 is the name of the macro - this is the same as in normal definitions and can use the same symbols.

Part 3 is a character - ANY character - that denotes the end of the macro. Probably the most common character seen here is ')' - a close bracket, but it could be anything (including letters and numbers).

Part 2 is everything else and can contain parameters, letters, numbers and random symbols. This must start with an invalid name character just so the compiler knows where the name finishes. This part contains all the parameters.

Examples - these are ALL valid search patterns (the replace patterns have been left off):

pawn Code:
// Part 1: MY_FUNC
// Part 2: (
// Part 3: )
#define MY_FUNC()
pawn Code:
// Part 1: MY_FUNC
// Part 2: (%0
// Part 3: )
#define MY_FUNC(%0)
pawn Code:
// Part 1: MY_FUNC
// Part 2: (%0-%1
// Part 3: )
#define MY_FUNC(%0-%1)
pawn Code:
// Part 1: MY_FUNC
// Part 2: $%0,%1
// Part 3: $
#define MY_FUNC$%0,%1$
pawn Code:
// Part 1: Module
// Part 2: ->%0(%1
// Part 3: )
#define Module->%0(%1)
pawn Code:
// Part 1: CRAZY
// Part 2: ^S%0Y%1
// Part 3: Z
#define CRAZY^S%0Y%1Z
pawn Code:
// Part 1: RF
// Part 2: :%0:%1[%2](%3)<%4
// Part 3: >
#define RF:%0:%1[%2](%3)<%4>
Those examples should tell you all you ever need to know about search patterns, though we will look at some more specially. A common misconception is that they must be of the form "NAME(parameter,parameter)" - this is simply not the case in PAWN (and is frankly an AWESOME feature)!

Replacements

There are four types of replacements (as I classify them) - definitions, function macros, code macros and special.
  • Definitions are like "MAX_PLAYERS" - they define a single number or string and are used instead of numbers.
  • Function macros are used where functions could be used and look a lot like functions (often I will use function naming conventions for these instead of macro conventions, but it varies).
  • Code macros generate a load of code, often multiple statements - these tend to use macro naming conventions to show people that something special is going on, these look a lot like function macros but can't always be used in the same place.
  • Special macros are used to add things to the language - a good example of this is "foreach" - this adds a new type of loop (the "foreach" loop) which looks like a regular PAWN statement, but is infact a special macro using the "for" loop.
Special macros will be addressed later and definitions have already been covered - this section looks at function and code macros.

Function Macros

Part 1 covered these to some extent in the "Macros" and "Parameters" sections, but there is more to them. A function macro is one which does the job which would otherwise be done by a function and which can be used anywhere a function can be used (mostly).

Imagine you have the following code:

pawn Code:
enum E_DATA
{
    Float:E_DATA_HEALTH,
    E_DATA_MONEY
}

new gPlayerData[MAX_PLAYERS][E_DATA];

public OnPlayerConnect(playerid)
{
    gPlayerData[playerid][E_DATA_MONEY] = 100;
    return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
    printf("player %d had %d", playerid, gPlayerData[playerid][E_DATA_MONEY]);
    return 1;
}
Using all those arrays can be quite unwieldy, so we can introduce functions to make it a bit easier:

pawn Code:
enum E_DATA
{
    Float:E_DATA_HEALTH,
    E_DATA_MONEY
}

new gPlayerData[MAX_PLAYERS][E_DATA];

GetMoney(playerid)
{
    return gPlayerData[playerid][E_DATA_MONEY];
}

SetMoney(playerid, money)
{
    gPlayerData[playerid][E_DATA_MONEY] = money;
}

public OnPlayerConnect(playerid)
{
    SetMoney(playerid, 100);
    return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
    printf("player %d had %d", playerid, GetMoney(playerid));
    return 1;
}
Those functions are very simple - the first version of the code didn't have them at all and wasn't TOO bad. It may have been slightly larger code, but not by much! We can combine both using function macros:

pawn Code:
enum E_DATA
{
    Float:E_DATA_HEALTH,
    E_DATA_MONEY
}

new gPlayerData[MAX_PLAYERS][E_DATA];

#define GetMoney(%0)                                                            \
    gPlayerData[(%0)][E_DATA_MONEY]

#define SetMoney(%0,%1)                                                         \
    gPlayerData[(%0)][E_DATA_MONEY] = (%1)

public OnPlayerConnect(playerid)
{
    SetMoney(playerid, 100);
    return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
    printf("player %d had %d", playerid, GetMoney(playerid));
    return 1;
}
This version now has the speed of the first version with the simplicity of the second. Note that although there is no maths in the macro, brackets are still used. Try and figure out what would happen if the following code was used ("%0" now has no brackets) given what you know about how macros match patters - people are unlikely to type this but mistakes can happen:

pawn Code:
#define GetMoney(%0)                                                            \
    gPlayerData[%0][E_DATA_MONEY]

public OnPlayerDisconnect(playerid, reason)
{
    printf("player %d had %d", playerid, GetMoney( 0], moo[10 ));
    return 1;
}
The macros here, as function macros, are designed to replace functions and be used like functions - so for this reason they use function naming conventions instead of macro naming conventions. The major downside is that the parameters are not named, so it's difficult to determine their meanings without looking at the code. The other problem with doing this is that people may try to do clever things like:

pawn Code:
SetMoney(playerid, money++);
This is explained in detail at the end of part 1. In these examples it doesn't so much matter as the parameter is only used once, so the variable is only incremented once - this is a good rule of thumb: If you are writing a macro to replace a small function, and each parameter is only used once, you can use function naming conventions if you desire. If the money parameter was used 2 or more times the following syntax may be better to discourage people from putting "++" in the parameters passed:

pawn Code:
SET_MONEY(playerid, money);
money++;
  • Examples
These are some examples of function macros:

pawn Code:
// Wrapper around printf.
#define DEBUG(%0)                       printf("Debug: %s", (%0))

// Square two numbers - watch out for increments.
#define SQUARE(%0)                      ((%0) * (%0))

// Set a timer to do a passed function every second.
#define EVERY_SECOND(%0)                SetTimer((%0), 1000, 1)

// Show an icon - calls "GetNextIcon", an imaginary custom function (or macro).
#define ShowIcon(%0,%1,%2,%3)                                                   \
    SetPlayerMapIcon(playerid, GetNextIcon(playerid), %1, %2, %3, %0, COLOR_RED)
Code Macros

A code macro is similar to a function macro, but can't be used in all places. This is usually due to code macros having more statements than a function macro, which only has one:

Function macro:

pawn Code:
// Declaration
#define MY_FUNC(%0)                     ((%0) + 7)

// Uses
if (MY_FUNC(var))

var = MY_FUNC(var);

OtherFunc(MY_FUNC(42));
Code macro:

pawn Code:
// Declaration
#define MY_FUNC(%0)                     new b = ((%0) + 7)

// All invalid
if (MY_FUNC(var))

var = MY_FUNC(var);

OtherFunc(MY_FUNC(42));
The function macro is valid syntax in all of those places because it just contains expressions (an expression is something which can go on the right hand side of an equals sign), the code macro contains a statement (a statement can include expressions, keywords and other bits such as braces). "if (new b = ((var + y))" is not valid syntax, neither is "var = new b = ((var) + 7);" (Side note, "a = b = c;" IS valid syntax - "b = c" is an expression).
  • Ommitting Brackets
Part 1 told you to put parameters to macros in brackets - we are about to change that statement!

Consider sending text to a player:

pawn Code:
SendClientMessage(playerid, COLOR_RED, "Hello there");
Now cosider that you want to add the player's ID to the message:

pawn Code:
new str[32];
format(str, sizeof (str), "Hello there %d", playerid);
SendClientMessage(playerid, COLOR_RED, str);
Is there no easier way to do it? Yes there is, using a function macro - one that looks and is used like a normal function:

pawn Code:
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
    new str[32];                                                                \
    format(str, sizeof (str), (%2), %3);                                        \
    SendClientMessage((%0), (%1), str)
Notice that "%3" is NOT in brackets and that the last statement has no semi-colon - what do you think that will do to the output?

pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d", playerid);
Will generate:

pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d"), playerid);
SendClientMessage((playerid), (COLOR_RED), str);
And:

pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d, you have $%d", playerid, money);
Will generate:

pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d, you have $%d"), playerid, money);
SendClientMessage((playerid), (COLOR_RED), str);
Now what would have happened if the "%3" was in brackets and we gave the last statement a semi-colon?

pawn Code:
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
    new str[32];                                                                \
    format(str, sizeof (str), (%2), (%3));                                      \
    SendClientMessage((%0), (%1), str);
pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d", playerid);
Will generate:

pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d"), (playerid));
SendClientMessage((playerid), (COLOR_RED), str);;
"playerid" is now in brackets (which is still fine) and there are TWO semi-colons at the end. One from the macro and the other from the end of the original text - the semi-colon is NOT part of the search text so is not replaced - think about what the following code will produce (note also that this code has no parameters but still has brackets):

pawn Code:
#define a() ;
a();
Back to the main example:

pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d, you have $%d", playerid, money);
Will generate:

pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d"), (playerid, money));
SendClientMessage((playerid), (COLOR_RED), str);;
"playerid, money" (the contents of "%3", as it was ended by a close bracket, not a comma) is now in brackets - which is WRONG! And the double semi-colon is still there.
  • Scoped Code Macros
The code above has a major flaw - if I do:

pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d", playerid);
SendClientMessageFormatted(playerid, COLOR_RED, "You have $%d", money);
The code generated will look like:

pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d"), playerid);
SendClientMessage((playerid), (COLOR_RED), str);
new str[32];
format(str, sizeof (str), ("You have $%d"), money);
SendClientMessage((playerid), (COLOR_RED), str);
This will generate an error because your code is declaring "str" twice. To avoid this you need to restrict the scope of the variables to just the few lines, the easiest way of doing this is with braces:

pawn Code:
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
    {                                                                           \
        new str[32];                                                            \
        format(str, sizeof (str), (%2), %3);                                    \
        SendClientMessage((%0), (%1), str);                                     \
    }
Which will generate:

pawn Code:
{
    new str[32];
    format(str, sizeof (str), ("Hello there %d"), playerid);
    SendClientMessage((playerid), (COLOR_RED), str);
};
{
    new str[32];
    format(str, sizeof (str), ("You have $%d"), money);
    SendClientMessage((playerid), (COLOR_RED), str);
};
This solves the variable scope problem but introduces a new problem - the braces have a semi-colon after them from the semi-colon at the end of the macro call (this is the same as the double semi-colon problem above). You need some way of ending your code requiring a semi-colon while still restricting the scope with braces. The only statement which allows this is the "do-while" loop, but you don't want to loop!

pawn Code:
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
    do                                                                          \
    {                                                                           \
        new str[32];                                                            \
        format(str, sizeof (str), (%2), %3);                                    \
        SendClientMessage((%0), (%1), str);                                     \
    }                                                                           \
    while (false)
Nearly there! Now the compiler complains about the constant expression in the loop ("false" is never true, so it thinks the code is pointless - note that this is a common method of coding in C/C++ where the compiler recognises and accepts this code). The final fix is:

pawn Code:
#if !defined FALSE
    // stock variables are not included if they're not used.
    stock
        bool:FALSE = false;
#endif

#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
    do                                                                          \
    {                                                                           \
        new str[32];                                                            \
        format(str, sizeof (str), (%2), %3);                                    \
        SendClientMessage((%0), (%1), str);                                     \
    }                                                                           \
    while (FALSE)
This now ends requiring a semi-colon and will not loop because "FALSE" is never true.
  • Examples
These are some examples of code macros:

pawn Code:
// Wrapper around printf with _DEBUG check.
#define DEBUG(%0)                       if (_DEBUG == 1) printf("Debug: %s", (%0))

// Declare a variable.
#define VAR(%0)                         new %0 = -1

// Shortcut to declare a public function.
#define PUBLIC(%0,%1)                                                           \
    forward %0(%1);                                                             \
    public %0(%1)
Long Macros

Lines in PAWN have to be under a certain size (512 characters including the end of line marker - effectively making the limit 510) AFTER all preprocessing is done. The final version of the "SendClientMessageFormatted" macro above will not actually compile for this reason. The code above illustrates concepts in the pre-processor, it's designed as a reference, not a code source, you may need to adjust some things to get it compiling.

To reduce the length of lines remove excess whitespace - the examples above have a lot of spaces and can be shrunk to one line.

I'll come back to this topic later.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)