PreProcessor Commands: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
(→‎__FILE__: example added)
m (→‎Arguments: fixed typo)
(75 intermediate revisions by 21 users not shown)
Line 1: Line 1:
=Introduction =
{{SideTOC|0.9}}
The parser allows you to use macros in configs. Macros are a bit similar to functions in programming and allow you to use a single definition many times in the config, without having to duplicate the whole definition again and again. It also gives you a centralized place to correct errors in this definition. This page mainly refers to '''{{ofp}}''', some examples won't work for {{arma}} and {{arma2}}.<br>
If you really want to dig into the depths of the preprocessor you'll need to confront it with a bunch of edge cases and see how it behaves. If you are interested in this kind of thing, you might want to have a look at the collection of test-cases linked at the end of this page.<br>
{{Feature arma3 | In {{arma3}}, preprocessor commands are <b>case-sensitive!</b>}}


Preprocessor commands refer to the C(++) syntax used by the engine to 'pre process' the text inside a config.cpp, mission.sqm, or description.ext file. In fact, preprocessor commands apply to '''any''' BI game text file containing class statements, since these statements are 'scrunched' by the engine's internal C processor. The majority use however is in addons, and specifically, the config.cpp of the addon.


Preprocessor commands can also be used in script files, as long as the script is loaded using the preprocessfile command, or run via the execVM command.
== Parsing ==


