使用Spirit

时间:2018-02-09 12:23:52

标签: c++ parsing vector boost boost-spirit

我有以下文字:

[70000000:45]
4, 5, 6, 7

[60000000:60]
1, 2, 3, 4

[80000:90]
4, 5, 6, 7, 8, 9

带方括号的行包含[freq:angle]形式的频率和角度,而后续行是与这些参数相关的数字向量。我可以有不同的频率和角度集,并为每个频率和角度定义一个向量。

我有以下结构:

struct Data {
  std::vector<int> frequencies;
  std::vector<int> elevations;
  std::vector<std::vector<double>> gains;
};

我需要在此结构中存储文件数据:在frequencies向量中,我将按顺序从上到下包含所有频率;在elevations向量中我会对高程数据有相同的看法,而在gains向量中我会有各自的增益向量。

范围是,如果我是索引,此索引处的向量元素将包含与文件中相关的频率,高程和增益数据。

例如,解析后,index = 1我将

data.frequencies[1] = 60000000
data.elevations[1] = 60
data.gains[1] = {1, 2, 3, 4}

我需要解析文件以填充结构。我能够解析frequencieselevations,并将它们存储在向量中,但我无法存储增益数据。我需要从这些数据中创建一个向量矢量。

您可以在下面找到代码;解析后parsed填充了频率和高程,我需要获得收益。我能做些什么才能解析它们?

#include <boost/optional/optional_io.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;

const std::string file1 = R"xx(
[70000000:45]
4, 5, 6, 7

[60000000:60]
1, 2, 3, 4

[80000:90]
4, 5, 6, 7, 8, 9
)xx";


struct Data {
  std::vector<int> frequencies;
  std::vector<int> elevations;
  std::vector<std::vector<double>> gains;

};

BOOST_FUSION_ADAPT_STRUCT(
  Data,
  (std::vector<int>, frequencies)
  (std::vector<int>, elevations)
  (std::vector<std::vector<double>>, gains)
)

template <typename It, typename Skipper = qi::space_type>
struct grammar : qi::grammar<It, Data(), Skipper> {

  grammar() : grammar::base_type(start) {

    auto frequencyParser = qi::int_[px::push_back(px::at_c<0>(qi::_val), qi::_1)];
    auto elevationParser = qi::int_[px::push_back(px::at_c<1>(qi::_val), qi::_1)];
    auto frequencyElevationParser = qi::lit('[') >> frequencyParser >> qi::lit(':') >> elevationParser >> qi::lit(']');
    auto gainsParser = qi::double_ % qi::lit(','); // Problem here where I want to parse vector rows

    start = *(frequencyElevationParser >> gainsParser);
  }

private:

  qi::rule<It, Data(), Skipper> start;
};

int main() {
  using It = std::string::const_iterator;
  Data parsed;
  bool ok = qi::phrase_parse(file1.begin(), file1.end(), grammar<It>(), qi::space, parsed);
  return 0;
}

3 个答案:

答案 0 :(得分:2)

本着我经常重复的口头禅Boost Spirit: "Semantic actions are evil"?的精神,我使用了一个特征,并将每个部分解析为Ast结构:

qi::rule<It, Data(), Skipper> start;
qi::rule<It, Ast::Data(), Skipper> section;

整个解析器不需要比这更复杂:

section = '[' >> int_ >> ':' >> int_ >> ']' >> double_ % ',';
start = *section;

魔术:

让我们创建一个Ast结构,并且只适应它,而不是你给出的不切实际的数据类型:

namespace Ast {
    struct Data {
        int frequency;
        int elevation;
        std::vector<double> gains;
    };
}

BOOST_FUSION_ADAPT_STRUCT(Ast::Data, frequency, elevation, gains)

现在,剩下要做的就是告诉Spirit如何将Data视为Ast::Data的容器:

namespace boost { namespace spirit { namespace traits {
    template <> struct container_value<MyLib::Data> { using type = Ast::Data; };

        static bool call(MyLib::Data& c, Ast::Data const& val) {
            c.frequencies.push_back(val.frequency);
            c.elevations.push_back(val.elevation);
            c.gains.push_back(val.gains);
            return true;
        }
    };
} } }

这简直太简单了。

样本

<强> Live On Coliru

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

const std::string file1 = R"xx(
[70000000:45]
4, 5, 6, 7

[60000000:60]
1, 2, 3, 4

[80000:90]
4, 5, 6, 7, 8, 9
)xx";

namespace MyLib {
    struct Data {
        std::vector<int> frequencies;
        std::vector<int> elevations;
        std::vector<std::vector<double> > gains;
    };
}

namespace Ast {
    struct Data {
        int frequency;
        int elevation;
        std::vector<double> gains;
    };
}

namespace boost { namespace spirit { namespace traits {
    template <> struct container_value<MyLib::Data> { using type = Ast::Data; };

