QapGen: Создаём мощные парсеры на C++

от автора

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, что значительно расширяет область применения и удобство использования инструмента.

111

111

Примера описания парсера для калькулятора на 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), которая представляет собой множество ожидаемых символов для данного лексера. Эта строка используется для оптимизации разбора — чтобы быстро понимать, какие символы допустимы на текущем уровне.

Основная логика

  1. Обработка интерфейсных лексеров

if(lexer.is_interface){   return polylexer2vecofchar(lexer); }

Если лексер — интерфейсный (полиморфный), делегируем обработку специализированной функции polylexer2vecofchar.

  1. Обход команд лексера

for(auto& c : lexer.cmds){   auto& fields = lexer.farr;   t_struct_cmd cmd;   QapAssert(load_obj(cmd, c));   ... } 
  • Каждая команда лексера (cmd) загружается из сериализованного представления.

  • Анализируем функцию cmd.func.value — имя метода go_*, который реализует логику разбора.

  1. Обработка конкретных 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 — реализует понижение приоритета вложенного лексера и объединяет множества символов с учётом исключений. // пока без учёта исключений ибо сложно.

  1. Оптимизация и объединение множеств символов

  • Используется множество символов (string out), которое постепенно расширяется символами из вложенных лексеров.

  • Проверки и ассерты гарантируют корректность типов и структур.

  1. Обработка ошибок и неподдерживаемых функций

Если встречается неизвестный 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, и выполняет две ключевые модификации:

  1. Заменяет символ => (стрелочку) на : (двоеточие) в описании лексеров.
    Это упрощает и унифицирует синтаксис, делая его более читаемым и однородным.

  2. Удаляет встроенный 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_class_def_fixer
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   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>   bool is_empty(TYPE&r){     string out;     save_obj(r,out);     if(drop_empty_lines(out).empty())return true;     return false;   }   void Do(t_body_semicolon&r)override{}   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;       }     }     Do(r.nested);   }   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=":";}   void Do(t_target_struct&r)override{Do(r.parent);Do(r.body);}   void Do(t_target_using&r)override{}   void Do(t_target_typedef&r)override{}   bool cppcode_killer=true;   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;   } };

Подробный разбор структуры 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&params=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&params=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;   } };

Полезные ресурсы

  • Официальный репозиторий QapGen

  • Примеры грамматик XML и сгенерированного из него кода.


ссылка на оригинал статьи https://habr.com/ru/articles/925420/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *