Захват квантификаторов и арифметика квантификаторов

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

Захват кванторов

Кто-нибудь знает, может ли аромат регулярного выражения захватывать квантификаторы? Под этим я подразумеваю, что количество символов, совпадающих с кванторами, такими как + и *, будет подсчитано и что это число может быть использовано снова в другом квантере.

Например, предположим, что вы хотели удостовериться, что у вас есть такое же количество Ls и R в такой строке: LLLRRRRR

Можно представить себе такой синтаксис, как

L(+)R{\q1}

где квант + для L захватывается и где захваченное число упоминается в кванторе для R как {\ q1}

Было бы полезно сбалансировать количество {@, =, -,/} в строках, таких как @@@@ "Звездные войны" ==== "1977" ---- "Научная фантастика" //// "Джордж Лукас"

Отношение к рекурсии

В некоторых случаях захват квантора мог бы элегантно заменить рекурсию, например, часть текста, обрамленная тем же числом Ls и Rs, a в

L(+) some_content R{\q1} 

Идея представлена ​​в некоторых деталях на следующей странице: Захваченные кванторы

В нем также обсуждается естественное расширение захваченных квантификаторов: арифметика квантификатора, в случаях, когда вы хотите сопоставить (3 * x + 1) количество символов, сопоставленных ранее.

Я пытаюсь выяснить, существует ли что-то подобное.

Заранее благодарим за понимание!

Обновление

Казимир дал фантастический ответ, который показывает два метода для проверки того, что различные части шаблона имеют одинаковую длину. Тем не менее, я бы не хотел полагаться ни на одно из них на повседневную работу. Это действительно трюки, которые демонстрируют отличную демонстрацию. На мой взгляд, эти красивые, но сложные методы подтверждают предпосылку вопроса: функция регулярного выражения для захвата числа символов, которые могут быть найдены в количественном выражении (например, + или *), сделает такие балансировочные шаблоны очень простыми и расширит синтаксис в приятный выразительный способ.

Обновление 2 (намного позже)

Я узнал, что у .NET есть функция, которая приближается к тому, о чем я спрашивал. Добавлен ответ, чтобы продемонстрировать эту функцию.

Ответ 1

Я не знаю механизм регулярных выражений, который может захватить квантификатор. Тем не менее, с помощью PCRE или Perl можно использовать некоторые трюки, чтобы проверить, есть ли у вас одинаковое количество символов. В вашем примере:

@@@@ "Star Wars" ==== "1977" ---- "Science Fiction" //// "George Lucas"

вы можете проверить, сбалансированы ли @ = - / с этим шаблоном, который использует знаменитый трюк Qtax, (готовы ли вы?): "собственнически-необязательная группа саморегуляции"
~(?<[email protected])((?:@(?=[^=]*(\2?+=)[^-]*(\3?+-)[^/]*(\4?+/)))+)([email protected])(?=[^=]*\2(?!=)[^-]*\3(?!-)[^/]*\4(?!/))~

подробнее:

~                          # pattern delimiter
(?<[email protected])                     # negative lookbehind used as an @ boundary
(                          # first capturing group for the @
    (?:
        @                  # one @
        (?=                # checks that each @ is followed by the same number
                           # of = - /  
            [^=]*          # all that is not an =
            (\2?+=)        # The possessive optional self-referencing group:
                           # capture group 2: backreference to itself + one = 
            [^-]*(\3?+-)   # the same for -
            [^/]*(\4?+/)   # the same for /
        )                  # close the lookahead
    )+                     # close the non-capturing group and repeat
)                          # close the first capturing group
([email protected])                      # negative lookahead used as an @ boundary too.

# this checks the boundaries for all groups
(?=[^=]*\2(?!=)[^-]*\3(?!-)[^/]*\4(?!/))
~

Основная идея

Группа, не участвующая в записи, содержит только один @. Каждый раз, когда эта группа повторяется, в группы захвата 2, 3 и 4 добавляется новый символ.

собственнически-необязательная самореферентная группа

Как это работает?

( (?: @ (?= [^=]* (\2?+ = ) .....) )+ )

