Можно ли создать свой язык программирования: Пишем свой язык программирования без мам, пап и бизонов. Часть 0: теория / Хабр

Содержание

Создать свой язык программирования – возможно ли в 2022 году

Автор Сергей Тимофеев На чтение 10 мин Просмотров 1к. Опубликовано
Обновлено

Создать свой язык программирования – возможно ли в 2022 году. Если вас не устраивает стандартный функционал Python или Java, всегда можно написать свою библиотеку. Либо же поступить более радикально — можно задуматься над созданием своего языка программирования. Возможно, вам удастся создать нечто уникальное и прославиться на весь мир. Дело это непростое, но очень увлекательное.

Содержание

  1. Первые шаги, почему написать свой язык может каждый
  2. Выбор языка – на чем и о чем писать
  3. Компилятор, интерпретатор или транспилятор — что выбрать
  4. Основные концепции – проблема синтаксиса и лексического ядра
  5. Лексер поможет разобраться с синтаксисом
  6. Из чего будет состоять новый язык программирования
  7. Нужно ли делать стандартную библиотеку для собственного языка программирования
  8. Стоит ли использовать стороннее ПО, анализаторы и компиляторы
  9. Что делать с управлением памятью
  10. Выбор типизации — динамическая или статическая
  11. Дополнительные особенности: самый простой и самый сложный в написании язык

Первые шаги, почему написать свой язык может каждый

Чтобы просто объяснить суть проблемы написания языка программирования, возьмем в качестве примера «молодежный сленг». Как он появился?

В качестве примера. Большинство читателей поймут фразу «пойдем чилить», хотя ни в русском, ни в английском ее не существует. Значит, это новое слово. Окунемся в дебри лингвистики. Здесь выяснится, что есть английское слово «Chill», что в прямом переводе оно означает «прохлада». Англоязычный читатель знает, что у этого слова есть и другой смысл — беззаботный расслабленный отдых в приятной обстановке. А в русском появилось «чилить», его смысл тоже понятен почти каждому. Таких словечек немало, и появился новый язык.

По этому-же принципу создаются и языки программирования. Только в качестве основы, изначально объясняющей смысл того или другого слова (команды), выступает другой язык программирования. А собеседником, которому адресуется это послание, является компьютер, с его двоичной логикой.

Парадокс заключается в том, что перед тем, как написать свой язык программирования, придется очень хорошо выучить любой другой язык. Он будет выступать в качестве «переводчика» между новым продуктом и машиной.

Выбор языка – на чем и о чем писать

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

Ассемблер хорош в плане производительности, но писать на нем код понравится не каждому

Впрочем, не стоит сильно переживать по этому поводу, разница будет незначительной. Если вы не стремитесь сделать высоконагруженные системы. Так, ядро Windows NT, основа Windows 2000/XP/Vista/7/8, созданы на Си и С++. Гораздо важнее понимать логику самого процесса. Важнее всего сформировать стратегию, общее видение будущего ЯП. Здесь необходимо ответить на следующие вопросы:

  • какая парадигма исполнения будет использоваться, императивная или функциональная;
  • вид типизации — динамическая или статическая;
  • для каких программ требуется новый язык;
  • что вкладывать в стандартную библиотеку;
  • что важнее, читаемость или производительность;
  • это абсолютно новая программа со своей логикой и командами или же подразумевается легкое освоение;
  • возможности поддержек макросов и шаблонов.

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

Компилятор, интерпретатор или транспилятор — что выбрать

Перед тем, как сделать свой собственный язык программирования, необходимо определиться со стратегией его перевода в машинный код. Чтобы написанные в среде команды могли пониматься процессором, необходимо трансформировать их в стандартный набор нулей и единичек. За эту работу отвечает компилятор. Логика здесь примерно такая:

  • Изначально создается парсер — раздел, который берет текст программы и распознает выражения, классы инструкции. Он формирует внутренние структуры данных для их представления. В дальнейшем работа ведется именно с ними.
  • Зачем разрешаются отдельные символы и проверяется само дерево программы. Это необходимо, чтобы избежать ошибок. Например, они гарантированно произойдут во время попытки суммирования логического и целого числа.
  • После этого проводится перевод программы в байт код или машинный код для конкретной виртуальной машины. В отдельных случаях требуется объединение полученного машинного кода с кодом статических библиотек, это заключительный этап работы компилятора.

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

Разница между компиляцией и интерпретацией заключается в том, в первом случае программа целиком трансформируется в машинный код, а затем отправляется на исполнение. В случае интерпретации выполнение ведется построчно, что может снизить быстродействие.

Простая схема позволяет понять разницу между компилятором и интерпретатором

Выбрать между компиляцией и интерпретацией можно, просто опираясь на свои предпочтения. Так, Pascal, C, C++, Swift являются компилируемыми языками, а не менее популярные Java, JavaScript, Python, Ruby – интерпретируемыми.

Основные концепции – проблема синтаксиса и лексического ядра

Разработка уникального синтаксиса очень сильно влияет на конечной вид продукта. В Python для вывода на экран Hello world потребуется написать print(‘Hello, world!’). В Java это уже смотрится по-другому.

Hello, world на языке Java гораздо длиннее, чем на Python

В С++ это программа выглядит совершенно иначе, и не сказать что намного понятнее для неспециалиста.

 

С++ имеет свои преимущества, но к ним не относится простота понимания кода

Вполне возможно, что ваш язык программирования будет исполнять нечто вроде ‘Hello, world!’ по принципу «все что в кавычках, выводится на экран. Никто не запретит заниматься творчеством.

Достаточно много эзотерических языков программирования, которые используют головоломную логику для работы. При написании своего языка программист сам определяет, какие лексические и синтаксические принципы будут использованы в проекте.

Лексер поможет разобраться с синтаксисом

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

Фактически каждому числу или символу лексер дает свое определение, лексемы. Только после этого в дело вступает парсер и распознает, какие команды запущены, какие вложены, есть ли условные операторы или циклы и так далее.

Из чего будет состоять новый язык программирования

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

Все не так сложно, как кажется на первый взгляд

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

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

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

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

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

Есть еще одна альтернатива. Можно заставить язык работать на уже известной платформе, и подключить возможность использовать его стандартную библиотеку. Это весьма популярный шаг, благодаря которому новый язык получает дополнительный стимул для развития. В этом механизме кроется один из секретов популярности Java. Все языки, работающие на базе Java Virtual Machine, применяют её стандартную библиотеку.

Стоит ли использовать стороннее ПО, анализаторы и компиляторы

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

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

Генератор синтаксического анализатора общего назначения сам по себе непростой проект

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

Что делать с управлением памятью

Существует несколько принципов работы с компьютерными ресурсами. Если для вас не принципиальна производительность, там можно просто не обращать на это внимание. Достаточно выделить какой-нибудь объем ячеек, который операционная система сама очистит после закрытия приложения.

Можно применить статическое распределение памяти, но в этом случае возникнуть проблемы с совместимостью при рекурсивном вызове подпрограмм. Чуть больше ресурсов потребует принцип «сборка мусора». В этом случае компьютер хранит только ту информацию, которая требуется в данный момент. Когда необходимость в ней проходит, идет команда на Освобождение памяти. По такому алгоритму работают Lisp и JavaScript.

Выбор типизации — динамическая или статическая

Значительная часть современных языков программирования работает с динамической типизацией. То есть связывается с типом в момент присвоения переменной какого-то значения. Часть ПО использует статическую типизацию. В этом случае заранее объявляется тип переменной. Он не может быть изменен в будущем. Первый вариант используют в Python, PHP, JavaScript. Второй — Java, Си, С++.

Просто о динамической и статической типизации

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

Дополнительные особенности: самый простой и самый сложный в написании язык

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

Место, куда рано или поздно приходит каждый программист

Чтобы написать действительно что-то полезное, придется приложить массу усилий. И говоря откровенно, одному человеку сейчас сделать такое не под силу. Достаточно вспомнить, что Microsoft долгие годы применяла написанный еще в 1973 году язык Си. Конечно история информатики знает немало выдающихся личностей, которые практически в одиночку создавали уникальные продукты. Но в последние десятилетия разработка ПО стала настолько сложной, что работать одному и сделать что-то новое практически невозможно. Никто не будет поддерживать язык, если у него нет удобной среды разработки.

