Перевод статьи «Write you a programming language».
Что это означает — написать язык? Это значит, что вам нужно написать программу, которая будет интерпретировать или компилировать язык программирования (т. е. новый язык). Но для написания этой программы вам придется использовать какой-нибудь из существующих языков (базовый язык). Также можно написать эту программу на чистом машинном коде.
Интерпретатор или компилятор?
Для начала нужно выбрать, что вы хотите написать: интерпретатор или компилятор (или оба). Какую роль они играют?
- Компьютер (машина) — это интерпретатор машинного кода (а это тоже язык, пусть и не человекочитаемый).
- Компилятор переводит исходный код на другой язык, например:
- на высокоуровневый язык программирования (транспиляция),
- на машинный код для виртуальной машины (байткод), например JVM,
- на низкоуровневый язык, например язык ассемблера, который затем будет переведен (другой программой) на машинный код.
- Интерпретатор собственно выполняет инструкции, например JavaScript, Lisp, Machine, JVM, gnuplot, calculator и т. д.
Любопытно, что вы можете написать для своего языка программирования и интерпретатор, и компилятор. Примеры — Lisp (CommonLisp), Scheme (Chez Scheme).
Также можно написать интерпретатор или компилятор для языка программирования на этом же языке. Для этого вам придется сначала написать первый интерпретатор или компилятор на другом языке, а затем, в новых версиях, вы сможете пользоваться уже новым языком (т. н. раскрутка).
Руководства по теме создания интерпретаторов и компиляторов можно разбить на категории в соответствии с «базовыми» и «новыми» языками, например:
- Make a Lisp — реализация Lisp на 82 языках
- Write Yourself a Scheme in 48 Hours — реализация Scheme на Haskell
- Write you a Haskell — подмножество Haskell 2010, реализованное на Haskell.
Если язык реализован как компилятор, он переводит исходный язык на язык назначения. Исходный язык — то же самое, что и «новый», но язык назначения — не то же, что «базовый» язык. Руководства можно разбить по категориям в соответствии с исходным языком и языком назначения. Например:
- Implementing a JIT Compiled Language with Haskell and LLVM. Компилятор, реализованный на Haskell, переводит язык Kaleidoscope на LLVM IR (intermediate representation, промежуточное представление кода).
- How to implement a PL. Компилятор, реализованный на JS, переводит язык «λanguage» на JS.
- the-super-tiny-compiler. Компилятор, реализованный на JS, переводит маленькое подмножество Lisp на C-подобный синтаксис.
Управление памятью
Следующее, что нужно определить, это как ваш язык программирования будет управлять памятью:
- вообще не будет (как, например, какой-нибудь декларативный язык)
- вам это безразлично. Выделяем память и позволяем операционной системе очищать ее после закрытия приложения.
- статическое управление памятью, как в C
- частный случай — Rust borrow checker
- частный случай — Zig
- сборка мусора, как в Lisp, JavaScript и т. п.
- частный случай — ссылочные возможности Pony
- возможно, некая смесь статического типа и сборки мусора?
Система типов
Далее надо разобраться, как в вашем языке будет обстоять дело с типами:
- отсутствие типизации (когда у вас есть только один тип), пример — lambda calculus или calculator;
- динамическая типизация, пример — Lisp;
- статическая типизация, пример — Haskell;
- структурная типизация, пример — TypeScript.
Можно выбрать и кое-что позаковыристее. Например, типизацию Мартина-Лёфа (ML), зависимые типы (Idris), линейные типы и т. п.
Парадигмы
Все, указанное выше, касалось всех языков программирования. На следующем этапе вы можете выбрать другие особенности (одну или больше), которые определят парадигму вашего языка.
Например, вы допускаете использование функций первого класса, строгие вычисления (с вызовом по соиспользованию), динамические типы, макросы — и получаете Lisp.
- Если вы используете гигиенические макросы и продолжения, вы получаете Scheme (приблизительно).
- используя неизменяемые типы данных, вы получаете Clojure (тоже приблизительно).
Или вы можете разрешить использование функций первого класса, ленивые вычисления, статические типы — и получить на выходе ML.
Дополнительные особенности
Поверх всего этого вы можете добавить дополнительные особенности, например:
- систему модулей
- оптимизацию хвостовой рекурсии
- сопоставление с образцом, как в функциональных языках программирования, и т. п.
Генераторы парсеров
Одним из факторов успеха MAL является то, что ему не нужен генератор парсера: парсер Lisp относительно легко имплементировать. Это делает его очень портируемым (реализован более чем на 80 языках). То же касается lis.py — у него есть еще более простой токенизатор.
Большинство руководств, не связанных с Lisp, предполагают необходимость какого-то специфичного генератора парсеров, что делает их менее портируемыми.
Список туториалов
Я собрал собственную коллекцию руководств по созданию языков программирования. Они находятся в репозитории на GitHub, так что вы вполне можете стать контрибьютором.
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]