Синтаксис по факту: модули, функции, выражения, образцы
Правило этой статьи: показывается только то, что либо взято дословно из файла репозитория, либо написано нами и проверено запуском. Там, где мы пробовали угадать форму и ошиблись, приведён отказ парсера — он информативнее описания.
Файл целиком
Начнём с программы, которую мы написали и прогнали. Она короткая, но задевает большинство конструкций:
модуль «Синтаксис»
объект «Позиция»
цена является числом
количество является числом
тотальная функция «Стоимость позиции»
принимает цена: число, количество: число
возвращает число
пример «Три по десять»
дано цена равно 10
дано количество равно 3
ожидается 30
пусть поз равно запись «Позиция» с цена равным цена и количество равным количество
поз.цена умножить на поз.количество
тотальная функция «Деление и остаток»
принимает а: число, б: число
возвращает число
пример «Семь и два»
дано а равно 7
дано б равно 2
ожидается 4.5
(а делить на б) плюс (а остаток от б)
тотальная функция «Скидка»
принимает сумма: число
возвращает число
пример «Десять процентов от тысячи»
дано сумма равно 1000
ожидается 100
10 процентов от сумма
$ node flang/bin/flang.mjs test синтаксис.flang --pretty
{ "valid": true, "total": 3, "passed": 3, "failed": 0, … }
Заголовок модуля
Файл начинается строкой модуль «Имя». Импорты идут с отступом под ней:
модуль «Проба импорта»
использует «Списки» из "../stdlib/lists.flang"
Это файл flang/examples/import-check.flang целиком (без функции). Путь —
в обычных двойных кавычках и разрешается относительно текущего файла. Ключевое
слово экспортирует в таблице лексера есть, но ни в одном .flang-файле
репозитория не используется; без него виден весь модуль. Подробнее про
связывание — глава «Стандартная библиотека».
Комментарии — // до конца строки. Файлы stdlib и core открываются
многострочными блоками таких комментариев, в которых объясняется, почему модуль
устроен именно так; читать их стоит.
Объявление типов
Записи объявляются словом объект — прямо как в FTS:
объект «Группа»
ключ является строкой
слова является список строки
Это из flang/examples/leetcode/049-group-anagrams.flang. Суммы типов —
словом тип, с перечислением вариантов:
тип «Число или ничто»
вариант «Есть число» содержит значение: число
вариант «Нет числа»
Это flang/stdlib/optional.flang. Вариант либо несёт поля (содержит имя: тип,
через запятую), либо не несёт ничего. Имена вариантов обязаны быть уникальны в
пределах модуля.
Спецификация показывает варианты без кавычек — вариант Слово содержит текст: строка. Обе формы читаются лексером, но в реальных файлах используется форма с
ёлочками, и на то есть причина: см. раздел про ключевые слова ниже.
Функции
тотальная функция «Первый элемент или запасное»
принимает элементы: список числа, запасное: число
возвращает число
пример «Список не пуст»
дано элементы равно [4, 5]
дано запасное равно 0
ожидается 4
пример «Пустой список даёт запасное»
дано элементы равно пустой список
дано запасное равно 42
ожидается 42
«Значение или запасное» от («Первый элемент» от элементы) и запасное
Разбираем по строкам:
тотальная— необязательная пометка класса (глава «Два класса программ»);принимает— параметры через запятую, каждый в формеимя: тип;возвращает— один тип;пример— блок сданона каждый параметр и одниможидается;- последнее выражение блока — тело функции.
Порядок свободный: примеры в корпусе стоят и до тела, и после него. Тело — это
одно выражение, а не последовательность операторов; у языка нет return, потому
что возвращать больше нечего.
Форма ожидается 4 — короткая. В унаследованных от FTS утилитах встречается
полная — ожидается результат равен 2000.
Типы в сигнатурах
Скаляры: число, строка, признак, ничто. Формы склонений тоже читаются —
лексер знает числа, числом, числу, строки, строкой, строку,
признака, признаком. Отсюда естественное список числа вместо список число.
Списки: список числа, список строки, список «Группа», и даже вложенно —
список список строки (из решения про анаграммы).
Именованные типы пишутся в ёлочках: возвращает «Число или ничто».
Унаследованные от FTS имена деньги и дата лексер знает; тайпчекер
приравнивает Деньги к числу, а Дата — к строке.
Выражения
Связывание
пусть удвоенное равно «Умножить значение» от («Первый элемент» от элементы) и 2
«Значение или запасное» от удвоенное и запасное
пусть не требует слова в: тело идёт следующей строкой того же уровня.
Ветвление
Блочная форма:
если акк начинается с "!"
то акк
иначе
если "([{" содержит буква
то соединить акк с буква
иначе …
И однострочная — из того же файла
(flang/examples/leetcode/020-valid-parentheses.flang):
если закрывающая равен "]" то "[" иначе "{"
Отдельной конструкции «иначе если» нет — вложенные если в ветке иначе
образуют лестницу. Логических связок тоже нет: ни и, ни или, ни не для
признаков. Конъюнкция пишется вложенными если, отрицание — сравнением
равен нет. Это заметная шероховатость, и она честно записана в
flang/examples/leetcode/index.json в списке недостающего.
Вызов
«Длина» от хвоста
«Значение или запасное» от удвоенное и запасное
«Символы с позиции» от текст и (позиция плюс 1)
Имя функции — всегда в кавычках, аргументы соединяются словом и. Вызвать
функцию без аргументов нечем: применение записывается только как «Имя» от ….
Арифметика и сравнения
Инфиксные: плюс, минус, умножить на, делить на, остаток от,
процентов от. Последние два стоит запомнить именно как инфиксные — мы
попробовали написать остаток от а и б по аналогии с вызовом и получили:
{"code":"FLANG_PARSE","message":"не разобрана конструкция: неожиданное 'остаток от'",
"span":{"line":24,"column":25}}
Правильно — а остаток от б.
Сравнения унаследованы от FTS: равен, не равен, больше, меньше,
не больше, не меньше. Формы равна, равно, равным — то же самое.
Порядок сравнения (больше/меньше) работает только для чисел. На строках
получите отказ ещё на check:
{"code":"FLANG_TYPE",
"message":"левый операнд сравнения «gt» имеет тип строка:
сравнения порядка допустимы только для чисел"}
Записи и варианты
Конструирование записи и вариантa устроено одинаково — словом с и
перечислением полей через и:
запись «Группа» с ключ равным ключ и слова равным (добавить слово к группа.слова)
вариант «Есть число» с значение равным голова
Вариант без полей пишется просто: вариант «Нет числа».
Доступ к полю — через точку: группа.ключ, диагностика.код. Единственная
точка в языке, где точка вообще используется.
Записи нельзя записать литералом. Мы попробовали очевидное:
$ … дано поз равно {цена: 10, количество: 3}
{"code":"FLANG_PARSE","message":"не разобрана конструкция: неожиданное '{'"}
В корпусе записи всегда строятся конструктором запись «…» с …, а в примерах
не встречаются вовсе.
Сопоставление с образцом
разбор элементов
случай пусто
то 0
случай голова и хвост
то 1 плюс «Длина» от хвоста
Образцы, которые встречаются в репозитории:
| Образец | Что связывает |
|---|---|
случай пусто |
пустой список |
случай голова и хвост |
голову и хвост непустого списка |
случай вариант «Есть число» с значение как зн |
вариант и его поле под именем зн |
случай «Нет числа» |
вариант без полей |
Обратите внимание на голова и хвост: имена не выбираются, это фиксированная
форма из таблицы ключевых слов (headTail: ["голова и хвост", "head and tail"]).
Форма случай вариант «Имя» с поле как имя — явная, с ключевым словом вариант.
Именно ей пользуется стандартная библиотека, и не из вкуса. В flang/SPEC.md,
раздел 10, описано известное ограничение: лексер решает, что перед ним, до того
как становится известен тип, поэтому вариант, названный ключевым словом,
в образце не разбирается.
| Имя варианта | Во что превращается |
|---|---|
Да, Нет |
литерал признака |
Плюс, Минус |
арифметический оператор |
Больше, Меньше |
сравнение |
Спецификация тут же признаёт долг: диагностика при этом не называет настоящую причину, и «сообщение обязано говорить „имя варианта совпадает с ключевым словом“, а не „ожидался образец“».
Встроенные формы над коллекциями
Функции не являются значениями, поэтому map/filter/fold принимают не
функцию, а тело:
свёртка элементы начиная с [первый] как акк и эл → добавить эл к акк
отобразить группы как группа → группа.слова
отфильтровать элементы где эл → эл не равен значение
Стрелка → отделяет тело от заголовка в однострочной записи; в многострочной
тело просто уходит на следующий уровень отступа:
свёртка группы начиная с нет как акк и группа
если акк равен да то да иначе группа.ключ равен ключ
Также есть добавить … к … (дописывает в конец) и длина. Приписать в начало
встроенной формы нет — flang/stdlib/lists.flang заводит для этого первую
функцию модуля.
Строки
длина текст
символ 2 в текст
подстрока акк с 1 по ((длина акк) минус 1)
соединить акк с буква
разделить текст по буква
"([{" содержит буква
акк начинается с "!"
к строке (…)
к числу (…)
Две семантические детали зафиксированы в спецификации и повторяются всеми слоями: индексация с 1, включительно с обоих концов, и длина в кодовых точках, а не в единицах UTF-16. Мы проверили второе:
тотальная функция «Длина текста»
принимает текст: строка
возвращает число
пример «Эмодзи считается одним»
дано текст равно "мир 🌍"
ожидается 5
длина текст
Пример проходит. Обоснование в спецификации прямое: «иначе кириллица и эмодзи считаются неверно».
Ещё одна деталь для тех, кто сверяется с index.json: там записано, что форма
символ не проходит check из-за расхождения порядка аргументов между
парсером и тайпчекером. На момент нашей проверки это уже неверно — символ 2 в текст проходит check и возвращает "и" от "мир".
Литералы
42, -3, 0.5, "строка", да, нет, ничто, [1, 2, 3], пустой список.
Экспоненциальная запись (1e3) лексером flang читается — регулярное выражение
в lexer.mjs её допускает. А вот лексер FTS, переписанный на flang в
flang/core/lexer.flang, её не поддерживает: это записано отдельным долгом в
flang/core/SPEC.md. Два разных лексера, не перепутайте.
Чего в синтаксисе нет
Коротко, чтобы не искать: нет циклов, нет присваивания, нет объявления
переменной вне пусть, нет исключений и try, нет логических операторов, нет
лямбд и вообще функций как значений, нет литерала записи, нет вызова без
аргументов, нет обобщённых типов. Полный проверенный перечень —
глава «Чего в языке пока нет».
Дальше — глава «Система типов», про то, как всё это проверяется.