在Boost Spirit中定义使用子解析器参数化的解析器

hmu*_*ner 6 c++ templates boost-spirit parser-generator boost-spirit-qi

我想将一些旧的手写解析代码转换为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 )
Run Code Online (Sandbox Code Playgroud)

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

 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.
Run Code Online (Sandbox Code Playgroud)

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

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

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

seh*_*ehe 5

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

这里面向最终用户的是qi::auto_.qi::auto_是一个解析器,而不是语法.

这具有明显的优势[1].

  • 最重要的是,它允许用户使用自己选择的队长以及可能使用的语法在语法中使用解析器qi::locals<>.
  • 此外,auto_齐表达终端已经被定义,因此没有必要在所有使用详细的模板参数列表来实例化一个语法:
  • 最后,解析器返回一个表达式模板,因此没有类型擦除,因此以这种方式组合几个auto_ parsers并不比手动编写语法效率低(而且包含在a中qi::rule<>qi::grammar<>导致性能开销)

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

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

正如您所看到的,这可以容纳一个队长,并且"神奇地"选择匹配的解析器parsed.现在,要从OP获取样本格式,您需要挂钩auto_解析器的自定义点:

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
} } }
Run Code Online (Sandbox Code Playgroud)

这就是所有需要的.这是一个解析的演示:

VECTOR[ 1 ,
 (               
     PAIR (0.97, 
           5),   
     PAIR (1.75,10)   
 )               
]
Run Code Online (Sandbox Code Playgroud)

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

Parsed:
 0.97 5 
 1.75 10 
Run Code Online (Sandbox Code Playgroud)

看到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";
}
Run Code Online (Sandbox Code Playgroud)

[1]潜在的缺点是定制点是固定的,因此您只能将1个auto_解析器与任何类型相关联.滚动您自己的基本模板可以让您获得更多控制,并使您(更多)轻松拥有不同的"解析器风格".然而,最终它可以拥有两全其美,所以我首先要方便.