Grupos de equilibrio en look-behind de longitud variable

TL; DR: El uso de capturas (y, en particular, grupos de equilibrio) dentro de las vistas de .NET cambia las capturas obtenidas, aunque no debería marcar la diferencia. ¿Qué pasa con las miradas de .NET que rompe el comportamiento esperado?

Estaba tratando de encontrar una respuesta a esta otra pregunta , como una excusa para jugar con los grupos de equilibrio de .NET. Sin embargo, no puedo hacer que funcionen dentro de un look variable de longitud variable.

En primer lugar, tenga en cuenta que no tengo la intención de utilizar esta solución particular de manera productiva. Es más por razones académicas, porque siento que está sucediendo algo con el aspecto de longitud variable detrás del cual no estoy al tanto. Y saber que podría ser útil en el futuro, cuando realmente necesito usar algo como esto para resolver un problema.

Considere esta entrada:

~(ab (c) d (ef (g) h) i) j (k (l (m) n) p) q 

El objective es hacer coincidir todas las letras, que están entre paréntesis que van precedidas de ~ , no importa qué tan profundo (así que todo va de a a i ). Mi bash fue verificar la posición correcta en un vistazo detrás, para poder obtener todas las letras en una sola llamada a Matches . Aquí está mi patrón:

 (?<=~[(](?:[^()]*|(?[(])|(?[)]))*)[az] 

En el lookbehind trato de encontrar un ~( , y luego utilizo la Depth stack del grupo nombrado para contar paréntesis de apertura extraños. Siempre que el paréntesis se abra en ~( nunca se cierra, el lookbehind debería coincidir. Si el paréntesis de cierre es ese se alcanza, (?...) no se puede extraer nada de la stack y la apariencia debe fallar (es decir, para todas las letras de j ). Desafortunadamente, esto no funciona. En su lugar, hago coincidir a , b , c , e , f , g y m . Entonces solo estos:

 ~(ab (c) _ (ef (g) _) _) _ (_ (_ (m) _) _) _ 

Eso parece significar que la mirada detrás no puede coincidir con nada una vez que he cerrado un paréntesis único, a menos que regrese al nivel más alto de anidación en el que he estado antes.

De acuerdo, esto podría significar que hay algo raro en mi expresión regular, o no entendí los grupos de equilibrio correctamente. Pero luego probé esto sin mirar atrás. Creé una cadena para cada letra como esta:

 ~(zb (c) d (ef (x) y) g) h (i (j (k) l) m) n ~(az (c) d (ef (x) y) g) h (i (j (k) l) m) n ~(ab (z) d (ef (x) y) g) h (i (j (k) l) m) n .... ~(ab (c) d (ef (x) y) g) h (i (j (k) l) z) n ~(ab (c) d (ef (x) y) g) h (i (j (k) l) m) z 

Y usé este patrón en cada uno de ellos:

 ~[(](?:[^()]*|(?[(])|(?[)]))*z 

Y como se desee, todos los casos coinciden, donde z reemplaza una letra entre ay i y todos los casos posteriores fallan.

Entonces, ¿qué efecto tiene el aspecto (de longitud variable) que rompe este uso de los grupos de equilibrio? Traté de investigar esto toda la noche (y encontré páginas como esta ), pero no pude encontrar un solo uso de esto en un vistazo detrás.

También me alegraría si alguien pudiera vincularme con información detallada sobre cómo el motor .NET Regex maneja las características específicas de .NET internamente. Encontré este artículo increíble , pero parece que no entra en la mira (de longitud variable), por ejemplo.

Creo que lo tengo.
Primero, como mencioné en uno de los comentarios, (?< =(?.)(?< -A>.)) Nunca coincide.
Pero luego pensé, ¿qué pasa con (?< =(?<-A>.)(?
.)) ? ¡Hace juego!
¿Y qué tal (?< =(?.)(?.)) ? Coincide con "12" , A captura "1" , y si miramos la colección Captures , es {"2", "1"} – primero dos, luego uno – se invierte.
Entonces, dentro de un lookbehind, .net coincide y captura de derecha a izquierda .

Ahora, ¿cómo podemos hacerlo capturar de izquierda a derecha? Esto es bastante simple, en realidad, podemos engañar al motor usando un lookahead:

 (?< =(?=(?.)(?.))..) 

Aplicado a su patten original, la opción más simple que se me ocurrió fue:

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

El desafío aquí fue que ahora la parte balanceada puede terminar en cualquier lugar, por lo que hacemos que llegue hasta la posición actual (Algo como \G o \Z sería útil aquí, pero no creo que .net lo tenga)

Es muy posible que este comportamiento esté documentado en alguna parte, intentaré buscarlo.

Aquí hay otro enfoque. La idea es simple: .net quiere hacer coincidir de derecha a izquierda? ¡Multa! Toma eso:
(consejo: empieza a leer desde abajo – así es como lo hace .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. | (? \) ) # 1. When seeing a closed paren, add to depth. )* ) \w # Match your letter 

Creo que el problema está en los datos y no en el patrón. Los datos tienen elementos ‘Post’ que deben coincidir, como

(a B C D e F )

donde de y f son necesarios para coincidir. Unos datos más equilibrados serían

(a B C D e F))


Así que la táctica que tomé en este ejemplo de datos requirió una situación posterior a la coincidencia después de los apoyos:

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

donde j & k debería ser ignorado … mi patrón falló y los capturó.

Lo interesante es que nombré los grupos de capturas para averiguar dónde entraron yj yk entraron en la captura tres. Te dejo con, no una respuesta, sino un bash de ver si puedes mejorarla.

 (~ # Anchor to a Tilde ( # Note that \x28 is ( and \x29 is ) ( # --- PRE --- (?\x28)+ # Push on a match into Paren ((?[^\x28\x29])(?:\s?))* )+ # Represents Sub Group 1 ( #---- Closing ((?[^\x28\x29])(?:\s?))* (?< -Paren>\x29)+ # Pop off a match from Paren )+ ( ((?[^\x28\x29])(?:\s?))* # Post match possibilities )+ )+ (?(Paren)(?!)) # Stop after there are not parenthesis ) 

Aquí está el partido roto con una herramienta que he creado por mi cuenta (tal vez algún día publique). Tenga en cuenta que ˽ muestra dónde se hizo coincidir un espacio.

 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