PreProcessor Commands

From BISim Wiki
Jump to: navigation, search

Contents

Introduction

Before a file is processed by VBS, the engine looks for certain codes (the preprocessor and config parser commands), and executes them before anything else.
This capability is mainly used include other files, and to define global macros.

Commands start either with a pound symbol (#) or with a double-underscore (__), and have NO semicolon at the end of the line.
They can be indented, just like regular script commands.

#define SIZE 0
#ifdef DEBUG
  #define SIZE 5
  #ifdef DEBUG1
    #define SIZE 10
  #endif
#endif

When, and how often, these commands are executed depends on their application:


Preprocessor Commands

All of the commands listed below are case-sensitive.

#define

Creates a named macro, which can be empty, or contain an assigned value.
Once the file is loaded, all occurrences beneath the define that match the name, will be replaced with its assigned value.
The defined name is case sensitive, alphanumeric, and must start with a letter or underscore.
The value contained by the define is of no specific type. See data types for details. Syntax:

#define NAME           // macro 'NAME' is defined, but empty
#define NAME1 value     // macro 'NAME1' is defined, and contains 'value'
#define Name1 value1    // macro 'Name1' (different from 'NAME1') is defined, and contains 'value1'

All occasions where the macro is used are replaced by its assigned value:
e.g. in a script:

#define GREETING "Hello"
hint GREETING;                         // outputs "Hello"
hint format["%1, Stranger",GREETING];  // outputs "Hello, Stranger"

or

#define MARGIN 100
if ((player distance enemy) < MARGIN) then {
  hint format["Enemy closer than %m",MARGIN]
};

Or in a config file:

#define __CURRENTDIR__ \myAddon\myTank
...
model = __CURRENTDIR__\data\Cartridge\cartridge;

The replacement only works forward (i.e. for anything that comes after the define):

#define WORD "one"  // initial definition of 'word'
systemChat WORD;    // outputs "one"
#define WORD "two"  // new definition of 'word'
systemChat WORD;    // outputs "two"

The scope of a define is limited to the file it is created in, so in order to share a define among several files it is recommended to place them into a separate definition file, and include this wherever the defines are required.

Be aware that a define will replace every occurrence of the defined name in the whole file, including any script commands, config properties, variable names or constants.

systemChat "hello1";     // outputs chat message "hello1"
#define systemChat hint  // OVERWRITES the chat command with the hint command
systemChat "hello2";     // outputs "hello2" via a hint message (as 'systemChat' is now 'hint')
...
#define tank tank33           // defines 'tank' as 'tank33'
systemChat name driver tank;  // returns the driver name for 'tank33'

Defines in strings

Defines within strings are only processed if single quotes are used.
Strings delimited by double quotes are not modified:

#define MY_NUM 2
player sidechat 'The number is MY_NUM'; // shows "The number is 2"
// with double quotes the define will be output verbatim:
player sidechat "The number is MY_NUM"; // shows "The number is MY_NUM"

Alternatively, the define can be used with a format command:

player sidechat format["The number you are thinking of is %1",MY_NUM];

Nested defines

Defines can be nested, and all levels will be expanded.
Multi-line defines though, cannot be used inside of another define, or passed as a parameter of a macro.

Define delimiters

The preprocessor finds the location of the defines in a file, by looking for certain delimiters (e.g. spaces, operators, punctuation marks, quotes, etc.).

#define two 2
hint 'onetwothree';   // no delimiters at all around 'two', so the output is "onetwothree"
hint 'one two three'; // 'two' has spaces on both sides, so it's recognized, and the output will be "one 2 three"
hint 'one two three'; // all of the following delimiters are recognized, and 'two' will be replaced by '2'
hint 'one-two+three'; 
hint 'one\two/three'; 
hint 'one:two;three'; 

Underscores are not recognized as a delimiter, so the following would not be modified:

hint 'one_two_three'; // outputs "one_two_three"

In strings, this limitation can easily be circumvented, by breaking up the string:

hint ('one'+'two'+'three');   // outputs "one2three"

In other cases though, where it is not possible to break up the surrounding parts, explicit delimiters can be used. They consist of double pound signs (##), and should be placed at the locations needed:

#define IDX 2
class Button_IDX {...   // 'idx' is not recognized as a define (underscore not a delimiter), and wouldn't be replaced
...
class Button_##IDX {... // with ## placed at the start of the define, 'idx' will now be replaced with a '2' 
                           (no ## is needed after the 'idx', since there's already a space delimiter there)
...
class Button_##IDX##_top {...  // this situation would require two explicit delimiters (no space at end)

This method can also be used in nested defines:

#define TWO 2
#define twenty ##TWO##0
hint str twenty; // outputs "20"


Data types

The data contained in a define is plain text (NOT a string!), and does not have any defined type.
While the examples below seem to be of an obvious type, that interpretation actually only happens once it is substituted and used in a specific environment (as in this case, the format and typeName commands):

#define A "aaa"
#define B 100
#define C [1]
#define D true
#define E {!alive _x}count (units group player)==0

systemChat format["%1, %2, %3, %4",typeName A,typeName B,typeName C,typeName D,typeName #];
// returns "STRING, SCALAR, ARRAY, BOOL, CODE"

But a define can also hold values that are not of any obvious data type:

#define two deux
#define COMPARISON (a>b)
#define OPERATOR *

These types of values only make sense once they have been applied to a specific command of config:

#define two deux
hint 'one two three'; // would output: one deux three
// This is different from <tt>#define two "deux"</tt>, 
// as that would include the quotation marks in the replacement:
#define two "deux"
hint 'one two three'; // would output: one "deux" three

// defines can contain script commands, operators or a combination of them:
#define OPERATOR *
#define COMPARISON ((3 OPERATOR 2)>5)
systemChat (if COMPARISON then {"bigger"} else {"smaller"});  // outputs "bigger" (as (3*2)>5 is true)

#define OPERATOR -
systemChat (if COMPARISON then {"bigger"} else {"smaller"});  // outputs "smaller" (as (3-2)>5 is false)

Code

If the defined value contains script commands, then the differences between how code can be used in a script file vs. a config has to be considered.

In Scripts

A define can contain any kinds of commands whose result can be used directly in other script constructs.
Both of the methods shown below will output "2 is BIGGER THAN 1"

#define BIGGER1 (2>1)
#define BIGGER2 (format["2 is %1 than 1",if (2>1) then {"BIGGER"} else {"SMALLER"}])

systemChat format["2 is %1 than 1",if BIGGER1 then {"BIGGER"} else {"SMALLER"}];
systemChat BIGGER2;
In Configs

While in a script, commands can be executed right in the define statement, in config files this has to be done via __EVAL or __EXEC.
Also, be aware that mission objects, global variables or status returns may not be defined yet, when the preprocessor evaluates these defines.

#define BIGGER1 (2>1)
#define BIGGER2 __EVAL(format["2 is %1 than 1",if (2>1) then {"BIGGER1"} else {"SMALLER1"}])
text1 = __EVAL(format["2 is %1 than 1",if BIGGER1 then {"BIGGER"} else {"SMALLER"}]); 
text2 = BIGGER2;


#undef

Undefines (deletes) a macro previously created by #define.
If the define is subsequently checked with #ifdef, the condition will not be fulfilled. (The #ifndef condition will be fulfilled.)
Syntax:

#undef NAME


#ifdef

Executes enclosed code only if the define exists.
The define does not have to have a value assigned, in order to fulfill this test. ifdefs can be nested. Syntax:

#ifdef NAME
  conditional segment
#endif

e.g.

#define DEBUG
_dist = player distance enemy;
#ifdef DEBUG
  hint str _dist;
#endif
if (_dist<100) then {...


#ifndef

Executes enclosed code only if the define does NOT exist (either never defined in the first place, or deleted via #undef.
Syntax:

#ifndef NAME
  conditional segment
#endif


#else

Executes an alternative segment, in case the #ifdef condition failed.
Syntax:

#ifdef NAME
  conditional segment (used if NAME IS defined)
#else
  conditional segment (used if NAME IS NOT defined)
#endif


#endif

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


#include

Reads the content of the referenced file, and inserts it into the position of the #include statement.
Syntax:

#include "PathAndFileName"  // either double quotes or 
#include <PathAndFileName>  // greater/smaller brackets can be used to delimit the path definition

The purpose of an include statement is to share common definitions among several files (e.g. constants or functions).
e.g. If there is one file that contains all the IDD & IDC definitions for a dialog, then it is easier (and less error-prone) to change only that definition file in case something needs to be re-arranged, rather than every occurrence in every dialog control: dialog_defines.hpp contains:

#define DLG_IDD 20000
#define TEXT1_IDC 20001
...
#define TRUE 1
#define FALSE 0

A dialog config (e.g. in description.ext) would then include this file, and use those #defines:

 #include "dialog_defines.hpp"
 class MyDialog {
   idd = DLG_IDD;       // DLG_IDD defined in include
   movingEnable = TRUE; // TRUE defined in include
   class Controls {
     idc = TEXT1_IDC;   // TEXT1_IDC defined in include
     shadow = TRUE;     // TRUE defined in include
     ...

The included file is first merged into the location of the #include statement, after which the combined file is being processed.
Multiple includes can be used, and they can be nested (i.e. an included files can include other files).
Included files (just like regular scripts) are interpreted sequentially, i.e. any macro that is used in the included file must be defined before the include statement, e.g.

#define DEBUG_OUTPUT
#include "functions.hpp" // functions.hpp can use the define DEBUG_OUTPUT

The file path is relative to the file the #include statement is executed from (i.e. the mission folder for Description.ext, and the addon folder for addons).
If the path starts with a backslash, then the file is read from either the standard VBS or custom addons. No absolute paths can be used. The extension is not relevant.

// If called from mission's init.sqf:
// uses the file incl.hpp from the missions's sub-folder \data
#include "data\incl.hpp" 

// If called from *anywhere*:
// reads the file from the \vbs2\headers folder, which is part of the default VBS addons 
// (it contains a list of DIK codes, e.g. #define DIK_TAB 0x0F)
#include "\vbs2\headers\dikCodes.hpp"  
systemChat str DIK_TAB                 // outputs 15 (0x0F is hex for 15)

It is not possible to use a #define in an #include statement:

#define path "incl.hpp"
#include path  // ILLEGAL!

__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 path to the currently processed file.

config

For a config.cpp (when converted to a bin) the created string depends on the context of when it's encountered. Either:

Example:

			
_file=__FILE__;
_file="P:\CWR2\Cars\cwr2_brdm\config.cpp"; // from command line
_file="this\included\folder\anything.hpp"; // via an include

Note that you cannot rely on a fully qualified filename via the command line.

other

For scripts packed into an addon, a relative path is created (relative to the program's exe file). For scripts in a mission folder, a direct path is created (drive letter and full path).

Example:

//recurse.sqf
_i = _this select 0;
if (_i < 100) then {[_i + 1] execVM __FILE__};


Config Parser Commands

While the functionality and syntax of config parser commands is similar to that of preprocessor ones (and are normally used in connection with each other), the following two config parser commands can only be used in config files, but not in scripts!

A config file can contain multiple occurrences of these commands, and they will be interpreted sequentially. They are executed before the preprocessor commands are interpreted.

Use of Variables

The variables used in config parser commands exist in their own namespace (the parsingNamespace), and therefore do not overlap the mission namespace.
This means that any global variables defined in mission scripts are not available within the parser, and that the parser can use the same variable names as some script, without affecting those script variables.

A variable used in this parsingNamespace can be evaluated from a script via the getVariable command: parsingNamespace getVariable "someVar".
Due to the fact that config files are interpreted when an addon or mission is first loaded (i.e. before any scripts are executed), it is not possible to modify these variables via the setVariable command.

__EXEC

Executes a script command (or several, if separated by semicolons). Typically used to assign variables in config files, which are then utilized in other calculations, or interpreted via the __EVAL command.
Syntax:

__EXEC(expression)
 __EXEC (_y = .345);    // assign an initial value to variable _y

 class Ctrl1 {
   y = __EVAL(_y)       // use this variable in a config property (via __EVAL)
   ...
 };
 __EXEC (_y = _y + .1)  // increment _y

 class Ctrl2 {
   y = __EVAL(_y)       // and use it again
   ...
 };

__EXEC terminates at the first closed parenthesis ")" it encounters, so expressions like the following will cause an error:

__EXEC (_val = (_arr select 0)*10);           // ILLEGAL!
__EXEC (_str = (_this select 0) setDamage 1); // ILLEGAL!

__EVAL

Executes one script command, a variable defined by __EXEC, or a combination of those, and returns the result.
Typically, this result is used in a config property. Syntax:

__EVAL(expression)
 __EXEC (_y = .1; _str1 = 'hello '; _str2 = 'world')
 y = __EVAL (_y);
 text = __EVAL (_str1 + _str2);

Unlike the __EXEC command, __EVAL can contain other parentheses, making more complex, and even conditional operations possible:

x = __EVAL (if (_idx>5) then {0} else {.5});

__EVAL will not allow semi-colons to be used in expressions, so the following will generate an error:

condition = __EVAL (_result=time>5; _result);

If you need to make use of a #defined value in your __EXEC or __EVAL string and you need to convert it to string using 'str', remember not to add extra brackets like this:

onSliderPosChanged = __EVAL("[" + str (MY_NUMERIC_DEFINE) + "] call compile preProcessFile 'my.sqf'");

The above will fail when parsing the __EVAL, the correct line would be:

onSliderPosChanged = __EVAL("[" + str MY_NUMERIC_DEFINE + "] call compile preProcessFile 'my.sqf'");

Be aware that, when using these evaluations in a mission's description.ext, that at that point the mission information is not available yet (i.e. the mission objects have not been created yet).


Comments

Two types of comments can be used in connection with preprocessor commands:

Single-line comment

Comment starts with two forward slashes (//), and ends at the next line break.
Syntax:

// line consists only of a comment
#define TRUE 1 // only this part of the line is commented out


Be aware that the preprocessor will interpret the double-slashes even in strings, making constructs like the following impossible:

#define URL "http://www.vbs2.com"  // <== THIS WILL NOT WORK

To circumvent this issue, either define the slashes separately:

#define SLASH "/"
#define URL __EVAL("http:" + SLASH + SLASH + "www.vbs2.com")

or exclude the protocol part from the define (the "http://"), and merge it later, as a regular string:

#define URL "www.vbs2.com"
openURL ("http://" + URL)

Multi-line comments

Comment starts with the characters /* and ends with the characters */
Syntax:

/* This
is a multi-line
comment */

Both comment types can start anywhere in a line (they don't have to be at the beginning of a line).
Special rules apply to comments in multi-line macros. See the section below for details.


Multi-line macros

Macros that are longer than one line need the line-continuation symbol ("\") at the end of each line.
The continuation symbol must be the last symbol in the line; nothing is allowed to follow after it — no comments, and not even spaces!

#define ICONS(icn) icon=\SomeAddon\icons\##icn;  \ // this comment would cause an error during preprocessing!
                   model=\SomeAddon\##icn.p3d;   

To insert comments they have to take up their own line, and use the multi-line comment syntax, i.e. the following would work (it still needs the continuation symbol at then end):

#define ICONS(icn) icon=\SomeAddon\icons\##icn;  \
                   /* this comment is legal */ \   
                   model=\SomeAddon\##icn.p3d;   


Macro Expansion

Defines can include tokens, which are substituted when expanded, as in the following example:

#define ADD(x,y) (x + y)                                // the define expects to be called with two tokens
hint str ADD(5,3);                                      // outputs "8"
#define SHOW(x,y) player sidechat format["%2, %1",x,y]  // the define expects two tokens, that are used in a command
SHOW(1,2);                                              // outputs "2, 1"
SHOW("World","Hello");                                  // outputs "Hello, World"
#define ISALIVE(_x_) alive _x_                          // the one passed token is used the 'alive' command
#define PRINT(_y_) player sideChat _y_                  // another define uses a token with 'sideChat'
if (ISALIVE(soldier1)) then {PRINT("still alive")};     // if 'soldier1' is alive, outputs "still alive"

The purpose of expanded macros is to make a config file easier to read, faster to enter, and to reduce the chance of typos.
In the example at the bottom of this page we define eight buttons and eight custom sounds with the help of two macros:


File formatting

Files should be encoded in ANSI.

Errors

If a file is failing to be preprocessed and is giving an error, but the contents of the file works elsewhere, or it seems like the file is not being read, then the file could be encoded incorrectly. To fix the encoding selecting the whole file content, then activate "Encoding | Encode in ANSI" (in Notepad++ — naming might be slightly different in other editors).

If the preprocessor runs in to an error, and shows an error number, use the table below to pinpoint the cause:

#ifdef something 
#include 999
#define func(100)
#define func(arg
#define something
#endif 
#whatsthis
#ifdef 
#ifdef 100


Example

Two macros are used to create 8 dialog buttons and 8 custom sounds.
When clicking any of these buttons, the associated custom sound is played.

Preprocessor.jpg

The full example, integrated in a mission, contains more extensive comments, and can be downloaded from Preprocessor.zip.

Definition of dialog controls

The comment syntax used here ("//" after "\") CANNOT be used in a real config file! We just use this arrangement for ease of overview in the wiki article.

#include "description_dialogs.hpp"         // general dialog classes are defined in this include

__EXEC(startRow = .2; startCol = .05)      // 2 variables used in the macro to calculate the control position

// four tokens are passed to the macro, which are used to define the position, label, tooltip and action.
#define MAKE_BUTTON(row,col,gender,word) \
  class TEXT##row##col : RscButton { \     // the 'row' and 'col' tokens are used to create unique class names
  idc = __EVAL(20000+row##col); \          // 'row' and 'col' values are used to generate unique IDC numbers,
  x = __EVAL(startCol+col*.16); \          // as well as x and y positions for the controls
  y = __EVAL(startRow+row*.1); \
  w = .15; \
  h = .05; \
  text = gender: word; \                   // 'word' and 'gender' will be replaced in the actual button caption
  tooltip = Click to play 'word' sound, spoken by a gender.; \           // same for the tooltip and the action
  action = playSound 'Sound_##gender##_##word##'; \          // We need delimiters here, due to the underscores
};

// create a dialog with 8 buttons, using the macro above
class DlgInput {
  idd = 20000;
  class controls {
    MAKE_BUTTON(1,1,Man,one);     // macro is called with unique arguments for each control that's created
    MAKE_BUTTON(1,2,Man,two);
    MAKE_BUTTON(1,3,Man,three);
    MAKE_BUTTON(1,4,Man,four);
    MAKE_BUTTON(2,1,Woman,one);
    MAKE_BUTTON(2,2,Woman,two);
    MAKE_BUTTON(2,3,Woman,three);
    MAKE_BUTTON(2,4,Woman,four);

    /* Once everything has been substituted, the first button define will look (internally) like this:
    class TEXT11 : RscButton {
      idc = 20011;
      x = 0.21;
      y = 0.3;
      w = 0.15;
      h = 0.05;
      text = "Man: one";
      tooltip = "Click to play 'one' sound, spoken by a Man.";
      action = "playSound 'Sound_Man_one'";
    };
    */
  };
};

Definition of custom sounds

//The passed tokens are used to define the sounds' class and sound name, as well as its path and subtitle:
#define MAKE_SOUND(type,word) \
  class Sound_##type##_##word {  \
    name = "";  \
    sound[] = {\sounds\type\word.ogg, 5, 1}; \
    titles[] = {0, type says 'word'}; \
  };

// CfgSounds is expanded with  8 custom sounds, defined via the macro above
class CfgSounds {
  MAKE_SOUND(Man,one);
  MAKE_SOUND(Man,two);
  MAKE_SOUND(Man,three);
  MAKE_SOUND(Man,four);
  MAKE_SOUND(Woman,one);
  MAKE_SOUND(Woman,two);
  MAKE_SOUND(Woman,three);
  MAKE_SOUND(Woman,four);
};
Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox