Улучшения в директиве повтора с переменным коэффициентом для X3

Мне нужно проанализировать последовательность элементов, где есть первое число, указывающее, сколько элементов должно быть проанализировано следующим.

В качестве упрощения того, что мне нужно: [3 10 20 30] следует анализировать, как показано ниже:

- Number of elements: 3
- Vector of elements: {10, 20, 30}

Привыкнув к Qi и его синтаксису «повторная директива + феникс», я попробовал то же самое для X3, но не смог компилировать. Я ищу в Интернете ту же проблему и нахожу следующую ссылку в переполнении стека: Boost Spirit X3 не может скомпилировать директиву Repeat с переменным коэффициентом

Я в шоке от того, как что-то столь изящно решенное в Qi может быть таким уродливым и громоздким в X3 (личное мнение, прошу никого не обижать). Конечно, я понимаю причины отказа от phoonix из-за замены его на С++ 14.

Но мне интересно, есть ли какие-либо дальнейшие улучшения в X3 по этому вопросу, потому что этот пост от 2015 года. Я искал, но ничего не нашел. Любой совет?

ПРИМЕЧАНИЕ. Код не включен из-за того же регистра / кода, что и опубликованная ссылка.

Спасибо.


person Pablo    schedule 24.01.2019    source источник


Ответы (1)


Обычно это означает, что для этой функции не было PR (или было, но с некоторыми проблемами). У repeat также есть проблемы с дизайном. Например, вы можете анализировать {10 20 30} с его помощью, но не {10, 20, 30} (требуется что-то вроде парсера list).

Я не могу согласиться с тем, что у Qi есть элегантный способ сделать это, потому что вы должны использовать правило с локальной переменной или передавать ссылку на внешнее значение. Естественный способ кажется repeat(len_parser)[item_parser], но у него есть дополнительные проблемы с дизайном шкиперов (или у шкиперов есть проблемы с дизайном, которые ограничивают гибкость сложных директив).

К счастью, Spirit X3 намного проще в написании собственных комбинаторов парсеров.

#include <boost/spirit/home/x3.hpp>

namespace x3e {

namespace x3 = boost::spirit::x3;

template <typename LenParser, typename Subject>
struct vlrepeat_directive : x3::unary_parser<Subject, vlrepeat_directive<LenParser, Subject>>
{
    using base_type = x3::unary_parser<Subject, vlrepeat_directive<LenParser, Subject>>;
    static bool const handles_container = true;

    vlrepeat_directive(LenParser const& lp_, Subject const& subject)
        : base_type(subject), lp(lp_) {}

    template<typename Iterator, typename Context, typename RContext, typename Attribute>
    bool parse(Iterator& first, Iterator const& last
      , Context const& context, RContext& rcontext, Attribute& attr) const
    {
        static_assert(x3::traits::has_attribute<LenParser, Context>::value, "must syntesize an attribute");

        Iterator iter = first;
        typename x3::traits::attribute_of<LenParser, Context>::type len;
        if (!lp.parse(iter, last, context, rcontext, len))
            return false;

        for (; len; --len) {
            if (!x3::detail::parse_into_container(
                    this->subject, iter, last, context, rcontext, attr))
                return false;
        }

        first = iter;
        return true;
    }

    LenParser lp;
};

template <typename LenParser>
struct vlrepeat_gen
{
    template <typename Subject>
    vlrepeat_directive<LenParser, typename x3::extension::as_parser<Subject>::value_type>
    operator[](Subject const& p) const
    {
        return { lp, x3::as_parser(p) };
    }

    LenParser lp;
};

template <typename Parser>
vlrepeat_gen<Parser> vlrepeat(Parser const& p)
{
    static_assert(x3::traits::is_parser<Parser>::value, "have to be a parser");
    return { p };
}

}

template <typename LenParser, typename Subject, typename Context>
struct boost::spirit::x3::traits::attribute_of<x3e::vlrepeat_directive<LenParser, Subject>, Context>
    : build_container<typename attribute_of<Subject, Context>::type> {};

И используйте его:

#include <iostream>
#include <vector>

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

    auto s = "5: 1 2 3 4 5", e = s + std::strlen(s);
    std::vector<int> v;
    if (phrase_parse(s, e, x3e::vlrepeat(x3::uint_ >> ':')[x3::int_], x3::space, v)) {
        std::cout << "Result:\n";
        for (auto x : v)
            std::cout << x << '\n';
    }
    else
        std::cout << "Failed!\n";
}

Выход:

Result:
1
2
3
4
5

https://wandbox.org/permlink/K572K0BMEqA8lMJm

(у него есть вызов detail::parse_into_container, который не является общедоступным API)

person Nikita Kniazev    schedule 24.01.2019
comment
Хороший ответ, большое спасибо. Я думаю, что это может быть включено как часть X3, а не шаблонный код, который нужен каждый раз. Думали ли вы о переносе предложения в Boost? - person Pablo; 25.01.2019
comment
Также, я думаю, есть еще случай, когда старый способ (повтор+феникс с qi или тот, что указан в ссылке для X3) может быть полезен. Иногда встречаются форматы, где количество повторов находится далеко-далеко от повторяющихся данных. - person Pablo; 25.01.2019
comment
Как я уже сказал, есть проблемы, которые не дадут парсеру попасть в Spirit (просто отключить предварительный пропуск первого элемента нельзя, наличие разных скипперов по длине и парсеров элементов приведет к очень некрасивой вещи), его тоже нужно интегрирована в уже существующую repeat. Странно, что длина далека от данных, но есть бесспорный пример: длина+SoA (несколько массивов одинакового размера). Что-то вроде with<decltype(len)>()[(len = uint_) >> repeat(len)[int_] >> repeat(len)[int_]] должно быть возможно реализовать. - person Nikita Kniazev; 25.01.2019