Russian Qt Forum

Программирование => С/C++ => Тема начата: m_ax от Апрель 20, 2014, 14:23



Название: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 20, 2014, 14:23
Недавно столкнулся с такой проблемой:

Мне нужно распарсить некоторый текст, формат которого предполагает наличие комментариев.
Комментарий начинается с определённого символа и заканчивается другим.
Всё что находится внутри закомментированого блока должно быть проигнорированно.
Комментарий может начинаться где угодно (не обязательно в начале строки)

Для парсинга использую boost::regex + boost::tokenizer..

Вопрос: Как наиболее логичнее организовать поддержку комментариев?
Метод "влоб" - это просто удалить из строки все комменты, и уже дальше парсить её.. Но этот вариант не очень(

Сейчас сделал так:
Написал свой bidirectional итератор, который при инкрементировании/декриментировании если встречает начало коммента, просто перескакивает его..
Подсовываю этот итератор в boost::regex и boost::tokenizer вместо std::string::const_iterator.. И всё работает)
Без существенного переписывания кода.. (спасибо гибкости boost)

Вот маленький пример: символ начала коммента - %, конец коммента - \n):
Код
C++ (Qt)
#include <iostream>
#include <boost/regex.hpp>
#include <string>
#include <list>
#include <boost/range/istream_range.hpp>
#include <algorithm>
 
template <class T = char>
class tex_string_iterator : public std::iterator<std::bidirectional_iterator_tag, T, ptrdiff_t, const T*, const T&>
{
public:
   typedef T char_type;
   typedef std::basic_string<T> string_type;
   typedef typename std::basic_string<T>::const_iterator string_iterator;
 
   tex_string_iterator(string_iterator begin, string_iterator end, T bra = '%', T ket = '\n')
       : _begin(begin), _end(end), _it(begin), _bra(bra), _ket(ket)
   {
       make_comments_list();
   }
 
   tex_string_iterator(const tex_string_iterator<T>& iter)
       : _begin(iter._begin), _end(iter._end), _it(iter._it),
         _bra(iter._bra), _ket(iter._ket), _comments_list(iter._comments_list) {}
 
   tex_string_iterator<T>& operator=(const tex_string_iterator & iter) {
       if (this != &iter) {
           _begin = iter._begin;
           _end = iter._end;
           _it = iter._it;
           _bra = iter._bra;
           _ket = iter._ket;
           _comments_list = iter._comments_list;
       }
       return *this;
   }
 
   tex_string_iterator() {}
 
   ~tex_string_iterator() {}
 
   const T& operator*() const { return *_it; }
 
   const T* operator->() const { return _it; }
 
   tex_string_iterator<T>& operator++() {
 
       if (*_it != _bra) {
           if (++_it == _end)
               return *this;
           if (*_it != _bra)
               return *this;
       }
 
       for (const auto & comment : _comments_list) {
           if (comment.begin() == _it) {
               _it = comment.end();
               if (_it == _end)
                   break;
 
               if (*(++_it) != _bra)
                   break;
           }
       }
 
       return *this;
   }
 
   tex_string_iterator<T> operator++(int) {
       tex_string_iterator<T> tmp = *this;
       ++*this;
       return tmp;
   }
 
   tex_string_iterator<T>& operator--() {
 
       if (*_it != _ket) {
           --_it;
           if (*_it != _ket)
               return *this;
       }
 
       for (const auto & comment :_comments_list) {
           if (comment.end() == _it) {
               _it = comment.begin();
               if (_it == _begin)
                   break;
 
               if (*(--_it) != _ket)
                   break;
           }
       }
 
       return *this;
   }
 
   tex_string_iterator<T> operator--(int) {
       tex_string_iterator<T> tmp = *this;
       --*this;
       return tmp;
   }
 
   template <class R>
   friend bool operator==(const tex_string_iterator<R> &, const tex_string_iterator<R> &);
 
   template <class R>
   friend bool operator!=(const tex_string_iterator<R> &, const tex_string_iterator<R> &);
 
private:
   string_iterator _begin;
   string_iterator _end;
   string_iterator _it;    //current iterator
   T _bra;                 //open comment symbol
   T _ket;                 //closed comment symbol
   std::list<boost::iterator_range<string_iterator>> _comments_list;
 
   void make_comments_list() {
       if (_begin == _end)
           return;
 
       auto first = _begin;
       auto second = _begin;
 
       while ((first = std::find(first, _end, _bra)) != _end) {
           second =  std::find(first, _end, _ket);
           _comments_list.push_back(boost::iterator_range<string_iterator>(first, second));
           first = second;
       }
   }
};
 
template<class T>
inline bool operator==(const tex_string_iterator<T> & x, const tex_string_iterator<T> & y) {
   return (x._it == y._it);
}
 
template<class T>
inline bool operator!=(const tex_string_iterator<T> & x, const tex_string_iterator<T> & y) {
   return !(x == y);
}
 
