Preprocessor
The preprocessor edits files before they go to the LIGO compiler. You can include commands called preprocessor directives to instruct the preprocessor to make changes to a file before the compiler receives it, such as including or excluding code and importing code from other files.
Preprocessor directives can allow you to make changes to files before the compiler processes them.
For example, the following contract has three entrypoints, but one is between #if
and #endif
directives.
The line #if INCLUDE_RESET
instructs the preprocessor to include the text between the directives (in this case, the third entrypoint) only if the INCLUDE_RESET
Boolean variable is set:
You can set these Boolean preprocessor variables with the #define
directive or by passing them to the -D
argument of the ligo compile contract
command.
For example, if the contract in the previous example is in a file named mycontract.jsligo
, this command causes the preprocessor and compiler to output a contract with only two entrypoints:
This command passes the INCLUDE_RESET
Boolean variable to the preprocessor and causes the compiler to output a contract with three entrypoints:
Viewing the preprocessor output
It's rarely necessary to view the output of the preprocessor, but if you need to see the output to debug directives, you can view the output with the ligo print preprocessed
command, as in this example:
Comments
The preprocessor ignores directives that are in comments, which prevents problems where comments in your code contain text that looks like a directive.
For example, this code is valid because the preprocessor ignores the text #endif
in the comment:
The preprocessor includes and excludes comments just like any other line of code.
String processing
The preprocessor ignores directives that are in strings, which prevents problems where strings in your code contain text that looks like a directive.
For example, this code includes a string with the text #endif
, but the preprocessor does not interpret this text as the #endif
directive:
Blank lines
When the preprocessor hides text, it includes a blank line consisting only of a newline character in place of the omitted line to keep line numbers in compiler errors consistent with the source file. Similarly, it includes a blank line in place of each preprocessor directive. These blank lines do not affect compilation.
For example, take this source file:
The output that goes to the compiler from this source file is three blank lines plus the linemarker that indicates the start of the file:
Linemarkers
As in C-based languages, the LIGO preprocessor includes linemarkers in files that it processes.
For example, if you include files with the #include
directive, the preprocessor inserts a line in the processed file to indicate where the included file starts and a line to indicate where the included file ends and the original file resumes.
The output begins with a linemarker that indicates the start of the original file that was sent to the preprocessor, in the format # <line number> "<path to file>"
.
Other linemarkers follow the same format, adding the number 1
to indicate the start of an imported file and the number 2
to indicate the end of the imported file and the resumption of the previous file.
The following example uses three files: a.txt
, b.txt
, and c.txt
.
File A includes file B and file B includes file C.
If you create these files and run the command ligo print preprocessed --syntax cameligo a.txt
, the output includes linemarkers that indicate where the files begin and end and the line number in the file that the preprocessor is on at the time:
The LIGO compiler ignores these linemarkers when it compiles the code.
Directives
These are the preprocessor directives that the LIGO preprocessor supports:
#define
and #undef
The #define
directive sets a Boolean variable (also known as a symbol) to true, and the #undef
directive unsets it, which is equivalent to setting it to false.
You can use these variables with the #if
directive to show or hide text from the compiler, as in this example:
#error
The #error
directive forces the preprocessor to stop and emit an error.
This directive can help you catch problems in complex files.
You can include an error message, as in this example:
#if
, #else
, #elif
, and #endif
These conditional directives allow you to include or exclude text conditionally.
They use a syntax similar to conditions in many other languages, starting with #if
and ending with #endif
.
Logic between these two directives can also include:
One
#else
directive before#endif
One or more
#elif
directives as a shorthand for a#else
immediately followed by an#if
For example:
The #if
and #elif
directives support basic Boolean logic, including:
||
for the disjunction ("or")&&
for the conjunction ("and")==
for equality!=
for inequality!
for negation(
and)
around expressions to specify order of operations
For real-world examples of this logic, see Dexter.
In one section, it uses different record types depending on the version of the FA standard in use (see dexter.mligo
line 84):
#import
The #import
directive prompts the preprocessor to include another file as a namespace in the current file.
For example, you can create a file with related type definitions, as in this example file named euro.jsligo
:
In another file, you can import this file, assign it the namespace Euro
, and use its definitions:
When you import a file with the #import
directive, LIGO packages the file as a namespace.
Therefore, any namespaces in the file are sub-namespaces of that namespace.
However, the namespace does not export those sub-namespaces automatically. As a result, if you import a file that contains namespaces, those namespaces are not accessible.
To work around this limitation, add the @public
decorator to the namespaces in the file.
For example, this file defines the Euro type as a namespace with the @public
decorator:
Because the namespace is public, you can access it as a sub-namespace when you import the file into another file:
For more information, see Namespaces.
#include
The #include
directive includes the entire text contents of the specified file, as in this example:
Unlike the #import
directive, the #include
directive does not package the included file as a namespace.