Анализ ряда именованных наборов других именованных наборов

Итак, я хочу написать... ну... не очень простой парсер с boost::spirit::qi. Я знаю основы буст духа, впервые познакомившись с ним за последние пару часов.

В основном мне нужно разобрать это:

# comment

# other comment

set "Myset A"
{
    figure "AF 1"
    {
        i 0 0 0
        i 1 2 5
        i 1 1 1
        f 3.1 45.11 5.3
        i 3 1 5
        f 1.1 2.33 5.166
    }

    figure "AF 2"
    {
        i 25 5 1
        i 3 1 3
    }
}

# comment

set "Myset B"
{
    figure "BF 1"
    {
        f 23.1 4.3 5.11
    }
}

set "Myset C"
{
    include "Myset A" # includes all figures from Myset A

    figure "CF"
    {
        i 1 1 1
        f 3.11 5.33 3
    }
}

В это:

struct int_point { int x, y, z; };
struct float_point { float x, y, z; };

struct figure
{
    string name;
    vector<int_point> int_points;
    vector<float_point> float_points;
};

struct figure_set
{
    string name;
    vector<figure> figures
};

vector<figure_set> figure_sets; // fill with the data of the input

Теперь, очевидно, было бы слишком много, если бы кто-то написал это для меня, но не могли бы вы дать несколько советов о том, что читать и как структурировать грамматику и синтаксические анализаторы для этой задачи?

А еще... может быть так, что boost::spirit - не лучшая библиотека, которую я мог бы использовать для этой задачи. Если да, то какой?

РЕДАКТИРОВАТЬ: Вот где я до сих пор. Но я еще не знаю, что делать дальше: http://liveworkspace.org/code/212c31dfc0b6fbdf6c462d8d931c0e9f

Я могу прочитать одну цифру, но пока не знаю, как анализировать набор цифр.


person Borislav Stanimirov    schedule 13.10.2012    source источник
comment
Пока ничего... Я все еще читаю и экспериментирую с намного, НАМНОГО более простыми грамматиками.   -  person Borislav Stanimirov    schedule 13.10.2012
comment
Добавлено, где я дошел до редактирования.   -  person Borislav Stanimirov    schedule 14.10.2012
comment
О, круто. Я только что написал это, и затем заметил, что вы связались с liveworkspace... Хорошо, просто отправьте сообщение как ответ   -  person sehe    schedule 14.10.2012
comment
Ну, моя ссылка очень неполная. Спасибо за ответ :)   -  person Borislav Stanimirov    schedule 14.10.2012


Ответы (1)


Вот мой взгляд на это

Я считаю, что правило, которое будет блокировать для вас, было бы

figure  = eps >> "figure" 
    >> name         [ at_c<0>(_val) = _1 ] >> '{' >> 
    *(
            ipoints [ push_back(at_c<1>(_val), _1) ]
          | fpoints [ push_back(at_c<2>(_val), _1) ]
     ) >> '}';

На самом деле это симптом того, что вы разбираете смешанные строки i и f в отдельные контейнеры.

См. альтернативу ниже.

Вот мой полный код: test.cpp

//#define BOOST_SPIRIT_DEBUG // before including Spirit
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <fstream>

namespace Format
{
    struct int_point   { int x, y, z;   }; 
    struct float_point { float x, y, z; }; 

    struct figure
    {
        std::string              name;
        std::vector<int_point>   int_points;
        std::vector<float_point> float_points;

        friend std::ostream& operator<<(std::ostream& os, figure const& o);
    };

    struct figure_set
    {
        std::string           name;
        std::set<std::string> includes;
        std::vector<figure>   figures;

        friend std::ostream& operator<<(std::ostream& os, figure_set const& o);
    };

    typedef std::vector<figure_set> file_data;
}

BOOST_FUSION_ADAPT_STRUCT(Format::int_point,   
        (int, x)(int, y)(int, z))
BOOST_FUSION_ADAPT_STRUCT(Format::float_point, 
        (float, x)(float, y)(float, z))
