使用语义动作解析逗号分隔的范围和数字列表
使用 Boost.Spirit X3,我想解析逗号- 将范围和单个数字(例如 1-4、6、7、9-12)的分隔列表放入单个 std::vector
.这是我想出的:
Using Boost.Spirit X3, I want to parse a comma-separated list of ranges and individual numbers (e.g. 1-4, 6, 7, 9-12) into a single std::vector<int>
. Here's what I've come up with:
namespace ast {
struct range
{
int first_, last_;
};
using expr = std::vector<int>;
}
namespace parser {
template<typename T>
auto as_rule = [](auto p) { return x3::rule<struct _, T>{} = x3::as_parser(p); };
auto const push = [](auto& ctx) {
x3::_val(ctx).push_back(x3::_attr(ctx));
};
auto const expand = [](auto& ctx) {
for (auto i = x3::_attr(ctx).first_; i <= x3::_attr(ctx).last_; ++i)
x3::_val(ctx).push_back(i);
};
auto const number = x3::uint_;
auto const range = as_rule<ast::range> (number >> '-' >> number );
auto const expr = as_rule<ast::expr> ( -(range [expand] | number [push] ) % ',' );
}
给定输入
"1,2,3,4,6,7,9,10,11,12", // individually enumerated
"1-4,6-7,9-12", // short-hand: using three ranges
这被成功解析为 ( Live On Coliru ):
this is successfully parsed as ( Live On Coliru ):
OK! Parsed: 1, 2, 3, 4, 6, 7, 9, 10, 11, 12,
OK! Parsed: 1, 2, 3, 4, 6, 7, 9, 10, 11, 12,
问题:我想我明白将语义动作 expand
应用于 range
部分是必要的,但为什么我还必须将语义动作 push
应用到 number
部分?没有它(即,对于 expr
,使用简单的 ( -(range [expand] | number) % ',')
规则,单个数字不会传播到AST ( 生活在 Coliru):
Question: I think I understand that applying the semantic action expand
to the range
part is necessary, but why do I also have to apply the semantic action push
to the number
part? Without it (i.e. with a plain ( -(range [expand] | number) % ',')
rule for expr
, the individual numbers don't get propagated into the AST ( Live On Coliru ):
OK! Parsed:
OK! Parsed: 1, 2, 3, 4, 6, 7, 9, 10, 11, 12,
额外问题:我什至需要语义动作来做到这一点吗?Spirit X3 文档似乎让他们望而却步.
Bonus Question: do I even need semantic actions at all to do this? The Spirit X3 documentation seems to discourage them.
推荐答案
语义动作抑制自动属性传播的FAQ.假设是语义动作会处理它.
The FAQ of this that semantic actions suppress automatic attribute propagation. The assumption being that the semantic action will take care of it instead.
通常有两种方法:
要么使用
operator%=
而不是operator=
将定义分配给规则
either use
operator%=
instead ofoperator=
to assign the definition to the rule
或使用 rule<>
模板的第三个(可选)模板参数,可以将其指定为 true
以强制自动传播语义.
or use the third (optional) template argument to the rule<>
template, which can be specified as true
to force automatic propagation semantics.
在这里,我主要通过删除范围规则本身内的语义操作来简化.现在,我们可以完全删除 ast::range
类型.没有更多的融合适应.
Here, I simplify mostly by removing the semantic action inside the range rule itself. Now, we can drop the ast::range
type altogether. No more fusion adaptation.
相反,我们使用 numer>>'-'>>number
的自然"合成属性,它是 int 的融合序列(fusion::deque
).
Instead we use the "naturally" synthesized attribute of numer>>'-'>>number
which is a fusion sequence of ints (fusion::deque<int, int>
in this case).
现在,剩下要做的就是确保 |
的分支产生兼容的类型.一个简单的 repeat(1)[]
解决了这个问题.
Now, all that's left to make it work, is to make sure the branches of |
yield compatible types. A simple repeat(1)[]
fixes that.
生活在 Coliru
#include <boost/spirit/home/x3.hpp>
#include <iostream>
namespace x3 = boost::spirit::x3;
namespace ast {
using expr = std::vector<int>;
struct printer {
std::ostream& out;
auto operator()(expr const& e) const {
std::copy(std::begin(e), std::end(e), std::ostream_iterator<expr::value_type>(out, ", "));;
}
};
}
namespace parser {
auto const expand = [](auto& ctx) {
using boost::fusion::at_c;
for (auto i = at_c<0>(_attr(ctx)); i <= at_c<1>(_attr(ctx)); ++i)
x3::_val(ctx).push_back(i);
};
auto const number = x3::uint_;
auto const range = x3::rule<struct _r, ast::expr> {} = (number >> '-' >> number) [expand];
auto const expr = x3::rule<struct _e, ast::expr> {} = -(range | x3::repeat(1)[number] ) % ',';
}
template<class Phrase, class Grammar, class Skipper, class AST, class Printer>
auto test(Phrase const& phrase, Grammar const& grammar, Skipper const& skipper, AST& data, Printer const& print)
{
auto first = phrase.begin();
auto last = phrase.end();
auto& out = print.out;
auto const ok = phrase_parse(first, last, grammar, skipper, data);
if (ok) {
out << "OK! Parsed: "; print(data); out << "
";
} else {
out << "Parse failed:
";
out << " on input: " << phrase << "
";
}
if (first != last)
out << " Remaining unparsed: '" << std::string(first, last) << '
';
}
int main() {
std::string numeric_tests[] =
{
"1,2,3,4,6,7,9,10,11,12", // individually enumerated
"1-4,6-7,9-12", // short-hand: using three ranges
};
for (auto const& t : numeric_tests) {
ast::expr numeric_data;
test(t, parser::expr, x3::space, numeric_data, ast::printer{std::cout});
}
}
打印:
OK! Parsed: 1, 2, 3, 4, 6, 7, 9, 10, 11, 12,
OK! Parsed: 1, 2, 3, 4, 6, 7, 9, 10, 11, 12,
相关文章