Как делают языки программирования

Название этой статьи отражает вопрос, который я слышу снова и снова на форумах или в электронных письмах. Которые я получаю. Я думаю, что все любопытные разработчики спрашивали об этом хотя бы один раз. Нормально быть очарованным тем, как работают языки программирования. К сожалению, большинство ответов, которые мы читаем, очень академичны или теоретичны. Некоторые другие содержат слишком много деталей реализации. Прочитав их, мы все еще удивляемся, как все работает на практике. Поэтому мы собираемся ответить на него. Да, мы увидим, каков процесс создания вашего собственного полного языка с компилятором для него, а что нет.

Обзор

Большинство людей, которые хотят узнать, как “создать язык программирования”, эффективно ищут информацию о том. Как построить компилятор. Они хотят понять механику, которая позволяет выполнять новый язык программирования.
Компилятор является фундаментальной частью головоломки, но создание нового языка программирования требует большего:

1) Язык должен быть разработан: создатель языка должен принять некоторые фундаментальные решения о парадигмах. Которые будут использоваться. И синтаксисе языка
2) Должен быть создан компилятор
3) Должна быть реализована стандартная библиотека
4) Должны быть предоставлены вспомогательные инструменты. Такие как редакторы и системы сборки

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

Проектирование языка программирования

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

Я думаю о разработке языка программирования как разделенном на две фазы:

  1. Фаза большой картины
  2. Фаза уточнения

На первом этапе мы отвечаем на фундаментальные вопросы о нашем языке.

  • Какую парадигму исполнения мы хотим использовать? Будет ли он императивным или функциональным? Или, может быть, на основе государственных машин или бизнес-правил?
  • Нужен ли нам статический тип или динамический?
  • В каких программах этот язык будет лучше всего работать? Будет ли он использоваться для небольших скриптов или больших систем?
  • Что для нас важнее всего: производительность? Читабельность?
  • Хотим ли мы, чтобы он был похож на существующий язык программирования? Будет ли он ориентирован на разработчиков C или легко учиться для тех, кто приходит из Python?
  • Хотим ли мы, чтобы он работал на определенной платформе (JVM, CLR)?
  • Какие возможности метапрограммирования мы хотим поддерживать, если таковые имеются? Макросы? Шаблоны? Отражение?

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

Построение компилятора

Создание компилятора — это самый захватывающий шаг в создании языка программирования. Как только у нас есть компилятор, мы действительно можем воплотить наш язык в жизнь. Компилятор позволяет нам начать играть с языком, использовать его и определить. Что мы упустили в первоначальном дизайне. Это позволяет увидеть первые результаты. Трудно превзойти радость от выполнения первой программы, написанной на нашем совершенно новом языке программирования. Какой бы простой она ни была.

Но как построить компилятор?

Как и все сложное мы делаем это поэтапно:

  1. Мы строим парсер: парсер-это часть нашего компилятора, которая берет текст наших программ и понимает. Какие команды они выражают. Он распознает выражения, операторы, классы и создает внутренние структуры данных для их представления. Остальная часть парсера будет работать с этими структурами данных, а не с исходным текстом
  2. (необязательно) Мы переводим дерево синтаксического анализа в абстрактное синтаксическое дерево. Как правило, структуры данных, создаваемые синтаксическим анализатором, являются немного низкоуровневыми. Поскольку они содержат много деталей. Которые не являются решающими для нашего компилятора. Из-за этого мы часто хотим перестроить структуры данных на что-то немного более высокое
  3. Мы разрешаем символы. В коде мы пишем такие вещи, как a + 1. Наш компилятор должен выяснить, к чему aотносится. Это поле? Это переменная? Является ли это параметром метода? Мы изучаем код, чтобы ответить на этот вопрос
  4. Мы проверяем дерево. Нам нужно проверить, чтобы программист не совершал ошибок. Пытается ли он суммировать логическое значение и int? Или доступ к несуществующему полю? Нам нужно выдавать соответствующие сообщения об ошибках
  5. Мы генерируем машинный код. На этом этапе мы переводим код в то, что машина может выполнить. Это может быть правильный машинный код или байт-код для какой-то виртуальной машины
  6. (необязательно) Мы выполняем связывание. В некоторых случаях нам нужно объединить машинный код, созданный для наших программ, с кодом статических библиотек. Которые мы хотим включить. Чтобы сгенерировать один исполняемый файл

