PreProcessor Commands
Contents |
Introduction
Before a file that's used by VBS2 is actually processed, the engine looks for certain preprocessor 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.
All preprocessor commands are case-sensitive.
Commands
#include
Syntax:
#include <PathAndFileName> #include "PathAndFileName"
The purpose of an include statement is to share common definitions among several files (e.g. constants or functions).
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 uses the define DEBUG_OUTPUT
#define
Syntax:
#define NAME #define NAME value
Creates a macro with either an empty, or an assigned, value.
In all occasions where this macro is then used, it will be replaced by its assigned value.
e.g.
#define GREETING "HELLO" hint GREETING;
The defined name is case sensitive.
The value parameter is optional (e.g. when used with #ifdef). Within define arguments, the token 'concatenation operator' ##, and 'stringize operator' # may be used.
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.
Once a define is present, it can be used in configs or scripts as seen in the examples below: In description.ext:
#include "dialog_defines.hpp" // contains, for example, DLG_IDD define
class MyDialog {
idd = DLG_IDD
In a script:
#define MARGIN 100
if ((player distance enemy) < MARGIN) then {
hint format["Enemy closer than %m",MARGIN]
};
Nested defines:
Defines can be nested, and they are all expanded.
Multi-line defines though, cannot be used inside of another define, or passed as a parameter of a macro.
Within nested defines the # operator works after expanding all defines. For example:
1. define fn_myfunc compile preprocessfile 'myfile.sqf' 2. define addquotes(x) #x addquotes(call fn_myfunc) result is: "call compile preprocessfile 'myfile.sqf'"
Macro Expansion
Defines can include tokens which are substituted when expanded as in the following example:
#define ICONS(icn) icon=\SomeAddon\icons\##icn; \
model=\SomeAddon\##icn.p3d;
Then, using:
ICONS(Peter); ICONS(Fred);
will be expanded during processing into:
icon=\SomeAddon\icons\Peter; model=\SomeAddon\Peter.p3d; icon=\SomeAddon\icons\Fred; model=\SomeAddon\Fred.p3d;
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.
#undef
Syntax:
#undef NAME
This will undefine (delete) a macro previously set by #define.
#ifdef
Syntax:
#ifdef NAME conditional segment #endif
Executes enclosed code only if the define exists.
e.g.
#define DEBUG
_dist = player distance enemy;
#ifdef DEBUG
hint str _dist;
#endif
if (_dist<100) then {...
ifdefs cannot be nested, as the preprocessor will generate an error if the outer definition doesn't exist.
#ifndef
Syntax:
#ifndef NAME conditional segment #endif
Executes enclosed code only if the define does NOT exist.
#else
Syntax:
#ifdef NAME conditional segment (used if NAME IS defined) #else conditional segment (used if NAME IS NOT defined) #endif
Executes alternative segment, in case the first condition failed.
#endif
This ends a conditional block as shown in the descriptions of #ifdef and #ifndef above.
__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:
- the command line parameter, OR
- the current include statement
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). [Note: untested what happens when this is used inside of a config]
Example:
//recurse.sqf
_i = _this select 0;
if (_i < 100) then {[_i + 1] execVM __FILE__};
__EXEC
(While technically a "config parser" command, it is nevertheless listed here, for ease of reference.)
Syntax:
__EXEC(expression)
Executes a script command. Typically used to assign variables, which are then used in other calculations, or output via the __EVAL macro.
__EXEC (_x = .345); __EXEC (_y = _y + 1); __EXEC (_str = format["name: %1",_name])</code>
__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
(While technically a "config parser" command, it is nevertheless listed here, for ease of reference.)
Syntax:
__EVAL(expression)
With this macro expressions can be evaluated, including previously assigned internal variables.
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});
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 though, that if these evaluations are used 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
Syntax:
// line consists only of a comment #define TRUE 1 // only this part of the line is commented out
Comment starts with two forward slashes, and ends at the next line break.
Multi-line comments
Syntax:
/* This is a multi-line comment */
Comment starts with the characters /* and ends with the characters */
Both comment types can start anywhere in a line (they don't have to be at the beginning of a line).
Line continuations
PreProcessor commands that are longer than one line need the line-continuation symbol ("\") at the end of each line (see the ICONS macro example above). Nothing is allowed to follow after this symbol - no comments, and not even spaces!
File formatting
Files should be encoded in ANSI.
Preprocessing errors
If a file is failing to be preprocessed and is giving an error but the contents of the file work elsewhere or it seems like the file is not being read then the file could be encoded incorrectly. To fix the encoding try selecting all and cutting the contents of the file, then select encode in ANSI, then paste the contents back in.