Улучшение/исправление регулярного выражения для комментариев к блоку стиля C

Я пишу (в С#) простой парсер для обработки языка сценариев, который очень похож на классический C.

В одном файле script у меня есть регулярное выражение, которое я использую, чтобы распознавать /* блокировать комментарии */, вступает в какой-то бесконечный цикл, принимая на себя 100% -ый процессор в течение многих лет.

Я использую Regex:

/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/

Любые предложения о том, почему это может быть заблокировано?

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

Дополнительная информация:

  • Работа в С# 3.0 с таргетингом на .NET 3.5;
  • Я использую метод Regex.Match(string, int) для начала сопоставления по определенному индексу строки;
  • Я оставил программу более часа, но совпадение не завершено;
  • Параметры, переданные конструктору Regex, RegexOptions.Multiline и RegexOptions.IgnorePatternWhitespace;
  • Регулярное выражение работает правильно для 452 моих 453 тестовых файлов.

Ответ 1

Некоторые проблемы, которые я вижу с вашим регулярным выражением:

Нет необходимости в последовательностях |[\r\n] в вашем регулярном выражении; отрицательный класс символов, такой как [^*], соответствует всем, кроме *, включая разделители строк. Это только метасимвол . (точка), который не соответствует этим.

Как только вы попадете в комментарий, единственным символом, который вам нужно найти, является звездочка; пока вы не видите одного из них, вы можете сожрать столько символов, сколько хотите. Это означает, что нет смысла использовать [^*], когда вы можете использовать [^*]+ вместо этого. На самом деле вы можете также добавить это в атомную группу - (?>[^*]+) - потому что у вас никогда не будет причин отказаться от каких-либо из этих нестероидов, как только вы их сопоставите.

Отфильтровывая посторонний мусор, конечная альтернатива внутри ваших внешних парнеров \*+[^*/], что означает "одна или несколько звездочек, за которыми следует символ, который не является звездочкой или косой чертой". Это всегда будет соответствовать звездочке в конце комментария, и ей всегда придется отбрасывать ее снова, потому что следующий символ - косая черта. На самом деле, если есть двадцать звездочек, ведущих к финальной косой чертой, эта часть вашего регулярного выражения будет соответствовать всем этим, тогда она будет давать им все, один за другим. Тогда конечная часть - \*+/ - будет соответствовать им для сохранения.

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

/\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/

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


Предоставлено David, здесь версия, соответствующая вложенным комментариям с любым уровнем вложенности:

(?s)/\*(?>/\*(?<LEVEL>)|\*/(?<-LEVEL>)|(?!/\*|\*/).)+(?(LEVEL)(?!))\*/

Он использует .NET Balancing Groups, поэтому он не будет работать ни в каком другом вкусе. Для полноты, здесь другая версия (из библиотеки RegexBuddy), которая использует синтаксис рекурсивных групп, поддерживаемый Perl, PCRE и Oniguruma/Onigmo:

/\*(?>[^*/]+|\*[^/]|/[^*])*(?>(?R)(?>[^*/]+|\*[^/]|/[^*])*)*\*/

Ответ 2

Нет, нет! Кто-нибудь еще не читал "Освоение регулярных выражений" (3-е издание)!? В этом случае Джеффри Фридл рассматривает эту точную проблему и использует ее в качестве примера (страницы 272-276), чтобы проиллюстрировать его метод "разворачивания в петлю". Его решение для большинства двигателей регулярных выражений выглядит так:

/\*[^*]*\*+(?:[^*/][^*]*\*+)*/

Однако, если механизм регулярных выражений оптимизирован для обработки ленивых кванторов (например, Perl is), то наиболее эффективное выражение намного проще (как было предложено выше):

/\*.*?\*/

(с эквивалентной "s" точкой совпадает со всеми применяемыми модификаторами.) Обратите внимание, что я не использую .NET, поэтому не могу сказать, какая версия для этого движка быстрее.

Ответ 3

Вы можете попробовать вариант Singleline, а не Multiline, тогда вам не нужно беспокоиться о \r\n. С этой возможностью следующие работали для меня с простым тестом, который включал комментарии, которые охватывали более одной строки:

/\*.*?\*/

Ответ 4

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

Если базовое предположение состоит в том, чтобы сопоставить все с "/*", пока не встретится первый "*/", тогда один из способов сделать это будет таким (как обычно, регулярное выражение не подходит для вложенных структур, поэтому вложение комментариев блока не работает):

/\*(.(?!\*/))*.?\*/             // run this in single line (dotall) mode

По существу это говорит: "/*", за которым следует то, за чем не следует "*/", за которым следует "*/".

В качестве альтернативы вы можете использовать более простое:

/\*.*?\*/                       // run this in single line (dotall) mode

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

Ответ 5

Я использую это в данный момент

\/\*[\s\S]*?\*\/