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 or COCOA).
  • 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 a Subs 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") is False but defined("DOTNET") is true, the compiler will emit the If clause with the at-runtime check of Environment.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 and TRACE 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 the Debug 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 and Release, 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