Здесь онлайн интерпретатор, здесь документация. В сентябре 2020 года я учился на 2 курсе. В том же месяце я впервые написал программу, которая мне понравилась. Она создаёт svg изображения растений, здесь её можно потрогать. Чуть позже я выяснил, что такие программы называют процедурными генераторами. Я увлекся этим, сделал ещё парочку (1, 2). Вот только на них я потратил куда больше времени. Долго работать над одной вещью мне не понравилось, особенно когда идея реализации уже придумана, и остаётся лишь написать код. Следовательно, нужно ускорить создание процедурных генераторов. Пришло в голову создать систему для создания генераторов, a.k.a. "генератор генераторов". В этом решении было две проблемы. Первая: уже оглядываясь назад, мне понятно, что дело было не в том, что языки неудобные, а в том, что я каждый раз менял инструменты. Из-за этого уходило время на их изучение. Вторая: я поставил очень широкую задачу, для которой невозможно создать DSL-язык. Нужно было остановиться на генераторах SVG-изображений, для начала. Также на языке сделана библиотека, чтобы классы преобразовывать в SVG-картинки (библиотека геометрии с ней плотно связана, получается tight coupling). Это динамический язык, его синтаксис схож с питоном и котлином. Особенность — поля у инстансов создаются динамически, рекурсивно. То есть можно объявить класс так: При создании получится экземпляр T, у которого a = 4, b = 3. Я думал, что такая система будет удобной для генераторов, потому что можно отдельно объявить какие-то компоненты как классы и связать их воедино сразу же внутри объявлений классов. Динамическое создание полей позволяет обращаться к ещё не созданным полям из родительских классов и менять их. Более сложный пример: Получится такое дерево: Как это сделано? В общем приближении, когда вызывается конструктор из функции, выполняется примерно следующий алгоритм: Вызываем bfs от корня и ищем поля, которые ещё не проинициализированы (bfs идет по полям типа класс1), Если полей не нашли, экземпляр класса создали. Пока поля находим (пусть нашли поле a): Создаем стек инициализации, добавляем найденное поле a, Для всех полей в стеке: Смотрим, есть ли справа от = не проинициализированные поля. Если есть, ставим эти поля в стек. Если a = b + 1, а b ещё неизвестно, то ставим b в стек, стек будет . Если не созданных полей справа от = нет, убираем последний элемент из стека, инициализируем его. Я не сразу решил делать язык. Хотелось сделать более простую систему, которой могли бы пользоваться не только программисты. Система должна быть атомарной (не должно быть лишнего) и полной (можно создать много генераторов, формально задать "любой генератор" я не могу). Вот к какой системе я пришел2: Есть два типа объектов: геометрические и контейнерные. Геометрические — это графические SVG-элементы: прямоугольник, эллипс, круг, отрезок, path и т.д. Контейнерные объекты содержат в себе геометрические и контейнерные. Интересно, что все контейнеры можно реализовать в языке. Есть три типа контейнерных объектов: Простой контейнер. Представляет из себя набор объектов. По сути просто массив, или <group> в SVG. Случайный контейнер. Выбирает случайный элемент внутри себя и всегда возвращает его. Это нужно для создания рандома. Рекурсивный контейнер. Он в каком-то смысле ключевой и повторяет идею языка. Нужно передать число numberOfElements в конструктор для создания такого числа вложенных рекурсивных контейнеров. В этой системе ещё можно менять параметры объектов, длину и ширину прямоугольника, радиус круга и т.д. Но хотелось добавить рандом и в эти параметры, чтобы они выглядели как формулы: rect.width = if(parent is Circle) randInt(10, 20) else 5. В этот момент я решил, что, пожалуй, нужно сразу делать язык, а не систему с контейнерами и формулами. Как я показал абзацем выше, в языке можно реализовать то, что создано через первоначальную систему. Давайте посмотрим на настоящем примере, как можно задать простой цветок. Стебель цветка упрощённо — это последовательность отрезков, начало которого является концом другого. То есть можно задать стебель как: Этот код должен давать нам что-то такое:  Но вместо этого, он может сказать: p2 not found . Это потому, что  анализ зависимостей работает не полностью, он не видит, что нужно проинициализировать p2 перед next. Я не стал его дорабатывать, потому что в нем есть фатальная проблема: в вызываемых функциях могут быть нужны поля, которые ещё не созданы (в данном случае функция plus предполагает, что мы уже знаем, чему равны x и y). А анализировать всю функцию непросто3: нельзя вызывать интерпретатор, потому что он может поменять какие-то значения. Чтобы это исправить, придется писать громоздкую конструкцию в next:  Чтобы добавить цветок, нужно немного изменить код: Если для самого стебля мы использовали идею рекурсивного контейнера, то для его разделения используем случайный контейнер: Можно вставить его в IDE и запустить  Чтобы цветы приобрели завершенный вид, нужно ещё сделать несколько шагов, которые я не вижу ценности разбирать: Стеблю и цветку нужно добавить цвета. Чтобы добавить тень, нужно каждый из отрезков продублировать и сместить копии влево на 1. Цвет этих сегментов нужно изменить на более темный. Цветкам нужно добавить лепестки, стеблям — листья. Чтобы не было ощущения, что все цветки "смотрят" на зрителя, их нужно изменить через svg transform. Для всех токенов4 объявлен метод evaluate(symbolTable: SymbolTable), который говорит, как токен должен интерпретироваться. Это очень удобно, но, наверное, никак иначе сделать нельзя :) Можно интерпретировать параллельно несколько программ (дебажить нельзя). Я использовал monaco editor, потому что у него есть удобный playground, в котором разобраны нужные кейсы. Пожалуй, самое лучшее в IDE — это дебаггинг. Только это ненастоящий дебаг, потому что сперва вся программа прогоняется. "Дебаг" — это просто запись значений переменных. Зато с дебагом синхронизируется консольный вывод. На втором месте подсвечивание ошибок. Сделал ли я DSL специально для генераторов? Нет. В генераторе цветов на C# не сильно больше кода, чем на языке, который я сделал. Понравилось ли мне это разрабатывать? Конечно, да! Какие уроки я извлек из этого? Прежде чем создавать что-то, нужно проверить, как это примерно будет выглядеть в конечном итоге, и вообще стоит ли ради этого итога работать. Нужно было: Набросать генератор цветов на моем языке, Понять, что он не лучше, чем реализация на императивном языке, Изменить подход / отказаться от реализации. 1есть ещё поля Int, Double, List, Dictionary. Class — это то, что мы объявляем (как и в других языках). 2когда я делаю генераторы, я представляю их через эту систему. Она как бы "большими мазками" описывает как будут строиться картинки, а детали уже описываются в коде. Мне кажется, эта система удобна при создании генераторов независимо от языка, контейнеры можно воспринимать как паттерны. 3вообще можно создать аннотацию @Requires() для функций класса и в ней писать, какие поля должны быть проинициализированы перед вызовом. 4токен — это любой элемент в языке, например while, {, "abc", 123, бинарные операторы (=, -, +, ||). Обычно токены разделены вайтспейсами.  Kotlin, Java программист