Создание своего языка программирования
11 шагов к созданию языка программирования
Изучите устройство компьютера
Это правило важно не только для тех, кто желает создать новый язык, но и для тех, кто просто сел за программирование. Вы не можете создать ничего стоящего, если не понимаете, как один код преобразуется в другой и исполняется компьютером. Без понимания того, как функционирует машина, вам будет очень сложно принимать решения в дальнейшем.
Изучите терминологию
В дальнейшем речь пойдёт о парсерах и лексерах, компиляторах и интерпретаторах, синтаксических деревьях и многом другом. Вам будет значительно проще черпать информацию из сети, спрашивать советов у опытных разработчиков, если у вас не будет пробелов в терминологии. Вкупе с пониманием технологий – это фундамент для постройки рабочего языка.
Определитесь с назначением языка
Сразу решите, будет ли ваш язык иметь узкую специализацию или ваша цель – универсальный инструмент, который окажется востребован в любой области IT. Оцените объём работы, которой вам предстоит проделать, поставьте цели, которых вы хотите достичь. Ну и самое главное – определитесь: вы хотите покорить мир или просто попробовать для себя что-то новое?
Очертите основные концепции
Есть ряд вопросов, на которые необходимо ответить:
Эти вопросы помогут сформировать облик будущего языка и породят ряд новых базисных вопросов, на которые тоже придётся дать ответы.
Поэкспериментируйте с синтаксисом
Наличие служебных символов может здорово упростить работу машине и увеличить производительность, но будет отталкивать потенциального пользователя. То же самое с видом функций – простые и интуитивно понятные или максимально отражающие действие.
Дайте название языку
Выберите язык для языка
Новый язык надо на чём-то написать. Суровые гики могут использовать язык ассемблера или машинные коды, но в XXI веке куда больше смысла ориентироваться на высокоуровневые языки: Pascal, C, C++, Swift (компилируемые языки) для интерпретируемого варианта; Java, JavaScript, Python, Ruby (интерпретируемые языки) — для компилируемого. Такие пары обеспечат минимальные потери в производительности.
Поработайте над лексером и парсером
Лексер – инструмент, отвечающий за лексический анализ, разбиения кода на составные единицы, называемые токенами. Парсер, отвечающий за синтаксический анализ, организует иерархию из токенов, определяя порядок их взаимодействия. Графически это можно представить следующим образом:
Не спешите хвататься за голову, для создания лексеров и парсеров есть специальные библиотеки и приложения, которые снимут эту достаточно сложную часть работы.
Создайте стандартную библиотеку
Вне зависимости от того, есть ли у языка встроенные возможности для исполнения базового функционала или требуются внешние библиотеки, вам необходимо создать ряд функций, на которых можно было продемонстрировать хотя бы в общих чертах все заложенные возможности.
Напишите уйму тестов
Создать язык – не так сложно, как заставить его нормально работать. Определить работоспособность всех механизмов языка вам помогут специальные тесты, очерчивающие границы «можно» и «нельзя» и не вызывающие при этом исключительных ситуаций, которые поставят возможности языка в тупик.
Опубликуйте язык
Когда язык будет готов – не смейте прятать его в стол. Даже если мировое господство – не ваша цель, публикация поможет собрать отзывы, найти единомышленников для дальнейших усовершенствований языка. Ну и в конце концов, этим вы сделаете личный вклад в программирование.
Мотивы создания нового языка программирования всегда очень разные: кто-то делает это от скуки, кто-то с целью преодоления существующих барьеров, кто-то для собственного удобства. Вы тоже можете попробовать создать собственный язык, руководствуясь следующими 11 пунктами. Кто знает, возможно, вы станете заметной личностью в мире программирования?
Изучите устройство компьютера
Это правило важно не только для тех, кто желает создать новый язык, но и для тех, кто просто сел за программирование. Вы не можете создать ничего стоящего, если не понимаете, как один код преобразуется в другой и исполняется компьютером. Без понимания того, как функционирует машина, вам будет очень сложно принимать решения в дальнейшем.
Изучите терминологию
В дальнейшем речь пойдёт о парсерах и лексерах, компиляторах и интерпретаторах, синтаксических деревьях и многом другом. Вам будет значительно проще черпать информацию из сети, спрашивать советов у опытных разработчиков, если у вас не будет пробелов в терминологии. Вкупе с пониманием технологий – это фундамент для постройки рабочего языка.
Определитесь с назначением языка
Сразу решите, будет ли ваш язык иметь узкую специализацию или ваша цель – универсальный инструмент, который окажется востребован в любой области IT. Оцените объём работы, которой вам предстоит проделать, поставьте цели, которых вы хотите достичь. Ну и самое главное – определитесь: вы хотите покорить мир или просто попробовать для себя что-то новое?
Очертите основные концепции
Есть ряд вопросов, на которые необходимо ответить:
Эти вопросы помогут сформировать облик будущего языка и породят ряд новых базисных вопросов, на которые тоже придётся дать ответы.
Поэкспериментируйте с синтаксисом
Наличие служебных символов может здорово упростить работу машине и увеличить производительность, но будет отталкивать потенциального пользователя. То же самое с видом функций – простые и интуитивно понятные или максимально отражающие действие.
Дайте название языку
Выберите язык для языка
Новый язык надо на чём-то написать. Суровые гики могут использовать язык ассемблера или машинные коды, но в XXI веке куда больше смысла ориентироваться на высокоуровневые языки: Pascal, C, C++, Swift (компилируемые языки) для интерпретируемого варианта; Java, JavaScript, Python, Ruby (интерпретируемые языки) — для компилируемого. Такие пары обеспечат минимальные потери в производительности.
Поработайте над лексером и парсером
Лексер – инструмент, отвечающий за лексический анализ, разбиения кода на составные единицы, называемые токенами. Парсер, отвечающий за синтаксический анализ, организует иерархию из токенов, определяя порядок их взаимодействия. Графически это можно представить следующим образом:
Не спешите хвататься за голову, для создания лексеров и парсеров есть специальные библиотеки и приложения, которые снимут эту достаточно сложную часть работы.
Создайте стандартную библиотеку
Вне зависимости от того, есть ли у языка встроенные возможности для исполнения базового функционала или требуются внешние библиотеки, вам необходимо создать ряд функций, на которых можно было продемонстрировать хотя бы в общих чертах все заложенные возможности.
Напишите уйму тестов
Создать язык – не так сложно, как заставить его нормально работать. Определить работоспособность всех механизмов языка вам помогут специальные тесты, очерчивающие границы «можно» и «нельзя» и не вызывающие при этом исключительных ситуаций, которые поставят возможности языка в тупик.
Опубликуйте язык
Когда язык будет готов – не смейте прятать его в стол. Даже если мировое господство – не ваша цель, публикация поможет собрать отзывы, найти единомышленников для дальнейших усовершенствований языка. Ну и в конце концов, этим вы сделаете личный вклад в программирование.
Как создать свой язык программирования? Теория и практика
Создание языка программирования очень сложная задача, но выполнимая. Мы расскажем основные факторы при создании своего первого языка.
Желание создать что-то своё и оставить след в истории посещает всех людей, в том числе программистов. Создание собственного языка программирования – это подходящая возможность. Мотивы бывают различные: от нечего делать, решения вечных проблем в других языках, разработка комфортного варианта для себя.
Создание языков – это посильная задача, которую может выполнить читатель, руководствуясь пошаговым алгоритмом из 12 этапов. Вероятно, что именно за вашим языком будущее.
Статья содержит специфические термины, без понимания которых не обойтись: лексеры, парсеры, интерпретаторы, компиляторы, деревья синтаксиса и остальное. Значительно проще вникнуть в суть, черпая информацию из интернета или при совместном изучении данных с опытным программистом. Базовые понятия терминологии – это фундаментальная величина для создания своего языка программирования.
Изучение компьютера
Это совет и необходимость одновременно, знание операционных систем поможет работать, изучать новые языки, стать на путь программиста. Невозможно создать быстрый, качественный и многофункциональный язык без понимания способа преобразования кода и его обработки. Познание функционирования системной машины – это обязательный этап дальнейшего развития.
Зачем вам новый язык программирования?
Заранее определитесь с предназначением языка. Существует 2 основных направления – универсальный инструмент или узкоспециализированное решение.
Востребованными оказываются языки с обоих направлений, но более популярны универсальные. Учтите, что многофункциональные языки требуют больше времени и усилий, а также у них множество конкурентов.
Сейчас стоит оценить количество предстоящей работы, установить цели и желания. Немаловажно определиться, вы планируете стать знаменитостью, а язык должен стать одним из лучших, или вам достаточно интересно провести время и расширить мировоззрение?
Каких концепций будет придерживаться новый язык?
На этапе планирования следует ответить на ряд ключевых вопросов, они зададут направление развития:
Последовательно отвечая на поставленные вопросы, в голове начнёт формировать облик детища, но появятся и другие вытекающие вопросы, требующие ответов.
Придумайте синтаксис для языка
При использовании особых символов можно существенно упростить работу компьютеру и повысить производительность кода. Недостатком является отталкивание нового пользователя. Подобный выбор стоит в отношении функций: либо элементарные и понятные, либо максимально информативные.
Назовите ваше детище
Вопрос с одной стороны простой, с другой – нет. Многие разработчики не выбирают глубокое и замысловатое название, отдают предпочтение простоте и лёгкости запоминания. Особенно эффективно давать имя языку с явной ассоциацией, чтобы потенциальный пользователь запоминал название после первого-второго произнесения. Сложные аббревиатуры и названия из 3 и больше слов – сложно запоминаются и быстро теряются в памяти. Имя должно быть относительно коротким и запоминающимся.
Выберите фундамент языка
Лексер и парсер
Лексер – это инструмент для анализа лексики, деления написанного кода на отдельные элементы, их называют токены. Далее вступает в работы парсер для синтаксического анализа, его роль – организация иерархии с учётом токенов, он восстанавливает цепь событий. В качестве графического примера рассмотрим простую схему:
Пугаться не стоит, так как уже есть готовые библиотеки для быстрого формирования лексеров и парсеров. Приложения упростят выполнение сложного этапа работы.
Создание основной библиотеки
Независимо от наличия встроенных возможностей в языке для работы с элементарным функционалом или максимального использования внешних библиотек, потребуется создание функций. Они помогут продемонстрировать возможности хотя бы очень базисно.
Создание и написание тестов
Процесс создания в большей мере сводится к оттачиванию работы, так как сформировать нормально работающий язык весьма сложно. Задача разработчика – выявить работоспособность встроенных элементов и механизмов их взаимодействия, здесь на помощь приходят тесты. По результатам тестирования делается вывод о недопустимом и разрешённом синтаксисе и сочетании функций. Возможно удастся расширить функционал языка.
Выпуск языка в свет
После завершения работы обязательно следует загрузить язык в сеть. Здесь вы найдёте единомышленников и людей, которые помогут совершенствовать детище. Публикация – это логическое завершение процесса, не стоит прятать работу в ящик, поделитесь ей.
Создаем свой язык программирования с блэкджеком и компилятором
В этом пособии с соответствующими примерами кода рассказываем о том, как написать при помощи Python свой язык программирования и компилятор к нему.
Введение
Изучение компиляторов и устройства языков программирования по видеоурокам и руководствам – дело для новичков тяжелое. В этих материалах нередко отсутствуют важные составляющие. Цель публикации – помочь людям, ищущим способ создать свой язык программирования и компилятор. Пример игрушечный, но позволит понять, с чего начать и в каком направлении двигаться.
Системные требования
Если вы незнакомы с нижеприведенными понятиями, не беспокойтесь – мы проясним необходимость этих компонентов далее, по ходу создания компилятора. В качестве лексера и парсера используется PLY. В роли низкоуровневого языка-посредника для генерации оптимизированного кода выступает LLVMlite.
Таким образом, к системе предъявляются следующие требования:
Свой язык программирования: с чего начать?
Начнем с того, что назовем свой язык программирования. В качестве примера он будет называться TOY. Пусть пример программы на языке TOY выглядит следующим образом:
Любой язык программирования включает множество составляющих его компонентов. Чтобы не застрять в мелочах, возьмем в качестве микропрограммы вызов одной функции нашего языка:
Как для этой однострочной программы формально описать грамматику языка? Чтобы это сделать, необходимо использовать расширенную Бэкус – Наурову форму (РБНФ) (англ. Extended Backus–Naur Form ( EBNF )). Это формальная система определения синтаксиса языка. Воплощается она при помощи метаязыка, определяющего в одном документе всевозможные грамматические конструкции. Чтобы в деталях разобраться с тем, как работает РБНФ, прочтите эту публикацию.
Создаем РБНФ (EBNF)
Создадим РБНФ, которая опишет минимальный функционал нашей микропрограммы. Начнем с операции суммирования:
Соответствующая РБНФ будет выглядеть следующим образом:
Дополним язык операцией вычитания:
В соответствующем РБНФ изменится первая строка:
Наконец, опишем функцию print:
В этом случае в РБНФ появится новая строка, описывающая работу с выражениями:
В таком ключе развивается описание грамматики языка. Как же научить компьютер транслироваться эту грамматику в бинарный исполняемый код? Для этого нужен компилятор.
Компилятор
Компилятор – это программа, переводящая текст ЯП на машинный или другие языки. Программы на TOY в этом руководстве будут компилироваться в промежуточное представление LLVM IR (IR – сокращение от Intermediate Representation) и затем в машинный язык.
Использование LLVM позволяет оптимизировать процесс компиляции без изучения самого процесса оптимизации. У LLVM действительно хорошая библиотека для работы с компиляторами.
Наш компилятор можно разделить на три составляющие:
Для лексического анализатора и парсера мы будем использовать RPLY, очень близкий к PLY. Это библиотека Python с теми же лексическими и парсинговыми инструментами, но с более качественным API. Для генератора кода будем использовать LLVMLite – библиотеку Python для связывания компонентов LLVM.
1. Лексический анализатор
Итак, первый компонент компилятора – лексический анализатор. Роль этого компонента заключается в том, чтобы разделять текст программы на токены.
Воспользуемся последней структурой из примера для РБНФ и найдем токены. Рассмотрим команду:
Наш лексический анализатор должен разделить эту строку на следующий список токенов:
Напишем код компилятора. Для начала создадим файл lexer.py, в программном коде которого будут определяться токены. Для создания лексического анализатора воспользуемся классом LexerGenerator библиотеки RPLY.
Создадим основной файл программы main.py. В этом файле мы впоследствии объединим функционал трех компонентов компилятора. Для начала импортируем созданный нами класс Lexer и определим токены для нашей однострочной программы:
При запуске main.py мы увидим на выходе вышеописанные токены. Вы можете изменить названия своих токенов, но для простоты согласования с функциями парсера их лучше оставить как есть.
2. Синтаксический анализатор
Второй компонент компилятора – синтаксический анализатор (он же парсер). Его роль – синтаксический анализ текста программы. Данный компонент принимает список токенов на входе и создает на выходе абстрактное синтаксическое дерево (АСД). Эта концепция более трудна, чем идея списка токенов, поэтому мы настоятельно рекомендуем хотя бы по приведенным выше ссылкам изучить принципы работы парсеров и синтаксических деревьев.
Чтобы воплотить в жизнь синтаксический анализатор, будем использовать структуру, созданную на этапе РБНФ. К счастью, анализатор RPLY использует формат, схожий с РБНФ. Самое сложное – присоединить синтаксический анализатор к АСД, но когда вы поймете идею, это действие станет действительно механическим.
Во-первых, создадим файл ast.py. Он будет содержать все классы, которые могут быть вызваны парсером, и создавать АСД.
Во-вторых, нам необходимо создать сам анализатор. Для этого в новом файле parser.py аналогично лексеру используем класс ParserGenerator из библиотеки RPLY:
Наконец, обновляем файл main.py, чтобы объединить возможности синтаксического и лексического анализаторов:
Теперь при запуске main.py мы получим значение 6. и оно действительно соответствует нашей однострочной программе «print(4 + 4 – 2);».
Таким образом, при помощи двух этих компонентов мы создали работающий компилятор, интерпретирующий язык TOY. Однако компилятор по-прежнему не создает исполняемый машинный код и не оптимизирован. Для этого мы перейдем к самой сложной части руководства – генерации кода с помощью LLVM.
3. Генератор кода
Третья и последняя часть компилятора – это генератор кода. Его роль заключается в том, чтобы преобразовывать АСД в машинный код или промежуточное представление. В нашем случае будет происходить преобразование АСД в промежуточное представление LLVM (LLVM IR).
LLVM может оказаться действительно сложным для понимания инструментом, поэтому обратите внимание на документацию LLVMlite. LLVMlite не имеет реализации для функции печати, поэтому вы должны определить собственную функцию.
Чтобы начать, создадим файл codegen.py, содержащий класс CodeGen. Этот класс отвечает за настройку LLVM и создание/сохранение IR-кода. В нем мы также объявим функцию печати.
Теперь обновим основной файл main.py, чтобы вызывать методы CodeGen:
Как вы можете видеть, для того, чтобы обработка происходила классическим образом, входная программа была вынесена в отдельный файл input.toy. Это уже больше похоже на классический компилятор. Файл input.toy содержит все ту же однострочную программу:
Еще одно изменение – передача парсеру методов module, builder и printf. Это сделано, чтобы мы могли отправить эти объекты АСД. Таким образом, для получения объектов и передачи их АСД необходимо изменить parser.py:
Наконец, самое важное. Мы должны изменить файл ast.py, чтобы получать эти объекты и создавать LLMV АСД, используя методы из библиотеки LLVMlite:
После изменений компилятор готов к преобразованию программы на языке TOY в файл промежуточного представления LLVM output.ll. Далее используем LLC для создания файла объекта output.o и GCC для получения конечного исполняемого файла:
Теперь вы можете запустить исполняем файл, для создания которого вами использовался свой язык программирования:
Следующие шаги
Мы надеемся, что после прохождения этого руководства вы разобрались в общих чертах в концепции РБНФ и трех основных составляющих компилятора. Благодаря этим знаниям вы можете создать свой язык программирования и написать оптимизированный компилятор при помощи Python. Мы призываем вас не останавливаться на достигнутом и добавить в свой язык и компилятор другие важные составляющие:
Итоговый программный код вы также найдете на GitHub.
Другие материалы по теме языков программирования
Как написать собственный язык программирования
Перевод статьи «Write you a programming language».
Что это означает — написать язык? Это значит, что вам нужно написать программу, которая будет интерпретировать или компилировать язык программирования (т. е. новый язык). Но для написания этой программы вам придется использовать какой-нибудь из существующих языков (базовый язык). Также можно написать эту программу на чистом машинном коде.
Интерпретатор или компилятор?
Для начала нужно выбрать, что вы хотите написать: интерпретатор или компилятор (или оба). Какую роль они играют?
Любопытно, что вы можете написать для своего языка программирования и интерпретатор, и компилятор. Примеры — Lisp (CommonLisp), Scheme (Chez Scheme).
Также можно написать интерпретатор или компилятор для языка программирования на этом же языке. Для этого вам придется сначала написать первый интерпретатор или компилятор на другом языке, а затем, в новых версиях, вы сможете пользоваться уже новым языком (т. н. раскрутка).
Руководства по теме создания интерпретаторов и компиляторов можно разбить на категории в соответствии с «базовыми» и «новыми» языками, например:
Если язык реализован как компилятор, он переводит исходный язык на язык назначения. Исходный язык — то же самое, что и «новый», но язык назначения — не то же, что «базовый» язык. Руководства можно разбить по категориям в соответствии с исходным языком и языком назначения. Например:
Управление памятью
Следующее, что нужно определить, это как ваш язык программирования будет управлять памятью:
Система типов
Далее надо разобраться, как в вашем языке будет обстоять дело с типами:
Можно выбрать и кое-что позаковыристее. Например, типизацию Мартина-Лёфа (ML), зависимые типы (Idris), линейные типы и т. п.
Парадигмы
Все, указанное выше, касалось всех языков программирования. На следующем этапе вы можете выбрать другие особенности (одну или больше), которые определят парадигму вашего языка.
Например, вы допускаете использование функций первого класса, строгие вычисления (с вызовом по соиспользованию), динамические типы, макросы — и получаете Lisp.
Или вы можете разрешить использование функций первого класса, ленивые вычисления, статические типы — и получить на выходе ML.
Дополнительные особенности
Поверх всего этого вы можете добавить дополнительные особенности, например:
Генераторы парсеров
Одним из факторов успеха MAL является то, что ему не нужен генератор парсера: парсер Lisp относительно легко имплементировать. Это делает его очень портируемым (реализован более чем на 80 языках). То же касается lis.py — у него есть еще более простой токенизатор.
Большинство руководств, не связанных с Lisp, предполагают необходимость какого-то специфичного генератора парсеров, что делает их менее портируемыми.
Список туториалов
Я собрал собственную коллекцию руководств по созданию языков программирования. Они находятся в репозитории на GitHub, так что вы вполне можете стать контрибьютором.