---
url: /templates/advanced/expressions.md
description: >-
Операторы и выражения шаблонного движка — арифметика, сравнения, логика,
конкатенация, pipe, Элвис, тернарный оператор, тесты is и таблица приоритетов.
---
# Выражения и операторы
Выражение — все, что вычисляется в значение: member.rank.level + 1, chat.memberCount >= 1000, member.username ?: 'нет'. Выражения пишутся внутри {{ }} и в тегах.
## Арифметика { #math }
| Оператор | Что делает | Пример | Результат |
|---|---|---|---|
| `+` | Сложение | `2 + 3` | `5` |
| `-` | Вычитание | `10 - 4` | `6` |
| `*` | Умножение | `2.5 * 2` | `5` |
| `/` | Деление с дробной частью | `7 / 2` | `3.5` |
| `//` | Деление с округлением до целого (0,5 — от нуля) | `5 // 2` | `3` |
| `%` | Остаток от деления (со знаком делимого) | `7 % 3` | `1` |
| `^` | Степень | `2 ^ 3` | `8` |
| `**` | Умножение с отбрасыванием дробной части | `2.2 ** 2.2` | `4` |
Оба операнда приводятся к числу: `'12' + 3` дает `15`. Если текст не число, результат пустой (`Undefined`), а в [строгом режиме](./types#strict) — ошибка `type_error`. Деление на ноль — ошибка `division_by_zero`.
::: warning `**` — это не степень
`**` — целочисленное умножение, а степень — `^`.
:::
## Склейка строк { #concat }
Оператор `~` превращает оба операнда в строки и склеивает их:
```template
{% set text = 'Уровень ' ~ member.rank.level ~ '!' %}
{{ "5" ~ "2" }} {# 52, а не 7 #}
```
## Сравнения { #compare }
| Оператор | Значит |
|---|---|
| `==`, `!=` | Равно, не равно |
| `<`, `<=`, `>`, `>=` | Меньше, меньше или равно, больше, больше или равно |
| `in` | Содержится ли: элемент в списке, ключ в карте, подстрока в строке |
Если оба значения можно превратить в числа, сравниваются числа, иначе — строки. Поэтому `'10' == 10` — `true`, а `null == 0` — тоже `true`: пустое значение приводится к нулю.
```template
{{ 'Броулбол' in ['Броулбол', 'Нокаут'] }} {# true #}
{{ 'vip' in { vip: 500 } }} {# true #}
{{ 'клуб' in 'Мой клуб Astral' }} {# true #}
```
## Логика { #logic }
| Оператор | Значит |
|---|---|
| `and` | И: истинно, если истинны оба |
| `or` | ИЛИ: истинно, если истинно хотя бы одно |
| `not` | НЕ: меняет истину на ложь |
`and` и `or` вычисляются лениво: если результат ясен по левой части, правая не вычисляется. Результат всегда логический: `true` или `false`.
::: danger `and` и `or` — один приоритет
В этом языке `and` и `or` равноправны и выполняются слева направо: `a or b and c` — это `(a or b) and c`. В большинстве языков программирования иначе. Всегда ставьте скобки, если смешиваете их. Редактор предупредит об этом.
:::
## Pipe: передача в функцию { #pipe }
Вертикальная черта передает значение слева первым аргументом в функцию справа:
| Запись | То же самое |
|---|---|
| `x \| abs` | `abs(x)` |
| `name \| upper` | `upper(name)` |
| `list \| join(', ')` | `join(list, ', ')` |
| `tag \| upper \| replace('#', '')` | `replace(upper(tag), '#', '')` |
Pipe работает и с [макросами](./tags#macro).
::: danger Pipe выполняется последним
У `|` очень низкий приоритет — ниже, чем у сравнений и арифметики. Запись `members | length < 10` значит `members | (length < 10)`, а не «длина меньше 10». Заключайте pipe в скобки: `(members | length) < 10`.
:::
## Значение по умолчанию: `?:` { #elvis }
Оператор Элвис `a ?: b` возвращает `a`, если оно [истинно](./types#truthy), иначе `b`:
```template
{{ member.username ?: 'без username' }}
{% set page = parameters.get('page') ?: 0 %}
```
## Тернарный оператор: `? :` { #ternary }
`условие ? если_да : если_нет`:
```template
{{ member.isPremium ? '⭐ Premium' : 'обычный аккаунт' }}
{{ r.type == 'project' ? 'проектный' : 'клубный' }}
```
## Присваивание с возвратом: `:=` { #walrus }
`:=` присваивает значение существующей переменной и сразу возвращает его — удобно внутри условия:
```template
{% set coins = 0 %}
{% if (coins := member.getAttribute('coins').value) > 100 %}Богач: {{ coins }}{% endif %}
```
`:=` не создает новую глобальную переменную, но обновляет уже существующую.
## Тесты `is` { #tests }
| Тест | Истинно, когда | Пример |
|---|---|---|
| `is null` | Значение `null` | `x is null` |
| `is defined` | Значение определено (не `Undefined`) | `parameters.page is defined` |
| `is empty` | Пусто: `null`, `Undefined`, `''`, `[]`, `{}`, `0` | `arguments.get(1) is empty` |
| `is number` | Число или текст, который можно превратить в число | `arguments.get(1) is number` |
| `is even`, `is odd` | Четное, нечетное | `loop.index is even` |
| `is iterable` | Список, карта или страница индекса | `x is iterable` |
| `is divisible by (n)` | Делится на `n` без остатка | `9 is divisible by (3)` |
| `is same as (x)` | Тот же самый объект | `null is same as (null)` |
Любой тест можно отрицать: `is not`.
У тестов самый низкий приоритет: `a and b is null` читается как `(a and b) is null`. Если сочетаете тест с `and` или `or`, берите тест в скобки: `(x is null) or y`.
```template
{% require arguments.get(1) is number returning 'Нужно число' %}
{% if loop.index is divisible by (3) %}{% do row() %}{% endif %}
```
## Приоритет операторов { #precedence }
Чем меньше число, тем раньше выполняется операция. Операторы одного уровня выполняются слева направо.
| Приоритет | Конструкция | Пример |
|---|---|---|
| 1 | Переменная | `member` |
| 10 | Вызов функции или метода | `round(x)`, `member.getAttribute('k')` |
| 20–80 | Литералы: карта, список, диапазон, строка, число, логическое, `null` | `[1, 2]` |
| 90 | Скобки | `(a + b)` |
| 95 | Унарный минус | `-(-1)` |
| 100 | `not` | `not false` |
| 101 | Доступ к свойству `.` и по индексу `[ ]` | `member.id`, `list[0]` |
| 103 | Степень `^` | `2 ^ 3` |
| 105 | `*`, `**`, `/`, `//`, `%` | |
| 110 | `+`, `-` | |
| 112 | Склейка `~` | `'a' ~ 'b'` |
| 115 | `<`, `<=`, `>`, `>=`, `in` | |
| 120 | `==`, `!=` | |
| 125 | `and`, `or` | |
| 130 | Pipe `\|` | `-5 \| abs` |
| 140 | Присваивание `:=` | `(x := 5) > 0` |
| 160 | Элвис `?:` | `a ?: 'по умолчанию'` |
| 170 | Тернарный `? :` | `a ? 1 : 2` |
| 180 | Тесты `is`, `is not` | `x is null` |
::: tip Скобки вокруг `not` и минуса
`not` и унарный минус стоят в таблице выше, чем точка. Чтобы не гадать, как прочитается `not member.bot` или `-member.rank.level`, пишите со скобками: `not (member.bot)`, `-(member.rank.level)`.
:::
::: info Запись `list[0] + list[1]`
Доступ по индексу стоит на одном уровне с точкой, поэтому такая запись разбирается и работает без скобок.
:::