Всегда ли нам нужен компилятор? Нет. Мы можем заменить его другими средствами для выполнения кода:

  • Мы можем написать интерпретатор: интерпретатор-это, по сути, программа, которая выполняет шаги 1-4 компилятора. А затем непосредственно выполняет то. Что задано абстрактным синтаксическим деревом
  • Мы можем написать транспайлер: транспайлер будет делать то, что указано в шагах 1-4, а затем выводить некоторый код на каком-то языке. Для которого у нас уже есть компилятор (например. C++ или Java)

Эти две альтернативы вполне допустимы, и часто имеет смысл выбрать одну из них. Потому что требуемое усилие обычно меньше.

Мы написали статью, объясняющую, как написать транспайлер. Взгляните на него, если хотите увидеть практический пример с кодом.

В этой статье мы более подробно объясняем разницу между компилятором и интерпретатором.

Стандартная библиотека для вашего языка программирования

Любой язык программирования должен делать несколько вещей:

  • Печать на экране
  • Доступ к файловой системе
  • Использование сетевых подключений
  • Создание графических интерфейсов

Это основные функции для взаимодействия с остальной частью системы. Без них язык в принципе бесполезен. Как мы предоставляем эти функции? Путем создания стандартной библиотеки. Это будет набор функций или классов, которые могут быть вызваны в программах, написанных на нашем языке программирования. Но которые будут написаны на каком-то другом языке. Например, многие языки имеют стандартные библиотеки, написанные по крайней мере частично на языке C.

Тогда стандартная библиотека может содержать гораздо больше. Например, классы для представления основных коллекций, таких как списки и карты, или для обработки общих форматов. Таких как JSON или XML. Часто он содержит расширенные функциональные возможности для обработки строк и регулярных выражений.

Другими словами, написание стандартной библиотеки-это большая работа. Это не гламурно, это не так концептуально интересно, как написание компилятора, но это все еще фундаментальный компонент. Чтобы сделать язык программирования жизнеспособным.

Есть способы избежать этого требования. Один из них заключается в том. Чтобы заставить язык работать на какой-то платформе и сделать возможным повторное использование стандартной библиотеки другого языка. Например, все языки, работающие на JVM, могут просто повторно использовать стандартную библиотеку Java.

Вспомогательные инструменты для нового языка программирования

Чтобы сделать язык пригодным для использования на практике. Нам часто приходится писать несколько вспомогательных инструментов.

Самый очевидный — редактор. Специализированный редактор с подсветкой синтаксиса. Встроенной проверкой ошибок и автозавершением в настоящее время просто необходим для того. Чтобы сделать любого разработчика продуктивным.

Но сегодня разработчики избалованы, и они будут ожидать всевозможных других вспомогательных инструментов. Например, отладчик может быть действительно полезен для борьбы с неприятной ошибкой. Или система сборки, подобная maven или gradle, может быть чем-то таким, о чем пользователи спросят позже.

В самом начале может быть достаточно редактора. Но по мере роста вашей пользовательской базы будет расти и сложность проектов. И потребуется больше вспомогательных инструментов. Надеюсь, в это время найдется сообщество, готовое помочь в их строительстве.

Краткие сведения

Создание языка программирования-это процесс, который многим разработчикам кажется загадочным. В этой статье мы попытались показать, что это всего лишь процесс. Это увлекательно и нелегко. Но это можно сделать.

Вы можете захотеть создать язык программирования по целому ряду причин. Одна хорошая причина-для развлечения. Другая-для изучения того. Как работают компиляторы. Ваш язык может оказаться очень полезным или нет. В зависимости от многих факторов. Однако если вы получаете удовольствие и/или учитесь во время его создания, то стоит потратить на это некоторое время.

И, конечно же. Вы сможете похвастаться своими коллегами-разработчиками.

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

Вас также могут заинтересовать некоторые из наших статей:

Скачать руководство с 68 ресурсами по созданию языков программирования

Получите руководство на свой почтовый ящик чтобы прочитать его на всех ваших устройствах когда у вас будет время