所有症状(包括奖励问题)都是不完善的属性传播机制的症状。
自动属性传播非常好,但仍然会有你必须帮助系统的情况。
查看您想要的规则和结果:
const auto foo
= *x3::alpha >> -(':' >> x3::double_) >> ';' >> even_int
| *x3::alpha >> '|' >> odd_int
;
我得出的结论是,您需要相同的规则,只是没有可选的双数用于偶数序数,并且对偶数和奇数使用不同的分隔符。
我会尝试更接近解析器表达式的声明性质,并尝试使判断更加高级。例如
Live On Coliru
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <optional>
namespace ast {
enum class discriminator { even, odd };
struct foo {
std::string s;
std::optional<double> od;
discriminator ind;
int id;
bool is_valid() const {
bool is_even = 0 == (id % 2);
switch (ind) {
case discriminator::even: return is_even;
case discriminator::odd: return not(is_even or od.has_value());
default: return false;
}
}
};
std::ostream& operator<<(std::ostream& os, const foo& foo)
{
os << std::quoted(foo.s); //
if (foo.od.has_value())
os << "(" << *foo.od << ")";
return os << " " << foo.id //
<< " (" << (foo.is_valid() ? "valid" : "INVALID") << ")";
}
} // namespace ast
BOOST_FUSION_ADAPT_STRUCT(ast::foo, s, od, ind, id)
namespace parser {
namespace x3 = boost::spirit::x3;
static const auto indicator_ = [] {
x3::symbols<ast::discriminator> sym;
sym.add //
(";", ast::discriminator::even) //
("|", ast::discriminator::odd);
return sym;
}();
static const auto foo //
= +x3::alpha >> -(':' >> x3::double_) >> indicator_ >> x3::int_;
}
int main()
{
for (std::string const input : {
"foobar:3.14;4",
"foobar;4",
"foobar|5",
// Invalid cases
"foobar:3.14;5",
"foobar;5",
"foobar|4",
"foobar:3.14|4",
}) //
{
ast::foo result;
if (parse(input.begin(), input.end(), parser::foo, result))
std::cout << std::quoted(input) << " -> " << result << std::endl;
else
std::cout << std::quoted(input) << " Syntax error" << std::endl;
}
}
打印
"foobar:3.14;4" -> "foobar"(3.14) 4 (valid)
"foobar;4" -> "foobar" 4 (valid)
"foobar|5" -> "foobar" 5 (valid)
"foobar:3.14;5" -> "foobar"(3.14) 5 (INVALID)
"foobar;5" -> "foobar" 5 (INVALID)
"foobar|4" -> "foobar" 4 (INVALID)
"foobar:3.14|4" -> "foobar"(3.14) 4 (INVALID)
请注意,您可以将此方法视为语法和语义的分离。
替代方案/从这里改进
当然你现在可以把解析写成
return parse(input.begin(), input.end(), parser::foo, result)
&& result.is_valid();
或者,如果您坚持可以像以前一样将该检查封装在语义操作中:
auto is_valid_ = [](auto& ctx) {
_pass(ctx) = _val(ctx).is_valid();
};
static const auto foo //
= x3::rule<struct foo_, ast::foo, true>{"foo"} //
= (+x3::alpha >> -(':' >> x3::double_) >> indicator_ >>
x3::int_)[is_valid_];
现在输出变成:
Live On Coliru
"foobar:3.14;4" -> "foobar"(3.14) 4 (valid)
"foobar;4" -> "foobar" 4 (valid)
"foobar|5" -> "foobar" 5 (valid)
"foobar:3.14;5" Syntax error
"foobar;5" Syntax error
"foobar|4" Syntax error
"foobar:3.14|4" Syntax error
没有融合
现在,上面明确地仍然使用具有自动属性传播的融合序列自适应。但是,由于您无论如何都深入研究语义操作¹,您当然可以在那里完成其余的工作:
Live On Coliru
#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <optional>
namespace ast {
struct foo {
std::string s;
std::optional<double> od;
int id;
};
std::ostream& operator<<(std::ostream& os, const foo& foo)
{
os << std::quoted(foo.s); //
if (foo.od.has_value())
os << "(" << *foo.od << ")";
return os << " " << foo.id;
}
} // namespace ast
namespace parser {
namespace x3 = boost::spirit::x3;
enum class discriminator { even, odd };
static const auto indicator_ = [] {
x3::symbols<discriminator> sym;
sym.add //
(";", discriminator::even) //
("|", discriminator::odd);
return sym;
}();
auto make_foo = [](auto& ctx) {
using boost::fusion::at_c;
auto& attr = _attr(ctx);
auto& s = at_c<0>(attr); // where are
auto& od = at_c<1>(attr); // structured bindings
auto& ind = at_c<2>(attr); // when you
auto& id = at_c<3>(attr); // need them? :|
bool is_even = 0 == (id % 2);
if (ind == discriminator::even)
_pass(ctx) = is_even;
else
_pass(ctx) = not(is_even or od.has_value());
_val(ctx) = ast::foo{
std::move(s),
od.has_value() ? std::make_optional(*od) : std::nullopt, id};
};
static const auto foo = x3::rule<struct foo_, ast::foo> {}
= (+x3::alpha >> -(':' >> x3::double_) >> indicator_ >>
x3::int_)[make_foo];
} // namespace parser
int main()
{
for (std::string const input : {
"foobar:3.14;4",
"foobar;4",
"foobar|5",
// Invalid cases
"foobar:3.14;5",
"foobar;5",
"foobar|4",
"foobar:3.14|4",
}) //
{
ast::foo result;
if (parse(input.begin(), input.end(), parser::foo, result))
std::cout << std::quoted(input) << " -> " << result << std::endl;
else
std::cout << std::quoted(input) << " Syntax error" << std::endl;
}
}
这有利有弊。优点是
- 缩短编译时间
-
discriminator 现在是解析器私有的
缺点:
- 你在做手动传播(比如
boost::optional->std::optional,这很笨拙)
- 语义操作¹
混合
正如您可能知道的那样,我不喜欢手写属性传播屈膝。如果您必须从 ast 中隐藏 ind 字段,或许可以这样做:
Live On Coliru
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <optional>
namespace ast {
struct foo {
std::string s;
std::optional<double> od;
int id;
};
std::ostream& operator<<(std::ostream& os, const foo& foo)
{
os << std::quoted(foo.s); //
if (foo.od.has_value())
os << "(" << *foo.od << ")";
return os << " " << foo.id;
}
} // namespace ast
namespace parser {
namespace x3 = boost::spirit::x3;
enum class discriminator { even, odd };
struct p_foo : ast::foo {
discriminator ind;
struct semantic_error : std::runtime_error {
using std::runtime_error::runtime_error;
};
void check_semantics() const {
bool is_even = 0 == (id % 2);
switch (ind) {
case discriminator::even:
if (!is_even)
throw semantic_error("id should be even");
break;
case discriminator::odd:
if (is_even)
throw semantic_error("id should be odd");
if (od.has_value())
throw semantic_error("illegal double at odd foo");
break;
}
}
};
}
BOOST_FUSION_ADAPT_STRUCT(parser::p_foo, s, od, ind, id)
namespace parser {
static const auto indicator_ = [] {
x3::symbols<discriminator> sym;
sym.add //
(";", discriminator::even) //
("|", discriminator::odd);
return sym;
}();
static const auto raw_foo //
= x3::rule<p_foo, p_foo>{} //
= +x3::alpha >> -(':' >> x3::double_) >> indicator_ >> x3::int_;
auto checked_ = [](auto& ctx) {
auto& _pf = _attr(ctx);
_pf.check_semantics();
_val(ctx) = std::move(_pf);
};
static const auto foo //
= x3::rule<struct foo_, ast::foo>{} //
= raw_foo[checked_];
} // namespace parser
int main()
{
for (std::string const input : {
"foobar:3.14;4",
"foobar;4",
"foobar|5",
// Invalid cases
"foobar:3.14;5",
"foobar;5",
"foobar|4",
"foobar:3.14|4",
"foobar:3.14|5",
}) //
{
ast::foo result;
try {
if (parse(input.begin(), input.end(), parser::foo, result))
std::cout << std::quoted(input) << " -> " << result << std::endl;
else
std::cout << std::quoted(input) << " Syntax error" << std::endl;
} catch(std::exception const& e) {
std::cout << std::quoted(input) << " Semantic error: " << e.what() << std::endl;
}
}
}
打印
"foobar:3.14;4" -> "foobar"(3.14) 4
"foobar;4" -> "foobar" 4
"foobar|5" -> "foobar" 5
"foobar:3.14;5" Semantic error: id should be even
"foobar;5" Semantic error: id should be even
"foobar|4" Semantic error: id should be odd
"foobar:3.14|4" Semantic error: id should be odd
"foobar:3.14|5" Semantic error: illegal double at odd foo
注意更丰富的诊断信息。
后脚本:最小的变化
后来,重新阅读您的问题,我突然意识到有一个较小的变化可以帮助您的语法。我用下面的话介绍了我的答案:
自动属性传播非常好,但仍会出现需要帮助系统的情况
在这里,您可以通过使两个分支具有相同的结构来帮助它。所以不是
const auto foo
= *x3::alpha >> -(':' >> x3::double_) >> ';' >> even_int
| *x3::alpha >> '|' >> odd_int
;
您可以在奇数分支的中间手动插入一个空的可选双精度:
const auto foo //
= +x3::alpha >> -(':' >> x3::double_) >> ';' >> even_int //
| +x3::alpha >> x3::attr(ast::optdbl{}) >> '|' >> odd_int;
(其中optdbl 是std::optional<double> 风格的别名)。
现在,如果你稍微重构一下 odd_int/even_int 规则,我会说这个方法比上面的其他选项更重要:
Live On Coliru
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <optional>
namespace ast{
using optdbl = std::optional<double>;
struct foo {
std::string s;
optdbl od;
int id;
};
std::ostream& operator<<(std::ostream& os, const foo& foo)
{
os << std::quoted(foo.s); //
if (foo.od.has_value())
os << "(" << *foo.od << ")";
return os << " " << foo.id;
}
}
BOOST_FUSION_ADAPT_STRUCT(ast::foo, s, od,id)
namespace parser {
namespace x3 = boost::spirit::x3;
static auto mod2check(int remainder) {
return [=](auto& ctx) { //
_pass(ctx) = _val(ctx) % 2 == remainder;
};
}
static auto mod2int(int remainder) {
return x3::rule<struct _, int, true>{} = x3::int_[mod2check(remainder)];
}
const auto foo //
= +x3::alpha >> //
(-(':' >> x3::double_) | x3::attr(ast::optdbl{})) >> //
(';' >> mod2int(0) | '|' >> mod2int(1)) //
;
} // namespace parser
int main()
{
for (std::string const input : {
"foobar:3.14;4",
"foobar;4",
"foobar|5",
// Invalid cases
"foobar:3.14;5",
"foobar;5",
"foobar|4",
"foobar:3.14|4",
}) //
{
ast::foo result;
if (parse(input.begin(), input.end(), parser::foo, result))
std::cout << std::quoted(input) << " -> " << result << std::endl;
else
std::cout << std::quoted(input) << " Syntax error" << std::endl;
}
}
¹Boost Spirit: "Semantic actions are evil"?