[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12. 正規表現の探索

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Regexp%20Search"
"intro/正規表現の探索"へのコメント(無し)

正規表現の探索は、GNU Emacsでは非常に多用されている。 2つの関数forward-sentenceforward-paragraphは、 これらの探索のよい例である。

正規表現の探索に ついては、節 `Regular Expression Search' in

GNU Emacsマニュアル
や節 `Regular Expressions' in
GNU Emacs Lispリファレンスマニュアル
に記述されている。 本章を執筆するにあたり、読者はこれらに関して少なくともある程度の知識を 持っていると仮定した。 主要な点は、正規表現により、文字列の字面どおりの探索に加えて、 パターンの探索もできることである。 たとえば、forward-sentenceのコードは、文末を表す可能性のある 文字のパターンを探索し、そこへポイントを移動する。

関数forward-sentenceのコードを説明するまえに、 文末を表すパターンとはどんなものであるかを考えることも価値がある。 このパターンについては次節で説明する。 続いて、正規表現の探索関数re-search-forwardを説明する。 さらに、関数forward-sentenceを説明する。 そして、本章の最後の節では、関数forward-paragraphを説明する。 forward-paragraphは複雑な関数であり、新たな機能もいくつか紹介する。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12.1 sentence-endのための正規表現

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=sentence-end"
"intro/sentence-endのための正規表現"へのコメント(無し)

シンボルsentence-endは、文末を表すパターンに束縛される。 この正規表現はどんなものであるべきか?

明らかに、文は、ピリオドや疑問符や感嘆符で終わる。 もちろん、これら3つの文字のうちの1つで終わる節のみを文末と考えるべきである。 つまり、パターンにはつぎの文字集合が含まれるべきである。

 
[.?!]

しかし、ピリオドや疑問符や感嘆符は文の途中に使われることもあるので、 forward-sentenceが単純にこれら3つの文字に移動してほしくはない。 たとえば、ピリオドは省略形のあとにも使われる。 つまり、別の情報が必要なのである。

慣習としては、文のうしろには2つの空白を置くが、 文の途中のピリオドや疑問符や感嘆符のうしろには空白を1つだけ置く。 つまり、ピリオドや疑問符や感嘆符のあとに空白が2つあれば、 文末を表すと考えられる。 しかし、ファイルでは、2つの空白のかわりに、タブだったり行末だったりもする。 つまり、正規表現は、これらの3つの場合を含む必要がある。 これらは、つぎのように表せる。

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

ここで、`$'は行末を表し、また、式の中にタブがあるのか空白が2つあるのか わかるようにしてある。 どちらも式の中には実際の文字を入れる。

括弧と縦棒のまえには2つのバックスラッシュ`\\'が必要である。 Emacsでは、最初のバックスラッシュはそれに続くバックスラッシュをクオートし、 2番目のバックスラッシュは、それに続く括弧や縦棒が特別な文字であることを表す。

また、つぎのように、文には複数個の復帰が続いてもよい。

 
[
]*

タブや空白のように、正規表現に復帰を入れるにはそのまま入れる。 アスタリスクは、RETが0回以上繰り返されることを表す。

文末は、ピリオドや疑問符や感嘆符のあとに適切な空白が続くだけはない。 空白のまえが、閉じ引用符や閉じ括弧の類でもよい。 もちろん、空白のまえにこれらが複数個あってもよい。 これらはつぎのような正規表現を必要とする。

 
[]\"')}]*

この式で、最初の`]'は正規表現の最初の文字である。 2番目の文字は`"'であり、そのまえには`\'があるが Emacsに`"'が特別な文字ではないことを指示する。 残りの3つの文字は`''と`)'と`}'である。

これらすべてで、文末に一致する正規表現のパターンが何であるかを表す。 そして、もちろん、sentence-endを評価すると つぎのような値になっていることがわかる。

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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12.2 関数re-search-forward

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=re-search-forward"
"intro/関数re-search-forward"へのコメント(無し)

関数re-search-forwardは、関数search-forwardにとてもよく似ている (See 節 8.1.3 関数search-forward)。

re-search-forwardは正規表現を探索する。 探索に成功すると、みつかった文字列の最後の文字の直後にポイントを置く。 逆向きの探索の場合には、みつかった文字列の最初の文字の直前にポイントを置く。 真値としてtを返すようにre-search-forwardに指示できる (したがって、ポイントの移動は「副作用」である)。

search-forwardのように、関数re-search-forwardは4つの引数を取る。

  1. 第1引数は、関数が探索する正規表現である。 正規表現は引用符のあいだの文字列である。

  2. 省略できる第2引数は、関数が探索する範囲を制限する。 限界はバッファの位置で指定する。

  3. 省略できる第3引数は、失敗した場合の関数の動作を指定する。 第3引数にnilを指定すると、探索に失敗した場合、 関数はエラーを通知(し、メッセージを表示)する。 それ以外の値を指定すると、探索に失敗した場合はnilを返し、 探索に成功するとtを返す。

  4. 省略できる第4引数は、繰り返し回数である。 負の繰り返し回数は、re-search-forwardに逆向き探索を指示する。

re-search-forwardの雛型はつぎのとおりである。

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

第2引数、第3引数、第4引数は省略できる。 しかし、最後の2つの引数のいずれか、あるいは、両者に値を渡したい場合には、 そのまえにある引数すべてに値を渡す必要がある。 さもないと、Lispインタープリタはどの引数に値を渡すのかを誤解することになる。

関数forward-sentenceでは、 正規表現は変数sentence-endの値であり、つぎのとおりである。

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

探索の範囲は、(文は段落を越えることはないので)段落の末尾までである。 探索に失敗すると、関数はnilを返す。 繰り返し回数は、関数forward-sentenceの引数で与える。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12.3 forward-sentence

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=forward-sentence"
"intro/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が非対話的に引数なしで呼ばれた場合には、 argにはnilが束縛される。

or式で前置引数を処理する。 これは、argに値が束縛されていればそのままにするが、 argnilが束縛されているときにはargの値を1にする。

whileループ    Two while loops.
正規表現の探索    A regular expression search.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

whileループ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-sentence%20while%20loops"
"intro/whileループ"へのコメント(無し)

or式に続けて2つのwhileループがある。 最初のwhileには、forward-sentenceの前置引数が負の数のときに 真となる判定条件がある。 これは、逆向き探索用である。 このループの本体は2番目のwhile節の本体に似ているが、同一ではない。 このwhileループを飛ばして、2番目のwhileに注目しよう。

2番目のwhileループは、ポイントを先へ進めるためのものである。 その骨格はつぎのように読める。

 
(while (> arg 0)            ; 判定条件
  (let 変数リスト
    (if (判定条件)
        真の場合の動作
      偽の場合の動作
  (setq arg (1- arg))))     ; while ループのカウンタを減らす

whileループは減少方式である (See 節 11.1.4 減少カウンタによるループ)。 これは、カウンタ(変数arg)が0より大きい限り真を返す判定条件と、 ループを1回廻るごとにカウンタの値を1減らす減少式を持つ。

コマンドのもっとも一般的な用法であるが、 forward-sentenceに前置引数を与えないとargの値は1なので、 このwhileループは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は、par-endの値に、 Lispインタープリタがつぎの式を評価した値を設定する。

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

この式では、(end-of-paragraph-text)でポイントを段落の末尾に移動し、 (point)でポイントの値を返す。 そして、save-excursionがポイントをもとの位置に戻す。 したがって、letは、save-excursionが返した値をpar-endに 束縛するが、これは段落の末尾の位置である (関数(end-of-paragraph-text)は、これから説明する forward-paragraphを使う)。

Emacsは、続いて、letの本体を評価する。 それはつぎのようなif式である。

 
(if (re-search-forward sentence-end par-end t) ; 判定条件
    (skip-chars-backward " \t\n")              ; 真の場合の動作
  (goto-char par-end)))                        ; 偽の場合の動作

ifは、第1引数が真かどうかを調べ、 そうならば、真の場合の動作を評価し、さもなければ、 Emacs Lispインタープリタは偽の場合の動作を評価する。 if式の判定条件は、正規表現の探索である。

関数forward-sentenceのこのような実際の動作は奇妙に思えるかもしれないが、 これはLispでこの種の操作を行う一般的な方法である。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

正規表現の探索

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-sentence%20re-search"
"intro/正規表現の探索"へのコメント(無し)

関数re-search-forwardは文末を探す、 つまり、正規表現sentence-endで定義されたパターンを探す。 パターンがみつかれば、つまり、文末がみつかれば、 関数re-search-forwardはつぎの2つのことを行う。

  1. 関数re-search-forwardは、副作用を及ぼす。 つまり、みつけた文字列の直後にポイントを移動する。

  2. 関数re-search-forwardは真値を返す。 この値をifが受け取り、探索が成功したことを知る。

ポイントを移動するという副作用は、関数ifに探索成功の値が 返されるまえに完了する。

関数ifが、re-search-forwardの呼び出しに成功し真値を受け取ると、 ifは真の場合の動作、式(skip-chars-backward " \t\n")を評価する。 この式は、目に見える文字がみつかるまで逆向きに空白やタブや復帰を飛ばして 目に見える文字の直後にポイントを移動する。 ポイントは文末のパターンの直後にあったので、 この操作により、ポイントは文末の目に見える文字、 普通はピリオドの直後にポイントを置く。

一方、関数re-search-forwardが文末のパターンの検索に失敗すると、 関数は偽を返す。 するとifは第3引数、つまり、(goto-char par-end)を評価し、 段落の末尾にポイントを移動する。

正規表現の探索はとても便利であり、if式の判定条件でも使っている re-search-forwardはパターンの例題として簡便である。 読者もこのパターンをしばしば利用するであろう。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12.4 forward-paragraph:関数の宝庫

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=forward-paragraph"
"intro/forward-paragraph:関数の宝庫"へのコメント(無し)

関数forward-paragraphは、段落の末尾にポイントを進める。 普通はM-}にバインドされており、 let*match-beginninglooking-atの それ自体で重要な関数を利用している。

詰め込み接頭辞(fill prefix)で始まる行から成る段落を処理するため、 forward-paragraphの関数定義は、forward-sentenceの関数定義に 比べてとても長い。

詰め込み接頭辞は、各行の先頭で繰り返すことができる文字の文字列から成る。 たとえば、Lispコードでは、段落ほどもある注釈の各行は`;;; 'で 始める約束になっている。 テキストモードでは4つの空白を詰め込み接頭辞とするのが一般的であり、 字下げした段落になる (詰め込み接頭辞について詳しくは、 See 節 `Fill Prefix' in

GNU Emacsマニュアル
)。

