Action Language

Apart from simple attributes, such as the number of tokens on a PetriNet Place, code blocks are another frequent requirement. Current tools often do away with this by typing it as a string, and then passing it on internally to, for example, a Python interpreter. In this case, the code is nothing else than a string and there is no control whatsoever over the structure and semantics. Also, requiring an existing interpreter for that language, restricts the set of platforms for which a Modelverse can be implemented.

In the Modelverse, we alleviate this problem by providing a built-in and explicitly modelled action language. This action language is primitive in the Modelverse, meaning that its abstract syntax tree can be represented, instead of just its textual representation. The abstract syntax tree is executed on-the-fly in the Modelverse, preventing the mapping to any other internal representation. The semantics of this language is also explicitly modelled, making it possible to automatically generate interpreters for any desired platform.

In this section, we now focus on how to use this action language. As PetriNets are a bit too simple to require actions, we will use Finite State Automata as an example.

Warning

The current action language compiler is rather basic and contains quite some problems.

Example: Finite State Automata

In the context of Finite State Automata, it is often necessary to define actions on specific transitions. Upon execution of the transition, the associated action is executed. When defining a model, we can also upload code snippets as attribute values:

>>> instantiate_attr_code("models/my_fsa", "transition_1", "action", open("actions/transition_1.alc", "r").read())

This will assign the action stored in the file actions/transition_1.alc to the action attribute of the transition. Of course, it is not necessary to open a file for this, as the function itself could just as well be inlined. The only requirement on the value, is that it is a string that can be parsed as Action Language:

>>> instantiate_attr_code("models/my_fsa", "transition_1", "action", \
...     """
...     include "primitives.alh"
...     Void function action(model : Element):
...         log("Executed action!")
...         return!
...     """

Action language attributes are considered identical to any other attribute. The exception is of course that they are assigned using the code specific function, as otherwise we would have uploaded a string. Note the difference between the assignment of a string, and the assignment of action code:

>>> instantiate_attr("models/my_fsa", "transition_1", "action", open("actions/transition_1.alc", "r").read())
>>> instantiate_attr_code("models/my_fsa", "transition_1", "action", open("actions/transition_1.alc", "r").read())

The first line assigns a string to the attribute, and is therefore not executable, nor is it parsed or considered in any way. The second line assigns code to the attribute, and is therefore executable, and parsed upon assignment.

The signature of each function, in the case of our example this is “Void function action(model : Element)”, depends on how the code will be used later on.

Multi-function code

Some code is ideally expressed using multiple functions. As the Modelverse does not support subfunctions, it supports the second best alternative: multiple functions can be defined. When multiple functions are defined, the main function will be executed, which can refer to any of the other functions defined in the same block. If no main function is found, the topmost function is executed.

Language description

Next we describe the basic constructs of the language, as offered by our compiler. The language is inspired by Python syntax, and highly resembles it. Nonetheless, it is much more minimal. 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

The BREAK construct is similar to that found in other languages. Contrary to Python, it is followed by an exclamation mark to differentiate it from the variable break. Its structure is as follows:

while condition:
    break!

While the Modelverse supports breaking out of multiple loops simultaneously, this is not currently supported by the HUTN parser.

Continue

The CONTINUE construct is similar to that found in other languages. Contrary to Python, it is followed by an exclamation mark to differentiate it from the variable continue. Its structure is as follows:

while condition:
    continue!

While the Modelverse supports continuing a higher loop directly, this is not currently supported by the HUTN parser.

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. When the function has returntype void, the expression must be empty:

return!

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. There is a set of standard header files included in the Modelverse, which are probably the only ones you will need.

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). To do this conversion, it is mandatory that the type of both arguments can be determined statically.

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, potentially blocking until there is input. 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.

Primitive operations

