[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

12. 正規表現の検索

GNU Emacs の中では、正規表現の検索が徹底的に活用されている。例えば、 forward-sentence とか forward-paragraph といった関数を調 べてみれば、こういった検索についてよく理解出来るだろう。

正規表現の検索は section `Regular Expression Search' in The GNU Emacs Manual, の中や section `Regular Expressions' in The GNU Emacs Lisp Reference Manual, の中で説明されている。この章を書く際に も、私は読者が少なくともこれらをある程度は知っていることを想定している。 大事な点は、正規表現を使うことで具体的な文字列そのものだけではなく、パター ンをも検索出来るということである。例えば forward-sentence のコー ドは文 (sentence) の終わりを示すパターンを検索し、その場所にポイントを移 動する。

実際に forward-sentence 関数のコードを見る前に、文の終わりを示すパ ターンがどんなものであるべきかを考えておいた方が良いだろう。このパターン については次のセクションで議論することにする。その次に、正規表現の検索を 行う関数である re-search-forward の説明をする。 forward-sentence 関数の説明はその後である。この章の最後の節では、 forward-paragraph 関数の説明をする。forward-paragraph は複雑 な関数なので、幾つか新しい特徴を紹介することになる。

12.1 sentence-end の正規表現  
12.2 関数 re-search-forward  search-forward とほぼ同じ
12.3 forward-sentence  正規表現検索の単純な例
12.4 forward-paragraph:関数の金脈  もうちょっと複雑な例
12.5 自分自身の `TAGS' ファイルの作成  
12.6 復習  
12.7 re-search-forward についての練習問題  正規表現の練習問題


12.1 sentence-end の正規表現

シンボル sentence-end は文末 (訳註:ここでは勿論英語の文章を想定 している。) を示すあるパターンにバインドされている。この正規表現はどうあ るべきだろうか?

明らかに、文末には終止符か疑問符、もしくは感嘆符が来る。実際、この三つの 文字の中の一つで終了する文節だけが文末と見倣されるべきである。これはパター ンの中に次の文字集合が含まれるということだ。

 
[.?!]

しかしながら、forward-sentence が単に終止符や疑問符、感嘆符に移動 するというだけではまずい。というのも、これらの文字は文中にも使われること があるからである。例えば終止符は略語の後にも使われる。従って、他の情報も 必要になる。

慣習的に、普通文末には二つの空白を打つが、文中の終止符、疑問符、感嘆符の の後には一つの空白しか打たない。従って、終止符、疑問符、感嘆符に続いて二 つの空白というのが文末の良い目印になるだろう。ただし、ファイルの中では二 つの空白はタブや行末であっても良い。つまり、正規表現の中には、これら三つ のどれかというものが含まれる。この部分は次のように書ける。

 
\\($\\| \\|  \\)
       ^   ^^
      TAB  SPC

ここで `$' は行末を表わす。それと、タブと二つの空白がこの表現の中に 入っていることを指摘しておく。両方とも、この表現の中に実際の文字が挿入さ れている。

括弧や縦棒の前には二つのバックスラッシュ `\\' が必要になる。最初の バックスラッシュは、Emacs の中でその後に続くバックスラッシュを quote するためのものであり、二番目のバックスラッシュは、その後に続く括弧や縦棒 が特殊文字であることを示すものである。

また、文の後には次のように一つ以上の復帰コードが続くこともある。

 
[
]*

タブや空白と同様、復帰コードも実際のコードを埋め込むことで正規表現の中に 挿入される。末尾のアスタリスクは、RET が零回以上繰り返すことを示す。

ただ、文末が必ずしも終止符や疑問符、ないしは感嘆符に続いて空白で終ってい るとは限らない。閉引用符や何らかの閉括弧が空白の前に来るかもしれない。実 際の所、このような記号が空白の前に二つ以上続くこともある。これらのために、 次のような表現が必要になる。

 
[]\"')}]*

この表現の中で、最初の `]' が最初に来ていることに注意しよう。(訳註: `['`]' で狭んで定める文字集合の中に `]' を含めるには、 このように文字集合の最初に `]' を記述する。) また、二番目の文字は `"' である。前の `\' は Emacs にこれが特殊文字ではなく文字列の 一部だと伝えるためのものである。残りの三文字は、`''`)'`}' そのものを表わす。これらの文字が零回以上現れるということになる。

これら全てを合せたものが、あるべき文末の正規表現を形成している。そして、 実際に sentence-end を評価してみると、次のような値が返される。 (訳註:Mule や Nemacs では、これに加えて日本語のための拡張がなされている ので、更に複雑になっている。)

 
sentence-end
     => "[.?!][]\"')}]*\\($\\|     \\|  \\)[       
]*"


12.2 関数 re-search-forward

re-search-forward 関数は、search-forward 関数と非常によく 似ている。(後者については、関数 search-forward, 参照。)

re-search-forward は正規表現を検索するためのものである。もし検索 が成功すれば、ただちに目的とする文字の後にポイントを移動する。後方検索の 場合は目的の文字の直後に移動する。検索成功時には re-search-forwardt を返す。(訳註:Emacs version 19 で はポイントの位置を返す。) (従って、ポイントの移動は「副作用」である。)

search-forward と同じく re-search-forward 関数も四つの引数 を持つ。

  1. 最初の引数は、検索する正規表現である。正規表現は引用符に囲まれた文字列で なければならない。

  2. 二番目の引数は省略可能であり、関数が検索する範囲を制限するために用いる。 これはバッファの中の位置として指定される。

  3. 三番目の引数も省略可能で、検索に失敗した場合の挙動を決めるためのものであ る。もし引数が nil なら失敗時にはエラーが返され、メッセージが表示 される。他の値の場合は失敗時には nil が返り、成功時には t が返される。(訳註:Emacs version 19 ではポイントの位置が返される。)

  4. 四番目の引数は繰り返しの回数である。負の引数を与えると、後方検索になる。

re-search-forward のテンプレートは次の通りである。

 
(re-search-forward "正規表現"
                検索範囲の限界
                検索失敗時の動作
                繰り返しの回数)

二番目から四番目までの引数は省略可能である。しかし、最後の二つの片方ない しは両方に値を渡したい場合は、それ以前の全ての引数を与えなければならない。 そうしないと Lisp インタプリタはどの引数を何処へ渡すかを間違えてしまう。

forward-sentence 関数では、正規表現は変数 sentence-end の 値である。つまり、次の通りである。

 
"[.?!][]\"')}]*\\($\\|  \\|  \\)[       
]*"

検索の限界はパラグラフの終わりまでである (文がパラグラフを越えて続くこと はないので)。検索に失敗した場合は nil が返される。また、繰り返し の回数は forward-sentence の引数として与えられる。


12.3 forward-sentence

カーソルを文の前方に移動するコマンドは、Emacs Lisp での正規表現検索の使 い方をストレートに説明してくれる。この関数は、実際以上に長くて複雑そうに 見えるが、それは、前方に検索するだけではなく後方にも検索出来るようになっ ていたり、オプションとして複数の文を移動することが出来るようになっている ためである。この関数は通常は M-e というキーにバインドされている。

以下が forward-sentence のコードである。

 
(defun forward-sentence (&optional arg)
  "Move forward to next sentence-end.  With argument, repeat.
With negative argument, move backward repeatedly to sentence-beginning.
Sentence ends are identified by the value of sentence-end
treated as a regular expression.  Also, every paragraph boundary
terminates sentences as well."
  (interactive "p")
  (or arg (setq arg 1))
  (while (< arg 0)
    (let ((par-beg
           (save-excursion (start-of-paragraph-text) (point))))
      (if (re-search-backward
           (concat sentence-end "[^ \t\n]") par-beg t)
          (goto-char (1- (match-end 0)))
        (goto-char par-beg)))
    (setq arg (1+ arg)))
  (while (> arg 0)
    (let ((par-end
           (save-excursion (end-of-paragraph-text) (point))))
      (if (re-search-forward sentence-end par-end t)
          (skip-chars-backward " \t\n")
        (goto-char par-end)))
    (setq arg (1- arg))))

ぱっと見ただけだと、この関数は長く感じてしまう。まずは骨組みを見て、それ から肉の部分を見ていくのが賢明だろう。骨組みを見るには、桁が左にあるもの から見て行けば良い。

 
(defun forward-sentence (&optional arg)
  "説明文字列..."
  (interactive "p")
  (or arg (setq arg 1))
  (while (< arg 0)
    while ループの本体
  (while (> arg 0)
    while ループの本体

こう書き直すとぐっと解りやすくなる。この関数定義は説明文字列と interactive 式、そして or 式と while ループからなっ ているのである。

では順に各々の部分を見ていくことにしよう。

まず、説明文字列が過不足なくかつ理解しやすく書かれていることに注意しよう。

この関数は interactive "p" 宣言を持っている。これは、(もし与えら れたなら) 処理された前置引数が引数としてこの関数に渡されることを意味する。 (これは数値である。) もしこの関数が引数を渡されなければ (引数は省略可能 である) その場合、引数 arg は1にバインドされる。また、 forward-sentence が非インタラクティブに引数無しで呼ばれた場合には、 argnil にバインドされる。

前置引数を扱うのは or 式である。この式では、arg の値がある 値にバインドされている場合はそのままにしておき、もし nil にバイン ドされていた場合は1にセットしている。

while ループ  
正規表現の検索  


while ループ

or 式の後には、二つの while ループが続く。最初の while ループには、前置引数が負の値ならば真を返すような真偽テスト が含まれている。これは後方検索のためのものである。このループの本体は二番 目の while ループの本体とそっくりであるが全く同じではない。取り敢 えずこちらの方はとばして、二番目のループの方に集中することにしよう。

二番目の while ループはポイントを前方に移動するものである。骨組み は次の通りである。

 
(while (> arg 0)            ; 真偽テスト
  (let 変数リスト
    (if (真偽テスト)
        then-part
      else-part
  (setq arg (1- arg))))     ; while ループのデクリメンタ

この while ループはデクリメントタイプの物である (減少カウンタを使ったループ, 参照)。 この中にはカウンタ (今の場合は変数 arg) が零よりも大きい間は真を 返すような真偽テストが含まれている。そして、ループを繰り返すごとにカウン タの値を1減らすようなデクリメンタが含まれている。

もし forward-sentence に前置引数が与えられなかったなら、といって もこれが普通の使い方だが、その場合 while ループは一度だけ繰り返す。 これは arg の値が1だからである。

while ループの本体は let 式からなる。これは局所変数を作成 する。また、その本体として if 式を持っている。

while ループの本体は次のようである。

 
(let ((par-end
       (save-excursion (end-of-paragraph-text) (point))))
  (if (re-search-forward sentence-end par-end t)
      (skip-chars-backward " \t\n")
    (goto-char par-end)))

ここで let 式は局所変数 par-end を生成、バインドしている。 この後見るように、この局所変数は正規表現検索に限界ないしは制限を与えるた めに用いられている。もしこの検索でパラグラフ内に適切な文末が見つからなけ ればパラグラフの終端で検索をやめる。

が、その前にまずどうやって par-end がパラグラフの終端の値にバイン ドされるかを見てみよう。ここでは let 式を使って、次のS式を Lisp インタプリタが評価した際に返される値に par-end の値をセットしてい る。

 
(save-excursion (end-of-paragraph-text) (point))

この式では (end-of-paragraph-text) によってポイントがパラグラフ終 端に移動し、(point) によってそのポイントの値が返される。そして、 save-excursion によって元の位置にポイントが戻されるというわけであ る。このようにして letpar-endsave-excursion 式が返す値、つまりパラグラフの終端の位置をバインドする。 (end-of-paragraph-text 関数は forward-paragraph を使ってい る。これについては後で簡単に触れる。)

Emacs は次に let 式の本体を評価する。これは次のような if 式である。

 
(if (re-search-forward sentence-end par-end t) ; if-part
    (skip-chars-backward " \t\n")              ; then-part
  (goto-char par-end)))                        ; else-part

if は最初の引数が真かどうかテストし、もし真なら then-part を評価 し、そうでなければ else-part を評価する。今の場合 if の真偽テスト は正規表現検索である。

forward-sentence のような関数の実際の動作は奇妙に感じられるかもし れない。しかし Lisp ではこのような操作が行われるのは、ごく一般的なことで ある。


正規表現の検索

re-search-forward 関数は文末、つまり正規表現 sentence-end で定義されたパターンを検索する。もしパターンが見つかったなら---即ち文末 が見つかったなら---その時は re-search-forward 関数は二つのことを 行う。

  1. re-search-forward 関数は副作用を実行する。即ち、ポイントを見つけ た文末まで移動する。

  2. re-search-forward 関数は真の値を返す。これは if によって返 される値であり、検索が成功したことを意味する。

副作用としてのポイントの移動は if 関数が検索成功の結果として値を 返すよりも前の時点に行われる。

if 関数が検索に成功した re-search-forward から呼び出されて 真の値を返す際には if は then-part、即ち (skip-chars-backward "\t\n") の評価も行う。このS式はタブや改行 などを含む全ての種類の空白文字を越えて、表示される文字 (printed character) の所まで前に戻り、その文字の直後にポイントを置く。ポイントは 既に文末のパターンの所まで移動しているので、この動作で文が目に見える文字 で終わっている部分の直後に来ることになる。通常はピリオドだろう。

一方、もし re-search-forward 関数が文末パターンを見つけられなかっ た場合には、関数は偽を返す。この場合は if は三番目の引数を評価す る。これは (goto-char par-end) である。これはパラグラフの終わりに までポイントを移動する関数である。

正規表現検索は極めて便利なものであり、forward-sentence---その中 では検索は if 式のテストになっている---で説明されたパターンは手軽 に使えるものである。あなたもこのパターンを取り入れたコードを見たり書いた りするであろう。


12.4 forward-paragraph:関数の金脈

forward-paragraph 関数はポイントをパラグラフの終わりまで移動する。 これは通常 M-} にバインドされており、例えば let*match-beginninglooking-at のようなそれ自身も重要であるよ うな他の幾つかの関数を利用している。

forward-paragraph 関数の定義は forward-sentence 関数の定義 に比べてかなり長い。これは各々の行が fill-prefix (行詰め接頭辞) で始まる ようなパラグラフも相手にしなければならないためである。

Fill prefix は 各々の行の先頭に繰り返し現れる文字列からなる。例えば Lisp コードでは便宜上パラグラフの各々の行が `;;; ' から始まる。また テキストモードでは四つの空白文字インデントされたパラグラフの fill prefix としてよく使われる。(Fill prefix についてのより詳しい情報は section `Fill Prefix' in The GNU Emacs Manual, を参照せよ。)

