14.04.2015, 20:37
(
Last edited by Misiur; 14/04/2015 at 09:23 PM.
)
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:
It doesn't matter if the code is not valid syntax BEFORE macro replacements - just as long as it's valid AFTER.
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 _ @
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):
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.
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:
Using all those arrays can be quite unwieldy, so we can introduce functions to make it a bit easier:
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:
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:
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:
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:
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:
Code macro:
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).
Consider sending text to a player:
Now cosider that you want to add the player's ID to the message:
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:
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?
Will generate:
And:
Will generate:
Now what would have happened if the "%3" was in brackets and we gave the last statement a semi-colon?
Will generate:
"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):
Back to the main example:
Will generate:
"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.
The code generated will look like:
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:
Which will generate:
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!
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:
This now ends requiring a semi-colon and will not loop because "FALSE" is never true.
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.
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))
- Definitions
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
pawn Code:
#define <part 1><part 2><part 3>
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>
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.
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;
}
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;
}
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;
}
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;
}
pawn Code:
SetMoney(playerid, money++);
pawn Code:
SET_MONEY(playerid, money);
money++;
- Examples
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)
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));
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));
- Ommitting Brackets
Consider sending text to a player:
pawn Code:
SendClientMessage(playerid, COLOR_RED, "Hello there");
pawn Code:
new str[32];
format(str, sizeof (str), "Hello there %d", playerid);
SendClientMessage(playerid, COLOR_RED, str);
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);
pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d"), playerid);
SendClientMessage((playerid), (COLOR_RED), str);
pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d, you have $%d", playerid, money);
pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d, you have $%d"), playerid, money);
SendClientMessage((playerid), (COLOR_RED), str);
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);
pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d"), (playerid));
SendClientMessage((playerid), (COLOR_RED), str);;
pawn Code:
#define a() ;
a();
pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d, you have $%d", playerid, money);
pawn Code:
new str[32];
format(str, sizeof (str), ("Hello there %d"), (playerid, money));
SendClientMessage((playerid), (COLOR_RED), str);;
- Scoped Code Macros
pawn Code:
SendClientMessageFormatted(playerid, COLOR_RED, "Hello there %d", playerid);
SendClientMessageFormatted(playerid, COLOR_RED, "You have $%d", money);
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);
pawn Code:
#define SendClientMessageFormatted(%0,%1,%2,%3) \
{ \
new str[32]; \
format(str, sizeof (str), (%2), %3); \
SendClientMessage((%0), (%1), str); \
}
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);
};
pawn Code:
#define SendClientMessageFormatted(%0,%1,%2,%3) \
do \
{ \
new str[32]; \
format(str, sizeof (str), (%2), %3); \
SendClientMessage((%0), (%1), str); \
} \
while (false)
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)
- Examples
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)
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.