xyzzy は Emacs 系エディタと同じく、起動時から存在する *scratch* バッファで Lisp プログラムを入力し、CTRL キーと j キーを同時に押すと(C-j と記述する)、それを実行してくれます。では、さっそく次のプログラムを入力してください。
(defun fact (n) (if (= n 0) 1 (* n (fact (1- n))))) ; ここで C-j を押すこと fact ; fact という関数が定義された
Lisp の場合、; から行末までがコメントになります。コメントを入力する必要はありません。defun は関数を定義し、if は条件分岐を実行する関数です。詳しい説明は Common Lisp 入門 関数定義 と 条件分岐 をお読みください。(1- n) は (- n 1) と同じ意味です。fact は階乗を求める関数です。それでは実行してみましょう。
(fact 10) 3628800 (fact 20) 2432902008176640000 (fact 30) 265252859812191058636308480000000 (fact 40) 815915283247897734345611269596115894272000000000 (fact 50) 30414093201713378043612608166064768844377641568960512000000000000
このように、xyzzy Lisp は多倍長整数をサポートしています。とてもエディタに搭載されている Lisp とは思えませんね。ちなみに、Emacs Lisp はほとんどの処理系で -134217728 から 134217727 まで、つまり 28 bit 長です。
fact は定義の中で自分自身を呼び出しています。これを再帰呼び出しといいます。Lisp では、再帰呼び出しを積極的に活用してプログラミングを行います。再帰呼び出しは、Common Lisp 入門 再帰定義 で詳しく説明しています。
再帰呼び出しを使えば、フィボナッチ数列も簡単に求めることができます。
(defun fibo (n) (if (<= 0 n 1) 1 (+ (fibo (1- n)) (fibo (- n 2))))) fibo (dotimes (x 10) (print (fibo x))) 1 1 2 3 5 8 13 21 34 55 nil
dotimes は繰り返しを行う関数 [*1] で、print はデータを出力する関数です。dotimes のほかによく使う繰り返し関数には dolist や while があります。これらの関数は Common Lisp 入門 繰り返し をお読みください。
関数 fibo は自分自身を 2 回呼び出しています。これを「二重再帰」といいます。この場合、同じ値を何回も求めることになるため、効率はとても悪くなります。私のオンボロマシン (Pentium 166 MHz) では (fibo 20) でさえ数秒かかります。この関数で大きな数を計算させるのはやめましょう。
もうひとつ数値計算の例を示します。xyzzy Lisp では分数(有理数)も扱えます。
(/ 10 3) 10/3 (/ 10 6) 5/3 (* 3/2 3/4) 9/8
うーん、凄いのひとことです。有理数を浮動小数点数に変換するには float を使います。
(float 1/3) 0.3333333
このほかにも、xyzzy Lisp では複素数を扱うことができます。
Common Lisp の場合、関数は 3 種類に分類することができます。
昔の Lisp 処理系では、引数を評価するタイプを EXPR や SUBR型、引数を評価しないタイプを NEXPR や FSUBR 型と呼び、ユーザーが NEXPR 型の関数を定義することができました。Common Lisp の場合、ユーザーが定義できるのは関数とマクロです。特殊形式を定義することはできないので、引数を評価したくない場合はマクロを使うことになります。
マクロを定義するには defmacro を使います。
(defmacro マクロ名 (<仮引数> ...) S式 ...)
defmacro の構文は defun と同じです。マクロの動作は次のようになります。
[S式] ─ 評価 → [新しいS式] ─ 評価 → [マクロの返り値] (マクロ展開) 図 :マクロの動作
S 式を評価することで新しい S 式を組み立てます。この部分がマクロ展開に相当します。そして、その S 式を評価した値がマクロの返り値となります。マクロを定義する場合、バッククオートを使うと簡単です。詳しい説明は Common Lisp 入門 マクロ をお読みください。
マクロの実行は、マクロ展開の分だけ確実に通常の関数よりも遅くなります。だったら、NEXPR 型の関数を定義できるようにした方が実行速度の点で有利なはずです。ところが、Common Lisp では必要最低限の特殊形式を定義し、よく使われる制御構造、たとえば dotimes, dolist, while などもマクロで定義されています。これではインタプリタでの動作が遅くなります。
では、なぜ実行速度が遅くなるのにマクロを使っているのでしょう。それは、Common Lisp がコンパイラの使用を前提とした処理系だからです。プログラムでマクロを呼び出している場所は、コンパイル時にマクロ展開されるため、コンパイル済みのコードにはマクロ呼び出しがなくなってしまうのです。つまり、コンパイル済みのコードは、マクロ展開しない分だけ確実にインタプリタよりも高速に実行することができるのです。Common Lisp はインタプリタの柔軟性とコンパイラの高速性を兼ね備えた処理系なのです。
xyzzy Lisp にはバイトコンパイルの機能があります。したがって、xyzzy Lisp でもプログラムをコンパイルした方が高速に実行できるのです。そこで、パズル「8 クイーン」を解くプログラムを使って、実行速度を比較してみましょう。
8 クイーンはコンピュータに解かせるパズルの中でもとくに有名です。8 行 8 列のチェス盤のマス目に、8 個のクイーンを互いの利き筋が重ならないように配置する問題です。解答例を図 2 に示します。
*-----------------* | Q . . . . . . . | | . . . . Q . . . | | . . . . . . . Q | | . . . . . Q . . | | . . Q . . . . . | | . . . . . . Q . | | . Q . . . . . . | | . . . Q . . . . | *-----------------* 図 2 : 8 クイーンの解答例
解は全部で 92 通りあります。プログラムは次のようになります。
List 1 : 8 クイーンの解法 ; 衝突するか (defun conflict (column line board) (let ((x (1- column))) (dolist (y board) (if (or (= (- column line) (- x y)) (= (+ column line) (+ x y))) (return t)) (decf x)))) ; チェックする (defun check (line board) (cond ((null board) nil) ((conflict (length board) line board) t) (t (check (car board) (cdr board))))) ; 本体 (defun queen (num board) (if num (dolist (x num) (unless (conflict (length board) x board) (queen (remove x num) (cons x board)))) (print board)))
このプログラムは Lisp らしくリストを使っていますが、配列を使った方が高速になるかもしれません。また、解はリストを出力しているだけです。興味のある人は、クイーンの配置を図 2 のように出力するプログラムを作ってみるといいでしょう。
プログラムをコンパイルするときは、コマンド byte-compile-file を使います。ファイル名を queen.l としましょう。M-x byte-compile-file と入力するとファイル名を聞いてくるので、queen.l と入力します。コンパイルが正常に終了すると queen.lc というファイルが出力されます。プログラムのロードはコマンド load-file を使います。
時間の計測には get-internal-real-time を使います。*scratch* で次のプログラムを実行しました。
(progn (setq a (get-internal-real-time)) (queen '(0 1 2 3 4 5 6 7) nil) (print (- (get-internal-real-time) a)))
それでは実行結果を示しましょう。Pentium 166 MHz, Windows 95 という貧弱な環境での評価です。
queen.l | 52 秒 |
queen.lc | 0.7 秒 |
インタプリタの 52 秒は相当に遅いですね。ところが、コンパイルしてみると 0.7 秒、約 70 倍も高速化しています。ちなみに 0.7 秒は相当に高速です。Perl では 1 秒程度かかります。Perl よりも速いのですから、マクロというよりもコンパイラ自体が優秀なのだと思います。予想を上回る結果にとても驚いています。
このような結果を見ると、Lisp 好きのプログラマとしては、いろいろなプログラムを作りたくなりますね。ますます xyzzy Lisp にはまりそうです。
パズル「8 クイーン」の解法プログラムを作りましたが、インタプリタでの動作は相当に遅いものでした。このプログラムでは Lisp らしくリストを使いましたが、今度は配列を使ってみましょう。配列の詳しい説明は Common Lisp 入門 配列 をお読みください。プログラムは次のようになります。
List 2 : 8 クイーンの解法 配列版 (1) ; スペシャル変数定義 (defvar *use_table* (make-array 8)) (defvar *board* (make-array 8)) ; 衝突するか (defun conflict (i n) (dotimes (j n t) (if (or (= (- (aref *board* j) j) (- i n)) (= (+ (aref *board* j) j) (+ i n))) (return)))) ; 8 クイーンの解法 (defun queen (n) (dotimes (i 8) (when (and (not (aref *use_table* i)) (conflict i n)) (setf (aref *board* n) i) (if (>= n 7) (print *board*) (progn (setf (aref *use_table* i) t) (queen (1+ n)) (setf (aref *use_table* i) nil))))))
さっそく実行してみたところ、なんと 83 秒もかかってしまいました。配列にすれば速くなるというものではないんですね。反省です。
原因を考えてみましょう。配列にデータを書き込むのに setf を使っていますが、実はこの setf はマクロなのです。どうやらリストを使ったプログラムよりも、マクロ展開によるオーバーヘッドが増えてしまったようです。とくに *use_table* はフラグとして使っているため、ひんぱんに書き込みが行われます。ここを改良すると少しは速くなりそうです。
そこで、配列ではなく整数値の論理演算を使って、フラグをビットで表すことにします。 Common Lisp には、論理演算を行う関数が用意されています。詳細は Common Lisp 入門 整数の論理演算とビット操作 をお読みください。
フラグを表す変数は used とします。used をグローバル変数にすると、ビットをオフにする処理が必要ですが、引数として関数 queen に渡せば、ビットオフの処理は不用になります。プログラムは次のようになります。
List 3 : 8 クイーンの解法 配列版(2) ; 大域変数定義 (defvar *board* (make-array 8)) ; 衝突するか (defun conflict (i n) (dotimes (j n t) (if (or (= (- (aref *board* j) j) (- i n)) (= (+ (aref *board* j) j) (+ i n))) (return)))) ; 8 クイーンの解法 (defun queen (n used) (dotimes (i 8) (when (and (not (logbitp i used)) (conflict i n)) (setf (aref *board* n) i) (if (>= n 7) (print *board*) (queen (1+ n) (logior used (ash 1 i)))))))
関数 queen で used を操作しています。まず logbitp を使って used をチェックし、再帰呼び出しするところで used のビットをオンにしています。さっそく実行したところ、今度は 41 秒に短縮しました。これはリストを使ったプログラムよりも速いですね。
次に、バイトコンパイルしてみました。結果は 1.4 秒と速くなりましたが、リストを使ったプログラムには負けてしまいました。うーん、やっぱり Lisp はリストが主役ということですね。
エディタのプログラムでよく使われる主なデータ型を説明します。
おなじみのバッファを表すデータです。各バッファにはポイント (point) とマーカー (marker) という位置を表すデータがあります。カレントバッファであれば、ポイントの位置にカーソルが表示されます。位置は 1 から始まる整数で表します。ほとんどの編集コマンドが、カレントバッファのポイント付近の内容を操作するようになっています。また、各バッファにはローカルキーマップやバッファローカルな変数などのデータが関連付けられています。
; カレントバッファを求める (selected-buffer) #<buffer: *scratch*>
マーカーを表すデータ型です。ひとつのバッファで複数のマーカーを設定することができます。
; 空のマーカーを作る (setq m (make-marker)) #<marker: *scratch*: -> ; 位置をセット (set-marker m 1) #<marker: *scratch*: 1> ; マーカーの位置を求める (marker-point m) 1
バッファを表示するための画面をウィンドウ (window) と呼びます。ウィンドウにはバッファがひとつ表示されます。
; ウィンドウの操作 (split-window) ; ウィンドウを 2 分割 (split-window-vertically) ; ウィンドウを縦に 2 分割
ユーザーが入力したキーとコマンドを対応づけるデータです。このデータは、先頭の要素がシンボル keymap のリストで表されています。ローカルキーマップはバッファをカスタマイズするときに使用します。
; xyzzy calc.l からの抜粋 (unless *calc-mode-map* (setq *calc-mode-map* (make-sparse-keymap)) ; 1. (define-key *calc-mode-map* #\RET 'calc-eval-line)) ; 2.
バッファ内のテキストを操作(取得、削除、挿入)する主な関数を表に示します。
関数名 | 機能 |
---|---|
buffer-substring start end | 指定された範囲を文字列として取り出す |
delete-region start end | 指定された範囲を削除する |
insert &rest string-or-char | カレントバッファのポイント位置に文字列や文字を挿入する |
ポイント(カーソル)を操作する関数を表に示します。
関数名 | 機能 |
---|---|
point | ポイントの位置を返す |
point-max | 最大のポイント値 (カレントバッファの大きさ) を返す |
point-min | 最小のポイント値 (通常は 0) を返す |
goto-char 位置 | ポイントを指定した位置に設定 |
backward-char 数 | ポイントを指定した数だけ左へ移動 |
forward-char 数 | ポイントを指定した数だけ右へ移動 |
テキストの検索には scan-buffer を使います。Emacs Lisp では search-forward や search-backward などいくつかありますが、xyzzy Lisp では scan-buffer ひとつにまとめられています。
scan-buffer 検索文字列 &key (表を参照)
検索に成功したら t を、失敗したら nil を返します。検索文字列には、コンパイルした正規表現を渡すことができます。使用できるキーワードを表に示します(xyzzy リファレンスより抜粋)。
キーワード | 機能 |
---|---|
:no-dup | non-nilならポイントの次の文字から検索する |
:case-fold | non-nilなら大文字小文字を区別しない |
:reverse | non-nilならポイントからバッファの先頭に向かって検索する |
:word-search | non-nilなら単語単位で検索する |
:tail | non-nilならポイントを一致した文字列の次の文字へ移動する |
:limit N | 検索する範囲を位置で指定する(文字数ではない) |
:regexp | non-nilなら正規表現 |
一致した文字列を取り出すには、次の関数を使います。
関数名 | 機能 |
---|---|
match-string group | 一致した文字列を取り出す |
match-biginning group | 一致した文字列の開始位置を返す |
match-end group | 一致した文字列の終了位置を返す |
[注] group | カッコ ( ) でまとめたグループの番号 0 が一致した文字列全体を表す |
*scratch* で次のプログラムを実行します。
---- *scratch* の先頭 ---- 123abc456DEF7890 (progn (goto-char 0) ;(goto-char 1)修正 2009/12/19 (scan-buffer "\\([a-z]+\\)\\([0-9]+\\)" :regexp t) (print (match-string 0)) (print (match-string 1)) (print (match-string 2))) "abc456" "abc" "456" "456"
Lisp プログラムでグループ ( ) を記述する場合は \\( \\) としてください。goto-char でスクラッチバッファの先頭にポインタを移動し、そこから scan-buffer でバッファを検索します。"456" が 2 回出力されていますが、最後は progn の返り値です。
エディタのコマンドを Lisp で作成する場合、関数本体のトップレベルに interactive を定義します。interactive は特殊形式で、関数が対話的に呼び出せることを表し、関数に与える引数をミニバッファで指定することができます。
interacrive arg-descriptor
arg-descriptor はコマンドに与える引数のタイプを宣言します。引数が不用の場合は省略します。arg-descriptor が文字列の場合は、最初の 1 文字で引数のタイプを指定します。
文字 | 引数のタイプ |
---|---|
* | カレントバッファが読み出し専用だとエラーを通知「スペシャル」 |
b | 既に存在するバッファの名前 |
B | バッファーの名前(存在しなくてもよい) |
f | 既に存在するファイルの名前 |
F | ファイルの名前(存在しなくてもよい) |
n | 数字(整数) |
N | 数字。コマンドがプレフィックス付きで起動された場合はそれを用いる |
p | 数字(整数)「入出力なし」 |
P | 数字。コマンドがプレフィックス付きで起動された場合はそれを用いる「入出力なし」 |
s | 文字列 |
S | シンボル |
D | ディレクトリ |
l | ファイルの複数選択です。ファイル名のリストが取れます。 |
「スペシャル」は特別なコードで、文字列の先頭でのみ意味を持ち、その後ろにタイプを指定するコードを指定します。残りの文字列はミニバッファのプロンプトとして表示されます。「入出力なし」は入力をまったく読まずに引数を計算します。プロンプト文字列は無視されます。
それでは簡単なコマンドを作ってみましょう。機能は、カーソル行の英小文字をすべて英大文字に変換するというものです。すでに upcase-region があるのでわざわざ作ることもないコマンドですが、例題としては手ごろでしょう。
いちばん簡単な方法は、行の先頭と終わりの位置を求めて upcase-region を呼び出すことです。行単位でポイントを移動する主な関数を示します。
関数名 | 機能 |
---|---|
goto-line line | line 行目の先頭に移動 |
beginning-of-line | 行の先頭に移動 [xyzzy では goto-bol] |
end-of-line | 行の終わりに移動 [xyzzy では goto-eol] |
forward-line &optional count | count 行前方の行頭に移動、count が負の場合は後方に移動 |
goto-bol, goto-eol でポイントを移動させれば point で位置を求めることができます。関数 upcase-region は変換の開始位置と終了位置を与えます。プログラムは次のようになります。
List 4 : 関数 upcase-line (defun upcase-line () (interactive "*p") (upcase-region (progn (goto-bol) (point)) (progn (goto-eol) (point))))
interactive の引数はコマンド upcase-word と同じにしました。ファイル名を sample.l とすると、M-x load-file で sample.l を読み込み、M-x upcase-line で実行します。
ところで、実際に試してみるとカーソルが行の終わりに移動してしまいます。カーソルの位置を動かしたくない場合は save-excursion を使います。
save-excursion S式
エクスカージョン(excursion、周遊)は、プログラムの中で一時的にポイントを移動したり、バッファを切り替えたりすることをいいます。save-excursion は、カレントバッファのポイントやマーカーの値を保存し、S 式を実行したあとで元の値に戻します。プログラムは次のようになります。
List 5 : 関数 upcase-line (改) (defun upcase-line () (interactive "*p") (save-excursion (upcase-region (progn (goto-bol) (point)) (progn (goto-eol) (point)))))
これでコマンドを実行したあとでもカーソルの位置は元のままです。
キーマップは、キー入力やマウスのクリックなど、いわゆるイベントとコマンドの対応を表したデータ構造です。新しく作成したコマンドをキーに割り当てたい場合は、このキーマップに登録します。
Emacs や xyzzy には多数のキーマップがありますが、コマンドを検索するときはその中から複数個のキーマップが使用されます。検索に使用される主なキーマップには次のものがあります。
検索の優先順位は、マイナーモードキーマップ、ローカルキーマップ、グローバルキーマップの順です。
Common Lisp では文字を表すデータを文字型データといい、文字の先頭に #\ をつけて表します。たとえば、#\a は a という文字を表します。xyzzy の場合、キー入力は文字で表します。CTRL, ALT(ESC), SHIFT のファンクションキーは \C-, \M-, \S- で表します。たとえば、CTRL と a を同時に押す (C-a) ことは #\C-a と表し、CTRL と SHIFT と a を同時に押すことは #\C-S-a と表します。ファイラーをオープンするときのように、複数のキーを入力するときはリストで表します。ファイラーのオープン C-c, C-f は (#\C-c #\C-f) となります。
リターンやタブなど特別な文字は、次のように表すことができます。リファレンスからの抜粋です。
SPC スペースキー TAB Tabキー #\C-i と同じ LFD #\C-j と同じ RET Enterキー #\C-m と同じ ESC Escキー #\C-[ と同じ DEL #\C-? と同じ NUL #\C-@ と同じ
このほかにもありますが、詳細はリファレンスの「キー表現使用可能文字」を参照してください。
キーマップにコマンドを登録する関数を示します。
関数名 | 機能 |
---|---|
define-key keymap key コマンド | keymap の key に対するコマンドを設定 |
global-set-key key コマンド | グローバルキーマップ の key に対するコマンドを設定 |
global-unset-key key コマンド | グローバルキーマップ の key に対するコマンドを削除 |
local-set-key key コマンド | 現在のローカルキーマップ の key に対するコマンドを設定 |
local-unset-key key コマンド | 現在のローカルキーマップ の key に対するコマンドを削除 |
それでは、upcase-line をグローバルキーマップに割り当ててみましょう。割り当てるキーは CTRL-C, l にします。upcase-line をロードして、*scratch* で次のプログラムを実行しました。
(global-set-key '(#\C-c #\l) 'upcase-line) t
これで登録は完了です。C-c, l でカーソル行の英小文字を英大文字に変換できます。実際には手動で登録するのではなく、プログラムファイルでキー登録を行えばいいでしょう。そうすれば、プログラムをロードした直後からキーを使うことができます。
バッファは、編集するテキストを収めている Lisp のデータです。xyzzy は複数のバッファを保持することができますが、その中でカレントバッファはただひとつしかありません。多くの編集コマンドではカレントバッファが操作対象となります。
バッファには多くの情報が含まれています。ポイントのように、関数を介してのみ操作できるデータもありますが、変数を介して直接参照できるデータもあります。そのような変数をバッファローカルな変数と呼び、特定のバッファでのみ有効となります。
たとえば、フィルカラムの値を保持している fill-column はバッファローカルな変数です。バッファごとに異なった値を fill-column に設定することができます。たとえば、あるバッファでは fill-column を 70 とし、違うバッファでは 60 と設定すると、fill-column の値を使うコマンド、たとえば fill-paragraph を実行すれば、バッファごとに指定した値で文書を整形することができます。
バッファローカルな変数を操作する主な関数を表に示します。
関数名 | 機能 |
---|---|
make-local-variable var | var をカレントバッファのローカル変数にする |
kill-local-variable var | var をバッファローカルな変数から削除する |
kill-all-local-variables | バッファローカルな変数をすべて削除する |
削除とは、バッファローカルな変数にしない、という意味です。したがって、通常の変数としての値を参照することができるようになります。主なバッファローカルな変数を表に示します(リファレンスより抜粋)。
変数名 | 概要 |
---|---|
make-backup-files | バックアップファイルを作るか否か |
buffer-read-only | nil 以外の値だと読み出し専用バッファ |
need-not-save | nil 以外の値だとセーブしない |
mode-name | メジャーモードの名前を保持 |
lock-file | ロックファイルを作るか否か |
kept-undo-information | アンドゥ情報を保存するか否か |
auto-save | 自動セーブするか否か |
カレントバッファを操作する主な関数を表に示します。
関数名 | 機能 |
---|---|
selected-buffer | カレントバッファを返す(Emacs では current-buffer) |
set-buffer buffer | buffer をカレントバッファにするが、それを表示するとは限らない |
set-buffer は、画面に表示されていないバッファでもカレントバッファにすることができます。しかし、そのバッファは画面には表示されません。ですが、編集コマンドを使ってバッファ内のデータを操作することはできます。
バッファの名前や対応するファイル名を求める関数を表に示します。
関数名 | 機能 |
---|---|
buffer-name buffer | バッファの名前を返す |
find-buffer name | 名前が name のバッファを返す(Emacs では get-buffer) |
get-buffer-file-name &optional buffer | ファイル名をフルパスで返す |
get-file-buffer filename | ファイル filename を表示しているバッファを返す |
バッファを生成する主な関数を表に示します。
関数名 | 機能 |
---|---|
get-buffer-create name | 新しくバッファを作る(既にあればそれを使う) |
switch-to-buffer buffer-or-name | 指定したバッファをカレントバッファにして画面に表示する バッファが存在しなければ新しく作成する |
create-new-buffer name | 新しくバッファを作るが、同名のバッファがある場合は数字をつけて区別する |
switch-to-buffer はよく使用される関数なので、簡単な例を示しましょう。次のコマンドを定義してください。
(defun test-cmd () (interactive) (switch-to-buffer "*test*"))
M-x test-cmd を実行すると、表示されているカレントバッファが *test* に切り替わります。
バッファを削除する主な関数を表に示します。
関数名 | 機能 |
---|---|
kill-buff buffer | バッファを削除する(問い合わせ有) |
delete-buffer buffer | バッファを削除する(問い合わせ無) |
xyzzy Lisp には標準入出力をバッファへ切り替える便利なマクロが用意されています。
マクロ名 | 機能 |
---|---|
with-input-from-selected-buffer | カレントバッファを標準入力とする |
with-output-to-selected-buffer | カレントバッファを標準出力とする |
with-output-to-buffer | 引数で指定したバッファを標準出力とする |
with-output-to-temp-buffer | 引数で指定したバッファを作成し標準出力とする |
それでは簡単な例を示しましょう。*scratch* で Lisp プログラムを実行する場合、標準出力は *scratch* へ書き込まれますが、ほかのバッファの場合では標準出力へ書き出しても、その結果は自分のバッファへ出力されません。この場合は with-out-to-selected-buffer を使いましょう。次のコマンドを入力してください。
(defun test-cmd () (interactive) (switch-to-buffer "*test*") (with-output-to-selected-buffer (print "change *test* buffer")))
このコマンドを実行すると、カレントバッファが *test* に切り替わり、バッファに "change *test* buffer" が表示されます。それから、このままだとバッファを削除するときにセーブするか問い合わせするので、バッファを *test* に切り替えたあとで、バッファローカルな変数 need-not-save に t をセットしておくといいでしょう。
複数のファイルを読み込んでいくと、それだけバッファが作られていきます。そのうちに、不用になったバッファをいっきに削除したくなります。そこで、*scratch* と変更されているバッファを除いて、すべてのバッファを削除するコマンド kill-all-buffer を作りましょう。
xyzzy に存在するバッファは関数 buffer-list で求めることができます。この関数はバッファをリストに格納して返します。バッファ名ではなく、バッファそのものを返すので注意してください。バッファの変更は関数 buffer-modified-p でチェックすることができます。変更されていれば t を、そうでなければ nil を返します。この 2 つの関数を使うと、プログラムは簡単に作ることができます。
List 6 : すべてのバッファを削除する (defun kill-all-buffer () (interactive) (let ((scratch-buff (find-buffer "*scratch*"))) (dolist (buffer (buffer-list)) (if (and (not (eq scratch-buff buffer)) (not (buffer-modified-p buffer))) (delete-buffer buffer)))))
*scratch* は名前で比較するのではなく、バッファ自身を find-buffer で求めて eq で比較しています。あとは dolist を使って、buffer-list が返したバッファをチェックするだけです。
バッファを削除するちょっと危険なコマンドなので、例題にはふさわしくないかもしれません。また、kill-all-buffer は無保証であり、使用したことにより生じた損害について、M.Hiroi は一切の責任を負いません。テストは十分にしたつもりですが、実際に使用する場合には十分に注意してください。