Russian Qt Forum
Ноябрь 23, 2024, 10:27 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Голосование
Вопрос: Хотите ли Вы в этом разбираться?
Да, хочу - 4 (22.2%)
Было бы полезно, но нет времени - 5 (27.8%)
Нет, это не окупает изучения - 4 (22.2%)
Та ну его нафиг! - 5 (27.8%)
Ваш вариант - 0 (0%)
Всего голосов: 11

Страниц: 1 ... 6 7 [8]   Вниз
  Печать  
Автор Тема: Хотите ли Вы в этом разбираться?  (Прочитано 51990 раз)
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #105 : Апрель 28, 2014, 21:35 »

Когда доберусь до компьютера переделаю и покажу.
Думаю, что заставлять парсер корректировать номера индексов не совсем верно, не его это задача. Но как это можно сделать покажу чуть ниже.

Как я вижу себе это сейчас, парсер разбирает данные из файла в структуру ModelData, дальше уже модель может разложить их, например, по векторам (точный размер данных у нас есть и мы можем сразу получить нужный кусок без переаллокаций) или собрать все данные по вертексам в одну структуру и сложить их в список. При этом, мы легко сможем корректировать индексы, если они отрицательные.

А это один из возможных вариантов решения по корректировки индексов самим парсером:
Код
C++ (Qt)
// Изменяем структуру списка поверхностей
// Добавляем метод addFace, в котором проверяем передаваемые индексы,
// корректируем их при необходимости и добавляем в коллекцию
struct FaceList : public deque<Face>
{
void addFace( const Face &f )
{
cout << "addFace" << endl;
push_back( f );
}
};
 
// И меняем правило получения списка поверхностей
// Теперь, после разбора всех индексов одного face, будет вызываться наш метод addFace, в котором мы все будем фиксить
// Правило можно упростить, перейдя на форму записи списков (%), но тогда потребуется еще кое что менять - лень.
m_face_lst = m_face[ boost::phoenix::bind( &FaceList::addFace, _val, _1 ) ] >> *m_face[ boost::phoenix::bind( &FaceList::addFace, _val, _1 ) ];
 
« Последнее редактирование: Апрель 28, 2014, 22:02 от Old » Записан
xokc
Птица говорун
*****
Offline Offline

Сообщений: 976



Просмотр профиля
« Ответ #106 : Апрель 28, 2014, 22:32 »

Код
C++ (Qt)
m_face_lst = m_face[ boost::phoenix::bind( &FaceList::addFace, _val, _1 ) ] >> *m_face[ boost::phoenix::bind( &FaceList::addFace, _val, _1 ) ];
 
И вы реально считаете, что так должен выглядеть современный код на C++?
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #107 : Апрель 29, 2014, 05:35 »

И вы реально считаете, что так должен выглядеть современный код на C++?
Конечно нет, точнее не обязательно. Улыбающийся
Это демонстрация специализированного инструмента для решения определенного круга задач.
Т.е. если разбор текстов для человека типовая задача, которую он постоянно решает, то можно воспользоваться spirit, предварительно разобравшись с ним. Он сможет значительно облегчить процесс написания парсеров.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #108 : Апрель 29, 2014, 10:57 »

Как я вижу себе это сейчас, парсер разбирает данные из файла в структуру ModelData, дальше уже модель может разложить их, например, по векторам (точный размер данных у нас есть и мы можем сразу получить нужный кусок без переаллокаций) или собрать все данные по вертексам в одну структуру и сложить их в список. При этом, мы легко сможем корректировать индексы, если они отрицательные.
Не понял. Да, было бы хорошо сделать это "потом" (когда все считано), но тогда утерян "текущий" индекс вертекса (см пример что я приводил выше).

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

Теперь чуть более сложные вещи: хотя нумерация вертексов всегда неуклонно нарастает, в файле есть объекты/группы.  Пример (источник тот же)
Цитировать
    g front cube
    f 1 2 3 4
    g back cube
    f 8 7 6 5
    g right cube
    f 4 3 7 8
    g top cube
    f 5 1 4 8
    g left cube
    f 5 6 2 1
    g bottom cube
    f 2 6 7 3
    # 6 elements
Визуально кубик тот же, но вместо 6 фейсов в 1 объекте на выходе должно быть 6 объектов по 1 фейсе в каждом. Имена объектов должны быть предъявлены юзверю. Как это сделать на спирите?