Fill prefix があるということは、forward-paragraph 関数は、中の行 が左端から始まっているようなパラグラフの終わりを見つけるだけではなく、そ のバッファの全て、ないしは多くの行がある fill prefix で始まっているよう 場合にもパラグラフの終わりを見つけなければならないということを意味する。

更に、時には fill prefix があっても無視した方が良い場合だってある。特に 空行でパラグラフが区切られているような場合などがそうだ。これにより、更に 複雑さが増す。

ここでは forward-paragraph 関数を全て書き出すのはやめて、その中の 一部だけを見ることにする。準備なしに読もうとすると、ちょっと臆してしまう かもしれない。

この関数のアウトラインは次のようである。

 
(defun forward-paragraph (&optional arg)
  "documentation..."
  (interactive "p")
  (or arg (setq arg 1))
  (let*
      変数リスト
    (while (< arg 0)        ; 後方に戻る場合のコード
      ...
      (setq arg (1+ arg)))
    (while (> arg 0)        ; 前方に進む場合のコード
      ...
      (setq arg (1- arg)))))

関数の最初の部分はいつもの通りである。引数のリストには一つ省略可能な引数 があるだけである。次に説明文字列が続く。

インタラクティブ宣言の中の小文字の `p' はもし前置引数があれば、それ を処理してから関数に渡すことを意味する。これは数値であり、いくつ分のパラ グラフを移動するかを表わす。次の行の or 式は関数に一つも引数が与 えられなかった場合を扱うための物である。これはこの関数がインタラクティブ ではなく他のコードから呼び出された場合に起きる。これについては以前説明し た。(関数 forward-sentence, 参照。) ここ までは、今まで慣れ親しんできた部分である。

let*  
前方に移動する場合の while ループ  
パラグラフとパラグラフの間での動作  
パラグラフの内部での動作  
Fill prefix が無い場合  fill prefix が無い場合
Fill prefix がある場合  fill prefix が有る場合
まとめ  forward-paragraph のまとめ


let*

forward-paragraph の次の行は let* 式で始まる。これは以前に 出てた式とは異なる。このシンボルは let* であって let では ない。

特殊形式 let* は基本的に let と同じなのだが、Emacs が変数 を順にセットしていくため、変数リストの中で後に出てくる変数がそれ以前に出 てきた変数の値を参照することが出来る、という点のみが異なっている。

この関数の let* 式の中では Emacs は二つの変数 fill-prefix-regexpparagraph-separate をバインドしてい るのだが、paragraph-separate がバインドされる値は fill-prefix-regexp の値に依存しているのである。

各々を順に見ていこう。シンボル fill-prefix-regexp は次のリストを 評価した値にセットされる。

 
(and fill-prefix 
     (not (equal fill-prefix ""))
     (not paragraph-ignore-fill-prefix)
     (regexp-quote fill-prefix))

これは最初の要素が関数 and であるようなS式である。

関数 and は各々の引数をそのどれかが nil を返すまで評価して いく。どれかが nil を返した場合は andnil を返す。 しかし、もしどの引数も nil を返さなければ、最後の引数を評価して返 された値を返す。(この場合、その値は nil ではないので、Lisp では真と見な される。) 別の言い方をすれば、and は引数全てが真である場合にのみ 真を返すわけである。

今の場合なら、fill-prefix-regexp は後に続く四つのS式を評価して全 て真 (即ち、非 nil) が返された場合にのみ、非nil の値を返す。 そうでない場合は fill-prefix-regexpnil にバインドされる。

fill-prefix
この変数を評価すると、もしあれば fill prefix の値が返される。fill prefix が無い場合は nil が返る。

(not (equal fill-prefix "")
このS式は fill prefix があった場合にそれが空文字列、つまり文字を一つも 含まない文字列かどうかを判定する。空文字列は fill prefix としての役には 立たない。

(not paragraph-ignore-fill-prefix)
このS式は、もし変数 paragraph-ignore-fill-prefixt 等 の真の値にセットされている場合に nil を返す。

(regexp-quote fill-prefix)
これは and 関数の最後の引数になる。もし and の全ての引数が 真であれば、and の値としてはこのS式を評価して返された値が返され ることになる。そしてそれが fill-prefix-regexp の値になる。

この and 式を評価して真が返された場合、fill-prefix-regexpregexp-quote によって修正された fill-prefix の値にバイ ンドされる。regexp-quote は、文字列を読み取り、それのみにマッチし、 その他の文字列にはマッチしないような正規表現を返す。結局、fill prefix が 存在する場合、fill-prefix-regexp はその fill prefix の値にちょう どマッチする値にセットされ、そうでなければ nil にセットされる。

let* 式の二番目の局所変数は paragraph-separate である。こ れは次のS式を評価して返された値にバインドされる。

 
(if fill-prefix-regexp
    (concat paragraph-separate 
            "\\|^" fill-prefix-regexp "[ \t]*$")
  paragraph-separate)))

このS式を見れば何故 let ではなく let* 式を使ったかが分る。 if 式の真偽テストは、変数 fill-prefix の値が nil か その他の値かということに依存しているのである。

もし fill-prefix-regexp が値を持たなかったなら、(訳註: つまり nil であれば) Emacs は if 式の else-part を評価し、 paragraph-separate を現在の局所的な値にバインドする。 (paragraph-separate はパラグラフの区切りにマッチする正規表現であ る。)

しかし、もし fill-prefix-regexp が値を持てば、(訳註: 真の値を持て ば) Emacs は if 式の then-part を評価し、 paragraph-separate の値を fill-prefix-regexp をパターンの 一部として含むような正規表現にバインドする。

より詳しく言うと paragraph-separate は、元々の paragraph-separate の値を fill-prefix-regexp に空行を加え た表現を連結した値にセットされることになる。`^'fill-prefix-regexp が行頭に来なければならないことを意味し、その後 に空白が来ても良いことが、"[ \t]*$" で定義されている。 `\\|' は、この正規表現か元の paragraph-separate かどちらかが マッチしなければならないことを示すものである。

