web-dev-qa-db-fra.com

Erreur lors de la compilation d'une expression verbale Java regex avec classe de caractères et limite Word

Pourquoi ce modèle ne parvient-il pas à être compilé:

Pattern.compile("(?x)[ ]\\b");

Erreur

ERROR Java.util.regex.PatternSyntaxException:
Illegal/unsupported escape sequence near index 8
(?x)[ ]\b
        ^
at Java_util_regex_Pattern$compile.call (Unknown Source)

Alors que les équivalents suivants fonctionnent?

Pattern.compile("(?x)\\ \\b");
Pattern.compile("[ ]\\b");
Pattern.compile(" \\b");

Est-ce un bogue dans le compilateur regex Java, ou est-ce que je manque quelque chose? J'aime utiliser [ ] en regex verbeux au lieu de backslash-backslash-space car il économise du bruit visuel. Mais apparemment, ce ne sont pas les mêmes!

PS: ce problème ne concerne pas les barres obliques inverses. Il s'agit d'échapper des espaces dans une expression rationnelle en utilisant une classe de caractères contenant un seul espace [ ] au lieu d'utiliser une barre oblique inverse.

D'une certaine manière, la combinaison de regex verbeux (?x) et une classe de caractères contenant un seul espace [ ] désactive le compilateur et lui fait ignorer l'échappement de limite de Word \b


Testé avec Java jusqu'à 1.8.0_151

45
Tobia

Il s'agit d'un bogue dans la méthode peekPastWhitespace() de Java dans la classe Pattern. En traçant tout ce problème ... J'ai décidé de jeter un œil à implémentation Pattern d'OpenJDK 8-b132 . Commençons à marteler cela du haut:

  1. compile() appelle expr() sur la ligne 1696
  2. expr() appelle sequence() en ligne 1996
  3. sequence() appelle clazz() sur la ligne 2063 car le cas de [ a été rencontré
  4. clazz() appelle peek() sur la ligne 2509
  5. peek() appelle peekPastWhitespace() sur la ligne 1830 puisque if(has(COMMENTS)) est évalué à true (en raison de l'ajout du drapeau x(?x) Au début du motif)
  6. peekPastWhitespace() (affiché ci-dessous) saute tous les espaces du motif.

peekPastWhitespace()

private int peekPastWhitespace(int ch) {
    while (ASCII.isSpace(ch) || ch == '#') {
        while (ASCII.isSpace(ch))
            ch = temp[++cursor]
        if (ch == '#') {
            ch = peekPastLine();
        }
    }
    return ch;
}

Le même bogue existe dans la méthode parsePastWhitespace() .

Votre expression régulière est interprétée comme []\\b, Ce qui est la cause de votre erreur car \b N'est pas pris en charge dans une classe de caractères en Java. De plus, une fois que vous avez résolu le problème \b, Votre classe de personnage n'a pas non plus de fermeture ].

Ce que vous pouvez faire pour résoudre ce problème:

  1. \\ Comme l'OP l'a mentionné, utilisez simplement une double barre oblique inverse et un espace
  2. [\\ ] Échappez à l'espace dans la classe de caractères pour qu'il soit interprété littéralement
  3. [ ](?x)\\b Placez le modificateur en ligne après la classe de caractères
22
ctwheels

J'aime utiliser [ ] Dans des expressions rationnelles au lieu de backslash-backslash-space car cela économise du bruit visuel. Mais apparemment, ce ne sont pas les mêmes!

"[ ]" Est identique à "\\ " Ou même à " ".

Le problème est le (?x) Au début activant le mode commentaires . Comme l'indique documentation

Autorise les espaces et les commentaires dans le modèle.
Dans ce mode, les espaces sont ignorés et les commentaires incorporés commençant par # Sont ignorés jusqu'à la fin d'une ligne.
Le mode Commentaires peut également être activé via l'expression d'indicateur intégrée (?x).

En mode commentaires, l'expression régulière "(?x)[ ]\\b" est identique à "[]\\b" Et ne se compile pas car la classe de caractères vide [] N'est pas analysée comme vide, mais analysée comme "[\\]" (Classe de caractères non fermée contenant un littéral ]).

Utilisez plutôt " \\b". Vous pouvez également conserver l'espace en mode commentaires en l'échappant avec une barre oblique inverse: "(?x)[\\ ]\\b" ou "(?x)\\ \\b".

