boost spirit x3 - parse tokens in any order

226 Views Asked by At

This is basically a follow up of a question i asked earlier and @sehe so graciously answered!

Question: How do i parse multiple command parsers using boost spirit x3 and here is the code given by @sehe - https://coliru.stacked-crooked.com/a/5879831b11c51f84

The follow up question is how to parse the command arguments in any order:

i.e. parse the following successfully

cmd1 param1=<value> param2=value OR
cmd1 param2=<value> param1=value

and so on

3

There are 3 best solutions below

1
On BEST ANSWER

I feel I have to mention we're not ChatGPT. You're in luck though, I like doing X3 finger exercises.

First, let's observe that Spirit Qi has a parser operator that comes close out of the box: Permutation Parser

Live On Coliru

#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>

namespace qi = boost::spirit::qi;

namespace ast {
    struct cmd1 { double param1, param2; };
    struct cmd2 { std::string param1; };

    using Command = boost::variant<cmd1, cmd2>;
    using boost::fusion::operator<<;
} // namespace ast

BOOST_FUSION_ADAPT_STRUCT(ast::cmd1, param1, param2)
BOOST_FUSION_ADAPT_STRUCT(ast::cmd2, param1)

template <typename It> struct CommandParser : qi::grammar<It, ast::Command()> {
    CommandParser() : CommandParser::base_type(start) {
        using namespace qi;
        quoted_string = lexeme['"' >> *~char_('"') >> '"'];

        cmd1  = lit("cmd1") >> ((lit("param1") >> '=' >> double_) ^ //
                               (lit("param2") >> '=' >> double_));
        cmd2  = lit("cmd2") >> ((lit("param1") >> '=' >> quoted_string));
        start = qi::skip(qi::space)[cmd1 | cmd2];

        BOOST_SPIRIT_DEBUG_NODES((cmd1)(cmd2)(start))
    }

  private:
    using Skipper = qi::space_type;
    qi::rule<It, ast::Command()>          start;
    qi::rule<It, ast::cmd1(), Skipper>    cmd1;
    qi::rule<It, ast::cmd2(), Skipper>    cmd2;
    qi::rule<It, std::string(), Skipper>  quoted_string;
};

template <typename It> boost::optional<ast::Command> parse_line(It first, It last) {
    static CommandParser<It> const p;
    ast::Command attr;

    // if (phrase_parse(first, last, qi::expect[parser::command >> qi::eoi], qi::space, attr))
    if (phrase_parse(first, last, p >> qi::eoi, qi::space, attr))
        return attr;
    return {};
}

auto parse_line(std::string_view input) { return parse_line(begin(input), end(input)); }

int main() {
    // for (std::string line; getline(std::cin, line) && !line.empty();) {
    for (std::string line :
         {
             R"()",
             R"(cmd1 param1 = 3.14 param2 = 8e-9)",
             R"(cmd1 param2 = 8e-9 param1 = 3.14)", // flipped order
             R"(cmd1 param1 = 3.14 param2 = -inf)",
             R"(cmd1 param2 = -inf param1 = 3.14)", // flipped order
             R"(cmd2 param1 = " hello world " )",

             // things that would not have parsed with question code:
             R"(cmd2 param1 = "" )",

             // things that should not parse
             R"(cmd2 param1 = 3.14 param2 = 8e-9)",
             R"(cmd1 param1 = " hello world " )",
             R"(cmd2 param1 = "" trailing rubbish)",
             R"(trailing rubbish)",
         }) //
    {
        std::cout << std::left << std::setw(40) << quoted(line);
        try {
            auto parsed = parse_line(line);
            std::cout << " -> " << parsed << std::endl;
        } catch (std::exception const& e) {
            std::cout << " -> ERROR " << e.what() << std::endl;
        }
    }
}

Printing

