-
Notifications
You must be signed in to change notification settings - Fork 10
Alk Language ‐ Reference Manual
- 1 Introduction
-
2 Language
- 2.1 Keywords
- 2.2 Preprocessing
- 2.3 Expressions
- 2.4 Declarations and Initializations
- 2.5 Functions
- 2.6 Instructions
- 3 Options
Alk is a programming language developed by the Alk Language Community, mainly formed by developers and professors of Faculty of Computer Science, University "Alexandru Ioan Cuza" University, Iasi. It firstly came out as an educational tool in 2016, meant to be used by the freshmen in order to develop and run specific algorithms.
Its main purpose is to deliver a system easy to use and platform-independent, as its main user target is the students. Due to its educational reasons, Alk should display as many theoretical principles as possible in a light-weighted environment: no heavy dependencies, allow a flexible syntax and intuitive semantics.
A configuration refers to a set of mappings between ids and values. Semantically, this represents an environment state at a given moment. Its main usage is related to Alk's input configuration and eventually the final metadata. Note than a configuration can have more than one such mapping. In case multiple mappings are defined, they should be space separated. It is recommended to keep these cells on separate lines where possible to increase visibility.
Syntax |
---|
var-name |-> value |
var-name1 |-> value1 var-name2 |-> value2 |
Var-name:
This refers to the variable name to which the given value will be assigned.
Value:
The value can be represented in any way. When an initial configuration is used, the Alk interpreter will parse the value as it does inside a normal Alk algorithm, which means that any kind of representation can be used (for example the interval or specification representations for an iterable data type value).
Alk allows the initialization of the environment through an input configuration, which can be specified by using the input option (-i). Also, if the metadata option (-m) is used, the final configuration will be printed.
Examples:
- Configuration with only one component: a |-> 5
- Configuration with multiple components: a |-> 5 b |-> 6.5 c |-> "abc" d |-> true e |-> [1, 2, 3] f |-> <1, 2, 3> g |-> {1, 2, 3} h |-> {x -> 1 y -> 2}
- Configuration making use of the Alk interpreter for parsing complex representations: a |-> [2 * x | x from [1..10]]
Below there is a table of all the keywords which can be interpreted. Alongside, there is a short description containing links to the topics which exploit those keywords.
Keyword | Description |
---|---|
break | Part of the break statement |
choose | Part of the choose statement |
continue | Part of the continue statement |
do | Part of the do-while statement |
else | Part of the conditional statement |
emptyList | Used to defined empty list |
emptySet | Used to defined empty set |
emptyStructure | Used to defined empty structure |
for | Part of the for statement |
foreach | Part of the foreach statement |
from | Used to represent specifications |
failure | Part of the failure statement |
if | Part of the conditional statement |
in | Used as inclusive operator |
include | Part of the include directive |
modifies | Used to represent global references inside functions |
out | Used to represent output parameters |
repeat | Part of the repeat-until statement |
return | Part of the return statement |
s.t. | Part of the choose statement |
success | Part of the success statement |
uniform | Part of the uniform statement |
uses | Used to represent global references inside functions |
until | Part of the repeat-until statement |
while | Part of the while statement |
xor | Used as xor operator |
The preprocessing is a stage before the visiting one, which handles the directives. The only directive supported now is the include directive which allows the user to attach an external file into the current code-base. These directives are processed recursively, so the final generated program should have no include node. In case a cycle is detected, the Alk interpreter will report this and abort the execution.
Syntax |
---|
#include "file-path" |
File-path:
The file path should point to a file which should be included in this current code. The inclusion will be done where the directive is used. In fact, this directive will be automatically replaced with the content of the file specified. The path can be absolute or relative. If the relative path is used, the origin is the file which includes it, not the initial file which is used in the -a option.
Examples:
Initial file | Secondary file (side.alk) | Final configuration |
---|---|---|
#include "side.alk" b = a + 2; | a = 5; | a |-> 5 b |-> 7 |
a = 9; #include "side.alk" b = a + 2; | a++; | a |-> 10 b |-> 12 |
a = 9; b = a + 2; #include "side.alk" | a++; | a |-> 10 b |-> 11 |
The integer data type is a numeric type which allows storing data in form of integer numbers. There is no fixed bound for the dimension of one integer, as it is backed by an unlimited data structure designed for numeric usage. The complexity of the operations is dependent upon the size of the integer, thus not all computations should be considered to be working in constant time.
To represent an integer, one should just simply write it in the decimal base, as this is the only way Alk can interpret it. For negative integers, the unary operator - should be used in the representation.
Examples:
- Integer: 123456789
- Integer with a large number of digits: 1234567891011121314151617181920
- Integer with minus sign: -123
- Zero integer: 0
The float data type is a numeric type which allows storing data in form of numbers with floating point. There is no fixed bound for the dimension of the integer part, while the fractional part is limited by a precision constant. By default, the precision is set to 10 decimals after the floating point and rounded after a HALF_EVEN strategy. However this can be changed at the command line using the precision option. The complexity of the operations is dependent upon the size of the float, thus not all computations should be considered to be working in constant time.
To represent a float, one should write it in the decimal base using a floating point which separates the integer part from the fractional part. These two parts should not be empty. The fractional part shouldn't necessarily respect the precision, it will be automatically trimmed. For negative floats, the unary operator - should be used in the representation.
Examples:
- Float: 1234.098765
- Float with a large number of digits and decimals: 123456789.201918171615141312111
- Float with minus sign: -123.456
- Float with 0 integer part: 0.123
- Float with 0 fractional part: 123.0
- Zero float: 0.0
The bool data type is a logical type which allows storing data in form of logical primitives. Therefore, a bool can have one out of the two possible values: true or false. The complexity of the operations with such types should be considered constant.
To represent a bool, one should write it using the true or false keywords.
Examples:
- True bool: true
- False bool: false
The string data type is a character type which allows storing data in form of character strings. There is no fixed bound for the length of the string and can be used as a single character representation when its size is one, or the empty string when its size is zero. The complexity of the operations is dependent upon the size of the string, thus not all computations should be considered to be working in constant time.
To represent a string, one should enclose a character sequence in double quotes. For the empty string, provide an empty sequence inside the double quotes pair.
Examples:
- String: "abcxyz"
- String also containing non-alphanumerical characters: "a1%x]\0 and &p./?"
- String of size 1 (representation of a single character): "a"
- Empty string: ""
An array is a heterogeneous compound data type which can store multiple values in a sequential way with contiguous allocation. The array is limited in size by a MAX_SIZE constant which is initially set to one billion. This can be however altered by the size option providing another maximum. The array is dynamically allocated, so there is no need to initialize the array beforehand.
The types of values contained in one array can be both simple or compound. Also, it doesn't have a limit in the nesting depth, so one can enclose a unlimited number of values inside the array. The complexities are either linear or constant, depending on the operations.
To represent an array, one should choose out of the four possible ways to define an iterable data type. The canonical representation is the expression based one, as is the most comprehensive.
Examples:
- Expression-based representation: [1, "abc", true, 0.5, [1..5], < "a", "b" >, {0.5, 0.6}, {x -> 2 y -> 3}]
- Interval-based representation: [1..5]
- Filter-based specification: [x from [1..5] | x % 2 == 1]
- Mapping-based specification: [2 * x | x from [1..5]]
- Empty array: []
A list is a heterogeneous compound data type which can store multiple values in a sequential way with discontinuous allocation. The list is limited in size by a MAX_SIZE constant which is initially set to one billion. This can be however altered by the size option providing another maximum.
The types of values contained in one list can be both simple or compound. Also, it doesn't have a limit in the nesting depth, so one can enclose a unlimited number of values inside the list. The complexities are either linear or constant, depending on the operations.
To represent a list, one should choose out of the four possible ways to define an iterable data type. The canonical representation is the expression based one, as is the most comprehensive. In order to represent the empty list, use the emptyList keyword, or the simple open/close syntax.
Examples:
- Expression-based representation: < 1, "abc", true, 0.5, [1..5], < "a", "b" >, {0.5, 0.6}, {x -> 2 y -> 3} >
- Interval-based representation: < 1..5 >
- Filter-based specification: < x from [1..5] | x % 2 == 1 >
- Mapping-based specification: < 2 * x | x from [1..5] >
- Empty list: emptyList
- Empty list with open/close syntax: < >
A set is a heterogeneous compound data type which can store multiple unique values in a non-sequential way with discontinuous allocation. The set is limited in size by a MAX_SIZE constant which is initially set to one billion. This can be however altered by the size option providing another maximum.
The types of values contained in one set can be both simple or compound. Also, it doesn't have a limit in the nesting depth, so one can enclose a unlimited number of values inside the set. The complexities are either linear or constant, depending on the operations.
To represent a set, one should choose out of the four possible ways to define an iterable data type. The canonical representation is the expression based one, as is the most comprehensive. In order to represent the empty set, use the emptySet keyword, or the simple open/close syntax.
Examples:
- Expression-based representation: {1, "abc", true, 0.5, [1..5], < "a", "b" >, {0.5, 0.6}, {x -> 2 y -> 3}}
- Interval-based representation: {1..5}
- Filter-based specification: {x from [1..5] | x % 2 == 1}
- Mapping-based specification: {2 * x | x from [1..5]}
- Empty set: emptySet
- Empty set with open/close syntax: {}
A structure is a heterogeneous compound data type which can store multiple values based on a unique identifier. The structure is limited in size by a MAX_SIZE constant which is initially set to one billion. This can be however altered by the size option providing another maximum. The structure is dynamically allocated, so there is no need to initialize the structure beforehand.
The types of values contained in one structure can be both simple or compound. Also, it doesn't have a limit in the nesting depth, so one can enclose a unlimited number of values inside the structure.
To represent a structure, one should enclose in curly braces a sequence of space separated pair components which are of form key -> value. An empty structure can't be defined, so it should contain at least one component.
Examples:
- Empty structure: emptyStructure
- Empty structure with open/close syntax: {->}
- Simple structure with two components: {x -> 2 y -> 5}
- Complex structure: {a -> 0.5 b -> [1, 2, 3, 4, 5] c -> < "a", "b" > d -> {0.5, 0.6} e -> {x -> 2 y -> 3} x -> 1 y -> "abc" z -> true}
The majority of the operators are C-like. However there are new sets of operators meant to simplify the operations between certain data types (setwise operators, inclusive operators). In this section, each operator will be explained in terms of behavior, data types and the result of the evaluation. Below it is a table meant to illustrate the priority of operators inside an expression.
In the table, if the number in the priority column is lower, then the operator will be executed quicker. For example, the conditional operator will be evaluated at the end. The ones which share the same priority, are executed in the order they appear in the expression. In order to suppress this order of evaluation, one can use the round parenthesis (for example: (2 + 3) * 4 will result in 20).
To eliminate the confusion, in case of multiple ++ or -- signs, they are parsed from left to right, which means that in an expression like 2+++3, the evaluation will consider that there is a postfix operator and a plus sign: (2++) + 3. In case of +++2, there is a prefix operator and a unary plus sign: ++(+2).
Priority | Operator set | Operators |
---|---|---|
1 | Unary | [], . |
2 | Increment / Decrement | ++ (post), -- (post) |
3 | Unary | + (unary), - (unary), ! |
4 | Increment / Decrement | ++ (pre), -- (pre) |
5 | Arithmetic | *, /, % |
6 | Arithmetic | +, - |
7 | Bitwise | <<, >> |
8 | Bitwise | & |
9 | Bitwise | |, xor |
10 | Setwise | U, ^, \ |
11 | Relational | <, >, <=, >= |
12 | Relational | ==, != |
13 | Inclusive | in |
14 | Logical | && |
15 | Logical | || |
16 | Conditional | ?: |
Some unary operators are used in order to suggest the mathematical representations of positive and negative numeric values. Other operators are used for data access (over arrays or structures). The complexity of these operations is constant. The table below explains the behavior of the unary operators when the values a and x are used. Note that in the example below we consider id as a literal identifier.
Operator | Representation | Description |
---|---|---|
Positive | +a | Keeps the sign of a |
Negative | -a | Changes the sign of a |
Not | !a | Changes the truth value of the bool value a |
Bracket | a[x] | Access the xth element of a |
Dot | a.id | Access the element stored under the id identifier inside a |
Depending on the operator used and the types of the operands, there can be several return types described in the table below:
Operator | Data type 1 | Data type 2 | Return type |
---|---|---|---|
Positive | integer | integer | |
Positive | float | float | |
Negative | integer | integer | |
Negative | float | float | |
Not | boolean | boolean | |
Bracket | array | integer greater or equal to 0 and less than the size of the array | data type of the element on the xth position |
Dot | structure | data type of the element stored at the specified identifier |
Examples:
Data type | Positive | Negative | Not | Bracket | Dot |
---|---|---|---|---|---|
Integer | +(-2) is -2 | -(-2) is 2 | |||
Float | -(+2.2) is -2.2 | +(+2.2) is 2.2 | |||
Bool | !true is false | ||||
Array | [1, 2, 3][2] is 3 | ||||
Structure | {x -> 2 y -> 3}.x is 2 |
There are four types of operators to easily increment or decrement a numeric value. The complexity of these operators is constant. The table below explains the behavior of the operators when a numeric value a is used.
Operator | Representation | Description |
---|---|---|
Postfix increment | a++ | Increases the value of a by 1, but the old value is used in the current expression |
Postfix decrement | a-- | Decreseas the value of a by 1, but the old value is used in the current expression |
Prefix increment | ++a | Increases the value of a by 1 and the new value is used in the current expression |
Prefix decrement | --a | Decreases the value of a by 1 and the new value is used in the current expression |
There is a difference between a postfix and prefix operator:
- The prefix operator changes the value of the operand and the expression continues to work with the updated value.
- The postfix operator changes the value of the operand, but the expression continues to work with its old value.
Note that only numeric values are valid for this kind of operations. Also, the result always has the same type as the initial value.
Examples:
Data type | Postfix increment | Postfix decrement | Prefix increment | Prefix decrement |
---|---|---|---|---|
Integer | 3++ is 3 | 3-- is 3 | ++3 is 4 | --3 is 2 |
Float | 3.2++ is 3.2 | 3.2-- is 3.2 | ++3.2 is 4.2 | --3.2 is 2.2 |
There are two subcategories of arithmetic operators: additive (+, -) and multiplicative (*, /, %). The complexity of those operators are theoretically constant. However, the size of the numbers can matter, so the real complexity is in fact O(N + M) for additive operators, and O(N * M) for the multiplicative operators, where N and M are the number of digits in the operands.
The table below explains the behavior of the operators when two numeric values a and b are used.
Operator | Representation | Description |
---|---|---|
Addition | a + b | Computes the sum of a and b |
Subtraction | a - b | Computes the difference between a and b |
Multiplication | a * b | Computes the product of a and b |
Division | a / b | Computes the division's result of a and b |
Mod operator | a % b | Computes the remainder left over when a is divided by b |
The string data type also has an implementation for the addition operator. The complexity of such operation is O(N) + O(M), where N and M are the lengths of the string operands. The table below explains the behavior of the additive operator when two strings a and b are used.
Operator | Representation | Description |
---|---|---|
Concatenation | a + b | Computes a string representing the concatenation of a and b |
The results of these first operators are numeric, but the exact resulted data type is sometimes dependent upon the operands. It means that it is relevant what data types a and b have: either integer or float. The table below illustrates of what data type the result is depending on the operator and operands. To be noted than an error is thrown when the mod operator works with a float operand.
Operator | Result data type | Operands data types |
---|---|---|
Concatenation | string | string, string |
Addition / Subtraction / Multiplication | integer | integer, integer |
Addition / Subtraction / Multiplication | float | float, integer / integer, float / float, float |
Division | float | integer, integer / float, integer / integer, float / float, float |
Mod operator | integer | integer, integer |
Examples:
Data type 1 | Data type 2 | Addition / Concatenation | Subtraction | Multiplication | Division | Mod operator |
---|---|---|---|---|---|---|
Integer | Integer | 3 + 2 is 5 | 3 - 2 is 1 | 3 * 2 is 6 | 3 / 2 is 1 | 3 % 2 is 1 |
Integer | Float | 3 + 2.2 is 5.2 | 3 - 2.2 is 0.8 | 3 * 2.2 is 6.6 | 3 / 2.2 is 1.3636363636 | |
Float | Integer | 3.2 + 2 is 5.2 | 3.2 - 2 is 1.2 | 3.2 * 2 is 6.4 | 3.2 / 2 is 1.6 | |
Float | Float | 3.2 + 2.2 is 5.4 | 3.2 - 2.2 is 1.0 | 3.2 * 2.2 is 7.04 | 3.2 / 2.2 is 1.4545454545 | |
String | String | "abc" + "xyz" is "abcxyz" |
There are five types of operators which work on the bits of integer values. The complexity of these operators is theoretically constant. However, it can be severely influenced by the size of the numbers used. Therefore we can assume that the real complexity is in fact max(log(N), log(M)), where N and M are the operands. The table below explains the behavior of the operators when the integer values a and b are used.
Operator | Representation | Description |
---|---|---|
Bitwise and | a & b | Computes the result of a bitwise and operation between a and b |
Bitwise or | a | b | Computes the result of a bitwise or operation between a and b |
Bitwise xor | a xor b | Computes the result of an bitwise xor operation between a and b |
Left shift | a << b | Computes the result after a bit shift of a with b bits to the left |
Right shift | a >> b | Computes the result after a bit shift of a with b bits to the right |
Note that only integer operands are valid for this kind of operations. Also, the result of these operations is always integer.
Examples:
Data type 1 | Data type 2 | Bitwise and | Bitwise or | Bitwise xor | Left shift | Right shift |
---|---|---|---|---|---|---|
Integer | Integer | 12 & 10 is 8 | 12 | 10 is 14 | 12 xor 10 is 6 | 12 << 10 is 12288 | 12 >> 2 is 3 |
There are three types of operators which work exclusively on sets and represent the mathematical set operators. The complexity of these operators is linear, so we can consider the complexity class O(max(|A|, |B|)), where A and B are the set operands. The table below explains the behavior of the operators when the set values a and b are used.
Operator | Representation | Description |
---|---|---|
Union | a U b | Computes the union of the represented sets by a and b |
Intersection | a ^ b | Computes the intersection of the represented sets by a and b |
Difference | a \ b | Computes the difference of the represented sets by a and b |
Note that the result of these operators is always a set and the operands should be evaluated to set.
Examples:
Data type 1 | Data type 2 | Union | Intersection | Difference |
---|---|---|---|---|
Set | Set | {1, 2, 3} U {2, 3, 4} is {1, 2, 3, 4} | {1, 2, 3} ^ {2, 3, 4} is {2, 3} | {1, 2, 3} \ {2, 3, 4} is {1} |
There are two subcategories of relational operators: equality (==, !=) and comparing (<, <=, >=, >).
There are six types of operators which work on multiple type of values in order to evaluate relational expressions. The complexity of these operators is linear over the size of the operands. For some simple data types, the complexity is O(max(N, M)) where N and M are the number of digits in the numeric operands or the length of the string operands. In case of bool types, the complexity is linear. For compound structures, the complexity is the same, but it multiplies with the complexity of checking for the equality of each member. The table below explains the behavior of the operators when the values a and b are used.
Operator | Representation | Description |
---|---|---|
Equal | a == b | Checks if a and b have equal values |
Not equal | a != b | Checks if a and b don't have equal values |
Lower than | a < b | Checks if a has a value lower than b |
Lower than or equal | a <= b | Checks if a has a value lower than b or if a and b have equal values |
Greater than or equal | a >= b | Checks if a has a value greater than b or if a and b have equal values |
Greater than | a > b | Checks if a has a value greater than b |
These kind of operations have different meanings depending on the data types used for the operands. However, the result is always consistent in data type; these operators always deliver a bool result. The table below describes how these checks are made for each kind of data type.
Operator | Result data type | Operands data types | Description |
---|---|---|---|
Equality: ==, != | bool | integer, integer / float, integer / integer, float / float, float | Check if the represented numeric values are equal or not |
Comparing: <, <=, =>, > | bool | integer, integer / float, integer / integer, float / float, float | Check the relation between the represented numeric values |
Equality: ==, != | bool | string, string | Check if the represented character strings are the same or not |
Comparing: <, <=, =>, > | bool | string, string | Check the lexicographical relation between the represented character strings |
Equality: ==, != | bool | bool, bool | Check if both bool have the same truth value or not |
Equality: ==, != | bool | array, array | Check if both arrays have the same size. In case they do, check each pair of elements from same positions in the two arrays to detect if they are equal or not. |
Equality: ==, != | bool | list, list | Check if both lists have the same size. In case they do, check each pair of elements from same positions in the two lists to detect if they are equal or not. |
Equality: ==, != | bool | set, set | Check if both sets have the same size. In case they do, check that both coincide with their intersection or not. |
Equality: ==, != | bool | structure, structure | Check if both structures have the same set of identifiers. In case they do, check if the values associated with each identifier in the structures are equal or not. |
Examples:
Data type 1 | Data type 2 | Equal | Not equal | Lower than | Lower than or equal | Greater than or equal | Greater than |
---|---|---|---|---|---|---|---|
Integer | Integer | 1 == 5 is false | 1 != 5 is true | 1 < 5 is true | 1 <= 5 is true | 1 >= 5 is false | 1 > 5 is false |
Integer | Float | 1 == 1.0 is true | 1 != 1.0 is false | 1 < 1.0 is false | 1 <= 1.0 is true | 1 >= 1.0 is true | 1 > 1.0 is false |
Float | Integer | 1.2 == 1 is false | 1.2 != 1 is true | 1.2 < 1 is false | 1.2 <= 1 is false | 1.2 >= 1 is true | 1.2 > 1 is true |
Float | Float | 1.2 == 1.3 is false | 1.2 != 1.3 is true | 1.2 < 1.3 is true | 1.2 <= 1.3 is true | 1.2 >= 1.3 is false | 1.2 > 1.3 is false |
String | String | "abc" == "xyz" is false | "abc" != "xyz" is true | "abc" < "xyz" is true | "abc" <= "xyz" is true | "abc" >= "xyz" is false | "abc" > "xyz" is false |
Bool | Bool | true == false is false | true != false is true | ||||
Array | Array | [1, "abc"] == [1, "abc"] is true | [1, "abc"] != [1, "abc"] is false | ||||
List | List | <1, "abc"> == <1.1, "abc"> is false | <1, "abc"> != <1.1, "abc"> is true | ||||
Set | Set | {1, "abc"} == {1, "abc"} is true | {1, "abc"} != {1, "abc"} is false | ||||
Structure | Structure | {x->1.1 y->"abc"} == {x->"abc" y->1.1} is false | {x->1.1 y->"abc"} != {x->"abc" y->1.1} is true |
The inclusive operator is used in order to detect if a specific element is contained by an iterable data type. The complexity is linear as each element in the iterable structure is checked against the probe, so we can consider O(N * X), where N is the size of the structure and X is the complexity to test equality. The table below explains the behavior of the inclusive operator when the value a and iterable value b are used.
Operator | Representation | Description |
---|---|---|
Inclusive | a in b | Checks if a is contained in b or not. |
Note that the inclusive operator always returns a bool value. Also, b should always evaluate to an iterable data type. In order to check the inclusion, the equal operator is used.
Examples:
Data type 1 | In array | In list | In set |
---|---|---|---|
Integer | 2 in [1, 2, 3] is true | 4 in <1, 2, 3> is false | 4 in {1, 2, 3} is false |
Float | 2.2 in [1, 2, 3] is false | 2.0 in <1, 2, 3> is true | 2.0 in {1, 2, 3} is true |
Bool | true in [false] is false | true in < true, false > is true | true in {true, false} is true |
String | "abc" in ["abc", "xyz"] is true | "abc" in <"Abc", "xyz"> is false | "abc" in {"Abc", "xyz"} is false |
Array | [1, 2] in [1, [1, 2.2], 3] is false | [1, 2] in <1, [1, 2.0], 3> is true | [1, 2] in {1, [1, 2], 3} is true |
List | <1, 2> in [1, <1, 2.2>, 3] is false | <1, 2> in <1, <1, 2.0>, 3> is true | <1, 2> in {1, <1, 2>, 3} is true |
Set | {1, 2} in [1, {1, 2.2}, 3] is false | {1, 2} in <1, {1, 2.0}, 3> is true | {1, 2} in {1, {1, 2}, 3} is true |
Structure | {x->2 y->3} in [1, {x->2.2 y->3}, 3] is false | {x->2 y->3} in <1, {x->2.0 y->3}, 3> is true | {x->2 y->3} in {1, {x->2 y->3}, 3} is true |
There are three types of operators which work on the bool values in order to evaluate logical expressions. The complexity for these operands is constant. The table below explains the behavior of the operators when the bool values a and b are used.
Operator | Representation | Description |
---|---|---|
Logical and | a && b | Computes the result of a logical and operation between a and b |
Logical or | a || b | Computes the result of a logical or operation between a and b |
Note that only bool values are valid for this kind of operations. Also, the result of these operations is always bool.
Examples:
Data type 1 | Data type 2 | Logical and | Logical or |
---|---|---|---|
Bool | Bool | true && false is false | true || false is true |
The conditional operator is used as an inline conditional statement and is the only ternary operator by now. The complexity for this operator is constant. The table below explains the behavior of the operator when the bool value a and other values b and c are used.
Operator | Representation | Description |
---|---|---|
Conditional | a ? b : c | Evaluates to b only if a is true, otherwise it evaluates to _c_ |
Note that a should always evaluate to a bool value, while there is no restriction over the data types of b and c. The result is either b or c.
Examples:
Data type 1 | Data type 2 | Data type 3 | Conditional |
---|---|---|---|
Bool | Integer | String | true ? 5 : "abc" is 5 |
Bool | Float | Bool | false ? 2.5 : true is true |
Bool | Array | List | true ? [1, 2, 3] : <4, 5, 6> is [1, 2, 3] |
Bool | Set | Structure | false ? {1, 2, 3} : {x->2 y->3} is {x->2 y->3} |
The builtin functions have the highest priority as long as they can be seen as predicates. An important aspect is that there can't be custom defined functions with the same name as the builtin functions.
There are several mathematical builtin functions. All of them work with numeric types and also evaluate to numeric values. The table below explains the behavior of the mathematical builtin functions when the numeric values a and b are used.
Builtin Function | Representation | Description |
---|---|---|
Sin | sin(a) | Returns the sine of a, where a is a numeric value representing radian units |
Cos | cos(a) | Returns the cosine of a, where a is a numeric value representing radian units |
Tan | tan(a) | Returns the tangent of a, where a is a numeric value representing radian units |
Asin | asin(a) | Returns the inverse sine of a, where a is a numeric value which should be in _[-1, 1]_ interval |
Acos | acos(a) | Returns the inverse cosine of a, where a is a numeric value which should be in _[-1, 1]_ interval |
Atan | atan(a) | Returns the inverse tangent of a, where a is a numeric value which should be in _[-1, 1]_ interval |
Log | log(a) | Returns the natural logarithm of a, where a is a positive numeric value |
Pow | pow(a, b) | Returns a power b |
Sqrt | sqrt(a) | Returns the square root of a |
Pi | pi() | Returns the value of _π_ |
Abs | abs(a) | Returns the absolute value of a |
Note that all the functions above are working with numeric type values: integer or float. The result of these functions are all float, no matter the operands' types. In the examples section we presume that the precision was set to 3 decimals after the floating point.
Examples:
Operand 1 | Operand 2 | Sin | Cos | Tan | Asin | Acos | Atan | Log | Pow | Sqrt | Pi | Abs |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Integer | Integer | pow(2, 3) is 8.0 | ||||||||||
Integer | Float | pow(2, 3.2) is 11.313 | ||||||||||
Float | Integer | pow(2.5, 3) is 15.625 | ||||||||||
Float | Float | pow(2.5, 3.5) is 24.705 | ||||||||||
Integer | sin(2) is 0.909 | cos(2) is -0.412 | tan(2) is -2.185 | asin(1) is 1.570 | acos(-1) is 3.141 | atan(1) is 0.785 | log(2) is 0.693 | sqrt(2) is 1.414 | abs(2) is 2.0 | |||
Float | sin(-2.5) is -0.598 | cos(-2.5) is -0.801 | tan(-2.5) is 0.747 | asin(-0.5) is -0.523 | acos(-0.5) is 2.094 | atan(-0.5) is -0.463 | log(0.5) is -0.693 | sqrt(0.5) is 0.707 | abs(-0.5) is 0.5 | |||
pi() is 3.141 |
There are only two conversion builtin functions while are used in order to change the data type of one numerical value from float to integer or from integer to float. The table below explains the behavior of the conversion builtin functions when the numeric value a is used.
Builtin Function | Representation | Description |
---|---|---|
Int | int(a) | Converts the numeric value of a to an integer representation. |
Float | float(a) | Converts the numeric value of a to a float representation. |
Note that the int function can produce data loss if a has a non-zero fractional part. Also, the only data type allowed for a is either float or integer.
Examples:
Data type | Int | Float |
---|---|---|
Integer | int(2) is 2 | float(2) is 2.0 |
Float | int(2.5) is 2 | float(2.5) is 2.5 |
There is only one string based function which is used in order to determine the size of a string. The table below explains the behavior of the string based builtin function when the string value a is used.
Builtin Function | Representation | Description |
---|---|---|
Len | len(a) | Returns the size of the string a in number of characters. |
Note that the len builtin function returns a positive integer and the only valid operand data type is string.
Examples:
Data type | Len |
---|---|
String | len("abc") is 3 |
There is only one IO function which is used in order to print to the terminal a provided value. The table below explains the behavior of the IO builtin function when the value a is used.
Builtin Function | Representation | Description |
---|---|---|
print(a) | Writes to the terminal a string which represents the value a |
Note than any kind of value is valid for the print function as all data types do have a string representation. This can't be used inside an expression as it doesn't return any value. It can however be used as a standalone function call.
Examples:
Data type | |
---|---|
Integer | print(2) prints 2 |
Float | print(2.5) prints 2.5 |
Bool | print(true) prints true |
String | print("abc") prints "abc" |
Array | print([1, 2, 3]) prints [1, 2, 3] |
List | print(< 1, 2, 3 >) prints < 1, 2, 3 > |
Set | print({1, 2, 3}) prints {1, 2, 3} |
Structure | print({x -> 2 y -> 5}) prints {x -> 2 y -> 5} |
There is only one probabilistic function which is used to randomly generate an integer value using an uniform distribution. The table below explains the behavior of the probabilistic builtin function when the integer value a is used.
Builtin Function | Representation | Description |
---|---|---|
UniformNat | uniformNat(a) | Returns a random integer number which is strictly less than a and greater than or equal to 0 |
Note than only integer values are valid for the operand. Also, the use of this function triggers an probabilistic execution.
Examples:
Data type | |
---|---|
Integer | uniformNat(4) returns a value in {0, 1, 2, 3} with uniform probability |
There is only one structural function which is used to compute a set containing a single element. The table below explains the behavior of the structural builtin function when the value a is used.
Builtin Function | Representation | Description |
---|---|---|
SingletonSet | singletonSet(a) | Returns a set containing a single element a |
Note that this function always return a set and the operand data type is irrelevant. This can also be reproduced with a simpler syntax {a}, where a is the only element in the set.
Examples:
Data type | SingletonSet |
---|---|
Integer | singletonSet(2) is {2} |
Float | singletonSet(2.5) is {2.5} |
Bool | singletonSet(true) is {true} |
String | singletonSet("abc") is {"abc"} |
Array | singletonSet([1, 2, 3]) is {[1, 2, 3]} |
List | singletonSet(<1, 2, 3>) is {<1, 2, 3>} |
Set | singletonSet({1, 2, 3}) is {{1, 2, 3}} |
Structure | singletonSet({x -> 2 y -> 3}) is {{x -> 2 y -> 3}} |
The builtin methods have a high priority; in fact they have the same priority as the dot operator and the bracket operator. However, the order of these operators is relevant in execution. Also, the parenthesis can override these rules. In order to call a builtin method, there is a special syntax which should be used.
Syntax for method-call |
---|
reference.method-name(method-parameters) |
Reference:
The reference usually has to point to a specific location which should be already created (the dynamic allocation doesn't activate in this scenario). The most basic type of reference is an identifier, which already exists or will be automatically created in the environment. A more complex reference is composed out of an identifier and a sequence of brackets and dot operators to indicate a specific location inside a compound data type value.
Method-name:
The methods are all builtin, meaning that the method-name should be chosen from a predefined list of names, depending upon the desired behavior. Custom methods are not supported at the moment.
Method-parameters:
The method-parameters component is a list of comma separated expressions. The number of expressions should match the number of parameters requested by the builtin method. Also, depending on the method, the evaluation data type values should match the parameters constraints (ex: at method works only with specific integers)
There are several query methods which are used in order to get information about some values (strings or compound). They do not modify the value. The table below explains the behavior of the query builtin methods when the target value a, the integer value x and the string value y are used.
Builtin Method | Representation | Description |
---|---|---|
At | a.at(x) | Evaluates to the element on the xth position in the compound value a. _x_ should be greater than 0 and less than the size |
Size | a.size() | Computes the size of a in constant time |
TopBack | a.topBack() | Evaluates to the last element in the value a |
TopFront | a.topFront() | Evaluates to the first element in the value a |
Split | a.split() | Returns an array of characters resulted after spliting the string a |
Split with regex | a.split(y) | Returns an array of strings resulted after splitting the string a with a regular expression _y_ |
Note that all these methods are returning different kind of data types (depending on the operand and method). Also, not all compound data types are eligible for all these methods. The table below illustrates these aspects.
Builtin Method | Data type | Return | Return Type |
---|---|---|---|
At | string, array or list | the element on the xth position in the value | the data type of the element at the xth position |
Size | array, list or set | the number of values nested in the value | integer |
Size | string | the number of characters in the string | integer |
TopBack | list | the last element in the value | the data type of the last element in the value |
TopFront | list | the first element in the value | the data type of the first element in the value |
TopFront | list | the first element in the value | the data type of the first element in the value |
Split | string | the characters which can be found in the string | an array of characters |
Split with regex | string | the strings resulted after splitting the string with a regex | an array of strings |
For the at method, one should provide a valid integer value x.
Examples:
Data type | At | Size | TopBack | TopFront | Split | Split with regex |
---|---|---|---|---|---|---|
String | "abc".at(2) is "c" | "abc".size() is 3 | "abc".split() is ["a", "b", "c"] | "a c".split(" ") is ["a", "c"] | ||
Array | [5, 7, 10].at(1) is 7 | [5, 7, 10].size() is 3 | ||||
List | <5, 7, 10>.at(1) is 7 | <5, 7, 10>.size() is 3 | <5, 7, 10>.topBack() is 10 | <5, 7, 10>.topFront() is 6 | ||
Set | {5, 7, 10}.size() is 3 |
There are several update methods which are used in order to modify some values (string or compound). They do modify the value and return a reference to the modified object. The table below explains the behavior of the query builtin methods when the target value a and values x, y are used.
Builtin Method | Representation | Description |
---|---|---|
Non-Sequential Insert | a.insert(x) | Insert value _x_ into a non-sequential value a |
Sequential Insert | a.insert(x, y) | Insert value _y_ into a sequential value a at position _x_ |
PopBack | a.popBack() | Remove the last element in a sequential value a |
PopFront | a.popFront() | Remove the first element in a sequential value a |
PushBack | a.pushBack(x) | Add the value _x_ as the last element of a sequential value a |
PushFront | a.pushFront(x) | Add the value _x_ as the first element of a sequential value a |
Remove | a.remove(x) | Removes value _x_ from a non-sequential value a |
RemoveAt | a.removeAt(x) | Removes the element at position _x_ from a sequential value a |
RemoveAllEqTo | a.removeAllEqTo(x) | Removes all elements equal to _x_ from value a |
Update | a.update(x, y) | Updates the value from position _x_ to value _y_ inside the sequential value a |
Note that there is a distinction between the sequential (array, list) values and non-sequential values (set). Also, there are restrictions on the values of x and y. All of these are described in the table below.
Builtin Method | Data type | First parameter | Second parameter |
---|---|---|---|
Non-Sequential Insert | set | Any kind of value | |
Sequential Insert | array, list | An integer greater or equal to 0 and less or equal to the size of the target value | Any kind of value |
PopBack | array, list | ||
PopFront | array, list | ||
PushBack | array, list | Any kind of value | |
PushFront | array, list | Any kind of value | |
Remove | set | Any kind of value | |
RemoveAt | array, list | An integer greater or equal to 0 and less than the size of the target value | |
RemoveAllEqTo | array, list | Any kind of value | |
Update | array, list | An integer greater or equal to 0 and less than the size of the target value | Any kind of value |
Note that all these methods return the modified value (which is the same as the target value used). This means that multiple methods can be chained: a.pushBack(x).popBack() is a valid expression.
Examples:
Method | Array | List | Set |
---|---|---|---|
Non-Sequential Insert | {1, 2, 3}.insert(4) is {1, 2, 3, 4} | ||
Insert | [1, 2, 3].insert(1, 4) is [1, 4, 2, 3] | <1, 2, 3>.insert(1, 4) is <1, 4, 2, 3> | |
PopBack | [1, 2, 3].popBack() is [1, 2] | <1, 2, 3>.popBack() is <1, 2> | |
PopFront | [1, 2, 3].popFront() is [2, 3] | <1, 2, 3>.popFront() is <2, 3> | |
PushBack | [1, 2, 3].pushBack(4) is [1, 2, 3, 4] | <1, 2, 3>.pushBack(4) is <1, 2, 3, 4> | |
PushFront | [1, 2, 3].pushFront(4) is [4, 1, 2, 3] | <1, 2, 3>.pushFront(4) is <4, 1, 2, 3> | |
Remove | {1, 2, 3}.remove(2) is {1, 3} | ||
RemoveAt | [1, 2, 3].removeAt(1) is [1, 3] | <1, 2, 3>.removeAt(1) is <1, 3> | |
RemoveAllEqTo | [1, 2, 1].removeAllEqTo(1) is [2] | <1, 2, 1>.removeAllEqTo(1) is <2> | |
Update | [1, 2, 3].update(1, 4) is [1, 4, 3] | <1, 2, 3>.update(1, 4) is <1, 4, 3> |
In the context of using Alk, the variables should not be declared, so there is no concept alike the default initialization. This is due to the fact that, Alk does not create pristine variables which do have a default value (or a garbage value). The variables created are in fact spawned because they are needed into an assign, so no intermediary value is used.
However, there is a similar concept to the default values present in Alk. The idea of unknown value is used when dealing with non-returning functions inside expressions or empty spaces inside dynamically allocated arrays. The next section will show that this unknown value is.
There is an important feature of Alk regarding the allocation. Due to the fact that one wants to use uninitialized compound data values, Alk offers support for dynamically allocation arrays/structures such that the references used do make sense and point to a valid memory address.
For this matter, imagine the following example: a[2].x = 5;. Note that this is the only instruction in the parsed algorithm. As variables should not be declared, this statement should work without any other specifications. For this job, Alk will automatically assign to the variable a an array data type value, which will have the size of 3, in order to allow the use of the bracket operator. Furthermore, at the index 2, a structure should be allocated in order to match the dot operator. Finally, after those two enclosed values were created, the assignment will work as Alk will automatically allocate a component with id x which will be assigned to 5. However, the process above has a practical flow: what happens to the extra allocated space? In the example above, what values will be assigned to a[0] and a[1]. For this matter, Alk uses a special kind of representation, the ? character which will symbolize that the specified additional space is unusable in the current state. So any usage of a[0] or a[1] will result in an error. However, one can assign new values to these references in order to properly access them afterwards.
Examples:
Assignment statement | Final configuration |
---|---|
a[2][3].x.y[1].z = 2; | a |-> [?, ?, [?, ?, ?, {x -> {y -> [?, {z -> 2}]}}]] |
a[0][1][2][3][4] = 2; | a |-> ?, [?, ?, [?, ?, ?, [?, ?, ?, ?, 2]]] |
a.x.y.z = 2; | a |-> {x -> {y -> {z -> 2}}} |
The expression based definitions are the basic way to define one iterable data type value. It allows one to enclose between specific characters, a series of comma separated expressions. Each expression will be evaluated and the result will be considered to be contained by the specified data type in the order provided. Note that a set can change the order of the elements.
Examples:
Representation | Final configuration |
---|---|
a = [1+1, {1, 2, 3} U {2, 3, 4}, 1.2/2]; | a |-> [2, {1, 2, 3, 4}, 0.6] |
a = <1+1, {1, 2, 3} U {2, 3, 4}, 1.2/2>; | a |-> <2, {1, 2, 3, 4}, 0.6> |
a = {1+1, {1, 2, 3} U {2, 3, 4}, 1.2/2}; | a |-> {0.6, 2, {1, 2, 3, 4}} |
The interval based definition provides a way to easily instantiate an iterable data type value with a sequence of integers contained in a specified interval. The order of the elements will be ascending.
Examples:
Representation | Final configuration |
---|---|
a = [1..5]; | a |-> [1, 2, 3, 4, 5] |
a = <1..5>; | a |-> <1, 2, 3, 4, 5> |
a = {1..5}; | a |-> {1, 2, 3, 4, 5} |
This type of representation is well suited in scenarios in which one wants to apply a certain filtering over the elements of a container. The filtering specification has a boolean expression provided which will be run using each element in a source value in order to detect if the chosen element shall pass the filtering or not. This depends on the result of the boolean expression.
Examples:
Representation | Final configuration |
---|---|
a = [x from [1..5] | x % 2 == 0]; | a |-> [2, 4] |
a = < x from [1..5] | x % 2 == 1 >; | a |-> <1, 3, 5> |
a = {x from [1..5] | x % 2 == 0}; | a |-> {2, 4} |
This type of representation is well suited in scenarios in which one wants to apply a certain mapping function over all elements in a source value. The mapping process has an expression whose resulting values will be contained inside the final value.
Examples:
Representation | Final configuration |
---|---|
a = [2 * x | x from [1..5]]; | a |-> [2, 4, 6, 8, 10] |
a = <2 * x | x from [1..5]>; | a |-> <2, 4, 6, 8, 10> |
a = {2 * x | x from [1..5]}; | a |-> {2, 4, 6, 8, 10} |
There are several ways to instantiate an empty iterable data type value. Note that for some of those there are special keywords which automatically evaluated to an empty specified compound value.
Examples:
Representation | Final configuration |
---|---|
a = []; | a |-> [] |
a = < >; | a |-> < > |
a = {}; | a |-> {} |
a = emptyList; | a |-> < > |
a = emptySet; | a |-> {} |
a = emptyStructure; | a |-> {->} |
a = {->}; | a |-> {->} |
Functions can be declared anywhere inside the algorithm and their score is global. This is due to the fact that function declarations are statements, so they can be used inside any sequence of statements. Note that it is recommended to declare the functions to the global scope anyway.
Syntax |
---|
function-name(param-list) modifies global-list |
function-name(param-list) uses global-list |
function-name(param-list) |
function-name() |
function-name() modifies global-list |
function-name() uses global-list |
Function-name:
The function name should be an id after which the function will be identified and eventually be called.
Param-list:
The parameters list is a component which should be replaced with a list of comma separated parameters. These can belong to a set of two types: input and output parameters. The input parameters can be changed, but the modifications won't be reflected in the calling state (so this parameters are in fact copies of the arguments used). The output parameters reference however the values provided at the function call, so any modification over these values will be made on the argument values as well. In order to mark which are the output parameters and which are not, the following syntax is used:
Syntax | Meaning |
---|---|
param-name | input parameter |
out param-name | Output parameter |
The param-name component should be an id with which the value given will be referenced. Note that functions open separate environments, so any variable used here should not affect the ones in the outer scope - so the same id can be used for different values.
Global-list:
The list which is after the modifies keyword is a list of variables from the global scope which can be changed by this function. This syntax is used in order to allow the reuse of some ids which were initially used in the global scope. Note that all the parameters which come after the modifies keyword are output parameters, so any change on those will reflect in the global scope.
Examples:
Algorithm | Output |
---|---|
func(a, b, c) { c = a + b; print(c); } a = 2; b = 3; c = 4; func(a, b, c); print(c); | 5 4 |
func(a, b, out c) { c = a + b; print(c); } a = 2; b = 3; c = 4; func(a, b, c); print(c); | 5 5 |
func(a, b) modifies c { c = a + b; print(c); } a = 2; b = 3; c = 4; func(a, b); print(c); | 5 5 |
func(a, b) { c = a + b; print(c); } a = 2; b = 3; c = 4; func(a, b); print(c); | 5 4 |
The function call is the main way to trigger the code inside a declared function. Note that a function can be called only after it was defined. The number of parameters should match, and the modifies list should have only ids of variables which are already in the global environment. Also, an important fact is that the output parameters can't take expressions or static values as argument. Therefore, the argument for an output parameter can be only an id (a reference).
A function call can be used inside a single function call statement. This kind of function should not necessarily return, as the returning value will be lost. The other use case is function calls inside expressions. For this scenario, the functions should return, otherwise an error will be shown.
There are already examples of function calls inside single statements in the previous section. Function calls inside expressions are shown in the next section.
In order to make a function return a value, the return statement should be used. This will take a computed value and return it to the calling context. Note that the execution of the function is halted at this point, so no other statement after the return will execute.
Syntax |
---|
return; |
return expression; |
Note that there can be no return value. In this case, the return call is used only to trigger the stopping of the function. If a value is used however, the value will be propagated to the calling context.
Expression:
The expression component should evaluate to a valid value which is meant to be sent to the calling context.
Examples:
Algorithm | Output |
---|---|
func(a, b) { return a + b; } a = 2; b = 3; print(func(a, b)); | 5 |
func() modifies a { return 2 * a; } a = 2; a += func() * 2; print(a); | 10 |
func(out a, out b) { a = 2 return ; b = 2; } a = 5; b = 5; func(a, b); print(a); print(b); | 2 5 |
The assignment statement is a way to directly assign a specific value to a reference. The value can be of any kind as the variable can change types at the runtime according to dynamic typing. The reference can be a simple identifier or a more complex reference which will be eventually subject to dynamic allocation.
Syntax |
---|
reference assignment-operator expression; |
Reference:
The reference usually has to point to a specific location in the store which eventually can be created by the dynamic allocation procedure for arrays and structures. The most basic type of reference is an identifier which already exists or will be automatically created in the environment. A more complex reference is composed out of an identifier and a sequence of brackets and dot operators to indicate a specific location inside a compound data type value.
Assignment-operator:
There are several assignment operators which behave suggestively to their representation. The most basic assignment operator is represented through an equal sign =, denoting that the value in the right side will be simply copied to the location suggested by the reference in the left side. The table below shows all the assignment operators and their behaviors:
Assignment operator | Representation | Description |
---|---|---|
Simple assign | = | Assigns the value without modifing it |
Addition assign | += | Assigns the result from the addition operation between the left-value and the specified right-value |
Subtraction assign | -= | Assigns the result from the subtraction operation between the left-value and the specified right-value |
Multiplication assign | *= | Assigns the result from the multiplication operation between the left-value and the specified right-value |
Division assign | /= | Assigns the result from the division operation between the left-value and the specified right-value |
Mod assign | %= | Assigns the result from the mod operation between the left-value and right-value |
Left shift assign | <<= | Assigns the result from the left shift operation between the left-value and right-value |
Right shift assign | >>= | Assigns the result from the right shift operation between the left-value and right-value |
Bitwise and assign | &= | Assigns the result from the bitwise and operation between the left-value and right-value |
Bitwise or assign | |= | Assigns the result from the bitwise or operation between the left-value and right-value |
Expression:
The expression should evaluate to the value which is desired to be assigned to the specified reference. As previously stated, the data type of the result shouldn't respect any limitations due to the dynamic allocation feature.
Examples:
Initial configuration | Assignment | Final configuration |
---|---|---|
a |-> [1, 2, 3] | a = true; | a |-> true |
a |-> "abc" | a += "def"; | a |-> "abcdef" |
a |-> 10.4 | a -= 2.7 * 2; | a |-> 5.0 |
a |-> 2 | a *= (1 << 1) + 1; | a |-> 6 |
a |-> 13 | a /= (6 >> 1); | a |-> 4 |
a |-> 13 | a %= 1 + 1 + 1; | a |-> 1 |
a |-> 5 | a <<= 6 * 2 / 4; | a |-> 40 |
a |-> 47 | a >>= 8 % 5; | a |-> 5 |
a |-> 5 | a &= a + 1; | a |-> 4 |
a |-> 6 | a |= a - 1; | a |-> 7 |
a |-> [1, 2, 3, 4] | a[2] = 5; | a |-> [1, 2, 5, 4] |
a |-> {x -> 3 y -> 2} | a.x = 7; | a |-> {x -> 7 y -> 2} |
a |-> [1, {x -> 3 y -> 2}, 2, 3] | a[1].y = 7; | a |-> [1, {x -> 3 y -> 7}, 2, 3] |
a |-> {x -> [1, 2, 3] y -> 2} | a.x[1] = 7; | a |-> {x -> [1, 7, 3] y -> 2} |
a[0][2].x[2].y = 7; | a |-> ?, ?, {x -> [?, ?, {y -> 7}]} |
The function call statement is used in order to execute a specific function whose return value is not intended to be used in any expression. It is recommended to run in this way only the functions which do not return values. As any function call, this should respect the norms described in the function-call section: match the name of the function, the number of parameters and pass only references to output parameters. Note that this statement can be used in order to also call builtin functions like print.
Syntax |
---|
function-call; |
Function-call:
This is the only part of the statement which should be defined. The definition is already described in the function declaration section.
Examples:
Initial configuration | Function Call | Description | Final configuration |
---|---|---|---|
print([1, 2, 3]); | Use the print builtin function in order to print [1, 2, 3] | ||
a |-> 15 b |-> 12 c |-> 0 | gcd(a, b); | Presume that the custom gcd function computes the greatest common division of a and b and assigns it to _c_ | a |-> 15 b |-> 12 c |-> 3 |
a |-> 2 b |-> 3 | swap(a, b); | Presume that the custom swap function interchanges the values of a and b | a |-> 3 b |-> 2 |
a |-> [5, 2, 4, 1, 3] | sort(a); | Presume that the custom sort function sorts the elements of a in-place | a |-> [1, 2, 3, 4, 5] |
The method call statement is used in order to execute a specific builtin method over a given target value. Is is not recommended to use query builtin methods in this fashion as they will have no effect after all. This is mainly meant to be used together with update builtin methods.
Syntax |
---|
method-call; |
Method-call:
This is the only part of the statement which should be defined. The definition is already described in the methods section.
Examples:
Initial configuration | Method Call | Final configuration |
---|---|---|
a |-> [1, 2, 3] | a.pushBack(4); | a |-> [1, 2, 3, 4] |
a |-> [1, <1, 2, 3>, 3] | a[1].popBack(); | a |-> [1, <1, 2>, 3] |
a |-> {x -> [1, 2, 3] y -> [1]} | a.x.popFront(); | a |-> {x -> [2, 3] y -> [1]} |
The increment/decrement statement is a sugar syntax for a simple assignment. This makes use of the increment/decrement operator described in the expressions section. The meaning of each syntax is related to the meaning of the operator used: increment or decrement, prefix or postfix.
Syntax |
---|
reference++; |
reference--; |
++reference; |
--reference; |
Reference:
The reference usually has to point to a specific location in the store which eventually can be created by the dynamic allocation procedure for arrays and structures. The most basic type of reference is an identifier which already exists or will be automatically created in the environemnt. A more complex reference is composed out of an identifier and a sequence of brackets and dot operators to indicate a specific location inside a compound data type value.
Examples:
Initial configuration | Increment/Decrement | Final configuration |
---|---|---|
a |-> 5 | a++; | a |-> 6 |
a |-> 5.5 | ++a; | a |-> 6.5 |
a |-> 5.5 | a--; | a |-> 4.5 |
a |-> 5 | --a; | a |-> 4 |
Compound instructions are used in order to group several statement into a single one. The execution is still sequential; basically there is no impact on efficiency when using compound instructions. The main reason to use those is to allow the developer to put multiple statements inside a complex statement like a conditional or repetitive instruction. The syntax is intuitive: one should enclose the sequence of statement inside curly braces.
Syntax |
---|
{ statements } |
Statements:
The statement component is a list of statements which are meant to be grouped inside the compound instruction. There is no restriction whatsoever on the type of statements. Examples:
Initial configuration | Assignment | Final configuration |
---|---|---|
a |-> 5 | { a = 5; a = 8; } | a |-> 8 |
a |-> 5.5 | { a += 1; print(a); a -= 2; } | a |-> 4.5 |
The conditional instruction is used in order to put a condition over the execution of a statement (or implicitly a sequence of statements if the compound instruction is used). The conditioning is based on an expression which should evaluate to a bool value (the use of any other kind of data type value will result into an error). In case the execution should choose between two statements based on a condition, the if and else keywords should be used. Otherwise, meaning that only one statement should be be executed or not, it is enough to use only the if keyword.
Syntax |
---|
if (bool-expression) statement1 |
if (bool-expression) statement1 else statement2 |
Bool-expression:
The bool-expression component refers to the condition used for the conditional instruction. It is clear the fact that this expression should evaluate to a bool value. In case the expression evaluates to true, then statement1 will be executed. Otherwise, it depends upon the syntax used. If no else keyword is used, then nothing happens (the expression evaluates to false). In case the else keyword is used and the expression evaluates to false, then statement2 will be executed.
Statement1 and statement2:
Statement1 refers to the statement which will be executed in case the expression evaluates to true. This can be any kind of statement (compound instruction included and even recommended). Statement2 refers to the statement which will be executed in case the else keyword is used and the expression evaluates to false. This also can represent any kind of statement (compound instruction included and even recommended).
Examples:
Initial configuration | Conditional instruction | Final configuration |
---|---|---|
a |-> 5 | if (a % 2 == 0) b = "a is even"; else b = "a is odd"; | a |-> 5 b |-> "a is odd" |
a |-> 10 | if (a % 2 == 0) b = "a is even"; else b = "a is odd"; | a |-> 5 b |-> "a is even" |
a |-> 12345 | if (a > 100) a = 100; | a |-> 100 |
a |-> 24 | if (a > 100) a = 100; | a |-> 24 |
a |-> 2 b |-> 5 | if (a > 0 && b > 0) { a = -a; b = -b; } | a |-> -2 b |-> -5 |
a |-> -2 b |-> -100 | if (a > 0 && b > 0) { a = -a; b = -b; } else { a = 0; b = 1; } | a |-> 0 b |-> 1 |
The while instruction is a repetitive statement with initial check which will execute a specific statement multiple times until a given condition evaluates to false. Note that the condition provided should always evaluate to a bool value, otherwise an error will be displayed. The statement can be of any type as there is no restriction over it (the statement can be even a compound instruction which is in fact recommended).
Syntax |
---|
while (bool-expression) statement |
Bool-expression:
The bool-expression component refers to the condition used for the while instruction. It is clear that this expression should evaluate to a bool value. Before the first iteration, the expression is evaluated. In case it evaluates to true then the statement is executed. Otherwise the process is halted and the while instruction is skipped. After each iteration, the condition is re-evaluated following the same behavior: if the condition evaluates to true the next iteration is triggered. Otherwise, the looping process ends and the while instruction is skipped.
Statement:
Statement refers to the component which should be executed every time the expression evaluates to true. Note that the statement can be of any kind. Due to instruction's definition, there are cases in which this statement will never be executed.
Examples:
Initial configuration | While instruction | Final configuration |
---|---|---|
a |-> 156 | while (a > 100) a--; | a |-> 100 |
a |-> 28 | while (a > 100) a--; | a |-> 28 |
a |-> 1 b |-> 10 | while (abs(a - b) > 5 && a < b) { a++; b--; } | a |-> 3 b |-> 8 |
a |-> 15 b |-> 12 | while (b > 0) { r = a % b; a = b; b = r; } | a |-> 3 b |-> 0 r |-> 0 |
The do-while instrucion is a repetitive statement with final check which will execute a specific statement multiple times until a given condition evaluates to false. Note that the condition provided should always evaluate to a bool value, otherwise an error will be displayed. The statement can be of any type as there is no restriction over it (the statement can be even a compound instruction which is in fact recommended). Comparing to a simple while instruction, the do-while instruction evaluates the condition only at the end. This means that the statement will be executed at least once.
Syntax |
---|
do statement while (bool-expression); |
Bool-expression:
The bool-expression component refers to the condition used for the do-while instruction. It is clear the fact that this expression should evaluate to a bool value. The first iteration will be done without checking the condition. After each iteration, the condition is evaluated: if the condition evaluates to true the next iteration is triggered. Otherwise, the looping process ends and the while instruction is skipped.
Statement:
Statement refers to the component which should be executed every time the expression evaluates to true. Note that the statement can be of any kind. Due to instruction's definition, the statement will execute at least once no matter the condition.
Examples:
Initial configuration | Do-while instruction | Final configuration |
---|---|---|
a |-> 156 | do a--; while (a > 100); | a |-> 100 |
a |-> 28 | do a--; while (a > 100); | a |-> 27 |
a |-> 5 b |-> 5 | do { a++; b--; } while (abs(a - b) > 5 && a < b); | a |-> 6 b |-> 4 |
a |-> 15 b |-> 12 | do { r = a % b; a = b; b = r; } while (b > 0); | a |-> 3 b |-> 0 r |-> 0 |
The repeat-until instruction is a repetitive statement with final check which will execute a specific statement multiple times until a given condition evaluates to true. Note that the condition provided should always evaluate to a bool value, otherwise an error will be displayed. The statement can be of any type as there is no restriction over it (the statement can be even a compound instruction which is in fact recommended). Comparing to a do-while instruction, the do-while is halted when the condition evaluates to false. The repeat-until statement is halted when the condition is evaluated to true
Syntax |
---|
repeat statement until (bool-expression); |
Bool-expression:
The bool-expression component refers to the condition used for the repeat-until instruction. It is clear the fact that this expression should evaluate to a bool value. The first iteration will be done without checking the condition. After each iteration, the condition is evaluated: if the condition evaluates to false the next iteration is triggered. Otherwise, the looping process ends and the while instruction is skipped.
Statement:
Statement refers to the component which should be executed every time the expression evaluates to false. Note that the statement can be of any kind. Due to instruction's definition, the statement will execute at least once no matter the condition.
Examples:
Initial configuration | Repeat-until instruction | Final configuration |
---|---|---|
a |-> 156 | repeat a--; until (a > 100); | a |-> 155 |
a |-> 234 | repeat a--; until (a == 100); | a |-> 100 |
a |-> -10 b |-> 10 | repeat { a++; b--; } until (abs(a - b) < 5); | a |-> -2 b |-> 2 |
a |-> 15 b |-> 12 | repeat { r = a % b; a = b; b = r; } until (b == 0); | a |-> 3 b |-> 0 r |-> 0 |
The for instruction is a repetitive statement with initial check which will execute a specific statement multiple times until a given condition evaluates to true. Note that the condition provided should always evaluate to a bool value, otherwise an error will be displayed. The statement can be of any type as there is no restriction over it (the statement can be even a compound instruction which is in fact recommended). Comparing to a while instruction, the for instruction also provides support for initial assignment (oftenly used for initializing an iterator) and support for intermediary assignment or increase/decrease (oftenly used to change the iterator).
Syntax |
---|
for (initial-expression; bool-expression; intermediary-expression) statement |
Bool-expression:
The bool-expression component refers to the condition used for the for instruction. It is clear the fact that this expression should evaluate to a bool value. At first, the initial-assign is executed. Before the first iteration, the expression is evaluated. In case it evaluates to true then the statement is executed. Otherwise the process is halted and the for instruction is skipped. After each iteration, the condition is re-evaluated following the same behavior: if the condition evaluates to true the next iteration is triggered. Otherwise, the looping process ends and the while instruction is skipped. Note that before the expression is re-evaluated, the intermediary-assign is executed.
Initial-expression:
The initial expression is used in order to assign to an eventual iterator a value. This will be executed before anything else. Note that the initial assign can be omitted.
Intermediary-expression:
The intermediary expression is executed after each loop before the expression is re-evaluated. This should be used in order to change the iterator in a more complex way than an increase/decrease (which can be done with the other syntax version).
Statement:
Statement refers to the component which should be executed every time the expression evaluates to true. Note that the statement can be of any kind. Due to instruction's definition, there are cases in which this statement will never be executed.
Examples:
Initial configuration | For instruction | Final configuration |
---|---|---|
s |-> 0 | for(i = 1; i <= 10; i++) s += i; | s |-> 55 i |-> 11 |
s |-> 0 | for(; s < 10; s++) s *= 2; | s |-> 15 |
s |-> 0 | for(i = 1; i <= 10; i += 2) s += i; | s |-> 25 i |-> 11 |
s |-> 0 | for(i = 1; i != 5 && i < 100; i++) s += i; | s |-> 10 i |-> 5 |
for(s = 0; false; s = 10) s += 2; | s |-> 0 | |
s |-> 0 | for(i = 10; i >= 1; i--) { s += i; s *= 2; } | s |-> 18434 i |-> 0 |
The foreach instruction is a repetitive statement which does not depend upon a given conditional expression. The foreach instruction is meant to be used when one wants to sequentially access the elements of an iterable compound data type value. The statement will assign a given variable to each element from the collection given and afterwards will execute the underlying statement. Note that the values do not reference the original ones inside the data type value (this means that any modification on the foreach variable won't take effect in the original structure).
Syntax |
---|
foreach id from iterable statement |
Id:
The id represent the variable which is meant to be used in order to access the given iterable. This variable can be used afterwards in the underlying statement. The denoted variable shouldn't be necessarily initialized in any way - it will be dynamically assigned if not already in the environment.
Iterable:
The iterable component represent the value which is meant to be iterated. There are only three data type values which are eligible for this operation: array, list and set. The order in which the elements will be iterated depends upon the way in which they are stored. For the array and list data types, the elements will be iterated normally from the lower index to the higher index. The sets however are iterated randomly (however, multiple iterations can iterate the set in the same order).
Statement:
Statement refers to the component which will be executed multiple types. Note that there is no limitation on the type of the statement (it can be a compound instruction). The number of executions is equal to the size of the iterated value. Each iteration will change the environment (as the iterator itself will be changed) so the statement will work on different values of id each time.
Examples:
Initial configuration | Foreach instruction | Final configuration |
---|---|---|
A |-> [1, 2, 3] s |-> 0 | foreach x from A s += x; | A |-> [1, 2, 3] s |-> 6 x |-> 3 |
s |-> 0 | foreach x from {5, 2, 3, 4} s = s * 10 + x; | s |-> 2354 x |-> 4 |
s |-> 0 | foreach x from <5, 2, 3, 4> { s = s * 10 + x; A[x] = 1; } | A |-> [?, ?, 1, 1, 1, 1] s |-> 5234 x |-> 4 |
s |-> 0 A |-> [0, 0, 0, 0, 0] | foreach x from {1, 2, 3} U {2, 3, 4} { s += x; A[x]++; } | A |-> [0, 1, 1, 1, 1] s |-> 10 x |-> 1 |
The break and continue instructions are not repetitive statements themselves. They are controlling the looping in a sense in which a continue statement can halt the execution of a loop while the break statement halts the execution of the entire lopping structure itself. They should be used when only inside of a looping statement (but not necessarily directly inside - it can be enclosed in a conditional statement for example). These only take effect on the closest looping statement, while the outer ones are not effected.
Syntax |
---|
continue; |
break; |
Examples:
Initial configuration | Foreach instruction | Final configuration |
---|---|---|
s |-> 0 | while (s < 10) { s++; if (s == 5) { break; } } | s |-> 5 |
s |-> 0 | for (i = 1; i < 5; i++) { if (i == 3) { continue; } s = s * 10 + i; } | s |-> 124 i |-> 5 |
s |-> 0 i |-> 1 | do { if (i == 3) { break; } s = s * 10 + i; i++; } while (i < 5); | s |-> 12 i |-> 3 |
s |-> 0 i |-> 1 | repeat { i++; if (i == 3) { continue; } s = s * 10 + i; } until (i == 5); | s |-> 245 i |-> 5 |
s |-> 0 A |-> [5, 7, 2, 3, 9] | foreach x in A { if (i == 3) { break; } s = s * 10 + x; } | A |-> [5, 7, 2, 3, 9] s |-> 572 x |-> 3 |
The choose statement is the main tool to trigger a nondeterministic execution. A choose statement nondeterministically chooses an element from a given iterable value and assigns it to a specified variable. One can also specify an expression which should evaluate to a bool value and can be used as a hint for the statement to choose only values validating the expression. If the choose instruction can't find a suitable value, the execution will result in failure.
Syntax |
---|
choose exp-name from iterable; |
choose exp-name from iterable s.t. exp-cond; |
We can consider the first syntax version as a sugar syntax for the case when one doesn't want to put constraints on the selected element: choose id in iterable s.t. true; is equivalent to choose id in iterable;
Exp-name:
The exp-name is an expression that denotes the component which defines the variable which will be used to store the chosen value selected by the choose statement from the iterable value (e.g., x, a[i], p.x, p.x[i], ...).
Iterable:
The iterable is a compound data type value which can be normally iterated and is valid for such operation: array, list and set. The data type used for this is totally irrelevant. However, an empty iterable will always cause a failure in the execution. For the choose to succeed, one should provide a non-empty iterable which eventually has at least one element satisfying the such that clause.
Exp-cond:
The expression exp-cond is not mandatory as it can be seen in the first syntax version. If such expression is defined, the choose statement will only assign the given variable a value which also makes the expression exp-cond evaluate to true. If the expression invalidates all the elements from the list, the execution will fail.
Examples:
Initial configuration | Choose/such that instruction | Final configuration (nondeterministic) | Success/Failure |
---|---|---|---|
choose x from {1, 2, 3} U {2, 3, 4}; | x |-> 3 | success | |
choose x from {}; | failure | ||
A |-> [1, 2, 3, 4, 5] | choose x from A; | A |-> [1, 2, 3, 4, 5] x |-> 1 | success |
choose x from <1..10> s.t. x % 2 == 0; | x |-> 6 | success | |
choose x from {1, 3, 5, 7} s.t. x % 2 == 0; | failure | ||
choose x from <1..10> s.t. x % 2 == 1; | x |-> 5 | success | |
choose x from [4, 3, 2, 1] s.t. x > 4; | failure |
The success and failure statements are used to halt the entire execution of a program. These should be used only in nondeterministic executions in order to symbolize the fact that a selection branch fails or succeeds. An execution can also fail due to the "such that" clause inside the choose statement. However, if one can't decide at that point if a branch should fail or not, it should use the failure statement afterwards. Opposite to this, the success statement marks the end of a branch in a desired state.
Syntax |
---|
failure; |
success; |
Examples:
Initial configuration | Success/Failure Instructions | Final configuration (nondeterministic) | Success/Failure |
---|---|---|---|
choose x in {1, 2, 3, 4}; if (x <= 2) failure; a = x; x = -2; success; | x |-> 1 | failure | |
choose x in {1, 2, 3, 4}; if (x <= 2) failure; a = x; x = -2; success; | a |-> 3 x |-> -2 | success |
Beside the classic builtin functions, which are used in deterministic executions, the random builtin function is the main way to trigger a probabilistic execution. It is used to get a number in a specified range with uniform distribution. That being said, this is a returning function so it should be used inside expressions.
Syntax |
---|
uniformNat(right-limit) |
Right-limit
This limit represents the upper bound of the selection interval. Note that the lower bound is always 0.
Examples:
Initial configuration | UniformNat Function | Final configuration (nondeterministic) |
---|---|---|
x = uniformNat(5); | x |-> 1 with 20% probability | |
x = uniformNat(5); | x |-> 4 with 20% probability | |
a |-> 1 | c = uniformNat(a); | a |-> 1 c |-> 0 with 100% probability |
The uniform instruction is the probabilistic version of the nondeterministic choose statement. The uniform instruction itself has the same syntax as the choose statement, but it used the uniform keyword in the beginning. The major difference is that this uniform statement returns a number from the given source with uniform probabilistic distribution.
Syntax |
---|
uniform exp-name from iterable; |
Id:
The exp-name expression should denote the component which defines the variable which will be used to store a value uniformly selected from the iterable value (e.g., x, a[i], p.x, p.x[i], ...).
Iterable:
The iterable should evaluate to a compound data type value, which can be normally iterated and is valid for such operation: array, list and set. The data type used for this is totally irrelevant. However, an empty iterable will always cause a failure in the execution. For the uniform to succeed, one should provide a non-empty iterable which eventually has at least one element satisfying the such that clause.
Examples:
Initial configuration | Uniform instruction | Final configuration (probabilistic) |
---|---|---|
uniform x from {1, 2, 3} U {2, 3, 4}; | x |-> 3 with 25% probability | |
A |-> [1, 2, 3, 4, 5] | uniform x from A; | A |-> [1, 2, 3, 4, 5] x |-> 1 with 20% probability |
This option is the most important as it defines the file which should be parsed. One can select a single file as an entry-point. However, if there are several files which are to be taken in consideration, make sure to use the include directive in order to copy all the content needed in a single file which will be at the end executed. By copy, one should understand the automatic preprocessing stage done by the Alk interpreter. The files on the disk won't be modified.
Syntax |
---|
-a "file-path" |
File-path:
This component should be replaced with a proper path toward a file containing an alk algorithm. The interpreter will consider the file there as the entry-point. The path can be either absolute or relative. In case the path is relative, the origin will be the current working directory.
Examples:
Command line | Description |
---|---|
./alki.sh -a "main.alk" | the option will tell the interpreter to start interpreting the main.alk file (Linux/Mac) |
./alki.sh -a "/home/user/work/main.alk" | the option will tell the interpreter to start interpreting the /home/user/work/main.alk file (Linux/Mac) |
alki.bat -a "main.alk" | the option will tell the interpreter to start interpreting the main.alk file (Windows) |
alki.bat -a "C:\work\main.alk" | the option will tell the interpreter to start interpreting the C:\work\main.alk file (Windows) |
The initial configuration option will allow the user to set a starting environment. This should be used when testing an algorithm with specific testcases. These testcases should follow the syntax described in the configuration section. The same type of configuration will be eventually showed by the interpreter (if using the metadata option) and can be used as input for other algorithms.
Syntax |
---|
-i "configuration" |
-i "file-path" |
Configuration:
The configuration can be written inline. This means that the interpreter will parse the value of the option in order to retrieve the configuration. Note that no spaces should be used inside and the string quotes should be skipped using the backslash character.
File-path:
As an alternative, one can pass the path to a file containing the initial configuration. This is in fact the recommended version as it is more flexible in terms of syntax. The path can be either absolute or relative. If the relative path is used, the origin is the working directory.
Note that in the examples below, the -a option is required in order to specify an entry-point file and trigger the execution. Also, the extension for the file used as input doesn't matter (in the examples the fictive .in extension is used).
Examples:
Command line | Description |
---|---|
./alki.sh -a "main.alk" -i "a|->12b|->15" | the option will tell the interpreter to use the specified starting configuration (Linux/Mac) |
./alki.sh -a "main.alk" -i "main.in" | the option will tell the interpreter to use the content in the main.in as starting configuration (Linux/Mac) |
./alki.sh -a "main.alk" -i "/home/user/work/main.in" | the option will tell the interpreter to use the content in the /home/user/work/main.in as starting configuration (Linux/Mac) |
alki.bat -a "main.alk" -i "a|->12b|->15" | the option will tell the interpreter to use the specified starting configuration (Windows) |
alki.bat -a "main.alk" -i "main.in" | the option will tell the interpreter to use the content in the main.in as starting configuration (Windows) |
alki.bat -a "main.alk" -i "C:\work\main.in" | the option will tell the interpreter to use the content in the C:\work\main.in as starting configuration (Windows) |
The precision option is used in order to specify the number of digits which should be used after the floating point when using float operations. If this option is not used, the default of 10 is used. In case the precision is not that important, one can set a lower precision like 1 or 2. If computations with higher precision should be used, one should set this option to numbers greater than 10.
Syntax |
---|
-p digits |
Digits:
The digits component should be replaced with a valid positive integer. The number here will represent the number of digits after the floating point when float operations are used.
Examples:
Command line | Description |
---|---|
./alki.sh -a "main.alk" -p 2 | the option will tell the interpreter to use a precision of 2 decimals after the floating point (Linux/Mac) |
./alki.sh -a "main.alk" -p 200 | the option will tell the interpreter to use a precision of 200 decimals after the floating point (Linux/Mac) |
./alki.sh -a "main.alk" | the lack of this option will tell the interpreter to use a precision of 10 decimals after the floating point (Linux/Mac) |
alki.bat -a "main.alk" -p 2 | the option will tell the interpreter to use a precision of 2 decimals after the floating point (Windows) |
alki.bat -a "main.alk" -p 200 | the option will tell the interpreter to use a precision of 200 decimals after the floating point (Windows) |
alki.bat -a "main.alk" | the lack of this option will tell the interpreter to use a precision of 10 decimals after the floating point (Windows) |
This option is used in order to trigger the metadata showing. The metadata represents the final configuration and eventually information about the execution: if it was deterministic, probabilistic or nondeterminstic. If the execution was probabilistic, the probability will be printed. Note that this is a boolean option, which means that no value should be assigned to the option. The simple presence of this option will trigger the behavior described.
Syntax |
---|
-m |
Examples:
Algorithm stored in main.alk | Command line | Platform | Output |
---|---|---|---|
a = 8; b = 10; c = a * b; print("abc"); | ./alki.sh -a "main.alk" -m | Linux/Mac | "abc" a |-> 8 b |-> 10 c |-> 80 |
a = 8; choose x in [1..a]; print(x); | ./alki.sh -a "main.alk" -m | Linux/Mac | 5 a |-> 8 x |-> 5 Note that the executed algorithm is nondeterministic. |
uniform x from {0..7}; print(x); | ./alki.sh -a "main.alk" -m | Linux/Mac | 5 x |-> 5 Note that the executed algorithm is probabilistic. The probability for this execution is: 0.125 |
a = 8; b = 10; c = a * b; print("abc"); | alki.bat -a "main.alk" -m | Windows | "abc" a |-> 8 b |-> 10 c |-> 80 |
a = 8; choose x in [1..a]; print(x); | alki.bat -a "main.alk" -m | Windows | 5 a |-> 8 x |-> 5 Note that the executed algorithm is nondeterministic. |
uniform x from {0..7}; print(x); | alki.bat -a "main.alk" -m | Windows | 5 x |-> 5 Note that the executed algorithm is probabilistic. The probability for this execution is: 0.125 |
This option allows the increasing of the maximum allocation size of one compound data type. All of these dimensions are capped to one billion elements by default, but can be altered through this option. Such capping is a sanity measure to ensure that the dynamic allocation does not overflow. This can somehow limit the memory used by Alk compound data type values.
Syntax |
---|
-z size |
Size:
The size component should be replaced with a valid positive integer number which will symbolize the maximum number of elements allowed inside one compound data. Note that this does not refer to physical memory, so this number is not the number of bytes or similar.
Examples:
Command line | Description |
---|---|
./alki.sh -a "main.alk" -z 2 | the option will tell the interpreter to allow the compound data values to store at most two elements (Linux/Mac) |
./alki.sh -a "main.alk" -p 2000000000 | the option will tell the interpreter to allow the compound data values to store at most 2 billion elements (Linux/Mac) |
./alki.sh -a "main.alk" | the lac of this option will tell the interpreter to allow the compound data values to store at most 1 billion elements (Linux/Mac) |
alki.bat -a "main.alk" -p 2 | the option will tell the interpreter to allow the compound data values to store at most two elements (Windows) |
alki.bat -a "main.alk" -p 2000000000 | the option will tell the interpreter to allow the compound data values to store at most 2 billion elements (Windows) |
alki.bat -a "main.alk" | the lack of this option will tell the interpreter to allow the compound data values to store at most 1 billion elements (Windows) |
The exhaustive execution option is a very powerful feature of Alk. It allows the user to generate multiple executions when using nondeterministic algorithms such that the final result is in fact an exhaustive search over all choices. This is highly valuable as it makes use of multithreading, so an efficient way of computing the results for nondeterminstic algorithms. Note that the option is a boolean option, meaning that it should not have any value assigned to it. The simple presence of this option will tell the interpreter to split the execution when a choose statement is found, such that all choice paths are evaluated in parallel.
Syntax |
---|
-e |
Note that the output is grouped for each execution, this means that the written lines are separated for each execution path. However, they are not sorted in any way. As a common fact, the shorter executions will be written first, while the executions which are way more complex are written at the end (as they end up later). Examples:
Algorithm in main.alk | Command line | Platform | Output |
---|---|---|---|
a = 4; choose x in [1..a]; print(x); | ./alki.sh -a "main.alk" -e | Linux/Mac | 4 Note that the executed algorithm is nondeterministic. 2 Note that the executed algorithm is nondeterministic. 1 Note that the executed algorithm is nondeterministic. 3 Note that the executed algorithm is nondeterministic. |
choose x in [1..3]; for (i=0; i < pow(10, 4) * x; i++) continue; print(x); | ./alki.sh -a "main.alk" -e | Linux/Mac | 1 Note that the executed algorithm is nondeterministic. 2 Note that the executed algorithm is nondeterministic. 3 Note that the executed algorithm is nondeterministic. |
a = 4; choose x in [1..a]; print(x); | alki.bat -a "main.alk" -e | Windows | 4 Note that the executed algorithm is nondeterministic. 2 Note that the executed algorithm is nondeterministic. 1 Note that the executed algorithm is nondeterministic. 3 Note that the executed algorithm is nondeterministic. |
choose x in [1..3]; for (i=0; i < pow(10, 4) * x; i++) continue; print(x); | alki.bat -a "main.alk" -e | Windows | 1 Note that the executed algorithm is nondeterministic. 2 Note that the executed algorithm is nondeterministic. 3 Note that the executed algorithm is nondeterministic. |
The help and version options are used in order to retrieve some information not execution related. This is why these are the only options which can be used without providing an entry-point file with the -a option. The help option provides a list of options which can be used and a short description for each of those. The version option will show the current version used of Alk. Note that these are boolean values.
Syntax |
---|
-h |
-v |
Examples:
Command line | Description |
---|---|
./alki.sh -h | this will show the list of options and how they should be used (Linux/Mac) |
./alki.sh -v | this will show the version of the used Alk (Linux/Mac) |
alki.bat -h | this will show the list of options and how they should be used (Windows) |
alki.bat -v | this will show the version of the used Alk (Windows) |
Alk Reference Manual
written by Lungu Alexandru-Ioan
scientific coordinator Prof. dr. Lucanu Dorel
Faculty of Computer Science, UAIC Iasi