GNU Emacs 32.0.50 (9008bf64c3) のマニュアルを PLaMo 翻訳で機械翻訳したものです。誤訳を含むことがあります。原文: https://www.gnu.org/software/emacs/manual/html_node/eintr/fwd_002dpara-while.html ・ emacs-doc.conao3.com

前進動作 while ループ

let*の本体の後半部分は、前方への移動を処理します。これはargの値が0より大きい限り繰り返されるwhileループです。この関数の最も一般的な用法では、引数の値は1となるため、whileループの本体はちょうど1回だけ評価され、カーソルは1段落分前方に移動します。

この部分では、pointが段落間にある場合、fillプレフィックスがある場合、およびfillプレフィックスがない場合の3つのケースを処理します。

while ループは次のようになります。

;; going forwards and not at the end of the buffer
(while (and (> arg 0) (not (eobp)))

  ;; between paragraphs
  ;; Move forward over separator lines...
  (while (and (not (eobp))
              (progn (move-to-left-margin) (not (eobp)))
              (looking-at parsep))
    (forward-line 1))
  ;;  This decrements the loop
  (unless (eobp) (setq arg (1- arg)))
  ;; ... and one more line.
  (forward-line 1)

  (if fill-prefix-regexp
      ;; There is a fill prefix; it overrides parstart;
      ;; we go forward line by line
      (while (and (not (eobp))
                  (progn (move-to-left-margin) (not (eobp)))
                  (not (looking-at parsep))
                  (looking-at fill-prefix-regexp))
        (forward-line 1))

    ;; There is no fill prefix;
    ;; we go forward character by character
    (while (and (re-search-forward sp-parstart nil 1)
                (progn (setq start (match-beginning 0))
                       (goto-char start)
                       (not (eobp)))
                (progn (move-to-left-margin)
                       (not (looking-at parsep)))
                (or (not (looking-at parstart))
                    (and use-hard-newlines
                         (not (get-text-property (1- start) 'hard)))))
      (forward-char 1))

    ;; and if there is no fill prefix and if we are not at the end,
    ;;     go to whatever was found in the regular expression search
    ;;     for sp-parstart
    (if (< (point) (point-max))
        (goto-char start))))

これは、式 (setq arg (1- arg)) をデクリメント値として用いる、デクリメントカウンタ while ループであることがわかります。この式は while からそれほど離れていませんが、別の Lisp マクロである unless マクロの中に隠されています。バッファの末尾に達していない限り――これは eobp 関数によって判定されます。この関数は ‘End Of Buffer P’ の略記であり、arg の値を 1 減らします。

(バッファの末尾に達した場合、それ以上先に進むことはできず、while 式の次のループでは、判定が and と (not (eobp)) の組み合わせとなるため、false と判定されます。not 関数は期待通りの動作をします。これは null の別名であり、引数が false のときに true を返す関数です。)

興味深いことに、バッファの末尾に達するか、あるいは段落区切りのローカル値が見えなくなるまで、段落間のスペースを抜けるまでループ回数は減算されません。

その2番目の while にも (move-to-left-margin) 式があります。関数の内容は一目瞭然です。これは progn 式の中にあり、その本体の最後の要素ではないため、ポイントを現在の行の左マージンに移動させるという副作用のためだけに呼び出されます。

looking-at 関数もその名の通りで、ポイント以降のテキストが引数として与えられた正規表現に一致すれば true を返します。

ループの残りの部分は、最初は難しく見えますが、理解が進むにつれて納得がいくようになります。

まず、フィルプレフィックスがある場合にどうなるかを考えてみましょう:

  (if fill-prefix-regexp
      ;; There is a fill prefix; it overrides parstart;
      ;; we go forward line by line
      (while (and (not (eobp))
                  (progn (move-to-left-margin) (not (eobp)))
                  (not (looking-at parsep))
                  (looking-at fill-prefix-regexp))
        (forward-line 1))

この式は、4つの条件が満たされている限り、点を1行ずつ前方に移動させます:

  1. ポインタがバッファの末尾にありません。
  2. テキストの左マージンに移動でき、バッファの末尾にいるわけではありません。
  3. ドット以降のテキストは段落を区切りません。
  4. パターンの次の要素は、塗りつぶしプレフィックスの正規表現です。

最後の条件は一見不可解に思えるかもしれませんが、forward-paragraph 関数の序盤でそのポイントが行頭に移動されたことを思い出せば納得がいくでしょう。つまり、テキストに塗りつぶしプレフィックスがある場合、looking-at 関数がそれを認識することになります。

フィルプレフィックスがない場合に何が起こるかを考えてみましょう。

    (while (and (re-search-forward sp-parstart nil 1)
                (progn (setq start (match-beginning 0))
                       (goto-char start)
                       (not (eobp)))
                (progn (move-to-left-margin)
                       (not (looking-at parsep)))
                (or (not (looking-at parstart))
                    (and use-hard-newlines
                         (not (get-text-property (1- start) 'hard)))))
      (forward-char 1))

This while loop has us searching forward for sp-parstart, which is the combination of possible whitespace with the local value of the start of a paragraph or of a paragraph separator. (The latter two are within an expression starting \(?: so that they are not referenced by the match-beginning function.)

二つの表現、

(setq start (match-beginning 0))
(goto-char start)

正規表現検索にマッチしたテキストの先頭に移動します。

(match-beginning 0) 式は新しく導入されました。これは、直前の検索でマッチしたテキストの開始位置を示す数値を返します。

ここで match-beginning 関数が使われているのは、前方検索の特性によるものです。前方検索が成功した場合、それが単純な検索であれ正規表現による検索であれ、ポイントは見つかったテキストの末尾に移動します。この場合、検索が成功すると、ポイントは sp-parstart のパターンの末尾に移動します。

しかし、point は別の場所ではなく、現在の段落の末尾に配置したいと考えています。実際、検索に段落区切りが含まれる可能性があるため、match-beginning を含む式を使用しない限り、point が次の段落の先頭に配置されてしまう恐れがあります。

引数に 0 を指定すると、match-beginning は直近の検索でマッチしたテキストの開始位置を返します。この場合、直近の検索では sp-parstart が検索対象となります。(match-beginning 0) 式は、そのパターンの終了位置ではなく、開始位置を返します。

(ちなみに、正の数を引数として渡された場合、match-beginning 関数は、その括弧付き式が \(?: で始まっていない限り、直前の検索におけるその括弧付き式内の点の位置を返します。引数が 0 であるのに、なぜここに \(?: が現れるのかは分かりません。)

フィルプレフィックスがない場合の最後の式は

(if (< (point) (point-max))
    (goto-char start))))

(なお、このコードスニペットは元のコードからそのままコピーしたものであるため、末尾に追加された2つの括弧は、直前の if と while に対応しています。)

これは、fillプレフィックスがなく、かつ末尾ではない場合、pointを正規表現検索 sp-parstart で見つかった箇所の先頭に移動させるというものです。

forward-paragraph関数の完全な定義には、前進用のコードだけでなく、後退用のコードも含まれています。

GNU Emacsでこれを読み、関数の全文を確認したい場合は、C-h f (describe-function) と関数名を入力してください。これにより、関数のドキュメントと、その関数のソースが含まれているライブラリ名が表示されます。ライブラリ名にポイントを置き、RET キーを押すと、直接ソースへ移動します。(必ずソースをインストールしておいてください!ソースがなければ、目をつぶって車を運転しようとするようなものです!)