Pull to refresh

Создание языковых расширений в RASE. Часть 1. Язык Intentions

Reading time6 min
Views1.2K

RealaxyСуществует укоренившееся мнение, что языковые расширения являются чем-то вроде фигур высшего пилотажа в программировании. Число публикаций на эту тему постоянно растет, однако доля русскоязычных среди них по понятным причинам ничтожна. Цель настоящего цикла статей — показать несложные и эффективные способы автоматизации обычных повседневных задач с помощью функционала для языковых расширений, доступного в средах разработки, основанных на JetBrains MPS.



В нашем случае такой средой будет Realaxy ActionScript Editor, бета-версию которого можно загрузить здесь. Впрочем, все изложенное ниже за несколькими несущественными частностями также применимо и для написания языковых расширений под Java в редакторе MPS.



С чего начать?


Самым простым и доступным применением потенциала платформы, специально заточенной под разработку предметных языков, будет создание в ней контекстного расширения на примере работы со строками. Наше пробное расширение будет решать задачу «умного» экранирования разных кавычек в случаях, подобных этому:



Скриншот


В строке встречаются и одинарные, и двойные кавычки. Наше учебное языковое расширение должно будет в зависимости от контекста предлагать экранирование символов, которые вызывают конфликт (экранирование двойных кавычек будет уместно только в тех строках, которые обрамлены одинарными, и наоборот). Такая демонстрационная задача хороша тем, что может решаться самыми разными способами, начиная от написания простеньких автоматизационных скриптов и заканчивая использованием плагинной архитектуры. В настоящей статье она будет решена с помощью Intentions, одного из аспектов языковых расширений в MPS.



Что такое Intentions?


Меню Intentions — одна из наиболее часто используемых функций любого редактора, основанного на IDEA. Это простой и быстрый контекстозависимый доступ к наиболее востребованным операциям, применимым к синтаксическим конструкциям языка (например, «Add Exeption», «Invert If Condition» или «Convert Variable to Field»). О доступности меню Intentions сигнализирует значок лампочки, появляющийся слева от строки.



Скриншот


В повседневной работе мы постоянно сталкиваемся с похожим функционалом, поскольку в том или ином виде он по умолчанию присутствует во всех современных IDE. В RASE (как, впрочем, и в MPS или в IntelliJ IDEA) список доступных для текущей позиции Intentions проще всего вызвать клавиатурным сочетанием Alt-Enter.



Более подробно об их сущности можно узнать из официальной документации на сайте JetBrains MPS.



Скриншот



Переходим от слов к делу


1. Сначала мы создаем новый проект. В функцию Main() помещаем две строки, одна будет обрамлена одинарными кавычками, а другая — двойными.



Скриншот


2. В редакторе имеется несколько режимов просмотра. ActionScript View, который предлагается нам по умолчанию, выглядит сравнительно аскетично и позволяет разработчику сфокусироваться непосредственно на AS. В MPS View, доставшемся редактору «по наследству» от одноименной платформы, раскрывается гораздо большее количество возможностей (в частности — для написания языковых расширений).



Скриншот


3. Далее в контекстном меню проекта (открывающемуся по правому щелчку мыши) добавляем новый язык:



Скриншот


4. Вводим имя языка (myLanguages.escapedStrings) и нажимаем на OK.



Скриншот


5. После указанных действий язык по имени myLanguages.escapedStrings успешно появляется новым рутом в нашем проекте. Можно заметить, что myLanguages.escapedStrings уже содержит некоторые так называемые аспекты (на скриншоте: structure, editor, constrains, typesystem и т.д). Позже мы подробно расскажем о том, как ими пользоваться.



Скриншот


6. Клавиатурным сокращением Alt-Enter или из контекстного меню языка myLanguages.escapedStrings вызываем диалоговое окно Language Properties.



Скриншот


7. В открывшемся окне во вкладке Dependencies в поле Extended languages выбираем com.realaxy.actionScript, после чего нажимаем ОК. Это означает, что это учебное языковое расширение мы будем создавать именно для ActionScript.



Скриншот


8. Добавим аспект Intentions. Intention — это некий автоматический скрипт, который выполняет определенное действие над выделенным языковым элементом (в нашем случае — экранированием кавычек в String Literal).



Скриншот


9. Создаем Intention declaration.



Скриншот


10. Присваиваем ей имя EscapeQuotes и привязываем к StringLiteral. После чего в блоке descriprion прописываем строку «Escape Quotes»



Скриншот


11. Наступает ключевой момент. На место конструкции isApplicable мы должны поместить условие, которому будет соответствовать наш Intention. Для этого не нужны особые ухищрения: мы работаем в среде с открытым исходным кодом, и нам достаточно лишь посмотреть, как в редакторе работает механизм работы подсветки ошибок и предупреждений. Наша цель — не изобретать велосипед, а использовать уже благополучно работающий код. Немножко отвлечемся и подумаем, где же его можно подглядеть? Для этого обратимся к ошибке, которую мы собираемся исправлять. Переведем курсор к нашей изобилующей кавычками строке, находящейся в Main(), чтобы вызвать контекстное меню.