There are a number of preprocessor commands recognized by the OFP and ArmA engine:
* '''[[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]] or [[execVM]] is used.


Comments


#include
== Macros ==
#define
#undef
#ifdef
#ifndef
#else
#endif


__LINE__
=== Comments ===
__FILE__


(The macros __EXEC & __EVAL are discussed in the [[Config_Parser|Config Parser article]].)
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.


==Note for experienced users==
Comments may span multiple lines, or only part of a line if needed.


The preprocessor mimics a subset of [http://en.wikipedia.org/wiki/C_preprocessor C preprocessor]. While basic functionality is similar, the preprocessor is not fully C-conformant, as order of substitution of arguments in macro invocation is different than in C, which means some intricate nested macro constructs, especially when using token concatenation operator, may produce different results than in C.
{{cc|this is a single-line comment}}
{{codecomment|/* this is a
multi-line
comment */}}
mycode = something; {{cc|only this part of the line is commented out}}
myArray = ["apple"{{codecomment|/*,"banana*/}},"pear"]; {{cc|// a portion in the middle of this line is commented out}}


= Preprocessor commands =
=== #define ===


==Comments==
Using the ''#define'' instruction, you can 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 doesn't start with a digit (RegEx: <tt>[a-zA-Z_][0-9a-zA-Z_]*</tt>). As an example:
<syntaxhighlight lang="cpp">
#define true 1
</syntaxhighlight>


Though not technically a command, comments are handled by the preprocessor.
The above means that whenever ''true'' is used in a config, the parser will replace this with the value ''1''.


A comment is a line in your code that is not actually processed by the game engine. They are used to make your code more readable. The preprocessor removes all comments from the file, before it is processed. Therefore, any comments written in your code, will never actually be "seen" by the engine. They are for humans only.
The define-statement does swallow all spaces in between the macro-keyword and any non-space-character in the body (Note that tabs aren't spaces! They don't get removed)
<syntaxhighlight lang="cpp">
#define MACRO                    test
MACRO // preprocesses to test (without any spaces)


There are two types of comments: single line comments and multi line comments.
#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)
</syntaxhighlight>


//this is a single line comment
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):
mycode = something; //only this part of the line is commented out
<syntaxhighlight lang="cpp">
/* this
#define MACRO#test
is a multi line
MACRO // preprocesses to "test"
comment */
</syntaxhighlight>


A single line comment begins after two forward slashes //, and ends at the next line break.
==== Arguments ====
You can add arguments 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.
<syntaxhighlight lang="cpp">
#define CAR(NAME) displayName = NAME;
</syntaxhighlight>


A multi line comment spans across line breaks. It starts with the characters /* and ends with the characters */
If you now use ''CAR("Mini")'', this will be replaced with ''displayName = "Mini";''. Multiple arguments can also be used:
<syntaxhighlight lang="cpp">
#define BLASTOFF(UNIT,RATE) UNIT setVelocity [0,0,RATE];
</syntaxhighlight>


Both comment types can start anywhere in a line (they don't have to be at the beginning of a line).
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 you can even pass in commas as 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).
<syntaxhighlight lang="cpp">
#define MACRO(arg) arg
MACRO("Some, content") // preprocesses to "Some content" (note the missing comma)
</syntaxhighlight>


==#include==
Quote escaping is also not supported in this context (neither with double- nor with single-quotes)
<syntaxhighlight lang="cpp">
#define MACRO(arg) arg
MACRO("Some ""content""") // preprocesses to "Some ""content"""
</syntaxhighlight>


'''Syntax:'''
Passing arrays with more than one element <tt>[el1,el2,...]</tt> as arguments into macros as well as any argument containing comas <tt>"some, sentence"</tt>, will need a small workaround:
#include <PathAndFileName>
<syntaxhighlight lang="cpp">
#include "PathAndFileName"
#define HINTARG(ARG) hint ("Passed argument: " + str ARG)
</syntaxhighlight>


The purpose of an include statement is to share common definitions among several files (e.g. constants or functions).
Incorrect usage:
<syntaxhighlight lang="cpp">
HINTARG([1,2,3,4,5,6,7,8,9,0]); // ERROR, won't even compile
</syntaxhighlight>


The number of 'chunks' (ie, the number of included files) is solely at the author's discretion. In general, they will put similar elements together. For example, all icons will be in one file, all buildings in another, all cars in another, and so on. It is particularly common to put base characteristics of similar 'models' into a single 'base' file, and then include this file into multiple variations of that base model. For example, a base file may define a soldier with khaki trousers. A second soldier class could include this standard base, and then define specific variations (which may change the trousers to pink, and so on). The base characteristics of the model do not need to be repeated in the main body of the text for the second soldier, as it's duplicating work and contains no new information.
Correct usage:
<syntaxhighlight lang="cpp">
#define array1 [1,2,3,4,5,6,7,8,9,0]
HINTARG(array1); // SUCCESS
</syntaxhighlight>


From the perspective of the game engine, the use of #include files is of <u>no consequence</u>, since the engine effectively processes all files as one file. All of the 'chunks' (the #included files) are merged into a single file for the purpose of compiling. Irrespective of how many #included files are used, the result is a single config.bin (config.bin is the resultant output of all this processing).
The argument replacement is performed before 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:
<syntaxhighlight lang="cpp">
#define ONE foo
#define TWO(ONE) ONE
TWO(bar) // will preprocess to bar
</syntaxhighlight>


Processing continues at the beginning of the included file until it's end, at which point, processing continues at the statement after the initial #include. Thus, anything required by that included file (if anything at all), must have 'appeared' before the #include statement. It is quite common for included files to have #include statement(s) of their own, under which circumstance, processing continues at the beginning of that included file, and so on.
==== Replacing parts of words ====
By default you can only replace whole words by arguments. If you need to replace only part of a word, you can 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 &nbsp; (space).
<syntaxhighlight lang="cpp">
class NAME##_Button_Slider: RscText \
{ \
model = \OFP2\Structures\Various\##FOLDER##\##FOLDER; \
</syntaxhighlight>


Example:
You can also use the single ''#'' to convert an argument to a string.
#include "someAddon\somePathInTheAddon\someFile.someExtension"
<syntaxhighlight lang="cpp">
statement = (this animate [#SEL, 0]); \
</syntaxhighlight>


Note that the #include syntax string does not use a preceding backslash, unlike other file references (such as icons or models, etc.) used in BI game files. Also note, that there is no default file extension, and no trailing semi-colon.
==== Multi-line ====
For longer definitions, you can stretch the macro across multiple lines. To create a multi-line definition, each line except the last one should end with a ''\'' character:
<syntaxhighlight lang="cpp">
#define DRAWBUTTON(NAME)\
__EXEC(idcNav = idcNav + 4) \
...
</syntaxhighlight>


Preprocessor commands do not have to be left-aligned, but can be indented, just like any other command.
{{Informative | The backslash must be the last character in a line when defining a multi-line macro. Any character (including spaces) after the backslash will cause issues.}}


:''NOTE'' - ArmA Tools Suite v1.14 Binarize.exe processing of #include is out of step with the capabilities of CfgConvert.exe.
=== #undef ===
:: If you desire single common include files in your addon config.cpp files in ArmA, CfgConvert.exe will support & process #includes that are


::: 1. Relative both above & below the current folder of the config.cpp being processed.
Undefine (delete) a macro previously set by the use of #define.
:::    ie. ''#include "..\inc\common.hpp"''
<syntaxhighlight lang="cpp">
:::        ''#include "inc\common.hpp"''
#undef NAME
</syntaxhighlight>


::: 2. Absolute paths.
=== #ifdef ===
:::    ie. ''#include "P:\Synide\common.hpp"
:::


::: Basically, all the various ways you would want to use #include works perfectly with CfgConvert.exe. The same cannot be said of Binarize.exe however. If you wish to make use of the virtues of true relative #include statements in ArmA config's I suggest prior to running BinPBO the user manually converts the config to it's rapified/binarized format. There is one issue to this approach and that is you should not have the text version of the config named config.cpp as binarize.exe will attempt to process it and if it has #include statements like ''#include "..\common.hpp"'' will fail. So, call your text version (for example) config.cfg and make use of #include's full gambit of functionality and use CfgConvert.exe to pre-convert it to a config.bin which when BinPBO is run binarize.exe will quite happily make use of. All, this can be automated in a simple dos batch file.
You can use a simple if-then construction to check whether a certain set of definitions has already been made:
<syntaxhighlight lang="cpp">
#ifdef NAME
...text that will be used if NAME is defined...
#endif
</syntaxhighlight>


==#define==
IFDEFs ''cannot'' be nested. The preprocessor will generate errors for all inner definitions if the outer definition doesn't exist.


'''Syntax:'''
=== #ifndef ===
#define NAME VALUE
The referenced name is case sensitive when used. Within define arguments, the token 'concatenation operator' ##, and 'stringize operator' # may be used.


Define statements may be referred to by the term 'macro'. It is a macro in the same way macros are used in Microsoft Word - one macro can expand into a (very large) sequence of keystrokes. You can effectively understand #defines as
Same as #ifdef, but checks for absence of definition instead.
defineName = defineValue;
<syntaxhighlight lang="cpp">
For example:
#ifndef NAME
#define true  1
...text that will be used if NAME ''isn't'' defined...
#define false 0
#endif
Macros can be expanded to the following example:
</syntaxhighlight>
#define ICONS(icn) icon=\SomeAddon\icons\##icn;  \
                    model=\SomeAddon\##icn.p3d;  \
                    picture=\SomeAddon\##icn.paa
Then, using: 
ICON(Peter);
ICON(Fred);
will be expanded during processing into:
icon=\SomeAddon\icons\Peter;
model=\SomeAddon\Peter.p3d;
picture=\SomeAddon\Peter.paa;
icon=\SomeAddon\icons\Fred;
model=\SomeAddon\Fred.p3d;
picture=\SomeAddon\Fred.paa;


The purpose of expanded macros as illustrated above is to allow simplified syntax, and to reduce typing load (and the likelihood of typos). In the case of the first example above, the #define macro creates more explicit, legible files, at the expense of increased typing.
=== #else ===
<syntaxhighlight lang="cpp">
#ifndef NAME
...text that will be used if NAME is -not- defined...
#else
...text that will be used if NAME -is- defined...
#endif
</syntaxhighlight>


Defines that span more than one line need the line-continuation symbol ("\") at the end of each line (see ICONS example above). '''Nothing''' is allowed to follow after this symbol - no comments, and not even spaces!
=== #endif ===


Nested defines:
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.
<syntaxhighlight lang="cpp">
#include "file.hpp"
#include <file.txt> // Brackets are equivalent to quotation marks and may be used in their place.
</syntaxhighlight>
 
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 [[CMA:DevelopmentSetup#Addon_development|Addon_development]]) or the Game's working directory (only with [[Arma_3_Startup_Parameters#Developer_Options -filePatching]] enabled)
 
 
You can define a path beginning with:
* drive (only with [[Arma_3_Startup_Parameters#Developer_Options -filePatching]] enabled):<!--
--><syntaxhighlight lang="cpp">#include "D:\file.txt"</syntaxhighlight><!--
-->
* PBO with [[PBOPREFIX]]:<!--
--><syntaxhighlight lang="cpp"> #include "\myMod\myAddon\file.txt"</syntaxhighlight>
* 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):<!--
--><syntaxhighlight lang="cpp">#include"\myMod\myAddon\file.txt" // Arma 3\@myMod\addons\myAddon.pbo\file.txt;</syntaxhighlight>
 
 
To move to parent directory use '..' (two dots) (Supported in Arma 3 since v1.49.131707):
<syntaxhighlight lang="cpp">
#include "..\file.sqf"
</syntaxhighlight>
 
Preprocessor does not support the use of macros for pre-defined file names.
<syntaxhighlight lang="cpp">
#define path "codestrip.txt"
#include path // this will cause an error
</syntaxhighlight>
 
=== # ===


Defines can be nested in each other, and they are all expanded. You can't seem to put a multi-line define inside of a multi-line define, nor can you pass a multi-line define in as a parameter of a macro.
'#' (single hash) operator wraps the text with quotation marks.
<syntaxhighlight lang="cpp">
#define STRINGIFY(s) #s;
#define FOO 123
test1 = STRINGIFY(123); //test1 = "123";
test2 = STRINGIFY(FOO); //test2 = "123";
</syntaxhighlight>


When including defines in defines, the # operator works after expanding all defines. For example:
This operator does only work on keywords following the rules for macro-names (see <tt>#define</tt>-section). If one wants to stringify anything else (like e.g. a number), one has to use a stringify-macro that takes an argment and stringifies that (as in the example above).
<syntaxhighlight lang="cpp">
#define MACRO Test #123
MACRO // preprocesses to Test 123 - note that there aren't any quotes inserted
</syntaxhighlight>


  1. define fn_myfunc compile preprocessfile 'myfile.sqf'
=== ## ===
  2. define addquotes(x) #x


addquotes(call fn_myfunc) result is: "call compile preprocessfile 'myfile.sqf'"
'##' (double hash) operator concatenates what's before the ## with what's after it.
<syntaxhighlight lang="cpp">
#define GLUE(g1,g2) g1##g2
#define FOO 123
#define BAR 456
test1 = GLUE(123,456); //test1 = 123456;
test2 = GLUE(FOO,BAR); //test2 = 123456;
</syntaxhighlight>


==#undef==
=== __EXEC ===
'''Syntax:'''
#undef NAME


This will undefine (delete) a macro previously set by the use of #define.
This '''config parser''' macro allows you to assign values to internal variables or just execute arbitrary code. The code inside <tt>__EXEC</tt> 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;)
_cat = [[parsingNamespace]] [[getVariable]] "cat"; {{cc|6}}
_lev = [[parsingNamespace]] [[getVariable]] "lev"; {{cc|4}}


==#ifdef==
{{Important | <tt>__EXEC</tt> macros are not suitable for <tt>SQF/SQS</tt> scripts but can be used in configs, including [[description.ext]]}}
'''Syntax:'''
{{Warning | <tt>__EXEC</tt> doesn't like round brackets <tt>()</tt> inside expressions. If you need to have grouping, perhaps you could calculate values inside the brackets separately and assign to local variables:
#ifdef NAME
<code>__EXEC(a {{=}} (1+2);) {{cc|ERROR}}</code>
  ...text that will be used if NAME is defined...
<div><code>__EXEC(_expr {{=}} 1+2;)
#endif
__EXEC(a {{=}} _expr;) {{cc|OK}}</code></div>
IFDEFs ''cannot'' be nested, as the preprocessor will generate an error if the outer definition doesn't exist.
}}


==#ifndef==
=== __EVAL ===
'''Syntax:'''
#ifndef NAME
  ...text that will be used if NAME ''isn't'' defined...
#endif


With this '''config parser''' macro you can evaluate expressions, including previously assigned internal variables. Unlike with <tt>__EXEC</tt>, <tt>__EVAL</tt> supports multiple parentheses


==#else==
w = __EVAL([[safeZoneW]] - (5 * ((1 / ([[getResolution]] [[select]] 2)) * 1.25 * 4)));
'''Syntax:'''
#ifndef NAME
  ...text that will be used if NAME ''isn't'' defined...
#else
  ...text that will be used if NAME ''is'' defined...
#endif


<tt>__EVAL</tt> macros MUST be assigned to a config property and the expression MUST be terminated with <tt>;</tt>. <tt>__EVAL</tt> can return only 2 types of data: [[Number]] and [[String]]. Any other type is represented as [[String]], even [[Boolean]] type, which will result in either <tt>"true"</tt> or <tt>"false"</tt>.
{{Important | <tt>__EVAL</tt> macros are not suitable for <tt>SQF/SQS</tt> scripts but can be used in configs, including [[description.ext]]. Both global and local variables set in <tt>__EXEC</tt> are available in <tt>__EVAL</tt>}}
{{Warning | <tt>__EVAL</tt> doesn't like curly brackets <tt>{}</tt>, if you need to have code in your expression use [[compile]] [[String]] instead:
<code>result <nowiki>=</nowiki> __EVAL([[call]] {123}); {{cc|ERROR}}</code>
<code>result <nowiki>=</nowiki> __EVAL([[call]] [[compile]] "123"); {{cc|OK}}</code>}}


==#endif==
This ends a conditional block as shown in the descriptions of #ifdef and #ifndef above.


==__LINE__==
=== __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.
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__==
=== __FILE__ ===
 
This keyword gets replaced with the CURRENT file being processed.
 
 
== Errors ==
 
=== Error 2 ===
 
;Problem: Preprocessor failed error 2.
 
;How to fix: Add quotation marks in the title, or the file path. (''e.g.'' <tt>#include "soGood.sqf"</tt>).
 
=== Error 6 ===
 
;Problem: Preprocessor failed on file X - error 6.
 
;Known reasons:
* "The problem is using <tt>#ifdef #ifdef #endif #endif</tt>, a.k.a nested <tt>#ifdef</tt>. This doesn't work in Arma. It's only possible to use <tt>#ifdef</tt> and <tt>#endif</tt> once and not nested."
* <tt>#endif</tt> without preceding <tt>#ifdef</tt> or <tt>#ifndef</tt>
 
=== Error 7 ===
 
;Problem: Preprocessor failed on file X - error 7.
 
;Known reasons: The preprocessor encountered an unknown directive. Read, you have probably a typo in the file (something like <tt>#inlcude</tt> or <tt>#defien</tt>). Double check all preprocessor directives in that file.


This keyword gets replaced with the path to the file where it is found.


For scripts packed into an addon, a relative path is displayed (relative to the program's exe file). For scripts in a mission folder, a direct path is displayed (drive letter and full path). [Note: untested what happens when this is used inside of a config]
== External links ==


Example:
* http://ofp-faguss.com/files/ofp_preprocessor_explained.pdf ([https://web.archive.org/web/20170319190732/http://ofp-faguss.com/files/ofp_preprocessor_explained.pdf Wayback Machine])
<pre>//recurse.sqf
* [https://github.com/Krzmbrzl/ArmaPreprocessorTestCases Collection of preprocessor test-cases]
_i = _this select 0;
if (_i < 100) then {[_i + 1] execVM __FILE__};</pre>




[[category:Operation Flashpoint: Editing]]
[[Category:Scripting_Topics]]
[[Category:ArmA: Addon Configuration]]

Revision as of 13:22, 21 March 2020

Template:SideTOC The parser allows you to use macros in configs. Macros are a bit similar to functions in programming and allow you to use a single definition many times in the config, without having to duplicate the whole definition again and again. It also gives you a centralized place to correct errors in this definition. This page mainly refers to Operation Flashpoint, some examples won't work for Arma and Arma 2.
If you really want to dig into the depths of the preprocessor you'll need to confront it with a bunch of edge cases and see how it behaves. If you are interested in this kind of thing, you might want to have a look at the collection of test-cases linked at the end of this page.
Template:Feature arma3


Parsing


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.

Comments may span multiple lines, or only part of a line if needed.

// this is a single-line comment

/* this is a
multi-line
comment */

mycode = 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, you can 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 doesn't 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 aren't 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

You can add arguments 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 you now use CAR("Mini"), this 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 you can even pass in commas as 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 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 you can only replace whole words by arguments. If you need to replace only part of a word, you can 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; \

You can also use the single # to convert an argument to a string.

statement = (this animate [#SEL, 0]); \

Multi-line

For longer definitions, you 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) \
	...
The backslash must be the last character in a line when defining a multi-line macro. Any character (including spaces) after the backslash will cause issues.

#undef

Undefine (delete) a macro previously set by the use of #define.

#undef NAME

#ifdef

You can use a simple if-then construction to check whether a certain set of definitions has already been made:

#ifdef NAME
	...text 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
	...text that will be used if NAME ''isn't'' defined...
#endif

#else

#ifndef NAME
	...text that will be used if NAME is -not- defined...
#else
	...text 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:


You can define a path beginning with:

  • drive (only with Arma_3_Startup_Parameters#Developer_Options -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 in Arma 3 since v1.49.131707):

#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 argment and stringifies that (as in the example above).

#define MACRO Test #123
MACRO // preprocesses to Test 123 - note that there aren't 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;

__EXEC

This config parser macro allows you 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;)
_cat = parsingNamespace getVariable "cat"; // 6
_lev = parsingNamespace getVariable "lev"; // 4
__EXEC macros are not suitable for SQF/SQS scripts but can be used in configs, including description.ext
__EXEC doesn't like round brackets () inside expressions. If you need to have grouping, perhaps you could calculate values inside the brackets separately and assign to local variables:

__EXEC(a = (1+2);) // ERROR

__EXEC(_expr = 1+2;) __EXEC(a = _expr;) // OK

__EVAL

With this config parser macro you can evaluate expressions, including previously assigned internal variables. Unlike with __EXEC, __EVAL supports multiple parentheses

w = __EVAL(safeZoneW - (5 * ((1 / (getResolution select 2)) * 1.25 * 4)));

__EVAL macros MUST be assigned to a config property and the expression MUST be terminated with ;. __EVAL can return only 2 types of data: Number and String. Any other type is represented as String, even Boolean type, which will result in either "true" or "false".

__EVAL macros are not suitable for SQF/SQS scripts but can be used in configs, including description.ext. Both global and local variables set in __EXEC are available in __EVAL
__EVAL doesn't like curly brackets {}, if you need to have code in your expression use compile String instead:

result = __EVAL(call {123}); // ERROR

result = __EVAL(call compile "123"); // OK


__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.


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, you have probably a typo in the file (something like #inlcude or #defien). Double check all preprocessor directives in that file.


External links