|
|
|
@ -0,0 +1,97 @@
|
|
|
|
|
--- |
|
|
|
|
title: Пишем Компилятор Для Функционального Языка на С++, Часть 0 - Вступление |
|
|
|
|
date: 2019-08-03T01:02:30-07:00 |
|
|
|
|
tags: ["C and C++", "Functional Languages", "Compilers"] |
|
|
|
|
description: "todo" |
|
|
|
|
--- |
|
|
|
|
Год назад, я был записан на курс по компиляторам. Я ждал этого момента почти два учебных года: еще со времени школы меня интересовало создание языков программирования. Однако я был разочарован - заданный нам финальный проект полностью состоял из склеивания вместе написанных профессором кусочков кода. Склеив себе такой грустный компилятор, я не почувствовал бы никакой гордости. А я хотел бы гордиться всеми своими проектами. |
|
|
|
|
|
|
|
|
|
Вместо стандартного задания, я решил -- с разрешением профессора -- написать компилятор для ленивого функционального языка, используя отличную книгу Саймона Пейтона Джоунса, _Implementing functional languages: a tutorial_. На курсе мы пользовались С++, и мой проект не был исключением. Получился прикольный маленький язык, и теперь я хочу рассказать вам, как вы тоже можете создать ваш собственный функциональный язык. |
|
|
|
|
|
|
|
|
|
### Примечание к Русской Версии |
|
|
|
|
Вы читаете русскою версию этой статьи. Оригинал ее был написан год назад, и с тех пор объем всей серии немного изменился. Я планировал описать только те части компилятора, которые я успел закончить и сдать профессору: лексический анализ, синтаксический разбор, мономорфную проверку типов, и компиляцию простых выражений с помощью LLVM. Закончив и описав все эти части, я решил продолжать разрабатывать компилятор, и описал сборку мусора, полиморфную проверку типов, полиморфные структуры данных, а также компиляцию более сложных выражений. Вместо того чтобы писать наивный перевод английской версии -- притворяясь что я не знаю о перемене моих планов -- я буду вносить в эту версию изменения соответствующие сегодняшнему состоянию компилятора. Части статей не затронутые этими изменениями я тоже не буду переводить слово в слово, иначе они будут звучать ненатурально. Тем не менее техническое содержание каждой статьи будет аналогично содержанию ее английской версии, и код будет тот же самый. |
|
|
|
|
|
|
|
|
|
### Мотивация |
|
|
|
|
Начать эту серию меня подтолкнули две причины. |
|
|
|
|
|
|
|
|
|
Во-первых, почти все учебники и вступления к созданию компиляторов, с которыми я сталкивался, были написаны об императивных языках, часто похожих на C, C++, Python, или JavaScript. Я считаю, что в компиляции функциональных языков -- особенно ленивых -- есть много чего интересного, и все это относительно редко упоминается. |
|
|
|
|
|
|
|
|
|
Во-вторых, меня вдохновили книги, как Software Foundations. Все содержание Software Foundations, например, написано в форме комментариев языка Coq. Таким образом, можно не только читать саму книгу, но и сразу же запускать находящийся рядом с комментариями код. Когда описываемый код под рукой, легче экспериментировать и интереснее читать. Принимая это во внимание, я выкладываю вместе с каждой статьей соответствующую версию компилятора; в самой статье описывается код именно из этой версии. Все части написанной мною программы полностью доступны. |
|
|
|
|
|
|
|
|
|
### Обзор |
|
|
|
|
Прежде чем начинать наш проект, давайте обсудим, чего мы будем добиваться, и какими способами. |
|
|
|
|
|
|
|
|
|
#### “Классические” Стадии Компилятора |
|
|
|
|
Части большинства компиляторов достаточно независимы друг от друга (по крайней мере в теории). Мы можем разделить их на следующие шаги: |
|
|
|
|
|
|
|
|
|
* Лексический анализ |
|
|
|
|
* Синтаксический разбор |
|
|
|
|
* Анализ и оптимизация |
|
|
|
|
* Генерация кода |
|
|
|
|
|
|
|
|
|
Не все вышеописанные шаги встречаются в каждом компиляторе. Например, компилятор в моих статьях совсем не оптимизирует код. Также, в некоторых компиляторах присутствуют шаги не упомянутые в этом списке. Язык Idris -- как и многие другие функциональные языки -- переводится сначала в упрощённый язык “TT”, и только после этого проходит через анализ. Иногда, с целью ускорить компиляцию, несколько шагов производятся одновременно. В целом, все эти стадии помогут нам сориентироваться, но никаким образом нас не ограничат. |
|
|
|
|
|
|
|
|
|
#### Темы, Которые Мы Рассмотрим |
|
|
|
|
Мы начнем с нуля, и пошагово построим компилятор состоящий из следующих частей: |
|
|
|
|
|
|
|
|
|
* Лексического анализа с помощью программы Flex. |
|
|
|
|
* Синтаксического разбора с помощью программы Bison. |
|
|
|
|
* Сначала мономорфной, а позже полиморфной проверки типов. |
|
|
|
|
* Вычисления программ используя абстрактную машину G-machine. |
|
|
|
|
* Компиляции абстрактных инструкций G-machine используя LLVM. |
|
|
|
|
* Простого сбора мусора. |
|
|
|
|
|
|
|
|
|
Наша цель - создать ленивый, функциональный язык. |
|
|
|
|
|
|
|
|
|
#### Темы, Которые Мы Не Рассмотрим |
|
|
|
|
Для того, чтобы создать любую нетривиальную программу, нужно иметь значительный объем опыта и знаний; одному человеку было бы сложно научить всему этому. У меня буквально не хватило бы на это времени, да и исход такой попытки был бы неблагоприятным: опытным читателям было бы труднее извлечь из статей новую информацию, а неопытным читателям все равно было бы недостаточно подробно. Вместо того, чтобы портить таким образом свои статьи, я буду полагаться на то, что вы достаточно комфортно себя чувствуете с некоторыми темами. В число этих тем входят: |
|
|
|
|
|
|
|
|
|
* [Теория алгоритмов](https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%B2), |
|
|
|
|
более конкретно [теория автоматов](https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%BE%D0%B2). |
|
|
|
|
Детерминированные и недетерминированные автоматы кратко упоминаются в первой статье во время лексического анализа, a синтаксический разбор мы выполним используя контекстно-свободную грамматику. |
|
|
|
|
* [Функциональное программирование](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), с легкой примесью [лямбда-исчисления](https://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%B8%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5). |
|
|
|
|
Мы будем пользоваться лямбда-функциями, каррированием, и системой типов Хиндли-Мильнер, которая часто встречается в языках семейства ML. |
|
|
|
|
* С++. Я стараюсь писать код правильно и по последним стандартам, но я не эксперт. Я не буду объяснять синтаксис или правила С++, но разумеется буду описывать что именно делает мой код с точки зрения компиляторов. |
|
|
|
|
|
|
|
|
|
#### Синтаксис Нашего Языка |
|
|
|
|
Саймон Пейтон Джоунс, в одном из своих [~~двух~~ многочисленных](https://www.reddit.com/r/ProgrammingLanguages/comments/dsu115/compiling_a_functional_language_using_c/f6t52mh?utm_source=share&utm_medium=web2x&context=3) трудов на тему функциональных языков, отметил что большинство из этих языков по сути очень похожи друг на друга; часто, главная разница состоит именно в их синтаксисе. На данный момент, выбор синтаксиса - наша главная степень свободы. Нам точно нужно предоставить доступ к следующим вещам: |
|
|
|
|
|
|
|
|
|
* Декларациям функций |
|
|
|
|
* Вызову функций |
|
|
|
|
* Арифметике |
|
|
|
|
* Aлгебраическим типам данных |
|
|
|
|
* Сопоставлению с образцом |
|
|
|
|
|
|
|
|
|
Позже, мы добавим к этому списку выражения let/in и лямбда-функции. С арифметикой разобраться не сложно - числа будут писаться просто как `3`, значения выражений как `1+2*3` будут высчитываться по обычным математическим правилам. Вызов функций ненамного сложнее. Выражение `f x` будет значить “вызов функции `f` с параметром `x`”, а `f x + g y` - “сумма значений `f x` и `g y`”. Заметьте, что вызов функций имеет приоритет выше приоритета арифметических операций. |
|
|
|
|
|
|
|
|
|
Теперь давайте придумаем синтаксис для деклараций функций. Я предлогаю следующий вариант: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
defn f x = { x + x } |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
А для типов данных: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
data List = { Nil, Cons Int List } |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Заметьте, что мы пока пользуемся мономорфными декларациями типов данных. Позже, в одиннадцатой части, мы добавим синтаксис для полиморфных деклараций. |
|
|
|
|
|
|
|
|
|
В последнюю очередь, давайте определимся с синтаксисом сопоставления с образцом: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
case l of { |
|
|
|
|
Nil -> { 0 } |
|
|
|
|
Cons x xs -> { x } |
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Представленная выше распечатка читается как: “если лист `l` сопоставим с `Nil`, то все выражение возвращает значение `0`; иначе, если лист сопоставим с `Cons x xs` (что, опираясь на декларацию `List`, означает, что лист состоит из значений `x`, с типом `Int`, и `xs`, с типом `List`), то выражение возвращает `x`”. |
|
|
|
|
|
|
|
|
|
Вот и конец нашего обзора! В следующей статье, мы начнем с лексического анализа, что является первым шагом в процессе трансформации программного текста в исполняемые файлы. |
|
|
|
|
|
|
|
|
|
### Список Статей |
|
|
|
|
* Ой! Тут как-то пусто. |
|
|
|
|
* Вы, наверно, читаете черновик. |
|
|
|
|
* Если нет, то пожалуйста напишите мне об этом! |