Пробел шкипера при использовании Boost.Spirit Qi и Lex

Давайте рассмотрим следующий код:

#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;
        }
    }
}

Это простой анализатор арифметических выражений со значениями и переменными. Это сборка с использованиемexpression_lexer для извлечения токенов, а затем сexpression_grammar разобрать токены.

Использование лексера в таком маленьком случае может показаться излишним и, вероятно, одним из них. Но это стоимость упрощенного примера. Также обратите внимание, что использование лексера позволяет легко определять токены с регулярным выражением, в то время как это позволяет легко определять их с помощью внешнего кода (и в частности, предоставленной пользователем конфигурации). С предоставленным примером не составит труда прочитать определение токенов из внешнего файла конфигурации и, например, позволить пользователю изменять переменные из%name в$name.

Код работает нормально (проверено в Visual Studio 2013 с Boost 1.61). За исключением того, что я заметил, что если я предоставлю строку, как5++5 это правильно терпит неудачу, но сообщает как напоминание просто5 скорее, чем+5 что означает оскорбление+ был "безвозвратно" потреблен. Очевидно, что токен, который был создан, но не соответствует грамматике, никоим образом не возвращается к исходному вводу. Но я не об этом. Просто примечание, которое я понял при проверке кода.

Теперь проблема с пропуском пробелов. Мне очень не нравится, как это делается. Несмотря на то, что я сделал это так, как кажется, представлен многими примерами, включая ответы на вопросы здесь, в StackOverflow.

Хуже всего кажется, что (нигде не задокументировано?)qi::in_state_skipper, Также кажется, что я должен добавитьwhitespace токен, как это (с именем), а не как все другие, использующиеlexer.whitespace вместо"WS" не похоже на работу.

И, наконец, необходимость "загромождать" грамматикуSkipper аргумент не кажется хорошим. Разве я не должен быть свободен от этого? В конце концов, я хочу сделать грамматику на основе токенов, а не прямого ввода, и хочу, чтобы пробел был исключен из потока токенов - он там больше не нужен!

Какие еще варианты можно пропустить? Какие преимущества в том, чтобы делать это, как сейчас?

Ответы на вопрос(1)

Ваш ответ на вопрос