はじめに

このドキュメントはDamian Conwayによる Exegesis 5: Pattern Matching の日本語訳です。原文は http://dev.perl.org/perl6/exegesis/E05.htmlにあります。 訳に関する意見、質問等は 訳者まで。

TITLE

Exegesis 5: パターンマッチング

AUTHOR

Damian Conway <damian@conway.org>

VERSION

  Maintainer: Allison Randal <al@shadowed.net>
  Date: 22 Aug 2002
  Last Modified: 25 Aug 2002
  Number: 5
  Version: 1
Come gather round Mongers, whatever you code

And admit that your forehead's about to explode

'Cos Perl patterns induce complete brain overload

If there's source code you should be maintainin'

Then you better start learnin' Perl 6 patterns soon

For the regexes, they are a-changin'

-- Bob Dylan, "The regexes they are a-changin'" (Perl 6 remix)

Apocalyse 5 marks a significant departure in the on-going design of Perl 6.
黙示録 5は現在進行中のPerl6のデザインに関する明確な方針について述べる。

以前の黙示録ではPerlの一般的な構文、データ構造、制御構造、演算子についての 漸進的なアプローチによる変更をとっていた。新たな機能が追加され、 古い機能は取り除かれ、存在する機能は拡張されそして単純化された。 しかし、その変更は矯正として記述されたもので、徹底的なものではなかった。

Larryは同じアプローチを正規表現についても取ることができた。彼は 構文の一部を 新たな(?...) 構造、細部のクリーンアップ、などで微調整することもできた。

しかしながら幸運なことに、彼はPerlの将来に関してより広い視野を持っていた。 そして、彼は正規表現にまつわる問題が、名前つきキャプチャを行うための (?$var:...)拡張や 再帰的な部分パターンを表記するためのメタトークン ¥R[:YourNamedCharClassHere:]機構の 欠如にあるのではないことを知った。

彼はそれらの機能が、個々は賞賛すべきものであるが本質的な問題を 含んだものであることに気が付いた。Perl 5での正規表現は すでにそのメタ構文の計算された重み(accumulated weight)の元でうめき声を あげていた。そして、付加されたものによる増大の10年間は 不可解で、複雑で、一貫性がなくて醜い表記をきれいにすることをおきざりに していた。

試作品を放り出すときが来ていたのだった。

Even more importantly, as powerful as Perl 5 regexes are, they are not nearly powerful enough. Modern text manipulation is predominantly about processing structured, hierarchical text. And that's just plain painful with regular expressions. The advent of modules like Parse::Yapp and Parse::RecDescent reflects the community's widespread need for more sophisticated parsing mechanisms. Mechanisms that should be native to Perl.
より重要なことは、Perl 5の正規表現では十分な強さを持っていないという ことである。近代的なテキスト操作は圧倒的に、構造化され階層構造を 持ったテキストについてのものである。そして、正規表現を使うことは 骨の折れることである。Parse::YappやParse::RecDescentのようなモジュールの 出現は、コミュニティにおけるより洗練された解析機構の 幅広い必要性を反映している。

As Piers Cawley has so eloquently misquoted: "It is a truth universally acknowledged that any language in possession of a rich syntax must be in want of a rewrite." Perl regexes are such a language. And Apocalypse 5 is precisely that rewrite.
Piers Cawleyは雄弁に間違った引用をしている:

豊富な構文の処理を行う言語はリライトを行うようになっていなければならないことは 一般的に認められている真実である。
Perlの正規表現はそのような言語である。そして、黙示録 5はリライトされたので ある。

diffとは何か?

So let's take a look at some of those new features. To do that, we'll consider a series of examples structured around a common theme: recognizing and manipulating data in the Unix diff 新たな機能のいくつかを見てみよう。そのために、一般的なテーマの 構造化されたものの例を考える: Unix の diff の 認識とデータ操作

古典的なdiffはゼロ個以上のテキスト変形から構成され、それぞれは "hunk"として知られている。hunkはコンテキストの一行以上のものが 後続する修正指定子からなる。それぞれのhunkは追加、削除、変更の いずれかで、hunkの型は一文字で指定される ('a', 'd', 'c'). これらの一文字指定子のそれぞれは影響を及ぼすオリジナルの ドキュメントの行番号が前置され、変換されるファイルの等価な行番号が 後続する。コンテキスト情報はオリジナルファイルの行から構成され (それぞれは'<'が前置される)、 変形ファイルの行が続く(それぞれは '>'が前置される)。削除は変換コンテキストを省略し、 追加はオリジナルコンテキストを省略する。もし両方のコンテキストが 現れたなら、それらは三つのハイフンで構成される行で区切られる。

Phew! You can see why natural language isn't the preferred way of specifying data formats.

もちろん好ましいやり方は、パターンとしてそれらのフォーマットを指定する ことである。そして、実際に そういったフォーマットに準拠した任意のデータにマッチする 少数のPerl 6でのパターンからそれを行うことができる:

    $file = rx/ ^  <$hunk>*  $ /;

    $hunk = rx :i { 
        [ <$linenum> a :: <$linerange> ¥n
          <$appendline>+ 
        |
          <$linerange> d :: <$linenum> ¥n
          <$deleteline>+
        |
          <$linerange> c :: <$linerange> ¥n
          <$deleteline>+
          --- ¥n
          <$appendline>+
        ]
      |
        (¥N*) ::: { fail "Invalid diff hunk: $1" }
    };

    $linerange = rx/ <$linenum> , <$linenum>
                   | <$linenum>
                   /;

    $linenum = rx/ ¥d+ /;

    $deleteline = rx/^^ ¥< <sp> (¥N* ¥n) /;
    $appendline = rx/^^ ¥> <sp> (¥N* ¥n) /;

    # and later...

    my $text is from($*ARGS);

    print "Valid diff" 
        if $text =‾ /<$file>/;

Starting gently

この例には多くの新規構文があるので、ゆっくり段階を追って、まずは 次のものから始めよう:

    $file = rx/ ^  <$hunk>*  $ /;

この文はパターンオブジェクトを生成している。Perl 6では"ルール"として も知られている。人々は多分これをまだ"正規表現"(regular expression) とも呼ぶだろう (rxというキーワードがそれを 反映している)。しかし、Perlのパターンは"正規"(regular)のようなもの からかけ離れたものになっているので、正規表現という用語は使わないように 努めよう。

任意のケースで、rxコンストラクタは 変数$file に格納される 新たなルールを構築する。Perl 5での等価な表現は以下の通り:

    # Perl 5
    my $file = qr/ ^  (??{$hunk})*  $ /x;

これはなぜ構文全体が変更される必要があったのかの理由を如実に 表している。

ルールコンストラクタの名前は qr から rxに変更された。それは、 Perl 6のルールコンストラクタがクォートに似たコンテキストでは ないからである。特に、変数は qqqxに対するもののように rxコンストラクタに展開(interpolate) されない。これが、実際に初期化する前に $hunk という変数を埋め込むことのできる理由である。

Perl 6では、埋め込まれた変数は"ソースコード"の一部としてよりも ルールの実装の一部となった。これまで見てきたように、パターンそれ自身は 変数が取り扱われるやり方を決定できる(つまり、リテラルとして 展開されるかサブパターンとして取り扱うか、あるいはそれを コンテナとして使うかということ)。

Lay it out for me

Perl 6のすべてのルールは暗黙裡にPerl 5での /x修飾子がオンになっている のと同じに扱われる。このため、最初のパターンを以下のように記述する ことが(そして注釈をつけることが)できる:

    $file = rx/ ^               # 文字列の先頭でなければならない
                <$hunk>         # Match what the rule in $hunk would match...
                        *       #          ...ゼロ回以上
                $               # 文字列の末尾でなければならない(改行がなくても良い)
              /;

/xがデフォルトなので、パターン中の 空白は無視される。これによりルールがより読みやすくなる。コメントもまた 優先されるので、ルールの意味するところを記述できるようになる。 コメントの中で閉じデリミタでさえも安全に使うことができる:

    $caveat = rx/ Make ¥s+ sure ¥s+ to ¥s+ ask
                  ¥s+ (mum|mom)                 # handle UK/US spelling
                  ¥s+ (and|or)                  # handle and/or
                  ¥s+ dad ¥s+ first
                /;

もちろん、このExegesisの中の例は一般的に良いコメントを表しては いない。なぜなら、それらのコメントは起きることを説明していて なぜそうなのかを説明していないからである。

メタキャラクタ^*の意味はPerl 5から変わっていない。 しかし、メタキャラクタ$の意味は 変わっている: もはや文字列の末尾の直前にある省略可能な 改行を許さないのである。もしそういった動作を望むのなら、陽に指定してやる 必要がある。たとえば、数字で終わる行にマッチさせるには: / ¥d+ ¥n? $/

Perl 6では、パターン中の¥n論理的な改行(次のいずれか: "¥015¥012" or "¥012" or "¥015" or "¥x85" or "¥x2028")にマッチし、 物理的なASCIIの改行 ("¥012") にはマッチしない。そして、¥nは 任意の種類の物理的改行マーカー(カレントのシステムの傾向によらず)に マッチすることを試みるので、複数のシステムからaggregratedな 文字列に対して正しくマッチするのである。

Interpolate ye not...

$file ルールの中の本当に新しい ものは<$hunk>要素である。 これは変数$hunk の中のものを 横取り(grab)するためのディレクティブで、ルールの中でその場所に マッチしようとする。重要な点は$hunk の内容がパターンマッチング機構が本当にマッチを必要としたときに にのみgrabされるということで、ルールが構築されているときでは ない。そのため、これはPerl 5の正規表現の 謎めいた(??{...})構造に似ている。

アングルブラケットはPerl 6のルールにおいて、より一般的な機構である。 それらは"metasyntactic markers"で、Perl 5の (?...)構文を置き換えるものである。 (アングルブラケットは)Perl 6のルールの他のさまざまな機能を指定するために 使われる。これは後で説明する。

次のようにアングルブラケットの中に変数を置かなかった場合:

        rx/ ^  $hunk*  $ /;