int main()
{
 
   std::string buffer = "text text %comment comment\n text nocomment";
 
   tex_string_iterator<char> ibegin(buffer.begin(), buffer.end(), '%', '\n');
   tex_string_iterator<char> iend(buffer.end(), buffer.end());
 
   boost::regex_iterator<tex_string_iterator<char>> it(ibegin, iend, boost::regex("(com)"));
   boost::regex_iterator<tex_string_iterator<char>> end;
   for (; it != end; ++it) {
       std::cout << it->str(1) << std::endl;
   }
 
  return 0;
}
 


Сейчас меня такой вариант устраивает..
Но хотелось бы и другие соображения/подходы пообсуждать. :)
  


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: sergek от Апрель 20, 2014, 18:02
Нагружать парсер дополнительной работой не здорово. Пусть делает свое дело, а на вход ему надо давать то, что он обрабатывает, без всяких лишних фрагментов. Если появится еще какие нибудь инструкции, например, документирования - что, еще одну ветку в парсере организовывать?
Я бы сделал, как вы говорите, "влоб" - выделил бы текст, обрабатываемый парсером.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 20, 2014, 19:51
Нагружать парсер дополнительной работой не здорово. Пусть делает свое дело, а на вход ему надо давать то, что он обрабатывает, без всяких лишних фрагментов. Если появится еще какие нибудь инструкции, например, документирования - что, еще одну ветку в парсере организовывать?
Я бы сделал, как вы говорите, "влоб" - выделил бы текст, обрабатываемый парсером.

Ну вобщем то сейчас парсер и делает свою работу.. В том смысле, что сами регулярки не изменились: как если бы комментов и вовсе не было.. (т.е. за комменты отвечает не сам парсер, а итератор: фактически обёртка над string::const_iterator)

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

А вообще, в таких ситуациях обычно как поступают? (ситуация то вполне рядовая..?) Просто интересно.. 


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 20, 2014, 20:10
Хотя в моём случае объёмы не такие уж и страшные, максимум порядка мегабайт.. Может и вправду влоб?


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: sergek от Апрель 20, 2014, 20:18
А вообще, в таких ситуациях обычно как поступают? (ситуация то вполне рядовая..?) Просто интересно.. 
По-моему, так и поступают, сначала подготавливают текст, потом подают на вход специализированного процессора. Затраты на распределение памяти сейчас минимальные. Для себя я давно решил- лучше сделать проще и понятнее, чем эффективнее и сложнее. Ну, разве что, если задача критична к затратам, или хочется сделать красиво из любви к искусству...


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 20, 2014, 20:33
Может и вправду влоб?
А завтра вам понадобится доставать информацию из комментариев... :) Например, соотношение информации и комментариев в файле.
Сейчас, как я понял, вы руками разбираете файл по токенам, а почему не описать правила для такого разбора? И случай с комментарием будет одним из правил. Сейчас, вы сможете просто игнорировать текст комментария, а завтра обрабатывать его по вновь описанным правилам.
Понимаете куда клоню? Не пробовали или медленно? :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: sergek от Апрель 20, 2014, 20:42
А завтра вам понадобится доставать информацию из комментариев... :) .
Значит, из текста выделить комментарии и подать на вход другого процессора. Который может только комментарии обрабатывать. Вы поняли, куда я клонил? ;)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 20, 2014, 20:52
Может и вправду влоб?
А завтра вам понадобится доставать информацию из комментариев... :) Например, соотношение информации и комментариев в файле.
Сейчас, как я понял, вы руками разбираете файл по токенам, а почему не описать правила для такого разбора? И случай с комментарием будет одним из правил. Сейчас, вы сможете просто игнорировать текст комментария, а завтра обрабатывать его по вновь описанным правилам.
Понимаете куда клоню? Не пробовали или медленно? :)

Вот я и боюсь, а что если завтра, действиетельно, понадобиться?)
Если так, то используя подход с пользовательским итератором, я могу, например, просто написать другой специализированный итератор для других целей..

Сейчас, к сожалению, моя задача не сводится к банальному разбиванию текста на токены.. Поэтому я использую несколько проходжов: вначале регуляркой, затем токенайзером (я об этом писал в теме igor'са "разбор QString")..

Есть ещё мысли покурить в сторону boost::spirit.. Но пока только мысли)
А Вы как бы поступили? (Да, парсю такой формат: http://en.wikipedia.org/wiki/BibTeX (http://en.wikipedia.org/wiki/BibTeX) + ещё один очень схожий)  


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 20, 2014, 20:55
Есть ещё мысли покурить в сторону boost::spirit.. Но пока только мысли)
Таки про него я и намекаю. Смотрите сразу новую версию, думаю вам понравиться. :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 20, 2014, 21:23
Есть ещё мысли покурить в сторону boost::spirit.. Но пока только мысли)
Таки про него я и намекаю. Смотрите сразу новую версию, думаю вам понравиться. :)

