Разбор структуры Selector с чередующимися токенами с использованием Boost Spirit X3 - PullRequest
1 голос
/ 20 апреля 2020

Я пытаюсь проанализировать следующую структуру:

struct Selector {
    std::string element;
    std::string id;
    std::vector<std::string> classes;
};

Эта структура используется для анализа селекторов в форме element#id.class1.class2.classn. Эти селекторы всегда начинаются с 1 или без элементов, могут содержать 1 или без идентификаторов и могут содержать от 0 до n классов.

Это становится еще более сложным, поскольку классы и идентификаторы могут появляться в любом порядке, поэтому все следующие селекторы действительны: element#id.class1, .class1#id.class2.class3, #id.class1.class2, .class1.class2#id. По этой причине я не смог использовать hold[] или at<T>() подходы, описанные здесь , и я также не смог использовать BOOST_FUSION_ADAPT_STRUCT.

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

auto element = [](auto& ctx){x3::_val(ctx).element = x3::_attr(ctx);};
auto id = [](auto& ctx){x3::_val(ctx).id = x3::_attr(ctx);};
auto empty = [](auto& ctx){x3::_val(ctx) = "";};
auto classes = [](auto& ctx){x3::_val(ctx).classes.insert(x3::_val(ctx).classes.end(), x3::_attr(ctx).begin(), x3::_attr(ctx).end());};

auto elementRule = x3::rule<class EmptyIdClass, std::string>() = +x3::char_("a-zA-Z") | x3::attr("");
auto idRule = x3::rule<class EmptyIdClass, std::string>() = ("#" >> +x3::char_("a-zA-Z")) | x3::attr("");
auto classesRule = x3::rule<class ClassesClass, std::vector<std::string>>() = *("." >> +x3::char_("a-zA-Z"));
auto selectorRule = x3::rule<class TestClass, Selector>() = elementRule[element] >> classesRule[classes] >> idRule[id] >> classesRule[classes];

Каков наилучший способ анализа этой структуры? Можно ли синтезировать эту структуру селектора естественным образом, используя BOOST_FUSION_ADAPT_STRUCT, и без действий semanti c?

Кажется, что каждый раз, когда мне кажется, что я знакомлюсь с Духом X3, я сталкиваюсь с новым испытанием. В этом конкретном случае я узнал о проблемах с backtracking , о проблеме с использованием at<T>(), которая была представлена ​​в Boost 1.70 здесь , и я также узнал, что hold[] не поддерживается X3.

Ответы [ 2 ]

1 голос
/ 20 апреля 2020

Я уже писал подобные ответы раньше:

Я не думаю, что вы можете напрямую адаптировать слияние. Хотя, если вы очень мотивированы (например, у вас уже есть адаптированные структуры), вы можете сделать несколько общих c помощников из этого.

Чтобы быть справедливым, немного перестройки в вашем Код кажется мне уже довольно приятным. Вот мои усилия, чтобы сделать его более элегантным / удобным. Я представлю вспомогательный макрос, такой же, как BOOST_FUSION_ADAPT_XXX, но не требующий никакого Boost Fusion.

Давайте начнем с AST

Как всегда, мне нравится начинать с основ. Понимание цели - это полдела:

namespace Ast {
    using boost::optional;

    struct Selector {
        // These selectors always 
        //  - start with 1 or no elements, 
        //  - could contain 1 or no ids, and
        //  - could contain 0 to n classes.
        optional<std::string> element;
        optional<std::string> id;
        std::vector<std::string> classes;

        friend std::ostream& operator<<(std::ostream& os, Selector const&s) {
            if  (s.element.has_value()) os << s.element.value();
            if  (s.id.has_value())      os << "#" << s.id.value();
            for (auto& c : s.classes)   os << "." << c;
            return os;
        }
    };
}

Обратите внимание, что я исправил возможность некоторых частей отражать реальную жизнь.

Вы могли бы использовать это чтобы обнаружить повторную инициализацию полей элемента / id.

Маги c Соус (см. ниже)

#include "propagate.hpp"
DEF_PROPAGATOR(Selector, id, element, classes)

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

Main di sh

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

int main() {
    auto name        = as<std::string>[x3::alpha >> *x3::alnum];
    auto idRule      = "#" >> name;
    auto classesRule = +("." >> name);

    auto selectorRule
        = x3::rule<class TestClass, Ast::Selector>{"selectorRule"}
        = +( name        [ Selector.element ]
           | idRule      [ Selector.id ]
           | classesRule [ Selector.classes ]
           )
        ;

    for (std::string const& input : {
            "element#id.class1.class2.classn",
            "element#id.class1",
            ".class1#id.class2.class3",
            "#id.class1.class2",
            ".class1.class2#id",
        })
    {
        Ast::Selector sel;
        std::cout << std::quoted(input) << " -->\n";
        if (x3::parse(begin(input), end(input), selectorRule >> x3::eoi, sel)) {
            std::cout << "\tSuccess: " << sel << "\n";
        } else {
            std::cout << "\tFailed\n";
        }
    }
}

