Whitespace-Skipper bei Verwendung von Boost.Spirit Qi und Lex

Betrachten wir den folgenden Code:

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

It ist ein einfacher Parser für arithmetische Ausdrücke mit Werten und Variablen. Es wird mit @ erstelexpression_lexer zum Extrahieren von Tokens und dann mitexpression_grammar, um die Token zu analysieren.

ie Verwendung von Lexer für solch einen kleinen Fall mag übertrieben erscheinen und ist wahrscheinlich einer. Aber das sind die Kosten eines vereinfachten Beispiels. Beachten Sie auch, dass die Verwendung von Lexer das einfache Definieren von Tokens mit regulären Ausdrücken ermöglicht, während dies das einfache Definieren von Tokens durch externen Code (und insbesondere durch die vom Benutzer bereitgestellte Konfiguration) ermöglicht. Mit dem bereitgestellten Beispiel wäre es überhaupt kein Problem, die Definition von Tokens aus einer externen Konfigurationsdatei zu lesen und dem Benutzer beispielsweise zu erlauben, Variablen von @ aus zu änder%name zu$name.

Der Code scheint einwandfrei zu funktionieren (in Visual Studio 2013 mit Boost 1.61 aktiviert). Außer dass mir aufgefallen ist, dass wenn ich einen String wie @ ge5++5 es schlägt richtig fehl, meldet sich aber nur als Erinnerung5 eher, als+5 was bedeutet, das beleidigende+ wurde "unwiederbringlich" verbraucht. Anscheinend wird ein Token, das erstellt wurde, aber nicht mit der Grammatik übereinstimmt, in keiner Weise zur ursprünglichen Eingabe zurückgeführt. Aber danach frage ich nicht. Nur eine Randnotiz, die mir bei der Überprüfung des Codes aufgefallen ist.

Nun, das Problem ist das Überspringen von Leerzeichen. Ich mag es sehr nicht, wie es gemacht wird. Obwohl ich es so gemacht habe, wie es scheint, wird es von vielen Beispielen geliefert, einschließlich Antworten auf Fragen hier auf StackOverflow.

Das Schlimmste scheint zu sein, dass (nirgends dokumentiert?)qi::in_state_skipper. Auch scheint es, dass ich das @ hinzufügen muwhitespace Token wie das (mit einem Namen), anstatt wie alle anderen @ zu verwendlexer.whitespace Anstatt von"WS" scheint nicht zu funktionieren.

Und schließlich muss die Grammatik mit dem @ "überladen" werdSkipper Argument scheint nicht nett zu sein. Sollte ich nicht frei davon sein? Schließlich möchte ich die Grammatik eher auf Token-Basis als auf direkter Eingabe erstellen, und ich möchte, dass das Leerzeichen vom Token-Stream ausgeschlossen wird - es wird dort nicht mehr benötigt!

Welche anderen Optionen muss ich Leerzeichen überspringen? Was sind die Vorteile, wenn man es so macht, wie es jetzt ist?

Antworten auf die Frage(2)

Ihre Antwort auf die Frage