Skipper de espaço em branco ao usar o Boost.Spirit Qi e Lex

Vamos considerar o seguinte código:

#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

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

template<typename Lexer>
class expression_lexer
    : public lex::lexer<Lexer>
{
public:
    typedef lex::token_def<> operator_token_type;
    typedef lex::token_def<> value_token_type;
    typedef lex::token_def<> variable_token_type;
    typedef lex::token_def<lex::omit> parenthesis_token_type;
    typedef std::pair<parenthesis_token_type, parenthesis_token_type> parenthesis_token_pair_type;
    typedef lex::token_def<lex::omit> whitespace_token_type;

    expression_lexer()
        : operator_add('+'),
          operator_sub('-'),
          operator_mul("[x*]"),
          operator_div("[:/]"),
          value("\\d+(\\.\\d+)?"),
          variable("%(\\w+)"),
          parenthesis({
            std::make_pair(parenthesis_token_type('('), parenthesis_token_type(')')),
            std::make_pair(parenthesis_token_type('['), parenthesis_token_type(']'))
          }),
          whitespace("[ \\t]+")
    {
        this->self
            = operator_add
            | operator_sub
            | operator_mul
            | operator_div
            | value
            | variable
            ;

        std::for_each(parenthesis.cbegin(), parenthesis.cend(),
            [&](parenthesis_token_pair_type const& token_pair)
            {
                this->self += token_pair.first | token_pair.second;
            }
        );

        this->self("WS") = whitespace;
    }

    operator_token_type operator_add;
    operator_token_type operator_sub;
    operator_token_type operator_mul;
    operator_token_type operator_div;

    value_token_type value;
    variable_token_type variable;

    std::vector<parenthesis_token_pair_type> parenthesis;

    whitespace_token_type whitespace;
};

template<typename Iterator, typename Skipper>
class expression_grammar
    : public qi::grammar<Iterator, Skipper>
{
public:
    template<typename Tokens>
    explicit expression_grammar(Tokens const& tokens)
        : expression_grammar::base_type(start)
    {
        start                     %= expression >> qi::eoi;

        expression                %= sum_operand >> -(sum_operator >> expression);
        sum_operator              %= tokens.operator_add | tokens.operator_sub;
        sum_operand               %= fac_operand >> -(fac_operator >> sum_operand);
        fac_operator              %= tokens.operator_mul | tokens.operator_div;

        if(!tokens.parenthesis.empty())
            fac_operand           %= parenthesised | terminal;
        else
            fac_operand           %= terminal;

        terminal                  %= tokens.value | tokens.variable;

        if(!tokens.parenthesis.empty())
        {
            parenthesised         %= tokens.parenthesis.front().first >> expression >> tokens.parenthesis.front().second;
            std::for_each(tokens.parenthesis.cbegin() + 1, tokens.parenthesis.cend(),
                [&](typename Tokens::parenthesis_token_pair_type const& token_pair)
                {
                    parenthesised %= parenthesised.copy() | (token_pair.first >> expression >> token_pair.second);
                }
            );
        }
    }

private:
    qi::rule<Iterator, Skipper> start;
    qi::rule<Iterator, Skipper> expression;
    qi::rule<Iterator, Skipper> sum_operand;
    qi::rule<Iterator, Skipper> sum_operator;
    qi::rule<Iterator, Skipper> fac_operand;
    qi::rule<Iterator, Skipper> fac_operator;
    qi::rule<Iterator, Skipper> terminal;
    qi::rule<Iterator, Skipper> parenthesised;
};


int main()
{
    typedef lex::lexertl::token<std::string::const_iterator> token_type;
    typedef expression_lexer<lex::lexertl::lexer<token_type>> expression_lexer_type;
    typedef expression_lexer_type::iterator_type expression_lexer_iterator_type;
    typedef qi::in_state_skipper<expression_lexer_type::lexer_def> skipper_type;
    typedef expression_grammar<expression_lexer_iterator_type, skipper_type> expression_grammar_type;

    expression_lexer_type lexer;
    expression_grammar_type grammar(lexer);

    while(std::cin)
    {
        std::string line;
        std::getline(std::cin, line);

        std::string::const_iterator first = line.begin();
        std::string::const_iterator const last = line.end();

        bool const result = lex::tokenize_and_phrase_parse(first, last, lexer, grammar, qi::in_state("WS")[lexer.self]);
        if(!result)
            std::cout << "Parsing failed! Reminder: >" << std::string(first, last) << "<" << std::endl;
        else
        {
            if(first != last)
                std::cout << "Parsing succeeded! Reminder: >" << std::string(first, last) << "<" << std::endl;
            else
                std::cout << "Parsing succeeded!" << std::endl;
        }
    }
}

É um analisador simples para expressões aritméticas com valores e variáveis. É construir usandoexpression_lexer para extrair tokens e, em seguida, comexpression_grammar analisar os tokens.

O uso de lexer para um caso tão pequeno pode parecer um exagero e provavelmente é um deles. Mas esse é o custo do exemplo simplificado. Observe também que o uso do lexer permite definir facilmente tokens com expressão regular, enquanto isso permite defini-los facilmente por código externo (e configuração fornecida pelo usuário em particular). Com o exemplo fornecido, não seria problema ler a definição de tokens de um arquivo de configuração externo e, por exemplo, permitir que o usuário alterasse variáveis de%name para$name.

O código parece estar funcionando bem (verificado no Visual Studio 2013 com Boost 1.61). Exceto que eu notei que, se eu fornecer string como5++5 falha corretamente, mas relata apenas como lembrete5 ao invés de+5 o que significa que o infrator+ foi "irrecuperavelmente" consumido. Aparentemente, um token que foi produzido, mas não corresponde à gramática, não é retornado à entrada original. Mas não é disso que estou perguntando. Apenas uma nota que percebi ao verificar o código.

Agora, o problema está em pular espaços em branco. Eu não gosto muito de como é feito. Embora eu tenha feito dessa maneira, como parece ser o fornecido por muitos exemplos, incluindo respostas a perguntas aqui no StackOverflow.

A pior coisa parece ser essa (em nenhum lugar documentado?)qi::in_state_skipper. Também parece que eu tenho que adicionar owhitespace token assim (com um nome) em vez de como todos os outros como usandolexer.whitespace ao invés de"WS" parece não funcionar.

E, finalmente, ter que "desordenar" a gramática com oSkipper argumento não parece bom. Eu não deveria estar livre disso? Afinal, eu quero fazer a gramática baseada em tokens em vez de entrada direta e quero que o espaço em branco seja excluído do fluxo de tokens - não é mais necessário lá!

Que outras opções tenho para pular espaços em branco? Quais são as vantagens de fazê-lo como é agora?

questionAnswers(1)

yourAnswerToTheQuestion