При первом появлении символа @группа захвата 2 еще не определена, поэтому вы не можете написать что-то вроде этого (\2 =), которое приведет к сбою шаблона. Чтобы избежать этой проблемы, нужно сделать необязательным backreference: \2?

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

Обратите внимание, что эта группа может быть видна следующим образом: , если существует группа 2, а затем сопоставить ее со следующим =

( (?(2)\2) = )

Рекурсивный путь

~(?<[email protected])(?=(@(?>[^@=]+|(?-1))*=)(?!=))(?=(@(?>[^@-]+|(?-1))*-)(?!-))(?=(@(?>[^@/]+|(?-1))*/)(?!/))~

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

подробнее:

(?<[email protected])                # left @ boundary
(?=                   # open a lookahead (to allow overlapped matches)
    (                 # open a capturing group
        @
        (?>           # open an atomic group
            [^@=]+    # all that is not an @ or an =, one or more times
          |           # OR
            (?-1)     # recursion: the last defined capturing group (the current here)
        )*            # repeat zero or more the atomic group
        =             #
    )                 # close the capture group
    (?!=)             # checks the = boundary
)                     # close the lookahead
(?=(@(?>[^@-]+|(?-1))*-)(?!-))  # the same for -
(?=(@(?>[^@/]+|(?-1))*/)(?!/))  # the same for /

Основное отличие от шаблона прецедента состоит в том, что это не заботится о порядке групп = - и /. (Однако вы можете легко внести некоторые изменения в первый шаблон, чтобы справиться с этим, с классами символов и отрицательными образами.)

Примечание. Для примерной строки, если быть более конкретным, вы можете заменить отрицательный lookbehind на якорь (^ или \A). И если вы хотите получить целую строку в качестве результата соответствия, вы должны добавить .* в конец (иначе результат совпадения будет пустым, так как игривый замечает его).

Ответ 2

Возвращаясь спустя пять недель, потому что я узнал, что .NET имеет нечто, близкое к идее "захвата квантора", упомянутого в вопросе. Эта функция называется "балансировочные группы".

Вот решение, с которым я столкнулся. Он выглядит длинным, но это довольно просто.

(?:@(?<c1>)(?<c2>)(?<c3>))+[^@=]+(?<-c1>=)+[^=-]+(?<-c2>-)+[^-/]+(?<-c3>/)+[^/]+(?(c1)(?!))(?(c2)(?!))(?(c3)(?!))

Как это работает?

  • Первая группа, не связанная с захватом, соответствует символам @. В этой группе, не связанной с захватом, у нас есть три именованные группы c1, c2 и c3, которые не соответствуют чему-либо, или, вернее, соответствуют пустой строке. Эти группы будут служить тремя счетчиками c1, c2 и c3. Поскольку .NET отслеживает промежуточные захваты, когда группа количественно определена, каждый раз, когда сопоставляется @, в коллекции захвата добавляются записи для групп c1, c2 и c3.

  • Далее, [^@=]+ съедает все символы до первого =.

  • Вторая квантифицированная группа (?<-c1>=)+ соответствует символам =. Кажется, что эта группа называется -c1, но -c1 не является именем группы. Синтаксис -c1 is.NET, чтобы поместить один захват из коллекции захвата группы c1 в эфир. Другими словами, это позволяет нам уменьшить c1. Если вы попытаетесь уменьшить c1, когда коллекция захвата пуста, совпадение не выполняется. Это гарантирует, что у нас никогда не будет больше символов =, чем @. (Позже мы должны убедиться, что мы не можем иметь больше @, чем символы =.)

  • Следующие шаги повторяют шаги 2 и 3 для символов - и /, уменьшая счетчики c2 и c3.

  • [^/]+ поглощает остальную часть строки.

  • (?(c1)(?!)) - это условие, которое гласит: "Если группа c1 была установлена, то сбой". Вы можете знать, что (?!) - общий трюк, чтобы заставить регулярное выражение терпеть неудачу. Это условие гарантирует, что c1 был декрементирован вплоть до нуля: другими словами, не может быть больше @, чем = символов.

  • Аналогично, (?(c2)(?!)) и (?(c3)(?!)) гарантируют, что не может быть больше @, чем символы - и /.

Я не знаю о вас, но даже это немного длиннее, я нахожу его действительно интуитивным.