スキップしてメイン コンテンツに移動

Perl 5 to 6 - 正規表現(またの名をルール)

これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 07 - Regexes (also called "rules")の日本語訳です。

原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。

本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。

Original text: Copyright© 2008-2010 Moritz Lenz

Japanese translation: Copyright© 2011 SATOH Koichi

NAME

"Perl 5 to 6" Lesson 07 - 正規表現(またの名をルール)

SYNOPSIS

grammar URL {
    token TOP {
        <schema> '://' 
        [<ip> | <hostname> ]
        [ ':' <port>]?
        '/' <path>?
    }
    token byte {
        (\d**{1..3}) <?{ $0 < 256 }>
    }
    token ip {
        <byte> [\. <byte> ] ** 3
    }
    token schema {
        \w+
    }
    token hostname {
        (\w+) ( \. \w+ )*
    }
    token port {
        \d+
    }
    token path {
        <[ a..z A..Z 0..9 \-_.!~*'():@&=+$,/ ]>+
    }
}

my $match = URL.parse('http://perl6.org/documentation/');
say $match<hostname>;       # perl6.org

DESCRIPTION

正規表現(Regex)はPerl6で一番改良のあった領域です。Perl5でそうであったほどに正規ではないので、もはやRegular expressionとは呼ばれません。

訳注: タイトルにある通り「ルール」とも呼ばれるようになりました。この記事でもルールと書いてある部分があります。

大きく3つの変更点と改良点があります。

整理された構文
書き易さを向上させる多くの細かい変更がなされました。例えば.はすべての文字にマッチするようになり、今までの意味論(改行以外全部)は\Nで提供されるようになりました。 修飾子は正規表現の頭に付くようになり、キャプチャしないグループは(?:...)より書き易い[...]になりました。
入れ子のキャプチャとマッチオブジェクト
Perl5では(a(b))(c)のような正規表現はマッチ成功時にはab$1b$2c$3にセットしていました。これは変更され、$0(列挙は0から始まります)はab$0[0]$/[0][0]b$1cを保持するようになりました。 マッチ変数はすべて$/経由でもアクセスできます。これはマッチオブジェクトとも呼ばれ、完全なマッチの木を格納しています。
名前付き正規表現とグラマー
サブルーチンやメソッドのように、正規表現に名前を付けて宣言できます。ルール中で他のルールを<name>のように参照できます。 複数の正規表現をグラマーの中に置くことができます。グラマーはクラスのように継承や合成をサポートしています。

これらの変更がルールをPerl5より書き易く、メンテナンスし易いものにしています。

変更点は極めて多岐に渡るので、ここではその上っ面を擦る程度しか紹介できません。

整理された構文

レター文字(アンダースコア、数字とすべてのUnicode letter)はそれ自身にマッチし、バックスラッシュでエスケープされた時は特別の(メタ構文的)意味を持ちます。 それ以外の文字の場合は逆になります——これらはエスケープされないときにメタ構文的な役割を持ちます。

字句通り         メタ構文的
a  b  1  2      \a \b \1 \2
\* \: \. \?     *  :  .  ? 

メタ構文的トークンすべてに意味があるわけではありません(今のところは)。未定義の意味を使うのは不正です。

文字列を正規表現中でエスケープする方法がもう1つあります: クォートすることです。

m/'a literal text: $#@!!'/

.の意味論が変更されたことと、[...]がキャプチャしないグループになったことは既に述べました。 文字クラスは<[...]>、否定形の文字クラスは<-[...]>です。^$はいつでも文字列の先頭と末尾にマッチします。行の先頭や末尾にマッチさせるには^^$$を使って下さい。

これは修飾子/s/mがなくなったということです。修飾子は正規表現の頭に付くようになり、ペアとして書かれます。

if "abc" ~~ m:i/B/ {
    say "Match";
}

修飾子は短い形式と長い形式があります。昔の/x修飾子はデフォルトになりました。つまり、空白は無視されます。

短い形式 長い形式         意味
--------------------------------------------------------------
:i      :ignorecase     大文字小文字の違いを無視する(かつての/i)
:m      :ignoremark     記号を無視する(アクセント記号、分音記号など)
:g      :global         可能な限り繰り返しマッチする(/g)
:s      :sigspace       正規表現中の空白が(省略可能な)空白にマッチする
:P5     :Perl5          Perl5互換の構文に戻す
:4x     :x(4)           4回マッチする(他の数字でも同様)
:3rd    :nth(3)         3番目のマッチ
:ov     :overlap        :gと似ているが、範囲がオーバーラップしたマッチも考慮する
:ex     :exhaustive     マッチ可能性をすべて尽くす
        :ratchet        バックトラックしない

:sigspaceにはもう少し説明が必要です。これはパターン中のすべての空白を<.ws>(ルールwsを呼び出し、結果を保存しません)に置換します。このルールはオーバーライドできます。デフォルトではワード文字列で囲まれている場合は1個以上の空白にマッチし、それ以外の位置では0個以上の空白にマッチします。

(他にも新しい修飾子はありますが、ここに挙げたものよりは重要ではないでしょう)

マッチオブジェクト

すべてのマッチはマッチオブジェクトと呼ばれるものを生成し、特殊変数$/に格納します。 これにはいろいろな使い方ができます。真理値コンテキストではマッチ成功時にはBool::Trueを返します。文字列コンテキストではマッチした文字列を返し、リストとして使われればキャプチャのリストを返します。ハッシュとして使われると名前付きキャプチャを返します。 .fromメソッドと.toメソッドはマッチした先頭と末尾の位置を返します。

if 'abcdefg' ~~ m/(.(.)) (e | bla ) $<foo> = (.) / {
    say $/[0][0];           # d
    say $/[0];              # cd
    say $/[1];              # e
    say $/<foo>             # f
}

$0$1などは$/[0]$/[1]などの単なる別名です。同様に$/<x>$/{'x'}$<x>という別名を持ちます。

$/[...]$/{...}でアクセスできるものもまた、マッチオブジェクト(あるいはそのリスト)であることに留意して下さい。 これによってルールの完全な解析木を作ることができます。

名前付き正規表現とグラマー

ルールは旧来のm/.../で使ったり、サブルーチンやメソッドのように宣言することができます。

regex a { ... }
token b { ... }
rule  c { ... }

これらの違いは、token:ratchet修飾子が有効になり(バックトラックしなくなる。Perl5で正規表現の各部を(?>...)で囲むようなもの)、rule:ratchet:sigspaceが有効になることです。 このようなルール(どのキーワードで宣言したかに関係なくルールと呼びます)を呼び出すには、その名前を角カッコで囲みます: <a>。これはサブルールを文字列の現在からマッチさせ、結果を$/<a>に格納します。つまりこれは名前付きキャプチャです。 結果をキャプチャすることなくルールを呼び出すには、名前の先頭にドットを付けます: <.a>

グラマーはルールの寄せ集めで、クラスに似ています(例えばSYNOPSISを見て下さい)。グラマーは継承したり、ルールをオーバーライドしたりできます。

grammar URL::HTTP is URL {
    token schema { 'http' }
}

MOTIVATION

Perl5の正規表現は解読不能になることがよくありますが、グラマーは巨大な正規表現を小さな読み易い断片に分割することを促進します。 名前付きキャプチャはルールを自己文書化し、多くのものが以前より一貫性ある形になりました。

最後に、グラマーはPerl6を含むほとんどすべてのプログラミング言語を構文解析できるくらい強力です。 このことがPerl6の構文をPerl5よりメンテナンスし易く、変更し易いものにしています(訳注: Perl6の構文はグラマーを使って定義されている)。Perl5では構文解析器はCで書かれており、構文解析時に変更できませんでした。

SEE ALSO

http://perlcabal.org/syn/S05.html

コメント

このブログの人気の投稿

Perl 7 より先に Perl 5.34 が出るぞという話

Perl 5 の次期バージョンとして一部後方互換でない変更 (主に間接オブジェクト記法の削除とベストプラクティスのデフォルトでの有効化) を含んだメジャーバージョンアップである Perl 7 がアナウンスされたのは昨年の 6 月 のことだったが、その前に Perl 5 の次期周期リリースである Perl 5.34 が 5 月にリリース予定 である。 現在開発版は Perl 5.33.8 がリリースされておりユーザから見える変更は凍結、4 月下旬の 5.33.9 で全コードが凍結され 5 月下旬に 5.34.0 としてリリース予定とのこと。 そういうわけで事前に新機能の予習をしておく。 8進数数値リテラルの新構文 見た瞬間「マジかよ」と口に出た。これまで Perl はプレフィクス 0 がついた数値リテラルを8進数と見做してきたが、プレフィクスに 0o (zero, small o) も使えるようになる。 もちろんこれは2進数リテラルの 0b や 16進数リテラルの 0x との一貫性のためである。リテラルと同じ解釈で文字列を数値に変換する組み込み関数 oct も` 新構文を解するようになる。 昨今無数の言語に取り入れられているリテラル記法ではあるが、この記法の問題は o (small o) と 0 (zero) の区別が難しいことで、より悪いことに大文字も合法である: 0O755 Try / Catch 構文 Perl 5 のリリース以来 30 年ほど待たれた実験的「新機能」である。 Perl 5 における例外処理が特別な構文でなかったのは予約語を増やさない配慮だったはずだが、TryCatch とか Try::Tiny のようなモジュールが氾濫して当初の意図が無意味になったというのもあるかも知れない。 use feature qw/ try / ; no warnings qw/ experimental::try / ; try { failable_operation(); } catch ( $e ) { recover_from_error( $e ); } Raku (former Perl 6) だと CATCH (大文字なことに注意) ブロックが自分の宣言されたスコープ内で投げられた例外を捕らえる...

Project Euler - Problem 35

問題 原文 How many circular primes are there below one million? 日本語訳 100万未満の巡回素数は何個か? 解答 回転させた数値がすべて素数ということは、すべての桁が奇数でなければいけません(ただし2を除く)。 追記 匿名氏にコメントでご指摘頂いたのでコードを一部修正しました。 いずれかの桁に5がある場合も、回転させると必ず5の倍数が現れるので除外できます。 もっと追記 前の修正に間違いが入っているのをご指摘頂いたので修正しました。 5自体は素数なので、巻き添えで除外してはいけません。 #!/usr/bin/env perl use strict; use warnings; use feature qw/say state/; use List::MoreUtils qw/all none/; sub is_prime($) { state %memos; my $n = shift; return 0 if $n < 2; return 1 if $n == 2; return 1 if $n == 3; return $memos{$n} if exists $memos{$n}; $memos{$n} = none { $n % $_ == 0 } 2 .. sqrt $n; } sub rotate($) { my $n = shift; substr($n, 1) . substr($n, 0, 1); } sub rotations($) { my $n = shift; my %seen = ($n => 1); $seen{$n} = 1 until exists $seen{$n = rotate $n}; keys %seen; } sub is_circular_prime($) { state %memos; my $n = shift; return 0 if $n =~ /[024568]/ and $n != 2 and $n != 5; return $memos{$n} if exists $memos{$n}; my ...

Project Euler - Problem 27

問題 しばらく止まってましたが今日から再開。 原文 Considering quadratics of the form: n 2 + an + b, where |a| < 1000 and |b| < 1000 Find the product of the coefficients, a and b, for the quadratic expression that produces the maximum number of primes for consecutive values of n, starting with n = 0. 日本語訳 |a| < 1000, |b| < 1000 として以下の二次式を考える (ここで|a|は絶対値): n 2 + an + b n=0から始めて連続する整数で素数を生成したときに最長の長さとなる上の二次式の, 係数a, bの積を答えよ. 解答 最大探索範囲は-999 <= a <= 999、-999 <= b <= 999なので、およそ4,000,000通りの係数の組合せを試すことになります。組合せ毎に数列を生成して、それが素数か判定するわけですからたまりません。簡単な検討を加えて範囲を絞りましょう。 与えられた二次式をf(n)とおくと、f(0) = b、f(1) = a + b + 1です。 f(n)が長さ2以上の素数列を生成するならこれらは素数ですから、次のことがいえます: bは素数である a + b + 1は素数である b = 2のとき、aは偶数である それ以外のとき、aは奇数である 素数判定関数 is_prime には同じ引数が与えられることがよくあるのでメモ化しています。 #!/usr/bin/perl use strict; use warnings; use feature qw/say/; sub prime_seq_len($$) { my ($coeff_a, $coeff_b) = @_; my $len = 0; my $n = 0; $len++, $n++ while is_prime($n * ($n + $coeff_a) ...

Perl 5 to 6 - コンテキスト

2011-02-27: コメント欄で既に改訂された仕様の指摘がありました ので一部補足しました。 id:uasi に感謝します。 これはMoritz Lenz氏のWebサイト Perlgeek.de で公開されているブログ記事 "Perl 5 to 6" Lesson 06 - Contexts の日本語訳です。 原文は Creative Commons Attribution 3.0 Germany に基づいて公開されています。 本エントリには Creative Commons Attribution 3.0 Unported を適用します。 Original text: Copyright© 2008-2010 Moritz Lenz Japanese translation: Copyright© 2011 SATOH Koichi NAME "Perl 5 to 6" Lesson 06 - コンテキスト SYNOPSIS my @a = <a b c> my $x = @a; say $x[2]; # c say (~2).WHAT # Str() say +@a; # 3 if @a < 10 { say "short array"; } DESCRIPTION 次のように書いたとき、 $x = @a Perl5では $x は @a より少ない情報—— @a の要素数だけ——しか持ちません。 すべての情報を保存しておくためには明示的にリファレンスを取る必要があります: $x = \@a Perl6ではこれらは反対になります: デフォルトでは何も失うことなく、スカラ変数は配列を単に格納します。 これは一般要素コンテキスト(Perl5で scalar と呼ばれていたもの)及びより特化された数値、整数、文字列コンテキストの導入によって可能となりました。無効コンテキストとリストコンテキストは変更されていません。 特別な構文でコンテキストを強制できます。 構文 コンテキスト ~stuff 文字列 ?stuff 真理値 +stuff ...

Project Euler - Problem 18

問題 原文 Find the maximum total from top to bottom of the triangle 日本語訳 三角形を頂点から下まで移動するとき、その最大の合計値を求めよ。 解答 動的計画法 を使ってボトムアップで簡単に解くことができる問題です。 簡単のため、小さい三角形で考えることにします: 0: j 1: h i 2: e f g 3: a b c d 2行目の各点を頂点として、2行の小さい三角形が作れることが分かります。 上の例で言えば、(e, a, b)と(f, b, c)、(g, c, d)の3つです。 (e, a, b)の頂点eから末端(a、b、c、dのいずれか)に移動したとき、その数値の合計は最大でe + max(a, b)となります(maxは最大値を選ぶ関数)。同様に他の2つもf + max(b, c)、g + max(c, d)と表せます。 これらをE、F、Gとおくことにして、例を次のように書き換えます: 0: j 1: h i 2: E F G (h, E, F)からなる三角形の最大値はH = h + max(E, F)、(i, F, G)からなる三角形のそれはI = i + max(F, G)です。 Eは「頂点eから末端に至る経路の最大値」で、FやGも同様ですから、HとIは「頂点h(やi)から末端に至る経路の最大値」となります。 これを先ほどと同様に置き換えて: 0: j 1: H I 頂点jから末端に至る経路の最大値はJ = j + max(H, I)となり、これが解です。 #!/usr/bin/perl use strict; use warnings; use feature qw/say/; use List::Util qw/max/; my @rows = map { [ split /\s+/ ] } <DATA>; until (@rows == 1) { my $curr_row = $rows[-2]; my $bigger_branch; for (my $i = 0; $i < @$curr_row; $i++) { $bigger_branch = ma...