Операторы
Арифметические
+ - * / % (остаток от деления)
Сравнение
== != меньше больше не больше (≤) не меньше (≥)
(старая запись < > <= >= тоже работает)
Логические
и или не (старая запись: && || !)
Побитовые
Побитовые операции записываются словами-функциями — только для целых чисел, оба операнда должны быть одного типа:
| Функция | Что делает | Старая запись | |
|---|---|---|---|
наложить_маску(а, б) | побитовое И | а & б | |
включить_биты(а, б) | побитовое ИЛИ | `а \ | б` |
переключить_биты(а, б) | побитовое исключающее ИЛИ | а ^ б | |
инвертировать_биты(а) | побитовое отрицание | ~а | |
сдвинуть_влево(а, н) | сдвиг влево | а << н | |
сдвинуть_вправо(а, н) | сдвиг вправо | а >> н |
целое а = 12;
целое б = 10;
целое и_результат = наложить_маску(а, б); // 8
целое или_результат = включить_биты(а, б); // 14
целое искл_результат = переключить_биты(а, б); // 6
целое не_результат = инвертировать_биты(5); // -6
Поскольку это обычные вызовы функций, порядок вычислений задаётся
скобками и вложенностью вызовов: наложить_маску(а, б) == 8 сначала
считает маску, потом сравнивает. (В старой записи а & б == 8
разбиралось как а & (б == 8) — приоритет побитовых операторов ниже,
чем у сравнения, как в C/Rust; со словами-функциями этой ловушки нет.)
Сдвиги (сдвинуть_влево, сдвинуть_вправо)
сдвинуть_влево (<<) и сдвинуть_вправо (>>, арифметический/знаковый) —
та же строгость, что у остальных побитовых операторов: только целые числа,
оба операнда (значение и количество сдвигов) — **одного и того же
типа**:
целое а = 1;
целое б = 3;
вывести(целое_в_текст(сдвинуть_влево(а, б))); // 8
целое число = 64;
целое степень = 2;
вывести(целое_в_текст(сдвинуть_вправо(число, степень))); // 16
Смешивание разных целочисленных типов запрещено явно (не по ошибке, а намеренно, ради единообразия с остальными побитовыми операциями) — количество сдвигов нужно привести к типу значения:
целое32 узкий = целое32(1);
сдвинуть_влево(узкий, целое32(3)); // ок — оба целое32
сдвинуть_влево(узкий, 3); // ошибка: 3 по умолчанию — целое (64-бит), а не целое32
⚠️ Честные ограничения:
- Сдвиг числом бит, равным или большим разрядности типа (например,
сдвинуть_влево(целое32(1), целое32(35))), — поведение LLVM/аппаратуры, не
гарантированное языком (обычно берётся по модулю разрядности, но
на это не стоит полагаться).
сдвинуть_вправовсегда арифметический (знаковый) — то же самое решение,
что уже негласно принято для остальных операций над целыми
(/, %, меньше, больше и т. д.): беззнаковые типы (беззнак8/16/32/64)
тоже получают знаковую семантику сдвига вправо. Это унаследованное
ограничение, не новое именно для сдвигов.
Почему в старой записи <</>> не конфликтовали с Список<Список<целое>>. Лексер
специально НЕ трогали — он по-прежнему выдаёт < и > как отдельные,
не склеенные токены, ровно как раньше. Распознавание <</>>
происходит только в ОДНОМ месте — при разборе ВЫРАЖЕНИЙ (два токена
</> подряд без пробела не могут означать ничего, кроме сдвига),
а закрытие вложенных обобщённых типов (Список<Список<целое>>)
целиком происходит в отдельном, никогда не пересекающемся с разбором
выражений коде разбора ТИПОВ — конфликта нет, проверено вживую (см.
ИСТОРИЯ_РАЗРАБОТКИ.md).
Текст
+ — конкатенация. ==/!= — сравнение по содержимому (не по адресу).
Интерполяция строк
Внутри текстового литерала %имя% подставляет значение переменной,
а %(выражение)% — значение ЛЮБОГО выражения (форматируемого типа —
см. ниже):
целое возраст = 25;
текст имя = "Никита";
вывести("Привет, %имя%! Тебе %возраст% лет.");
вывести("Через год будет %(возраст + 1)% лет.");
// Результат интерполяции — обычное текстовое значение: его можно
// сохранить в переменную, вернуть из функции, передать куда угодно,
// склеить через "+" — не только напечатать напрямую.
текст сообщение = "Итог: %(площадь(3, 4))% м²";
Поддерживаются только форматируемые типы: текст, любые целые
(целое, целое8/16/32/64, беззнак8/16/32/64), дробное
(и дробное32/64), верно (печатается как да/нет). Попытка
вставить структуру, контракт или другой неформатируемый тип —
ошибка семантического анализа, а не крах или тихая порча вывода.
Экранирование: %% вставляет буквальный символ %
("скидка 5%%" → скидка 5%). Одиночный %, за которым нет имени
с закрывающим % (например, "100% готово"), тоже остаётся обычным
текстом. Старая запись $имя / ${выражение} по-прежнему работает
(\$ вставляет буквальный $).
⚠️ Честное ограничение: строковые литералы ВНУТРИ %(...)% (и ${...}) не
поддерживаются (вложенная строка вроде "%(ф("да"))%" не разберётся
корректно) — лексер разбирает внешний текстовый литерал целиком ДО
того, как парсер узнаёт о вложенном %(...)%, поэтому неэкранированная
кавычка внутри %(...)% преждевременно закрыла бы внешний литерал.
Обычные выражения без вложенных строк (арифметика, вызовы функций и
методов, доступ к полям, индексация) работают без ограничений.