![](https://habrastorage.org/webt/ay/hr/hf/ayhrhfalcz4lcjnfbjbxd8vrsxc.jpeg)
Каждый разработчик ПО использует язык программирования, и обычно даже несколько. Лишь немногие из нас создают языки программирования. Это логично, ведь работу, которую мы выполняем, чаще всего можно сделать при помощи уже существующих языков. И совершенствованием этих языков уже занимаются другие люди, а мы можем сосредоточиться на нашей непосредственной задаче.
Но из-за этого мы теряем возможность научиться чему-то новому. Я нашла это новое, когда создала язык, руководствуясь глупым принципом: поток управления должен осуществляться через исключения и ничего больше. Я создала его как шутку, но неожиданно для себя в процессе разработки получила новые знания.
В том, чтобы делать собственные инструменты, есть нечто особенное
Каждый серьёзный столяр создаёт часть своих инструментов самостоятельно. Кто-то изготавливает под себя верстак, или козлы, или зажимные приспособления под множество инструментов и условий работы. Всё это столяр может создать из дерева. Но у него не всегда есть доступ к станкам для изготовления всех используемых им инструментов: для производства деталей стамесок и рубанков, не говоря уже об электроинструменте.
У нас, программистов, ситуация иная. Мы имеем почти полный контроль над машиной и, теоретически, способны создать всё с нуля. Так как мы работаем с программными инструментами и сами пишем ПО, то можем создавать инструменты для себя, начиная с операционной системы и выше.
Это привилегия, недоступная для большинства областей деятельности.
▍ Чему можно научиться, создав язык
Один из инструментов, с которым мы взаимодействуем больше всего — это язык программирования. Мы используем его для выполнения задачи, и он формирует наш подход к её решению. Язык программирования становится нашим инструментом рассуждений, даже когда мы не сидим за клавиатурой; именно это и делает его подходящим для обучения. Создавая новый язык программирования, вы можете научиться многому.
Вы узнаете о грамматике и архитектуре языка.
Перед тем, как приступать к реализации языка программирования, вам необходимо решить, каким он вообще должен быть. Будет ли он императивным, функциональным или каким-то ещё? Он объектно-ориентированный? Он имеет традиционный синтаксис, позаимствованный у другого языка, или вы будете создавать что-то новое и причудливое? Это лишь некоторые из вопросов, на которые вам предстоит ответить при проектировании языка.
В процессе работы вы узнаете, почему другие языки проектировались именно таким образом. Возможно, вы даже поймёте это в процессе первоначального проектирования. Например, при работе над моим следующим языком программирования Lilac я узнала, почему так часто используется точка с запятой, потому что попробовала выбрать какой-то другой символ. Обсуждая это с моим другом, мы обнаружили множество потенциальных недостатков у всех других вариантов! Если же вам не повезёт, то вы узнаете всё это на этапе реализации, и подобные уроки уже никогда не забудете.
Вы узнаете о парсинге.
Это один из первых аспектов, с которым вы столкнётесь в начале реализации своего языка. Без парсинга языка невозможно сделать практически ничего другого. Перед тем, как приступать к написанию парсера, вам нужно выбрать его тип. Если вы новичок, то пока не стоит слишком вдаваться в подробности. Но если вам действительно интересны парсеры, то вы найдёте в них замечательную тему для глубокого исследования.
Вы узнаете о среде исполнения.
Для запуска кода вам нужно будет написать среду исполнения (или компилятор), то есть придётся серьёзно поразмыслить о том, как будет работать код.
Как на самом деле работает выбрасывание исключений?
Как узнать, в каком месте памяти искать переменную, когда на неё ссылаются?
При выполнении рекурсивной функции, насколько глубокой может быть рекурсия? И почему?
Это лишь некоторые из вопросов, на которые вам предстоит ответить. И список вопросов будет очень длинным.
Вы можете подстроить свой язык под то, чему хотите научиться. Мой первый язык, Hurl, научил меня основам создания интерпретатора, проектирования языка и написания грамматики. Мой второй язык, Lilac, позволит мне больше узнать о системах типов, средах исполнения и инструментарии управления.
В процессе создания языка вы будете глубже понимать другие языки. Когда я создавала Hurl и у меня возникали ошибки парсинга, язык выдавал мне только сырые имена токенов. Это напомнило мне ошибки, которые я иногда наблюдала в своей интеграции Neovim с Rust LSP, поэтому я начала работать над тем, чтобы эти ошибки было проще понимать. Каждое решение, принимаемое при проектировании и реализации языка, заставит вас углубить понимание используемых языков, повысив ваше качество как их пользователя.
▍ Язык будет плохим, но это нормально
При написании собственного языка для обучения здорово то, что с большой вероятностью он окажется плохим. Да, разумеется, возможно создавать новые хорошие языки, и это замечательно! Но по моему опыту, лучше отделять обучение чему-то от реализации этого с максимальным качеством.
Когда вы приступаете к делу, зная, что язык будет плохим, это даёт вам свободу! «Плохой» не означает, что он будет бесполезным для вас. По большей мере это означает нехватку совершенства и отточенности, свойственных «реальным» языкам, а также изъяны, препятствующие широкому распространению вашего проекта. Но вы можете разработать что-то, решающее ваши собственные задачи, позволяющее успешно участвовать в Advent of Code или добиться почёта среди друзей-ботанов. Всё это пойдёт вам на пользу.
Вы всё равно не создадите новый Python, так что можете сосредоточиться на том, что вам интересно и полезно для обучения. Можно избавиться от всего скучного, но необходимого для применения в реальном мире. При обучении можно выбрать чёткую цель и сделать процесс увлекательным, так вы повысите вероятность завершения проекта. Вполне допускается что-то намеренно ломать или принимать смешные архитектурные решения, которые просто вас забавляют. Язык всё равно будет плохим, так почему бы не расслабиться?
▍ Как приступить к созданию языка
Необходимость сесть за пустой экран редактора и приступить к «написанию нового языка» может быть пугающей. Долгое время я считала (даже в должности Principal Software Engineer), что в этом есть какая-то тёмная магия, недоступная моему пониманию. Это большой объём работы, но справиться с ним может любой из программистов. С каждым годом это становится всё проще, потому что появляется много новых ресурсов для обучения новичков.
Первым делом я бы порекомендовала воссоздать по руководствам чей-то чужой язык. Лично я для этого воспользовалась потрясающей книгой Crafting Interpreters. Хорошие отзывы я слышала об Writing An Interpreter In Go и Build Your Own Lisp. Все эти ресурсы позволят вам под руководством опытного наставника получить представление о том, как работают языки.
Однако стоит отметить, что мне показалось хорошей идеей выбрать реализацию не того языка, который использован в книге. В Crafting Interpreters рассказывается о Java и C, поэтому я использовала Rust. Выбрав другой язык, вы будете вынуждены глубже осваивать концепции, чтобы перенести их на иную платформу. Вы не сможете бездумно перепечатывать код, вам придётся изучить его глубже.
После этого вы уже можете сами выбирать путь своего развития. Я начала разрабатывать Hurl, просто экспериментируя и выбирая то, что понравится. Такой подход сработал и позволил мне подкрепить знания, полученные из Crafting Interpreters. Готовясь к созданию Lilac, я прочитала пока одну книгу и составила короткий список других для прочтения. Когда я спросила у друзей рекомендации, то они посоветовали мне следующие книги:
- Introduction to Compilers and Language Design, которую я прочитала и получила от неё большое удовольствие.
- Engineering a Compiler.
- Programming Languages: Application and Interpretation.
- Compilers: Principles, Techniques, and Tools, она же «книга с драконом».
Выбор чтения зависит от того, что вы хотите сделать дальше и чему хотите научиться.
▍ Вперёд, создавайте что-нибудь интересное
Я думаю, всем нам стоит создавать свои языки. Это отличный способ обучения и придумывания новых идей. В конце концов, это превосходная возможность экспериментировать и развлекаться с компьютером.
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
![](https://habrastorage.org/webt/tp/sk/z-/tpskz-7w7a8e74isoowuep40xeg.png)
Комментарии (31)
riky
14.08.2024 17:18+5На самом деле хорошая мысль, тоже время от времени пишу в стол свои небольшие ЯП, интересно продумывать их архитектуру.
xenon
14.08.2024 17:18+3Я писал свой ЯП :-) И он был ужасен, да. Причем, во многом - намеренно ужасен. Для системы мониторинга okerr был создан язык Sequence. Идея в чем - программный код для проверок (например, запущен ли systemd сервис, сколько места на диске, есть ли бэкапы с таким-то именем за последние сутки, какой load average) должны подгружаться с центрального сервера, а он - не доверенный. ( например, вы доверяете apt/dnf установить пакет, когда вы это захотели, но не доверяете же ему самому устанавливать что он хочет и когда хочет, так же и агент окерр на системе должен быть заведомо безопасен). С одной стороны, подгрузка кодов с сервера - хорошо (можно прямо на сервере из удобного UI нарулить что хочешь, скажем, перенастроить проверки для дюжины серверов и сервера начнут репортить новые метрики).
Однако, пользователь okerr не хочет же, чтобы код с чужого сервера мониторинга мог писать файлы на своем сервере? Не хочет даже чтобы он мог читать лишние файлы. Более того, не хочет чтобы проверка могла войти в вечный цикл!
Вот и пришлось написать этот уникальный язык, на котором нельзя сделать вечный цикл. Читать файлы можно (но интерпретатор должен заранее получить в аргументах список разрешенных для чтения файлов, прочитать другой файл, не из этого списка не получится). С одной стороны, язык получился в какой-то мере функциональным (им можно узнать размер свободного места на каждом подмонтированном диске, например), с другой - он получился mostly harmless, мне сложно даже вообразить, чтобы на нем можно было написать какую-то "вредную" программу. Заметьте - там не было циклов, никаких там for/while! Но при этом он мог (к примеру) выдать метрики по свободному месту для произвольного числа подмонтированных дисков (там была итерация по конечному списку).
Можно сказать, что каждая программа на sequence исполнялась за заранее определенное количество шагов (правда какие-то шаги могли исполнится много раз, как бы параллельно, но каждая из этих параллельных веток обрабатывалась последовательно). Представьте код как дерево из нескольких уровней. Количество уровней равно количеству строк в скрипте, а ветвлений между уровнями либо нет вообще, либо конечное число.
В общем, со временем пришлось признать этот опыт неудачным. Язык получился некрасивый, писать на нем тяжело и неприятно. Хотя со своей задачей он справлялся, но не столько ценно это оказалось. Может быть для управления сотнями серверов это было бы оправдано, но в таких случаях обычно есть всякие ansible.Так что теперь в okerr используется очень простая схема - каждый скрипт проверки пишет на STDOUT свои данные, скрипты можно делать хоть на bash, но все скрипты локальные. Пример (провека la для Load Average):
# okerrmod --dump la
NAME: mx:la
TAGS: la
METHOD: numerical|maxlim=2
STATUS: 0.12Все стало ну очень просто и легко и понятно. Писать проверки - тоже одно удовольствие и быстро делается.
Но чтобы активировать новую проверку - надо все-таки выполнить несколько команд от рута. Сделать это с центрального сервера - теперь нельзя. Зато вся система получилась очень простой, понятной и легко расширяемой после того как выбросили из нее эту не слишком-то нужную фичу, ради которой разрабатывали аж целый собственный язык.
akakoychenko
14.08.2024 17:18+1Кажется, для подобных штук просто DSL разрабатывают, либо, вообще, задают конфигурацию в JSON/YAML. Зачем там именно язык?
xenon
14.08.2024 17:18Конфигурация подразумевала бы что у нас все коды проверок заранее написаны, а через конфиг мы их просто активируем и передаем параметры. Но таким образом нельзя было бы написать новую проверку, с какой-то своей логикой. А вот к DSL его вполне можно отнести (в вики-статье по DSL даже Perl отнесен к этой категории).
Задумка была в том чтобы получить "безопасный пайтон (perl/shell/php)". Вам нужно написать, допустим, свой собственный код проверки, специфичный для вашего сервера/приложения, вы его пишете, загружаете на сервер мониторинга. А клиентский код на ваших серверах (которые вы мониторите) обращаются к [недоверенному] серверу мониторинга, выкачивают код проверок и запускают у себя.
Это все можно было бы сделать на том же пайтоне, но в этом случае требуется большое доверие к серверу (сервису) мониторинга. Что если там через уязвимость другой клиент взломает ваш аккаунт, поломает эти ваши скрипты и вставит в них "rm -rf / "? Что если сами админы мониторинга так вот похулиганят? Это риск, а я сам параноик, и понимаю, когда клиенты такие же. Поэтому вот и захотелось язык для этого. Но во многом это было не каким-то "бизнес-требованием" а именно собственным смелым интересным авантюрным экспериментом.
Вот пара скриптов на sequence для понимания степени ужасности:
SET DIR=/var/lib/okerr/backups SET RE=.gz METHOD numerical minlim=10240 TAG backup DIR path=$DIR maxdepth=2 FILTER type=="REG" GREP field=basename re=$RE FILTER mage<86400 REPLACE field=basename search="\d{8}" replace=DATE dest=tplname GROUP key=tplname max=ctime FORK NAME $_name:backup:$tplname DETAILS $basename: $size bytes FORMAT $size
NAME $_name:df METHOD numerical maxlim=80 TAG df DF FILTER not ('/media/' in path or '/var/lib/docker' in path) FORK EXPORT path=path NAME $_name-$path DETAILS $percent% ($usedg/${totalg}G used, $freeg left) FORMAT $percent
akakoychenko
14.08.2024 17:18безопасный пайтон (perl/shell/php)
но ведь задача ж явно решенная. Если загуглить python/php online, то там десятки реально работающих сайтов... Плюс всевозможные литкоды, где надо решать задачки, причем, можно самому выбрать язык. Ну не верю, что все они используют что-то монструозное вроде виртуальных машин со сбрасываемым состоянием после каждого запроса. И, не верю, что оно там все из говна и палок, ибо сайты эти все живут годами, и ничего. Интернет то среда агрессивная, давно б уже все заэксплуатировали
xenon
14.08.2024 17:18+1Не знаю, как они сделали. хотел бы знать. но тут дело еще в том, что для мониторинга нужна некоторая условная безопасность. То есть, скрипт должен иметь возможность узнать что-то о машине, на которой он выполняется (и это в контексте задачи - допустимо и даже требуется). А полная изоляция (которая может быть если там какие-нибудь там linux containers) - не подходит.
mayorovp
14.08.2024 17:18Ну не верю, что все они используют что-то монструозное вроде виртуальных машин со сбрасываемым состоянием после каждого запроса
На самом деле они используют не монструозную виртуальную машину со сбрасываемым состоянием после каждого запроса. Зачастую эта виртуалка запускается прямо в браузере.
Daddy_Cool
14.08.2024 17:18+2Ха! А мы создали свой язык (интерпретируемый) для управления координатником. Я всё собираюсь написать статью про это. Вещь примитивная, но отчаянно полезная.
Refridgerator
14.08.2024 17:18+3Я придумал уже 3 ЯП, один из которых применяется в металлургическом производстве. Второй - для описания музыки текстом. Третий ориентирован на математические вычисления с расширенным набором операторов. И ни один из них не кажется мне ужасным)
Daddy_Cool
14.08.2024 17:18+1О! А можно поподробнее, причем про все три? Очень интересно! Или уже есть в ваших публикациях?
Про описание музыки текстом - по идее есть Lilypond (и можно придумать разные варианты), но традиционный нотный текст двумерен, а обычный текст - одномерен - по сути всё есть строка, и это кажется главная причина почему Lilypond не очень-то востребован (может кроме как промежуточный формат, типа midi).
Refridgerator
14.08.2024 17:18+1Статьи на эту темы вряд ли будут в обозримом будущем, но в комментариях неоднократно упоминал. Для музыки - нужен для моего ДСП-процессора и хочется попробовать неравномерные темперации (это когда в частности до-диез не равен ре-бемолю). Для математики - стало не хватать Вольфрама и хочется свести количество скобочек к минимуму, что достигается вводом дополнительных операторов умножения и деления с другими приоритетами. Например
sin x pi + cos x pi
. Илиa+b//c+d
вместо(a+b)/(c+d)
. Параллельное присваиваниеa,b=a+b,a-b
. Определение (и приведение) типа справа, как в паскалеx=1:quaternion
.По работе - надстройка над SQL для описания логики обмена данными (в обе стороны) между контроллерами Simatic и СУБД.
ncix
14.08.2024 17:18Про язык для музыки - очень интересно! А как вы его применяете? Можно пример кода?
Мне как программисту и музыканту это крайне интересно.
GospodinKolhoznik
14.08.2024 17:18Почему 3 новых языка, а не 3 бибилотеки или фреймворка для уже существующих языков?Всё равно ваши языки никогда не сравняться по возможностям и удобству использования с существующими развитыми языками.
Refridgerator
14.08.2024 17:18Так ЯП - это же не обязательно самодостаточный компилятор, а в первую очередь синтаксис и грамматика. Я читал Страуструпа и знаю, что си с классами компилировался в си, и пошёл тем же путём, только в c#, в котором описывается вся необходимая инфраструктура. Когда-нибудь потом и c++ подключу.
GospodinKolhoznik
14.08.2024 17:18Я про то, что пользователям всегда будет мало возможностей вашего языка. И либо им придётся смириться с его ограниченностью, либо же вы навечно погрязнете в доработки своего языка, чтобы удовлетворить их бесконечные новые хотелки.
Refridgerator
14.08.2024 17:18+1Я пока что единственный их пользователь, сам с собой как-нибудь найду компромисс.
kristina_ponomareva
Чтобы выучить все языки программирования - нужно написать свой, такое мне нравится! А если так подумать, бОльшее количество языков программирования помогло бы реализовать многие идеи, которые пока что находятся лишь в голове стартаперов. Отличная статья и отличная идея, точно применю её на практике :)