$hunkの内容はパターンが解析された ときにはまだ展開されないということに注意すること。繰り返すが、 パターンは変数の内容を、そのマッチの場所に到達したときにgrabする。 しかし今回、$hunkを囲む アングルブラケットを取り除いているので、パターンは変数の内容を アトミックなリテラル文字列としてマッチしようとする (サブパターンとしてではない)。"アトミック"とは繰り返し修飾子 *$hunkの内容すべてに適用される ということを意味する(Perl 5でそうであるような 最後のキャラクタのみに 適用されるのではない)。

言い換えれば、Perl 6のパターン中の生の変数は、 quotemetaされたものとして 展開され、かつ、キャプチャしない括弧に囲まれた Perl 5の正規表現のようなものとしてマッチが行われるということである。 これは以下のような例で手ごろなものである:

        # Perl 6
        my $target = <>;                  # 検索する文字列を取り出す
        $text =‾ m/ $target* /;           # 内容をリテラルとして検索する

Perl 5では以下のように書かねばならなかった:

        # Perl 5
        my $target = <>;                  # 検索する文字列を取り出す
        chomp $target;                    # Perl 5ではautochompingされない
        $text =‾ m/ (?:¥Q$target¥E)* /x;  # Search for it, quoting metas

生の配列やハッシュもまたリテラルとして展開される。たとえば、 Perl 6のパターンの中で配列を使ったならば、マッチャーは 配列の要素のいずれか(それぞれをリテラルとして)にマッチさせることを 試みる。このため:

        # Perl 6
        @cmd = ('get','put','try','find','copy','fold','spindle','mutilate');

        $str =‾ / @cmd ¥( .*? ¥) /;     # Match a cmd, followed by stuff in parens

これは以下と等価である:

        # Perl 5 
        @cmd = ('get','put','try','find','copy','fold','spindle','mutilate');
        $cmd = join '|', map { quotemeta $_ } @cmd;

        $str =‾ / (?:$cmd) ¥( .*? ¥) /;

話は変わるが、配列をアングルブラケットの中に置くと、マッチャーは 配列の各要素をリテラルとしてではなくパターンとしてマッチを試みる。

The incredible $hunk

<$hunk>がマッチしようと 試みているものは、プログラムの中で定義される次のひとつである。 以下は詳細なバージョンである:

    $hunk = rx :i {                             # 大小文字を無視...
        [                                       #   非キャプチャグループの開始
            <$linenum>                          #     $linenum中のサブルールにマッチ
            a                                   #     リテラルの'a'にマッチ
            ::                                  #     この選択肢をコミット
            <$linerange>                        #     $linerange中のサブルールにマッチ
            ¥n                                  #     改行にマッチ
            <$appendline>                       #     $appendline中のサブルールにマッチ...
                          +                     #         ...一回以上の繰り返し
        |                                       #   または...
          <$linerange> d :: <$linenum> ¥n       #     Match $linerange, 'd', $linenum, newline
          <$deleteline>+                        #     Then match $deleteline once-or-more
        |                                       #   または...
          <$linerange> c :: <$linerange> ¥n     #     Match $linerange, 'c', $linerange, newline
          <$deleteline>+                        #     Then match $deleteline once-or-more
          --- ¥n                                #     Then match three '-' and a newline
          <$appendline>+                        #     Then match $appendline once-or-more
        ]                                       #   End of non-capturing group
      |                                         # Or...
        (                                       #   Start a capturing group
            ¥N*                                 #     Match zero-or-more non-newlines
        )                                       #     End of capturing group
        :::                                     #     Emphatically commit to this alternative
        { fail "Invalid diff hunk: $1" }        #     Then fail with an error msg
    };

注意すべき最初の事柄は、Perl 5におけるqr と同様にPerl 6のrxは (ほとんど)任意のデリミタを選択することができるということである。 $hunkパターンは {...}を使っているけれども、 以下のものを使うことができる:

        rx/pattern/     # 標準
        rx[pattern]     # Alternative bracket-delimiter style
        rx<pattern>     # Alternative bracket-delimiter style
        rxE<laquo>formeE<raquo>      # D<eacute>limiteurs trE<egrave>s chic
        rx>pattern<     # Inverted bracketing is allowed too (!)
        rxE<raquo>MusterE<laquo>      # AnfE<uuml>hrungszeichen in richtiger Reihenfolge
        rx!pattern!     # Excited
        rx=pattern=     # Unusual
        rx?pattern?     # No special meaning in Perl 6
        rx#pattern#     # Careful with these: they disable internal comments

修飾された修飾子

実際のところ、rx のデリミタとして 許されていないのは':''('だけである。 これは、':'がPerl 6において パターン修飾子として使われ、 '('はパターン修飾子に 対して渡されるであろう引数を区切るために使われるからである。

Perl 6では、パターン修飾子は後ろにではなく前に置かれる。 これは構文解析を楽にする。なぜなら、正規表現の終端に達して /s/m/i/x、 を見つけたときに後戻りしてルールの内容を再解釈しないですむからである。 そして同じ理由で読むのも楽にする。

$hunkルールで使われている 唯一の修飾子は:i (case-insensitivity) 修飾子である。これはPerl 5のときと同じ動作をする。

Perl 6でのその他のルール修飾子は以下の通り:

:e or :each

これはPerl 5の/g修飾子の置き換え である。可能な限りマッチ(もしくは置換)を行うようにする。名前が 変わったわけは、"each"が"globally"よりも短くてわかりやすいからである。 また、:each 修飾子はもはや "グローバル"(global)ではないやり方の他の修飾子との組み合わせが可能な ためである(後述)。

:x($count)

この修飾子は:eに似て繰り返し マッチや置換を行うようにするが、:e とは異なり成功しなければならないマッチの数を正確に指定する。たとえば:

        "fee fi "       =‾ m:x(3)/ (f¥w+) /;  # 失敗
        "fee fi fo"     =‾ m:x(3)/ (f¥w+) /;  # 成功 ("fee","fi","fo" にマッチ)
        "fee fi fo fum" =‾ m:x(3)/ (f¥w+) /;  # 成功 ("fee","fi","fo" にマッチ)

繰り返し回数は定数である必要はないということに注意:

        m:x($repetitions)/ pattern /

すべての定数の場合に対応したきちんとした略称がある:

        m:1x/ pattern /         # m:x(1)/ pattern / と同じ
        m:2x/ pattern /         # m:x(2)/ pattern / と同じ
        m:3x/ pattern /         # m:x(3)/ pattern / と同じ
        # etc.

:nth($count)

この修飾子は繰り返しマッチや置換を行おうとするが、最初の $count-1回の成功したマッチを無視する。 たとえば:

        my $foo = "fee fi fo fum";

        $foo =‾ m:nth(1)/ (f¥w+) /;        # 成功 (matches "fee")
        $foo =‾ m:nth(2)/ (f¥w+) /;        # 成功 (matches "fi")
        $foo =‾ m:nth(3)/ (f¥w+) /;        # 成功 (matches "fo")
        $foo =‾ m:nth(4)/ (f¥w+) /;        # 成功 (matches "fum")
        $foo =‾ m:nth(5)/ (f¥w+) /;        # 失敗
        $foo =‾ m:nth($n)/ (f¥w+) /;       # $n の値による

        $foo =‾ s:nth(3)/ (f¥w+) /bar/;    # $fooの新しい内容: "fee fi bar fum"

これにも略称がある:

        $foo =‾ m:1st/ (f¥w+) /;           # 成功 (matches "fee")
        $foo =‾ m:2nd/ (f¥w+) /;           # 成功 (matches "fi")
        $foo =‾ m:3rd/ (f¥w+) /;           # 成功 (matches "fo")
        $foo =‾ m:4th/ (f¥w+) /;           # 成功 (matches "fum")
        $foo =‾ m:5th/ (f¥w+) /;           # 失敗

        $foo =‾ s:3rd/ (f¥w+) /bar/;       # $fooの新しい内容: "fee fi bar fum"

By the way, Perl isn't going to be pedantic about these "ordinal" versions of repetition specifiers. If you're not a native English speaker, and you find :1th, :2th, :3th, :4th, etc. easier to remember, that's perfectly okay.

繰り返し修飾子の様々なタイプはコロンによって区切って組み合わせることが 可能である:

        my $foo = "fee fi fo feh far foo fum ";

        $foo =‾ m:2nd:2x/ (f¥w+) /;        # 成功 ("fi", "feh"にマッチ)
        $foo =‾ m:each:2nd/ (f¥w+) /;      # 成功 ("fi", "feh", "foo"にマッチ)
        $foo =‾ m:x(2):nth(3)/ (f¥w+) /;   # 成功 ("fo", "foo"にマッチ)
        $foo =‾ m:each:3rd/ (f¥w+) /;      # 成功 ("fo", "foo"にマッチ)
        $foo =‾ m:2x:4th/ (f¥w+) /;        # 失敗 (:2xを満足させるだけのマッチがない)
        $foo =‾ m:4th:each/ (f¥w+) /;      # 成功 ("feh"にマッチ)

        $foo =‾ s:each:2nd/ (f¥w+) /bar/;  # $foo now "fee bar fo bar far bar fum ";

これら二つの修飾子の順番には特定の意味はないことに注意。

:p5 or :perl5

この修飾子はPerl 6にルールの内容をPerl 5の構文における正規表現である かのように解釈させる。これは主にPerl 5のコードの移植を助けるための ものである。そして意地悪を軽減するためのものである(?)。

:w or :word

この修飾子はパターンの中に現れた空白を、マッチ対象の文字列中の省略可能な 空白にマッチさせる。たとえば、陽に省略可能な空白を置く代わりに:

        $cmd =‾ m/ ¥s* <keyword> ¥s* ¥( [¥s* <arg> ¥s* ,?]* ¥s* ¥)/;

単純に次のように書ける:

        $cmd =‾ m:w/ <keyword> ¥( [ <arg> ,?]* ¥)/;

:w修飾子は、空白が実際にあるべき 場合を十分賢く見つけ出す。たとえば:

        $str =‾ m:w/a symmetric ally/

これは以下のものと同じで:

        $str =‾ m/a ¥s+ symmetric ¥s+ ally/

次のようなものではない:

        $str =‾ m/a ¥s* symmetric ¥s* ally/

"asymmetrically"のような文字列に 誤ってマッチしてしまうことはない。

:any

