使用Boost Spirit X3解析具有交替标记的Selector结构 [英] Parsing Selector struct with alternating tokens using Boost Spirit X3

查看:53
本文介绍了使用Boost Spirit X3解析具有交替标记的Selector结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试解析以下结构:

I am trying to parse the following struct:

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

此结构用于解析形式为 element#id.class1.class2.classn 的选择器.这些选择器始终以1或不包含任何元素开头,可以包含1或不包含id,并且可以包含0到n个类.

This struct is used to parse selectors in the form element#id.class1.class2.classn. These selectors always start with 1 or no elements, could contain 1 or no ids, and could contain 0 to n classes.

这会变得更加复杂,因为类和id可以以任何顺序出现,因此以下选择器都是有效的: element#id.class1 .class1#id.class2.class3 #id.class1.class2 .class1.class2#id .因此,我无法使用 =" =" https://stackoverflow.com中描述的 hold [] at< T>()方法/a/39541129/1404614>此处,而且我也无法使用 BOOST_FUSION_ADAPT_STRUCT .

This gets even more complicated though, because classes and id can appear in any order, so the following selectors are all valid: element#id.class1, .class1#id.class2.class3, #id.class1.class2, .class1.class2#id. For this reason, I have not been able to use hold[], or at<T>() approaches described here, and I also have not been able to use BOOST_FUSION_ADAPT_STRUCT.

我能够合成此结构的唯一方法是遵循以下规则:

The only way that I have been able to synthesize this struct, is with the following rules:

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 自然地合成此选择器结构,而无需语义动作?

What would be the best way to parse this struct? Is it possible to synthesize this selector struct naturally, using BOOST_FUSION_ADAPT_STRUCT, and without semantic actions?

似乎每次我觉得我都能够抓住Spirit X3的诀窍时,我偶然遇到了一个新挑战.在这种情况下,我了解了回溯的问题,有关使用 at< T>() 此处,并且我还了解到X3不支持 hold [] .

It seems like everytime I think I am am getting the hang of Spirit X3, I stumble upon a new challenge. In this particular case, I learned about issues with backtracking, about an issue with using at<T>() that was introduced in Boost 1.70 here, and I also learned that hold[] is not supported by X3.

推荐答案

我之前写过类似的答案:

I've written similar answers before:

  • Parsing CSS with Boost.Spirit X3 (a treasure trove for more complete CSS parsing in both Qi and X3)
  • Using boost::spirit to parse named parameters in any order (Qi and X3 in the comments)
  • Boost Spirit x3: parse into structs
  • Combining rules at runtime and returning rules

我认为您不能直接进行融合调整.尽管如果您很有动力(例如,您已经有适应的结构),您可以 从中获得一些通用的帮助.

I don't think you can directly fusion-adapt. Although if you are very motivated (e.g. you already have the adapted structs) you could make some generic helpers off that.

说句公道话,对我的代码来说,对代码进行一点重组似乎已经很不错了.这是我努力使其更优雅/更方便的方法.我将像BOOST_FUSION_ADAPT_XXX一样介绍一个辅助宏,但不需要任何Boost Fusion.

To be fair, a little bit of restructuring in your code seems pretty nice to me, already. Here's my effort to make it more elegant/convenient. I'll introduce a helper macro just like BOOST_FUSION_ADAPT_XXX, but not requiring any Boost Fusion.

和往常一样,我喜欢从基础开始.理解目标是成功的一半:

As always, I like to start with the basics. Understanding the goal is half the battle:

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;
        }
    };
}

请注意,我固定了某些部分的可选性以反映现实生活.

Note that I fixed the optionality of some parts to reflect real life.

可以使用它来检测元素/id字段的重复初始化.

You could use this to detect repeat-initialization of element/id fields.

魔术酱(见下文)

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

我们稍后将对此进行深入研究.可以说它生成了您必须繁琐地编写的语义动作.

We'll dig into this later. Suffice it to say it generates the semantic actions that you had to tediously write.

现在,我们可以大大简化解析器规则,并运行测试:

Now, we can simplify the parser rules a lot, and run the tests:

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";
        }
    }
}

查看 在魔盒上直播" ,打印:

See it Live On Wandbox, printing:

"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

魔术

现在,我如何生成这些动作?使用一点Boost预处理器:

The Magic

Now, how did I generate those actions? Using a little bit of 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 {};

现在,您可能会看到它定义了名为AST类型的静态const变量.

Now, you might see that it defines static const variables named like the Ast types.

您可以在另一个命名空间中随意调用此宏,例如 namespace Actions {}

真正的魔力是 Propagators :: Prop< F> ,它具有一些分配功能,可以容纳容器属性和成员.否则,它将中继到 x3 :: traits :: move_to :

The real magic is Propagators::Prop<F> which has a bit of dispatch to allow for container attributes and members. Otherwise it just relays to 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

A lot of the complexity in the propagator type is from handling container attributes. However, you don't actually need any of that:

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 ]
       )
    ;

绰绰有余,并且可以将传播帮助器简化为:

Is more than enough, and the propagation helper can be simplified to:

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);
        }
    };
}

如您所见,蒸发标记分发具有有益的作用.

As you can see evaporating the tag dispatch has a beneficial effect.

再次参见简化版本 在魔盒上直播 .

See the simplified version Live On Wandbox again.

对于此网站上的后代:

  • test.cpp

  • 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";
        }
    }
}

  • propagate.hpp

  • propagate.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

  • 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 = {};
    }
    

  • 这篇关于使用Boost Spirit X3解析具有交替标记的Selector结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