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_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¶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; } };
Полезные ресурсы
ссылка на оригинал статьи https://habr.com/ru/articles/925420/
Добавить комментарий