詰め込み接頭辞があると、関数forward-paragraphは、 もっとも左のコラムから始まる行から成る段落の末尾を探すだけでなく、 詰め込み接頭辞で始まる行を含む段落の末尾も探す必要がある。

さらに、段落を区切る空行の場合には、 詰め込み接頭辞を無視するほうが実用的である。 これも複雑さが増す理由である。

関数forward-paragraphの全体を示すかわりに、その一部だけを示す。 前準備もなしに読むと、気力をくじかれる!

関数の概略はつぎのとおりである。

 
(defun forward-paragraph (&optional arg)
  "説明文..."
  (interactive "p")
  (or arg (setq arg 1))
  (let*
      変数リスト
    (while (< arg 0)        ; 戻すコード
      ...
      (setq arg (1+ arg)))
    (while (> arg 0)        ; 進めるコード
      ...
      (setq arg (1- arg)))))

関数の始めの部分は決まりきっていて、 省略できる1個の引数から成る関数の引数リストである。 これに説明文が続く。

宣言interactiveの小文字の`p'は、もしあれば処理した前置引数を 関数に渡すことを意味する。 これは数であり、何個の段落だけ先へ進むかを表す繰り返し回数である。 つぎのor式は、関数に引数が渡されなかった場合の共通処理で、 対話的にではなく他のコードから関数が呼び出された場合にこうなる (See 節 12.3 forward-sentence)。 この関数の馴染みのある部分はこれで終わりである。

let*    The let* expression.
先へ進めるwhileループ    The forward motion while loop.
段落のあいだ    Movement between paragraphs.
段落の中    Movement within paragraphs.
詰め込み接頭辞なし    When there is no fill prefix.
詰め込み接頭辞あり    When there is a fill prefix.
まとめ    Summary of forward-paragraph code.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

let*

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-para%20let"
"intro/let*式"へのコメント(無し)

関数forward-paragraphのつぎの行はlet*式で始まる。 これはこれまでに見てきた式とは種類が異なる。 シンボルはlet*であり、letではない。

スペシャルフォームlet*letに似ているが、 Emacsは1つずつ順に各変数を設定し、変数リストのうしろの変数では、 Emacsがすでに設定した変数リストのまえの部分の変数の値を利用してもよいところが 異なる。

この関数のlet*式では、Emacsは2つの変数、 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である。

関数andは、引数の1つが値nilを返すまで各引数を評価する。 nilを返した場合には、and式もnilを返す。 しかし、値nilを返す引数がなければ、最後の引数を評価した値を返す (そのような値はnil以外なので、Lispでは真と解釈される)。 いいかえれば、and式は、すべての引数が真であるときに限り真値を返す

ここでは、つぎの4つの式を評価して真値(つまり、非nil)が得られれば、 変数fill-prefix-regexpには非nilの値が束縛される。 さもなければ、fill-prefix-regexpnilに束縛される。

fill-prefix
この変数を評価すると、もしあれば、詰め込み接頭辞の値が返される。 詰め込み接頭辞がなければ、この変数はnilを返す。

(not (equal fill-prefix "")
この式は、既存の詰め込み接頭辞が空文字列、つまり、文字をまったく含まない 文字列かどうかを調べる。 空文字列は、意味のある詰め込み接頭辞ではない。

(not paragraph-ignore-fill-prefix)
この式は、変数paragraph-ignore-fill-prefixtなどの真値を設定してオンになっているとnilを返す。

(regexp-quote fill-prefix)
これは関数andの最後の引数である。 andのすべての引数が真ならば、 この式を評価した結果の値をand式が返し、 変数fill-prefix-regexpに束縛される。

このand式が正しく評価されると、fill-prefix-regexpには、 関数regexp-quoteで修正したfill-prefixの値が束縛される。 regexp-quoteは、文字列を読み取り、その文字列のみに一致し それ以外には一致しない正規表現を返す。 つまり、fill-prefix-regexpには、 詰め込み接頭辞があれば、詰め込み接頭辞だけに一致するものが設定される。 さもなければ、この変数にはnilが設定される。

let*式の2番目のローカル変数はparagraph-separateである。 これにはつぎの式を評価した値が束縛される。

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

この式は、letではなくlet*を使った理由を示している。 ifの判定条件は、変数fill-prefix-regexpnilであるか それ以外の値であるかに依存している。

fill-prefix-regexpに値がなければ、Emacsはif式の偽の場合の動作を 評価して、ローカル変数にparagraph-separateを束縛する (paragraph-separateは、段落の区切りに一致する正規表現である)。

一方、fill-prefix-regexpに値があれば、 Emacsはif式の真の場合の動作を評価して、 paragraph-separateには、 パターンとしてfill-prefix-regexpを含む正規表現を束縛する。

特に、paragraph-separateには、段落を区切るもとの正規表現に、 fill-prefix-regexpに空行が続くという代替パターンを連結したものを 設定する。 `^'はfill-prefix-regexpが行の先頭から始まることを指定し、 "[ \t]*$"は行末に空白が続いてもよいことを指定する。 `\\|'は、この部分がparagraph-separateに対する 代替の正規表現であることを指定する。

