
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 — список базовых классов.
- В фигурных скобках — правила разбора для каждого поля (обязательно/опционально, тип парсинга).
Пример сгенерированного 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.
Автор: Adler3D
