Action Language

The primary language of the Modelverse, is its action language. This language serves as a nicer representation of the underlying Modelverse Graph Language, which is stored as a graph. The action language gets parsed by the parser, resulting in a kind of abstract syntax graph. This abstract syntax graph highly resembles an executable graph in the Modelverse Graph Language.

Users will mainly use this language to write actions and constraints, but maybe also to implement their own interfaces, or override core functionality of the Modelverse.

Warning

The current parser is unable to handle whitespaces in a general way. As such, all indentation needs to be done using tabs, and the difference needs to be exactly 1. This is different from Python, where tabs and whitespaces can be mixed, and the number can even vary.

Files

There are two kinds of files: code files, and headers. This distinction is similar to C and related languages. Code in header files will always be copied throughout all including files. As such, it is a good idea to have header files which declare the signatures of functions, but leave the actual implementation to the code files. Code files do not need to import the headers they are implementing: all variable names are automatically shared.

Language description

The language is inspired by Python syntax, and highly resembles it. Nonetheless, it is a lot simpler. In the remainder of this subsection, we go over the different language constructs.

If

The IF construct is similar to that found in other languages. Its structure is as follows:

if condition1:
   do_when_condition1
elif condition2:
   do_when_condition2
else:
   do_otherwise

Each condition can be an expression. The code blocks can be any other kind of code. Just like Python, indentation is the only way to structure code. Unlike Python, only tabs are allowed (thus no space), and the number of tabs must be exactly one higher for a deeper block.

The presence of an “elif” and “else” is optional.

While

The WHILE construct is similar to that found in other languages. Contrary to Python, there is no support for the “else” construct. Its structure is as follows:

while condition:
    action

Conditions and actions are similar to the If construct.

Break

Whereas this is supported in the Modelverse, the parser currently does not consider this keyword.

Continue

Whereas this is supported in the Modelverse, the parser currently does not consider this keyword.

Return

The RETURN construct is again similar to how it is expected. To prevent ambiguity in the grammar, an exclamation mark should follow after the expression to return. Its structure is as follows:

return expression!

The expression can be any expression, similar to the condition in an If and While.

Function call

Function calls happen like usual, by appending an opening and closing parenthesis at the end. Its structure is as follows:

my_function(argument_a, argument_b)

Arguments can again be any kind of expression. Named parameters are not supported in the grammar, though the Modelverse can internally handle them.

Function definition

Defining a function makes the function available as a variable in the remainder of the context. Forward declaration is unnecessary: all function names are retrieved before going over the body of the functions. This makes mutual recursion easy.

A function needs to define its return type, as well as the type of all its parameters. In case the type is unknown, or can be anything, Element can be used as a kind of “void pointer”. Its structure is as follows:

Element function function_name(parameter_a : Integer):
    body_of_the_function

First comes the returntype, followed by the keyword “function”. Again, indentation needs to happen using tabs.

Assignment

Assignment is like usual. Its structure is:

a = expression

This assumes that a is already defined.

Declaration

All variables used in the Modelverse need to be defined statically. Defining a variable reserves a part of the Modelverse State to hold the value for this value. As such, variables cannot be used if they are not defined. Additionally, a declared variable will have a type defined with it. Again, if the type can vary or is unknown, this can be Element.

Warning

Contrary to other languages, declaring a variable does not equal defining the variable. Therefore, after a variable is declared, it can be used, but its contents will be non-existing. It is wise to always assign a value to a declared value right after declaration!

Its structure is as follows:

String abc

There are two kinds of declaration: local declaration, and global declaration. Local declarations happen in the current block of the code, and their references get removed when exiting the block. Global declarations have identically the same syntax, but are specified at the top level (i.e., they have no tabs in front of them). Global declarations do not assign a value and are thus external references, to be defined in the future or by some other module. Sharing between modules is possible this way, as all global names are linked together when linking the application together. A global declaration that is never defined is flagged as invalid by the compiler and will prevent compilation.

Definition

Defining a variable is similar to a global declaration, but now there is an immediate assignment. Immediate assignment is only possible at the top level. Note that the assignment must be of a constant which requires no execution of functions or such. Also, it is impossible to assign the value of another variable.

Its structure is as follows:

String abc = "abc"

It is possible that a definition does not yet know the value to assign, or is an empty element. To notify the other files that this is the defining element, use the import syntax to assign ? to it:

Element abc = ?

This results in the Modelverse creating an empty node as placeholder for a value. The value will now be defined and accessible.

Imports

Direct imports in the Modelverse are possible, but are not recommended for ordinary users. This syntax allows a variable to be bound to an absolute location in the Modelverse, which is resolved at compile time. The primary use case for this is the creation of bootstrap files, but it is also possible for other purposes. Use with care!

Its structure is as follows:

String abc = ?path/in/modelverse

A special case is when the path is empty, which indicates that a new (anonymous) node should be created for it:

Element abc = ?

Include

Other files can easily be included using the include syntax. This is a raw include: the contents of the file are just copied inside the file at that exact spot. Generally, you should only include .alh files, unless you know what you are doing.

Constants

The Modelverse supports a set of constants that can be used. All constants can be assigned in a definition of globals.

Constant type Values Example
Integer Any possible integer, either positive (no prefix) or negative (- prefix) 1
Float Any possible floating point value; scientific notation is not supported 1.0
Boolean Either True or False True
String Any possible string, enclosed with either double or single quotes “abc”
Action An action construct from the Modelverse, prefixed with an exclamation mark !If

Operators

While operators are seemingly supported by the compiler, these are actually expanded to function calls to relevant functions. For example, 1 + 2, is expanded to integer_addition(1, 2)

User I/O

User input and output is done through the keyworded operations input() and output(msg). input() returns the first message in the input queue of the current user. output(msg) places the value of the expression in the output queue of the current user.

All I/O is done using queues: the value is only read and written to a specific place in the Modelverse. To actually read or write it at the client side, these special places should be accessed through dedicated Modelverse operations.

Examples

We now show some simple code fragments written in valid Action Language code. As this code is directly executable in the Modelverse, the main function is always called “main” and all includes are also present. We do not currently handle how to execute this code: this is explained further on.

Fibonacci

The Fibonacci code is fairly simple. First, the primitives are imported to make sure that we know about all operations on primitives. Then, a function fib is defined, which will recursively compute the specified Fibonacci number. This is wrapped by a main function, which takes input from the user and outputs the result of the fib function.

This code looks as follows:

include "primitives.alh"

Integer function fib(param : Integer):
    if (param <= 2):
        return 1!
    else:
        return fib(param - 1) + fib(param - 2)!

Void function main():
    while(True):
        output(fib(input()))

Factorial

Similarly, the code for computing a factorial is given below:

include "primitives.alh"

Integer function factorial(n : Integer):
    if(n <= 1):
        return 1!
    else:
        return n * factorial(n - 1)!

Void function main():
    while(True):
        output(factorial(input()))

Binary to decimal converter

A simple binary to decimal converter is given below:

include "primitives.alh"

Integer function b2d(param : String):
    Integer value
    value = 0
    Integer length
    length = string_len(param)
    Integer counter
    counter = integer_subtraction(length, 1)
    Integer accumul
    accumul = 1

    while (counter >= 0):
        if (string_get(param, counter) == "1"):
            value = integer_addition(value, accumul)
        accumul = integer_multiplication(accumul, 2)
        counter = integer_subtraction(counter, 1)

    return value!

Void function main():
    while(True):
        output(b2d(input()))