Boost.Spirit.Qi:如何使用Nabialek技巧返回属性

12

在参考了多个教程后(例如 http://boost-spirit.com/home/articles/qi-example/nabialek-trick/),我想使用Nabialek技巧实现动态解析器。解析已经很好的实现了,但是我无法传递属性。像https://dev59.com/4m025IYBdhLWcg3wW0kc#9109972这样的解释表明,属性应该是可能的,但不是参数。

这只是一个将字符串和数字解析到结构体中的小例子。它仅用于展示我的问题;此方法应该在稍后使用真正需要动态解析器的更大系统中使用。

问题:如何使用Nabialek技巧传递属性?

我不是精通spirit的专家,请耐心等待。我使用的是gcc 4.8.1和boost 1.54。

#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;

//------------------------------------------------------------------------------
// Data structure
struct myline {
  myline()
      : _n(0), _s("") {
  }

  myline(int n, std::string s)
      : _n(n), _s(s) {
  }

  void set(int n, std::string s) {
    _n = n;
    _s = s;
  }

  int _n;
  std::string _s;
};

BOOST_FUSION_ADAPT_STRUCT(::myline, (int, _n) (std::string, _s))

//------------------------------------------------------------------------------
// Parser grammar
template<typename It, typename Skipper = qi::space_type>
struct parser: qi::grammar<It, myline(), Skipper> {
  parser()
      : parser::base_type(start) {
    using namespace qi;

    start = line;

    string %= qi::lexeme["'" >> *~qi::char_("'") >> "'"];

    one = (string >> "@" >> qi::int_)[_val = phx::construct<myline>(_2, _1)];
    two = (qi::int_ >> "@" >> string);

    keyword.add("one", &one)("two", &two);

    line = keyword[_a = _1] >> qi::lazy(*_a);

    on_error<fail>(
        start,
        std::cout << phx::val("Error! Expecting ") << _4
        << phx::val(" here: \"") << phx::construct<std::string>(_3, _2)
        << phx::val("\"\n"));

    BOOST_SPIRIT_DEBUG_NODES((start)(line)(one)(two))
  }

private:
  template<typename Attr> using Rule = qi::rule<It, Attr(), Skipper>;

  Rule<myline> start, one, two;
  qi::rule<It, myline, Skipper, qi::locals<Rule<myline>*> > line;

  Rule<std::string> string;

  qi::symbols<char, Rule<myline>*> keyword;
};

//------------------------------------------------------------------------------
int main() {
  for (const std::string input : std::vector<std::string> { "one 'test'@1",
                                                            "two 2@'test'" }) {
    auto f(std::begin(input)), l(std::end(input));
    const static parser<decltype(f)> p;

    myline parsed_script;
    bool ok = qi::phrase_parse(f, l, p, qi::space, parsed_script);

    if (!ok) {
      std::cout << "invalid input\n";
    }

    std::cout << parsed_script._n << ": " << parsed_script._s << std::endl;

    if (f != l) {
      std::cout << "unparsed: '" << std::string(f, l) << "'" << std::endl;
    }
  }
}

解析结果:
<start>
  <try>one 'test'@1</try>
  <line>
    <try>one 'test'@1</try>
    <one>
      <try> 'test'@1</try>
      <success></success>
      <attributes>[[1, [t, e, s, t]]]</attributes>
    </one>
    <success></success>
    <attributes>[]</attributes><locals>(0x43b0e0)</locals>
  </line>
  <success></success>
  <attributes>[[0, []]]</attributes>
</start>
<start>
  <try>two 2@'test'</try>
  <line>
    <try>two 2@'test'</try>
    <two>
      <try> 2@'test'</try>
      <success></success>
      <attributes>[[2, [t, e, s, t]]]</attributes>
    </two>
    <success></success>
    <attributes>[]</attributes><locals>(0x43b110)</locals>
  </line>
  <success></success>
  <attributes>[[0, []]]</attributes>
</start>
1个回答

17

你一直在认真听灵修课的内容 :)

然而,有一些问题需要解决:

  1. the attribute declaration of the line rule was wrong:

    qi::rule<It, myline, Skipper, qi::locals<Rule<myline>*> > line;
    

    needs to be

    qi::rule<It, myline(), Skipper, qi::locals<Rule<myline>*> > line;
    
  2. Automatic attribute propagation is inhibited in the presence of semantic actions. See a recent answer for more information: Boost.spirit: parsing number char and string. So you need to explicitely engage Spirit's auto-rule behaviour using %=:

    line = keyword[_a = _1] >> qi::lazy(*_a);
    

    nees to be

    // NOTE the %=
    line %= omit [ keyword[_a = _1] ] >> qi::lazy(*_a);
    

    Notes:

    • the %= can go on the string rule (no semantic actions imply automatic attribute propagation)
    • we need to explicitely omit[] the result of the keyword match, because we can't really assign the Rule<>* to our myline attribute
