5

我想递归解析一个字符串并将结果存储在一个结构中。我编写了一个可以处理一次迭代的解析器。输入格式如下:

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 >> elementsto修改start = *(vertex >> elements),但这只会产生巨大的编译错误。也一样start = +(vertex >> elements)。任何想法我应该如何修改规则?

#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:如果需要,可以在这里找到编译错误。

编辑:我正在尝试编写 Wavefront .OBJ 解析器。这里给出的输入只是我的问题的简化。

4

1 回答 1

11

有几种方法:)

  1. 自定义属性特征
  2. 同样使用语义动作
  3. 语义动作中的一切,在细节层面

1.自定义属性特征

最干净的,IMO 将用BOOST_FUSION_ADAPT_STRUCTSpirit 的自定义容器属性特征替换 Fusion Sequence Adaptation ( ):

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;
            }
        };
}}}

如果不更改语法,这只会产生相同的效果。但是,现在您可以修改解析器以期望所需的语法:

    vertex   = 'v' >> qi::double_ >> qi::double_ >> qi::double_;
    elements = 'f' >> qi::int_ >> qi::int_ >> qi::int_;

    start = *(vertex | elements);

并且由于这些特征,Spirit 将“只知道”如何插入ElemParseData. 在 Coliru上现场观看

2.同样使用语义动作

您可以将其连接到语义操作中:

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

这看起来大致相同,它也一样:住在 Coliru

3. 语义动作中的一切,在细节层面

这是唯一不需要任何管道的解决方案,除了可能包含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;

笔记:

  • 这里的一个小优势是值的复制更少
  • 一个缺点是你失去了“原子性”(如果一行在第二个值之后解析失败,前两个值将ElemParseData不可撤销地推入成员中)。

边注

读取循环中有一个错误,更喜欢更简单的选项:

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

或者考虑boost::spirit::istream_iterator

于 2013-07-15T21:19:49.523 回答