では let* 式の本体部分に入ろう。本体の最初の部分では、この関数に 負の引数が与えらた場合、即ち後方に戻る場合を扱っている。この部分は省略す ることにする。


前方に移動する場合の while ループ

let* 式の本体の二番目の部分は前方に進む場合を扱っている。これは arg が零よりも大きい間は繰り返すような while ループである。 この関数を使う場合、大抵この引数は1である。従って while ループの 本体はちょうど一度だけ実行され、それによりカーソルはパラグラフ一つ分だけ 移動する。

この while ループは次のような形をしている。

 
(while (> arg 0)
  (beginning-of-line)

  ;; パラグラフとパラグラフの間に居る場合
  (while (prog1 (and (not (eobp))
                     (looking-at paragraph-separate))
           (forward-line 1)))

  ;; パラグラフ内で、fill prefix がある場合
  (if fill-prefix-regexp
      ;; fill prefix があるので、paragraph-start は関係無し。
      (while (and (not (eobp))
                  (not (looking-at paragraph-separate))
                  (looking-at fill-prefix-regexp))
        (forward-line 1))

    ;; パラグラフ内で、fill prefix がない場合
    (if (re-search-forward paragraph-start nil t)
        (goto-char (match-beginning 0))
      (goto-char (point-max))))

  (setq arg (1- arg)))

