Разбор структуры с полями enum и контейнерами STL с помощью Boost Spirit / Fusion - PullRequest
0 голосов
/ 28 июня 2019

новый для повышения, мне действительно нужно повышение духа, чтобы написать простой синтаксический анализатор для заполнения некоторой структуры данных.

Вот примерно то, как они выглядят:

struct Task
{
    const string dataname;
    const Level level;
    const string aggregator;
    const set<string> groupby;
    void operator();
};


struct Schedule
{
    map<Level, ComputeTask> tasks;
    // I have left just to make it seems that 
    // the struct wrapping over the map is not
    // useless (this is not the full code)
    void operator()(const InstancePtr &node); 
};

Относительно TaskЯ не знаю, как я мог бы использовать BOOST_FUSION_ADAPT_STRUCT, как упоминалось в примере employee , или его вариант, чтобы он работал с полями контейнера enum и STL.

Аналогичный вопросдля Schedule, но на этот раз я также использую тип пользователя (возможно, уже зарегистрирован в fusion, возможно, это рекурсивно?).

Я разрабатываю формат файла, определения структур и форматы файлов могут измениться так,Я предпочитаю использовать boost вместо созданного вручную, но трудно поддерживаемого кода.Я также делаю это для целей обучения.

Вот как может выглядеть файл:

level: level operation name on(data1, data2, data3)
level: level operation name on()
level: level operation name on(data1, data2)

Строка - это запись map в Schedule, предшествующая: - это ключ, а остальная часть определяет Task.Где level заменены на некоторые ключевые слова уровня, соответствующие enum Level, аналогичный случай для operation, name является одним из разрешенных имен (в наборе ключевых слов), on() является ключевым словом и внутрикруглые скобки - это ноль или более строк, предоставленных пользователем, которые должны заполнить поле set<string> groupby в Task.

Я хочу, чтобы он был читабельным, и я мог бы даже добавить английские ключевые слова, которые не добавляют ничего, кроме читабельности, что является еще одной причиной для использования некоторой библиотеки синтаксического анализа вместо кода, созданного вручную.

Не стесняйтесьпопросить более подробную информацию, если вы считаете, что мой вопрос недостаточно ясен ..

Спасибо.

Ответы [ 2 ]

1 голос
/ 29 июня 2019

Я бы порекомендовал решение от пользователя @sehe. Это очень гибко.

Но я также хотел бы поделиться чистым решением C ++. Как я уже писал в моем комментарии выше, ваш язык ввода довольно прост. Вы даже можете прочитать первые элементы с помощью стандартного оператора извлечения. Остальные можно прочитать в цикле с помощью std :: istream: iterator.

Вы также можете использовать C ++ std :: regex для проверки ввода. Поскольку ваш язык является обычным языком типа Chomsky-3, это легко возможно. И если входная строка верна, вы можете использовать элементы std :: regex и std :: regex_token_iterator, чтобы получить данные.

Я создал пример для вас. Данные упакованы в структуру. Для этой структуры я переписал оператор вставки и извлечения. Так что легкий ввод и вывод возможен с использованием функций std :: iostream.

В основном у меня есть однострочник для чтения всего входного файла и помещения данных в вектор. Итак, я определяю переменную с аргументами конструктора. Вот и все. Все данные будут доступны по желанию. В целях отладки я печатаю результат на экране.

И в качестве упражнения я помещаю данные в карту.

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <iterator>
#include <regex>
#include <sstream>


std::istringstream testData(
R"#(level1: levelA operation0 name0 on(data10, data12, data13)
level2: levelB operation1 name1 on(  data1  )
level3: levelC operation2 name2 on()
level4: levelD operation3 name3 on(data2, data3)
level5: levelE operation4 name4 on(data4, data5, data6, data7)
level6: levelF operation5 name5 on(data8, data9)
)#");


const std::regex InputFileRegEx(R"#((\w+)(?:[\:\s]+)(\w+)(?:\s+)(\w+)(?:\s+)(\w+)(?:\s+)(?:on\s*\()(.*)(?:\)))#");

struct Data
{   // Our Data
    std::string levelLeft{};            // Left Element for Map
    struct Right{                       // Right element for Map. Sub Struct
        std::string levelRight{};
        std::string operation{};
        std::string name{};
        std::vector<std::string> data;  // The data in the on( section
    } r;

