Руководство

Как писать на «Глаголе» и как пользоваться площадкой. Язык живой — примеры ниже исполняются на площадке прямо сейчас.

У языка два стиля — регистра сложности над одним ядром: начинающий словесный, для школьников (плюс, равно, больше) и профессионал компактный, символьный (+, =, >). Обе формы — синонимы; выбираешь по вкусу.

Язык

Как исполняется программа пример №79

Твой файл — это и есть главное. Отдельного «главного класса»/main нет: запускаешь файл — он исполняется сверху вниз. Действия (напечатай …, вызовы) и значения (x -- это …) выполняются по порядку.

Но определения — глаголы и типы — «подняты»: видны во всём файле независимо от места, как методы в Java. Поэтому можно писать главную логику сверху, а вспомогательные глаголы — ниже:

напечатай (факториал 5)                    прим 120  глагол ниже, но виден

факториал числа -- это если число равно 0, то 1, иначе число умножить на (факториал (число минус 1))

Коротко: «рецепты» (глаголы/типы) — вне порядка (как методы), «действия и значения» — по порядку (как строки в теле метода). Значение нельзя использовать до его связывания — оно вычисляется на своём месте.

Модуль — набор определений (раздел/библиотека): в нём только глаголы/типы, без исполняемого кода — подключи ничего «не запускает». А запуск целого приложения (сервера) — отдельной строкой-примитивом поручи приложение … (в работе).

Сущность (неизменяемое имя) пример №2

Связывание значения с именем — определение, не присваивание. Переопределять нельзя.

приветствие -- это "Здравствуй, мир!"
напечатай приветствие          прим Здравствуй, мир!

Диграф -- нормализуется в тире «—» (как TeX). Комментарий — слово прим до конца строки.

Имена сущностей пример №30

Имя — смысловое существительное (ширина, итог, цена), а не x, x2. Имя можно склонять — падеж несёт роль аргумента, — поэтому цифру к слову приклеивать нельзя (см. ниже). Нужны две похожие величины — дай каждой своё имя:

ширина -- это 3
высота -- это 5

Богатая палитра именования — одно имя может иметь несколько форм над одним ядром:

Многословные имена пример №73

Часто нужны две-три похожие величины. Не число1/число2 (цифра к слову — ошибка) и не костыль-дефис — а естественные русские формы:

Видимость имени человеку даёт подсветка (в редакторе/на площадке) и выделение в агентском выводе, а не кавычки: канон хранения — голый (Пункт А). Отдельно — формульный индекс-символ x_1 (см. пример №46): там основа — один символ; здесь — целое слово с буквой-меткой.

Строгое и ленивое — когда вычисляется пример №6

Вид глагола решает, когда считать значение:

Форма связыванияКогда вычисляется
имя -- это …лениво — при первом обращении, с запоминанием
… помни как имялениво (несовершенный вид)
… запомни / запиши / зафиксируй как имястрого — сразу (совершенный вид)

Ленивое (-- это, помни) считается при первом обращении и запоминается (мемоизация) — поэтому им можно ссылаться вперёд. Строгое (запомни) — «сделано сейчас». У глаголов: инфинитив → аргументы жадные, отглагольное существительное → ленивые.

Честная ленивость (пример №109): неиспользуемое ленивое не вычисляется вообще — даже если внутри ошибка. «Ошибка» ниже не срабатывает, потому что её ни разу не тронули; программа спокойно идёт дальше — одинаково на всех трёх бэкендах (интерпретатор, компилятор в байт-код, браузер):

1 / 0, помни как ошибка     прим деление на ноль — но её не используем
напечатай "работаем дальше"       прим не тронута → не считается, падения нет

Потоки — бесконечные ленивые последовательности пример №110

Поток — ленивая, потенциально бесконечная последовательность (аналог Java Stream, но по-русски). Он бесконечен «на бумаге»; считается ровно столько, сколько возьмёт терминал. Печатать поток целиком нельзя — он потенциально бесконечен; язык честно об этом скажет и подскажет взять конечное.

Условие пишется прямо на месте (пример №114): через неявный элемент оно, без заведения предиката — отфильтровывай натуральные, оно меньше 5, пока натуральные, оно меньше 10. Маркер — запятая перед оно; работает и над обычным списком (отфильтруй … , оно больше 2). Именованный предикат в творительном (… небольшим) тоже остаётся допустим.

Свёртка потока — сперва возьми конечное (сумма (пока натуральные небольшим)): свёртка обязана пройти все элементы, а поток бесконечен. Кванторы все/никакой над бесконечным потоком без контрпримера не завершатся — как любой ленивый язык (на площадке runaway останавливается лимитом шагов песочницы, а не висит). Шаги конвейера чисты: действие/условие — значение-глагол, а он не бывает повелительным, поэтому напечатай/эффекты в ленивый шаг не попадают — печатай над готовым результатом (в терминале).

пока — декларативный двойник счётного цикла: порождаем числа и останавливаемся по условию, а накапливаем свёрткой (сумма), без единой изменяемой переменной. Витрина силы — решето простых: бесконечные натуральные фильтруем предикатом простоты и берём первые N (пример №113).

Ленивое слияние конечного списочного конвейера и параллелизм — следующие шаги. Здесь ленивы связывания и потоки; обычные списки — строгие.

Объединение строк пример №3

Роль аргумента несёт предлог/падеж, а не позиция — порядок слов свободен (пример №4 — тот же вызов в разном порядке слов).

соедини "Привет" с ", мир!", запомни как фразу
напечатай фразу                прим Привет, мир!

Операции над строкой: длина строки, символ N из строки (символ как строка), цифра N из строки (цифра как число; отсчёт с 0), слова строки (разбить текст на список слов по пробелам). Примеры — Расшифровка (#91), частотный анализ текста (#106).

Строки: поиск, замена, образцы пример №107

Полный набор строковых операций. Все преобразования дают новую строку (исходная не меняется); отсчёт позиций — с 1, границы включительно. Роль аргумента несёт предлог, порядок слов свободен.

напечатай (подстрока "пирог" с 3 по 5)              прим рог
напечатай (замена "а" на "О" в "банан")             прим бОнОн
напечатай (соедини (разрез "а,б,в" по ",") через "-")  прим а-б-в
напечатай (замени по образцу "\\d+" на "N" в "год 2026")  прим год N

Образец (regex) — обычная строка-значение, поэтому слэш в ней удваивается: "\\d+" означает «одна и более цифр». Пока это сырой синтаксис регулярных выражений; читаемый русский язык образцов — в планах. Java- и браузерный движки чуть расходятся на экзотике — опирайтесь на общий набор (\d \w \s . * + ? {n,m} [] () |).

Списки-значения пример №14

Список — первоклассное значение: конструктор всё:, неизменяем. Операции-имена управляют родительным падежом — как в русском: «длина ряда», а не «длина ряд».

ряд -- это всё: 3, 1, 2
напечатай (длина ряда)         прим 3
напечатай (первый из ряда)     прим 3
напечатай (хвост ряда)         прим [1, 2]

Скобочный литерал пример №126 — тот же список, но короче и симметрично показу (пишу = вижу): [3, 1, 2] вместо всё: 3, 1, 2. Вложенные массивы читаются как на бумаге — [[1, 2], [3, 4]]; словесное всё: остаётся (для начинающих). Как аргумент глагола составное значение берётся в скобки (применение крепче): напечатай ([1, 2, 3]), а x[i] сразу после значения — это индекс. Отрицательные — унарный минус: -1, -(a + b). Доступ по номеру (с 1) безопасен (вне границ → ничего) и чейнится у вложенных: сетка[i][j] даёт нечто(элемент) или ничего.

Свой глагол — жадный и ленивый пример №8

Инфинитив называет, повелительное вызывает. Форма заголовка = вид вычисления: инфинитив → жадный, отглагольное существительное → ленивый (аргументы считаются при обращении).

удвоить строку -- это соединить строку со строкой
удвой "ля", запомни как эхо
напечатай эхо                  прим ляля

Имя функции — по роду работы: действие называют глаголом (удвоить, отсортировать), а значение — существительным/прилагательным с аргументом в родительном (факториал числа, квадрат числа, максимум ряда). «Вычислить» подразумевается — машина и так вычисляет; довольно назвать, ЧТО и ЧЕГО. Параметр в родительном (числа) в теле зовётся именительным (число).

квадрат числа -- это число умножить на число
напечатай (квадрат 8)          прим 64

Несколько аргументов номинал соединяет союзом и (среднее длины и ширины). Булеву функцию можно пометить -- это предикат. Параметры — по падежу, порядок свободен; локальные — в придаточном где.

Локальные связывания — «где» пример №54

Придаточное где даёт подвыражению имя — тело читается как фраза (как where в Haskell). Пишется в конце тела через запятую; связанное имя видно во всём теле. Одно где на тело.

итог заказа -- это заказ плюс доставка, где доставка -- это заказ разделить на 10
напечатай (итог 100)          прим 110

Работает и после многострочного если … иначе … (запятая, затем где):

скидка суммы -- это
  если сумма больше 100, то сумма минус льгота,
  иначе сумма,
  где льгота -- это сумма разделить на 10

Не путать с где: в шапке таблицы блока проверка — там двоеточие вводит колонки случаев, это другое употребление слова.

Функции высшего порядка пример №74

Глагол — это значение: его можно передать другому глаголу и там применить. Роль аргумента несёт падеж, а не значок. Три роли — три падежа:

Так свёртки (map/fold/filter) становятся библиотекой на Глаголе, а не встроенными. «Отобрази» применяет переданное действие к каждому элементу:

удвоение числа -- это число умножить на 2
отобрази список действием -- это
  если список пуст, то пустой список,
  иначе добавить (действие (первый списка)) к (отобрази (хвост списка) действием)
напечатай (отобрази (всё: 1, 2, 3) удвоением)      прим [2, 4, 6]

Значение-глагол передают только по имени — правильно названный глагол уже имеет регулярный творительный (§86); лямбд и значков нет. Вычисленный глагол сперва связывают с именем, потом передают. Голова вызова применяется n-арно: (действие (первый списка)).

Локальный где-глагол захватывает окружение — это замыкание. «Граница» строит предикат «мерка», поймавший порог; связываем его именем барьер и передаём в фильтр:

граница порога -- это мерка, где мерка числа -- это число больше порога
барьер -- это граница 2
напечатай (отфильтруй (всё: 1, 2, 3, 4, 5) барьером)   прим [3, 4, 5]

Глагол можно и вернуть: «сумматор» отдаёт замыкание-прибавитель, помнящее свой шаг.

сумматор шага -- это надбавка
  где надбавка числа -- это число плюс шаг
прибавитель -- это сумматор 5
напечатай (прибавитель 10)                         прим 15

Бэкенды v1 — интерпретатор и браузерный JS (замыкания нативны). Компилятор в байт-код поддержит значение-глагол позже (пока — интерпретатором).

Обработка ошибок пример №75, №76, №77, №78

«Что-то пошло не так» бывает трёх разных сортов, и путать их вредно — поэтому и механизма три. Разница простая, по-житейски:

Отсюда правило выбора: ждёшь, что значения может не быть → может быть; операция может провалиться и причина важна → итог; «этого не может случиться» → паника брось. Плюс отдельный, четвёртый случай — чужой Java-код: он кидает исключения по-своему, поэтому на границе оборачиваешь его в попытка (…) и получаешь наш итог.

Зачем так, а не просто «исключения как в Java»? Потому что ожидаемую ошибку-значение видно прямо в результате — её нельзя молча забыть (компилятор/выбор заставят разобрать оба случая). А падение (паника) остаётся только для настоящих багов. Так обычный сбой и баг не путаются, и программа ведёт себя предсказуемо. Ниже — те же три механизма подробно:

деление числа и делителя -- это
  если делитель равно 0, то сбой "на ноль нельзя",
  иначе успех (число разделить на делитель)
печать итога -- это выбор итога:
  успех (частное), то напечатай частное
  сбой (причина), то напечатай причину

печать (деление 20 4)                 прим 5
печать (деление 20 0)                 прим на ноль нельзя

На границе с Java — попытка превращает исключение в значение (демо так восстанавливается после ошибки БД, а не падает):

разбор строки -- это выбор (попытка (создать «java.lang.Integer» строки)):
  успех (число), то соедини "число " с строкой
  сбой (причина), то соедини "не число: " с строкой

А брось — для багов, «этого не может быть»: программа падает с сообщением, чтобы ошибку заметили и починили (обычные сбои возвращай значением, не паникой):

корень числа -- это
  если число меньше 0, то брось "корень из отрицательного — так не бывает",
  иначе число
напечатай (корень 9)                  прим 9
напечатай (корень (0 минус 4))        прим ⚠ падение с сообщением

Безопасные обёртки пишутся НА Глаголе, а не встроены: достань список -- это если список пуст, то ничего, иначе нечто (первый списка). Стандартные первый/деление на неверном входе паникуют (как и раньше). Компилятор в байт-код: значения/выбор/брось — да; попытка — позже (пока интерпретатором).

Составные имена, синонимы, аббревиатуры пример №43, №44

Многословное имя (функции или сущности) заключают в ёлочки «…» — так пишет ИИ — или в лапки "…", которые легче набрать с клавиатуры. Обе формы задают одно имя, зови любой:

«площадь прямоугольника» ширины и высоты -- это ширина умножить на высота
напечатай ("площадь прямоугольника" 3 и 4)   прим 12

Рядом с именем в скобках — синонимы/аббревиатуры (можно несколько), и зовёшь любым. Работает и для существительных, и для глаголов:

колхоз (КХ, община) -- это 100                напечатай КХ  100
«наибольший общий делитель» (НОД) числа и делителя -- этонапечатай (НОД 48 и 36)                        прим 12

Отдельные примеры по возможностям именования: №45 имя-номинал, №46 индексы-символы (x_1), №47 многоаргумент через «и», №48 предикаты.

Предложные роли пример №49

Аргумент можно пометить предлогом (в/на/по/над/под/за/до) — тогда роль читается по-русски, а порядок свободен: метка опознаёт аргумент, а не позиция. Предлог стоит и в определении, и в вызове; имя-слот в вызове — читаемая метка:

возвести число в степень -- этонапечатай (возвести 2 в степень 10)     прим 1024
напечатай (возвести в степень 10 2)     тот же результат  порядок свободен

Легаси-предлоги (с/из/от/к) сохраняют прежний смысл (соедини A с B, первый из ряда).

Символы-двойники пример №50

Один и тот же оператор пишут любым из двух знаков — типографским (его подставляет ИИ из всей таблицы Unicode) или клавиатурным (его легко набрать руками). Обе записи — синонимы над одним ядром:

напечатай (7 × 5)     то же, что 7 * 5  35
напечатай (10 ÷ 2)    то же, что 10 / 2  5
если a  b тото же, что a <= b

Все пары «типографский ↔ клавиатурный» (левый знак ставит ИИ, правый набирают руками):

Пиши как удобно — многовариантные операторы пример №51

Там, где в других языках спорят о «правильном» знаке «не равно», «Глагол» принимает все привычные варианты сразу — из C/Java, из Паскаля/Haskell, из SQL. Читаем либерально, а печатаем канон (закон Постела):

Все синонимы «не равно» — любой читается одинаково:

Все четыре — одно и то же сравнение. Пиши тем, что под рукой; на смысл это не влияет. Печатается канон (закон Постела): человеку — !=, ИИ — .

Профили отображения — «Вид»

Исходники хранятся в клавиатурном ASCII (это канон: --, *, <=, !=) — их легко набрать и читать в git. Селектор «Вид» на площадке показывает тот же код в другой форме, не меняя смысла:

Обе формы — один и тот же разбор и исполнение; перевод делает общий движок на сервере (одна проверенная истина, а не «на глаз»). На сравнительных примерах (№50, №51), которые нарочно показывают обе записи сразу, селектор скрыт.

Интеграция с Java (Spring / JPA)

«Глагол» живёт на JVM, поэтому дотягивается до всей экосистемы Java — от вызова готовых классов до полноценного веб-приложения на Spring Boot с базой данных. Разделы ниже идут по нарастанию: вызвать Java → свой класс → аннотации → методы-контроллеры → репозиторий и БД → родная русская поверхность над всем этим → связи и поиск. Живой пример — CRUD «Питомцы» на настоящей БД, написанный целиком на «Глаголе».

Java-interop — зовём Java пример №52

«Глагол» на JVM, поэтому из него можно вызывать готовый Java-код (первый шаг к Spring/Jmix). Ядро-типы (число/строка/логическое/список) авто-мостятся; произвольный Java — явно:

подключи «java.util.ArrayList» как Набор
набор -- это создать Набор
у набор вызвать «add» 5
напечатай (у набор вызвать «size»)          прим 1
напечатай«java.lang.Math» вызвать «max» 3 7)   прим 7

