Раздел 2 · Программирование / CS

Разрабатываем язык: одиннадцать миров, одиннадцать решений

junior~55 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Разрабатываем язык: одиннадцать миров, одиннадцать решений

Правила доступа в сервисе разрослись из трёх условий в сто пятьдесят. JSON-конфиги уже копируют друг друга, валидатор молчит, и каждый релиз кто-нибудь даёт новым правилам противоречить со старыми. Ты решаешься: нужен маленький собственный язык. Назовём его minilang.

Разработать язык это не придумать синтаксис за вечер. Это цепочка решений:

  1. Читать. Лексер и парсер превращают исходник в дерево.
  2. Представлять. Дерево или небольшое ядро выражений становится моделью программы.
  3. Проверять. Типы и правила говорят, какие программы имеет смысл исполнять.
  4. Исполнять. Интерпретатор, компилятор и среда выполнения.
  5. Расширять. Макросы, DSL, возможность достроить язык под домен.
  6. Размещать. Память, конкурентность, системные интерфейсы, инструменты.

Одиннадцать больших языковых миров уже отвечали на эти вопросы. Каждый ответ проверен практикой и имеет цену. В этом уроке ты пройдёшь по всем одиннадцати и соберёшь из их ответов проект minilang: с настоящим синтаксисом каждого языка, с пользой решения и с тем, что оно стоит.

Главная мысль: язык это связка модели данных, правил исполнения, среды выполнения и инструментов разработки. Каждый заимствованный ответ меняет соседние решения, а не только строчку в грамматике.

Карта, а не родословная

Сначала разведи три вида связи между технологиями. Они часто склеиваются в одну стрелку, и от этого путаются даже опытные разработчики.

  • Родство. Modula-2 продолжает линию Pascal, Oberon продолжает Modula-2.
  • Влияние. Go заимствует идеи сразу из C, семейства Pascal/Modula/Oberon и языков с CSP-конкурентностью. Это не новая версия одного из них.
  • Общая среда исполнения. Erlang и Elixir работают на BEAM, но у них разные синтаксис, средства расширения и инструменты.

Поэтому цепочка «ML → Haskell → Rust → Zig» больше путает, чем объясняет. Haskell связан с ML идеями типов, но вырос также из традиции нестрогих функциональных языков, в частности Miranda. Zig не потомок Rust. А C и Unix находятся на разных уровнях: один язык, другая операционная система.

Это карта инженерных привычек, не классификация с непроницаемыми границами. В OCaml есть объекты, в Common Lisp тоже. На Rust можно писать actors, а на Go использовать общую память. Название мира показывает центр внимания, а не список разрешённых приёмов.

1. ALGOL, Pascal, Modula, Oberon · сначала границы

Первое решение minilang: из каких кусков состоит программа и что каждый кусок прячет от соседей. Мир Никлауса Вирта отвечает: модуль с явным интерфейсом. Наружу операции, внутрь представление.

Одна и та же функция на четырёх языках этой семьи: даны 2 и 3, вернуть 5. Смотри, где стоит тип результата и как функция возвращает значение.

ALGOL 60

begin
integer s;
integer procedure total (base, extra);
value base, extra;
integer base, extra;
total := base + extra;
s := total(2, 3)
end

Тип пишется перед словом procedure, а результат возвращается присваиванием имени процедуры. Параметры из списка value передаются по значению; параметр без этого списка работает по подстановке текста аргумента.

Pascal

function Total(base, extra: integer): integer;
begin
  Total := base + extra
end;

Тип результата переехал в конец заголовка. Возврата по-прежнему нет: результатом становится последнее присваивание имени Total.

Modula-2

PROCEDURE Total(base, extra: INTEGER): INTEGER;
BEGIN
  RETURN base + extra
END Total;

Появился RETURN с выражением, а имя процедуры повторяется после END.

Oberon-07

PROCEDURE Total(base, extra: INTEGER): INTEGER;
BEGIN
  RETURN base + extra
END Total

Здесь RETURN это не оператор посреди тела, а завершающая часть объявления процедуры. Одна процедура, один выход с результатом.

Почти за полвека этой семьи менялась не «красота», а дисциплина: блочная структура ALGOL, модули и раздельная компиляция Modula-2, аскеза Oberon. У Oberon есть и второй поворот: Вирт и Юрг Гуткнехт проектировали не только язык, но и операционную среду вокруг него. Загрузка модулей, команды, работа с текстом и сборка мусора (GC) складывались в одну систему. Не «напишем синтаксис, а остальное как-нибудь прикрутим». Вирт описывает этот проект изнутри.

Решение для minilang: ядро из нескольких модулей с явными интерфейсами; лексер не знает про типы, типы не знают про исполнение. Цена: небольшой набор механизмов не выражает любой замысел напрямую. Маленькая грамматика сама по себе не делает реализацию маленькой.

В 16-arch/11 · Контракт модуля разберёшь, что должно оставаться верным при замене внутреннего представления.

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.