まず直ちに分ることは、これは減少カウンタの while ループであり、デ クリメンタとして (setq (1- arg)) というS式を使っているということ である。このループの本体は次の三つのS式からなる。

 
;; パラグラフの間に居る場合 
(beginning-of-line)       
(while 
    while ループの本体)

;; パラグラフ内で、fill prefix がある場合
(if 真偽テスト    
    then-part             

;; パラグラフ内で、fill prefix がない場合
  else-part               

Emacs Lisp のインタプリタが while ループの本体を評価する場合、ま ず最初に (beginning-of-line) という式を評価して行頭に移動する。次 に内部の while ループに入る。この while ループは、カーソル がパラグラフとパラグラフの間の空白にあった場合、その外に移動させるための ものである。最後に、実際にパラグラフの終りにポイントを移動する if 式が来る。


パラグラフとパラグラフの間での動作

まずは内部の while ループを見ることにしよう。このループはポイント がパラグラフとパラグラフの間にあった場合を扱うためのものである。ここでは 三つの新しい関数が登場する。prog1eobp、そして looking-at である。

while ループは次の通りである。

 
(while (prog1 (and (not (eobp))
                   (looking-at paragraph-separate))
              (forward-line 1)))

この while ループは本体部分を持たない! 真偽テストは次のS式であ る。

 
(prog1 (and (not (eobp))
            (looking-at paragraph-separate))
       (forward-line 1)))

