web-dev-qa-db-ja.com

冗長なJava文字クラスとWord境界を持つ正規表現のコンパイルエラー

このパターンがコンパイルに失敗するのはなぜですか:

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

エラー

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

次の同等のものは動作しますか?

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

これはJava regexコンパイラのバグですか、何か不足していますか?視覚的ノイズを節約するため、バックスラッシュ-バックスラッシュ-スペースの代わりに冗長正規表現で[ ]を使用したいです。 。しかし、明らかにそれらは同じではありません!

PS:この問題はバックスラッシュに関するものではありません。バックスラッシュを使用する代わりに、単一のスペース[ ]を含む文字クラスを使用して、冗長正規表現でスペースをエスケープすることです。

どういうわけか、冗長な正規表現(?x)と単一のスペースを含む文字クラス[ ]の組み合わせにより、コンパイラがスローされ、Wordの境界エスケープ\bが認識されなくなります。


Java 1.8.0_151まででテスト済み

45
Tobia

これは、PatternクラスのJavaのpeekPastWhitespace()メソッドのバグです。この問題全体をたどって...私は OpenJDK 8-b132のPattern実装 を調べることにしました。これを上から叩き始めましょう:

  1. compile()は1696行でexpr()を呼び出します
  2. expr()は1996年にsequence()を呼び出します
  3. sequence()は、[のケースが満たされたため、2063行でclazz()を呼び出します
  4. clazz()は2509行目でpeek()を呼び出します
  5. peek()は、peekPastWhitespace()trueに評価されるため(パターンの先頭にxフラグ(?x)を追加したため、1830行でif(has(COMMENTS))を呼び出します。 )
  6. peekPastWhitespace()(以下に投稿)パターン内のallスペースをスキップします。

peekPastWhitespace()

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

parsePastWhitespace() メソッドにも同じバグが存在します。

Javaの文字クラスでは[]\\bがサポートされていないため、正規表現は\bとして解釈されていますが、これがエラーの原因です。さらに、\bの問題を修正すると、キャラクタークラスには終了]もありません。

この問題を解決するためにできること:

  1. \\ OPが述べたように、単純に二重のバックスラッシュとスペースを使用します
  2. [\\ ]文字クラス内のスペースをエスケープして、文字どおりに解釈されるようにします
  3. [ ](?x)\\b文字クラスの後にインライン修飾子を配置します
22
ctwheels

視覚的なノイズを節約するため、バックスラッシュ-バックスラッシュ-スペースの代わりに冗長正規表現で_[ ]_を使用するのが好きです。しかし、明らかにそれらは同じではありません!

_"[ ]"_は_"\\ "_または_" "_と同じです。

問題は、コメントモードを有効にする最初の_(?x)_ですドキュメント 状態として

パターン内の空白とコメントを許可します。
このモードでは、空白は無視され、_#_で始まる埋め込みコメントは行末まで無視されます。
コメントモードは、埋め込みフラグ式_(?x)_を使用して有効にすることもできます。

コメントモードでは、正規表現"(?x)[ ]\\b"は_"[]\\b"_と同じであり、空の文字クラス_[]_は空として解析されず、_"[\\]"_のように解析されるため、コンパイルされません。 (リテラル_]_を含む閉じられていない文字クラス)。

代わりに_" \\b"_を使用してください。または、バックスラッシュでエスケープすることにより、コメントモードでスペースを保持します:"(?x)[\\ ]\\b"または"(?x)\\ \\b"

31
Socowi

free-spacing(verbose)mode (?x)[ ]のスペースは無視されるため、正規表現エンジンは正規表現を[]\\bとして認識します。
\\bを削除すると、[]のように表示され、Unclosed character classに関するエラーが発生します-文字クラスを空にできないため、]を直接配置します[の後は、文字クラスを閉じるメタシンボルではなく、そのクラスに属する最初の文字として扱われます。

したがって、[は閉じられていないため、正規表現エンジンは\bが配置されていると見なしますinsideその文字クラス。ただし、\bはそこに配置できないため(文字ではなく「場所」を表します)、「サポートされていないエスケープシーケンス」(文字クラス内でその部分はスキップされました)に関するエラーが表示されます。

言い換えると、[ ]を使用して冗長モード(少なくともJava)でスペースをエスケープすることはできません。 "\\ "または"[\\ ]"を使用する必要があります。

12
Pshemo

回避策

[ ]と文字通り同じである空白を個別にエスケープすることに加えて、正規表現全体でxモードをオンにできますが、空白を必要とするパターンでの作業中は無効にできます。

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

または、代わりにqoutingメタ文字\Q...\Eを使用します。

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

なぜExceptionなのか?

拡張モードまたはコメントモード(x)では、空白は無視されますが、さまざまなフレーバーの文字クラス内のスペースの処理方法は異なります。

たとえば、PCREでは、文字クラスの文字を除くすべての空白文字は無視されます。つまり、[ ]は有効な正規表現ですが、Javaには例外がありません。

このモードでは、空白は無視されます...

限目。したがって、この[ ]はこの[]と等しく、無効であり、PatternSyntaxException例外をスローします。

JavaScriptを除くほとんどすべての正規表現フレーバーは、少なくとも1つのデータユニットを持つ文字クラスを必要とします。空の文字クラスは、閉じ括弧が必要な閉じられていないセットとして扱われます。 []]はほとんどのフレーバーで有効です。

[ ]の異なるフレーバーのフリースペースモード:

  • PCRE有効
  • .NET有効
  • Perl有効
  • Ruby有効
  • TCL有効
  • Java 7無効
  • Java 8無効
5
revo

何が起こるかを正確に分析しましょう。

Java.util.regex.Pattern のソースコードを見てください

パターン内の空白とコメントを許可します。 このモードでは、空白は無視され、#で始まる埋め込みコメントは行末まで無視されます。

コメントモードは、埋め込みフラグ式(?x)でも有効にできます。

あなたの正規表現はあなたにこれを案内します line

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

コード呼び出しに気付いた場合 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;
}

あなたの場合、正規表現に空白があります(?x)[ ]\\bこれは何かを返します(正しく分析できません):

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

chと等しくない場合、ここで例外がスローされます

throw error(s);
5
YCF_L