代码之家  ›  专栏  ›  技术社区  ›  Greg Bacon

使用boost::spirit,我如何要求一个记录的一部分在它自己的行上?

  •  6
  • Greg Bacon  · 技术社区  · 14 年前

    我有一个记录解析器,它抛出几个异常中的一个,以指示哪个规则失败。

    头条:

    #include <iostream>
    #include <sstream>
    #include <stdexcept>
    #include <string>
    
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/classic_position_iterator.hpp>
    
    using namespace boost::spirit;
    using namespace boost::spirit::ascii;
    using namespace boost::spirit::qi;
    using namespace boost::spirit::qi::labels;
    
    using boost::phoenix::function;
    using boost::phoenix::ref;
    using boost::spirit::qi::eol;
    using boost::spirit::qi::fail;
    using boost::spirit::qi::lit;
    using boost::spirit::qi::on_error;
    
    using BOOST_SPIRIT_CLASSIC_NS::file_position;
    using BOOST_SPIRIT_CLASSIC_NS::position_iterator;
    

    我们使用 position_iterator Spirit.Classic ,因此下面的流插入运算符很方便。

    std::ostream&
    operator<<(std::ostream& o, const file_position &fp)
    {
      o << fp.file << ": " << fp.line << ',' << fp.column;
      return o;
    }
    

    模板 err_t 找出抛出与不同形式的解析失败相关联的异常的样板。

    template <typename Exception>
    struct err_t {
      template <typename, typename, typename>
      struct result { typedef void type; };
    
      template <typename Iterator>
      void operator() (info const &what, Iterator errPos, Iterator last) const
      {
        std::stringstream ss;
        ss << errPos.get_position()
           << ": expecting " << what
           << " near '" << std::string(errPos, last) << "'\n";
        throw Exception(ss.str());
      }
    };
    

    与它们一起使用的异常 埃雷特 包装:

    class MissingA : public std::runtime_error {
      public: MissingA(const std::string &s) : std::runtime_error(s) {}
    };
    
    class MissingB : public std::runtime_error {
      public: MissingB(const std::string &s) : std::runtime_error(s) {}
    };
    
    class MissingC : public std::runtime_error {
      public: MissingC(const std::string &s) : std::runtime_error(s) {}
    };
    
    function<err_t<MissingA> > const missingA = err_t<MissingA>();
    function<err_t<MissingB> > const missingB = err_t<MissingB>();
    function<err_t<MissingC> > const missingC = err_t<MissingC>();
    function<err_t<std::runtime_error> > const other_error =
      err_t<std::runtime_error>();
    

    语法寻找简单的序列。没有 eps , start 规则失败而不是 a 输入为空。

    template <typename Iterator, typename Skipper>
    struct my_grammar
      : grammar<Iterator, Skipper>
    {
      my_grammar(int &result)
        : my_grammar::base_type(start)
        , result(result)
      {
        a = eps > lit("Header A") > eol;
        b = eps > lit("Header B") > eol;
        c = eps > lit("C:") > int_[ref(result) = _1] > eol;
        start = a > b > c;
    
        a.name("A");
        b.name("B");
        c.name("C");
    
        on_error<fail>(start, other_error(_4, _3, _2));
        on_error<fail>(a, missingA(_4, _3, _2));
        on_error<fail>(b, missingB(_4, _3, _2));
        on_error<fail>(c, missingC(_4, _3, _2));
      }
    
      rule<Iterator, Skipper> start;
      rule<Iterator, Skipper> a;
      rule<Iterator, Skipper> b;
      rule<Iterator, Skipper> c;
      int &result;
    };
    

    my_parse ,我们将流的内容转储到 std::string 使用 位置迭代器 跟踪解析的位置。

    int
    my_parse(const std::string &path, std::istream &is)
    {
      std::string buf;
      is.unsetf(std::ios::skipws);
      std::copy(std::istream_iterator<char>(is),
                std::istream_iterator<char>(),
                std::back_inserter(buf));
    
      typedef position_iterator<std::string::const_iterator> itertype;
      typedef my_grammar<itertype, boost::spirit::ascii::space_type> grammar;
      itertype it(buf.begin(), buf.end(), path);
      itertype end;
    
      int result;
      grammar g(result);
    
      bool r = phrase_parse(it, end, g, boost::spirit::ascii::space);
      if (r && it == end) {
        std::cerr << "success!\n";
        return result;
      }
      else {
        file_position fpos = it.get_position();
        std::cerr << "parse failed at " << fpos << '\n';
        return -9999;
      }
    }
    

    最后,主程序

    int main()
    {
      std::stringstream ss;
      ss << "Header A\n"
         << "Header B\n"
         << "C: 3\n";
    
      int val = my_parse("path", ss);
      std::cout << "val = " << val << '\n';
    
      return 0;
    }
    

    上面的代码抛出 MissingA :

    terminate called after throwing an instance of 'MissingA'
      what():  path: 2,1: expecting  near 'Header B
    C: 3
    '

    我原以为船长会用掉这条线,但是 lexeme[eol] 结果却产生了同样的结果。

    我一定漏掉了一些显而易见的东西,因为这似乎是要编写的最琐碎的解析器之一。我做错什么了?

    1 回复  |  直到 14 年前
        1
  •  7
  •   hkaiser    14 年前

    是的,船长吃换行符。 lexeme[eol] 也没有帮助,因为lexeme指令在切换到no skipper模式之前调用skipper(请参见 here 更多细节)。

    为了避免跳过换行符,请使用其他跳过符类型,或将 eol 组成部分 no_skip[eol] ,在语义上等价于 lexeme[] ,但它不调用skipper。不过,请注意 no_skip[] 只是最近才添加的,所以只能在下一个版本中使用(boost v1.43)。但它已经在boost svn中了(参见 here 对于初步文件)。