Patrón de espacios en blanco cuando se usa Boost.Spirit Qi y Lex

Consideremos el siguiente 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;
        }
    }
}

Es un analizador simple para expresiones aritméticas con valores y variables. Es construir usandoexpression_lexer para extraer tokens, y luego conexpression_grammar analizar los tokens.

El uso de lexer para un caso tan pequeño puede parecer una exageración y probablemente lo sea. Pero ese es el costo del ejemplo simplificado. También tenga en cuenta que el uso de lexer permite definir fácilmente tokens con expresión regular, mientras que permite definirlos fácilmente mediante código externo (y la configuración proporcionada por el usuario en particular). Con el ejemplo proporcionado, no sería un problema leer la definición de tokens de un archivo de configuración externo y, por ejemplo, permitir al usuario cambiar variables de%name a$name.

El código parece estar funcionando bien (verificado en Visual Studio 2013 con Boost 1.61). Excepto que he notado que si proporciono una cadena como5++5 falla correctamente pero informa como recordatorio solo5 más bien que+5 lo que significa el infractor+ fue "irrecuperable" consumido. Aparentemente, un token que se produjo pero no coincidía con la gramática no se devuelve de ninguna manera a la entrada original. Pero eso no es lo que estoy preguntando. Solo una nota al margen me di cuenta al verificar el código.

Ahora el problema es con la omisión de espacios en blanco. No me gusta mucho cómo se hace. Si bien lo he hecho de esta manera, ya que parece ser el que proporcionan muchos ejemplos, incluidas las respuestas a las preguntas aquí en StackOverflow.

Lo peor parece ser eso (¿en ningún lugar documentado?)qi::in_state_skipper. También parece que tengo que agregar elwhitespace token como ese (con un nombre) en lugar de como todos los demáslexer.whitespace en lugar de"WS" no parece funcionar

Y finalmente tener que "abarrotar" la gramática con elSkipper El argumento no parece agradable. ¿No debería estar libre de eso? Después de todo, quiero hacer la gramática basada en tokens en lugar de entrada directa y quiero que el espacio en blanco se excluya del flujo de tokens, ¡ya no es necesario!

¿Qué otras opciones tengo para omitir espacios en blanco? ¿Cuáles son las ventajas de hacerlo como es ahora?

Respuestas a la pregunta(1)

Su respuesta a la pregunta