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 对象具有所有名称空间,在本例中 foobarnamespaces 向量中两次. 我有一个关于这种奇怪行为的小例子 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)。

现在唯一的解决方法是重构语法,这样就不需要回滚了。