prog1 の最初の引数は and 式である。この中には、ポイントが バッファの最後にあるかどうか、そしてポイントの直後にパラグラフの区切りを 示す正規表現にマッチするパターンがあるかどうかのテストが含まれている。

もしカーソルがバッファの最後ではなく、またカーソルの後にパラグラフの区切 りを示す文字があれば、and 式は真になる。Lisp インタプリタは and 式を評価した後、prog1 の二番目の引数である forward-line を評価する。これはポイントを一行だけ前に移動する。た だし、prog1 が返す値は最初の引数の値である。従って、while ループはポイントがバッファの最後にはなく、またパラグラフとパラグラフの間 にある間だけ繰り返される。最終的にポイントがあるパラグラフに移動したなら、 and 式は偽を返す。いずれにしろ forward-line コマンドは実行 されることに注意しよう。これはポイントがパラグラフとパラグラフの間から移 動した場合、ポイントはパラグラフの二行目の先頭に置かれることになることを 意味している。


パラグラフの内部での動作

外側の while ループでの次のS式は if 式である。 Lisp インタプリタは fill-prefix-regexp 変数が nil 以外の値 を持っている場合には if 式の then-part を評価し、nil の場 合、つまり fill prefix が無い場合は else-part を評価する。


Fill prefix が無い場合

まず最初に fill prefix が無い場合のコードを見るのが単純で良いだろう。こ のコードは次のようにまた別の if 式からなっている。

 
(if (re-search-forward paragraph-start nil t)
    (goto-char (match-beginning 0))
  (goto-char (point-max)))

このS式は、大抵の人が forward-paragraph コマンドの主目的と考える であろう仕事を実際に行う。つまり、まず次のパラグラフの始まりを前方に向かっ て検索するような正規表現の検索を行い、もし見つかれば、ポイントをその位置 に移動する。別のパラグラフの始まりが見つからない場合は、現在のバッファで アクセス可能な最後の位置まで移動する。

この中で、これまでで慣れ親しんでいない部分は match-beginning の使 い方だけである。この関数自体も初めて出てくるものだ。 match-beginning 関数は、最後に行った正規表現検索でマッチしたテキ ストの始まりの位置を表わす数値を返す関数である。

ここで match-beginning 関数が使われているのは前方検索の性質のため である。つまり、前方検索が成功した場合、通常の検索か正規表現の検索かどう かにかかわらず、ポイントを見つけたテキストの位置にまで移動してしまう。今 の場合なら、検索に成功した場合はポイントは paragraph-start のパター ンの終わりにまで移動するのだが、これは現在のパラグラフの終わりではなく、 次のパラグラフの始まりである。

