代码之家  ›  专栏  ›  技术社区  ›  Davide

boost::spirit::qi::语法和可变模板

  •  2
  • Davide  · 技术社区  · 7 年前

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/variant.hpp>
    #include <iostream>
    #include <string>
    
    using namespace boost::spirit;
    
    template <class Attribute>
    using command_rule =
        qi::rule<std::string::iterator, Attribute, ascii::space_type>;
    
    template <class Attribute>
    using command_grammar =
        qi::grammar<std::string::iterator, Attribute, ascii::space_type>;
    
    struct Latitude {
    
      struct return_type {
        double lat_;
      };
    
      struct grammar : command_grammar<return_type()> {
        grammar() : grammar::base_type{latitude_} {
          latitude_ = "LAT=" >> qi::double_;
        }
    
      private:
        command_rule<return_type()> latitude_;
      };
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Latitude::return_type, (double, lat_))
    
    struct Longitude {
    
      struct return_type {
        double lon_;
      };
    
      struct grammar : command_grammar<return_type()> {
        grammar() : grammar::base_type{longitude_} {
          longitude_ = "LON=" >> qi::double_;
        }
    
      private:
        command_rule<return_type()> longitude_;
      };
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Longitude::return_type, (double, lon_))
    

    template <class... Commands>
    struct device_grammar : boost::spirit::qi::grammar<
                                std::string::iterator,
                                boost::variant<typename Commands::return_type...>(),
                                boost::spirit::ascii::space_type> {
    
      typedef boost::variant<typename Commands::return_type...> return_type;
    
      device_grammar() : device_grammar::base_type{rule_}{
        build_rule<typename Commands::grammar...>();
      }
    
    private:
    
      template <class CommandGrammar> void build_rule() {
        rule_ = CommandGrammar();
      }
    
      template <class FirstGrammar, class SecondGrammar, class... Others>
      void build_rule() {
        build_rule<SecondGrammar, Others...>();
        rule_ = rule_ | FirstGrammar();
      }
    
      boost::spirit::qi::rule<std::string::iterator, return_type(),
                              boost::spirit::ascii::space_type>
          rule_;
    };
    
    typedef device_grammar<Latitude, Longitude> CoordinatesGrammar;
    

    代码编译(参见下面的完整示例);问题是,当它试图解析输入字符串时,会生成分段错误。 有人能帮我解决这个问题吗?

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/variant.hpp>
    #include <iostream>
    #include <string>
    
    template <class... Commands>
    struct device_grammar : boost::spirit::qi::grammar<
                                std::string::iterator,
                                boost::variant<typename Commands::return_type...>(),
                                boost::spirit::ascii::space_type> {
    
      typedef boost::variant<typename Commands::return_type...> return_type;
    
      device_grammar() : device_grammar::base_type{rule_}{
        build_rule<typename Commands::grammar...>();
      }
    
    private:
    
      template <class CommandGrammar> void build_rule() {
        rule_ = CommandGrammar();
      }
    
      template <class FirstGrammar, class SecondGrammar, class... Others>
      void build_rule() {
        build_rule<SecondGrammar, Others...>();
        rule_ = rule_ | FirstGrammar();
      }
    
      boost::spirit::qi::rule<std::string::iterator, return_type(),
                              boost::spirit::ascii::space_type>
          rule_;
    };
    
    using namespace boost::spirit;
    
    template <class Attribute>
    using command_rule =
        qi::rule<std::string::iterator, Attribute, ascii::space_type>;
    
    template <class Attribute>
    using command_grammar =
        qi::grammar<std::string::iterator, Attribute, ascii::space_type>;
    
    struct Latitude {
    
      struct return_type {
        double lat_;
      };
    
      struct grammar : command_grammar<return_type()> {
        grammar() : grammar::base_type{latitude_} {
          latitude_ = "LAT=" >> qi::double_;
        }
    
      private:
        command_rule<return_type()> latitude_;
      };
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Latitude::return_type, (double, lat_))
    
    struct Longitude {
    
      struct return_type {
        double lon_;
      };
    
      struct grammar : command_grammar<return_type()> {
        grammar() : grammar::base_type{longitude_} {
          longitude_ = "LON=" >> qi::double_;
        }
    
      private:
        command_rule<return_type()> longitude_;
      };
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Longitude::return_type, (double, lon_))
    
    typedef device_grammar<Latitude, Longitude> CoordinatesGrammar;
    
    struct print : public boost::static_visitor<> {
    
      void operator()(Latitude::return_type &t) const {
        std::cout << "Latitude = " << t.lat_ << " deg" << std::endl;
        ;
      }
    
      void operator()(Longitude::return_type &t) const {
        std::cout << "Longitude = " << t.lon_ << " deg" << std::endl;
        ;
      }
    };
    
    int main() {
      std::string s;
    
      CoordinatesGrammar g;
      CoordinatesGrammar::return_type v;
      while (1) {
        std::getline(std::cin, s);
        auto it = s.begin();
        if (qi::phrase_parse(it, s.end(), g, ascii::space, v)) {
          print p;
          boost::apply_visitor(p, v);
        }
      }
      return 0;
    }
    

    编辑: 据我所知,问题出在线路上

    rule_ = CommandGrammar();
    ...
    rule_ = rule_ | FirstGrammar();
    

    语法对象似乎不能是临时的,必须作为类的成员存储。我该怎么做?

    编辑: 我还尝试将此类对象存储在 std::tuple ,但它似乎仍然不起作用。

    2 回复  |  直到 7 年前
        1
  •  2
  •   sehe    7 年前

    您正在创建的内容与qi的自动解析器非常相似: http://www.boost.org/doc/libs/1_64_0/libs/spirit/doc/html/spirit/qi/reference/auto.html

    create_parser<> qi::auto_ 立即:

    Live On Coliru

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace Commands {
        namespace qi = boost::spirit::qi;
    
        template <class T> using Rule = qi::rule<std::string::const_iterator, T()>;
    
        template <typename... T>
        auto parse(std::string const& s) {
            boost::variant<T...> v;
    
            auto it = s.begin();
            if (qi::parse(it, s.end(), qi::auto_, v))
                return v;
            throw std::runtime_error(std::string(__FUNCTION__) + " failed");
        }
    }
    
    struct Latitude  { double lat_; };
    BOOST_FUSION_ADAPT_STRUCT(Latitude, lat_)
    
    struct Longitude { double lon_; };
    BOOST_FUSION_ADAPT_STRUCT(Longitude, lon_)
    
    namespace boost { namespace spirit { namespace traits {
        template <> struct create_parser<Latitude> {
            using type = Commands::Rule<Latitude>;
            static type const& call() {
                static type const s_rule = qi::skip(qi::space)["LAT=" >> qi::auto_];
                return s_rule;
            };
        };
    
        template <> struct create_parser<Longitude> {
            using type = Commands::Rule<Longitude>;
            static type const& call() {
                static type const s_rule = qi::skip(qi::space)["LON=" >> qi::auto_];
                return s_rule;
            };
        };
    } } }
    
    struct print {
        using result_type = void;
        void operator()(Latitude const &t)  const { std::cout << "Latitude = " << t.lat_ << " deg" << std::endl; }
        void operator()(Longitude const &t) const { std::cout << "Longitude = " << t.lon_ << " deg" << std::endl; }
    };
    
    #include <sstream>
    
    int main() {
        std::istringstream iss("LAT=4.3\n LON=5.0");
        std::string s;
    
        print printer;
    
        while (std::getline(iss, s)) try {
            auto v = Commands::parse<Latitude, Longitude>(s);
            boost::apply_visitor(printer, v);
        }
        catch (std::exception const& e) {
            std::cout << "'" << s << "': " << e.what() << "\n";
        }
    }
    

    打印

    Latitude = 4.3 deg
    Longitude = 5 deg
    

    更好的东西

    如果你不使用 qi::rule<>

    http://coliru.stacked-crooked.com/a/84f7a8c9a453fc1b

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace Commands {
        template <typename... T>
        auto parse(std::string const& s) {
            boost::variant<T...> v;
    
            auto it = s.begin();
            if (boost::spirit::qi::parse(it, s.end(), boost::spirit::qi::auto_, v))
                return v;
            throw std::runtime_error(std::string(__FUNCTION__) + " failed");
        }
    
        struct Latitude { double lat_; };
        struct Longitude { double lon_; };
    
        static inline std::ostream& operator<<(std::ostream& os, Latitude const &t) { return os << "Latitude = " << t.lat_ << " deg"; }
        static inline std::ostream& operator<<(std::ostream& os, Longitude const &t) { return os << "Longitude = " << t.lon_ << " deg"; }
    }
    
    BOOST_FUSION_ADAPT_STRUCT(Commands::Latitude, lat_)
    BOOST_FUSION_ADAPT_STRUCT(Commands::Longitude, lon_)
    
    namespace boost { namespace spirit { namespace traits {
        #define MAP_PARSER(T, expr) \
        template <> struct create_parser<T> { \
            using type = decltype(qi::attr_cast<T, T>(qi::copy(expr))); \
            static type const& call() { static type const s_rule = qi::attr_cast<T, T>(qi::copy(expr)); return s_rule; }; \
        };
    
        #define AUTO_MAP_PARSER(T, caption) MAP_PARSER(T, qi::skip(qi::space)[qi::lit(caption) >> '=' >> qi::auto_])
    
        AUTO_MAP_PARSER(::Commands::Longitude, "LON")
        AUTO_MAP_PARSER(::Commands::Latitude, "LAT")
    } } }
    
    #include <sstream>
    
    int main() {
        std::istringstream iss("LAT=4.3\n LON=5.0");
        std::string s;
    
        while (std::getline(iss, s)) try {
            using namespace Commands;
            std::cout << "Parsed '" << s << "' into " << parse<Latitude, Longitude>(s) << "\n";
        } catch (std::exception const& e) {
            std::cout << "'" << s << "': " << e.what() << "\n";
        }
    }
    

    打印

    Parsed 'LAT=4.3' into Latitude = 4.3 deg
    Parsed ' LON=5.0' into Longitude = 5 deg
    
        2
  •  2
  •   sehe    7 年前

    除了 Spirit Qi answer 我给出:

    如果您有能力启用c++1z,则可以将Spirit X3与折叠表达式一起使用:

    Live On Coliru

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/home/x3.hpp>
    
    namespace Commands {
        namespace x3 = boost::spirit::x3;
        template <typename... T>
        auto parse(std::string const& s) {
            using V = boost::variant<T...>;
            V v;
    
            auto it = s.begin();
            if (x3::parse(it, s.end(), parser_for(v), v))
                return v;
            throw std::runtime_error(std::string(__FUNCTION__) + " failed");
        }
    
        struct Latitude { double lat_; };
        struct Longitude { double lon_; };
    
        auto label_for(Latitude) { return "LAT"; }
        auto label_for(Longitude) { return "LON"; }
    
        template <typename T, typename P>
        auto as_cmd(P p) { return x3::rule<struct _, T>{} 
                = x3::skip(x3::space)[x3::lit(label_for(T{})) >> '=' >> p]; }
    
        template <typename T>    auto parser_for(T)                      { return as_cmd<T>(x3::double_); }
        template <typename... T> auto parser_for(boost::variant<T...> _) { return (parser_for(T{}) | ...); }
    
        static inline std::ostream& operator<<(std::ostream& os, Latitude const &t) { return os << "Latitude = " << t.lat_ << " deg"; }
        static inline std::ostream& operator<<(std::ostream& os, Longitude const &t) { return os << "Longitude = " << t.lon_ << " deg"; }
    }
    
    BOOST_FUSION_ADAPT_STRUCT(Commands::Latitude, lat_)
    BOOST_FUSION_ADAPT_STRUCT(Commands::Longitude, lon_)
    
    #include <iostream>
    #include <sstream>
    
    int main() {
        std::istringstream iss("LAT=4.3\n LON=5.0");
        std::string s;
    
        while (std::getline(iss, s)) try {
            using namespace Commands;
            std::cout << "Parsed '" << s << "' into " << parse<Latitude, Longitude>(s) << "\n";
        } catch (std::exception const& e) {
            std::cout << "'" << s << "': " << e.what() << "\n";
        }
    }
    

    打印

    Parsed 'LAT=4.3' into Latitude = 4.3 deg
    Parsed ' LON=5.0' into Longitude = 5 deg