解析为多个向量成员 [英] parsing into several vector members
问题描述
我想递归地解析一个字符串并将结果存储在一个结构中.我写了一个可以处理一次迭代的解析器.输入的格式如下:
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:
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
问题在于它仅解析前4行,在遇到的第三个'v'处停止.完整的代码如下.如何修改此代码,以便也将其余输入解析为相同的结构?我尝试将启动规则从start = vertex >> elements
修改为start = *(vertex >> elements)
,但这只会产生巨大的编译错误. start = +(vertex >> elements)
也是如此.有什么想法我应该如何修改规则?
The problem is that it only parses the first 4 lines, it stops at the third encountered 'v'. The complete code is given below. How do I modify this code so it also parses the rest of the input into the same struct? I've tried modifying the start rule from start = vertex >> elements
to start = *(vertex >> elements)
, but that only gives a huge compilation error. Same goes for start = +(vertex >> elements)
. Any ideas how I should modify 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();
}
P.S .:如果需要,可以在此处找到编译错误.
P.S.: If needed, the compilation error can be found here.
我正在尝试编写Wavefront .OBJ解析器.这里给出的输入只是我的问题的简化.
I'm trying to write an Wavefront .OBJ parser. The input given here is just a simplification of my problem.
推荐答案
有几种方法:)
- 自定义属性特征
- 使用语义动作也一样
- 语义动作中的所有细节,
1.自定义属性特征
最干净的IMO将通过Spirit的自定义容器属性特征替换融合序列适配(BOOST_FUSION_ADAPT_STRUCT
):
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);
由于这些特性,Spirit会只知道"如何插入ElemParseData
.观看 在Coliru上直播
And because of the traits, Spirit will "just know" how to insert into ElemParseData
. See it live on Coliru
您可以通过语义动作将其连接起来
You can wire it up in semantic actions:
start = *(
vertex [phx::bind(insert, _val, _1)]
| elements [phx::bind(insert, _val, _1)]
);
具有insert
类型为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
这是唯一不需要任何类型管道的解决方案,除了可能包含boost/spirit/include/phoenix.hpp
:
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;
注意:
- 这里的一个优势是,值的复制较少
- 缺点是您失去了原子性"(例如,如果 line 在第二个值之后无法解析,则前两个值将被不可撤销地推入
ElemParseData
成员中)
- 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();
}
或考虑boost::spirit::istream_iterator
这篇关于解析为多个向量成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!