Boost Spirit Qi: привязка к структуре с вектором кортежей - PullRequest
1 голос
/ 12 марта 2020

Boost Spirit Разбор Qi, безусловно, является уникальным приложением C ++, которое идет с крутой кривой обучения. В этом случае я пытаюсь разобрать строку, содержащую синтаксически правильную C ++-инициализацию списка struct, содержащую std::vector из std::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. Закомментированный start работает как положено, если я также закомментирую vector член struct. (Я также пробовал 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 } } };"

Ответы [ 2 ]

1 голос
/ 13 марта 2020

Помимо двух вещей, которые вы упомянули (которые являются правильными), я бы предложил

  1. некоторые упрощения:

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

    Обратите внимание, это удаляет все подпункты -expressions, as-string и deepcopy, вместо этого используется целочисленный синтаксический анализатор:

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

    Синтаксический анализатор raw[] будет приятно отображать совпадения исходной строки.

  2. Далее up,

    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>
    
1 голос
/ 12 марта 2020

Я отвечаю на свой вопрос. Я сделал две ошибки. Во-первых, правило label имело неправильный тип атрибута, std::string() вместо std::tuple<std::string, int>().

Вторая ошибка заключалась в том, что мне нужно было #include <boost/fusion/adapted/std_tuple.hpp>. Я обнаружил это только случайно, поскольку этого не было в документации 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";
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...