Perl6の正規表現の世界 ~ キャプチャマーカー編 ~

こんにちは.最近Perl6に思いを馳せつつRubyソースコードを読んだりしているid:anatofuzです. ところで9月はOkinawa.pmとRoppongi.pmが開催される予定なので皆さんよろしくお願いします.

題材

さて今回ですが個人的にPerl6のスピードを他言語と比較測定しようとしています. 題材として青空文庫から正規表現で漢字とふりがなを取り出すというスクリプトを作成しています.

どういうことかというと青空文庫テキスト版は蜜蜂《みつばち》の用に漢字+<<>>の順でるびが表記されています. これを正規表現でキャプチャするには例えばPerl5の場合

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use feature 'say';
use Encode;

my $filename = "./dogura_magura.txt";
open my $fh,'<:encoding(utf-8)',$filename or die qw/can't open/;

my @kana;

while (my $line = <$fh>) {
    chomp $line;
    while( $line =~ m![、。]?(\p{Han}+)(\p{Hiragana}+)!g){
        push @kana,[$1,$2];
    }
}

print scalar @kana;

この場合 m![、。]?(\p{Han}+)《(\p{Hiragana}+)》!unicodeプロパティを使ってキャプチャしています. ちなみにRubyで書くとこんな感じです

#!/usr/bin/env ruby

File.open("./dogura_magura.txt",'r') do |f|
    hoge = f.read.scan(/[、。]?(\p{Han}+)(\p{Hiragana}+)/)
    p hoge.count
end

Perl6で書く

ではこれをPerl6で書いてみましょう

#!/usr/bin/env perl6
use v6;

my $file = "./dogura_magura.txt";
my $fh = open $file, :r;
my $hoge;

for $fh.lines -> $line {
    if ($line ~~ m:g/<[、。]>?(<:Han>+)""(<:Hiragana>+)""/ ) {
        say $/[0];
        $hoge += $/.conj;
    }
}

say $hoge;

$fh.close;

Perl6の場合正規表現がPerl5と大分異なっており,まず文字クラスは[]ではなく<[]>と表記します. Unicodeプロパティは<:プロパティ名>と表記し,文字列はダブルクォーテーションでくくる必要があります.

実際にこれでコードを実行すると

# say $0;
「蜜蜂《みつばち》」
 0 => 「蜜蜂」
 1 => 「みつばち」

こういった具合に $0に配列としてキャプチャ結果が代入されます. なぜ配列で$0から入るかはこちらの資料を御覧ください.

qiita.com

キャプチャマーカー

さて本題です.

Perl6の正規表現にはキャプチャマーカー(Capture markers)と呼ばれる機能があります. 構文的には <()>と書くとキャプチャマーカーです.

公式ドキュメントによれば以下のように書かれています.

A <( token indicates the start of the match's overall capture, while the corresponding )> token indicates its endpoint. The <( is similar to other languages \K to discard any matches found before the \K.

 say 'abc' ~~ / a <( b )> c/;            # OUTPUT: «「b」␤» 
 say 'abc' ~~ / <(a <( b )> c)>/;        # OUTPUT: «「bc」␤» 

As the example above, you can see <( sets the start point and )> sets the endpoint; since they are actually independent each other, the inner-most start point wins (the one attache to b) and the outer-most end wins (the one attached to c).

CaptureMarkers%3E)

つまりこれはどういうことかというと 「正規表現の開始位置と終了位置を書き換えることが可能」という訳です.

二番目の例を見てみましょう

 say 'abc' ~~ / <(a <( b )> c)>/;        # OUTPUT: «「bc」␤» 

こちらはまず内部の <(b)>のキャプチャマーカーが処理され,abcという文字列の中のbがキャプチャされます. キャプチャマーカーではこの時点でキャプチャが走り,キャプチャマーカーはエンドポイントであるbcという文字列を返します. 次のキャプチャではこの末尾のcがキャプチャされるので全体的なアウトプットは bcとなります.

青空文庫の例

ではこれを応用(?)してみましょう.

先程の青空文庫正規表現で後ろのかなの部分にキャプチャマーカーを当てます

 m:g/<[、。]>?(<:Han>+)"《"<(<:Hiragana>+)>"》"/

こうするとどうなるでしょうか. 答えはこのようになります.

「みつばち」
 0 => 「蜜蜂」

挙動を確認するとまずキャプチャマーカーでかなの部分が読み込まれます. 読み込まれた場所はPerl6は利用しないで捨てられます.

続いてキャプチャする箇所が前にあるのでPerl6はキャプチャを後ろに戻し漢字の箇所をキャプチャし返却します.

なかなかの挙動でしたね.正直使いこなせるのか怪しいところがあります.

ちなみにこれは吉祥寺.pmのid:magnoliakさんと探っていました. 常日頃からコラボしている吉祥寺.pmとOkinawa.pmのコラボpm Roppongi.pmは9月開催予定なのでよろしくお願いします