この修飾子はルールに、与えられた文字列に可能なあらゆる方法で マッチするようにし、同時に可能なマッチのすべてを返すようにする。たとえば:

        my $str = "ahhh";

        @matches =  $str =‾ m/ah*/;         # "ahhh"を返す
        @matches =  $str =‾ m:any/ah*/;     # "ahhh", "ahh", "ah", "a" を返す

:u0, :u1, :u2, :u3

これらの修飾子はドットメタキャラクタ(.) がUnicodeデータに対してどのようにマッチするかを指定する。 :u0が指定された場合、ドットは単一の バイトにマッチする。:u1 が指定された 場合にはドットは単一のコードポイント(つまり、一バイト以上の単一のUnicode "キャラクタ"をあらわすもの)にマッチする。 :u2が指定された場合には、ドットは 単一のgrapheme(書記素。つまり、アクセントのような修飾コードポイントがゼロ個以上 後続した基本コードポイント)にマッチする。 :u3が指定された場合には、ドットは 言語に依存したマナーによる適切な"なにか"にマッチする。

もしUnicodeを使わないのであれば、この修飾子は無視してかまわない。 通常どおり、Perlは"正しいことを行う"ために努力する。最後に、 ルールのデフォルトの振る舞いはオーバーライドするプラグマ (たとえばuse bytes)が効力を持たない 限り、:u2である。

/s, /m, /e といった修飾子はもはや有効でない ことに注意。それはこれらが必要でなくなったからである。 /sはドットメタキャラクタ . が改行にもマッチするようになったので 必要なくなった。"改行以外の何か"にマッチさせたい場合には、新たな メタトークン¥N ("¥nの反対")を使用する。

/m修飾子は、 ^$が常に文字列の先頭や末尾を意味する ようになったため必要なくなった。行の先頭や末尾にマッチさせるには、 新たなメタトークン^^$$ を代わりに使用する。

/e修飾子はもはや必要ではない。 なぜなら、Perl 6は文字列展開子(string interpolator) $(...) を提供しているからである。 だから、次のような置換は:

        # Perl 5
        s/(¥w+)/ get_val_for($1) /e;

次のように置き換えられる:

        # Perl 6
        s/(¥w+)/$( get_val_for($1) )/;

Take no prisoners

$hunk ルールの最初のキャラクタは 開きスクウェアブラケットである。Perl 5ではそれはキャラクタクラスの始まりを 表すが、Perl 6では異なっている。Perl 6においては、スクウェアブラケットは 非キャプチャグループの境界を表す。つまり、Perl 6におけるスクウェアブラケット のペアはPerl 5における(?:...)と 同じであるが、より低いline-noisyである。

ところで、Perl 6においてキャラクタクラスを使うには、スクウェアブラケット をメタ構文的アングルブラケット(metasyntactic angle brackets)の ペアの内側においてやる必要がある。だから:

        # Perl 5
        / [A-Za-z] [0-9]+ /x          # 数字が後続する A-Z か a-z

Perl 5では上記のようになるが、Perl 6では以下のようになる:

        # Perl 6
        / <[A-Za-z]> <[0-9]>+ /       # 数字が後続する A-Z か a-z

Perl 5でのキャラクタクラスの捕集合:

        # Perl 5
        / [^A-Za-z]+ /x               # A-Zでもa-zでもないひとつ以上のキャラクタ

becomes in Perl 6: から、Perl 6では以下のようになる:

        # Perl 6
        / <-[A-Za-z]>+ /              #  A-Zでもa-zでもないひとつ以上のキャラクタ

The external minus sign is used (instead of an internal caret) because Perl 6 allows proper set operations on character classes, and the minus sign is the "difference" operator. So we could also create: 外側にあるマイナス記号は(内部に置かれたキャレットの代わりに)Perl 6に キャラクタクラスに関して適切な集合演算を許す働きをする。ここでマイナス記号は 差("difference")演算子である。だから、以下のようにすることができる:

        # Perl 6
        / < <alpha> - [A-Za-z] >+ /   # A-Z、a-z以外のアルファベットすべて
                                      # (つまり、アクセントつきアルファベット)

Perl 6では、キャラクタクラス指定は意図的に少々不便なものになっている。 なぜなら、キャラクタクラスといったものがUnicodeの世界では良くないアイデア であるからである。たとえば、先の例にあるキャラクタクラス [A-Za-z]Ã, &eaute;, ø, のようなLatin-1の標準アルファベットキャラクタにはマッチせず、 キリル文字やひらがな、Ogham、Cherokee、クリンゴンといったコードセットから 離れたものになっている。

Meanwhile, back at the $hunk...

$hunkパターンの非キャプチャグループ は三つの選択肢をグループ化している。それぞれは(Perl 5と同じように) メタキャラクタ|で区切られている。 最初の選択肢:

        <$linenum> a :: <$linerange>
        ¥n                         
        <$appendline>+

は、変数$linenumの内容をgrabして、 サブパターンとして扱ってマッチを行おうとする。それからリテラル文字の 'a' (ルールに:i修飾子があるので 'A'も)にマッチする。 それから変数$linerangeの内容に マッチする。さらに改行が続き、 $appendlineにあるパターンに 繰り返しマッチさせることを試みる。

しかし、aの後ろにあるダブルコロンは なんだろうか? パターンはそこで二つのコロンにマッチすることを 試みるべきなのだろうか?

This or nothing

実際には違う。ダブルコロンはPerl 6における新たなパターン制御構造である。 パターンがマッチングに成功したときには何の効果もない(無視される)がm パターンマッチが失敗しようとするとき、そしてダブルコロンを通り越して バックトラックを起こそうとしたとき -- たとえば、ひとつ少ない回数の繰り返しで 先行するものの再マッチをしようとするとき -- に ダブルコロンは それを囲むグループ全体 (つまり、この場合は[...]で囲まれた部分) がマッチングに失敗するようにする。

これはこの例において最適化を行うのに便利である。 'a'が後続する行番号にマッチして、 その後で失敗したならば、同じグループ内にある他の二つの選択肢のいずれかを 試す場所がない。'a'を見つけているので、 代わりに'd''c' をマッチさせるチャンスはない。

だから、一般的にはダブルコロンの意味はこう: "この場所で、私はカレントグループ中のこの選択肢をコミットした -- この場所より 後で失敗したならほかのものを試す必要はない" (At this point I'm committed to this alternative within the current group -- don't bother with the others if this one fails after this point)。

これに似た他の制御ディレクティブもある。シングルコロンの意味するところは: "先行する要素へのバックトラッキングは必要ない" ("Don't bother backtracking into the previous element")。 これは以下のようなパターンで便利である:

        rx:w/ $keyword [-full|-quick|-keep]+ : end /

ここで、(リテラルとして)キーワードと、一つ以上の三種類のオプションのマッチに 成功したが、'end'でマッチに失敗したと 仮定しよう。この場合、バックトラックする場所がなくて、一つ少ない オプションのマッチを試したとしてもやはり 'end'を見つけ出すところで失敗する。 そして別のオプションをバックトラックして試しては 失敗を繰り返す。 繰り返しの後にコロンを使うことにより、マッチャーに最初の適用の後で 諦めることを指示する。

However, the single colon isn't just a "Greed is Good" operator. It's much more like a "Resistance is Futile" operator. That is, if the preceding repetition had been non-greedy instead: しかしながら、シングルコロンは単なる"欲張りは良いこと"(Greed is Good)演算子 ではない。"抵抗は無意味だ"演算子がよりふさわしい。だから、先行する繰り返しが ものぐさ(non-greedy)なものであった場合:

        rx:w/ $keyword [-full|-quick|-keep]+? : end /

コロンを越えたバックトラッキングは +?より多くの オプションにマッチしようとすることを阻害する。ここで、 ものぐさな繰り返しは最初に繰り返しの最小の回数(つまりここでは一回)に マッチすることを試し、後続するコロンはより長いマッチを試すためのバックトラック を阻害するので、x+?:xのややこしい書き方に過ぎないということに 注意すること。同様に、 x*?:x??:xのゼロ回にマッチする 不可解なやり方である。

Generally, though, a single colon tells the pattern matcher that there's no point trying any other match on the preceding repetition, because retrying (whether more or fewer repetitions) would just waste time and would still fail. それでも、一般的にはひとつのコロンはパターンマッチャーに先行する 繰り返しに関して試すべき他のマッチがないことを教える。なぜなら、 リトライは(繰り返しが少なくなろうが多くなろうが)単に時間を浪費して 失敗するだけだからだ。

There's also a three-colon directive. Three colons means: "If we have to backtrack past here, cause the entire rule to fail" (i.e. not just this group). If the double-colon in $hunk had been triple: 同様に、三つのコロンのディレクティブがある。三つのコロンの意味するところは: "バックトラックをここで行う必要があるとしたら、ルール全体を失敗させる" (このグループに限らない)。$hunk のダブルコロンがトリプルコロンだったとすると:

        <$linenum> a ::: <$linerange>
        ¥n                         
        <$appendline>+

行番号と'a'にマッチして、 その後での失敗が$hunkルール全体を 即座に失敗させる(それを起動した $fileルールが他の選択肢で 成功する可能性があったとしても)。

So, in general, a triple-colon specifies: "At this point I'm committed to this way of matching the current rule -- give up on the rule completely if the matching process fails at this point". だから、一般的にはトリプルコロンの意味するところは: "この場所でカレントルールのこのマッチングをコミットする -- この場所で マッチングプロセスが失敗したらルールを完全にあきらめる"。

四つのコロン・・・これはちょっとわかりづらい。そのため、代わりに 特別な名前のついたディレクティブがある: <commit>がそうだ。 <commit>を越えたバックトラックは マッチ全体を即座に失敗させる。そしてもしカレントルールがより大きな ルールの一部としてマッチを行っていたならば、その大きなルールも 同様に失敗する。言い換えれば、 "Blow up this Entire Planet and Possibly One or Two Others We Noticed on our Way Out Here" 演算子である。

If the double-colon in $hunk had been a <commit> instead: $hunkの中のダブルコロンが <commit>だったとすると:

        <$linenum> a <commit> <$linerange>
        ¥n                         
        <$appendline>+

行番号と'a'にマッチして その後で失敗した場合には$hunk ルール全体が即座に失敗し、同時にそれを起動した $fileルールも即座に失敗させる。

