Expression reference
The AX expression language is used in mapping and condition nodes, and allows arbitrary data transformations in script form. It is the most powerful way we provide to manipulate data.
This reference will give an overview over the available syntax elements, then follow with detailed descriptions and examples for all operators and library functions.
Syntax
Formal grammar
statement ::= bool_op
bool_op ::= [bool_op BOOL_OP] not_op
not_op ::= ["not"] in_op
in_op ::= [in_op "in"] comp_op
comp_op ::= [comp_op COMPARISON_OP] add_op
add_op ::= [add_op ADD_OP] mult_op
mult_op ::= [mult_op MUL_OP] parens
parens ::= "(" statement ")" | atom
atom ::= literal | parameter | function_call | if_expr
literal ::= bool | number | string | list | object | lambda
bool ::= "true" | "false" | "TRUE" | "FALSE" | "True" | "False"
number ::= ["+"|"-"] "0".."9"+ ["." "0".."9"+] [["+"|"-"] ("e"|"E") "0".."9"+]
string ::= ESCAPED_STRING
list ::= "[" (statement ("," statement)*)? "]"
object ::= "{" (statement ":" statement ("," statement ":" statement)* )? "}"
lambda ::= "[" NAME ("," NAME)* "->" statement "]"
parameter ::= "#" dotted_access
dotted_access ::= indexed_access ("." ["#"] indexed_access)*
indexed_access ::= NAME ("[" ( ("#" NAME) | "0".."9"+ | ESCAPED_STRING ) "]")*
function_call ::= NAME "(" (statement ("," statement)*)? ")"
if_expr ::= "if" statement "then" statement "else" statement
COMPARISON_OP ::= "==" | "!=" | "<=" | "<" | ">=" | ">"
ADD_OP ::= "+" | "-"
MUL_OP ::= "//" | "*" | "/" | "%"
BOOL_OP ::= "and" | "or" | "xor"
ESCAPED_STRING
are characters quoted in "
, i.e. "foo"
. If the string contains "
, it is masked as the sequence \"
.
NAME
is a sequence of letters, digits or underscores, i.e. foo_123
. The first character must not be a digit.
Operator precedence
The following table lists all operators, ordered by their precedence, from least binding (evaluated last) to most binding (evaluated first). Operators in the same group are evaluated from left to right.
Operator(s) | Description |
---|---|
or , and , xor | Boolean operations |
not x | Boolean negation |
in | Membership/substring test |
== , != , <= , < , >= , > | Equality and comparisons |
+ , - | Addition and subtraction |
* , / , // , % | Multiplication, division, floor division, modulo |
(x) , #x.y[z] , f(x) , if ... then ... else | Grouping, variable access, function calls, conditionals |
This means that 2 + 3 * 4
is equivalent to 2 + (3 * 4)
and yields 14, while both (2 + 3) * 4
or int(2 + 3) * 4
would yield 20.
Literals
All of the types discussed in data types can be created using an expression. This is called a literal.
Booleans
Booleans can be spelled as true
/True
/TRUE
and false
/False
/FALSE
.
Numbers
Numbers can have an optional sign (+
/-
) and decimal part (.123
). Additionally, they support scientific E notation.
Examples:
1
-2
3.14159
+1234.56
1.23e5 (= 123 000)
Strings
String literals are any characters quoted in "
. If the character "
itself should appear in a string, it must be written ("masked") as \"
.
There are no other quotation rules, which makes the string equivalent to "raw strings" in other languages.
Examples:
"" (the empty string)
"foo"
"this is a string"
"\"Hello\", she said"
Lists
Lists are formed from comma-separated expressions within []
:
[] (the empty list)
["foo"] (one-element list containing the string "foo")
[1, 2, 3]
["foo", 2, {}] (values of any type can appear in a list)
Objects
Similar to lists, objects are comma-separated key-value pairs enclosed by {}
. Key and value are separated by the character :
. While both key and value can be arbitrary expressions, the key must result in a string value when evaluated:
{} (the empty object)
{"a": "b", "b": "c"}
{upper(#x): lower(#x)} (Maps the upper-case spelling of #x to its lower-case spelling)
{"a": []} (all types are allowed as value)
{[]: "a"} (not allowed!)
This syntax is compatible with JSON objects, but note that strings must not contain any escape sequences (except \"
)
Parameter access
Parameters can be accessed by using #
plus the parameter name:
param1 = "a"
#param1
"a"
List elements can be extracted by index (starting at 0):
param1 = ["a", "b", "c"]
#param1[1]
"b"
The index value can itself be a parameter:
param1 = ["a", "b", "c"]
param2 = 0
#param1[#param2]
"a"
Objects can be navigated similarly, by using their key strings:
param1 = {"a": 1, "b": 2, "c": 3}
#param1["c"]
3
However, if the key is constant and does not contain special characters, .
can be used as shorthand:
param1 = {"a": 1, "b": 2, "c": 3}
#param1.c
3
It's possible to combine these to access nested structures:
complex_object = {
"some_key": "a",
"letters": {
"a": [1], "b": [2, 3], "c": [4, 5, 6]
}
}
letter = "b"
#complex_object.letters[#letter][0]
2
Operators
+
, *
, …)
Arithmetic (number + number
Adds two numbers.
param1 = 2
param2 = 3
#param1 + #param2
5
number - number
Subtracts two numbers.
param1 = 3
param2 = 2
#param1 - #param2
1
number * number
Multiplies two numbers.
param1 = 2
param2 = 3
#param1 * #param2
6
number / number
Divides two numbers.
param1 = 3
param2 = 2
#param1 / #param2
1.5
WARNING
Output is a decimal.
number // number
Divides two numbers, rounding towards zero to give a whole number.
param1 = 14
param2 = 5
#param1 // #param2
2
number % number
Returns the remainder of the division x // y, so that x == (x // y) * y + (x % y)
param1 = 14
param2 = 5
#param1 % #param2
4
==
, >=
, …)
Comparison (Equality (==)
Compares two values and checks if they are equal.
Lists are equal if they have the same length, and a[i] == b[i]
for every index i.
Objects are equal if they have the same set of keys, and a[k] == b[k]
for every key k.
param1 = "a"
param2 = "b"
#param1 == #param2
false
param1 = "a"
param2 = "a"
#param1 == #param2
true
param1 = 2
param2 = 3
#param1 == #param2
false
param1 = 2
param2 = 2
#param1 == #param2
true
param1 = [1, 2]
param2 = "c"
[1, 2, "c"] == #param1 + [#param2]
true
Inequality (!=)
The inverse of ==
, i.e. the same as not (a == b)
.
param1 = "a"
param2 = "b"
#param1 != #param2
true
param1 = "a"
param2 = "a"
#param1 != #param2
false
param1 = 2
param2 = 3
#param1 != #param2
true
param1 = 3
param2 = 3
#param1 != #param2
false
Greater than/less than (<, <=, >, >=)
Only available if both operands are numbers. Apply int()
or numeric()
first if necessary.
#a <= #b
is equivalent to #a < #b or #a == #b
.
2 < 3 -> true
3 < 3 -> false
3 <= 3 -> true
3 > 2 -> true
3 > 3 -> false
3 >= 3 -> true
and
, or
, not
, xor
)
Logical (These operators are used to combine boolean (yes/no) statements, e.g. "the first condition, but not the second one".
Logical NOT
Negates the logical value of its following boolean value.
Truth Table:
parameter 1 | Output |
---|---|
true | false |
false | true |
param1 = true
not #param1
false
param1 = false
not #param1
true
Logical AND
Logical conjunction connects two boolean values by the operator and.
Truth Table:
parameter 1 | parameter 2 | Output |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
param1 = true
param2 = true
#param1 and #param2
true
param1 = true
param2 = false
#param1 and #param2
false
Logical OR
Logical disjunction connects two boolean values by the operator or.
Truth Table:
parameter 1 | parameter 2 | Output |
---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
param1 = true
param2 = false
#param1 or #param2
true
param1 = false
param2 = false
#param1 or #param2
false
Logical XOR
Exclusive or, i.e. "one but not both". Equivalent to (a or b) and not (a and b)
.
Truth Table:
parameter 1 | parameter 2 | Output |
---|---|---|
true | true | false |
true | false | true |
false | true | true |
false | false | false |
+
, *
)
Concatenation (string + string
Concatenates two or more strings.
param1 = "foo"
param2 = "bar"
#param1 + #param2
"foobar"
list + list
Returns a new list containing all elements of the first, followed by all elements of the second.
param1 = [1, 2]
param2 = [2, 3]
#param1 + #param2
[1, 2, 2, 3]
string * number
Repeats a string.
param1 = "ab"
#param1 * 3
"ababab"
in
string in string
Checks if first argument is a substring of the second (case sensitive). For convenience, numbers are also allowed and implicitly cast to string.
"oob" in "foobar" -> true
"FOO" in "foobar" -> false
2 in "123" -> true
value in list
Checks if value is an element of the list (exact match, i.e. ==
).
3 in [1, 2, 3] -> true
"3" in [1, 2, 3] -> false
"foo" in ["foo", "bar"] -> true
"foo" in ["foobar"] -> false
if ... then ... else
This construct first evaluates the expression following if, and implicitly casts it to bool. If it is true, the expression following then will be evaluated and returned, otherwise the expression following else.
WARNING
All three sections must be present – the else part cannot be omitted.
input_boolean = true
input_string1 = "a"
input_string2 = "b"
if #input_boolean then #input_string1 else #input_string2
"a"
value = 3
divisor = 0
if #value > 0 and #divisor > 0 then
#value / #divisor
else
0
0
This construct can be nested to test more complicated conditions. However, each such nesting must have all three sections.
Here is an example of a nested if/then/else:
number = 7
if #number = 0 then
"exactly zero"
else
if #number > 10 then
"kinda big"
else
"could be just right or smaller than 0"
"could be just right or smaller than 0"
User-defined functions/lambdas
Several functions allow customization by calling a user-defined function, also called a lambda expression. For example, in case of filter
, the function receives two arguments: the list itself, and a lambda expression. The expression is then evaluated for each item in the list; if it yields True, the element is kept, otherwise it is discarded.
A simple lambda expression which would multiply its two arguments:
[argument1, argument2 -> #argument1 * #argument2]
The most basic case, which just returns its argument:
[x -> #x]
Within the lambda expression, the arguments work just like parameters in a mapping node, but they are not available outside of it; we say they are private to the lambda expression. If an argument has the same name as a parameter, it takes priority and the parameter value cannot be used within the lambda.
The names of arguments do not matter for functionality. For example filter()
will always use the first argument for the item value and the second for the item index, no matter what they are named.