Первый опыт использования JetBrains MPS

Этот пост – заметка самому себе на тему того, как работать с JetBrains MPS. Решил ее выложить публично т.к. насколько я знаю, в интернетах нет хорошего туториала по MPS 1.5 да и к тому же real-world примера я пока не видел, а у меня тут как раз нечто что является вполне себе работающей DSL. Но пока что я только экспериментирую – так что этот пост будет скорее в стиле Hello World.

Вступление

Ладно, раз уж я начал целый пост писать на эту тему, попробую сначала объяснить что такое MPS. Расшифровывается это как MetaProgramming System (там может еще где-то тире было после Meta), но по сути дела система эта не для метапрограммирования вовсе, а для кодогенерации. Но это не важно – MPS уникальна в том, что позволяет определять структурированные языки – структурированные значит что вы с помощью MPSного DSL определяете ваш особый DSL.

На вопрос «зачем» могу сказать что хоть создание DSL на F# и Ruby получается вполне неплохо, хочется попробовать нечто в чем можно писать интересную логику. Естественно, генерировать Java никакого желания нет, но к счастью идея о том что MPS только для Java – это заблуждение. На самом деле прелесть генераторов в том, что они умеют генерировать всё, что угодно (C#, Scala, you name it).

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

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

Создаем новый проект

и создаем новый язык

Далее идет вопрос о создании solution’а (по аналогии с Visual Studio это наверное скорее project чем solution, но не важно) в котором можно будет тестировать DSL. Заметьте что в отличии от DSL Tools, тестовый проект не вынесен в отдельный блок который мы изначально не видим. Ну и конечно в отличии от DSL Tools, весь наш DSL умещается в одном «проекте» (тут очень легко с терминологией запутаться) а не в двух.

Жмем Finish и получаем вот это:

Концепты

Концепция – это просто сущность DSLи. В нашем случае, например, есть корневая концепция «проект». Сначала добавляем ее:

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

  • Tab позволяет прыгать между элементами
  • Enter добавляет что-то – например параметр или название чего-то
  • Ctrl+Space это просто эпичный shortcut т.к. он показывает варианты ввода (это фактически вызов IntelliSense)
  • Alt+Enter показывает подсказки в стиле контекстных подсказок ReSharper’а

Насчет концепта – это почти как Java-класс. Мы реализуем INamedConcept (просто псевдодекоратор чтобы получить свойство name), делаем концепт «корневым» и добавляем свойство startDate – заметьте что для свойств доступны только 3 типа – string, int и boolean.

С концепцией пока всё. Теперь нам нужно описать редактор.

Редактор

Переходим на таб Editor для нашего концепта (или концепции – как правильно?) и кликаем прямо в пустое пространство:

Далее начинается шаманство под заголовком node cell layout: – тут нужно определить структуру нашей DSLи. В принципе, Ctrl+Space решает, но следует помнить хотя бы горизонтальные и вертикальные layout’ы – это [/ и [> соответственно:

Теперь внутренности определяются либо через constant (это фиксированный текст которые всегда будет в определении) либо через переменные в фигурных скобках – тут будет подсказка:

Для нашего DSL конечным результатом (пока) будет вот это:

Примерно на этом этапе можно нажать Ctrl+F9 и регенерировать проект.

Генератор

Генератор – это то, что создаст из DSL осмысленный код. Выбираем его из контекстного меню проекта:

Даем генератору гламурненькое имя

Теперь добавляем в него реализацию. Для начала, дабы не мудрить, спроецируем наш DSL на Java-класс:

baseLanguage это Java, для тех кто не понял.

В плане ввода для шаблона определяем наш концепт (Project). Дальше нужно добавить реализацию. Идем в секцию <<static methods>> и добавляем static void Main(). Внутри добавляем, например, выписку по проекту (пока что пример у нас простой):

Причина, по которой я так странно описал строку, в том, что я последствии могу нажать Alt+Enter на тех строках которые нужно подменить, и заменить их макросами. Макрос – это то, что превращает из нашего концепта в нечто осмысленное. Вы уже догадались какие intentions к чему применить? Мне кажется тут всё понятно.

Теперь последний штрих – нужно задать мэппинг с концепта на генератор. Для этого, редактируем нод main:

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

Проверка

Помните песочницу? Ну, это, проект EstimationDSL.sandbox? Самое время ей воспользоваться. Сначала делаем Ctrl+F9 чтобы все перегенерировать. Теперь добавляем новую «модель» (тут с терминологией становится совсем плохо):

Задаем название, плюс у меня почему-то есть соблазн прописать стереотип как generator, хотя я понятия не имею что это значит:

Идем дальше и появляется вот такое окно – в этом окне можно настроить те фичи, которые будут использованы непосредственно для этого конкретного тестового скрипта:

В плане языков выбираем наш новый язык, а также добавляем generator – он в принципе должен добавляться сам, но что-то в моей MPS «слетело» и теперь приходится его добавлять руками:

В табе Advanced выбираем наш язык:

Вот собственно и все. Теперь добавляем новый тестовый элемент (у нас это Project):

И получаем – ура! – редактор для нашего DSL:

Чтобы быстренько протестировать DSL, вводим данные и выбираем в меню проекта Generate Text:

Результат не заставит себя ждать:

А это, собственно, именно то, что нам и хотелось получить.

Заключение

MPS – штука сложная, но судя по всему, зная азы, с ней как-то можно разобраться. Если интересно, в принципе для нее существует скринкаст – он весьма посредственный и у автора сильный бельгийский акцент (а я, как вы знаете, люблю придираться к акцентам), но пока я сам ничего не создал, альтернатив нет. Кстати, пока вы не спросили – да, соблазн есть, но пока что нужно разобраться как генерировать .Net-ный код в этой системе. Собственно, я уверен что как текст код генерировать можно, но было бы круто если бы все это дело встраивалось в Visual Studio, а то у нас в VS-экосистеме с configuration management итак очень плохо.

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

Еще посетила такая мысль: почему это не коммерческое ПО? Я бы сразу налепил на это ценник и записал бы 15 скринкастов для JetBrains TV. Ну да ладно, может это все впереди? :)

Оставить комментарий