QapDSLv2 — это язык который транслируется в обычный C++ код. Он позволяет удобно и компактно задавать грамматики/правила разбора, значительно упрощая разработку компиляторов и анализаторов.
QapGen — это генератор дерева_лексеров/парсеров описанных на QapDSLv2. Сама грамматика QapDSLv2 описана на QapDSLv2 на 100%. Поэтому QapGen как основной читатель этой грамматики сам генерирует часть своего кода(весь парсер QapDSLv2).
Основные фишки QapDSLv2 + QapGen — это:
Отсутствие этапа токенизации — дерево лексеров разбивает входной поток на лексемы и сохраняет их в строго типизированных древовидных С++ структурах пропуская этап токенизации.
Полное сохранение всех лексем(даже разделители сохраняются, такие как пробелы/переходы на новую строку и комментарии) в результирующем дереве.
Возможность сохранить как оригинальное дерево, так и модифицированное обратно в код/текст без потери разделителей/комментариев.
Генерация оптимизированного кода полиморфных лексеров.
Автоматическая генерация кода посетителей(это такой паттерн проектирования).
А теперь пример самой сочной части(рекурсивно самоописывающийся код):
t_target_struct:i_target_item{
t_keyword{
string kw=any_str_from_vec(split("struct,class",","));
" "? // optional separator
}
t_body_semicolon:i_struct_impl{";"}
t_body_impl:i_struct_impl{
"{" // жрём скобочку
vector<TAutoPtr<i_target_item>> nested?; // запускаем рекурсию!
" "?
vector<TAutoPtr<i_struct_field>> arr?; // парсим поля
" "?
TAutoPtr<t_struct_cmds> cmds?; // УГ из QapDSLv1
" "?
TAutoPtr<t_cpp_code> c?; // нагло пожираем остальной С++ код
" "?
"}"
}
t_parent{
string arrow_or_colon=any_str_from_vec(split("=>,:",","));
" "?
t_name parent;
}
//точка входа в парсер:
TAutoPtr<t_keyword> kw?; // парсим struct/class
t_name name; //парсим имя
" "? // опциональный разделитель
TAutoPtr<t_parent> parent?; // парсим имя интерфэйса если есть
" "?
TAutoPtr<i_struct_impl> body; // парсим тело структуры или точку с запятой.
}
Про эту статью
Это вторая статья про QapDSL(и одна из первых про QapGen), но первую статью про QapDSLv1 я читать не рекомендую, т.к она безнадёжна устарела, но в этой статье будут описаны изменения по сравнению с первой версией языка, так что я не знаю как вы будете в этом разбираться.
Про соседнюю статью
Я решил выложить сразу две стать в одно время. В этой статье после описания нововведений и разбора примера простого калькулятора, будет вторая половина статьи с самой настоящей сложной технической жестью про QapGen, а в той статье про сравнение с конкурентами на основе парсинга JSON, про механизм полиморфного разбора, а также описание QapDSLv2 и громкая хвала его преимущества.
Основные цели и мотивация для создания QapDSLv2

В комментариях к первой версии отмечалась перегруженность синтаксиса излишними конструкциями, что усложняло чтение и поддержку кода. Вторая версия QapDSL была разработана с целью упростить и укоротить синтаксис, повысить гибкость и расширить возможности интеграции с C++.
Ключевые нововведения QapDSLv2
Новый укороченный синтаксис
Синтаксис стал более компактным и выразительным. Например, вместо
t_num_with_sep{t_num num;t_sep sep;{go_auto(num);go_auto(sep);}}
теперь можно писать
t_num_with_sep{t_num num;" "} // код с go_auto генерируется автоматически.
// " " - заменится на анонимный t_sep
// но только тогда когда сверху в дереве написано using " " as t_sep;
Более полный пример из кода QapGen
// legacy lexer для парсинга команд из QapDSLv1
t_struct_cmd{ // парсить строки виды 'O+=go_minor<major>(minor);'
TAutoPtr<i_struct_cmd_xxxx> mode?; // парсит 'O+='
t_name func; // парсит имя go_* метода
" "? // " " - заменится на анонимный t_sep, вопросик - опциональность.
string templ_params=str<TAutoPtr<t_templ_params>>()?; // парсит шаблоны
"(" // пожирает открывающуюся скобочку
t_cmd_params params; //парсит параметры из вызова метода
")" // пожирает закрывающуюся скобочку
" "?
TAutoPtr<i_struct_cmd_so> cmdso?; // опциональна фигня, надо удалить.
" "?
";" // пожирает точку с запятой.
}
Атрибуты для полей
Нужны для того чтобы можно было прокидывать инфу о полях из QapDSLv2 в С++ код и тем самым получать возможность управлять кодом обходящим AST. Например в тэг-атрибут можно запихать js-код обработчик поля/ноды. Или это плохой пример?
t_test20250618_atrr{
t_foo{{}[::]} // тест баяна.
t_foo foo; [skip] //аттрибут указывающий зачем-то что это надо пропустить
t_sep sep; [optimize,"sep",("sep"),sep[x]] // вот так можно писать
}
Возможность вставлять C++ код в QapDSL без разделителя — «баян» [::]
Это нововведение позволяет интегрировать произвольный C++ код в QapDSLv2 без необходимости использовать специальные разделители([::]), что упрощает работу с кодом и улучшает читаемость.
Кроссплатформенная версия QapGen
Теперь QapGen(генератор парсеров) компилируется g++/clang/cl.exe, и генерируемый им код также компилируется под linux/windows, что значительно расширяет область применения и удобство использования инструмента.

