There are several ways :)
- Custom attribute traits
- The same using semantic actions
- 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