Introduction to Asa

Beginner Level

This topic is intended for beginner programmers.

Asa is a systems programming language built to be used in a similar fashion to C++, but be a good replacement. It aims to improve upon many of the shortcomings of that aging and bloated language, without sacrificing performance.

Hello World!


TODO:
Update this with whatever imports are actually needed, when the std library is more organized. Currently, importing all with Builtin.* should get the printl function.
hello.asa
#import Builtin.*;

main :: (){
    printl("Hello World!");
}
Shell
$ asa ./hello.asa
$ ./build/hello
Hello World!

See:


Comments

Comments in Asa follow the same rules as those in C/C++.

Single line comments begin with two forward slashes: //

Asa
// This is a comment

Everything before the // is considered code, and everything after is considered a comment, until it reaches the end of the line:

Asa
printl("this is code!");  // this is a comment
printl("back to code again!");

Multi-line comments begin with /*, and end with */

Asa
/* This is a multi
        line
             comment!
*/

Everything between /* and */ is considered a comment, including newlines. The comment only ends when it reaches the close symbol, */.

Due to how multi-line comments are parsed, this means that you can also place them within lines of code without issue:

Asa
if (/* some spurious comment */ true){
}

Identifiers

Identifiers in Asa may contain any alphanumeric character or underscore, but must begin with an alphabetic character or underscore.

Asa
this_is_an_identifier = 5;
thisIsAlso = 2;
_this2 = 1;
EVEN_THIS = 6;

Builtin Operators


TODO:
This is a stub, and does not contain all operators.
Action Symbol Description Example
Addition +
+=
Adds two values together 1 + 2 == 3;
x += 2;
Subtraction -
-=
Subtracts one value from another 5 - 3 == 2;
x -= 2;
Negation - Gives the opposite signed value x = -3;
-3 == 0-3;
Multiplication *
*=
Multiplies one value by another 4 * 3 == 12;
x *= 5;
Division /
/=
Divides one value by another 12 / 3 == 4;
x /= 2;
Modulo %
%=
Gives the remainder after division 5 % 3 == 2;
x %= 2;
Left bit shift <<
<<=
Shifts the bits of the value to the left by the given amount 2 << 1 == 4;
x <<= 3;
Right bit shift >>
>>=
Shifts the bits of the value to the right by the given amount 8 >> 1 == 4;
x >>= 2;
Bitwise And &
&=
Performs a logical bitwise AND on the bits of two values 0b101 & 0b111 == 0b101;
x &= 0b1001;
Bitwise Or |
|=
Performs a logical bitwise OR on the bits of two values 0b100 | 0b011 == 0b111;
x |= 0b01;
Bitwise Xor ^
^=
Performs a logical bitwise XOR on the bits of two values 0b101 ^ 0b011 == 0b110;
x ^= 0b01;
Bitwise Not ~
~=
Performs a logical bitwise NOT on the bits of a value ~0b011 == 0b100;
x ~= 0b01;

The Compile Time Define Operator

One of the operators you will be using most often in Asa is the compile time define. This is the double colon, ::.


It shares many of the characteristics of the set operator (=), and sometimes their functionality even overlaps.

Importantly, anything defined with this operator is immutable. This means that it cannot change during runtime. But, that does not mean it cannot be re-defined at compile time. In that way, it can somewhat also be compared to the #define preprocessor keyword from C or C++.


Compile time define is used to assign any expression to a name. A very basic example would be:

Asa
x :: 5;

This code will evaluate the exact same as:

Asa
x : const = 5;

See type modifiers

Of course, more interesting expressions are better. Let's put a lambda expression on the right side:

Asa
x :: int(){ return 5; }

Now this is a function.

Other uses

:: is used for defining other things as well, such as structs, modules, and special functions.

See:


Structs

You can create a struct by writing the name, and defining it as a struct expression:

Asa
someStruct :: struct{
    // ...
}

Then to create an instance:

Asa
y : someStruct = someStruct();

Struct Members

Member variables and functions are defined as normal. Functions, though, have slightly different behavior. For example:

struct_members.asa
someStruct :: struct{

    x : int = 10;

    memberFunction :: (v : int){
        this.x = v;
    }
}