Скриншот


12. Через контекстное меню или с помощью клавиатурной комбинации Shift-Ctrl-T (на Маке — Shift-Cmd-T) вызываем диалоговое окно Show Type. Оно по праву может считаться одним из самых полезных в AS-разработке, поскольку почти все системные сообщения (warnings, info и errors) так или иначе относятся к системе типов.



Скриншот


13. Итак, в появившемся окне мы видим информацию о том, что это за объект, что за тип и где он находится в иерархии типов. Нажимаем на кнопку Go To Rule Which Caused Error (как видно из предшествующего скриншота, для экономии времени мы могли бы сразу перейти к этой функции IDE). Переходим в появившуюся вкладку и видим конструкцию, из которой понимаем, что интересующий нас код скорее всего исполняется при вызове isCorrect. Только как до него добраться?



Скриншот


14. Если при нажатом Ctrl (или Cmd) навести указатель мыши на название метода, появится подчеркивание, напоминающее нам гипертекстовую ссылку. Кликнув на него, переходим к исходной точке кода. Она-то нам и нужна.



Скриншот


15. Далее копируем код метода isCorrect, переходим во вкладку IntentionDeclaration и вставляем его в метод isApplicable, чтобы упростить и модифицировать условие под наши нужды:



Скриншот


16. Теперь пришла пора добавить код, который будет выполняться при вызове нашего Intention. В методе execute прописываем модификацию нашего node.value



Скриншот


17. Стоп. Почему бы нам для большего удобства не воспользоваться регулярными выражениями? Сказано — сделано. По сочетанию Ctrl-L (Cmd-L на Маке) импортируем язык regexp.



Скриншот


18. В результате получаем следующую конструкцию...



Скриншот


19.… в которую остается лишь вставить соответствующий регексп.



Скриншот


Вся декларация Intention при этом выглядит следующим образом:



Скриншот


20. Наконец, Intention можно компилировать. Нажимаем Ctrl-F9 (на Маке — Cmd-F9) или в главном меню выбираем Build > Make Module(s), после чего переходим в Main(), пробуем запустить наше языковое расширение, нажав Alt-Enter на содержащей ошибку строчке… и замечаем, что ничего не происходит. В чем же дело?



Скриншот


21. Всё просто: мы скомпилировали языковое расширение, но не импортировали его в проект. По уже знакомому сочетанию Ctrl-L (Cmd-L на Маке) мы исправляем это недоразумение.



Скриншот


22. Снова нажимаем на нашей «неисправной» строке сочетание Alt-Enter.



Скриншот


23. И получаем искомый результат.



Скриншот


24. Что делать дальше — вопрос вкуса. Пока наше расширение работает только с строкам, заключенным в двойные кавычки. Поскольку мы хотим быстро сделать его применимым еще и к кавычкам одинарным, самый простой путь — элементарно продублировать с помощью Ctrl-D (Cmd-D) нашу декларацию Intention Escape Quotes и поменять в ней лишь тип данных, название и символ кавычек там, где это нужно.



Скриншот


25. Слегка адаптируем наш код:



Скриншот


Конечно, можно было добиться большей компактности кода и обойтись, например, одним рутом вместо двух на каждую из кавычек. Однако не забываем, что главной целью было получить быструю и эффективную автоматизацию рутинного действия, и в нашем случае эта задача решена просто и изящно.


26. Миссия выполнена. После повторной компиляции рута по Ctrl-F9 (на Маке — Cmd+F9) мы получаем готовый скрипт для экранирования кавычек. Вот результат его работы:



Скриншот



Вместо заключения


Как вы могли убедиться, автоматизировать выполнение повседневных задач в основанных на JetBrains MPS редакторах совсем несложно. Полученное решение легко перенести в другие проекты, добавив в их свойства наше языковое расширение.



Скриншот


Функционал, о котором рассказывает эта статья, является лишь первым шагом в мир LOP и DSL. Тем не менее, одного только языка Intentions с избытком достаточно, чтобы разработчик мог организовать «здесь и сейчас» собственную производственную инфраструктуру под свои задачи.



Тем же читателям, кто не собирается останавливаться на достигнутом и хотел бы более обстоятельно вникнуть в практику создания языковых расширений, советуем следить за обновлениями. В следующей статье та же самая задача будет решена другим способом, с более обширным использованием различных языковых аспектов.



Ссылка на исходники проекта.



Отдельное спасибо Евгению Потапенко (potapenko) за всестороннюю помощь и поддержку при написании этого материала.

Tags:
Hubs:
+17
Comments5

Articles

Change theme settings