[Tutorial] PAWN Pre-Processor - Other directives - Part 3/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

Other Directives

This is just a very simple enumeration of the other available pre-processor directives. Note that this list is covered very well in the PAWN language guide (aka pawn-lang.pdf or Pawn_Langugae_Guide.pdf), so I'll only go in to detail with directives which will be used in future guides.
  • #include
This includes the text from another file in to the current file. There are two styles of include:

Code:
#include "a.pwn"
This style includes a file from the same directory as the file which is doing the including. This can use directories (including ".." to go up a level).

Code:
#include <a>
This style includes a file from the standard include directory (by default "pawno\include". This can again include directories in the path. The file extension can be ommitted from either include style if the extension is "pawn", "p" or "inc".

Very frequently includes have what are called "include guards". These are blocks of code looking something like the following:

Code:
#if defined _MY_INC_INCLUDED
    #endinput
#endif
#define _MY_INC_INCLUDED
#pragma library "MyInc"
Every line of that code is redundant, the PAWN compiler is not the same as the C compiler on which this code is undoubtedly based. A C compiler will include a file every time it is included, this can lead to recursive inclusion if a file includes itself, or includes another file which then includes the first etc. For this reason C includes use that style of code to prevent multiple inclusions of the same file - the PAWN file only allows one copy of a file to be included ever.

The "#pragma library" line is used to automatically load PAWN native libraries - a feature which is not used in SA:MP (though does work), SA:MP has its own plugin system.

This inbuilt include guard can be avoided if you need to include a file multiple times (this is rare, but does have its uses). The PAWN compiler defines a symbol of the form "_inc_filename" for every included file, where "filename" is the name of the file excluding any directories or extensions. To remove the symbol simply add the following to your code (I add this at the very start of the file to which it applies):

Code:
#undef _inc_filename
This feature is surprisingly picky given that I've never seen it as an issue in any released script ever! If two files in different directories have the same name the second will not be included as a file with the same NAME is already included. Additionally to use this feature you MUST use the native directory separator of the current operating system (for PAWNO - windows, if you are running under WINE I have NO idea what the effect will be). Assume the following two VERY simple libraries:

inc\b.pwn:

Code:
#define A 10
b.pwn:

Code:
#define B 10
This code will not work as the "b.pwn" file has already been included:

Code:
#include <a_samp>
#include "b.pwn"
#include "inc\b.pwn"

main()
{
    printf("%d %d", A, B);
}
However this code WILL work because the wrong directory separator was used:

Code:
#include <a_samp>
#include "b.pwn"
#include "inc/b.pwn"

main()
{
    printf("%d %d", A, B);
}
Additionally this code will not work as the symbol definition only works correctly for files ending in "pawn", "p" or "inc". The symbol still exists for other filetypes, it is just not available to PAWN:

Code:
#include "inc\b.pwn"

#if defined _inc_b
    #error Will not get called
#endif
This code will give an error though (assuming the file is renamed "b.p" or similar):

Code:
#include "inc\b"

#if defined _inc_b
    #error Will get called
#endif
Further, this will not work either as the right extension, but the wrong separator, is used:

Code:
#include "inc/b"

#if defined _inc_b
    #error Will not get called
#endif
Despite these problems, this feature is useful as it allows you to see what files are and are not included:

Code:
#if !defined _inc_a_samp
    #include <a_samp>
#endif
I will show some very complex uses of this in a later tutorial.
  • #tryinclude
This is similar to "#include" but will not give an error if the file does not exist. You can use this in conjunction with the file definitions explained above to get some nice compile-time configuration:

Code:
#tryinclude <a_mysql>
#if !defined inc_a_mysql
    #tryinclude <mysql>
    #if !defined inc_mysql
        #error No mysql includes found
    #endif
#endif
  • #undef
This is the opposite of "#define". If you have previously defined a symbol using "#define" this will remove it again@

Code:
#include <a_samp>

#undef MAX_PLAYERS
#define MAX_PLAYERS 32
  • #error
Already shown a few examples of this. This will simply halt compilation with a custom error:

Code:
#error Check this error!
Gives:

Code:
C:\file.pwn(1) : fatal error 111: user error: Check this error!


Compilation aborted.Pawn compiler 3.2.3664              Copyright © 1997-2006, ITB CompuPhase


1 Error.
  • #endinput
Ends a file early. Used in include guards (though doesn't need to be) to finish the file before other code in the file is parsed. Note that the code after this point may not be valid syntax, but it doesn't matter as it is never read by the compiler:

Code:
#endinput

This is
gibberish!!!

..:r32AEWhg43ЈЈ
The above is a valid file, simply because the "#endinput" means that everything else is never read.
  • #if
"#if" includes, or excludes, code from a mode based on properties defined at compile time. Things set by "#define" are set at compile time so are known to the compiler. These can't be changed after the mode has been compiled. For example:

Code:
public OnPlayerConnect(playerid)
{
    #if playerid == 0
        printf("Player %d joined", playerid);
    #endif
}
The code above will NOT work because "playerid" is not known by the compiler, it is only known when the mode is run and a player connects. That code will give an error because the compiler doesn't know the value of "playerid".

Most checks which can be used in a normal "if" statement can be used in "#if" directives - with the major exception of function calls (but not macro calls). The one stipulation is that the result MUST be constant. The code below will only print to the console when the server is run if there are more than 200 maximum players defined at compile time:

Code:
main()
{
    #if MAX_PLAYERS > 200
        print("This server can support more than 200 players");
    #endif
}
There are two possibilities for this code:

Code:
#define MAX_PLAYERS 100
Will compile as:

Code:
main()
{
}
Notice that the code has been entirely removed - it doesn't generate an "if" statement, it generates no code at all. The other option is:

Code:
#define MAX_PLAYERS 300
Will compile as:

Code:
main()
{
        print("This server can support more than 200 players");
}
There are a whole range of checks you can do:

Code:
#if a == b

#if a > b

#if (a == b) && (a <= 27)

#if defined a
That last one checks wether "#define" has been called for a symbol or not:

Code:
#if defined SYMBOL
    // Will be false
#endif
Code:
#define SYMBOL

#if defined SYMBOL
    // Will be true
#endif
Code:
#define SYMBOL
#undef SYMBOL

#if defined SYMBOL
    // Will be false
#endif
The examples above used a little trick not mentioned before - definitions with no replacement. This is perfectly valid and can be used, for example, to not generate code under certain circumstances or just to mark something as done (see below).
  • #elseif
Very similar to the "else if" keyword (technically two keywords) in regular PAWN. If the associated "#if" (and all previous "#elseif"s) fails then this check is run. The checks which can be run are exactly the same as those which can be run by "#if".
  • #else
This defines the start of code to be included or directives to be evaluated if all previous "#if" and "#endif" checks failed. "#else" does not have to be the last directive in a "#if" block, but it makes sense most of the time:

Code:
#if a > b
    // Run if a is greater than b.
#else
    // Run if a is less than or equal to b.
#elseif b > a
    // Run if a is less than b.
#endif
This is a valid "#if" block however if "b" is greater than "a" then both the "#else" and "#elseif" blocks will be executed. The "#if" check will fail - setting some internal variable to 0 to represent the fact that no branch has been run yet. The "#else" will see this and thus execute, but will not update the variable (this may be a bug, but could be useful). As this internal variable is still 0 when the "#elseif" is reached it will be evaluated and may be true, setting the internal variable to 1. Interestingly, the fact that "#else" doesn't update the internal settings makes the following valid:

Code:
#if a > b
    // Run if a is greater than b.
#else
    // Run if a is less than or equal to b.
#elseif b > a
    // Run if a is less than b.
#else
    // Run if a is equal to b.
#endif
Code:
#if a > b
    // Run if a is greater than b.
#else
    // Run if a is less than or equal to b.
#else
    // Run if a is less than or equal to b.
#else
    // Run if a is less than or equal to b.
#endif
  • #endif
Ends a pre-processor optional include block. This comes after the last block, wether this is just a "#if", a "#elseif" or a "#else". Valid complete blocks include:

Code:
#if a > b
    // Do something here.
#endif
Code:
#if a > b
    // Do something here.
#else
    // Do something here.
#endif
Code:
#if a > b
    // Do something here.
#elseif b > a
    // Do something here.
#endif
Code:
#if a > b
    // Do something here.
#elseif b > a
    // Do something here.
#else
    // Do something here.
#endif
Invalid blocks include:

Code:
#if a > b
    // Do something here.
Code:
#if a > b
    // Do something here.
#else
    // Do something here.
Code:
#elseif b > a
    // Do something here.
#endif
  • #assert
Checks compile time constants are correct. For example:

Code:
#assert MAX_PLAYERS > 32
Is equivalent to:

Code:
#if MAX_PLAYERS <= 32
    #error
#endif
This can use all the checks available to "#if".
  • Others
The other directives (listed in pawn-lang.pdf, not all work) are:
  • #pragma directive - Gives instructions to the compiler.
  • #section "sname" - Restricts the scope of static variables and functions (broken).
  • #line num - Changes the current file line number.
  • #file "fname" - Changes the current file name. Does not define a new "_inc_fname" symbol.
See the main documentation for more information.

Defines With No Replace

Symbols can be defined with no replacement:

Code:
#define SYMBOL
Whenever "SYMBOL" appears in code it will be replaced with nothing. This has two main uses.
  • Markers
The first main use of this system is to mark something as done. These symbols are mainly used by the preprocessor, for example:

Code:
#tryinclude <my_inc>
#if defined _inc_my_inc
    // Do something
#endif
The value of "_inc_my_inc" is irrelevant - if it exists you have included the file, a value such as "0" or "42" is meaningless to wether the file is included or not, so it isn't set. You can't do:

Code:
#tryinclude <my_inc>
#if _inc_my_inc == 1
    // Do something
#endif
Because if the file ISN'T included then "_inc_my_inc" won't exist at all and you will get a compiler error from trying to compare something that doesn't exist to 1.
  • Optional code
The other main use of this is for code which may or may not exist. For example:

Code:
#if RETURN_VAL == 2
    #define RETURN_TAG Float:
#else
    #define RETURN_TAG
#endif

RETURN_TAG MyFunc();
If "RETURN_VAL" is "2" this will compile as:

Code:
Float: MyFunc();
If it is not "2" the code will simply compile as:

Code:
MyFunc();
In this way code can be configured at compile time, for example adding the "static" definition to functions if required:

Code:
#if defined HIDE_FUNCTIONS
    #define STATIC static
#else
    #define STATIC
#endif

STATIC MyFunc();
Here, if "HIDE_FUNCTIONS" is defined (note that this is a "marker" replacement less macro), "STATIC" will be defined with a value, otherwise it will be defined with no value. The "STATIC" macro is placed before the "MyFunc" definition which will either define a static function or a normal function (one with nothing before it) depending on compile time options.
Reply


Messages In This Thread
PAWN Pre-Processor - Other directives - Part 3/7 - by Misiur - 14.04.2015, 21:04

Forum Jump:


Users browsing this thread: 1 Guest(s)