Почему qi::skip не работает с токенами из лексера?

Я использую 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?


person ZeeByeZon    schedule 26.08.2015    source источник


Ответы (1)


Поведение, которое вы описываете, - это то, что я ожидал от своего опыта.

Когда вы пишете

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. Таким образом, foo, bar и baz все еще могут быть чувствительны к пробелам в приведенном выше примере. Директива 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 , что, по-видимому, вы действительно хотите пропустить. (Если вы пропустите все жетоны всех типов, то вы не сможете ничего сопоставить, так что это не будет иметь смысла, поэтому я не уверен, что вы намеревались сделать token_type шкипером.)

Я предполагаю, что когда qi увидел этот typedef token_type в вашем списке параметров, он либо проигнорировал его, либо интерпретировал как часть возвращаемого значения правила или что-то в этом роде, точно не зная, что он будет делать.

person Chris Beck    schedule 26.08.2015
comment
Это звучит очень странно для меня. У меня есть другой код, в котором я использую только qi (без lex), а шкипер распространяется на подправила. Вот почему из-за qi::skip(...)[m_variable] правило должно быть объявлено с типом шкипера: skipped_t - person ZeeByeZon; 26.08.2015
comment
Обратите внимание, что на упомянутой вами странице шкипер распространяется по всем правилам... - person ZeeByeZon; 26.08.2015
comment
обратите внимание, что на странице, которую вы упомянули, шкипер распространяется по всем правилам... нет, это не так. вот пример hartmut, смотрите его в прямом эфире на coliru. coliru.stacked-crooked.com/a/fd62add9e9ccb930 или вариант с использованием директивы skip только: coliru.stacked-crooked.com/a/5068a53556449c01 - person Chris Beck; 26.08.2015
comment
Итак, я понимаю, что в моем простом примере я должен просто использовать пропуск в правиле m_variable (как в закомментированной части моего кода). Но есть ли в моем реальном приложении (которое намного сложнее) простой способ пропустить комментарии везде по умолчанию и отключить пропуск с помощью no_skip в некоторых определенных местах? - person ZeeByeZon; 27.08.2015
comment
возможно, в идеале было бы попытаться добавить своего шкипера в качестве параметра шаблона ко всем этим правилам, но я на самом деле не знаю, как это сделать, возможно, вам нужно прочитать о точках настройки или о чем-то еще. то, что я, вероятно, сделал бы вместо этого, просто написал бы skip(my_whitespace_rule) [ пропустив строку... ‹rule› ] для каждого определения правила. и, возможно, использовать макрос для части skip(my_whitespace_rule) на случай, если я захочу позже изменить его массово. YMMV - person Chris Beck; 27.08.2015