| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
キルリングはリストであるが、 関数rotate-yank-pointerの働きでリングに変換されている。 コマンドyankとyank-popは、 関数rotate-yank-pointerを使っている。 本付録では、関数rotate-yank-pointerとともに、 コマンドyankとyank-popを説明する。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
rotate-yank-pointer
rotate-yank-pointer"へのコメント(無し)
関数rotate-yank-pointerは、kill-ring-yank-pointerが指す キルリングの要素を変更する。 たとえば、kill-ring-yank-pointerが第2要素を指していたら 第3要素を指すように変更する。
rotate-yank-pointerのコードをつぎに示す。
(defun rotate-yank-pointer (arg)
"Rotate the yanking point in the kill ring."
(interactive "p")
(let ((length (length kill-ring)))
(if (zerop length)
;; 真の場合の動作
(error "Kill ring is empty")
;; 偽の場合の動作
(setq kill-ring-yank-pointer
(nthcdr (% (+ arg
(- length
(length
kill-ring-yank-pointer)))
length)
kill-ring)))))
|
関数は複雑に見えるが、いつものように、部分部分に分解すれば理解できる。 まず、骨格から見てみよう。
(defun rotate-yank-pointer (arg)
"Rotate the yanking point in the kill ring."
(interactive "p")
(let 変数リスト
本体...)
|
この関数は、1つの引数、argを取る。 短い説明文字列があり、小文字の`p'を指定したinteractiveがある。 これは、前置引数を処理した数を引数として関数に渡すことを意味する。
関数定義の本体はlet式であり、これには変数リストと本体がある。
let式は、この関数内部でのみ使える変数を宣言する。 この変数は、lengthであり、キルリング中の要素数に束縛される。 関数lengthを呼び出してこれを行う (この関数は変数lengthと同じ名前である。 しかし、一方は関数の名称としての使い方であり、 他方は変数の名称としての使い方である。 この2つはまったく異なる。 同様に、英語の話者は、 『I must ship this package immediately.』と 『I must get aboard the ship immediately.』との`ship'の意味を 区別できる)。
関数lengthは、リスト内の要素の個数を返すので、 (length kill-ring)は、キルリングにある要素の個数を返す。
B.1.1 rotate-yank-pointerの本体The Body of rotate-yank-pointer.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
rotate-yank-pointerの本体rotate-yank-pointerの本体"へのコメント(無し)
rotate-yank-pointerの本体はlet式であり、 let式の本体はif式である。
if式の目的は、キルリングに何かがあるかどうかを調べることである。 キルリングが空ならば、関数errorが関数の評価を停止し、 エコー領域にメッセージを表示する。 一方、キルリングに何かがあれば、関数の処理を実施する。
if式の判定条件と真の場合の動作をつぎに示す。
(if (zerop length) ; 判定条件
(error "Kill ring is empty") ; 真の場合の動作
...
|
キルリングに何もなければ、その長さは0であるはずなので、 ユーザーにエラーメッセージ`Kill ring is empty'を表示する。 if式では関数zeropを用いるが、これは値が0ならば真を返す。 zeropが真を返したならば、ifの真の場合の動作が評価される。 真の場合の動作は、関数errorで始まるリストである。 これは関数message(see 節 1.8.5 関数message)に似ており、 1行のメッセージをエコー領域に表示する。 しかし、メッセージの表示に加えて、 errorはこれを含んだ関数の評価を停止させる。 この場合は、キルリングの長さが0ならば、 関数の残りの部分は評価されないことを意味する。
(筆者の意見では、この関数の名前に「error」を使うことは、 少なくとも人間にとっては、少々誤解を招く。 よりよい名前は「cancel」であろう。 厳密にいえば、長さが0のリストを指すポインタをつぎの要素を 指すように巡回できないので、コンピュータの視点からは 「error」は正しい用語である。 しかし、キルリングが満杯か空かを調べるだけでも、 人間はこの種のことを試せるものと考える。 これは、探査行為である。)
(人間の視点からは、探査行為や発見行為は、必ずしもエラーではなく、 したがって、たとえコンピュータ内部であっても、「error」と呼ぶべきではない。 Emacsのコードでは、高潔に振舞っている人間がその環境を探査していて エラーを引き起こしたということになる。 これは残念である。 エラーがあった場合にはコンピュータは同じ手順を踏むのであるが、 「cancel」のような用語のほうが、はっきりと意味を表す。)
if式の偽の場合の動作The else-part of the ifexpression.剰余関数 %The remainder, %, function.rotate-yank-pointerにおける%の利用Using %inrotate-yank-pointer.最後の要素を指す Pointing to the last element.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
if式の偽の場合の動作if式の偽の場合の動作"へのコメント(無し)
if式の偽の場合の動作は、キルリングに何かがあるときに kill-ring-yank-pointerの値を設定することに費される。 コードはつぎのとおりである。
(setq kill-ring-yank-pointer
(nthcdr (% (+ arg
(- length
(length kill-ring-yank-pointer)))
length)
kill-ring)))))
|
これには説明が必要であろう。 明らかに、kill-ring-yank-pointerには、 前節で説明した関数nthcdrを使って キルリングのどこかのCDRを設定する(See 節 8.5 copy-region-as-kill)。 これをどうやって行っているのであろう?
コードの詳細を見るまえに、関数rotate-yank-pointerの目的を考えてみよう。
関数rotate-yank-pointerは、 kill-ring-yank-pointerが指すものを変更する。 kill-ring-yank-pointerがリストの先頭要素を指している場合に rotate-yank-pointerを呼び出すと、2番目の要素を指すように変わる。 kill-ring-yank-pointerが2番目の要素を指している場合に rotate-yank-pointerを呼び出すと、3番目の要素を指すように変わる (また、rotate-yank-pointerに1より大きな引数を与えると、 その数だけポインタを進める)。
関数rotate-yank-pointerは、 kill-ring-yank-pointerが指すものを再設定するためにsetqを使う。 kill-ring-yank-pointerがキルリングの第1要素を指している場合、 もっとも簡単な場合であるが、関数rotate-yank-pointerは、 kill-ring-yank-pointerが2番目の要素を指すようにする。 いいかえれば、kill-ring-yank-pointerには、キルリングのCDRと 同じ値が設定される必要がある。
つまり、つぎのような場合、
(setq kill-ring-yank-pointer
("some text" "a different piece of text" "yet more text"))
(setq kill-ring
("some text" "a different piece of text" "yet more text"))
|
コードはつぎのようにする必要がある。
(setq kill-ring-yank-pointer (cdr kill-ring)) |
その結果、kill-ring-yank-pointerはつぎのようになる。
kill-ring-yank-pointer
=> ("a different piece of text" "yet more text"))
|
実際のsetq式では関数nthcdrを使ってこれを行う。
すでに見たように(see 節 7.3 nthcdr)、 関数nthcdrは、リストのCDRを繰り返し取る。 CDRのCDRのCDR ...を取る。
つぎの2つの式は同じ結果になる。
(setq kill-ring-yank-pointer (cdr kill-ring)) (setq kill-ring-yank-pointer (nthcdr 1 kill-ring)) |
しかし、関数rotate-yank-pointerでは、nthcdrの第1引数は、 多くの算術を含んだ複雑に見える式である。
(% (+ arg
(- length
(length kill-ring-yank-pointer)))
length)
|
いつものように、もっとも内側の式から始めて、外側に向かって調べる必要がある。
もっとも内側の式は、(length kill-ring-yank-pointer)である。 これは、kill-ring-yank-pointerの現在の長さを調べる (kill-ring-yank-pointerは、その値がリストである変数の名前である)。
この長さは、つぎの式の内側にある。
(- length (length kill-ring-yank-pointer)) |
この式では、lengthは、関数の始めにあるlet文にて キルリングの長さを設定した変数である (変数の名前をlengthではなくlength-of-kill-ringとすれば、 この関数がより明確になると考える読者がいるかもしれない。 しかし、関数のテキスト全体を見れば短いので、 ここでやっているように関数を小さな部分部分に分解しなければ、 変数をlengthと命名しても邪魔ではない)。
したがって、(- length (length kill-ring-yank-pointer))は、 キルリングの長さとkill-ring-yank-pointerのリストの長さの差を取る。
これが関数rotate-yank-pointerにどのように適合するのかを、 つぎの状況で分析してみよう。 kill-ringと同じでkill-ring-yank-pointerは キルリングの第1要素を指し、 rotate-yank-pointerを引数1で呼び出したとする。
この場合、変数lengthはキルリングの長さであり、 kill-ring-yank-pointerはキルリング全体を指しているので、 変数lengthと式(length kill-ring-yank-pointer)の値は同じである。 そのため、
(- length (length kill-ring-yank-pointer)) |
の値は0になる。 argは1なので、式全体では
(+ arg (- length (length kill-ring-yank-pointer))) |
の値は1になる。
したがって、nthcdrの引数は、つぎの式の結果である。
(% 1 length) |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
%
%"へのコメント(無し)
(% 1 length)を理解するには%を理解する必要がある。 (C-h f % RETとタイプして探した)その説明文によれば、 関数%は、第1引数を第2引数で割ったときの余りを返す。 たとえば、5を2で割った余りは1である (2を2倍して余り1を加えると5になる)。
算術をほとんどしない人には、 小さな数を大きな数で割ることができて余りがあることに驚くかもしれない。 例では、5を2で割った。 逆に、2を5で割った結果はどうなるであろう? 小数を使えば、答えは、2/5、つまり、0.4である。 しかし、整数しか使えない場合には、結果は異なったものになる。 明らかに、5を0倍すればよいだろうが、余りはいくつだろう? 答えを探すには、子どものころから馴染み深い場合分けを考える。
これに対比させると、
などなどである。
したがって、このコードでは、lengthの値は5なので、
(% 1 5) |
を評価した結果は1である (式の直後にカーソルを置いてC-x C-eとタイプして確認した。 もちろん、エコー領域には1と表示される)。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
rotate-yank-pointerにおける%の利用rotate-yank-pointerにおける%の利用"へのコメント(無し)
kill-ring-yank-pointerがキルリングの先頭を指し、 rotate-yank-pointerに渡した引数が1の場合には、 %式は1を返す。
(- length (length kill-ring-yank-pointer))
=> 0
|
したがって、
(+ arg (- length (length kill-ring-yank-pointer)))
=> 1
|
であり、lengthの値に関係なく、
(% (+ arg (- length (length kill-ring-yank-pointer)))
length)
=> 1
|
となる。
この結果、式setq kill-ring-yank-pointerはつぎのように簡約できる。
(setq kill-ring-yank-pointer (nthcdr 1 kill-ring)) |
この動作を理解するのは簡単である。 キルリングの第1要素を指していたものが、kill-ring-yank-pointerは 第2要素を指すように設定される。
明らかに、rotate-yank-pointerに渡す引数が2の場合、 kill-ring-yank-pointerには(nthcdr 2 kill-ring)が設定される。 引数に他の値を指定すると変わる。
同様に、kill-ring-yank-pointerがキルリングの第2要素を 指している状態では、その長さはキルリングの長さより1小さいので、 余りの計算は式(% (+ arg 1) length)をもとに行われる。 つまり、rotate-yank-pointerへ渡す引数が1ならば、 kill-ring-yank-pointerは、キルリングの第2要素から第3要素に移動する。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
最後の疑問は、kill-ring-yank-pointerがキルリングの最後の 要素を指しているとどうなるかである。 rotate-yank-pointerを呼び出すと、 キルリングからは何も取り出せないのであろうか? 答えは否である。 これとは異なる有用なことが起こる。 kill-ring-yank-pointerは、キルリングの先頭を指すように設定される。
これがどのように行われるかを、キルリングの長さを5、 rotate-yank-pointerに渡す引数を1と仮定して、コードを見てみよう。 kill-ring-yank-pointerがキルリングの最後の要素を指していると、 その長さは1である。 コードはつぎのとおりであった。
(% (+ arg (- length (length kill-ring-yank-pointer))) length) |
変数に数値を入れると、式はつぎのようになる。
(% (+ 1 (- 5 1)) 5) |
もっとも内側の式から外側に向かって評価する。 (- 5 1)の値は4、(+ 1 4)の合計は5、 5を5で割った余りは0である。 したがって、rotate-yank-pointerが実行することは
(setq kill-ring-yank-pointer (nthcdr 0 kill-ring)) |
であり、kill-ring-yank-pointerはキルリングの先頭を指すように設定される。
rotate-yank-pointerを連続して呼び出すと、 キルリングの最後に達するまでは、 kill-ring-yank-pointerはキルリングの要素を順番に指し、先頭に戻る。 リストには終わりがないかのように先頭に戻るので、 キルリングをリングとよぶのである (リングとは、終わりがないもの?)。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
yank
yank"へのコメント(無し)
rotate-yank-pointerを理解していれば、関数yankは簡単である。 唯一の巧妙な部分は、rotate-yank-pointerに渡す引数を計算する部分である。
コードはつぎのとおりである。
(defun yank (&optional arg)
"Reinsert the last stretch of killed text.
More precisely, reinsert the stretch of killed text most
recently killed OR yanked.
With just C-U as argument, same but put point in front
(and mark at end). With argument n, reinsert the nth
most recently killed stretch of killed text.
See also the command \\[yank-pop]."
(interactive "*P")
(rotate-yank-pointer (if (listp arg) 0
(if (eq arg '-) -1
(1- arg))))
(push-mark (point))
(insert (car kill-ring-yank-pointer))
(if (consp arg)
(exchange-point-and-mark)))
|
このコードをざっと見ると、最後の数行はすぐにわかりそうである。 マークをプッシュする、つまり、記録する。 kill-ring-yank-pointerが指す最初の要素(CAR)は挿入するものである。 関数に渡された引数がconsならば、ポイントとマークを交換して、 挿入したテキストの最後ではなく先頭にポイントを移動する。 このオプションは説明文で解説してある。 関数自体は、"*P"を指定した対話的なものである。 これは、読み出し専用のバッファでは動作せず、 未処理の前置引数を関数に渡すことを意味する。
引数の渡し方 Pass the argument to rotate-yank-pointer.負の引数を渡す Pass a negative argument.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
yankの難しい部分は、rotate-yank-pointerに渡す引数を決定する ための計算を理解することである。 幸い、一見したほど難しくはない。
一方あるいは両方のif式を評価すると数になり、 その数がrotate-yank-pointerに渡される引数となる。
注釈を付けると、コードはつぎのようになる。
(if (listp arg) ; 判定条件
0 ; 真の場合の動作
(if (eq arg '-) ; 偽の場合の動作、内側のif
-1 ; 内側のifの真の場合の動作
(1- arg)))) ; 内側のifの偽の場合の動作
|
このコードは2つのif式から成り、 一方の偽の場合の動作に他方が含まれている。
最初の、つまり、外側のif式では、yankに渡された引数が リストかどうかを調べる。 奇妙であるが、引数なしでyankが呼ばれると、これは真になる。 省略できる引数に渡される値はnilであり、 (listp nil)を評価すると真を返すからである。 そこで、yankに引数を渡さないと、yankの中の rotate-yank-pointerに渡す引数は0である。 つまり、予想どおりに、ポインタは移動されずに、kill-ring-yank-pointerが 指す先頭要素が挿入される。 同様に、yankへの引数がC-uであると、これはリストとして読まれ、 この場合もrotate-yank-pointerには0が渡される (C-uは、未処理の前置引数である(4)となり、 これは1要素のリストである)。 同時に、関数のうしろの部分で、この引数はconsであることがわかるので、 ポイントを挿入したテキストの始まりに、 マークを挿入したテキストの終わりに移動する (interactiveの引数Pは、省略可能な引数が略されていたり、 C-uだったりした場合にこれらの値を提供するためのものである)。
外側のif式の真の場合の動作では、引数がなかったりC-uであった 場合の処理を行う。 偽の場合の動作では、それ以外の状況を処理する。 偽の場合の動作自身は、別のif式である。
内側のif式では、引数が負かどうかを調べる (METAと-キーを同時に押し下げるか、 ESCキーに続けて-キーを押すとこのようになる)。 この場合には、関数rotate-yank-pointerには、 引数として-1が渡される。 そうするとkill-ring-yank-pointerは逆向きに移動し、望んだ動作となる。
内側のifの判定条件が偽(つまり、引数はマイナス記号ではない)であると、 式の偽の場合の動作が評価される。 これは式(1- arg)である。 2つのif式のために、引数が正の数か(マイナス記号だけではない)負の数の 場合にこの式が評価される。 (1- arg)は、引数から1を引いた値を返す (1-関数は、引数から1を引く)。 つまり、yankへの引数が1ならば、0に減らされ、 予想どおりに、 kill-ring-yank-pointerが指す先頭要素が挿入される。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
最後に、剰余関数%や関数nthcdrに負の引数を与えると どうなるのであろうか?
試してみればわかる。 (% -1 5)を評価すると、負の数が返される。 負の数でnthcdrを呼び出すと、第1引数が0の場合と同じ値を返す。 つぎのコードを評価するとこのことがわかる。
`=>'のまえの式を評価すると`=>'のあとに 示した結果になる。 いつものように、コードの直後にカーソルを置いてC-x C-e (eval-last-sexp)とタイプして行った。 GNU EmacsのInfoで読んでいる場合には、読者自身で試してほしい。
(% -1 5)
=> -1
(setq animals '(cats dogs elephants))
=> (cats dogs elephants)
(nthcdr 1 animals)
=> (dogs elephants)
(nthcdr 0 animals)
=> (cats dogs elephants)
(nthcdr -1 animals)
=> (cats dogs elephants)
|
したがって、yankにマイナス記号や負の数を渡すと、 kill-ring-yank-pointerは先頭に達するまで逆向きに巡回される。 そして、先頭で止まる。 リストの最後に達するとリストの先頭へ戻ってリングを形成するのとは異なり、 先頭で止まる。 これには意味がある。 なるべく最近に切り取ったテキストの断片を戻したいとしばしば思うだろうが、 30回もまえのキルコマンドからテキストを挿入したくはないであろう。 そこで、終わりに向かってはリングである必要があるが、 逆向きで先頭に戻った場合には巡回しない。
マイナス記号付きのどんな数をyankに渡しても、-1と解釈される。 これはプログラムの記述を明らかに簡単にする。 キルリングの先頭へ向けて一度に1よりも大きく戻る必要はないので、 マイナス記号に続く数の大きさを調べるように関数を書くよりもよほど簡単である。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
yank-pop
yank-pop"へのコメント(無し)
yankを理解していれば、関数yank-popは簡単である。 場所を節約するために説明文を省くと、つぎのようになる。
(defun yank-pop (arg)
(interactive "*p")
(if (not (eq last-command 'yank))
(error "Previous command was not a yank"))
(setq this-command 'yank)
(let ((before (< (point) (mark))))
(delete-region (point) (mark))
(rotate-yank-pointer arg)
(set-mark (point))
(insert (car kill-ring-yank-pointer))
(if before (exchange-point-and-mark))))
|
この関数には小文字の`p'を指定したinteractiveがあるので、 前置引数は処理してから関数に渡される。 コマンドはyankの直後でのみ使え、それ以外の場合にはエラーメッセージが送られる。 この検査には、すでに説明した変数last-commandを使っている (See 節 8.5 copy-region-as-kill)。
let節では、ポイントがマークのまえにあるかうしろにあるかに依存して 変数beforeに真か偽を設定し、 ポイントとマークのあいだのリージョンを削除する。 これは直前のyankで挿入したリージョンであり、 この部分のテキストを置き換えるのである。 つぎに、kill-ring-yank-pointerを巡回して、 直前に挿入したテキストを再度挿入しないようにする。 新たに挿入するテキストの先頭にマークを設定し、 kill-ring-yank-pointerが指す先頭要素をポイントに挿入する。 これにより、テキストの終わりにポイントが置かれる。 直前のyankで挿入したテキストの先頭にポイントを置いてあった場合には、 ポイントとマークを入れ換えて、新たに挿入したテキストの先頭に 再度ポイントを置く。 以上である。
| [ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |