spirit::qi 与 fusion 的关联容器 parsing

我们先从 fusion 奇特的用法讲起。我们可以将一个 struct adapt 成为 fusion 里面的 sequence,前面几个 parsing 的例子已经看到这种用法了,这样 spirit 就能按照这个顺序一个一个的将东西塞进这个 sequence 里面去。如果不使用默认规则,我们可以利用 phoenix 提供的 at_c 打乱顺序。为了实现类似 python 支持的字典,我们需要构造一个 key table,这个可以使用 qi::symbols 模板帮助我们生成,然后对于不同的 value类型,我们需要使用 boost.variant 进行封装,这样解析出来的 key 记录在一个 local variable 里面,而在 value 的部分根据这个 local variable 的值就能建立 mapping 了。需要注意的是将 variant 对一个正常类型的赋值是不支持的,我们必须使用 boost::get 模板将具体的类型传递进去,这意味着我们需要在 semantic action 中调用这个模板,为此我们需要首先弄一个 lazy 版本的 get。

namespace demo {
  template <typename Result, typename Inner>
  struct get_impl {
    template <typename T>
    struct result {
      typedef Result type;
    };

    template <BOOST_VARIANT_ENUM_PARAMS(typename T)>
    Result operator()(boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> const& v) const {
      return boost::get<Inner>(v);
    }
  };

  phoenix::function<get_impl<int, int> > const get_int = get_impl<int, int>();
  phoenix::function<get_impl<const std::string&, std::string> > const
  get_str = get_impl<const std::string&, std::string>();
}

这里的 get_int 和 get_str 均为 lazy function。下面是一个简单的例子:

namespace demo {
  struct cup {
    int         height ;
    std::string name ;
  } ;
}

BOOST_FUSION_ADAPT_ASSOC_STRUCT(
  demo::cup,
  (int, height, key::height)
  (std::string, name, key::name)
)

namespace demo {
  namespace spirit = boost::spirit ;
  namespace qi = boost::spirit::qi ;
  namespace ascii = boost::spirit::ascii ;
  namespace phoenix = boost::phoenix ;

  struct keys_ : qi::symbols<char, int> {
    keys_ () {
      add
        ("height", 0)
        ("name",   1)
        ;
    }
  } ;
  template <class Iter>
  struct cup_grammar
    : qi::grammar<Iter, cup(), qi::locals<int>, ascii::space_type> {

    cup_grammar () : cup_grammar::base_type (start) {
      using qi::lit ;
      using qi::lexeme ;
      using qi::int_ ;
      using qi::_val ;
      using qi::_1 ;
      using qi::omit ;
      using ascii::char_ ;
      using namespace qi::labels ;
      using phoenix::at_c ;
      using phoenix::if_ ;

      quoted_string %=
           lexeme [lit('"') >> +(char_ - '"') >> lit('"')]
        ;

      value_type   %= (int_ | quoted_string) ;

      start_seq %=
           lit ("cup")
        >> lit ('{')
        >> int_
        >> ','
        >> quoted_string
        >> lit ('}')
        ;

      start_map =
           lit ("cup")
        >> lit ('{')
        >> (
                omit[keys  [_a = _1]]
             >> '='
             >> value_type [if_(_a == 0)[
                              at_c<0>(_val) = get_int(_1)
                            ].else_[
                              at_c<1>(_val) = get_str(_1)
                            ]
                           ]
            ) % ','
        >> lit ('}')
        ;

      start %=
          start_seq
        | start_map ;
    }

    qi::rule<Iter, cup (), qi::locals<int>, ascii::space_type> start ;
    qi::rule<Iter, cup (), ascii::space_type> start_seq ;
    qi::rule<Iter, cup (), qi::locals<int>, ascii::space_type> start_map ;
    qi::rule<Iter, std::string (), ascii::space_type> quoted_string ;
    qi::rule<Iter, boost::variant<int, std::string> (), ascii::space_type> value_type ;
    keys_ keys ;
  } ;
}

另外 fusion 可以将一个 struct 当成 associative container,比如下面的代码,

#include <iostream>
#include <boost/fusion/include/map.hpp>
#include <boost/fusion/include/map_fwd.hpp>
#include <boost/fusion/include/at_key.hpp>
#include <boost/fusion/include/adapt_assoc_struct.hpp>

namespace key {
  struct height ;
  struct name ;
}

namespace demo {
  struct cup {
    int         height ;
    std::string name ;
  } ;
}

BOOST_FUSION_ADAPT_ASSOC_STRUCT(
  demo::cup,
  (int, height, key::height)
  (std::string, name, key::name)
)

{
  using namespace key ;
  namespace fusion = boost::fusion ;

  demo::cup D = {15, "big"} ;

  std::cout << "name is "<< fusion::at_key<name> (D) << std::endl
            << "height is " << fusion::at_key<height> (D) << std::endl ;
}

那么一种想法就是对原先的 parsing 语法,是否能够简化调用呢?答案似乎是否定的,原因有这么几个看来我们似乎能将字符串转换成 type,但是似乎没法用类型作为返回值供 value 部分进行映射(从某种角度上来说我们这里就是需要 dynamic polymorphism 进行判定)。一种可能的想法是利用 variant 本身的 dynamic 特性,但是事实上 variant 是“半静态的”(天哪,怎么做到的?),这个可以继续研究下,兴许还能简化上面的想法,比如 key 也放在 variant 里面呢?

——————
And you shall circumcise the flesh of your foreskin; and it shall be a token of the covenant between me and you.

Advertisements
spirit::qi 与 fusion 的关联容器 parsing

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s