For Development HEAD DRAFTSearch (procedure/syntax/module):

6.12 正規表現

GaucheはPOSIXの拡張正規表現にほぼ上位互換で、さらに Perl 5の正規表現から拡張機能を採り入れた正規表現エンジンを持っています。 Gaucheはまた、リテラル正規表現用の構文を備えています。

正規表現用のリテラル構文が用意されており、 また、正規表現オブジェクトは適用可能、つまり文字列に対して 手続きのように振る舞い、自分自身にマッチするかどうかを判定できるようになっています。 この二つの機能によって、文字列のマッチをスキャンするようなイディオムを 簡潔に書けるようになっています。

(find #/pattern/ list-of-strings)
  ⇒ match object or #f

6.12.1 正規表現の構文

Reader Syntax: #/regexp-spec/
Reader Syntax: #/regexp-spec/i

リテラルの正規表現オブジェクトを表記します。読まれた際に<regexp>の インスタンスとなります。

末尾に文字iが与えられた場合は、マッチ時に大文字小文字を区別しない 正規表現オブジェクトとなります。

string->regexpに対してこの構文を使う利点は、 正規表現のコンパイルが一度しか行われない点です。この構文は、 内部ループの中でも、正規表現のコンパイルのオーバヘッドを気にせずに 使うことができます。動的に正規表現を作成したい場合のみstring->regexpを 使って下さい。

Gaucheの組み込み正規表現構文はPOSIX拡張正規表現に準じたものに、 Perlの拡張の一部を採り入れたものです。 (別構文として、Scheme正規表現(SRE)もサポートされています。 SREについてはscheme.regex - R7RS正規表現を参照してください。)

re*

reの0回以上の繰り返しにマッチします。

re+

reの1回以上の繰り返しにマッチします。

re?

reの0回または1回の出現にマッチします。

re{n}
re{n,m}

回数に範囲のある繰り返しです。 re{n}ren回の繰り返しにマッチします。 re{n,m}ren回以上、m回以下の 繰り返しにマッチします。但しn <= mとします。 2番目の形式ではnmのどちらかを省略することが できます。nが省略された場合は0とみなされます。 mが省略された場合は無限大とみなされます。

re*?
re+?
re??
re{n,m}?

上記の繰り返し構造とほぼ同じですが、これらの構文は「non-greedy」または 「lazy」と呼ばれるマッチ戦略を用います。すなわち、まずreがマッチする 最小の回数を試し、それが失敗したら順に繰り返しの回数を増やしてゆきます。 最後の形式ではnmのどちらかは省略できます。 次の例を比べてみてください:

(rxmatch-substring (#/<.*>/ "<tag1><tag2><tag3>") 0)
  ⇒ "<tag1><tag2><tag3>"

(rxmatch-substring (#/<.*?>/ "<tag1><tag2><tag3>") 0)
  ⇒ "<tag1>"
(re…)

捕捉クラスタリング。括弧でくくられた正規表現の列がグループとして 扱われ、またそれにマッチした文字列はサブマッチとして保存されます。

(?:re…)

捕捉無しクラスタリング。reはグループとして 扱われますが、サブマッチとして保存されません。

(?<name>re…)

名前つきの捕捉とクラスタリング。(re…)と同様ですが、 マッチした文字列に名前nameがつけられます。マッチした文字列には インデックスの数字と名前のどちらでも参照できます。

同じ名前が複数回正規表現内に出現した場合、どの名前付き捕捉にマッチした 部分文字列が返されるかは不定です。

(?i:re…)
(?-i:re…)

大文字小文字の区別の制御。 (?i:re…)re…が大文字小文字にかかわらず マッチするようにします。 (?-i:re…)はその逆です。

Perlの正規表現では’?’と’:’の間に他のいくつかのフラグを使うことが できますが、Gaucheでは今のところこのフラグのみをサポートしています。

pattern1|pattern2|…

パターンのいずれかにマッチします。

\n

バックリファレンス。nは整数です。 n番目(1から数える)の捕捉カッコに捕捉された文字列と一致する場合に、\nが マッチします。補足カッコがネストしている場合、開きカッコの順番で数えます。 n番目のカッコが繰り返しの中にあり、複数回マッチ している場合は、最後にマッチした文字列との比較が行われます。

\k<name>

名前によるバックリファレンス。 名前nameを持つ捕捉カッコで捕捉された文字列と一致する場合に、 \k<name>がマッチします。参照しているカッコが繰り返しの中にあり、 複数回マッチしている場合は、最後にマッチした文字列との比較が行わ れます。同じ名前nameを持つ捕捉カッコが複数ある場合には、 それらのカッコの最後にマッチした文字列のいずれかと一致する場合、 マッチが成功します。

.

任意の1文字にマッチします。改行文字にもマッチします。

[char-set-spec]

char-set-specで指定される文字集合内の文字にマッチします。 char-set-specについては文字集合を参照して下さい。

\s, \d, \w

それぞれ空白文字(char-set:ascii-whitespace, #[\u0009-\u000d ])、 数字(char-set:ascii-digit, #[0-9])、 単語を構成する文字(char-set:ascii-word, #[A-Za-z0-9_])にマッチします。 ASCIIの外の文字は含まれないことに注意してください。

文字集合内でも、その外でも使えます。

\S, \D, \W

それぞれ\s\d\wで指定される文字集合の補集合の 文字にマッチします。

^, $

それぞれ、パターンの最初または最後に指定された場合、 文字列の最初か最後にマッチします。

これらの文字は、パターンの最初(^の場合)か最後($の場合)に 現れた時のみ特別な意味を持ち、それ以外の場所ではこれらの文字自身にマッチします。 これらの文字を特殊文字と認識する段階では、肯定および否定の先読み ((?=...), (?!...), (?<=...), (?<!...))と アトミックなクラスタリング((?>...))はあたかもそれが独立した パターンであるかのように扱われます。つまり、^がこれらの構造の先頭に現れた 場合は、これらの構造が全体の正規表現中のどこにあるかには関わらず、文字列先頭への マッチとみなされます。$がこれらの構造の末尾に現れたば場合も同様です。

\b, \B

\bは単語の境界の空文字列にマッチします。 \Bはその逆です。

\;
\"
\#

これらはそれぞれ;"、および#と同じです。 Emacs等、Scheme構文を理解するエディタを混乱させないために使うことができます。

(?=pattern)
(?!pattern)

肯定および否定の先読み。 patternが文字列の現在の位置にマッチする(あるいはマッチ しない)ときにマッチが成功しますが、現在の位置は変更しない ので、後に続く正規表現は現在と同じ位置から適用されます。

例えば、次の表現は、電話番号のうち日本の番号("81"から始まるもの) を除く文字列にマッチします。

\+(?!81)\d{9,}
(?<=pattern)
(?<!pattern)

肯定および否定の後読み。 現在の位置の左側にpatternにマッチする文字列がある場合に マッチが成功(あるいは失敗)します。先読みと同様、現在の位置は 変更しません。

内部的にこの表現は、patternを逆転させたうえで、現在の位置 から左に向かってマッチを進めることで実現されています。したがって、 patternには任意のものにマッチする表現を含めることができますが、 マッチの順番や長さが重要な場合(例えば2通りにマッチしうる捕捉の カッコ)などは、左から右に現在位置が進むときとは異なる場所に マッチするかもしれません。

(?>pattern)

アトミックなクラスタリング。patternがいったんマッチすると、 そのマッチは確定します。後続のパターンが失敗した場合でも、 pattern内にバックトラックして他のマッチが試みられることはありません。

re*+
re++
re?+

それぞれ(?>re*)、(?>re+)、(?>re?)と同じです。

(?test-pattern then-pattern)
(?test-pattern then-pattern|else-pattern)

条件つきマッチング。test-patternが成功すれば then-patternへと、そうでなければ else-patternへと(もしあれば)マッチを進めます。

test-patternには以下の形式が書けます。

(integer)

バックリファレンス。integer番目の捕捉クラスタリングのマッチに 成功していた場合に成功となります。

(?=pattern)
(?!pattern)

肯定および否定の先読み。入力の現在位置から、入力を消費することなく patternのマッチを試み、それがそれぞれ成功もしくは失敗した 場合に、このtest-patternを成功とみなします。

(?<=pattern)
(?<!pattern)

肯定および否定の後読み。入力の現在位置から左側に向かって、 patternの逆向きにマッチを試みます、 それがそれぞれ成功もしくは失敗した場合に、 このtest-patternを成功とみなします。


6.12.2 正規表現を使う

Regexpオブジェクトとrxmatchオブジェクト

Builtin Class: <regexp>

正規表現オブジェクトのクラスです。string->regexp あるいはsre->regexpを使って実行時に作成できます。 また、Gaucheはリテラルの正規表現を表す構文を持っており、 ロード時に作成することもできます。

Gaucheの正規表現エンジンはマルチバイト文字列に対応しています。

Builtin Class: <regmatch>

正規表現マッチオブジェクトのクラスです。正規表現エンジンrxmatchは、 一致した場合にこのオブジェクトを返します。部分一致の情報を含めた 全てのマッチに関する情報がこのオブジェクトに含まれています。

一致した部分文字列やそのインデックスのリストではなく マッチオブジェクトを返すことの利点は効率です。 regmatchオブジェクトはマッチの内部状態を保持しており、 要求された時にはじめて該当する部分文字列やインデックスを計算します。 これは特にマルチバイト文字列に有効です。マルチバイト文字列 へのインデックスアクセスは遅いからです。

Function: string->regexp string :key case-fold multi-line

文字列stringを正規表現とみなして、<regexp>のインスタンスを 作成して返します。

キーワード引数case-foldに真の値が与えられた場合、作成される正規表現は 大文字小文字を区別しないものとなります。 (大文字小文字を区別しない正規表現に関しては上の説明を参照して下さい)。

キーワード引数multi-lineに真の値が与えられた場合、 ^$はそれぞれ入力文字列の最初と最後に加え、 各行の先頭と末尾にもマッチします。 行の区切りはLFのみ、CRLF、あるいはCRのみのいずれも認識されます。

Function: sre->regexp sre :key multi-line

Scheme正規表現 sre を取り、コンパイルした<regexp>オブジェクトを 返します。正規表現全体が常に0番の捕捉グループになります。

キーワード引数multi-lineに偽の値が与えられた場合 (デフォルト)、boleolはそれぞれboseosのように 振る舞います (つまり、入力文字列の最初と最後にのみマッチします)。

Function: regexp? obj

objが正規表現オブジェクトなら真の値を返します。

Function: regexp->string regexp

正規表現regexpを記述する元になった文字列を返します。 返される文字列は変更不可な文字列です。

Function: regexp->sre regexp

正規表現regexpを生成するScheme正規表現式(SRE)を返します。 SREについて詳しくはscheme.regex - R7RS正規表現を参照してください。

Function: regexp-num-groups regexp
Function: regexp-named-groups regexp

正規表現regexp中の、捕捉グループ(サブマッチに使われるグループ)の総数、 及び名前つき捕捉グループのalistをそれぞれ返します。

捕捉グループの総数は、この正規表現でマッチした場合のマッチオブジェクトの rxmatch-num-matchesと同じになります。正規表現全体もひとつのグループだと みなされるので、総数は常に1以上です。

regexp-named-groupsから返されるalistは carに名前(シンボル)、cdrにそのサブマッチ番号を持つペアを 要素とします。alist内でのグループの順番は不定です。

(regexp-num-groups #/abc(?<foo>def)(ghi(?<bar>jkl)(mno))/)
  ⇒ 5
(regexp-named-groups #/abc(?<foo>def)(ghi(?<bar>jkl)(mno))/)
  ⇒ ((bar . 3) (foo . 1))

マッチを試みる

Function: rxmatch regexp string :optional start end

正規表現オブジェクトregexpに一致するものを文字列stringから 探します。一致が見付かった場合は<regmatch>オブジェクトを返し、 見付からなかった場合は#fを返します。

If start and/or end are given, only the substring between start (inclusive) and end (exclusive) is searched.

他のScheme処理系ではこれは matchregexp-searchstring-matchなど 様々な名で呼ばれています。

内部的に、Gaucheは正規表現のマッチにバックトラックを使っています。 複数のマッチの可能性がある場合、その時点の状態をスタックにセーブして一つの可能性を試し、 だめだったら戻ってもう一つの可能性を試します。正規表現によっては、 セーブする状態が入力の大きさに比例してしまう場合があります。 セーブする状態が大きくなりすぎると、次のエラーが投げられます。

ERROR: Ran out of stack during matching regexp #/.../. Too many retries?

このエラーが起きた場合、他のパーズ手法を組み合わせることを検討してください。 Gaucheの正規表現エンジンはそれ一つで何もかもやってしまうことを目的として作られては いません。多くの場合、複雑な正規表現を駆使するよりも、 正規文法より強力な文法を使う方がうまくいきます。

入力から複数のマッチを取り出したり、あるいはストリーム入力 (ポートから読み出すデータなど) にマッチをかけたい場合は、gauche.generatorモジュールのgrxmatch も使えるかもしれません (ジェネレータの操作参照)。

Generic application: regexp string

正規表現オブジェクトは直接文字列に対して適用することもできます。 これは(rxmatch regexp string)と同じ動作をしますが、 表記が短くて済みます。この機能は適用可能なオブジェクト で述べているメカニズムを 使って実装されています。

マッチの結果を取り出す

Function: rxmatch-start match :optional (i 0)
Function: rxmatch-end match :optional (i 0)
Function: rxmatch-substring match :optional (i 0)

rxmatchが返すマッチオブジェクトmatchから情報を取り出します。 iが省略されるか0の場合、これらの手続きはそれぞれ一致した 文字列の開始インデックス、終了インデックス、および一致した部分文字列を 返します。iに正の整数が与えられた場合は、i番目のサブマッチ に関する情報を返します。iにシンボルが与えられた場合は、名前 iを持つサブマッチの情報を返します。同じ名前iを持つ複数の サブマッチがある場合には、成功したサブマッチの情報を返します。 iにそれ以外の値を与えるのはエラーです。

簡便のために、match#fを渡すことも許されています。 その場合、これらの手続きは#fを返します。

これらの手続きはScshでmatch:startmatch:endmatch:substringと呼ばれているものと等価です。

Function: rxmatch-after match :optional (i 0)
Function: rxmatch-before match :optional (i 0)

マッチオブジェクトmatchの前および後の文字列を返します。 正の整数がiに与えられた場合はi番目のサブマッチの前および後の 文字列を返します。シンボルが与えられた場合は、その名前を持つ サブマッチの前後の文字列を返します。

(define match (rxmatch #/(\d+)\.(\d+)/ "pi=3.14..."))

(rxmatch-after match) ⇒ "..."
(rxmatch-after match 1) ⇒ ".14..."

(rxmatch-before match) ⇒ "pi="
(rxmatch-before match 2) ⇒ "pi=3."
Function: rxmatch-substrings match :optional start end
Function: rxmatch-positions match :optional start end

複数のサブマッチ (0番目のサブマッチはマッチ全体) を取り出す手続きです。 それぞれ、部分文字列のリストと、開始位置と終了位置のコンスのリストが返されます。

(rxmatch-substrings (#/(\d+):(\d+):(\d+)/ "12:34:56"))
  ⇒ ("12:34:56" "12" "34" "56")

(rxmatch-positions (#/(\d+):(\d+):(\d+)/ "12:34:56"))
  ⇒ ((0 . 8) (0 . 2) (3 . 5) (6 . 8))

簡便のために、match#fを渡すことも許されています。 その場合、これらの手続きは()を返します。

省略可能なstartend引数は取り出すサブマッチのインデックスの 範囲を指定します。省略された場合、startは0、 end(rxmatch-num-matches match)の値が使われます。 例えばマッチ全体が不要なら、start1を渡せば良いのです。

(rxmatch-substrings (#/(\d+):(\d+):(\d+)/ "12:34:56") 1)
  ⇒ ("12" "34" "56")
Function: rxmatch->string regexp string :optional selector …

文字列に正規表現でマッチをかけ、マッチした文字列を得る、便利な関数です。 マッチしなかった場合は#fが帰ります。

selectorが与えられなかった場合、この手続きは次の式と同じです。

(rxmatch-substring (rxmatch regexp string))

selectorに整数が与えられた場合は、 それで指定されるサブマッチの文字列が返されます。

selectorにはシンボルafterbeforeを与えることも出来ます。 その場合は、マッチした文字列の前や後の文字列が返されます。これらのシンボルの後に さらにサブマッチを指定する整数を与えることもできます。

gosh> (rxmatch->string #/\d+/ "foo314bar")
"314"
gosh> (rxmatch->string #/(\w+)@([\w.]+)/ "foo@example.com" 2)
"example.com"
gosh> (rxmatch->string #/(\w+)@([\w.]+)/ "foo@example.com" 'before 2)
"foo@"
Generic application: regmatch :optional index
Generic application: regmatch 'before :optional index
Generic application: regmatch 'after :optional index

マッチオブジェクトは直接整数のインデックスもしくはシンボルに対して適用することが できます。整数に適用したときは(rxmatch-substring regmatch index)、 シンボルbeforeのときは(rxmatch-before regmatch)、シンボル afterのときは(rxmatch-after regmatch)、そのほかのシンボルのときは (rxmatch-substring regmatch symbol)と同じ動作をします。

表記が短くて済みます。 この機能は適用可能なオブジェクト で述べているメカニズムを使って実装されています。

(define match (#/(\d+)\.(\d+)/ "pi=3.14..."))

  (match)           ⇒ "3.14"
  (match 1)         ⇒ "3"
  (match 2)         ⇒ "14"

  (match 'after)    ⇒ "..."
  (match 'after 1)  ⇒ ".14..."

  (match 'before)   ⇒ "pi="
  (match 'before 2) ⇒ "pi=3."

(define match (#/(?<integer>\d+)\.(?<fraction>\d+)/ "pi=3.14..."))

  (match 1)         ⇒ "3"
  (match 2)         ⇒ "14"

  (match 'integer)  ⇒ "3"
  (match 'fraction) ⇒ "14"

  (match 'after 'integer)   ⇒ ".14..."
  (match 'before 'fraction) ⇒ "pi=3."
Function: rxmatch-num-matches match
Function: rxmatch-named-groups match

それぞれ、matchの持つマッチの数、 および名前つきグループの名前とインデックスのalistを返します。 これらは、matchを返した正規表現オブジェクトに対する regexp-num-groupsregexp-named-groups手続きに対応します。 元の正規表現オブジェクトなしに、matchの中身を調べたい時に便利です。

返されるマッチの数には「マッチ全体」も含まれます。 つまり、<regmatch>オブジェクトに対しては常に正の整数が返ることになります。 値を持たないマッチもカウントされます(下の例を参照)。 rxmatch-named-matchesが返すalistについても、元の正規表現の持つ 名前つきグループの情報がマッチの有無によらず返されます。

簡便のために、match#fを渡すこともできます。 その場合、rxmatch-num-matchesは0を、 rxmatch-named-groups()を返します。

(rxmatch-num-matches (rxmatch #/abc/ "abc")) ⇒ 1
(rxmatch-num-matches (rxmatch #/(a(.))|(b(.))/ "ba")) ⇒ 5
(rxmatch-num-matches #f) ⇒ 0

(rxmatch-named-groups
 (rxmatch #/(?<h>\d\d):(?<m>\d\d)(:(?<s>\d\d))?/ "12:34"))
 ⇒ ((s . 4) (m . 2) (h . 1))

便利なユーティリティ

Function: regexp-replace regexp string substitution
Function: regexp-replace-all regexp string substitution

string中でregexpにマッチした部分をsubstitutionで 置き換えます。regexp-replaceは最初にマッチした部分のみを置き換え、 regexp-replace-allは全てのマッチを置き換えます。

substitutionは文字列か手続きです。 文字列の場合、バックスラッシュに続く数値、もしくは \k<name>という形式でサブマッチ文字列を参照できます \0はマッチ文字列全体を参照します。文字列リテラルにバックスラッシュを 埋め込む場合は二つのバックスラッシュが必要であることに注意して下さい。 バックスラッシュそのものをsubstitution中で使いたい場合は 二つのバックスラッシュを重ねます; 文字列リテラルの場合は4つのバックスラッシュが 必要になります。

(regexp-replace #/def|DEF/ "abcdefghi" "...")
  ⇒ "abc...ghi"
(regexp-replace #/def|DEF/ "abcdefghi" "|\\0|")
  ⇒ "abc|def|ghi"
(regexp-replace #/def|DEF/ "abcdefghi" "|\\\\0|")
  ⇒ "abc|\\0|ghi"
(regexp-replace #/c(.*)g/ "abcdefghi" "|\\1|")
  ⇒ "ab|def|hi"
(regexp-replace #/c(?<match>.*)g/ "abcdefghi" "|\\k<match>|")
  ⇒ "ab|def|hi"

substitutionが手続きである場合、string中の各マッチについて、 マッチオブジェクトを引数としてその手続きが呼ばれます。その手続きが返す 値をdisplayで表現したものが置換文字列として使われます。

(regexp-replace #/c(.*)g/ "abcdefghi"
                (lambda (m)
                  (list->string
                   (reverse
                    (string->list (rxmatch-substring m 1))))))
 ⇒ "abfedhi"

註: regexp-replace-all は文字列でマッチした部分の後ろの部分に ついて再帰的に自分自身を適用します。従って、regexpが 文字列先頭のアサーション (^) を含んでいても、それはstringの 先頭だけにマッチするとは限りません。

註: 文字列中の、正規表現にマッチする部分すべてに対して何か操作をしたいが、 置き換えた文字列が欲しいわけではない、という場合は、 gauche.lazyモジュールのlrxmatchや、 gauche.generatorモジュールのgrxmatchが使えるでしょう。 これらは文字列に対して繰り返し正規表現でのマッチを、必要に応じて適用し、 前者はマッチオブジェクトの遅延シーケンスを、後者はマッチオブジェクトを生成する ジェネレータを返します。

(map rxmatch-substring (lrxmatch #/\w+/ "a quick brown fox!?"))
 ⇒ ("a" "quick" "brown" "fox")
Function: regexp-replace* string rx1 sub1 rx2 sub2 …
Function: regexp-replace-all* string rx1 sub1 rx2 sub2 …

まず、regexp-replace あるいは regexp-replace-all を 正規表現 rx1、置換 sub1string に適用し、 その結果にさらに regexp-replace あるいは regexp-replace-all を正規表現 rx2、置換 sub2 で 適用し、以下同様です。これらの関数はひとつの文字列上で複数回置換を行う ときに便利です。

Function: regexp-quote string

string中で、正規表現において特別な意味を持つ文字を全てエスケープした 文字列を返します。

(regexp-quote "[2002/10/12] touched foo.h and *.c")
 ⇒ "\\[2002/10/12\\] touched foo\\.h and \\*\\.c"

以下のマクロにおいて、match-exprはマッチオブジェクトか #fを生成する式でなければなりません。通常それは rxmatchを呼ぶ式になりますが、それだけに限られるわけではありません。

Macro: rxmatch-let match-expr (var …) form …

match-exprを評価し、それがマッチオブジェクトを返したら、 マッチした文字列をvar …に束縛し、formを評価します。 最初のvarはマッチした文字列全体に束縛され、 以降の変数はサブマッチ文字列に束縛されます。実際のサブマッチ文字列が 与えられた変数より少なかった場合は、余った変数は#fに束縛されます。

特定のマッチ文字列を受け取る必要が無いときは、その場所の 変数の変わりに#fを置いておくこともできます。

(rxmatch-let (rxmatch #/(\d+):(\d+):(\d+)/
                      "Jan  1 23:59:58, 2001")
   (time hh mm ss)
  (list time hh mm ss))
 ⇒ ("23:59:58" "23" "59" "58")

(rxmatch-let (rxmatch #/(\d+):(\d+):(\d+)/
                      "Jan  1 23:59:58, 2001")
   (#f hh mm)
  (list hh mm))
 ⇒ ("23" "59")

このマクロはscshのlet-matchに相当します。

Macro: rxmatch-if match-expr (var …) then-form else-form

match-exprを評価し、それがマッチオブジェクトを返したら マッチした文字列を変数var …に束縛してthen-formを 評価します。マッチオブジェクトが返されなければ束縛は行われず、 else-formが評価されます。変数varをマッチ文字列に 束縛するルールはrxmatch-letと同じです。

(rxmatch-if (rxmatch #/(\d+:\d+)/ "Jan 1 11:22:33")
    (time)
  (format #f "time is ~a" time)
  "unknown time")
 ⇒ "time is 11:22"

(rxmatch-if (rxmatch #/(\d+:\d+)/ "Jan 1 11-22-33")
    (time)
  (format #f "time is ~a" time)
  "unknown time")
 ⇒ "unknown time"

このマクロはscshのif-matchに相当します。

Macro: rxmatch-cond clause …

clauseの条件を順に評価してゆき、条件を満たすものが現れたら そのclauseの残りのフォームを評価し、最後のフォームの値を rxmatch-condの値とします。clauseは以下のいずれかの 形式でなければなりません。

(match-expr (var …) form …)

match-exprを評価し、それがマッチオブジェクトを返した場合は マッチ文字列を変数var …に束縛した上で form …を評価します。

(test expr form …)

exprを評価し、それが真の値を返した場合はform …を評価します。

(test expr => proc)

exprを評価し、それが真の値を返した場合は それを唯一の引数として手続きprocを呼びます。

(else form …)

このclauseは、もし与えられたとすれば最後のclauseでなければ なりません。全てのclauseが失敗した場合に、form …が 評価されます。

else clauseが与えられず、かつ全てのclauseが 失敗した場合の戻り値は未定義です。

;; 何通りかの日付のフォーマットをパーズする
(define (parse-date str)
  (rxmatch-cond
    ((rxmatch #/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)$/ str)
        (#f mm dd yyyy)
      (map string->number (list yyyy mm dd)))
    ((rxmatch #/^(\d\d\d\d)\/(\d\d?)\/(\d\d?)$/ str)
        (#f yyyy mm dd)
      (map string->number (list yyyy mm dd)))
    ((rxmatch #/^\d+\/\d+\/\d+$/ str)
        (#f)
     (errorf "ambiguous: ~s" str))
    (else (errorf "bogus: ~s" str))))

(parse-date "2001/2/3") ⇒ (2001 2 3)
(parse-date "12/25/1999") ⇒ (1999 12 25)

このマクロはscshのmatch-condに相当します。

Macro: rxmatch-case string-expr clause …

string-exprがまず評価され、続いてclauseが順に検査されます。 clauseは以下のいずれかの形式でなければなりません。

(re (var …) form …)

reはリテラル正規表現オブジェクトでなければなりません (正規表現参照)。string-exprの結果が文字列であり reにマッチした場合は、マッチ文字列が変数var …に 束縛され、formが評価されます。最後のformの値がrxmatch-case の値となります。

string-exprの結果の文字列がreにマッチしないか、 string-exprの結果が文字列以外であった場合は次のclauseへと 処理が進みます。

(test proc form …)

手続きprocstring-exprの結果を引数として呼ばれます。 それが真の値を返した場合はformが順に評価され、最後のformの 値がrxmatch-caseの値として返されます。

proc#fを返した場合は次のclauseへと 処理が進みます。

(test proc => proc2)

手続きprocstring-exprの結果を引数として呼ばれます。 それが真の値を返した場合は、その値を引数としてproc2が呼ばれ、 その返り値がrxmatch-caseの値として返されます。

proc#fを返した場合は次のclauseへと 処理が進みます。

(else form …)

このフォームは、与えられる場合は最後のclauseでなければなりません。 他の全てのclauseが失敗した場合に、formが順に評価され、最後のformの 値がrxmatch-caseの値として返されます。

(else => proc)

このフォームは、与えられる場合は最後のclauseでなければなりません。 他の全てのclauseが失敗した場合に、procが評価されます。 その値は一引数の手続きにならなければなりません。 その手続きが、string-exprの値を引数として呼ばれます。 procの結果がrxmatch-caseの値となります。

else clauseが与えられず、かつ全てのclauseが 失敗した場合の戻り値は未定義です。

上のparse-dateの例はrxmatch-caseを使うとより単純になります。

(define (parse-date2 str)
  (rxmatch-case str
    (test (lambda (s) (not (string? s))) #f)
    (#/^(\d\d?)\/(\d\d?)\/(\d\d\d\d)$/ (#f mm dd yyyy)
     (map string->number (list yyyy mm dd)))
    (#/^(\d\d\d\d)\/(\d\d?)\/(\d\d?)$/ (#f yyyy mm dd)
     (map string->number (list yyyy mm dd)))
    (#/^\d+\/\d+\/\d+$/                (#f)
     (errorf "ambiguous: ~s" str))
    (else (errorf "bogus: ~s" str))))

6.12.3 正規表現の調査と合成

Gaucheはregexpの文字列表記を読むと、その文字列をパーズして 抽象構文木 (AST) を作り、それにいくらかの最適化を施したのち、 正規表現エンジンで実行されるインストラクション列へとコンパイルします。

以下の手続きはこのプロセスをユーザプログラムに見せるものです。 プログラム的に正規表現をいじる場合は、文字列形式よりも ASTの方が扱いやすいでしょう。

Function: regexp-parse string :key case-fold multi-line

正規表現の文字列表記であるstringをパーズし、S式で表現された ASTを返します。ASTの仕様は下で述べます。

case-foldキーワード引数に真の値が与えられた場合は、 大文字小文字を区別せずにマッチを行うASTが変えされます (エンジン自体に「大文字小文字を区別しない」モードがあるわけではなく、 パーザの段階で異なるASTが生成されます。)

Function: regexp-parse-sre sre

sreをSRFI-115で定義されるScheme正規表現(SRE)としてパーズし、 結果をASTで返します。 SREについて詳しくはscheme.regex - R7RS正規表現を参照してください。 see scheme.regex - R7RS正規表現.

Function: regexp-optimize ast

正規表現のASTに対して、いくつかの原始的な最適化を施して結果のASTを返します。

現在のところ、最適化はごく簡単なものです。将来はもっと賢くする予定です。

Function: regexp-compile ast :key multi-line

正規表現のASTを受け取り、マッチに使える正規表現オブジェクトを返します。 渡されるASTの一番外側は、0番めの捕捉グループでなければなりません (ast(0 #f x …)という形でなければ ならない、ということです)。regexp-parseは常にこの捕捉グループを 追加します。正規表現にマッチした全体の文字列を捕捉するためです。

註:この関数は渡されたASTが有効なものであるかどうかについて 簡単なチェックを行いますが、有効でないASTを受け入れてしまうかもしれません。 その場合、変えされる正規表現オブジェクトの動作は不定です。 正しい形式のASTを渡すのは呼出側の責任です (この関数が有効でないASTを見つけてエラーにする場合でも、 そのエラーメッセージからどこがおかしいかを判断するのが難しい場合があります。 この手続きをASTが有効であるかどうかのチェッカーとして使うのは避けましょう。)

Function: regexp-ast regexp

正規表現オブジェクトregexpが使っているASTを返します。

Function: regexp-unparse ast :key (on-error :error)

正規表現のastから、その正規表現の文字列表記を再構成して返します。 キーワード引数on-errorは、キーワード:error (デフォルト) もしくは#fです。それが:errorの場合、astが不正 であればエラーが報告され、#fの場合はregexp-unparseから #fが返されます。

以下にASTの構造を示します。この仕様は当初、内部的に使うためだけに設計されたので、 コードで直接いじるのには若干不便です (例えば、部分木を削除したり 追加した場合、捕捉グループの番号を振りなおして一貫性を保つ必要があるかもしれません。)

<ast> : <clause>   ; special clause
      | <item>     ; matches <item>

<item> : <char>       ; matches char
       | <char-set>   ; matches char set
       | (comp . <char-set>) ; matches complement of char set
       | any          ; matches any char
       | bos | eos    ; beginning/end of string assertion
       | bol | eol    ; beginning/end of line assertion
       | bow | eow | wb | nwb ; word-boundary/negative word boundary assertion
       | bog | eog    ; beginning/end of grapheme assertion

<clause> : (seq <ast> ...)       ; sequence
       | (seq-uncase <ast> ...)  ; sequence (case insensitive match)
       | (seq-case <ast> ...)    ; sequence (case sensitive match)
       | (alt <ast> ...)         ; alternative
       | (rep <m> <n> <ast> ...) ; repetition at least <m> up to <n> (greedy)
                               ; <n> may be `#f'
       | (rep-min <m> <n> <ast> ...)
                               ; repetition at least <m> up to <n> (lazy)
                               ; <n> may be `#f'
       | (rep-while <m> <n> <ast> ...)
                               ; like rep, but no backtrack
       | (<integer> <symbol> <ast> ...)
                               ; capturing group.  <symbol> may be #f.
       | (cpat <condition> (<ast> ...) (<ast> ...))
                               ; conditional expression
       | (backref . <integer>) ; backreference by group number
       | (backref . <symbol>)  ; backreference by name
       | (once <ast> ...)      ; standalone pattern.  no backtrack
       | (assert . <asst>)     ; positive lookahead assertion
       | (nassert . <asst>)    ; negative lookahead assertion

<condition> : <integer>     ; (?(1)yes|no) style conditional expression
       | (assert . <asst>)  ; (?(?=condition)...) or (?(?<=condition)...)
       | (nassert . <asst>) ; (?(?!condition)...) or (?(?<!condition)...)

<asst> : <ast> ...
       | ((lookbehind <ast> ...))


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT