M.Hiroi's Home Page

xyzzy Lisp Programming

[ PrevPage | xyzzy Lisp | NextPage ]

まずは動かしてみよう

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 では複素数を扱うことができます。

-- note --------
[*1] 正確にいうと dotimes は関数ではなくマクロです。dolist や while もマクロですが、ここではわかりやすさを優先しました。

マクロとコンパイラの怪しい関係

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.l52 秒
queen.lc0.7 秒

インタプリタの 52 秒は相当に遅いですね。ところが、コンパイルしてみると 0.7 秒、約 70 倍も高速化しています。ちなみに 0.7 秒は相当に高速です。Perl では 1 秒程度かかります。Perl よりも速いのですから、マクロというよりもコンパイラ自体が優秀なのだと思います。予想を上回る結果にとても驚いています。

このような結果を見ると、Lisp 好きのプログラマとしては、いろいろなプログラムを作りたくなりますね。ますます xyzzy Lisp にはまりそうです。


8クイーンふたたび

●配列を使ってみる

パズル「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.
  1. make-sparse-keymap で空の疎なキーマップを作成する。
    make-keymap は完全なキーマップを作成する。
  2. define-key でキーバインディングを設定する。
    この場合、RET キーを押すと calc-eval-line が評価される。
  3. バッファにキーマップをセットするには use-keymap を使う。
    (use-keymap *calc-mode-map*) とすると、カレントバッファにセットされる。

バッファ内のデータ操作

●テキストの操作

バッファ内のテキストを操作(取得、削除、挿入)する主な関数を表に示します。

表 1 : テキストの取得、削除、挿入を行う関数
関数名機能
buffer-substring start end指定された範囲を文字列として取り出す
delete-region start end指定された範囲を削除する
insert &rest string-or-charカレントバッファのポイント位置に文字列や文字を挿入する

●ポイントの操作

ポイント(カーソル)を操作する関数を表に示します。

表 2 : ポイントの操作
関数名機能
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 リファレンスより抜粋)。

表 3 : scan-buffer のキーワード
キーワード機能
:no-dupnon-nilならポイントの次の文字から検索する
:case-foldnon-nilなら大文字小文字を区別しない
:reversenon-nilならポイントからバッファの先頭に向かって検索する
:word-searchnon-nilなら単語単位で検索する
:tailnon-nilならポイントを一致した文字列の次の文字へ移動する
:limit N検索する範囲を位置で指定する(文字数ではない)
:regexpnon-nilなら正規表現

●検索結果の取得

一致した文字列を取り出すには、次の関数を使います。

表 4 : 文字列の取り出し
関数名機能
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"
-- [修正] (2009/12/19) --------
(goto-char 1) ではポインタをバッファの先頭に移動することはできません。修正するとともにお詫び申しあげます。

Lisp プログラムでグループ ( ) を記述する場合は \\( \\) としてください。goto-char でスクラッチバッファの先頭にポインタを移動し、そこから scan-buffer でバッファを検索します。"456" が 2 回出力されていますが、最後は progn の返り値です。


コマンド作成の基礎知識(1)

●interactive の使い方

エディタのコマンドを Lisp で作成する場合、関数本体のトップレベルに interactive を定義します。interactive は特殊形式で、関数が対話的に呼び出せることを表し、関数に与える引数をミニバッファで指定することができます。

interacrive arg-descriptor

arg-descriptor はコマンドに与える引数のタイプを宣言します。引数が不用の場合は省略します。arg-descriptor が文字列の場合は、最初の 1 文字で引数のタイプを指定します。

表 5 : interactive で使用する主なコード文字(リファレンスより抜粋)
文字引数のタイプ
*カレントバッファが読み出し専用だとエラーを通知「スペシャル」
b既に存在するバッファの名前
Bバッファーの名前(存在しなくてもよい)
f既に存在するファイルの名前
Fファイルの名前(存在しなくてもよい)
n数字(整数)
N数字。コマンドがプレフィックス付きで起動された場合はそれを用いる
p数字(整数)「入出力なし」
P数字。コマンドがプレフィックス付きで起動された場合はそれを用いる「入出力なし」
s文字列
Sシンボル
Dディレクトリ
lファイルの複数選択です。ファイル名のリストが取れます。