Спасибо, посмотрю.. Надеюсь, проблема с комментариями там элегантнее решается)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 20, 2014, 21:35
Спасибо, посмотрю.. Надеюсь, проблема с комментариями там элегантнее решается)
Одной строкой описания правила. :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 21, 2014, 11:36
Комментарий начинается с определённого символа и заканчивается другим.
К сожалению - необязательно, примеры
Цитировать
void foo();   // here can be anything /* ' " etc
...
f 1 2 3  # 44 vertices

Метод "влоб" - это просто удалить из строки все комменты, и уже дальше парсить её.. Но этот вариант не очень(
Для этого, QString::split и др "быстрых" решений есть хороший тест: пользователь хочет видеть где ошибка, напр подсветкой
Цитировать
f 1 2.0 3
Error in line 33: integer expected


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 21, 2014, 11:48
К сожалению - необязательно, примеры
Обязательно.
В этих примерах комментарий начинается от // и #, а заканчивается eol (концом строки).


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 21, 2014, 12:00
К сожалению - необязательно, примеры
Обязательно.
В этих примерах комментарий начинается от // и #, а заканчивается eol (концом строки).


Это не принципиально.. Вот Вам вариант с более слжными комментами:

Код
C++ (Qt)
#include <string>
#include <list>
#include <boost/regex.hpp>
#include <boost/range/istream_range.hpp>
#include <algorithm>
 
template <class T = char>
class tex_string_iterator : public std::iterator<std::bidirectional_iterator_tag, T, ptrdiff_t, const T*, const T&>
{
public:
   typedef T char_type;
   typedef std::basic_string<T> string_type;
   typedef typename std::basic_string<T>::const_iterator string_iterator;
   typedef boost::regex regex_type;
 
   tex_string_iterator(string_iterator begin, string_iterator end, const regex_type & comment)
       : _begin(begin), _end(end), _it(begin), _comment(comment)
   {
       make_comments_list();
   }
 
   tex_string_iterator(const tex_string_iterator<T>& iter)
       : _begin(iter._begin), _end(iter._end), _it(iter._it),
         _comment(iter._comment), _comments_list(iter._comments_list) {}
 
   tex_string_iterator<T>& operator=(const tex_string_iterator & iter) {
       if (this != &iter) {
           _begin = iter._begin;
           _end = iter._end;
           _it = iter._it;
           _comment = iter._comment;
           _comments_list = iter._comments_list;
       }
       return *this;
   }
 
   tex_string_iterator() {}
 
   ~tex_string_iterator() {}
 
   const T& operator*() const { return *_it; }
 
   const T* operator->() const { return _it; }
 
   tex_string_iterator<T>& operator++() {
       auto cur = _it;
       ++_it;
       for (const auto & comment : _comments_list) {
           if ((comment.begin() == _it) || (comment.begin() == cur)) {
               _it = comment.end();
               if (_it == _end)
                   break;
               ++_it;
           }
       }
 
       return *this;
   }
 
   tex_string_iterator<T> operator++(int) {
       tex_string_iterator<T> tmp = *this;
       ++*this;
       return tmp;
   }
 
   tex_string_iterator<T>& operator--() {
       auto cur = _it;
       --_it;
       for (const auto & comment : _comments_list) {
           if ((comment.end() == _it) || (comment.end() == cur)) {
               _it = comment.begin();
               if (_it == _begin)
                   break;
               --_it;
           }
       }
 
       return *this;
   }
 
   tex_string_iterator<T> operator--(int) {
       tex_string_iterator<T> tmp = *this;
       --*this;
       return tmp;
   }
 
   template <class R>
   friend bool operator==(const tex_string_iterator<R> &, const tex_string_iterator<R> &);
 
   template <class R>
   friend bool operator!=(const tex_string_iterator<R> &, const tex_string_iterator<R> &);
 
private:
   string_iterator _begin;
   string_iterator _end;
   string_iterator _it;    //current iterator
   regex_type _comment;
   std::list<boost::iterator_range<string_iterator>> _comments_list;
 
   void make_comments_list() {
       if (_begin == _end)
           return;
 
       auto first = _begin;
       boost::match_results<string_iterator> what;
       while (boost::regex_search(first, _end, what, _comment)) {
           _comments_list.push_back(boost::iterator_range<string_iterator>(what[1].first, what[1].second));
           first = what[1].second;
       }
   }
};
 
template<class T>
inline bool operator==(const tex_string_iterator<T> & x, const tex_string_iterator<T> & y) {
   return (x._it == y._it);
}
 
template<class T>
inline bool operator!=(const tex_string_iterator<T> & x, const tex_string_iterator<T> & y) {
   return !(x == y);
}
 
 
int main()
{
 
   boost::regex expr("([#\\\\][^\\n]*)");
 
   std::string buffer = "text#comment\ntext\\comment ";
 
   tex_string_iterator<char> iter(buffer.begin(), buffer.end(), expr);
   tex_string_iterator<char> end(buffer.end(), buffer.end(), expr);
 
   for (; iter != end; ++iter) {
       std::cout << *iter;
   }
 
   return 0;
}
 

Цитировать
Для этого, QString::split и др "быстрых" решений есть хороший тест: пользователь хочет видеть где ошибка, напр подсветкой
Это тоже не проблема.. Я же буду знать итератор, на том месте где проблема возникнет..  


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 21, 2014, 12:09
Что значит не принципиально? :)
Комментарии всегда начинаются с определённой последовательности и заканчиваются определённой последовательностью. Обязательно. :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 21, 2014, 12:51
Что значит не принципиально? :)
Комментарии всегда начинаются с определённой последовательности и заканчиваются определённой последовательностью. Обязательно. :)

