| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
本章では、GNU Emacsで使われている数個の関数を詳しく調べよう。 つまり「ウォークスルー」をしてみよう。 これらの関数はLispコードの例題として取り上げたが、仮想的な例題ではない。 最初の簡略した関数定義を除いて、GNU Emacsで実際に使っているコードである。 これらの定義から多くのことを学べるはずである。 ここで説明する関数は、すべて、バッファに関連するものである。 それ以外の関数についてはのちほど説明する。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
このウォークスルーでは、個々の新しい関数を、あるときは詳しく、 あるときはその概要を説明する。 Emacs Lisp関数に興味を持ったときには、 C-h fに続けて関数名(とRET)を入力すれば、 いつでもその関数の説明文を得られる。 同様に、変数の説明文が必要な場合には、C-h vに続けて 変数名(とRET)を入力すればよい。
また、関数のソースファイルを見るには、関数find-tagsを使う。 M-.(つまり、METAと同時にピリオドキーを押すか、 ESCキーに続けてピリオドキー)をタイプし、 mark-whole-bufferのようなソースコードを見たい関数の名前を プロンプトに対して入力し、RETをタイプする。 Emacsはソースコードのバッファに切り替えて画面に表示する。 もとのバッファに戻るにはC-x b RETとタイプする。
読者のEmacsの初期設定状態に依存して、 `TAGS'と呼ばれる「タグテーブル(tags table)」を指定する必要もあろう。 読者が使用するのはディレクトリ`emacs/src'であろうから、 コマンドM-x visit-tags-tableを使って、 `/usr/local/lib/emacs/19.23/src/TAGS'のようなパス名を指定する。 読者独自のタグテーブルの作成方法については、 12.5 専用タグファイルの作成方法や See 節 `Tag Tables' in
Emacs Lispに慣れてくると、ソースコードを読む際にはfind-tagsを多用する ようになり、独自のタグテーブルを作成するようになるであろう。
Lispコードを収めたファイルのことをライブラリ(libraries)と呼ぶ。 この用法は、法律図書や技術図書のような特化した図書(ライブラリ)からきている。 各ライブラリ、つまり、ファイルには、 ある特定の目的や動作に関連する関数群を収める。 たとえば、`abbrev.el'は省略入力を、 `help.el'はオンラインヘルプを扱うものである (ある1つの目的のために複数のライブラリがある場合もある。 たとえば、`rmail...'のファイル群には、 電子メールを読むためのコードが収めてある)。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
beginning-of-bufferの簡略した定義beginning-of-bufferの簡略した定義"へのコメント(無し)
コマンドbeginning-of-bufferには十分慣れていて理解しやすいであろうから、 この関数から始めよう。 対話的関数として使うと、beginning-of-bufferは、 バッファの先頭にカーソルを移動し、それまでカーソルがあった位置にマークを 設定する。 一般にはM-<にバインドしてある。
本節では、もっとも多く使われる形式に簡略した関数を説明する。 簡略版の関数は正しく動作するが、複雑なオプションを処理するコードは含まない。 別の節で、完全な関数を説明する (See 節 5.3 beginning-of-bufferの完全な定義)。
コードを調べるまえに、関数定義には何が含まれるかを考えてみよう。 M-x beginning-of-bufferやM-<のようなキー列で関数を呼べるように、 関数を対話的にする式を含んでいる必要がある。 バッファのもとの位置にマークを設定するコードが必要である。 バッファの先頭にカーソルを移動するコードも必要である。
では、簡略版の関数のコード全体を示そう。
(defun simplified-beginning-of-buffer () "Move point to the beginning of the buffer; leave mark at previous position." (interactive) (push-mark) (goto-char (point-min))) |
すべての関数定義と同様に、この定義でもスペシャルフォームdefunに 続けて5つの部分がある。
simplified-beginning-of-bufferである。
()である。
この関数定義では、引数リストは空である。 つまり、この関数は引数を必要としない (関数の完全な定義では、省略可能な引数を取る)。
interactive式は、関数が対話的に使われることをEmacsに伝える。 simplified-beginning-of-bufferは引数を必要としないので、 interactiveに引数はない。
関数の本体はつぎの2行である。
(push-mark) (goto-char (point-min)) |
最初の式は(push-mark)である。 Lispインタープリタがこの式を評価すると、 カーソルがどこにあってもその現在位置にマークを設定する。 また、このマークの位置はマークリングに保存される。
つぎの行は(goto-char (point-min))である。 この式はバッファで取りえるポイントの最小位置にカーソルを移動する。 つまり、カーソルの先頭(あるいは、ナロイングしている場合には、 バッファの参照可能な範囲の先頭。 See 節 6. ナロイングとワイドニング)に移動する。
式(goto-char (point-min))でカーソルをバッファの先頭に移動するまえに、 コマンドpush-markでカーソルの位置にマークを設定する。 そのため、必要ならば、C-x C-xとタイプすればもとの位置に戻れる。
以上が関数定義のすべてである。
goto-charのような知らない関数に出会ったときには、 コマンドdescribe-functionを使えば、何をする関数かを調べることができる。 このコマンドを使うには、C-h fに続けて関数名を入力してから RETを押す。 コマンドdescribe-functionは、関数の説明文字列を ウィンドウ`*Help*'に表示する。 たとえば、goto-charの説明文はつぎのとおりである。
One arg, a number. Set point to that number. Beginning of buffer is position (point-min), end is (point-max). |
(describe-functionのプロンプトには、 カーソルのまえにあるシンボルが取り込まれる。 したがって、関数の直後にカーソルを置いてC-h f RETとタイプすれば、 入力量を減らせる。)
end-of-bufferの関数定義は、beginning-of-bufferと同じように 書けるが、関数の本体には(goto-char (point-min))のかわりに (goto-char (point-max))を使う。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
mark-whole-bufferの定義mark-whole-bufferの定義"へのコメント(無し)
関数mark-whole-bufferを理解するのは、 関数simplified-beginning-of-bufferを理解するのと同じくらい容易である。 ここでは、簡略版ではなく完全な関数を見てみよう。
関数beginning-of-bufferほどは多用されないが、 関数mark-whole-bufferも有用である。 関数mark-whole-bufferは、 バッファの先頭にポイントを、バッファの最後にマークを置いて バッファ全体をリージョンとする。 一般にはC-x hにバインドされる。
関数の完全なコードはつぎのとおりである。
(defun mark-whole-buffer () "Put point at beginning and mark at end of buffer." (interactive) (push-mark (point)) (push-mark (point-max)) (goto-char (point-min))) |
他のすべての関数定義と同様に、関数mark-whole-bufferは 関数定義の雛型にあてはまる。 雛型はつぎのとおりである。
(defun 関数名 (引数リスト) "説明文..." (interactive-expression...) 本体...) |
この関数はつぎのように動作する。 関数名はmark-whole-bufferである。 空の引数リスト`()'がこれに続き、関数には引数を必要としないことを意味する。 さらに、説明文が続く。
つぎの行の式(interactive)は、関数が対話的に使われることをEmacsに 指示する。 これらの詳細は、前節で述べた関数simplified-beginning-of-bufferと 同様である。
4.3.1 mark-whole-bufferの本体Only three lines of code.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
mark-whole-bufferの本体mark-whole-bufferの本体"へのコメント(無し)
関数mark-whole-bufferの本体は、つぎの3行である。
(push-mark (point)) (push-mark (point-max)) (goto-char (point-min)) |
最初の行は、式(push-mark (point))である。
この行は、関数simplified-beginning-of-bufferの本体では (push-mark)と書かれた行とまったく同じことを行う。 いずれの場合も、Lispインタープリタはカーソルの現在位置にマークを設定する。
なぜ、mark-whole-bufferでは(push-mark (point))と書き、 beginning-of-bufferでは(push-mark)と書いたのかはわからない。 たぶん、コードを書いた人が、push-markの引数を省略でき、 引数がない場合にはpush-markはポイント位置に自動的にマークを 設定することを知らなかったのではないかと想像する。 あるいは、つぎの行と同じような構造にしたかったのであろう。 いずれにしても、この行により、Emacsはポイントの位置を調べて、 そこにマークを設定する。
mark-whole-bufferのつぎの行は(push-mark (point-max))である。 この式は、バッファで取り得るポイントの最大値の位置にマークを設定する。 これはバッファの最後である(バッファをナロイングしている場合には、 バッファの参照可能な部分の最後である。 ナロイングについてより詳しくは See 節 6. ナロイングとワイドニング)。 このマークを設定すると、ポイントに設定されていた直前のマークはなくなるが、 Emacsはその位置を最近の他のマークと同様に記録しておく。 つまり、必要ならばC-u C-SPCを2回タイプすれば その位置に戻れるのである。
関数の最後の行は、(goto-char (point-min)))である。 これはbeginning-of-bufferのときとまったく同じに書いてある。 この式では、カーソルをバッファの最小ポイント つまり、バッファの先頭(あるいは、バッファの参照可能な部分の先頭)に移動する。 この結果、バッファの先頭にポイントがあり、バッファの最後にマークが設定される。 したがって、バッファ全体がリージョンとなる。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
append-to-bufferの定義append-to-bufferの定義"へのコメント(無し)
コマンドappend-to-bufferは、コマンドmark-whole-bufferと 同様に単純である。 このコマンドは、カレントバッファのリージョン(つまり、バッファのポイントと マークのあいだの部分)を指定したバッファにコピーする。
コマンドappend-to-bufferは、関数insert-buffer-substringを 用いてリージョンをコピーする。 insert-buffer-substringの名前からわかるように、 バッファのある部分を構成する文字列、つまり、「部分文字列」を 別のバッファに挿入する。 append-to-bufferの大部分は、insert-buffer-substringが動作する ように条件を設定することである。 つまり、テキストを受け取るバッファとコピーすべきリージョンを 設定することである。 つぎは、関数の完全なテキストである。
(defun append-to-buffer (buffer start end)
"Append to specified buffer the text of the region.
It is inserted into that buffer before its point.
When calling from a program, give three arguments:
a buffer or the name of one, and two character numbers
specifying the portion of the current buffer to be copied."
(interactive "BAppend to buffer: \nr")
(let ((oldbuf (current-buffer)))
(save-excursion
(set-buffer (get-buffer-create buffer))
(insert-buffer-substring oldbuf start end))))
|
この関数は、雛型を埋めたものと考えれば理解できるであろう。
もっとも外側の雛型は関数定義である。 ここでは、(いくつかの部分を埋めると)つぎのとおりである。
(defun append-to-buffer (buffer start end) "説明文..." (interactive "BAppend to buffer: \nr") 本体...) |
関数の最初の行には、関数名と3つの引数がある。 引数は、テキストのコピーを受け取るバッファbuffer、 カレントバッファのコピーすべきリージョンの 始めstartと最後endである。
関数のつぎの部分は説明文であるが、これは明白であろう。
4.4.1 append-to-bufferのinteractive式A two part interactive expression. 4.4.2 append-to-bufferの本体Incorporates a letexpression.4.4.3 append-to-bufferのsave-excursionHow the save-excursionworks.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
append-to-bufferのinteractive式append-to-bufferのinteractive式"へのコメント(無し)
関数append-to-bufferは対話的に使われるので、 関数にはinteractive式が必要である (interactiveの復習は、 3.3 関数を対話的にするを参照)。 この式はつぎのように読める。
(interactive "BAppend to buffer: \nr") |
この式では、二重引用符のあいだに引数が、`\n'で区切られて2つある。
最初の部分は`BAppend to buffer: 'である。 この`B'は、関数にバッファ名を渡すことをEmacsに指示する。 Emacsは、`B'に続く文字列`Append to buffer: 'をミニバッファに 表示してユーザーに名前を問い合わせる。 そして、Emacsは、指定されたバッファを 関数の引数リストの変数bufferに束縛する。
改行`\n'は引数の最初の部分と2番目の部分を区切る。 `\n'に続く`r'は、関数の引数リストのシンボル bufferに続く2つの引数(つまり、startとend)に ポイントとマークの値を束縛するようにEmacsに指示する。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
append-to-bufferの本体append-to-bufferの本体"へのコメント(無し)
関数append-to-bufferの本体はletで始まる。
すでに説明したように(see 節 3.6 let)、let式の目的は、 letの本体の内側のみで使う変数を作り初期値を設定することである。 つまり、let式の外側で同じ名前の変数があっても、 それらとは混乱しないようにする。
let式の概略を含んだappend-to-bufferの雛型を示せば、 関数定義全体にどのようにlet式をあてはめるかがわかるであろう。
(defun append-to-buffer (buffer start end)
"説明文..."
(interactive "BAppend to buffer: \nr")
(let ((変数 値))
本体...)
|
let式には3つの要素がある。
let。
(variable value)。
let式の本体。関数append-to-bufferでは、変数リストはつぎのとおりである。
(oldbuf (current-buffer)) |
let式のこの部分では、式(current-buffer)が 返す値を変数oldbufに束縛する。 変数oldbufには、どのバッファを使用していたかを記録する。
Lispインタープリタが変数リストとlet式の本体とを区別できるように、 変数リストの要素を括弧で囲む。 そのため、変数リストの2要素リストを括弧で囲むのである。 したがって、つぎのようになる。
(let ((oldbuf (current-buffer))) ... ) |
oldbufのまえにある2つの括弧のうち、 最初の括弧は変数リストの区切りを表し、 2番目の括弧は2要素リスト(oldbuf (current-buffer))の始まりを表す ことに注意してほしい。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
append-to-bufferのsave-excursion
append-to-bufferのsave-excursion"へのコメント(無し)
append-to-bufferのlet式の本体は、 save-excursion式から成っている。
関数save-excursionは、ポイントとマークの位置を記録し、 save-excursionの本体の式の実行を完了すると これらの位置を復元する。 さらに、save-excursionはもとのバッファも記録しておき復元する。 append-to-bufferではこのようにsave-excursionを使う。
複数行にまたがるリストは、最初のシンボル以外は最初のシンボルよりも 字下げして書き、Lisp関数もこのように書く。 この関数定義では、つぎに示すように、letはdefunよりも字下げし、 save-excursionはletよりも字下げする。
(defun ...
...
...
(let...
(save-excursion
...
|
このような書き方をすると、save-excursionの本体の2行が save-excursionの括弧に囲まれていること、 また、save-excursion自体もletの括弧に囲まれていること がわかりやすくなる。
(let ((oldbuf (current-buffer)))
(save-excursion
(set-buffer (get-buffer-create buffer))
(insert-buffer-substring oldbuf start end))))
|
関数save-excursionの使い方は、雛型の項目を埋めていくと考えればよい。
(save-excursion 本体の最初の式 本体の2番目の式 ... 本体の最後の式) |
この関数では、save-excursionの本体には2つの式があるだけである。 本体はつぎのとおりである。
(set-buffer (get-buffer-create buffer)) (insert-buffer-substring oldbuf start end) |
関数append-to-bufferを評価すると、 save-excursionの本体の2つの式が順番に評価される。 最後の式の値が関数save-excursionの値として返される。 これ以外の式は、副作用を起こすためだけに評価される。
save-excursionの本体の最初の行では、 関数set-bufferを用いてカレントバッファをappend-to-bufferの 第1引数で指定したものに切り替える (バッファを切り替えることは副作用である。 まえにも述べたように、Lispでは副作用が主要な目的である)。 2番目の行で、関数の主要な処理を行う。
関数set-bufferは、Emacsの注意をテキストをコピーする先のバッファに向け、 save-excursionでもとに戻す。 この行はつぎのとおりである。
(set-buffer (get-buffer-create buffer)) |
このリストのもっとも内側の式は(get-buffer-create buffer)である。 この式では関数get-buffer-createを使うが、 この関数は指定した名前のバッファを取得するか、 そのようなバッファが存在しなければその名前でバッファを作成する。 つまり、append-to-bufferを使えば、 既存でないバッファにもテキストをコピーできる。
get-buffer-createにより、set-bufferで不必要なエラーが 発生することを避けている。 set-bufferには、切り替え先のバッファが必要である。 存在しないバッファを指定すると、Emacsは失敗する。 get-buffer-createは、バッファが存在しなければバッファを作成するので、 set-bufferにはつねにバッファが与えられる。
append-to-bufferの最後の行が、テキストの追加操作を行う。
(insert-buffer-substring oldbuf start end) |
関数insert-buffer-substringは、第1引数で指定したバッファから カレントバッファに文字列をコピーする。 ここでは、insert-buffer-substringへの引数は、 letで作成し束縛した変数oldbufの値であり、 その値は、コマンドappend-to-bufferを 実行したときのカレントバッファである。
insert-buffer-substringの処理が終了すると、 save-excursionはもとのバッファに戻し、 append-to-bufferが終了する。
本体の動作の概要はつぎのとおりである。
(let ( |
まとめると、append-to-bufferはつぎのように動作する。 変数oldbufにカレントバッファを記録する。 必要ならば作成して新しいバッファを取得し、そのバッファへ切り替える。 oldbufの値を用いて、もとのバッファのリージョンのテキストを 新しいバッファに挿入する。 save-excursionを使っているので、もとのバッファに戻る。
append-to-bufferを調べることで、ある程度複雑な関数について 知ることができた。 letとsave-excursionの使い方や、 バッファを切り替えたあとで、もとのバッファに戻す方法がわかったと思う。 多くの関数で、let、save-excursion、set-buffer をこのように使う。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
本章で説明したさまざまな関数の概要をまとめておく。
describe-functiondescribe-variablefind-tagsave-excursionsave-excursionの引数を評価し終えたら、 もとの値に戻す。 カレントバッファも記録しておき、終了後にもとに戻す。
push-markgoto-char(point-min)のような位置を返す式のいずれかである。
insert-buffer-substringmark-whole-bufferset-bufferget-buffer-createget-bufferget-bufferは、指定した名前のバッファが 存在しなければnilを返す。| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
simplified-end-of-bufferの関数定義を書き、動作を確認せよ。
ifとget-bufferを用いて書け。
find-tagを用いて、関数copy-to-bufferのソースを探してみよ。| [ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |