Проекты растут. Кодовая база растёт. Время компиляции растёт вместе с ними и переходит все границы разумного. В какой-то момент я решил: а что если попробовать написать свой компилятор? Пока только для разработки, не для прода. На замену Clang++/G++/CL.EXE.Эта статья — о первом шаге на этом пути. О парсере C++, который я написал на своём DSL специально сделанном для этого изначально. Матчинг запущен? Кооперация предложена?
Проекты растут. Кодовая база растёт. Время компиляции растёт вместе с ними и переходит все границы разумного.
В какой-то момент я решил: а что если попробовать написать свой компилятор? Пока только для разработки, не для прода. На замену Clang++/G++/CL.EXE.
Эта статья — о первом шаге на этом пути. О парсере C++, который я написал на своём DSL специально сделанном для этого изначально.

просто картинка которая понравилась и которая создана/найдена с помощью "компилятора простейших математических формул"(мой предыдущих проект)
ИнструментыQapGen - генератор парсеров специально сделанный для того чтобы когда-то написать самый быстрый загрузчик CST(конкретное дерево разбора) С++.
QapDSLv2 - язык для QapGen-а на котором описываются грамматики языков программирования.
Про грамматику QapDSLv2
Грамматика — это ветка из лексеров. Лексер состоит из полей, к полям можно привязывать через "=" правила. Поля - это обычно тоже лексеры. Исключение - это деградировавшие до самого дна лексеры-терминалы которые либо пишут прямо в поля-строки лексера, либо вообще в никуда (когда их задача - это связаться с константной-зашитой последовательностью символов).
Пример фрагмента простой грамматики для javascript чтобы понять синтаксис QapDSLv2:
struct t_js_let_var_stat:i_stat{ // тут ключевое слово "struct" у лексеров можно не писать
"let" // константная-зашитая последовательность символов
" " //ещё одна, а можно было просто пробел выше зашить
t_name type; // это поле структуры как в С++, только у лексера
" "
t_name var; // ещё одно поле лексера
"="
t_js_expr expr; // ко всем трём полям неявно привязано правило "dev.go_auto"
";" // а ко всем зашитым константам неявно привязано правило "dev.go_const"
};
// или можно вот так ещё:
t_js_const_var_stat:i_stat{ // ещё одна структура-лексер для константной переменной.
"const "
t_name type;
" "
string var=str<t_name>();//привязаное к полю правило, парсит t_name и сохраняет в var.
"=" // оно разворачивается в "dev.go_str<t_name>(this->var);"
t_js_expr expr;
";"
}Во что это развернётся в С++: // надо ли раскрывать спойлеры???
Возможно тут будет немного больно, я предупредил:struct t_js_let_var_stat:public i_stat{
#define DEF_PRO_STRUCT_INFO(NAME,PARENT,OWNER)NAME(t_js_let_var_stat)PARENT(i_stat)
#define DEF_PRO_VARIABLE(ADDBEG,ADDVAR,ADDEND)\
ADDBEG()\
ADDVAR(t_name,type,DEF,$,$)\
ADDVAR(t_name,var,DEF,$,$)\
ADDVAR(t_js_expr,expr,DEF,$,$)\
ADDEND()
//=====+>>>>>t_js_let_var_stat
#include "QapGenStructNoTemplate.inl"
//<<<<<+=====t_js_let_var_stat
public:
void Use(i_visitor&A){A.Do(*this);}
static SelfClass*UberCast(ParentClass*ptr){return i_visitor::UberCast<SelfClass>(ptr);}
public:
bool go(i_dev&dev){
t_fallback $(dev,"t_js_let_var_stat",nullptr,this,__LINE__);
auto&ok=$.ok;if(!ok)return ok;
ok=dev.go_const("let");$(ok,",\"l\"");
if(!ok)return ok;
ok=dev.go_const(" ");$(ok,",\" \"");
if(!ok)return ok;
ok=dev.go_auto(type);$(ok,"t_name,\"c\"");
if(!ok)return ok;
ok=dev.go_const(" ");$(ok,",\" \"");
if(!ok)return ok;
ok=dev.go_auto(var);$(ok,"t_name,\"c\"");
if(!ok)return ok;
ok=dev.go_const("=");$(ok,",\"=\"");
if(!ok)return ok;
ok=dev.go_auto(expr);$(ok,"t_js_expr,\"\"");
if(!ok)return ok;
ok=dev.go_const(";");$(ok,",\";\"");
if(!ok)return ok;
return ok;
}
};
struct t_js_const_var_stat:public i_stat{
#define DEF_PRO_STRUCT_INFO(NAME,PARENT,OWNER)NAME(t_js_const_var_stat)PARENT(i_stat)
#define DEF_PRO_VARIABLE(ADDBEG,ADDVAR,ADDEND)\
ADDBEG()\
ADDVAR(t_name,type,DEF,$,$)\
ADDVAR(string,var,DEF,$,$)\
ADDVAR(t_js_expr,expr,DEF,$,$)\
ADDEND()
//=====+>>>>>t_js_const_var_stat
#include "QapGenStructNoTemplate.inl"
//<<<<<+=====t_js_const_var_stat
public:
void Use(i_visitor&A){A.Do(*this);}
static SelfClass*UberCast(ParentClass*ptr){return i_visitor::UberCast<SelfClass>(ptr);}
public:
bool go(i_dev&dev){
t_fallback $(dev,"t_js_const_var_stat",nullptr,this,__LINE__);
auto&ok=$.ok;if(!ok)return ok;
ok=dev.go_const("const ");$(ok,",\"c\"");
if(!ok)return ok;
ok=dev.go_auto(type);$(ok,"t_name,\"c\"");
if(!ok)return ok;
ok=dev.go_const(" ");$(ok,",\" \"");
if(!ok)return ok;
ok=dev.go_str<t_name>(var);$(ok,"t_name,\"c\"");
if(!ok)return ok;
ok=dev.go_const("=");$(ok,",\"=\"");
if(!ok)return ok;
ok=dev.go_auto(expr);$(ok,"t_js_expr,\"\"");
if(!ok)return ok;
ok=dev.go_const(";");$(ok,",\";\"");
if(!ok)return ok;
return ok;
}
};Настоящая грамматика для С++:
А вот как выглядит настоящий t_var_expr для С++: // много и сложно// начнём с t_var_expr, т.к он требуется для реализации t_var_stat
struct t_var_expr:i_expr{
TAutoPtr<t_global> global?; // "?" == опциональность
t_part head;
vector<t_step> arr?;
};
struct t_global{"::"};
struct t_part{
" "?
TAutoPtr<i_name> name; // ищите наследников i_name ниже
" "?
TAutoPtr<i_ext> ext?; // ищите наследников i_ext ниже
" "?
vector<t_arr_body> arr?;
" "?
};
struct t_arr_body{
" "?
"["
" "?
TAutoPtr<t_lev15> expr?; // t_lev15 - любое выражение С++, не ищите его тут нет
" "?
"]"
" "?
};
struct t_step{
" "?
string oper=any_str_from_vec(split("->,.,::",","));
t_part part; //t_part объявлен выше
};
struct t_arr_ext:i_ext{
t_arr_body body; //t_arr_body объявлен выше
};
struct t_call_ext:i_ext{
TAutoPtr<t_concrete_params> concrete_params?; // t_concrete_params=="<" ... ">"
" "?
vector<t_call_params> arr; // t_call_params == "(" ... ")"
};
struct t_tmpl_ext:i_ext{
TAutoPtr<t_concrete_params> concrete_params; // t_concrete_params == "<" ... ">"
};
struct t_op_impl:i_name{
"operator"
" "?
string op=any_str_from_vec(split("=,+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>=,итд",","));
" "?
};
struct t_name_impl:i_name{string value=str<t_name>();};
// ...---===###[ T_VAR_STAT ]###===---...
struct t_var_stat:i_stat{
TAutoPtr<i_var_impl> impl=diff<t_expr_stat>();// ищите наследников i_var_impl
" "?
};
struct t_typename{"typename" " "?};
struct t_var_case:i_var_impl{ // вот он - наследник i_var_impl
TAutoPtr<t_typename> tn?;
" "?
t_var_expr expr; // реализован вначале этого фрагмента кода
" "?
TAutoPtr<i_form_init> init?; // i_form_init - это "("...")" или "{"..."}"
" "? // ... - вражение(t_expr)
};
struct t_func_case:i_var_impl{ // вот он - наследник i_var_impl
t_fv_class_stat::t_type_expr::t_fv_stat body=diff<t_var_case>();// всё сложно
};struct t_enum_class_stat:i_class_stat{
"enum"
" "?
t_name name?; // любое С++ имя, ну там типа "azAZ09"...
" "?
t_body body?;
};
struct t_body{
TAutoPtr<i_body> body; // ищите наследников i_body
};
struct t_empty_body:i_body{ // вот он - наследник i_body
";"
};
struct t_impl_body:i_body{ // вот он - наследник i_body
"{"
vector<t_item> arr=vec(",")?;
TAutoPtr<t_comma_with_sep> comma?; // struct t_comma_with_sep{"," " "?};
"}"
};
struct t_item{
" "?
t_name name;
" "?
TAutoPtr<t_value> value?;
};
struct t_value{
"="
" "?
t_expr expr; // почти любое С++ выражение
" "?
};struct t_if_stat:i_stat{
"if"
" "?
"("
" "?
TAutoPtr<i_if_cond> cond; // ищите наследников i_if_cond
" "?
")"
" "?
TAutoPtr<i_stat> bef; // i_stat - любое утверждение в С++
" "?
TAutoPtr<t_else> aft?; // "?" == опциональность
" "?
}
struct t_init_cond:i_if_cond{ // вот он - наследник i_if_cond
TAutoPtr<t_for_stat::i_for_init> body; // тут всё сложно, там 3 ветки вариантов
};
struct t_expr_cond:i_if_cond{ // вот он - наследник i_if_cond
t_expr body=diff<t_init_cond>(); // t_expr - любое выражение С++
};
struct t_else{
"else" " "? TAutoPtr<i_stat> body; // i_stat - любое утверждение в С++
};t_fv_class_stat:i_class_stat{
t_type_expr{
t_type_expr_with_sep_and_cv{
vector<t_const_with_sep> cvs?;
" "?
TAutoPtr<t_type_expr> body;
}
t_name_part{
TAutoPtr<i_name_part> body;
}
t_amp{
string body=any_str_from_vec(split("&,&&",","));
" "?
}
t_brackets_name_part:i_name_part{
t_amp_part:i_part{
t_amp body;
" "?
}
t_star_part:i_part{
string stars=any("*");
" "?
TAutoPtr<t_amp> amp?;
" "?
}
"("
" "?
TAutoPtr<i_part> stamp_part;
" "?
TAutoPtr<t_name_part> namepart?;
")"
" "?
TAutoPtr<t_arr_body> arrbody?;
" "?
}
t_cv{string body=any_str_from_vec(split("const,volatile",",")); " "?}
t_raw_name_part:i_name_part{
TAutoPtr<t_cv> cv?;
string name=str<t_name>();
}
t_ap_name_part:i_name_part{
TAutoPtr<t_cv> cv1?;
string ap=any_str_from_vec(split("&,&&,*",","));
" "?
TAutoPtr<t_cv> cv2?;
t_name_part name;
" "?
}
t_func_param{
" "?
TAutoPtr<i_func_param> body;
" "?
TAutoPtr<i_init_body> value?;
" "?
}
t_func_params{
" "?
"("
" "?
vector<t_func_param> arr=vec(",")?;
" "?
")"
" "?
}
t_pfunc{
t_addr{
t_type_expr_with_sep_and_cv type;
" "?
"::"
" "?
}
t_type_expr_with_sep_and_cv type;
"("
" "?
t_addr addr?;
"*"
" "?
string name=str<t_name>()?;
" "?
")"
" "?
t_func_params params;
" "?
}
t_pfunc_func_param:i_func_param{
t_pfunc value;
}
t_var_args_func_param:i_func_param{
"..."
}
t_type_func_param:i_func_param{
t_const{
"const"
t_sep sep?;
}
t_type_expr_with_sep_and_cv type;
t_sep sep?;
TAutoPtr<t_const> cv?;
TAutoPtr<t_name_part> namepart?;
}
t_expr_func_param:i_func_param{
t_expr body=minor<t_type_func_param>();
}
t_fv_stat{
t_impl_func_body:i_fv_body{
t_sep sep?;
t_raw_func_body body;
}
t_zero_func_body:i_fv_body{
"=" " "? "0" ";" " "?
}
t_delete_func_body:i_fv_body{
"=" " "? "delete" " "? ";" " "?
}
t_override{
string value=any_str_from_vec(split("override,final",","));
" "?
}
t_braxcept{
"("
" "?
t_expr expr;
" "?
")"
" "?
}
t_noexcept{
"noexcept"
" "?
TAutoPtr<t_braxcept> expr?;
}
t_func_fv_end:i_fv_end{
TAutoPtr<t_func_params> params;
TAutoPtr<t_const_with_sep> cv1?;
" "?
vector<t_override> overrides?;
TAutoPtr<t_noexcept> noe?;
TAutoPtr<i_fv_body> body?;
}
t_var_fv_end:i_fv_end{
t_func_fv_item:i_fv_item{
TAutoPtr<t_func_params> params;
TAutoPtr<t_const_with_sep> cv1?;
}
t_var_fv_item:i_fv_item{
vector<t_arr_body> arrbody?;
TAutoPtr<i_init_body> value?;
}
t_item{
","
t_sep sep0?;
TAutoPtr<t_const_with_sep> cv?;
t_sep sep1?;
string name=str<t_name>();
t_sep sep2?;
TAutoPtr<i_fv_item> body?;
t_sep sep3?;
}
TAutoPtr<i_fv_item> body?;
vector<t_item> arr?;
";"
}
" "?
vector<t_keyword> keywords?;
TAutoPtr<t_type_expr> type;
TAutoPtr<t_const_with_sep> cv?;
" "?
TAutoPtr<t_func_path> path?;
TAutoPtr<t_callconv> callconv?;
t_name_part name;
" "?
TAutoPtr<i_fv_end> way;
}
t_impl_typeexpr:i_typeexpr{
t_global{
"::"
}
TAutoPtr<t_global> global?;
t_sep sep?;
t_scopes scopes;
vector<t_ptr> ptrs?;
TAutoPtr<t_ref> ref?;
}
t_decl_typeexpr:i_typeexpr{
"decltype"
" "?
"("
" "?
t_expr expr;
" "?
")"
" "?
}
TAutoPtr<i_typeexpr> body;
}
t_type_expr::t_fv_stat body;
}У меня она как-то неявно поделилась на несколько частей.
Часть 1: Стандартная С++ мелочь(string + char + t_sep + t_name)
Вот она полностью:
Так охота написать "не смотрите, т.к это скучно!", но на что тогда смотреть?:typedef array<char,2> ARRAY2char;
typedef array<char,4> ARRAY4char;
t_str_item_raw:i_str_item{
string body=any(dip_inv("\"\\\n"));
}
t_str_item_hex:i_str_item{
"\\x"
ARRAY2char body=any_arr_char(gen_dips("09afAF"));
}
t_str_item_num:i_str_item{
"\\u"
ARRAY2char body=any_arr_char(gen_dips("09"));
}
t_str_item_fix:i_str_item{
"\\"
char body=any_char("tfbrn\\\"\'"+gen_dips("07"));
}
t_str_item{
t_impl{
"\""
vector<TAutoPtr<i_str_item>> arr?;
"\""
}
string value=str<t_impl>();
}
t_char_item_raw:i_char_item{
string body=any(dip_inv("'\\\n"));
}
t_char_item_hex:i_char_item{
"\\x"
ARRAY2char body=any_arr_char(gen_dips("09afAF"));
}
t_char_item_num:i_char_item{
"\\u"
ARRAY4char body=any_arr_char(gen_dips("09"));
}
t_char_item_fix:i_char_item{
"\\"
char body=any_char("tfbrn\\\"\'"+gen_dips("07"));
}
t_char_item{
t_impl{
"'"
TAutoPtr<i_char_item> body;
"'"
}
string value=str<t_impl>();
}
t_sep_seq:i_sep{
string body=any(" \t\r\n");
}
t_c_comment:i_sep{
"/*"
string body=end("*/");
}
t_cpp_comment:i_sep{
"//"
string body=any(dip_inv("\n"))?;
}
t_sep{
t_impl{
vector<TAutoPtr<i_sep>> arr;
}
string value=str<t_impl>();
}
using " " as t_sep;
t_name{
t_keyword{
string value=any_str_from_vec(split("new,delete,default,consteval,false,true,nullptr,this,struct,class,for,if,while,do,const,constexpr,else,operator,continue,break,return,goto,virtual,override,public,private,protected,friend,template,typedef,using,namespace,decltype,volatile,final,switch,case,catch,try",","));
}
t_impl{
char A=any_char(gen_dips("azAZ")+"_$@");
string B=any(gen_dips("azAZ09")+"_$@")?;
}
t_impl_ex{
t_impl impl=diff<t_keyword>();
}
string value=str<t_impl_ex>();
}Часть 2: Не шаблонные уровни задающие приоритеты операций:
Лоза из 16 уровней за которую меня по делу ругают: // рекомендую открыть и промотатьt_lev00{
t_item:i_lev00_prefix{string oper=any_str_from_vec(split("*,+,-,++,--,!,~",","));" "?}
t_cast:i_lev00_prefix{"(" " "? TAutoPtr<t_type_expr_proxy> type; " "? ")" " "?}
t_call:i_lev00_postfix{"(" " "? TAutoPtr<t_lev15> body?; " "? ")" " "?}
t_index:i_lev00_postfix{"[" " "? TAutoPtr<t_lev15> body; " "? "]" " "?}
t_dot:i_lev00_postfix{"." " "? t_name name;" "?}
t_arrow:i_lev00_postfix{"->" " "? t_name name;" "?}
t_inc:i_lev00_postfix{"++" " "?}
t_dec:i_lev00_postfix{"--" " "?}
t_operator_call:i_lev00_postfix{
"operator"
" "?
string op = any_str_from_vec(split("=,+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>=,||,&&,|,^,&,==,!=,<,<=,>,>=,<<,>>,+,-,*,/,%,++,--,~,!,(),[],->",","));
" "?
TAutoPtr<t_call_params> params?;
" "?
}
t_operator_cast_call:i_lev00_postfix{
"operator"
" "?
TAutoPtr<t_type_expr_proxy> type;
" "?
t_call_params params?;
" "?
}
vector<TAutoPtr<i_lev00_prefix>> bef?;
" "?
TAutoPtr<i_expr> expr;
" "?
vector<TAutoPtr<i_lev00_postfix>> aft?;
}
t_lev01{
string oper=any_str_from_vec(split("&",","))?;
" "?
TAutoPtr<t_lev00> expr;
" "?
}
t_lev02{
t_oper{string value=any_str_from_vec(split(".*,->*",","));}
t_item{" "? t_oper oper;" "?t_lev01 expr;}
t_lev01 expr;
vector<t_item> arr?;
" "?
}
t_lev03{
t_oper{string value=any_str_from_vec(split("*,/,%",","));}
t_item{" "? t_oper oper;" "?t_lev02 expr;}
t_lev02 expr;
vector<t_item> arr?;
" "?
}
t_lev04{
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_lev05{
t_oper{string value=any_str_from_vec(split("<<,>>",","));}
t_item{" "? t_oper oper;" "?t_lev04 expr;}
t_lev04 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{"&"[::]inline static const string value="&";}
t_item{" "? t_oper oper;" "?t_lev07 expr;}
t_lev07 expr;
vector<t_item> arr?;
" "?
}
t_lev09{
t_oper{"^"[::]inline static const string value="^";}
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_mega:i_mega{TAutoPtr<t_mega_expr> body;}
t_pico:i_mega{TAutoPtr<t_lev13> body=diff<t_mega_expr>();}
t_mega12:i_mega12{TAutoPtr<t_mega_expr> body;}
t_pico12:i_mega12{TAutoPtr<t_lev12> body=diff<t_mega_expr>();}
t_ter_op:i_ter_oper{
TAutoPtr<i_mega12> cond;
" "? "?" " "?
TAutoPtr<i_mega> true_expr;
" "? ":" " "?
TAutoPtr<i_mega> false_expr;
}
t_nop_op:i_ter_oper{
t_lev12 body;
}
TAutoPtr<i_ter_oper> body;
" "?
}
t_lev14{
t_oper{string value=any_str_from_vec(split("=,+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>=",","));}
t_item{" "? t_oper oper;" "?t_lev13 expr;}
t_lev13 expr;
vector<t_item> arr?;
" "?
}
t_lev15{
t_oper{","[::]inline static const string value=",";}
t_item{" "? t_oper oper;" "?t_lev14 expr;}
t_lev14 expr;
vector<t_item> arr?;
" "?
}
t_expr{
t_lev14 body;
}Часть 3: шаблонные уровни задающие приоритеты операций:
Это тоже самое только для шаблонов: // рекомендую открыть и сразу закрытьt_tmpl_lev03{
string oper=any_str_from_vec(split("&,*,+,-,!,~",","))?;
TAutoPtr<i_tmpl_expr> expr;
}
t_tmpl_lev05{
t_oper{
string value=any_str_from_vec(split("*,/,%",","));
}
t_item{
t_oper oper;
t_tmpl_lev03 expr;
}
t_tmpl_lev03 expr;
vector<t_item> arr?;
}
t_tmpl_lev06{
t_oper{
string value=any_str_from_vec(split("+,-",","));
}
t_item{
t_oper oper;
t_tmpl_lev05 expr;
}
t_tmpl_lev05 expr;
vector<t_item> arr?;
}
t_tmpl_lev09{
t_oper{
string value=any_str_from_vec(split("==,!=",","));
}
t_item{
t_oper oper;
t_tmpl_lev06 expr;
}
t_tmpl_lev06 expr;
vector<t_item> arr?;
}
t_tmpl_lev10{
t_oper{
string value=any_str_from_vec(split("&",","));
}
t_item{
t_oper oper;
t_tmpl_lev09 expr;
}
t_tmpl_lev09 expr;
vector<t_item> arr?;
}
t_tmpl_lev11{
t_oper{
string value=any_str_from_vec(split("^",","));
}
t_item{
t_oper oper;
t_tmpl_lev10 expr;
}
t_tmpl_lev10 expr;
vector<t_item> arr?;
}
t_tmpl_lev12{
t_oper{
string value=any_str_from_vec(split("|",","));
}
t_item{
t_oper oper;
t_tmpl_lev11 expr;
}
t_tmpl_lev11 expr;
vector<t_item> arr?;
}
t_tmpl_lev13{
t_oper{
string value=any_str_from_vec(split("&&",","));
}
t_item{
t_oper oper;
t_tmpl_lev12 expr;
}
t_tmpl_lev12 expr;
vector<t_item> arr?;
}
t_tmpl_lev14{
t_oper{
string value=any_str_from_vec(split("||",","));
}
t_item{
t_oper oper;
t_tmpl_lev13 expr;
}
t_tmpl_lev13 expr;
vector<t_item> arr?;
}
t_tmpl_expr{
t_tmpl_lev14 body;
}в них нет символов "больше"(>) и "меньше"(<) - вот для этого они и нужны, а так они полная старая копия из части 2 которую мне лень синхронизировать и поэтому она устарела и поэтому грамматика неполноценная, но я как-нибудь это исправлю.
Часть 4: Всякая мелочь, её очень много и поэтому не буду её показывать тут.
Часть 5: Наследники i_class_stat.
Это то что может встречаться на самом высоком уровне в С++ коде - их немало.
Покажу некоторые: // рекомендую открыть и сразу закрытьt_dtor_class_stat:i_class_stat{
vector<t_kw_with_sep> kw?;
TAutoPtr<t_callconv> callconv?;
TAutoPtr<t_func_path> path?;
"~"
t_sep sep0?;
string name=str<t_name>();
t_sep sep1?;
t_fv_class_stat::t_type_expr::t_func_params params;
TAutoPtr<i_func_body> body;
}
t_ctor_class_stat:i_class_stat{
t_impl{
t_explicit{
"explicit"
t_sep sep;
}
vector<t_kw_with_sep> kw?;
TAutoPtr<t_callconv> callconv?;
TAutoPtr<t_explicit> prefix?;
TAutoPtr<t_func_path> path?;
string name=str<t_name>();
t_sep sep?;
TAutoPtr<t_concrete_params> concrete_params?;
t_fv_class_stat::t_type_expr::t_func_params params;
TAutoPtr<i_func_body> body;
}
t_impl body;
}
t_oper_cast_class_stat:i_class_stat{
t_impl{
TAutoPtr<t_callconv> callconv?;
TAutoPtr<t_func_path> path?;
"operator"
t_sep sep0?;
t_fv_class_stat::t_type_expr type;
t_sep sep1?;
t_fv_class_stat::t_type_expr::t_func_params params;
TAutoPtr<t_const_with_sep> cv?;
TAutoPtr<i_func_body> body;
}
t_impl body;
}
t_common_oper_class_stat:i_class_stat{
t_impl{
vector<t_keyword> keywords?;
t_fv_class_stat::t_type_expr type;
TAutoPtr<t_const_with_sep> cv0?;
t_sep sep0?;
TAutoPtr<t_callconv> callconv?;
TAutoPtr<t_func_path> path?;
"operator"
t_sep sep1?;
string oper=any_str_from_vec(split("=,+=,-=,*=,/=,%=,|=,&=,^=,<<=,>>=,||,&&,|,^,&,==,!=,<,<=,>,>=,<<,>>,+,-,*,/,%,++,--,~,!,(),[],->",","));
t_fv_class_stat::t_type_expr::t_func_params params;
TAutoPtr<t_const_with_sep> cv1?;
TAutoPtr<i_func_body> body;
}
t_impl body;
}Часть 6: Наследники i_stat
Это то что может встречаться внутри методов/функций.
Покажу некоторые: // рекомендую открытьt_sep_stat:i_stat{
" "?
";"
" "?
}
t_while_stat:i_stat{
"while"
" "?
"("
" "?
t_expr cond;
" "?
")"
" "?
TAutoPtr<i_stat> body;
" "?
}
t_do_while_stat:i_stat{
"do" " "?
t_raw_func_body body;
" "?
"while"
" "?
"("
" "?
t_expr cond;
" "?
")"
" "?
";"
" "?
}
t_goto_stat:i_stat{
"goto" " "? t_name name; " "? ";" " "?
}
t_common_oper_stat:i_stat{t_common_oper_class_stat::t_impl body;}
t_switch_stat:i_stat{
t_num_ce:i_case_expr{string value=any(gen_dips("09"));}
t_char_ce:i_case_expr{string value=str<t_char_item::t_impl>();}
t_name_ce:i_case_expr{t_name name;}
t_case:i_case{
"case" " "?
TAutoPtr<i_case_expr> expr;
" "? ":" " "?
vector<TAutoPtr<i_stat>> stat?; " "?
}
t_default:i_case{"default" " "? ":" " "? vector<TAutoPtr<i_stat>> arr?; " "?}
"switch"
" "?
"("
" "?
t_expr expr;
" "?
")"
" "?
"{"
" "?
vector<TAutoPtr<i_case>> arr?;
" "?
"}"
" "?
}
t_try_stat:i_stat{
"try"
" "?
t_raw_func_body body;
" "?
vector<t_catch_block> arr;
" "?
}
t_return_stat:i_stat{
"return" " "?
TAutoPtr<i_setter> expr?;
" "?
";"
" "?
}
t_break_stat:i_stat{"break" " "? ";" " "?}
t_continue_stat:i_stat{"continue" " "? ";" " "?}
t_expr_stat:i_stat{
t_lev15 body;
" "?
";"
" "?
}Часть 7: Нужное для реализации лямбд
Покажу некоторые: // рекомендую не открывать вообщеt_capture_default{
string value=any_str_from_vec(split("=,&",","));
" "?
}
t_capture_item{
t_main:i_capture_item{
string cr=any_str_from_vec(split("=,&",","))?;
" "?
t_name name;
" "?
TAutoPtr<t_set_init_body> value?;
" "?
}
t_this:i_capture_item{
"this"
" "?
}
t_var_args:i_capture_item{
"..."
" "?
}
" "?
vector<TAutoPtr<i_capture_item>> item;
}
t_template_params_for_lambda{
"<"
" "?
vector<t_template_class_stat::t_template_param> arr=vec(",")?;
" "?
">"
" "?
}
t_capture_list{
"[" " "?
t_capture_default def?;
" "?
TAutoPtr<t_comma_with_sep> comma?;
vector<t_capture_item> items=vec(",")?;
" "? "]"
" "?
TAutoPtr<t_template_params_for_lambda> templ_params?;
" "?
}
t_lambda_param{
t_auto_param:i_lambda_param{
"auto" " "?
string refs=any_str_from_vec(split("&,&&,*",","))?;
" "?
t_name name?;
" "?
}
t_type_param:i_lambda_param{
TAutoPtr<t_for_stat::i_decl> type;
" "?
TAutoPtr<t_ref> ref?;
" "?
t_fv_class_stat::t_type_expr::t_name_part name?;
" "?
}
t_var_args:i_lambda_param{
"..."
" "?
}
t_this_param:i_lambda_param{ // C++23
"this"
" "?
}
" "?
vector<TAutoPtr<i_lambda_param>> arr;
" "?
}
t_lambda_params{
"("
" "?
vector<t_lambda_param> params=vec(",")?;
" "?
")"
" "?
}
t_lambda_spec{
t_kw:i_lambda_spec_item{
" "?
string kw=any_str_from_vec(split("mutable,constexpr,consteval,noexcept",","));
" "?
}
t_throw:i_lambda_spec_item{
" "?
"throw" " "? "("
vector<t_fv_class_stat::t_type_expr> types=vec(",")?;
")" " "?
}
t_trailing_return{
"->" " "?
t_fv_class_stat::t_type_expr type;
" "?
}
" "?
vector<TAutoPtr<i_lambda_spec_item>> arr?;
" "?
TAutoPtr<t_trailing_return> ret?;
" "?
}
t_lambda_template{
"template" " "?
t_template_params_for_lambda body;
}
t_lambda_expr:i_expr{
TAutoPtr<t_lambda_template> templ?;
" "?
t_capture_list cap;
" "?
TAutoPtr<t_lambda_params> params?;
" "?
t_lambda_spec spec?;
" "?
t_raw_func_body body;
" "?
}Часть 8: Наследники i_expr
Покажу некоторые: // рекомендую открытьt_initializer_list:i_expr{
t_item{
" "?
TAutoPtr<i_setter> body;
" "?
}
"{"
" "?
vector<t_item> arr=vec(",")?;
" "?
"}"
" "?
}
t_block_expr:i_expr{
"("
" "?
t_lev15 expr;
" "?
")"
" "?
}
t_this_expr:i_expr{"this"}
t_bool_expr:i_expr{
string value=any_str_from_vec(split("false,true",","));
}
t_string_expr:i_expr{
t_item{t_str_item::t_impl body;" "?}
string prefix=any_str_from_vec(split("u8,L",","))?;
vector<t_item> arr;
string suffix=any_str_from_vec(split("s,sv,U",","))?;
}
t_raw_string_expr:i_expr{
string prefix=any_str_from_vec(split("u8,L",","))?;
"R\""
string delimiter=any(gen_dips("azAZ09")+"_")?;
"("
string body=end(")"+delimiter);
"\""
string suffix=any_str_from_vec(split("s,sv,U",","))?;
}
t_char_expr:i_expr{
string value=str<t_char_item::t_impl>();
}
t_num_expr:i_expr{
t_frac{
"."
string frac = any(gen_dips("09"))?;
}
t_exp{
char e=any_char("eE");
string sign=any_str_from_vec(split("+,-",","))?;
string exp=any(gen_dips("09"));
}
t_udl:i_num_expr_suffix{
"_"
string value=str<t_name>();
}
t_suffix:i_num_expr_suffix{
" "?
string suffix=any_str_from_vec(split("u,U,l,L,ll,LL,ul,UL,ull,ULL,f,F,lf,LF,s,ms,us,ns,min,h",","));
}
t_impl{
string whole=any(gen_dips("09"));
TAutoPtr<t_frac> frac?;
TAutoPtr<t_exp> exp?;
}
string value=str<t_impl>();
TAutoPtr<i_num_expr_suffix> suffix?;
}
t_nullptr_expr:i_expr{
"nullptr"
}
t_hex_expr:i_expr{t_impl{
"0"
char x=any_char("xX");
string value=any(gen_dips("09afAF"));
}
string value=str<t_impl>();
}Часть 9: t_for_stat - цикл for
Скрытый текстt_for_stat:i_stat{
t_init_decl{
" "?
t_fv_class_stat::t_type_expr::t_name_part name;
" "?
TAutoPtr<i_init_body> body?;
" "?
}
t_cv{string body=any_str_from_vec(split("const,volatile,unsigned,signed,short,long",",")); " "?}
t_cv2{string body=any_str_from_vec(split("const,volatile,unsigned,signed",",")); " "?}
t_fulltype{
TAutoPtr<t_typename> tn1?;
vector<t_cv> cv?;
TAutoPtr<t_typename> tn2?;
t_fv_class_stat::t_type_expr type;
}
t_type_decl:i_decl{
t_structbind:i_varname_or_structbind{
t_dots_item:i_structbind_item{" "? "..." " "?}
t_name_item:i_structbind_item{" "? t_name name;" "?}
"["
" "?
vector<TAutoPtr<i_structbind_item>> arr=vec(",");
" "?
"]"
}
t_varname:i_varname_or_structbind{
t_fv_class_stat::t_type_expr::t_name_part name;
}
TAutoPtr<t_typename> tn1?;
vector<t_cv> cv?;
TAutoPtr<t_typename> tn2?;
t_fv_class_stat::t_type_expr type;
" "?
TAutoPtr<i_varname_or_structbind> name;
" "?
}
t_long_decl:i_decl{
vector<t_cv2> cv?;"long" " "?
t_fv_class_stat::t_type_expr::t_name_part name;
" "?
}
t_ll_decl:i_decl{
vector<t_cv2> cv?;"long" " "? "long" " "?
t_fv_class_stat::t_type_expr::t_name_part name;
" "?
}
t_decl_for_init:i_for_init{
t_item{"," " "? t_init_decl body;}
TAutoPtr<i_decl> decl;
TAutoPtr<i_init_body> body?;
vector<t_item> arr?;
}
t_expr_for_init:i_for_init{
t_expr body=minor<t_decl_for_init>();
}
t_init{TAutoPtr<i_for_init> body?;" "? ";" " "?}
t_classic_header:i_for_header{
t_init init;
TAutoPtr<t_expr> cond?;
" "? ";" " "?
TAutoPtr<t_lev15> inc?;
}
t_range_header:i_for_header{
TAutoPtr<t_init> init?;
TAutoPtr<i_decl> decl;
" "?
":" " "?
t_expr range;
}
"for" " "? "(" " "?
TAutoPtr<i_for_header> header;
" "? ")" " "?
TAutoPtr<i_stat> body;
" "?
}Где всё посмотреть целиком: inp.qapdsl.h
Где выхлоп? — вот: QapCppFrontLexers.h
Что уже работает?Поддерживается почти всё, но почему бы не перечислить ещё раз это всё:
Для тех не хочет читать код, но хочет увидеть что поддерживается:Грамматика поддерживает основные конструкции C++:
Операторы:
Все приоритеты: от :: (глобальный доступ) до , (оператор запятая)
Арифметика: +, -, *, /, %
Сравнение: <, >, <=, >=, ==, !=
Логика: &&, ||, !
Присваивание: =, +=, -=, и т.д.
Инкремент/декремент: ++, --
Литералы:
Числовые с суффиксами (u, l, f, s, ms, и т.д.)
Строковые ("...", R"delimiter(...)delimiter")
Символьные ('a')
Логические (true, false)
Шаблоны:
template <typename T>
Параметры шаблонов
Вызовы шаблонов (func<int>())
Лямбды:
Захваты: [&], [=], [x], [&x]
Параметры
Тело в фигурных скобках
Объявления:
Переменные: int x = 5;
Функции: void foo(int a) { ... }
Классы, структуры, объединения
Перечисления
Препроцессор: (частично)
#pragma, #include — проходятся, но не обрабатываются
Концепты: (частично)
Атрибуты: [[...]]— вроде бы поддерживаются
Структурное связывание: полностью
ссылка на файл где "закомментировано всё нерабочее"(из того о чём мне известно).
upd:
также не работает пока:
module, import, export , co_await(но он вроде работает?), co_yield, синтаксис рефлексии.
Скорость парсинга:Для сравнения: на моей машине cl.exe компилирует мой код со скоростью ~70KB/s
Файлы в которые специально добавлялись сложные конструкции:
contests.h attrs.h et_danger.cpp neo4evidno2.cpp neo4evidno.cpp
T<1>(2) — шаблон или сравнение? — всегда шаблон: подразумевается что на этапе семантического анализа будет костыль который при необходимости развернёт шаблон в цепочку сравнений.
struct S { int x; }; — объявление или выражение? — всегда t_class_stat, т.к внутри фигурных скобочек находиться выражение которое однозначно интерпретируется только в контексте t_class_stat, альтернатива t_uniform_init отваливается, т.к внутри не может распарсить int x;
{
"arr": [
{
"#": "t_fv_class_stat",
"body": {
"body": {
"#": "t_keywords_uok",
"keywords": [],
"type": {
"body": {
"#": "t_impl_typeexpr",
"global": null,
"sep": {
"value": ""
},
"scopes": {
"first": {
"sep": {
"value": ""
},
"name": "void",
"concrete_params": null
},
"arr": []
},
"ptrs": [],
"ref": null
}
},
"cv": null,
"path": null,
"callconv": null,
"name": {
"body": {
"#": "t_raw_name_part",
"cv": null,
"name": "foo"
}
}
},
"way": {
"#": "t_func_fv_end",
"params": {
"arr": []
},
"cv1": null,
"overrides": [],
"noe": null,
"body": {
"#": "t_impl_func_body",
"no_except": "",
"init_list": null,
"type": null,
"body": {
"arr": [
{
"#": "t_class_stat",
"body": {
"attrs": [],
"keyword": "struct",
"attrs2": [],
"name": "S",
"fin": "",
"parents": null,
"body": {
"arr": [
{
"#": "t_fv_class_stat",
"body": {
"body": {
"#": "t_keywords_uok",
"keywords": [],
"type": {
"body": {
"#": "t_impl_typeexpr",
"global": null,
"sep": {
"value": ""
},
"scopes": {
"first": {
"sep": {
"value": ""
},
"name": "int",
"concrete_params": null
},
"arr": []
},
"ptrs": [],
"ref": null
}
},
"cv": null,
"path": null,
"callconv": null,
"name": {
"body": {
"#": "t_raw_name_part",
"cv": null,
"name": "x"
}
}
},
"way": {
"#": "t_var_fv_end",
"body": null,
"arr": []
}
}
},
{
"#": "t_sep_class_stat",
"sep": {
"value": " "
}
}
],
"name": null
}
}
}
]
}
}
}
}
}
]
}Как решалась проблема с if (auto& [ok, value] = func(); ok && value > 0)
Тут ok && value жадно парсилось как тип + rvalue_ref + имя_переменной, а затем парсер падал, т.к не понимал что происходит "что ещё за >?" думал он. но я быстро понял, что это мой косяк и я забыл добавить альтернативу на t_expr или ещё что-то более забористое и всё сразу заработало.
Оценка затраты времени на разработку парсера осложняется тем что сильно меньшая половина его писалась 11 лет назад и умело полноценно парсить только то что тогда было использовано мною в мои проектах и только заголовочная часть грамматики, то есть вся реализация всех методов/функций пожиралась одним(ну ладно семью) простейшим вот этим лексером(можете кстати оценить как грамматика выглядела на старте разработки в этом месяце — весь это файл до этого не менялся 11 лет). Доработка началась третьего числа этого месяца и заняла почти 10 дней примерно по десять часов в день. Оригинальная грамматика с которой я продолжил разработку в этом месяце тогда 11 лет назад заняла где-то пару недель в таком же режиме. Итого: порядка 200 часов. // А время разработки QapGen+QapDSLv2 — от трёх до девяти месяцев в том же режиме.