""                                       -> --
"cmd1 param1 = 3.14 param2 = 8e-9"       ->  (3.14 8e-09)
"cmd1 param2 = 8e-9 param1 = 3.14"       ->  (3.14 8e-09)
"cmd1 param1 = 3.14 param2 = -inf"       ->  (3.14 -inf)
"cmd1 param2 = -inf param1 = 3.14"       ->  (3.14 -inf)
"cmd2 param1 = \" hello world \" "       ->  ( hello world )
"cmd2 param1 = \"\" "                    ->  ()
"cmd2 param1 = 3.14 param2 = 8e-9"       -> --
"cmd1 param1 = \" hello world \" "       -> --
"cmd2 param1 = \"\" trailing rubbish"    -> --
"trailing rubbish"                       -> --

You might consider staying with Qi for this.

Other Approaches

To get similar things done in X3 you some heroics will be required. Let me try by

  • ignoring missing or repeated assignments (cmd1 param1 = 3 param1 = 4 is fine)
  • using latest PFR additions for c++20 member name reflection
  • dropping the need for/use of Fusion adaptation

Note this builds on the ideas developed here Boost Spirit x3: parse into structs and specifically the workaround mentioned in the last comment

Here are the quick-and-dirty heroics:

namespace detail {
    template <typename Attr> auto member_parser = x3::eps;
    template <>
    auto member_parser<std::string> = x3::rule<struct quoted_string, std::string>{"quoted_string"} =
        x3::lexeme['"' >> *~x3::char_('"') >> '"'];

    template <> auto member_parser<double> = x3::double_;

    template <size_t II, typename T, typename Tuple> auto handle_member(Tuple const& tied) {
        auto&&      val = std::get<II>(tied);
        std::string name{boost::pfr::get_name<II, T>()};

        using Attr = std::decay_t<decltype(val)>;

        auto assign = [name](auto& ctx) { boost::pfr::get<II>(*x3::get<T>(ctx)) = _attr(ctx); };
        return x3::rule<struct _>{name.c_str()} = (x3::lit(name) >> '=' >> member_parser<Attr>)[assign];
    }

    template <typename T, typename Tuple, size_t... I>
    auto params_impl(Tuple const& tied, std::integer_sequence<size_t, I...>) {
        return *(handle_member<I, T, Tuple>(tied) | ...);
    }
} // namespace detail

template <typename T> auto make_parser(T const& v = {}) {
    std::string tname = boost::typeindex::type_id<T>().pretty_name();
    tname             = tname.substr(tname.find_last_of(":") + 1);
    std::cout << "---- " << tname << std::endl;

    auto set_context = [](auto& ctx) { x3::get<T>(ctx) = &_val(ctx); };

    return x3::rule<struct _, T>{tname.c_str()} = //
        x3::with<T>(static_cast<T*>(nullptr))     //
            [x3::eps[set_context]                 //
             >> x3::lit(tname)                    //
             >> detail::params_impl<T>(boost::pfr::structure_tie(v),
                                       std::make_index_sequence<boost::pfr::tuple_size<T>::value>{})];
}

I would probably clean it up to use static type info instead of requiring default-constructability, but in the interest of speed let's keep it as that. Now, use it:

namespace parser {
    auto const command = make_parser<ast::cmd1>() | make_parser<ast::cmd2>();
} // namespace parser

Or indeed, with some more factory help:

template <typename... Cmd> auto commands() { return (make_parser<Cmd>() | ...); }

auto const command = commands<ast::cmd1, ast::cmd2>();

Integrating in the example test cases:

Live On Coliru

#include <boost/pfr.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/type_index.hpp>
#include <iomanip>
#include <iostream>
#include <optional>

namespace x3 = boost::spirit::x3;

namespace ast {
    struct cmd1 { double param1, param2; };
    struct cmd2 { std::string param1; };

    using Command = boost::variant<cmd1, cmd2>;
} // namespace ast

namespace parser {
    namespace detail {
        template <typename Attr> auto member_parser = x3::eps;
        template <>
        auto member_parser<std::string> = x3::rule<struct quoted_string, std::string>{"quoted_string"} =
            x3::lexeme['"' >> *~x3::char_('"') >> '"'];

        template <> auto member_parser<double> = x3::double_;

        template <size_t II, typename T, typename Tuple> auto handle_member(Tuple const& tied) {
            auto&&      val = std::get<II>(tied);
            std::string name{boost::pfr::get_name<II, T>()};

            using Attr = std::decay_t<decltype(val)>;

            auto assign = [name](auto& ctx) { boost::pfr::get<II>(*x3::get<T>(ctx)) = _attr(ctx); };
            return x3::rule<struct _>{name.c_str()} = (x3::lit(name) >> '=' >> member_parser<Attr>)[assign];
        }

        template <typename T, typename Tuple, size_t... I>
        auto params_impl(Tuple const& tied, std::integer_sequence<size_t, I...>) {
            return *(handle_member<I, T, Tuple>(tied) | ...);
        }
    } // namespace detail

    template <typename T> auto make_parser(T const& v = {}) {
        std::string tname = boost::typeindex::type_id<T>().pretty_name();
        tname             = tname.substr(tname.find_last_of(":") + 1);

        auto set_context = [](auto& ctx) { x3::get<T>(ctx) = &_val(ctx); };

        return x3::rule<struct _, T>{tname.c_str()} = //
            x3::with<T>(static_cast<T*>(nullptr))     //
                [x3::eps[set_context]                 //
                 >> x3::lit(tname)                    //
                 >> detail::params_impl<T>(boost::pfr::structure_tie(v),
                                           std::make_index_sequence<boost::pfr::tuple_size<T>::value>{})];
    }

    template <typename... Cmd> auto commands() { return (make_parser<Cmd>() | ...); }

    auto const command = commands<ast::cmd1, ast::cmd2>();
} // namespace parser

template <typename It> std::optional<ast::Command> parse_line(It first, It last) {
    ast::Command attr;

    // if (phrase_parse(first, last, x3::expect[parser::command >> x3::eoi], x3::space, attr))
    if (phrase_parse(first, last, parser::command >> x3::eoi, x3::space, attr))
        return attr;
    return std::nullopt;
}

auto parse_line(std::string_view input) { return parse_line(begin(input), end(input)); }

int main() {
    // for (std::string line; getline(std::cin, line) && !line.empty();) {
    for (std::string line :
         {
             R"()",
             R"(cmd1 param1 = 3.14 param2 = 8e-9)",
             R"(cmd1 param2 = 8e-9 param1 = 3.14)", // flipped
             R"(cmd1 param1 = 3.14 param2 = -inf)",
             R"(cmd1 param2 = -inf param1 = 3.14)", // flipped
             R"(cmd2 param1 = " hello world " )",

             // things that would not have parsed with question code:
             R"(cmd2 param1 = "" )",

             // things that should not parse
             R"(cmd2 param1 = 3.14 param2 = 8e-9)",
             R"(cmd1 param1 = " hello world " )",
             R"(cmd2 param1 = "" trailing rubbish)",
             R"(trailing rubbish)",
         }) //
    {
        std::cout << std::left << std::setw(37) << quoted(line);
        try {
            if (auto parsed = parse_line(line)) {
                apply_visitor(
                    [](auto const& cmd) {
                        std::cout << " -> " << boost::typeindex::type_id_runtime(cmd).pretty_name()
                                  << boost::pfr::io(cmd) << std::endl;
                    },
                    *parsed);
            } else {
                std::cout << " -> --" << std::endl;
            }
        } catch (std::exception const& e) {
            std::cout << " -> ERROR " << e.what() << std::endl;
        }
    }
}

Printing

""                                    -> --
"cmd1 param1 = 3.14 param2 = 8e-9"    -> ast::cmd1{3.14, 8e-09}
"cmd1 param2 = 8e-9 param1 = 3.14"    -> ast::cmd1{3.14, 8e-09}
"cmd1 param1 = 3.14 param2 = -inf"    -> ast::cmd1{3.14, -inf}
"cmd1 param2 = -inf param1 = 3.14"    -> ast::cmd1{3.14, -inf}
"cmd2 param1 = \" hello world \" "    -> ast::cmd2{" hello world "}
"cmd2 param1 = \"\" "                 -> ast::cmd2{""}
"cmd2 param1 = 3.14 param2 = 8e-9"    -> --
"cmd1 param1 = \" hello world \" "    -> --
"cmd2 param1 = \"\" trailing rubbish" -> --
"trailing rubbish"                    -> --

Summarizing

I would probably make a general grammar and AST like