しかしながら、我々の目的はポイントを次のパラグラフの先頭ではなく現在のパ ラグラフの終わりにまで移動することである。この二つの位置は、パラグラフと パラグラフの間に幾つかの空行がある場合などでは、当然異なる。

引数0で呼ばれた場合、 match-beginning は最も最近、正規表現の検索 に成功した位置を返す。今の場合、最も最近の正規表現の検索は paragraph-start を探すものなので、match-beginning はそのパ ターンの開始位置を返す。(終了位置ではない。) この開始位置は現在のパラグ ラフの最後である。

(ついでにいうと、引数として正の数が渡された場合、 match-beginning 関数は最後の正規表現の中の括弧でくくられた部分の表 現の開始位置を返す。これは便利な機能である。)


Fill prefix がある場合

さっき議論した、内部の方の if 式は、それを含む fill prefix がある かどうかをテストする if 式の else-part になっていたのであった。 fill prefix がある場合は、then-part の方が評価される。この部分は次の通り である。

 
(while (and (not (eobp))
            (not (looking-at paragraph-separate))
            (looking-at fill-prefix-regexp))
  (forward-line 1))

このS式がやることは、次の三つの条件が真である間だけ、ポイントを一行ずつ 前方に移動することである。

  1. ポイントはバッファの最後ではない。

  2. ポイントに続くテキストはパラグラフの区切りではない。

  3. ポイントに続くパターンは fill prefix を表わす正規表現である。

最後の条件はちょっと戸惑うかもしれないが、ポイントが forward-paragraph 関数によって既に行頭に移動していることを思い出 せば納得出来るだろう。つまり、テキストに fill prefix があれば、 looking-at 関数がそれを見ることになるのである。


まとめ

以上をまとめてみよう。前方に移動する場合の forward-paragraph 関数 は次のことを行う。

復習のために、これまで議論してきたコードを見やすいよう整形して挙げておく。

 
(interactive "p")
(or arg (setq arg 1))
(let* (
       (fill-prefix-regexp
        (and fill-prefix (not (equal fill-prefix ""))
             (not paragraph-ignore-fill-prefix)
             (regexp-quote fill-prefix)))

       (paragraph-separate
        (if fill-prefix-regexp
            (concat paragraph-separate
                    "\\|^"
                    fill-prefix-regexp
                    "[ \t]*$")
          paragraph-separate)))

  backward-moving-code (omitted) ...

  (while (> arg 0)                ; 前方に進む場合のコード
    (beginning-of-line)

    (while (prog1 (and (not (eobp))
                       (looking-at paragraph-separate))
             (forward-line 1)))

    (if fill-prefix-regexp
        (while (and (not (eobp))  ; then-part
                    (not (looking-at paragraph-separate))
                    (looking-at fill-prefix-regexp))
          (forward-line 1))
                                  ; else-part: 内部の if
      (if (re-search-forward paragraph-start nil t)
          (goto-char (match-beginning 0))
        (goto-char (point-max))))

    (setq arg (1- arg)))))        ; デクリメンタ

forward-paragraph 関数の完全な定義は、上に挙げた前方に進むコード だけでなく、後方に戻るコードも含んでいる。

もし、この文書を GNU Emacs の中で読んでいるなら、この関数全体のコードを 見たい場合には M-. (find-tag) とタイプし、プロンプトが出た ら関数名をタイプすれば良い。find-tag 関数がまず `TAGS' テー ブルの名前を聞いてきた場合は、あなたの `emacs/src' ディレクトリの中 の `TAGS' ファイルの名前を答える。これは `/usr/local/lib/emacs/19.23/src/TAGS'. のようなパス名である。(正確 な `emacs/src' ディレクトリのパス名はあなたの Emacs がどのようにイ ンストールされているかに依る。もしパス名が分らなければ、C-h i とし て Info に入り、C-x C-f とタイプして emacs/info ディレクト リのパス名を知ることが出来る。`TAGS' ファイルのパスは対応する `emacs/src' パスであることが多い。(訳註:無いことも多い。) もっとも Info ファイルが他の場所にあることも多いが。

あるいは、`TAGS' ファイルがないディレクトリに、自分自身の `TAGS' ファイルを作成することも出来る。 自分自身の `TAGS' ファイルの作成, を参照。


12.5 自分自身の `TAGS' ファイルの作成

