Boost.x3:备选方案之间的属性累积
Boost.x3: attribute accumulates between alternatives
我有一个用于解析标识符的解析器,如 foo, bar, baz
,还有一个用于解析嵌套标识符的解析器,如 foo::bar, foo::bar.baz, foo::bar.baz.baham
它们都解析为相同的 ast 结构,如下所示:
struct identifier : x3::position_tagged{
std::vector <std::string> namespaces;
std::vector <std::string> classes;
std::string identifier;
};
identifier
的解析器如下所示:
#define VEC_ATR x3::attr(std::vector<std::string>({})) //ugly hack
auto const identifier_def =
VEC_ATR
>> VEC_ATR
>> id_string;
对于 nested_identifier
这样的:
auto const nested_identifier_def =
x3::lexeme[
(+(id_string >> "::") >> +(id_string >> ".") > id_string)
| (+(id_string >> "::") >> VEC_ATR > id_string)
| (VEC_ATR >> +(id_string >> ".") > id_string)
| identifier
];
我知道我为宏感到羞耻。
标识符解析器工作正常,但是
nested_identifier
有一个奇怪的行为
如果我尝试解析类似 foo::bar::baz
的东西,则从解析器中掉出的 ast 对象具有所有名称空间,在本例中 foo
和 bar
在 namespaces
向量中两次.
我有一个关于这种奇怪行为的小例子
here。
任何人都可以向我解释为什么会发生这种情况,以及如何避免这种情况?
出现这种行为的原因是替代解析器在其分支之一失败时不会自动回滚对外部属性所做的更改。
你的情况是这样的:
- Initially the attribute is
[{},{},""]
.
- The first alternative branch is tried.
id_string >> "::"
matches twice and adds foo
and bar
to the first vector ->[{foo,bar},{},""]
.
id_string >> "."
fails to match -> the sequence fails -> the alternative branch fails (leaving the attribute unchanged).
- The second alternative branch is tried.
id_string >> "::"
matches twice and adds foo
and bar
to the first vector ->[{foo,bar,foo,bar},{},""]
.
attr(vector<string>({}))
succeeds (attr
always succeeds) and substitutes the empty second vector with a vector with an empty string -> [{foo,bar,foo,bar},{""},""]
.
id_string
matches and baz
is added to the attribute ->[{foo,bar,foo,bar},{""},baz]
.
- The second alternative branch succeeds.
在Spirit.Qi中,这种情况下的解决方案非常简单,只需使用hold directive. Unfortunately this directive is not yet implemented in Spirit.X3. A possible alternative could be putting each of the alternative branches in its own x3::rule
either explicitly or with as<ast::identifier>(alternative_branch)
as used by sehe. Here是一个显示as
方法的简化示例。
另一种可能是实施 hold
指令,这是我的尝试(running on WandBox):
#include <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/core/skip_over.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
namespace boost { namespace spirit { namespace x3
{
template <typename Subject>
struct hold_directive : unary_parser<Subject, hold_directive<Subject>>
{
typedef unary_parser<Subject, hold_directive<Subject> > base_type;
static bool const is_pass_through_unary = true;
static bool const handles_container = Subject::handles_container;
hold_directive(Subject const& subject)
: base_type(subject) {}
template <typename Iterator, typename Context
, typename RContext, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context const& context, RContext& rcontext, Attribute& attr) const
{
Attribute copy(attr);
if (this->subject.parse(first, last, context, rcontext, copy))
{
traits::move_to(copy, attr);
return true;
}
return false;
}
};
struct hold_gen
{
template <typename Subject>
hold_directive<typename extension::as_parser<Subject>::value_type>
operator[](Subject const& subject) const
{
return { as_parser(subject) };
}
};
auto const hold = hold_gen{};
}}}
请注意,从 Boost1.70 开始,@sehe 提出的解决方案不再有效(参见 this discussion)。
现在唯一的解决方法是重构语法,这样就不需要回滚了。
我有一个用于解析标识符的解析器,如 foo, bar, baz
,还有一个用于解析嵌套标识符的解析器,如 foo::bar, foo::bar.baz, foo::bar.baz.baham
它们都解析为相同的 ast 结构,如下所示:
struct identifier : x3::position_tagged{
std::vector <std::string> namespaces;
std::vector <std::string> classes;
std::string identifier;
};
identifier
的解析器如下所示:
#define VEC_ATR x3::attr(std::vector<std::string>({})) //ugly hack
auto const identifier_def =
VEC_ATR
>> VEC_ATR
>> id_string;
对于 nested_identifier
这样的:
auto const nested_identifier_def =
x3::lexeme[
(+(id_string >> "::") >> +(id_string >> ".") > id_string)
| (+(id_string >> "::") >> VEC_ATR > id_string)
| (VEC_ATR >> +(id_string >> ".") > id_string)
| identifier
];
我知道我为宏感到羞耻。
标识符解析器工作正常,但是
nested_identifier
有一个奇怪的行为
如果我尝试解析类似 foo::bar::baz
的东西,则从解析器中掉出的 ast 对象具有所有名称空间,在本例中 foo
和 bar
在 namespaces
向量中两次.
我有一个关于这种奇怪行为的小例子
here。
任何人都可以向我解释为什么会发生这种情况,以及如何避免这种情况?
出现这种行为的原因是替代解析器在其分支之一失败时不会自动回滚对外部属性所做的更改。
你的情况是这样的:
- Initially the attribute is
[{},{},""]
.- The first alternative branch is tried.
id_string >> "::"
matches twice and addsfoo
andbar
to the first vector ->[{foo,bar},{},""]
.id_string >> "."
fails to match -> the sequence fails -> the alternative branch fails (leaving the attribute unchanged).- The second alternative branch is tried.
id_string >> "::"
matches twice and addsfoo
andbar
to the first vector ->[{foo,bar,foo,bar},{},""]
.attr(vector<string>({}))
succeeds (attr
always succeeds) and substitutes the empty second vector with a vector with an empty string ->[{foo,bar,foo,bar},{""},""]
.id_string
matches andbaz
is added to the attribute ->[{foo,bar,foo,bar},{""},baz]
.- The second alternative branch succeeds.
在Spirit.Qi中,这种情况下的解决方案非常简单,只需使用hold directive. Unfortunately this directive is not yet implemented in Spirit.X3. A possible alternative could be putting each of the alternative branches in its own x3::rule
either explicitly or with as<ast::identifier>(alternative_branch)
as used as
方法的简化示例。
另一种可能是实施 hold
指令,这是我的尝试(running on WandBox):
#include <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/core/skip_over.hpp>
#include <boost/spirit/home/x3/core/parser.hpp>
namespace boost { namespace spirit { namespace x3
{
template <typename Subject>
struct hold_directive : unary_parser<Subject, hold_directive<Subject>>
{
typedef unary_parser<Subject, hold_directive<Subject> > base_type;
static bool const is_pass_through_unary = true;
static bool const handles_container = Subject::handles_container;
hold_directive(Subject const& subject)
: base_type(subject) {}
template <typename Iterator, typename Context
, typename RContext, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context const& context, RContext& rcontext, Attribute& attr) const
{
Attribute copy(attr);
if (this->subject.parse(first, last, context, rcontext, copy))
{
traits::move_to(copy, attr);
return true;
}
return false;
}
};
struct hold_gen
{
template <typename Subject>
hold_directive<typename extension::as_parser<Subject>::value_type>
operator[](Subject const& subject) const
{
return { as_parser(subject) };
}
};
auto const hold = hold_gen{};
}}}
请注意,从 Boost1.70 开始,@sehe 提出的解决方案不再有效(参见 this discussion)。
现在唯一的解决方法是重构语法,这样就不需要回滚了。