Журнал «Компьютерра» № 10 от 14 марта 2006 года
Шрифт:
<%=p.Content%>
<%}%>
</BODY>
</HTML>
Создание языка предметной области начинается с этапа моделирования данных – той информации, которая будет впоследствии записана в терминах DSL. Для простоты предположим, что нам необходимо разработать DSL, на котором можно описать структуру статьи для журнала «Компьютерра». Предметная область этой задачи включает в себя понятия статья, раздел и подраздел[Понятие «подраздел», конечно же, является избыточным, но кому нужны эти скучные профессиональные детали?]. Для статьи характерны название, автор и некоторая
Проиллюстрируем вышесказанное диаграммой (см. рис. 1), описывающей модель данных нашего DSL, который условно назовем «Структура статьи в КТ».
Следующий этап проектирования состоит в том, чтобы внести разработанную нами модель данных в языковой инструментарий. При этом для каждого понятия предметной области необходимо создать соответствующую концепцию языка. Например, концепция «статья» выглядит в MPS так, как изображено на рис. 2. На этом проектирование нашего языка можно считать завершенным.
Кто-нибудь может возразить: язык – это в первую очередь знаковая система, а то, что мы только что создали, является скорее некоторой объектной моделью. Действительно, мы разработали только часть языка, традиционно называемую абстрактным синтаксисом. Если так можно выразиться, «знаковой системой» DSL в контексте языковых инструментариев являются редакторы, обеспечивающие визуальное отображение понятий языка.
Разработка редактора для DSL – занятие еще более увлекательное, нежели проектирование самого DSL, поскольку тут гораздо более широкое поле для творчества.
MPS обладает встроенным дизайнером для создания редакторов DSL, основанном на идее вложенных ячеек. Поясним эту идею на примере редактора для концепции «статья». На рис. 3 ячейка-контейнер верхнего уровня содержит две дочерние ячейки, расположенные вертикально. Верхняя ячейка содержит константное слово «статья», а нижняя является горизонтальным контейнером для других ячеек. И так далее.
После определения «раскладки» составных частей остается связать с редактором атрибуты «название» и «автор», дополнить его возможностью выбора автора из списка, и получится нечто, изображенное на рис. 4. Процесс редактирования документов при помощи такого редактора очень прост и вполне удобен (хотя и слегка непривычен). Например, для добавления подраздела необходимо перейти в ячейку «…добавьте подраздел…» и начать ввод текста. После нажатия клавиши Enter фокус ввода переместится на следующий подраздел.
К автоматизации процесса разработки DSL можно подходить с различных сторон. Классический путь, существовавший задолго до появления языковых инструментариев, заключается в создании грамматики DSL, пригодной для обработки специальными программами – генераторами синтаксических анализаторов.
Генератор синтаксических анализаторов (ГСА) – это утилита, на вход которой поступает файл с описанием правил грамматики некоторого языка, называемого целевым. В результате работы генератор формирует исходные тексты на C++ (или, допустим, Java), содержащие код для обработки конструкций целевого языка и, возможно, для формирования объектной модели. Написание собственного ГСА «с изюминкой» долгое время являлось престижной академической работой в области computer science, поэтому число подобных инструментов сегодня исчисляется десятками. Этот факт даже получил отражение в названиях многих ГСА: «еще один компилятор компиляторов» (yacc), «еще один инструмент для распознавания языков» (ANTLR) и т. п.
В качестве примера приведем фрагмент грамматики ANTLR для языка арифметических выражений, содержащих числа, а также операции ‘+’ и ‘*’. Хотя подобная запись и выглядит страшновато, при наличии определенных навыков она воспринимается достаточно легко.
expr : mexpr (‘+’ mexpr)* ‘;’!;
mexpr : number (‘*’ number)*;
number : (‘0’..’9’)+;
Несмотря на ряд трудностей, связанных с повсеместным применением ГСА, на сегодняшний день они являются распространенным средством автоматизации разбора исходных текстов*. Например, распознаватель SQL для широко известной открытой СУБД PostgreSQL разработан при помощи пары lex и yacc. Интересно отметить, что эта «сладкая парочка» оказала существенное влияние на открытый софт, породив целое направление так называемых «малых языков» (по сути своей являющихся DSL), с которыми пользователи *nix-систем часто имеют дело при редактировании конфигурационных файлов.
* Тот, кто боролся с неоднозначностями и устранением левой рекурсии путем введения фиктивных правил в грамматику, хорошо понимает, трудности какого рода приходится преодолевать.
DSL сам по себе, пусть даже и с хорошим редактором, не представляет интереса до тех пор, пока мы не привяжем его понятия к языку реализации – как правило, некоторому универсальному языку программирования, например Java или С#. Для решения этой задачи в языковых инструментариях применяются технологии метапрограммирования (см. врезку «Что такое метапрограмма?»).
Вид метапрограммы существенно зависит как от структуры DSL, так и от языка реализации проекта. Например, в случае DSL «Структура статьи в КТ» можно сгенерировать документ HTML или макрос для Word, который в процессе выполнения сформирует шаблон будущей статьи с необходимой разметкой документа. При этом метапрограмма, генерирующая HTML, будет сильно отличаться от метапрограммы-генератора документа Word.
Вообще говоря, метапрограммирование – интересная и мощная, но довольно сложная технология. Именно поэтому в окончательном варианте статьи опущен пример, связанный с написанием метапрограммы для нашего DSL «Структура статьи в КТ». Отметим лишь, что процесс написания метапрограмм можно радикально облегчить, если мы хорошо представляем себе конечный результат – исходный код на языке реализации. Поэтому при ведении проекта на DSL целесообразно использовать прототипирование, то есть вначале создать «скелет» разрабатываемого приложения, а уж затем проектировать DSL и метапрограммы-генераторы для него.
Обобщая, можно выделить следующие этапы разработки приложений с участием языковых инструментариев:
1. Cоздание прототипа, содержащего частичную реализацию минимально необходимого набора бизнес-функций («скелет» будущего приложения).
2. Определение существенных абстракций проекта, разработка DSL.
3. Создание метапрограмм-генераторов для понятий DSL.
4. Перевод разработанного прототипа приложения в окружение языкового инструментария.
5. Реализация бизнес-функций проекта в терминах DSL.
6. Автоматическая генерация исходных текстов на языке реализации проекта.
Этапы 2–6 схематически изображены на диаграмме (рис. 5), там же показана взаимосвязь между языковыми инструментариями и классическими средами разработки.
Возможно, многим покажется, что вышеописанный подход к разработке – не более чем преодоление трудностей, которые мы сами же себе и создали, приняв решение вести проект системы на DSL, однако список полученных при этом преимуществ весьма внушителен: