【问题标题】:Boost Spirit Qi: binding to struct with vector of tuplesBoost Spirit Qi:用元组向量绑定到结构体
【发布时间】:2020-03-12 19:16:11
【问题描述】:

Boost Spirit Qi 解析当然是 C++ 的一个独特应用,它具有陡峭的学习曲线。在这种情况下,我试图解析一个字符串,该字符串包含语法正确的 C++ 列表初始化的 struct,其中包含 std::vectorstd::tuple<std::string, short>。这是struct的声明:

typedef std::vector<std::tuple<std::string, int>> label_t;

struct BulkDataParmas
{
    std::string strUUID;
    short subcam;
    long long pts_beg;
    long long pts_len;
    long long pts_gap;
    label_t labels;
};

这是我将这种结构绑定到 Qi 属性的失败尝试。如果我还注释掉structvector 成员,注释掉的start 将按预期工作。 (我也试过std::pair 而不是std::tuple)。

BOOST_FUSION_ADAPT_STRUCT
(
    BulkDataParmas,
    (std::string, strUUID)
    (short, subcam)
    (long long, pts_beg)
    (long long, pts_len)
    (long long, pts_gap)
    (label_t, labels)
)



template <typename Iterator>
struct load_parser : boost::spirit::qi::grammar<Iterator, BulkDataParmas(), boost::spirit::ascii::space_type>
{
    load_parser() : load_parser::base_type(start)
    {
        namespace qi = boost::spirit::qi;
        namespace ascii = boost::spirit::ascii;
        using qi::attr;
        using qi::short_;
        using qi::int_;
        using qi::long_long;
        using qi::lit;
        using qi::xdigit;
        using qi::lexeme;
        using ascii::char_;
        using boost::proto::deep_copy;

        auto hex2_ = deep_copy(xdigit >> xdigit >> xdigit >> xdigit);
        auto hex4_ = deep_copy(hex2_ >> hex2_);
        auto hex6_ = deep_copy(hex4_ >> hex2_);
        auto fmt_  = deep_copy('"' >> hex4_ >> char_('-') >> hex2_ >> char_('-') >> hex2_ >> char_('-') >> hex2_ >> char_('-') >> hex6_ >> '"');
        uuid = qi::as_string[fmt_];

        quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];

        label = '{' >> quoted_string >> ',' >> int_ >> '}';

        start = '{' >>  uuid >> ',' >> short_ >> ',' >> long_long >> ',' >> long_long >> ',' >> long_long >> ',' >> '{' >> -(label >> *(',' >> label)) >>'}' >> '}';
//        start = '{' >>  uuid >> ',' >> short_ >> ',' >> long_long >> ',' >> long_long >> ',' >> long_long >> '}';
    }

private:

    boost::spirit::qi::rule<Iterator, std::string()> uuid;
    boost::spirit::qi::rule<Iterator, std::string()> quoted_string;
    boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> label;
    boost::spirit::qi::rule<Iterator, BulkDataParmas(), boost::spirit::ascii::space_type> start;
};

这是一个要解析的示例字符串:

"{ \"68965363-2d87-46d4-b05d-f293f2c8403b\", 0, 1583798400000000, 86400000000, 600000000, { { \"motorbike\", 5 }, { \"aeroplane\", 6 } } };"

【问题讨论】:

    标签: c++ parsing boost boost-spirit boost-spirit-qi


    【解决方案1】:

    除了你提到的两件事(这是正确的),我建议

    1. 一些简化:

      uuid = '"' >> qi::raw [
          hex_<4>{} >> qi::repeat(3)['-' >> hex_<2>{}] >> '-' >> hex_<6>{}
      ] >> '"';
      

      注意,这会删除所有子表达式,as-string 和 deepcopy,而不是使用整数解析器:

      template<int N> using hex_ = boost::spirit::qi::int_parser<std::intmax_t, 16, 2*N, 2*N>;
      

      raw[] 解析器将很好地公开匹配的源字符串。

    2. 接下来,

      quoted_string = '"' >> *~qi::char_('"') >> '"';
      

      这里我建议使用* 来接受空字符串(这经常 引用字符串的“点”,所以我们可以明确地嵌入 空格或有意为空的字符串)。另外,使用~charset 是 更高效。

      还删除了lexeme[],因为无论如何都已经声明了规则而没有船长。

    3. 完成:

      label = '{' >> quoted_string >> ',' >> qi::int_ >> '}';
      
      start = qi::skip(ascii::space) [ '{'
          >> uuid      >> ','
          >> qi::auto_ >> ','
          >> qi::auto_ >> ','
          >> qi::auto_ >> ','
          >> qi::auto_ >> ','
          >> '{' >> -(label % ',') >> '}'
          >> '}' >> ';'
      ];
      

      请注意,我合并了船长的选择。所以你不必在phrase_parse 中繁琐地传递正确的东西。船长通常不是调用者应该能够更改的东西。

    4. 现在让我们也对改编进行现代化改造:

      BOOST_FUSION_ADAPT_STRUCT(BulkDataParams, strUUID, subcam, pts_beg, pts_len, pts_gap, labels)
      

      之后,您可以以现代方式重新拼写类型,而不会冒任何兼容性问题的风险。请注意,这也是在那里的开始规则中更喜欢qi::auto_ 的原因,因此您不会在例如解析器结果以预期的方式隐式转换为目标类型。

      struct BulkDataParams {
          std::string strUUID;
          int16_t subcam;
          int64_t pts_beg;
          int64_t pts_len;
          int64_t pts_gap;
          label_t labels;
      };
      
    5. 现在让我们输入调试输出和测试体:

      Live On Wandbox

      #define BOOST_SPIRIT_DEBUG
      #include <boost/spirit/include/qi.hpp>
      #include <boost/fusion/adapted/std_tuple.hpp>
      #include <iostream>
      #include <iomanip>
      
      using label_t = std::vector<std::tuple<std::string, int>>;
      
      namespace std {
          std::ostream& operator<<(std::ostream& os, label_t::value_type const& t) {
              auto const& [k,v] = t;
              return os << "[" << std::quoted(k) << "," << v << "]";
          }
      
          std::ostream& operator<<(std::ostream& os, label_t const& m) {
              os << "{";
              for (auto&& el:m) os << el << ",";
              return os << "}";
          }
      }
      
      struct BulkDataParams {
          std::string strUUID;
          int16_t subcam;
          int64_t pts_beg;
          int64_t pts_len;
          int64_t pts_gap;
          label_t labels;
      };
      
      BOOST_FUSION_ADAPT_STRUCT(BulkDataParams, strUUID, subcam, pts_beg, pts_len, pts_gap, labels)
      
      template <typename Iterator> struct load_parser : boost::spirit::qi::grammar<Iterator, BulkDataParams()> {
          load_parser() : load_parser::base_type(start) {
              namespace qi = boost::spirit::qi;
              namespace ascii = boost::spirit::ascii;
      
              uuid = '"' >> qi::raw [
                  hex_<4>{} >> qi::repeat(3)['-' >> hex_<2>{}] >> '-' >> hex_<6>{}
              ] >> '"';
      
              quoted_string = '"' >> *~qi::char_('"') >> '"';
      
              label = '{' >> quoted_string >> ',' >> qi::int_ >> '}';
      
              start = qi::skip(ascii::space) [ '{'
                  >> uuid      >> ','
                  >> qi::auto_ >> ','
                  >> qi::auto_ >> ','
                  >> qi::auto_ >> ','
                  >> qi::auto_ >> ','
                  >> '{' >> -(label % ',') >> '}'
                  >> '}' >> ';'
              ];
      
              BOOST_SPIRIT_DEBUG_NODES(
                  (uuid) (quoted_string) (label) (start)
              )
          }
      
          template<int N> using hex_ = boost::spirit::qi::int_parser<std::intmax_t, 16, 2*N, 2*N>;
      
        private:
          boost::spirit::qi::rule<Iterator, std::string()> uuid;
          boost::spirit::qi::rule<Iterator, std::string()> quoted_string;
          boost::spirit::qi::rule<Iterator, label_t::value_type(), boost::spirit::ascii::space_type> label;
          boost::spirit::qi::rule<Iterator, BulkDataParams()> start;
      };
      
      int main() {
      
          for (std::string const input : {
              R"({ "68965363-2d87-46d4-b05d-f293f2c8403b", 0, 1583798400000000, 86400000000, 600000000, { { "motorbike", 5 }, { "aeroplane", 6 } } };)",
          })
          {
              auto f = begin(input), l = end(input);
              BulkDataParams bdp;
              load_parser<std::string::const_iterator> p;
              if (parse(f, l, p, bdp)) {
                  std::cout << "Parsed: " << boost::fusion::as_vector(bdp) << "\n";
              } else {
                  std::cout << "Parse Failed\n";
              }
      
              if (f != l) {
                  std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << "\n";
              }
          }
      }
      

      常规输出:

      Parsed: (68965363-2d87-46d4-b05d-f293f2c8403b 0 1583798400000000 86400000000 600000000 {["motorbike",5],["aeroplane",6],})

      调试输出:

      <start>
        <try>{ "68965363-2d87-46d</try>
        <uuid>
          <try>"68965363-2d87-46d4-</try>
          <success>, 0, 158379840000000</success>
          <attributes>[[6, 8, 9, 6, 5, 3, 6, 3, -, 2, d, 8, 7, -, 4, 6, d, 4, -, b, 0, 5, d, -, f, 2, 9, 3, f, 2, c, 8, 4, 0, 3, b]]</attributes>
        </uuid>
        <label>
          <try> { "motorbike", 5 },</try>
          <quoted_string>
            <try>"motorbike", 5 }, { </try>
            <success>, 5 }, { "aeroplane"</success>
            <attributes>[[m, o, t, o, r, b, i, k, e]]</attributes>
          </quoted_string>
          <success>, { "aeroplane", 6 }</success>
          <attributes>[[[m, o, t, o, r, b, i, k, e], 5]]</attributes>
        </label>
        <label>
          <try> { "aeroplane", 6 } </try>
          <quoted_string>
            <try>"aeroplane", 6 } } }</try>
            <success>, 6 } } };</success>
            <attributes>[[a, e, r, o, p, l, a, n, e]]</attributes>
          </quoted_string>
          <success> } };</success>
          <attributes>[[[a, e, r, o, p, l, a, n, e], 6]]</attributes>
        </label>
        <success></success>
        <attributes>[[[6, 8, 9, 6, 5, 3, 6, 3, -, 2, d, 8, 7, -, 4, 6, d, 4, -, b, 0, 5, d, -, f, 2, 9, 3, f, 2, c, 8, 4, 0, 3, b], 0, 1583798400000000, 86400000000, 600000000, [[[m, o, t, o, r, b, i, k, e], 5], [[a, e, r, o, p, l, a, n, e], 6]]]]</attributes>
      </start>
      

    【讨论】:

    • 如何生成调试输出?
    • 这是完整列表中的#define BOOST_SPIRIT_DEBUG。在这种情况下,我将一些operator&lt;&lt; 重载偷偷带入std 命名空间,以使其适用于label_t。请注意,使用 X3 您将使用 BOOST_SPIRIT_X3_DEBUG
    【解决方案2】:

    我正在回答我自己的问题。我犯了两个错误。首先,规则label 的属性类型错误,std::string() 而不是std::tuple&lt;std::string, int&gt;()

    第二个错误是我需要#include &lt;boost/fusion/adapted/std_tuple.hpp&gt;。我偶然发现了这一点,因为这不在 Spirit 2.5 文档中。

    template <typename Iterator>
    struct load_parser : boost::spirit::qi::grammar<Iterator, BulkDataParmas(), boost::spirit::ascii::space_type>
    {
        load_parser() : load_parser::base_type(start)
        {
            namespace qi = boost::spirit::qi;
            namespace ascii = boost::spirit::ascii;
            using qi::attr;
            using qi::short_;
            using qi::int_;
            using qi::long_long;
            using qi::lit;
            using qi::xdigit;
            using qi::lexeme;
            using ascii::char_;
            using boost::proto::deep_copy;
    
            auto hex2_ = deep_copy(xdigit >> xdigit >> xdigit >> xdigit);
            auto hex4_ = deep_copy(hex2_ >> hex2_);
            auto hex6_ = deep_copy(hex4_ >> hex2_);
            auto fmt_  = deep_copy('"' >> hex4_ >> char_('-') >> hex2_ >> char_('-') >> hex2_ >> char_('-') >> hex2_ >> char_('-') >> hex6_ >> '"');
            uuid = qi::as_string[fmt_];
    
            quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];
    
            label = '{' >> quoted_string >> ',' >> int_ >> '}';
    
            start = '{' >>  uuid >> ',' >> short_ >> ',' >> long_long >> ',' >> long_long >> ',' >> long_long >> ',' >> '{' >> -(label >> *(',' >> label)) >>'}' >> '}';
    //        start = '{' >>  uuid >> ',' >> short_ >> ',' >> long_long >> ',' >> long_long >> ',' >> long_long >> '}';
        }
    
    private:
    
        boost::spirit::qi::rule<Iterator, std::string()> uuid;
        boost::spirit::qi::rule<Iterator, std::string()> quoted_string;
        boost::spirit::qi::rule<Iterator, std::tuple<std::string, int>(), boost::spirit::ascii::space_type> label;
        boost::spirit::qi::rule<Iterator, BulkDataParmas(), boost::spirit::ascii::space_type> start;
    };
    

    测试代码:

    void doTestParser2()
    {
        for
        (
            auto& input : std::list<std::string>
            {
                "{ \"68965363-2d87-46d4-b05d-f293f2c8403b\", 0, 1583798400000000, 86400000000, 600000000, {  } };",
                "{ \"68965363-2d87-46d4-b05d-f293f2c8403b\", 0, 1583798400000000, 86400000000, 600000000, { { \"motorbike\", 5 } } };",
                "{ \"68965363-2d87-46d4-b05d-f293f2c8403b\", 0, 1583798400000000, 86400000000, 600000000, { { \"motorbike\", 5 }, { \"aeroplane\", 6 } } };"
            }
        )
        {
            using namespace boost::spirit;
    
            auto f(std::begin(input)), l(std::end(input));
            load_parser<decltype(f)> p;
    
            try
            {
                BulkDataParmas result { };
                std::string sresult { };
                bool ok = qi::phrase_parse(f, l, p > ';', qi::ascii::space, result);
    
                if (!ok)
                    std::cerr << "invalid input" << std::endl;
                else
                {
                    std::cout << "ok: " << input << std::endl;
                    std::cout << "UUID:     " << result.strUUID << std::endl;
                    std::cout << "subcam:   " << result.subcam << std::endl;
                    std::cout << "pts_beg:  " << result.pts_beg << std::endl;
                    std::cout << "pts_len:  " << result.pts_len << std::endl;
                    std::cout << "pts_gap:  " << result.pts_gap << std::endl;
                    for (auto const& tup : result.labels)
                    {
                        std::cout << "label:    " << std::get<0>(tup) << std::endl;
                        std::cout << "level:    " << std::get<1>(tup) << std::endl;
                    }
    
                }
    
            }
            catch (const qi::expectation_failure<decltype(f)>& e)
            {
                std::cerr << "expectation_failure at '" << std::string(e.first, e.last) << "'\n";
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多