【问题标题】:boost::spirit::qi::grammar and variadic templatesboost::spirit::qi::语法和可变参数模板
【发布时间】:2017-08-03 13:37:25
【问题描述】:

我在使用可变参数模板定义语法时遇到了问题。

我首先定义了一些包含在某些结构(例如纬度、经度)中的简单语法,如下所示:

#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_))

然后,我想将它们组合成一个完整的语法,能够解析属于任何这些简单语法的字符串。为此,我定义了一个可变参数模板结构,它试图将子语法列表扩展为“语法1 | 语法2 | ...”这样的表达式

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;

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

非常感谢。

示例代码(g++-4.9 或 clang++-3.9):

#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 中,但它似乎仍然无法正常工作。

【问题讨论】:

标签: c++ parsing variadic-templates boost-spirit-qi


【解决方案1】:

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

如果您将 create_parser&lt;&gt; 专门用于您的数据类型,您可以直接使用 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&lt;&gt;,您也不需要对迭代器进行硬编码。让我们进入完全有趣的模式并摆脱访客:

[Live On Coliru](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

【讨论】:

  • 非常感谢,您的解决方案非常有趣。我真正想做的是只用一个模板参数调用parse 函数,因此需要一个包含各种简单语法的“容器”。
  • 嗯?你去:minimal change。即使出于某些 /other/ 原因,您确实需要 qi::grammar 实例而不是 qi::auto_,您也可以在 ~3 行代码中添加它:paste.ubuntu.com/25253281 (x3 version)
【解决方案2】:

除了我给的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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多