Т.е. я имел в виду, что комменты могут начинаться и заканчиваться и с более чем одного символа) 
(Не правильно выразился))


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 21, 2014, 12:59
В этих примерах комментарий начинается от // и #, а заканчивается eol (концом строки).
Так eol не фиксирован, все 3 варианта (\n, \r, \n\r) актуальны. И возникает неприятный эффект: "сожрав" eol для комментариев теряем индекс текущей строки. И старая проблема вложенности никуда не делась, напр
Цитировать
"start quote
void foo(); // "
end quote"

Код
C++ (Qt)
template <class T = char>
class tex_string_iterator : public std::iterator<std::bidirectional_iterator_tag, T, ptrdiff_t, const T*, const T&>
 

Ну как всегда: "вынос мозга" я получаю сразу же, а вот до удобства юзания далековато :) Мельком глянул BibText, вроде все норм, мой велик прокатит. Ставлю {}, получаю "первый левель", затем парсю содержимое по иерархии. Как у Вас это сделать - не знаю.

Но хотелось бы и другие соображения/подходы пообсуждать.
Как насчет отслеживания (номера) текущей строки? Это необходимо хотя бы для выдачи ошибки


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 21, 2014, 13:07
Так eol не фиксирован, все 3 варианта (\n, \r, \n\r) актуальны.
Ну и что? Как это отменяет завершение одно строчных комментариев?

И возникает неприятный эффект: "сожрав" eol для комментариев теряем индекс текущей строки.
Так "сжирайте" его с умом, что бы таких детских проблем не было.

И старая проблема вложенности никуда не делась, напр
Еще одна выдуманная проблема?
Здесь нужно пропускать все от start quote до end quote, вообще все.
Но номера строк продолжать считать, ну если делать с умом.



Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 21, 2014, 13:15
Цитировать
Ну как всегда: "вынос мозга" я получаю сразу же, а вот до удобства юзания далековато..
Напротив, это удобно: я пользуюсь всё теми же регулярками и токенайзерами.. Единственное что изменилось - в boost::regex, boost::tokenizer теперь используют не string::const_iterator, а пользовательский tex_string_iterator
Код
C++ (Qt)
boost::regex expr("([#\\\\][^\\n]*)");
 
   std::string buffer = "Te#st\nyy\\yy ";
 
   tex_string_iterator<char> iter(buffer.begin(), buffer.end(), expr);
   tex_string_iterator<char> end(buffer.end(), buffer.end(), expr);
 
   for (; iter != end; ++iter) {
       std::cout << *iter;
   }
 
Вывод:
Код
Bash
Teyy
 
Цитировать
Как насчет отслеживания (номера) текущей строки? Это необходимо хотя бы для выдачи ошибки
Никаких проблем не вижу.. Зная позицию пользовательского итератора, я всегда могу сказать на какой строчке он находится.. поскольку этот итератор указывает на позицию в исходной строке..
Комментарии никуда не деваются: пользовательский итератор их просто пропускает (если встречает при декрементировании/инкрементировании)
  


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 21, 2014, 13:34
Никаких проблем не вижу.. Зная позицию пользовательского итератора, я всегда могу сказать на какой строчке он находится.. поскольку этот итератор указывает на позицию в исходной строке..
Я так понял "исходная строка" - это весь текст (ну или его часть, но неск строк), поэтому получить из позиции номер строки не так уж просто. А читая строку за строкой теряем "контекст" (напр след строка должна знать что коммент открыт)

Здесь нужно пропускать все от start quote до end quote, вообще все.
Надо-то надо, но каким образом?

Но номера строк продолжать считать, ну если делать с умом.
Ах, оказывается надо делать, да еще и "с умом"! А я-то думал что с таким мощным инструментарием делать придется минимум  :'(


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 21, 2014, 13:41
Надо-то надо, но каким образом?
Считайте номера строк не в пользовательском коде, а в самом вашем анализаторе.
Указатель прошел eol -> инкремент номера.
Вот в мощном инструментарии это можно сделать в итераторе.