BOOST_FUSION_ADAPT_STRUCT(Format::figure,      
        (std::string, name)
        (std::vector<Format::int_point>, int_points)
        (std::vector<Format::float_point>, float_points))
BOOST_FUSION_ADAPT_STRUCT(Format::figure_set,  
        (std::string, name)
        (std::set<std::string>, includes)
        (std::vector<Format::figure>, figures))

namespace Format
{
    std::ostream& operator<<(std::ostream& os, figure const& o)
    {
        using namespace boost::spirit::karma;
        return os << format_delimited(
                "\n    figure" << no_delimit [ '"' << string << '"' ] << "\n    {"
                << *("\n       i" << int_ << int_ << int_)
                << *("\n       f" << float_ << float_ << float_)
                << "\n    }"
                , ' ', o);
    }

    std::ostream& operator<<(std::ostream& os, figure_set const& o)
    {
        using namespace boost::spirit::karma;
        return os << format_delimited(
                "\nset" << no_delimit [ '"' << string << '"' ] << "\n{"
                << *("\n    include " << no_delimit [ '"' << string << '"' ])
                << *stream
                << "\n}"
                , ' ', o);
    }
}

namespace /*anon*/
{
    namespace phx=boost::phoenix;
    namespace qi =boost::spirit::qi;

    template <typename Iterator> struct skipper
        : public qi::grammar<Iterator>
    {
        skipper() : skipper::base_type(start, "skipper")
        {
            using namespace qi;

            comment = '#' >> *(char_ - eol) >> (eol|eoi);
            start   = comment | qi::space;

            BOOST_SPIRIT_DEBUG_NODE(start);
            BOOST_SPIRIT_DEBUG_NODE(comment);
        }

      private:
        qi::rule<Iterator> start, comment;
    };

    template <typename Iterator> struct parser
        : public qi::grammar<Iterator, Format::file_data(), skipper<Iterator> >
    {
        parser() : parser::base_type(start, "parser")
        {
            using namespace qi;
            using phx::push_back;
            using phx::at_c;

            name    = eps >> lexeme [ '"' >> *~char_('"') >> '"' ];

            include = eps >> "include" >> name;
            ipoints = eps >> "i"       >> int_         >> int_   >> int_;
            fpoints = eps >> "f"       >> float_       >> float_ >> float_;

            figure  = eps >> "figure" 
                >> name         [ at_c<0>(_val) = _1 ] >> '{' >> 
                *(
                        ipoints [ push_back(at_c<1>(_val), _1) ]
                      | fpoints [ push_back(at_c<2>(_val), _1) ]
                 ) >> '}';
            set     = eps >> "set" >> name >> '{' >> *include >> *figure >> '}';
            start   = *set;
        }

      private:
        qi::rule<Iterator, std::string()        , skipper<Iterator> > name, include;
        qi::rule<Iterator, Format::int_point()  , skipper<Iterator> > ipoints;
        qi::rule<Iterator, Format::float_point(), skipper<Iterator> > fpoints;
        qi::rule<Iterator, Format::figure()     , skipper<Iterator> > figure;
        qi::rule<Iterator, Format::figure_set() , skipper<Iterator> > set;
        qi::rule<Iterator, Format::file_data()  , skipper<Iterator> > start;
    };
}

namespace Parser {

    bool parsefile(const std::string& spec, Format::file_data& data)
    {
        std::ifstream in(spec.c_str());
        in.unsetf(std::ios::skipws);

        std::string v;
        v.reserve(4096);
        v.insert(v.end(), std::istreambuf_iterator<char>(in.rdbuf()), std::istreambuf_iterator<char>());

        if (!in) 
            return false;

        typedef char const * iterator_type;
        iterator_type first = &v[0];
        iterator_type last = first+v.size();

        try
        {
            parser<iterator_type>  p;
            skipper<iterator_type> s;
            bool r = qi::phrase_parse(first, last, p, s, data);

            r = r && (first == last);

            if (!r)
                std::cerr << spec << ": parsing failed at: \"" << std::string(first, last) << "\"\n";
            return r;
        }
        catch (const qi::expectation_failure<char const *>& e)
        {
            std::cerr << "FIXME: expected " << e.what_ << ", got '" << std::string(e.first, e.last) << "'" << std::endl;
            return false;
        }
    }
}

