Pull to refresh

JetBrains MPS — IDE для разработки проблемно-ориентированных языков программирования

Reading time 4 min
Views 15K

Введение


MPS — это среда разработки языков программирования на платформе Intellij Idea. Она предоставляет возможность моделировать языки программирования, описывать структуры, сосредотачиваясь конкретно на дизайне и бизнес-логике, не отвлекаясь на парсеры, лексеры и различные сторонние фичи языка. Что нужно сразу обозначить: язык, который разрабатывается с помощью MPS, не компилируется в рабочую программу. Чаще всего он генерируется в модели других языков либо в текстовую форму.

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

Документация очень подробная, описывает все возможности, но если нет осознания «что я делаю и зачем мне это делать», то она вряд ли будет полезна. Также есть серия уроков от JetBrains на YouTube, но опять же, я более-менее начал понимать, что происходит, только после двух просмотров всей серии и досканального изучения предлагаемых sample проектов.

Я планирую написать серию постов об этой замечательной среде, чтобы по окончанию прочтения у энтузиастов оставалось как можно меньше вопросов «как сделать эту штуку», и как можно больше понимания структуры, чтобы можно было эффективно пользоваться документацией. Познавать MPS мы будем во время создания языка для описания закономерностей погоды. Почему, собственно говоря, и нет?

Концепция


MPS (дальше — среда / MPS) предоставляет возможность создавать модули двух типов — Language и Solution. Первый является описанием языка и его аспектов, второй используется для разработки каких-либо проектов, тестирования языка / языков, расширений языков.

Я начну с Language.

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

  1. Мы описываем модель языка и необходимые аспекты этого языка, например, систему типов, поведение в редакторе.
  2. Мы описываем, как модель нашего языка компилируется в текст или в модель другого языка.
  3. Мы собираем наш язык в плагин для Intellij Idea и используем его. Все переходы из одной модели в другую делаются за нас, в итоге мы получаем готовый скомпилированный код.

Следует заметить, что написание кода в формате MPS немного отличается от написания текста программы, потому что мы на самом деле редактируем AST (Abstract Syntax Tree), а то, что мы видим в редакторе кода — проекция AST.

Итак, 1 статья — 1 кусок конечного проекта.

Создаем проект в MPS


MPS_1_START

На данном этапе мы выбираем название проекта и название языка, также можно создать Sandbox solution — модуль, в котором мы будем смотреть, как работает наш язык.

image

У нас есть пустой проект. Совсем пустой. Но в языке WeatherPrediction есть вложенные директивы — structure, editor… Это аспекты языка — в них мы описываем поведение языка в разных ситуациях. Например, structure содержит основные концепты языка, а editor — то, как они будут отображаться в редакторе кода. Это должно звучать очень абстрактно, особенно если Вы еще не знакомы с MPS. Понимаю. Так что сразу в бой.

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

Пример root концепта на Java
public class Weather{

}

Чтобы создать концепт, тыкаем ЛКМ по WeatherPrediction -> New -> Concept.

image

У концепта есть 3 типа данных, которые он может содержать:

  1. properties — здесь можно хранить любые примитивные данные, аля строки, числа и boolean Абстрактный пример — целочисленная переменная, у которой должно быть name: string, value: integer, final: boolean

  2. children — здесь хранятся элементы других концептов. Как абстрактный пример, для концепта Program туда можно было бы пихнуть массив Statement

  3. references — здесь хранятся ссылки на другие реализованные концепты в AST, которые есть в текущем scope. Абстрактный пример — переменная, у которой значение — ссылка на другую переменную по имени( в реальности это не совсем так, но это же абстрактный пример).

Назовем его PredictionList, определим его как root концепт и наследуем интерфейс INamedConcept.

image

Что здесь происходит: Мы определяем концепт, называем его PredictionList, говорим, что его можно реализовать как root концепт и наследуемся от INamedConcept. Если посмотреть на его definition (Ctrl + B)

image

то мы увидим, что это interface concept, у которого есть property name: string, что, собственно говоря, логично из названия

Обратите внимание, что синтаксис похож на язык программирования. Это так: этот код написан на языке jetbrains.mps.lang.structure, который описывает концепты языка.

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

image

ЛКМ на sandbox(generation required) -> New -> PredictionList

image

Заменим no name на Saint Petersburg

Бум! У нас есть дефолтная визуализация концепта. Чтобы посмотреть AST, нажмите на любое место в редакторе и нажмите хоткей Alt + X

image

Здесь видна структура всего всего, это очень круто и удобно отслеживать состояние дерева. Сразу видим, что name у PredictionList = Saint Petersburg. Так, это очень здорово, но мы же хотим чтобы все было по красоте, поэтому мы открываем редактор концепта PredictionList и создаем ему editor aspect. Нажимаем на зеленый плюсик в нижнем списке, прямо под кодом нашего концепта, выбираем Editor -> Concept Editor

image

Здесь мы можем описать то, как будет отображаться наш PredictionList в редакторе кода.
Пока не будем вдаваться в подробности, как тут это все сделано, просто пишем [- и у нас создается массив ячеек. Все просто: в каждой ячейке — какой то константный текст / property / reference / children. И да, отображение описывается другим языком — jetbrains.mps.lang.editor.
Мы хотим, чтобы наш список предсказаний погоды выглядел следующим образом:
Weather prediction rules for %name%.

Так и пишем.

image

В первой ячейке — константный текст, во второй — {name}, обращение к property по ключу name.

Пересобираем наш язык (Ctrl + F9) и смотрим в Sandbox solution, где мы до этого создали пустой PredicitonList по имени Saint Petersburg.

image

Все работает, AST то же самое, что и до наших модификаций.

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

Спасибо за внимание! Пожалуйста, все пожелания, непонятки и вопросы и пишите в комменты. Если вопросы конкретные и простые, отвечу в комментариях, иначе добавлю в следующий пост.
Tags:
Hubs:
+24
Comments 28
Comments Comments 28

Articles