PreProcessor Commands: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
(added additional information about #define)
(added additional information about #define; Added link to preprocessor-test-case-collection)
Line 1: Line 1:
[[Category:Scripting Topics]]
[[Category:Scripting Topics]]
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 ARMA 2.<br>
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 ARMA 2.<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>
{{Important|In ArmA 3, preprocessor commands are <b>case-sensitive!</b>}}
{{Important|In ArmA 3, preprocessor commands are <b>case-sensitive!</b>}}


Line 30: Line 31:


=== #define ===
=== #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:
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:


   #define true 1
   #define true 1


The above means that whenever ''true'' is used in a config, the parser will replace this with the value ''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 ====
==== Arguments ====
Line 44: Line 58:


   #define BLASTOFF(UNIT,RATE) UNIT setVelocity [0,0,RATE];
   #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 reomved 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 <tt>[el1,el2,...]</tt> as arguments into macros as well as any argument containing comas <tt>"some, sentence"</tt>, will need a small workaround:
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:
Line 216: Line 240:
== External links ==
== External links ==
* 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])
* 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])
* [https://github.com/Krzmbrzl/ArmaPreprocessorTestCases Collection of preprocessor test-cases]

Revision as of 10:24, 2 April 2019

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

In ArmA 3, preprocessor commands are case-sensitive!


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"/*,"bananna*/,"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-keywoprd (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 reomved 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) \
  ...

NOTE: 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 definiton instead.

#ifndef NAME
  ...text that will be used if NAME isn't defined...
#endif


#else

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

#include "D:\file.txt"
#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";

##

'##' (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 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 that I'm using #ifdef #ifdef #endif #endif. 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