【问题标题】:Got stuck porting legacy boost::spirit code在移植遗留 boost::spirit 代码时遇到问题
【发布时间】:2020-10-07 13:54:03
【问题描述】:

我正在将一些遗留代码从 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* 有关,但我不知道如何修复它以使其再次工作。

我的想法已经用完了,请大家看看我的错误在哪里?

【问题讨论】:

    标签: c++ boost boost-spirit boost-spirit-qi


    【解决方案1】:

    哦。你正在做很棒的事情。可悲/幸运的是,它过于复杂了。

    所以让我们先修复,然后简化。

    错误

    就像你说的,

    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 好吧)。

    但是,这是因为您将 other 事物作为属性。原来*blank 将属性公开为vector&lt;char&gt;。所以你可以通过omit[]-ing 来“修复”这些问题。让我们将其包装在像 ignore 这样的无属性规则中,这样我们可以减少混乱。

    现在调用是

    void Parser::deferred_fill::operator()(T&amp;&amp; ...) const [with T = {boost::iterator_range&lt;It&gt;&amp;, boost::iterator_range&lt;It&gt;&amp;, bool&amp;, Command&amp;}]

    所以它是兼容和编译的。解析:

    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}
    

    让我们改进一下

    • 这需要船长
    • 这需要自动属性传播
    • 其他一些风格元素

    船长

    让我们使用内置功能,而不是显式“调用”船长:

    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

    使用词素跳过

    还是同一个话题,lexemes 实际上抑制了船长¹,所以你不必raw[]。这也将暴露的属性更改为 vector&lt;char&gt;

    void operator() (std::vector<char> const& id, std::vector<char> const& arg1, bool checking, Command& command) const
    

    Live On Coliru

    自动属性传播

    Qi 有语义动作,但它的真正优势在于它们是可选的:Boost Spirit: "Semantic actions are evil"?

    • push_back(_val, _1) 实际上是 *p+pp % delim² 的自动属性传播语义,所以直接放弃吧:

      start = skip(blank) [
              (send | ignore) % eol
          ];
      

      (注意send|ignore 实际上合成了optional&lt;Command&gt;,这对于自动传播来说很好)

    • std::vectorstd::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&lt;&lt; 重载,我们可以只使用 std::cout &lt;&lt; cmd;

    此时,这一切都在一小部分代码中运行:Live On Coliru

    样式 2 的元素

    • 如果可以的话,让你的解析器无状态。这意味着它可以是 const,所以你可以:

      • 无需昂贵的建设即可重复使用它
      • 优化器有更多的工作要做
      • 它更易于测试(有状态的事物更难证明是幂等的)

      因此,不要让commands 成为成员,而是返回它们。在此过程中,我们可以将parse 设为静态函数

    • 与其硬编码迭代器类型,不如将其作为模板参数灵活使用。这样,如果您在 char[] 缓冲区、stringstring_view 中的某个时间点有命令,您就不会被 multi_pass_adaptoristream_iterator 的开销所困扰。

    • 此外,使用合适的入口点从 qi::grammar 派生您的 Parser 意味着您可以将其用作解析器表达式(实际上是 a non-terminal,就像 rule&lt;&gt;)作为任何其他解析器。

    • 考虑启用规则调试(参见示例)

    完整代码

    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 skipper issues

    ²(还有一些,但我们不要离题)

    【讨论】:

    • 一如既往,一流的答案,既解决了所提出的问题,也提供了改进代码的新观点。我总是从你的帖子中学到至少一件事。非常感谢!
    • 干杯。这就是我这样做的目的。我也是一边回答一边学习,我也是从别人的答案中学习。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    • 1970-01-01
    • 2016-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多