Работает на обоих бэкендах (единый рефлективный движок). Исключение Java становится ошибкой «Глагола». Пока это только ВЫЗОВ Java; определение классов/аннотаций (Spring/Jmix-приложение) — следующими шагами. Дробные типы и мост для коллекций — позже.

Методы записи — параметры и тело пример №56

У записи бывают методы — так наш класс становится, например, Spring-контроллером. Метод объявляется в блоке записи: метод «имя» (парам — тип, …) возвращает тип: тело. Параметры необязательны; тело — одно выражение (его значение возвращается). Имя параметра Spring находит по имени (для @RequestParam), а в теле на параметр ссылаются в любом падеже (по основе: кличку находит кличка):

запись «Умножитель»:
  метод «удвоить» (значение  число) возвращает число:
    у «java.lang.Math» вызвать «multiplyExact» значение 2

умножитель -- это создать «Умножитель»
напечатай (у умножитель вызвать «удвоить» 21)   прим 42

Тело метода — ограниченное выражение: литерал, ссылка на параметр, создать … и у … вызвать … (interop). Этого хватает на контроллер, который принимает данные и зовёт бэкенд. На этом механизме работает живой мини-CRUD /питомцы: метод добавить (кличка) мутирует хранилище (совершенный вид «добавь» = эффект), метод список его читает. Метод с @SpringBootApplication реально поднимает Spring Boot (доказано автотестом). Работает на обоих бэкендах.

Хранилище — персистентность (БД) пример №57

Сущность (запись, помеченная @Entity) сохраняется в базу через хранилище — репозиторий Spring Data. Объявление одной строкой:

запись «Владелец» помечена «jakarta.persistence.Entity»:
  идентификатор  число помечено «jakarta.persistence.Id» и «jakarta.persistence.GeneratedValue»
  имя  строка

хранилище «Владельцы» хранит «Владелец»     interface extends JpaRepository<Владелец, Long>

Контроллер получает хранилище как поле-зависимость (тип — в ёлочках, помечено @Autowired) и зовёт его методы (save, findAll, count, deleteById…) прямо в теле метода — так же и параметр может быть типизирован сущностью (Spring свяжет поля запроса):

запись «Владельцы-сервис» помечена «...RestController»:
  репозиторий  «Владельцы» помечено «...Autowired»
  метод «создать» (владелец  «Владелец») возвращает строку помечен «...PostMapping» ("/владельцы"):
    у репозиторий вызвать «save» владельца
  метод «сколько» возвращает число помечен «...GetMapping» ("/сколько"):
    у репозиторий вызвать «count»

Полное приложение (запись с @SpringBootApplication) поднимается на Spring Boot + H2: POST реально пишет владельца в базу, GET читает — round-trip через БД доказан автотестом (ПерсистентностьE2ETest). Ключ реализации: эмитированные классы материализуются на диск, и Spring/Hibernate находят их сканированием как обычный classpath. На площадке показаны только ОБЪЯВЛЕНИЯ (сам Spring/БД в песочницу не тянем); запуск приложения — как jar/тест.

Родная поверхность — без англицизмов пример №58, №59

Всё выше можно писать по-русски, без getX/setX и Java-имён аннотаций — под капотом остаётся тот же байт-код. Доступ к полям записи:

Правило: если голова — объявленный глагол, это вызов; иначе — доступ к полю. Склонённое имя переменной резолвится по основе (владельцу/владельцавладелец).

Конструирование с полями пример №82 — собрать значение целиком ОДНИМ неизменяемым выражением (вместо «создать + задать + задать…»). Поле метит творительный падеж («чем»): то, что другие языки пишут значком (имя= в Scala/Kotlin, имя: в Ruby/JS), Глагол выражает падежом:

владелец -- это создать «Владелец» с именем "Пётр", фамилией "Петров", городом "Тула"

Имя поля резолвится по основе/лемме (именемимя, ценойцена). Идиоматичная поверхность — неизменяемая; задай (мутация) остаётся низкоуровневым interop-люком.

Неизменяемое обновление пример №83копия <значения> с полем значение, … создаёт НОВУЮ запись с изменёнными полями, а исходная не тронута (как .copy в Scala, record-update в Haskell):

переехавший -- это копия владельца с городом "Сочи"

Слово-голова копия снимает неоднозначность с предлогом с (роль-аргумент в вызовах). Оба JVM-бэкенда.

Родные слова (гибрид: ключевые слова + расширяемые привязки):

сущность «Владелец»:          = запись + @Entity
  ключ идентификатор          = число + @Id + @GeneratedValue
  имя  строка

хранилище «Владельцы» хранит «Владелец»

контроллер «Владельцы-сервис»:  = запись + @RestController
  встрой репозиторий  «Владельцы»   = поле + @Autowired
  при создании "/владельцы" метод «создать» (владелец  «Владелец») возвращает строку:
    у репозиторий вызвать «save» владельца
  при получении "/сколько" метод «сколько» возвращает число:
    у репозиторий вызвать «count»

Слова сущность/контроллер/приложение (точка входа Spring Boot)/ключ/встрой/при получении|создании|замене|удалении — ядро. Прочие Java-аннотации подключаются привязкой пометка «FQCN» как слово (расширяемо, как подключи…как): напр. столбец@Column. Низкоуровневая форма (запись, помечена «FQCN», у…вызвать) остаётся — это двойная запись над одним ядром.

Веб-возврат: контроллер отдаёт данные пример №81

Метод может вернуть не только строку/число, но и саму сущность и список сущностей — именно это нужно контроллеру, чтобы отдать данные, а Spring MVC сам превратит их в JSON:

контроллер «Владельцы-сервис»:
  встрой репозиторий  «Владельцы»
  при получении "/владельцы" метод «список» возвращает список «Владелец»:
    у репозиторий вызвать «findAll»    = List<Владелец>  JSON-массив

Возврат — обычный JVM-тип (сущность или List), библиотека сериализации в ядре не «зашита». Круговые связи (владелец↔питомец) при печати в JSON зациклятся — один конец связи помечают, чтобы он себя не пересказывал, тем же расширяемым механизмом привязки: пометка «com.fasterxml.jackson.annotation.JsonIgnore» как скрыто, затем владелец — один «Владелец» помечено скрыто. Никакой магии: аннотация подключается из кода, а не из компилятора. Доступ к полю сущности — снаружи метода (в теле метода поддержаны условие/арифметика/ вызовы, но не чтение полей — берите поле у возвращённого значения: (имя владельца)).

Связи и CRUD пример №60

Связь «один-ко-многим» (у владельца много питомцев) — поле-коллекция питомцы — много «Питомец»: под капотом List<Питомец> + @OneToMany(cascade=ALL, fetch=EAGER). Список кладут через задай:

