PreProcessor Commands
The PreProcessor and the Config Parser allow to use macros in configs. The purpose of macros is to re-use the same definition in scripts and configs multiple times. It also gives a centralized place to correct errors in this definition. Know that edge cases may arise - see the collection of test-cases linked at the end of this page.
Parsing
See Config Parser below:
- Config.cpp - parsed when PBO is binarized.
- localize cannot be used in macros, as it would hardcode string of current language instead of creating reference.
- Description.ext - parsed when mission is loaded or previewed in missions list.
- SQF script - parsed when preprocessFile, preprocessFileLineNumbers, compileScript or execVM is used.
Macros
Comments
A comment is a line within code that is not actually processed by the game engine. They are used to make code more readable or to add notes for future reference. The preprocessor removes all comments from the file before it is processed. Therefore, comments are never actually "seen" by the game engine.
// this is a single-line comment
/*
this is a
multi-line
comment
*/
myValue = something; // only this part of the line is commented out
myArray = { "apple"/*, "banana"*/, "pear" }; // a portion in the middle of this line is commented out
#define
Using the #define instruction, it is possible to define a keyword and assign a definition to it. The keyword may contain any letter, digit or underscore in arbitrary order, as long as it does not start with a digit (RegEx: [a-zA-Z_][0-9a-zA-Z_]*). As an example:
#define true 1
The above means that whenever true is used in a config, the parser will replace this with the value 1.
The define-statement does swallow all spaces in between the macro-keyword and any non-space-character in the body (Note that tabs are not spaces! They don't get removed)
#define MACRO test
MACRO // preprocesses to test (without any spaces)
#define MACRO test // There's a tab between MACRO and test
MACRO // preprocesses to " test" (without quotes - they are only used to show that the tab character didn't get removed)
The space between the macro-keyword and the body is also fully optional (though very useful to tell the preprocessor where the macro name ends and where the body begins):
#define MACRO#test
MACRO // preprocesses to "test"
Arguments
Arguments can be added to more complex macros, by including them between brackets after the keyword. For the name of the arguments the same rule as for the macro-keyword (see above) apply.
#define CAR(NAME) displayName = NAME;
If CAR("Mini") is used, it will be replaced with displayName = "Mini";. Multiple arguments can also be used:
#define BLASTOFF(UNIT,RATE) UNIT setVelocity [0,0,RATE];
Macro arguments may be composed of any characters, as long as they do not contain a comma (because commas are used as argument-delimiters). If quotes are being used, they have to be balanced. The same applies to single-quotes This is because String detection is working in macro arguments - Therefore commas can even get passed in as a macro argument as long as they are part of a String (This only works with Strings wrapped in double-quotes though). Note however that although the macro gets resolved properly, the comma gets removed from the String (probably a bug).
#define MACRO(arg) arg
MACRO("Some, content") // preprocesses to "Some content" (note the missing comma)
Quote escaping is also not supported in this context (neither with double- nor with single-quotes)
#define MACRO(arg) arg
MACRO("Some ""content""") // preprocesses to "Some ""content"""
Passing arrays with more than one element [el1,el2,...] as arguments into macros as well as any argument containing comas "some, sentence", will need a small workaround:
#define HINTARG(ARG) hint ("Passed argument: " + str ARG)
Incorrect usage:
HINTARG([1,2,3,4,5,6,7,8,9,0]); // ERROR, won't even compile
Correct usage:
#define array1 [1,2,3,4,5,6,7,8,9,0]
HINTARG(array1); // SUCCESS
The argument replacement is performed before the expansion of the macro body. That means one doesn't have to worry about name-conflicts between argument-names of the current macro and already defined macros:
#define ONE foo
#define TWO(ONE) ONE
TWO(bar) // will preprocess to bar
Replacing parts of words
By default, only whole words can be replaced by arguments. If only a part of the word should be replaced, use the ## instruction. This is necessary when either the start or the end of the argument connects to another character that is not a ; (semi-colon) or (space).
class NAME##_Button_Slider: RscText \
{ \
model = \OFP2\Structures\Various\##FOLDER##\##FOLDER; \
It is also possible to use the single # to convert an argument to a string.
statement = (this animate [#SEL, 0]); \
Multi-line
For longer definitions, one can stretch the macro across multiple lines. To create a multi-line definition, each line except the last one should end with a \ character:
#define DRAWBUTTON(NAME)\
__EXEC(idcNav = idcNav + 4) \
// ...
#undef
Undefine (delete) a macro previously set by the use of #define.
#undef NAME
#if
Description: Checks condition and processes content if condition returns 1. Skips content if it returns 0
#if __A3_DEBUG__ // Check if game is started in debug mode
#include "debug.hpp" // Include some file if debug mode is enabled
#endif
#ifdef
A simple if-then construction to check whether a certain set of definitions has already been made:
#ifdef NAME
// config that will be used if NAME is defined
#endif
IFDEFs cannot be nested. The preprocessor will generate errors for all inner definitions if the outer definition doesn't exist.
#ifndef
Same as #ifdef, but checks for absence of definition instead.
#ifndef NAME
// config that will be used if NAME is -not- defined
#endif
#else
Provides alternative code to #if, #ifdef, #ifndef checks.
#ifndef NAME
// config that will be used if NAME is -not- defined
#else
// config that will be used if NAME -is- defined
#endif
#endif
This ends a conditional block as shown in the descriptions of #ifdef and #ifndef above.
#include
Copies the code from a target file and pastes it where #include directive is.
#include "file.hpp"
#include <file.txt> // Brackets are equivalent to quotation marks and may be used in their place.
Source directory is:
- For any file without starting the include path with \ - the file's current directory
- When starting with \ - the internal filesystem root (see Addon Development) or the Game's working directory (only with -filePatching enabled)
A path beginning can be defined as follow:
- drive (only with -filePatching enabled):
#include "D:\file.txt"
- PBO with PBOPREFIX:
#include "\myMod\myAddon\file.txt"
- PBO (keep in mind that in this case, if the PBO's file name will be changed, all '#include' referencing it will need to be updated):
#include"\myMod\myAddon\file.txt" // Arma 3\@myMod\addons\myAddon.pbo\file.txt;
To move to parent directory use '..' (two dots) (Supported since 1.50):
#include "..\file.sqf"
Preprocessor does not support the use of macros for pre-defined file names.
#define path "codestrip.txt"
#include path // this will cause an error
#
'#' (single hash) operator wraps the text with quotation marks.
#define STRINGIFY(s) #s
#define FOO 123
test1 = STRINGIFY(123); //test1 = "123";
test2 = STRINGIFY(FOO); //test2 = "123";
This operator does only work on keywords following the rules for macro-names (see #define-section). If one wants to stringify anything else (like e.g. a number), one has to use a stringify-macro that takes an argument and stringifies that (as in the example above).
#define MACRO Test #123
MACRO // preprocesses to Test 123 - note that there are not any quotes inserted
##
'##' (double hash) operator concatenates what's before the ## with what's after it.
#define GLUE(g1,g2) g1##g2
#define FOO 123
#define BAR 456
test1 = GLUE(123,456); //test1 = 123456;
test2 = GLUE(FOO,BAR); //test2 = 123456;
__LINE__
This keyword gets replaced with the line number in the file where it is found. For example, if __LINE__ is found on the 10th line of a file, the word __LINE__ will be replaced with the number 10.
__FILE__
This keyword gets replaced with the CURRENT file being processed.
__has_include
Description: Checks if given file exists. Returns 1 if it does, 0 if it does not exist.
#if __has_include("\z\ace\addons\main\script_component.hpp")
// file exists! Do something with it
#else
// file does not exist
#endif
__DATE_ARR__
Description: Is replaced for current date in format array.
__DATE_ARR__ // 2020,10,28,15,17,42
__DATE_STR__
Description: Is replaced for current date in format string.
__DATE_STR__ // "2020/10/28, 15:17:42"
__DATE_STR_ISO8601__
Description: Is replaced for current date in format string. The date is presented in ISO8601 standard.
__DATE_STR_ISO8601__ // "2020-10-28T14:17:42Z"
__TIME__
Description: Is replaced for current time.
__TIME__ // 15:17:42
__TIME_UTC__
Description: Is replaced for UTC time.
__TIME_UTC__ // 14:17:42
__COUNTER__
Description: Counts 1 up everytime it is defined.
__COUNTER__ // 0
__COUNTER__ // 1
__COUNTER__ // 2
__COUNTER__ // 3
__COUNTER_RESET__
__COUNTER__ // 0
__COUNTER__ // 1
__COUNTER__ // 2
__TIMESTAMP_UTC__
Description: Is replaced to the current timestamp in UNIX timestamp.
__TIMESTAMP_UTC__ // 1622639059
__COUNTER_RESET__
Description: Resets counter. See example above.
__RAND_INTN__
Description: Gets replaced by a random N bit integer. N can be 8, 16, 32 or 64, e.g:
__RAND_INT8__
__RAND_INT16__
__RAND_INT32__
__RAND_INT64__
__RAND_INT8__ // e.g -102
__RAND_UINTN__
Description: Gets replaced by a random unsigned N bit integer. N can be 8, 16, 32 or 64, e.g:
__RAND_UINT8__
__RAND_UINT16__
__RAND_UINT32__
__RAND_UINT64__
__RAND_UINT8__ // 108
__GAME_VER__
Description: Gets replaced by the game version.
__GAME_VER__ // 02.00.146790
__GAME_VER_MAJ__
Description: Gets replaced by the major game version.
__GAME_VER_MAJ__ // 02
__GAME_VER_MIN__
Description: Gets replaced by the minor game version.
__GAME_VER_MIN__ // 00
__GAME_BUILD__
Description: Gets replaced by the build number.
__GAME_BUILD__ // 146790
__A3_DIAG__
Description: Gets replaced by 1 on diag, undefined otherwise.
__GAME_BUILD__ // 1
__ARMA__
Description:
__ARMA__ // 1
__ARMA3__
Description:
__ARMA3__ // 1
__A3_DEBUG__
Description: This macro is only set when the -debug parameter was provided. It can be used to switch mods or scripts to debug mode dynamically.
__A3_DEBUG__ // 1 (If -debug was enabled)
Config Parser
__EXEC
This Config Parser macro allows to assign values to internal variables or just execute arbitrary code. The code inside __EXEC macros runs in parsingNamespace and variables defined in it will also be created in parsingNamespace. The variables can then be used to create more complex macros:
__EXEC(cat = 5 + 1;)
__EXEC(lev = cat - 2;)
__EXEC / __EVAL
Exec/Evals are used in paramfiles to create constant parameters from variables.
A paramfile is a config.cpp, a desc.ext, an rvmat etc.
Paramfile is an excellent name for these as all they can store is paramaters. Not functions and not arithmatic expressions. (#defines CAN pre-process arithmetic to produce a constant.)
Being paramfiles, the Bis compiler does not handle C statements like the following
var = 1; var2 = var1 + 77; // the compiler cannot access var
or
var = myCfunc(); // sqf funcs yes, C funcs no
The way around this limitation is to use exec/eval SQF statements to produce constants
The only difference between an __EXEC and an __EVAL is that __EXECs set internal variables, __EVALS set eternal paramaters. ANY sqf expressions that produce a constant can be used in either
__EXEC wil set or reset an internal variable to a value
In it's simplest form:
__EXEC(fred= 77);
the variable 'fred' is only known and shared by execs and evals
A complex eval would be:
__EXEC(fred=1; mary=2 * Cos 80); // note the semi required between statements
Neither __EXEC nor __EVAL can 'read' an existing parameter
__EVALs can set an outside var.
new_var=__EVAL(fred+99; fred=fred+1;);
- Note that #defines can be used in exec or eval
- note that these are pre-processor commands. they don't exist in the binarised equivalent.
- note that sqf is inherent in the compiler thus
angle= Rad+90; // requires no exec/eval
Errors
Error 2
- Problem
- Preprocessor failed error 2.
- How to fix
- Add quotation marks in the title, or the file path. (e.g. #include "soGood.sqf").
Error 6
- Problem
- Preprocessor failed on file X - error 6.
- Known reasons
- "The problem is using #ifdef #ifdef #endif #endif, a.k.a nested #ifdef. This doesn't work in Arma. It's only possible to use #ifdef and #endif once and not nested."
- #endif without preceding #ifdef or #ifndef
Error 7
- Problem
- Preprocessor failed on file X - error 7.
- Known reasons
- The preprocessor encountered an unknown directive. Read, as a typo most has likely found a way into the file (something like #inlcude or #defien). Double-check all preprocessor directives in that file.