А я-то думал что с таким мощным инструментарием делать придется минимум  :'(
Я бы не назвал ваш анализатор мощным инструментарием: у него детские ситуации вызывают огромные проблемы. :)
А в мощных инструментариях все есть из коробки: и номер строки, и номер столбца, а при необходимости можно написать даже свое поведение.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 21, 2014, 15:57
Я бы не назвал ваш анализатор мощным инструментарием: у него детские ситуации вызывают огромные проблемы. :)
Не стоит считать себя "взрослым" - Вы еще очень юны  :)

А в мощных инструментариях все есть из коробки: и номер строки, и номер столбца, а при необходимости можно написать даже свое поведение.
Та может быть, но такого пока не вижу. Зато уже длинная сопля темплейтов и какая-то вариация regexp, в который тоже придется вникать :'( Отмазки ТС не кажутся мне убедительными
Сейчас, к сожалению, моя задача не сводится к банальному разбиванию текста на токены..
Чего это она "не сводится"? По-моему прекрасно сводится. Если я не в курсе каких-то нюансов BibText - поясните на примере.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 21, 2014, 16:06
Не стоит считать себя "взрослым" - Вы еще очень юны  :)
Нет проблем, я вам уже говорил, сороковник это не возраст для мужчины. :)

Та может быть, но такого пока не вижу.
Ну и что?
Здесь не школа, что бы каждую мелочь разжевывать. :)

Зато уже длинная сопля темплейтов и какая-то вариация regexp, в который тоже придется вникать :'(
Еще раз, ну и что?
Проблемы индейцев шерифа не волнуют. Не вникайте.

Отмазки ТС не кажутся мне убедительными
Всем наплевать. :)

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


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 21, 2014, 18:51
Всем наплевать. :)
Вы уже неск раз прибегаете к этой формуле которая звучит поразительно глупо :) Вас никто не уполномачивал выражать мнение "всех" :) И если мне плевать - то я просто не стану тратить на это слова и время. Чего попусту "хлопать крыльями"?  :)

Мы обсуждаем конкретный вопрос, никому ненужно вас в чем-то переубеждать или уговаривать разбираться.Так же не интересно заново обсуждать проблемы вашего анализатора, для этого уже есть тема.
m_ax предложил обсудить др подходы - вот я и обсуждаю. И мне лично неясно чем же простецкая разбивка на токены плоха в данном случае. Да, в своем парсере я действовал именно так - но ведь просто потому что я так думаю, это совсем не значит что я навязываю свое решение.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 21, 2014, 18:58
вот я и обсуждаю.
Так а где вы обсуждаете? :)
Я пока увидел, только детские расстройства: так длинно, там не понятно, а этого я вообще не вижу.

Если бы вам это было нужно, вы бы думали и разбирались (вся нужная для этого информация в теме уже есть), а так вы просто капризничаете. :)

