Я пытаюсь извлечь /*... */
комментарии стиля из исходных файлов, используя std :: regex. Но "regex_search" иногда приводит к сбоям (необработанное исключение) при длинных совпадениях, охватывающих несколько строк.
Пример STD (не работает)
Этот пример падает для меня:
#include <iostream>
#include <regex>
int main()
{
std::string in = "/*\naaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaa\n*/";
std::regex e(".*/\\*(\n|.)*?\\*/");
std::smatch m;
while (std::regex_search(in, m, e))
{
std::cout << m[0].str() << std::endl;
in = m.suffix();
}
return 0;
}
Я использую Visual Studio 2013, поэтому это может быть проблемой, связанной с компилятором.
Изменить. Как отмечал @TC в комментариях, код работает под GCC 4.9, и он выдает исключение. Это может быть проблемой только для компилятора Visual C++, или просто потому, что GCC выделяет больший стек.
Пример Qt (рабочий)
Я пробовал реализовать то же самое в Qt без проблем, поэтому я не думаю, что допустил ошибки. Но я бы очень хотел не полагаться на какие-либо внешние библиотеки.
QRegularExpression re(".*/\\*(\n|.)*?\\*/");
QRegularExpressionMatchIterator it = re.globalMatch(QString(in.c_str()));
while (it.hasNext())
{
QRegularExpressionMatch match = it.next();
QString word = match.captured(0);
}
Вопрос
Возможно, это ошибка в реализации std :: regex? Я допустил ошибку?
Я думаю, что это не проблема компилятора (если вы не используете gcc <4.9). Крушение регулярного выражения, потому что количество шагов для получения результата слишком велико. Попробуйте сделать то же самое с этим шаблоном:
/\\*[\\s\\S]*?\\*/
или с этим, который эмулирует притяжательный квантор:
/\\*(?=((?:[^*]+|\\*(?!/))*))\\1\\*/
(Эти два шаблона предназначены для работы с режимом ECMAScript, то есть, если я не ошибаюсь, режим по умолчанию вашего механизма регулярных выражений)
о вашем оригинальном шаблоне:
Первая ошибка заключается в том, чтобы начать свой шаблон с помощью .*
(Это не требуется, так как вы используете метод regex_search
). Поскольку квантификатор по умолчанию жадный, этот первый подшаблон будет соответствовать всем символам до конца каждой строки. После получения соответствия движок регулярного выражения должен возвращать символ по символу, пока не найдет /*
в строке (обратите внимание, что если у вас несколько /*
в одной строке, будет найдено только последнее).
Вторая ошибка - использовать что-то вроде (\\n|.)*?
для описания всех символов до следующего (т.е. */
).
Использование такого типа конструкции имеет несколько затрат:
.
будет соответствовать, а \\n
будет тестироваться ничем (однако это зависит от того, как выглядят ваши комментарии, но запись (?:.|\\n)*?
может быть более эффективной).(?:a)+
a+
(?:a)+
может быть в 150 раз медленнее, чем a+
На вопрос, который вы задаете в комментариях, я дам вам общий ответ.
Да, количество шагов или шагов отступлений где-то ограничено. Если механизм регулярных выражений достаточно умен, во время предварительного анализа он может обнаружить, что шаблон заставит слишком много работать, прежде чем пытаться что-то сделать, но это не всегда так.
Чтобы точно знать, что происходит, вы можете поместить свой шаблон регулярного выражения в блок try/catch и проверить эти две ошибки:
if (e.code() == std::regex_constants::error_complexity)
std::cerr << "The complexity of an attempted match against a regular expression exceeded a pre-set level.\n";
else if (e.code() == std::regex_constants::error_stack)
std::cerr << "There was insufficient memory to determine whether the regular expression could match the specified character sequence.\n";
regex_error
и позволить пользователю восстановиться.
Хех. Недавно у меня была такая же проблема с моим статическим аналитическим кодом. Итак, вот решение, хотя оно и полагается на стороннюю библиотеку (мой):
// http://www.benhanson.net/lexertl.html
#include <lexertl/generator.hpp>
#include <lexertl/iterator.hpp>
int main()
{
std::string in = "/*\naaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaa\n*/";
lexertl::rules rules;
lexertl::state_machine sm;
rules.push("[/][*](\n|.)*?[*][/]", 1);
rules.push(".|\n", rules.skip());
lexertl::generator::build(rules, sm);
lexertl::citerator iter(in.c_str(), in.c_str() + in.size(), sm);
lexertl::citerator end;
for (; iter != end; ++iter)
{
std::cout << iter->str() << std::endl;
}
return 0;
}
std::regex e(".*/\\*[\\x00-\\xff]*?\\*/");
тоже работает
"/\\*(\n|.)*\\*/"
должно делать то же самое, я не прав?.*
выражения не важен для примера, но я использовал его, потому что хотел уловить уровень отступа комментария. А если я пропущу?
часть, то я думаю, что это будет захватывать несколько комментариев, таких как/* ... */ /* ... */
как один огромный комментарий.