ではlet*の本体に移ろう。 let*の本体の始めの部分は、関数に負の引数を与えた場合の処理であり、 逆向きに戻す。 本節では、割愛する。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

先へ進めるwhileループ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-para%20while"
"intro/先へ進めるwhileループ"へのコメント(無し)

let*の本体の2番目の部分は、先へ進める処理を行う。 これは、argの値が0より大きい限り繰り返すwhileループである。 関数のもっとも一般的な使い方では引数の値は1であり、 whileループの本体がちょうど1回だけ評価され、 カーソルを1段落分進める。

この部分では3つの状況を処理する。 ポイントが段落のあいだにある場合、 ポイントが段落の中にあり詰め込み接頭辞がある場合、 ポイントが段落の中にあり詰め込み接頭辞がない場合である。

whileループはつぎのとおりである。

 
(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))
                  (not (looking-at paragraph-separate))
                  (looking-at fill-prefix-regexp))
        (forward-line 1))

    ;; 段落の中で、詰め込み接頭辞なし
    (if (re-search-forward paragraph-start nil t)
        (goto-char (match-beginning 0))
      (goto-char (point-max))))

  (setq arg (1- arg)))

減少式として式(setq (1- arg))を使っているので、 減少カウンタのwhileループであることはすぐにわかる。 ループの本体は3つの式から成る。

 
;; 段落のあいだ
(beginning-of-line)       
(while 
    whileの本体)

