PreProcessor Commands: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
No edit summary
Line 1: Line 1:
=Introduction =
[[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.
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.
 
There are a number of preprocessor commands recognized by the OFP and ArmA engine:
 
*//Comment
*/*Comment(s)*/
 
*__LINE__
*__FILE__
*__EXEC see [[Config_Parser|Config Parser article]]
*__EVAL see [[Config_Parser|Config Parser article]]
 
#include
#define
#undef
#ifdef
#ifndef
#else
#endif
 
 
==Note for experienced users==


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.
== Parsing ==
* '''[[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.


= Preprocessor commands =


==Comments==
== Macros ==
 
Though not technically a command, comments are handled by the preprocessor.


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


There are two types of comments: single line comments and multi line comments.
There are two types of comments: single line comments and multi line comments.


  //this is a single line comment
  {{codecomment|//this is a single line comment}}
  mycode = something; //only this part of the line is commented out
  mycode = something; {{codecomment|//only this part of the line is commented out}}
  /* this
  {{codecomment|/* this
  is a multi line
  is a multi line
  comment */
  comment */}}
 
A single line comment begins after two forward slashes //, and ends at the next line break.
 
A multi line comment spans across line breaks. It 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).
 
==#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).
=== #define ===
Using the ''#define'' instruction you can define a keyword and assign a definition to it. An example:


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.
  #define true 1


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 above means that whenever you use ''true'' in your config, the parser will replace this with the value ''1''.


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.
==== Arguments ====
You can add arguments to your more complex macros, by including them between brackets after the keyword:


Example:
  #define CAR(NAME) displayName = NAME;
#include "someAddon\somePathInTheAddon\someFile.someExtension"


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.
If you now use ''CAR("Mini")'', this will be replaced with ''displayName = "Mini";''.


Preprocessor commands do not have to be left-aligned, but can be indented, just like any other command.
==== 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).


:''NOTE'' - ArmA Tools Suite v1.14 Binarize.exe processing of #include is out of step with the capabilities of CfgConvert.exe.
  class NAME##_Button_Slider: RscText \
:: If you desire single common include files in your addon config.cpp files in ArmA, CfgConvert.exe will support & process #includes that are
  { \
      model = \OFP2\Structures\Various\##FOLDER##\##FOLDER; \


::: 1. Relative both above & below the current folder of the config.cpp being processed.
You can also use the single ''#'' to convert an argument to a string.
:::    ie. ''#include "..\inc\common.hpp"''
:::        ''#include "inc\common.hpp"''


::: 2. Absolute paths.
   statement = (this animate [#SEL, 0]); \
:::   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.
==== Multi-line ====
For longer definitions, you can stretch the macro across multiple lines. Each line, save the last one, ends with a space and ''\'' character:


==#define==
  #define DRAWBUTTON(NAME) \
      __EXEC(idcNav = idcNav + 4) \
  ...


'''Syntax:'''
#define NAME VALUE
The referenced name is case sensitive when used. The value parameter can be optional (eg: when used with #ifdef alone), but is generally 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
=== #undef ===
defineName = defineValue;
Undefine (delete) a macro previously set by the use of #define.
For example:
#define true  1
#define false 0
 
==Macro Expansion==
Macros can include tokens which are substituted when expanded as in the following example:
#define ICONS(icn) icon=\SomeAddon\icons\##icn;  \
                    model=\SomeAddon\##icn.p3d;  \
                    picture=\SomeAddon\##icn.paa
Then, using: 
ICONS(Peter);
ICONS(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.
 
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!
 
===Nested defines:===
 
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.
 
When including defines in 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'"
 
==#undef==
'''Syntax:'''
  #undef NAME
  #undef NAME
This will undefine (delete) a macro previously set by the use of #define.




==#ifdef==
=== #ifdef ===
'''Syntax:'''
You can use a simple if-then construction to for example check whether a certain set of definitions has already been made:
  #ifdef NAME
  #ifdef NAME
   ...text that will be used if NAME is defined...
   ...text that will be used if NAME is defined...
Line 141: Line 68:
IFDEFs ''cannot'' be nested, as the preprocessor will generate an error if the outer definition doesn't exist.
IFDEFs ''cannot'' be nested, as the preprocessor will generate an error if the outer definition doesn't exist.


==#ifndef==
 
'''Syntax:'''
=== #ifndef ===
Same as #ifdef, but checks for absence of definiton instead.
  #ifndef NAME
  #ifndef NAME
   ...text that will be used if NAME ''isn't'' defined...
   ...text that will be used if NAME ''isn't'' defined...
Line 148: Line 76:




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




==#endif==
=== #endif ===
This ends a conditional block as shown in the descriptions of #ifdef and #ifndef above.
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.
=== #include ===
Copies the code from a target file and pastes it where #include directive is.
#include "codestrip.hpp"
#include <codestrip.txt
 
Brackets are equal to quotation marks. 
 
Source directory is:
* for description.ext, addon configs – game root folder (where exe file is)
* for global config and resource – their source folder
 
Alternatively you may write a path starting from drive:
#include "d:\temp\codestrip.txt
 
To move to parent directory use '..' (two dots):
#include "..\codestrip.txt"
 
Addons location is saved to memory. To include file from one of them write:
#include "<addon folder name>\<file>
 
Preprocessor does not support computed includes (macro for file name).
#define path "codestrip.txt"
#include path
This code will cause an error. Macros will be explained later.


==__FILE__==


This keyword gets replaced with the CURRENT file being processed.
=== # ===
'#' (single hash) operator wraps the text with quotation marks.
text = #(myText)


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


*the command line paramater, OR
=== __EXEC ===
*the current include statement
This macro allows you to assign values to internal variables. These variables can be used to create complex macros with counters for example.


example:
  __EXEC(cat = cat + 1; lev = 0)


_file=__FILE__;
Note: this macro terminates at the first '')'' it encounters, so the following will not be possible:
_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. The string, is, the argument passed on the command line.
  __EXEC(string1 = "if ((_this select 0) == 22) then {true}")


When you evaluate ''string1'' it returns ''"if ((_this select 0"'' and can cause unexpected results.


 
=== __EVAL ===
===other===
With this macro you can evaluate expressions, including previously assigned internal variables.
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]


Example:
  w = __EVAL(but_w / 10);
<pre>//recurse.sqf
_i = _this select 0;
if (_i < 100) then {[_i + 1] execVM __FILE__};</pre>


= Related links =
Note: this macro also terminates at the first '')'' it encounters.


* [http://forums.bistudio.com/showthread.php?t=118397 OFP PreProcessor explained PDF]
=== __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.




[[category:Operation Flashpoint: Editing]]
== External links ==
[[Category:ArmA: Addon Configuration]]
* http://ofp-faguss.com/files/ofp_preprocessor.pdf
[[Category:Scripting Topics]]

Revision as of 10:39, 23 October 2011

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.

Parsing


Macros

Comments

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.

There are two types of comments: single line comments and multi line comments.

//this is a single line comment
mycode = something; //only this part of the line is commented out
/* this
is a multi line
comment */


#define

Using the #define instruction you can define a keyword and assign a definition to it. An example:

  #define true 1

The above means that whenever you use true in your config, the parser will replace this with the value 1.

Arguments

You can add arguments to your more complex macros, by including them between brackets after the keyword:

  #define CAR(NAME) displayName = NAME;

If you now use CAR("Mini"), this will be replaced with displayName = "Mini";.

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. Each line, save the last one, ends with a space and \ character:

  #define DRAWBUTTON(NAME) \
     __EXEC(idcNav = idcNav + 4) \
  ...


#undef

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

#undef NAME


#ifdef

You can use a simple if-then construction to for example 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, as the preprocessor will generate an error 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 "codestrip.hpp"
#include <codestrip.txt

Brackets are equal to quotation marks.

Source directory is:

  • for description.ext, addon configs – game root folder (where exe file is)
  • for global config and resource – their source folder

Alternatively you may write a path starting from drive:

#include "d:\temp\codestrip.txt

To move to parent directory use '..' (two dots):

#include "..\codestrip.txt"

Addons location is saved to memory. To include file from one of them write:

#include "<addon folder name>\<file>

Preprocessor does not support computed includes (macro for file name).

#define path "codestrip.txt"
#include path

This code will cause an error. Macros will be explained later.


#

'#' (single hash) operator wraps the text with quotation marks.

text = #(myText)


__EXEC

This macro allows you to assign values to internal variables. These variables can be used to create complex macros with counters for example.

  __EXEC(cat = cat + 1; lev = 0)

Note: this macro terminates at the first ) it encounters, so the following will not be possible:

  __EXEC(string1 = "if ((_this select 0) == 22) then {true}")

When you evaluate string1 it returns "if ((_this select 0" and can cause unexpected results.

__EVAL

With this macro you can evaluate expressions, including previously assigned internal variables.

  w = __EVAL(but_w / 10);

Note: this macro also terminates at the first ) it encounters.

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


External links