使用Boost Spirit X3分析具有交替标记的Selector结构



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

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

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

然而,这变得更加复杂,因为类和id可以按任何顺序出现,所以以下选择器都是有效的: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自然地合成这个选择器结构,而不需要语义操作?

似乎每次我觉得自己掌握了Spirit X3的窍门时,我都会遇到一个新的挑战。在这个特殊的案例中,我了解了回溯的问题,了解了使用Boost 1.70中介绍的at<T>()的问题,还了解到X3不支持hold[]

我以前写过类似的答案:

  • 使用BoostSpiritX3解析CSS(Qi和X3中更完整的CSS解析宝库(
  • 使用boost::spirit以任何顺序解析命名参数(评论中的Qi和X3(
  • Boost Spirit x3:解析为structs
  • 在运行时组合规则并返回规则

我不认为你可以直接融合适应。尽管如果你很有动力(例如,你已经有了适应的结构(,你可以制作一些通用的助手。

公平地说,在你的代码中进行一点重组对我来说已经很好了。这是我为使它更优雅/更方便所做的努力。我将介绍一个辅助宏,就像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字段的重复初始化。

魔汁(见下文(

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

我们稍后再深入研究。只要说它产生了你不得不乏味地写的语义动作就足够了。

主菜

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

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

查看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

魔术

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

#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类型的静态常量变量。

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

真正神奇的是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());
}
};
}

奖金

传播程序类型的许多复杂性来自于处理容器属性。然而,你实际上并不需要这些:

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 << "tFailedn";
    }
    }
    }
    
  • 宣传.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 = {};
    }
    

也许不是,你想要的,那么请通知我,我会删除答案,但对于这个简单的解析,你不需要Boost,也不需要Spirit。

一个简单的正则表达式可以将给定的字符串拆分为一个令牌。我们可以观察到以下情况:

  • "元素"名称从行的开头开始,是一个由字母数字字符组成的字符串
  • "id"总是以散列#开头
  • 并且,类名总是以点.开头

因此,我们可以形成一个正则表达式来匹配这3种类型的令牌。

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

您可以在这里查看regex的解释。

然后,我们可以编写一个简单的程序,读取选择器,将其拆分为标记,然后将这些标记分配给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 << "nnSelectorntElement:t" << s.element << "ntID:tt" << s.id << "ntClasses:t";
for (const std::string& c : s.classes)
std::cout << c << " ";
}
std::cout << "nn";
return 0;
}

当然,我们可以通过一些额外的检查来实现更复杂的正则表达式。

最新更新