Застрял при портировании legacy boost :: spirit code - PullRequest
1 голос
/ 17 июня 2020

Я портирую устаревший код из VS2010 и boost1.53 в VS2017 и boost1.71.

Я застрял на последние два часа при попытке его компиляции.

Код:

#include <string>
#include <vector>
#include <fstream>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
using qi::_1; using qi::_2; using qi::_3; using qi::_4;

enum TYPE { SEND, CHECK, COMMENT };

struct Command
{
    TYPE type;
    std::string id;
    std::string arg1;
    std::string arg2;
    bool checking;
};

class Parser
{
    typedef boost::spirit::istream_iterator It;
    typedef std::vector<Command> Commands;

    struct deferred_fill
    {
        template <typename R, typename S, typename T, typename U> struct result { typedef void type; };//Not really sure still necessary
        typedef void result_type;//Not really sure still necessary

        void operator() (boost::iterator_range<It> const& id, boost::iterator_range<It> const& arg1, bool checking, Command& command) const
        {
            command.type = TYPE::SEND;
            command.id.assign(id.begin(), id.end());
            command.arg1.assign(arg1.begin(), arg1.end());
            command.checking = checking;
        }
    };

private:
    qi::symbols<char, bool> value;
    qi::rule<It> ignore;
    qi::rule<It, Command()> send;
    qi::rule<It, Commands()> start;
    boost::phoenix::function<deferred_fill> fill;

public:
    std::vector<Command> commands;

    Parser()
    {
        using namespace qi;
        using boost::phoenix::push_back;

        value.add("TRUE", true)
                 ("FALSE", false);

        send = ("SEND_CONFIRM" >> *blank >> '(' >> *blank >> raw[*~char_(',')] >> ','
                                                >> *blank >> raw[*~char_(',')] >> ','
                                                >> *blank >> value >> *blank >> ')' >> *blank >> ';')[fill(_1, _2, _3, _val)];

        ignore = *~char_("\r\n");

        start = (send[push_back(_val, _1)] | ignore) % eol;
    }

    void parse(const std::string& path)
    {
        std::ifstream in(path, std::ios_base::in);
        if (!in) return;

        in >> std::noskipws;//No white space skipping
        boost::spirit::istream_iterator first(in);
        boost::spirit::istream_iterator last;

        qi::parse(first, last, start, commands);
    }
};

int main(int argc, char* argv[])
{
    Parser parser;
    parser.parse("file.txt");

    return 0;
}

Компилятор жалуется следующим образом (копировать только первые строки):

1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(116): error C2039: 'type': no es un miembro de 'boost::result_of<const Parser::deferred_fill (std::vector<Value,std::allocator<char>> &,std::vector<Value,std::allocator<char>> &,boost::iterator_range<Parser::It> &,Command &)>'
1>        with
1>        [
1>            Value=char
1>        ]
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(114): note: vea la declaración de 'boost::result_of<const Parser::deferred_fill (std::vector<Value,std::allocator<char>> &,std::vector<Value,std::allocator<char>> &,boost::iterator_range<Parser::It> &,Command &)>'
1>        with
1>        [
1>            Value=char
1>        ]
1>z:\externos\boost_1_71_0\boost\phoenix\core\detail\function_eval.hpp(89): note: vea la referencia a la creación de instancias de plantilla clase de 'boost::phoenix::detail::function_eval::result_impl<F,void (Head,const boost::phoenix::actor<boost::spirit::argument<1>>&,const boost::phoenix::actor<boost::spirit::argument<2>>&,const boost::phoenix::actor<boost::spirit::attribute<0>>&),const boost::phoenix::vector2<Env,Actions> &>' que se está compilando
1>        with
1>        [
1>            F=const boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::terminal,boost::proto::argsns_::term<Parser::deferred_fill>,0> &,
1>            Head=const boost::phoenix::actor<boost::spirit::argument<0>> &,
1>            Env=boost::phoenix::vector4<const boost::phoenix::actor<boost::proto::exprns_::basic_expr<boost::phoenix::detail::tag::function_eval,boost::proto::argsns_::list5<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::terminal,boost::proto::argsns_::term<Parser::deferred_fill>,0>,boost::phoenix::actor<boost::spirit::argument<0>>,boost::phoenix::actor<boost::spirit::argument<1>>,boost::phoenix::actor<boost::spirit::argument<2>>,boost::phoenix::actor<boost::spirit::attribute<0>>>,5>> *,boost::fusion::vector<std::vector<char,std::allocator<char>>,std::vector<char,std::allocator<char>>,boost::iterator_range<Parser::It>,std::vector<char,std::allocator<char>>,boost::iterator_range<Parser::It>,std::vector<char,std::allocator<char>>,bool,std::vector<char,std::allocator<char>>,std::vector<char,std::allocator<char>>> &,boost::spirit::context<boost::fusion::cons<Command &,boost::fusion::nil_>,boost::fusion::vector<>> &,bool &> &,
1>            Actions=const boost::phoenix::default_actions &
1>        ]

Я предполагаю, что ошибка связана с использованием boost :: spirit :: istream_iterator, вместо char *, но я не могу понять, как это исправить, чтобы снова заработало.

У меня закончились идеи, пожалуйста, кто-нибудь может увидеть, где моя ошибка?

1 Ответ

3 голосов
/ 18 июня 2020

Ав. Вы делаете потрясающие вещи. К сожалению / к счастью, это слишком сложно.

Итак, давайте сначала исправим, а затем упростим.

Ошибка

Как вы и сказали,

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

² (и еще кое-что, но не будем отвлекаться)

...