为什么qi :: skip使用词法分析器中的标记失败?

时间:2015-08-26 12:17:39

标签: c++ boost boost-spirit boost-spirit-qi boost-spirit-lex

我使用boost :: spirit lex和qi来解析一些源代码。

我已经使用词法分析器从输入字符串中跳过了空格。我想要做的是根据解析器中的上下文切换跳过注释。

这是一个基本的演示。有关我的问题,请参阅Grammar :: Grammar()中的注释:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/phoenix.hpp>

#include <iostream>

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

typedef lex::lexertl::token<char const*, boost::mpl::vector<std::string>, boost::mpl::false_ > token_type;
typedef lex::lexertl::actor_lexer<token_type> lexer_type;

struct TokenId
{
   enum type
   {
      INVALID_TOKEN_ID = lex::min_token_id,
      COMMENT
   };
};

struct Lexer : lex::lexer<lexer_type>
{
public:
   lex::token_def<std::string> comment;
   lex::token_def<std::string> identifier;
   lex::token_def<std::string> lineFeed;
   lex::token_def<std::string> space;

   Lexer()
   {
      comment = "\\/\\*.*?\\*\\/|\\/\\/[^\\r\\n]*";
      identifier = "[A-Za-z_][A-Za-z0-9_]*";
      space = "[\\x20\\t\\f\\v]+";
      lineFeed = "(\\r\\n)|\\r|\\n";

      this->self = space[lex::_pass = lex::pass_flags::pass_ignore];
      this->self += lineFeed[lex::_pass = lex::pass_flags::pass_ignore];
      this->self.add
         (comment, TokenId::COMMENT)
         (identifier)
         (';')
         ;
   }
};

typedef Lexer::iterator_type Iterator;

void traceComment(const std::string& content)
{
   std::cout << "  comment: " << content << std::endl;
}

class Grammar : public qi::grammar<Iterator>
{
   typedef token_type skipped_t;

   qi::rule<Iterator, qi::unused_type, qi::unused_type> m_start;
   qi::rule<Iterator, qi::unused_type, qi::unused_type, skipped_t> m_variable;
   qi::rule<Iterator, std::string(), qi::unused_type> m_comment;

public:
   Lexer lx;

public:
   Grammar() :
      Grammar::base_type(m_start)
   {
// This does not work (comments are not skipped in m_variable)
      m_start = *(
            m_comment[phx::bind(&traceComment, qi::_1)]
         |  qi::skip(qi::token(TokenId::COMMENT))[m_variable]
         );

      m_variable = lx.identifier >> lx.identifier >> ';';
      m_comment = qi::token(TokenId::COMMENT);
/** But this works:
      m_start = *(
         m_comment[phx::bind(&traceComment, qi::_1)]
         | m_variable
         );

      m_variable = qi::skip(qi::token(TokenId::COMMENT))[lx.identifier >> lx.identifier >> ';'];
      m_comment = qi::token(TokenId::COMMENT);
*/
   }
};

void test(const char* code)
{
   std::cout << code << std::endl;

   Grammar parser;
   const char* begin = code;
   const char* end = code + strlen(code);
   tokenize_and_parse(begin, end, parser.lx, parser);

   if (begin == end)
      std::cout << "-- OK --" << std::endl;
   else
      std::cout << "-- FAILED --" << std::endl;
   std::cout << std::endl;
}

int main(int argc, char* argv[])
{
   test("/* kept */ int foo;");
   test("int /* ignored */ foo;");
   test("int foo /* ignored */;");
   test("int foo; // kept");
}

输出结果为:

/* kept */ int foo;
  comment: /* kept */
-- OK --

int /* ignored */ foo;
-- FAILED --

int foo /* ignored */;
-- FAILED --

int foo; // kept
  comment: // kept
-- OK --

skipped_t有什么问题吗?

1 个答案:

答案 0 :(得分:2)

您所描述的行为是我对我的经历所期望的。

写作时

my_rule = qi::skip(ws) [ foo >> lit(',') >> bar >> lit('=') >> baz ];

这与编写

基本相同
my_rule = *ws >> foo >> *ws >> lit(',') >> *ws >> bar >> *ws >> lit('=') >> *ws >> baz;

(假设ws是没有属性的规则。如果语法中有属性,则忽略该属性,就像使用qi::omit一样。)

值得注意的是,船长没有在foo规则内传播。因此foobarbaz在上面仍然可以是空格敏感的。 skip指令正在做的是导致语法不关心此规则中的前导空格,或者此规则中',''='周围的空格。

此处有更多信息:http://boost-spirit.com/home/2010/02/24/parsing-skippers-and-skipping-parsers/

编辑:

另外,我不认为skipped_t正在做你认为的那样。

当您使用自定义浏览器时,大多数情况下您直接指定解析器的实际实例作为该规则的跳过解析器。当您使用类型而不是对象时,例如qi::skip(qi::blank_type),这是一种速记,标记类型qi::blank_type已通过先前的模板声明链接到类型qi::blank,而qi知道它何时看到qi::blank_type它应该实例化qi::blank解析器对象的某些地方。

我没有看到任何证据表明您实际设置了该机制,您只需将skipped_t token_type键入qi::skipped_t即可m_comment。如果您希望以这种方式工作(如果它甚至可能,我不知道),您应该做什么,阅读qi自定义点,而是将token_type声明为空结构,这是通过一些模板样板连接到规则qi,这可能是你实际想要跳过的。 (如果你跳过所有类型的所有代币,那么你就不可能匹配任何东西,这样就没有意义,所以我不确定你的意图是什么使token_type船长。)

我的猜测是当asynctask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);在参数列表中看到typedef {{1}}时,它要么忽略它,要么将其解释为规则的返回值的一部分或类似的东西,不确定确切地说它会做什么。