Directives - IS4Code/Sona GitHub Wiki
Directives are pieces of extra syntax placed either alone or attached to other elements, which affect the interpretation of the code in a specific way, depending on the kind of the directive used.
The syntax of a directive consists of the #
character, followed immediately by the name of the directive and whitespace. If this collides with the usage of the #
operator, using whitespace or parentheses after it fixes it.
Inside a directive, the normal parsing rules do not apply:
- Whitespace is significant and may affect the result of the directive.
- The directive is terminated by a newline or another
#
. To prevent it from terminating by a line end, it can be escaped with\
. - Some directives allow arbitrary expressions, in which case parser rules may also extend them across multiple lines.
- Some standard keywords may not be recognized, or other directive-specific keywords may be introduced.
Note that while the directive syntax resembles that used by the preprocessor in other languages, there is no separate preprocessing phase ‒ some information is expressible only through a directive, and the parser reacts to it directly.
Attributes offer means to attach information to existing code elements. They come in two forms: local, starting with #:
, #item
, #type
, #method
, #property
, #return
, #param
, #field
, #event
, or #constructor
, followed immediately by a code element, and global, using #program
, #entry
, #assembly
, or #module
, usable as a top-level statement.
Inside the attribute syntax, using any sorts of parentheses ((…)
, […]
, {…}
) reverts back to the usual parsing rules within the parentheses, allowing newlines and insignificant whitespace as if outside the attribute. Additionally, a line ending with a comma (,
) extends the directive to the next line.
Inline source code can be embedded using #inline
. It can be used as a stand-alone statement, expression, type, or pattern, to insert a piece of F# code in its place, up until #endinline
.
Pragmas serve to configure the compiler to output slightly different code from parts of its syntax. A pragma directive starts with #pragma
, followed by name of the specific pragma and its parameters. Other than affecting the code that follows it, a pragma has no effect on the point of code where it is used itself, basically equivalent to whitespace.
pragma:
'#pragma' pragma_name pragma_args;
Each individual pragma type has its own state stack, with the top state being the one that currently affects the code. This stack can be manipulated using #pragma push
and #pragma pop
; using the pragma without these directives always affects the top state.
push_pragma:
'#pragma' 'push' name args;
#pragma push
is used to push a new state on top of the stack of the pragma identified by name
. Its effect is the same as using #pragma name args
, but can be reverted using #pragma pop name
.
Example:
#pragma push echo printf
echo "Test"
#pragma pop echo
echo "Test"
This configures the echo
pragma to use printf
for the next line, and restores it afterwards to the default (printfn
).
push_pragma:
'#pragma' 'pop' name;
#pragma pop
is used to remove the last pushed state of name
and restore the previous state. It is an error if used without the corresponding prior #pragma push
.
once_pragma:
'#pragma' 'once' name args;
#pragma once
can serve as a replacement for #pragma push
in situations when the effect of the pragma is supposed to apply to only one code element. The program is invalid if a normal #pragma
or #pragma push
is used while a #pragma once
for that same pragma is in use, or if the program ends and there is no code element that utilizes it.
echo_fragma:
'#pragma' 'echo' name;
#pragma echo
configures the name of the function used by the subsequent echo
statements, such as printf
, printfn
, eprintf
, eprintfn
.
newline_pragma:
'#pragma' 'newline' string;
#pragma newline
sets the newline sequence used when outputting non-verbatim strings spanning multiple lines. It takes a string argument, such as "\n"
or "\r\n"
.
The particular value of #pragma newline
is consistent across any single string literal, even if its value is changed anywhere inside of it.
type_kind_pragma:
'#pragma' ('tuple' | 'record' | 'option') ('class' | 'struct');
These three pragmas are used to affect what kind of implementation type is used by default for tuples, records, and options, taking a struct
or class
argument to indicate whether these types should have value or reference semantics.
type_kind_pragma:
'#pragma' 'collection' ('array' | 'list');
Changes the implementation type of collection expressions and types ([]
) between arrays and lists.
With #pragma collection array
(the default):
- An expression
[a, b, …]
creates a value of typearray<>
. - The types
[]
andT[]
correspond toarray<>
andarray<T>
.
With #pragma collection list
:
- An expression
[a, b, …]
creates a value of typelist<>
. - The types
[]
andT[]
correspond tolist<>
andlist<T>
.
recursive_pragma:
'#pragma' 'recursive' ('on' | 'true' | 'enabled' | 'off' | 'false' | 'disabled');
#pragma recursive
changes the semantics of all variable bindings to be able to refer to the newly bound variables from within the expressions that initialize them.
forwardref_pragma:
'#pragma' 'forwardref';
#pragma forwardref
enables forward referencing of elements in packages that are in its effect. This allows package-level code to refer to elements in the same package declared further in the code. This has some limitations:
- It is not possible to call forward-referenced
inline
functions. - Initialization of variables that depend on variables declared later may require runtime checks ensuring the variables are properly initialized before use.