这是修复后的版本:
#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;

//------------------------------------------------------------------------------
// Data structure
struct myline {
  myline()
      : _n(0), _s("") {
  }

  myline(int n, std::string s)
      : _n(n), _s(s) {
  }

  void set(int n, std::string s) {
    _n = n;
    _s = s;
  }

  int _n;
  std::string _s;
};

BOOST_FUSION_ADAPT_STRUCT(::myline, (int, _n) (std::string, _s))

//------------------------------------------------------------------------------
// Parser grammar
template<typename It, typename Skipper = qi::space_type>
struct parser: qi::grammar<It, myline(), Skipper> {
  parser()
      : parser::base_type(start) {
    using namespace qi;

    start  = line;

    string = qi::lexeme["'" >> *~qi::char_("'") >> "'"];

    one    = (string >> "@" >> qi::int_) [_val           = phx::construct<myline>(_2, _1)];
    two    = (qi::int_ >> "@" >> string);

    keyword.add("one", &one)("two", &two);

    // NOTE the %=
    line %= omit [ keyword[_a = _1] ] >> qi::lazy(*_a);

    on_error<fail>(
        start,
        std::cout << phx::val("Error! Expecting ") << _4
        << phx::val(" here: \"") << phx::construct<std::string>(_3, _2)
        << phx::val("\"\n"));

    BOOST_SPIRIT_DEBUG_NODES((start)(line)(one)(two))
  }

private:
  template<typename Attr> using Rule = qi::rule<It, Attr(), Skipper>;

  Rule<myline> start, one, two;
  qi::rule<It, myline(), Skipper, qi::locals<Rule<myline>* > > line;

  Rule<std::string> string;

  qi::symbols<char, Rule<myline>* > keyword;
};

//------------------------------------------------------------------------------
int main() {
  for (const std::string input : std::vector<std::string> { "one 'test1'@1",
                                                            "two 2@'test2'" }) {
    auto f(std::begin(input)), l(std::end(input));
    const static parser<decltype(f)> p;

    myline parsed_script;
    bool ok = qi::phrase_parse(f, l, p, qi::space, parsed_script);

    if (!ok) {
      std::cout << "invalid input\n";
    }

    std::cout << parsed_script._n << ": " << parsed_script._s << std::endl;

    if (f != l) {
      std::cout << "unparsed: '" << std::string(f, l) << "'" << std::endl;
    }
  }
}

输出:

<start>
  <try>one 'test1'@1</try>
  <line>
    <try>one 'test1'@1</try>
    <one>
      <try> 'test1'@1</try>
      <success></success>
      <attributes>[[1, [t, e, s, t, 1]]]</attributes>
    </one>
    <success></success>
    <attributes>[[1, [t, e, s, t, 1]]]</attributes><locals>(0x6386c0)</locals>
  </line>
  <success></success>
  <attributes>[[1, [t, e, s, t, 1]]]</attributes>
</start>
1: test1
<start>
  <try>two 2@'test2'</try>
  <line>
    <try>two 2@'test2'</try>
    <two>
      <try> 2@'test2'</try>
      <success></success>
      <attributes>[[2, [t, e, s, t, 2]]]</attributes>
    </two>
    <success></success>
    <attributes>[[2, [t, e, s, t, 2]]]</attributes><locals>(0x6386f0)</locals>
  </line>
  <success></success>
  <attributes>[[2, [t, e, s, t, 2]]]</attributes>
</start>
2: test2

1
请查看此固定示例,它在Coliru上实时展示:http://coliru.stacked-crooked.com/view?id=339ded0e400164a92e13f5cb65e2e54e-414c51b0c3fcfe921b347f2307ac5c70 - sehe
1
非常感谢,运行得非常好。看来我还需要学习几堂课程;-) - Mike M
1
很遗憾,现在使用 Boost.Spirit 的用户太少了,否则会有更多的赞。当你转世时,你将得到它们 :-) - TemplateRex
1
TemplateRex,不要在NicolBolas能听到你的时候提到这个。我非常确定他会要求你证明你的说法:)。此外,这已经得到了非常高的赞同,所以我不会抱怨@TemplateRex。 - sehe
2
@sehe 在某个时候,我打算学习Boost.Spirit。毕竟,在学习了量子力学和模板元编程之后,它有多难呢?;-) - TemplateRex
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接