Important
This is page is very much still a work in progress

Asa is a systems programming language built to be used similarly to C++, but be a good replacement. It aims to improve upon many of the shortcomings of that aging language, without sacrificing performance.
main :: (){
printl("Hello World!");
}
One of the operators you will be using most often in Asa is the compile time define. This is defined as the double colon, ::.
It shares many of the characteristics of the set operator (=), and sometimes their functionality even overlaps. It can also be compared to the #define preprocessor keyword from C or C++, although Asa does not have a preprocessor.
Compile time define is used to assign an expression to a name. The most basic example would be:
x :: 5;
This code will evaluate to the exact same as x = 5;
Of course, more interesting expressions are better. Let's put a lambda expression on the right side:
x :: int(){ return 5 }
Now this is a function.
:: can be used for defining other things as well. Such as structs, modules, and special functions.
You can create a struct by writing the name, and defining it as a struct expression:
someStruct :: struct{
}
You can define a module by writing the name, and defining it as a module expression:
moduleName :: module{
}
You can create a struct by writing the name, and defining it as a struct expression:
someStruct :: struct{
// ...
}
Then to create an instance:
y : someStruct = someStruct();
Member variables and functions are defined as normal. Functions, though, have slightly different behavior.
For example:
someStruct :: struct{
x : int = 10;
memberFunction :: (v : int){
this.x = v;
}
}
The difference with functions defined inside of a struct is that that they can only be called with the access operator (.), but they can also access members within the struct itself. To do this, you must use the this keyword, which references the instance the function is within.
Then member access is as normal like:
s : someStruct = someStruct();
printl(s.x); // -> 10
s.memberFunction(3);
printl(s.x); // -> 3
There are some functions that have specific names and functionality for handling structs.
create :: someStruct(){
// ...
}
In Asa, most things you write are "expressions". In other words, the individual components should be able to be evaluated all on their own. For example, take the following function:
functionName :: (){
}
Given the above function, we can split it into its sub-expressions:
The name:
functionName
and a lambda:
(){}
The lambda is simply an unnamed function. You can create one with parentheses containing the function arguments, followed by curly braces containing the body. Like: (x : int){}.
If you want the lambda to have a return value, it should be preceded by the type. Like: int(x : int){ return x+1; }
Just like in C++, there are some special ways to define functions for certain use cases.
Operator overloading is used to override builtin behavior or add new behavior to existing or new symbols. For example:
operator@ :: int(x : int, y : int){
return x * y;
}
printl(3 @ 1);
// -> Outputs 3
Defining an operator overload is done with the following syntax:
operator<symbol> :: <return type>(<Left value>, <Right value>){
}
The <symbol> can be any ASCII special character, and can be a double character as well. Example: $ or $$. The only symbols you cannot overload are the compiler define :: and a few punctuation symbols. Also, the list of available symbols is predefined, so some combinations may be missing.
In Asa, there are a number of keywords for modifying the behavior of the compiler during compilation. These all start with the character #. Any compiler keyword is an expression that will be evaluated at compile time.
#importOne of the most used keywords, 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. 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 looks in multiple locations for the specified module. First, it looks in the local directory from where it is 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.
#fileThis 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:
#file "./otherFile.asa";
This would compile and import the entire other file at the given path. The path is evaluated relative to the file which #file is in.
#externThis 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.
#inlineThis is a function modifier that tells the compiler it should be in-lined for performance.
#replaceableThis is a function modifier that tells the compiler another function with the same identity can be defined. While function overloading typically requires a different identity, if #replaceable is used in the original, it can be overloaded without an error. This will effectively delete the #replaceable function and use the newly defined one.
#hideastThis is a function modifier that just hides the function from showing in the AST verbose output. This is primarily used for compiler development.
#variantThis 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 the following (except for the identities being the same, which would throw an error):
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;
}
#newTODO: #new is for manually creating objects from their name, like: #new structName. It should be removed and replaced by an automatic create function addition.
#castTODO: #cast is for manually casting a value to a builtin type, like: #cast 5 : float. It should be removed and replaced by an automatic cast function addition.