Пробел шкипера при использовании 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
что означает оскорбление +
был "безвозвратно" потреблен. Очевидно, что токен, который был создан, но не соответствует грамматике, никоим образом не возвращается к исходному вводу. Но я не об этом. Просто примечание, которое я понял при проверке кода.
Теперь проблема с пропуском пробелов. Мне очень не нравится, как это делается. Несмотря на то, что я сделал это так, как кажется, представлен многими примерами, включая ответы на вопросы здесь, в Stackru.
Хуже всего кажется, что (нигде не задокументировано?) qi::in_state_skipper
, Также кажется, что я должен добавить whitespace
токен, как это (с именем), а не как все другие, как использование lexer.whitespace
вместо "WS"
не похоже на работу.
И, наконец, нужно "загромождать" грамматику Skipper
аргумент не кажется хорошим. Разве я не должен быть свободен от этого? В конце концов, я хочу сделать грамматику на основе токенов, а не прямого ввода, и хочу, чтобы пробел был исключен из потока токенов - он там больше не нужен!
Какие еще варианты можно пропустить? Какие преимущества в том, чтобы делать это, как сейчас?
1 ответ
По какой-то странной причине только сейчас я нашел другой вопрос, ошибка грамматики / лексера SQL Boost.Spirit, в котором предусмотрено какое-то другое решение для пропуска пробелов. Лучше!
Ниже приведен пример кода, переработанный в соответствии с предложениями:
#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;
}
}
}
Различия следующие:
whitespace
токен добавлен в лексерself
как и все остальные токены.- Однако с этим связано действие. Действие заставляет лексера игнорировать токен. Что именно то, что мы хотим.
- мой
expression_grammar
больше не занимаетSkipper
аргумент шаблона. И поэтому он также удален из правил. lex::lexertl::actor_lexer
используется вместоlex::lexertl::lexer
так как теперь есть действие, связанное с токеном.- Я зову
tokenize_and_parse
вместоtokenize_and_phrase_parse
так как мне больше не нужно пропускать шкипера. - Также я изменил первое назначение
this->self
в лексере от=
в+=
как это кажется более гибким (устойчивым к изменениям порядка). Но это не влияет на решение здесь.
Я хорошо с этим. Он идеально соответствует моим потребностям (или, точнее, моему вкусу). Однако мне интересно, есть ли другие последствия таких изменений? Какой подход предпочтительнее в некоторых ситуациях? Это я не знаю.