Difference between revisions of "PreprocessorRef"
(→#define: Convert to named link) |
|||
(40 intermediate revisions by 13 users not shown) | |||
Line 2: | Line 2: | ||
== Overview == | == Overview == | ||
− | Wesnoth loads just one configuration file directly: '''data/_main.cfg'''. However the '''WML preprocessor''' allows | + | Wesnoth loads just one configuration file directly: '''data/_main.cfg'''. However, the '''WML preprocessor''' allows the inclusion of more files. Whenever a WML file is read by Wesnoth, it is passed through the preprocessor. |
The preprocessor can interpret a simple language of string expansions known as ''macros''. A macro should always be defined '''before''' the place where it needs to be used. | The preprocessor can interpret a simple language of string expansions known as ''macros''. A macro should always be defined '''before''' the place where it needs to be used. | ||
Line 10: | Line 10: | ||
== Preprocessor directives == | == Preprocessor directives == | ||
− | The following directives are used to create and use ''macros'', i.e. shortcuts which reduce repetition of information. See [ | + | The following directives are used to create and use ''macros'', i.e. shortcuts which reduce repetition of information. See [https://www.wesnoth.org/macro-reference.html the macro reference] for the list of predefined core macros. |
+ | |||
+ | Macros have scoping rules such that addons have separate preprocessing contexts, meaning that they can be overridden however an author of UMC wishes to override them, without worrying about breaking other add-ons. | ||
The preprocessor has changed several times, so don't expect old Wesnoth versions to behave exactly the same as the current stable and development series. | The preprocessor has changed several times, so don't expect old Wesnoth versions to behave exactly the same as the current stable and development series. | ||
Line 22: | Line 24: | ||
All subsequent occurences of '''{''symbol'' [''arguments'']}''' (see below) will be replaced by the contents of the ''substitution'' block, with all occurrences of any parameter {''parameter''} within ''substitution'' replaced by the corresponding value in ''arguments''. For example, the ENEMY_UNIT macro for the [[#Macro inclusions|macro inclusion]] example below could be defined as follows: | All subsequent occurences of '''{''symbol'' [''arguments'']}''' (see below) will be replaced by the contents of the ''substitution'' block, with all occurrences of any parameter {''parameter''} within ''substitution'' replaced by the corresponding value in ''arguments''. For example, the ENEMY_UNIT macro for the [[#Macro inclusions|macro inclusion]] example below could be defined as follows: | ||
− | + | <syntaxhighlight lang="wml"> | |
− | + | #define ENEMY_UNIT TYPE X Y | |
− | + | ## the ordering above is important, since the preprocessor does not distinguish | |
− | + | ## data into different types; only the ordering is used to determine which | |
− | + | ## arguments apply to which parameters. | |
− | + | [unit] | |
− | + | type={TYPE} ## the unit will be of type TYPE, so different | |
− | + | ## instantiations | |
− | + | ## of this macro can create different units. | |
− | + | x={X} | |
− | + | y={Y} | |
− | + | side=2 ## the unit will be an enemy, regardless of the parameter | |
− | + | ## values. This reduces "repetition of information", | |
− | + | ## since it is no longer necessary to specify | |
− | + | ## each created unit as an enemy. | |
− | + | [/unit] | |
+ | #enddef | ||
+ | </syntaxhighlight> | ||
(See [[SingleUnitWML]] for further information on creating units using WML.) | (See [[SingleUnitWML]] for further information on creating units using WML.) | ||
− | '''Important note:''' Although macros may look like they're simplifying the code, they do not help with wml bloating. Macros are very good at ''disguising'' WML bloat, but they do nothing to ''alleviate'' it. So instead of using macros to generate redundant and repetitive instructions, you should be considering how to eliminate redundancy through programming techniques of abstraction. The most popular way to improve your code is using custom [[EventWML|events]] and [[InternalActionsWML#.5Bfire_event.5D|fire_event]] tags. See also: [[Wml_optimisation]]. | + | '''Important note:''' Although macros may look like they're simplifying the code, they do not help with wml bloating. Macros are very good at ''disguising'' WML bloat, but they do nothing to ''alleviate'' it. So instead of using macros to generate redundant and repetitive instructions, you should be considering how to eliminate redundancy through programming techniques of abstraction. The most popular way to improve your code is using custom [[EventWML|events]] and [[InternalActionsWML#.5Bfire_event.5D|fire_event]] tags. See also: [[Wml_optimisation|WML Optimisation]]. |
+ | |||
+ | ==== Whitespace in Macros ==== | ||
+ | |||
+ | When expanding a macro, '''''all''''' whitespace in the definition of the macro is preserved. The <tt>#arg</tt> declarations for optional arguments are removed including the final newline. The body of the optional argument (the default value) is similarly processed just as if it were a macro, so all whitespace is preserved. | ||
+ | |||
+ | There are two main practical implications of these rules: | ||
+ | |||
+ | * When using a macro to define simple constants to be used inline in the middle of an attribute value, the entire content and the <tt>#enddef</tt> must be on one line, like so: | ||
+ | : <syntaxhighlight lang=wml> | ||
+ | #define MY_CONSTANT | ||
+ | 42#enddef</syntaxhighlight> | ||
+ | * If using a macro inside a quoted string, you should not indent the contents of the macro, as the indentation will be preserved upon macro substitution. | ||
+ | * Similarly if you use an optional argument in the middle of an attribute value, the entire content and the <tt>#endarg</tt> must be on one line, like so: | ||
+ | : <syntaxhighlight lang=wml> | ||
+ | #define MY_MACRO | ||
+ | #arg OPTIONAL_ARG | ||
+ | default#endarg | ||
+ | key = "composite {OPTIONAL_ARG} value" | ||
+ | #enddef</syntaxhighlight> | ||
+ | |||
+ | === #arg === | ||
+ | |||
+ | {{DevFeature1.13|7}} | ||
+ | |||
+ | '''Syntax: #arg ''symbol'' ''<newline>'' ''default value'' #endarg''' | ||
+ | |||
+ | Defines an optional argument for a macro along with its default value. Optional arguments can be used to make a macro more flexible and to allow its user to specify certain parameters only when necessary. | ||
+ | |||
+ | For example, one could define a shortcut macro for [message] with only one required argument (the text displayed), but several optional ones: | ||
+ | |||
+ | <syntaxhighlight lang="wml"> | ||
+ | #define MESSAGE TEXT | ||
+ | |||
+ | #arg SPEAKER_ID | ||
+ | narrator#endarg | ||
+ | |||
+ | #arg CAPTION | ||
+ | #endarg | ||
+ | |||
+ | #arg SOUND | ||
+ | #endarg | ||
+ | |||
+ | #arg IMG | ||
+ | #endarg | ||
+ | |||
+ | [message] | ||
+ | speaker={SPEAKER_ID} | ||
+ | message={TEXT} | ||
+ | caption={CAPTION} | ||
+ | sound={SOUND} | ||
+ | image={IMG} | ||
+ | [/message] | ||
+ | #enddef | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | The caller of the macro can then decide which, if any, of the default values to override: | ||
+ | <syntaxhighlight lang="wml"> | ||
+ | {MESSAGE _"Halt!" SPEAKER_ID="Guard Captain"} | ||
+ | {MESSAGE _"Two days pass..." IMG=wesnoth-icon.png SOUND=ambient/morning.ogg} | ||
+ | {MESSAGE _"..."} | ||
+ | {MESSAGE _"Welcome!" CAPTION=_"Elóndra's shop of wonders" IMG=portraits/elves/shyde.png} | ||
+ | {MESSAGE _"*smash*" SPEAKER_ID="Bridge Troll" SOUND=mace.ogg} | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | For a multiline optional argument defined and called as follows: | ||
+ | <syntaxhighlight lang="wml"> | ||
+ | #define MY_MACRO | ||
+ | #arg MULTILINE | ||
+ | [some_tag] | ||
+ | some_attribute = "some value" | ||
+ | [/some_tag] | ||
+ | #endarg | ||
+ | ... | ||
+ | #enddef | ||
+ | |||
+ | {MY_MACRO (MULTILINE=[other_tag] | ||
+ | other_attribute = "other value" | ||
+ | [/other_tag])} | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | '''Note:''' As with #enddef, the final line break before #endarg is included in the default value. This means that if the symbol is used in the middle of a line, you should place the #endarg immediately after the value has ended, without a line break in between. | ||
=== #undef === | === #undef === | ||
Line 47: | Line 132: | ||
'''Syntax:''' '''#undef ''symbol'' ''' | '''Syntax:''' '''#undef ''symbol'' ''' | ||
− | Removes the previous definition of the macro named ''symbol''. | + | Removes the previous definition of the macro named ''symbol''. Wesnoth expects this to be done when overriding an existing macro, and will warn you if you redefine an existing macro without an explicit #undef before it. |
=== Inclusion directive {} === | === Inclusion directive {} === | ||
Line 87: | Line 172: | ||
==== Macro inclusions ==== | ==== Macro inclusions ==== | ||
− | '''Syntax: {''symbol'' [''arguments'']}''' | + | '''Syntax: {''symbol'' [''arguments''] [''optional arguments'']}''' |
− | If the macro named ''symbol'' is defined, the preprocessor will replace this instruction by the expression ''symbol'' was previously defined as, using ''arguments'' as parameters. The number of arguments must be exactly the same as in the original definition or an error will occur. | + | If the macro named ''symbol'' is defined, the preprocessor will replace this instruction by the expression ''symbol'' was previously defined as, using ''arguments'' as parameters. The number of normal arguments must be exactly the same as in the original definition or an error will occur. Optional arguments can only be placed '''after''' all normal arguments, however they can be specified in any order desired. |
You can create multiple word arguments by using parentheses to delimit the contents. For example, in '''{ENEMY_UNIT Wolf Rider 18 24}''' the four words will be interpreted as separate arguments and cause the preprocessor to fail since the macro was defined above with only three; instead, you should use '''{ENEMY_UNIT (Wolf Rider) 18 24}'''. | You can create multiple word arguments by using parentheses to delimit the contents. For example, in '''{ENEMY_UNIT Wolf Rider 18 24}''' the four words will be interpreted as separate arguments and cause the preprocessor to fail since the macro was defined above with only three; instead, you should use '''{ENEMY_UNIT (Wolf Rider) 18 24}'''. | ||
+ | |||
+ | Optional arguments can also be delimited by placing parentheses, however they must be placed around both the argument name '''and''' content: | ||
+ | |||
+ | <syntaxhighlight lang="wml"> | ||
+ | {MESSAGE _"I'll smash you!" (SPEAKER_ID=Bridge Troll) } # Correct | ||
+ | {MESSAGE _"I'll smash you!" SPEAKER_ID=(Bridge Troll) } # Wrong | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | This way even complex arguments can be passed: | ||
+ | |||
+ | <syntaxhighlight lang="wml"> | ||
+ | {MODIFY_UNIT ( | ||
+ | [filter_adjacent] | ||
+ | canrecruit=yes | ||
+ | [/filter_adjacent] | ||
+ | ) side 2} | ||
+ | </syntaxhighlight> | ||
Using the name of an existing macro as the name of a macro argument is possible, but the argument will always take precedence over the original macro: | Using the name of an existing macro as the name of a macro argument is possible, but the argument will always take precedence over the original macro: | ||
− | + | <syntaxhighlight lang="wml"> | |
− | + | #define VARIABLE | |
− | + | #enddef | |
− | + | #define MACRO VARIABLE | |
− | + | {VARIABLE} # is calling for the argument, not for the macro above | |
+ | #enddef | ||
+ | </syntaxhighlight> | ||
=== #ifdef and #ifndef === | === #ifdef and #ifndef === | ||
Line 107: | Line 211: | ||
'''Syntax:''' '''#ifdef ''symbol'' ''substitution-if-defined'' [#else ''substitution-if-not-defined'' ] #endif''' | '''Syntax:''' '''#ifdef ''symbol'' ''substitution-if-defined'' [#else ''substitution-if-not-defined'' ] #endif''' | ||
− | If ''symbol'' has been defined with '''#define''' or as a built-in macro, the whole block will be replaced by ''substitution-if- | + | If ''symbol'' has been defined with '''#define''' or as a built-in macro, the whole block will be replaced by ''substitution-if-defined''. If not, it will be replaced by ''substitution-if-not-defined'' if it is available. |
'''#ifndef''' is the exact opposite of '''#ifdef''', reversing the logic: | '''#ifndef''' is the exact opposite of '''#ifdef''', reversing the logic: | ||
− | '''Syntax:''' '''#ifndef ''symbol'' ''substitution-if-not- | + | '''Syntax:''' '''#ifndef ''symbol'' ''substitution-if-not-defined'' [#else ''substitution-if-defined''] #endif''' |
=== #ifhave and #ifnhave === | === #ifhave and #ifnhave === | ||
Line 117: | Line 221: | ||
'''Syntax:''' '''#ifhave ''path'' ''substitution-if-path-exists'' [#else ''substitution-if-path-does-not-exist''] #endif''' | '''Syntax:''' '''#ifhave ''path'' ''substitution-if-path-exists'' [#else ''substitution-if-path-does-not-exist''] #endif''' | ||
− | Checks for the existence of a file. Uses the same relative paths as include directives | + | Checks for the existence of a file. Uses the same relative paths as [[#Inclusion_directive_.7B.7D|include directives]]. |
Example: | Example: | ||
− | + | <syntaxhighlight lang="wml"> | |
− | + | #ifhave ~add-ons/My_Addon/_main.cfg | |
− | + | {MY_ADDON_MACROS} | |
+ | #endif | ||
+ | </syntaxhighlight> | ||
'''#ifnhave''' does the opposite of '''#ifhave''': | '''#ifnhave''' does the opposite of '''#ifhave''': | ||
Line 138: | Line 244: | ||
Example: | Example: | ||
− | + | <syntaxhighlight lang="wml"> | |
− | + | #ifver WESNOTH_VERSION >= 1.9.7+ | |
− | + | [message] | |
− | + | speaker=narrator | |
− | + | message= _ "I’m on Wesnoth 1.9.7+, 1.9.8 or later!" | |
− | + | [/message] | |
− | + | #else | |
− | + | #ifver WESNOTH_VERSION == 1.9.7 | |
− | + | [message] | |
− | + | speaker=narrator | |
− | + | message= _ "I’m on Wesnoth 1.9.7, and I’ll include some workaround code for bug #9001!" | |
− | + | [/message] | |
− | + | #endif | |
+ | #endif | ||
+ | </syntaxhighlight> | ||
'''#ifnver''' does the opposite of '''#ifver''': | '''#ifnver''' does the opposite of '''#ifver''': | ||
Line 165: | Line 273: | ||
Example: | Example: | ||
− | + | <syntaxhighlight lang="wml"> | |
− | + | #ifver WESNOTH_VERSION < 1.11.10 | |
− | + | #error This add-on does not support Wesnoth 1.11.10! | |
+ | #endif | ||
+ | </syntaxhighlight> | ||
=== #warning === | === #warning === | ||
Line 176: | Line 286: | ||
Example: | Example: | ||
− | + | <syntaxhighlight lang="wml"> | |
− | + | #ifver WESNOTH_VERSION < 1.11.10 | |
− | + | #warning On Wesnoth 1.11.9 or earlier, bug workarounds enabled! | |
+ | #endif | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === #deprecated === | ||
+ | |||
+ | {{DevFeature1.13|11}} | ||
+ | |||
+ | '''Syntax:''' '''#deprecated 1 ''message''''' | ||
+ | |||
+ | '''Syntax:''' '''#deprecated 2 ''version'' ''message''''' | ||
+ | |||
+ | '''Syntax:''' '''#deprecated 3 ''version'' ''message''''' | ||
+ | |||
+ | '''Syntax:''' '''#deprecated 4 ''message''''' | ||
+ | |||
+ | The effect of this directive depends on whether it appears within a macro definition. | ||
+ | |||
+ | * If it appears at file level, outside any macro definition, then it immediately outputs a warning saying that the file is deprecated, with the provided message. Multiple '''#deprecated''' directives at toplevel in a single file will result in separate messages. | ||
+ | * If it appears inside a macro definition ('''#define ... #enddef'''), then it doesn't output anything. Instead, it marks the macro as deprecated. When that macro is later used, only then will the preprocessor output a warning saying that the macro is deprecated, with the provided message. Multiple '''#deprecated''' directives within a single macro will be merged into one message. | ||
+ | |||
+ | Note that deprecation messages will only appear if they have been set to. {{DevFeature1.13|12}} This can be done by enabling debug mode, or by going to Advanced Preferences and setting the log-level for the deprecation logdomain. (This can also be done on the command-line.) | ||
+ | |||
+ | If you provide a deprecation level of 2 or 3, it is required to indicate the earliest version in which the feature could be removed. However, if you provide a deprecation level of 1 or 4, any provided ''version'' will instead be parsed as part of the message, so you will probably not want to provide one at all. Other deprecation levels are not valid. See the documentation for [[InterfaceActionsWML#.5Bdeprecated_message.5D|[deprecated_message]]] for the meaning of the various ''level'' values. | ||
== Built-in macros == | == Built-in macros == | ||
The following macros are automatically defined with empty contents (unless specified otherwise) by the game engine depending on the configuration or gameplay mode. | The following macros are automatically defined with empty contents (unless specified otherwise) by the game engine depending on the configuration or gameplay mode. | ||
+ | |||
+ | Note that except during an actual game, '''MULTIPLAYER''', '''EDITOR''' and previous campaign defines, are not guaranteed to be undefined, especially when coming back to the titlescreen. So it is not enough to hide contents inside of an #ifdef to prevent them from being seen by the campaign selection dialog, for example for multiplayer specific '''[campaign]'''s or '''[modification]'''s '''type=mp''' must be used. | ||
* A campaign define symbol (see ''define'' in [[CampaignWML]]): defined when playing a single-player campaign. | * A campaign define symbol (see ''define'' in [[CampaignWML]]): defined when playing a single-player campaign. | ||
* A campaign difficulty level, usually '''EASY''', '''NORMAL''' or '''HARD''' (see ''difficulties'' in [[CampaignWML]]): defined according to the chosen difficulty when starting a single-player campaign, also stored in saved games. | * A campaign difficulty level, usually '''EASY''', '''NORMAL''' or '''HARD''' (see ''difficulties'' in [[CampaignWML]]): defined according to the chosen difficulty when starting a single-player campaign, also stored in saved games. | ||
* '''MULTIPLAYER''': defined when in multiplayer mode. | * '''MULTIPLAYER''': defined when in multiplayer mode. | ||
− | |||
* '''EDITOR''': defined when running the built-in map editor. | * '''EDITOR''': defined when running the built-in map editor. | ||
− | * '''DEBUG_MODE''': defined when the game has been launched in debug mode (i.e. with '''-d''' or '''--debug''' in the command line). | + | * '''DEBUG_MODE''': defined when the game has been launched in debug mode (i.e. with '''-d''' or '''--debug''' in the command line). Can also be set by typing <code>:</code> to bring up [[CommandMode]], then typing <code>debug</code>, and then restarting the scenario. |
− | * '''APPLE''': defined while processing the main game data when running on Mac OS X. | + | * '''APPLE''': defined while processing the main game data when running on Mac OS X. This primarily exists to switch Control for Command in hotkeys, which is why there are no defines for other platforms. |
* '''WESNOTH_VERSION''': defined containing just the game version number when running the WML preprocessor. | * '''WESNOTH_VERSION''': defined containing just the game version number when running the WML preprocessor. | ||
+ | * '''CURRENT_FILE''': Expands to the name of the current WML file. | ||
+ | * '''CURRENT_DIRECTORY''': Expands to the preprocessor path of the parent directory for the current WML file (e.g. for <code><user data dir>/data/add-ons/My_Addon/_main.cfg</code> this evaluates to <code>~add-ons/My_Addon</code>). | ||
+ | * '''LEFT_BRACE''': {{DevFeature1.15|2}} Expands to <code>{</code>. | ||
+ | * '''RIGHT_BRACE''': {{DevFeature1.15|2}} Expands to <code>}</code>. | ||
+ | * '''SCHEMA_VALIDATION''': defined if the validator is being run. | ||
+ | * '''__WMLUNITS__''': defined when the tool to create [https://units.wesnoth.org units.wesnoth.org] is being run. | ||
+ | |||
+ | A ''very large'' number of additional macros are provided as part of the default game core WML. For a full list of those, check the [https://www.wesnoth.org/macro-reference.html macro reference]. | ||
== Command-line preprocessor == | == Command-line preprocessor == | ||
Line 207: | Line 349: | ||
comma separated list of defines to be used by '--preprocess' command. If 'SKIP_CORE' is in the define list the data/core won't be preprocessed. | comma separated list of defines to be used by '--preprocess' command. If 'SKIP_CORE' is in the define list the data/core won't be preprocessed. | ||
− | The command will preprocess | + | The command will first preprocess '''data/core/macros''' and '''data/core/terrain-graphics''', and afterwards the specified path. You can specify a single file to be preprocessed (if you want to preprocess multiple separate files, you'll need to run a different command line for each one), or an entire directory, which will be preprocessed according to the rules used by the inclusion directive above. |
− | The | + | The resulting preprocessed files will be written in the target directory. There will be two types of files: .cfg files --- the normal ones, and .plain files containing line markers and textdomain changes. |
If by chance, the simple macro define doesn't suffice, you can use: | If by chance, the simple macro define doesn't suffice, you can use: | ||
Line 221: | Line 363: | ||
'''Syntax: --preprocess-output-macros [<target file>]''' | '''Syntax: --preprocess-output-macros [<target file>]''' | ||
− | This file could be fed to the 'input-macros' argument next time you run it. For example, a scenario | + | This file could be fed to the 'input-macros' argument next time you run it. For example, a scenario would be: parsing just the core first time, and for the intended target files, you would add SKIP_CORE but import the generated macros file - that will be faster than preprocessing the core again. If the target file is not specified, the output file will be _MACROS_.cfg in the target directory of the preprocess's command. |
− | If ''file/directory'' and ''target directory'' are not absolute paths, they will be considered relative to the | + | If ''file/directory'' and ''target directory'' are not absolute paths, they will be considered relative to the current directory. |
Some examples: | Some examples: | ||
Line 233: | Line 375: | ||
* Add the MY_CAMPAIGN and HARD defines before preprocessing a campaign's files: | * Add the MY_CAMPAIGN and HARD defines before preprocessing a campaign's files: | ||
-p ~/.wesnoth/data/add-ons/My_Campaign ~/result --preprocess-defines=MY_CAMPAIGN,HARD | -p ~/.wesnoth/data/add-ons/My_Campaign ~/result --preprocess-defines=MY_CAMPAIGN,HARD | ||
+ | * File myfile.cfg depends on macros defined in data/gui/macros/_initial.cfg: | ||
+ | -p data/gui/macros/_initial.cfg /tmp --preprocess-output-macros=macros | ||
+ | -p myfile.cfg ~/result --preprocess-input-macros=/tmp/macros | ||
+ | |||
+ | When it starts getting complicated, such as a file that depends on multiple other files, it is probably easiest to simply "include" the dependencies: | ||
+ | |||
+ | * File myfile.cfg depends on macros defined in data/gui/macros/_initial.cfg: | ||
+ | Add {gui/macros/_initial.cfg} to the beginning of myfile.cfg | ||
+ | -p myfile.cfg ~/result | ||
If you want a more detailed (and potentially overwhelming) log, you can simply add the switches '''--log-debug=all''' or '''--log-info=all''' to the command line, so you can see how things are preprocessed in detail. | If you want a more detailed (and potentially overwhelming) log, you can simply add the switches '''--log-debug=all''' or '''--log-info=all''' to the command line, so you can see how things are preprocessed in detail. | ||
Line 238: | Line 389: | ||
== See Also == | == See Also == | ||
− | * [[SyntaxWML]] | + | * [[SyntaxWML]] (explains relationship between comments and preprocessor directives) |
* [[ReferenceWML]] | * [[ReferenceWML]] | ||
[[Category: WML Reference]] | [[Category: WML Reference]] |
Latest revision as of 15:21, 3 November 2024
Contents
Overview
Wesnoth loads just one configuration file directly: data/_main.cfg. However, the WML preprocessor allows the inclusion of more files. Whenever a WML file is read by Wesnoth, it is passed through the preprocessor.
The preprocessor can interpret a simple language of string expansions known as macros. A macro should always be defined before the place where it needs to be used.
The preprocessor is applied recursively, so included files will be parsed for macros, and after macro expansion will be parsed for macros again, and so on. As a result, you should not write a recursive macro that references itself, because it will cause errors (but, alas, not necessarily error messages).
Preprocessor directives
The following directives are used to create and use macros, i.e. shortcuts which reduce repetition of information. See the macro reference for the list of predefined core macros.
Macros have scoping rules such that addons have separate preprocessing contexts, meaning that they can be overridden however an author of UMC wishes to override them, without worrying about breaking other add-ons.
The preprocessor has changed several times, so don't expect old Wesnoth versions to behave exactly the same as the current stable and development series.
Note: In multiplayer scenarios, these directives will appear to work only for the host and not for other clients. This is because the preprocessor is run only on the host, and the clients receive the resultant WML from the server. It's particularly important to keep this in mind before using preprocessor conditionals.
#define
Syntax: #define symbol [parameters] <newline> substitution #enddef
All subsequent occurences of {symbol [arguments]} (see below) will be replaced by the contents of the substitution block, with all occurrences of any parameter {parameter} within substitution replaced by the corresponding value in arguments. For example, the ENEMY_UNIT macro for the macro inclusion example below could be defined as follows:
#define ENEMY_UNIT TYPE X Y
## the ordering above is important, since the preprocessor does not distinguish
## data into different types; only the ordering is used to determine which
## arguments apply to which parameters.
[unit]
type={TYPE} ## the unit will be of type TYPE, so different
## instantiations
## of this macro can create different units.
x={X}
y={Y}
side=2 ## the unit will be an enemy, regardless of the parameter
## values. This reduces "repetition of information",
## since it is no longer necessary to specify
## each created unit as an enemy.
[/unit]
#enddef
(See SingleUnitWML for further information on creating units using WML.)
Important note: Although macros may look like they're simplifying the code, they do not help with wml bloating. Macros are very good at disguising WML bloat, but they do nothing to alleviate it. So instead of using macros to generate redundant and repetitive instructions, you should be considering how to eliminate redundancy through programming techniques of abstraction. The most popular way to improve your code is using custom events and fire_event tags. See also: WML Optimisation.
Whitespace in Macros
When expanding a macro, all whitespace in the definition of the macro is preserved. The #arg declarations for optional arguments are removed including the final newline. The body of the optional argument (the default value) is similarly processed just as if it were a macro, so all whitespace is preserved.
There are two main practical implications of these rules:
- When using a macro to define simple constants to be used inline in the middle of an attribute value, the entire content and the #enddef must be on one line, like so:
#define MY_CONSTANT 42#enddef
- If using a macro inside a quoted string, you should not indent the contents of the macro, as the indentation will be preserved upon macro substitution.
- Similarly if you use an optional argument in the middle of an attribute value, the entire content and the #endarg must be on one line, like so:
#define MY_MACRO #arg OPTIONAL_ARG default#endarg key = "composite {OPTIONAL_ARG} value" #enddef
#arg
(Version 1.13.7 and later only)
Syntax: #arg symbol <newline> default value #endarg
Defines an optional argument for a macro along with its default value. Optional arguments can be used to make a macro more flexible and to allow its user to specify certain parameters only when necessary.
For example, one could define a shortcut macro for [message] with only one required argument (the text displayed), but several optional ones:
#define MESSAGE TEXT
#arg SPEAKER_ID
narrator#endarg
#arg CAPTION
#endarg
#arg SOUND
#endarg
#arg IMG
#endarg
[message]
speaker={SPEAKER_ID}
message={TEXT}
caption={CAPTION}
sound={SOUND}
image={IMG}
[/message]
#enddef
The caller of the macro can then decide which, if any, of the default values to override:
{MESSAGE _"Halt!" SPEAKER_ID="Guard Captain"}
{MESSAGE _"Two days pass..." IMG=wesnoth-icon.png SOUND=ambient/morning.ogg}
{MESSAGE _"..."}
{MESSAGE _"Welcome!" CAPTION=_"Elóndra's shop of wonders" IMG=portraits/elves/shyde.png}
{MESSAGE _"*smash*" SPEAKER_ID="Bridge Troll" SOUND=mace.ogg}
For a multiline optional argument defined and called as follows:
#define MY_MACRO
#arg MULTILINE
[some_tag]
some_attribute = "some value"
[/some_tag]
#endarg
...
#enddef
{MY_MACRO (MULTILINE=[other_tag]
other_attribute = "other value"
[/other_tag])}
Note: As with #enddef, the final line break before #endarg is included in the default value. This means that if the symbol is used in the middle of a line, you should place the #endarg immediately after the value has ended, without a line break in between.
#undef
Syntax: #undef symbol
Removes the previous definition of the macro named symbol. Wesnoth expects this to be done when overriding an existing macro, and will warn you if you redefine an existing macro without an explicit #undef before it.
Inclusion directive {}
This directive can be used to include macros, single files or sets of files from a target directory.
File/directory inclusions
Syntax: {path}
Includes the file with the specified path, which will in turn run the preprocessor on it and perform any required substitutions or inclusions within it. The path may not contain .. or the inclusion will be skipped.
The exact location in which the path will be resolved will depend on its prefix:
- {path}: If path isn't a known macro (see below), the game will assume it's a relative path to a file in the main game data/ directory and include it.
- {~path}: As above, but instead of the game data directory, the path is resolved relative to the user data/ directory, where user made add-ons can normally be found.
- {./path}: The path is resolved relative to the location of the current file containing this inclusion.
Information for locating the user data and game data directories can be found in EditingWesnoth.
Forward slashes (/) should always be used as the path delimiter, even if your platform uses a different symbol such as colons (:) or backslashes (\)! It is also very important to respect the actual letter case used to name files and directories for compatibility with case-sensitive filesystems on Unix-based operating systems.
When path points to a directory instead of a file, the preprocessor will include all files found within with the .cfg extension, in alphabetical order; files without this extension (such as .map or .png files) are ignored.
Some directories are handled in a special fashion according to their contents:
- If there's a file named _main.cfg in the target directory, only that file will be included and preprocessed. It may include other files from its own directory or subdirectories within it, of course. This is used for managing WML directories as self-contained packages, like user made add-ons.
- If there are files named _main.cfg in subdirectories of the target and there isn't one in the target itself, they will be all preprocessed. Given the following layout:
dir/ dir/a/_main.cfg dir/a/other.cfg dir/b/_main.cfg dir/b/other.cfg dir/other.cfg
Using {dir} will cause dir/a/_main.cfg, dir/b/_main.cfg and dir/other.cfg to be included.
- If there's a file named _final.cfg but no _main.cfg, the file is guaranteed to be included and processed after all the other files in the directory.
- If there's a file named _initial.cfg but no _main.cfg, the file is guaranteed to be included and processed before all the other files in the directory.
Macro inclusions
Syntax: {symbol [arguments] [optional arguments]}
If the macro named symbol is defined, the preprocessor will replace this instruction by the expression symbol was previously defined as, using arguments as parameters. The number of normal arguments must be exactly the same as in the original definition or an error will occur. Optional arguments can only be placed after all normal arguments, however they can be specified in any order desired.
You can create multiple word arguments by using parentheses to delimit the contents. For example, in {ENEMY_UNIT Wolf Rider 18 24} the four words will be interpreted as separate arguments and cause the preprocessor to fail since the macro was defined above with only three; instead, you should use {ENEMY_UNIT (Wolf Rider) 18 24}.
Optional arguments can also be delimited by placing parentheses, however they must be placed around both the argument name and content:
{MESSAGE _"I'll smash you!" (SPEAKER_ID=Bridge Troll) } # Correct
{MESSAGE _"I'll smash you!" SPEAKER_ID=(Bridge Troll) } # Wrong
This way even complex arguments can be passed:
{MODIFY_UNIT (
[filter_adjacent]
canrecruit=yes
[/filter_adjacent]
) side 2}
Using the name of an existing macro as the name of a macro argument is possible, but the argument will always take precedence over the original macro:
#define VARIABLE
#enddef
#define MACRO VARIABLE
{VARIABLE} # is calling for the argument, not for the macro above
#enddef
#ifdef and #ifndef
Unlike the other preprocessor directives, #ifdef and #ifndef are not mere conveniences. They are often necessary to distinguish between different gameplay modes or difficulties (see Built-in macros below).
Syntax: #ifdef symbol substitution-if-defined [#else substitution-if-not-defined ] #endif
If symbol has been defined with #define or as a built-in macro, the whole block will be replaced by substitution-if-defined. If not, it will be replaced by substitution-if-not-defined if it is available.
#ifndef is the exact opposite of #ifdef, reversing the logic:
Syntax: #ifndef symbol substitution-if-not-defined [#else substitution-if-defined] #endif
#ifhave and #ifnhave
Syntax: #ifhave path substitution-if-path-exists [#else substitution-if-path-does-not-exist] #endif
Checks for the existence of a file. Uses the same relative paths as include directives.
Example:
#ifhave ~add-ons/My_Addon/_main.cfg
{MY_ADDON_MACROS}
#endif
#ifnhave does the opposite of #ifhave:
Syntax: #ifnhave path substitution-if-path-does-not-exist [#else substitution-if-path-exists] #endif
#ifver and #ifnver
Syntax: #ifver symbol operator version-number <newline> substitution-if-condition-met [#else substitution-if-condition-not-met] #endif
Compares a version number defined in a macro against an argument for conditional block inclusions, like #ifdef and #ifhave. operator is one of == (equal), != (not equal), < (less), <= (less or equal), > (greater), >= (greater or equal). The specified symbol should have been previously defined as plain text without more macro inclusions within it, and it must not require any arguments.
Versions with text suffixes are sorted in binary order and come after all versions with the same number. The most common suffixes begin with "+", but as this represents multiple possible versions, comparing versions against it is not recommended.
Example:
#ifver WESNOTH_VERSION >= 1.9.7+
[message]
speaker=narrator
message= _ "I’m on Wesnoth 1.9.7+, 1.9.8 or later!"
[/message]
#else
#ifver WESNOTH_VERSION == 1.9.7
[message]
speaker=narrator
message= _ "I’m on Wesnoth 1.9.7, and I’ll include some workaround code for bug #9001!"
[/message]
#endif
#endif
#ifnver does the opposite of #ifver:
Syntax: #ifnver symbol operator version-number <newline> substitution-if-condition-not-met [#else substitution-if-condition-met] #endif
#error
Syntax: #error [message]
Causes the WML preprocessor to fail unconditionally upon encountering the line. For add-ons, this will cause the game to display an error and return to the titlescreen if the add-on is required for the user's action (such as playing a campaign or loading a saved game). For core WML, this will cause the game to quit entirely.
Please note that in spite of the example below, it is not advisable to use this mechanism in published add-ons for version or feature-checking, since the message is not displayed in a form that permits translation and the additional trace information may confuse players. This directive is only intended as a debugging aid for content creators.
Example:
#ifver WESNOTH_VERSION < 1.11.10
#error This add-on does not support Wesnoth 1.11.10!
#endif
#warning
Syntax: #warning [message]
Causes the WML preprocessor to emit a warning upon encountering the line. The message will only be relayed to stderr, not to the player in the game UI. This directive is only intended as a debugging aid for content creators.
Example:
#ifver WESNOTH_VERSION < 1.11.10
#warning On Wesnoth 1.11.9 or earlier, bug workarounds enabled!
#endif
#deprecated
(Version 1.13.11 and later only)
Syntax: #deprecated 1 message
Syntax: #deprecated 2 version message
Syntax: #deprecated 3 version message
Syntax: #deprecated 4 message
The effect of this directive depends on whether it appears within a macro definition.
- If it appears at file level, outside any macro definition, then it immediately outputs a warning saying that the file is deprecated, with the provided message. Multiple #deprecated directives at toplevel in a single file will result in separate messages.
- If it appears inside a macro definition (#define ... #enddef), then it doesn't output anything. Instead, it marks the macro as deprecated. When that macro is later used, only then will the preprocessor output a warning saying that the macro is deprecated, with the provided message. Multiple #deprecated directives within a single macro will be merged into one message.
Note that deprecation messages will only appear if they have been set to. (Version 1.13.12 and later only) This can be done by enabling debug mode, or by going to Advanced Preferences and setting the log-level for the deprecation logdomain. (This can also be done on the command-line.)
If you provide a deprecation level of 2 or 3, it is required to indicate the earliest version in which the feature could be removed. However, if you provide a deprecation level of 1 or 4, any provided version will instead be parsed as part of the message, so you will probably not want to provide one at all. Other deprecation levels are not valid. See the documentation for [deprecated_message] for the meaning of the various level values.
Built-in macros
The following macros are automatically defined with empty contents (unless specified otherwise) by the game engine depending on the configuration or gameplay mode.
Note that except during an actual game, MULTIPLAYER, EDITOR and previous campaign defines, are not guaranteed to be undefined, especially when coming back to the titlescreen. So it is not enough to hide contents inside of an #ifdef to prevent them from being seen by the campaign selection dialog, for example for multiplayer specific [campaign]s or [modification]s type=mp must be used.
- A campaign define symbol (see define in CampaignWML): defined when playing a single-player campaign.
- A campaign difficulty level, usually EASY, NORMAL or HARD (see difficulties in CampaignWML): defined according to the chosen difficulty when starting a single-player campaign, also stored in saved games.
- MULTIPLAYER: defined when in multiplayer mode.
- EDITOR: defined when running the built-in map editor.
- DEBUG_MODE: defined when the game has been launched in debug mode (i.e. with -d or --debug in the command line). Can also be set by typing
:
to bring up CommandMode, then typingdebug
, and then restarting the scenario. - APPLE: defined while processing the main game data when running on Mac OS X. This primarily exists to switch Control for Command in hotkeys, which is why there are no defines for other platforms.
- WESNOTH_VERSION: defined containing just the game version number when running the WML preprocessor.
- CURRENT_FILE: Expands to the name of the current WML file.
- CURRENT_DIRECTORY: Expands to the preprocessor path of the parent directory for the current WML file (e.g. for
<user data dir>/data/add-ons/My_Addon/_main.cfg
this evaluates to~add-ons/My_Addon
). - LEFT_BRACE: (Version 1.15.2 and later only) Expands to
{
. - RIGHT_BRACE: (Version 1.15.2 and later only) Expands to
}
. - SCHEMA_VALIDATION: defined if the validator is being run.
- __WMLUNITS__: defined when the tool to create units.wesnoth.org is being run.
A very large number of additional macros are provided as part of the default game core WML. For a full list of those, check the macro reference.
Command-line preprocessor
Syntax: --preprocess <source file/directory> <target directory>
Or the short form:
Syntax: -p <source file/directory> <target directory>
You can specify a list of predefined defines with:
Syntax: --preprocess-defines=DEFINE1,DEFINE2,etc
comma separated list of defines to be used by '--preprocess' command. If 'SKIP_CORE' is in the define list the data/core won't be preprocessed.
The command will first preprocess data/core/macros and data/core/terrain-graphics, and afterwards the specified path. You can specify a single file to be preprocessed (if you want to preprocess multiple separate files, you'll need to run a different command line for each one), or an entire directory, which will be preprocessed according to the rules used by the inclusion directive above.
The resulting preprocessed files will be written in the target directory. There will be two types of files: .cfg files --- the normal ones, and .plain files containing line markers and textdomain changes.
If by chance, the simple macro define doesn't suffice, you can use:
Syntax: --preprocess-input-macros <file>
To import an existing file that contains macros, and they will be available in the defines database before processing the specified files.
There is also the possibility to export the preprocessed defines/macro list with:
Syntax: --preprocess-output-macros [<target file>]
This file could be fed to the 'input-macros' argument next time you run it. For example, a scenario would be: parsing just the core first time, and for the intended target files, you would add SKIP_CORE but import the generated macros file - that will be faster than preprocessing the core again. If the target file is not specified, the output file will be _MACROS_.cfg in the target directory of the preprocess's command.
If file/directory and target directory are not absolute paths, they will be considered relative to the current directory.
Some examples:
- Preprocess the entire tutorial dir, and write the results in the ~/result folder:
-p ~/wesnoth/data/campaigns/tutorial ~/result
- Add the MULTIPLAYER define to the list and preprocess a scenario's config file:
-p ~/.wesnoth/data/add-ons/My_Campaign/scenarios/01_First_Scenario.cfg ~/result --preprocess-defines=MULTIPLAYER
- Add the MY_CAMPAIGN and HARD defines before preprocessing a campaign's files:
-p ~/.wesnoth/data/add-ons/My_Campaign ~/result --preprocess-defines=MY_CAMPAIGN,HARD
- File myfile.cfg depends on macros defined in data/gui/macros/_initial.cfg:
-p data/gui/macros/_initial.cfg /tmp --preprocess-output-macros=macros -p myfile.cfg ~/result --preprocess-input-macros=/tmp/macros
When it starts getting complicated, such as a file that depends on multiple other files, it is probably easiest to simply "include" the dependencies:
- File myfile.cfg depends on macros defined in data/gui/macros/_initial.cfg:
Add {gui/macros/_initial.cfg} to the beginning of myfile.cfg -p myfile.cfg ~/result
If you want a more detailed (and potentially overwhelming) log, you can simply add the switches --log-debug=all or --log-info=all to the command line, so you can see how things are preprocessed in detail.
See Also
- SyntaxWML (explains relationship between comments and preprocessor directives)
- ReferenceWML