如何使用单独的访问者制作递归 Spirit X3 解析器 class
How to make recursive Spirit X3 parser with a separate visitor class
我正在处理递归规则调用的解析器应用程序。除了查看可以在此处找到的 Boost Spirit X3 的递归 AST 教程示例:
https://www.boost.org/doc/libs/develop/libs/spirit/doc/x3/html/index.html,我正在寻找一种解决方案,其中包含某些类型的 std::variant 以及相同类型的 std::vector
变体类型。
在标题为: 的 Whosebug post 中,我发现 sehe 的答案中的代码是我的解析器的一个不错的起点。
我在这里重复了代码,但我限制了要测试的输入字符串。因为原文中的完整列表与这里的这个问题无关。
//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>
struct value: std::variant<int,float,std::vector<value>>
{
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
friend std::ostream& operator<<(std::ostream& os, base_type const& v) {
struct {
std::ostream& operator()(float const& f) const { return _os << "float:" << f; }
std::ostream& operator()(int const& i) const { return _os << "int:" << i; }
std::ostream& operator()(std::vector<value> const& v) const {
_os << "tuple: [";
for (auto& el : v) _os << el << ",";
return _os << ']';
}
std::ostream& _os;
} vis { os };
return std::visit(vis, v);
}
};
namespace parser {
namespace x3 = boost::spirit::x3;
x3::rule<struct value_class, value> const value_ = "value";
x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";
x3::real_parser<float, x3::strict_real_policies<float> > float_;
const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");
const auto value__def
= "float" >> (':' >> float_)
| "int" >> (':' >> x3::int_)
| o_tuple_
;
BOOST_SPIRIT_DEFINE(value_, o_tuple_)
const auto entry_point = x3::skip(x3::space) [ value_ ];
}
int main()
{
for (std::string const str : {
"float: 3.14",
"int: 3",
"tuple: [float: 3.14,int: 3]",
"tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
}) {
std::cout << "============ '" << str << "'\n";
//using boost::spirit::x3::parse;
auto first = str.begin(), last = str.end();
value val;
if (parse(first, last, parser::entry_point, val))
std::cout << "Parsed '" << val << "'\n";
else
std::cout << "Parse failed\n";
if (first != last)
std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
}
}
但是我想使用传统的访问者 class,而不是让 ostream 成为变体 class 中的朋友。你只知道一个 struct/class 和一堆函数 objects 用于你在变体中遇到的每种类型和一个 "for loop" 用于为每个调用 std::visit 的向量
元素.
我对传统访问者的目标 class 是能够在打印期间维护状态机。
我自己编写此访问者 class 的尝试失败了,因为我 运行 遇到了我的 GCC 8.1 编译器的问题。在编译期间使用 GCC std::variant 恰好是 std::variant_size 并且我收到以下错误:
error: incomplete type 'std::variant_size' used in nested name specifier
这里有更多相关信息:
是否可以对 GCC 施加此约束,以便为我包含的代码示例编写访问者 class,以便可以删除 ostream 内容?
Is it possible giving this constraint on GCC to write a visitor class for the code example I included, so that the ostream stuff can be removed?
当然可以。基本上,我看到三种方法:
1。添加模板机械
您可以特化 GCC 意外需要的实现细节:
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
};
namespace std {
template <> struct variant_size<value> :
std::variant_size<value::base_type> {};
template <size_t I> struct variant_alternative<I, value> :
std::variant_alternative<I, value::base_type> {};
}
2。不要(再次live)
扩展 std 命名空间令人担忧(尽管我认为这是合法的
user-defined 种)。所以,你可以使用我最喜欢的模式并隐藏它
estd::visit
在函数对象本身中调度:
template <typename... El>
void operator()(std::variant<El...> const& v) const { std::visit(*this, v); }
现在你可以简单地调用仿函数,它会自动调度
在你自己的 variant-derived 类型上,因为 operator()
重载确实
没有 GCC stdlib 的问题:
if (parse(first, last, parser::entry_point, val))
{
display_visitor display { std::cout };
std::cout << "Parsed '";
display(val);
std::cout << "'\n";
}
3。把事情说清楚
我最不喜欢这个,但它确实有优点:没有魔法,也没有
技巧:
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
base_type const& as_variant() const { return *this; }
base_type& as_variant() { return *this; }
};
struct display_visitor {
void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
// ...
再次,live
摘要
经过深思熟虑,我推荐最后一种方法,因为它相对简单。聪明往往是code-smell :)
未来访客的完整列表:
//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
base_type const& as_variant() const { return *this; }
base_type& as_variant() { return *this; }
};
struct display_visitor {
std::ostream& _os;
void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
void operator()(float const& f) const { _os << "float:" << f; }
void operator()(int const& i) const { _os << "int:" << i; }
void operator()(std::vector<value> const& v) const {
_os << "tuple: [";
for (auto& el : v) {
operator()(el);
_os << ",";
}
_os << ']';
}
};
namespace parser {
namespace x3 = boost::spirit::x3;
x3::rule<struct value_class, value> const value_ = "value";
x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";
x3::real_parser<float, x3::strict_real_policies<float> > float_;
const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");
const auto value__def
= "float" >> (':' >> float_)
| "int" >> (':' >> x3::int_)
| o_tuple_
;
BOOST_SPIRIT_DEFINE(value_, o_tuple_)
const auto entry_point = x3::skip(x3::space) [ value_ ];
}
int main()
{
for (std::string const str : {
"float: 3.14",
"int: 3",
"tuple: [float: 3.14,int: 3]",
"tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
}) {
std::cout << "============ '" << str << "'\n";
//using boost::spirit::x3::parse;
auto first = str.begin(), last = str.end();
value val;
if (parse(first, last, parser::entry_point, val))
{
display_visitor display { std::cout };
std::cout << "Parsed '";
display(val);
std::cout << "'\n";
}
else
std::cout << "Parse failed\n";
if (first != last)
std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
}
}
我正在处理递归规则调用的解析器应用程序。除了查看可以在此处找到的 Boost Spirit X3 的递归 AST 教程示例: https://www.boost.org/doc/libs/develop/libs/spirit/doc/x3/html/index.html,我正在寻找一种解决方案,其中包含某些类型的 std::variant 以及相同类型的 std::vector 变体类型。
在标题为:
我在这里重复了代码,但我限制了要测试的输入字符串。因为原文中的完整列表与这里的这个问题无关。
//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>
struct value: std::variant<int,float,std::vector<value>>
{
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
friend std::ostream& operator<<(std::ostream& os, base_type const& v) {
struct {
std::ostream& operator()(float const& f) const { return _os << "float:" << f; }
std::ostream& operator()(int const& i) const { return _os << "int:" << i; }
std::ostream& operator()(std::vector<value> const& v) const {
_os << "tuple: [";
for (auto& el : v) _os << el << ",";
return _os << ']';
}
std::ostream& _os;
} vis { os };
return std::visit(vis, v);
}
};
namespace parser {
namespace x3 = boost::spirit::x3;
x3::rule<struct value_class, value> const value_ = "value";
x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";
x3::real_parser<float, x3::strict_real_policies<float> > float_;
const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");
const auto value__def
= "float" >> (':' >> float_)
| "int" >> (':' >> x3::int_)
| o_tuple_
;
BOOST_SPIRIT_DEFINE(value_, o_tuple_)
const auto entry_point = x3::skip(x3::space) [ value_ ];
}
int main()
{
for (std::string const str : {
"float: 3.14",
"int: 3",
"tuple: [float: 3.14,int: 3]",
"tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
}) {
std::cout << "============ '" << str << "'\n";
//using boost::spirit::x3::parse;
auto first = str.begin(), last = str.end();
value val;
if (parse(first, last, parser::entry_point, val))
std::cout << "Parsed '" << val << "'\n";
else
std::cout << "Parse failed\n";
if (first != last)
std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
}
}
但是我想使用传统的访问者 class,而不是让 ostream 成为变体 class 中的朋友。你只知道一个 struct/class 和一堆函数 objects 用于你在变体中遇到的每种类型和一个 "for loop" 用于为每个调用 std::visit 的向量 元素.
我对传统访问者的目标 class 是能够在打印期间维护状态机。
我自己编写此访问者 class 的尝试失败了,因为我 运行 遇到了我的 GCC 8.1 编译器的问题。在编译期间使用 GCC std::variant 恰好是 std::variant_size 并且我收到以下错误:
error: incomplete type 'std::variant_size' used in nested name specifier
这里有更多相关信息:
是否可以对 GCC 施加此约束,以便为我包含的代码示例编写访问者 class,以便可以删除 ostream 内容?
Is it possible giving this constraint on GCC to write a visitor class for the code example I included, so that the ostream stuff can be removed?
当然可以。基本上,我看到三种方法:
1。添加模板机械
您可以特化 GCC 意外需要的实现细节:
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
};
namespace std {
template <> struct variant_size<value> :
std::variant_size<value::base_type> {};
template <size_t I> struct variant_alternative<I, value> :
std::variant_alternative<I, value::base_type> {};
}
2。不要(再次live)
扩展 std 命名空间令人担忧(尽管我认为这是合法的
user-defined 种)。所以,你可以使用我最喜欢的模式并隐藏它
estd::visit
在函数对象本身中调度:
template <typename... El>
void operator()(std::variant<El...> const& v) const { std::visit(*this, v); }
现在你可以简单地调用仿函数,它会自动调度
在你自己的 variant-derived 类型上,因为 operator()
重载确实
没有 GCC stdlib 的问题:
if (parse(first, last, parser::entry_point, val))
{
display_visitor display { std::cout };
std::cout << "Parsed '";
display(val);
std::cout << "'\n";
}
3。把事情说清楚
我最不喜欢这个,但它确实有优点:没有魔法,也没有 技巧:
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
base_type const& as_variant() const { return *this; }
base_type& as_variant() { return *this; }
};
struct display_visitor {
void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
// ...
再次,live
摘要
经过深思熟虑,我推荐最后一种方法,因为它相对简单。聪明往往是code-smell :)
未来访客的完整列表:
//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
base_type const& as_variant() const { return *this; }
base_type& as_variant() { return *this; }
};
struct display_visitor {
std::ostream& _os;
void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
void operator()(float const& f) const { _os << "float:" << f; }
void operator()(int const& i) const { _os << "int:" << i; }
void operator()(std::vector<value> const& v) const {
_os << "tuple: [";
for (auto& el : v) {
operator()(el);
_os << ",";
}
_os << ']';
}
};
namespace parser {
namespace x3 = boost::spirit::x3;
x3::rule<struct value_class, value> const value_ = "value";
x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";
x3::real_parser<float, x3::strict_real_policies<float> > float_;
const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");
const auto value__def
= "float" >> (':' >> float_)
| "int" >> (':' >> x3::int_)
| o_tuple_
;
BOOST_SPIRIT_DEFINE(value_, o_tuple_)
const auto entry_point = x3::skip(x3::space) [ value_ ];
}
int main()
{
for (std::string const str : {
"float: 3.14",
"int: 3",
"tuple: [float: 3.14,int: 3]",
"tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
}) {
std::cout << "============ '" << str << "'\n";
//using boost::spirit::x3::parse;
auto first = str.begin(), last = str.end();
value val;
if (parse(first, last, parser::entry_point, val))
{
display_visitor display { std::cout };
std::cout << "Parsed '";
display(val);
std::cout << "'\n";
}
else
std::cout << "Parse failed\n";
if (first != last)
std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
}
}