enum class CmdType { cmd1, cmd2, ... };
using Param = std::string;
using Value = variant<double, std::string>;
using Args  = std::multimap<Param, Value>;

struct Cmd {
    CmdType cmd;
    Args    args;
};

And create a validator function that validates the correctness of the commands after parsing. This way you get a very simple grammar that's easy to maintain, and way more flexibility regarding validation logic.

2
On

Not really another answer - just an attempt to generalize cmd followed by tag=value pairs based on the suggestion by @sehe

#include <bits/stdc++.h>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

namespace client::ast
{
    enum class CmdType : int {
        Cmd1,
        Cmd2,
        Cmd3,
        Cmd4
    };

    using Arg = boost::variant<std::string, double, int, bool>;
    using Args = std::map<std::string, Arg>;
    struct Command
    {
        CmdType m_cmdType;
        Args m_args;
    };

    using boost::fusion::operator<<;
}

BOOST_FUSION_ADAPT_STRUCT(client::ast::Command, m_cmdType, m_args);

namespace parser {
    /// write a x3 parser for a command with the following syntax:
    ///   <cmd> <param1>=<value1> <param2>=<value2>....
    /// where <cmd> is one of the enum values in client::Command
    //// param1..n are strings
    /// value1..n are <string, double, int, bool> values
    struct CmdTypeTable : x3::symbols<client::ast::CmdType> {
        CmdTypeTable() {
            add
                ("Cmd1", client::ast::CmdType::Cmd1)
                ("Cmd2", client::ast::CmdType::Cmd2)
                ("Cmd3", client::ast::CmdType::Cmd3)
                ("Cmd4", client::ast::CmdType::Cmd4)
            ;
        }
    } CmdTypeParser;

    x3::rule<class cmd_class, client::ast::Command> const cmd = "Command";

    auto const quoted_string = x3::lexeme['"' >> *~x3::char_('"') >> '"'];
    auto const cmd_def = CmdTypeParser >> *(x3::lexeme[+~x3::char_(" =")] >> '=' >> (quoted_string | x3::double_ | x3::int_ | x3::bool_));
    BOOST_SPIRIT_DEFINE(cmd);
}

////////////////////////////////////////////////////////////////////////////
//  Main program
////////////////////////////////////////////////////////////////////////////
int
main()
{
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Command with the following syntax:\n";
    std::cout << "    <cmd> <param1>=<value1> <param2>=<value2>....\n\n";
    std::cout << "/////////////////////////////////////////////////////////\n\n";
    std::cout << "Type [q or Q] to quit\n\n";
    std::string str;
    while (getline(std::cin, str))
    {
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
            break;

        client::ast::Command cmd;
        auto iter = str.begin();
        auto end = str.end();
        bool r = x3::phrase_parse(iter, end, parser::cmd, ascii::space, cmd);

        if (r && iter == end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            std::ranges::for_each(cmd.m_args, [](auto const &arg) {
                std::cout << arg.first << " = " << arg.second << "\n";
            });
            std::cout << "\n-------------------------\n";
        }
        else
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing failed\n";
            std::cout << "-------------------------\n";
        }
    }

    std::cout << "Bye... :-) \n\n";
    return 0;
}
5
On

The following x3 code, when defined(USE_CHK_REASSIGN) assures that parameters are only assigned once, and all parameters are assigned in any order.

Now, the suggested x3 alternative to the qi version has the following distinct advantages:

  1. Immediate diagnosis of reassignment.
  2. diagnosis of incomplete assignment.

For case 1, the qi version will not pass:

but will also not say which param was attemptedly reassigned. Instead, it simply prints "--".

For case 2, the qi version will pass, without assigning all.

For example, with input, "cmd1 param1 = 1.1", passes but leaves param2 with the default value.

@sehe, could you please highlight any advantage that the qi version has over the suggested x3 alternative?

//#define USE_CHK_REASSIGN
#ifdef USE_CHK_REASSIGN
  #include <boost/optional/optional_io.hpp>
#endif
#include <boost/variant.hpp>
#include <iostream>
#include <string_view>
#include <boost/fusion/include/make_map.hpp>
  template
  < typename... Keys
  , typename... Vals
  >
  std::ostream&
