Балансирующие группы в виде переменной длины

TL;DR: использование захвата (и, в частности, групп балансировки) внутри видимости.NET изменяет полученные захваты, хотя это не должно иметь значения. Что с внешними взглядами.NET нарушает ожидаемое поведение?

Я пытался придумать ответ на этот другой вопрос в качестве предлога, чтобы поиграться с балансирующими группами.NET. Тем не менее, я не могу заставить их работать внутри вида переменной длины.

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

Рассмотрим этот вход:

~(a b (c) d (e f (g) h) i) j (k (l (m) n) p) q

Цель состоит в том, чтобы сопоставить все буквы, которые находятся внутри скобок, которым предшествует ~, неважно, насколько глубоко (так что все из a в i). Моя попытка состояла в том, чтобы проверить правильность позиции во взгляде сзади, чтобы я мог получить все буквы за один звонок Matches, Вот мой образец:

(?<=~[(](?:[^()]*|(?<Depth>[(])|(?<-Depth>[)]))*)[a-z]

Во взгляде сзади я пытаюсь найти ~(, а затем я использую стек именованной группы Depth посчитать лишние открывающие скобки. Пока скобка открыта в ~( никогда не закрывается, взгляд должен совпадать. Если закрывающая скобка к этому достигнута, (?<-Depth>...) не может вытолкнуть что-либо из стека, и просмотр позади должен потерпеть неудачу (то есть для всех писем из j). К сожалению, это не работает. Вместо этого я соответствую a, b, c, e, f, g а также m, Так что только эти:

~(a b (c) _ (e f (g) _) _) _ (_ (_ (m) _) _) _

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

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

~(z b (c) d (e f (x) y) g) h (i (j (k) l) m) n
~(a z (c) d (e f (x) y) g) h (i (j (k) l) m) n
~(a b (z) d (e f (x) y) g) h (i (j (k) l) m) n
....
~(a b (c) d (e f (x) y) g) h (i (j (k) l) z) n
~(a b (c) d (e f (x) y) g) h (i (j (k) l) m) z

И использовал этот шаблон на каждом из них:

~[(](?:[^()]*|(?<Depth>[(])|(?<-Depth>[)]))*z

И по желанию все случаи совпадают, где z заменяет букву между a а также i и все дела после этого терпят неудачу.

Так что же делает (переменная длина) внешний вид, который нарушает это использование балансировочных групп? Я пытался исследовать это весь вечер (и нашел страницы, подобные этой), но я не смог найти ни одного использования этого во взгляде.

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

2 ответа

Решение

Я думаю, что понял.
Во-первых, как я уже упоминал в одном из комментариев, (?<=(?<A>.)(?<-A>.)) никогда не совпадает
Но потом я подумал, что насчет (?<=(?<-A>.)(?<A>.))? Это соответствует!
А как насчет (?<=(?<A>.)(?<A>.))? Сопоставлено с "12", A захватывает "1"и если мы посмотрим на Captures коллекция, это {"2", "1"} - сначала два, потом один - все наоборот.
Таким образом, находясь внутри области просмотра,.net сопоставляется и захватывает справа налево.

Теперь, как мы можем сделать захват слева направо? На самом деле это довольно просто - мы можем обмануть движок, посмотрев вперед:

(?<=(?=(?<A>.)(?<A>.))..)

Применительно к вашему оригинальному шаблону, самый простой вариант, который я придумал, был:

(?<=
    ~[(]
    (?=
        (?:
            [^()]
            |
            (?<Depth>[(])
            |
            (?<-Depth>[)])
        )*
        (?<=(\k<Prefix>))   # Make sure we matched until the current position
    )
    (?<Prefix>.*)           # This is captured BEFORE getting to the lookahead
)
[a-z]

Проблема заключалась в том, что теперь сбалансированная часть может заканчиваться где угодно, поэтому мы заставляем ее доходить до текущей позиции (что-то вроде \G или же \Z было бы полезно здесь, но я не думаю, что.net имеет это)

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

Вот другой подход. Идея проста -.net хочет совпадать справа налево? Отлично! Возьми это:
(совет: начните читать снизу - вот как это делает.net)

(?<=
    (?(Depth)(?!))  # 4. Finally, make sure there are no extra closed parentheses.
    ~\(
    (?>                     # (non backtracking)
        [^()]               # 3. Allow any other character
        |
        \( (?<-Depth>)?     # 2. When seeing an open paren, decreace depth.
                            #    Also allow excess parentheses: '~((((((a' is OK.
        |
        (?<Depth>  \) )     # 1. When seeing a closed paren, add to depth.
    )*
)
\w                          # Match your letter

Я думаю, что проблема с данными, а не с шаблоном. Данные имеют элементы "Post", которые должны быть сопоставлены, такие как

(ab (c) def)

где де и е должны быть сопоставлены. Более сбалансированные данные будут

(ab (c) (d) (e) (f))


Таким образом, уловка, которую я взял на этом примере данных, требовала ситуации после матча после фигурных скобок:

~ (ab (c) d (ef (g) h) i) jk

где J & K следует игнорировать... мой шаблон не удалось и захватил их.

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

(~                         # Anchor to a Tilde
 (                         # Note that \x28 is ( and \x29 is )      
  (                          # --- PRE ---
     (?<Paren>\x28)+          # Push on a match into Paren
     ((?<Char1>[^\x28\x29])(?:\s?))*
   )+                         # Represents Sub Group 1
  (                           #---- Closing
   ((?<Char2>[^\x28\x29])(?:\s?))*
   (?<-Paren>\x29)+           # Pop off a match from Paren

  )+  
  (
     ((?<Char3>[^\x28\x29])(?:\s?))*   # Post match possibilities
  )+

 )+
(?(Paren)(?!))    # Stop after there are not parenthesis    
)

Вот совпадение с инструментом, который я создал сам (возможно, однажды я опубликую). Обратите внимание, что ˽ показывает, где пробел был сопоставлен.

Match #0
               [0]:  ~(a˽b˽(c)˽d˽(e˽f˽(g)˽h)˽i)˽j˽k
       ["1"] → [1]:  ~(a˽b˽(c)˽d˽(e˽f˽(g)˽h)˽i)˽j˽k
       →1 Captures:  ~(a˽b˽(c)˽d˽(e˽f˽(g)˽h)˽i)˽j˽k
       ["2"] → [2]:  (e˽f˽(g)˽h)˽i)˽j˽k
       →2 Captures:  (a˽b˽(c)˽d˽, (e˽f˽(g)˽h)˽i)˽j˽k
       ["3"] → [3]:  (g
       →3 Captures:  (a˽b˽, (c, (e˽f˽, (g
       ["4"] → [4]:  g
       →4 Captures:  a˽, b˽, c, e˽, f˽, g
       ["5"] → [5]:  ˽i)
       →5 Captures:  ), ), ˽h), ˽i)
       ["6"] → [6]:  i
       →6 Captures:  ˽, h, ˽, i
       ["7"] → [7]:  
       →7 Captures:  ˽d˽, , ˽j˽k, 
       ["8"] → [8]:  k
       →8 Captures:  ˽, d˽, ˽, j˽, k
   ["Paren"] → [9]:  
  ["Char1"] → [10]:  g
      →10 Captures:  a, b, c, e, f, g
  ["Char2"] → [11]:  i
      →11 Captures:  ˽, h, ˽, i
  ["Char3"] → [12]:  k
      →12 Captures:  ˽, d, ˽, j, k
Другие вопросы по тегам