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

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

Logical (and, or, not, xor)

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.

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

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.