operator<<
  ( std::ostream&os
  , boost::fusion::map
    < boost::fusion::pair
      < Keys
      , Vals
      >...
    >const&map
  )
  {
  ; auto const pair_print=[]
      < typename Key
      >
      ( std::ostream&o
      , auto const&m
      )
      { o<<Key{}<<"->"<<boost::fusion::at_key<Key>(m)<<'\n'
      ;};
  ; ( ... , pair_print.template operator()<Keys>(os,map))
  ; return os
  ;}
#include <boost/spirit/home/x3.hpp>

namespace fusion = boost::fusion;
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

namespace ast
  {
    enum class keys_enum
      : unsigned
      { key0
      , key1
      };
    constexpr unsigned num_keys
      = 1+(unsigned)keys_enum::key1
      ;
      static constexpr std::string_view 
    key_names[num_keys]=
      { "key0"
      , "key1"
      };
      template
      < keys_enum KeyEnum
      >
    struct keys_type
      {
            friend
          std::ostream&
        operator<<
          ( std::ostream&os
          , keys_type<KeyEnum>
          )
          { return os<<key_names[(unsigned)KeyEnum]
          ;}
      #ifdef USE_CHK_REASSIGN
          template
          < typename Context
          >
          void
        operator()(Context&ctx)
          { 
          ; std::cout<<"reassign check:KeyEnum="<<key_names[(unsigned)KeyEnum]<<";\n"
          ; auto&map=x3::_val(ctx)
          ; auto&val=fusion::at_key<keys_type>(map)
          ; std::cout<<":_val="<<val<<";\n"
          ; bool defined=val.is_initialized()
          ; if(defined)
            {
            ; std::cout<<":redefinition disallowed!\n"<<std::flush
            ; _pass(ctx)=false
            ;}
            else
            { auto const&attr=x3::_attr(ctx)
            ; std::cout<<":_attr="<<attr<<";\n"
            ; val = attr
            ;}
          ;}
      #endif//USE_CHK_REASSIGN
      };
    using key0_attr
      = keys_type<keys_enum::key0>
      ;
    using key1_attr
      = keys_type<keys_enum::key1>
      ;
  }//ast
namespace parser 
  {
    auto key0_def
      =  x3::lit("key0")
      >> x3::attr(ast::key0_attr())
      ;
    auto key1_def
      =  x3::lit("key1")
      >> x3::attr(ast::key1_attr())
      ;
    auto val0_def
      = x3::int_
      ;
    auto val1_def
      = x3::upper
      ;
    auto key_val_def
      = key0_def >> val0_def
      #ifdef USE_CHK_REASSIGN
        [ ast::keys_type<ast::keys_enum::key0>()
        ] 
      #endif//USE_CHK_REASSIGN
      | key1_def >> val1_def
      #ifdef USE_CHK_REASSIGN
        [ ast::keys_type<ast::keys_enum::key1>()
        ] 
      #endif//USE_CHK_REASSIGN
      ;
    auto key_vals_def
      = x3::repeat(ast::num_keys)[key_val_def]
      ;
    auto map_key_vals_def
      = key_vals_def
      ;
  }//parser