So, in general, a <commit> means: "At this point I'm committed to this way of completing the current match -- give up all attempts at matching anything if the matching process fails at this point". であるから、一般的には<commit> が意味するところは: "この場所でカレントマッチのこの選択をコミットする -- このマッチングプロセスが この場所で失敗したならすべてのマッチングをあきらめる"。

Failing with style

その他の二つの選択肢:

        | <$linerange> d :: <$linenum> ¥n
          <$deleteline>+                 
        | <$linerange> c :: <$linerange> ¥n
          <$deleteline>+  --- ¥n  <$appendline>+                

これらは最初のものの変種である。

スクウェアブラケットの中の三つの選択肢にマッチするものがなかったなら、 ブラケットの外にある選択肢が試される:

        |  (¥N*) ::: { fail "Invalid diff hunk: $1" }

これは非改行キャラクタの並びをキャプチャする (¥N は "not ¥n"。 ¥S が "not ¥s" あるいは ¥W が "not ¥w" であるのと同じこと)。 それからパターンの内側にあるPerlコードのブロックを起動する。 このfailの呼び出しは、その場所で マッチが失敗するようにする。そして、エラー変数 $! に関連付けられたエラーメッセージが セットされる(これは$0の一部 としてもアクセス可能である)。

繰り返しの後でトリプルコロンを使っていることに注意すること。 ブロック中のfailがバックトラックを 引き起こすが、再度試すべきキャラクタはない。なぜなら、元々の失敗が 望んだ動作であるからだ。トリプルコロンの存在はその場所に最初に バックトラッキングが到達したら即座にルール全体を失敗させる。

$hunkルールの全般的な効果は、 diffのhunkのひとつにマッチするかエラーメッセージと共に失敗するかの いずれかである。

Home, home on the (line)range

三番目と四番目のルール:

    $linerange = rx/ <$linenum> , <$linenum>
                   | <$linenum> 
                   /;

    $linenum = rx/ ¥d+ /;

は数字の並びから構成される行番号、そしてカンマで区切られた二つの行番号 もしくはひとつの行番号を指定している。 $linerangeルールは次のように書くことも できる:

    $linerange = rx/ <$linenum> [ , <$linenum> ]? /;

これはより効果的かもしれない。なぜなら、バックトラックをする必要がなく、 また、二番目の選択肢で 最初の$linenumに再度マッチする 必要がないからだ。 しかしながら、ルールオプティマイザはこのようなケースを見つけ出して 自動的に一般的なプリフィックスにするだろうから、手動でそれを行うために 可読性を減じる価値はないかもしれない。

What's my line?

最後の二つのルールはdiffにおける独立したコンテキスト行の構造を 指定している(テキストがhunkによって追加されたり削除されたりする行):

        $deleteline = rx/^^ ¥< <sp> (¥N* ¥n) /
        $appendline = rx/^^ ¥> <sp> (¥N* ¥n) /

^^マーカーは各ルールが 行の先頭から始まっていることを保証している。

行の最初のキャラクタは'<'もしくは '>'のいずれかでなければならない。 アングルブラケットがPerl 6のメタキャラクタであるためにこれらのキャラクタを エスケープする必要があることに注意。別の選択肢としては"リテラル文字列" メタ構文を使うものがある:

        $deleteline = rx/^^ <'<'> <sp> (¥N* ¥n) /
        $appendline = rx/^^ <'>'> <sp> (¥N* ¥n) /

つまり、シングルクォートに囲まれた文字列のアングルブラケットはリテラルとして 文字のならびにマッチする(空白や他のメタトークンも含む)。

あるいはクォートメタメタ構文 (¥Q[...])を使うこともできる:

        $deleteline = rx/^^ ¥Q[<] <sp> (¥N* ¥n) /
        $appendline = rx/^^ ¥Q[>] <sp> (¥N* ¥n) /

Perl 5の¥Q...¥E構造はPerl 6では ¥Qマーカーに置き換えられたことに注意。 これはグループを後ろに取る。

単一文字のキャラクタクラスを使うこともできる:

        $deleteline = rx/^^ <[<]> <sp> (¥N* ¥n) /
        $appendline = rx/^^ <[>]> <sp> (¥N* ¥n) /

はたまたnamed キャラクタ (¥c[CHAR NAME HERE])を使うこともできる:

        $deleteline = rx/^^ ¥c[LEFT ANGLE BRACKET] <sp> (¥N* ¥n) /
        $appendline = rx/^^ ¥c[RIGHT ANGLE BRACKET] <sp> (¥N* ¥n) /

どのやり方が単にアングルブラケットをエスケープするよりも良いかは もちろん個々人の好みの問題である。

The final frontier

先行するアングルブラケットの後に、一つのリテラルの空白がくることが 期待されている。繰り返すが、これを指定するのに エスケープ(¥ )、 リテラル(<' '>)、 クォートメタ(¥Q[ ])、 キャラクタクラス(<[ ]>)、 deterministic nomimalism (¥c[SPACE]) が使えるが、Perl 6ではスペースのキャラクタにシンプルな名前を 提供している:<sp> これは好ましいオプションである。なぜなら、行のノイズを軽減し、かつ スペースを目立たせて見失いにくくするからである。

Perl 6では、以下に挙げるような便利なサブパターンの名前をあらかじめ定義 している:

<dot>

これはリテラルのドットキャラクタ('.') にマッチする(つまり、¥.のエレガントな 同義語)。

<lt><gt>

これらはそれぞれ'<''>' にリテラルとしてマッチする。 これらはまた別の記述法をわれわれに提供する:

        $deleteline = rx/^^ <lt> <sp> (¥N* ¥n) /
        $appendline = rx/^^ <gt> <sp> (¥N* ¥n) /

<ws>

これは空白の任意の並びにマッチする(つまり、 ¥s+のよりエレガントな同義語である)。 省略可能な空白はしたがって、 <ws>?もしくは  <ws>*で指定する (Perl 6はいずれも受け付ける)。

<alpha>

これは単一のアルファベット文字にマッチする(つまり、キャラクタクラス <[A-Za-z]> のようなものであるが、 アクセントつきキャラクタや非ローマンスクリプトのアルファベットキャラクタも 取り扱う)。

<ident>

これは[ [<alpha>|_] ¥w* ]の 短縮形である(つまり、Perlを含めた多くの言語における標準の識別子)。

これらの名前つきサブパターンを使うことでルールを明確で、読みやすく、 より自己記述的なものとする。そして にまとめられているものをみることができる。それらはフルに一般化されていて・・・自分自身のものを作ることができる。

Match-maker, match-maker...

やっと実際に読み込む準備ができて、diffファイルのマッチを行える。 Perl 5では以下のようにして行う:

        # Perl 5

        local $/;          # 入力レコードセパレータを無効にする (slurp modeにする)
        my $text = <>;     # 入力ストリームを$textにSlurp up する

        print "Valid diff" 
            if $text =‾ /$file/;

同じことを(構文が明らかに違うにもかかわらず) Perl 6でできて、この場合はきれいなものになるだろう。 しかし一般的には、マッチングを開始する前に入力全体をslurp upする必要が あることがclunkyである。入力は巨大なものである可能性があり、 早期にマッチングに失敗するかもしれない。あるいは、入力を対話的 (そして入力がマッチに失敗したら即エラーメッセージを出す)にマッチしたい かもしれない。異なるフォーマットの並びのマッチングをしたいかもしれないし、 マッチに失敗したときにはオリジナルの状態の入力ストリームを放っておける ようにしたいかもしれない。

入力ストリームに対して即座にパターンマッチを行うことができないことは、 Perl 5のテキスト処理における数少ない弱点の一つである。もちろん、 一行ごとに読み込んで行ごとにパターンマッチングを行うことはできるけれども、 わからない数の行から構成される構造にマッチを試みることは痛みを伴う。

しかしPerl 6ではそうではない。Perl 6では、入力ストリームを スカラー変数に束縛することができる(Perl 5におけるtied変数のように)。 そして、そのストリーム中のキャラクタがすでにメモリにあるかのように マッチを行うことができる:

        my $text is from($*ARGS);       # スカラーを入力ストリームにバインドする

        print "Valid diff" 
            if $text =‾ /<$file>/;      # 入力ストリームに対してマッチさせる

重要な点は、マッチの後で実際にパターンにマッチしたキャラクタだけが 入力ストリームから取り去れられるということである。

変数をスキップして書くだけということも許される:

        print "Valid diff" 
            if $*ARGS =‾ /<$file>/;     # 入力ストリームに対してマッチを行う

あるいは:

        print "Valid diff" 
            if <> =‾ /<$file>/;         # 入力ストリームに対してマッチを行う

しかしこれはこれから決める(to be decided)ことである。

A cleaner approach

先の例は正当なdiffファイルを解析する問題を見事に解決する(たったの 6つのルールで!)。しかし、それはプリコンパイルされたパターンを 格納する変数の乱雑な並びによるものである。

以下のようなサブルーチンの集合を書いたとしよう:

    my $print_name = sub ($data) { print $data{name}, "¥n"; };
    my $print_age  = sub ($data) { print $data{age}, "¥n"; };
    my $print_addr = sub ($data) { print $data{addr}, "¥n"; };

    my $print_info = sub ($data) {
        $print_name($data);
        $print_age($data);
        $print_addr($data);
    };

    # and later...

    $print_info($info);

上記のように書くこともできるが、それは正しい方法ではない。 正しい方法は名前つきサブルーチンや名前つきメソッドの集合のように、 クラスやモジュールの名前空間に集めることである:

    module Info {

        sub print_name ($data) { print $data{name}, "¥n"; }
        sub print_age ($data)  { print $data{age}, "¥n"; }
        sub print_addr ($data) { print $data{addr}, "¥n"; }

        sub print_info ($data) {
            print_name($data);
            print_age($data);
            print_addr($data);
        }
    }

    Info::print_info($info);

これはPerl 6のパターンを使っている。実行時に生成されるパターンオブジェクト の並びとして記述することも可能であるけれども、名前つきパターン の集合として指定して文法の名前空間にコンパイル時にまとめてしまう方が良い。