;; 段落の中で、詰め込み接頭辞あり
(if 判定条件
    真の場合の動作

;; 段落の中で、詰め込み接頭辞なし
  偽の場合の動作

Emacs Lispインタープリタがwhileループの本体を評価するとき、 最初に行うことは、式(beginning-of-line)を評価して ポイントを行の先頭に移動することである。 続いて、内側のwhileループがくる。 このwhileループは、段落のあいだに空行があれば、 そこからカーソルを移動するためのものである。 最後のif式で、ポイントを段落の末尾に実際に移動する。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

段落のあいだ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-para%20between%20paragraphs"
"intro/段落のあいだ"へのコメント(無し)

まず、内側のwhileループを説明しよう。 このループは、ポイントが段落のあいだにある場合を扱う。 3つの新たな関数、prog1eobplooking-atを使っている。

説明しているwhileループはつぎのとおりである。

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

このwhileループには本体がない!  ループの判定条件はつぎの式である。

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

prog1の第1引数はand式である。 この中では、ポイントがバッファの最後にあるかどうか、 段落を区切る正規表現に一致するものがポイントに続いているかどうか、 を検査する。

カーソルがバッファの最後になくて、 カーソルに続く文字の列が段落を区切るものであれば、and式は真になる。 and式を評価したあと、Lispインタープリタはprog1の第2引数、 forward-lineを評価する。 これは、ポイントを1行分先へ進める。 しかし、prog1が返す値は第1引数の値であるため、 ポイントがバッファの最後になくて、かつ、 段落のあいだにあるかぎり、whileループは繰り返される。 最終的にポイントが段落に達するとand式は偽になる。 しかし、いずれにしてもコマンドforward-lineは実行されることに 注意してほしい。 つまり、段落と段落のあいだでポイントを移動したときには、 段落の第2行目の先頭にポイントが移動するのである。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

段落の中

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-para%20within%20paragraph"
"intro/段落の中"へのコメント(無し)

外側のwhileループのつぎの式はif式である。 変数fill-prefix-regexpnil以外の値を持っている場合には、 Lispインタープリタはifの真の場合の動作を評価し、 fill-prefix-regexpの値がnilの場合、 つまり、詰め込み接頭辞がない場合には偽の場合の動作を評価する。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

詰め込み接頭辞なし

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-para%20no%20fill%20prefix"
"intro/詰め込み接頭辞なし"へのコメント(無し)

詰め込み接頭辞がない場合のコードを見るほうが簡単である。 このコードは、さらに内側にif式を含み、つぎのようになっている。

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

この式は、ほとんどの人がコマンドforward-paragraphの主要な目的で あると考えることを行う。 つぎの段落の先頭をみつけるための正規表現の探索を行い、 みつかればポイントをそこへ移動する。 段落の始まりがみつからなければ、バッファの参照可能なリージョンの最後に ポイントを移動する。

この部分で馴染みがないのはmatch-beginningの使い方であろう。 これもわれわれにとっては新しいものである。 関数match-beginningは、直前の正規表現の探索で一致した テキストの先頭位置を与える数を返す。

関数match-beginningを使うのは、探索の性質のためである。 普通の探索であれ正規表現の探索であれ、 探索に成功するとポイントは探し出したテキストの終わりに移動する。 この場合では、探索に成功すると、 ポイントはparagraph-startに一致したテキストの終わりに移動するが、 これは、今の段落の末尾ではなく、つぎの段落の始まりである。

しかし、つぎの段落の始まりにではなく、今の段落の末尾にポイントを 置きたいのである。 段落のあいだには何行かの空行がありえるので、2つの位置は異なるであろう。

引数に0を指定すると、match-beginningは、直前の正規表現の探索で 一致したテキストの始まりの位置を返す。 この例では、直前の正規表現の探索は、paragraph-startを探したものであり、 match-beginningは、パターンの終わりではなく始まりの位置を返す。 始まりの位置は、段落の末尾である。

(引数に正の数を指定すると、関数match-beginningは、 直前の正規表現の中の括弧表現にポイントを置く。 これは便利な機能である。)



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

詰め込み接頭辞あり

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-para%20with%20fill%20prefix"
"intro/詰め込み接頭辞あり"へのコメント(無し)

説明したばかりの内側のif式は、詰め込み接頭辞の有無を調べる if式の偽の場合の動作である。 詰め込み接頭辞がある場合には、このif式の真の場合の動作が評価される。 それはつぎのとおりである。

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

この式は、つぎの3つの条件が真である限り、ポイントを1行進める。

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

  2. ポイントに続くテキストは段落の区切りではない。

  3. 詰め込み接頭辞の正規表現に一致するパターンがポイントに続けてある。

このまえにある関数forward-paragraphで行の先頭にポイントが移動している ことを思い出さないと、最後の条件に惑わされるかもしれない。 つまり、テキストに詰め込み接頭辞がある場合、 関数looking-atはそれをみつけるのである。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

まとめ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=fwd-para%20summary"
"intro/まとめ"へのコメント(無し)

まとめると、関数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)))

  ポインタをまえへ戻すコード(省略) ...

  (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))  ; 真の場合の動作
                    (not (looking-at paragraph-separate))
                    (looking-at fill-prefix-regexp))
          (forward-line 1))
                                  ; 内側の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がタグテーブルの名前を問い合わせてきたら、 読者のサイトのディレクトリ`emacs/src'のタグファイルの名前を与える。 ディレクトリ`emacs/src'は、 `/usr/local/lib/emacs/19.23/src/TAGS'のようなパス名であろう (ディレクトリ`emacs/src'の正確なパスは、 Emacsをどのようにインストールしたかに依存する。 わからない場合には、C-h iとタイプしてInfoに入り、 C-x C-fとタイプしてディレクトリ`emacs/info'のパスを調べる。 タグファイルは`emacs/src'のパスに対応する場合が多いが、 infoファイルをまったく別の場所に置く場合もある)。