namespace ast
  {
      using
    val0_attr = typename
      x3::traits::attribute_of
      < decltype(parser::val0_def)
      , x3::unused_type
      >::type
      ;
      using
    val1_attr = typename
      x3::traits::attribute_of
      < decltype(parser::val1_def)
      , x3::unused_type
      >::type
      ;
      using 
    map_key_vals_attr = 
      decltype
      ( fusion::make_map
        < key0_attr
        , key1_attr
        >
      #ifdef USE_CHK_REASSIGN
        ( boost::optional<val0_attr>{}
        , boost::optional<val1_attr>{}
      #else
        ( val0_attr{}
        , val1_attr{}
      #endif
        )
      )
      ;
      using
    key_val_attr = typename
      x3::traits::attribute_of
      < decltype(parser::key_val_def)
      , x3::unused_type
      >::type
      ;
      using
    key_vals_attr = typename
      x3::traits::attribute_of
      < decltype(parser::key_vals_def)
      , x3::unused_type
      >::type
      ;
  }//ast
namespace parser 
  {
  #ifdef USE_CHK_REASSIGN
    auto map_key_vals
        //"immediate" rule to get rule attribute stored into rcontext
        //so it can be accessed by semantic actions.
        //No need for BOOST_SPIRIT_DEFINE because no recursion.
      = x3::rule<class map_key_vals_rule,ast::map_key_vals_attr>
        {"map_key_vals_rule"}
      = map_key_vals_def
      ;
  #endif//USE_CHK_REASSIGN
  }//parser
////////////////////////////////////////////////////////////////////////////
//  Main program
////////////////////////////////////////////////////////////////////////////
int
main()
{
  std::cout<<std::boolalpha;
  {//tests
    for 
    ( std::string str
    : { R"(key0 5 key1 C key1 D)"
        //should fail because all keys assigned but stops before reading reassignment.
      , R"(key0 6 key1 E)"
        //should pass
      , R"(key1 F key0 7)"
        //should pass despite reverse order
      , R"(key1 G)"
        //should fail because key0 not assigned
      , R"(key1 H key1 I)"
        //should fail because key1 reassigned 
      }
    )
    {
        std::cout<<":str="<<str<<";\n";
        ast::map_key_vals_attr map_key_vals_val;
        auto iter = str.begin();
        auto end = str.end();
        bool result = 
          x3::phrase_parse
          ( iter, end
        #ifdef USE_CHK_REASSIGN
          , parser::map_key_vals
        #else 
          , parser::map_key_vals_def
        #endif//USE_CHK_REASSIGN
          , ascii::space
          , map_key_vals_val
          );
        bool at_end = (iter == end);
        std::cout << ":map_key_vals_val=\n";
        std::cout<<map_key_vals_val<<";\n";
        if (result && at_end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            std::cout << "\n-------------------------\n";
        }
        else
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing failed\n";
            std::cout << "result="<< result <<"\n";
            std::cout << "at_end="<< at_end <<"\n";
            if(!at_end)
            { std::string remaining_input(iter,end)
            ; std::cout << "remaining input="<<remaining_input<<"\n"
            ;}
            std::cout << "-------------------------\n";
        }
    }
  }//tests
  return 0;
}

which produces following output:

:str=key0 5 key1 C key1 D;
reassign check:KeyEnum=key0;
:_val=--;
:_attr=5;
reassign check:KeyEnum=key1;
:_val=--;
:_attr=C;
:map_key_vals_val=
key0-> 5
key1-> C
;
-------------------------
Parsing failed
result=true
at_end=false
remaining input=key1 D
-------------------------
:str=key0 6 key1 E;
reassign check:KeyEnum=key0;
:_val=--;
:_attr=6;
reassign check:KeyEnum=key1;
:_val=--;
:_attr=E;
:map_key_vals_val=
key0-> 6
key1-> E
;
-------------------------
Parsing succeeded

-------------------------
:str=key1 F key0 7;
reassign check:KeyEnum=key1;
:_val=--;
:_attr=F;
reassign check:KeyEnum=key0;
:_val=--;
:_attr=7;
:map_key_vals_val=
key0-> 7
key1-> F
;
-------------------------
Parsing succeeded

-------------------------
:str=key1 G;
reassign check:KeyEnum=key1;
:_val=--;
:_attr=G;
:map_key_vals_val=
key0->--
key1-> G
;
-------------------------
Parsing failed
result=false
at_end=false
remaining input=key1 G
-------------------------
:str=key1 H key1 I;
reassign check:KeyEnum=key1;
:_val=--;
:_attr=H;
reassign check:KeyEnum=key1;
:_val= H;
:redefinition disallowed!
:map_key_vals_val=
key0->--
key1-> H
;
-------------------------
Parsing failed
result=false
at_end=false
remaining input=key1 H key1 I
-------------------------

Compiler was clang++ version 17.0.1 with:

 -c -O0 -g -ggdb -ftemplate-backtrace-limit=0 -O0 -g -ggdb  -std=c++2b -ftemplate-backtrace-limit=0 -fdiagnostics-show-template-tree -fno-elide-type -fmacro-backtrace-limit=0 -fexceptions -fcxx-exceptions -Wno-deprecated