Спасибо
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #109 : Апрель 29, 2014, 11:11 »

Не понял. Да, было бы хорошо сделать это "потом" (когда все считано), но тогда утерян "текущий" индекс вертекса (см пример что я приводил выше).
Почему он утерян, у нас все индексы лежат строго в том же порядке что и в файле.

Вообще складывать все в контейнеры не есть хорошо, точнее не есть задача парсера. Сам парсер "ничего не должен знать" о формате данных.
Здесь ModelData это промежуточный формат данных.
Парсер знает только его, но ничего не знает про модель/загрузчик. А модель/загрузчик ничего не знает про парсер, но знает ModelData.

Считав "единицу информации" (напр вертекс, фейсет) он должен вызвать метод класса напр "model", а тот уж разберется что с этой единицей делать.
Тогда парсер будет знать про класс Model, и жестко привязан к ее методам. Улыбающийся

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

Визуально кубик тот же, но вместо 6 фейсов в 1 объекте на выходе должно быть 6 объектов по 1 фейсе в каждом. Имена объектов должны быть предъявлены юзверю. Как это сделать на спирите?
Добавляем структуру группа, которая будет хранить имя и список поверхностей.
И добавляем правило, что-то типа:
m_group = lit( "g" ) >> m_name >> m_faceList;
« Последнее редактирование: Апрель 29, 2014, 11:23 от Old » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #110 : Апрель 29, 2014, 13:45 »

Тогда парсер будет знать про класс Model, и жестко привязан к ее методам. Улыбающийся
Это нормально, еще лучше сделать интерфейс модели (ограничить познания парсера).

Добавляем структуру группа, которая будет хранить имя и список поверхностей.
И добавляем правило, что-то типа:
m_group = lit( "g" ) >> m_name >> m_faceList;
Да, но как это "синхронизировать"? Собственно проблема та же самая что и с отрицательными индексами. Пример:

В какой-то момент приходит "g" или индекс < 0 или "usemtl" и.т.п.. При "ручном" разборе нам известно: сейчас (на данный момент) считано столько-то вертексов и фейсетов, поэтому не составляет труда  сменить принимающие контейнеры ("g"), пересчитать индекс, завести материал и.т.п. Но как это сделать в spirit где все идет "одной струей"? Ведь допустив дальнейшее чтение мы теряем счетчики, "поезд уйдет"

Еще подобный вопрос: куда/как пристроить индикатор для юзверя с возможностью отмены? В общем, хотелось бы не только "правила", но и удобные callback'и, без них прожить трудно, какая-то бяка да найдется

Спасибо

« Последнее редактирование: Апрель 29, 2014, 13:46 от Igors » Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #111 : Апрель 29, 2014, 16:49 »

Это нормально, еще лучше сделать интерфейс модели (ограничить познания парсера).
Ну я не считаю это нормальным.
Парсер - это маленькая подсистемка загрузки ресурсов (скорее всего закрытая) и знакомить ее с публичным классом из совершенно другой подсистемы считаю неправильным и очень вредным. И совершенно не важно будет использоваться интерфейс или нет.
К тому же, не всегда целесообразно делать в модели публичные методы для ее модификации на примитивном уровне: добавить текстурную координату, добавить нормаль. В моделе могут быть более высокоуровневые методы для этого.
А сейчас ModelData это закрытая внутренняя структура, которую можно менять как угодно, ведь про неёзнают только внутренности загрузчика и модели.

Да, но как это "синхронизировать"?
Тут нужно разобраться, как сработает это правило:
m_group = lit( "g" ) >> m_name >> m_faceList;

Сначала будет найдено имя команды 'g', дальше будет проверено и загружено имя группы, а потом правило будет ждать описание одного или нескольких фейсов. Только когда все это будет соблюдено правило отработает и мы получим на выходе заполненную структуру Group.
Код
C++ (Qt)
struct Group
{
   string    name;
   FaceList faces;
};
 

Тоже самое с материалами, вы описываете полное правило, начиная от usemtl, потом могут идти такие то параметры. А на выходе получите готовую структуру Material.

Еще подобный вопрос: куда/как пристроить индикатор для юзверя с возможностью отмены? В общем, хотелось бы не только "правила", но и удобные callback'и, без них прожить трудно, какая-то бяка да найдется
Ну так итераторы же. Улыбающийся
Он знает начало, конец и текущее положение. А также всегда может послать исключение или перейти в конец, для завершения парсинга.
Или написать специализированный парсер, который встраивать в главное правило разбора.