簡単にソースの場所までジャンプ出来るように、あなた自身の `TAGS' ファ イルを作成することが出来る。例えば、もしあなたの `~/emacs' ディレク トリに沢山のファイルがあったとすると---何を隠そう、私もこの場所に137個の `.el' ファイルがあり、その内17個をロードしているのだが---そのディレ クトリに `TAGS' ファイルを作ることで、grep やその他の道具で 関数名を検索するよりはずっと簡単に、特定の関数の位置にジャンプすることが 出来るようになる。

TAGS ファイルは、Emacs の配布に含まれる etags プログラムを 使って作成出来る。普通、etags は Emacs が構築された時に一緒にコン パイルされインストールされる。(ただし、etags は Emacs Lisp 関数や Emacs の一部分ではない。これは C のプログラムである。)

`TAGS' ファイルを作成するには、まずこのファイルを作成したいディレク トリに移動する。Emacs の中だと、M-x cd コマンドを使うか、そのディ レクトリにあるファイルをビジットするか、あるいは C-x d (dired) を使うことで移動することが出来る。そして

 
M-! etags *.el

とタイプしてやれば良い。etags プログラムは普通のシェルで使える全 てのワイルドカードを理解出来る。例えば、もし二つのディレクトリに対して一 つの `TAGS' ファイルを作りたい場合は、二番目のディレクトリを `../elisp/' だとして、次のようにタイプすればよい。

 
M-! etags  *.el ../elisp/*.el

また、etags が受け付けるオプションのリストを見たい場合には

 
M-! etags --help

とタイプする。

etags プログラムは Emacs Lisp, Common Lisp, Scheme, C, Fortran, Pascal, LaTeX, そして大抵のアセンブラを扱うことが出来る。このプログラム には言語を特定するためのスィッチはない。その代わりにファイル名や中身から そのファイルの言語を認識するのである。

また `etags' は、自分自身でコードを書いたり、既に書いた関数を後から 参照したりするのにも大変便利である。新しい関数を書いたら、ときおり etags プログラムを走らせよう。そうすることでそれらの関数が `TAGS' ファイルに付け加わる。


12.6 復習

ここでは最近導入した関数の簡単なまとめを載せておく。

while
本体部分のS式を、本体の最初の要素が真を返す間だけ繰り返し評価する。最後 は nil を返す。(つまり、この式は副作用のためだけに評価される。)

例)

 
(let ((foo 2))
  (while (> foo 0)
    (insert (format "foo is %d.\n" foo))
    (setq foo (1- foo))))

     =>      foo is 2.
             foo is 1.
             nil
(insert 関数は引数をポイントに挿入する。また format 関数 は引数を message 関数が整形するのと同様に整形して出来たストリン グを返す。\n は改行である。

re-search-forward
パターンを検索し、発見した場合はその直後にポイントを移動する。

search-forward と同様、四つの引数を取る。

  1. 検索パターンを表わす正規表現。

  2. 省略可能。検索の限界。

  3. 省略可能。検索が失敗した場合にどうするか。nil を返すかエラーメッ セージを出すか。

  4. 省略可能。検索を何回繰り返すか。負の数の場合は後方に検索する。

let*
幾つかの変数を特定の値に局所的にバインドし、残りの引数を評価する。値 としては最後の引数の値を返す。局所変数をバインドする際、より先にバイン ドした変数があれば、その値を利用出来る。

例)

 
(let* ((foo 7)
      (bar (* 3 foo)))
  (message "`bar' is %d." bar))
     => `bar' is 21.

match-beginning
最後の正規表現で見つかったテキストの始まりの位置を返す。

looking-at
ポイントに続くテキストが引数の正規表現にマッチした場合に真として t を返す。

eobp
ポイントがそのバッファのアクセス可能な最大位置にある場合に真として t を返す。アクセス可能な範囲というのは、もしナローイングがかかっ ていない場合はバッファの最後の位置であり、ナローイングがかかっている場合 は、その部分の最後である。

prog1
引数を順に評価していき、最初の引数の値を返す。

例)

 
(prog1 1 2 3 4)
     => 1


12.7 re-search-forward についての練習問題


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Matsuda Shigeki on April, 10 2002 using texi2html