The difference with functions defined inside of a struct is 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:

struct_members.asa (continued)
s : someStruct = someStruct();

printl(s.x); // -> 10

s.memberFunction(3);

printl(s.x); // -> 3
Shell
$ asa struct_members.asa
$ ./build/struct_members
10
3

Struct Specific Functions

There are some functions that have specific names and functionality for handling structs.

create

Asa
create :: someStruct(){
    // ...
}

Modules

You can define a module by writing the name, and defining it as a module expression:

Asa
moduleName :: module{

}

Modules can have sub-modules to any depth:

Asa
moduleA :: module{
    moduleB :: module{
        moduleC :: module{

        }
    }
}

Accessing

You can use the access operator . to use members of a module.

Asa
moduleA :: module{
    moduleB :: module{
        x = 4;
    }
}

Accessing the variable x from outside moduleA would be done like so:

Asa
moduleA.moduleB.x = 5;

Expressions

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:

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

Asa
(x : int){}

If you want the lambda to have a return value, it should be preceded by the type. Like:

Asa
int(x : int){ return x+1; }

Now, neither of those pieces of code do anything. So giving that lambda expression a name with the compiler define operator :: lets you easily use it anywhere:

Asa
someFunc :: int(x : int){ return x+1; }

someFunc(3); // Returns 4

But the name someFunc is just an alias for the lambda expression. You could have evaluated it by just appending parenthesis to the end of the lambda itself and passing the arguments:

Asa
someFunc :: int(x : int){ return x+1; }(3);  // Returns 4

Special Function Definitions

Just like in C++, there are some special ways to define functions for certain use cases.

Operator Overloading

Operator overloading is used to override builtin behavior or add new behavior to existing or new symbols. For example:

operator_overload.asa
operator(@) :: int(x : int, y : int){
    return x * y;
}

printl(3 @ 2);
Shell
$ asa operator_overload.asa
$ ./build/operator_overload
6

Defining an operator overload is done with the following syntax:

operator(<symbol>) :: <return type>(<Left value>, <Right value>){
}

The <symbol> can be any overloadable operator token. Examples: +, ==, [], or ... The compile time define :: and a few punctuation symbols cannot be overloaded. Also, the list of available symbols is predefined, so some combinations may be missing.


Strings

Strings are defined in the String module. They are simply an address to a null-terminated character array, and a length:

Asa
// This is all a string is:
string :: struct {
    address : *char = ?;
    length : uint32 = ?;
}

You can declare a string variable like any other struct:

Asa
s : string;

And you can use string literals to create a string with a value:

Asa
s : string = "This is a string!";

A string literal is just any text surrounded by double quotes.

Formatted Strings

Like a regular string, formatted strings, or f-strings for short, are surrounded by quotes. But f-strings are also preceded by an f character:

Asa
s : string = f"this is an f-string";

They are not a different data type from string, rather they are just a different way to process string literals. They allow you to easily concatenate data with strings in a much shorter way.

For example, the following is a standard way to insert multiple pieces of data into a string:

manual_concatenation.asa
x : int = 5;
y : float = 1.1;
z : string = "other str";

combination : string = "The combined values are: x:" + string(x) + ", y:" + string(y) + ", z:" + z;
Shell
$ asa manual_concatenation.asa
$ ./build/manual_concatenation
The combined values are: x:5, y:1.1, z:other str

This is a lot of code to do something so simple! So instead we can use an f-string:

fstring_concatenation.asa
x : int = 5;
y : float = 1.1;
z : string = "other str";

combination : string = f"The combined values are: x:{x}, y:{y}, z:{z}";
Shell
$ asa fstring_concatenation.asa
$ ./build/fstring_concatenation
The combined values are: x:5, y:1.1, z:other str

The output is the same, but we no longer need to manually cast the values into strings, that is done automatically. Also, we do not need to manually concatenate multiple strings together using +. Just put the expression in between curly braces, like {5+3}, and it will be evaluated, casted to string, and concatenated in place.


F-strings do not give any performance benefit compared to its manual counterpart, as it is essentially expanding into the manual form under the hood.