Analysis of several elements of the vector

I want to parse the string recursively and save the results in one structure. I wrote a parser that can handle one iteration. The input is formatted as follows:

v 1.5 2.0 2.5 v 3.0 3.5 4.0 f 1 2 3 f 4 5 6 v 4.5 5.0 5.5 v 6.0 6.5 7.0 f 7 8 9 f 10 11 12 

The problem is that it analyzes only the first 4 lines, stops at the third "v" encountered. The full code is given below. How to change this code so that it also analyzes the rest of the input in the same structure? I tried changing the start rule from start = vertex >> elements to start = *(vertex >> elements) , but this only gives a huge compilation error. The same goes for start = +(vertex >> elements) . Any ideas on how to change the rules?

 #include <iostream> #include <sstream> #include <fstream> #include "boost/spirit/include/qi.hpp" #include "boost/spirit/include/support_iso8859_1.hpp" #include "boost/fusion/include/adapt_struct.hpp" struct ElemParseData { std::vector<float> verts; std::vector<unsigned int> idx; }; BOOST_FUSION_ADAPT_STRUCT( ElemParseData, (std::vector<float>, verts) (std::vector<unsigned int>, idx) ) bool doParse( ElemParseData &parseData, const std::string &data ) { namespace qi = boost::spirit::qi; namespace iso8859 = boost::spirit::iso8859_1; struct objGram : qi::grammar<std::string::const_iterator, ElemParseData(), iso8859::space_type> { objGram() : objGram::base_type(start) { vertex = *('v' >> qi::double_ >> qi::double_ >> qi::double_); elements = *('f' >> qi::int_ >> qi::int_ >> qi::int_); start = vertex >> elements; } qi::rule<std::string::const_iterator, ElemParseData(), iso8859::space_type> start; qi::rule<std::string::const_iterator, std::vector<float>(), iso8859::space_type> vertex; qi::rule<std::string::const_iterator, std::vector<unsigned int>(), iso8859::space_type> elements; } objGrammar; std::string::const_iterator f = data.cbegin(); bool res = qi::phrase_parse( f, data.cend(), objGrammar, iso8859::space, parseData ); // print everything that hasn't been processed by the parser std::cout << "#### Trail ####" << std::endl; std::cout << std::string(f, data.cend()) << std::endl; return res; } int main( int argc, char* argv[] ) { std::stringstream ss; std::filebuf fb; if ( fb.open("parsetest.txt", std::ios::in) ) { std::istream is(&fb); while (is) ss << char(is.get()); fb.close(); } ElemParseData parseData; bool res = doParse( parseData, ss.str() ); // print results std::cout << std::endl << "Parsing result: " << res << std::endl; std::cout << "---######### ResultData #########---" << std::endl; std::cout << "---- Begin vertex data ----" << std::endl; std::vector<float>::iterator it; for ( it = parseData.verts.begin(); it != parseData.verts.end(); ++it ) std::cout << *it << std::endl; std::cout << "---- End vertex data ----" << std::endl; std::cout << std::endl; std::cout << "---- Begin index data ----" << std::endl; std::vector<unsigned int>::iterator idxIt; for ( idxIt = parseData.idx.begin(); idxIt != parseData.idx.end(); ++idxIt ) std::cout << *idxIt << std::endl; std::cout << "---- End index data ----" << std::endl; std::cout << "Press enter to exit" << std::endl; std::cin.get(); } 

PS: If necessary, a compilation error can be found here .

EDIT: I'm trying to write Wavefront.OBJ parser. The input entered here is just a simplification of my problem.

+5
source share
1 answer

There are several ways :)

  • Custom Attribute Properties
  • Same thing with semantic actions
  • All in semantic actions, at the level of detail

1. Custom attribute properties

The cleanest, IMO will replace the Fusion sequence adaptation ( BOOST_FUSION_ADAPT_STRUCT ) with the container attribute attributes 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 a change in grammar, this simply leads to the same effect. However, now you can change 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 hell, the Spirit will β€œjust know” how to embed in ElemParseData . Watch live on Coliru

2. The same with semantic actions

You can associate it with semantic actions:

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

With insert 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()); } }; 

It looks basically the same and it does the same thing: live on Coliru

3. Everything in semantic actions, at the level of detail

This is the only solution that does not require any plumbing, with the possible exception 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 small advantage here would be that fewer copies of the values
  • The disadvantage is that you lose "atomicity" (if the string cannot be analyzed after the second word, say, the second value, they will be irrevocably transferred to the members of ElemParseData ).

Side note

There is an error in the read loop; prefer simpler options:

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

Or consider boost::spirit::istream_iterator

+11
source

Source: https://habr.com/ru/post/951440/


All Articles