
QapDSL — декларативное описание AST и парсеров для C++
QapDSL — это специализированный язык (DSL), который позволяет описывать абстрактные синтаксические деревья (AST) и правила их разбора для языков программирования, прежде всего C++. Такая формализация помогает автоматизировать построение парсеров, генерацию кода, анализ исходников и даже рефакторинг.
Зачем нужен QapDSL?
- Компактно и наглядно описывать структуру и грамматику языка.
- Автоматически генерировать C++-структуры, парсеры, сериализаторы и визиторы.
- Ускорять эксперименты с языками, создавая прототипы компиляторов и анализаторов.
- Упрощать анализ и рефакторинг сложных языков, в т.ч. C++.
Пример QapDSL-описания
Рассмотрим, как описывается объявление класса C++ на QapDSL:
t_class{ string keyword; t_sep sep0; string name; t_sep sep1; TAutoPtr<t_parents> parents; t_sep sep2; TAutoPtr<t_class_body> body; t_sep sep3; { M+=go_any_str_from_vec(keyword,split("struct,class,union",",")); O+=go_auto(sep0); M+=go_str<t_name>(name); O+=go_auto(sep1); O+=go_auto(parents); O+=go_auto(sep2); O+=go_auto(body); O+=go_auto(sep3); M+=go_const(";"); } }
- Поле
keyword
— ключевое слово (struct
,class
,union
). - name — имя класса, parents — список базовых классов.
- В фигурных скобках — правила разбора для каждого поля («M+=» — обязательное правило. «O+=» — опциональное, делее тип парсинга).
Пример сгенерированного C++-кода
Вот пример C++-структуры, которую может сгенерировать QapGen по приведённому выше QapDSL-описанию:
class t_class{ #define DEF_PRO_STRUCT_INFO(NAME,PARENT,OWNER)NAME(t_class)OWNER(t_inl_file) #define DEF_PRO_VARIABLE(ADDBEG,ADDVAR,ADDEND)\ ADDBEG()\ ADDVAR(string,keyword,DEF,$,$)\ ADDVAR(t_sep,sep0,DEF,$,$)\ ADDVAR(string,name,DEF,$,$)\ ADDVAR(t_sep,sep1,DEF,$,$)\ ADDVAR(TAutoPtr<t_parents>,parents,DEF,$,$)\ ADDVAR(t_sep,sep2,DEF,$,$)\ ADDVAR(TAutoPtr<t_class_body>,body,DEF,$,$)\ ADDVAR(t_sep,sep3,DEF,$,$)\ ADDEND() //=====+>>>>>t_class #include "QapGenStructNoTemplate.inl" //<<<<<+=====t_class public: bool go(i_dev&dev){ t_fallback scope(dev,__FUNCTION__); auto&ok=scope.ok; auto&D=scope.mandatory; auto&M=scope.mandatory; auto&O=scope.optional; static const auto g_static_var_0=QapStrFinder::fromArr(split("struct,class,union",",")); M+=dev.go_any_str_from_vec(keyword,g_static_var_0); if(!ok)return ok; O+=dev.go_auto(sep0); if(!ok)return ok; M+=dev.go_str<t_name>(name); if(!ok)return ok; O+=dev.go_auto(sep1); if(!ok)return ok; O+=dev.go_auto(parents); if(!ok)return ok; O+=dev.go_auto(sep2); if(!ok)return ok; O+=dev.go_auto(body); if(!ok)return ok; O+=dev.go_auto(sep3); if(!ok)return ok; M+=dev.go_const(";"); if(!ok)return ok; return ok; } };
- Структура полностью повторяет схему из QapDSL и содержит макросы для генерации кода и сериализации.
- Метод
go
реализует правила разбора для каждого поля — как и в QapDSL. - Включение
QapGenStructNoTemplate.inl
добавляет автогенерированные методы для RTTI/визиторов/сериализации.
QapDSL в действии: как это работает?
- Пишем QapDSL-описание грамматики.
- Автогенератор создает C++-структуры и код парсера.
- Получаем AST, по которому можно строить анализаторы, рефактореры, сериализаторы и т.д.
В проекте Sgon можно встретить QapDSL
-описание, закодированное в base64 или encodeURIComponent
внутри комментария — это целостная схема AST и грамматики.
Пример живого проекта: cpp_ast_scheme.cpp в QapGen и репозиторий QapGen.
Сравнение с аналогами
QapDSL | ANTLR | Yacc/Bison | protobuf | |
---|---|---|---|---|
Тип | DSL для AST+грамматики | Генератор парсеров | Генератор парсеров | DSL для сериализации |
AST | Автоматически | Через actions | Ручное | Только структуры данных |
Язык генерации | C++ | Java, C++, Python и др. | C/C++ | C++, Python и др. |
Поддержка C++-синтаксиса | Глубокая | Возможно | Возможно | Нет |
Порог вхождения | Средний | Средний/Высокий | Средний/Высокий | Низкий |
QapDSL: плюсы и минусы
- + Одна схема — и AST, и парсер.
- + Просто расширять и поддерживать новые конструкции C++ (шаблоны, пространства имён, препроцессор).
- + Автоматическая генерация кода, сериализация, визиторы.
- – Меньше документации и сообщества, чем у ANTLR/Yacc.
- – Ориентация прежде всего на C++ и AST-heavy задачи.
Где посмотреть/попробовать?
- Репозиторий: Adler3d/QapGen
- Пример AST для C++: samples/cpp_ast_scheme.cpp
- Демо-примеры: samples в репозитории
Заключение
QapDSL — мощный инструмент для тех, кто работает с AST, парсерами и анализом кода C++. Он позволяет компактно описывать самые сложные конструкции C++ и автоматизировать рутинные задачи, связанные с синтаксисом. Если вы любите декларативные подходы и часто пишете компиляторы или анализаторы — обязательно попробуйте QapDSL!
upd:
QapDSL:
t_var_decl{ string type_name; string var_name; { M+=go_str<t_type>(type_name); M+=go_const(" "); M+=go_str<t_name>(var_name); M+=go_const(";"); } }
Что даётся на вход Лексеру:
Строка программы, например:
int x;
Лексер разбивает текст на лексемы (токены):
int // лексема типа // пробел (разделитель) x // идентификатор (имя переменной) ; // символ конца объявления
Какой AST получается на выходе:
После разбора получится структура:
t_var_decl{ type_name = "int" var_name = "x" }
То есть, поле type_name содержит строку «int», а var_name — строку «x».
Автор: Adler3d. Статья подготовлена при поддержке GitHub Copilot.