ASPL is a simple yet powerful modern cross-platform programming language.
It is especially suited for people who are just getting into programming, but can also be a powerful tool for more experienced programmers.
ASPL has (obviously) been influenced by multiple different other languages, most notably C# and V.
Going through this introduction will take you under an hour, but you will learn the most important skills for developing software with ASPL.
Please keep in mind that this document is not (yet) a complete walkthrough of the language and there is still a lot of information missing.
Note
This introduction is written very beginner-friendly, but if you aren't familiar with software engineering at all yet, you might want to learn the basics of programming through other media (e.g. blog posts or videos) first.
ASPL supports most modern operating systems and easily cross-compiles between them.
Please refer to the installation instructions to install ASPL on your system.
print("Hello World!")
Save this code to a file and run it with aspl run file.aspl
.
That's it. You have just written your very first ASPL program! 🎉
ASPL supports two styles of comments:
If a line starts with //
, everything after that is ignored by the ASPL parser and treated as a comment.
For example:
print("Hello World!") // You can write whatever you want here
// This is a comment too
print("But this is executed again!")
If you want to span a comment over multiple lines (very useful for commenting out pieces of code temporarily), you can use /*
to start a comment and */
to end it.
Note that multi-line comments can be nested in ASPL.
print("Hello World!")
/*Start of comment
This is a comment
print("This is ignored since it's in a comment")
/*
Nested comment
*/
End of comment*/
print("This is executed again")
Functions are a computer science concept inspired by mathematical functions.
They can take an input value, process it and return an output.
Some functions don't return anything though, for example print
- it only writes the text you gave it to the console.
As already shown above, you can invoke a function like this:
myFunction("argument 1", 42, "argument 3") // takes 3 arguments
myOtherFunction() // takes 0 arguments
Functions that return something can be used as an expression and their return value can for example be passed to other functions:
print(multiply(20 * 20)) // prints 400
A statement is an instruction to the computer. For example, while
, return
, function declarations, ...
An expression, on the other hand, is also like an instruction, but it returns a value, e.g. an access to a variable, mathematical operators, the this
keyword, ...
Expressions can be passed to functions, assigned to variables, used with operators and so on.
If you want to refer to any instruction, whether it's an expression or a statement, use the term node
.
ASPL is statically typed. That means that all variables are associated with a certain type, e.g. a string or an integer.
You can declare a variable with the var
keyword, optionally followed by its types, the variable name, and its initial value.
All variables in ASPL have an initial value. If no types are given, the type is inferred from the initial value.
var int i = 0
var float f = 1f
var s = "Hello"
Updating a variable works like this:
var i = 0
print(i)
i = 1
print(i)
The output of the above program is:
0
1
Note that you cannot assign a value of a wrong type to a variable:
var s = "Hello"
s = 0 // error, the variable 's' can only hold strings
ASPL has a very advanced and powerful type system.
Every expression and everything holding an expression (e.g. a variable) can have either a single type (e.g. literals or simple variables) or of a multitype:
var int i = 0 // i can hold values of the type integer (since int is an alias to integer)
var int|float x = 0f // x can hold values of either integer or float, even though it's initialized with a float here
If something expects a certain type (e.g. a variable or function parameter), it will fail to compile if any of the types of the expression is incompatible with the expected type:
var int|float x = 0f
var string|float y = 0f
x = y // error, y could be of type string, but x cannot hold strings
Type aliases allow to refer to a certain type with another name:
var int a = 0
var integer b = 0
a = b // no error, a and b are both of type integer
All built-in type aliases are:
bool
=> boolean
int
=> integer
It is currently not possible to declare custom type aliases.
All types in ASPL are non-nullable by default:
var int i = 0
i = null // error, i cannot hold null
If you want to allow a variable (or property, etc.) to hold null, you can append a ?
to its type:
var int? i = 0
i = null // no error, i can hold null
Note that the ?
is just a shorthand for type|null
, so the following is also valid:
var int|null i = 0
i = null // no error, i can hold null
If you are sure that a value of type type?
is not null in a certain situation (for example because you checked it before), you can use the ?!
("null forgiving") operator to force the compiler to treat it as a value of type type
:
var string? s = "Hello"
if(s != null){
print(s?!.length) // no error, s is assured to not be null here
}
Note that the ?!
operator is just a shorthand for a cast to type
, so the following is also valid:
var string? s = "Hello"
if(s != null){
print(string(s).length) // no error, s is assured to be not null here
}
The oftype
expression checks if a certain expression is of a certain type at runtime:
var any x = 0
print(x oftype int) // true
print(x oftype string) // false
The if
statement is an essential control flow structure in computer programming. It only executes a certain block of code if a condition is met, i.e. a given expression evaluates to true:
if(true){
print("Will certainly be printed")
}
if(false){
print("Will certainly not be printed")
}
Combined with, for example, variables or user input, this allows a programmer to dynamically react to a program's environment:
if(input("Do you like ASPL? ") == "y"){
print("Great!")
}
For the above example, it would also be useful to execute some other code if the condition was not met. For this, there is the else
keyword that can only follow after an if
statement:
if(input("Do you like ASPL? ") == "y"){
print("Great!")
}else{
print("Please let us know how we can improve!")
}
Furthermore, if you want to check for multiple possible scenarios, you might want to use elseif
:
var requestedProduct = input("Which product do you want to buy? ")
if(requestedProduct == "apple"){
print("You successfully bought an apple")
}elseif(requestedProduct == "banana"){
print("You successfully bought a banana")
}else{
print("I'm sorry, but we don't offer the product: " + product)
}
Similar to the if
statement, a while
statement is a conditional control structure. But instead of executing the code in the body only once, it will be executed as long as the condition evaluates to true:
// This never evaluates to true and thus directly skips the body
while(false){
print("Will never be executed")
}
// Prints all numbers from 0 to 9
var i = 0
while(i < 10){
print(i)
i++
}
// This will repeat the body forever
while(true){
print("Inside infinite loop...")
}
There is no else
for while loops.
A repeat
statement is like a while statement, but instead of taking a condition, it takes an integer specifying how often the code in the body should be repeated:
repeat(100){
print("Repeat...")
}
You can also create a variable that will automatically hold the value of the current iteration:
repeat(42, i = 0){
print(i)
}
As you can see, you can (and should) even specify the start value of the variable.
Assertions are statements that are used to make sure certain conditions are met. If they are not met, the program will crash and print the location of the failed assertion.
They are used for debugging purposes and can also help in documentation.
assert true // ignored, no error
assert false // error, the assertion failed
A string is simply a sequence of characters, as in most other programming languages. However, ASPL strings are special in two ways:
- They are immutable, i.e. you cannot change a string after it has been created
var s = "Hello"
s[0] = "h" // error, strings are immutable
s = "h" + s.after(0) // this is the correct way to change a string
This makes a lot of code much cleaner, less error-prone, and allows for better optimizations.
- They are natively Unicode, i.e. they can hold any character from any language:
var s = "👋"
print(s) // prints 👋
assert s.length == 1
Warning
Native Unicode strings are NOT fully implemented in ASPL yet. For example, the length
property currently returns the number of bytes, not the number of characters. This is most likely to change in the future.
A list contains a variable amount of elements of the same type:
var list<string> l = ["Hello", "World", "!"]
print(l) // ["Hello", "World", "!"]
You can access an element in the list through indexing:
var list<string> l = ["Hello", "World", "!"]
print(l[0]) // Hello
print(l[2]) // !
Note: Indices start at 0 in ASPL (like in most other languages)!
Adding elements to the list works using the .add
method:
var list<string> l = ["Hello", "World"]
l.add("!")
print(l) // ["Hello", "World", "!"]
Removing elements can be either done by value or by index:
var list<string> l = ["Hello", "World", "!"]
l.removeAt(0)
l.remove("!")
print(l) // ["World"]
A map is similar to a list, but contains pairs of elements instead of individual elements.
In a map, each pair contains a key and an associated value; you may know this concept from other languages under the terms dictionary
or associative array
.
var map<string, int> m = {"Universe" => 42}
print(m["Universe"]) // 42
m["zero"] = 0
print(m["zero"]) // 0
m.removeKey("zero")
print(m) // {"Universe" => 42}
m.removeValue(42)
print(m) // {}
As already described above, a function is a block of code that accepts certain arguments, which are then stored in special variables called "parameters". Functions also may or may not return a single value.
There are the following built-in functions:
print
- write a string to the console and start a new line after that (except if the second parameter is false)input
- optionally print a prompt to the console and then return the user inputkey
- optionally print a prompt to the console and then return the key the user has pressedrange
- return a list containing all numbers between and including the two given argumentsexit
- stop the program and return a given exit code to the operating systempanic
- print an error message to the console and exit with code 1; see here for more information on panics
Moreover, you, and of course also developers of code libraries, can define their own custom functions.
This makes it possible to split a program into several smaller subroutines and reuse code. Because of this, functions are an essential concept in virtually every programming language today.
function hello(){
print("Hello World!")
}
The above code declares a function called hello
that prints Hello World!
to the console when executed.
It can be invoked like this:
hello()
Another example with parameters:
function greet(string person){
print("Hello " + person)
}
greet("Jonas") // Hello Jonas
greet("Marie") // Hello Marie
A callback is an anonymous function:
var greet = callback(string name){
print("Hello " + name + "!")
}
greet.invoke("John") // Hello John!
// Alternative (and preferred) syntax for invoking a callback
greet.("John") // Hello John!
In this case, the callback itself does not have a name, although it is stored inside a variable called greet
.
A callback can also be used as a closure.
That means that callbacks can access the variables in their creation scope, even after it has already been popped.
Furthermore, this
also points to the instance in whose methods the callable was created.
function create() returns callback{
var num = 42
return callback(){
print(num)
}
}
var callback c = create()
c.invoke() // 42
In ASPL, the term module
is used for code libraries, i.e. files that contain e.g. functions and classes and are intended to be used together in other applications.
The modules of the standard library (stdlib
) are automatically shipped with every complete ASPL installation.
Of course, programmers can also create their own third-party modules.
Modules are imported using the import
statement:
import math
print(math.sqrt(9)) // 3
Currently, modules do not need any special structure, but they might require a module.json
or something similar in the future.
You can install modules that are not in the standard library by using the install
command, for example: aspl install https://github.com/user/repo
These modules are stored in the .aspl_modules
folder in the user's home directory.
The modules of the standard library are located in the stdlib
folder of the ASPL installation.
You can organize your code into folders and modules. To access functions and types defined in other folders, you can import them:
project/folder/hello.aspl
:
function hello(){
print("Hello World!")
}
project/main.aspl
:
import folder
folder.hello() // Hello World!
Namespaces are always implicit in ASPL and inferred from the folder structure.
If you import a module, you automatically import the main namespace of the module. You can import other folders in the module by prefixing the name of the folder with the name of the module:
import encoding.ascii // imports the ascii folder of the encoding module
By default, all symbols, i.e. functions, methods, classes, properties and enums, are private in ASPL; this means that
- functions can only be called from within the same module they are defined in (module bound)
- classes can only be used from within the same module they are defined in (module bound)
- note that instantiating a class from outside that class is only possible if the constructor is marked as
[public]
- note that instantiating a class from outside that class is only possible if the constructor is marked as
- enums can only be used from within the same module they are defined in (module bound)
- properties and methods can only be accessed from within the same class or children of the class they are defined in (class bound)
You can export a symbol/make it public by using the [public]
attribute:
[public]
function hello(){
print("Hello World!")
}
There is also the [readpublic]
attribute, which makes a property read-only from outside the class it is defined in:
class Person{
[readpublic]
property string name
method construct(string name){
this.name = name
}
}
Note that the property can still be modified from within the class; it is not instance bound, that means that all instances of the class can modify properties that are readpublic to the class, even if those properties are in a different instance.
ASPL uses a garbage collector to automatically clean up memory after it has been used.
This means that you don't have to remember manually deallocating memory or checking if it has already been freed. It allows for much faster development times and prevents leaks as well as bugs.
Warning
The C backend currently does not ensure a strict left-to-right evaluation order for function/method/callback invocations! This is at the moment considered a bug, although the specification of the argument evaluation order may still change in the future.
Evaluation in ASPL is always left-to-right; while this may seem irrelevant in some situations, it is especially important for boolean operators like &&
(logical and):
var list<int>? l = get_list_or_null()
if(l != null && l?!.length > 0){
// ...
}
In this example, it is crucial that the l != null
side of the &&
operator is evaluated before the l?!.length > 0
side, as l?!.length
would throw an error if l
was null
.
Important
The error handling mechanisms described here are still experimental. They may contain bugs and are not fully implemented yet, which is why this feature set is currently hidden behind the -enableErrorHandling
flag.
ASPL uses a variation of the result type concept to handle errors with some influence from exception handling.
Every function (or method) that can fail has to be marked with the [throws]
attribute:
[throws]
function divide(int a, int b) returns int{
if(b == 0){
throw new ArithmeticError("Division by zero")
}
return a / b
}
As you can see above, the actual error is thrown using the throw
keyword, similar to exceptions in other languages.
The error thrown here is an instance of a class called ArithmeticError
. A declaration of this class could look like this:
[error]
class ArithmeticError{
property string message
method construct(string message){
this.message = message
}
method string() returns string{
return message
}
}
The [error]
attribute is used to mark a class as an error, and the string()
cast method has to be defined to allow the error to be printed by the ASPL runtime.
You can catch errors using catch
blocks (example 1).
If you try to catch an error from an expression inside another expression, you either have to specify a fallback value using the fallback
keyword (example 2) or escape from the catch
block and simultaneously return from the function the catch block was defined in using the escape
keyword (example 3); both work similar to return
, but "return on a different level", which is why they have been assigned separate keywords and using the regular return
is not allowed inside catch blocks
.
divide(10, 0) catch error{
print(error.message)
}
print(divide(10, 0) catch error{
print(error.message)
fallback 0
})
function reciprocal_squared(int a) returns int{
var r = divide(1, a) catch error{
print(error.message)
escape 0
}
return r * r
}
If you don't want to or cannot handle an error, you can pass it on to the caller:
[throws]
function reciprocal(int a) returns int{
return divide(1, a)! // ! is the error propagation operator
}
As you can see, the !
operator here is used to propagate the error.
Note that this requires the caller to also be marked with the [throws]
attribute.
If an error is so severe that the program cannot continue, you can use the panic
keyword to stop the execution immediately:
function divide(int a, int b) returns int{
if(b == 0){
panic("Division by zero")
}
return a / b
}
Functions that can panic only have to be marked with the [throws]
attribute if they can also throw an error. Recovering from panics is, at least currently, not possible.
If you want to make sure a certain piece of code is executed once a scope is left (via break, return, error propagation or simply after reaching the end of a block), you can use the defer
statement:
function modifyFile(){
var file = io.open_file("file.txt", "r")
defer { file.close() }
// Modify the file somehow
}
Note that...
- ...defer blocks will be evaluated in their reverse order of appearance in the code ("reverse order" like opening/closing tags in markdown languages)
- ...when returning a value, the defer blocks are executed after the return value has been evaluated
- ...defer blocks may not use the defer statement in their own body
- ...defer blocks may not use the return statement or throw/propagate any errors
- ...defer blocks may not use the break or continue statements with a level greater than their own loop depth
- ...defer blocks will not be called when the program exits directly using calls to
exit
orpanic
and thus some resources may stay open, but normally the OS will close most of them (e.g. file handles) automatically when the process exits anyway
Classes are the base of object-oriented programming in ASPL.
They encapsulate data and make it easily accessible as well as passable.
Classes can have properties and methods.
class Hello{
property string name
property int number = 5
method construct(string name){
print("An instance has been created")
this.name = name
}
method hello(){
print("Hello from " + name)
print("My number is: " + number)
}
}
You can instantiate a new instance of the Hello
class using the new
keyword:
var Hello instance = new Hello("Daniel")
instance.hello()
The above program will output:
An instance has been created
Hello from Daniel
My number is: 5
For information on inheritance in ASPL, see inheritance.
Static classes are classes that cannot be instantiated and can only contain static properties and methods (see static properties and static methods):
[static]
class Example{
[static]
property int number = 42
[static]
method hello(){
print("Hello World!")
}
}
Example:number = 5
Example:hello() // Hello World!
Properties are a class's way of storing data.
They are similar to variables, but bound to an instance of a class instead of a scope:
class Example{
property int number = 42 // default value is 42
}
var Example a = new Example()
var Example b = new Example()
print(a.number) // 42
print(a.number) // 42
a.number = -1
print(a.number) // -1
print(b.number) // 42
Static properties are properties that are not bound to an instance of a class, but to the class itself:
class Example{
[static]
property int number = 42
}
print(Example:number) // 42
Example:number = -1
print(Example:number) // -1
Methods are class-bound functions:
import math
class Point{
property int x
property int z
method construct(int x, int z){
this.x = x
this.z = z
}
method distanceTo(Point other){
return math.sqrt(math.pow(double(x - other.x), 2d) + math.pow(double(y - other.y), 2d))
}
}
The class Point
has two methods:
construct
: This is a special method called theconstructor
, which allows you to initialize an instance with arguments; it is automatically called every time a class is instantiateddistanceTo
: This method calculates the distance between the point it is called on and another point (which is passed to it) using the Pythagorean theorem.
Similar to static properties, static methods are methods that act on a class itself instead of an instance of that class and thus can only use static properties instead of regular ones:
class Example{
[static]
property string name = "Daniel"
[static]
method hello(){
print("Hello " + name)
}
}
Example:hello() // Hello Daniel
Classes can inherit properties and methods from other classes using the extends
keyword:
class Base{
property int number = 42
method construct(){
print("Base class constructor")
}
}
class Example extends Base{
}
var Example e = new Example() // prints "Base class constructor"
print(e.number) // 42
ASPL uses multiple inheritance instead of interfaces or traits:
class Mammal{
}
class Pet{
}
class Dog extends Mammal, Pet{
}
var Dog d = new Dog()
assert d oftype Mammal
assert d oftype Pet
Important
Multiple inheritance is a tricky concept and generally not very popular amongst other programming languages. While it simplifies a lot of things and can make intuitive sense sometimes, there are also good reasons not to use it. Because of this, ASPL might switch to single inheritance + interfaces/traits in the future, although this is currently not planned.
You can explicitly invoke the implementation of a method in certain parent classes even if the method is overridden in the current class using the parent
keyword; you might also know this concept from other languages as super
.
class Base{
[public]
method hello(){
print("Hello from Base")
}
}
class Example extends Base{
[public]
method hello(){
print("Hello from Example")
parent(Base).hello()
}
}
var Example e = new Example()
e.hello()
The above program will output:
Hello from Example
Hello from Base
Enums are a way to define a list of constants.
They are defined using the enum
keyword:
enum Color{
Red,
Green,
Blue
}
The above code defines an enum called Color
with three so-called enum fields:
Color.Red
Color.Green
Color.Blue
The values of the enum fields are assigned automatically, starting at 0.
You can also specify custom values though:
enum Color{
Red = 1,
Green = 2,
Blue = 3
}
There is a [flags]
attribute that allows for creating bitflags.
Multiple enum fields can be merged with the |
operator in these enums.
This is widely used for passing multiple options as one value.
You can check if an enum flag contains a field like this:
var Color c = Color.Red || Color.Green
print((c && Color.Red) == Color.Red) // true - the enum flag contains the Red field
Limitations:
- No field value overriding
- Only up to 32 enum fields
One of ASPL's main goals has always been seamless cross-compilation and support for as many platforms as possible. In fact, you can simply cross-compile your ASPL code to any other platform using the -os
and -arch
flags:
aspl -os windows -arch amd64 compile hello.aspl
The above command will compile hello.aspl
to a 64-bit Windows executable.
Such a sophisticated cross-compilation toolchain is only possible due to ASPL's unique architecture based on Zig CC (when using the C backend) and a mechanism called "templating" (when using the AIL backend; see the templates
folder for more information).
Additionally, ASPL uses dynamic loading of system graphics libraries (see the thirdparty/ssl
folder for more information) to enable cross-compilation of graphical applications; in theory, this can even work on macOS! In practice, this has not been properly implemented yet and might suffer from licensing issues.
This option is currently hidden behind the -ssl
flag, but will probably soon be on by default.
Note
Cross-compiling to macOS using the C backend is currently not possible due to issues with the garbage collector. This will hopefully be fixed soon.
You can compile your code in production mode using the -prod
flag:
aspl -prod compile hello.aspl
This will disable all debug features and use optimizations to make your code run faster. Furthermore, the production
conditional compilation symbol will be defined instead of debug
.
Usually, you will want to compile your code in debug mode when developing and in production mode when releasing your application.
Like in many other compiled languages, you can make parts of your code only compile in certain situations using the $if
and $else
keywords, for example:
$if debug{
print("Debug build")
}$else{
print("Production build")
}
The above code will print Debug build
when compiling in debug mode and Production build
when compiling in production mode (using the -prod
compiler option).
In addition to the debug
and production
symbols, there are also the following symbols:
windows
/linux
/macos
/android
/ios
/... - the target operating systemamd64
/arm64
/arm32
/rv64
/rv32
/i386
/... - the target architecturex32
/x64
- the bitness of the target architecture (32-bit or 64-bit)console
/gui
- the target application type (console or GUI); this is only relevant for Windowsmain
- whether the current file is part of the main module
You can also define your own symbols using the -d
compiler option:
aspl -d mySymbol compile hello.aspl
$if mySymbol{
print("mySymbol is defined")
}
ASPL currently supports two different backends: ail
and c
.
The ail
backend compiles your code to a bytecode format called AIL
(formerly an acronym for "ASPL Intermediate Language") and uses templating to bundle the AIL bytecode together with an AIL interpreter into a single executable.
Benefits of the ail
backend:
- Faster compilation
- Better debugging experience
- Not dependent on a C compiler
- Less bug prone
- Easier to develop for the ASPL team
On the other hand, the c
backend compiles your code to C code and then uses a C compiler to compile it to an executable.
Benefits of the c
backend:
- Faster execution
- Smaller executable size (sometimes)
- Even better cross-compilation
- Easier C interoperability
- Does not require prebuilt templates
You can choose a backend using the -backend
compiler option:
aspl -backend c compile hello.aspl
It's generally advised to use the ail
backend for development/debug builds and the c
backend for production; note that the ail
backend is currently the default for all builds.
The different backends are designed to be as compatible as possible, so you can easily switch between them, optimally without changing anything in your code (as long as you don't use C interoperability).
Note
This section only applies to the C backend.
Warning
This is a rather sophisticated optimization technique; while it may speed up your program significantially, it's a complicated matter and might cause issues in some cases (although that's a bug and should be reported).
ASPL allocates wrapper C-objects for all ASPL objects created in your program; i.e. if you write "Hello World"
, it will be translated to something like ASPL_STRING_LITERAL("Hello World")
(where ASPL_STRING_LITERAL
is a helper function allocating the actual wrapper object).
These objects can either be allocated on the stack or on the heap, both having their own advantages and disadvantages. Since ASPL is permanently using a GC and the C backend has been carefully designed to support both of these options, you can easily toggle between the different allocation methods.
By default, wrapper objects are allocated on the stack. You can change this using the -heapBased
compiler flag.
Various performance tests have shown that neither of these methods is per se faster than the other one, so carefully profile your program first and test if switching the allocation method is really worth it.
Tip
Some very large programs might experience stack-overflow issues when allocating on the stack; passing -heapBased
can potentially fix this. Alternatively, you can also try increasing the stack size using the -stacksize
compiler option (although this is not supported on all platforms).
You can embed resources (such as images, audio files, etc.) directly into your executable using the $embed
compile-time function:
var image = $embed("image.png")
In the above example, the image.png
file will be embedded into the executable and the image
variable will contain a list<byte>
containing the raw image data.
You can, for example, then load this image with the graphics module:
import graphics
var image = Canvas:fromFileData($embed("image.png"))
img.save("embedded.png")
Note
This feature is currently only supported in the AIL backend.
ASPL has a built-in debugger that allows you to step through your code and inspect variables, stack traces, etc.
Just import the breakpoints
module and use the break
function to set a breakpoint:
import breakpoints
var x = ["a", "b", "c"]
breakpoints.break()
print(x.length)
You can also pass a message to the break
function to make it easier to identify the breakpoint, as it will be printed to the console when the breakpoint is reached:
import breakpoints
var x = ["a", "b", "c"]
breakpoints.break("x is initialized")
print(x.length)
As soon as the program reaches the breakpoint, the debugger will be started, and you can use several commands such as stack
, scope
, continue
or quit
to inspect the current state of the program.
For more commands, see the help
command.
An "implementation call" is how ASPL code interacts with native (e.g. C) libraries and thus also the operating system and the actual hardware.
For example, the graphics
module uses the graphics.window.show
call to tell the host OS to open a graphical window:
implement("graphics.window.show", handle)
Note that you will almost never directly use implementation calls inside your codebase; instead, modules wrapping around native libraries (including most stdlib modules) should expose wrapper functions for these implementation calls. For example, this is the code of the nanosleep
function from the time
module:
[public]
function nanosleep(long nanoseconds){
implement("time.nanosleep", nanoseconds)
}
Implementation calls are a special compiler feature and should not be confused with functions or other types of callables; amongst other things, their return type is always any
, so you have to manually cast the return value to some other ASPL type:
[public]
function abs(string relative) returns string{
return string(implement("io.path.relative_to_absolute", relative))
}
The implement
keyword also takes an arbitrary amount of arguments - beware that there is no compiler check that validates the arguments in any way; making a mistake here could either lead to compilation errors, runtime crashes or even broken runtime behavior (e.g. memory corruptions).
Also note that the built-in functions (i.e. print
, input
, exit
, ...) are, by definition, no implementation calls, although they behave very similarly. This is simply due to these functions being universally available on nearly every operating system and very frequently used.
More documentation on how to write your own implementation calls will be added soon.
Deploying apps to mobile operating systems has traditionally been a horrible experience. Not only because of bad tooling, but also because of the lack of cross-platform GUI toolkits in many programming languages.
Since ASPL's graphics
module is designed to be platform-agnostic and non-native, most apps written for desktop operating systems will also work on phones, tablets, etc. with little to no adjustments.
ASPL apps can easily be deployed to Android using ASAPP.
iOS support is also planned, but currently not one of the main priorities.