web-dev-qa-db-fra.com

"L'arrière-plan à longueur variable n'est pas implémenté" mais ce n'est pas une longueur variable

J'ai un regex très fou que j'essaie de diagnostiquer. Il est également très long, mais je l'ai réduit au script suivant. Exécutez à l'aide de Strawberry Perl v5.26.2.

use strict;
use warnings;

my $text = "M Y H A P P Y T E X T";
my $regex = '(?i)(?<!(Mon|Fri|Sun)day |August )abcd(?-i)';

if ($text =~ m/$regex/){
    print "true\n";
}
else {
    print "false\n";
}

Cela donne l'erreur "Lookbehind de longueur variable non implémenté dans l'expression régulière."

J'espère que vous pourrez aider avec plusieurs problèmes:

  1. Je ne vois pas pourquoi cette erreur se produirait, car toutes les valeurs de recherche possibles sont de 7 caractères: "lundi", "vendredi", "dimanche", "août".
  2. Je n'ai pas écrit cette expression régulière moi-même et je ne sais pas comment interpréter la syntaxe (?i) et (?-i). Quand je me débarrasse du (?i) l'erreur disparaît réellement. Comment Perl interprétera-t-il cette partie de l'expression rationnelle? Je penserais que les deux premiers caractères sont évalués en "parenthèses littérales facultatives" sauf que les parenthèses ne sont pas échappées et dans ce cas, j'obtiendrais une erreur de syntaxe différente car les parenthèses fermantes ne seraient alors pas mises en correspondance.
  3. Ce comportement commence quelque part entre Perl 5.16.3_64 et 5.26.1_64, au moins dans Strawberry Perl. La première version est très bien avec le code, ce dernier ne l'est pas. Pourquoi ça a commencé?
56
Stephen

J'ai réduit votre problème à ceci:

my $text = 'M Y H A P P Y T E X T';
my $regex = '(?<!st)A';
print ($text =~ m/$regex/i ? "true\n" : "false\n");

En raison de la présence de /i (insensible à la casse) et présence de certaines combinaisons de caractères telles que "ss" ou "st" qui peut être remplacé par un Typographic_ligature ce qui en fait une longueur variable (/August/i correspond par exemple à la fois sur AUGUST (6 caractères) et august (5 caractères, le dernier étant U + FB06)).

Cependant, si nous supprimons /i (insensible à la casse), il fonctionne car les ligatures typographiques ne sont pas appariées.

Solution: Utilisez les modificateurs aa c'est-à-dire:

/(?<!st)A/iaa

Ou dans votre regex:

my $text = 'M Y H A P P Y T E X T';
my $regex = '(?<!(Mon|Fri|Sun)day |August )abcd';
print ($text =~ m/$regex/iaa ? "true\n" : "false\n");

De perlre :

Pour interdire les correspondances ASCII/non ASCII (comme "k" avec "\ N {KELVIN SIGN}"), spécifiez deux fois le "a", par exemple /aai ou /aia. (La première occurrence de "a" limite le \d, etc., et la deuxième occurrence ajoute les restrictions "/ i".) Mais, notez que les points de code en dehors de la plage ASCII utiliseront les règles Unicode pour /i correspondance, donc le modificateur ne restreint pas vraiment les choses à seulement ASCII; il interdit simplement le mélange de ASCII et non ASCII .

Voir une discussion étroitement liée ici

76
anubhava

C'est parce que st peut être une ligature. La même chose se produit pour fi et ff:

#!/usr/bin/Perl
use warnings;
use strict;

use utf8;

my $fi = 'fi';
print $fi =~ /fi/i;

Alors imaginez quelque chose comme fi|fi où, en effet, les longueurs des alternatives ne sont pas les mêmes.

21
choroba

st pourrait être représenté en 1 caractère ligature stylistique comme ou , donc sa longueur pourrait être 2 ou 1.

Trouvez rapidement la liste complète de Perl des ligatures à 2 → 1 caractères à l'aide d'une commande bash:

$ Perl -e 'print $^V'
v5.26.2
$ for lig in {a..z}{a..z}; do \
    Perl -e 'print if /(?<!'$lig')x/i' 2>/dev/null || echo $lig; done

ff fi fl ss st

Ils représentent respectivement les ligatures , , , ß Et /.
( Représente ſt, en utilisant le caractère obsolète caractère long s ; il correspond à st et il ne le fait pas pas correspond à ft.)

Perl prend également en charge les ligatures stylistiques restantes, Et Pour ffi et ffl, bien que cela ne soit pas remarquable dans ce contexte car les lookbehinds ont déjà des problèmes avec et / séparément.

