Boost Spirit Qi - 用两个组件序列解析列表

Boost Spirit Qi - list parsing with two component sequences

我正在尝试为参数列表编写一个解析器,将允许以下内容:

myFunc( arg0, arg1, namedArg0 = valueA, namedArg1 = valueB )

在上面的示例中,我希望前两个参数解析为 TypeA 的实体,然后包含在 std::vector 中。后两个参数将解析为 TypeB,它将包含在 std::vector 中。所有 TypeA 参数都应位于所有 TypeB 参数之前。但我希望所有内容都从一个逗号分隔的列表中解析出来。应该可以只有 TypeA 参数,只有 TypeB 参数或一系列 TypeA 元素后跟一系列 TypeB 元素。

我在定义规则时遇到问题,这样分隔最后一个 TypeA 参数和第一个 TypeB 参数的逗号不会被误认为是另一个 TypeA 参数。

我当前的实现如下。谁能就如何解决这个问题提出任何建议?

这里的主要区别是 TypeA 参数应该是单个符号,而 TypeB 参数应该采用以下形式:symbol = symbol。

问题似乎与 TypeA 参数等同于 TypeB 参数的第一部分这一事实有关,因此导致 TypeA 序列的结尾不清楚?

谢谢!

struct Params
{
    std::vector<TypeA> a_elements;
    std::vector<TypeB> b_elements;

    Params(const std::vector<TypeA>& a_vec, const std::vector<TypeB>& b_vec)
    : a_elements( a_vec ), b_elements( b_vec ) {}

    static Params create(const std::vector<TypeA>& a_vec, const std::vector<TypeB>& b_vec)
    {
        return Params( a_vec, b_vec );
    }
};

struct ParamsParser : qi::grammar<Iterator, Params(), Skipper>
{
    qi::rule<Iterator, Params(), Skipper>                           start_rule;
    qi::rule<Iterator, std::vector<TypeA>(), Skipper>               type_a_vec_opt_rule;
    qi::rule<Iterator, std::vector<TypeB>(), Skipper>               type_b_vec_opt_rule;
    qi::rule<Iterator, std::vector<TypeA>(), Skipper>               type_a_vec_rule;
    qi::rule<Iterator, std::vector<TypeB>(), Skipper>               type_b_vec_rule;
    qi::rule<Iterator, TypeA(), Skipper>                            type_a_rule;
    qi::rule<Iterator, TypeB(), Skipper>                            type_b_rule;    
    qi::rule<Iterator, std::string(), Skipper>                      symbol_rule;

    ParamsParser() : ParamsParser::base_type( start_rule, "params_parser" )
    {
        start_rule =
        // version 1:
          ( ( '(' >> type_a_vec_rule >> ',' >> type_b_vec_rule >> ')' )
           [ qi::_val = boost::phoenix::bind( Params::create, qi::_1, qi::_2 ) ] )
        // version 2:
        | ( ( '(' >> type_a_vec_opt_rule >> ')' )
           [ qi::_val = boost::phoenix::bind( Params::create, qi::_1, std::vector<TypeB>() ) ] )
        // version 3:
        | ( ( '(' >> type_b_vec_opt_rule >> ')' )
           [ qi::_val = boost::phoenix::bind( Params::create, std::vector<TypeA>(), qi::_1 ) ] )
        ;

        type_a_vec_opt_rule = -type_a_vec_rule;
        type_b_vec_opt_rule = -type_b_vec_rule;
        type_a_vec_rule     = ( type_a_rule % ',' );        
        type_b_vec_rule     = ( type_b_rule % ',' );
        type_a_rule         = ( symbol_rule );
        type_b_rule         = ( symbol_rule >> '=' >> symbol_rule );
        symbol_rule         = qi::char_( "a-zA-Z_" ) >> *qi::char_( "a-zA-Z_0-9" );
    }
};

两个问题。首先,您要确保不匹配可以匹配命名参数的位置参数¹。

其次:您希望将它们放在不同的集合中。

In the above example, I would like the first two arguments to resolve to entities of TypeA, which would then be contained by a std::vector< TypeA >. The second two arguments would resolve to TypeB, which would be contained by a std::vector< TypeB >. All TypeA arguments should come before all TypeB arguments.

所以,你会天真地写

argument_list = '(' >> -positional_args >> -named_args >> ')';

在哪里

positional_args = expression % ',';
named_args      = named_arg % ',';
named_arg       = identifier >> '=' > expression)

当然,您已经观察到这会因位置参数和命名参数之间的可选插入而出错。但首先是第一件事。

让我们防止位置匹配 named 可以匹配的地方:

positional_args = (!named_arg >> expression) % ',';

这很直白。根据您精确的 expression/identifier 作品,您可以使用更有效的微分器,但这是最简单的方法。

现在,以同样的精神继续,对于 positionals/named 之间的 ',' 最简单的方法是......简单地检查 IF 是否存在位置 THEN必须跟在 ), 之后(然后应该被消耗)。快点:

argument_list = '(' >> positional_args >> -named_args >> ')';
positional_args = *(expression >> (&lit(')') | ','));

