Conditional Compilation
Like all Elements languages, Mercury supports Conditional Compilation in order to conditionally include or exclude code from being built into the executable based on the current configuration, target platform, or other conditions.
The standard Visual Basic #If
, #Else
and #ElseIf
directives can bed used to surround code with conditions for inclusion/exclusion. In addition, the defined()
and static()
system functions can also be used to tie blocks of code to conditions.
Defines
Conditional compilation is applied based on the presence or lack of presence of named conditional defines. Defines are identifers that live in a namespace sepaarte from the rest of the code, and can be set fopr the project in four ways:
- Pre-defined by the compiler (e.g.
ELEMENTS
,DOTNET
orCOCOA
). - Set in the project via the
<ConditionalDefines>
(or, legacy,<DefineConstents>
) setting, editable via the "Manage Conditional Defines" Sheet Manager in Fire and Water and the Project Properties in Visual Studio. - Passed to the compiler via the EBuild command line.
- Set in code using the
#Const
directive.
A define is either a simple name (e.g. "DEBUG
") or can optionally have a value assigned ("VERSION=5
").
Defines (whether they have a value or not) can be treated as boolean values — if the define is present (defined), they evaluate to True
, and if they are not present (undefined) they evaluate to False
:
#If DEBUG
' debug code goes here
#End If
Multiple defines can also be combined in a logical expression, using AndAlso
, OrElse
, Xor
and Not
:
#If DEBUG AndAlso COCOA
' debug code for the Cocoa platform goes here
#End If
Finally, arithmetic expressions can be used to evaluate defines that have a value:
#If DEBUG AndAlso COCOA AndAlso VERSION > 3
' Version 3+ debug code for the Cocoa platform goes here
#End If
Validity of Undefined Code
Unlike most other compilers, the conditional compilation system in Elements is not a dumb text-pre-processor that simply strips all undefined code from the file before compilation. Instead, Conditional compilation directives are part of the syntax tree of a Mercury code file.
This brings with it two limitations:
- Code in sections of the file that will not be compiled int the binary due to the current set of conditional defines still must be syntactically valid Mercury code. It can refer to types or identifiers that are unknown, but it must not be grammatically wrong.
#If
/#End IF
sections cannot intersect language constructs. For example, an#If
block may not start before of aSub
s declaration and then close inside it.
These restrictions allow several benefits. They allow the compiler (and IDE smarts) to build a single syntax tree that covers all versions of an #If
'ed code file, letting you get Code Completion in active and inactive portions of the code.
They also enable more advanced conditional compilation using the methods desxcribed in the next section.
Conditional Compilation w/ defined()
In addition to the traditional #If
directive, Mercury has two System Functions that allow you to integrate conditional compilation more naturally with the normal flow of the language.
The defined()
system function takes a string literal parameter with the name of a single define, and will – at compile-time – evaluate to True
if the define is set, and to False
, if not. If False
, the compiler will (where possible) eliminate code that should not need to compile.
If defined("DEBUG") Then
' debug code goes here
End If
This code works the same as the example above, but it flows more natural with the regular execution flow of the Mercury language.
The real power of defined()
comes into play when it can be combined with other conditions that evaluate at run time. Consinder the following example
If defined("COCOA") OrElse (defined("DOTNET") AndAlso Environment.OS = OperatingSystem.macOS) Then
' code that only works on Mac
End If
In this example, the first part of the condition, defined("COCOA")
, will be evaluated statically at compile time:
- When building a native Mac (Cocoa) app, the If statement is determined to be true, and (through boolean shot circuit) the following code can be included unconditionally.
- When building, say, a native Windows, both
defined()
conditions will evaluate to false, and the entire block of code will be excluded. - However, if we're building for .NET, our executable could in theory run on any platform, including the Mac. Since
defined("COCOA")
isFalse
butdefined("DOTNET")
is true, the compiler will emit the If clause with the at-runtime check ofEnvironment.OS
, so the code block will run depending on the actual platform.
Similar to defined()
, the static()
system function takes a simple boolean expression that must be possible to resolve at compile time. Its result is True
if the expression is true, and False
, if the expression is false.
Just as with defined()
, the compiler will be able to smartly omit code where possible.
Note: just as with classic #If
directives, code omitted due to defined()
or static()
must of course be syntactically valid, but is free to make reference of types and identifiers that would be invalid. For example, this code will compile clean, even if ThisMethodDoesNotExist
is unknown, since the static()
call evaluates to False
:
If static(2+2=5) Then
ThisMethodDoesNotExist()
End If
Visual Basic Standard Defines
Microsoft's Visual Basic.NET has a few standard defines that are set by the compiler. In Mercury, these are handled differently.
-
The
VBC_VER
define is not supported, as it refers tom specific versions of Microsoft's compiler implementation. Elements as its own Standard Conditional Defines you can use to check compiler version, platform and more. You can use#If ELEMENTS
to check for Mercury vs. Microsoft Visual Basic.NET. -
The
DBEUG
andTRACE
defines are simple defines declared in the project. As far as the compiler is concerned, there is nothing special or magical about them compared to defines you would make up yourself. By default, All new projects created from template haver these defines set for theDebug
configuration. -
The
CONFIG
define also a simple constant defined in the Elements project. For all Mercury projects created from template, this constant will be predefined for the two standard configurations,Debug
andRelease
, that the templates create. If you add Mercury code to existing Elements projects and need this define, you might need to add it yourself. If you add additional configurations to your project, yuo might also need to set it. -
the
TARGET
define will not be set by the Mercury templates, as it is used infrequently, and its terminology conflicts with how the term "Target" is used in Elements. If needed, you can set this define yourself, of course. -
The
_MYTYPE
define will also not be set by the Mercury templates, as Mercury's My Type System does not rely on it.
When importing/converting .vbproj
projects to Mercury, the importer will set the CONFIG
for all configurations, if not already present in the original project, and it will also add the DEBUG
and TRACE
defines for the Debug
configuration, and set the _MYTYPE
define, based on the MyType
setting in the .vbproj
.
See Also
- The shared Conditional Compilation topic for all languages
- Standard Conditional Defines provided by the Compiler
defined()
,exists()
and andstatic()
System Functions