Примера описания парсера для калькулятора на QapDSLv2
t_simple_calc{
//обьявляемы и реализуем вложенные в t_simple_calc лексеры
t_term{
TAutoPtr<i_term> value; // вызываем парсинг полиморфной ноды/лексера.
}
t_number:i_term{
t_ext{
"." // жрём точечку.
string v=any(gen_dips("09")); // жрём всё от 0 до 9 пока оно есть.
}
t_impl{
string bef=any(gen_dips("09"));
TAutoPtr<t_ext> ext?; // парсим опциональный t_ext
}
string value=str<t_impl>(); // парсим t_impl и сохраняем его в строку.
}
t_divmul{
t_elem{
string oper=any_str_from_vec(split("/,*",",")); // сжираем / или *
t_term expr; // запускаем полиморфа который есть скобки и числа
}
t_term first;
vector<t_elem> arr?; // запускаем опциональный парсинг массива t_elem
}
t_addsub{
t_elem{
string oper=any_str_from_vec(split("+,-",",")); // сжираем + или -
t_divmul expr;
}
t_divmul first; // парсим t_divmul, т.к у него выше приоретет
vector<t_elem> arr?; // теперь опционально все остальные
}
t_scope:i_term{
"(" // едим скобку
t_addsub value; // начинаем с + или -,т.к у них низкий приортет
")" // ещё скобочку
}
// точка входа:
t_addsub value; // парсим t_addsub
}
Пример запуска парсера калькулятора описанного на QapDSLv2
Смотрите сначала разбор, а потом это если захочется увидеть реальный код
struct t_simple_calc_evalutor:t_simple_calc{
typedef t_simple_calc t_ast;
struct t_go:i_term_visitor{
void Do(t_number*ptr){Do(*ptr);}
void Do(t_scope*ptr){Do(*ptr);}
template<class TYPE>void Do(vector<TYPE>&arr){for(auto&ex:arr)Do(ex);}
void Do(t_term&ref){
auto*ptr=ref.value.get();
ptr->Use(*this);
}
void Do(t_number&ref){
rv=t_rv{"imm",ref.value};
v=std::stod(ref.value);
}
void Do(t_scope&ref){
Do(ref.value);
}
void Do(t_divmul&ref){
Do(ref.first);
auto cur_rv=rv;
auto cur=v;
for(auto&ex:ref.arr){
Do(ex.expr);
if("/"==ex.oper)cur/=v;
if("*"==ex.oper)cur*=v;
if("/"==ex.oper)cur_rv=div(cur_rv,rv);
if("*"==ex.oper)cur_rv=mul(cur_rv,rv);
}
v=cur;
rv=cur_rv;
}
void Do(t_addsub&ref){
Do(ref.first);
auto cur_rv=rv;
auto cur=v;
for(auto&ex:ref.arr){
Do(ex.expr);
if("+"==ex.oper)cur+=v;
if("-"==ex.oper)cur-=v;
if("+"==ex.oper)cur_rv=add(cur_rv,rv);
if("-"==ex.oper)cur_rv=sub(cur_rv,rv);
}
v=cur;
rv=cur_rv;
}
struct t_rv{
string type;
string value;
string get_reg()const{
auto t=split(value,"\n");
if(t.back().empty())t.pop_back();
auto b=t.back();
return split(b,"=")[0];
}
int get_reg_id()const{
return std::stoi(split(get_reg(),"\2")[0].substr(1));
}
};
t_rv rv_do(string cmd,t_rv a,t_rv b){
int reg_id=0;
auto reg=[](int reg_id){return "\1"+std::to_string(reg_id)+"\2";};
auto alloc_reg=[&](){return reg(reg_id++);};
auto foo=[&](const t_rv&a){
if(a.type!="imm")return a;
auto reg=alloc_reg();
return t_rv{"asm",reg+"="+a.value};
};
auto fix=[&](const t_rv&a,int bef,int aft){
return t_rv{"asm",join(split(a.value,reg(bef)),reg(aft))};
};
string out;
auto ra=foo(a);auto a_id=ra.get_reg_id();reg_id=a_id+1;
auto rb=foo(b);auto b_id=rb.get_reg_id();reg_id=std::max(a_id+1,b_id+1);
for(int i=0;i<=b_id;i++)if(i<=a_id)if(rb.value.find(reg(i))!=std::string::npos)rb=fix(rb,i,reg_id++);
out+=ra.value+"\n";
out+=rb.value+"\n";
out+=alloc_reg()+"="+cmd+"("+ra.get_reg()+","+rb.get_reg()+")\n";
return t_rv{"asm",out};
}
t_rv div(t_rv a,t_rv b){
return rv_do("div",a,b);
}
t_rv mul(t_rv a,t_rv b){
return rv_do("mul",a,b);
}
t_rv add(t_rv a,t_rv b){
return rv_do("add",a,b);
}
t_rv sub(t_rv a,t_rv b){
return rv_do("sub",a,b);
}
real v=0;
t_rv rv;
};
};
void main_2021(IEnvRTTI&Env){
//{Sys$$<t_simple_calc>::GetRTTI(Env);};
t_simple_calc::t_addsub ast;
string inp;
std::cin>>inp;
string input=inp;//"100+2*(10+1*2)+30/2-15-16-3/1+3.14/2.5*1-1000";
auto ok=load_obj(Env,ast,input);
int gg=1;
t_simple_calc_evalutor::t_go go;
go.Do(ast);
std::cout<<"Result: "<<go.v<<std::endl<<std::endl;
string output=join(split(join(split(go.rv.value,"\1"),"r"),"\2"),"");
std::cout<<output<<std::endl;
int gg2=2;
}
Код обхода дерева и вычисления результата — это очень наглядный и профессионально сделанный пример реализации интерпретатора для простого калькулятора с генерацией промежуточного кода (ассемблероподобного).
Разбор кода обхода AST и вычисления результата
Структура t_simple_calc_evalutor
Расширяем базовый парсер t_simple_calc
, добавляя функциональность вычисления выражения:
struct t_simple_calc_evalutor : t_simple_calc {
typedef t_simple_calc t_ast;
struct t_go : i_term_visitor {
// Реализация обхода различных узлов AST
};
};
t_go
— это посетитель, реализующий обход дерева выражения и вычисление значения.
Основные методы обхода
Обход термов и полиморфных узлов
void Do(t_term& ref) {
auto* ptr = ref.value.get();
ptr->Use(*this);
}
Вы вызывам метод Use
у конкретного типа узла, обеспечивая полиморфизм.
Обработка чисел
void Do(t_number& ref) {
rv = t_rv{"imm", ref.value};
v = std::stod(ref.value);
}
Здесь парсим строковое представление числа в double
и сохраняете его.
Обработка скобок
void Do(t_scope& ref) {
Do(ref.value);
}
Просто рекурсивно обрабатываем выражение внутри скобок.
Обработка операций умножения и деления
void Do(t_divmul& ref) {
Do(ref.first);
auto cur_rv = rv;
auto cur = v;
for (auto& ex : ref.arr) {
Do(ex.expr);
if ("/" == ex.oper) cur /= v;
if ("*" == ex.oper) cur *= v;
cur_rv = ("/" == ex.oper) ? div(cur_rv, rv) : mul(cur_rv, rv);
}
v = cur;
rv = cur_rv;
}
Сначала вычисляем первый операнд, затем последовательно применяем операции из массива
arr
.Параллельно формируем промежуточный код в
rv
.Обработка операций сложения и вычитания
Аналогично, но с операциями +
и -
.
Промежуточный код (структура t_rv и методы add, sub, mul, div)
t_rv
хранит тип результата (imm
— immediate,asm
— ассемблероподобный код) и строковое представление.Методы
add
,sub
,mul
,div
генерируют код с регистрами, используя внутреннюю логику выделения и замены регистров.
Это отличное дополнение, показывающее, как можно не только вычислять значение, но и генерировать промежуточный код — важный этап для компиляторов и оптимизаторов.
Пример запуска из main_2021
t_simple_calc::t_addsub ast;
string input;
std::cin >> input;
auto ok = load_obj(Env, ast, input);
t_simple_calc_evalutor::t_go go;
go.Do(ast);
std::cout<<"Result: "<<go.v << std::endl;
std::cout<<join(split(join(split(go.rv.value,"\1"),"r"),"\2"),"")<<std::endl;
Загружаем AST из входной строки.
Запускаем обход и вычисление.
Выводим результат и сгенерированный промежуточный код.
Как парсить более сложные выражения с большим количеством операторов?
Да точно также! Просто набрасываем ещё кучу уровней и пошло поехало. Для того чтобы не ошибиться и не заниматься этим вручную просто пишем скрипт на js который создаёт все необходимые лексеры каждого уровня за нас:
var pad2=num=>(num<10?"0"+num:""+num);
return(
`+,-,!,~
*,/,%
+,-
<<,>>
<,<=,>,>=
==,!=
&
^
|
&&
||`.split("\r").join("").split("\n").map((ops,i)=>{
var e="t_lev"+pad2(i==1?3:i+3);
var n="t_lev"+pad2(i+4);
if(!i){
return `t_lev03{
string oper=any_str_from_vec(split(`+JSON.stringify(ops)+`,","))?;
TAutoPtr<i_expr> expr;
}`;
}
var oa=ops.split(",");
if(oa.length==1){
return n+`{
t_oper{"`+oa[0]+`" inline static const string value="`+oa[0]+`";}
t_item{t_oper oper; `+e+` expr;}
`+e+` expr;
vector<t_item> arr?;
}`;
}
return n+`{
t_oper{string value=any_str_from_vec(split("`+ops+`",","));}
t_item{t_oper oper;`+e+` expr;}
`+e+` expr;
vector<t_item> arr?;
}`;
}).join("\n"));
Вот что мы получаем на выходе:
t_lev03{
string oper=any_str_from_vec(split("+,-,!,~",","))?;
TAutoPtr<i_expr> expr;
}
t_lev05{
t_oper{string value=any_str_from_vec(split("*,/,%",","));}
t_item{t_oper oper;t_lev03 expr;}
t_lev03 expr;
vector<t_item> arr?;
}
t_lev06{
t_oper{string value=any_str_from_vec(split("+,-",","));}
t_item{t_oper oper;t_lev05 expr;}
t_lev05 expr;
vector<t_item> arr?;
}
t_lev07{
t_oper{string value=any_str_from_vec(split("<<,>>",","));}
t_item{t_oper oper;t_lev06 expr;}
t_lev06 expr;
vector<t_item> arr?;
}
t_lev08{
t_oper{string value=any_str_from_vec(split("<,<=,>,>=",","));}
t_item{t_oper oper;t_lev07 expr;}
t_lev07 expr;
vector<t_item> arr?;
}
t_lev09{
t_oper{string value=any_str_from_vec(split("==,!=",","));}
t_item{t_oper oper;t_lev08 expr;}
t_lev08 expr;
vector<t_item> arr?;
}
t_lev10{
t_oper{"&" inline static const string value="&";}
t_item{t_oper oper; t_lev09 expr;}
t_lev09 expr;
vector<t_item> arr?;
}
t_lev11{
t_oper{"^" inline static const string value="^";}
t_item{t_oper oper; t_lev10 expr;}
t_lev10 expr;
vector<t_item> arr?;
}
t_lev12{
t_oper{"|" inline static const string value="|";}
t_item{t_oper oper; t_lev11 expr;}
t_lev11 expr;
vector<t_item> arr?;
}
t_lev13{
t_oper{"&&" inline static const string value="&&";}
t_item{t_oper oper; t_lev12 expr;}
t_lev12 expr;
vector<t_item> arr?;
}
t_lev14{
t_oper{"||" inline static const string value="||";}
t_item{t_oper oper; t_lev13 expr;}
t_lev13 expr;
vector<t_item> arr?;
}
Тут мы для некоторых уровней вставляем С++ код объявления заинлайненой константы. Это очень удобно и это единственный вариант когда я одобряю вставку дополнительного С++ кода в лексеры. Так то это ужасно плохая практика.
Дальше пишем вот такой код в интерпретаторе/трансляторе/обходчике и у нас всё чётко - никаких проблем(начинайте смотреть с DoLevel):
template<class TYPE>
void exprUse(TYPE&expr){
Do(&expr);
}
//template<class TYPE>
void exprUse(TAutoPtr<t_simple_stat_lex::i_expr>&expr){
QapAssert(expr);
expr->Use(*this);
}
void call_anyoper(vector<t_expr_value>&values,const string&oper){
auto params=get_types_from_values(values);
t_cmd_dev<t_oper_stat> cmddev(values,params,oper);
bool ok=cmddev.main(dev);
if(ok)return;
dev.push_frame();
weak_call_xxxx<t_oper>(dev,values,oper);
QapAssert(dev.isRet());
dev.pop_frame();
}
void call_binoper(const string&oper,t_expr_value&&bef,t_expr_value&&aft){
dev.push();
vector<t_expr_value> values;
values.push_back(std::move(bef));
values.push_back(std::move(aft));
call_anyoper(values,oper);
dev.pop();
}
void call_oneoper(const string&oper,t_expr_value&&value){
dev.push();
vector<t_expr_value> values;
values.push_back(std::move(value));
call_anyoper(values,oper);
dev.pop();
}
template<class TYPE>
void DoLevel(TYPE*p)
{
exprUse(p->expr);
if(dev.isErr())return;
auto&arr=p->arr;
if(arr.empty())return;
t_expr_value buff=std::move(dev.expr_buff);
for(int i=0;i<arr.size();i++)
{
auto&ex=arr[i];
exprUse(ex.expr);
if(dev.isErr())return;
const string&oper=ex.oper.value;
call_binoper(oper,std::move(buff),std::move(dev.expr_buff));
if(dev.isErr())return;
buff=std::move(dev.expr_buff);
dev.expr_buff.value=nullptr;
}
dev.expr_buff=std::move(buff);
}
void Do(t_lev03*p){
exprUse(p->expr);
if(dev.isErr())return;
const auto&oper=p->oper;
if(oper.empty())return;
call_oneoper(oper,std::move(dev.expr_buff));
}
void Do(t_lev05*p){DoLevel(p);}
void Do(t_lev06*p){DoLevel(p);}
void Do(t_lev07*p){DoLevel(p);}
void Do(t_lev08*p){DoLevel(p);}
void Do(t_lev09*p){DoLevel(p);}
void Do(t_lev10*p){DoLevel(p);}
void Do(t_lev11*p){DoLevel(p);}
void Do(t_lev12*p){DoLevel(p);}
void Do(t_lev13*p){DoLevel(p);}
void Do(t_lev14*p){DoLevel(p);}
Данный код реализует обход уровней лексера с учётом приоритетов операторов через единый шаблонный метод DoLevel
, что минимизирует дублирование логики для разных уровней. Использование шаблонных функций и централизованного вызова call_binoper
и call_oneoper
позволяет эффективно управлять стеком вызовов и состоянием вычислений в dev
. Такой подход обеспечивает модульность, упрощает расширение грамматики новыми операторами и улучшает сопровождение кода за счёт чёткого разделения обязанностей между обходом AST и генерацией промежуточных команд. Это решение соответствует принципам чистой архитектуры и облегчает внедрение новых языковых конструкций в парсер.
Полное описание всех не упомянутых go_* методов
"go_* методы" - это то во что превращается код описания полей вида:
t_some_lexer field=minor<t_major>(); // заменяется на dev.go_minor<t_major>(field);
// или
string var=str<t_some_lexer>(); // это заменяется на dev.go_str<t_some_lexer>(var);
// или
string var=any("?*"); // это заменяется на dev.go_any(var,"?*");
go_minor<t_major>(t_minor&)
- этот метод нужен для того чтобы понижать приоритет минора и давать возможность отработать мажору первым в вышестоящем лексере.
go_str<t_lexer>(string&)
- этот метод нужен чтобы разобрать лексером t_lexer
лексему, а затем сохранить её в строку, чтобы потом удобно ей пользоваться как строкой, а не неудобной веткой/листом.
go_without<t_lexer>(string&)
- этот метод нужен чтобы прочитать всё в строку пока t_lexer
ничего не может понять/разобрать. очень дорогой метод.
go_vec(vector<t_lexer>&arr,const string&sep)
- УГ. не нужен. сами изучайте если интересно.
go_auto(t_lexer&)
- ТОП1 метод, настолько крут что используется для всех полей без инициализатора по умолчанию.
Инструкция по установке/запуску/использованию
Как установить/запустить QapGen?
git clone https://github.com/adler3d/QapGen
cd QapGen/stable
chmod +x build.sh
./build.sh
cd Release
./QapGen.elf your_grammar_file.qapdsl.hpp dontoptimize_polymorphs > output.inl
Далее если всё отработало успешно, то подключаете output.inl
к своему проекту.
Свой проект можете сделать из простого калькулятор расположенного по адресу:
https://github.com/adler3d/QapGen/tree/master/src/SimpleCalc
Для этого надо открыть файл:
QapGen/src/SimpleCalc/SimpleCalc/SimpleCalc.cpp
И в нём заменить #include "t_simple_calc.inl"
на #include "output.inl"
output.inl
нужно положить рядом с SimpleCalc.cpp
Далее надо удалить класс t_simple_calc_evalutor
и в main_2021
довести код до рабочего. Для этого надо оттуда всё убрать и написать такой код:
string input="your_script_or_code_or_text";
t_your_root_lexer root_lexer;
auto ok=load_obj(root_lexer,input);
if(!ok)return;
// далее используя посетителей обходим дерево хранящееся в root_lexer
Далее надо собрать/скомпилировать SimpleCalc.cpp
и запустить прогу:
g++ -O2 -std=c++17 SimpleCalc.cpp -o SimpleCalc.elf
./SimpleCalc.elf
Для тех кто под Windows
есть SimpleCalc.sln
для MSVS2017
и build.bat
в QapGen/src/SimpleCalc
Разбор t_poly_tool — инструмента разрешения конфликтов у полиморфных лексеров
Краткое описание
t_poly_tool
— это мощный инструмент для управления конфликтами приоритетов в системе полиморфных лексеров. Он строит граф зависимостей между типами, проверяет наличие циклов и с помощью топологической сортировки формирует корректный порядок обхода. Это позволяет надёжно разрешать конфликты приоритетов и выбирать правильную реализацию при парсинге. Интеграция с шаблоном go_poly
обеспечивает динамический выбор подходящего варианта обхода, что значительно упрощает поддержку и расширение грамматики.
Хотеть больше подробностей
Общая идея
t_poly_tool
— это конфигурационный инструмент, который управляет зависимостями и приоритетами между различными типами (семействами) в системе лексеров. Он:
Загружает и сохраняет конфигурацию из файла
config.cfg
.Хранит информацию о типах, их связях и событиях, которые отражают зависимости между ними.
...
...
Ключевые компоненты
1. Загрузка и сохранение конфигурации
Метод
get()
— синглтон, который загружает конфиг из файла, либо создаёт новый, если файл отсутствует.Метод
save_doc()
— сохраняет текущую конфигурацию обратно в файл.
2. Управление списком типов и событий
find()
— поиск или создание записи по имени типа.get_base_arr()
— получение и синхронизация базового массива элементов с входными данными, с сохранением в конфиг при необходимости.get_mass()
— получение индекса типа в массиве по имени.
3. Структуры для топологической сортировки и проверки циклов
t_point
— узел графа зависимостей с входящими и исходящими рёбрами, флагом посещения и группой.-
t_points
— коллекция точек с методами:load_points()
— инициализация точек по списку имён типов.load_edges_from_events()
— построение рёбер графа из событий зависимостей.isCyclical()
— проверка на наличие циклов в графе с помощью обхода в ширину.toList()
— построение топологического порядка обхода точек с учётом зависимостей.
4. Управление событиями и перестройка порядка
list_apply_events()
— статический метод, который принимает список типов и событий, проверяет циклы и возвращает корректный порядок.remake()
— перестраивает порядок типов в массиве с учётом событий и сохраняет конфигурацию.
5. Шаблон go_poly — основной механизм обхода полиморфных реализаций
Хранит массив результатов обхода (
out_arr
), ссылку наdev
(интерфейс устройства/парсера), ссылку на целевой объектref
и вспомогательные данные.Метод
go_for<T>()
— пытается выполнить обход конкретного типаT
, сохраняя результат и позицию.Метод
main()
— выбирает наиболее подходящий вариант обхода из множества, используя информацию из конфигурации (t_poly_tool
), сортирует по «массе» (приоритету) и позиции, применяет топологический порядок, и устанавливает результат вref
.
Больше техно-жести!!! А ну покажи код!
struct t_poly_tool:public t_config_2013{
//struct t_lex{
// std::function<void(t_poly_tool*)> func;
// CharMask m;
//};
t_doc doc;
static t_poly_tool&get(/*IEnvRTTI&Env*/){
static const string fn="config.cfg";
static t_poly_tool tool;
static t_doc&doc=tool.doc;
static QapClock clock;
static bool first=true;
if(first){clock.Start();/*doc.lines.reserve(2048);*/}
if(clock.MS()<360*1000)if(!first)return tool;
first=false;
clock.Stop();clock.Start();
CrutchIO IO;
bool ok=IO.LoadFile(fn);
if(ok){t_doc tmp;doc=std::move(tmp);/*doc.lines.reserve(2048);*/}
if(!ok){
IO.mem.clear();
QapAssert(save_obj(/*Env,*/doc,IO.mem));
IO.SaveFile(fn);
return tool;
}
clock.Stop();clock.Start();
QapAssert(load_obj(/*Env,*/doc,IO.mem));clock.Stop();clock.Start();
real time=clock.MS();
clock.Stop();clock.Start();
//doc.lines.reserve(2048);
return tool;
}
void save_doc(/*IEnvRTTI&Env,*/const string&fn){
CrutchIO IO;
QapAssert(save_obj(/*Env,*/this->doc,IO.mem));
IO.SaveFile(fn);
}
public:
t_line&find(const string&type){
auto&arr=doc.lines;
for(int i=0;i<arr.size();i++)
{
auto&ex=arr[i];
if(ex.head==type)return ex;
}
arr.push_back(t_line());
auto&back=arr.back();
back.head=type;
return back;
}
template<class TYPE>
vector<t_item>&get_base_arr(/*IEnvRTTI&Env,*/const string&basetype,vector<TYPE>&inp,const vector<string>&types){
auto&base=find(basetype);
auto&arr=base.arr;
if(arr.size()==inp.size())return arr;
if(arr.size()){
if(arr.size()!=types.size())QapNoWay();
for(auto&ex:arr){
QapAssert(qap_includes(types,ex.type));
}
return arr;
}
QapAssert(base.arr.empty());
arr.resize(inp.size());
for(int i=0;i<arr.size();i++){
auto&ex=arr[i];
ex.type=inp[i].info;
}
save_doc(/*Env,*/"config.cfg");
return arr;
}
static int get_mass(const vector<t_item>&arr,const string&type){
for(int i=0;i<arr.size();i++){
auto&ex=arr[i];
if(ex.type==type)return i;
}
QapAssert(false);
return -1;
}
public:
struct t_point{
int id;
string name;
vector<int> inp;
vector<int> out;
bool passed;
int group;
};
public:
template<class TYPE>
struct t_out_rec{
const char*info;
TAutoPtr<TYPE> object;
int pos;
int mass;
t_out_rec(){pos=-1;mass=-1;}
t_out_rec(t_out_rec&&ref){info=std::move(ref.info);object=std::move(ref.object);pos=ref.pos;mass=ref.mass;}
};
public:
struct t_points{
vector<t_point> arr;
t_point&find(const string&name){
for(int i=0;i<arr.size();i++){
auto&ex=arr[i];
if(name==ex.name)return ex;
}
QapAssert(false);
return *(t_point*)nullptr;
}
void set_passed(bool value){
for(int i=0;i<arr.size();i++){
auto&ex=arr[i];
ex.passed=value;
}
}
void load_points(const vector<string>&inp){
arr.resize(inp.size());
for(int i=0;i<inp.size();i++){
auto&ex=inp[i];
auto&p=arr[i];
p.name=ex;
p.id=i;
p.passed=false;
p.group=-1;
}
}
void load_edges_from_events(const vector<t_event>&events){
for(int i=0;i<events.size();i++){
auto&ex=events[i];
auto&pa=find(ex.A.type);
auto&pb=find(ex.B.type);
pa.inp.push_back(pb.id);
pb.out.push_back(pa.id);
}
}
struct t_wave{
t_points&points;
vector<int> prev;
vector<int> next;
int first_id;
int group;
bool result;
void update(int id)
{
auto&ex=points.arr[id];
ex.group=group;
ex.passed=true;
auto&arr=ex.out;
for(int i=0;i<arr.size();i++){
auto&id=arr[i];
auto&ex=points.arr[id];
QapAssert(id!=first_id);
if(first_id==id)result=true;
next.push_back(ex.id);
}
}
void run()
{
points.set_passed(false);
next.push_back(first_id);
for(int iter=0;!next.empty();iter++)
{
prev=std::move(next);
for(int i=0;i<prev.size();i++){
auto&id=prev[i];
auto&ex=points.arr[id];
if(ex.passed)continue;
update(ex.id);
}
}
}
};
bool isCyclical(){
t_wave wave={*this};
wave.result=false;
string view;view.resize(arr.size());
for(int i=0;i<arr.size();i++){
auto&ex=arr[i];
for(int i=0;i<arr.size();i++){view[i]=arr[i].group<0?'N':'0'+arr[i].group;}
if(ex.group>=0)continue;
wave.first_id=ex.id;
wave.group=ex.id;
wave.run();
}
return wave.result;
}
vector<string> toList(){
vector<string> out;
set_passed(false);
vector<int> heads;
for(int i=0;i<arr.size();i++){
auto&ex=arr[i];
if(!ex.out.empty())continue;
heads.push_back(ex.id);
}
vector<int> next;
for(int iter=0;!heads.empty();iter++)
{
vector<int> next_heads;
for(int i=0;i<heads.size();i++){
auto&id=heads[i];
auto&ex=arr[id];
auto&inp=ex.inp;
for(int i=0;i<inp.size();i++){
auto&id=inp[i];
auto&ex=arr[id];
if(ex.passed)continue;
bool found=false;
for(int i=0;i<next_heads.size();i++)if(next_heads[i]==ex.id)found=true;
if(found)continue;
next_heads.push_back(ex.id);
}
}
for(int i=0;i<next_heads.size();i++){
auto&id=next_heads[i];
auto&ex=arr[id];
auto&out=ex.out;
bool ok=true;
for(int i=0;i<out.size();i++){
auto&id=out[i];
for(int i=0;i<next_heads.size();i++){
if(next_heads[i]==id)ok=false;
}
}
if(!ok)continue;
next.push_back(ex.id);
}
for(int i=0;i<next.size();i++){
auto&id=next[i];
auto&ex=arr[id];
ex.passed=true;
}
std::sort(heads.begin(),heads.end());
for(int i=0;i<heads.size();i++){
auto&id=heads[i];
auto&ex=arr[id];
out.push_back(ex.name);
}
heads=std::move(next);
}
return out;
}
};
public:
typedef t_config_2013::t_event t_event;
typedef t_config_2013::t_item t_item;
static vector<string> list_apply_events(const vector<string>&arr,vector<t_event>&events){
t_points points;
points.load_points(arr);
points.load_edges_from_events(events);
bool ok=!points.isCyclical();
QapAssert(ok);
auto list=points.toList();
return list;
}
void remake(vector<t_item>&points,vector<t_event>&events){
vector<string> arr;
arr.resize(points.size());
for(int i=0;i<arr.size();i++){
arr[i]=points[i].type;
}
auto out=list_apply_events(arr,events);
QapAssert(out.size()==points.size());
for(int i=0;i<arr.size();i++){
points[i].type=out[i];
}
}
public:
template<class TYPE>
struct go_poly{
vector<t_out_rec<TYPE>>&out_arr;
i_dev&dev;
TAutoPtr<TYPE>&ref;
t_fallback&scope;
int&count;
int&first_id;
const string&strbasetype;
//IEnvRTTI&Env;
struct t_lex{
const char*pname=nullptr;
std::function<void(typename TYPE::t_poly_impl*)> func;
CharMask m;
};
template<class T>
void go_for(){
t_fallback scope(dev,__FUNCTION__);
T tmp;
scope.ok=tmp.go(dev);
t_out_rec<TYPE> rec;
static const string strtype=T::ProxySys$$::GetFullName();
rec.info=strtype.c_str();
if(scope.ok)
{
rec.object=make_unique<T>(std::move(tmp));
//auto*p=rec.object.get();
//*p=std::move(tmp);
if(!count)first_id=out_arr.size();
count++;
}
dev.getPos(rec.pos);
out_arr.push_back(std::move(rec));
scope.ok=false;
}
template<size_t N=0>
void main(array<t_lex,N>*plexs=nullptr)
{
typedef t_poly_tool::t_out_rec<TYPE> t_out_rec;
if(!count){scope.ok=false;return;}
auto use=[this](t_out_rec&ex){
QapAssert(ex.object);
ref=std::move(ex.object);
dev.setPos(ex.pos);
scope.ok=true;
};
if(count==1)
{
auto&ex=out_arr[first_id];
use(ex);
return;
}
#ifndef QAP_POLY_TOOL_DEBUG
auto id=QAP_MINVAL_ID_OF_VEC(out_arr,-ex.pos);
use(out_arr[id]);
return;
#endif
auto&tool=t_poly_tool::get(/*Env*/);
static vector<string> types;
if(types.empty()&&plexs){
for(auto&ex:*plexs)types.push_back(ex.pname);
}
auto&arr=tool.get_base_arr(/*Env,*/strbasetype,out_arr,types);
vector<t_out_rec> out;
auto update_mass=[&](){
for(int i=0;i<out.size();i++){
auto&ex=out[i];
ex.mass=t_poly_tool::get_mass(arr,ex.info);
}
};
for(int i=0;i<out_arr.size();i++){
auto&ex=out_arr[i];
if(!ex.object)continue;
out.push_back(std::move(ex));
}
update_mass();
vector<int> idarr;idarr.resize(out.size());
for(int i=0;i<out.size();i++){idarr[i]=i;}
if(true)
{
auto comp_pos=[&out](const int&a,const int&b)->bool{return out[a].pos>out[b].pos;};
std::sort(std::begin(idarr),std::end(idarr),comp_pos);
QapAssert(out[idarr[0]].pos>out[idarr[1]].pos);
int fix_count=0;
for(int i=1;i<out.size();i++){
auto&pa=idarr[i-1];
auto&pb=idarr[i-0];
auto&a=out[pa];
auto&b=out[pb];
//if(a.mass<b.mass)continue;
auto&base=tool.find(strbasetype);
auto&events=base.events;
auto find_event=[&events](const string&a,const string&b)->bool{
for(int i=0;i<events.size();i++){
if(events[i].A.type!=a)continue;
if(events[i].B.type!=b)continue;
return true;
}
return false;
};
bool flag_ab=find_event(a.info,b.info);
bool flag_ba=find_event(b.info,a.info);
QapAssert(!flag_ba);
if(flag_ab)continue;
events.push_back(t_config_2013::t_event());
auto&back=events.back();
back.time=cur_date_str();
back.A.pos=IToS(a.pos);
back.A.type=a.info;
back.B.pos=IToS(b.pos);
back.B.type=b.info;
fix_count++;
}
if(fix_count){
auto&base=tool.find(strbasetype);
auto&events=base.events;
tool.remake(base.arr,events);
tool.save_doc(/*Env,*/"config.cfg");
update_mass();
}
}
auto comp_func=[&out](const int&a,const int&b)->bool{return out[a].mass<out[b].mass;};
std::sort(std::begin(idarr),std::end(idarr),comp_func);
QapAssert(out[idarr[0]].mass<out[idarr[1]].mass);
for(int i=1;i<out.size();i++){
auto&pa=idarr[i-1];
auto&pb=idarr[i-0];
auto&a=out[pa];
auto&b=out[pb];
if(a.pos>b.pos)continue;
string msg="wrong mass for:\n";
msg+="a.info = "+string(a.info)+"\n";
msg+="a.pos = "+IToS(a.pos)+"\n";
msg+="b.info = "+string(b.info)+"\n";
msg+="b.pos = "+IToS(b.pos)+"\n";
QapDebugMsg(msg);
}
use(out[idarr[0]]);
}
};
};
Разбор кода, оптимизирующего код полиморфных лексеров
Технический разбор функции lexer2vecofchar
Назначение
Функция lexer2vecofchar
принимает объект лексера и возвращает строку символов (string
), которая представляет собой множество ожидаемых символов для данного лексера. Эта строка используется для оптимизации разбора — чтобы быстро понимать, какие символы допустимы на текущем уровне.
Основная логика
Обработка интерфейсных лексеров
if(lexer.is_interface){
return polylexer2vecofchar(lexer);
}
Если лексер — интерфейсный (полиморфный), делегируем обработку специализированной функции polylexer2vecofchar
.
Обход команд лексера
for(auto& c : lexer.cmds){
auto& fields = lexer.farr;
t_struct_cmd cmd;
QapAssert(load_obj(cmd, c));
...
}
Каждая команда лексера (
cmd
) загружается из сериализованного представления.Анализируем функцию
cmd.func.value
— имя методаgo_*
, который реализует логику разбора.
Обработка конкретных
go_*
методов
go_const
— возвращает один первый символ, который лексер ожидает.go_any
/go_any_char
— собирает множество символов, которые лексер может принять, используя функциюcollect_expected_chars
.go_any_str_from_vec
— аналогично, но для строк из вектора.go_auto
— рекурсивно обрабатывает вложенные лексеры, выдирая их типы из описания полей. Грамотно обрабатывает случаи с (TAutoPtr
,vector
).go_str
/go_vec
— работает с шаблонными параметрами, извлекая вложенные лексеры и объединяя их множества символов.go_minor
— реализует понижение приоритета вложенного лексера и объединяет множества символов с учётом исключений. // пока без учёта исключений ибо сложно.
Оптимизация и объединение множеств символов
Используется множество символов (
string out
), которое постепенно расширяется символами из вложенных лексеров.Проверки и ассерты гарантируют корректность типов и структур.
Обработка ошибок и неподдерживаемых функций
Если встречается неизвестный go_*
метод — выводится отладочное сообщение.
Посмотреть исходники lexer2vecofchar
string lexer2vecofchar(const t_lexer&lexer)const{
if(lexer.is_interface){
return polylexer2vecofchar(lexer);
}
string out;
for(auto&c:lexer.cmds){
auto&fields=lexer.farr;
t_struct_cmd cmd;
QapAssert(load_obj(cmd,c));
auto*p=t_struct_cmd_mode::UberCast(cmd.mode.get());
//QapAssert(p);
bool opt=p?p->body=='O':false;
auto fn=cmd.func.value;
if(fn=="go_const"){
QapAssert(cmd.params.arr.size()==1);
auto s=cmd.params.arr[0].body;
QapAssert(s.size()>2);
//TAutoPtr<vector<i_str_item>> si;//load_obj(si,s);
auto bs=BinString::fullCppStr2RawStr(s);
out.push_back(bs[0]);
QapAssert(!opt);
return out;
int gg=1;
}
auto bad_type_checker=[](auto&lex,const string&type){
string s=lex2str(*lex.get());
return s.substr(0,type.size())==type;
};
if(fn=="go_any"||fn=="go_any_char"){
QapAssert(cmd.params.arr.size()==2);
auto&cpa0b=cmd.params.arr[0].body;
auto&f=find_field(lexer,cpa0b);
if(fn=="go_any")QapAssert(bad_type_checker(f.type,"string"));
if(fn=="go_any_char")QapAssert(bad_type_checker(f.type,"char"));
auto p0=cmd.params.arr[0].body;
auto p1=cmd.params.arr[1].body;// выражение типа gen_dips("09")+"str"+"other_str"
t_cmd_param p1p;
QapAssert(load_obj(p1p,p1));
collect_expected_chars(p1p,out);
if(opt)continue;
return out;
}
if(fn=="go_any_str_from_vec"){
QapAssert(cmd.params.arr.size()==2);
auto&cpa0b=cmd.params.arr[0].body;
auto&f=find_field(lexer,cpa0b);
QapAssert(bad_type_checker(f.type,"string"));
auto p0=cmd.params.arr[0].body;
auto p1=cmd.params.arr[1].body;// выражение типа split("0,9",",")
t_cmd_param p1p;
QapAssert(load_obj(p1p,p1));
collect_expected_chars(p1p,out,true);
if(opt)continue;
return out;
}
if(fn=="go_end"){
QapAssert(cmd.params.arr.size()==1);
auto&cpa0b=cmd.params.arr[0].body;
auto&f=find_field(lexer,cpa0b);
auto p0=cmd.params.arr[0].body;
auto p1=cmd.params.arr[1].body;
auto p1s=BinString::fullCppStr2RawStr(p1);
if(p1s.size()==1)QapDebugMsg("use go_any(dip_inv("+p1+")) instead of go_end("+p1+")");
QapDebugMsg("go_end with str - under construction");
}
typedef t_cppcore::t_varcall_expr::t_var t_var;
auto tp2var=[](const auto&tp,t_var&out){
auto*ptp=t_cppcore::t_varcall_expr::t_template_part::UberCast(tp.get());
string s;save_obj(ptp->expr,s);
QapAssert(load_obj(out,s));
};
struct t_for_autoptr_r{
bool con=false;
bool ret=false;
};
auto for_autoptr=[&](const t_var&var,const string&tpn)->t_for_autoptr_r{
if(tpn!="TAutoPtr")return {};
t_var inner_param;
tp2var(var.tp,inner_param);
auto inner_pn=inner_param.name.value;
vector<string> ftn;
if(inner_param.tp){
auto*p=t_cppcore::t_varcall_expr::t_dd_part::UberCast(inner_param.tp.get());
QapAssert(p);
auto&arr=p->arr;
for(auto&ex:arr){
ftn.push_back(ex.name.value);
}
}
string ln=inner_pn+(ftn.size()?"::":"")+join(ftn,"::");
auto*pl_inner=find_lexer_by_name_but_relative(ln,lexer);
QapAssert(pl_inner);
auto nested_chars=lexer2vecofchar(*pl_inner);
QapAssert(!nested_chars.empty());
out+=nested_chars;
if(!opt)return {false,true};
return {true,false};
};
if(fn=="go_auto"){
QapAssert(cmd.params.arr.size()==1);
auto&cpa0b=cmd.params.arr[0].body;
auto&f=find_field(lexer,cpa0b);
auto*pvc=t_cppcore::t_varcall_expr::UberCast(f.type.get());
QapAssert(pvc);
auto&v=pvc->var;
if(v.name.value=="TAutoPtr"){
auto r=for_autoptr(v,v.name.value);
if(r.ret)return out;
if(r.con)continue;
}
if(v.name.value=="vector"){
QapAssert(v.tp);
t_var template_param;
tp2var(v.tp,template_param);
auto tpn=template_param.name.value;
QapAssert(tpn!="vector");
auto r=for_autoptr(template_param,tpn);
if(r.ret)return out;
if(r.con)continue;
auto*pl=find_lexer_by_name_but_relative(tpn,lexer);
QapAssert(pl);
out+=lexer2vecofchar(*pl);
if(!opt)return out;
continue;
}
auto*pl=find_lexer_by_name_but_relative(v.name.value,lexer);
QapAssert(pl);
out+=lexer2vecofchar(*pl);
if(!opt)return out;
continue;
}
if(fn=="go_str"||fn=="go_vec"){
QapAssert(cmd.params.arr.size()==1);
auto&cpa0b=cmd.params.arr[0].body;
auto&f=find_field(lexer,cpa0b);
auto*pvc=t_cppcore::t_varcall_expr::UberCast(f.type.get());
QapAssert(pvc);
auto&v=pvc->var;
if(fn=="go_str"&&v.name.value!="string")QapDebugMsg("go_str support only string type of field");
QapAssert(cmd.templ_params.size());
TAutoPtr<t_templ_params> tp;
QapAssert(load_obj(tp,cmd.templ_params));
QapAssert(tp->body.size());
TAutoPtr<t_type_templ_params> ttp;
QapAssert(load_obj(ttp,tp->body));
QapAssert(ttp);
QapAssert(ttp->first.body);
auto*ptit=t_meta_lexer::t_type_item_type::UberCast(ttp->first.body.get());
QapAssert(ptit);
if(ptit->type.value=="vector"||ptit->type.value=="TAutoPtr"){
QapAssert(ptit->param);
auto*ptta=t_meta_lexer::t_type_templ_angle::UberCast(ptit->param->body.get());
QapAssert(ptta);
QapAssert(ptta->params);
QapAssert(ptta->params->first.body);
auto*ptit2=t_type_item_type::UberCast(ptta->params->first.body.get());
QapAssert(ptit2);
if(ptit2->param){
QapAssert(ptit2->type.value=="TAutoPtr");
auto*ptta=t_meta_lexer::t_type_templ_angle::UberCast(ptit2->param->body.get());
QapAssert(ptta);
QapAssert(ptta->params);
QapAssert(ptta->params->first.body);
auto*ptit3=t_type_item_type::UberCast(ptta->params->first.body.get());
if(ptit3->param)QapDebugMsg("unexpected param:"+lex2str(ptit3->param)+"\nin:"+cmd.templ_params);
QapAssert(!ptit3->param);
auto*pl=find_lexer_by_name_but_relative(ptit3->type.value,lexer);
QapAssert(pl);
out+=lexer2vecofchar(*pl);
if(opt)continue;
return out;
}
auto*pl=find_lexer_by_name_but_relative(ptit2->type.value,lexer);
QapAssert(pl);
out+=lexer2vecofchar(*pl);
if(opt)continue;
return out;
}
QapAssert(!ptit->param);
auto*pl=find_lexer_by_name_but_relative(ptit->type.value,lexer);
QapAssert(pl);
out+=lexer2vecofchar(*pl);
if(opt)continue;
return out;
}
if(fn=="go_minor"){
QapAssert(cmd.params.arr.size()==1);
auto&cpa0b=cmd.params.arr[0].body;
auto&f=find_field(lexer,cpa0b);
auto*pvc=t_cppcore::t_varcall_expr::UberCast(f.type.get());
QapAssert(pvc);
auto&v=pvc->var;
if(v.name.value=="string")QapDebugMsg("go_diff dont support string type of field");
struct t_for_autoptr_r2{
bool con=false;
bool ret=false;
bool pass()const{return !con&&!ret;}
string out;
};
auto for_autoptr2=[&](const t_var&var,const string&tpn)->t_for_autoptr_r2{
if(tpn!="TAutoPtr")return {};
t_var inner_param;
tp2var(var.tp,inner_param);
auto inner_pn=inner_param.name.value;
auto*pl_inner=find_lexer_by_name_but_relative(inner_pn,lexer);
QapAssert(pl_inner);
auto nested_chars=lexer2vecofchar(*pl_inner);
QapAssert(!nested_chars.empty());
if(!opt)return {false,true,nested_chars};
return {true,false,nested_chars};
};
string our;bool template_lexer=false;
if(v.name.value=="TAutoPtr"){
auto r=for_autoptr2(v,v.name.value);
our+=r.out;
template_lexer=true;
//if(r.ret)return out;
//if(r.con)continue;
}
if(v.name.value=="vector"){
template_lexer=true;
QapAssert(v.tp);
t_var template_param;
tp2var(v.tp,template_param);
auto tpn=template_param.name.value;
QapAssert(tpn!="vector");
auto r=for_autoptr2(template_param,tpn);
our+=r.out;
if(r.pass()){
auto*pl=find_lexer_by_name_but_relative(tpn,lexer);
QapAssert(pl);
our+=lexer2vecofchar(*pl);
//if(!opt)return out;
//continue;
}
}
if(!template_lexer){
auto*pl=find_lexer_by_name_but_relative(v.name.value,lexer);
QapAssert(pl);
our+=lexer2vecofchar(*pl);
}
out+=our;
if(opt)continue;
return out;
// unreachable code now because of wrong hi-level semantic logic. minus is not acceptable in most complex cases
auto minus=[](const string&our,const string&major){
auto m=CharMask::fromStr(our,true);
auto e=CharMask::fromStr(major,true);
string out;
for(size_t i=0;i<256;i++)if(!e.mask[i]&&m.mask[i])out.push_back(static_cast<char>(i));
return out;
};
string major;
//---
QapAssert(cmd.templ_params.size());
TAutoPtr<t_templ_params> tp;
QapAssert(load_obj(tp,cmd.templ_params));
QapAssert(tp->body.size());
TAutoPtr<t_type_templ_params> ttp;
QapAssert(load_obj(ttp,tp->body));
QapAssert(ttp);
QapAssert(ttp->first.body);
auto*ptit=t_meta_lexer::t_type_item_type::UberCast(ttp->first.body.get());
QapAssert(ptit);
if(ptit->type.value=="vector"||ptit->type.value=="TAutoPtr"){
QapAssert(ptit->param);
auto*ptta=t_meta_lexer::t_type_templ_angle::UberCast(ptit->param->body.get());
QapAssert(ptta);
QapAssert(ptta->params);
QapAssert(ptta->params->first.body);
auto*ptit2=t_type_item_type::UberCast(ptta->params->first.body.get());
QapAssert(ptit2);
if(ptit2->param){
QapAssert(ptit2->type.value=="TAutoPtr");
auto*ptta=t_meta_lexer::t_type_templ_angle::UberCast(ptit->param->body.get());
QapAssert(ptta);
QapAssert(ptta->params);
QapAssert(ptta->params->first.body);
auto*ptit3=t_type_item_type::UberCast(ptta->params->first.body.get());
QapAssert(!ptit3->param);
auto*pl=find_lexer_by_name_but_relative(ptit3->type.value,lexer);
QapAssert(pl);
major+=lexer2vecofchar(*pl);
out+=minus(our,major);
if(opt)continue;
return out;
}
auto*pl=find_lexer_by_name_but_relative(ptit2->type.value,lexer);
QapAssert(pl);
major+=lexer2vecofchar(*pl);
out+=minus(our,major);
if(opt)continue;
return out;
}
QapAssert(!ptit->param);
auto*pl=find_lexer_by_name_but_relative(ptit->type.value,lexer);
QapAssert(pl);
major+=lexer2vecofchar(*pl);
out+=minus(our,major);
if(opt)continue;
return out;
}
QapDebugMsg("unsupported go_* method: "+fn);
int gg_cmds=1;
}
return out;
}
Почему это круто и важно для статьи
Глубокая интеграция с типовой системой — функция учитывает сложные типы (
TAutoPtr
,vector
), что показывает мощь и гибкость этой системы.Оптимизация разбора — формирование множества ожидаемых символов позволяет значительно ускорить парсинг, избегая лишних попыток.
Рекурсивный и полиморфный подход — лексеры могут содержать вложенные лексеры, и функция умеет корректно обходить эту структуру.
Чёткая и строгая проверка корректности — множество
QapAssert
и отладочных сообщений помогает быстро выявлять ошибки.
Краткое резюме
Функция lexer2vecofchar
— ключевой компонент оптимизации полиморфных лексеров, который строит множество допустимых символов для текущего лексера, учитывая вложенные структуры и типы. Это позволяет эффективно фильтровать входные данные на раннем этапе разбора, повышая производительность и надёжность парсера. Благодаря строгому контролю типов и рекурсивной обработке, функция обеспечивает корректную работу даже с самыми сложными грамматиками.
Советы и рекомендации по расширению и отладке грамматик
"Разделитель" как самый популярный лексер, нужно вставлять после каких-нибудь других
лексеров в списке полей вышестоящего лексера, а не впереди всех, т.к тогда возможны конфликты. Особенно если вышестоящий лексер полиморфный. Это касается не только "разделителя", а всех других опциональных лексеров.
особенно если в двух и более полиморфных лексера одного семейства вначале используется общий для них всех лексер/go_*метод.
Разбор кода t_class_def_fixer
Назначение
t_class_def_fixer
— это двойной посетитель (visitor), который проходит по структуре, описывающей грамматику QapDSL, и выполняет две ключевые модификации:
Заменяет символ
=>
(стрелочку) на:
(двоеточие) в описании лексеров.
Это упрощает и унифицирует синтаксис, делая его более читаемым и однородным.Удаляет встроенный C++ код, который идёт в конце описания лексеров, если он не содержит определённой строки.
Это позволяет очистить описание лексеров от лишнего кода, если он не нужен, и избежать потенциальных конфликтов.
Основные компоненты и методы
Наследование и макросы
struct t_class_def_fixer:
t_templ_sys_v04,
t_meta_lexer::i_target_item_visitor,
t_meta_lexer::t_target_struct::i_struct_impl::i_visitor
{
#define ADD(F)typedef t_meta_lexer::F F;
ADD(t_target_semicolon)
ADD(t_target_sep)
ADD(t_target_struct)
ADD(t_target_using)
ADD(t_target_typedef)
ADD(i_target_item)
#undef ADD
...
};
Класс наследует несколько интерфейсов и системных классов для обхода и модификации дерева грамматики.
Макрос
ADD
упрощает объявление типов из пространства имёнt_meta_lexer
, облегчая дальнейшую работу.
Универсальные методы обхода
template<class TYPE>
void Do(TYPE*p){if(p)Do(*p);}
template<class TYPE>
void Do(TYPE&r){r.Use(*this);}
template<class TYPE>
void Do(vector<TYPE>&arr){for(auto&ex:arr)Do(&ex);}
template<class TYPE>
void Do(vector<TAutoPtr<TYPE>>&arr){for(auto&ex:arr)if(ex)Do(*ex.get());}
template<class TYPE>
void Do(TAutoPtr<TYPE>&r){if(r)Do(*r.get());}
Универсальные шаблонные методы
Do
обеспечивают рекурсивный обход узлов и коллекций узлов дерева грамматики.Это стандартный приём для реализации паттерна посетителя, позволяющий легко расширять обход без дублирования кода.
Обработка тела с реализацией (t_body_impl)
void Do(t_body_impl&r)override{
if(cppcode_killer){
if(lex2str(r.c).find("inline static const string value=\"")==string::npos){
r.c=nullptr; // убиваем C++ код идущий в конце описания lexer`а
}
}
Do(r.nested);
}
Если включён флаг
cppcode_killer
, и в кодеr.c
нет строки"inline static const string value=\""
, то содержимоеr.c
удаляется.Таким образом, удаляется «лишний» C++ код, оставляя только важные константы.
Затем рекурсивно обрабатываются вложенные лексеры
r.nested
.
Замена стрелочки на двоеточие
void Do(t_target_struct::t_parent&r){
if(r.arrow_or_colon.size()) r.arrow_or_colon=":";
}
Если у родителя (
t_parent
) есть символarrow_or_colon
(который может быть=>
или:
), он заменяется на двоеточие:
.Это делает синтаксис похожим на С++ и исправляет эту досадную ошибку из QapDSLv1
Обход других узлов
void Do(t_target_struct&r)override{
Do(r.parent);
Do(r.body);
}
void Do(t_target_semicolon&r)override{}
void Do(t_target_sep&r)override{}
void Do(t_target_using&r)override{}
void Do(t_target_typedef&r)override{}
Для структур вызывается обход родителя и тела.
Для остальных типов — пустая реализация, так как модификации не требуются.
Основной метод запуска
string main(const string&data){
t_target tar;
auto fs=load_obj_full(tar,data);
if(!fs.ok){cerr<<fs.msg<<endl;return "fail";}
if(tar.arr.empty()){return "!target\n\n"+fs.msg;}
for(auto&ex:tar.arr){
auto*p=ex.get();
p->Use(*this);
}
string out;
save_obj(tar,out);
return out;
}
Загружает исходный QapDSL-код в объект
tar
.Если загрузка не удалась или пустой результат — возвращает ошибку.
Итерируется по всем элементам и применяет к ним посетителя (
Use(*this)
), вызывая модификации.Сохраняет изменённый объект обратно в строку и возвращает результат.
Итог
t_class_def_fixer
— компактный и эффективный инструмент для постобработки QapDSL-кода, который обеспечивает:
Унификацию синтаксиса через замену стрелочек на двоеточия.
Очистку описаний лексеров от лишнего C++ кода, что упрощает дальнейшую работу и снижает шум.
Гибкий и рекурсивный обход дерева грамматики с помощью шаблонных методов и паттерна посетителя.
Этот класс отлично демонстрирует, как можно аккуратно и безопасно модифицировать сложные структуры грамматик, сохраняя при этом читаемость и расширяемость кода.
Подробный разбор структуры t_qapdls_v1_to_v2, которая реализует конвертер кода QapDSL в QapDSLv2.
Общее назначение
Перевести синтаксис и семантику старой версии в новую, более короткую и удобную.
...
Profit!
Ключевые части и методы
Наследование и макросы
struct t_qapdls_v1_to_v2:
t_templ_sys_v04,
t_meta_lexer::i_target_item_visitor,
t_meta_lexer::t_target_struct::i_struct_impl::i_visitor,
t_meta_lexer::i_struct_cmd_xxxx::i_visitor
{
#define ADD(F)typedef t_meta_lexer::F F;
ADD(t_target_semicolon)
ADD(t_target_sep)
ADD(t_target_struct)
ADD(t_target_using)
ADD(t_target_typedef)
ADD(i_target_item)
ADD(t_struct_cmd_mode)
ADD(t_struct_cmd_anno)
#undef ADD
...
};
...
Обработка команд лексера (t_body_impl)
void Do(t_body_impl&r)override{
Do(r.nested);
if(!r.cmds)return;
auto&arr=r.cmds->arr;
vector<string> fields;
for(auto&ex:arr){
opt=false;
if(ex.body.mode)Do(ex.body.mode.get());
auto&f=ex.body.func.value;
QapAssert(f.size()>3&&f.substr(0,3)=="go_");
auto res=f.substr(3);
auto¶ms=ex.body.params.arr;
QapAssert(params.size());
string pad;
string pad2;
for(int i=0;i<lexers.size();i++)pad+=" ";
for(int i=1;i<lexers.size();i++)pad2+=" ";
if(res=="const"){
fields.push_back("\n"+pad+params[0].body+"\n"+pad2);
continue;
}
auto&src=find_field(r.arr,params[0].body);
if(res=="auto"){
src.qst=nullptr;
if(opt)load_obj(src.qst,"?");
fields.push_back("\n"+pad+lex2str(src)+"\n"+pad2);
continue;
}
vector<string> sparams;
for(int i=1;i<params.size();i++){
sparams.push_back(params[i].body);
}
fields.push_back("\n"+pad+lex2str(src.type)+""+src.name.value+"="+res+ex.body.templ_params+"("+join(sparams,",")+")"+string(opt?"?":"")+";\n"+pad2);
}
r.arr.clear();
auto mem=join(fields,"");
t_load_dev dev(mem);
bool ok=dev.go_auto(r.arr);
QapAssert(ok);
r.cmds=nullptr;
}
Обрабатывает команды
go_*
в теле лексера.-
Для каждой команды:
Определяет режим
opt
(опциональность).Извлекает имя функции без префикса
go_
(field
).Находит соответствующее поле в структуре по имени.
Формирует новую строку с описанием поля в формате QapDSLv2.
Собирает все преобразованные поля в строку, затем парсит её обратно в структуру
r.arr
. // вы только посмотрите на эту наглую вольность! Жертвовать производительностью ради красивой архитектуры!Удаляет команды
r.cmds
.
Вспомогательные методы
bool opt=false;
void Do(t_struct_cmd_mode&r){opt=r.body=='O';}
void Do(t_struct_cmd_anno&r){opt=r.mode[0]=='o';if(r.mode.size()>1)opt=r.mode[1]=='o';}
t_struct_field& find_field(const vector<TAutoPtr<i_struct_field>>&arr,const std::string&name) const {
for(auto&ex:arr){
auto*pf=t_struct_field::UberCast(ex.get());
QapAssert(pf);
if(pf->name.value==name) return *pf;
}
QapDebugMsg("can't find field '"+name+"' inside lexer '"+lexers.back()+"'");
static t_struct_field tmp;
return tmp;
}
Переменная
opt
хранит состояние опциональности текущей команды.Методы
Do
дляt_struct_cmd_mode
иt_struct_cmd_anno
устанавливаютopt
в зависимости от модификаторов.Метод
find_field
ищет поле по имени в массиве полей, с отладочным сообщением при отсутствии.
Обход лексеров с запоминание уровней вложенности.
vector<string> lexers;
void Do(t_target_struct&r){
lexers.push_back(r.name.value);
Do(r.parent);
Do(r.body);
lexers.pop_back();
}
Ведёт стек имён лексеров для контекста при поиске полей.
Основной метод запуска
string main(const string&data){
t_target tar;
auto fs=load_obj_full(tar,data);
if(!fs.ok){cerr<<fs.msg<<endl;return "fail";}
if(tar.arr.empty()){return "!target\n\n"+fs.msg;}
for(auto&ex:tar.arr){
auto*p=ex.get();
p->Use(*this);
}
string out;
save_obj(tar,out);
return out;
}
Итог
t_qapdls_v1_to_v2
— это мощный и аккуратный конвертер, который:
Автоматически преобразует старый синтаксис и структуру QapDSLv1 в новую, более лаконичную и современную QapDSLv2.
Упрощает описание лексеров, команд и полей, сохраняя при этом семантику.
Позволяет легко мигрировать и использовать преимущества нового синтаксиса без ручной переработки.
Показывает как на самом деле надо обходить дерево для его удобной модификации! Это одна из лучших практик!
Весь код целиком
struct t_qapdls_v1_to_v2:
t_templ_sys_v04,
t_meta_lexer::i_target_item_visitor,
t_meta_lexer::t_target_struct::i_struct_impl::i_visitor,
t_meta_lexer::i_struct_cmd_xxxx::i_visitor
{
#define ADD(F)typedef t_meta_lexer::F F;
ADD(t_target_semicolon)\
ADD(t_target_sep)\
ADD(t_target_struct)\
ADD(t_target_using)\
ADD(t_target_typedef)\
ADD(i_target_item)\
ADD(t_struct_cmd_mode)\
ADD(t_struct_cmd_anno)\
//
#undef ADD
template<class TYPE>
void Do(TYPE*p){if(p)Do(*p);}
template<class TYPE>
void Do(TYPE&r){r.Use(*this);}
template<class TYPE>
void Do(vector<TYPE>&arr){for(auto&ex:arr)Do(&ex);}
template<class TYPE>
void Do(vector<TAutoPtr<TYPE>>&arr){for(auto&ex:arr)if(ex)Do(*ex.get());}
template<class TYPE>
void Do(TAutoPtr<TYPE>&r){if(r)Do(*r.get());}
//template<class TYPE>
void Do(t_body_semicolon&r)override{}
bool opt=false;
void Do(t_struct_cmd_mode&r){opt=r.body=='O';}
void Do(t_struct_cmd_anno&r){opt=r.mode[0]=='o';if(r.mode.size()>1)opt=r.mode[1]=='o';}
t_struct_field&find_field(const vector<TAutoPtr<i_struct_field>>&arr,const std::string&name)const{
for(auto&ex:arr){
auto*pf=t_struct_field::UberCast(ex.get());
QapAssert(pf);
if(pf->name.value==name)return *pf;
}
QapDebugMsg("can't find field '"+name+"' inside lexer '"+lexers.back()+"'");
static t_struct_field tmp;
return tmp;
}
void Do(t_body_impl&r)override{
Do(r.nested);
if(!r.cmds)return;
auto&arr=r.cmds->arr;
vector<string> fields;
for(auto&ex:arr){
opt=false;
if(ex.body.mode)Do(ex.body.mode.get());
auto&f=ex.body.func.value;
QapAssert(f.size()>3&&f.substr(0,3)=="go_");
auto res=f.substr(3);
auto¶ms=ex.body.params.arr;
QapAssert(params.size());
string pad;
string pad2;
for(int i=0;i<lexers.size();i++)pad+=" ";
for(int i=1;i<lexers.size();i++)pad2+=" ";
if(res=="const"){
fields.push_back("\n"+pad+params[0].body+"\n"+pad2);
continue;
}
auto&src=find_field(r.arr,params[0].body);
if(res=="auto"){
src.qst=nullptr;
if(opt)load_obj(src.qst,"?");
fields.push_back("\n"+pad+lex2str(src)+"\n"+pad2);
continue;
}
vector<string> sparams;
for(int i=1;i<params.size();i++){
sparams.push_back(params[i].body);
}
fields.push_back("\n"+pad+lex2str(src.type)+""+src.name.value+"="+res+ex.body.templ_params+"("+join(sparams,",")+")"+string(opt?"?":"")+";\n"+pad2);
}
r.arr.clear();
auto mem=join(fields,"");
t_load_dev dev(mem);
bool ok=dev.go_auto(r.arr);
QapAssert(ok);
r.cmds=nullptr;
}
void Do(t_target_semicolon&r)override{}
void Do(t_target_sep&r)override{}
void Do(t_target_struct::t_parent&r){if(r.arrow_or_colon.size())r.arrow_or_colon=":";}
vector<string> lexers;
void Do(t_target_struct&r)override{lexers.push_back(r.name.value);Do(r.parent);Do(r.body);lexers.pop_back();}
void Do(t_target_using&r)override{}
void Do(t_target_typedef&r)override{}
string main(const string&data){
t_target tar;
auto fs=load_obj_full(/*Env,*/tar,data);
if(!fs.ok){cerr<<fs.msg<<endl;return "fail";}
if(tar.arr.empty()){return "!target\n\n"+fs.msg;}
for(auto&ex:tar.arr){
auto*p=ex.get();
p->Use(*this);
}
string out;
save_obj(tar,out);
return out;
}
};