Журнал «Компьютерра» № 10 от 14 марта 2006 года - Компьютерра. Страница 34
Кто-нибудь может возразить: язык – это в первую очередь знаковая система, а то, что мы только что создали, является скорее некоторой объектной моделью. Действительно, мы разработали только часть языка, традиционно называемую абстрактным синтаксисом. Если так можно выразиться, «знаковой системой» 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, однако список полученных при этом преимуществ весьма внушителен:
полностью подавляется расхождение проекта и исходного кода. Высокоуровневые абстракции проекта, записанные на DSL, связаны с кодом реализации проекта. При этом проект всегда остается актуальным, ведь изменения исходного кода теперь вторичны по отношению к изменениям в самом DSL;
облегчаются доработки проекта под изменившиеся требования заказчика. Этому способствует сама философия метапрограммирования: DSL как бы параметризует проект. При небольших изменениях проекта разработчику нужно лишь слегка модифицировать конкретную конфигурацию, описанную на DSL. Серьезные изменения, затрагивающие предметную область, тоже становятся проще, поскольку в таких случаях алгоритм действий четко определен: вначале модифицируем сам DSL и редакторы для него, а затем – генераторы исходного кода на языке реализации;
упрощается управление проектом. Например, при вхождении в проект новых участников для получения общего представления о проекте (в его актуальном состоянии!) достаточно показать им лишь ту часть исходного кода, которая записана на DSL. Более того, взаимодействие между участниками проекта становится эффективнее. Допустим, аналитик проекта, хорошо представляющий себе бизнес-логику приложения, теперь сможет самостоятельно описывать на DSL ее отдельные части[В принципе, достаточно лишь понимания аналитиком кода на DSL. При этом он (как правило, все же, она) сможет на словах рассказать разработчикам, что нужно сделать, а затем, просмотрев код на DSL, проконтролировать их работу]. При этом проект избавляется от лишних документов, а разработчики – от их прочтения (на что тоже нужно время) и двусмысленного толкования;