    // Overload the extractor operator. With that someting like "Data d;std::cin >> d; " is easiliy possible
    friend std::istream& operator >> (std::istream& is, Data& d) {
        std::string line; getline(is, line);                // Read a complete line
        std::smatch sm{};                                   // Prepare match result values
        if (std::regex_match(line, sm, InputFileRegEx)) {   // CHeck, if the input string is valid
            // Copy all data
            d.levelLeft = sm[1]; d.r.levelRight = sm[2]; d.r.operation = sm[3]; d.r.name = sm[4]; std::string str(sm[5]);
            str.erase(remove_if(str.begin(), str.end(), isspace), str.end()); std::regex comma(","); d.r.data.clear();
            if (str.size()) std::copy(std::sregex_token_iterator(str.begin(), str.end(), comma, -1), std::sregex_token_iterator(), std::back_inserter(d.r.data));
        }
        else is.setstate(std::ios::failbit);
        return is;
    }
    // Overload inserter operator. Only for debug purposes and for illustration
    friend std::ostream& operator << (std::ostream& os, const Data& d) {
        // Print normal data members
        std::cout << d.levelLeft << " :: " << d.r.levelRight << ' ' << d.r.operation << ' ' << d.r.name << " --> ";
        // Print the mebers of the vector
        std::copy(d.r.data.begin(), d.r.data.end(), std::ostream_iterator<std::string>(os, " "));std::cout << '\n';
        return os;
    }
};

using MyMap = std::map<std::string, Data::Right>;

int main()
{
    // Read all test data in an array of test data. The one-Liner  :-)
    std::vector<Data> dataAll{std::istream_iterator<Data>(testData), std::istream_iterator<Data>() };

    // For debug purposes. Print to console
    std::copy(dataAll.begin(), dataAll.end(), std::ostream_iterator<Data>(std::cout, "\n"));

    MyMap myMap{};  // Put all Data in map
    for (const Data& d : dataAll) myMap[d.levelLeft] = d.r;

    return 0;
}

Итак, основная функция мала, а остальные тоже не очень большой код. Скорее просто.

Надеюсь, это даст некоторое представление.

0 голосов
/ 28 июня 2019

Итак, некоторые предположения, приведенные в качестве примеров, не дают ясного смысла.Но вот что:

Идет со случайным перечислением:

enum class Level { One, Two, Three, LEVEL };

Sidenote: std::set<> может потребоваться быть последовательным контейнером, потому что обычно groupby операции не являютсякоммутативный (порядок имеет значение).Конечно, я не знаю о вашем домене,

Адаптация:

BOOST_FUSION_ADAPT_STRUCT(ComputeTask, level, aggregator, dataname, groupby)
BOOST_FUSION_ADAPT_STRUCT(Schedule, tasks)

Обратите внимание, что я тонко помещаю адаптированные поля в грамматическом порядке.Это очень помогает в будущем.

Простейшая грамматика, которая приходит на ум:

template <typename It>
struct Parser : qi::grammar<It, Schedule()> {
    Parser() : Parser::base_type(_start) {
        using namespace qi;

        _any_word    = lexeme [ +char_("a-zA-Z0-9-_./") ];
        _operation   = _any_word; // TODO
        _group_field = _any_word; // TODO
        _dataname    = _any_word; // TODO

        _level       = no_case [ _level_sym ];
        _groupby     = '(' >> -(_group_field % ',') >> ')';
        _task        = _level >> _operation >> _dataname >> "on" >> _groupby;
        _entry       = _level >> ':' >> _task;
        _schedule    = _entry % eol;
        _start       = skip(blank) [ _schedule ];

        BOOST_SPIRIT_DEBUG_NODES((_start)(_schedule)(_task)(_groupby)(_level)(_operation)(_dataname)(_group_field))
    }
  private:
    struct level_sym : qi::symbols<char, Level> {
        level_sym() { this->add
            ("one", Level::One)
            ("two", Level::Two)
            ("three", Level::Three)
            ("level", Level::LEVEL);
        }
    } _level_sym;

    // lexemes
    qi::rule<It, std::string()> _any_word;
    qi::rule<It, std::string()> _operation, _dataname, _group_field; // TODO
    qi::rule<It, Level()> _level;

    using Skipper = qi::blank_type;
    using Table = decltype(Schedule::tasks);
    using Entry = std::pair<Level, ComputeTask>;

    qi::rule<It, std::set<std::string>(), Skipper> _groupby;
    qi::rule<It, ComputeTask(), Skipper> _task;
    qi::rule<It, Entry(), Skipper> _entry;
    qi::rule<It, Table(), Skipper> _schedule;
    qi::rule<It, Schedule()> _start;
};

Я изменил ввод, чтобы в расписании были уникальные ключи для Level, в противном случае только одназапись будет фактически результатом.

