Операторы

Арифметические

+ - * / % (остаток от деления)

Сравнение

== != меньше больше не больше (≤) не меньше (≥) (старая запись < > <= >= тоже работает)

Логические

и или не (старая запись: && || !)

Побитовые

Побитовые операции записываются словами-функциями — только для целых чисел, оба операнда должны быть одного типа:

ФункцияЧто делаетСтарая запись
наложить_маску(а, б)побитовое Иа & б
включить_биты(а, б)побитовое ИЛИ`а \б`
переключить_биты(а, б)побитовое исключающее ИЛИа ^ б
инвертировать_биты(а)побитовое отрицание~а
сдвинуть_влево(а, н)сдвиг влевоа << н
сдвинуть_вправо(а, н)сдвиг вправоа >> н
целое а = 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% готово"), тоже остаётся обычным текстом. Старая запись $имя / ${выражение} по-прежнему работает (\$ вставляет буквальный $).

⚠️ Честное ограничение: строковые литералы ВНУТРИ %(...)% (и ${...}) не поддерживаются (вложенная строка вроде "%(ф("да"))%" не разберётся корректно) — лексер разбирает внешний текстовый литерал целиком ДО того, как парсер узнаёт о вложенном %(...)%, поэтому неэкранированная кавычка внутри %(...)% преждевременно закрыла бы внешний литерал. Обычные выражения без вложенных строк (арифметика, вызовы функций и методов, доступ к полям, индексация) работают без ограничений.