Вы уже неск раз прибегаете к этой формуле которая звучит поразительно глупо :)
Это единственный способ прекратить ваши повествования о себе. :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 21, 2014, 22:24
Igors, давайте пока абстрагируемся от конкретных реализаций.. Шаблоны-не шаблоны - это сейчас не так важно..
Важен сам концепт. У меня такое ощущение, что мы о разных вещах говорим(

Объясню ещё раз, почему для меня тема с комментами здесь актуальна.. На очень простом примере.

Вначале была задача:
Имеется текст. В тексте встречаются выражения такого вида \command{ arg1, arg2, arg3, ... argN}, где  \command - это некоторая команда, arg1, ... argN - список её аргументов. Число аргументов не фиксировано.
Нужно извлечь из текста список всех аргументов (без повторений, в порядке их появления в тексте).

Эта задача решается просто. Один из вариантов решения - это вначале пройтись регуляркой и выцепить содержимое того что содержится в фигурных скобках. Затем уже, например, токенайзером, вытащить список всех аргументов..
И здесь вроде бы всё хорошо работает.. Вот возможный код такого решения:
Код
C++ (Qt)
   boost::sregex_iterator it(buffer.begin(), buffer.end(), boost::regex("\\\\cite[\\s\\t]*\\{([^}]*)\\}"));
   boost::sregex_iterator end;
   for (; it != end; ++it) {
       std::string str(it->str(1));
       boost::tokenizer<boost::char_separator<char>> tok(str, boost::char_separator<char>(","));
       for (auto it = tok.begin(); it != tok.end(); ++it) {
           std::string arg = boost::algorithm::trim_copy(*it);
           if (std::find_if(args_list.begin(), args_list.end(), std::bind(std::equal_to<std::string>(), _1, arg)) == args_list.end())
                 args_list.push_back(arg);
       }
   }
 
(здесь команда называется \cite)

А теперь слегка усложним задачу.. Сейчас наш формат допускает наличие комментариев. Комментарий начинается с символа % и заканчивается концом строки.
Теперь, если какая то команда окажется в закоментированном блоке, то предыдущее решение по прежнему выцепит её..  Более того, коменты могут быть и внутри команды, что ещё попортит нам крови при разбиении на токены..

Так вот я и спрашиваю: как лучше поступить? Мой вариант такой: написать свой итератор, который просто "перепрыгивает через коменты".. Тогда концептуально в самом разборе ничего не измениться. Нужно только поменять string::const_iterator на пользовательский tex_string_iterator. Всё.
Вот как это может выглядеть:
Код
C++ (Qt)
   tex_string_iterator<char> ibegin(buffer.begin(), buffer.end(), boost::regex("(%[^\\n]*)"));
   tex_string_iterator<char> iend(buffer.end(), buffer.end(), boost::regex("(%[^\\n]*)"));
 
   boost::regex_iterator<tex_string_iterator<char>> it(ibegin, iend, boost::regex("\\\\cite[\\s\\t]*\\{([^}]*)\\}"));
   boost::regex_iterator<tex_string_iterator<char>> end;
   for (; it != end; ++it) {
       std::string str(it->str(1));
       boost::tokenizer<boost::char_separator<char>> tok(str, boost::char_separator<char>(","));
       for (auto it = tok.begin(); it != tok.end(); ++it) {
           std::string arg = boost::algorithm::trim_copy(*it);
           if (std::find_if(args_list.begin(), args_list.end(), std::bind(std::equal_to<std::string>(), _1, arg)) == args_list.end())
               args_list.push_back(arg);
       }
   }
 

Другой возможный вариант, который обсуждался здесь - это просто удалить из текста перед парсингом все коменты..

И наконец - это Spirit::Qi 

Вот Вы, Igors какой концепт предлагаете?   
 
     


 


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 22, 2014, 04:47
m_ax, смотрите на что наткнулся: https://bitbucket.org/sergiu/bibtex-spirit


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 22, 2014, 06:18
Вот Вы, Igors какой концепт предлагаете?
Иш ты, концепт-монцепт.
Мы тут строки сосчитать не можем, а он туда же.
И слово такое обобщенно задроченное подобрал "концепт".
А потом этими руками хлеб берешь.

;) :D


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 22, 2014, 07:39
m_ax, смотрите на что наткнулся: https://bitbucket.org/sergiu/bibtex-spirit


Круто) Спасибо за ссылку)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 22, 2014, 07:40
Вот Вы, Igors какой концепт предлагаете?
Иш ты, концепт-монцепт.
Мы тут строки сосчитать не можем, а он туда же.
И слово такое обобщенно задроченное подобрал "концепт".
А потом этими руками хлеб берешь.

;) :D

Не виноватаяя.. Он сам пришёл  ;D


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 22, 2014, 12:22
А теперь слегка усложним задачу.. Сейчас наш формат допускает наличие комментариев. Комментарий начинается с символа % и заканчивается концом строки.
Теперь, если какая то команда окажется в закоментированном блоке, то предыдущее решение по прежнему выцепит её..  Более того, коменты могут быть и внутри команды, что ещё попортит нам крови при разбиении на токены..
А зачем Вы смешиваете регулярку и токенайзер? Делайте все токенами, тогда сразу получаете токен содержащий коммент целиком и пропускаете его. Может у Вас текст вшит во что-то "большое" - ну то я не знаю


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 22, 2014, 14:47
А теперь слегка усложним задачу.. Сейчас наш формат допускает наличие комментариев. Комментарий начинается с символа % и заканчивается концом строки.
Теперь, если какая то команда окажется в закоментированном блоке, то предыдущее решение по прежнему выцепит её..  Более того, коменты могут быть и внутри команды, что ещё попортит нам крови при разбиении на токены..
А зачем Вы смешиваете регулярку и токенайзер? Делайте все токенами, тогда сразу получаете токен содержащий коммент целиком и пропускаете его. Может у Вас текст вшит во что-то "большое" - ну то я не знаю
Так не получится.. Вернее получится, но не красиво.. Да, команды вшиты в большой текст, встречаются то здесь, то там..  И комментарии могут быть внутри команд, например:
Код
Bash
text text text \cite{arg1, %comment \n
arg2, % comment \n
...
% comment
}
text text text % \cite {argN} \n
 
 
и т.д..

Вобщем не хочется мне думать при разборе ещё и о комментах..


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 23, 2014, 09:40
Так не получится.. Вернее получится, но не красиво.. Да, команды вшиты в большой текст, встречаются то здесь, то там..  И комментарии могут быть внутри команд, например:
Ну хорошо, надо регуляркой так надо. Тогда почему не так

- найти регуляркой команду (\cite)
- проверить не закомментирована ли она (просмотрев назад, если % да, если eol нет)
- заглотить токенайзером  все что в скобках и извлечь аргументы
- учесть отработанную позицию (}) для след регулярки