сущность «Владелец»:
  ключ идентификатор
  имя  строка
  питомцы  много «Питомец»

задай владельцу питомцы (всё: барсик, рекс)
напечатай (у (питомцы владельца) вызвать «size»)   прим 2

Двунаправленная связь (пример №61): у питомца — свой владелец (владелец — один «Владелец» = @ManyToOne), у владельца — его питомцы (питомцы — много «Питомец» по владельцу = @OneToMany, обратная сторона; по <поле> указывает поле-ссылку в дочерней сущности). Поиск — производные запросы: в блоке хранилища найти по фамилии даёт метод, который Spring Data реализует сам (findByФамилия):

хранилище «Владельцы» хранит «Владелец»:
  найти по фамилии              прим точное совпадение
  найти по началу фамилии       прим по префиксу (StartingWith)

Связь «многие-ко-многим» (у ветеринара много специальностей, и наоборот) — специальности — общие «Специальность» (= @ManyToMany). Валидация — родными словами после типа: фамилия — строка обязательно (= @NotBlank), телефон — строка только цифры (= @Pattern). Полная модель Petclinic (владельцы, питомцы, виды, приёмы, ветеринары, специальности) — пример №62. Полноценный живой веб-CRUD над этой моделью — /demo: список и поиск владельцев по фамилии, добавление с валидацией, карточка с питомцами и приёмами, ветеринары со специальностями — всё на реальном JPA/H2, эмитировано из клиника-app.гл.

U/D над репозиторием — родные глаголы совершенного вида (эффект): сохрани владельца в репозиторий (create/update) и удали владельца из репозитория (delete). Это сахар над у репозиторий вызвать «save»/«delete». Полный цикл (владелец с питомцами пишется в БД каскадом и читается назад) доказан автотестом СвязиJpaTest; на площадке репозитория/БД нет — показано построение связи в памяти.

Ещё о языке

Дальше — возможности самого «Глагола», не связанные с Java: условия и выбор, собственные типы данных (записи и суммы вариантов), истинность, арифметика, тесты. Всё это работает и в интерпретаторе, и в компиляторе, без единой строки Java.

Условия пример №9

Условие — выражение если … то … иначе; «иначе» обязательно. Запятые по вкусу.

знак -- это если 7 больше 0, то "плюс", иначе "не плюс"
напечатай знак                 прим плюс

Выбор — многоветочный пример №24

Замена цепочек если … иначе если …. Единая форма (как Kotlin when): сопоставление со значением, охранники-условия и разбор по конструктору (см. «Типы»), опциональный предмет (в родительном). Ветви — по строке, первая подходящая побеждает. иначе опционально: блок с иначе им и закрывается, а без него — пустой строкой или концом кода; если ни одна ветвь не подошла — ошибка исполнения.

назвать число -- это выбор числа:
  0, то "ноль"
  если число чётно, то "чётное"
  иначе "нечётное"

Длинную ветвь можно переносить по строкам: строка продолжается, если кончается союзом/оператором/то или следующая начинается с и/или — без бесконечных строк вправо.

Записи — свой класс пример №53

Запись — базовый способ описать данные с именованными полями. Это возможность самого языка: Java для неё не нужна (пример №53 работает сам по себе). Под капотом рождается настоящий именованный JVM-класс с полями, конструктором и геттерами/сеттерами — он же служит фундаментом под сущности Spring/Jmix, когда до них дойдёт дело. Объявление — блок (как «тип»), кончается пустой строкой; поле — имя — тип, где тип из ядра (строка, число, логическое):

запись «Владелец»:
  имя  строка
  возраст  число

владелец -- это создать «Владелец»
задай владельцу имя "Иван"
напечатай (имя владельца)     прим Иван

Читают поле в родительном ((имя владельца)), пишут — глаголом задай (совершенный вид, эффект). Работает на обоих бэкендах. Как пометить запись Java-аннотациями и связать со Spring/Jmix — ниже, в разделе про интеграцию с Java.

Типы данных (АТД) пример №25

Свой тип — сумма вариантов: тип перечисляет конструкторы, по одному на строку, блок закрывает пустая строка. Вариант — конструктор (поле-тип, …) или голый конструктор (перечисление). Тип рекурсивен (поле того же типа). Значение строится вызовом конструктора двумя равноправными формами: скобочно-запятой, как в объявлении (узел(лист(1), лист(2))), или аппликацией через пробел (узел (лист 1) (лист 2)); голый конструктор — сразу значение.

тип дерево:
  лист (число)
  узел (дерево, дерево)

дер -- это узел(лист(1), лист(2))    то же, что: узел (лист 1) (лист 2)

Разбирают значение только через «выбор» — ветвь по конструктору связывает поля с именами (деструктуризация), как record-паттерны в Java 21:

сложить дерево -- это выбор дерева:
  лист (значение), то значение
  узел (л, п), то (сложить л) плюс (сложить п)

напечатай (сложить дер)         прим 3

Ближайший аналог в Java — sealed interface + record: конструктор с полями ≈ класс (много значений), голый конструктор ≈ enum-константа (одно значение). Пока отложено: именованные поля с прямым доступом, вложенные паттерны. Статическая типизация конструкторов и проверка исчерпаемости — готовы, см. следующий раздел и раздел «Типы: подсказки и строгость».

Полнота разбора (проверка исчерпаемости) пример №131

выбор без иначе обязан разобрать КАЖДЫЙ вариант типа — забыть случай нельзя (гарантия в духе Haskell/SPARK). Если разобраны не все варианты и иначе не написан, проверка типов даёт замечание со списком пропущенных вариантов — по именам, а не абстрактно «не все случаи»:

тип фигура:
  круг (радиус)
  квадрат (сторона)

площадь -- это выбор (круг 5):
  круг (радиус), то 3 умножить на радиус умножить на радиус
  прим забыли ветвь «квадрат», иначе не написан

… проверка сообщит: разбор «фигура» неполон: не разобран(ы) квадрат. При строгости свободная/обычная это предупреждение — программа всё равно запускается; при строгаяблокирующая ошибка, программа не стартует.

Работает и для встроенных типов. «может быть» (нечто/ничего) и «итог» (успех/сбой, см. раздел «Обработка ошибок») — тоже АДТ, только встроены в язык, а не объявлены программой через тип …. Полнота видит их точно так же — забытую ветвь сбой (самый частый баг обработки ошибок: разобрали успех, забыли провал) поймает та же проверка:

исход -- это выбор (успех 42):
  успех (значение), то значение   прим забыли «сбой»: разбор «итог» неполон: не разобран(ы) сбой

Честные границы. Проверка срабатывает, только когда тип разбираемого значения известен выводом (прямая конструкция вроде круг 5 или явная аннотация типа) — если тип не выведен, проверка молчит (без ложных срабатываний), это честная граница, не баг. Проверяются только варианты-конструкторы; типы и арность ПОЛЕЙ конструктора, вложенные паттерны и guard-ветви (если …, то … внутри выбор) в эту проверку не входят — отдельный, ещё не начатый срез.

Система типов: одна ось — от котика до микроконтроллера

Прежде чем нырять в детали — целостная картина. У Глагола одна система типов, растянутая по одной оси — строгости. Это не «динамический язык против статического», а плавный пандус от нуля церемоний до строгих гарантий:

Несколько несущих принципов, на которых всё держится:

Ниже — то же по частям: подсказки и строгость, ярусы чисел, диапазоны, именованные типы, сигнатуры, генерики. Один язык обслуживает и школьника, и системного инженера, не заставляя первого платить за строгость, нужную второму.

Типы: подсказки и строгость пример №68

Типы в «Глаголе» необязательны. Не написал — язык выведет тип сам (что 5 — число, а "привет" — строка) и промолчит, пока не заметит явного противоречия. Хочешь подсказать явно — припиши тип словом типа:

ответ типа Число -- это удвоить 21
удвоить число типа Число -- это число умножить на 2,
    гарантирует результат типа Число

У глагола можно указать и тип входа (число типа Число), и тип результата (гарантирует результат типа Число) — оба по желанию. Составные типы пишутся родительным падежом, как по-русски: список чисел, множество имён, отображение из имени в владельца.

Ярусы числа. Число — общая аннотация «какое-то число, ярус не закреплён», но под ней различаются четыре яруса точности: целоедесятичноедробь, а сверху — неточное плавающее. Литерал сам определяет свой ярус: 5целое; точное деление (/, разделить на) — всегда дробь (даже 10 / 2 — дробь, просто без остатка). Ярус можно расширить только вверх по решётке: значение типа целое годится и там, где ждут дробь, но не наоборот — типа целое у дробного значения поймает несоответствие. Аннотация типа целое/типа дробь/типа плавающее проверяется по тем же правилам строгости, что и обычные типы. Важная честность: десятичный литерал вроде 19.99 на деле даёт ярус дробь (точная дробь 1999/100), а не десятичное — язык хранит десятичные литералы как точные дроби, а не как значения фиксированного масштаба. Ярус десятичное (масштаб хранится: 10.00 и 10 различимы) сейчас получают только через деньги X/округли X до N знаков — денежную арифметику; вывод статического типа из этих источников (чтобы деньги X сама размечалась как десятичное) — следующий шаг, ещё не сделан. Пример 68 показывает ярусы на рабочих аннотациях.

Диапазоны. Ярус целое можно сузить до конкретных границ: типа целое от N до M — целое число, ограниченное отрезком [N, M] (обе границы включительно, границы — целые). Диапазон — подтип целое: он годится всюду, где ждут просто целое или более широкий ярус, но не наоборот. Если связываемый литерал выходит за границы, это ловится статически — до запуска программы, ещё на разборе:

высота типа целое от 0 до 40000 -- это 30000   прим внутри границ — чисто
напечатай высота                                      прим 30000

Напиши вместо 30000 число 50000 — и разбор сообщит о замечании ещё до запуска (при строгости обычная и строгая это ошибка — программа не запускается; при свободная — предупреждение, но программа бежит), не дожидаясь падения в рантайме. Честно про границы: сейчас так проверяются только литералы прямо в связывании — выражение (a плюс b) или значение, пришедшее снаружи (аргумент, ввод, JPA-поле), диапазон пока не проверяет ни статически, ни во время исполнения; рантайм-проверка внешних значений — следующий срез работы. Пример 68 показывает и ярусы, и диапазон рядом.

Именованный тип: своё имя ограничению пример №134

Диапазон (да и любой тип) можно назвать один раз и применять к разным величинам — через ту же форму тип X:, что и алгебраические типы, но с типом на той же строке, что двоеточие (а не вариантами с отступом):

тип оценка: целое от 2 до 5

математика типа оценка -- это 5
история типа оценка -- это 4

Границы и смысл теперь заданы в одном месте: поменять разом, а по имени сразу видно, что это оценка, а не просто число. Проверка работает по имени так же, как встроенная — математика типа оценка -- это 6 заранее сообщит «вне диапазона 2..5». Именем можно назвать не только диапазон, но и базовый тип (тип идентификатор: строка) или контейнер (тип имена: список строк).

Чем отличается от запись. запись/сущность порождает настоящий Java-класс (для базы данных, Spring) — это «переходник в Java». тип же не порождает никакого класса: это чистое имя для смысла, живущее только внутри «Глагола». Поэтому имя для диапазона — это тип, а не запись.

Строгость проверки настраивается и говорит, насколько сурово спрашивать за несоответствие типов: свободная — только предупреждения, программа всё равно запускается (так по умолчанию, чтобы новичку ничего не мешало); обычная — противоречие объявленному типу или выход за объявленный диапазон становится ошибкой, программа не запускается; строгая — то же плюс дополнительные гарантии (размеченные сигнатуры глаголов, полнота разбора, распаковка под «железо», где без точных типов не собрать быстрый результат). Уровень задаётся на модуле — библиотека «Азбука» строгость: строгая: — либо на отдельном глаголе, клаузой после тела (рядом с требует/гарантирует): … -- это …, строгость строгая. Действует правило «ближе к делу — важнее»: пометка на глаголе перекрывает модуль, а модуль — общую настройку.

