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

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 (大文字なことに注意) ブロックが自分の宣言されたスコープ内で投げられた例外を捕らえる...

(multi-)term-mode に dirtrack させる zsh の設定

TL;DR .zshrc に以下を書けば良い: # Enable dirtrack on (multi-)term-mode. if [[ " $TERM " = eterm * ]]; then chpwd() { printf '\032/%s\n' " $PWD " } fi 追記 (May 14, 2025): oh-my-zsh を使っていれば emacs プラグインが勝手にやってくれる: plugins = ( emacs ) 仔細 term-mode は Emacs 本体に付属する端末エミュレータである。基本的には Emacs 内でシェルを起動するために使うもので、古い shell-mode よりも端末に近い動きをするので便利なのだが、一つ問題がある。シェル内でディレクトリを移動しても Emacs バッファの PWD がそのままでは追従しない点だ。 こういう追従を Emacs では Directory Tracking (dirtrack) と呼んだりするが、 shell-mode や eshell ではデフォルトで提供しているのに term-mode だけそうではない。 要するにシェル内で cd してもバッファの PWD は開いた時点のもの (基本的には直前にアクティヴだったバッファの PWD を継承する) のままなので、移動したつもりで C-x C-f などをするとパスが違ってアレっとなることになる。 実は term-mode にも dirtrack 機能自体は存在しているのだが、これは シェルがディレクトリ移動を伴うコマンドを実行したときに特定のエスケープシーケンスを含んだ行を印字することで Emacs 側に通知するという仕組み になっている。 Emacs と同じく GNU プロジェクトの成果物である bash は Emacs 内での動作を検出すると自動的にこのような挙動を取るが、zsh は Emacs の事情なんか知ったことではないので手動で設定する必要がある。 まずもって「ディレクトリ移動のコマンドをフックする」必要がある訳だが、zsh の場合これは簡単で cd / pushd / popd のようなディレクトリ...

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 ...

macOS で GUI 版 Emacs を使う設定

macOS であっても端末エミュレータ上で CLI 版 Emacs を使っているプログラマは多いと思うが、端末側に修飾キーを取られたり東アジア文字の文字幅判定が狂ってウィンドウ描画が崩れたりなどしてあまり良いことがない。 それなら GUI 版の Emacs.app を使った方がマウスも使える上に treemacs などはアイコンも表示されてリッチな UI になる。 しかし何事も完璧とはいかないもので、CLI だと問題なかったものが GUI だと面倒になることがある。その最大の原因はシェルの子プロセスではないという点である。つまり macOS の GUI アプリケーションは launchd が起動しその環境変数やワーキングディレクトリを引き継ぐので、ファイルを開こうとしたらホームディレクトリ ( ~/ ) でなくルートディレクトリ ( / ) を見に行くし、ホームディレクトリなり /opt/local なりに好き勝手にインストールしたツールを run-* 関数やら shell やら flycheck やらで実行しようとしてもパスが通っていない。 ワーキングディレクトリに関しては簡単な解決策があり、 default-directory という変数をホームディレクトリに設定すれば良い。ただし起動時にスプラッシュスクリーンを表示する設定の場合、このバッファのワーキングディレクトリは command-line-default-directory で設定されており、デフォルト値が解決される前に適用されてしまうので併せて明示的に初期化する必要がある: (setq default-directory "~/") (setq command-line-default-directory "~/") 次にパスの問題だが、まさにこの問題を解決するために exec-path-from-shell というパッケージがある。これを使うとユーザのシェル設定を推定し、ログインシェルとして起動した場合の環境変数 PATH と MANPATH を取得して Emacs 上で同じ値を setenv する、という処理をやってくれる。MELPA にあるので package-install するだけで使えるようになる。 このパッケージは GUI ...

Perl の新 class 構文を使ってみる

Perl 5 のオブジェクト指向機能は基本的には Python の影響を受けたものだが、データを名前空間 (package) に bless する機構だけで Perl 4 以来の名前空間とサブルーチンをそのままクラスとメソッドに転換し第一級のオブジェクト指向システムとした言語設計は驚嘆に価する。 実際この言語のオブジェクトシステムは動的型付言語のオブジェクト指向プログラミングに要求されるおよそあらゆる機能を暗にサポートしており、CPAN には Moose を筆頭とした屋下屋オブジェクトシステムが複数存在しているがその多くは Pure Perl ライブラリである。つまり「やろうと思えば全部手書きで実現できる」わけである。 そういうわけで Perl のオブジェクト指向プログラミングサポートは機能面では (静的型検査の不在という現代的には極めて重大な欠如を除けば) 申し分ないのだが、しかし Moose その他の存在が示しているように一つ明らかな欠点がある。記述の冗長さだ。 コンストラクタを含むあらゆるメソッドは第一引数としてレシーバを受ける単なるサブルーチンとして明示的に書く必要があるし、オブジェクトのインスタンス変数 (a.k.a. プロパティ / データメンバ) は bless されたデータに直接的ないし間接的に プログラマ定義の方法 で格納されるためアクセス手段は実装依存である。これはカプセル化の観点からは望ましい性質だが、他者の書いたクラスを継承するときに問題となる。ある日データ表現を変更した親クラスがリリースされると突然自分の書いた子クラスが実行時エラーを起こすようになるわけだ。 そうならないためにはインスタンス変数へのアクセスに (protected な) アクセサを使う必要があるのだが、そのためには親クラスが明示的にそれらを提供している必要があるし、そもそも Perl にはメソッドのアクセス修飾子というものがないので完全な制御を与えるならばオブジェクトの内部状態がすべて public になってしまう。 そのような事情もあり、特にパフォーマンスが問題にならないようなアプリケーションコードでは Moose のようなリッチな語彙を提供するオブジェクトシステムを使うことが 公式のチュートリアルでも推奨 されてきた。Perl コアのオブジェクトシステムの改良は...