【问题标题】:Define parsers parameterized with sub-parsers in Boost Spirit在 Boost Spirit 中定义使用子解析器参数化的解析器
【发布时间】:2014-01-21 09:10:31
【问题描述】:

我想将一些旧的手写解析代码转换为 Boost Spirit 并在此过程中学习(更多)精神。旧代码使用流和模板来解析一些数据类型和一些容器的定义。

一些典型的格式:

VECTOR[number_of_items,(item_1, item_2 .... item_n)]
PAIR(p1, p2)
RECT[(left,top)-(right,bottom)]
Point( x, y )
Size( x, y )

解析函数是模板,以项目的类型作为模板参数,并使用流作为输入,例如

 template<class T> std::istream& operator>>(std::Stream& in, std::vector<T>& v);

 template<class T1, class T2> std::istream& operator>>(std::istream& in, std::pair<T1, T2>& p);

 template<class T1, class T2> std::istream& operator>>(std::istream& in, RectType<T>& r);
 etc.

向量的解析器(流提取器)调用模板类型的解析器。

使用这些可以解析整数矩形、双矩形以及字符串和整数对向量的定义。

是否可以使用 Spirit 编写模板化解析器来调用模板类型的子解析器?

【问题讨论】:

标签: c++ templates boost-spirit boost-spirit-qi parser-generator


【解决方案1】:

正如另一个答案几乎已经阐明的那样,Qi 已经有了一种机制,可以在给定属性类型的情况下动态生成解析器。

这里面向最终用户的位是qi::auto_qi::auto_ is a parser,而不是 语法

这具有明显的优势[1]

  • 最重要的是,它允许用户通过自己选择的skipperqi::locals&lt;&gt; 在语法中使用解析器。
  • 另外,auto_ Qi 表达式终端已经定义,因此完全不需要使用详细的模板参数列表来实例化语法:
  • 最后,解析器返回一个表达式模板,因此不会进行类型擦除,因此以这种方式组合多个 auto_ 解析器的效率不低于手动编写语法(而两者都包装在 qi::rule&lt;&gt;qi::grammar&lt;&gt; 会产生性能开销)

让我们看看它是如何使用的:

std::vector<std::pair<double, int> > parsed;
bool result_ = qi::phrase_parse(first, last, qi::auto_, qi::space, parsed);

如您所见,这可以容纳一个船长,并“神奇地”选择匹配parsed 的解析器。现在,要从 OP 获取示例格式,您需要 hook into the customization point for the auto_ parser

namespace boost { namespace spirit { namespace traits {   
    // be careful copying expression templates. Boost trunk has `qi::copy` for this too, now
    #define PARSER_DEF(a) using type = decltype(boost::proto::deep_copy(a)); static type call() { return boost::proto::deep_copy(a); }

    template<typename T1, typename T2> 
        struct create_parser<std::pair<T1, T2> > 
        {
            PARSER_DEF('(' >> create_parser<T1>::call() >> ',' >> create_parser<T2>::call() >> ')');
        };

    template<typename TV, typename... TArgs>
        struct create_parser<std::vector<TV, TArgs...> >
        {
            PARSER_DEF('[' >> qi::omit[qi::uint_] >> ',' >> '(' >> create_parser<TV>::call() % ',' >> ')' >> ']' );
        };

    #undef PARSER_DEF
} } }

这就是真正需要的一切。这是一个解析的演示:

VECTOR[ 1 ,
 (               
     PAIR (0.97, 
           5),   
     PAIR (1.75,10)   
 )               
]

并将解析后的数据打印为:

Parsed:
 0.97 5 
 1.75 10 

Live On Coliru

完整代码清单

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/qi.hpp>

namespace qi = boost::spirit::qi;

namespace boost { namespace spirit { namespace traits {   
    // be careful copying expression templates. Boost trunk has `qi::copy` for this too, now
    #define PARSER_DEF(a) using type = decltype(boost::proto::deep_copy(a)); static type call() { return boost::proto::deep_copy(a); }

    template<typename T1, typename T2> 
        struct create_parser<std::pair<T1, T2> > 
        {
            PARSER_DEF(lexeme [ lit("PAIR") ] >> '(' >> create_parser<T1>::call() >> ',' >> create_parser<T2>::call() >> ')');
        };

