Приоритет выражений
Парсер шаблонного движка — сложный механизм, состоящий из множества сгруппированных и взаимосвязанных грамматических правил, полностью описать которые довольно сложно. В данной статье мы постараемся как можно проще объяснить, по каким правилам и в каком порядке вычисляются те или иные выражения.
Дерево выражений
Любое выражение — это набор упорядоченных правил, по которым парсер шаблонного движка пытается распознать, что именно представляет собой то или иное выражение.
Многие выражения могут состоять из подвыражений (например, правые и левые операнды бинарных выражений), которые шаблонный движок так же пытается распознать по тому же самому набору правил.
Сложные составные выражения декомпозируются в своеобразное "дерево", где каждый узел является выражением. У выражений и их правил есть определённые приоритеты, которые определяют порядок декомпозиции сложных выражений и порядок их вычисления соответственно.
Например, для вычисления простейшего составного бинарного выражения x * y + z строится такое дерево с учётом приоритетов:

На примере выше шаблонный движок производит декомпозицию выражения от выражения в корне с наименьшим приоритетом (сложение), к более простым выражениям в листьях с наивысшим приоритетом, как показано сплошными стрелочками.
После построения этого дерева, происходит его вычисление от листьев и узлов с наивысшим приоритетом, к корню с наименьшим приоритетом, как показано стрелочками прочерком.
Приоритет выражений
Для декомпозиции выражения шаблонный движок последовательно пытается распознать в нём конкретный тип выражения согласно таблице ниже, начиная с самого низкого приоритета.
Не запутайтесь!
Декомпозиция выражения происходит от самого низкого приоритета к высокому для построения дерева вычисления выражения.
А вот вычисляется оно уже начиная с листьев с высоким приоритетом к корню с низшим приоритетом.
TIP
Чем меньше число, тем выше приоритет выражения. Другими словами, чем ниже выражение в списке, тем ниже его приоритет.
| Приоритет | Тип выражения | Пример |
|---|---|---|
| 1 | Переменная-идентификатор | variable |
| 10 | Вызов функции или метода | function(argument) |
| 20 | Карта | { key: 'value' } |
| 30 | Список | [1, 2, 3] |
| 40 | Список-диапазон | [1..10] |
| 50 | Строка | "hello", 'world' |
| 60 | Число | 123, 1.5 |
| 70 | Логический тип | true, false |
| 80 | Нулевая ссылка | null |
| 90 | () — выражение-скобки | (anyExpression) |
| 95-100 | Унарные операторы | -(-1), not false |
| 101-140 | Бинарные операторы | 2 + 2, x := 3 + 3 |
| 150 | Доступ к значениям списков и карт | list[0] |
| 160 | Оператор Элвиса | a ?: b |
| 170 | Тернарный оператор | a ? b : c |
| 180 | Тестовые выражения | 1 is null |
Контроль приоритетов
Для контроля приоритетов вычисления выражений необходимо использовать круглые скобки. Например, если пример выше был бы записан как x * (y + z), то дерево такого выражения выглядело бы следующим образом:

На примере выше дерево изменилось таким образом, что выражение сложения будет иметь приоритет выше, чем выражение умножения.
Ограничения
Архитектура парсера шаблонного движка не идеальна и в некоторых случаях необходимо использовать скобки, чтобы помочь ему произвести правильную декомпозицию выражения.
В особенности этому подвержено выражение доступа к значениям списков и карт в комбинации с бинарными операторами. Например, выражение:
{{ list[0] + list[1] }}
Это выражение вернёт ошибку. Из-за особенностей внутренней реализации оператора доступа, шаблонный движок не сможет правильно декомпозировать такой шаблон, ему необходимо помочь это сделать, обернув один или оба из операндов в скобки:
{{ list[0] + (list[1]) }}
Исправление таких мелких неудобств потребует полного пересмотра архитектуры большинства правил парсера, что в свою очередь может привести к возникновению других проблем в других неожиданных местах, поэтому пока что мы оставляем это поведение как есть.