Как создать свой язык программирования? Теория и практика

Создание языка программирования очень сложная задача, но выполнимая. Мы расскажем основные факторы при создании своего первого языка.

Желание создать что-то своё и оставить след в истории посещает всех людей, в том числе программистов. Создание собственного языка программирования – это подходящая возможность. Мотивы бывают различные: от нечего делать, решения вечных проблем в других языках, разработка комфортного варианта для себя. 

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

Статья содержит специфические термины, без понимания которых не обойтись: лексеры, парсеры, интерпретаторы, компиляторы, деревья синтаксиса и остальное. Значительно проще вникнуть в суть, черпая информацию из интернета или при совместном изучении данных с опытным программистом. Базовые понятия терминологии – это фундаментальная величина для создания своего языка программирования.

Изучение компьютера

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

Зачем вам новый язык программирования?

Заранее определитесь с предназначением языка. Существует 2 основных направления – универсальный инструмент или узкоспециализированное решение. 

Востребованными оказываются языки с обоих направлений, но более популярны универсальные. Учтите, что многофункциональные языки требуют больше времени и усилий, а также у них множество конкурентов. 

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

Каких концепций будет придерживаться новый язык?

На этапе планирования следует ответить на ряд ключевых вопросов, они зададут направление развития:

  • интерпретация или компиляция? Код для компилирования будет преобразовываться в машинный, затем исполняться. При использовании интерпретатора код обрабатывается построчно. На вопрос нет правильного ответа, перед разработчиком стоит сложный выбор, в какую сторону уклон делать: функциональность, безопасность, скорость работы, удобство и т. д.;
  • типизация? Если да, то разработчику будет необходимо вручную устанавливать типы данных. В противном случае придётся описывать систему, которая будет определять типы;
  • в языке будет встроен автоматический алгоритм очистки мусора или управление отдать в руки пользователя?
  • планируемая модель языка программирования: структурное, ООП, функциональная логика. Кто знает, может вам удастся разработать что-то совсем иное;
  • как язык будет себя вести в отношении конкурентов, вставка из других языков планируется? Учитывать этот аспект важно при изначальной разработке языка;
  • планируется поддержка базового функционала языка или передать все функции на сторону фреймворков?
  • какой ожидается конечный вид архитектуры программы?

Последовательно отвечая на поставленные вопросы, в голове начнёт формировать облик детища, но появятся и другие вытекающие вопросы, требующие ответов.

Придумайте синтаксис для языка

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

При этом синтаксис может быть каким вы только сами захотите. К примеру, существует язык от отечественных разработчиков, который называется YoptaScript. Он имеет очень забавный синтаксис и писать программы на нем приносит лишь смех 🙂

PS: этот язык является лишь шуткой и его не стоит воспринимать как реальный язык. Посмотреть язык вы можете на их официальном сайте.

Назовите ваше детище

Вопрос с одной стороны простой, с другой – нет. Многие разработчики не выбирают глубокое и замысловатое название, отдают предпочтение простоте и лёгкости запоминания. Особенно эффективно давать имя языку с явной ассоциацией, чтобы потенциальный пользователь запоминал название после первого-второго произнесения. Сложные аббревиатуры и названия из 3 и больше слов – сложно запоминаются и быстро теряются в памяти. Имя должно быть относительно коротким и запоминающимся.

Выберите фундамент языка

Выбор языка, который будет взят за основу – это важнейший шаг. Если знаний достаточно, можно писать на ассемблере или даже машинном коде, но в современном мире лучше присмотреться к другим высокоуровневым языкам: C, C++, Swift, Pascal и другие компилируемые варианты пригодные для интерпретируемых решений. В качестве достойных языков с интерпретаторами стоит присмотреться к: Java, JavaScript, Ruby, Python – для компиляторов. Перечисленные пары снизят потерю скорости работы.

Лексер и парсер

Лексер – это инструмент для анализа лексики, деления написанного кода на отдельные элементы, их называют токены. Далее вступает в работы парсер для синтаксического анализа, его роль – организация иерархии с учётом токенов, он восстанавливает цепь событий. В качестве графического примера рассмотрим простую схему:

