可変長の否定戻り読み

「foo でないものの後に続いている bar」にマッチさせたいという場合には否定戻り読み (negative lookbehind) が使える.
上の例を表す正規表現

(?<!foo)bar

となる.


「foo または hoge でないものの後に続いている bar」も同様に

(?<!foo|hoge)bar

と書けたら嬉しいのだが,可変長の戻り読みを許しているエンジンは少ないらしい.

lazyeagle: Variable length lookbehind not implemented in regex だと… http://twitter.com/lazyeagle/status/3841821998
finalfusion: @lazyeagle 戻り読みで可変長許しているのは少数派ですよ。 http://twitter.com/finalfusion/status/3841869401
lazyeagle: @finalfusion そうだったんですか… 少数派ということは,許している正規表現エンジンもあるんですか? http://twitter.com/lazyeagle/status/3841918295
finalfusion: @lazyeagle 確かJavaと、.NETのが使えたような http://twitter.com/finalfusion/status/3842015369
finalfusion: Perlの場合、\Kを使うと(5.10から追加)戻り読みなしでも同じようなマッチをするぱたーんをかけなくもないです http://twitter.com/finalfusion/status/3842033645

鬼車 (Ruby 1.9)

鬼車では | の場合のみ許されているようだ.
つまり,上記のような例なら可能だが,例えば

(?<!o+)bar

のようなものはダメらしい.
実際に Version 5.9.1 で試してみたら

(?<!foo|hoge)bar

は期待通りに foobar, hogebar にはマッチせず fugabar にはマッチしたが,

(?<!o+)bar

は onig_new のときに invalid pattern in look-behind というエラーが返った.


鬼車を採用している Ruby 1.9 でも同様の結果となる.

irb(main):001:0> /(?<!foo|hoge)bar/.match 'foobar'
=> nil
irb(main):002:0> /(?<!foo|hoge)bar/.match 'hogebar'
=> nil
irb(main):003:0> /(?<!foo|hoge)bar/.match 'fugabar'
=> #<MatchData "bar">
irb(main):004:0> /(?<!o+)bar/.match 'foobar'
SyntaxError: (irb):4: invalid pattern in look-behind: /(?<!o+)bar/
        from /opt/local/bin/irb1.9:12:in `<main>'

Java

教えていただいた Java を試してみた.

import java.util.regex.*;

public class RegexTest {
  public static void main(String[] args) {
    Pattern p = Pattern.compile(".*(?<!foo|hoge)bar");
    System.out.println(p.matcher("foobar").matches()); // => false
    System.out.println(p.matcher("hogebar").matches()); // => false
    System.out.println(p.matcher("fugabar").matches()); // => true

    Pattern q = Pattern.compile(".*(?<!o+)bar");
    System.out.println(q.matcher("foobar").matches()); // => false
    System.out.println(q.matcher("fugabar").matches()); // => true
  }
}

素晴しい!

Perl (PCRE)

Perl 5.10 から導入された \K を使うことで可変長の肯定戻り読みは可能になることがわかったが,否定戻り読みはどうすればいいのかわからなかった…

use strict;
use warnings;

sub test1 {
  my $str = shift;
  if ($str =~ /(foo|hoge)\Kbar/) {
    print "true\n";
  } else {
    print "false\n";
  }
}

test1('foobar');  # => true
test1('hogebar'); # => true
test1('fugabar'); # => false

sub test2 {
  my $str = shift;
  if ($str =~ /(o+)\Kbar/) {
    print "true\n";
  } else {
    print "false\n";
  }
}

test2('foobar');  # => true
test2('hogebar'); # => false


一応こうすることで,| のみなら可能になる.

use strict;
use warnings;

sub test {
  my $str = shift;
  if ($str =~ /(?<!foo|(?<=h)oge)bar/) {
    print "true\n";
  } else {
    print "false\n";
  }
}

test('foobar');  # => false
test('hogebar'); # => false
test('fugabar'); # => true

参照

http://www.geocities.jp/kosako3/oniguruma/doc/RE.ja.txt の「否定戻り読み」のところ
http://java.sun.com/javase/ja/6/docs/ja/api/java/util/regex/Pattern.html
http://www.pcre.org/pcre.txt の「Resetting the match start」のところと「Lookbehind assertions」のところ