Apart from the minimal syntax, the Modelverse supports a wide library of functions. These functions are all built on top of the previously defined constructs. A list of these functions is shown below.

  • Boolean function value_eq(a: Element, b: Element)
  • Boolean function value_neq(a: Element, b: Element)
  • Boolean function bool_and(a: Boolean, b: Boolean)
  • Boolean function bool_or(a: Boolean, b: Boolean)
  • Boolean function bool_not(a: Boolean)
  • Element function create_node()
  • Element function create_edge(a: Element, b: Element)
  • Element function create_value(a: Element)
  • Boolean function is_edge(a: Element)
  • Integer function read_nr_out(a: Element)
  • Element function read_out(a: Element, b: Integer)
  • Integer function read_nr_in(a: Element)
  • Element function read_in(a: Element, b: Integer)
  • Element function read_edge_src(a: Element)
  • Element function read_edge_dst(a: Element)
  • Boolean function delete_element(a: Element)
  • Boolean function element_eq(a: Element, b: Element)
  • Boolean function element_neq(a: Element, b: Element)
  • Float function cast_float(a: Element)
  • String function cast_string(a: Element)
  • Boolean function cast_boolean(a: Element)
  • Integer function cast_integer(a: Element)
  • String function cast_id(a: Element)
  • String function cast_value(a: Element)
  • Element function dict_add(a: Element, b: Element, c: Element)
  • Element function dict_add_fast(a: Element, b: Element, c: Element)
  • Element function dict_delete(a: Element, b: Element)
  • Element function dict_delete_node(a: Element, b: Element)
  • Element function dict_read(a: Element, b: Element)
  • Element function dict_read_edge(a: Element, b: Element)
  • Element function dict_read_node(a: Element, b: Element)
  • Integer function dict_len(a: Element)
  • Boolean function dict_in(a: Element, b: Element)
  • Boolean function dict_in_node(a: Element, b: Element)
  • Element function dict_keys(a: Element)
  • Float function float_addition(a: Float, b: Float)
  • Float function float_subtraction(a: Float, b: Float)
  • Float function float_multiplication(a: Float, b: Float)
  • Float function float_division(a: Float, b: Float)
  • Float function float_neg(a: Float)
  • Boolean function float_gt(a: Float, b: Float)
  • Boolean function float_gte(a: Float, b: Float)
  • Boolean function float_lt(a: Float, b: Float)
  • Boolean function float_lte(a: Float, b: Float)
  • Integer function integer_addition(a: Integer, b: Integer)
  • Integer function integer_subtraction(a: Integer, b: Integer)
  • Integer function integer_multiplication(a: Integer, b: Integer)
  • Integer function integer_division(a: Integer, b: Integer)
  • Integer function integer_modulo(a: Integer, b: Integer)
  • Boolean function integer_gt(a: Integer, b: Integer)
  • Boolean function integer_gte(a: Integer, b: Integer)
  • Boolean function integer_lt(a: Integer, b: Integer)
  • Boolean function integer_lte(a: Integer, b: Integer)
  • Boolean function integer_neg(a: Integer)
  • Element function list_read(a: Element, b: Integer)
  • Element function list_append(a: Element, b: Element)
  • Element function list_insert(a: Element, b: Element, c: Integer)
  • Element function list_delete(a: Element, b: Integer)
  • Integer function list_len(a: Element)
  • Boolean function list_in(a : Element, b : Element)
  • Element function set_add(a: Element, b: Element)
  • Element function set_add_node(a: Element, b: Element)
  • Element function set_pop(a: Element)
  • Element function set_read(a : Element)
  • Element function set_remove(a: Element, b: Element)
  • Boolean function set_in(a: Element, b: Element)
  • Element function set_remove_node(a: Element, b: Element)
  • Element function set_in_node(a: Element, b: Element)
  • Integer function set_len(a: Element)
  • String function string_join(a: String, b: String)
  • String function string_get(a: String, b: Integer)
  • String function string_substr(a: String, b: Integer, c: Integer)
  • Integer function string_len(a: String)
  • Element function string_split(a: String, b: String)
  • Boolean function string_startswith(a: String, b: String)
  • String function log(a: String)
  • Element function read_root()
  • Element function read_taskroot()
  • Element function input()
  • Element function output(a : Element)
  • Boolean function is_physical_int(a : Element)
  • Boolean function is_physical_float(a : Element)
  • Boolean function is_physical_string(a : Element)
  • Boolean function is_physical_action(a : Element)
  • Boolean function is_physical_boolean(a : Element)
  • Boolean function is_physical_none(a : Element)
  • Boolean function is_error(a : Element)
  • Boolean function has_value(a : Element)
  • Float function time()
  • String function hash(a : String)
  • Void function sleep(a : Float)
  • Void function interruptable_sleep(a : Float)
  • Element function exec(a : Element)
  • Element function resolve(var_name : String)
  • Boolean function has_input()
  • Element function list_pop(lst : Element, index : Integer)
  • Element function list_pop_final(lst : Element)
  • Element function set_copy(set : Element)
  • String function set_to_string(set : Element)
  • String function list_to_string(set : Element)
  • String function dict_to_string(dict : Element)
  • Element function set_overlap(sa : Element, sb : Element)
  • Element function set_equality(sa : Element, sb : Element)
  • Element function dict_eq(da : Element, db : Element)
  • Element function dict_copy(dict : Element)
  • Element function set_to_list(s : Element)
  • Element function create_tuple(a : Element, b : Element)
  • Void function dict_overwrite(a : Element, b : Element, c : Element)
  • Void function set_merge(sa : Element, sb : Element)
  • Element function make_reverse_dictionary(dict : Element)
  • Element function set_create()
  • Element function list_create()
  • Element function dict_create()
  • String function reverseKeyLookup(a: Element, b: Element)
  • Element function reverseKeyLookupMulti(a: Element, b: Element)
  • Element function dict_values(dict : Element)