Лексер Boost Spirit констатирует перекрестное опыление

Я пытаюсь использовать состояния лексера для контекстно-зависимого синтаксического анализа, но кажется, что разные состояния лексера перекрестно опыляются. Вот очень простой пример

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_container.hpp>

#include <iostream>
#include <string>

using namespace boost::spirit;

template <typename Lexer>
struct strip_comments_tokens : lex::lexer<Lexer>
{
    strip_comments_tokens() 
      : strip_comments_tokens::base_type(lex::match_flags::match_default)
    {
        ccomment = "\\/\\*";
        endcomment = ".*\\*\\/";
        hello = "hello";

        this->self.add
            (ccomment)
            (hello);

        this->self("COMMENT").add
            (endcomment);
    }

    lex::token_def<> ccomment, endcomment;
    lex::token_def<std::string> hello;
};

template <typename Iterator>
struct strip_comments_grammar : qi::grammar<Iterator>
{
    template <typename TokenDef>
    strip_comments_grammar(TokenDef const& tok)
      : strip_comments_grammar::base_type(start)
    {
        start =  *(   tok.ccomment 
                      >>  qi::in_state("COMMENT") 
                      [
                          tok.endcomment 
                      ]
              |   tok.hello [ std::cout << _1 ]
        );
    }

    qi::rule<Iterator> start;
};


int main(int argc, char* argv[])
{
    typedef std::string::iterator base_iterator_type;

    typedef 
        lex::lexertl::lexer<lex::lexertl::token<base_iterator_type> > 
    lexer_type;

    typedef strip_comments_tokens<lexer_type>::iterator_type iterator_type;

    strip_comments_tokens<lexer_type> strip_comments;           // Our lexer
    strip_comments_grammar<iterator_type> g (strip_comments);   // Our parser 

    std::string str("hello/*hello*/hello");
    base_iterator_type first = str.begin();

    bool r = lex::tokenize_and_parse(first, str.end(), strip_comments, g);

    return 0;
}

я бы ожидал ввода

"hello/*hello*/hello"

для обозначения как привет, комментарий, конец комментария, привет. Но происходит то, что входные данные размечаются как hello ccomment hello, поэтому грамматика перестает работать. Если вы измените ввод на

"hello/*anything else*/hello" 

все работает как положено.

Есть идеи?


person Anton Autushka    schedule 30.12.2014    source источник


Ответы (1)


Вы никогда не изменяете состояние лексера. Так что он всегда находится в состоянии "INITIAL".

Настройка состояния лексера должна выполняться на этапе лексера (по моему опыту и после долгих экспериментов нет надежного способа получить обратную связь от этапа синтаксического анализатора).

Итак, вам нужно обновиться до actor_lexer и прикрепить семантические действия к token_defs, добавленным в таблицы лексера:

typedef 
    lex::lexertl::actor_lexer<lex::lexertl::token<base_iterator_type> > 
lexer_type;

А также

this->self += 
     ccomment [ lex::_state = "COMMENT" ]
   | hello;

this->self("COMMENT") += 
    endcomment [ lex::_state = "INITIAL" ];

Тем не менее, я полагаю, что намного проще вообще не использовать токены. Если вы действительно хотите знать, как использовать состояния Lexer для пропуска, см.:

Однако я бы предложил подход Simplify And Profit с использованием lex::_pass = lex::pass_flags::pass_ignore:

Вот мое мнение:

Прямой эфир на Coliru

#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp> // for the parser expression *strip_comments.hello

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

template <typename Lexer>
struct strip_comments_tokens : lex::lexer<Lexer> {
    strip_comments_tokens() 
      : strip_comments_tokens::base_type(lex::match_flags::match_default)
    {
        ccomment   = "\\/\\*.*\\*\\/";
        hello      = "hello"; // why not "."?

        this->self += 
             ccomment [ lex::_pass = lex::pass_flags::pass_ignore ]
  // IDEA: | lex::token_def<char>(".") // to just accept anything
           | hello
           ;
    }

    lex::token_def<lex::omit>   ccomment;
    lex::token_def<std::string> hello;
};

int main() {
    typedef std::string::const_iterator base_iterator_type;
    typedef lex::lexertl::actor_lexer<
                lex::lexertl::token<base_iterator_type/*, boost::mpl::vector<char, std::string>, boost::mpl::false_*/>
            > lexer_type;

    strip_comments_tokens<lexer_type> strip_comments;         // Our lexer

    std::string const str("hello/*hello*/hello");
    std::string stripped;

    base_iterator_type first = str.begin();
    bool r = lex::tokenize_and_parse(first, str.end(), strip_comments, *strip_comments.hello, stripped);

    if (r)
        std::cout << "\nStripped: '" << stripped << "'\n";
    else
        std::cout << "Failed: '" << std::string(first, str.end()) << "'\n";
}
person sehe    schedule 30.12.2014
comment
Мое намерение состояло в том, чтобы изменить состояние лексера извне, потому что мой синтаксический анализ зависит от контекста, и лексер не всегда знает, как интерпретировать входной поток. Я считаю, что именно по этой причине существует qi::in_state. В настоящее время я не вижу другого пути, кроме как полностью избавиться от лексера и поместить весь лексический материал в грамматику, но это слишком громоздкое решение. - person Anton Autushka; 30.12.2014
comment
@AntonAutushka Ммм. Я только что немного обновил свой ответ соответствующими ссылками (см., в частности, комментарии к первому связанному ответу). Что касается громоздкости, по моему опыту, использование лексера с Boost Spirit делает все более громоздким. Мое руководство было бы таким: будьте очень уверены, что вам /нужно/ это и почему. - person sehe; 30.12.2014
comment
Ваш код прекрасно исправляет ошибку в моем маленьком уродливом примере. Но это не то, что мне нужно. Мне нужно, чтобы in_state работал :) Чтобы быть более реальным, рассмотрите этих двух парней x = /b/g и x = a/b/g. Первое — это регулярное выражение JavaScrsipt, второе — обычное арифметическое выражение. И вы просто не можете отличить одно от другого на уровне лексера. Отсюда моя ситуация. - person Anton Autushka; 30.12.2014
comment
Это просто известный крайний случай сканера в этой грамматике (и подобных языках). Я бы сделал так, чтобы лексер не заботился об этом. Вы можете получить массу преимуществ в производительности только от токенизации (на уровне грамматики решите, что это регулярное выражение, если выражение начинается с /). Если вы еще не поняли, что производительность является проблемой, я бы определенно подумал о том, чтобы не лексировать. - person sehe; 30.12.2014
comment
Если вы хотите разобрать полный язык ECMAScript, я бы сказал 1. не сворачивайте свой собственный 2. не симулируйте гибкость, используя Spirit. Просто используйте ANTLR, flex, CoCo/C++,..., желательно с существующим определением грамматики. - person sehe; 30.12.2014