тот самый лексер который жрал все тела функций 11 лет назад
Что дальше?Планы по развитию:
Никто ничего не заметит и не придумает — проект помрёт опять.
Почему проект должен помереть по мои прогнозам:

Компилятор вот-вот потребует ещё чуть чуть памяти и упрётся в границу если не 2ГБ, то 3ГБ либо 4ГБ и упадёт :(
но проект — не главное, его не жалко. главное: кооп
Как развивать проект если компилятор вот вот откажется его компилировать? Дробить на файл? Грамматика очень плохо дробиться на файлы/модули как мне кажется.
Интересная статистика от QapGen-а про грамматику:
> QapGen.exe inp.qapdsl.h>out.h
{"version":"1.0"}
{"parse_ms":556.683}
{"Do(tar) done in ":102.445} // первый проход по дереву для генерации ast2json
{"parse_ms":572.304}
{"Do(tar) done in ":11.0758} // второй проход по дереву для генерации пустого посетителя
{"parse_ms":532.23}
{"Do(tar) done in ":6.77717} // третий проход по дереву я не помню зачем он
{"parse_ms":541.226} // основной парсин С++ грамматики QapGen-ом
{"need_igrab done in ":1.08629} // сбор интерфэйсов
{"need_grab done in ":0.661759} // сбор лексеров
{"interface_autogen done in ":0.637013} // генераци интерфейсов
{"lexers_grab done in ":12.7505} // ещё один сбор лексеров в другую струкутур
{"Do(tar) done in ":494.345} // первый проход генерации кода
{"buildLexerTree done in ":2.96149} // ещё один сбор лексров в третью струкуру
{"pass_with_log_err_gen done in ":556.134} // генерим ошибки и код для них первый раз
{"pass2_with_log_err_gen done in ":1104.39} //генерим ошибки и код для них второй раз
{"poly_end done in ":391.131} // создаём код для полиморфных лексеров
{"parse_ms":541.226,"total_ms":5166.62,"lexers":465,"fields":1176,"g_unique_pool_ptr_counter":101896}
// Итого: 456 лексеров, 1176 полей-правил в них, 101К объектов в куче при парсинге создано.
> wc inp.qapdsl.h
1894 3322 42664 inp.qapdsl.h
// Итого: 1894 строки грамматики, 3322 связи, 42КБ плотного кода.
> wc out.h
22421 34925 856731 out.h
// Итого: 22421 строка генереного кода, 856КБ файл.
"out.h" - зажимается 7zip-ом в 6%
"inp.qapdsl.h" - зажимается 7zip-ом в 15%Выводы:пишите в комментариях.
Итог:Парсер вроде работает. Строит CST для реальных файлов. Грамматика растёт.
Код открыт и доступен на GitHub: https://github.com/adler3d/QapCppFront
Финал------------------------------------------------------------- // Матчинг запущен? Кооперация предложена? — нет? — Тогда мы идём к вам!
Рассказываю историю, услышанную на канале Homo Deus в видео про кооп и дилемму заключённого: короче, только самый важный момент-вывод: двое в агрессивной среде получают просто космическую выгоду если матчатся/кооперируются и единственное что их останавливает на какое-то время — это низкая вероятность встречи. Эти статьи и проекты которые я делаю/пишу/конструирую — это мои броски в этой игре, а ваши реакции/комментарии/действия — это ваши броски в этой игре. Среда невероятно агрессивная на первый взгляд, но я верю в кооп. Пусть он победит. Пусть из статьи выйдет что-то ещё кроме комментариев/реакций/действий. Пусть родится нечто новое — ваш ход:
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Дайджест C++: новости, полезные материалы и «свой язык» на десерт | 0 | 7.94 | 27-05-2026 |
| 2 | Алиасинг памяти в C++: прошлое, настоящее, будущее | 0 | 7 | 10-06-2026 |
| 3 | Создаём DSL на C#: Добавляем семантику | 0 | 6.17 | 13-07-2026 |
| 4 | [Перевод] Доверьтесь компилятору: C++23 против трюков из 90-х | 0 | 10.4 | 27-07-2026 |
| 5 | [Перевод] Доверьтесь компилятору: C++23 против трюков из 90-х | 0 | 10.4 | 27-07-2026 |
| 6 | Создаём DSL на C#: Диагностика | 0 | 9.54 | 04-08-2026 |
| 7 | [Перевод] Очередная библиотека для парсинга аргументов командной строки на C++. Встречайте FancyArgumentParser | 0 | 7.77 | 20-07-2026 |
| 8 | Почему C++ или Rust никогда не смогут заменить Python | -1 | 5.93 | 28-07-2026 |
| 9 | Худший язык программирования всех времён /s | -2 | 6 | 01-07-2026 |
| 10 | С++: Пиши, сокращай, оптимизируй | 0 | 10.41 | 23-07-2026 |