    template<> struct push_back_container<MyLib::Data, Ast::Data> {
        static bool call(MyLib::Data& c, Ast::Data const& val) {
            c.frequencies.push_back(val.frequency);
            c.elevations.push_back(val.elevation);
            c.gains.push_back(val.gains);
            return true;
        }
    };
} } }

BOOST_FUSION_ADAPT_STRUCT(Ast::Data, frequency, elevation, gains)

namespace MyLib {
    template <typename It, typename Skipper = qi::space_type>
    struct grammar : qi::grammar<It, Data(), Skipper> {

        grammar() : grammar::base_type(start) {
            using namespace qi;

            section = '[' >> int_ >> ':' >> int_ >> ']' >> double_ % ',';
            start = *section;
        }

      private:
        qi::rule<It, Data(), Skipper> start;
        qi::rule<It, Ast::Data(), Skipper> section;
    };
}

int main() {
    using It = std::string::const_iterator;
    MyLib::Data parsed;
    bool ok = qi::phrase_parse(file1.begin(), file1.end(), MyLib::grammar<It>(), qi::space, parsed);
}

答案 1 :(得分:1)

使用X3,这变得更加简单:

#include <boost/spirit/home/x3.hpp>
#include <iostream>

namespace x3 = boost::spirit::x3;

const std::string file1 = R"xx(
[70000000:45]
4, 5, 6, 7

[60000000:60]
1, 2, 3, 4

[80000:90]
4, 5, 6, 7, 8, 9
)xx";


struct Data {
  std::vector<int> frequencies;
  std::vector<int> elevations;
  std::vector<std::vector<double>> gains;
};

int main() {
  Data parsed;

  auto appender = [](auto& ctx)
  {
      Data& data = x3::_val(ctx);
      auto& entry = x3::_attr(ctx);
      data.frequencies.push_back(boost::fusion::at_c<0>(entry));
      data.elevations.push_back(boost::fusion::at_c<1>(entry));
      data.gains.emplace_back(std::move(boost::fusion::at_c<2>(entry)));
  };

  auto block = x3::rule<struct r_block, Data>{}  =
      (('[' >> x3::int_ >> ':' >> x3::int_ >> ']') >> (x3::double_ % ','))[appender];

  auto it = file1.begin();
  while (x3::phrase_parse(it, file1.end(), block, x3::space, parsed))
    ;

  if (it != file1.end()) {
      std::cout << "Not all input parsed" << std::endl;
  }

  assert(parsed.elevations.size() == parsed.frequencies.size()
      && parsed.frequencies.size() == parsed.gains.size());

  for (int i = 0; i < parsed.frequencies.size(); ++i) {
      std::cout << "frequency: " << parsed.frequencies[i] << std::endl;
      std::cout << "elevations: " << parsed.elevations[i] << std::endl;
      std::cout << "gains: ";
      std::copy(parsed.gains[i].begin(), parsed.gains[i].end(), std::ostream_iterator<double>(std::cout, ","));
      std::cout << std::endl;
  }

  return 0;
}

答案 2 :(得分:0)

我自己找到了解决方案

我在语法中为qi::rule添加了std::vector<double>,然后我用它来解析向量,将结果附加到结构中,就像其他字段一样。在更正后的代码下面:

#include <boost/optional/optional_io.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;

const std::string file1 = R"xx(
[70000000:45]
4, 5, 6, 7

[60000000:60]
1, 2, 3, 4

[80000:90]
4, 5, 6, 7, 8, 9
)xx";


struct Data {
  std::vector<int> frequencies;
  std::vector<int> elevations;
  std::vector<std::vector<double>> gains;
};

BOOST_FUSION_ADAPT_STRUCT(
  Data,
  (std::vector<int>, frequencies)
  (std::vector<int>, elevations)
  (std::vector<std::vector<double>>, gains)
)

template <typename It, typename Skipper = qi::space_type>
struct grammar : qi::grammar<It, Data(), Skipper> {

  grammar() : grammar::base_type(start) {

    auto frequencyParser = qi::int_[px::push_back(px::at_c<0>(qi::_val), qi::_1)];
    auto elevationParser = qi::int_[px::push_back(px::at_c<1>(qi::_val), qi::_1)];
    auto frequencyElevationParser = qi::lit('[') >> frequencyParser >> qi::lit(':') >> elevationParser >> qi::lit(']');
    vectorParser = qi::double_ % qi::lit(',');
    auto gainsParser = vectorParser[px::push_back(px::at_c<2>(qi::_val), qi::_1)];

    start = *(frequencyElevationParser >> gainsParser);
  }

private:

  qi::rule<It, Data(), Skipper> start;
  qi::rule<It, std::vector<double>(), Skipper> vectorParser;
};

int main() {
  using It = std::string::const_iterator;
  Data parsed;
  bool ok = qi::phrase_parse(file1.begin(), file1.end(), grammar<It>(), qi::space, parsed);
  return 0;
}