Значения, пришедшие из Java-мира, получают тип внешнее — честная отметка «здесь наш вывод типов заканчивается»: язык не выдумывает про них лишнего. Пока проверка работает локально (в пределах глагола и связываний); сквозной вывод по всей программе и оптимизации под «железо» появятся, когда дойдём до соответствующих бэкендов.

Сигнатуры под строгостью: размечаешь границы, не тело пример №136

На ярусе строгая — режиме реального ПО — у каждого глагола размечена сигнатура: тип каждого параметра и тип результата. А тело выводится само: внутри ничего размечать не нужно.

площадь ширины типа целое и высоты типа целое -- это
    ширина умножить на высоту,
    гарантирует результат типа целое

Убери типа целое у параметра или строку гарантирует … результат — и под строгостью получишь ✗ с точным указанием, где дописать тип. Сдвинь ползунок на свободную — сигнатур никто не требует, вывод молча заполнит: это пандус для набросков и для детей, а не проектный центр.

Правило простое и предсказуемое — типы на сигнатуре (так требуют Rust, Go, Kotlin, TypeScript; в Haskell это соглашение). Всегда знаешь, где ставить тип: на границе глагола, не внутри. Это даёт сразу несколько выгод реального кода:

Тело большого и сложного глагола выводится тем надёжнее, чем крепче размечены границы: каждое промежуточное выражение получает тип локально — от известных параметров, литералов и сигнатур вызываемых глаголов. Если конкретную промежуточную величину не закрепляет ничто (редкий случай), проверка попросит уточнить именно её — локально и предсказуемо, а не завалит всё.

Генерики: один глагол «для любого типа» пример №137

Иногда глагол работает с любым типом одинаково — вернуть, что дали; взять первый элемент списка чего угодно. Чтобы не дублировать код под число, строку, список отдельно, тип оставляют переменной. Под свободной/обычной это выводится само:

тождество штуки -- это штука   прим работает для любого типа: «тождество 5»→5, «тождество "да"»→"да"

Под строгой переменную типа объявляют явно — отдельной строкой-сигнатурой имя: входы даёт результат. Переменная типа — одиночная заглавная буква (Т, У); конкретный тип — слово (Число, список). Повторение буквы связывает: Т … даёт Т значит «вернётся ровно тот тип, что пришёл».

тождество: Т даёт Т
тождество штуки -- это штука

Тело внизу — то же самое, что под свободной; сигнатура сверху читается как контракт одной строкой. Ещё примеры сигнатур (буква = переменная, слово = тип):

первый: список Т даёт Т          прим первый элемент списка чего угодно
пара: Т, У даёт пару Т и У      прим две разные переменные — Т и У
найди: список Т даёт может быть Т

Важно не путать переменную типа и «любое число». Т (буква) — это что угодно. А «любое число» — это Число (слово): ярус-супертип, принимающий целое, дробь и т.д. через решётку ярусов, а не переменная. Буква против слова — и видно, что это.

Разделители в сигнатуре (:, ,, скобки) — это якоря для беглого чтения: глазу есть за что зацепиться. Все они набираются на русской раскладке, без переключения. Сигнатуру можно писать и на обычном уровне — ради читаемости, — а обязательна она под строгой. Символы < > | & из дженериков других языков нам не нужны: переменные — буквы, «или»-типы — это тип … : с вариантами (алгебраические типы).

Истинность и сравнения

Значения да/нет (синонимы истина/ложь). Сравнения: равно/=, больше/>, меньше/<. Равенство согласуется по роду и числу: «сумма равна 0», «остаток равен 0», «числа равны» — всё это равенство.

Логические связки: и (все истинны), или (хотя бы одно) — с коротким замыканием, «или» ниже «и» по приоритету. Повторяющийся союз с запятыми: A, и B, и C. Операнды-вызовы берутся в скобки: (число больше 0) и (число меньше 10).

напечатай (5 больше 3)         прим да

Краткие прилагательные-предикаты пример №11

Краткая форма = утверждение свойства → да/нет. не отрицает.

напечатай (10 чётно)          прим да
напечатай ("" не пусто)        прим нет

Есть: пуст (строки), чётно, нечётно, положительно, отрицательно (числа).

Арифметика пример №10

Символы + − × ÷ // % или слова плюс/минус/умножить на/разделить на/нацело/остаток от деления на. Приоритеты обычные. Составной аргумент печати — в скобках (см. «Скобки»).

напечатай (2 + 3 * 4)                        прим 14
напечатай (2 плюс 3 умножить на 4)           прим 14
напечатай (17 / 5)                          прим 3.4 — точно
напечатай (17 // 5)                         прим 3 — целочисленное
напечатай (17 остаток от деления на 5)       прим 2

Числа: точность и дроби пример №117

«Глагол» держит точность по умолчанию — как научный калькулятор, а не как обычный тип int/double. Есть один тип число, а внутри — целое (без предела) и дробь; язык сам выбирает представление и не теряет точность.

напечатай (1 / 3)               прим ⅓
напечатай (0.1 плюс 0.2)        прим 0.3 — ровно
напечатай (7 // 2)              прим 3  (целочисленное)

Плавающее: корни и тригонометрия пример №118

Для науки и скорости есть приближённые вычисления (double, IEEE-754). Они — по ЯВНОМУ запросу, чтобы «точность по умолчанию» не размывалась.

напечатай (корень 2)          прим 1.4142135623730951
напечатай (синус (пи / 2))    прим 1.0
напечатай (приблизительно (1 / 3))  прим 0.3333333333333333

Деньги: десятичное и округление пример №119

Для ERP — деньги без потерь. «Десятичное» — точное число с ЗАПОМНЕННЫМ масштабом (число знаков после запятой) и управляемым округлением; под капотом — Java-BigDecimal, так что в поля базы (JPA/Jmix) ложится один-в-один.

напечатай (деньги 1000)                   прим 1000.00
напечатай ((деньги 19.99) разделить на 3)   прим 6.66
напечатай (округли 2.5 до 0 знаков банковски)  прим 2

Высокая точность: √, π, e пример №120

Для науки — √n, π, e до любого числа верных знаков (обычное корень/пи/е дают быстрый double ~15 знаков; здесь — хоть тысяча). Форма — та же до N знаков, что у округли; результат — десятичное.

напечатай (корень 2 до 30 знаков)   прим 1.414213562373095048801688724210
напечатай (пи до 30 знаков)         прим 3.141592653589793238462643383280

Алгебраические структуры пример №121 №122

Новый математический тип — комплексные, кватернионы, матрицы, поля Галуа — не «вшивают» в ядро. Вместо этого автор объявляет алгебраическую структуру (по Винбергу): даёт базовые операции, а операторы + − × ÷ над её значениями выводятся. Ядро несёт один механизм — три алгебраических класса:

КлассАвтор даётОператоры
кольцосложение, умножение, противоположное, ноль, единица + − ×запрещён: «в кольце нет деления»)
поле+ ещё обратное+ − × ÷
телокак поле, но × некоммутативно+ − × ÷ (кватернионы)

Блок <класс> <Имя>: <поля> объявляет структуру; операции — обычные глаголы с отступом. Конструктор Имя a и b строит значение, имя поля читает компонент (вещ z). Смешение с обычным числом — через операцию вложение (число «вкладывается» в структуру). Показ и равенство — свои (показать/равно) или по умолчанию (покомпонентно).

поле Комплексное: вещ, мним
  сложение первого и второго -- это Комплексное (вещ первого + вещ второго) и (мним первого + мним второго)
  ноль -- это Комплексное 0 и 0
  единица -- это Комплексное 1 и 0
  вложение числа -- это Комплексное число и 0

i -- это Комплексное 0 и 1                прим мнимая единица
напечатай ((3 + 4 * i) * (1 + 2 * i))       прим -5+10i

Объявив константу i = 0+1i, дальше пишем числа как математики: 3 + 4 * i, а не Комплексное 3 и 4 — обычное число само вкладывается в структуру. (Слитный литерал 3+4i — задел на будущее.) Конечное поле GF(5) (пример №122) — та же машина: сюрприз в том, что «половина единицы» равна 3, ведь 2 × 3 = 6 ≡ 1 (mod 5). Обратное считается по малой теореме Ферма.

Кватернионы пример №123 — экземпляр класса тело: деление есть, но умножение некоммутативно (i·j = k, а j·i = −k). Это ровно то, ради чего в ядре отдельный класс «тело»: механизм не опирается на переставимость ×, а ÷ выводится как правое деление a × обратное b. Автор пишет своё умножение (формулу Гамильтона).

Матрицы 2×2 пример №125 — экземпляр класса кольцо, канонический пример именно кольца: + − × есть, а ÷ запрещён. Умножение некоммутативно (A×B ≠ B×A — поворот и отражение не переставить местами), и деления нет, ведь не всякая матрица обратима: определитель может быть 0. Данные — вложенный список, строится скобочным литералом матрица ([[0, -1], [1, 0]]); определитель — обычный глагол поверх структуры. Показ — красивый 2D (операция показать зовёт встроенный рисунок): высокие скобки и выровненные колонки — напечатай матрица печатает ⎡ 0 −1 ⎤ / ⎣ 1   0 ⎦.

Полноразмерные матрицы n×m — библиотекой пример №127: матрица там — кольцо над вложенным списком (данные строим скобочным литералом [[0, -1], [1, 0]]), а + − ×, транспонирование, определитель и доступ к элементу — функции библиотеки, которую подключают. Матрица — это по сути многомерный массив: скобочный литерал и делает его запись естественной.

Скобки пример №16

Правило одно: применение глагола крепче операторов, поэтому составной аргумент (арифметика, вызов, сравнение) берётся в скобки — «(вычисли это как одно значение)». Зато между вызовами внешние скобки не нужны: удвоить 3 плюс удвоить 4 — это сумма двух вызовов.

напечатай (длина ряда)                       прим вызов как аргумент
напечатай ((2 плюс 3) умножить на 4)         прим 20  внутренние скобки меняют порядок

Переносы длинных строк пример №34

Строк «вправо до бесконечности» быть не должно. Длинную строку переносят: строка продолжается на следующей, если ещё не закончена — висит оператор (плюс, и, +), предлог (к, с, из, от, на), то или запятая; или следующая строка начинается с оператора/предлога/союза.

сумма -- это 1 плюс 2 плюс
  3 плюс 4                       прим висит «плюс»
годно -- это (10 больше 0)
  и (10 чётно)                   прим ведёт «и»

Роли по падежу и предлогу пример №12

Роли: объект (вин.), инструмент (твор. / «с»), источник («из/от» + род.), адресат («к» + дат.). Так различаются несколько аргументов — например, два числа в НОД.

сократить число делителем -- это если делитель равно 0, то число,
  иначе сократить делитель с (число остаток от деления на делитель)
нод -- это сократить 48 с 18
напечатай нод                  прим 6 (алгоритм Евклида)

Рекурсия = «цикл» пример №13

Ядро функциональное и неизменяемое — императивных циклов нет; повтор выражается рекурсией.

свернуть число -- это если число меньше 10, то число,
  иначе (число остаток от деления на 10) плюс свернуть (число нацело 10)
сумма -- это свернуть 1234
напечатай сумма                прим 10 (сумма цифр)

Хвостовая рекурсия безопасна на любой глубине (пример №115). Если само-вызов стоит ПОСЛЕДНИМ действием (в «хвосте» — в `если` или `выбор`, как в аккумулятор-стиле сумма-до числа с итогом), движок превращает рекурсию в ЦИКЛ: стек не растёт, глубина любая (как tailrec в Kotlin/Scala). Так «рекурсия с аккумулятором» полноценно заменяет счётный цикл. Оговорка: спасается только ХВОСТОВАЯ рекурсия. Наивная не-хвостовая (число × факториал(…)) и глубокий обход дерева по-прежнему ограничены стеком.

Ввод-вывод пример №23

Вывод — напечатай. Чтение со ввода: прочитай строку / прочитай число, результат — жадно через запомни как. На площадке строки ввода набираются заранее в панели «Ввод»; программа читает их по порядку.

напечатай "Как тебя зовут?"
прочитай строку, запомни как имя
напечатай (соедини "Привет, " с имя)     прим ввод: Маша  Привет, Маша

Родные тесты пример №17

Тест — часть языка. Инлайн проверь: … должно …; матчеры: равняться, быть больше/меньше, быть чётным/пустым/… (творительный), вызвать ошибку. Прогон — кнопкой «✓ Проверить» (команда /проверь). Провал не рушит программу; ниже — покрытие глаголов: сколько твоих глаголов задеты проверками.

удвоить число -- это число умножить на 2
проверь: удвоить 21 должно равняться 42     прим ✓

Data-driven — таблица случаев, по вердикту на строку:

проверка «удвоение»:
  удвоить число должно равняться итогу, где:
    число | итог
       3  |   6
      21  |  42

Тестовые двойники (дублёры) пример №128

Для тестов ЭФФЕКТНОГО кода (зависит от репозитория/службы) — тест-двойники (по духу Spock, но родной формой). заглушка [«Тип»] как имя создаёт двойник; пусть <двойник> «метод» <арг…> → значение стабит ответ (незастабленный зов → ничего); а проверь: <двойник> получил «метод» <арг…> [ровно N раз] / НЕ получил проверяет ВЗАИМОДЕЙСТВИЯ. Метафора: двойник получает зов и стрелкой → отвечает. Зависимость внедряют обычным аргументом. любое — матчер «любой аргумент». Тестовые двойники — интерпретатор-only (тест-время).

заглушка «Хранилище» как склад
пусть склад «найти» 7нечто("Пётр")     прим стаб: зов → ответ
напечатай (у склад вызвать «найти» 7)      прим нечто(Пётр)
проверь: склад получил «найти» 7 ровно 1 раз   прим verify

Два вида двойника: заглушка (= Spock Mock) — полностью поддельный; и шпион <объект> как имя (= Spock Spy) — вокруг реального объекта: незастабленный зов идёт в НАСТОЯЩИЙ (и пишется), а стаб пусть его переопределяет.

шпион (создать «java.util.ArrayList») как корзина
у корзина вызвать «add» "яблоко"          прим реальный add
проверь: корзина получил «add» "яблоко"    прим и записано

Соответствия Spock: заглушкаMock(), шпионSpy(), пусть … →>>, получил1 * mock.m(). Тестирование ВСТРОЕНО В ЯЗЫК, а не прикручено библиотекой (Mockito/Spock — библиотеки).

Проверка свойств (property-based) пример №129

Обычная проверка задаёт КОНКРЕТНЫЙ вход; свойство формулирует ЗАКОН для любого входа, а движок сам генерирует сотню случайных примеров и ищет контрпример. Форма: свойство «имя»: для любого <генератор> <имя> [и любого …], <предикат>. Квантор любого — тот же корень, что матчер любое. Генераторы: целое / целого от A до B, логическое, дробь, плавающее / плавающего от A до B, строка / строки до N, список <элемента> (напр. список целых). Работает на трёх бэкендах (фиксированный сид → одинаковые прогоны).

свойство «сложение целых коммутативно»:
    для любого целого а и любого целого б,
    а плюс б равно б плюс а

Найдя нарушение, движок сжимает контрпример до минимального. Например для ложного для любого целого а, а меньше 500 отчёт покажет не случайное большое число, а точную границу:

✗ свойство «…»: контрпример а=500 (сид …; сжато за 4 шага)

Свои генераторы объявляют так же, как проверки: генератор <имя>: <выражение-генератора>. Комбинаторы: выбор из [a, b, c] (перечисление), пара из (X) и (Y) (дуалис), <конструктор> из (X) (применить конструктор к образцам). Сжатие структурное.

генератор цвет: выбор из ["красный", "зелёный", "синий"]

свойство «цвет — всегда один из трёх»:
    для любого цвета ц,
    (ц равно "красный") или ((ц равно "зелёный") илиравно "синий"))