Пугаться не стоит, так как уже есть готовые библиотеки для быстрого формирования лексеров и парсеров. Приложения упростят выполнение сложного этапа работы.

Создание основной библиотеки

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

Создание и написание тестов

Процесс создания в большей мере сводится к оттачиванию работы, так как сформировать нормально работающий язык весьма сложно. Задача разработчика – выявить работоспособность встроенных элементов и механизмов их взаимодействия, здесь на помощь приходят тесты. По результатам тестирования делается вывод о недопустимом и разрешённом синтаксисе и сочетании функций. Возможно удастся расширить функционал языка.

Выпуск языка в свет

После завершения работы обязательно следует загрузить язык в сеть. Здесь вы найдёте единомышленников и людей, которые помогут совершенствовать детище. Публикация – это логическое завершение процесса, не стоит прятать работу в ящик, поделитесь ей.

Как мне создать язык программирования?

Как мне создать язык программирования?

Инструмент

Веб-сайт tomassetti.me изменился: теперь он является частью strumenta.com. Вы по-прежнему будете находить все новости в обычном качестве, но в новом оформлении.

    Название этой статьи отражает вопрос, который я снова и снова слышу на форумах или в электронных письмах.

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

    Итак, мы собираемся ответить на него. Да, мы увидим, каков процесс создания вашего собственного полного языка с компилятором для него, а что нет.

    Обзор

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

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

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

    Разработка языка программирования

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

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

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

    На первой фазе мы отвечаем на основные вопросы о нашем языке.

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

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

    Создание компилятора

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Резюме

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

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

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

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

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

    • 68 Ресурсы, которые помогут вам создать языки программирования
    • Полное руководство по (внешним) предметно-ориентированным языкам

     

    Категории

    Подробнее о языковом дизайне

    Как я написал свой собственный «правильный» язык программирования

    Создание компилятора Bolt: часть 1

    10 мая 2020 г.

    7 мин чтения строить.

    Что означают все этапы? Я должен изучить OCaml и C++? Подождите, я даже не слышал об OCaml…

    Не волнуйтесь. Когда я начал этот проект 6 месяцев назад, я никогда не создавал компилятор и не использовал OCaml или C++ ни в одном серьезном проекте. Я объясню все в свое время.

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

    Вот что эта серия призвана исправить. Язык Bolt, который я написал в рамках своей диссертации на третьем курсе, представляет собой параллельный объектно-ориентированный язык в стиле Java. Некоторые из основных моментов этой серии:

    • Мы реализуем объекты и классы с переопределением наследования и методов
    • Параллелизм (насколько я мог сказать, когда писал это, ни одно другое руководство по языку программирования не освещало это)
    • Обобщения : возможность писать класс типа LinkedList , а затем создание его экземпляра с помощью LinkedList , LinkedList и так далее.
    • Введение в проверку типов в компиляторе
    • Компиляция в LLVM (этот пост занял второе место в Hacker News!) — LLVM используется C, C++, Swift, Rust и многими другими языками.

    Поэтому я рекомендую вам перейти по ссылкам в обзоре «Серии», чтобы узнать об этих конкретных функциях. Оставшаяся часть этого поста будет направлена ​​на то, чтобы убедить вас, почему стоит писать собственный язык программирования, а в следующем посте будет описана структура компилятора.

    Зачем вам писать свой собственный язык программирования?

    Вопрос, который мы действительно должны задать, это зачем создавать свой собственный язык ? Возможные ответы:

    1. Это весело
    2. Иметь собственный язык программирования круто
    3. Это хороший побочный проект

    Ментальные модели

    мотивация: наличие правильных ментальных моделей . Видите ли, когда вы изучаете свой первый язык программирования, вы смотрите на программирование через призму этого языка. Перенесемся к вашему второму языку, и это кажется сложным, вам нужно заново выучить синтаксис, а этот новый язык делает вещи по-другому. Используя больше языков программирования, вы понимаете, что языки имеют общие темы. В Java и Python есть объекты, Python и JavaScript не требуют написания типов, этот список можно продолжить. Погружаясь глубже в теорию языков программирования, вы читаете о существующих языковых конструкциях — Java и Python — объектно-ориентированные языки программирования, а также Python и JavaScript имеют динамическую типизацию .

    Языки программирования, которые вы использовали, основаны на идеях, присутствующих в более старых языках, о которых вы, возможно, не слышали. Simula и Smalltalk представили концепцию объектно-ориентированных языков программирования. Лисп ввел понятие динамической типизации. И постоянно появляются новые исследовательские языки, которые вводят новые концепции. Более распространенный пример: Rust строит безопасность памяти в низкоуровневый язык системного программирования.

    Создание собственного языка (особенно если вы добавляете новые идеи) помогает вам более критически относиться к дизайну языка, поэтому, когда вы начинаете изучать новый язык, это становится намного проще. Например, я никогда не программировал на Hack до своей стажировки в Facebook прошлым летом, но знание этих концепций языка программирования значительно облегчило освоение.

    Что такое компиляторы?

    Итак, вы разработали свой модный новый язык, и он совершит революцию в мире, но есть одна проблема. Как ты его запускаешь? Это роль компилятора. Чтобы объяснить, как работают компиляторы, давайте сначала вернемся в 19 век, в эпоху телеграфа. Вот у нас есть этот причудливый новый телеграф, но как мы будем отправлять сообщения? Та же проблема, другой домен. Телеграфист должен взять речь, преобразовать ее в азбуку Морзе и выстукивать код. Первое, что делает оператор, — это осмысливает речь — он разбивает ее на слова ( лексинг ), а затем понимает, как эти слова используются в предложении ( parsing ) — являются ли они частью именной группы, придаточного предложения и т. д. Они проверяют, имеет ли смысл классификация слов по категориям или типам (прилагательное, существительное, глагол) и проверяют, имеет ли предложение грамматический смысл (мы можем не используйте «runs» для описания существительного, так как это глагол, а не существительное). Наконец, они переводят ( компилируют ) каждое слово в точки и тире (азбуку Морзе), которые затем передаются по проводам.

    Кажется, что это работает, потому что так много автоматический для людей. Компиляторы работают так же, за исключением того, что для этого нам нужно явно запрограммировать компьютеры. В приведенном выше примере описан простой компилятор, состоящий из 4 этапов: lex, parse, type-check и затем трансляция в машинные инструкции. Оператору также нужны дополнительные инструменты, чтобы набирать азбуку Морзе; для языков программирования это среда выполнения .

    На практике оператор, вероятно, создает некоторую стенографическую запись, которую он знает, как преобразовать в азбуку Морзе. Теперь вместо того, чтобы напрямую преобразовывать речь в азбуку Морзе, они преобразуют речь в свою стенографию, а затем преобразуют стенографию в азбуку Морзе. Во многих практических языках вы не можете просто перейти от исходного кода к машинному коду, у вас есть desugaring или понижение стадий, где вы поэтапно удаляете языковые конструкции (например, развертывание циклов for), пока не останется небольшой набор инструкций, которые можно выполнить. Дешугаризация значительно упрощает более поздние этапы, поскольку они оперируют более простым представлением. Этапы компиляции сгруппированы в разделы внешнего интерфейса, среднего уровня и внутреннего интерфейса, где внешний интерфейс выполняет большую часть синтаксического анализа/проверки типов, а средний и внутренний уровни упрощают и оптимизируют код.

    Варианты конструкции компилятора

    На самом деле мы можем сформулировать многие языки и дизайн компилятора в терминах приведенной выше аналогии:

    Переводит ли оператор слова на лету в азбуку Морзе по мере их передачи, или он заранее преобразует слова в азбуку Морзе , а потом передать азбукой Морзе? Интерпретируемые языка, такие как Python, делают первое, в то время как скомпилированные с опережением времени языка, такие как C (и Bolt), делают второе. Java на самом деле находится где-то посередине — она использует точно в срок , который выполняет большую часть работы заранее, переводя программы в байт-код, а затем во время выполнения компилирует байт-код в машинный код.

    Теперь рассмотрим сценарий, в котором появилась новая азбука Лорзе, являющаяся альтернативой азбуке Морзе. Если операторов учат, как преобразовать стенографию в код Лорзе, говорящему не нужно знать, как это делается, они получают это бесплатно. Точно так же человеку, говорящему на другом языке, просто нужно сказать оператору, как перевести его в стенографию, и тогда он получает перевод на лорс 9.0010 и азбука Морзе! Вот как работает LLVM . LLVM IR (промежуточное представление) действует как ступенька между программой и машинным кодом. C, C++, Rust и целый ряд других языков (включая Bolt) нацелены на LLVM IR, который затем компилирует код для различных архитектур машин.

    Статическая или динамическая типизация? В первом случае оператор либо проверяет, имеют ли слова грамматический смысл, прежде чем начать постукивать. Или они этого не делают, а затем на полпути говорят: «Да, это не имеет смысла» и останавливаются. Динамическую типизацию можно рассматривать как более быструю для экспериментов (например, Python, JS), но когда вы отправляете это сообщение, вы не знаете, остановится ли оператор на полпути (сбой).

    Я объяснил это на примере воображаемого телеграфиста, но любая аналогия работает. Создание этой интуиции имеет большое значение для понимания того, какие языковые функции подходят для вашего языка: если вы собираетесь экспериментировать, то, возможно, динамическая типизация лучше, так как вы можете двигаться быстрее. Если вы используете большую кодовую базу, ее сложнее вычитывать, и вы с большей вероятностью сделаете ошибки, поэтому вам, вероятно, следует перейти на статическую типизацию, чтобы избежать поломок.

    Типы

    Наиболее интересной частью компилятора (на мой взгляд) является проверка типов. В нашей аналогии оператор классифицировал слова как части речи (прилагательные, существительные, глаголы), а затем проверял, правильно ли они используются. Типы работают так же, мы классифицируем программные значения на основе поведения, которое мы хотели бы, чтобы они имели. Например. int для чисел, которые можно перемножать, String для потоков символов, которые можно объединять вместе. Роль средства проверки типов состоит в том, чтобы предотвратить нежелательное поведение, например объединение int s или умножение String s вместе — эти операции не имеют смысла, поэтому их нельзя допускать. С типом , проверяющим , программист аннотирует значения типами, а компилятор проверяет их правильность. При выводе типа компилятор выводит и проверяет типы. Мы называем правила, проверяющие типы суждениями о типах , и их совокупность (вместе с самими типами) образует систему типов.

    На самом деле оказывается, что можно сделать намного больше: системы типов не просто проверяют, int s или String s используются правильно. Более богатые системы типов могут доказать более сильные инварианты о программах: они завершатся, безопасно получат доступ к памяти или что они не содержат гонок данных. Например, система типов Rust гарантирует безопасность памяти и свободу от гонки данных, а также проверку традиционных типов int s и String s.

    Я создаю материалы о своем путешествии в области разработки программного обеспечения в своем информационном бюллетене!

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

    Ознакомьтесь с предыдущими выпусками!

    Адрес электронной почты

    Подписываясь, вы соглашаетесь с Условиями обслуживания и Политикой конфиденциальности Revue.

    Куда подходит Болт?

    Языки программирования до сих пор не решили проблему написания безопасного параллельного кода. Bolt, как и Rust, предотвращает гонки данных (объясняется в этом документе Rust), но использует более детальный подход к параллелизму. До того, как воины-клавишники набросятся на меня в Твиттере, я думаю, что Rust проделал блестящую работу, начав разговор об этом — хотя Bolt, скорее всего, никогда не станет мейнстримом, он демонстрирует другой подход.

    Если мы сейчас посмотрим на конвейер, то увидим, что Bolt содержит фазы лексирования, синтаксического анализа и дешугаризации/понижения. Он также содержит несколько фаз сериализации и десериализации Protobuf: они предназначены исключительно для преобразования между OCaml и C++. Он нацелен на LLVM IR, а затем мы подключаем пару библиотек времени выполнения (pthreads и libc) и, наконец, выводим наш объектный файл , двоичный файл, содержащий машинный код.

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