Для др комментариев (многострочных) решения нет. Напр если в середине файла непарная кавычка - она покроет все до конца, ну это решается постановкой.

---------------

По поводу концептуальности. Связавшись с тулзом/либой ее остается очень немного  :) Мы должны "принимать без доказательств" что вот это так - потому что оно так сделано и менять это нельзя. Усилия направлены на "прикручивание" т.е. добиться чтобы тулза делала что надо. Такое проектирование практически часто эффективно (но не всегда) и оно ограничено.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 23, 2014, 09:57
Такое проектирование практически часто эффективно (но не всегда) и оно ограничено.
Практически часто редко ограниченно эффективно... :) Зато какие слова весомые.

Понимаете, вот ваш CTextAnalizer очень ограничен, он может делать только то, о чем вы думали в момент его создания. Без его модификации ничего нового он не сделает, ну это и не удивительно, если все засунуто в класс MainWindow, невозможно изменить поведение подсистемы не изменив сам класс.
Удачные решения состоят из кучи маленьких подсистем, которые делают исключительно свою работу. Поэтому, при таком концепте, легко можно изменить поведение одной подсистемы ничего более не меняя в остальной системе.
И совершенно не важно, применяется там буст или стд, можно не используя их писать удобные расширяемые решения. В данном случае мы обсуждаем итератор, в котором используются регулярки из boost, но никто не запрещает вместо них использовать регулярки Qt (или любые другие).

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


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 23, 2014, 10:24
Так не получится.. Вернее получится, но не красиво.. Да, команды вшиты в большой текст, встречаются то здесь, то там..  И комментарии могут быть внутри команд, например:
Ну хорошо, надо регуляркой так надо. Тогда почему не так

- найти регуляркой команду (\cite)
- проверить не закомментирована ли она (просмотрев назад, если % да, если eol нет)
- заглотить токенайзером  все что в скобках и извлечь аргументы
- учесть отработанную позицию (}) для след регулярки

Потому что, это плохой костыльный подход, от которого я изначально хотел избавиться. Этот подход плох тем, что мне придётся переписывать логику разбора текста, захламляя код доп. проверками и т.п.. И всё равно это будет менее эффективно/медленнее работать.. Что если "смотреть назад" придётся очень далеко?
 
Цитировать
Для др комментариев (многострочных) решения нет. Напр если в середине файла непарная кавычка - она покроет все до конца, ну это решается постановкой.
А Вы не путаете цитаты (quote) с комментариями (comment)?


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Igors от Апрель 23, 2014, 12:22
Потому что, это плохой костыльный подход, от которого я изначально хотел избавиться. Этот подход плох тем, что мне придётся переписывать логику разбора текста, захламляя код доп. проверками и т.п.. И всё равно это будет менее эффективно/медленнее работать.. Что если "смотреть назад" придётся очень далеко?
Речь идет о комментах "до конца строки", вычислительные расходы пренебрежимо малы и написать этот "просмотр вверх" - десяток строк. Что касается "идейности" то в упор не понимаю в чем же "костыльность" такого решения? Нужно проверить не экранировано ли найденное? Нужно, вот и делаем (самым простым образом). А так зачем Ваш парсер ловит комменты которые могут быть совсем посторонними?

А Вы не путаете цитаты (quote) с комментариями (comment)?
Здесь нет принципиальной разницы: все это "экранированный" текст который должен быть считан как один токен (и вероятно пропущен)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 23, 2014, 12:41
Речь идет о комментах "до конца строки", вычислительные расходы пренебрежимо малы и написать этот "просмотр вверх" - десяток строк.
Вот-вот.. У вас слегка изменилась задача и вам приходится по всему коду дописывать эти 10 строк с проверками и пр.
Далее, появится поддержка  многострочных комментов.. Будете писать ещё +10 (и если бы 10) строк по всему коду с доп. проверками и т.д..?

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