Тонкая настройка: опции (N прогонов) и (сид S) после имени; при условии <предикат> отбрасывает негодные входы (движок берёт следующий, а сжатие их уважает); отметь «метка», когда <предикат> считает долю входов с меткой и печатает распределение.

свойство «деление обратно умножению» (200 прогонов):
    для любого целого от 1 до 9 а и любого целого от 1 до 9 б,
    при условии б не равно 0,
    отметь «б большое», когда б больше 5,
    (а умножить на б) нацело б равно а

держится (200 прогонов; 45% «б большое»). Аналог QuickCheck/ScalaCheck, но родной формой и встроенный в язык. Честно: это ПОИСК контрпримеров на случайных входах, а не доказательство — отсутствие контрпримера закон не гарантирует.

Параллелизм — данный (по данным) пример №130

Наречие параллельно перед отобрази/отфильтруй над коллекцией. Результат ТОТ ЖЕ, что последовательно; на JVM-компиляторе — реально по ядрам, на интерпретаторе и в браузере — последовательно. Безопасно ИЗ-ЗА ЧИСТОТЫ: функции чисты, данные неизменяемы → гонок данных нет по построению, замки не нужны.

удвоенные -- это параллельно отобрази удвой числа
крупные   -- это параллельно отфильтруй большой числа

Порядок сохраняется (параллель == последовательность для чистых функций). Свёртка для параллели должна быть ассоциативной. Честно: ускорение = число ядер ЦЕЛИ; на одноядерных (браузер) — тот же результат, но без ускорения. Общая память и замки — НЕ в языке (только через внешнее на JVM).

Структурная конкурентность — блок параллельно: запускает НЕЗАВИСИМЫЕ задачи разом и ждёт ВСЕХ; имена доступны после блока (задачи чисты, без ввода-вывода). Общее время = самой долгой задачи, а не сумма.

параллельно:
    отчёт   -- это медленныйА данных,
    прогноз -- это медленныйБ истории

напечатай отчёт          прим сюда попадём, когда ОБЕ готовы

На JVM-компиляторе задачи бегут конкурентно (виртуальные потоки); на интерпретаторе и в браузере — последовательно (тот же результат). Ошибка — первой по порядку задачи (детерминированно). После блока — пустая строка.

Обещаниепоручи <выражение> пускает задачу и сразу отдаёт управление (результат посчитается отдельно); дождись <обещание> забирает результат (ошибка задачи всплывает тут).

обещанное -- это поручи медленный данных
… другая работа …
итог -- это дождись обещанное

Дальше по дорожной карте: каналы, акторы и реактивные события.

Два режима конца предложения пример

По умолчанию построчно (строка = предложение). Директива далее прозой — предложение кончается точкой, перевод строки незначим; далее построчно возвращает.

Полный полигон — сортировка вставками (№15) и оба богатых примера, где всё сведено вместе.

Таблица примеров

Номер — живая ссылка: открывает площадку с загруженным и запущенным примером (по нарастанию сложности; богатые — последними).

