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

时间:2016-12-10 16:50:51

标签: c++ boost boost-spirit

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

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

在上面的例子中,我希望前两个参数解析为TypeA的实体,然后由std :: vector< TypeA>。后两个参数将解析为TypeB,它将由std :: vector< TypeB>。所有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" );
    }
};

1 个答案:

答案 0 :(得分:5)

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

第二:你希望他们分开收藏。

  

在上面的例子中,我希望前两个参数解析为TypeA的实体,然后由std::vector< TypeA >包含。TypeB。后两个参数将解析为std::vector< TypeB >TypeA将包含TypeB。所有argument_list = '(' >> -positional_args >> -named_args >> ')'; 参数都应该出现在所有positional_args = expression % ','; named_args = named_arg % ','; named_arg = identifier >> '=' > expression) 个参数之前。

所以,你天真地写了

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

其中

','

当然你已经注意到这会因位置和命名args之间的可选插值而出错。但首先要做的事情。

让我们阻止位置匹配命名匹配的位置:

)

这是相当生硬的。根据您精确的表达式/标识符产生,您可以使用更有效的区分器,但这是最简单的方法。

现在,为了继续本着相同的精神,关于定位/命名之间的,可以起作用的最简单的事情是......只需检查如果有位置那么必须遵循argument_list = '(' >> positional_args >> -named_args >> ')'; positional_args = *(expression >> (&lit(')') | ',')); positional_args(应该随后使用)。很快:

argument_list
  

请注意vector<TypeA>现在如何允许空匹配,因此vector<TypeB>规则

中不再是可选的

结果是:

  1. 你有语法
  2. 自然解析为两个后续容器,如positional_args argument_list = '(' >> -(argument % ',') >> ')';
  3. 你还想要什么?

    异议,你的荣誉!!

    我几乎可以感受到回应:我想要更优雅! 毕竟,现在TypeAOrB被“阻止”了解有关命名args的知识以及期望参数列表的结束。

    是。这是一个有效的问题。我同意更多涉及的语法,我更愿意实际写

    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);
    

    然后这会自然地解析为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";
            }
        }
    }
    

    我认为这和上面一样笨拙,所以如果你能拥有你想要的自然载体,为什么会使AST复杂化呢?

    吃蛋糕然后吃它

    是的,您可以将所有这些与凤凰魔法 OR 结合起来使用自定义属性传播来对AST“桶”中的参数类型进行排序。 SO上有几个答案,说明如何做那样的诡计。

    但是我认为提出的问题并没有充分的理由来介绍其中任何一个。

    ¹OT rant:为什么不使用正确的术语而不是像“typeA”和“typeB”这样的烟雾和镜子。如果它真的是一个秘密,不要在SO上发布问题。如果不是,请不要隐藏上下文,因为99%的时间问题和解决方案都来自上下文

    ² ======== () ======== 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)'

    ³用于反比特目的 Live On Coliru 的完整来源:

    variable1
    Strongly Agree    count1
    Agree             count2
    Subtotal          count1 + count2
    Neutral           count3
    Disagree          count4
    Strongly Disagree count6
    

    打印

             Strongly Agree    Agree   Neutral   Disagree    Strongly Disagree    Subtotal
    variable1    count1        count2   count3    count4     count5              count1+count2