2016-09-13 2 views
2

Рассмотрим следующий код:Пробелы шкипер при использовании Boost.Spirit Ци и 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 для анализа токенов.

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

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

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

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

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

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

ответ

2

По какой-то странной причине только сейчас я нашел другой вопрос: Boost.Spirit SQL grammar/lexer failure, где some other solution предоставляется пропущенный пробел. Лучше!

Итак ниже приведен пример кода переработан по предложениям там:

#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 
      | whitespace [lex::_pass = lex::pass_flags::pass_ignore] 
      ; 

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

    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> 
class expression_grammar 
    : public qi::grammar<Iterator> 
{ 
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> start; 
    qi::rule<Iterator> expression; 
    qi::rule<Iterator> sum_operand; 
    qi::rule<Iterator> sum_operator; 
    qi::rule<Iterator> fac_operand; 
    qi::rule<Iterator> fac_operator; 
    qi::rule<Iterator> terminal; 
    qi::rule<Iterator> parenthesised; 
}; 


int main() 
{ 
    typedef lex::lexertl::token<std::string::const_iterator> token_type; 
    typedef expression_lexer<lex::lexertl::actor_lexer<token_type>> expression_lexer_type; 
    typedef expression_lexer_type::iterator_type expression_lexer_iterator_type; 
    typedef expression_grammar<expression_lexer_iterator_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_parse(first, last, lexer, grammar); 
     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; 
     } 
    } 
} 

Отличия следующие:

  1. whitespace маркер добавляется в лексер-х self как и все другие лексемы.
  2. Однако с этим связано действие. Действие заставляет lexer игнорировать токен. Это именно то, что мы хотим.
  3. Мой expression_grammar больше не принимает Skipper шаблон аргумент. И поэтому он также исключается из правил.
  4. lex::lexertl::actor_lexer используется вместо lex::lexertl::lexer, так как теперь существует действие, связанное с токеном.
  5. Я звоню tokenize_and_parse вместо tokenize_and_phrase_parse, поскольку мне больше не нужно пропускать шкипера.
  6. Также я изменил первое назначение на this->self в lexer от = до +=, поскольку он кажется более гибким (устойчивым к изменениям заказа). Но это не влияет на решение здесь.

У меня все хорошо. Он идеально подходит для моих потребностей (или, лучше сказать, моего вкуса). Однако я задаюсь вопросом, есть ли какие-либо другие последствия таких изменений? Предполагается ли какой-либо подход в некоторых ситуациях? Этого я не знаю.

+1

Тот же ответ приведен в http://stackoverflow.com/questions/13361519/troubles-with-boostspiritlex-whitespace в разделе «Упрощение и прибыль». Как я пропустил эти ?! –

+0

Спасибо, что я сам отвечаю на кредит. У меня не было времени (и, честно говоря, я не очень люблю Лекса ...) – sehe

+0

@sehe Если только Qi поддерживает соответствие с регулярным выражением ... Или это так? –