ПримерЧто показывает
1Приветпечать строки
2Связываниесущность: имя = значение
30Именасмысловые имена; дефис, ёлочки-аббревиатуры, индекс x_1; цифра к слову — нельзя
73Многословные именабуква-индекс «Пункт А» (голый) и имя-фраза «первое слагаемое» в ёлочках
3Объединениесоединение строк, предлог «с»
4Порядокроль по падежу — свободный порядок слов
5Списокобобщающее «всё:»
6Строго/ленивовид глагола: когда вычислять
7Ленивостьнаблюдаемая ленивость связывания
8Свой глаголжадный/ленивый глагол, параметры по падежу
9Условияесли … то … иначе, сравнения
10Арифметикасимволы и слова; рекурсия (сумма 1..N)
11Предикатыкраткие прилагательные, «не»
12Роли (НОД)многоаргументный глагол — алгоритм Евклида
13Рекурсия«цикл» функционально: глагол зовёт себя
16Скобкивсе случаи группировки — растёт с языком
24Выбор (case)многоветочный «выбор»: сопоставление + охранники
25Типы данных (АТД)свой тип: суммы вариантов, конструкторы, разбор по конструктору
34Переносы строкперенос длинной строки по незавершённости
14Спискисписок-значение, операции в родительном падеже
15Сортировка (вставками)сортировка вставками — чисто функционально
31Пузырёксортировка пузырьком — проходы над неизменяемым списком
32Поисклинейный и двоичный поиск (с честной оговоркой про O(n)-индекс)
17Тестыродные проверки, data-driven, покрытие глаголов
18Палиндром (#9)LeetCode: разворот цифр, рекурсия
19Степень двойки (#231)LeetCode: рекурсия, остаток
20Fizz Buzz (#412)LeetCode: условия + строки, data-driven тест
21Лестница (#70)LeetCode: числа Фибоначчи
22Максимум спискаLeetCode: рекурсия по хвосту
26Пути в сетке (#62)LeetCode Medium: рекурсия по двум аргументам
27Дерево поиска (#98)LeetCode Medium: разбор типа с границами
28Степень (#50)LeetCode Medium: быстрое возведение O(log n)
29Размен монет (#322)LeetCode Medium: минимум монет, рекурсия
33Расшифровка (#91)LeetCode Medium: Decode Ways, строковые операции
35Edit Distance (#72)LeetCode Hard: расстояние редактирования, минимум из трёх
36Регулярка (#10)LeetCode Hard: сопоставление с «.»/«*»
37Шаблон (#44)LeetCode Hard: wildcard-сопоставление «?»/«*»
38Чередование (#97)LeetCode Hard: interleaving двух строк
39Подпоследовательности (#115)LeetCode Hard: число различных подпоследовательностей
23Ввод-выводчтение со ввода, панель «Ввод»
начинающий / профессионалвсё вместе, в двух стилях

Среда (площадка)

Модульность — свои подсистемы пример №63

Программу можно делить на именованные единицы с собственной областью видимости — не «папки для файлов», а языковой механизм изоляции. Две роли пространств имён:

Отдельно — служба (пример №64): это объект-сервис (инъектируемый @Service-бин с методами), конструктивно — класс, как контроллер/приложение, а не модуль. Службу внедряют (встрой), а не импортируют — этим она и отличается от статической библиотеки.

библиотека «Арифметика» открывает удвоить, площадь:
  удвоить число -- это число умножить на 2
  вспомогательное число -- это число плюс 1     прим внутреннее  снаружи не видно

подключи библиотека «Арифметика»
напечатай (удвоить 21)                  прим 42  импортировано напрямую
напечатай (площадь из «Арифметика» 5)   прим 25  квалифицированный доступ «из»

Видимость. Нет открывает → всё публично (для новичка — просто). Есть открывает A, B → публично только это, остальное внутреннее. Направленный экспорт открывает X для «Клиент» открывает X только модулю «Клиент» — так низкоуровневое прячут от верхних слоёв (вертикальная и горизонтальная изоляция подсистем внутри монолита). Импортподключи библиотека «Имя» (после чего имена доступны напрямую), а при совпадении имён — квалификация родительным через «из»: площадь из «Арифметика» (голый родительный остаётся за аргументами). Ацикличность зависимостей — правило компилятора: цикл модулей — ошибка.

Слои (пример №66): модуль может объявить слой Чистой Архитектуры — сущности → правила → адаптеры → инфраструктура (внутрь→наружу). Действует Правило Зависимостей: зависеть можно только ВНУТРЬ, к стабильному (внешний слой — от внутреннего, но не наоборот). библиотека «Ядро» слой сущности: — низкоуровневое не протекает на верхние слои; нарушение направления — ошибка компиляции. Это вертикальная изоляция сверх направленного экспорта.

Дружелюбно для самых маленьких: модульность полностью опциональна — программа без единого модульного слова работает как раньше, и споткнуться о видимость/ацикличность/слои новичок не может (слои проверяются только у модулей, которые их объявили).

Программирование по договору пример №65

У глагола может быть контракт (Design by Contract, Мейер): требуетпредусловие (проверяется на входе), гарантирует/обеспечиваетпостусловие (на выходе; может ссылаться на результат). Нарушение — ошибка выполнения. Клаузы пишутся после тела через запятую:

утроить число -- это число умножить на 3, требует: число больше 0
половина числа -- это число разделить на 2, гарантирует: результат меньше число

Договор дополняет модульность и валидацию: открывает говорит что публично, обязательно/только цифры задают инварианты данных, а договор — как ведёт себя глагол. Это часть парадигмы «встроенной проверяемости»: контракты — живая исполнимая спецификация. Проверяет интерпретатор; компилятор их опускает (как отключаемые проверки-утверждения).

Кавычки: имя или строка пример №67

Лапки "…" и ёлочки «…» — это одно и то же (взаимозаменяемы): типографические варианты одного знака. Человек всегда может вводить лёгкие лапки; ёлочки — каноническая форма (их ставит показ/агент). Что означает кавычка — имя или строку — решает позиция, а не пара кавычек:

Вложенность. Ёлочки направленные («»), поэтому вкладываются со счётом глубины: «внешнее «внутреннее»». Лапки симметричны — не вкладываются ("внеш "внутр"" неоднозначно); для кавычек внутри строки используйте экранирование \" или ёлочки снаружи.

Что нельзя (и почему так решено). Сослаться на многословное нульарное имя-значение голым квотом — нельзя: «общий делитель» в позиции значения станет строкой «общий делитель», а не значением. Это сознательный размен: правило «в значении → строка» простое и без граблей (опечатка в имени не превращается тихо в строку через поиск по таблице имён). Обход — однословное имя: осд -- это 12. Многословные функции (с аргументами) работают: «общий делитель» 12 8.

Каноническая форма — ёлочки; лапки нормализуются в ёлочки (отображение по режимам — клавиатурный/агентский — дорабатывается отдельно). В самом языке обе формы уже полностью равнозначны.

Имена и индексы пример №46

Имя в «Глаголе» — смысловое слово, которое можно склонять (падеж несёт роль). Поэтому цифру нельзя приклеивать к слову: шаг1, число2 — не имена. Причина принципиальная: ряд1 не просклонять (ряда1? движок не знает), и падеж-роль ломается. Это категориальная ошибка, и компилятор отвергает её с подсказкой.

Что использовать вместо этого:

Индекс к слову (значение_1) тоже нельзя: основа индекса — один символ, а не слово; для перечисления — порядковое (первое значение).

Коллекции: пара пример №94

Пара — упорядоченная двойка (возрождённый дуалис, §6): две вещи, что ходят вместе. Это несущий элемент коллекций — запись словаря есть пара «ключ → значение», а ещё пара удобна, чтобы вернуть два значения разом. Строится сочинительным «и», члены — первое/второе:

точка -- это пара 3 и 8
напечатай точка              -- (3, 8)
напечатай (первое точки)     -- 3

Пары равны структурно — по обоим членам, и порядок важен: пара 1 и 2пара 2 и 1. Пара упорядочена (есть «первое» и «второе»); симметрию «оба» дадим отдельно.

Коллекции: множество пример №95

Множество — набор без повторов; порядок обхода = порядок добавления. Литерал — множество: A, B, C (точно) или все разные: A, B, C (для детей) — синонимы:

набор -- это множество: "спорт", "музыка", "спорт"   -- {спорт, музыка}
напечатай (содержит "кино" в наборе)   -- нет

Операции: содержит X в <множестве>, добавить X в <множество> (неизменяемо, дубликат не растит), длина, и алгебра множеств — объединение/пересечение/разность A с B (второе множество через предлог «с»). Равенство множеств не зависит от порядка (в отличие от списка и пары): множество: 1, 2, 3 = множество: 3, 2, 1.

Коллекции: словарь (map) пример №96

Словарь — пары «ключ → значение» (map); порядок обхода = порядок добавления. Литерал — стрелка (или диграф ->):

телефоны -- это словарь: "Иван""111", "Мария""222"
напечатай телефоны["Иван"]     -- нечто ("111")

Доступ по ключу — скобки телефоны["Иван"] — возвращает «может быть» (нечто (значение) или ничего); разбирай через выбор. Ещё: ключи/значения/пары (как списки), длина, и добавить (пара K и V) в <словарь> (неизменяемо; тот же ключ — замена значения). Запись словаря «ключ → значение» — это пара. Равенство не зависит от порядка. NB: «словарь» — это коллекция; модули-пространства имён теперь называются словом библиотека.

Живая задача — частотный словарь (пример №99): словарь-аккумулятор строим свёрткой по списку слов; для каждого берём текущий счёт (словарь[слово] → «может быть» → 0) и кладём +1. Здесь словарь читается естественнее ручных параллельных списков.

Коллекции: список — доступ по номеру пример №97

Список — индексируемая последовательность. Доступ по номеру — с 1 (по-человечески, а не с 0; ноль-смещение остаётся под капотом). Скобки числа[N] и словесно элемент N из чисел — одно и то же; вне границ → ничего (оба дают «может быть», разбирай выбором):

числа -- это всё: 10, 20, 30
напечатай числа[2]      -- нечто (20)

Добавление: добавить X в <список> — в конец (умолчание), добавить X к <списку> — в начало (цепочка «голова + хвост», §20). Плюс содержит X в <списке>, длина, первый/хвост. Счёт с 1 согласуется с математикой и школой; 0-смещение живёт лишь на границе с Java-массивами.

Коллекции: операции — это библиотека на Глаголе пример №98

Как в Java коллекции построены поверх Core, так у нас операции коллекций написаны на самом Глаголе (стандартная библиотека-«прелюдия»), а не встроены в ядро. Ядро несёт лишь неприводимый минимум — две «двери»:

Поверх них содержит, объединение/пересечение/разность, ключи/значения — обычные глаголы прелюдии (разобрать → обработать список → собрать). Значит, свою операцию пишут так же — язык открыт, а не закрыт. Множество квадратов:

квадраты набора -- это множество из (возвести (элементы набора))
возвести список -- это
    если список пуст, то пустой список,
    иначе добавить ((первый списка) умножить на (первый списка)) к (возвести (хвост списка))

напечатай (квадраты (множество: 1, 2, 3))   -- {1, 4, 9}

Своё определение с тем же именем затеняет библиотечное. NB: добавить и доступ [] пока остаются примитивами ядра — их перенос требует перегрузки глаголов по ролям (отдельная возможность).

Конвейер именной группой пример №101

Конвейер именной группой — аналог Java Stream API. В Java обработка потока пишется цепочкой через точки: numbers.stream().filter(even).map(sq).sum(). Точка тут — синтаксический клей, которым язык компенсирует отсутствие падежа: получатель обязан стоять слева. У нас падеж есть, и он несёт роль сам — поэтому клей не нужен. Шаги конвейера связывает родительный падеж («чего»), без точек и скобок:

напечатай (сумма квадратов чётных ряда)   -- чётные 2,4,6 → квадраты 4,16,36 → сумма 56

Читается слева направо, как по-русски: «сумма квадратов чётных чисел». То же со скобками — сумма (квадраты (чётные ряда)) — эквивалентно, но многословнее (скобки Лиспа вместо падежа).

Как это понимается. Шаг конвейера — это имя, которое разрешается в глагол, берущий ровно один аргумент (рамка {объект}). Идущие подряд шаги сворачиваются с ближайшим значением справа налево. Свёртка применяется, только когда обычное («плоское») прочтение невозможно по рамке — иначе язык ничего не трогает; а если оба прочтения законны, он не гадает, а просит уточнить скобками. Союз «и» — перечисление, конвейером не читается (наибольшее числа и делителя — два аргумента).

Терминалы и комбинаторы стандартной библиотеки

Шаги конвейера — обычные глаголы прелюдии. Терминалы сворачивают коллекцию в одно значение: сумма, количество, наибольшее, наименьшее чисел (пример №102). Групповые строят словарь: подсчёт ряда — частотный словарь «элемент → сколько раз» (тот самый частотный словарь — одной строкой), группировка ряда меркой — «ключ → список подошедших» (пример №103). Комбинаторы берут функцию творительным падежом (§ФВП): отобрази/отфильтруй/сверни ряд действием.

Имена терминалов — номиналы (подсчёт, сумма), а не повеления (посчитай): терминал даёт значение, а значение по-русски именуется существительным с родительным (функциональная морфология). Оттого и читается: частоты -- это подсчёт слов.

Лидер по мере (argmax/argmin) пример №105

наибольшее/наименьшее чисел берут максимум/минимум коллекции. Но часто нужен не максимум значения, а элемент, у которого значение наибольшее — по некоторой мере. Мера — функция творительным падежом (§ФВП), отображающая элемент в число:

размер слова -- это длина слова
наибольшее (всё: "кот", "пёс", "ёжик") размером   прим ёжик — самое длинное
наименьшее ряда размером                          прим самое короткое

Раз сравнивается мера (число), а не сам элемент, это работает и над не-числами — строками, парами, записями. На этом стоит мода (самое частое): пара частотного словаря с наибольшим счётом, и берём её ключ. Прелюдия уже свернула это в один номинал — мода ряда:

мода слов                                          прим самое частое слово — одним словом

прим внутри (для понимания) — argmax частотного словаря по счёту:
первое (наибольшее (пары (подсчёт слов)) счётом)    где  счёт пары -- это второе пары

Это в точности Java Collections.max(coll, comparator) / Stream.max(comparing(…)). Как и в JDK, конкретные статистики (медиана, дисперсия) в стандарт не зашиты — их собирают из этих примитивов. Тонкость: перегруженный глагол (например второе) как меру передать нельзя — нужна неперегруженная обёртка (счёт); встроенные (длина) тоже заворачивают в номинал.

Поток: сортировка, top-N, кванторы, flatMap пример №108

Жадная часть Stream API — обычные глаголы прелюдии над любой коллекцией. Всё возвращает значение, поэтому имена — номиналы, компонуются и идут шагами конвейера:

напечатай (упорядочение (всё: 3, 1, 2))                прим → [1, 2, 3]
напечатай (первые 2 (наоборот (упорядочение чисел)))     прим два наибольших

Ленивость (бесконечные последовательности, ленивый limit) и параллелизм — отдельно, Фаза 3 (вид глагола = ленивость). Здесь всё жадное: годится для конечных коллекций.

Строки в потоке: слова и мода пример №106

Чтобы пустить в конвейер текст, его надо разбить на слова. Примитив ядра слова строки режет строку по пробелам в список слов (пустые отбрасывает). Дальше — вся библиотека: частотный анализ ТЕКСТА читается одной русской фразой, где в Java нужны Scanner + HashMap + Collectors.groupingBy + max:

текст -- это "кот пёс кот ёж кот"
слова текст                                        прим [кот, пёс, кот, ёж, кот]
подсчёт (слова текст)                              прим {кот -> 3, пёс -> 1, ёж -> 1}
мода слов текста                                   прим кот — конвейер как ИМЕННАЯ ГРУППА без скобок

слова — полноправный ШАГ конвейера, поэтому частотный анализ читается одной русской фразой «мода слов текста» (как сумма квадратов чётных ряда), без вложенных скобок.

Обходимость своего типа

Чтобы тип попал под всю библиотеку, в Java его помечают implements Iterable. У нас интерфейс не нужен: обходимость — это номинал, возвращающий список. Напишите обход своего дерева в список — и сумма, содержит, конвейер заработают по нему сами (пример №104):

листва дерева -- это выбор дерева:
  лист (значение), то всё: значение
  узел (л, п), то сцепить (листва л) (листва п)

напечатай (сумма (листва образца))    -- 6
напечатай (сумма листвы образца)      -- 6 — конвейером, без скобок

Перегрузка глаголов по сигнатуре пример №100

Русский сам перегружает глагол предлогом: идти в школу · идти на работу · идти к другу — глагол один, предлог задаёт смысл. «Глагол» это поддерживает: один корень может нести несколько определений, если они различаются падежной рамкой.

положить предмет в короб -- это соединить предмет с " — убрано в короб"
положить предмет к стене -- это соединить предмет с " — прислонено к стене"

напечатай (положить "лыжи" в "короб")   -- лыжи — убрано в короб
напечатай (положить в "короб" "санки")  -- порядок слов свободен

СИГНАТУРА у нас — это падежная рамка, а НЕ типы. Это важно и отличается от привычного: в большинстве языков «сигнатура» = типы+арность. У нас на поверхности динамическая типизация, и перегрузы различает управление — какими предлогами/падежами глагол управляет. Рамка — мультимножество ролей (роль + кратность), поэтому арность учитывается сама собой: подсчёт слов свод (объект×2) ≠ подсчёт слов (объект×1).

Как выбирается перегруз (лестница). Рамка вызова сравнивается точно, ступень за ступенью: из «Модуль» → свой модуль → импортированные → верхний уровень → прелюдия → встроенные. Не совпало на ступени — идём ниже; не совпало нигде — ошибка вида глагол «положить»: нет рамки {объект, на}; есть: {объект, в}, {объект, к}.

Ограничения (читать обязательно)

  1. Перегрузки по типам нет. Различает только падежная рамка. удвоить число и удвоить строку — это одна рамка {объект} и, значит, дубль-ошибка.
  2. Совпадение строгое. Ни частичного, ни «наиболее подходящего» подбора — язык не угадывает. Не совпало — честная ошибка со списком доступных рамок.
  3. Глагол как значение (ФВП) — только при ОДНОМ перегрузе. Голое имя (отобрази список удвоением) рамки не несёт, поэтому передать перегруженный глагол значением нельзя — будет ошибка «перегружен … как значение передать нельзя». Синтаксиса уточнения рамки в v1 нет.
  4. Мост из метода записи (§4) — тоже только при одном перегрузе: он вызывает позиционно и рамку не различает.
  5. Встроенные глаголы — нижний слой и в перегрузке по рамке не участвуют: сначала ищется пользовательский/библиотечный перегруз, и лишь потом встроенный.
  6. Прелюдия — низший приоритет. Своё определение перекрывает библиотечное с той же рамкой; библиотечные с другими рамками остаются доступны (это может удивить). Явно достать библиотечное всегда можно: содержит из «Прелюдия» 2 в набор.
  7. Импортированные модули ищутся раньше верхнего уровня — существующее правило модулей.
  8. Дубль — это совпадение и корня, и рамки в одном пространстве имён: ошибка «глагол … с рамкой {объект, в} уже определён».

Нативная компиляция под микроконтроллер (стадия 1)

Третий бэкенд — «Глагол» компилируется в C и исполняется на МК (в эмуляторе QEMU; реальное железо STM32F103 — следующая стадия). В отличие от площадки, этот пример не исполняется в браузере — нужен локальный тулчейн под целевую архитектуру: arm-none-eabi-gcc + qemu-system-arm (ARM) или riscv-none-elf-gcc + qemu-system-riscv32 (RISC-V).

Поддержана ВТОРАЯ архитектура — RISC-V (rv32), тем же эмиттером Глагол→C: смена C-компилятора на riscv-none-elf-gcc (флаги -march=rv32imac -mabi=ilp32) и запуск qemu-system-riscv32 -M virt -bios none -semihosting. Честно: арх-различие локализовано в ДВУХ местах — мк/rt.c (semihosting-вызов) и мк/startup.c (стартап: таблица векторов у ARM / _start у RISC-V) через #if defined(__riscv) … #else … #endif; эмиттер КомпиляторC и остальная логика рантайма (число→строка и т.д.) — ОБЩИЕ, архитектурно-независимые. Проверено в эмуляции (qemu-system-riscv32 -M virt). Это разблокирует МК на RISC-V за пределами эталонной платформы virt — первый такой таргет, ESP32-C3, описан ниже; RP2350-RISC-V — впереди.

Поддержаны пять ядер — Cortex-M3 (-mcpu=cortex-m3, QEMU-машина lm3s6965evb), Cortex-M4 (-mcpu=cortex-m4, QEMU-машина mps2-an386), Cortex-M7 (-mcpu=cortex-m7, QEMU-машина mps2-an500) — все три на общем линкере мк/cortexm.ld — Cortex-M0/M0+ (-mcpu=cortex-m0plus, QEMU-машина microbit) на своём линкере мк/microbit.ld (16 КБ RAM), и Cortex-M33 (-mcpu=cortex-m33, QEMU-машина mps2-an505) на своём линкере мк/an505.ld. Различие ядра/платы — всего три поля: -mcpu, имя QEMU-машины и линкер; эмиттер C, rt.c и стартап — общие для всех пяти. M7 не вносит нового: те же флаги, что и у M3/M4, меняется только -mcpu=cortex-m7 и QEMU-машина mps2-an500. Честно: (а) карта памяти РАЗНАЯ по платам — у M3/M4/M7 общий линкер мк/cortexm.ld, а у microbit всего 16 КБ RAM, поэтому свой линкер мк/microbit.ld (общий 64-КБ линкер вешает HardFault); (б) сборка — строго freestanding (-nostdlib -nostartfiles, никакой libc), а библиотеку libgcc линкуем ТОЧНЫМ путём под конкретное ядро (arm-none-eabi-gcc -mcpu=<ядро> -print-libgcc-file-name) — она несёт программное деление __aeabi_uidiv, понадобится для нацело/остатка с непостоянным делителем на Cortex-M0/M0+ (ARMv6-M без аппаратного деления); (в) microbit — это плата на nRF51 (Cortex-M0), она валидирует ядро/класс M0/M0+ (M0+ инструкционно-совместим с M0), а mps2-an500 — плата ARM MPS2+ с ядром Cortex-M7, а не конкретный чип вроде STM32F7 с его периферией. Проверено пока в эмуляции на уровне ядра, не на конкретном чипе с периферией.

Cortex-M33 (ARMv8-M, TrustZone) поддержан на QEMU-машине mps2-an505 (плата MPS2+ с SSE-200). Честно: у этой платы СВОЯ карта памяти — стек и данные должны лежать в писабельном BRAM 0x10000000, поэтому свой линкер мк/an505.ld (единый регион вместо FLASH/RAM-пары у M3/M4/M7); регион 0x30000000 на mps2-an505 НЕ writable-RAM — стекирование туда даёт STKERR-фолт (проверено спайком). TrustZone-специфики в стартапе НЕ потребовалось — обычный ARM Cortex-M стартап. M33 — это ядро RP2350 (Raspberry Pi Pico 2). Проверено в эмуляции.

ESP32-C3 (RISC-V rv32imc) — ПЕРВЫЙ таргет через стек РЕАЛЬНОГО вендора (Espressif), не эталонная QEMU-платформа. Честно: (а) переиспользование, не форк — тот же эмиттер Глагол→C и та же арх-ветка RISC-V в мк/rt.c/мк/startup.c, что и у virt выше, эмиттер и стартап НЕ менялись; (б) отличается ТОЛЬКО плата-слой — компилятор riscv32-esp-elf-gcc (тулчейн Espressif, не mainline riscv-none-elf-gcc), QEMU-форк Espressif (машина esp32c3, bare-metal через -kernel — БЕЗ полного образа esp-idf) и линкер мк/esp32c3.ld (код в IRAM 0x40380000, данные/стек в DRAM 0x3FC80000 — своя карта памяти чипа, не платы virt); (в) Espressif-QEMU при старте печатает в консоль баннер загрузчика (Not initializing SPI Flash, Loading kernel at address) — это шум эмулятора, не вывод программы, интеграционный тест отфильтровывает эти строки перед сверкой с интерпретатором. Тулчейн и QEMU — билды Espressif под Windows, качаются вручную с релизов github.com/espressif/qemu (QEMU) и github.com/espressif/crosstool-NG (компилятор riscv32-esp-elf-gcc). Проверено в эмуляции; следующий шаг — реальная плата ESP32-C3 (например, ESP32-C3 SuperMini).

Конвейер

.гл → (эмиттер --си) → app.c → (arm-none-eabi-gcc + мк/rt.c + мк/startup.c + линкер ядра) → app.elf → (qemu-system-arm -M <машина ядра> -semihosting) → вывод в консоль. Вывод — через ARM semihosting (та же «печать», что видно в консоли эмулятора).

Команды (Cortex-M3, QEMU lm3s6965evb)

java -jar "build/libs/глагол-all.jar" --си примеры-мк/1-привет.гл -o app.c

arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \
  -T src/main/resources/мк/cortexm.ld -o app.elf \
  src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \
  "$(arm-none-eabi-gcc -mcpu=cortex-m3 -print-libgcc-file-name)"

qemu-system-arm -M lm3s6965evb -nographic -semihosting -kernel app.elf

Ожидаемый вывод примера примеры-мк/1-привет.гл: Глагол на Cortex-M / 42 / 142 — построчно, совпадает с интерпретатором.

Команды (Cortex-M4, QEMU mps2-an386)

Та же app.c и тот же линкер — меняются только -mcpu и QEMU-машина:

arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \
  -T src/main/resources/мк/cortexm.ld -o app.elf \
  src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \
  "$(arm-none-eabi-gcc -mcpu=cortex-m4 -print-libgcc-file-name)"

qemu-system-arm -M mps2-an386 -nographic -semihosting -kernel app.elf

Вывод — тот же, что и на Cortex-M3 (совпадает с интерпретатором).

Команды (Cortex-M7, QEMU mps2-an500)

Та же app.c и тот же линкер cortexm.ld — меняются только -mcpu и QEMU-машина:

arm-none-eabi-gcc -mcpu=cortex-m7 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \
  -T src/main/resources/мк/cortexm.ld -o app.elf \
  src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \
  "$(arm-none-eabi-gcc -mcpu=cortex-m7 -print-libgcc-file-name)"

qemu-system-arm -M mps2-an500 -nographic -semihosting -kernel app.elf

Вывод — тот же, что и на Cortex-M3/M4 (совпадает с интерпретатором).

Команды (Cortex-M0/M0+, QEMU microbit)

Та же app.c — меняются -mcpu, QEMU-машина И линкер (у microbit только 16 КБ RAM, общий с M3/M4/M7 линкер cortexm.ld ей не подходит — вешает HardFault):

arm-none-eabi-gcc -mcpu=cortex-m0plus -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \
  -T src/main/resources/мк/microbit.ld -o app.elf \
  src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \
  "$(arm-none-eabi-gcc -mcpu=cortex-m0plus -print-libgcc-file-name)"

qemu-system-arm -M microbit -nographic -semihosting -kernel app.elf

Вывод — тот же, что и на Cortex-M3/M4/M7 (совпадает с интерпретатором): именно это сверяет интеграционный тест СиQemuDiffTest (теперь для всех пяти ядер ARM, включая Cortex-M33 ниже). Библиотека libgcc (её точный путь берём arm-none-eabi-gcc -mcpu=<ядро> -print-libgcc-file-name) несёт программное деление __aeabi_uidiv — понадобится для нацело/остатка с непостоянным делителем на Cortex-M0/M0+ (ARMv6-M без аппаратного деления); в текущем примере деления нет (константный делитель свёрнут в умножение), на M3/M4/M7 линковка libgcc безвредна.

Команды (Cortex-M33, QEMU mps2-an505)

Та же app.c — меняются -mcpu, QEMU-машина И линкер (у mps2-an505 свой единый писабельный BRAM 0x10000000, общий линкер cortexm.ld ей не подходит — регион 0x30000000, куда он кладёт стек, на этой плате не writable-RAM и даёт STKERR-фолт):

arm-none-eabi-gcc -mcpu=cortex-m33 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \
  -T src/main/resources/мк/an505.ld -o app.elf \
  src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \
  "$(arm-none-eabi-gcc -mcpu=cortex-m33 -print-libgcc-file-name)"

qemu-system-arm -M mps2-an505 -nographic -semihosting -kernel app.elf

Вывод — тот же, что и на Cortex-M3/M4/M7/M0 (совпадает с интерпретатором): именно это сверяет интеграционный тест СиQemuDiffTest для всех пяти ядер ARM. TrustZone-специфики (M33 — ARMv8-M) в стартапе не потребовалось — обычный ARM Cortex-M стартап.

Команды (RISC-V rv32, QEMU virt) — вторая архитектура

Та же app.c — компилятор, арх-флаги, линкер и QEMU-машина другие (свой линкер мк/virt-riscv.ld: RAM платы virt начинается с адреса 0x80000000, не с нуля, как у ARM):

riscv-none-elf-gcc -march=rv32imac -mabi=ilp32 -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \
  -T src/main/resources/мк/virt-riscv.ld -o app.elf \
  src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \
  "$(riscv-none-elf-gcc -march=rv32imac -mabi=ilp32 -print-libgcc-file-name)"

qemu-system-riscv32 -M virt -bios none -nographic -semihosting -kernel app.elf

Вывод — тот же, что и на всех пяти ядрах ARM (совпадает с интерпретатором): интеграционный тест СиQemuDiffTest прогоняет его шестым таргетом в общем списке (арх-агностичный харнесс: описание таргета — данные, компилятор/флаги/линкер/QEMU резолвятся по нему, а не жёстко в коде теста).

Команды (ESP32-C3, Espressif QEMU esp32c3) — реальный вендор-чип

Та же app.c и та же арх-ветка RISC-V в rt.c/startup.c — меняются компилятор (Espressif-тулчейн), линкер (карта памяти чипа) и QEMU (Espressif-форк с машиной esp32c3):

riscv32-esp-elf-gcc -march=rv32imc -mabi=ilp32 -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \
  -T src/main/resources/мк/esp32c3.ld -o app.elf \
  src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \
  "$(riscv32-esp-elf-gcc -march=rv32imc -mabi=ilp32 -print-libgcc-file-name)"

qemu-system-riscv32 -M esp32c3 -nographic -semihosting -kernel app.elf

Вывод после старта содержит баннер загрузчика Espressif-QEMU (строки Not initializing SPI Flash и Loading kernel at address) — это шум эмулятора, не программы; ПОСЛЕ фильтрации этих строк вывод совпадает с остальными шестью таргетами и с интерпретатором. Именно так его сверяет СиQemuDiffTest — седьмым таргетом в общем списке, с собственным полем-фильтром баннера.

Границы подмножества (честно)

Стадия 1 — прямолинейное подмножество: целое, логическое, строка-литерал; арифметика (плюс/ минус/умножить на, целочисленное деление нацело/остаток), сравнения (в т.ч. строк — равно/раньше/позже через побайтовый strcmp, а не сравнение указателей), предикаты (чётно/нечётно/положительно/отрицательно; пусто — для строки), не/и/или; если…то…иначе и выбор: (сопоставление/охранники → вложенный тернарник; «иначе» обязательно); связывания -- это (в т.ч. локальные где внутри глагола — компилируются в C-локали); свои глаголы (параметры, рекурсия — хвостовая идёт C-циклом, стек не растёт); напечатай; проверки проверь: (равенство/сравнение — как C-assert: молчит при успехе, печатает и выходит при провале). Точное деление разделить на — ВНЕ МК-профиля (число под МК фиксированное, дробей нет) — используй нацело. Число под МК — фиксированное 64-битное (long): переполнение молча заворачивается по модулю 2⁶⁴, как в C, — числовой башни (целое без предела/дроби/десятичное) на этой стадии НЕТ. Вне подмножества (числовая башня — дроби/десятичное/ плавающее, коллекции, модульность и т.д.) эмиттер честно бросает ошибку «под МК: …» вместо молчаливо неверного C. Рост подмножества идёт отдельными стадиями: свои глаголы, где-связывания и конкурентность-задачи (FreeRTOS) — уже здесь; коллекции/куча, каналы между задачами, периферия — впереди.

Конкурентность на МК: задачи FreeRTOS (стадия 2)

Стадия 2: конкурентность Глагола исполняется на настоящем RTOS. поручи X пускает вычисление X отдельной задачей и возвращает обещание; дождись забирает результат.

сумма -- это дождись (поручи (2 плюс 3))
произведение -- это дождись (поручи (10 умножить на 4))
напечатай сумму
напечатай произведение          прим 5, затем 40

Под МК обещание передаётся через уведомления задачи (task notification), а не через очередь — без буферов, ради экономии памяти и тактов (критично на МК). Каждое обещание получает свой индекс уведомления, поэтому дождись берёт результат нужной задачи. Компилятор порождает главную задачу (весь верхнеуровневый код), задачу-вычислитель на каждый поручи, и точку входа с планировщиком; не-конкурентные программы FreeRTOS не тянут.

Проверено прогоном на Cortex-M3 в QEMU (пример выше печатает 5 и 40 — как интерпретатор). FreeRTOS вендорено в мк/freertos/ (лицензия MIT). Каналы/сообщения между задачами и реактивное «когда/делай» на прерываниях — следующие стадии.

Глоссарий терминов

Однозначные определения понятий «Глагола» (термины легко перепутать — фиксируем):

Число · дробь
Один тип число с автопродвижением: целое (без предела) и дробь p/q. Точность по умолчанию: 1/3=⅓, 0.1+0.2=0.3 ровно (§Числа).
Нацело (//)
Целочисленное деление (округление к нулю): 7 // 2=3. Пара к остаток от деления (%). Обычное / — точное, даёт дробь.
Плавающее · приблизительно
Неточное double (IEEE) для науки/скорости — по запросу приблизительно X или из функций (корень/синус/логарифм…). Заражает точное (правило Scheme); ∞/NaN как в IEEE (§Плавающее).
Десятичное · деньги · округли
Точное число с запомненным масштабом (Java-BigDecimal) для денег/ERP. деньги X = округли X до 2 знаков; примитив округли X до N знаков [режим] (банковски/вниз/вверх/точно). Показ хранит хвостовые нули (§Деньги).
Высокая точность (√/π/e до N)
корень X до N знаков, пи до N знаков, е до N знаков — материализация до любого числа верных знаков → десятичное. НЕ символьная математика (√2 как объект / CAS — не поддержано; §Высокая точность).
Алгебраическая структура · класс · алгебра
Структура — конкретный тип с операциями (комплексные, GF(5), кватернионы). Алгебраический класскольцо/поле/тело: диктует обязательные операции и законные операторы (кольцо без ÷). «Алгебра» в узком смысле — алгебра над полем (векторы с умножением); в широком мы зовём всё это «алгебраическими структурами». Оператор + − × ÷ над значением-экземпляром диспетчится на его структуру (§Алгебраические структуры). Это НЕ CAS: законы объявляем классом, не проверяем.
Словесный стиль
Многословная явная запись для начинающего: плюс, равно, больше. Один из двух стилей (§11.1).
Символьный стиль
Лаконичная запись для профессионала: +, =, >. Второй стиль; обе формы — синонимы над одним ядром.
Прозой / построчно
Режим терминатора предложения: построчно (по умолчанию) — конец строки завершает фразу; прозой — завершает точка, переносы незначимы. Ось синтаксиса предложений, не именования (директивы далее прозой / далее построчно).
Начертание
Как один и тот же исходник отображается: клавиатурный ASCII-канон ↔ типографский вид (ввод <= → показ ; x1x₁).
Вид глагола
Несовершенный (помни) — ленивое вычисление; совершенный (запомни, сохрани) — эффект/строгое. Вид несёт «когда/как».
Падеж = роль
Роль аргумента задаёт падеж или предлог, а не позиция: от Ивана к Петру = к Петру от Ивана.
Ярлык-ёлочки «…» · лапки "…"
Метка или составное имя. Ёлочки — основная форма (в т.ч. для машин/ИИ); лапки — та же роль с клавиатуры (для людей). §95.
Запись · сущность · контроллер · приложение · служба
Классы-POJO с предзаполненными аннотациями: без аннотаций · @Entity · @RestController · @SpringBootApplication · @Service. Родная поверхность над Java/Spring.
Хранилище · встрой
Репозиторий Spring Data · внедрение зависимости (@Autowired).
Библиотека · раздел
Модули-пространства имён: библиотека — переиспользуемая библиотека определений; раздел — нейтральное пространство имён.
Открывает · из · подключи
Экспорт (публичное лицо модуля) · квалифицированный доступ (площадь из «Модуль») · импорт модуля.
Слой
Слой Чистой Архитектуры (сущности → правила → адаптеры → инфраструктура) с Правилом Зависимостей: зависеть можно только внутрь, к стабильному.
Типа
Приписывает тип имени, аргументу или результату: число типа Число, ряд типа список чисел. Тип необязателен — без него язык выводит его сам.
Внешнее
Тип значения, пришедшего из Java-мира: безопасная отметка «здесь наш вывод типов заканчивается» (язык не выдумывает про такое значение лишнего).
Свободная · обычная · строгая
Уровни строгости проверки типов: только предупреждения (программа всё равно бежит) · предупреждения на явных противоречиях · ошибка с остановкой. Уровень задаётся на модуле; ближняя пометка важнее общей настройки.
Договор
Пред/постусловия глагола: требует (на входе), гарантирует/ обеспечивает (на выходе, ссылается на результат).
Проверь · проверка
Встроенные тесты: инлайн-утверждение · блок с таблицей случаев (§21).
Пометка
Комментарий: прим (до конца строки), прим+ (с пояснением).
Роль
Функция ОДНОГО аргумента, заданная падежом/предлогом: объект (без предлога), инструмент (с), адресат (к), источник (из/от), предложные (в/на/по/…). Механизм №1 языка: падеж = роль → свободный порядок слов.
Сигнатура (падежная рамка)
Рамка глагола ЦЕЛИКОМ — мультимножество ролей его параметров (роль + кратность). Это НЕ типы! В большинстве языков сигнатура = типы+арность; у нас перегрузы различает управление — какими предлогами/падежами глагол управляет: добавить X в набор → {объект, в}, добавить X к списку → {объект, к}. «Роль» — про один аргумент, «сигнатура» — про всю рамку.
Прелюдия
Стандартная библиотека «Глагола», написанная на самом Глаголе. Подмешивается в каждую программу автоматически — без подключи; поэтому содержит, объединение/пересечение/разность, ключи/значения доступны сразу. Живёт в прелюдия.гл; операции — обычные глаголы поверх «дверей» ядра (элементы, множество из), а не встроенные. Своё определение с тем же именем затеняет библиотечное. Имя — по традиции языка Haskell (Prelude); суть — «то, что доступно всегда».
Ядро
Неприводимый минимум языка: типы значений, литералы, равенство, доступ [], «двери» разбора/сборки коллекций. Всё, что можно выразить на Глаголе, живёт не в ядре, а в прелюдии.

Подсветка в редакторе

Файлы .гл можно подсветить в VS Code, IntelliJ IDEA и (ожидаемо) OpenIDE через TextMate-грамматику. Скачать и пошаговые инструкции по установке — на странице «Инструменты».