Expressions Precedence
A template engine parser is a complex mechanism consisting of many grouped and interconnected grammar rules, which are quite difficult to fully describe. In this article, we will try to explain as simply as possible the rules and order in which expressions are evaluated.
Expressions Tree
Any expression is a set of ordered rules by which the template engine parser attempts to recognize what exactly a particular expression is.
Many expressions can consist of subexpressions (for example, the right and left operands of binary expressions), which the template engine also attempts to recognize using the same set of rules.
Complex compound expressions are decomposed into a kind of "tree" of nodes where each node is an expression. Expressions and their rules have certain precedences determining the order in which complex expressions are decomposed that also affects the order in which they are evaluated.
For example, in order to calculate the simplest compound binary expression x * y + z the following tree is constructed by taking into account the precedences:

This example shows how the template engine decomposes the expression from the lowest-precedence root expression (sum) to simpler expressions at the highest-precedence leaves, as shown by the solid arrows.
After constructing this tree, it is evaluated from the highest-precedence leaves and nodes to the lowest-precedence root, as shown by the dashed arrows.
Expressions Precedences
To decompose an expression, the template engine sequentially tries to recognize a specific expression type in it according to the table below, starting with the lowest precedence.
Don't get confused!
Expression decomposition goes from the lowest precedence to highest to construct the expression evaluation tree.
The expression is evaluated starting from the leaves having the highest precedence to the root having the lowest precedence.
TIP
The lower the number, the higher the precedence. In other words, the lower an expression is in the list, the lower its precedence.
| Precedence | Expression Type | Example |
|---|---|---|
| 1 | Variable-identifier | variable |
| 10 | Function or method invokation | function(argument) |
| 20 | Map | { key: 'value' } |
| 30 | List | [1, 2, 3] |
| 40 | Comprehension list | [1..10] |
| 50 | String | "hello", 'world' |
| 60 | Number | 123, 1.5 |
| 70 | Boolean | true, false |
| 80 | Null Reference | null |
| 90 | () — parentheses expression | (anyExpression) |
| 95-100 | Unary Operators | -(-1), not false |
| 101-140 | Binary Operators | 2 + 2, x := 3 + 3 |
| 150 | List or Map Values Access | list[0] |
| 160 | Elvis Operator | a ?: b |
| 170 | Ternary Operator | a ? b : c |
| 180 | Test Expressions | 1 is null |
Precedence Control
To control the priority of expression evaluation, use parentheses. For example, if the example above would be written as x * (y + z), the expression tree would look as follows:

The example above shows that the tree has changed so that the sum expression has higher precedence than the multiplication expression.
Limitations
The template engine's parser architecture isn't perfect, and in some cases, parentheses must be used to help it properly decompose expressions.
This is particularly true for List or Map Values Access expressions in combination with binary operators. Take a look at this expression:
{{ list[0] + list[1] }}
This expression will return an error. Due to the internal implementation shenanigans of the access operator, the template engine will not be able to correctly decompose such a template. It must be assisted in this by wrapping one or both of the operands in parentheses:
{{ list[0] + (list[1]) }}
Fixing these minor inconveniences would require a complete redesign of most of the parser's rules, which could lead to other issues in unexpected places, so we're leaving this behavior as is for now.