boost::spirit::karma грамматика: разделенный запятыми вывод из структуры с необязательными атрибутами

Мне нужен вывод через запятую из структуры с опциями. Например, если у меня есть эта структура:

MyStruct 
{ 
    boost::optional<std::string> one;
    boost::optional<int> two;
    boost::optional<float> three;
};

Вывод, например: { "строка", 1, 3.0 } или { "строка" } или { 1, 3.0 } и так далее.

Теперь у меня есть такой код:

struct MyStruct
{
    boost::optional<std::string> one;
    boost::optional<int> two;
    boost::optional<float> three;
};

BOOST_FUSION_ADAPT_STRUCT
(MyStruct,
one,
two,
three)

template<typename Iterator>
struct MyKarmaGrammar : boost::spirit::karma::grammar<Iterator, MyStruct()>
{
    MyKarmaGrammar() : MyKarmaGrammar::base_type(request_)
    {
        using namespace std::literals::string_literals;
        namespace karma = boost::spirit::karma;

        using karma::int_;
        using karma::double_;
        using karma::string;
        using karma::lit;
        using karma::_r1;

        key_ = '"' << string(_r1) << '"';

        str_prop_ = key_(_r1) << ':' 
            << string
            ;

        int_prop_ = key_(_r1) << ':' 
            << int_
            ;

        dbl_prop_ = key_(_r1) << ':' 
            << double_
            ;

        //REQUEST
        request_ = '{'
            <<  -str_prop_("one"s) <<
                -int_prop_("two"s) <<
                -dbl_prop_("three"s)
            << '}'
            ;
    }

private:
    //GENERAL RULES
    boost::spirit::karma::rule<Iterator, void(std::string)> key_;
    boost::spirit::karma::rule<Iterator, double(std::string)> dbl_prop_;
    boost::spirit::karma::rule<Iterator, int(std::string)> int_prop_;
    boost::spirit::karma::rule<Iterator, std::string(std::string)> str_prop_;

    //REQUEST
    boost::spirit::karma::rule<Iterator, MyStruct()> request_;
};

int main()
{
    using namespace std::literals::string_literals;

    MyStruct request = {std::string("one"), 2, 3.1};

    std::string generated;
    std::back_insert_iterator<std::string> sink(generated);

    MyKarmaGrammar<std::back_insert_iterator<std::string>> serializer;

    boost::spirit::karma::generate(sink, serializer, request);

    std::cout << generated << std::endl;
}

Это работает, но мне нужен вывод через запятую. Я пробовал с грамматикой вроде:

request_ = '{'
    <<  (str_prop_("one"s) |
        int_prop_("two"s) |
        dbl_prop_("three"s)) % ','
    << '}'
    ;

Но я получаю эту ошибку компиляции:

/usr/include/boost/spirit/home/support/container.hpp:194:52: error: no type named ‘const_iterator’ in ‘struct MyStruct’
         typedef typename Container::const_iterator type;

Спасибо!

1 ответ

Решение

Ваша структура не является контейнером, поэтому list- operator% не будет работать. В документации говорится, что ожидается, что атрибут будет контейнерным типом.

Итак, как и в аналоге Ци, я показал вам создать условное delim производство:

 delim         = (&qi::lit('}')) | ',';