Что касается "идейности" то в упор не понимаю в чем же "костыльность" такого решения?
Ну здесь только грустный смайлик остаётся поставить(  
Old тоже выше уже писал почему это костыльное/ограниченное  решение..  
Вы вообще читаете предыдущие сообщения?



Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 29, 2014, 13:06
Написал на Spiritе маленький парсер для извлечения из команд (\cite) списка ключей..
С поддержкой комментариев:
Код
C++ (Qt)
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
 
#include <string>
#include <functional>
#include <algorithm>
 
 
template <class Container>
bool add_unique_key(const std::string & key, Container & container)
{
   using namespace std::placeholders;
   if (std::find_if(container.begin(), container.end(), std::bind(std::equal_to<std::string>(), _1, key)) == container.end())
   {
       container.push_back(key);
       return true;
   }
   return false;
}
 
 
template <class ForwardIterator, class Container>
bool key_extractor(ForwardIterator & it, ForwardIterator end, Container & container)
{
   using namespace boost::spirit;
   namespace ph = boost::phoenix;
   namespace sn = boost::spirit::standard_wide;
 
   auto skipper = sn::space | '%' >> *(qi::char_ - (qi::eol | qi::eoi)) >> (qi::eol | qi::eoi);
   qi::rule<ForwardIterator, std::string(), decltype(skipper)> key, expr;
 
   key = +(qi::char_ - qi::char_(",{}%"));
   expr = qi::lexeme[qi::lit("\\cite")] >> '{'
       >>
       key[ ph::bind(&add_unique_key<Container>, qi::_1, ph::ref(container)) ] % ','
       >> '}'
       | qi::char_ ;
 
   return qi::phrase_parse(it, end, *expr, skipper) && (it == end);
}
 
 

И всё бы хорошо, если бы не одно но.. Как заставить skipper отличать начало комментария % от того, что началом комментария не является, т.е. \%
 ???

Или придётся на вход парсера свой skipper_iterator подавать? Не хотелось бы(


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 29, 2014, 13:40
Я сейчас далеко от компьютера, поэтому проверить не могу.
Попробуйте в скипере проверять вначале случай с экранированным % с помощью  оператора '!', тогда правило будет завершаться.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 29, 2014, 13:48
Я сейчас далеко от компьютера, поэтому проверить не могу.
Попробуйте в скипере проверять вначале случай с экранированным % с помощью  оператора '!', тогда правило будет завершаться.

Уже пробовал(  
Но тогда началом коммента также будет считаться: любой символ кроме '\\' за которым следует '%'.
Это плохо, поскольку в следующей ситуации коммент "откусит" предыдущий символ (3):

text, text, \cite{key1, keq2, key3% start comment
, key4}  

Т.е. началом коммента будет 3%, что неправильно(



Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 29, 2014, 14:35
Я имел ввиду проверять именно комбинацию из двух символов '\%'


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 29, 2014, 14:57
Я имел ввиду проверять именно комбинацию из двух символов '\%'
Так тоже не выходит..
Код
C++ (Qt)
nocom = qi::lit("\\%");
skipper = sn::space | !nocom >> '%' >> *(qi::char_ - (qi::eol | qi::eoi)) >> (qi::eol | qi::eoi);
 
По сути, это ни чем не отличается от просто
Код
C++ (Qt)
skipper = sn::space | '%' >> *(qi::char_ - (qi::eol | qi::eoi)) >> (qi::eol | qi::eoi);
 



Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 29, 2014, 15:11
Я думал о чем то таком:

Код
C++ (Qt)
nocom = qi::lit("\\%");
skipper = sn::space | ( !nocom | '%' ) >> *(qi::char_ - (qi::eol | qi::eoi)) >> (qi::eol | qi::eoi);
 

Но можно попробовать добавить правило обработки комментариев в основной парсер вместо скипетра и игнорировать результат.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 29, 2014, 16:50
Думал-думал. Все таки, пока не вижу, как это решить в скипере.
Сейчас попробую решить с правилом в основном правиле.


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 29, 2014, 16:55
Думал-думал. Все таки, пока не вижу, как это решить в скипере.
Сейчас попробую решить с правилом в основном правиле.
Похоже, что в Skipper не решит эту проблему в принципе(

Да, либо в правило в основном правиле или skipper_iterator  :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 29, 2014, 17:02
Да, либо в правило в основном правиле или skipper_iterator  :)
Ох, не к добру это, в двух разных темах в течении 10 минут прозвучало слово итератор. Ох, не к добру. :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 29, 2014, 18:55
Сделал так, вроде работает: :)
Код
C++ (Qt)
qi::symbols<char const, char const> sp_ch;
sp_ch.add( "\\%", '%' );
 
key = +( ( sp_ch | qi::char_ ) - qi::char_(",{}%") );
 


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 29, 2014, 22:53
Работает)
Идея понятна, спасибо)

ещё заменил здесь (последнюю строчку)..
Код
C++ (Qt)
expr = qi::lexeme[qi::lit("\\cite")] >> '{'
       >>
       key[ ph::bind(&add_unique_key<Container>, qi::_1, ph::ref(container)) ] % ','
       >> '}'
       | (sp_ch | qi::char_) ;
 
 


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 29, 2014, 23:07
Хотя в контексте этой проблемы, такое решение больше похоже на хак..  :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: Old от Апрель 30, 2014, 06:39
Хотя в контексте этой проблемы, такое решение больше похоже на хак..  :)
Нам нужно спасти экранированный символ от скипера, мы это и делаем - из потока достаем с экраном, возвращаем без (хотя никто не мешает вернуть и с экраном) - скипер пролетает. :)


Название: Re: Игнорирование комментариев при парсинге.
Отправлено: m_ax от Апрель 30, 2014, 09:20
Океюшки)