提问人:Jaime Ivan Cervantes 提问时间:4/20/2020 更新时间:4/21/2020 访问量:229
使用 Boost Spirit X3 解析具有交替标记的 Selector 结构
Parsing Selector struct with alternating tokens using Boost Spirit X3
问:
我正在尝试解析以下结构:
struct Selector {
std::string element;
std::string id;
std::vector<std::string> classes;
};
此结构用于解析 形式的选择器。这些选择器始终以 1 个或无元素开头,可以包含 1 个或没有 id,并且可以包含 0 到 n 个类。element#id.class1.class2.classn
不过,这变得更加复杂,因为 classes 和 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 中引入的使用问题,我还了解到 X3 不支持这个问题。at<T>()
hold[]
答:
也许不是,你想要的,那么请通知我,我会删除答案,但对于这个不知何故的简单解析,你不需要 Boost,也不需要 Spirit。
一个简单的正则表达式将用于将给定的字符串拆分为一个令牌。我们可以观察到以下几点:
- “元素”名称从行的开头开始,是一串字母数字字符。
- “id”总是以哈希开头
#
- 而且,类名总是以点开头
.
因此,我们可以形成一个正则表达式来匹配这 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;
}
当然,我们可以通过一些额外的检查来做一个更复杂的正则表达式。
评论
我以前写过类似的答案:
- 使用 Boost.Spirit X3 解析 CSS(在 Qi 和 X3 中更完整地解析 CSS 的宝库)
- 使用 boost::spirit 以任意顺序解析命名参数(注释中的 Qi 和 X3)
- Boost Spirit x3:解析为结构体
- 在运行时合并规则并返回规则
我不认为你可以直接融合适应。虽然如果你非常有动力(例如,你已经有了改编好的结构),你可以从中制作一些通用的帮助程序。
公平地说,在你的代码中进行一些重组对我来说已经很不错了。这是我努力使它更优雅/更方便。我将像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;
}
};
}
请注意,我修复了某些部分的可选性以反映现实生活。
您可以使用它来检测 element/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 << "\tFailed\n";
}
}
}
在 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 类型命名的静态 const 变量。
你可以自由地在另一个命名空间中调用这个宏,比如
命名空间 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。
完整列表
对于本网站的后代:
测试.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 = {}; }
评论
propagator
hold[]
as<T>()
selectorRule
-name[Selector.element] >> *( '#' >> name [Selector.id] | '.' >> name [Selector.classes])
#id1#id2
评论