31
Socowi

Il ressemble à cause du mode espacement libre (verbeux) (?x) espace dans [ ] est ignoré, donc le moteur d'expression régulière voit votre expression régulière comme []\\b.
Si nous supprimons \\b ce serait vu comme [] et nous obtiendrions une erreur à propos de Unclosed character class - la classe de caractères ne peut pas être vide donc ] placé directement après [ est traité comme le premier caractère appartenant à cette classe au lieu du méta symbole qui ferme la classe de caractères.

Donc depuis [ n'est pas fermé, le moteur d'expression régulière voit \b comme étant placé à l'intérieur de cette classe de caractères. Mais \b ne peut pas être placé là (il ne représente pas un caractère mais "place") donc nous voyons une erreur sur "séquence d'échappement non supportée" (à l'intérieur de la classe de caractère, mais cette partie a été ignorée).

En d'autres termes, vous ne pouvez pas utiliser [ ] pour échapper à l'espace en mode verbeux (au moins en Java). Vous devez soit utiliser "\\ " ou "[\\ ]".

12
Pshemo

Une solution de contournement

Outre les espaces blancs qui s'échappent séparément, qui sont littéralement les mêmes que [ ], vous pourriez avoir le mode x activé pour toute l'expression régulière mais le désactiver tout en travaillant sur des modèles qui ont besoin d'espaces, en ligne:

(?x)match-this-(?-x: with spaces )\\b
    ^^^^^^^^^^^     ^^^^^^^^^^^^^ ^^^
    `x` is on            off       on

ou une alternative serait d'utiliser des méta-caractères qouting \Q...\E:

(?x)match-this-\Q with s p a c e s \E\\b
    ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^  ^^^
    `x` is on            off          on

Pourquoi un Exception?

En mode étendu ou commentaire (x), les espaces blancs sont ignorés, mais le traitement des espaces au sein des classes de caractères dans différentes versions est géré différemment.

Par exemple, dans PCRE, tous les caractères d'espacement sont ignorés, à l'exception de ceux d'une classe de caractères. Cela signifie [ ] est une expression rationnelle valide mais Java n'a pas d'exception:

Dans ce mode, les espaces sont ignorés ...

Période. Donc ça [ ] est égal à ce [] qui n'est pas valide et lève une exception PatternSyntaxException.

Presque toutes les versions regex sauf JavaScript ont besoin d'une classe de caractères pour avoir au moins une unité de données. Ils traitent une classe de caractères vide comme un ensemble non fermé qui a besoin d'un crochet de fermeture. Dire que, []] est valable dans la plupart des versions.

Mode d'espacement libre dans différentes saveurs sur [ ]:

  • PCRE valide
  • .NET valide
  • Perl valide
  • Ruby valide
  • TCL valide
  • Java 7 Invalide
  • Java 8 Invalide
5
revo

Permet d'analyser ce qui se passe exactement.

Jetez un œil au code source de Java.util.regex.Pattern

Autorise les espaces et les commentaires dans le modèle. Dans ce mode, les espaces sont ignorés et les commentaires incorporés commençant par # sont ignorés jusqu'à la fin d'une ligne.

Le mode Commentaires peut également être activé via l'expression d'indicateur intégrée (? X).

Votre regex vous guide vers cela ligne

private void accept(int ch, String s) {
    int testChar = temp[cursor++];
    if (has(COMMENTS))
        testChar = parsePastWhitespace(testChar);
    if (ch != testChar) {
        throw error(s);
    }
}

Si vous remarquez votre appel de code parsePastWhitespace (testChar);

private int parsePastWhitespace(int ch) {
    while (ASCII.isSpace(ch) || ch == '#') {
        while (ASCII.isSpace(ch))//<----------------Here is the key of your error
            ch = temp[cursor++];
        if (ch == '#')
            ch = parsePastLine();
    }
    return ch;
}

Dans votre cas, vous avez un espace blanc dans votre expression régulière (?x)[ ]\\b cela retournera quelque chose (je ne peux pas l'analyser correctement):

    if (ch != testChar) {
        throw error(s);
    }

qui n'est pas égal à ch et ici une exception est levée

throw error(s);
5
YCF_L