Понимание оператора списка (%) в Boost.Spirit

Можете ли вы помочь мне понять разницу между a % b парсер и его расширен a >> *(b >> a) форма в Boost.Spirit? Хотя в справочном руководстве говорится, что они эквивалентны,

Оператор списка, a % b, является двоичным оператором, который соответствует списку из одного или нескольких повторений a разделены вхождениями b, Это эквивалентно a >> *(b >> a),

следующая программа дает разные результаты в зависимости от того, какой из них используется:

#include <iostream>
#include <string>
#include <vector>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

struct Record {
  int id;
  std::vector<int> values;
};

BOOST_FUSION_ADAPT_STRUCT(Record,
  (int, id)
  (std::vector<int>, values)
)

int main() {
  namespace qi = boost::spirit::qi;

  const auto str = std::string{"1: 2, 3, 4"};

  const auto rule1 = qi::int_ >> ':' >> (qi::int_ % ',')                 >> qi::eoi;
  const auto rule2 = qi::int_ >> ':' >> (qi::int_ >> *(',' >> qi::int_)) >> qi::eoi;

  Record record1;
  if (qi::phrase_parse(str.begin(), str.end(), rule1, qi::space, record1)) {
    std::cout << record1.id << ": ";
    for (const auto& value : record1.values) { std::cout << value << ", "; }
    std::cout << '\n';
  } else {
    std::cerr << "syntax error\n";
  }

  Record record2;
  if (qi::phrase_parse(str.begin(), str.end(), rule2, qi::space, record2)) {
    std::cout << record2.id << ": ";
    for (const auto& value : record2.values) { std::cout << value << ", "; }
    std::cout << '\n';
  } else {
    std::cerr << "syntax error\n";
  }
}

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

1: 2, 3, 4, 
1: 2, 

rule1 а также rule2 отличаются только тем, что rule1 использует оператор списка ((qi::int_ % ',')) а также rule2 использует его расширенную форму ((qi::int_ >> *(',' >> qi::int_))). Тем не мение, rule1 произведенный 1: 2, 3, 4, (как и ожидалось) и rule2 произведенный 1: 2,, Я не могу понять результат rule2: 1) почему он отличается от rule1 и 2) почему были 3 а также 4 не входит в record2.values даже если phrase_parse вернули как-нибудь?

2 ответа

Решение

Обновленная версия X3 добавлена

Прежде всего, вы попали в глубокую ловушку здесь:

Правила ци не работают с auto, использование qi::copy или просто использовал qi::rule<>, Ваша программа имеет неопределенное поведение, и она действительно сработала для меня (Вальгринд указал, откуда возникли висячие ссылки).

Итак, во-первых:

const auto rule = qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')                 >> qi::eoi); 

Теперь, когда вы удаляете избыточность в программе, вы получаете:

Воспроизведение проблемы

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

int main() {
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')));
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ >> *(',' >> qi::int_))));
}

печать

1: 2, 3, 4, 
1: 2, 

Причина и исправление

Что случилось с 3, 4 который был успешно разобран?

Ну, правила распространения атрибута указывают, что qi::int_ >> *(',' >> qi::int_) подвергает tuple<int, vector<int> >, В попытке магически DoTheRightThing(TM) Spirit случайно пропускает огонь и "назначает" int в ссылку на атрибут, игнорируя оставшиеся vector<int>,

Если вы хотите, чтобы атрибуты контейнера анализировались как "атомарная группа", используйте qi::as<>:

test(qi::copy(qi::int_ >> ':' >> qi::as<Record::values_t>() [ qi::int_ >> *(',' >> qi::int_)]));

Вот as<> действует как барьер для эвристики совместимости атрибутов, и грамматика знает, что вы имели в виду:

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

#include <iostream>
#include <string>
#include <vector>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

struct Record {
  int id;
  using values_t = std::vector<int>;
  values_t values;
};

BOOST_FUSION_ADAPT_STRUCT(Record, id, values)

namespace qi = boost::spirit::qi;

template <typename T>
void test(T const& rule) {
    const std::string str = "1: 2, 3, 4";

    Record record;

    if (qi::phrase_parse(str.begin(), str.end(), rule >> qi::eoi, qi::space, record)) {
        std::cout << record.id << ": ";
        for (const auto& value : record.values) { std::cout << value << ", "; }
        std::cout << '\n';
    } else {
        std::cerr << "syntax error\n";
    }
}

int main() {
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')));
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ >> *(',' >> qi::int_))));
    test(qi::copy(qi::int_ >> ':' >> qi::as<Record::values_t>() [ qi::int_ >> *(',' >> qi::int_)]));
}

Печать

1: 2, 3, 4, 
1: 2, 
1: 2, 3, 4, 

Поскольку пришло время начать людей с X3 (новая версия Spirit), и потому что мне нравится бросать вызов msyelf для выполнения соответствующих задач в Spirit X3, вот версия Spirit X3.

Там нет проблем с auto в X3.

"Сломанный" случай также ведет себя намного лучше, вызывая это статическое утверждение:

    // If you got an error here, then you are trying to pass
    // a fusion sequence with the wrong number of elements
    // as that expected by the (sequence) parser.
    static_assert(
        fusion::result_of::size<Attribute>::value == (l_size + r_size)
      , "Attribute does not have the expected size."
    );

Это хорошо, правда?

Обходной путь кажется немного менее читабельным:

test(int_ >> ':' >> (rule<struct _, Record::values_t>{} = (int_ >> *(',' >> int_))));

Но было бы тривиально написать свой собственный as<> "директива" (или просто функция), если вы хотите:

namespace {
    template <typename T>
    struct as_type {
        template <typename Expr>
            auto operator[](Expr&& expr) const {
                return x3::rule<struct _, T>{"as"} = x3::as_parser(std::forward<Expr>(expr));
            }
    };

    template <typename T> static const as_type<T> as = {};
}

DEMO

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

#include <iostream>
#include <string>
#include <vector>

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/home/x3.hpp>

struct Record {
    int id;
    using values_t = std::vector<int>;
    values_t values;
};

namespace x3 = boost::spirit::x3;

template <typename T>
void test(T const& rule) {
    const std::string str = "1: 2, 3, 4";

    Record record;

    auto attr = std::tie(record.id, record.values);

    if (x3::phrase_parse(str.begin(), str.end(), rule >> x3::eoi, x3::space, attr)) {
        std::cout << record.id << ": ";
        for (const auto& value : record.values) { std::cout << value << ", "; }
        std::cout << '\n';
    } else {
        std::cerr << "syntax error\n";
    }
}

namespace {
    template <typename T>
    struct as_type {
        template <typename Expr>
            auto operator[](Expr&& expr) const {
                return x3::rule<struct _, T>{"as"} = x3::as_parser(std::forward<Expr>(expr));
            }
    };

    template <typename T> static const as_type<T> as = {};
}

int main() {
    using namespace x3;
    test(int_ >> ':' >> (int_ % ','));
    //test(int_ >> ':' >> (int_ >> *(',' >> int_))); // COMPILER asserts "Attribute does not have the expected size."

    // "clumsy" x3 style workaround
    test(int_ >> ':' >> (rule<struct _, Record::values_t>{} = (int_ >> *(',' >> int_))));

    // using an ad-hoc `as<>` implementation:
    test(int_ >> ':' >> as<Record::values_t>[int_ >> *(',' >> int_)]);
}

Печать

1: 2, 3, 4, 
1: 2, 3, 4, 
1: 2, 3, 4, 
Другие вопросы по тегам