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_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 и сгенерированного из него кода.

Комментарии (0)