Ав. Вы делаете потрясающие вещи. К сожалению / к счастью, это слишком сложно.
Итак, давайте сначала исправим, а затем упростим.
Ошибка
Как вы и сказали,
void operator() (boost::iterator_range<It> const& id, boost::iterator_range<It> const& arg1, bool checking, Command& command) const
Не не соответствует тому, что на самом деле вызывается:
void Parser::deferred_fill::operator()(T&& ...) const [with T =
{std::vector<char>&, std::vector<char>&,
boost::iterator_range<boost::spirit::basic_istream_iterator<...> >&,
Command&}]
Причина НЕ в итераторе (как вы можете видеть boost::spirit__istream_iterator
в порядке).
Однако это потому, что вы получаете другие вещи как атрибуты. Оказывается, *blank
предоставляет атрибут как vector<char>
. Так что вы можете "исправить" это, добавив omit[]
те. Вместо этого давайте обернем его правилом без атрибутов, например ignore
, чтобы уменьшить беспорядок.
Теперь вызов выполняется с помощью
void Parser::deferred_fill::operator()(T&& ...) const [with T = {boost::iterator_range<It>&, boost::iterator_range<It>&, bool&, Command&}]
Значит совместим и компилируется. Разбор:
SEND_CONFIRM("this is the id part", "this is arg1", TRUE);
С
Parser parser;
parser.parse("file.txt");
std::cout << std::boolalpha;
for (auto& cmd : parser.commands) {
std::cout << '{' << cmd.id << ", "
<< cmd.arg1 << ", "
<< cmd.arg2 << ", "
<< cmd.checking << "}\n";
}
Отпечатки
{"this is the id part", "this is arg1", , TRUE}
Давайте улучшим это
- Это требует шкипера
- Это вызывает автоматическое c распространение атрибута
- Некоторые другие элементы стиля
Шкиперы
Вместо того, чтобы явно «вызывать» шкипера, давайте используйте встроенную возможность:
rule<It, Attr(), Skipper> x;
определяет правило, которое пропускает входные последовательности, соответствующие синтаксическому анализатору типа Skipper
. Вам действительно нужно передать шкипера этого типа.
- используя
qi::phrase_parse
вместо qi::parse
- с помощью директивы
qi::skip()
Я всегда выступаю за второй подход, потому что он делает интерфейс более дружелюбным и менее подверженным ошибкам.
Итак, объявление типа капитана:
qi::rule<It, Command(), qi::blank_type> send;
Мы можем сократить правило до:
send = (lit("SEND_CONFIRM") >> '('
>> raw[*~char_(',')] >> ','
>> raw[*~char_(',')] >> ','
>> value >> ')' >> ';')
[fill(_1, _2, _3, _val)];
И затем пропустите шкипера из правила start
:
start = skip(blank) [
(send[push_back(_val, _1)] | ignore) % eol
];
Вот и все. По-прежнему компилируется и сопоставляется.
Live On Coliru
Пропуск с лексемами
Все тот же topi c , lexemes
фактически запрещает шкиперу¹, поэтому вам не нужно raw[]
. Это также изменяет открытые атрибуты на vector<char>
:
void operator() (std::vector<char> const& id, std::vector<char> const& arg1, bool checking, Command& command) const
Live On Coliru
Automati c Attribute Propagation
Ци имеет семанти c действия, но его реальная сила в том, что они необязательны: Дух усиления: «Семанти c действия - зло»?
-
push_back(_val, _1)
на самом деле является семантикой автоматического c распространения атрибута anwyays для *p
, +p
и p % delim
² в любом случае, так что просто удалите его:
start = skip(blank) [
(send | ignore) % eol
];
(обратите внимание, что send|ignore
фактически синтезирует optional<Command>
, что подходит для автоматического c распространения)
std::vector
совместим по атрибутам с std::string
, например. Итак, если мы можем добавить заполнитель для arg2
, мы можем сопоставить макет структуры Command
:
send = lit("SEND_CONFIRM") >> '('
>> attr(SEND) // fill type
>> lexeme[*~char_(',')] >> ','
>> lexeme[*~char_(',')] >> ','
>> attr(std::string()) // fill for arg2
>> value >> ')' >> ';'
;
Теперь, чтобы иметь возможность удалить fill
и его реализацию, мы должны адаптировать Command
как последовательность слияния:
BOOST_FUSION_ADAPT_STRUCT(Command, type, id, arg1, arg2, checking)
Элементы стиля 1
Использование пространства имен для ваших типов команд упрощает ADL использование перегрузок operator<<
для команд , так что мы можем просто std::cout << cmd;
На этом этапе все работает в части кода: Live On Coliru
Элементы стиля 2
Если можете, сделайте свой синтаксический анализатор без состояния. Это означает, что он может быть константным, поэтому вы можете:
- повторно использовать его без дорогостоящей конструкции
- оптимизатор имеет больше возможностей для работы
- он более тестируемый (состояния с состоянием труднее доказать идемпотентность)
Итак, вместо того, чтобы иметь commands
член, просто верните их. Пока мы это делаем, мы можем сделать parse
stati c function
Вместо жесткого кодирования типа итератора его можно гибко использовать в качестве аргумента шаблона. Таким образом, вы не застрянете с накладными расходами multi_pass_adaptor
и istream_iterator
, если в какой-то момент у вас есть команда в буфере char[]
, string
или string_view
.
Кроме того, создание вашего парсера из qi::grammar
с подходящей точкой входа означает, что вы можете использовать его как выражение парсера (на самом деле нетерминальный , как и rule<>
) как любой другой синтаксический анализатор.
Рассмотрите возможность включения отладки правил (см. пример)
Полный код
Live On Coliru
#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <fstream>
namespace qi = boost::spirit::qi;
namespace Commands {
enum TYPE { SEND, CHECK, COMMENT };
enum BOOL { FALSE, TRUE };
struct Command {
TYPE type;
std::string id;
std::string arg1;
std::string arg2;
BOOL checking;
};
typedef std::vector<Command> Commands;
// for (debug) output
static inline std::ostream& operator<<(std::ostream& os, TYPE t) {
switch (t) {
case SEND: return os << "SEND";
case CHECK: return os << "CHECK";
case COMMENT: return os << "COMMENT";
}
return os << "(unknown)";
}
static inline std::ostream& operator<<(std::ostream& os, BOOL b) {
return os << (b?"TRUE":"FALSE");
}
using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(Commands::Command, type, id, arg1, arg2, checking)
namespace Commands {
template <typename It>
class Parser : public qi::grammar<It, Commands()> {
public:
Commands commands;
Parser() : Parser::base_type(start) {
using namespace qi;
value.add("TRUE", TRUE)
("FALSE", FALSE);
send = lit("SEND_CONFIRM") >> '('
>> attr(SEND) // fill type
>> lexeme[*~char_(',')] >> ','
>> lexeme[*~char_(',')] >> ','
>> attr(std::string()) // fill for arg2
>> value >> ')' >> ';'
;
ignore = +~char_("\r\n");
start = skip(blank) [
(send | ignore) % eol
];
BOOST_SPIRIT_DEBUG_NODES((start)(send)(ignore))
}
private:
qi::symbols<char, BOOL> value;
qi::rule<It> ignore;
qi::rule<It, Command(), qi::blank_type> send;
qi::rule<It, Commands()> start;
};
static Commands parse(std::istream& in) {
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
It first(in >> std::noskipws), //No white space skipping
last;
Commands commands;
if (!qi::parse(first, last, parser, commands)) {
throw std::runtime_error("command parse error");
}
return commands; // c++11 move semantics
}
}
int main() {
try {
for (auto& cmd : Commands::parse(std::cin))
std::cout << cmd << "\n";
} catch(std::exception const& e) {
std::cout << e.what() << "\n";
}
}
Печать
(SEND "this is the id part" "this is arg1" TRUE)
Или действительно с определенным BOOST_SPIRIT_DEBUG:
<start>
<try>SEND_CONFIRM("this i</try>
<send>
<try>SEND_CONFIRM("this i</try>
<success>\n</success>
<attributes>[[SEND, [", t, h, i, s, , i, s, , t, h, e, , i, d, , p, a, r, t, "], [", t, h, i, s, , i, s, , a, r, g, 1, "], [], TRUE]]</attributes>
</send>
<send>
<try></try>
<fail/>
</send>
<ignore>
<try></try>
<fail/>
</ignore>
<success>\n</success>
<attributes>[[[SEND, [", t, h, i, s, , i, s, , t, h, e, , i, d, , p, a, r, t, "], [", t, h, i, s, , i, s, , a, r, g, 1, "], [], TRUE]]]</attributes>
</start>
¹ пока предварительно пропускание по мере необходимости; см. Проблемы со шкипером Boost Spirit
² (и еще кое-что, но не будем отвлекаться)