Les futures versions de Perl pourraient inclure plus de ligatures stylistiques, bien que tout ce qui reste soit spécifique à la police (par exemple Linux Libertine a des ligatures stylistiques pour ct et ch ) ou discutablement stylistique (comme le néerlandais ij pour ij ou l'espagnol obsolète pour ll). Il ne semble pas approprié d'avoir ce traitement pour les ligatures qui ne sont pas entièrement interchangeables (personne n'accepterait dœs Pour does), bien qu'il existe d'autres scénarios, tels que l'inclusion de ß grâce à son la forme majuscule étant SS .

Perl 5.16.3 (et les anciennes versions similaires) ne tombe que sur ss (pour ß) Et ne parvient pas à développer les autres ligatures dans les lookbehinds (elles ont une largeur fixe et ne correspondront pas). Je n'ai pas cherché le correctif pour détailler exactement quelles versions sont affectées.

Perl 5.14 a introduit le support de ligature, donc les versions antérieures n'ont pas ce problème.

Solutions de contournement

Solutions de contournement pour /(?<!August)x/i (seule la première évitera correctement August):

  • /(?<!Augus[t])(?<!Augu(?=st).)x/i (absolument complet)
  • /(?<!Augu(?aa:st))x/i (juste le st dans le lookbehind est "ASCII-safe" ²)
  • /(?<!(?aa)August)x/i (l'ensemble du lookbehind est "ASCII-safe" ²)
  • /(?<!August)x/iaa (toute l'expression régulière est "ASCII-safe" ²)
  • /(?<!Augus[t])x/i (rompt la recherche de ligature ¹)
  • /(?<!Augus.)x/i (légèrement différent, correspond plus)
  • /(?<!Augu(?-i:st))x/i (sensible à la casse st dans lookbehind, ne correspondra pas à AugusTx)

Ces jouets avec la suppression du modificateur insensible à la casse ¹ ou l'ajout du modificateur ASCII-safe ² à divers endroits, nécessitant souvent que l'auteur de l'expression régulière connaisse spécifiquement la ligature à largeur variable.

La première variation (qui est la seule complète) correspond aux largeurs variables avec deux lookbehinds: d'abord pour la version à six caractères (pas de ligatures comme indiqué dans la première citation ci-dessous) et deuxième pour toutes les ligatures, en utilisant un avant lookahead (qui a une largeur nulle!) pour st (y compris les ligatures), puis en tenant compte de sa largeur de caractère unique avec un .

Deux segments de la page de manuel perlre :

¹ Modificateur insensible à la casse /i & Ligatures

Il existe un certain nombre de caractères Unicode qui correspondent à une séquence de plusieurs caractères sous /i. Par exemple, "LATIN SMALL LIGATURE FI" doit correspondre à la séquence fi. Perl n'est actuellement pas en mesure de le faire lorsque les multiples caractères sont dans le modèle et sont divisés en groupes, ou lorsqu'un ou plusieurs sont quantifiés. Ainsi

"\N{LATIN SMALL LIGATURE FI}" =~ /fi/i;          # Matches [in Perl 5.14+]
"\N{LATIN SMALL LIGATURE FI}" =~ /[fi][fi]/i;    # Doesn't match!
"\N{LATIN SMALL LIGATURE FI}" =~ /fi*/i;         # Doesn't match!
"\N{LATIN SMALL LIGATURE FI}" =~ /(f)(i)/i;      # Doesn't match!

² Modificateur ASCII-safe /aa (Perl 5.14+)

Pour interdire les correspondances ASCII/non ASCII (comme k avec \N{KELVIN SIGN}), Spécifiez deux fois a, par exemple /aai Ou /aia . (La première occurrence de a restreint le \d, Etc., et la deuxième occurrence ajoute les restrictions /i.) Mais, notez que le code pointe en dehors du ASCII utilisera les règles Unicode pour la correspondance /i, Donc le modificateur ne restreint pas vraiment les choses à seulement ASCII; il interdit simplement le mélange de ASCII et non ASCII.

Pour résumer, ce modificateur fournit une protection pour les applications qui ne souhaitent pas être exposées à l'ensemble d'Unicode. Le spécifier deux fois offre une protection supplémentaire.

2
Adam Katz

Mettez (?i) après coup d'oeil:

(?<!(Mon|Fri|Sun)day |August )(?i)abcd(?-i)

ou

(?<!(Mon|Fri|Sun)day |August )(?i:abcd)

Pour moi, cela semble être un bug.

0
Hegel F.