int main()
{
    Format::file_data data;
    bool ok = Parser::parsefile("input.txt", data);

    std::cerr << "Parse " << (ok?"success":"failed") << std::endl;
    std::cout << "# figure sets exported automatically by karma\n\n";

    for (auto& set : data)
        std::cout << set;
}

Он выводит проанализированные данные в качестве проверки: output.txt

Parse success
# figure sets exported automatically by karma


set "Myset A"
{ 
    figure "AF 1"
    { 
       i 0 0 0 
       i 1 2 5 
       i 1 1 1 
       i 3 1 5 
       f 3.1 45.11 5.3 
       f 1.1 2.33 5.166 
    }  
    figure "AF 2"
    { 
       i 25 5 1 
       i 3 1 3 
    }  
} 
set "Myset B"
{ 
    figure "BF 1"
    { 
       f 23.1 4.3 5.11 
    }  
} 
set "Myset C"
{ 
    include  "Myset A"
    figure "CF"
    { 
       i 1 1 1 
       f 3.11 5.33 3.0 
    }  
}

Вы заметите, что

  • изменен порядок точечных линий (все int_points предшествуют всем float_points)
  • также добавляются незначащие цифры, например. в последней строке 3.0 вместо 3, чтобы показать, что тип плавающий.
  • вы «забыли» (?) о включениях в свой вопрос

Альтернатива

Есть что-то, что сохраняет фактические точечные линии в исходном порядке:

typedef boost::variant<int_point, float_point> if_point;

struct figure
{
    std::string            name;
    std::vector<if_point>  if_points;
}

Теперь правила становятся простыми:

name    = eps >> lexeme [ '"' >> *~char_('"') >> '"' ];

include = eps >> "include" >> name;
ipoints = eps >> "i"       >> int_         >> int_   >> int_;
fpoints = eps >> "f"       >> float_       >> float_ >> float_;

figure  = eps >> "figure" >> name >> '{' >> *(ipoints | fpoints) >> '}';
set     = eps >> "set"    >> name >> '{' >> *include >> *figure  >> '}';
start   = *set;

Обратите внимание на элегантность в

figure  = eps >> "figure" >> name >> '{' >> *(ipoints | fpoints) >> '}';

И вывод остается в точном порядке ввода: output.txt

Еще раз, полный демонстрационный код (только на github): test.cpp

Бонусное обновление

Наконец, я сделал свою первую правильную грамматику кармы для вывода результатов:

name    = no_delimit ['"' << string << '"'];
include = "include" << name;
ipoints = "\n        i" << int_   << int_   << int_;
fpoints = "\n        f" << float_ << float_ << float_;

figure  = "figure" << name << "\n    {" << *(ipoints | fpoints) << "\n    }";
set     = "set"    << name << "\n{" 
            << *("\n   " << include)
            << *("\n   " << figure)  << "\n}";

start   = "# figure sets exported automatically by karma\n\n" 
            << set % eol;

На самом деле это было значительно удобнее, чем я ожидал. См. последнюю версию полностью обновленного списка: test.hpp

person sehe    schedule 13.10.2012
comment
Ну позвольте мне просто сказать, это было здорово. Я узнал больше из вашего поста, чем из чтения документов и руководств за последние 10 часов или около того :) - person Borislav Stanimirov; 14.10.2012
comment
@BorislavStanimirov Прочтите и это обновление :) Я думаю, вам это понравится. (Я посмотрю, смогу ли я использовать правильную грамматику кармы вместо полусырых операторов выходного потока, которые у меня есть сейчас) - person sehe; 14.10.2012
comment
@BorislavStanimirov Я добавил грамматику кармы, чтобы избавиться от уродливых операторов ostream (включая boost::static_visitor, который сейчас больше не нужен). Спокойной ночи - person sehe; 14.10.2012