int main() {
    Parser<std::string::const_iterator> const parser;

    for (std::string const input : { R"(ONE: level operation name on(data1, data2, data3)
TWO: level operation name on()
THREE: level operation name on(data1, data2))" })
    {
        auto f = begin(input), l = end(input);
        Schedule s;
        if (parse(f, l, parser, s)) {
            std::cout << "Parsed\n";
            for (auto& [level, task] : s.tasks) {
                std::cout << level << ": " << task << "\n";
            }
        } else {
            std::cout << "Failed\n";
        }

        if (f != l) {
            std::cout << "Remaining unparsed input: " << std::quoted(std::string(f,l)) << "\n";
        }
    }
}

Печать

Parsed
One: LEVEL operation name on (data1, data2, data3)
Two: LEVEL operation name on ()
Three: LEVEL operation name on (data1, data2)

И, дополнительно с BOOST_SPIRIT_DEBUG определено:

<_start>
  <try>ONE: level operation</try>
  <_schedule>
    <try>ONE: level operation</try>
    <_level>
      <try>ONE: level operation</try>
      <success>: level operation na</success>
      <attributes>[One]</attributes>
    </_level>
    <_task>
      <try> level operation nam</try>
      <_level>
        <try>level operation name</try>
        <success> operation name on(d</success>
        <attributes>[LEVEL]</attributes>
      </_level>
      <_operation>
        <try>operation name on(da</try>
        <success> name on(data1, data</success>
        <attributes>[[o, p, e, r, a, t, i, o, n]]</attributes>
      </_operation>
      <_dataname>
        <try>name on(data1, data2</try>
        <success> on(data1, data2, da</success>
        <attributes>[[n, a, m, e]]</attributes>
      </_dataname>
      <_groupby>
        <try>(data1, data2, data3</try>
        <_group_field>
          <try>data1, data2, data3)</try>
          <success>, data2, data3)\nTWO:</success>
          <attributes>[[d, a, t, a, 1]]</attributes>
        </_group_field>
        <_group_field>
          <try>data2, data3)\nTWO: l</try>
          <success>, data3)\nTWO: level </success>
          <attributes>[[d, a, t, a, 2]]</attributes>
        </_group_field>
        <_group_field>
          <try>data3)\nTWO: level op</try>
          <success>)\nTWO: level operati</success>
          <attributes>[[d, a, t, a, 3]]</attributes>
        </_group_field>
        <success>\nTWO: level operatio</success>
        <attributes>[[[d, a, t, a, 1], [d, a, t, a, 2], [d, a, t, a, 3]]]</attributes>
      </_groupby>
      <success>\nTWO: level operatio</success>
      <attributes>[[LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], [[d, a, t, a, 1], [d, a, t, a, 2], [d, a, t, a, 3]]]]</attributes>
    </_task>
    <_level>
      <try>TWO: level operation</try>
      <success>: level operation na</success>
      <attributes>[Two]</attributes>
    </_level>
    <_task>
      <try> level operation nam</try>
      <_level>
        <try>level operation name</try>
        <success> operation name on()</success>
        <attributes>[LEVEL]</attributes>
      </_level>
      <_operation>
        <try>operation name on()\n</try>
        <success> name on()\nTHREE: le</success>
        <attributes>[[o, p, e, r, a, t, i, o, n]]</attributes>
      </_operation>
      <_dataname>
        <try>name on()\nTHREE: lev</try>
        <success> on()\nTHREE: level o</success>
        <attributes>[[n, a, m, e]]</attributes>
      </_dataname>
      <_groupby>
        <try>()\nTHREE: level oper</try>
        <_group_field>
          <try>)\nTHREE: level opera</try>
          <fail/>
        </_group_field>
        <success>\nTHREE: level operat</success>
        <attributes>[[]]</attributes>
      </_groupby>
      <success>\nTHREE: level operat</success>
      <attributes>[[LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], []]]</attributes>
    </_task>
    <_level>
      <try>THREE: level operati</try>
      <success>: level operation na</success>
      <attributes>[Three]</attributes>
    </_level>
    <_task>
      <try> level operation nam</try>
      <_level>
        <try>level operation name</try>
        <success> operation name on(d</success>
        <attributes>[LEVEL]</attributes>
      </_level>
      <_operation>
        <try>operation name on(da</try>
        <success> name on(data1, data</success>
        <attributes>[[o, p, e, r, a, t, i, o, n]]</attributes>
      </_operation>
      <_dataname>
        <try>name on(data1, data2</try>
        <success> on(data1, data2)</success>
        <attributes>[[n, a, m, e]]</attributes>
      </_dataname>
      <_groupby>
        <try>(data1, data2)</try>
        <_group_field>
          <try>data1, data2)</try>
          <success>, data2)</success>
          <attributes>[[d, a, t, a, 1]]</attributes>
        </_group_field>
        <_group_field>
          <try>data2)</try>
          <success>)</success>
          <attributes>[[d, a, t, a, 2]]</attributes>
        </_group_field>
        <success></success>
        <attributes>[[[d, a, t, a, 1], [d, a, t, a, 2]]]</attributes>
      </_groupby>
      <success></success>
      <attributes>[[LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], [[d, a, t, a, 1], [d, a, t, a, 2]]]]</attributes>
    </_task>
    <success></success>
    <attributes>[[[One, [LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], [[d, a, t, a, 1], [d, a, t, a, 2], [d, a, t, a, 3]]]], [Two, [LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], []]], [Three, [LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], [[d, a, t, a, 1], [d, a, t, a, 2]]]]]]</attributes>
  </_schedule>
  <success></success>
  <attributes>[[[[One, [LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], [[d, a, t, a, 1], [d, a, t, a, 2], [d, a, t, a, 3]]]], [Two, [LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], []]], [Three, [LEVEL, [o, p, e, r, a, t, i, o, n], [n, a, m, e], [[d, a, t, a, 1], [d, a, t, a, 2]]]]]]]</attributes>