« Последнее редактирование: Апрель 30, 2014, 09:18 от Old » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #112 : Апрель 30, 2014, 10:13 »

Тоже самое с материалами, вы описываете полное правило, начиная от usemtl, потом могут идти такие то параметры. А на выходе получите готовую структуру Material.
В том-то и дело что получать нечего - там нет ничего кроме имени. Но эта строка меняет контекст дальнейшего разбора. Как я делал руками: получив "usemtl mat1"

- проверяю есть ли материал с таким именем std::map<std::string, int>. Если нет - добавляю. В любом случае получаю уникальный ID материала и устанавливаю его как текущий.

- у фейса есть доп поле materialID, оно = индексу текущего материала (хотя в самой строке "f" этой инфы нет)

Ситуация с группой/объектом и др та же самая. Идеально было бы чтобы spirit заливал не в контейнеры, а в локальные структуры и после каждого чтения отдавал управление мне. Как это сделать?

Спасибо
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #113 : Апрель 30, 2014, 10:16 »

В том-то и дело что получать нечего - там нет ничего кроме имени.
Ну так правило смотрит не только одну строку, она будет просматривать все последующие строки и определять параметры, которые относятся к материалу.
Вы думаете строками - не надо. Нужно думать правилами. Улыбающийся

Дальше пока не читал. Улыбающийся
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #114 : Апрель 30, 2014, 10:21 »

в локальные структуры и после каждого чтения отдавал управление мне. Как это сделать?
Ну так это уже есть.

Например это правило:
Код
C++ (Qt)
m_vertex = lit( "v" ) >> double_[ at_c<0>(_val) = _1 ] >> double_[ at_c<1>(_val) = _1 ] >> double_[ at_c<2>(_val) = _1 ] >> -double_[ at_c<3>(_val) = _1 ];
заполняет структуру Vertex, дальше вы можете вызвать свой метод, передав эту структуру в качестве параметра.
Как я уже показал на примере метода addface.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #115 : Май 03, 2014, 22:45 »

Переписал сейчас BibTeX parser на Spiritе.. Всё работает и всё хорошо, но возникло пара вопросов.. По поводу прикручивания erreor handling..

