Skip to content

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:

xyz-diagram.png

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.

PrecedenceExpression TypeExample
1Variable-identifiervariable
10Function or method invokationfunction(argument)
20Map{ key: 'value' }
30List[1, 2, 3]
40Comprehension list[1..10]
50String"hello", 'world'
60Number123, 1.5
70Booleantrue, false
80Null Referencenull
90() — parentheses expression(anyExpression)
95-100Unary Operators-(-1), not false
101-140Binary Operators2 + 2, x := 3 + 3
150List or Map Values Accesslist[0]
160Elvis Operatora ?: b
170Ternary Operatora ? b : c
180Test Expressions1 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:

xyz-diagram2.png

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:

JuniperBot Template
{{ 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:

JuniperBot Template
{{ 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.

All rights sniffed.