</_start>

Полный список

Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <iomanip>
#include <experimental/iterator>

enum class Level { One, Two, Three, LEVEL };

struct ComputeTask {
    std::string dataname;
    Level level;
    std::string aggregator;
    std::set<std::string> groupby;
};

struct Schedule {
    std::map<Level, ComputeTask> tasks;
};

//////////////////////
// FOR DEBUG DEMO ONLY
static inline std::ostream& operator<<(std::ostream& os, Level l) {
    switch(l) {
        case Level::One: return os << "One";
        case Level::Two: return os << "Two";
        case Level::Three: return os << "Three";
        case Level::LEVEL: return os << "LEVEL";
    }
    return os << "?";
}

static inline std::ostream& operator<<(std::ostream& os, ComputeTask const& task) {
    os << task.level << ' ' << task.aggregator << ' ' << task.dataname << " on (";
    copy(begin(task.groupby), end(task.groupby), std::experimental::make_ostream_joiner(os, ", "));
    return os << ')';
}

/////////////
// FOR PARSER
BOOST_FUSION_ADAPT_STRUCT(ComputeTask, level, aggregator, dataname, groupby)
BOOST_FUSION_ADAPT_STRUCT(Schedule, tasks)

namespace qi = boost::spirit::qi;

template <typename It>
struct Parser : qi::grammar<It, Schedule()> {
    Parser() : Parser::base_type(_start) {
        using namespace qi;

        _any_word    = lexeme [ +char_("a-zA-Z0-9-_./") ];
        _operation   = _any_word; // TODO
        _group_field = _any_word; // TODO
        _dataname    = _any_word; // TODO

        _level       = no_case [ _level_sym ];
        _groupby     = '(' >> -(_group_field % ',') >> ')';
        _task        = _level >> _operation >> _dataname >> "on" >> _groupby;
        _entry       = _level >> ':' >> _task;
        _schedule    = _entry % eol;
        _start       = skip(blank) [ _schedule ];

        BOOST_SPIRIT_DEBUG_NODES((_start)(_schedule)(_task)(_groupby)(_level)(_operation)(_dataname)(_group_field))
    }
  private:
    struct level_sym : qi::symbols<char, Level> {
        level_sym() { this->add
            ("one", Level::One)
            ("two", Level::Two)
            ("three", Level::Three)
            ("level", Level::LEVEL);
        }
    } _level_sym;

    // lexemes
    qi::rule<It, std::string()> _any_word;
    qi::rule<It, std::string()> _operation, _dataname, _group_field; // TODO
    qi::rule<It, Level()> _level;

    using Skipper = qi::blank_type;
    using Table = decltype(Schedule::tasks);
    using Entry = std::pair<Level, ComputeTask>;

    qi::rule<It, std::set<std::string>(), Skipper> _groupby;
    qi::rule<It, ComputeTask(), Skipper> _task;
    qi::rule<It, Entry(), Skipper> _entry;
    qi::rule<It, Table(), Skipper> _schedule;
    qi::rule<It, Schedule()> _start;
};

int main() {
    Parser<std::string::const_iterator> const parser;

    for (std::string const input : { R"(ONE: level operation name on(data1, data2, data3)
TWO: level operation name on()
THREE: level operation name on(data1, data2))" })
    {
        auto f = begin(input), l = end(input);
        Schedule s;
        if (parse(f, l, parser, s)) {
            std::cout << "Parsed\n";
            for (auto& [level, task] : s.tasks) {
                std::cout << level << ": " << task << "\n";
            }
        } else {
            std::cout << "Failed\n";
        }

        if (f != l) {
            std::cout << "Remaining unparsed input: " << std::quoted(std::string(f,l)) << "\n";
        }
    }
}
...