Посмотреть Live On Wandbox , печать:

"element#id.class1.class2.classn" -->
    Success: element#id.class1.class2.classn
"element#id.class1" -->
    Success: element#id.class1
".class1#id.class2.class3" -->
    Success: #id.class1.class2.class3
"#id.class1.class2" -->
    Success: #id.class1.class2
".class1.class2#id" -->
    Success: #id.class1.class2

Волхвы c

Теперь, как я генерировал эти действия? Используя немного Boost Preprocessor:

#define MEM_PROPAGATOR(_, T, member) \
    Propagators::Prop<decltype(std::mem_fn(&T::member))> member { std::mem_fn(&T::member) };

#define DEF_PROPAGATOR(type, ...) \
    struct type##S { \
        using T = Ast::type; \
        BOOST_PP_SEQ_FOR_EACH(MEM_PROPAGATOR, T, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
    } static const type {};

Теперь вы можете видеть, что он определяет переменные stati c const, названные как типы Ast.

You Вы можете вызывать этот макрос в другом пространстве имен, скажем, namespace Actions { }

Настоящий волхв c - это Propagators::Prop<F>, который имеет небольшую диспетчеризацию для учета атрибутов и членов контейнера , В противном случае он просто переходит на x3::traits::move_to:

namespace Propagators {
    template <typename F>
    struct Prop {
        F f;
        template <typename Ctx>
        auto operator()(Ctx& ctx) const {
            return dispatch(x3::_attr(ctx), f(x3::_val(ctx)));
        }
      private:
        template <typename Attr, typename Dest>
        static inline void dispatch(Attr& attr, Dest& dest) {
            call(attr, dest, is_container(attr), is_container(dest));
        }

        template <typename T>
        static auto is_container(T const&)           { return x3::traits::is_container<T>{}; }
        static auto is_container(std::string const&) { return boost::mpl::false_{}; }

        // tags for dispatch
        using attr_is_container = boost::mpl::true_;
        using attr_is_scalar    = boost::mpl::false_;
        using dest_is_container = boost::mpl::true_;
        using dest_is_scalar    = boost::mpl::false_;

        template <typename Attr, typename Dest>
        static inline void call(Attr& attr, Dest& dest, attr_is_scalar, dest_is_scalar) {
            x3::traits::move_to(attr, dest);
        }
        template <typename Attr, typename Dest>
        static inline void call(Attr& attr, Dest& dest, attr_is_scalar, dest_is_container) {
            dest.insert(dest.end(), attr);
        }
        template <typename Attr, typename Dest>
        static inline void call(Attr& attr, Dest& dest, attr_is_container, dest_is_container) {
            dest.insert(dest.end(), attr.begin(), attr.end());
        }
    };
}

BONUS

Большая сложность в типе пропагатора связана с обработкой атрибутов контейнера. Тем не менее, вам на самом деле ничего этого не нужно:

auto name = as<std::string>[x3::alpha >> *x3::alnum];

auto selectorRule
    = x3::rule<class selector_, Ast::Selector>{"selectorRule"}
    = +( name        [ Selector.element ]
       | '#' >> name [ Selector.id ]
       | '.' >> name [ Selector.classes ]
       )
    ;

более чем достаточно, и помощник по распространению может быть упрощен до:

namespace Propagators {
    template <typename F> struct Prop {
        F f;
        template <typename Ctx>
        auto operator()(Ctx& ctx) const {
            return call(x3::_attr(ctx), f(x3::_val(ctx)));
        }
      private:
        template <typename Attr, typename Dest>
        static inline void call(Attr& attr, Dest& dest) {
            x3::traits::move_to(attr, dest);
        }
        template <typename Attr, typename Elem>
        static inline void call(Attr& attr, std::vector<Elem>& dest) {
            dest.insert(dest.end(), attr);
        }
    };
}

Как вы можете видеть, испаряясь отправка тегов имеет положительный эффект.

См. Упрощенную версию Live On Wandbox снова.

ПОЛНЫЙ ЛИСТИНГ

