Почему некоторые движки регулярных выражений совпадают. * Дважды в одной входной строке? - PullRequest
0 голосов
/ 16 сентября 2018

Многие движки регулярных выражений соответствуют .* дважды в одной строке, например, при выполнении замены строки на основе регулярных выражений:

  • 1-е совпадение - по определению- вся (однострочная) строка, как и ожидалось.
  • Во многих движках есть 2-е совпадение, а именно пустая строка ;то есть, хотя 1-е совпадение использовало всю входную строку, .* снова сопоставляется снова , что затем соответствует пустой строке в конце входной строки.

    • Примечание: Чтобы убедиться, что найдено только одно соответствие, используйте ^.*

Мои вопросы:

  • Есть ли веская причина для такого поведения?Как только входная строка будет использована полностью, я не ожидаю еще одной попытки найти совпадение.

  • Кроме проб и ошибок, вы можете почерпнуть из документации / regex dialect /поддерживаемые стандартом, какие двигатели демонстрируют такое поведение?

Обновление : полезный ответ Revo объясняет как изтекущее поведение;Что касается потенциала , почему , см. этот связанный вопрос .

Языки / платформы, которые ДЕЙСТВУЮТ поведение:

 # .NET, via PowerShell (behavior also applies to the -replace operator)
 PS> [regex]::Replace('a', '.*', '[$&]'
 [a][]  # !! Note the *2* matches, first the whole string, then the empty string

 # Node.js
 $ node -pe "'a'.replace(/.*/g, '[$&]')"
 [a][]

 # Ruby
 $ ruby -e "puts 'a'.gsub(/.*/, '[\\0]')"
 [a][]

 # Python 3.7+ only
 $ python -c "import re; print(re.sub('.*', '[\g<0>]', 'a'))"
 [a][] 

 # Perl 5
 $ echo a | perl -ple 's/.*/[$&]/g'
 [a][] 

 # Perl 6
 $ echo 'a' | perl6 -pe 's:g/.*/[$/]/'
 [a][]

 # Others?

Языки / платформы, на которых НЕ проявляется такое поведение:

# Python 2.x and Python 3.x <= 3.6
$ python -c "import re; print(re.sub('.*', '[\g<0>]', 'a'))"
[a]  # !! Only 1 match found.

# Others?

bobble bubble поднимает несколько полезных вопросов:

Если вы сделаете этоленивый, как .*?, вы можете получить 3 совпадений в некоторых и 2 совпадений в других .То же самое с .??.Как только мы используем начальный якорь, я думал, что мы должны получить только одно совпадение, но интересно, что ^.*? дает два совпадения в PCRE для a, тогда как ^.* должно привести к одному совпадениюповсюду.


Вот фрагмент PowerShell для тестирования поведения на разных языках с несколькими регулярными выражениями:

Примечание: Предполагаетсячто Python 3.x доступен как python3, а Perl 6 - как perl6.
Вы можете вставить весь фрагмент непосредственно в командную строку и вызвать его из истории для изменения входных данных.

& {
  param($inputStr, $regexes)

  # Define the commands as script blocks.
  # IMPORTANT: Make sure that $inputStr and $regex are referenced *inside "..."*
  #            Always use "..." as the outer quoting, to work around PS quirks.
  $cmds = { [regex]::Replace("$inputStr", "$regex", '[$&]') },
          { node -pe "'$inputStr'.replace(/$regex/g, '[$&]')" },
          { ruby -e "puts '$inputStr'.gsub(/$regex/, '[\\0]')" },
          { python -c "import re; print(re.sub('$regex', '[\g<0>]', '$inputStr'))" },
          { python3 -c "import re; print(re.sub('$regex', '[\g<0>]', '$inputStr'))" },
          { "$inputStr" | perl -ple "s/$regex/[$&]/g" },
          { "$inputStr" | perl6 -pe "s:g/$regex/[$/]/" }

  $regexes | foreach {
    $regex = $_
    Write-Verbose -vb "----------- '$regex'"
    $cmds | foreach { 
      $cmd = $_.ToString().Trim()
      Write-Verbose -vb ('{0,-10}: {1}' -f (($cmd -split '\|')[-1].Trim() -split '[ :]')[0], 
                                           $cmd -replace '\$inputStr\b', $inputStr -replace '\$regex\b', $regex)
      & $_ $regex
    }
  }

} -inputStr 'a' -regexes '.*', '^.*', '.*$', '^.*$', '.*?'

Пример вывода для регулярного выражения ^.*, который подтверждает ожидание всплывающего пузырька, что при использовании начального якоря (^) выдается только один совпадение на всех языках:

VERBOSE: ----------- '^.*'
VERBOSE: [regex]   : [regex]::Replace("a", "^.*", '[$&]')
[a]
VERBOSE: node      : node -pe "'a'.replace(/^.*/g, '[$&]')"
[a]
VERBOSE: ruby      : ruby -e "puts 'a'.gsub(/^.*/, '[\\0]')"
[a]
VERBOSE: python    : python -c "import re; print(re.sub('^.*', '[\g<0>]', 'a'))"
[a]
VERBOSE: python3   : python3 -c "import re; print(re.sub('^.*', '[\g<0>]', 'a'))"
[a]
VERBOSE: perl      : "a" | perl -ple "s/^.*/[$&]/g"
[a]
VERBOSE: perl6     : "a" | perl6 -pe "s:g/^.*/[$/]/"
[a]

1 Ответ

0 голосов
/ 16 сентября 2018

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

Как только входная строка будет использована полностью, с какой стати вы должны рассматривать тот факт, что в качестве пустой строки ничего не осталось?

Позиция, называемая конец строки темы , оставлена ​​. Это позиция и может быть сопоставлена. Как и другие утверждения и якоря нулевой ширины \b, \B, ^, $ ..., которые утверждают, точка-звезда .* может соответствовать пустой строке. Это сильно зависит от регулярных выражений двигателя. Например. TRegEx делает это по-другому.

А если да, разве это не должно привести к бесконечному циклу?

Нет, это одна из основных задач двигателей регулярных выражений. Они поднимают флаг и хранят текущие данные курсора, чтобы избежать возникновения таких циклов. Perl docs объясните это так :

Распространенное злоупотребление этой силой проистекает из способности делать бесконечное циклы, использующие регулярные выражения, с чем-то таким безобидным, как:

'foo' =~ m{ ( o? )* }x;

o? совпадает в начале foo и с позиции в строка не перемещается при совпадении, o? будет соответствовать снова и снова из-за квантификатора *. Еще один распространенный способ создания аналога цикл с модификатором цикла /g ...

Таким образом, Perl допускает такие конструкции, насильно ломая бесконечность петля . Правила для этого различны для петель более низкого уровня, заданных жадные квантификаторы *+{}, а для более высоких уровней, таких как /g модификатор или split() оператор.

Циклы нижнего уровня прерываются (то есть цикл прерывается) когда Perl обнаруживает, что повторяющееся выражение соответствует подстроке нулевой длины.

Теперь вернемся к вашим вопросам:

Есть ли веская причина для такого поведения?

Да, есть. Каждый движок регулярных выражений должен решать значительное количество задач для обработки текста. Один из которых касается совпадений нулевой длины . Ваш вопрос поднимает другой вопрос,

В: Как должен работать двигатель после сопоставления строки нулевой длины?

A: Все зависит.

PCRE (или Ruby здесь) не пропускает совпадения нулевой длины.

Это соответствует, затем поднимает флаг, чтобы снова не совпадать с той же позицией с (то же самое)? рисунок . В PCRE .* соответствует всей строке темы, затем останавливается сразу после нее. Находясь в конце, текущая позиция является значимой позицией в PCRE, позиции могут быть сопоставлены или подтверждены, поэтому остается позиция (строка нулевой длины), которую нужно сопоставить. PCRE снова проходит через регулярное выражение (если модификатор g включен) и находит совпадение в конце темы.

Затем PCRE пытается перейти к следующей немедленной позиции, чтобы снова запустить весь процесс, но он терпит неудачу, поскольку позиции не осталось.

Если вы хотите предотвратить повторение второго матча, вам нужно каким-то образом сообщить двигателю:

^.*

Или для лучшего понимания происходящего:

(?!$).*

См. живое демо здесь специально взгляните на окно отладчика .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...