Суть в следующем:
Пусть имеется bib файл с записями.. Если в одной из записей (bibtex_entry) что то пошло не так, то все последующие записи игнорируются и парсинг прекращается(

И это в общем то понятно..
 Хорошо, это проблема решается, если повесить на грамматику error handling (обработку ошибок).. Тогда в лог выводится вся информация о том где и что было сделано неправильно..

Но хочется следующей реакции: Попадается дифектная запись (bibtex_entry), далее выводится сообщение в лог (игнорируя эту запись), и далее процесс парсинга продолжается (а не останавливается). Эдакий оптимистический вариант.. Чёрт с ней с этой записью, но главное, чтобы после неё  все распарсились..

Сейчас сделано так:
Код
C++ (Qt)
#ifndef BIBTEX_PARSER_H
#define BIBTEX_PARSER_H
 
#include <string>
 
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/fusion/include/map.hpp>
 
#include "core/bibtex_entry.h"
 
namespace bibmake {
 
namespace core {
 
using namespace boost::spirit;
namespace phx = boost::phoenix;
namespace sw = boost::spirit::standard_wide;
 
namespace detail {
 
template <class Logger>
class logger_wrapper_impl
{
public:
   logger_wrapper_impl(const Logger & logger) : _logger(logger) {}
 
   template <class, class, class, class>
   struct result
   {
       typedef void type;
   };
 
   template <class Iterator, class String>
   void operator()(const String & what,
                   Iterator it_1,
                   Iterator it_2,
                   Iterator it_3) const
   {
       _logger(what, it_1, it_2, it_3);
   }
 
private:
   const Logger & _logger;
};
 
struct default_logger
{
   template <class Iterator, class String>
   void operator()(const String & what, Iterator /*it_1*/, Iterator it_2, Iterator it_3) const
   {
       std::cerr   << "Error! Expecting "
                   << what
                   << " here: \""
                   << std::string(it_2, it_3)
                   << "\""
                   << std::endl;
   }
};
 
} /* namrspace detail */
 
template <class ForwardIterator>
class inner_brace_text : public qi::grammar<ForwardIterator, std::string()>
{
public:
   inner_brace_text() : inner_brace_text::base_type(start_)
   {
       escaped_brace_ = qi::char_('\\') >> qi::char_;
 
       simple_text_ = +(escaped_brace_ | ~qi::char_("{}"));
 
       inner_brace_text_ %= simple_text_
               |
               qi::as_string
               [
               qi::char_('{') >> *(inner_brace_text_ | simple_text_) >> qi::char_('}')
               ];
 
       start_ %= '{' >> qi::omit[*sw::space] >> *inner_brace_text_ >> qi::omit[*sw::space] >> '}';
   }
 
private:
   qi::rule<ForwardIterator, std::string()> simple_text_, inner_brace_text_, escaped_brace_, start_;
};
 
 
template <class ForwardIterator>
class inner_quote_text : public qi::grammar<ForwardIterator, std::string()>
{
public:
   inner_quote_text() : inner_quote_text::base_type(start_)
   {
       escaped_quote_ = qi::char_('\\') >> qi::char_;
 
       inner_quote_text_ = +(escaped_quote_ | ~qi::char_('"'));
 
       start_ %= '"' >> qi::omit[*sw::space] >> *inner_quote_text_ >> qi::omit[*sw::space] >> '"';
   }
 
private:
   qi::rule<ForwardIterator, std::string()> inner_quote_text_, escaped_quote_, start_;
};
 
 
template <class ForwardIterator, class Skipper>
class bibtex_parser : public qi::grammar<ForwardIterator, bibtex_entry(), Skipper>
{
public:
   template <class Logger = detail::default_logger>
   bibtex_parser(const Logger & logger = detail::default_logger()) : bibtex_parser::base_type(start_, "BibTeX Parser")
   {
       type_ = +qi::alpha;
 
       key_ = +(qi::alnum | qi::char_("-_:/"));
 
       tag_ = +(qi::alnum | qi::char_("-_:/"));
 
       simple_content_ = +qi::alnum;
 
       content_ = inner_brace_content_ | inner_quote_content_ | simple_content_;
 
       body_ = '{' > key_ > ',' > fields_ >> -qi::lit(',') > '}';
 
       fields_ = -(field_ % ',');
 
       field_ = tag_ > '=' > content_;
 
       start_ = *~qi::lit('@') >> qi::lit('@')
               >>
               type_[phx::at_c<0>(_val) = _1]
               >>
               body_
               [
                   phx::at_c<1>(_val) = phx::at_c<0>(_1),
                   phx::at_c<2>(_val) = phx::at_c<1>(_1)
               ];
 
 
       start_.name("start");
       body_.name("body");
       fields_.name("fields");
       field_.name("field");
       type_.name("type");
       key_.name("citation key");
       tag_.name("tag");
       content_.name("content");
 
       phx::function<detail::logger_wrapper_impl<Logger>> logger_wrapper = detail::logger_wrapper_impl<Logger>(logger);
 
       qi::on_error<qi::fail>(start_, logger_wrapper(_4, _1, _3, _2));
   }
 
private:
   qi::rule<ForwardIterator, bibtex_entry(), Skipper> start_;
   qi::rule<ForwardIterator, std::pair<std::string, bibtex_entry::fields_type>(), Skipper> body_;
   qi::rule<ForwardIterator, bibtex_entry::fields_type(), Skipper> fields_;
   qi::rule<ForwardIterator, std::pair<std::string, std::string>(), Skipper> field_;
   qi::rule<ForwardIterator, std::string()> type_;
   qi::rule<ForwardIterator, std::string()> key_;
   qi::rule<ForwardIterator, std::string()> tag_;
   inner_brace_text<ForwardIterator> inner_brace_content_;
   inner_quote_text<ForwardIterator> inner_quote_content_;
   qi::rule<ForwardIterator, std::string()> simple_content_;
   qi::rule<ForwardIterator, std::string()> content_;
};
 
 
template <class ForwardIterator, class Container, class Logger = detail::default_logger>
bool bibtex_parse(ForwardIterator begin,
                 ForwardIterator end,
                 Container & container,
                 const Logger & logger = detail::default_logger())
{
   auto skipper = sw::space | '%' >> *(qi::char_ - (qi::eol | qi::eoi)) >> (qi::eol | qi::eoi);
 
   bibtex_parser<ForwardIterator, decltype(skipper)> parser(logger);
 
   return qi::phrase_parse(begin, end, *parser, skipper, container) && (begin == end);
}
 
} /* namespace core */
 
} /* namespace bibmake */
 
#endif // BIBTEX_PARSER_H
 

Т.е. сейчас есть некая defaul реализация логгера, которая выводи информацию о проблеме.
Как я понимаю, то поведение, которое я добиваюсь - это вместо qi::on_error<qi::fail> поставить qi::on_error<qi::retry>, но тогда в пользовательском логгере придётся смещать указатель после того проблемного места? Ну например итерировать его на следующею позицию..

Это нормально? Или есть другие альтернативы?

Да, пользоватьльский логгер имеет следующую семантику:
Код
C++ (Qt)
struct default_logger
{
   template <class Iterator, class String>
   void operator()(const String & what, Iterator /*it_1*/, Iterator it_2, Iterator it_3) const
   {
       std::cerr   << "Error! Expecting "
                   << what
                   << " here: \""
                   << std::string(it_2, it_3)
                   << "\""
                   << std::endl;
   }
};
 

где it_1 - итератор в то место, где начался разбор данного правила с соответствующим обработчиком.
it_2 - итератор в то место, где конкретно произошла ошибка.
it_3 - конец входной строки.
what - что вызвало ошибку: строка, представляющая описание произошедшей ошибки.

Т.е. как правильно организовать вывод в лог и дальнейшее "проскальзывание" проблемных мест (с занесением их в лог)т?

 
« Последнее редактирование: Май 03, 2014, 22:56 от m_ax » Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #116 : Май 04, 2014, 08:53 »

Т.е. как правильно организовать вывод в лог и дальнейшее "проскальзывание" проблемных мест (с занесением их в лог)т?
Я никогда не использовал лог. У меня всегда было, что при ошибке дальнейший разбор не имеет смысла.
Если можно, выложите или отправьте мне в личку архив с кодом, что бы можно было по разбираться. В данном случае я бы пытался перезапускать главное правило с начала. Оно рассчитывает на мусор перед entry. А вот как это сделать еще не представляю. Улыбающийся
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #117 : Май 04, 2014, 09:09 »

Еще подумалось.
Вы устанавливаете обработку ошибки на правило start, можно попробовать выделить правило entry и ставить обработчик ошибки на него с retry.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #118 : Май 04, 2014, 09:33 »

Цитировать
Вы устанавливаете обработку ошибки на правило start, можно попробовать выделить правило entry и ставить обработчик ошибки на него с retry.
С retry, как я понял, нужно руками двигать итератор, на то место с которого будет следующая попытка..
Это можно сделать, например в логгере..

Выкладываю минимальный проект, который воспроизводит проблему.  Файл bibliography.bib имеет 4 записи, причём во второй записи сделана ошибка (пропущена запятая)
Код
Bash
@article{Khusainov_JETPLett_2009,
 year={2009},
 issn={0021-3640},
 journal={JETP Letters},
 volume={90},
 number={5},
 doi={10.1134/S002136400917010X},
 title={Separate re-entrant superconductivity in asymmetric ferromagnet/superconductor/ferromagnet trilayers},
 url={http://dx.doi.org/10.1134/S002136400917010X},
 publisher={SP MAIK Nauka/Interperiodica},
 keywords={74.50.+r; 74.62.-c},
 author={Khusainov, M.G. and Khusainov, M.M. and Ivanov, N.M. and Proshin, Yu.N.},
 pages={359--364} <<<--------- здесь должна быть запятая
 language={English}
}
 

Первая запись парситься успешно, вторая вываливается (с логом), и на этом всё заканчивается(
А хотелось бы, чтобы  записи, которые идут после второй (неправильной) также были распарсены..)

 
Возможно, retry как раз то, что нужно..  Улыбающийся
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2095



Просмотр профиля
« Ответ #119 : Май 04, 2014, 11:18 »

Кажется получилось)

Сделал с retry:
Код
C++ (Qt)
qi::on_error<qi::retry>(start_, logger_wrapper(_4, _1, _3, _2));
 
а в самом  logger_wrapper после вызова пользовательского логгера перемещаю начальный указатель на то место, где возникла проблема:
Код
C++ (Qt)
void operator()(const String & what,
                   Iterator & it_1,
                   Iterator it_2,
                   Iterator it_3) const
   {
       _logger(what, it_1, it_2, it_3);
       it_1 = it_2;
   }
 
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Страниц: 1 ... 6 7 [8]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.356 секунд. Запросов: 24.