Compiler Directives
In Asa, there are a number of directives for modifying the behavior of the compiler during compilation. These all start with the character #. Any compiler directive is an expression that will be evaluated at compile time.
Primary Compiler Directives:
Beginner Level
This topic is intended for beginner programmers.
#import
One of the most used directives, used for importing modules by name. For example:
This imports the module Window from the directory modules/Rendering and makes its exported members available without qualifying them with the module name.
The module expression can be more complex, for example sub-directories: #import A.B.C.ModuleName;, which would be in modules/A/B/C.
You can also wildcard import modules by using the base path followed by an asterisk: #import Builtin.*;, which would import all modules of all files in the directory modules/Builtin/.
#import_qualified
Like #import, this imports the module, but #import_qualified requires qualified access through the module name, like Foo.someFunc();.
Both #import and #import_qualified look in multiple locations for the specified module:
- First, they look in the local directory from where they are called. If there is a matching module (including all path components), then it will stop there.
- If it does not find a matching module locally, it will look in the global modules folder, which is installed next to the
asaexecutable. This allows you to override builtin modules for specific use cases.
#embed
This is similar to #import, but instead of only including a single module, it loads the entire file. Also, it does so by an explicit path. For example:
This would compile and import the entire other file at the given path. The path is evaluated relative to the file which #embed is in.
#library
Used to specify the name of a library to link against. For example:
This would link against libssl. It is equivalent to passing -lssl with --clangoptions.
#library_static
Used to specify the name of a library to statically link against. For example:
This would link against libssl. It is equivalent to passing /path/to/libssl.a with --clangoptions.
#linenum
Gets the line number as an integer of its own location in the source code file. For example:
#linecol
Gets the line column as an integer of its own location in the source code file. For example:
#line
Gets the line contents as a string at its own location in the source code file. For example:
#filepath
Gets the full path of the source code file as a string. For example:
#funcname
Gets the name of the function it is in as a string. For example:
#modulename
Gets the name of the immediate module it is in as a string. For example:
The above would print moduleA. But in the case of nested modules like so:
It would print the name of the module it is immediately within, moduleB.
#asaversion
Gets the version of the Asa compiler as a string as it was when compiled. For example:
The above would print the current version string of the asa compiler.
#counter
Acts as an automatically incrementing integer. For example:
#nameof(I)
Gets the name of an identifier as a string. For example:
#typeof(T)
Gets the type name of an identifier or type as a string. For example:
#sizeof(T)
Gets the size in bytes of an identifier or type as an integer. For example:
Advanced Compiler Directives:
Intermediate Level
This topic is intended for intermediate programmers.
#extern
This is a method to reference external functions from libraries. For example:
The above would allow you to use the printf function from the C standard library. #extern use is simply stating the function name and parameters as they are defined, and not including a body.
#compiles(expression)
Checks at compile time if expression will compile successfully, or cause a compiler error. Returns true if it compiled without error, and false if it failed. The error message is thrown out, and never printed to stdout or stderr.
#variant
This is a function modifier that creates variants of the function. For example:
Then you can use it like so:
This looks like arguments with extra steps, but the main difference is that it permanently creates a completely different function for each combination during the compilation process. So the above would have equivalent code to:
This is useful if you want to allow for a function to work with multiple types:
#set_attribute(ast_node, attribute_name, value)
Sets or changes the given AST node's attribute to the new value
#setflag(name, value)
Sets a global flag to the given value.
#getflag(name)
Returns the value of the named global flag.
#recommend_options(string)
Sets compiler options at compile time. For security, it asks the user with a prompt before applying, and the user can reject the options. Uses the same syntax as options applied through the command line:
The same as this command:
#error(string, ast_node)
Causes a compiler error at compile time, using the same error system that the compiler uses for it's builtin errors. It uses the string as the error message, and the ast_node as the line context shown in the error message.
Note
This requires that the second argument is a reference to an AST node. It will work if you pass something else, such as a variable name: #error("...", x);. But it will be a new node generated for this line, rather than for the definition or set of x
#warning(string, ast_node)
Causes a compiler warning at compile time, using the same error system that the compiler uses for it's builtin errors. It uses the string as the warning message, and the ast_node as the line context shown in the warning message.
Note
This requires that the second argument is a reference to an AST node. It will work if you pass something else, such as a variable name: #warning("...", x);. But it will be a new node generated for this line, rather than for the definition or set of x
#stack_push(string, ast_node)
This pushes a given AST node to the named stack. If the stack does not exist yet, it creates a new one. Example:
Note
Like mentioned above, the second argument is an AST node. If you pass a literal or expression, this will pass a new AST node representing that expression.
#stack_pop(string)
This pops a single item off of the given stack. It does not return anything.
#stack_last(string)
This returns the last AST node added to the named stack.
#print_ast(ast_node)
This prints out the given AST node to the terminal.
Note
For compile time equals definitions using ::, it is the AST of its value, rather than its definition.
#parent(ast_node)
This returns the parent node of the given AST node. You can use this for cases like above, where you want the AST node of the definition rather than the right-hand-side.
#print(string)
Different from regular print(); function, in that it prints the given string to the terminal at compile time only. Does nothing at runtime.
#printl(string)
Different from regular printl(); function, in that it prints the given string to the terminal with a newline at compile time only. Does nothing at runtime.
#if(condition, ast_node)
Evaluates condition as a boolean. If it is "true", the ast_node is included in compilation. If it is "false", it is ignored.
#make_directive(name, (ast_arguments), ast_node)
Creates a new custom compiler directive with the given name, arguments, and body.
#return(ast_node)
Used in conjunction with #make_directive to let the expression result in a given AST node.
#context
Returns the AST node of the current context. For example, if used inside a ::asa #make_directive definition, #context will be the node where it is called.