Вам нужно что-то подобное здесь. Однако все об этом перевернуто. Вместо "обнаружения" конца входной последовательности по наличию {, нам нужно отследить отсутствие предшествующего поля из-за того, что "еще не было вывода поля с момента открытия скобки".

Это немного сложнее, поскольку требуемое состояние не может поступать из того же источника, что и входные данные. Мы будем использовать парсер-член для простоты здесь:

private:
  bool _is_first_field;

Теперь, когда мы генерируем открывающую скобку, мы хотим инициализировать это true:

    auto _f = px::ref(_is_first_field); // short-hand
    request_ %= lit('{') [ _f = true ]

Примечание: использование %= вместо = сообщает Spirit, что мы хотим, чтобы автоматическое распространение атрибутов происходило, несмотря на наличие семантического действия ( [ _f = true ] )

Теперь нам нужно сгенерировать разделитель:

    delim = eps(_f) | ", ";

Просто. Использование также просто, за исключением того, что мы хотим условно reset _f:

    auto reset = boost::proto::deep_copy(eps [ _f = false ]);

    str_prop_ %= (delim << key_(_r1) << string  << reset) | ""; 
    int_prop_ %= (delim << key_(_r1) << int_    << reset) | ""; 
    dbl_prop_ %= (delim << key_(_r1) << double_ << reset) | ""; 

Очень тонкий момент здесь заключается в том, что я изменил на объявленные типы атрибутов правила с T в optional<T>, Это позволяет Карме сотворить магию, чтобы завершить работу генератора значений, если он пуст (boost::none), и пропустить reset!

ka::rule<Iterator, boost::optional<double>(std::string)> dbl_prop_;
ka::rule<Iterator, boost::optional<int>(std::string)> int_prop_;
ka::rule<Iterator, boost::optional<std::string>(std::string)> str_prop_;

Теперь давайте соберем несколько тестов:

Тестовые случаи

Жить на Колиру

#include "iostream"
#include <boost/optional/optional_io.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>

struct MyStruct {
    boost::optional<std::string> one;
    boost::optional<int> two;
    boost::optional<double> three;
};

BOOST_FUSION_ADAPT_STRUCT(MyStruct, one, two, three)

namespace ka = boost::spirit::karma;
namespace px = boost::phoenix;

template<typename Iterator>
struct MyKarmaGrammar : ka::grammar<Iterator, MyStruct()> {
    MyKarmaGrammar() : MyKarmaGrammar::base_type(request_) {
        using namespace std::literals::string_literals;

        using ka::int_;
        using ka::double_;
        using ka::string;
        using ka::lit;
        using ka::eps;
        using ka::_r1;

        auto _f    = px::ref(_is_first_field);
        auto reset = boost::proto::deep_copy(eps [ _f = false ]);

        key_ = '"' << string(_r1) << "\":";

        delim = eps(_f) | ", ";

        str_prop_ %= (delim << key_(_r1) << string  << reset) | ""; 
        int_prop_ %= (delim << key_(_r1) << int_    << reset) | ""; 
        dbl_prop_ %= (delim << key_(_r1) << double_ << reset) | ""; 

        //REQUEST
        request_ %= lit('{') [ _f = true ]
            <<  str_prop_("one"s) <<
                int_prop_("two"s) <<
                dbl_prop_("three"s)
            << '}';
    }

  private:
    bool _is_first_field = true;
    //GENERAL RULES
    ka::rule<Iterator, void(std::string)> key_;
    ka::rule<Iterator, boost::optional<double>(std::string)> dbl_prop_;
    ka::rule<Iterator, boost::optional<int>(std::string)> int_prop_;
    ka::rule<Iterator, boost::optional<std::string>(std::string)> str_prop_;
    ka::rule<Iterator> delim;

    //REQUEST
    ka::rule<Iterator, MyStruct()> request_;
};

template <typename T> std::array<boost::optional<T>, 2> option(T const& v) {
    return { { v, boost::none } };
}

int main() {
    using namespace std::literals::string_literals;

    for (auto a : option("one"s))
    for (auto b : option(2))
    for (auto c : option(3.1))
    for (auto request : { MyStruct { a, b, c } }) {
        std::string generated;
        std::back_insert_iterator<std::string> sink(generated);

        MyKarmaGrammar<std::back_insert_iterator<std::string>> serializer;

        ka::generate(sink, serializer, request);

        std::cout << boost::fusion::as_vector(request) << ":\t" << generated << "\n";
    }
}

Печать:

( one  2  3.1): {"one":one, "two":2, "three":3.1}
( one  2 --):   {"one":one, "two":2}
( one --  3.1): {"one":one, "three":3.1}
( one -- --):   {"one":one}
(--  2  3.1):   {"two":2, "three":3.1}
(--  2 --):     {"two":2}
(-- --  3.1):   {"three":3.1}
(-- -- --):     {}

¹ Обратите внимание, что это ограничивает повторное использование синтаксического анализатора, а также делает его неконстантным и т. Д. karma::locals верный ответ на это, добавив немного больше сложности

Другие вопросы по тегам