以下は、先に挙げたdiffを解析する例を上記のやり方 (幾つかのbells-and-whistlesが追加されている)で書き直したものである:

    grammar Diff {
        rule file { ^  <hunk>*  $ }

        rule hunk :i { 
            [ <linenum> a :: <linerange> ¥n
              <appendline>+ 
            |
              <linerange> d :: <linenum> ¥n
              <deleteline>+
            |
              <linerange> c :: <linerange> ¥n
              <deleteline>+
              --- ¥n
              <appendline>+
            ]
          |
            <badline("Invalid diff hunk")>
        }

        rule badline ($errmsg) { (¥N*) ::: { fail "$errmsg: $1" }

        rule linerange { <linenum> , <linenum>
                       | <linenum>
                       }

        rule linenum { ¥d+ }

        rule deleteline { ^^ <out_marker> (¥N* ¥n) }
        rule appendline { ^^ <in_marker>  (¥N* ¥n) }

        rule out_marker { ¥<  <sp> }
        rule in_marker  { ¥>  <sp> }
    }

    # and later...

    my $text is from($*ARGS);

    print "Valid diff" 
        if $text =‾ /<Diff.file>/;

What's in a name?

grammar宣言はルールのための 新たな名前空間を生成する(class宣言や module宣言がメソッドやサブルーチンの ための新たな名前空間を生成するのと同じ)。文法の名前の後にブロックが 指定されていたならば:

        grammar HTML {

            rule file :iw { ¥Q[<HTML>]  <head>  <body>  ¥Q[</HTML>] }

            rule head :iw { ¥Q[<HEAD>]  <head_tag>+  ¥Q[<HEAD>] }

            # etc.

        } # HTML文法の明確な終端

新たな名前空間はブロックに閉じ込められる。そうでない場合には、名前空間は カレントファイルのそのソースセクションの終わりまで継続する:

        grammar HTML;

        rule file :iw { ¥Q[<HTML>]  <head>  <body>  ¥Q[</HTML>] }

        rule head :iw { ¥Q[<HEAD>]  <head_tag>+  ¥Q[<HEAD>] }

        # etc.

        # HTML文法の暗黙的な終端
        __END__

この構文形式は、classmoduleのブロックなしバージョンと 同じく、単純にファイル毎に一つの名前空間とするためにデザインされた ものである。一つのファイルに二つ以上の文法やクラス、モジュールを置くと コンパイル時エラーとなる。

名前空間の中では、名前つきルールはrule 宣言子を使って定義される。これはモジュールにおける sub宣言子やクラスにおける method宣言子と同様のものである。 クラスメソッドと同様に、名前つきルールはその名前空間の外において 参照されるときには文法を通す必要がある。したがって実際のマッチは 次のようになる:

        $text =‾ /<Diff.file>/;         # 文法を通して起動する

are really just predefined named rules that come standard with Perl 6. 名前つきルールにマッチさせたいのなら、その名前をアングルブラケットで 囲んでやる。実際にそのような構造の多くをすでに見ている -- <sp>, <ws>, <ident>, <alpha>, <commit> -- これらはPerl 6に標準で備わっているあらかじめ定義されている名前つきルール である。

サブルーチンやメソッドと同じように、固有の名前空間の中では ルールは修飾する必要がない:

        rule linerange { <linenum> , <linenum>
                       | <linenum>
                       }

これは以下のものと同じである:

        rule linerange { <Diff.linenum> , <Diff.linenum>
                       | <Diff.linenum>
                       }

名前つきルールを使うことによりいくつかの明確な利点がある (パターンが見やすくなることは別として)。一例を挙げると、コンパイラは 埋め込まれた名前つきルールについて最適化を行うことができるかもしれない。 たとえば、linerangeルールの中の <linenum>マッチをインラインで 行うことが可能かもしれない。rx バージョン:

        $linerange = rx{ <$linenum> , <$linenum>
                       | <$linenum>
                       };

ではこれはできない。なぜなら、パターンマッチング機構は実際にマッチを 行うまで$linenumの中身を知ることが ないからである。

ところで名前つきルールの中で <$subrule>的な サブパターンの展開を使うことができる。そして rx的なルールの中で名前つきサブパターンを 使うこともできる。rulerx の間の違いは、 ruleが名前をもつことができて デリミタとして{...} を使わなければ ならないのに対して、rxは 名前を持たずまた、許される任意のデリミタを使うことができるという点である。

Bad line! No match!

このバージョンのdiff解析器はbadline という名前のついた新たなルールを持っている。このルールは ルールとサブルーチン/メソッドの間の別の相似点を明らかにする: ルールは引数をとることができる。badline ルールはhunkルールの末尾で エラーメッセージを作り出している。以前はルールはこう終わっていた:

        |  (¥N*) ::: { fail "Invalid diff hunk: $1" }

しかしこのバージョンでは次のように終わっている:

        |  <badline("Invalid diff hunk")>

エラーの条件をより抽象化している。これはわかりやすくて保守しやすいが 新たなbadlineサブルールに 引数を与えることが可能になることを要求している。これを行うために、 引数リストを取るように宣言する:

        rule badline($errmsg) { (¥N*) ::: { fail "$errmsg: $1" }

サブルーチンの定義と構文的に良く似ていることに注意:

        sub  subname($param)  { ... }

引数はアングルブラケットの中にあるルール名の後に括弧でくくられることで サブルールに渡される:

        |  <badline("Invalid diff hunk")>

引数は括弧なしで渡すこともできるが、その場合には 分割されたルールの本体のように解釈される:

        rule list_of ($pattern) { 
                <$pattern> [ , <$pattern> ]*
        }

        # and later...

        $str =‾ m:w/  ¥[                  # リテラルの開きスクウェアブラケット
                      <list_of ¥w¥d+>     # rx/¥w¥d+/を渡してlist_of サブルールを呼び出す
                      ¥]                  # リテラルの閉じスクウェアブラケット
                   /;

一つのルールは必要に応じて任意の数の引数を取ることができる:

        rule seplist($elem, $sep) {
                <$elem>  [ <$sep> <$elem> ]*
        }

そしてそれらの引数は、Perl 6の標準のpair-based 機構を使って 名前によって渡される(Apocalypse 3で記述されているように)。

        $str =‾ m:w/
                    ¥[                                      # リテラルの左スクウェアブラケット
                    <seplist(sep=>":", elem=>rx/<ident>/)>  # コロンで区切られた識別子のリスト
                    ¥]                                      # リテラルの右スクウェアブラケット
                   /;

このリストの要素指定子はそれ自身が無名ルールであることに注意。これは seplistルールが 後でパターンとして展開されるということである ($elemパラメータが seplistの中のアングルブラケットの 中に現れるため)。

Thinking ahead

この文法バージョンのdiff解析器の他の変更はコンテキスト行の先頭にある '<''>' のマッチングをfactored outした ことにある。これは以前はこうだった:

        $deleteline = rx/^^ ¥< <sp> (¥N* ¥n) /
        $appendline = rx/^^ ¥> <sp> (¥N* ¥n) /

今はこう:

        rule deleteline { ^^ <out_marker> (¥N* ¥n) }
        rule appendline { ^^ <in_marker>  (¥N* ¥n) }

        rule out_marker { ¥<  <sp> }
        rule in_marker  { ¥>  <sp> }

これは明らかな利点なしに文法を複雑化しているために後戻りしているように 思える。しかし、その利点は後で 取り込む行や削除する行に対して異なるマーカーを使っている 別のタイプのdiffファイルに遭遇したときに明らかになる。

What you match is what you get

変数ベースのものと文法バージョンの両方ともがdiffを認識する 大きな仕事をするが、それがすべてだ。もし構文チェックをしたいだけなら それでも良い。しかし、一般的にはデータを解析するときに本当にしたいことは それに対して何かを行いたいものだ: 何か別の構文に変換したり、 その内容を変更したり、プログラムで扱うために Perlの内部データ構造に変換することもあるだろう。

先の例にマッチしたdiffを表す階層的なPerlのデータ構造を構築したいと しよう。われわれが必要とする新たなコードは?

必要ない。

Perl 6はパターンにマッチしたときは常に自動的に マッチの様々な内容を表す結果オブジェクト("result obuject")を構築する。

その結果オブジェクトは$0という名前で (プログラムの名前は$*PROGとなった)、 マッチが起こった場所にレキシカルスコープである。結果オブジェクトは パターンによってマッチした完全な文字列を格納し、文字列コンテキストに おいて使われたときには文字列として評価される。たとえば:

        if ($text =‾ /<Diff.file>/) {
                $difftext = $0;
        }

これは手軽ではあるが、データ構造を展開するためにはあまり便利ではない。 しかし、マッチの中の、括弧を使ってキャプチャされたコンポーネントは このオブジェクトの配列属性の要素となっていて、配列インデックス演算子 を通してアクセスすることができる。だから、たとえばパターンが 以下のようなものであれば:

    rule linenum_plus_comma { (¥d+) (,?) };

そしてマッチが成功すれば結果オブジェクトの配列要素 1 ($0[1])は最初の括弧による キャプチャ(ここでは数字列)の結果が代入され、配列の要素 2 ($0[2])はカンマとなる。 結果オブジェクトの配列要素 0はパターンマッチの完全な文字列が 代入されるということに注意。

$0の配列要素には短縮形がある。 $0[1]$1として参照することができる。 同様に、$0[2]$2で参照でき、以下 $0[3]$3などのように参照できる。 $0と同様、これらの数値変数は それぞれパターンマッチが行われたスコープにレキシカルである。

名前つきサブルールによってマッチしたマッチ文字列の部分は、結果オブジェクトの ハッシュ属性に登録されハッシュのルックアップ演算子を通してアクセスすることが 可能である。パターンが以下のようなもので:

        rule deleteline { ^^ <out_marker> (¥N* ¥n) }

マッチに成功した場合、結果オブジェクトの 'out_marker'キー のためのハッシュエントリ($0{out_marker}) はout_markerサブルールにマッチした 結果オブジェクトが格納される。

A hypothetical solution to a very real problem

ハッシュへの名前つきキャプチャは非常に便利であるけれども、 以下のようなルールではうまくいかない:

        rule linerange {
              <linenum> , <linenum>
            | <linenum>
        }

問題は、ルールの$0 のハッシュ属性 は'linenum'キーの一つのエントリに しか格納できないということである。だから、選択肢 <linenum> , <linenum> がマッチしたならば、結果オブジェクトは二番目の <linenum>が最初の <linenum>のマッチのための エントリを上書きしてしまう。

これに対する解決策は"hypothetical variables"(仮定変数?)で知られる 新たなPerl 6パターンマッチング機能である。hypothetical変数は パターンの中で宣言と束縛が行われる変数(ルールの中のクロージャのような もの)である。この変数は宣言されるが、 my, our, tempといったものを使うのではなく 新たなキーワードletを使う。 これはhypothetical assumptionを示すために数学者や哲学者がつかう ので選ばれた。

一度宣言されれば、hypothetical変数は通常の束縛演算子を使って束縛が 行われる。たとえば:

        rule checked_integer {
                (¥d+)                   # 一つ以上の数字にマッチし、それをキャプチャする
                { let $digits := $1 }   # hypothetical 変数 $digits に束縛
                -                       # ハイフンにマッチ
                (¥d)                    # 数字一つにマッチしそれをキャプチャ
                { let $check := $2 }    # hypothetical 変数 $checkに束縛
        }

この例では、数字の列が見つかったら変数 $digitsがその部分文字列に束縛される。 そして、ダッシュとチェックデジットがマッチしたら。その数字は $checkに束縛される。しかしながら、 ダッシュかチェックデジットがマッチしなかったら、マッチは失敗して クロージャを通してバックトラックが起こる。このバックトラックは hypothetical変数$digits束縛解除を自動的に行う。したがって、ルールがマッチに 失敗したならば、その中にあるhypothetical変数は何の値にも関連付けられない。

それぞれのhypothetical変数は実際には結果オブジェクトのハッシュ属性の 対応するエントリの別名である。だから、ルールの中の $digitsのようなhypothetical変数は そのルールの結果オブジェクトの $0{digits}要素にセットする

だから、たとえば行の範囲の二つの行番号を区別するには:

        rule linerange {
              <linenum> , <linenum>
            | <linenum>
        }

二つのhypothetical変数、 $from$to にそれらを束縛する:

        rule linerange {
              (<linenum>)               # linenum にマッチしキャプチャ結果は$1となる
              { let $from := $1 }       # 結果をhypothetical変数にセーブする
              ,                         # カンマにマッチ
              (<linenum>)               # linenum にマッチしキャプチャ結果は$2となる
              { let $to := $2 }         # 結果をhypothetical変数にセーブする
            |
              (<linenum>)               # linenum にマッチしキャプチャ結果は$3となる
              { let $from := $3 }       # 結果をhypothetical変数にセーブする
        }

これで結果オブジェクトはハッシュエントリ $0{from}と(多分) $0{to}を持つ(最初の選択肢がマッチした 場合)。実際には、第二の選択肢の中で対応したhypothetical変数に代入することに よって$0{to}が結果オブジェクトに 常に存在することを保証するすることができる:

        rule linerange {
              (<linenum>)
              { let $from := $1 }
              ,         
              (<linenum>)
              { let $to := $2 }
            |
              (<linenum>)
              { let $from := $3; let $to := $from }
        }

問題は解決した。

しかし新たな問題を引き起こしている。すべてのhypothesizingはルールを 醜くて複雑なものにしている。だから、Perl 6は見やすい短縮形を用意している:

        rule linerange {
              $from := <linenum>          # linenum ルールにマッチし、結果を$fromに束縛する
              ,                           # カンマにマッチ
              $to := <linenum>            # linenum ルールにマッチし、結果を$toに束縛する
            |                             # または・・・
              $from := $to := <linenum>   # linenum ルールにマッチし、
        }                                 #   結果を$fromと$toの両方に束縛する

あるいはよりコンパクトに:

        rule linerange {
              $from:=<linenum> , $to:=<linenum>
            | $from:=$to:=<linenum>
        }

Perl 6のルールが束縛演算子 (:=) が直後に続く変数を持っていたならば、その変数は決して展開されない。 その代わりに、hypothetical変数として扱われて、後続するルールの結果に 束縛される(上記の例では、 <linenum>サブルールが マッチした結果)。

繰り返されたキャプチャの内容を束縛する hypothetical配列やhypotheticalハッシュを使うこともできる。 たとえば、hunkのセットの名前を選ぶことができるだろう:

        rule file { ^  @adonises := <hunk>*  $ }

これは<hunk>にマッチしたすべてを 一つの配列(マッチの後で $0{'@adonises'}で参照できる。 この場合のキーにはsigilが含まれることに注意)に集めている。

hypotheticalハッシュに束縛することを選択することもできる:

        rule config {
            %init :=            # Hypothetically, bind %init to...
                [               # グループの開始
                    (<ident>)   # 識別子にマッチしそれをキャプチャ
                    ¥h*=¥h*     # 省略可能な水平的空白を伴った等号
                    (¥N*)       # 行の残りをマッチしてキャプチャする
                    ¥n          # 改行にマッチ
                ]*
        }

[...]*のグルーピングの繰り返しは それぞれ繰り返しの中で二つの部分文字列をキャプチャして、それを キー/値のペアへと変換され、その後でハッシュに追加される。 各繰り返しの中で最初のキャプチャされた部分文字列はキーとなり、 二番目のキャプチャされた部分文字列はそれから連想される値となる。 hypotheticalハッシュ%init$0{'%init'} (繰り返すがmキーの一部分であるsigilに注意)として ルールの結果オブジェクトを通してアクセスすることもできる。

The nesting instinct

Of course, those line number submatches in: もちろん、以下でマッチした行番号は:

        rule linerange {
              $from:=<linenum> , $to:=<linenum>
            | $from:=$to:=<linenum>
        }

will have returned their own result objects. And it's a reference to those nested result objects that actually gets stored in linerange's $0{from} and $0{to}. 固有の結果オブジェクトを返す。

同様に、次に高いルールの中で:

    rule hunk :i { 
        [ <linenum> a :: <linerange> ¥n
          <appendline>+ 
        |
          <linerange> d :: <linenum> ¥n
          <deleteline>+
        |
          <linerange> c :: <linerange> ¥n
          <deleteline>+
          --- ¥n
          <appendline>+
        ]
    };

<linerange>にマッチしたものは そのオブジェクト$0 を返す。このため、hunkルールの中では hunkの行番号の範囲の数字の"from"に次のようにしてアクセスすることができる: $0{linerange}{from}

同様に、最も高いレベルで:

    rule file { ^  <hunk>*  $ }

hunkのならびにマッチしようとしているので、hypothetical変数 $hunk(と当然ながら $0{hunk})は 個々の<hunk>にマッチした 結果オブジェクトのならびからなる配列属性を持った結果オブジェクトから 構成される。

だから、たとえば三番目のhunkの行の範囲の数字の"from"に以下のようにしてアクセス することができる: $0{hunk}[2]{linerange}{from}

Extracting the insertions

より便利なように、"append" hunkによって挿入されたのか "change" hunkによって挿入されたのかに関係なく、挿入されたdiffの各行を 場所を特定したり出力したりすることができる:

    my $text is from($*ARGS);

    if $text =‾ /<Diff.file>/ {
        for @{ $0{file}{hunk} } -> $hunk {
             print @{$hunk{appendline}}
                 if $hunk{appendline};
        }
    }

ここで、if文はdiffファイルのために パターンに対してテキストをマッチさせようとしている。もしこれが成功すれば、 forループは 結果オブジェクト<hunk>* を grabして、それを配列として扱って各マッチしたオブジェクトを繰り返し $hunkにセットする。 各hunkマッチの追加された行の配列はそれから出力される (hunkの中の配列に対するリファレンスがあれば)。

Don't just match there; do something!

Perl 6のパターンはその内側に属性コードブロックを持つことができるので、 解析の間に構文を変化させるようなパターンを持つことが簡単にできる・ これはしばしば便利なテクニックである。なぜなら、階層的な表現の 様々な部分を(解析しているルールの中で) ローカルに扱うことを許すからである。

たとえば、逆の(reverse)diffファイルが欲しかったとしよう。つまり、 ファイルAからファイルBへ変換するために必要な変更を指定するのではなく、 逆方向の、ファイルBからファイルAへの逆方向の変換が必要だとする。 これは作るのがかなり簡単である。すべての"append"を"delete"にして すべての"delete"を"append"にし、そしてすべて"change"を逆にするのである。

以下のコードがそれを行うものである:

    grammar ReverseDiff {
        rule file { ^  <hunk>*  $ }

        rule hunk :i { 
            [ <linenum> a :: <linerange> ¥n
              <appendline>+ 
              { @$appendline =‾ s/<in_marker>/< /;
                let $0 := "${linerange}d${linenum}¥n"
                        _ join "", @$appendline;
              }
            |
              <linerange> d :: <linenum> ¥n
              <deleteline>+
              { @$deleteline =‾ s/<out_marker>/> /;
                let $0 := "${linenum}a${linerange}¥n"
                        _ join "", @$deleteline;
              }
            |
              $from:=<linerange> c :: $to:=<linerange> ¥n
              <deleteline>+
              --- ¥n
              <appendline>+
              { @$appendline =‾ s/<in_marker>/</;
                @$deleteline =‾ s/<out_marker>/>/;
                let $0 := "${to}c${from}¥n"
                        _ join("", @$appendline)
                        _ "---¥n"
                        _ join("", @$deleteline);
              }
            ]
          |
            <badline("Invalid diff hunk")>
        }

        rule badline ($errmsg) { (¥N*) ::: { fail "$errmsg: $1" } }

        rule linerange { $from:=<linenum> , $to:=<linenum>
                       | $from:=$to:=<linenum>
                       }

        rule linenum { (¥d+) }

        rule deleteline { ^^ <out_marker> (¥N* ¥n) }
        rule appendline { ^^ <in_marker>  (¥N* ¥n) }

        rule out_marker { ¥<  <sp> }
        rule in_marker  { ¥>  <sp> }
    }

    # and later...

    my $text is from($*ARGS);

    print @{ $0{file}{hunk} }
        if $text =‾ /<Diff.file>/;

fileのためのルール定義 badline, linerange, linenum, appendline, deleteline, in_marker , out_marker は以前のものと同じである。

diffの逆転の動作はhunkルールの中で 行われる。動作させるために、ルールの三つの主な選択肢のそれぞれを 拡張する必要があって、結果オブジェクトを変更するそれぞれのクロージャを 追加している。

Smarter alternatives

最初の選択肢("append" hunkにマッチする)の中で、以前のようにマッチして いた:

        <linenum> a :: <linerange> ¥n
        <appendline>+ 

しかしその後で埋め込まれたクロージャを実行している:

        { @$appendline =‾ s/<in_marker>/</;
          let $0 := "${linerange}d${linenum}¥n"
                  _ join "", @$appendline;
        }

データの各行の"marker"矢印をappendであったかのように (各行に適用するために)smartマッチ演算子を使って 逆転している。置換の中でin_marker ルールを再利用していることにも注意すること。

それから結果オブジェクト(hypothetical変数 $0)を"逆転した"append hunkを表す 文字列に束縛する。つまり、行の範囲と行番号の順番を逆にして その間に'd'("delete")を追加し、 その後に逆転したデータのすべてを置く:

          let $0 := "${linerange}d${linenum}¥n"
                  _ join "", @$appendline;

"delete"選択肢の変更も同様である。以前と同じように内容をキャプチャして マーカー矢印を逆にし、 $linerange$linenum, を入れ替えて 'd''a' に変更して データ行を追加する。

三番目の選択肢の中で:

        $from:=<linerange> c :: $to:=<linerange> ¥n
        <deleteline>+   
        --- ¥n
        <appendline>+
        { @$appendline =‾ s/<in_marker>/</;
          @$deleteline =‾ s/<out_marker>/>/;
          let $0 := "${to}c${from}¥n"
                  _ join("", @$appendline)
                  _ "---¥n"
                  _ join("", @$deleteline);
        }

'c'の両側に行範囲がある。 そのため、それらを区別する名前が必要なので $from$toというhypothetical変数に 束縛する。その後で二つの範囲を逆転するが、 'c'はそのままにしておく (どのように変更するかだけを変えたため)。追加行と削除行のマーカーは 両方とも逆転し、行の二つの集合も同様に逆転する。

一度これらの変換が各hunkに対して行われたならば、その <hunk>サブルール にマッチ成功した結果は、すでに逆にされたhunkの文字列となる。

残ったものすべては、文法に対してテキストをマッチさせるもので、 (変更された)hunkを出力する:

    print @{ $0{file}{hunk} }
        if $text =‾ /<ReverseDiff.file>/;

And, since the file rule is now in the ReverseDiff grammar's namespace, we need to call the rule through that grammar. Note the way the syntax for doing that continues the parallel with methods and classes.
file ルールはReverseDiff文法の 名前空間にあるので、文法を通してルールを呼び出す必要がある。 メソッドとクラスに対応し続ける構文の方法に注意すること。

Rearranging the deck-chairs...

It might have come as a surprise that we were allowed to bind the pattern's $0 result object directly, but there's nothing magical about it. $0 turns out to be just another hypothetical variable...the one that happens to be returned when the match is complete.
パターンの結果オブジェクト$0を 直接束縛することを許しているいることに驚きを覚えるかもしれない。しかし、 それに関して不思議なことはない。$0 は結果として別のhypothetical変数になるのである・・・マッチが完了したときに 結果が返される。

同様に、$1, $2, $3などもすべてhypothecalで、 ルールの中で陽に束縛することが可能である。これは正しい番号付け変数 が常に正しい部分文字列であることを保証するのに便利である。たとえば、 単純なPerl 5のメソッド呼び出し (もちろん、すべてのPerl 5のメソッド呼び出しにマッチさせるには より複雑なルールが必要となる) にマッチするPerl 6でのルールを考えてみよう:

        rule method_call :w {
            # 直接構文にマッチ:   $var->meth(...)
            ¥$  (<ident>)  -¥>  (<ident>)  ¥(  (<arglist>)  ¥)

          | # 間接構文にマッチ: meth $var (...)
            (<ident>)  ¥$  (<ident>)  [ ¥( (<arglist>) ¥) | (<arglist>) ]
        }

        my ($varname, methodname, $arglist);

        if ($source_code =‾ / $0 := <method_call> /) {
            $varname    = $1 // $5;
            $methodname = $2 // $4;
            $arglist    = $3 // $6 // $7;
        }

By binding the match's $0 to the result of the <method_call> subrule, we bind its $0[1], $0[2], $0[3], etc. to those array elements in <method_call>'s result object. And thereby bind $1, $2, $3, etc. as well. Then it's just a matter of sorting out which numeric variable ended up with which bit of the method call.
マッチの$0<method_call>サブルールの結果に 束縛することにより、$0[1], $0[2], $0[3]などを 結果オブジェクト<method_call> の中の配列要素に束縛している。したがって、 $1, $2, $3なども同様に束縛される。 それからメソッド呼び出しになっている数値変数の分類を行っている。

これはうまくいく。しかし、常に$1に 変数名があり、$2にメソッド名が あり、$3に引数リストが あるともっといい。そうすれば最後の六行を次のようにすることができる:

        my ($varname, methodname, $arglist) =
                $source_code =‾ / $0 := <method_call> /;

<-- In Perl 5 there was no way to do that, but in Perl 6 it's relatively easy. We just modify the method_call rule like so: --> Perl 5ではこのようなことを行う方法はなかったが、Perl 6では簡単にできる。 method_callルールを以下のように 書き換えるだけである:

        rule method_call :w {
            ¥$  $1:=<ident>  -¥>  $2:=<ident>  ¥( $3:=<arglist> ¥)
          | $2:=<ident>  ¥$  $1:=<ident>  [ ¥( $3:=<arglist> ¥) | $3:=<arglist> ]
        }

あるいは説明的に:

        rule method_call :w {
            ¥$                          #   リテラルの $ にマッチ
            $1:=<ident>                 #   変数名にマッチし、それを$1に束縛する
            -¥>                         #   リテラルの -> にマッチ
            $2:=<ident>                 #   メソッド名にマッチし、それを$2に束縛する
            ¥(                          #   開き括弧にマッチ
            $3:=<arglist>               #   引数リストにマッチし、それを$3に束縛する
            ¥)                          #   閉じカッコにマッチ
          |                             # または
            $2:=<ident>                 #   メソッド名にマッチし、それを$2に束縛する
            ¥$                          #   リテラルの $ にマッチ
            $1:=<ident>                 #   変数名にマッチし、それを$1に束縛する
            [                           #   以下のいずれか...
              ¥( $3:=<arglist> ¥)       #     括弧の中の引数リストにマッチし、それを$3に束縛する
            |                           #   または...
                 $3:=<arglist>          #     引数リストにマッチし、それを$3に束縛する
            ]
        }

Now the rule's $1 is bound to the variable name, regardless of which alternative matches. Likewise $2 is bound to the method name in either branch of the |, and $3 is associated with the argument list, no matter which of the three possible ways it was matched.
これで、このルールの$1 は どの選択肢がマッチしたかによらず変数名が束縛される。同様に、 |のどちらのブランチであるかに よらず$2 はメソッド名が束縛され、 $3は引数リストとなる。

Of course, that's still rather ugly (especially if we have to write all those comments just so others can understand how clever we were).
もちろん、これはまだ見づらい(特にもしすべてのコメントを書くことができれば ほかの人にわれわれがどれくらい賢かったかを理解させることができる)。

そのため、より良い解決策は適切な名前つきルールを (自動キャプチャ機能を伴って)すべてに対して使うことである。そして、その後で 結果オブジェクトのハッシュ属性の中の必要な情報を取り出すのである:

        rule varname    { <ident> }
        rule methodname { <ident> }

        rule method_call :w {
            ¥$  <varname>  -¥>  <methodname>  ¥( <arglist> ¥)
          | <methodname>  ¥$  <varname>  [ ¥( <arglist> ¥) | <arglist> ]
        }

        $source_code =‾ / <method_call> /;

        my ($varname, $methodname, $arglist) =
                $0{method_call}{"varname","methodname","arglist"}

Deriving a benefit

上記の例で表されているように、文法の中で名前つきルールを使うことで 構文を明らかなものとし、プログラムを解析するときに要求される変数の数を 減少させることができる。しかし、それらの利点以上に、ルールの構築を 実行時からコンパイル時に移すことによる利益がある。 文法の内側に名前つきルールを置くことには別の利点がある: ルールの継承ができるのである。

たとえば、ReverseDiff文法は通常のDiff文法とほぼ同じである。違いといえば hunkの中だけである。だから、 ReverseDiffはすべてを継承して、単に hunkだけを再定義するという手段を 取らない理由はない。これは次のようにする:

    grammar ReverseDiff is Diff {

        rule hunk :i { 
            [ <linenum> a :: <linerange> ¥n
              <appendline>+ 
              { $appendline =‾ s/ <in_marker> /</;
                let $0 := "${linerange}d${linenum}¥n"
                        _ join "", @$appendline;
              }
            |
              <linerange> d :: <linenum> ¥n
              <deleteline>+
              { $deleteline =‾ s/ <out_marker> />/;
                let $0 := "${linenum}a${linerange}¥n"
                        _ join "", @$deleteline;
              }
            |
              $from:=<linerange> c :: $to:=<linerange> ¥n
              <deleteline>+
              --- ¥n
              <appendline>+
              { $appendline =‾ s/ <in_marker> /</;
                $deleteline =‾ s/ <out_marker> />/;
                let $0 := "${to}c${from}¥n"
                        _ join("", @$appendline)
                        _ "---¥n"
                        _ join("", @$deleteline);
              }
            ]
          |
            <badline("Invalid diff hunk")>
        }
    }

ReverseDiff is Diffの構文は Perl 6での標準の継承動作である。クラスは同じ表記を使う:

        class Hacker is Programmer {...}
        class JAPH is Hacker {...}
        # etc.

同様に、前述したDiffの例はReveseDiff文法を新たに派生した既定文法として 表されていた。継承の結果として、ReverseDiffはDiff文法のすべてのルールを 継承する。それからReverseDiffバージョンの hunkを定義してやって、作業を行う。

Different diffs

文法的な継承は文法のルールの振る舞いをいじるのに便利なだけではない。 二つ以上の関連した文法で幾つかの特徴を共有するが、別の場所では異なるような 場合にも便利である。たとえば、"classic"と同様に"unified" diffフォーマットw サポートすることを考えてみよう。

unified diffはヘッダ情報の二行を持ち、それにhunkの並びが後続する。 ヘッダ情報は古いファイルの名前と修正日時(行の先頭に三つのマイナス記号がつけられ ている)があり、続いて新しいファイルの名前と修正日時(行の先頭に三つの プラス記号がつけられている)がある。各hunkはオフセット行を持ち、続いて 共有コンテキストを表す一行以上の行か、追加すべき行もしくは削除すべき行が 置かれる。オフセット行は二つのアットマークで始まり、 古い行のオフセットと行数が続くマイナス記号があり、 そして新しい行オフセットと行数が後続するプラス記号が続く。そして、さらに 二つのアットマークが置かれる。コンテキスト行は二つのスペースが前置される。 挿入される行はプラス記号とスペースが前置される。削除される行はマイナス記号と スペースが前置される(訳注: GNU diff 2.8.4ではフォーマットが少々異なるようだ。 たとえば、追加される行で前置されるのはプラス記号だけでスペースはない)。

しかし、これは重要ではない。

重要なのは完全に別の文法を記述することができるという ことである。だから:

    grammar Diff::Unified {

        rule file { ^  <fileinfo>  <hunk>*  $ }

        rule fileinfo {
                <out_marker><3> $oldfile:=(¥S+) $olddate:=[¥h* (¥N+?) ¥h*?] ¥n
                <in_marker><3>  $newfile:=(¥S+) $newdate:=[¥h* (¥N+?) ¥h*?] ¥n
        }

        rule hunk { 
            <header>
            @spec := ( <contextline>
                     | <appendline>
                     | <deleteline>
                     | <badline("Invalid line for unified diff")>
                     )*
        }

        rule header {
            ¥@¥@ <out_marker> <linenum> , <linecount> ¥h+
                 <in_marker>  <linenum> , <linecount> ¥h+
            ¥@¥@ ¥h* ¥n
        }

        rule badline ($errmsg) { (¥N*) ::: { fail "$errmsg: $1" } }

        rule linenum   { (¥d+) }
        rule linecount { (¥d+) }

        rule deleteline  { ^^ <out_marker> (¥N* ¥n) }
        rule appendline  { ^^ <in_marker>  (¥N* ¥n) }
        rule contextline { ^^ <sp> <sp>    (¥N* ¥n) }

        rule out_marker { ¥+ <sp> }
        rule in_marker  {  - <sp> }
    }

これは新たなdiffフォーマットを正しく表現している(そして解析できる)が、 繰り返す必要のない努力とコードである。この文法のルールの多くは オリジナルのdiff解析器と同じである。そして、それを継承することによって grabできるのである:

    grammar Diff::Unified is Diff  {

        rule file { ^  <fileinfo>  <hunk>*  $ }

        rule fileinfo {
                <out_marker><3> $newfile:=(¥S+) $olddate:=[¥h* (¥N+?) ¥h*?] ¥n
                <in_marker><3>  $newfile:=(¥S+) $newdate:=[¥h* (¥N+?) ¥h*?] ¥n
        }

        rule hunk { 
            <header>
            @spec := ( <contextline>
                     | <appendline>
                     | <deleteline>
                     | <badline("Invalid line for unified diff")>
                     )*
        }

        rule header {
            ¥@¥@ <out_marker> <linenum> , <linecount> ¥h+
                 <in_marker>  <linenum> , <linecount> ¥h+
            ¥@¥@ ¥h* ¥n
        }

        rule linecount { (¥d+) }

        rule contextline { ^^ <sp> <sp>  (¥N* ¥n) }

        rule out_marker { ¥+ <sp> }
        rule in_marker  {  - <sp> }
    }

このバージョンでは appendline, deleteline, linenumなどのためのルールを 指定する必要がないことに注意すること。それらは Diff 文法を継承することにより 自動的(automagically)に提供される。そのため、オリジナルとは異なる 新たな文法の部分だけを指定すればよいのである。

特に、in_markerルールと out_markerルールをfactoring out したことの効用がある。これをあらかじめやっておいたので、 これら二つのマーカーを新たな文法においてマッチさせるために ルールを変更するだけでできた。結果として、継承された appendline ルールと deleteline ルール (which use in_markerout_marker をサブルールとして使っている) に新たな in_marker ルールや out_marker ルールを代わりに マッチさせるようにすることができるようになった。

もし、多態性のように怪しげに見えたのなら、あなたは正しい。 パターンマッチとOOはPerl 6では深く結びついているのである。

Let's get cooking

要約: Perl 6のパターンと文法はPerlのテキストマッチングの能力を 非常に拡張する。しかし、すべての特別な能力を使う必要はない。 実際にそれが必要となるまで、文法や埋め込みクロージャ、表明、その他の 複雑な事柄を無視してよい。

新たなルールの構文は、Perl 5の正規表現にあった"行ノイズ"の多くを きれいにした。しかし、基本的な部分は変わってはいない。多くのPerl 5の パターンはごく単純かつ自然にPerl 6へと変換される。

これをデモンストレーションし、Perl 6のパターンの詳細を説明するために、 Perl CookbookやRegexp::CommonモジュールにあったPerl 5の正規表現 を、それと等価なPerl 6のルールに移植した例を挙げよう:

Cのコメントにマッチする:

        # Perl 5
        $str =‾ m{ /¥* .*? ¥*/ }xs;

        # Perl 6
        $str =‾ m{ /¥* .*? ¥*/ };

Perlの識別子から先行する修飾子を取り除く

        # Perl 5
        $ident =‾ s/^(?:¥w*::)*//;

        # Perl 6
        $ident =‾ s/^[¥w*¥:¥:]*//;

80キャラクタを超える行に対して警告する

        # Perl 5
        warn "Thar she blows!: $&"
                if $str =‾ m/.{81,}/;


        # Perl 6
        warn "Thar she blows!: $0"
                if $str =‾ m/¥N<81,>/;

ローマ数字(Roman numeral)にマッチする

        # Perl 5
        $str =‾ m/ ^ m* (?:d?c{0,3}|c[dm]) (?:l?x{0,3}|x[lc]) (?:v?i{0,3}|i[vx]) $ /ix;

        # Perl 6
        $str =‾ m:i/ ^ m* [d?c<0,3>|c<[dm]>] [l?x<0,3>|x<[lc]>] [v?i<0,3>|i<[vx]>] $ /;

行終端子に関係なく行を展開する

        # Perl 5
        push @lines, $1
                while $str =‾ m/¥G([^¥012¥015]*)(?:¥012¥015?|¥015¥012?)/gc;

        # Perl 6
        push @lines, $1
                while $str =‾ m:c/ (¥N*) ¥n /;

クォートされた文字列(Friedl形式)にマッチし、内容をキャプチャする:

        # Perl 5
        $str =‾ m/ " ( [^¥¥"]* (?: ¥¥. [^¥¥"]* )* ) " /x;

        # Perl 6
        $str =‾ m/ " ( <-[¥¥"]>* [ ¥¥. <-[¥¥"]>* ]* ) " /;

十進表記のIPv4アドレスにマッチする:

        # Perl 5
        my $quad = qr/(?: 25[0-5] | 2[0-4]¥d | [0-1]??¥d{1,2} )/x;

        $str =‾ m/ $quad ¥. $quad ¥. $quad ¥. $quad /x;


        # Perl 6
        rule quad {  (¥d<1,3>) :: { fail unless $1 < 256 }  }

        $str =‾ m/ <quad> <dot> <quad> <dot> <quad> <dot> <quad> /x;


        # Perl 6 (same great approach, now less syntax)
        rule quad {  (¥d<1,3>) :: <($1 < 256)>  }

        $str =‾ m/ <quad> <dot> <quad> <dot> <quad> <dot> <quad> /x;

訳注: Perl 6でのルールにある x修飾子は余計ではなかろうか?

浮動小数点数にマッチし、構成要素を返す:

        # Perl 5
        ($sign, $mantissa, $exponent) =
                $str =‾ m/([+-]?)([0-9]+¥.?[0-9]*|¥.[0-9]+)(?:e([+-]?[0-9]+))?/;


        # Perl 6
        ($sign, $mantissa, $exponent) =
                $str =‾ m/(<[+-]>?)(<[0-9]>+¥.?<[0-9]>*|¥.<[0-9]>+)[e(<[+-]>?<[0-9]>+)]?/;

Match a floating-point number maintainably, returning components:

        # Perl 5
        my $digit    = qr/[0-9]/;
        my $sign_pat = qr/(?: [+-]? )/x;
        my $mant_pat = qr/(?: $digit+ ¥.? $digit* | ¥. digit+ )/x;
        my $expo_pat = qr/(?: $signpat $digit+ )? /x;

        ($sign, $mantissa, $exponent) =
                $str =‾ m/ ($sign_pat) ($mant_pat) (?: e ($expo_pat) )? /x;


        # Perl 6
        rule sign     { <[+-]>? }
        rule mantissa { <digit>+ [¥. <digit>*] | ¥. <digit>+ }
        rule exponent { [ <sign> <digit>+ ]? }

        ($sign, $mantissa, $exponent) = 
                $str =‾ m/ (<sign>) (<mantissa>) [e (<exponent>)]? /;

ネストした括弧にマッチする:

        # Perl 5
        our $parens = qr/ ¥(  (?: (?>[^()]+) | (??{$parens}) )*  ¥) /x;
        $str =‾ m/$parens/;


        # Perl 6
        $str =‾ m/ ¥(  [ <-[()]> + : | <self> ]*  ¥) /;

Match nested parentheses maintainably:

        # Perl 5
        our $parens = qr/
                   ¥(                   # リテラルの '(' にマッチ
                   (?:                  # 非キャプチャグループの開始
                       (?>              #     バックトラックしない...
                           [^()] +      #         括弧以外にマッチ (繰り返し)
                       )                #     非バックトラック領域の終わり
                   |                    # または
                       (??{$parens})    #    再帰的にパターン全体にマッチ
                   )*                   # グループを閉じ、繰り返しマッチ
                   ¥)                   # リテラルの ')' にマッチ
                 /x;

        $str =‾ m/$parens/;


        # Perl 6
        $str =‾ m/ <'('>                # リテラルの '(' にマッチ
                   [                    # 非キャプチャグループの開始
                        <-[()]> +       #    括弧以外にマッチ (繰り返し)
                        :               #    ...決してバックトラックしない
                   |                    # または
                        <self>          #    再帰的にパターン全体にマッチ
                   ]*                   # グループを閉じ、繰り返しマッチ
                   <')'>                # リテラルの ')' にマッチ
                 /;