「スペシャル」は特別なコードで、文字列の先頭でのみ意味を持ち、その後ろにタイプを指定するコードを指定します。残りの文字列はミニバッファのプロンプトとして表示されます。「入出力なし」は入力をまったく読まずに引数を計算します。プロンプト文字列は無視されます。

●アルファベットの変換

それでは簡単なコマンドを作ってみましょう。機能は、カーソル行の英小文字をすべて英大文字に変換するというものです。すでに upcase-region があるのでわざわざ作ることもないコマンドですが、例題としては手ごろでしょう。

いちばん簡単な方法は、行の先頭と終わりの位置を求めて upcase-region を呼び出すことです。行単位でポイントを移動する主な関数を示します。

表 6 : 行単位の移動
関数名機能
goto-line lineline 行目の先頭に移動
beginning-of-line行の先頭に移動 [xyzzy では goto-bol]
end-of-line行の終わりに移動 [xyzzy では goto-eol]
forward-line &optional countcount 行前方の行頭に移動、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)))))

これでコマンドを実行したあとでもカーソルの位置は元のままです。


コマンド作成の基礎知識(2)

●キーマップ

キーマップは、キー入力やマウスのクリックなど、いわゆるイベントとコマンドの対応を表したデータ構造です。新しく作成したコマンドをキーに割り当てたい場合は、このキーマップに登録します。

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-@ と同じ

このほかにもありますが、詳細はリファレンスの「キー表現使用可能文字」を参照してください。

●コマンドの登録

キーマップにコマンドを登録する関数を示します。

表 7 : コマンドを登録する関数
関数名機能
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 を実行すれば、バッファごとに指定した値で文書を整形することができます。

バッファローカルな変数を操作する主な関数を表に示します。

表 8 : バッファローカルな変数の操作関数
関数名機能
make-local-variable varvar をカレントバッファのローカル変数にする
kill-local-variable varvar をバッファローカルな変数から削除する
kill-all-local-variablesバッファローカルな変数をすべて削除する

削除とは、バッファローカルな変数にしない、という意味です。したがって、通常の変数としての値を参照することができるようになります。主なバッファローカルな変数を表に示します(リファレンスより抜粋)。

表 9 : 主なバッファローカルな変数
変数名概要
make-backup-filesバックアップファイルを作るか否か
buffer-read-onlynil 以外の値だと読み出し専用バッファ
need-not-savenil 以外の値だとセーブしない
mode-nameメジャーモードの名前を保持
lock-fileロックファイルを作るか否か
kept-undo-informationアンドゥ情報を保存するか否か
auto-save自動セーブするか否か

●カレントバッファの操作

カレントバッファを操作する主な関数を表に示します。

表 10 : カレントバッファの操作関数
関数名機能
selected-bufferカレントバッファを返す(Emacs では current-buffer)
set-buffer bufferbuffer をカレントバッファにするが、それを表示するとは限らない

set-buffer は、画面に表示されていないバッファでもカレントバッファにすることができます。しかし、そのバッファは画面には表示されません。ですが、編集コマンドを使ってバッファ内のデータを操作することはできます。

●バッファとファイルの関係

バッファの名前や対応するファイル名を求める関数を表に示します。

表 11 : バッファ名、ファイル名の操作関数
関数名機能
buffer-name bufferバッファの名前を返す
find-buffer name名前が name のバッファを返す(Emacs では get-buffer)
get-buffer-file-name &optional bufferファイル名をフルパスで返す
get-file-buffer filenameファイル filename を表示しているバッファを返す

●バッファの生成

バッファを生成する主な関数を表に示します。

表 12 : バッファを生成する関数
関数名機能
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* に切り替わります。

●バッファの削除

バッファを削除する主な関数を表に示します。

表 13 : バッファを削除する関数
関数名機能
kill-buff bufferバッファを削除する(問い合わせ有)
delete-buffer bufferバッファを削除する(問い合わせ無)

●標準入出力をバッファへ切り替えるマクロ

xyzzy Lisp には標準入出力をバッファへ切り替える便利なマクロが用意されています。

表 14 : 標準入出力を切り替えるマクロ
マクロ名機能
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 は一切の責任を負いません。テストは十分にしたつもりですが、実際に使用する場合には十分に注意してください。


Copyright (C) 2000-2003 Makoto Hiroi
All rights reserved.

[ PrevPage | xyzzy Lisp | NextPage ]