ディレクトリにタグファイルがなくても、 読者専用の`TAGS'ファイルを作成できる。 See 節 Create Your Own `TAGS' File.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12.5 専用タグファイルの作成方法

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=etags"
"intro/専用タグファイルの作成方法"へのコメント(無し)

ソースを調べ廻る際の補助として、個人用のタグファイルを作成できる。 筆者のディレクトリ`~/emacs'には、137個の`.el'ファイルがあり、 そのうちの17個をロードしている。 読者のディレクトリ`~/emacs'にも多数のファイルがある場合、 タグファイルを作っておけば望みの関数にジャンプでき、 grepなどのツールで関数名を探すより簡単である。

タグファイルを作成するには、Emacsのディストリビューションに含まれる プログラムetagsを使う。 普通、Emacsを作るときにetagsもコンパイルされてインストールされる (etagsはEmacs Lispの関数でもEmacsの一部でもない。 Cのプログラムである)。

タグファイルを作るには、タグファイルを作りたいディレクトリにまず移動する。 Emacsの中では、コマンドM-x cdで行うか、 そのディレクトリのファイルを訪問するか、 C-x ddired)でディレクトリを表示する。 続いて、

 
M-! etags *.el

とタイプすれば、タグファイル`TAGS'が作られる。 プログラムetagsでは、普通のシェルの「ワイルドカード」を使える。 たとえば、2つのディレクトリから1つのタグファイル`TAGS'を作るには、 2番目のディレクトリを`../elisp/'とすると、 つぎのようなコマンドを入力する。

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

また

 
M-! etags --help

とタイプすれば、etagsが受け付けるオプションの一覧が表示される。

プログラムetagsは、Emacs Lisp、Common Lisp、Scheme、C、 Fortran、Pascal、LaTeX、ほとんどのアセンブラを扱える。 このプログラムには言語を指定するスイッチはない。 ファイル名とその内容から入力ファイルの言語を認識する。

また、自分でコードを書いているときにすでに書いてある関数を参照するときにも、 `etags'はとても助けになる。 新たに関数を書き加えるごとにetagsを走らせれば、 それらはタグファイル`TAGS'の一部になる。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12.6 復 習

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Regexp%20Review"
"intro/復 習"へのコメント(無し)

説明した関数のうち、いくつかを簡素にまとめておく。

while
第1引数が真である限り、式の本体を繰り返し評価する。 そして、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のように4つの引数を取る。

  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



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

12.7 re-search-forwardの演習問題

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=re-search%20Exercises"
"intro/re-search-forwardの演習問題"へのコメント(無し)


[ << ] [ >> ]           [表紙] [目次] [索引] [検索] [上端 / 下端] [?]