Note how positional_args now allows for empty match, so it's no longer optional in the argument_list rule

结果:

  1. 你有你的语法
  2. 自然地解析为两个后续容器,如vector<TypeA>vector<TypeB>

你还想要什么?

反对,法官大人!!

我几乎能感觉到回答:我想要更优雅! 毕竟,现在 positional_args 是 "encumbered" 对命名参数的了解以及期望参数列表的结尾。

是的。这是一个合理的担忧。我同意更复杂的语法我更愿意实际写

 argument_list = '(' >> -(argument % ',') >> ')';

然后这将自然地解析为 TypeAOrB² 的容器,并且您将进行一些语义检查以确保没有位置参数出现在位置参数之后:

Live On Coliru³

arguments %= 
    eps [ found_named_arg = false ] // initialize the local (_a)
    >> '(' >> -(argument(found_named_arg) % ',') >> ')';

argument  %= (named_arg > eps [named_only=true])  
           | (eps(!named_only) >> positional);

我认为这和上面的一样笨拙,所以如果你无论如何都能拥有你想要的自然向量,为什么还要使 AST 复杂化呢?

吃你的蛋糕

是的,您可以将所有这些与大量 Phoenix 魔法结合起来使用自定义属性传播将参数类型分类到您的 AST "buckets"。 SO 上存在几个答案,展示了如何进行这样的欺骗。

  • parsing into several vector members

不过,我认为对于所提出的问题,没有充分的理由介绍这些内容。


¹ OT 咆哮:为什么不使用适当的术语而不是像 "typeA" 和 "typeB" 这样的烟雾和镜子。如果它真的是一个秘密,请不要 post 在 SO 上提出问题。如果不是,请不要隐藏上下文,因为 99% 的时间问题和解决方案都来自上下文

²boost::variant<TypeA, TypeB>

³ Live On Coliru 的完整来源,用于防比特腐化:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;

struct Positional { };
struct Named      { };

using Arg  = boost::variant<Positional, Named>;
using Args = std::vector<Arg>;

template <typename Iterator>
struct ArgsParser : qi::grammar<Iterator, Args()>
{
    ArgsParser() : ArgsParser::base_type( start_rule, "params_parser" )
    {
        using namespace qi;
        start_rule = skip(space) [ arguments ];

        arguments %= 
            eps [ found_named_arg = false ] // initialize the local (_a)
            >> '(' >> -(argument(found_named_arg) % ',') >> ')';

        argument  %= (named_arg > eps [named_only=true])  
                   | (eps(!named_only) >> positional);

        named_arg  = "n" >> attr(Named{});
        positional = "p" >> attr(Positional{});
    }

  private:
    using Skipper = qi::space_type;
    qi::rule<Iterator, Args()> start_rule;

    qi::rule<Iterator, Args(),       Skipper, qi::locals<bool> > arguments;
    qi::rule<Iterator, Arg(bool&),   Skipper> argument;
    qi::rule<Iterator, Named(),      Skipper> named_arg;
    qi::rule<Iterator, Positional(), Skipper> positional;

    qi::_a_type  found_named_arg;
    qi::_r1_type named_only;
};

// for debug output
static inline std::ostream& operator<<(std::ostream& os, Named)      { return os << "named";      }
static inline std::ostream& operator<<(std::ostream& os, Positional) { return os << "positional"; }

int main() {
    using It = std::string::const_iterator;
    ArgsParser<It> const p;

    for (std::string const input : {
            "()",
            "(p)",
            "(p,p)",
            "(p,n)",
            "(n,n)",
            "(n)",
            // start the failing
            "(n,p)",
            "(p,p,n,p,n)",
            "(p,p,n,p)",
            })
    {
        std::cout << " ======== " << input << " ========\n";

        It f(input.begin()), l(input.end());
        Args parsed;
        if (parse(f,l,p,parsed)) {
            std::cout << "Parsed " << parsed.size() << " arguments in list: ";
            std::copy(parsed.begin(), parsed.end(), std::ostream_iterator<Arg>(std::cout, " "));
            std::cout << "\n";
        } else {
            std::cout << "Parse failed\n";
        }

        if (f!=l) {
            std::cout << "Remaining input unparsed: '" << std::string(f,l) << "'\n";
        }
    }
}

版画

 ======== () ========
Parsed 0 arguments in list: 
 ======== (p) ========
Parsed 1 arguments in list: positional 
 ======== (p,p) ========
Parsed 2 arguments in list: positional positional 
 ======== (p,n) ========
Parsed 2 arguments in list: positional named 
 ======== (n,n) ========
Parsed 2 arguments in list: named named 
 ======== (n) ========
Parsed 1 arguments in list: named 
 ======== (n,p) ========
Parse failed
Remaining input unparsed: '(n,p)'
 ======== (p,p,n,p,n) ========
Parse failed
Remaining input unparsed: '(p,p,n,p,n)'
 ======== (p,p,n,p) ========
Parse failed
Remaining input unparsed: '(p,p,n,p)'