parsing into several vector members

前端 未结 1 1843
轻奢々
轻奢々 2020-12-03 12:53

I want to recursively parse a string and store the results in one struct. I\'ve written a parser that can handle one iteration. The input is formatted as follows:

         


        
相关标签:
1条回答
  • 2020-12-03 13:23

    There are several ways :)

    1. Custom attribute traits
    2. The same using semantic actions
    3. Everything in semantic actions, at detail level

    1. Custom attribute traits

    The cleanest, IMO would to replace the Fusion Sequence Adaptation (BOOST_FUSION_ADAPT_STRUCT) by custom container attribute traits for Spirit:

    namespace boost { namespace spirit { namespace traits {
    
        template<> 
            struct is_container<ElemParseData, void> : mpl::true_ { };
        template<> 
            struct container_value<ElemParseData, void> { 
                 typedef boost::variant<float, unsigned int> type;
            };
        template <>
            struct push_back_container<ElemParseData, std::vector<float>, void> {
                static bool call(ElemParseData& c, std::vector<float> const& val) {
                    c.verts.insert(c.verts.end(), val.begin(), val.end());
                    return true;
                }
            };
        template <>
            struct push_back_container<ElemParseData, std::vector<unsigned int>, void> {
                static bool call(ElemParseData& c, std::vector<unsigned int> const& val) {
                    c.idx.insert(c.idx.end(), val.begin(), val.end());
                    return true;
                }
            };
    }}}
    

    Without changes to the grammar, this will simply result in the same effect. However, now you can modify the parser to expect the desired grammar:

        vertex   = 'v' >> qi::double_ >> qi::double_ >> qi::double_;
        elements = 'f' >> qi::int_ >> qi::int_ >> qi::int_;
    
        start = *(vertex | elements);
    

    And because of the traits, Spirit will "just know" how to insert into ElemParseData. See it live on Coliru

    2. The same using semantic actions

    You can wire it up in semantic actions:

        start = *(  
                   vertex   [phx::bind(insert, _val, _1)] 
                 | elements [phx::bind(insert, _val, _1)]
                 );
    

    With insert a member of type inserter:

    struct inserter {
        template <typename,typename> struct result { typedef void type; };
    
        template <typename Attr, typename Vec>
            void operator()(Attr& attr, Vec const& v) const { dispatch(attr, v); }
        private:
        static void dispatch(ElemParseData& data, std::vector<float> vertices) {
            data.verts.insert(data.verts.end(), vertices.begin(), vertices.end());
        }
        static void dispatch(ElemParseData& data, std::vector<unsigned int> indices) {
            data.idx.insert(data.idx.end(), indices.begin(), indices.end());
        }
    };
    

    This looks largely the same, and it does the same: live on Coliru

    3. Everything in semantic actions, at detail level

    This is the only solution that doesn't require any kind of plumbing, except perhaps inclusion of boost/spirit/include/phoenix.hpp:

    struct objGram : qi::grammar<std::string::const_iterator, ElemParseData(), iso8859::space_type>
    {
        objGram() : objGram::base_type(start)
        {
            using namespace qi;
    
            auto add_vertex = phx::push_back(phx::bind(&ElemParseData::verts, _r1), _1);
            auto add_index  = phx::push_back(phx::bind(&ElemParseData::idx,   _r1), _1);
            vertex   = 'v' >> double_ [add_vertex] >> double_ [add_vertex] >> double_ [add_vertex];
            elements = 'f' >> int_    [add_index]  >> int_    [add_index]  >> int_    [add_index] ;
    
            start = *(vertex(_val) | elements(_val));
        }
    
        qi::rule<std::string::const_iterator, ElemParseData(), iso8859::space_type> start;
        qi::rule<std::string::const_iterator, void(ElemParseData&), iso8859::space_type> vertex, elements;
    } objGrammar;
    

    Note:

    • One slight advantage here would be that there is less copying of values
    • A disadvantage is that you lose 'atomicity' (if a line fails to parse after, say, the second value, the first two values will have been pushed into the ElemParseData members irrevocably).

    Side note

    There is a bug in the read loop, prefer the simpler options:

    std::filebuf fb;
    if (fb.open("parsetest.txt", std::ios::in))
    {
        ss << &fb;
        fb.close();
    }
    

    Or consider boost::spirit::istream_iterator

    0 讨论(0)
提交回复
热议问题