SAMUEL HP


Asa Introduction

Important

This is page is very much still a work in progress

Introduction to the Asa programming language



So, you want to use Asa?

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!");
}

The Compile Time Define Operator:

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;
Note

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.

Other uses:

:: can be used for defining other things as well. Such as structs, modules, and special functions.

Defining Structs:

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

someStruct :: struct{

}

Defining Modules:

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

moduleName :: module{

}

Structs:

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();

Struct Members:

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

Struct specific functions:

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

Create:

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

Modules:

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

moduleName :: module{

}

Modules can have sub-modules to any depth:

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

        }
    }
}

Accessing:

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

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

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

moduleA.moduleB.x = 5;

TODO: I am considering how to implement public/private

One way is by making anything you want to be "private" just be put in a submodule

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

    somePublicFunc :: (){
        moduleB.x = 100;
    }
}

Or, a prefix keyword, which doesnt really fit the style of anything else

moduleA :: module{
    private x = 4;

    public somePublicFunc :: (){
        moduleB.x = 100;
    }

    private somePrivateFunc :: (){
        moduleB.x = 0;
    }
}

Or a modifier, but those only currently exist for functions and seem messy for variables

moduleA :: module{
    // Both of these feel bad
    x : int #private = 4;
    #private y : int = 4;

    somePublicFunc :: () #public; {
        moduleB.x = 100;
    }

    somePrivateFunc :: () #private; {
        moduleB.x = 0;
    }
}

Or a type modifier like const, but that also reads weird, and makes less sense when there's no specified type

moduleA :: module{
    x : private int = 4;
    y : private = 2;

    somePublicFunc :: public () {
        moduleB.x = 100;
    }

    somePrivateFunc :: private () {
        moduleB.x = 0;
    }
}

Then there is the C++ way which is very un-C++ like in my opinion and ugly, but is more like "sections", similar to the sub-module idea above

moduleA :: module{

private:
    x : int = 4;
    y = 2;

    somePrivateFunc :: private () {
        moduleB.x = 0;
    }

public:
    somePublicFunc :: () {
        moduleB.x = 100;
    }
}

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:

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; }


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@ :: 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.


Compiler keywords:

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.


#import

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


#file

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:

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


#linenum

Gets the line number as an integer of it's own location in the source code file. For example:

1 // some line
2 print(#linenum); // Prints 2

#linecol

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

print(#linecol); // Prints 6 TODO: Verify this

#line

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

print(#line); 

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


#filename

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

// In a file called main.asa
print(#filename); 

The above would print 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`
TODO: I may make #typeof return a type object in the future, and make #typename do this

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


#inline

This is a function modifier that tells the compiler it should be in-lined for performance.


#replaceable

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


#hideast

This is a function modifier that just hides the function from showing in the AST verbose output. This is primarily used for compiler development.


#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 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;
}

#new

TODO: #new is for manually creating objects from their name, like: #new structName. It should be removed and replaced by an automatic create function addition.


#cast

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