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.


#import

One of the most used directives, used for importing modules by name. For example:

#import Rendering.Window;
#import Rendering.Drawing.Line;

This imports the module Window in 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 imports the module but 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 asa executable. 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:

#embed "./otherFile.asa";

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:

#library "ssl";

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:

#library_static "/path/to/libssl.a";

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:

// some line
print(#linenum); // Prints 4

#linecol

Gets the line column as an integer of its own location in the source code file. For example:

print(#linecol); // Prints 6

#line

Gets the line contents as a string at its own location in the source code file. For example:

print(#line);

The above prints print(#line);. (Putting this in a comment would be recursive.)


#filepath

Gets the full path of the source code file as a string. For example:

// In a file at /home/user/main.asa
print(#filepath);

The above would print /home/user/main.asa.


#funcname

Gets the name of the function it is in as a string. For example:

someFunc :: int(){
    print(#funcname);
    ...
}

The above would print someFunc.


#modulename

Gets the name of the immediate module it is in as a string. For example:

moduleA :: module{
    print(#modulename);
}

The above would print moduleA. But in the case of nested modules like so:

moduleA :: module{
    moduleB :: module{
        print(#modulename);
    }
}

The above would print moduleB.


#asaversion

Gets the version of the Asa compiler as a string as it was when compiled. For example:

print(#asaversion);

The above would print the current version string of the asa compiler.


#counter

Acts as an automatically incrementing integer. For example:

print(#counter); // Prints 0
print(#counter); // Prints 1
print(#counter); // Prints 2

#nameof(I)

Gets the name of an identifier as a string. For example:

someVar : int = 0;
print(#nameof(someVar));

The above would print the string someVar.


#typeof(T)

Gets the type name of an identifier or type as a string. For example:

someVar : int = 0;
print(#typeof(someVar)); // Would print `int`
print(#typeof(string));  // Would print `string`

#sizeof(T)

Gets the size in bytes of an identifier or type as an integer. For example:

someVar : int = 0;
print(#sizeof(someVar)); // Would print `4`
print(#sizeof(int8));    // Would print `1`

#extern

This is a method to reference external functions from libraries. For example:

#extern printf :: int32 (s : const *char, ...);

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.

printl(#compiles(1 + 1)); // Prints `true`
printl(#compiles(undefinedVariable + 1)); // Prints `false`

#variant

This is a function modifier that creates variants of the function. For example:

foo :: ()
    #variant w = 0;
{
    printl(w);
}

Then you can use it like so:

foo<w=7>(); // -> 7
foo<w=2>(); // -> 2

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:

foo :: (){
    printl(7);
}

foo :: (){
    printl(2);
}

This is useful if you want to allow for a function to work with multiple types:

foo :: T(v : T)
    #variant T = int;
{
    return v * 2;
}

#set_attribute(ast_node, attribute_name, value)

Sets or changes the given AST node's attribute to the new value

@public:
x : int = 0;

#set_attribute(#parent(x), "public", false);

#setflag(name, value)

Sets a global flag to the given value.

#setflag(FOO, true);
// OR:
#setflag FOO true;

#getflag(name)

Returns the value of the named global flag.

#getflag(FOO);

#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:

#recommend_options("-O3 -w all");  // Applies optimization level 3 and enables all warnings

The same as this command:

asa main.asa -O3 -w all

#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.

#error("This is an error", SOME_AST);

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.

#warning("This is a warning", SOME_AST);

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:

#stack_push("some stack", true);

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.

// "some stack" => {0, 4, 2, 1}

#stack_pop("some stack");

// "some stack" => {0, 4, 2}

#stack_last(string)

This returns the last AST node added to the named stack.

// "some stack" => {0, 4, 2, 1}

printl(#stack_last("some stack"));  // Prints: 1

#print_ast(ast_node)

This prints out the given AST node to the terminal.

x :: 4;

#print_ast(x);

// Prints:
// :(Expression_Term){
//     6:(Integer_Node){}
// }
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.

x :: 4;

#print_ast(#parent(x));

// Prints:
// x:(Compiler_Define){
//     :(Expression_Term){
//         6:(Integer_Node){}
//     }
// }

#print(string)

Prints the given string to the terminal at compile time.

#print("This prints only during compilation");

#printl(string)

Prints the given string to the terminal at compile time with a newline.

#printl("This prints only during compilation");

#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.

#if(true, printl(4));  // The print statement is included in the compiled program
x :: { i : int = 9 };
#if(1 == 2, x);  // The code `i : int = 9` is not included in the program

#make_directive(name, (ast_arguments), ast_node)

Creates a new custom compiler directive with the given name, arguments, and body.

#make_directive("custom_add", (A, B), {
    #return A + B;
});

#custom_add(1, 2);  // Returns AST node of {1+2}

#return(ast_node)

Used in conjunction with #make_directive to let the expression result in a given AST node.

#make_directive("custom_add", (A, B), {
    #return A + B;
});

#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.

#make_directive("custom_add", (A, B), {
    #printast(#context);
});

#custom_add(1, 2);  // `#context` refers to this line