    template<typename TV, typename... TArgs>
        struct create_parser<std::vector<TV, TArgs...> >
        {
            PARSER_DEF(lexeme [ lit("VECTOR") ] >> '[' >> qi::omit[qi::uint_] >> ',' >> '(' >> create_parser<TV>::call() % ',' >> ')' >> ']' );
        };

    #undef PARSER_DEF
} } }

#include <boost/spirit/home/karma.hpp>
namespace karma = boost::spirit::karma;

int main()
{
    std::string const input("VECTOR[ 1 ,\n"
                " (               \n"
                "     PAIR (0.97, \n"
                "           5),   \n"
                "     PAIR (1.75,10)   \n"
                " )               \n"
            "]");

    std::cout << input << "\n\n";

    auto first = input.begin();
    auto last = input.end();

    std::vector<std::pair<double, int> > parsed;
    bool result_ = qi::phrase_parse(first, last, qi::auto_, qi::space, parsed);

    if (first!=last)
        std::cout << "Remaining unparsed input: '" << std::string(first, last) << "'\n";

    if (result_)
        std::cout << "Parsed:\n " << karma::format_delimited(karma::auto_ % karma::eol, " ", parsed) << "\n";
    else
        std::cout << "Parsing did not succeed\n";
}

[1] 一个潜在的缺点是自定义点是固定的,因此您只能关联 1 个 @ 987654337@ 任何类型的解析器。滚动您自己的基本模板为您提供更多控制权,并使您能够(更)轻松地拥有不同的“解析器风格”。但是,最终有可能两全其美,所以我会先去方便。

【讨论】:

  • +1 感谢您对qi::auto_ 的解释。直到现在我才明白。
【解决方案2】:

是的,这是可能的。我会这样实现

#include <boost/spirit/home/qi.hpp>

namespace qi = boost::spirit::qi;

template < typename _Type, typename _Iterator, typename _Enable = void >
struct parser;

template < typename _Type, typename _Iterator >
struct parser < _Type, _Iterator, typename std::enable_if < std::is_arithmetic<_Type> ::value > ::type > :
    qi::grammar<_Iterator, _Type() >
{
    parser()
        : parser::base_type(impl)
    {
        impl = qi::create_parser<_Type>() ;
    }

    qi::rule<_Iterator, _Type()> impl;
};

template < typename _Iterator >
struct parser < double, _Iterator> :
    qi::grammar<_Iterator, double() >
{
    parser()
        : parser::base_type(impl)
    {
        impl = qi::double_;
    }

    qi::rule<_Iterator, double()> impl;
};

template < typename _First, typename _Second, typename _Iterator >
struct parser < std::pair<_First, _Second>, _Iterator> :
    qi::grammar<_Iterator, std::pair<_First, _Second>() >
{
    parser()
        : parser::base_type(impl)
    {
        impl =  qi::lit('(') >> first >> ',' >> second >> ')';
    }

    qi::rule<_Iterator, std::pair<_First, _Second>()> impl;

    parser<_First, _Iterator> first;
    parser<_Second, _Iterator> second;
};

template < typename _Type, typename _Alloc, typename _Iterator >
struct parser < std::vector<_Type, _Alloc>, _Iterator> :
    qi::grammar<_Iterator, std::vector<_Type, _Alloc>() >
{
    parser()
        : parser::base_type(impl)
    {
        impl = qi::lit('[') >> qi::omit[qi::uint_] >> ",(" >> item % ',' >> ")]";
    }

    qi::rule<_Iterator, std::vector<_Type, _Alloc>()> impl;

    parser<_Type, _Iterator> item;
};

使用示例:

int main(int agrc, char *argv[])
{
    typedef std::pair<double, int> pair;

    using string = std::string;

    string input_ = { "[1,((0.97,5),(1.75,10))]" };

    string::const_iterator iterator_ = input_.begin();
    string::const_iterator end_ = input_.end();

    std::vector<pair> pairs_;

    bool result_ = qi::parse(iterator_, end_, parser <std::vector<pair>, string::const_iterator> (), pairs_);

    return 0;
}

【讨论】:

  • 有点酷。我怀疑它可以做得很多更短更灵活。让我检查
  • 是的。我是对的 :) 这是相同但更灵活的in 31 lines of code。这比your version 少了51 行。有关背景想法,请参阅我的答案。顺便说一下,为灵感 +1!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-21
  • 1970-01-01
相关资源
最近更新 更多