準備
モジュールのロード
-
Guileの正規表現ライブラリを使うときには,(ice-9 regex) モジュールをプログラムの冒頭などでロードする必要があります.このモジュールの中に正規表現用の手続きが定義されています.
(use-modules (ice-9 regex))
-
REPLは上記のモジュールを自動的にロードします.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> ,import (guile) (system base compile) (ice-9 readline) (system repl common) (ice-9 session) (ice-9 regex) (ice-9 threads) guile>
ちなみに,REPLの ,import コマンドはロード済みのモジュールを表示します. - (参考)make-regexp手続き,regexp-exec手続き,regexp?手続き,および,それらの引数として指定できる各種フラグは,システム標準の手続きおよび大域変数として実装されています.従って,これらは上記のモジュールをロードしなくても利用できます.これら以外の手続きは上記のモジュールの中で定義されています.
基本的な利用方法
-
正規表現を使ったマッチング処理はおおよそ次のような手順で行います.
- まず,make-regexp手続きとregexp-exec手続きを組み合わせて正規表現に文字列をマッチさせます.その結果,マッチした部分列があったときには,検索対象文字列やマッチした部分列の開始位置と終了位置のペアからなるベクタが得られます.逆に,マッチした部分列がなかったときには#fが返ってきます.Guileは,マッチしたときに得られるベクタのことをマッチ構造(match structure)と呼んでいます.
- 次に,マッチ構造に関する各種手続き(match:substring,match:start,match:endなど)を使って,マッチした部分列を抽出するなどの処理を行います.
- make-regexp手続きとregexp-exec手続きの組み合わせの代わりにstring-match手続きを使うこともできます.こちらのほうが簡便です.しかし,一つの正規表現に複数の文字列をマッチさせるときには非効率的です(その理由は後述します).string-match手続きは,マッチング処理の結果を試験的に確認したい場合や,正規表現が色々と変化する場合に限定して利用したほうがよいでしょう,
コンパイルとマッチング
make-regexp(コンパイル)
- 呼び出し形式
(make-regexp pat) (make-regexp pat flag ... )
make-regexp手続きは,patによって指定された正規表現を内部的なデータ構造に変換(コンパイル)して,コンパイル後の正規表現(内部的なデータ構造)を返します.なお,検索対象文字列とのマッチング処理は,(文字列として記述した正規表現ではなく)コンパイル後の正規表現を利用して行います. - pat は正規表現を記述した文字列です.
-
flag はコンパイル後の正規表現の振る舞いを制御するためのフラグです.以下の大域変数が指定できます.
- regexp/icase
マッチングを行うときに大文字(uppercase)と小文字(lowercase)を区別しないようにコンパイル後の正規表現を作ります.(余談)「icase」は,おそらく,「ignore case」を略したもの思われます. - regexp/newline
検索対象の文字列が改行文字('\n')を含んでいるときに,ハット記号(^)が改行文字('\n')の直後の境界にマッチし,ドル記号($)が 改行文字の直前の境界にマッチするようにコンパイル後の正規表現を作ります.
(補足)ピリオド(.)や補集合演算を含むブラケット表現([^ ... ])が改行文字にマッチすることはありません.しかし,文字数にはカウントされるようです.
(補足)このフラグは,複数行からなるテキストデータが検索対象の文字列として与えられていて,その中の一つの行を正規表現を使って抽出するといった処理を意図しています. - regexp/basic
patが基本正規表現(Basic Regular Expression)の文法に沿って記述されていることを指定します.ちなみに,基本正規表現はobsoleteです. - regexp/extended
patが拡張正規表現(Extended Regular Expression)の文法に沿って記述されていることを指定します.これは既定値です.通常,指定する必要はありません.
- regexp/icase
-
具体例
(make-regexp "[A-Z]+") ……(1) (make-regexp "[A-Z]+" regexp/icase) ……(2) (make-regexp "^[A-Z]+$") ……(3) (make-regexp "^[A-Z]+$" regexp/newline) ……(4)
上記の(1)と(2)の正規表現は,英字(の大文字)からなる長さが1文字以上の文字列(検索対象文字列の部分列)がマッチします.(3)と(4)の正規表現は,英字(の大文字)であるような長さが1文字以上の文字列全体がマッチします.それぞれの手続き呼び出しは,文字列として与えられた正規表現を内部的なデータ構造に変換(コンパイル)して,コンパイル後の正規表現(内部的なデータ構造)を返します.ただし,フラグを指定したりしなかったりすることによって,コンパイル後の正規表現の振る舞いが次のように変化します.(1) 英字の大文字だけからなる部分列がマッチします.小文字はマッチしません.例えば: - "aaBBBcc"は,大文字だけからなる部分列"BBB"がマッチします.
- "aabbbcc"は,大文字をまったく含んでいないので,どの部分列もマッチしません.
(2) 大文字と小文字の区別を無視して,英字からなる部分列にマッチします.例えば: - "aaBBBcc"と"aabbbcc"は文字列全体がマッチします.
- "**BBB**"は部分列"BBB"がマッチします.
- "**bbb**"は部分列"bbb"がマッチします.
- "**123**"は(どの部分列も)マッチしません.
(3) 英字の大文字だけからなる文字列全体(文字列の先頭から末尾まで)がマッチします.例えば: - 上で示した文字列は,いずれも小文字や特殊記号('*')を含んでいるので,何もマッチしません.
- "ABC"や"PQR"は,大文字だけからなる文字列なので,文字列全体がマッチします.
(4) 検索対象の文字列が改行文字('\n')を含む場合,正規表現のハット記号(^)とドル記号($)が,それぞれ,改行文字の直後と直前の境界にマッチします.このフラグを指定したことによって,次の条件を満たす部分列がマッチします - 検索対象の文字列の先頭,改行文字,検索対象の文字列の末尾に挟まれていて,途中に改行文字を含まない.
- 英字の大文字だけから構成されている.
- "AABBBCC"は文字列全体がマッチします.
- "aaBBBcc"や"**BBB**"は,小文字や特殊記号を含むので,何もマッチしません.
- "AABBB\nCC"は,部分列"AABBB"(文字列の先頭と改行文字に挟まれていて,英字の大文字だけで構成された部分列)がマッチします.
- "aa\nBBB\ncc"は,部分列"BBB"(改行文字に挟まれていて,英字の大文字だけで構成された部分列)がマッチします.
- "**\nbbb\nCC"は,部分列"CC"(改行文字と末尾に挟まれていて,英字の大文字だけで構成されている部分列)がマッチします.
- "**\nbbb\ncc"は,大文字からなる部分列がないので,何もマッチしません.
- "AAa\n*BBB\nCC+"は,先頭・改行文字・末尾に挟まれた部分列が小文字や特殊記号を含むので,何もマッチしません.
regexp-exec(マッチング)
- 呼び出し形式
(regexp-exec rx str) (regexp-exec rx str start) (regexp-exec rx str start flags)
正規表現rxに文字列strをマッチさせ,次のような値を返します.-
マッチに成功したら
- 検索対象文字列と
- マッチした部分列の開始位置と終了位置のペア
- マッチに失敗したら #f を返します.
-
マッチに成功したら
- rx はコンパイル後の正規表現(make-regexp手続きの返り値)です.
- str は文字列です.
- start は$0$以上$\ell$以下の整数で(ただし,$\ell$はstrの長さ),strの文字位置です.これを指定したとき,start文字目〜末尾の部分列が検索対象になります(start文字目の直前までの部分列は無視されます).
-
flags はマッチング処理の振る舞いを制御するためのフラグです.次のような大域変数が指定できます.なお,以下の説明は,flagsを指定するときにはstartを指定していること,および,start文字目は検索対象文字列の先頭文字になることを前提としています.
-
regexp/notbol
これを指定したとき,start文字目の直前にある空文字列を正規表現としてのハット記号(^)にマッチさせないことにします.ちなみに,notbolは「not the beginning of a line」を略したものです. -
regexp/noteol
文字列strの末尾にある空文字列を正規表現としてのドル記号($)にマッチさせないことにします.ちなみに,noteolは「not the end of a line」を略したものです.
-
regexp/notbol
- (補足)上記のフラグは整数です.整数を表すビット列の各ビットをフラグとして利用しています.
-
(注意)上記の2つのフラグを同時に指定するときには,以下に示すように,logior手続きを使って2つのフラグを1つにまとめたものを指定します.
(regexp-exec rx str start (logior regexp/notbol regexp/noteol))
なお,logior手続きは引数として指定されれたフラグ(整数)のビットごとの論理和を返します. - regexp-exec手続きの具体例は下記の実行例を参照して下さい.
実行例
- 簡単な実行例を示します.
$ guile ...... 起動メッセージ ...... GNU Guile 3.0.5 guile> (define rx (make-regexp "[a-z]+")) ……(1) guile> (define str "#AbcDE#") ……(2) guile> (regexp-exec rx str) ……(3) $1 = #("#AbcDE#" (2 . 4)) ……(4) guile> (substring str 2 4) ……(5) $2 = "bc" guile> (regexp-exec rx "ABCDE") ……(6) $3 = #f guile>
これは次のような処理を行っています.(1) 正規表現[a-z]+を内部的なデータ構造にコンパイル(make-regexp)し,コンパイル後の正規表現を変数rxに束縛しています.この正規表現には,英字の小文字からなる長さが1文字以上の文字列がマッチします. (2) 文字列"#AbcDE#"を変数strに束縛しています. (3) rxに束縛されている正規表現([a-z]+)に,strに束縛されている文字列("#AbcDE#")をマッチ(regexp-exec)させています. (4) 上記のマッチ(regexp-exec)は成功し,その結果として,下記の要素からなるベクタが返ってきています. - 検索対象の文字列そのもの "#AbcDE#"
- マッチした部分列の開始位置と終了位置のペア (2 . 4)
(5) 確認のために,上記の開始位置と終了位置を利用して,マッチした部分列("bc")を抽出(substring)しています.英字の小文字からなる部分列がマッチしたことが分かります. (6) rxに束縛されている正規表現([a-z]+)に,他の文字列("ABCDE")をマッチ(regexp-exec)させています.このマッチ(regexp-exec)は失敗し,その結果として#fが返ってきています.マッチが失敗したとき,つまり,マッチする部分列がないときには #f が返ってきます. - (注意)マッチした部分列の終了位置は,部分列の終端の文字位置に1を加えた値になります.これは,文字列を扱う様々な手続きの仕様に合わせるためです.例えば,(substring $s$ $p$ $q$) は,文字列$s$の$p$文字目〜$q-1$文字目の部分列を取り出すので,目的とする部分列を正しく取り出すためには,$q$ $=$ 部分列の終端の文字位置$+1$ でなければなりません.
-
もう一つ簡単な実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (define rx (make-regexp "^[a-z]+$" regexp/icase regexp/newline)) ……(1) guile> (regexp-exec rx "AABBBCC") ……(2) $1 = #("AABBBCC" (0 . 7)) guile> (regexp-exec rx "AaBbBCc") ……(3) $2 = #("AaBbBCc" (0 . 7)) guile> (regexp-exec rx "#AaBbBCc#") ……(4) $3 = #f guile> (regexp-exec rx "#Aa\nBbB\nCc#") ……(5) $4 = #("#Aa\nBbB\nCc#" (4 . 7)) guile> (regexp-exec rx "#Aa\nBbB\nCc#" 1) ……(6) $5 = #("#Aa\nBbB\nCc#" (1 . 3)) guile> (regexp-exec rx "#Aa\nBbB\nCc#" 1 regexp/notbol) ……(7) $6 = #("#Aa\nBbB\nCc#" (4 . 7)) guile>
これは次のようなことを試しています.(1) 正規表現 ^[a-z]+$ を内部的なデータ構造にコンパイル(make-regexp)し,コンパイル後の正規表現を変数rxに束縛しています.コンパイル後の正規表現は,次の条件を満たす文字列がマッチします. - 英字から構成されている.
(補足)regexp/icaseフラグを指定したことによって,大文字と小文字を区別しません. - 検索対象文字列の先頭,改行文字('\n'),検索対象文字列の末尾に挟まれていて,途中に改行文字を含まない.
(補足)この条件はregexp/newlineを指定したことによって,ハット記号(^)とドル記号($)は,それぞれ,検索対象文字列の先頭と末尾だけでなく,改行文字('\n')の直後と直前の境界にもマッチします.
(2) rxに束縛されている正規表現に,英字の大文字だけから構成された文字列("AABBBCC")をマッチ(regexp-exec)させています.この場合,文字列全体が上の条件a.とb.を満たすので,文字列全体がマッチしています. (3) rxに束縛されている正規表現に,英字の大文字と小文字が混在した文字列("AaBbBCc")をマッチ(regexp-exec)させています.この場合も,文字列全体が上の条件a.とb.を満たすので,文字列全体がマッチしています. (4) rxに束縛されている正規表現に,英字以外の文字('#')を含む文字列("#AaBbBCc#")をマッチ(regexp-exec)させています.この場合,上の条件a.とb.を同時に満たす部分列がまったくないので,マッチに失敗しています. (5) rxに束縛されている正規表現に,英字以外の文字('#')と改行文字('\n')を含む文字列("#Aa\nBbB\nCc#")をマッチ(regexp-exec)させています.その結果を見ると,改行文字に挟まれた部分列(4文字目〜6文字目の部分列"BbB")がマッチしています. その部分列は上の条件a.とb.を満たしています. (6) rxに束縛されている正規表現に,英字以外の文字('#')と改行文字('\n')を含む文字列("#Aa\nBbB\nCc#")の1文字目以降をマッチ(regexp-exec)させています.つまり,先頭の'#'はマッチの対象から除外しています.その結果を見ると,1文字目〜2文字目の部分列"Aa"がマッチしています.先頭の'#'を無視したことによって,1文字目の'A'の直前の境界がハット記号(^)にマッチし,3文字目の改行文字の直前の境界がドル記号($)にマッチしています. (7) regexp/notbolフラグを指定して,上の(6)と同じマッチ処理(regexp-exec)を行っています.この場合,1文字目の'A'の直前の境界がハット記号(^)にマッチできないので,その帰結として,改行記号に挟まれた部分列"BbB"がマッチしています. - 英字から構成されている.
string-match(コンパイル&マッチング)
-
呼び出し形式
(string-match pat str) (string-match pat str start)
1番目の呼び出し形式は(regexp-exec (make-regexp pat) str)と等価です.2番目の呼び出し形式は(regexp-exec (make-regexp pat) str start)と等価です. - 実行例
$ guile ...... 起動メッセージ ...... guile> (string-match "[a-z]+" "#AbcDe#") $1 = #("#AbcDe#" (2 . 4)) guile>
これは正規表現 [a-z]+ を文字列 "#AbcDe#" にマッチ(string-match)させています.この場合,2文字目〜3文字目の部分列"bc"がマッチしています. -
(参考)string-match手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.
(define (string-match pattern str . args) (let ((rx (make-regexp pattern)) (start (if (pair? args) (car args) 0))) (regexp-exec rx str start)))
- (補足)string-matchは,簡便に利用できる手続きです.しかし,呼び出されるたびに正規表現のコンパイル(make-regexp)を行います.正規表現を利用した処理は,一つの正規表現に対して複数の文字列をマッチさせることが多いと思います.そのような場合,正規表現を何度もコンパイルするのは無駄です.特にコンパイル(make-regexp)は実行コストの高い処理なので,そのような場合には,正規表現を一度だけコンパイル(make-regexp)して,複数の文字列に対してマッチ処理(regexp-exec)を繰り返すようにしたほうがよいでしょう.
マッチ構造(match structure)
- マッチ構造に関する手続きを説明する前に,マッチ構造そのものについて説明します.
マッチ構造の成分
-
regexp-exec手続きやstring-match手続きを使って,正規表現$\alpha$に文字列$x$をマッチさせたとき,マッチに失敗したときには#fが返ってきますが,マッチが成功したときには次のようなベクタが返ってきます.
#($x$ ($s_0$ . $t_0$) ($s_1$ . $t_1$) ... ($s_n$ . $t_n$))ここで:
- 第0成分の $x$ は,検索対象の文字列そのものです.
- 第1成分の ($s_0$ . $t_0$) は,正規表現$\alpha$にマッチした$x$の部分列の開始位置$s_0$と終了位置$t_0$のペアです.
- 第2成分以降の各成分 ($s_i$ . $t_i$) は,$\alpha$の$i$番目の部分式にマッチした$x$の部分列の開始位置$s_i$と終了位置$t_i$のペアです(部分式については後述します).
- $\alpha$が部分式を持たないときには ($s_1$ . $t_1$) 〜 ($s_n$ . $t_n$) はありません.
- 終了位置$t_i$は,文字列を扱う色々な手続きに合わせて,マッチした部分列の最後の文字位置に1を加えた値になっています.
-
簡単な実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (define rx (make-regexp "[0-9][.][0-9][.][0-9]")) ……(1) guile> (define str "GNU Guile 3.0.7 released") ……(2) guile> (regexp-exec rx str) ……(3) $1 = #("GNU Guile 3.0.7 released" (10 . 15)) guile> (substring str 10 15) ……(4) $2 = "3.0.7" guile>
これは次のような処理を行っています.(1) ソフトウェアのバージョン番号によく利用されている "$a$.$b$.$c$" ($a$,$b$,$c$は数字)という形式にマッチする正規表現を変数rxに束縛しています. (2) 文字列 "GNU Guile 3.0.7 released" を変数strに束縛しています. (3) 正規表現rxに文字列strをマッチさせています.その結果($1)として, - 検索対象の文字列strそのもの "GNU Guile 3.0.7 released"
- 正規表現にマッチした部分列の開始位置と終了位置のペア (10 . 15)
(4) 上の結果を見ると,10文字目〜14文字目の部分列がマッチしたことが分かります.そこで,確認のために,その部分列($2)を求めています.
部分式マッチング(subexpression matching)
- 正規表現の部分式とは,丸括弧で囲まれた式のことです.部分式は,それを囲む左丸括弧の位置をもとに左から順に1番から始まる番号が付けられます.
-
例えば,正規表現 ((ab*)*(cd)?)(ef)* は4つの部分式を持っていて,各部分式を囲む左丸括弧の位置をもとに左から順に番号が付けられます.従って,
- 1番目の部分式は ((ab*)*(cd)?),
- 2番目の部分式は (ab*),
- 3番目の部分式は (cd),
- 4番目の部分式は (ef)
#($x$ ($s_0$ . $t_0$) ($s_1$ . $t_1$) ($s_2$ . $t_2$) ($s_3$ . $t_3$) ($s_4$ . $t_4$))といったマッチ構造(ベクタ)が返ってきます.ここで,- $x$ は検索対象の文字列そのもの
- ($s_0$ . $t_0$) は正規表現全体 ((ab*)*(cd)?)(ef)* にマッチした$x$の部分列の位置情報
- ($s_1$ . $t_1$) は1番目の部分式 ((ab*)*(cd)?) にマッチした$x$の部分列の位置情報
- ($s_2$ . $t_2$) は2番目の部分式 (ab*) にマッチした$x$の部分列の位置情報
- ($s_3$ . $t_3$) は3番目の部分式 (cd) にマッチした$x$の部分列の位置情報
- ($s_4$ . $t_4$) は4番目の部分式 (ef) にマッチした$x$の部分列の位置情報
-
簡単な実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (string-match "(ab)*(cd)*" "abcd") $1 = #("abcd" (0 . 4) (0 . 2) (2 . 4)) guile>
これは,正規表現 (ab)*(cd)* に文字列 "abcd" をマッチ(string-match)させています.マッチは成功し,その結果として返ってきたマッチ構造($1)は次のような情報を表しています.- "abcd" は,検索対象の文字列そのものです.
- (0 . 4) は,正規表現 (ab)*(cd)* にマッチした$x$の部分列の位置情報です.これは,文字列全体が正規表現にマッチしたことを示しています.
- (0 . 2) は,1番目の部分式 (ab) にマッチした$x$の部分列の位置情報です.これは,0文字目〜1文字目の部分列 "ab" がマッチしたことを示しています,
- (2 . 4) は,2番目の部分式 (cd) にマッチした$x$の部分列の位置情報です.これは,2文字目〜3文字目の部分列 "cd" がマッチしたことを示しています,
-
(補足)繰り返しを指定された部分式は,複数の部分列にマッチする可能性があります.例えば,正規表現 (ab)* に文字列 "abab" をマッチさせたとき,正規表現全体は文字列全体("abab")にマッチし,そのマッチ処理の過程で部分式 (ab) は,0文字目〜1文字目の部分列"ab"と2文字目〜3文字目の部分列"ab"に2度マッチします.このような場合,つまり,正規表現全体のマッチ処理の過程で部分式が複数の部分列に繰り返しマッチした場合,
- 部分式にマッチした部分列の位置情報は, 最後にマッチした部分列の位置情報になります.
guile> (string-match "(ab)*" "abab") $2 = #("abab" (0 . 4) (2 . 4))
この実行結果($2)は,(1番目の)部分式 (ab) が2文字目〜3文字目の部分列 "ab" にマッチしたことを示しています.しかし,実際には0文字目〜1文字目の部分列"ab"と2文字目〜3文字目の部分列"ab"に2度マッチしていて,最後にマッチした部分列の位置情報を返してきています. -
(補足)正規表現全体がマッチに成功した場合であっても,0回を含む繰り返し(*や?や{0,$n$})が指定された部分式は,どの部分列にもマッチしていない可能性があります.繰り返し指定の意味を交えて言うと,その部分式は0回マッチしたと言えます.そのような場合,つまり,正規表現全体のマッチ処理の過程で部分式にマッチした部分列がなかった場合,
- 部分式にマッチした部分列の位置情報は (-1 . -1) になります.
guile> (string-match "(ab)*(cd)*" "cd") $3 = #("cd" (0 . 2) (-1 . -1) (0 . 2)) guile>
これは,正規表現 (ab)*(cd)* に文字列 "cd" をマッチ(string-match)させています.マッチは成功していますが,1番目の部分式 (ab) にマッチする部分列がありません.別の言い方をすると,1番目の部分式(ab)は0回マッチしたと言えます.そのため,1番目の部分式 (ab) にマッチした部分列の位置情報は (-1 . -1) になっています.
マッチ構造を利用するための手続き
各手続きに共通の引数
-
以下の手続きの説明において match は,次のようなマッチ構造(ベクタ)とします.
#($x$ ($s_0$ . $t_0$) ($s_1$ . $t_1$) ... ($s_n$ . $t_n$))ここで,$x$は検索対象の文字列であり,($s_i$ . $t_i$)は正規表現全体や部分式にマッチした$x$の部分列の位置情報です.
- さらに,num は0以上$n$以下の整数とします.これは正規表現全体または部分式(の番号)を示しています.numの値を$k$とおくとき,$k=0$ならば正規表現全体を示し,$k>0$ならば$k$番目の部分式を示しています.以下の呼び出し形式において,numを省略したときの既定値は$0$(正規表現全体)です.numに$n$(部分式の個数)より大きな整数を指定するとエラーが発生します.
match:substring
-
呼び出し形式
(match:substring match) (match:substring match num)
- マッチ構造matchに含まれる成分を利用して,正規表現や部分式にマッチした部分列を返します.
- numを省略するか,numに0を指定したときには,正規表現全体に対する部分列($s_0$文字目〜$t_0-1$文字目の部分列)を返します.
- numに正整数$k$を指定したときには,$k$番目の部分式に対する部分列($s_k$文字目〜$t_k-1$文字目の部分列)を返します.ただし,$k$番目の部分式にマッチした部分列がないとき(つまり,$s_k=t_k=\,$-1のとき)には #f を返します.
-
簡単な実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (string-match "(ab)*(cd)+" "xabcdcdx") ……(1) $1 = #("xabcdcdx" (1 . 7) (1 . 3) (5 . 7)) guile> (match:substring $1) ……(2) $2 = "abcdcd" guile> (match:substring $1 0) ……(3) $3 = "abcdcd" guile> (match:substring $1 1) ……(4) $4 = "ab" guile> (match:substring $1 2) ……(5) $5 = "cd" guile>
これは次のような処理を行っています.(1) 正規表現(ab)*(cd)+に文字列"xabcdcdx"をマッチ(string-match)させています.マッチは成功し,マッチ構造(上記の$1)が返ってきています.このマッチ構想は次の結果を示しています. - 正規表現(ab)*(cd)+は,1文字目〜6文字目の部分列がマッチしています.
- 1番目の部分式(ab)は,1文字目〜2文字目の部分列がマッチしています.
- 2番目の部分式(cd)は,5文字目〜6文字目の部分列がマッチしています.
(2) 正規表現(ab)*(cd)+にマッチした部分列(1文字目〜6文字目の部分列"abcdcd")を抽出(match:substring)しています. (3) これも,正規表現(ab)*(cd)+にマッチした部分列(1文字目〜6文字目の部分列"abcdcd")を抽出(match:substring)しています. (4) 1番目の部分式(ab)にマッチした部分列(1文字目〜2文字目の部分列"ab")を抽出(match:substring)しています. (5) 2番目の部分式(cd)にマッチした部分列(5文字目〜6文字目の部分列"cd")を抽出(match:substring)しています. -
(参考)match:substring手続きは(ice-9 regexp)モジュールの中で次のように定義されています.
(define* (match:substring match #:optional (num 0)) (let* ((start (match:start match num)) (end (match:end match num))) (and start end (substring (match:string match) start end))))
なお,このノートの説明と合わせるために,オプション引数の名前をnumに変更しています(元々の名前はnです).
match:start
-
呼び出し形式
(match:start match) (match:start match num)
- マッチ構造matchに含まれる成分を利用して,正規表現や部分式にマッチした部分列の開始位置($s_i$)を返します.
- numを省略するか,numに0を指定したときには,正規表現全体に対する部分列の開始位置($s_0$)を返します.
- numに正整数$k$を指定したときには,$k$番目の部分式に対する部分列の開始位置($s_k$)を返します.ただし,$k$番目の部分式にマッチした部分列がないとき(つまり,$s_k=t_k=\,$-1のとき)には #f を返します.
-
(参考)match:start手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.
(define* (match:start match #:optional (num 0)) (let ((start (car (vector-ref match (1+ num))))) (if (= start -1) #f start)))
なお,このノートの説明と合わせるために,オプション引数の名前をnumに変更しています(元々の名前はnです).
match:end
-
呼び出し形式
(match:end match) (match:end match num)
- マッチ構造matchに含まれる成分を利用して,正規表現や部分式にマッチした部分列の終了位置($t_i$)を返します.
- numを省略するか,numに0を指定したときには,正規表現全体に対する部分列の終了位置($t_0$)を返します.
- numに正整数$k$を指定したときには,$k$番目の部分式に対する部分列の終了位置($t_k$)を返します.ただし,$k$番目の部分式にマッチした部分列がないとき(つまり,$s_k=t_k=\,$-1のとき)には #f を返します.
-
(参考)match:end手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.
(define* (match:end match #:optional (num 0)) (let* ((end (cdr (vector-ref match (1+ num))))) (if (= end -1) #f end)))
なお,このノートの説明と合わせるために,オプション引数の名前をnumに変更しています(元々の名前はnです).
match:count,match:string,match:prefix,match:suffix,regexp-match?
-
呼び出し形式
(match:count match) (match:string match) (match:prefix match) (match:suffix match) (regexp-match? obj)
- match:count手続きは,正規表現と部分式の個数を返します.別の言い方をすると,マッチ構造(ベクタ)の成分数から1(文字列$x$の分)を引いた値を返します.
- match:string手続きは,検索対象の文字列$x$(マッチ構造の第0成分)を返します.
- match:prefix手続きは,正規表現にマッチした部分列の前にある部分列(0文字目〜$s_0-1$文字目の部分列)を返します.
- match:suffix手続きは,正規表現にマッチした部分列の後ろにある部分列($t_0$文字目〜文字列$x$の末尾の部分列)を返します.
- regexp-match?手続きは,objがマッチ構造のデータ型に適合すれば#tを返し,そうでなければ#fを返します.ここで,objは任意のオブジェクトです.(補足)マッチ構造のデータ型とは,第0成分が文字列型で,第1成分以降が整数型のペア型であるようなベクタ型のことを言います.詳しくは下記のプログラムを参照して下さい.
-
(参考)上記の手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.
(define (match:count match) (- (vector-length match) 1)) (define (match:string match) (vector-ref match 0)) (define (match:prefix match) (substring (match:string match) 0 (match:start match 0))) (define (match:suffix match) (substring (match:string match) (match:end match 0))) (define (regexp-match? obj) (and (vector? obj) (string? (vector-ref obj 0)) (let loop ((i 1)) (cond ((>= i (vector-length obj)) #t) ((and (pair? (vector-ref obj i)) (integer? (car (vector-ref obj i))) (integer? (cdr (vector-ref obj i)))) (loop (+ 1 i))) (else #f)))))
なお,regexp-match?の引数名をGuileのマニュアルに合わせてobjに変更しています(元々の名前はmatchです).
その他の手続き
regexp?
- 呼び出し形式
(regexp? obj)
objがコンパイル後の正規表現だったとき#tを返し,そうでないときには#fを返します. - obj は任意のオブジェクトです.
fold-matches
- 呼び出し形式
(fold-matches regexp str init proc) (fold-matches regexp str init proc flags)
文字列strに対して,正規表現regexpにマッチする部分列のマッチ構造を次々と求めて,それらにprocを適用した結果を累積して返します.おおよそ次のような処理を行っています.-
start $\leftarrow$ $0$;
value $\leftarrow$ init;
(補足) startは文字列strの文字位置です. valueはprocを適用した結果を累積するための変数です. - 以下のa.〜c.を終了するまで繰り返します.
- startがstrの長さを超えたときには m $\leftarrow$ #f とし,そうでないときには
m $\leftarrow$ regexp-exec(regexp,str,start)
とします. - m$=$#fのとき(つまり,startがstrの長さを超えたか,または,上記のマッチが失敗したとき),valueを返して終了する.
-
m$\not=$#fのとき(つまり,上記のマッチが成功して,mがマッチ構造のとき),
start $\leftarrow$ match:end(m); value $\leftarrow$ proc(m,value);
とします(そして,a.に戻って処理を続けます).
- startがstrの長さを超えたときには m $\leftarrow$ #f とし,そうでないときには
-
start $\leftarrow$ $0$;
value $\leftarrow$ init;
- regexp は正規表現です.文字列で記述しても,コンパイル後のものを指定しても,どちらでもかまいません.
- str は検索対象の文字列です.
- init は累積を始めるための初期値です.
-
proc は累積していくための手続きです.この手続きは,
(proc match value)という形式で呼び出すことができて,valueと同じ型の値を返すものでなければいけません.ここで,matchはマッチ構造であり,valueはそれまでの累積結果です.
- flags はregexp-exec手続きのものとまったく同じです.
-
(参考)fold-matches手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.その処理の骨格は上で述べた通りです.ただし,以下の処理では,(変数abutsを利用して)マッチした部分列の直後にある空文字列を累積の対象としないように制御しています.さらに,(変数bolを利用して)start$>0$のとき,ハット記号(^)が文字列の先頭にマッチしないように制御しています.
(define* (fold-matches regexp string init proc #:optional (flags 0)) (let ((regexp (if (regexp? regexp) regexp (make-regexp regexp)))) (let loop ((start 0) (value init) (abuts #f)) ; True if start abuts a previous match. (define bol (if (zero? start) 0 regexp/notbol)) (let ((m (if (> start (string-length string)) #f (regexp-exec regexp string start (logior flags bol))))) (cond ((not m) value) ((and (= (match:start m) (match:end m)) abuts) ;; We matched an empty string, but that would overlap the ;; match immediately before. Try again at a position ;; further to the right. (loop (+ start 1) value #f)) (else (loop (match:end m) (proc m value) #t)))))))
-
簡単な実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (define (add-list m lst) (cons (match:substring m) lst)) ……(1) guile> (fold-matches "<[a-zA-Z0-9]*>" "<ab>c <1>23 X<Y>Z" '() add-list) ……(2) $1 = ("<Y>" "<1>" "<ab>") guile>
これは次のような処理を行っています.(1) マッチ構造(m)からマッチした部分列を抽出(match:substring)して,リスト(lst)に追加する手続きadd-listを定義しています. (2) 文字列 "<ab>c <1>23 X<Y>Z" からHTMLのタグのような部分列(三角括弧で囲まれた英数字列)を取り出して,そのような部分列からなるリスト($1)を作成しています.つまり,まず"<ab>"を取り出してリストに追加し,次に"<1>"を取り出してリストに追加し,最後に"<Y>"を取り出してリストに追加しています.なお,リストの要素は抽出された順の逆順に並んでいます.
list-matches
- 呼び出し形式
(list-matches regexp str) (list-matches regexp str flags)
文字列strに対して,正規表現regexpにマッチする部分列のマッチ構造を次々と求めて,それらマッチ構造からなるリストを返します.正確な処理は下記のプログラムを参照して下さい. - regexp は正規表現です.文字列で記述しても,コンパイル後のものを指定しても,どちらでもかまいません.
- str は検索対象の文字列です.
- flags はregexp-exec手続きのものとまったく同じです.
-
(参考)list-matches手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.
(define* (list-matches regexp string #:optional (flags 0)) (reverse! (fold-matches regexp string '() cons flags)))
-
簡単な実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (define (disp-substr m) (write (match:substring m)) (newline)) guile> (for-each disp-substr (list-matches "<[a-zA-Z0-9]*>" "<ab>c <1>23 X<Y>Z")) "<ab>" "<1>" "<Y>" guile>
これはfold-matchesのところで示した実行例とほとんど同じ処理を行っています.つまり,文字列 "<ab>c <1>23 X<Y>Z" からHTMLのタグのような部分列(三角括弧で囲まれた英数字列)を取り出してリストを作っています.ただし,list-matchesは,部分列のリストではなく,マッチ構造のリストを返してきます.そこで,それぞれのマッチ構造にdisp-substr手続きを適用することによって,マッチした部分列を一覧表示しています.
regexp-substitute
- 呼び出し形式
(regexp-substitute port match item ...)
item ... に指定した文字列を左から順にportに出力します.portが#fのときには,出力する内容からなる文字列を作って返します. - port は出力ポートです.
- match はマッチ構造です.
-
item として次のものが指定できます.
item 説明 文字列 文字列を指定したときには,文字列そのものを出力します. 整数 整数$k$を指定したときには,(match:substring match $k$) を出力します. 'pre シンボル 'pre を指定したときには,(match:prefix match) を出力します. 'post シンボル 'post を指定したときには,(match:suffix match) を出力します. -
簡単な実行例を示します.
guile> (define m (string-match "<(my.*)>" "<mycmd>")) ……(1) guile> (regexp-substitute (current-output-port) m "<span class=\"" 1 "\">") (newline) ……(2) <span class="mycmd"> guile>
筆者はHTMLで勉強ノートを作る際に,<mycmd> などの手前勝手なタグを使っていて,ノートがおおよそ完成したところで
<span class="mycmd">
といった<span>タグに置き換えています.手前勝手なタグを上のような<span>タグに置き換える処理はregexp-substituteを使うと簡単にできます.上の実行例は次のような処理を行っています.(1) 正規表現 <(my.*)> を文字列 "<mycmd>" にマッチ(string-match)させて,その結果(マッチ構造)を変数mに束縛しています. (2) 1番目の部分式 (my.*) は "mycmd" にマッチします.そこで,regexp-substitute手続きを使って,"mycmd"の前後に<span>タグを構成する文字列("<span class=\""と"\">")を付加して標準出力(current-output-port)に出力しています. -
もう1つ実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (string-match "((two)|(three)|/)+" "one/two/three/four") ……(1) $1 = #("one/two/three/four" (3 . 14) (13 . 14) (4 . 7) (8 . 13)) guile> (regexp-substitute (current-output-port) $1 'pre " to " 'post "\n") ……(2a) one to four guile> (let ((p (current-output-port))) ... (display (match:prefix $1) p) ... (display " to " p) ... (display (match:suffix $1) p) ... (display "\n" p)) ……(2b) one to four guile> (regexp-substitute (current-output-port) $1 'post 0 'pre "\n") ……(3) four/two/three/one guile> (regexp-substitute (current-output-port) $1 'post "/" 3 "/" 2 "/" 'pre "\n") ……(4) four/three/two/one guile>
これは次のような処理を行っています.(1) 正規表現 ((two)|(three)|/)+ に文字列 "one/two/three/four" をマッチ(string-match)させています.その返り値のマッチ構造($1)から次のような事実が分かります. - 正規表現全体は3文字目〜13文字目の部分列 "/two/three/" にマッチしています.
-
その正規表現全体に対する結果から,
- (match:prefix $1) は0文字目〜2文字目の部分列 "one" を返し,
- (match:suffix $1) は14文字目〜17文字目の部分列 "four" を返す
- 2番目の部分式 (two) は4文字目〜6文字目の部分列 "two" にマッチしています.
- 3番目の部分式 (three) は8文字目〜12文字目の部分列 "three" にマッチしています.
(2a) regexp-substitute手続きを使って, - シンボル 'pre が表す文字列(注:(match:prefix $1)の返り値)
- 文字列 " to "
- シンボル 'post が表す文字列(注:(match:suffix $1)の返り値)
- 改行文字 "\n"
(2b) シンボル 'pre や 'post の意味を確かめるために,regexp-substitute手続きの代わりに,display手続き,match:prefix手続き,match:suffix手続きを使って(2a)と同じ文字列を直接出力しています. (3) regexp-substitute手続きを使って, - シンボル 'post が表す文字列(注:(match-suffix $1)の返り値)
- 正規表現全体にマッチした部分列(注:(match:subtring $1 0)の返り値)
- シンボル 'pre が表す文字列(注:(match-prefix $1)の返り値)
- 改行文字 "\n"
(let ((p (current-output-port))) (display (match:suffix $1) p) (display (match:substring $1 0) p) (display (match:prefix $1) p) (display "\n" p)) (4) 2番目と3番目の部分式にマッチした部分列を利用して,"one","two","three","four"を逆順に並べてスラッシュ('/')で区切った文字列を標準出力に出力しています.この処理は (let ((p (current-output-port))) (display (match:suffix $1) p) (display "/" p) (display (match:substring $1 3) p) (display "/" p) (display (match:substring $1 2) p) (display "/" p) (display (match:prefix $1) p) (display "\n" p)) -
(参考)regexp-substitute手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.
(define (regexp-substitute port match . items) ;; If `port' is #f, send output to a string. (if (not port) (call-with-output-string (lambda (p) (apply regexp-substitute p match items))) ;; Otherwise, process each substitution argument in `items'. (for-each (lambda (obj) (cond ((string? obj) (display obj port)) ((integer? obj) (display (match:substring match obj) port)) ((eq? 'pre obj) (display (match:prefix match) port)) ((eq? 'post obj) (display (match:suffix match) port)) (else (error 'wrong-type-arg obj)))) items)))
regexp-substitute/global
- 呼び出し形式
(regexp-substitute/global port regexp str item ...)
正規表現 regexp に文字列 str をマッチさせた結果をもとに,item に指定した文字列(やオブジェクト)を左から順にportに出力します.portが#fのときには,出力する内容からなる文字列を作って返します. - port は出力ポートです.
- regexp は正規表現です.文字列で記述したものと,コンパイル後のものの,どちらでも指定できます.
- str は検索対象の文字列です.
-
item として次のものが指定できます.
item 説明 文字列 文字列を指定したときには,文字列そのものを出力します. 整数 整数$k$を指定したときには,(match:substring match $k$) を出力します. 手続き マッチ構造を引数とする手続きが指定できます.その手続きをマッチ構造に適用した返り値を出力します.その返り値は,出力ポートportに出力可能なオブジェクトであれば何でもかまいません(文字列である必要はありません). 'pre シンボル 'pre を指定したときには,(match:prefix match) を出力します. 'post 下記参照. -
シンボル 'post を指定したとき,正規表現にマッチするすべての部分列に対してregexp-substitute/globalを再帰的に実行します.おおよそ次のような処理を行います.
- m $\leftarrow$ regexp-exec(regexp,str) または string-match(regexp,str);
- マッチに失敗したとき,strを出力して終了する.
-
マッチに成功したとき,item ... に対して,次の処理を繰り返す.
- item $=$ 'postのとき,
(regexp-substitute/global port regexp (match:suffix m) item ...)を(再帰的に)実行する.
- item $\not=$ 'postのとき,item を(regexp-substituteの場合と同じように)出力する.
- item $=$ 'postのとき,
-
実行例
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (define (to-upcase m) (string-upcase (match:substring m))) ……(1) guile> (regexp-substitute/global #f "[a-z]+" "<!-- abc -->" to-upcase) ……(2) $1 = "ABC" guile> (regexp-substitute/global #f "[a-z]+" "<!-- abc -->" 'pre to-upcase 'post) ……(3) $2 = "<!-- ABC -->" guile> (regexp-substitute/global #f "[a-z]+" "<!-- abc:def -->" 'pre to-upcase 'post) ……(4) $3 = "<!-- ABC:DEF -->" guile>
これは次のような処理を行っています.(1) マッチ構造mを受け取って,正規表現にマッチした部分列(match:substring)の中の英字の小文字を大文字に変換して返す手続きを定義しています. (2) 正規表現 [a-z]+ を文字列 "<!-- abc -->" させて,マッチした部分列 "abc" を大文字に変換して返しています. (3) 上と同じマッチング処理を行っているのですが,マッチした部分列を大文字に変換したもの("ABC")に,マッチした部分列の前後の部分列("<!-- "と" -->")を連結して返しています.この場合の 'post は (regexp-substitute/global #f "[a-z]+" " -->" 'pre to-upcase 'post)と同じ処理を行っています.つまり,マッチした部分列 "abc" の残りの部分列 " -->" に対して再帰的に処理しています.ただし,この再帰的な処理はマッチに失敗するので,文字列 " -->" を返して終了します(結果的に,regexp-substituteの'postと同じ処理を行っています).(4) 検索対象の文字列を "<!-- abc:def -->" に変えて上と同じような処理を行っていす.正規表現 [a-z]+ は,まず部分列 "abc" がマッチします.そのときには,'pre("abc"の前にある"<!-- ")と"abc"を大文字に変換した文字列("ABC")を出力して,'postを処理します.この'postは (regexp-substitute/global #f "[a-z]+" ":def -->" 'pre to-upcase 'post)と同じ処理を行います.今度は"def"がマッチし,'pre(":")と"DEF"を出力して,'postを処理します.この'postは(regexp-substitute/global #f "[a-z]+" " -->" 'pre to-upcase 'post)と同じ処理を行います.今度はマッチに失敗するので,文字列 " -->" を出力して終了します.
以上をまとめると,"<!-- ", "ABC", ":", "DEF"," -->" が順に出力され,結果的にこれらを連結した文字列($3)が返ってきます. - (参考)上記の実行例のように,'post を item ... の最後に指定したときには,正規表現にマッチする部分列に関してループ処理(Scheme的な言い方をすれば,末尾再帰的な処理)を行います.実際には,下位の呼び出しは上位の呼び出しに戻っていくのですが,'postのあとに何も処理することがないので,結果的に(あるいは,見かけ上)末尾再帰的な処理を行うことになります.
-
(参考)regexp-substitute手続きは,(ice-9 regexp)モジュールの中で次のように定義されています.
(define (regexp-substitute/global port regexp string . items) ;; If `port' is #f, send output to a string. (if (not port) (call-with-output-string (lambda (p) (apply regexp-substitute/global p regexp string items))) ;; Walk the set of non-overlapping, maximal matches. (let next-match ((matches (list-matches regexp string)) (start 0)) (if (null? matches) (display (substring string start) port) (let ((m (car matches))) ;; Process all of the items for this match. Don't use ;; for-each, because we need to make sure 'post at the ;; end of the item list is a tail call. (let next-item ((items items)) (define (do-item item) (cond ((string? item) (display item port)) ((integer? item) (display (match:substring m item) port)) ((procedure? item) (display (item m) port)) ((eq? item 'pre) (display (substring string start (match:start m)) port)) ((eq? item 'post) (next-match (cdr matches) (match:end m))) (else (error 'wrong-type-arg item)))) (if (pair? items) (if (null? (cdr items)) (do-item (car items)) ; This is a tail call. (begin (do-item (car items)) ; This is not. (next-item (cdr items)))))))))))
-
(補足)正規表現regexpに検索対象文字列strがまったくマッチしなかったときには,strそのものが出力されます.この点は,regexp-substituteと異なります(regexp-substituteは何も出力しません).比較のための実行例を以下に示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (regexp-substitute #f (string-match "[^a-z]+" "abc")) $1 = "" guile> (regexp-substitute/global #f "[^a-z]+" "abc") $2 = "abc" guile>
何も出力しないほうが好ましいように思いますが,マッチに成功しないような正規表現と文字列の組み合わせに対してこの手続きを適用するとは考えにくいので,実用上の問題になることはないように思います.
regexp-quote
- 呼び出し形式
(regexp-quote str)
文字列strが拡張正規表現を記述しているものと見なして,その中の特殊文字(注:拡張正規表現の特殊文字)をバックスラッシュによってクォート(エスケープ)します. - str は文字列です.
-
簡単な実行例を示します.
$ guile GNU Guile 3.0.5 ...... 起動メッセージ ...... guile> (regexp-quote "[0-9].[0-9]") $1 = "\\[0-9]\\.\\[0-9]" guile>
これは,バージョン番号風の数字とピリオドからなる正規表現の中のピリオドをクォートしようと思ってregexp-quoteを実行しています.しかしながら,少し意図に反して,左角括弧もクォートされています. - (余談)この手続きはすべての特殊文字をクォートしてしまうので,一般的に役立つとは思えません.それから,仕様書はバックスラッシュによってクォート(エスケープ)すると説明しているのですが,中括弧はブラケット表現に変換したりします(理由は不明です).なお,メタ文字(^,-,])を除くと,ブラケット表現も特殊文字の特殊機能を無効化するので,間違いではありません.
- (注意)特殊文字をクォート(エスケープ)するときには,バックスラッシュを2重にしなければなりません.これは,Guileのreaderにバックスラッシュをエスケープさせるためです.例えば,正規表現の \\ をGuileの手続きなどで指定するときには,それぞれのバックスラッシュを2重にして "\\\\" としなければなりません.