Для потомков на этом сайте:

  • test. cpp

    //#define BOOST_SPIRIT_X3_DEBUG
    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    #include <iomanip>
    
    namespace x3 = boost::spirit::x3;
    
    namespace Ast {
        using boost::optional;
    
        struct Selector {
            // These selectors always 
            //  - start with 1 or no elements, 
            //  - could contain 1 or no ids, and
            //  - could contain 0 to n classes.
            optional<std::string> element;
            optional<std::string> id;
            std::vector<std::string> classes;
    
            friend std::ostream& operator<<(std::ostream& os, Selector const&s) {
                if  (s.element.has_value()) os << s.element.value();
                if  (s.id.has_value())      os << "#" << s.id.value();
                for (auto& c : s.classes)   os << "." << c;
                return os;
            }
        };
    }
    
    #include "propagate.hpp"
    DEF_PROPAGATOR(Selector, id, element, classes)
    
    #include "as.hpp"
    int main() {
        auto name = as<std::string>[x3::alpha >> *x3::alnum];
    
        auto selectorRule
            = x3::rule<class selector_, Ast::Selector>{"selectorRule"}
            = +( name        [ Selector.element ]
               | '#' >> name [ Selector.id ]
               | '.' >> name [ Selector.classes ]
               )
            ;
    
        for (std::string const& input : {
                "element#id.class1.class2.classn",
                "element#id.class1",
                ".class1#id.class2.class3",
                "#id.class1.class2",
                ".class1.class2#id",
            })
        {
            Ast::Selector sel;
            std::cout << std::quoted(input) << " -->\n";
            if (x3::parse(begin(input), end(input), selectorRule >> x3::eoi, sel)) {
                std::cout << "\tSuccess: " << sel << "\n";
            } else {
                std::cout << "\tFailed\n";
            }
        }
    }
    
  • пропагат.hpp

    #pragma once
    #include <boost/preprocessor/cat.hpp>
    #include <boost/preprocessor/seq/for_each.hpp>
    #include <functional>
    
    namespace Propagators {
        template <typename F> struct Prop {
            F f;
            template <typename Ctx>
            auto operator()(Ctx& ctx) const {
                return call(x3::_attr(ctx), f(x3::_val(ctx)));
            }
          private:
            template <typename Attr, typename Dest>
            static inline void call(Attr& attr, Dest& dest) {
                x3::traits::move_to(attr, dest);
            }
            template <typename Attr, typename Elem>
            static inline void call(Attr& attr, std::vector<Elem>& dest) {
                dest.insert(dest.end(), attr);
            }
        };
    }
    
    #define MEM_PROPAGATOR(_, T, member) \
        Propagators::Prop<decltype(std::mem_fn(&T::member))> member { std::mem_fn(&T::member) };
    
    #define DEF_PROPAGATOR(type, ...) \
        struct type##S { \
            using T = Ast::type; \
            BOOST_PP_SEQ_FOR_EACH(MEM_PROPAGATOR, T, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
        } static const type {};
    
  • as.hpp

    #pragma once
    #include <boost/spirit/home/x3.hpp>
    
    namespace {
        template <typename T>
        struct as_type {
            template <typename...> struct tag{};
            template <typename P>
            auto operator[](P p) const {
                return boost::spirit::x3::rule<tag<T,P>, T> {"as"}
                       = p;
            }
        };
    
        template <typename T>
            static inline const as_type<T> as = {};
    }
    
1 голос
/ 20 апреля 2020

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

Простое регулярное выражение сделает для разделения данной строки на токен. Мы можем наблюдать следующее:

  • Имя «элемента» начинается в начале строки и представляет собой строку буквенно-цифровых символов.
  • «id» начинается всегда с ха sh #
  • и имена классов всегда начинаются с точки .

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

((^\w+)|[\.#]\w+)

Вы можете посмотреть здесь для объяснения регулярного выражения.

Затем мы можем написать простую программу, которая читает селекторы, разбивает их на токены и затем назначает их структуре Selector.

Пожалуйста, смотрите следующий пример. Это должно дать вам представление о том, как это можно сделать.

#include <iostream>
#include <vector>
#include <regex>
#include <sstream>
#include <string>
#include <iterator>
#include <cctype>

struct Selector {
    std::string element;
    std::string id;
    std::vector<std::string> classes;
};

std::stringstream inputFileStream{ R"(element1#id1.class11.class12.class13.class14
element2#id2.class21.class22
#id3.class31.class32.class33.class34.class35
.class41.class42,class43#id4
.class51#id5.class52.class53.class54.class55.class56
)"};

//std::regex re{R"(([\.#]?\w+))"};
std::regex re{ R"(((^\w+)|[\.#]\w+))" };

int main() {

    std::vector<Selector> selectors{};

    // Read all lines of the source file
    for (std::string line{}; std::getline(inputFileStream, line); ) {

        // Split the line with selector string into tokens
        std::vector<std::string> tokens(std::sregex_token_iterator(line.begin(), line.end(), re), {});

        // Here we will store the one single selector
        Selector tempSelector{};

        // Go though all tokens and check the type of them
        for (const std::string& token : tokens) {

            // Depending on the structure element type, add it to the correct structure element field
            if (token[0] == '#') tempSelector.id = std::move(token.substr(1));
            else if (token[0] == '.') tempSelector.classes.emplace_back(token.substr(1));
            else if (std::isalnum(token[0])) tempSelector.element = token;
            else std::cerr << "\n*** Error: Invalid token found: " << token << "\n";
        }
        // Add the new selector to the vector of selectors
        selectors.push_back(std::move(tempSelector));
    }


    // Show debug output
    for (const Selector& s : selectors) {
        std::cout << "\n\nSelector\n\tElement:\t" << s.element << "\n\tID:\t\t" << s.id << "\n\tClasses:\t";
        for (const std::string& c : s.classes)
            std::cout << c << " ";
    }
    std::cout << "\n\n";

    return 0;
}

Конечно, мы могли бы сделать более сложное регулярное выражение с некоторой дополнительной проверкой.

...