多くの Emacs(-Lisp)
関係の教本の常識を無視して、いきなりここから
始めます。これがもっとも早い道だと信じるからで、事実私もそうしてきました。
多くの Emacs-Lisp
プログラマがで最終的に目指す目標の一つが、「メ
ジャーモードが書けるようになる」ことでしょう。メジャーモードとは、例えば
c-mode
のように対象となるテキストの種別に適した、もっと言うと「専用
の」編集モードの事を指します。
GNU Emacs
では、編集ファイルのファイル名のパターンと、そのファイ
ルを編集する時に用いるメジャーモードを決定するものとして、変数
auto-mode-alist
を使用しています。これは今まで、いろいろなパッケー
ジをインストールしたことのある人なら、設定したことがあるでしょうから、詳し
い構造などについては述べません。
では、早速メジャーモードを書いてみましょう。その前に、メジャーモードの備 えているべき最低限の条件について整理してみましょう。
たったこれだけなのです。
モード名は、変数 major-mode
にシンボルとして入れます。
(setq major-mode 'my-mode)
ついでに、モードラインのモード名フィールドも変えましょう。これは、変数
mode-name
に文字列で設定します。
(setq mode-name "MY mode")
どちらも、今から書こうとするモードの名前を設定します。好きな名前で構いま せん。
「どのキーを押した時に、どの機能を呼び出すか」という対応表のことを
Emacs
ではキーマップと言います。C-n, C-p など、どのモー
ドでもほぼ共通で使えるキーバインドはグローバルマップに、モードに固有のキー
バインドは、ローカルマップに設定します。通常グローバルマップは変数
global-map
を常に使用します。(see section インタラクティブ関数をキーにバインドする)
メジャーモードが固有のキーバインドを使用する場合、独自のローカルマップを
作成し、そのローカルマップを使用する宣言をしなければなりません。ローカルマッ
プを作成するには次のようにします。この例では変数 my-local-map
をロー
カルマップとしています。
(setq my-local-map (make-keymap))
これで、my-local-map
というローカルマップが作成できました。あとは、
このマップに必要なキーバインドを定義して行くだけです。
ローカルマップにキーを割当てるには、関数 define-key
を使用します。
define-key
は引数を三つ取り、順に、キーマップ、割当キー(文字列)、機
能(シンボル)となっています。では vi
のように hjkl に左下上右
を割当ててみましょう。
(define-key my-local-map "h" 'backward-char) (define-key my-local-map "j" 'previous-line) (define-key my-local-map "k" 'next-line) (define-key my-local-map "l" 'forward-char)
では、今 my-local-map
に割当てたバインド有効となるように、ローカ
ルマップの使用宣言をします。
(use-local-map my-local-map)
これで、カレントバッファで hjkl
が有効になります。
メジャーモードを起動する関数を定義する前に インタラクティブ関数について
知っておきましょう。Emacs-Lisp
の関数には、インタラクティブ関数と、
そうでない関数があります。C-n や M-x gnus のようにキーボード操
作で直接呼び出すことができる関数をインタラクティブ関数と言います。それ以外
の関数は、ユーザが直接呼び出すことはできず、様々な処理の下請関数としてだけ
呼ばれます。C-f にバインドされている forward-char
ももちろん
インタラクティブ関数となっています。
メジャーモードの核となる関数は、当然インタラクティブ関数にしなければなり ませんから、関数定義は次のような形になります。
(defun my-mode () (interactive) モード名の設定 キーマップの設定 )
今のところは、「関数定義の先頭におまじない (interactive)
を入れる」
と覚えておいて下さい。(see section 余談 2 関数・変数)
簡単なインタラクティブ関数を定義して、実際にキーに割当ててみましょう。バッ ファに `Hello, world!' とだけ表示する関数は次のようになります。
(defun hello-word () (interactive) (insert "Hello, world!\n"))
すぐに試してみたいので、グローバルマップにキー割当してしまいましょう。
(define-key global-map "\C-ch" 'hello-world)
これで、C-ch を押すとバッファに `hello, world!' が挿入されます。
ここまでで、必要最低限の知識は全て揃いました。次のようなメジャーモードが 作成できるはずです。
my-mode
という名前のメジャーモードである。
実際の関数定義は、次のようになります。
(defun my-mode () (interactive) (setq major-mode 'my-mode mode-name "MY mode") (setq my-local-map (make-keymap)) (define-key my-local-map "h" 'backward-char) (define-key my-local-map "j" 'previous-line) (define-key my-local-map "k" 'next-line) (define-key my-local-map "l" 'forward-char) (define-key my-local-map "\C-ch" 'hello-world) (use-local-map my-local-map)) (defun hello-world () (interactive) (insert "Hello, world!\n"))
問 1-1
a-z どのキーを押しても、「僕るねえもんナリよ」が挿入される「るねきち モード」を作りなさい。
答の提出をもって、参加表明とみなします。よろしゅうに
あっという間に宿題を提出した優秀な人へ追加問題。
問1-2
a-z を押すと、対応するアルファベットで、「僕luneえもんAなりよ」〜 「僕luneえもんZなりよ」と、文字列の入る
luneえもんモード
を作成せよ。
暇な人だけ解いて下さい。
大事なことを書き忘れたかもしれない。面倒なので、これは junk風の書き方で 許して。
Emacs-Lisp
プログラムは、.el
という拡張子のファイルにする。
;-*- Emacs-Lisp -*-
と入れる。
defun
した関数を評価するには、その関数にカーソルを合わせて ESC
C-x する。すると即座に呼び出せる。
@center{--- ここまで Emacs-Lisp mode ---}
Lisp
式(S式と言う)をすぐに実行(評価と言う)するには
*scratch*
バッファを利用する。
*scratch*
バッファで何かS式を書いたら閉じ括弧の右で C-j を押
すとすぐに評価されて結果が *scratch*
バッファに入る。
これだけ知ってれば、あなたも今日から Emacs-Lisp
プログラマ。では
good luck!
関数を探す時に使うと便利なのが、apropos
です。M-x apropos
で出て来るプロンプトで、関数名の正規表現を入力します。何か文字列操作のため
の関数を探したかったら、
M-x apr Apropos: string
などと問い合わせると良いでしょう。正規表現での指定なので、`string' で 始まる関数を調べたかったら、`^string'という検索していも可能です。
ですから、今回の場合は、
M-x apr Apropos: ^....-.......-....$
で、検索すれば良かったということになります。
本章では、Lisp
言語とみた場合の変数の扱いや、制御構造の表現の仕方
について簡単に触れます。
Lisp
では変数は一つのシンボルとして存在しています。シンボルへの値
の代入は set
によって行います。シンボル foo
へ、値5を代入す
るには、
(set 'foo 5)
としますが、一般的にはこれと等価な、
(setq foo 5)
という書式を用います。setq
は値の代入なので、常に変数の値は更新され
ます。
defvar
は setq
とは異なり変数の宣言のみを行います。書式は
(defvar シンボル 初期値 ドキュメンテーション文字列)
となっています。もし第一引数のシンボルが既に存在していた場合はその値は変更
しません。このため、Emacs-Lisp
プログラムで使用するカスタマイズ可能
な変数のデフォルト値の設定をするためによく使われます。
Emacs
では多くの Emacs-Lisp
プログラムが動作するため、シン
ボル名の衝突は確実に回避しなければなりません。関数の内外にかかわらず単純に
setq
や defvar
されたシンボルはすべてグローバル変数になって
しまうので他のプログラムの存在を考えると好ましくありません。そこで変数の有
効範囲(スコープ)を制限するために let
を用います。
(let (変数リスト) 実行部...)
「変数リスト」の部分は「変数名」または「(変数名 初期値)」の任意の個数の並
びです。変数名だけ指定するとその変数の値は nil
にセットされます。次
の例は変数 case-fold-search
を t
にセットしてインクリメンタ
ルサーチを呼び出します。
(let ((case-fold-search t)) (isearch-forward))
case-fold-search
は検索の時に大文字小文字を区別しないというフラグで、
グローバル変数となっています。let
はグローバル変数の値を一時的に変
更するためにも利用できます。
しかしカスタマイズ変数などはグローバルに値を保持する必要があるので、グロー
バル変数はやはり必要です。さらに、関数名はすべてグローバルシンボルとして扱
われます。そこで、グローバルシンボルを使用する時には、「すべてのグローバル
シンボルには作成パッケージ固有の接頭辞をつける」ことが強く勧められています。
例えば `supercite' パッケージで用いるシンボルには全て sc-
とい
う接頭辞がついています。
Emacs-Lisp
で主に用いる制御構造を説明します。
if
は第一引数を評価し、それが nil
でない値(今後
non-nil
)を返した場合第二引数を評価しその値を返し、nil
だった
場合第三引数以降を (もしあれば)評価し、最後の値を返します。
(if CONDITION T-body Else-body...)
もし、条件が non-nil
の時に評価したい関数が複数ある場合は、
progn
を用いて次のようにします。
(if CONDITION (progn T-body...) Else-body...)
progn
は任意個の引数を取り、最後の引数の値を progn
の値とし
て返します。
or
は与えられた引数全てを順に評価し、non-nil
を返すものが
あった場合、それを or
の返す値とします。もしすべての引数を評価した
ものが nil
だった場合、or
は nil
を返します。
ある変数の値が t
の時に動作が禁止される関数などは次のように表現し
ます。
(or foo-key-map (setq foo-key-map (make-key-map)))
また、or
のもつ「または」という意味で、次のように使うこともできます。
(if (or A B) 処理)
and
は与えられた引数を順に評価し、nil
を返すものが見つかっ
たら直ちに nil
を返します。最後の引数まで non-nil
を返した場
合、and
は最後の引数の値を返します。
cond
は次の書式によります。
(cond (式1 式1がnon-nilの時に返す式...) (式2 式2がnon-nilの時に返す式...) : (式n 式nがnon-nilの時に返す式...))
次の例は、変数 var-a
, 変数 var-b
, 関数 func-c
の値
を順次調べて、どれかが non-nil
の時に後続する関数群を評価します。も
し、var-a
, var-b
, func-c
のどれも nil
を返す時
は、最後の条件ブロックの式が t
なので最後のブロックを評価します。
(cond (var-a (message "A!")) (var-b (insert "B!")) ((func-c) (insert "C")) (t (message "NO!") (ding)))
なお、cond
は途中の条件式が non-nil
を返し後続するブロックを
評価したら、残りの条件ブロックは評価せずに抜けてしまいます。 cond
の返す値は、(non-nil
を返した)条件ブロックの最後の式が返す値です。
最初の条件式が non-nil
である間、二番目以降の引数をくり返し評価し
ます。
(while 条件式 実行部...)
これはループを形成する時に用いることができます。C の for
または
while
のような繰り返しを行う時は、通常 let
と組み合わせて次
のように用います。
(let ((i ?a)) (while (<= i ?z) (insert i) (setq i (1+ i))))
上の例は、a〜z をバッファ中に挿入します。この例から想像が付くように、
Emacs-Lisp
では文字コードを `?文字' で表現します。?a
は
a
の文字コードを示すので、97 と等価です。
while
は常に nil
を返します(最後は条件式が nil
となっ
て終了するから)。
catch
と throw
は対にして使います。catch
の書式を見
ると、
(catch タグ 実行部...)
となっていて、「実行部...」のいずれかで「タグ」が throw
される
と直ちに catch
を抜けます。throw
は
(throw タグ 値)
のように使用し、この時の catch
の返す値は第二引数の「値」となります。
もし「実行部...」で「タグ」が throw
されなかった時は「実行部
...」の最後の値が catch
の返す値となります。
この関数のペアは、終了のタイミングの予測できないループを表現する時に使う
と便利です。例えば、バッファ末までに判定関数 foo
を満足する行がある
か調べる場合を考えます。catch
, throw
を使わずに書く場合は次
のようになるでしょう。
(let (found) ;局所フラグ nil 初期値 (while (and (not found)(not eobp)) (if (foo) (setq found t) (forward-line 1))) (if found 見つかった場合の処理 見つからなかった場合の処理))
ループが回る毎にフラグ found
が t
でないことを調べているので、
少々無駄な気がします。これを catch
, throw
を使って書き直すと、
(if (catch 'found (while (not eobp) (if (foo) (throw 'found t) (forward-line 1)))) 見つかった場合の処理 見つからなかった場合の処理)
となります。もし (foo)
が non-nil
を返す行があった場合はルー
プ内部で 'found
が throw
されるので、catch
関数の値は
'found
となり最も外側の if
は見つかった場合の処理を評価しま
す。逆に、 foo
を満たす行が見つからなかった場合、内部の
while
が nil
を返して終了するため、外側の if
も、見つ
からなかった場合の処理を評価します。
Emacs-Lisp
で使える演算子には以下のものがあります。
%, *, +, -, /
1+, 1-
<, <=, =, /=, >, >=
Lisp
では *
, +
, -
は複数のオペランドを取ること
ができます。-
は引数が一つの時はその符号を反転し、二つ以上の時は一
つ目の引数から残りの引数全てを引きます。
また次の述語関数も必要でしょう。
numberp(integerp)
t
を返す
独自に定義した関数で受け取った引数が数値かどうか判定する時などに利用しま す。
利用頻度が高いと思われるものだけ紹介します。
max, min
random
t
を与えると乱数の種を変えて値を
返す
問
前問「るねきちモード」の a-z のキーバインドのうち、どれか一つのキー を押すと「自爆」と言ってバッファを消去する機能を付け加えよ。
ヒント: char-to-string, ding, message, erase-buffer
基本的なことですが、変数は
var
と、単体で参照し、関数は
(func args...)
と括弧つきの形で参照します。
defun
の形は次のようになっています。
(defun 関数名 (引数リスト) 関数定義)
引数に何もとらない時は空リストにして
(defun 関数名 () 関数定義)
さて、Emacs-Lisp
の場合関数は必ず値を返します。関数の値は、その関
数中で最後に評価されたものの値となります。例えば次の関数の返り値は5となり
ます。
(defun foo () 5)
したがって次の例では、変数 bar
に値5が返ることとなります。
(setq bar (foo))
関数の返す値は、「最後に評価されたもの」であり「最後に書いてある式の値」で はないので注意して下さい。
(defun baz (arg) (if (< arg 0) (- arg) arg))
という例では、引数 arg
が負の場合 (- arg)
が、正の場合
arg
が baz
の返り値となります。
変数の値を途中で表示させたい時には message
関数を使うと便利です。
message
関数は C の printf
のようなフォーマットが使えます。
例えば、途中で変数 foo
の値を見たい時は
(message "foo = %d" foo)
とします。もし、次の message
などがすぐに出てしまい読み取れない時な
どは、sit-for
関数を使ってn秒間止まらせると良いでしょう。
(message "foo = %d" foo) (sit-for 2) ;2秒間停止 : (message "bar = %d" bar)
defun
した関数を評価する時は、ESC C-x を使うのが良いでしょ
う。この時関数を定義したらその場で ESC C-x をしてしまいましょう。す
るとその間数名は以後どの場所でも、ESC TAB によって補完することが可能
になります(変数名も同様)。
(defun lune-random () なんちゃらかんちゃら) (defun lune-mode () (interactive) (setq key lun
というところで、ESC TAB を押すと、lune-random
が補完されます。
さて出来上がった関数を評価する時は主に二つの方法が考えられます。
*scratch*
バッファに移ってS式を入れて C-j する。
上の lune-random
関数を試したい時は、
Eval: (lune-random) RET
*scratch*
バッファに移動
(lune-random) C-j
(2)の場合、C-j を押す直前のS式が評価されます。したがって、
(setq random (lune-random)) ~~
の、最後の括弧の上で C-j を押すと (lune-random)
だけが評価さ
れ setq
されず、行末で C-j を押すと setq
全体が評価さ
れます。
どんなメジャーモードでも書けるようになるために、必要最低限の関数を一気に 覚えてしまいましょう。ここでとり上げる必要最低限のコマンドは以下のものです。
カーソルの移動コマンドには、相対移動、絶対移動、検索移動があります。いず れもキーに割当てられている機能なので、関数名を知っているものもあるでしょう。
Emacs-Lisp
プログラムではあまり用いることのない関数ですが、各関数
と引数について説明します。
forward-char
, backward-char
どちらも引数を一つ取り、移動桁
数を決定します。前に3つ進みたい時は、(forward-char 3)
のように呼び
出します。もちろんこれらの関数は、C-f, C-b にバインドされてい
ます。
forward-word
, backward-word
も移動単語数を指定する引数を一
つ取ります。M-f, M-b にバインドされています。
行移動というと、C-n, C-p にバインドされている
next-line
, previous-line
を想像すると思いますが、これらの関
数は Emacs-Lisp
プログラム中で用いません。これらの関数には
goal-column
の制御などの機能が含まれるため、プログラム中からはより
単純で信頼性の高い forward-line
を使用します。なお、
backward-line
という関数はないので、上に移動するには負の引数を渡し
ます。
カーソルの絶対移動の基準となる「ポイント」について知っておく必要がありま
す。Emacs
ではカーソルの位置をバッファの先頭からのオフセットで管理
しています。このオフセット値を返す関数が (point)
です。これに関連し
て、バッファの先頭は (point-min)
、末尾は (point-max)
で得る
ことができます。
指定するポイント位置に移動する関数が goto-char
です。引数を一つ取
り、移動先のポイントを受け取ります。バッファの先頭に移動するには次のように
します。
(goto-char (point-min))
なお、バッファの先頭にジャンプする M-< の関数名を知っている人は
「(beginning-of-buffer)
でもいいんでしょ?」と思われるかもしれません
が、これと (end-of-buffer)
は特別な理由のない限り、
Emacs-Lisp
プログラム中から利用してはいけません。これは、マーク位置
を変更してしまうため、ユーザに思わぬ動作を起こさせる可能性があるからです。
n行目に移動する関数として、goto-line
があります。行番号引数として
渡します。なお、バッファの先頭は1行目と数えます。逆に、現在の行番号を得る
には次のようにします。
(count-lines (point-min) (point))
n桁目に移動する関数は move-to-column
で、0から始まる桁数を引数と
して渡します。現在の桁数は (current-column)
で得ることができます。
また、行頭/行末への移動関数は、beginning-of-line
,
end-of-line
です。この二つは比較的よく使われます。
相対移動絶対移動共に現在のポイント位置が移動できる終端まで来たかどうかの チェックをする必要がある場合があります。ポイント位置がバッファ中の特定の位 置にあるかどうかを検査する関数には以下のものがあります。
(bobp) / (eobp)
(bolp) / (eolp)
関数 bobp/eobp
は現在のポイント位置がバッファ先頭/末尾なら真(t) を、
そうでなければ偽(nil)を返します。バッファ末に達するまでなにかの処理を繰り
返すというケースはしばしば必要になります。次のような形で書けるでしょう。
(while (not (eobp)) ;(while 条件式 処理1...処理n) 処理 (forward-line 1))
ここまでに出てきた関数をまとめましょう。
・ポイント値を返す関数 point, point-min, point-max ・移動関数 forward-char, backward-char forward-word, backward-word forward-line goto-char, goto-line move-to-column beginning-of-line, end-of-line ・位置に関する述語関数 bobp, eobp, bolp, eolp
プログラム中でカーソル位置を決定するのに最も頻繁に使用するのが検索です。 ここは必ず押さえましょう。
検索は大別して、
に分けられます(分けます)。これらのうち、インクリメンタルサーチは対話的に用
いることを前提としているため、Emacs-Lisp
プログラム中からは滅多に利
用することはないでしょう。これ以外のものの利用の仕方を説明します。
検索関数には検索したいパターンを文字列として渡すわけですが、その文字列中
に \
を含む場合は注意が必要です。\
は Emacs-Lisp
で扱
う文字列中で特別な意味を持つエスケープキャラクタとなっています。 C言語の文
字列中で使う \n
のような働きを持っています。主なシーケンスには次の
ものがあります。
\\
\C-英字
\^英字
も可)
\e
\"
\n
\r
\a
\b
\f
\t
Cを扱ったことのある人は「\\
とかなら慣れてるから平気」と思われる
かもしれません。しかし、正規表現の検索パターンを指定する場合には、正規表現
での \
エスケープと、Lisp
の \
エスケープが重なってし
まうので非常に繁雑です。正規表現検索で、\
自身を探すにはパターン文
字列として、\\\\
を指定することになります。
このような注意点があることだけを念頭において検索関数の理解に進みましょう。
探したい文字列がはっきりとわかっている場合には普通の文字列検索である
search-forward
, search-backward
を利用します。これらの関数は
引数を一つから四つ取ります(二つ目以降は省略可)。
(search-forward 文字列 範囲 エラー処理 繰り返し回数)
各引数について説明します。
nil
を、単に nil
を返して欲しい時は t
を、検索範囲末まで移動して
欲しい時は nil
, t
以外を渡す.
後述する正規表現検索も同数の引数を取りますが引数の意味として違うのは、第一 引数だけです。もちろん正規表現検索関数の第一引数は正規表現のパターン文字列 を指定します。
search-forward/backward
を用いた典型的な処理形態は次のようになり
ます。
(if (search-forward "文字列" nil t) (progn 見つかった場合の処理) 見つからなった場合の処理)
`TeX' を検索する時に `LaTeX' にはマッチして欲しくない時のよう
に、単語単位での検索に有効なのが word-search-forward(backward)
です。
引数は search-forward
の第一引数を単語に置き換えたものです。
re-search-forward
, re-search-backward
がおそらく最もよく用
いる検索関数となるでしょう。第一引数に検索したい正規表現パターンを指定しま
す。Emacs-Lisp
で扱える正規表現全てについてはほかの解説書に譲ります。
ここでは必要最低限のものに絞ってメタキャラクタの説明をします。
.
*
+
?
^
$
[文字リスト]
[^文字リスト]
[X-Y]
[-^A-Z]
これだけ覚えておけば、ほとんどの検索が可能です。なお、正規表現の検索と組み 合わせた処理は極めて重要なので、別に節を設けて解説します。
単語の先頭にポイントが位置する時に、単語末までポイントを移動したい、ある
いはその逆のことをしたい時などに skip-chars-forward
,
skip-chars-backward
が利用できます。これらの関数は一つまたは二つの
引数を取ります。
(skip-chars-forward スキップ文字リスト スキップ境界)
1.スキップ文字リスト 正規表現の[]の中味と同様に指定し ます。 2.スキップ境界 文字スキップを行う境界をポイント 値で指定します。これを越えてポイ ントが進むことはありません。
英単語の先頭にポイントがある時に、単語末までポイントを移動するには次のよ うにします。
(skip-chars-forward "A-Za-z")
次の例で `def' を検索した場合の検索後のポイント位置は
abc def ghi ~ ~
前方向検索の時は `def' の次の位置、後ろ方向検索の時は `d' の位置 になります。これはインクリメンタルサーチなどを前後方向で行った場合のカーソ ル位置と同じなので、容易に想像がつくことでしょう。
しかし、検索がマッチした部分をアクセスする時に、検索後のポイント位置を当
てにしていたのでは、検索方向によって場合分けしなければならないので通常はこ
れを利用しません。検索のマッチ部分を取得する関数が match-beginning
,
match-end
です。上記の例の、マッチ部分の始まり(dの位置)と、終わり(f
の次の位置)はそれぞれ、
(match-beginning 0) (match-end 0)
で得ることができます。どちらも引数として数値である0を渡しています。実はこ の部分は、正規表現にグループを用いた場合のグループ番号を意味していて、 0 は「マッチした部分全体」という特殊な意味を持っています。例えば上記の例を次 の正規表現で検索した場合を考えてみましょう。
(re-search-forward "\\(a.*\\) *\\(d.*\\) *\\(g.*\\)" nil t)
Emacs
の正規表現のグルーピングは \(グループ\)
で行いますが、
Emacs-Lisp
で \
を正規表現関数に渡すためには \\
と表
記しなければならないことに注意して下さい。さて、この正規表現の意味は、
・`a'で始まる任意の文字列(これをグループ1とする)のあとに `d'で始まる任意の文字列(これをグループ2とする)と `g'で始まる任意の文字列(これをグループ3とする)が続く
となります。
・検索関数 search-forward, search-backward word-search-forward, word-search-backward re-search-forward, re-search-backward ・文字スキップ skip-chars-forward, skip-chars-backward ・検索結果位置取得 match-beginning, match-end
問
先のるねきちモードにおいて、a を押した時に既にバッファ中に存在す る「僕るねえもん `A_n' なりよ」を数え、その数に応じて「僕るねえもん `A_(n+1)' なりよ」を挿入する用に書き換えよ。
すなわち、b を押した時にバッファ中に「僕るねえもん `B' なり よ」がいた場合は「僕るねえもん `B2' なりよ」を、c を押した時 にバッファ中に「僕るねえもん `C' なりよ」、「僕るねえもん `C2' なりよ」、…、「僕るねえもん `C10' なりよ」がいた場合は「僕るねえも ん `C11' なりよ」を挿入する。
ヒント: point, re-search-*ward, \\(\\), buffer-substring match-beginning, match-end, string-to-int ・全てのマッチする文字列に対して処理 現在位置を保存 先頭へ (while (re-search-forward パターン nil t) 処理) 処理 位置を復帰 ポイント: ・グローバル変数は避けましょう。 ・A1, A2, A5 なんて時はどうしましょうかね? 適当に仕様を決めて下さい(A3とかA6とか)。 ただ、これによってアルゴリズムがかなり変わる↑
カーソル移動のための関数を書く場合を除き、Emacs-Lisp
中でポイント
移動を行った場合には、ユーザのその後の編集の事を考慮し、ポイント位置を復帰
しておく必要があります。そのための関数が save-excursion
です。
(save-excursion 実行部...)
のようにポイント移動を伴う部分を save-excursion
の中に閉じこめるこ
とにより、「実行部...」でいかなる場所にポイントを移動しようと、
save-excursion
を抜けると同時に、ポイントは元の位置に復帰します。さ
らに、マーク位置も保存されるので、「実行部...」でマーク位置にアクセス
する関数を書いた場合も、ユーザのその後の編集操作に支障を来しません。
次の例は、ポイントのある行を kill-ring
に入れつつ二重化します。
(defun duplicate-line () (interactive) (save-excursion (beginning-of-line) (copy-region-as-kill (point) (progn (end-of-line) (point))) (forward-line 1) (yank)))
関数中でポイントを移動していますが、実行が終わると関数起動時のポイント位置 に復帰します。
既にメジャーモードの練習関数で、文字列を挿入する関数 insert
は使
用済みです。ほとんどの文字列操作は insert
関数で用が足りますが、次
のものを知っておくと便利な場合が有ります。
同じ文字をたくさん入れたい時に使用できます。引数を二つ取り、最初の引数は 文字コード、二つ目の引数は個数です。文字 `a' を100個入れたい時は次の ようにします。
(insert-char ?a 100)
A-Z, a-z, 0-9, など一般のキーに割当てられている関数が
これです。define-key
などでキーに結び付けられた関数中で、押したキー
そのものを挿入したいときにこの関数を利用します。この関数も、繰り返し挿入回
数を指定する引数を取るので通常は(self-insert-command 1)
のように呼
び出します。(see section キーの割当て)
ちなみに、キーバインドされた関数から、その関数が起動されたキーを知るた
めには、関数(this-command-keys)
を参照します。次のような関数をいろい
ろなキーに割当てて実行してみるとおもしろいでしょう。
(defun show-my-key () (interactive) (insert (this-command-keys)))
バッファ中に挿入したいのは文字列だけとは限りません。なにかの計算によって
選られた数値を文字列化して挿入したいことがあります。このような時に用いるの
が format
関数で、Cの printf
でのフォーマットとほぼ同じもの
が利用できます。ここで Emacs-Lisp
で扱うことのできる型には、以下の
ものがあります。
これらの型のものが単体で用いられる場合、それを「アトム atom
」と言
います。逆に様々な型のアトムが集合したものに、「リスト list
」と
「配列 array
」があります。これらの概念については、一般の
Lisp
の参考書などを見ると説明が載っています。それを理解していると複
雑な処理が効率的に書けるようになることがあるかもしれませんが、とくに理解し
ていなくてもメジャーモードの作成には支障ありません。余裕ができたら覚えましょ
う。(see section リスト)
さて、関数 format
には、すべての型の値を文字列に変換するためのキー
ワードが三種類有ります。
%s
%d,%o,%x
%c
format
関数は第一引数に上記の `%' を含むフォーマット文字列を、
第二引数以降に文字列中の `%?' に対応する変数/定数を受け取ります。そし
て、それらの引数を文字列化したもので元の `%?' を置き換え、すべて置き
換えることで出来上がった文字列を返します。
これを用いて各種の値を表示させてみます。
(setq foo 50 bar ?x baz "hoge") (insert (format "%d, %o, %x %s %c %s\n" foo foo foo 'foo bar baz ))
format
関数の第一引数中、`%?' が文字列に変換される様子は次のよ
うになります。
%d → "50" %o → "62" %x → "32" %s → "foo" %c → "x" %s → "hoge"
従って format 関数が返す文字列は
``50, 62, 32 foo x hero''
となります。
バッファ中に存在する数値文字列を読み込みその値をもとになにかを計算し結果 を返すという処理を想定してみましょう。必要な処理内容は以下のものとなります。
ここでは、これらの処理に必要な関数をすべて覚えてしまいましょう。
バッファの内容を文字列として返す関数は buffer-substring
です。こ
の関数は非常によく使うので、いやでも覚えてしまうでしょう。
(buffer-substring ポイント値1 ポイント値2)
第一引数と第二引数の間の内容を文字列として返します。通常この関数は、検索結
果を保持している関数 match-beginning
, match-end
と共に用いら
れます。(see section 検索結果へのアクセス)
例として
Bytes: 67 Date : 10:23pm 6/28/93 Author:net66331 (luneえもん)
という行から時刻を抽出する関数を定義してみましょう。そのためには、このフォー
マットで書かれている行を表現する正規表現を考える必要があります。簡単のため、
ここでは「行頭が Bytes:
で始まり、時刻文字列があり、(ハンドル)で終
わる行」というものにします。これをそのまま正規表現にすると、
^Bytes:.*[0-9 ][0-9]:[0-9][0-9][ap]m.*(.*)$ ~~~~~~~~~~~|~~~~~~~~~~|~~~~
となるでしょう。しかし今回の場合時刻を取り出したいので、この部分をグループ 化して、
^Bytes:.*\([0-9 ][0-9]\):\([0-9][0-9]\)\([ap]m\).*(.*)$
とします。検索が成功した場合、(match-beginning 1)
と
(match-end 1)
に `時' の部分の先頭と末尾のポイント値が入るはず
です(以下も同様)。これを Lisp 中に書くときは \
を \\
でエス
ケープすることを忘れないようにしましょう。
(defun access-time () (interactive) (re-search-forward "^Bytes:.*\\([0-9 ][0-9]\\):\\([0-9][0-9]\\)\\([ap]m\\).*(.*)$" nil t) (message (concat (if (string= (buffer-substring (match-beginning 3) (match-end 3)) "am") "午前" "午後") (buffer-substring (match-beginning 1) (match-end 1)) "時" (buffer-substring (match-beginning 2) (match-end 2)) "分")))
関数 string=
は文字列どうしが等しいかどうかを比較します。この例では、
グルーピングした3番目の部分、つまり am
か pm
の部分が、
am
だったら `午前' を返し、そうでなかったら `午後' を返し
ています。さらに、グルーピングの1番目と2番目、つまり「時」と「分」の部分に
それぞれ `時' 、`分' を添えています。そしてそれらを
concat
で全て結合したものを message
関数に渡しています。
次の節に進む前に、もう少し分かり易く書き直しておきましょう。全く同じ動作 をします。
(defun access-time () (interactive) (re-search-forward "^Bytes:.*\\([0-9 ][0-9]\\):\\([0-9][0-9]\\)\\([ap]m\\).*(.*)$" nil t) (let((h (buffer-substring (match-beginning 1) (match-end 1))) (m (buffer-substring (match-beginning 2) (match-end 2))) (ap (buffer-substring (match-beginning 3) (match-end 3)))) (message "%s%s時%s分" (if (string= ap "am") "午前" "午後") h m)))
数値を表わす文字列を実際の数値に変換するための関数は、
string-to-int
です。
(string-to-int "数値文字列")
もし、文字列が数値として意味のない文字列である場合は0を返します。では、早
速この関数を使って先程の例を24時間制で表示するように書き換えてみましょう
(展開が予想できましたね?)。先の例では、h
と m
に時刻を表わす
数値文字列が入っているので、これを string-to-int
で数値に変換し、も
じ `pm' だったら「時」に12を足しましょう。
(defun access-time () (interactive) (re-search-forward "^Bytes:.*\\([0-9 ][0-9]\\):\\([0-9][0-9]\\)\\([ap]m\\).*(.*)$" nil t) (let*((h (buffer-substring (match-beginning 1) (match-end 1))) (m (buffer-substring (match-beginning 2) (match-end 2))) (ap (buffer-substring (match-beginning 3) (match-end 3))) (hour (string-to-int h)) (min (string-to-int m))) (if (string= ap "pm") (setq hour (+ 12 hour))) (message "%d時%d分" hour min)))
新しい形 let*
が出てきました。let
との違いは、変数の初期化に
それ以前のローカル変数の値を利用できる点です。上の例では、変数 hour
の初期化に `h' の値を利用しているので、let*
を使う必要がありま
す。
余談となりますが、文字コードを返す関数として、string-to-char
があ
ります。これは、引数として与えた文字列の先頭の一文字の文字コードを返します。
今回の例では %s
による(出力時の)文字列への変換を用いたので、数値
→文字列(変数間)の型変換は用いませんでしたが、string-to-int
の逆の
仕事をする int-to-string
という関数があります。必要に応じて利用する
と良いでしょう。
ついでに文字列に対する種々の操作関数を覚えておきましょう。 M-x
apropos で string
をキーに探せばいろいろ出てきますが、ここでは主な
ものを取り上げます。
(string-equal "文字列1" "文字列2") ;string= と同じ (string< 文字列1 文字列2) (string> 文字列1 文字列2) ;文字列の大小比較 (string-match 正規表現 文字列) ;第一引数の正規表現が第二引数の文 ;字列中の何文字目にマッチするか。 ;マッチしなければ nil (stringp 変数) ;変数の値が文字列かどうか (substring 文字列 開始 終了) ;文字列の「開始」〜「終了」の部分 ;文字列。第三引数を省略すると開始 ;位置から文字列末尾まで。位置を負 ;で与えると文字列の後ろから数える。
バッファの一部を削除する関数で Emacs-Lisp
中から主に用いるのは以
下のものでしょう。
delete-char
と delete-backward-char
は引数として削除する文字
数を指定します。しかし複数文字を削除する時は通常 delete-region
を用
います。
(delete-region 削除開始ポイント値 削除終了ポイント値)
手で編集する時の関数 kill-region
は、kill-ring
を変えてしま
うので、Emacs-Lisp
中から利用してはいけません
(1)。
なにかのパターンを検索して該当部分を削除するということが多いので、
delete-region
も match-beginning
, match-end
と共に用
いられることがほとんどです。
バッファ中に出現する特定のパターンを削除するというケースは非常に多くあり ます。たとえば、
[Continued]
というパターンを全て削除するコードは以下のようになります。
(defun kill-more () (interactive) (goto-char (point-min)) (while (re-search-forward (delete-region (match-beginning 0) (match-end 0))))
このような、
(while (検索関数 パターン nil t) (delete-region (match-beginning ??) (match-end ??)))
という関数の組み合わせは特定のパターンを全て削除する時の定石として覚えてお きましょう。
単純な文字列の置換は、対話的に済ませることが多いのであまりプログラムでは
必要とはなりません。やはり、正規表現検索と組み合わせる ことが多くなります。
特定のパターンを一括置換する場合は次のように なります。次の例は、コントロー
ルコードの ^L
を `山記号' と L
に置換します。
(defun replace-C-l () (interactive) (goto-char (point-min)) (while (search-forward "\C-l" nil t) (replace-match "^L")))
この例の場合は、マッチした部分全体を置換していますがそのような時に使う関数
が replace-match
で、sed
の s/old/new/
の後半にあたり
ます。
(replace-match 置換文字列 大小文字を保存するかのフラグ \を特別扱いしないかのフラグ)
第二引数以降は省略可能です。また \
を特別扱いしないフラグをセットし
ない場合は「置換文字列」の部分に次の表記が利用できます。
\&
\n
\(\)
で指定したグループの内容(nは1-9)
\\
\
自身
既にお気付きでしょうが、前節の一括削除は replace-match
を使うと
(while (検索関数 パターン nil t) (replace-match ""))
と簡単に書くことができます。
・文字列挿入 insert, insert-char, self-insert-command this-command-keys(関連) ・文字列の取り込み buffer-substring ・型変換 format, string-to-int, int-to-string string-to-char, char-to-string ・文字列比較等 string=, string<, string>, string-match stringp, substring ・削除 delete-char, delete-backward-char delete-region, erase-buffer ・置換 replace-match
既に多くの人が利用しているので、次の関数は既習としましょう。
(sleep-for 秒数)
(sit-for 秒数)
(ding)
Emacs-Lisp
ではダイナミックスコープ(動的スコープ)を採用しています。
これは、C言語などのスタティックスコープとは異なり、実行時に参照する変数の
実体が決定するものです。具体例を見てみましょう。次のプログラムの実行結果を
予想し、実際に確かめて見て下さい。
/*---- C言語 ----*/ char *s = "外側のs"; sub() { printf("sub: s = %s\n", s); } main() { printf("main(外): s = %s\n", s); { char *s = "mainの中のs"; printf("main(中): s = %s\n", s); sub(); } sub(); }
;;; -*- Emacs-Lisp -*- (defvar s "外側のs") (defun sub () (insert (format "sub: s = %s\n" s))) (defun main () (insert (format "main(外): s = %s\n" s)) (let ((s "mainの中のs")) (insert (format "main(中): s = %s\n" s)) (sub)) (sub))
C言語では、あらゆるシンボルのスコープ(通用範囲)は、コンパイル時に決定され ます。先程のCのプログラムでは、次の図のような入れ子構造のスコープが形成さ れています。
+-----------------globalな箱----------------------+ | char *s = "外側のs"; | |+---------------- subの箱 ----------------------+| || sub() || || { || || printf("sub: s = %s\n", s); || || } || |+-----------------------------------------------+| |+---------------- mainの箱 ---------------------+| || main() || || { || || printf("main(外): s = %s\n", s); || ||+----------- local-blockの箱 -----------------+|| ||| { ||| ||| char *s = "mainの中のs"; ||| ||| printf("main(中): s = %s\n", s); ||| ||| sub(); ||| ||| } ||| ||+---------------------------------------------+|| || sub(); || || } || |+-----------------------------------------------+| +-------------------------------------------------+
それぞれの箱の壁は、内側から外側しか見ることのできないマジックミラーになっ
ていると考えると分かり易いかもしれません。なにかのシンボルが参照されている
場合、もしその箱の内部でそのシンボルが宣言されていた場合(例えば
local-block
の箱の中のs
)、そのシンボルが最優先で結合されます。
逆に箱の内部でシンボルが宣言されていない場合 (例えば sub
の箱の
s
)、コンパイラは箱をどんどん外側に見ていき、見つかった場合そのシン
ボル(ここでは global
な箱に存在する s
)と結合します。
つまり sub()
では、変数 s
は global
な箱で宣言され
ている s
と結合され、これは sub()
がいつ何時どこから呼ばれよ
うと変わることはありません。常に `外側のs' の格納されているアドレスを
差しています。
スタティックスコープでは、シンボルとその実体との結合は、シンボルの参照が 行われている箇所の、ソースプログラムでの位置によって決定されます。
ところが、ダイナミックスコープでは、変数とその実体との結合は実行時に行わ
れます。先程の Emacs-Lisp
プログラムの例を評価順に追って考える必要
があります。
(defvar s "外側のs") ;これはロード時に評価される →main (insert (format "main(外): s = %s\n" s)) ;外側のsが有効 (let ((s "mainの中のs")) ;ここでローカルなsが発生 (insert (format "main(中): s = %s\n" s)) ;let中のsは"mainの中のs" (sub) ;letを抜けると同時に ) ;ローカルなsは消滅 (sub) ;外側のsが再び有効 →main 終わり
極端な例として、次のものの評価を追ってみると良いでしょう。
(defun hoge () (message "x = %d" x)) (defun foo () (let ((x 1)) (hoge))) (defun bar () (let ((x 2)) (hoge))) (defun baz () (hoge))
(foo)
, (bar)
, (baz)
と順に評価してみて下さい。
ダイナミックスコープの性質を利用すると、ある関数から下請関数を呼ぶする場 合、引数の受け渡しを省略することができます。
(defun natural-rand (n) (let ((r (random t))) (abs-r) (% r n))) (defun abs-r () (if (< r 0) (setq r (- r))))
しかし、このような利用法は関数の汎用性を損なうだけでなく、どの変数を参照 しているのかが分かりにくく、可読性を落とすことになるので、特殊な処理でスピー ドを重視するようなもの以外では、利用しない方が良いでしょう。
(1),(2)の好きな方を作成せよ。
選択問題(1)
ASCII-NET のログを解析し、直前の書き込みとの時間的間隔が一番大 きい書き込みを発見せよ。
つまり、以下のような書き込みがあった場合、
|Bytes: 3001 Date : 6:46am 7/12/93 Author:net92851 (ほんまたける) | やっと出来ました。いまから学校か。結構辛い! | |Bytes: 36 Date : 12:14pm 7/12/93 Author:pcs39334 (はすらあ) | おおついに! | |Bytes: 33 Date : 3:19pm 7/12/93 Author:net66331 (luneきち) | バイトはどうなさったのでしょう の場合、6:46am→12:14pm→3:19pm 5h28m 3h05m なので、二番目の書き込みが該当する。 ヒント: match-beginning, match-end, buffer-substring, string-to-int
選択問題(2)
先のるねきちモードのメッセージ「僕るねえもんXyyなりよ」はちょっと 長いので、「るねXyyなりよ」に変更し、次の機能を付加せよ。
「るねXyyなりよ」をたくさん表示させた状態で、
[A-Z][0-9]*
(以後これを「るね番号」と呼ぶ)に移動。
(self-insert-command)
(call-interactively 'fill-paragraph)
ヒント: cond, looking-at, (substring (recent-keys) 負の数), string= 「delete-region & insert」または「replace-match」 (註: 関数 recent-keys は最近押されたキーを文字列として返す) 余裕があれば、 (1')2,4,6,8 のキーは一回押しただけでは動かず、二 回目以降から動く。
(つまり(recent-keys)
の末尾二文字が同じ時に動く) かなり暇なら、 るね番号を縦に結んだ線がなんとなく揃うようにfill-column
を調整する。 (b3)直前キーが 626 なら、一つ右のるね番号に移動してからその真上にある (8で移動できる)るね番号全てを「自爆」に置換 (b4)直前キーが 424 なら、一つ左のるね番号に移動してからその真上にある (8 で移動できる)るね番号全てを「自爆」に置換 なお、以後これを「るねきちモードII」と呼ぶ。
Emacs
の持っている機能のうち最も強力なものの一つが文字列やファイ
ル名の補完入力で、入力支援のためのメジャーモードには必須の機能と言っても過
言ではないでしょう。本章では、補完入力機能を実装するために必要な知識とその
方法について説明します。
補完入力関数の前に、通常の入力関数について説明します。文字列入力は、
read-string
という関数によって行います。
(read-string プロンプト文字列 [初期入力])
第一引数の文字列をプロンプトとして出し、ミニバッファから文字列を読み込んで その結果を返します。この時に第二引数を与えると、それを読み込み時に既に入力 されていた文字列であるかのようにミニバッファに挿入します。
日本語文字列やスペースを含む文字列を読み込む場合などは補完が有効に働かな
いので、read-string
関数が役に立ちます。次の例は、天候を読み込み日
付と共にバッファ中に挿入します。
(defun insert-date-weather () (interactive) (insert (substring (current-time-string) 0 10) "\t" (read-string "Weather: ") "\n"))
もうひとつ、ファイル名を読み込む read-file-name
を紹介します。第
二引数以降は省略可能です。
(read-file-name プロンプト文字列 [ディレクトリ [デフォルト名 [要マッチ]]])
「ディレクトリ」はファイル名を入力するデフォルトのディレクトリ名を指定しま
すが、これを省略するとカレントバッファの属するディレクトリとなります。「デ
フォルト名」を指定すると、ユーザ自身が何も入力せずにリターンキーを押した場
合に、この値が read-file-name
の結果として返されます。「要マッチ」
に t
を指定した場合は実際に存在するファイル名以外の入力を認めません。
t
でも nil
でもない値を指定した場合は、補完入力の途中でリター
ンキーを押した場合に本当にそのファイルでよいかどうかの確認をします。
ミニバッファで補完入力を行う関数が completing-read
です。
(completing-read プロンプト 補完テーブル 選択(述語)関数 要マッチ 初期入力)
第一引数の「プロンプト」はミニバッファに出すプロンプト文字列、第四引数の
「要マッチ」は read-file-name
のものと同様補完候補と必ず一致すべき
かどうかを指定するフラグ、第五引数はミニバッファに最初から入力されている文
字列で、それぞれ特に説明の必要はないでしょう。
第二引数の「補完テーブル」はスペースキーやタブキーを押した時に補完される
単語を格納した変数です。このテーブルの構造は連想リスト (association
list)
(通称 alist
)と呼ばれるもので、Lisp
言語では非常に良く
使われます。alist
に限らず「リスト」は、Lisp
の機能を最大限
に活かした Emacs-Lisp
プログラムを書くためには必須の概念ですから、
この機会に覚えておきましょう。
Lisp
で扱う対象の最小単位は「アトム」といい、今までに出てきた関数
や変数などの名前を表わす「シンボル」や、数値、文字列、t
、nil
などは全てこれに属します。
「リスト」とは、「アトムまたはリスト、の集合体」です。Lisp
ではア
トム、リストを括弧で括って並べることで集合体を表現します。つまり、それぞれ
のアトム foo
, t
, "bar"
5
を
(foo t "bar" 5)
のように並べたものがリストとなります。ではこれを変数 x
にセットして
みましょう。
(setq x (foo t "bar" 5)) ;×間違い
これでは期待通りになりません。(foo ...)
という形は、「関数foo
の
評価」という意味なので、Lisp
インタプリタは関数 foo
に幾
つかの引数を渡した結果を x
に代入しようとします(たいていは未定義エ
ラーとなるでしょう)。リストの形で渡したい時は、次のように ' をつけてクォー
トする必要があります。
(setq x '(foo t "bar" 5))
' は、次のオブジェクトを評価しない、つまり「後続するものを変数や関数の参
照だと思わずにそのまま渡してくれ」と Lisp
インタプリタに指示する働
きを持っています。
なお、本稿ではリストを表記する時は、関数評価との混乱を避けるため、' でクォー トして表わすことにします。
car
, cdr
, cons
, list
, append
などのリ
スト処理関数を覚える時には、リストがどういう構造で格納されているかについて
理解しておくと非常にスムーズに関数の働きが理解できます。(see section リスト作成)(see section リストの要素の参照)(see section リストの要素の追加)(see section リストどうしの結合)
リスト中の各要素は「コンスセル」と呼ばれる記憶領域に格納されます。たとえ
ば前述のリストの例 '(foo t "bar" 5)
はみかけから分かるように四つの
要素から成っていますが、これらの各要素はそれぞれコンスセルに格納されていま
す。コンスセルは次のような構造をとっています。
+--------------+--------------+ | | 次の | | 要素 | コンスセルの | | | アドレス | +--------------+--------------+
これにしたがって '(foo t "bar" 5)
の格納されている様子を図にしてみ
ましょう。記号Λは、それが最後のコンスセルであることを意味します。通常はΛ
を表わす Lisp
シンボルとしては nil
が入っています。
+---------+---------+ +---------+---------+ | | | | | | | foo | *----+----→| t | * | | | | | | | | +---------+---------+ +---------+----+----+ +---------+---------+ +-----------------/ | | | | +---------+---------+ | 5 | Λ | | | | | | | | +-→| "bar" | * | +---------+---------+ | | | | ↑ +---------+----+----+ \----------------------------------/
(setq x '(foo t "bar" 5))
はシンボル x
にこれらのリスト(コン
スセルの連結)の先頭のアドレスを代入します。
+--x--+ | * | +-----+ ↓ +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+ | foo | *--+→| t | *--+→|"bar"| *--+→| 5 | Λ | +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+
また、概念的に、'(foo t "bar" 5)
の各要素を次のように把握してお
くのも良いでしょう。
'(foo t "bar" 5) ↓ ↓ ↓ ↓ fooと tと "bar"と 5と '(t "bar" 5) '("bar" 5) '(5) Λ へのポインタ へのポインタ へのポインタ のペア のペア のペア のペア
次に、リストの要素にリストがある場合について考えてみましょう。
nil
と、これまで例として用いた '(foo t "bar" 5)
と
hoge
からなるリストは次のように表現できます。
'(nil (foo t "bar" 5) hoge)
リストの要素として、リストをそのままの形で書くだけで良いのです。
もう一つ、要素が一つもないリスト「空リスト」の存在も知っておく必要があり
ます。空リストはリストの要素に何もないので、リストを括る括弧の中に何も書か
ずに '()
と表現します。空リストは常に nil
を値として持ってい
ます。Emacs-Lisp
では空リストと nil
は全く同じ意味を持ちます。
さて (setq x '(foo t "bar" 5))
について理解しておくべき重要な
事項は次のものです。
x
はコンスセルの鎖の先頭(ここでは foo
)のアドレスを指す
これまでは、リストの内容そのものを '
でクォートして並べましたが、
各要素を列挙してそれらから構成されるリストを作成することができます。関数
list
がそれです。引数は任意の個数だけ書けます。
(list 'foo t "bar" baz)
のように書くことで、`foo t "bar" [bazの値]' からなるリストを作成し、
このリストへのポインタを返します。引数を全て評価したものを連結するので、
baz
は `シンボル baz' ではなく `baz の値' になることに注
意して下さい。例えばこの場合、(setq baz 5)
としていた場合、
'(foo t "bar" 5)
というリストが生成されます。
リストの中にリストがある場合も、要素の部分に list
関数で内側のリ
ストを書けば良く、前述の
'(nil (foo t "bar" 5) hoge)
は
(list nil (list 'foo t "bar" baz) 'hoge)
で作成することができます。
要素の連結した形であるリストから、要素そのものを取り出す時に利用する関数
に car
(かあ), cdr
(くだー), nth
(えぬす) があります。
リストは複数のコンスセルから成っていますが、car
と cdr
は引数
として与えられたリストの先頭のコンスセルの、要素部分(car部
)とポ
インタ部分(cdr部
)をそれぞれ返します。つまり、'(foo t "bar" 5)
の
先頭のコンスセル(foo
が入っているもの)は
+--------------+--------------+ | | tの入っている| | foo | コンスセル | | | へのポインタ | +--------------+--------------+
となっているので、(car '(foo t "bar" 5))
は foo
を返し、
(cdr '(foo t "bar" 5))
は次のコンスセルへのポインタ、すなわち
t
を先頭とするリスト '(t "bar" 5)
を返します。 (see section リストの構造)
では、'(foo t "bar" 5)
の cdr
をどんどん辿って行くとどうなるで
しょう。次の例では、変数 x
に cdr
の結果を入れて行きます。
(setq x '(foo t "bar" 5));x は '(foo t "bar" 5) ---(A) (setq x (cdr x)) ;x は '(t "bar" 5) ---(B) (setq x (cdr x)) ;x は '("bar" 5) ---(C) (setq x (cdr x)) ;x は '(5) ---(D) (setq x (cdr x)) ;x は 'nil ---(E)
(A)〜(E)の代入により変数 x
の指し示すものは次のように変動します。
(A) (B) (C) (D) (E) +--x--+ +--x--+ +--x--+ +--x--+ +--x--+ | * | | * | | * | | * | | * | +-----+ +-----+ +-----+ +-----+ +-----+ ↓ ↓ ↓ ↓ +→ nil +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+ | foo | *--+→| t | *--+→|"bar"| *--+→| 5 | Λ | | | (B) | | | (C) | | | (D) | | | (E) | +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+
car
と cdr
を組み合わせると、リストの二番目三番目……の要
素を取り出すことができます。
(car (cdr '(foo t "bar" 5))) ;t (car (cdr (cdr '(foo t "bar" 5)))) ;"bar" (car (cdr (cdr (cdr '(foo t "bar" 5))))) ;5
しかしこの方法ではもっと後ろの要素を取り出すのが大変なので、そのような時は
nth
を使います。nth
は
(nth n番目 リスト) ;「n番目」は0から始まる
のように用いることで、リストの「n番目」の要素を返します。
(setq x (cdr x))
のようにすることで、リストの先頭を順次切り捨てて
行くことができます。これとは逆にリストに要素を追加する関数が、 cons
です。
(cons 要素 リスト)
は、「リスト」に「要素」を追加したリストを返します。list
関数もリス
トを生成して返しますが、list
は引数全てを要素とするリストを新規に作
成して返すのに対し、cons
は既存のリストの先頭に新たな一つの要素を追
加したリストを返します。それではリストの逆の手順をとりつつ '(foo t
"bar" 5)
を構築して行きましょう。(see section リストの要素の参照)
(setq x (cons 5 '())) ;(setq x (cons 5 nil))と同じ (5) (setq x (cons "bar" x)) ;x は ("bar" 5) (setq x (cons t x)) ;x は (t "bar" 5) (setq x (cons 'foo x)) ;x は (foo "bar" 5)
最後の行を評価する前の x
は次のようになっています。
+--x--+ | * | +-----+ ↓ +-----+-----+ +-----+-----+ +-----+-----+ | t | *--+→|"bar"| *--+→| 5 | Λ | +-----+-----+ +-----+-----+ +-----+-----+
(cons 'foo x)
により、cons
はまず、'foo
を car
部とするコンスセルを新たに作成します。そして、その cdr
部にそれまで
x
が指していたリストへのポインタを格納します。上の図は次のように変
化します。
+--x--+ | * | +-----+ ↓ +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+ | foo | *--+→| t | *--+→|"bar"| *--+→| 5 | Λ | +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+ \________________これ全体が(cons 'foo x)________________/
そして最後に (setq x (cons 'foo x))
により x
が (cons
'foo x)
で生成されたリストを指し示すこととなります。
+--x--+ | * | +-----+ ↓ +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+ | foo | *--+→| t | *--+→|"bar"| *--+→| 5 | Λ | +-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+
たくさんの要素を一つのリストに組み上げる list
、一つのリストに一つ
の要素を追加する cons
は既に覚えました。つまり、「要素だけ」と「リ
スト対要素」のリスト合成関数は覚えたので、「リスト対リスト」の結合関数
append
を覚えましょう。append
は引数として与えられたリストを
全て結合します。
(append リスト1 リスト2 リスト3 ... リストn)
のようにした場合、「リスト1」から「リストn」のコンスセルを順次コピーしそれ
らの全てをつなげます。つまり、「リスト1」の最後のコンスセルのコピーの
cdr部
(nil
が入っている)に「リスト2」の最初のコンスセルのコピー
へのポインタを格納し、「リスト2」の最後のコンスセルのコピーのcdr部
に「リスト3」の最初のコンスセルへのポインタを格納し、...という手順を繰
り返すことで、「リスト1」から「リストn」までのすべての要素(コンスセル)が一
つの鎖に連結されます。そして append
は連結されたリストを返します。
この append
の動作が、cons
のものとは少し違うことに気付い
たでしょうか。cons
では第二引数として与えられたリストを構成するコン
スセルのいずれも変化していません。cons
の第一引数の要素をcar
部
とするコンスセルを生成し、そのcdr部
が第二引数のリストを指し示す
ようにしただけにすぎません。これに対し append
は第一引数から第
(n-1)引数までのリストのコンスセルをすべてコピーし直します。 append
によって返されたリストは、どの部分も新たな領域に確保されたものです。
ある対象物がリストなのか、あるいはアトムなのかを判定するための関数はそれ
ぞれ listp
, atom
です。
(listp 引数) (atom 引数)
は「引数」が リスト/アトム である場合に t
を返し、そうでない場合に
nil
を返します。
また、リストに要素がいくつ含まれるかを返す関数に length
がありま
す。length
はリストの要素としてリストがあった場合はそれを一つと数え
ます。つまり、
'(a b c) '(1 '(a (b c) x) 4)
は、どちらも長さ3と数えます。length
は、リストの他に、文字列や (こ
こでは述べませんが)配列の長さを数える時にも利用できます。
association list
は
'(連想キー 値1 値2 ... 値n)
というリストが、一個以上集まってさらにリストになったもので、
'((連想キー1 値1..値n) (連想キー2 ..) ...)
という形式をとっているもののことです。このリストを利用して検索キーが一つだ けの簡単なデータベースを作ることができます。たとえば、
隆 波動拳、昇龍拳、竜巻旋風脚 拳 昇龍拳、波動拳、竜巻旋風脚 春麗 スピニングバードキック、百烈キック E.本田 スーパー頭付き、百烈張り手
という必殺技データベースを作りたい時に、
(setq winning-shot-alist '(("隆" "波動拳" "昇龍拳" "竜巻旋風脚") ("拳" "昇龍拳" "波動拳" "竜巻旋風脚") ("春麗" "スピニングバードキック" "百烈キック") ("E.本田" "スーパー頭付き" "百烈張り手")))
などとして alist
を作っておきます。この alist
から連想キーを
持っている list
を取り出す関数が assoc
で、
(assoc 連想キー 連想リスト)
とすることで、「連想リスト」の中から「連想キー」を持っているリスト一つを見
つけだしそのリストを返します。もし該当するキーを持つリストが見つからなかっ
た場合は nil
を返します。今回の winning-shot-alist
に対して、
キーボードから読み込んだものをキーとして該当するものを探す場合は次のように
します。
(let ((key (read-string "誰のわざ? ")) list) (setq list (assoc key winning-shot-alist)) (if list (message "%sの必殺技は%sです。" key (cdr list)) (message "%sって誰?" key)))
assoc
で得られるリストは、連想キーを含めたリストですから、値だけを
取り出したい時は、その cdr
部を取る必要があります。
検索後に、グルーピングしておいた文字列を取得するために、
(buffer-substring (match-beginning 1) (match-end 1))
などとすること
はほとんど定石と言ってよいでしょう。にもかかわらず、これを一気に行う関数が
ありません(よね?)。そこで、(buffer-substring (match-beginning n)
(match-end n))
という機能の関数を定義したいのですが、この程度で関数にする
のはちょっと抵抗を感ずるかもしれません。このような場合に、C言語の引数付き
define
のような役割をする「マクロ」を利用すると良いでしょう。
Cのマクロ定義と違って Lisp
のマクロの展開のされ方は少し特殊です。
次のCのマクロの例
#define inc(x) ((x)++)
は、定義の引数以外は文字どおりに展開されます。しかし、Lisp
のマクロ
は事情が違います。(setq i (1+ i))
のような動作を行うマクロ定義は次
のようになります。
(defmacro inc(x) (list 'setq x (list '1+ x)))
このような定義を行った場合、Lisp
プログラム中に (inc i)
が出
現した場合 Lisp
インタプリタはマクロ定義の仮引数 x
に
i
を代入した上でマクロ定義を Lisp
そのものとして評価します。
つまり、上の定義のうち(クォートなしの) x
を i
とみなし
(list 'setq x (list '1+ x))
を評価します(実際に (setq x 'i)
してからこの式を評価してみると良いでしょう)。そして、得られた結果が期待し
たS式と同じになっているか確かめます。
(buffer-substring (match-beginning 番号) (match-end 番号))
を表わすマクロを定義してみましょう。
さて、これまで連想リストの説明をしてきたので、completing-read
に
渡す補完テーブルの説明に移ります。completing-read
の引数を復習して
おきましょう。
(completing-read プロンプト 補完テーブル 選択(述語)関数 要マッチ 初期入力)
completing-read
の第二引数に補完テーブルを alist
の形で渡し
ます。例えば曜日を読み込む時の補完テーブルは次のような形です。
'(("Sun.") ("Mon.") ("Tue.") ("Wed.") ("Thu.") ("Fri.") ("Sat."))
今まで説明した alist
とは少し形が違い、それぞれの(内側の)リストが連
想キーだけで構成されていて、それに対応する値が存在していません。補完入力を
したいだけであれば、このような形で構いません。これを用いた曜日入力モジュー
ルは次のようになります。
(defvar day-alist '(("Sun.") ("Mon.") ("Tue.") ("Wed.") ("Thu.") ("Fri.") ("Sat."))) (defun read-day-of-the-week () (interactive) (completing-read "Day of the week?: " day-alist nil t))
day-alist
に連想キーとして入っている文字列を補完候補として入力を
促し、スペースやタブで補完しながら文字を読み込みます。この例では第四引数の
「要マッチ」が t
なので候補以外の文字列は入力することができません。
さて、ここでは第三引数の「選択関数」のところには nil
を指定しまし
たが、ここには補完テーブル中の要素のうち、特定の条件を満たすもの(選
択関数の返す値が t)
を候補にしたい場合に利用します。「選択関数」には
alist
の各要素が(リストのまま)
渡されます。次の例では、月の名
前を読み込む時に、大の月だけを選んで補完候補とします。
(defvar month-alist '(("Jan." 31) ("Feb." 28) ("Mar." 31) ("Apr." 30) ("May." 31) ("Jun." 30) ("Jul." 31) ("Aug." 31) ("Sep." 30) ("Oct." 31) ("Nov." 30) ("Dec." 31))) (defun read-odd-month () (interactive) (completing-read "Odd month: " month-alist 'check-odd-month t)) (defun check-odd-month (list) (eq 31 (car (cdr list))))
関数 check-odd-month
には month-alist
の各要素、すなわち
'("Jan." 31)
, '("Feb." 28)
, ..., '
("Dec." 31) が渡さ
れるので、これらの第二要素を (car (cdr list))
によって取り出し、
`31' かどうかを判定した結果を返しています。
変数 completion-ignore-case
を t
にすると、補完文字列の大
文字小文字を無視します。例えば、月名を読む時に小文字で入力したイニシャルを
元に補完して欲しい時は、
(let ((completion-ignore-case t)) : (completing-read ...)...)
のように completion-ignore-case
に局所的に t
をセットして補
完入力関数を呼びます。
前出の completing-read
はミニバッファで補完候補を読むものでしたが、
バッファ中にある文字列を元に補完したものを得たい場合などに用いるのが
try-completion
です。Emacs-lisp
の関数を随時補完してくれる
M-TAB がその代表的なものです。
(try-completion 文字列 補完テーブル [選択関数])
第一引数の「文字列」を「補完テーブル」中のすべての候補と比較し、マッチし
たものがあった場合、マッチしたもの共通部分の先頭からの文字列を返します。も
しマッチするものがなかった場合は nil
を返し、「文字列」が「補完テー
ブル」の中のただ一つの候補と完全一致した場合には、t
を返します。少々
分かりにくいので例を挙げて説明します。
例えば補完候補に foo
, bar
, baz
, bazz
,
hoge
, hore
があった場合、次のような補完結果が得られます。
「文字列(第一引数)」 try-completion の結果 "f" "foo" "b" "ba" "ba" "ba" "bar" t "baz" "baz" "h" "ho" "ho" "ho" "hog" "hoge" "x" nil
これらの結果を考察すると try-completion
の返り値を次のように判断
すると良いことがわかります。
これより try-completion
を用いてバッファ中の文字列の随時補完をす
る手順は次のようになります。
(defun complete-something () (interactive) (補完文字列の先頭を探す) ;;(*1) (先頭からポイントまでの文字列を取得) (setq 結果 (try-completion 文字列 テーブル)) (cond ((eq 結果 t) (これ以上補完の必要ないと表示)) ((eq 結果 nil) (一致するものがないと表示)) ((string= 文字列 結果) (先頭からポイントまでを切り取り、結果で置き換える)) (t (候補一覧を表示)))) ;;(*2)
(*1)は一般的に、文字列の要素となり得ないものを後方に探します。例えば改行
文字や、空白文字は補完文字列とならないことがほとんどなので、
(skip-chars-backward "^ \t\n")
とすれば十分でしょう。
続いて(*2)のための関数を紹介します。
関数 all-completions
は try-completion
と同じ引数を取り(第
二引数まで)、一致する文字列候補の全てをリストにして返します。例えば先の例
で、文字列 `ba' で補完させる場合に一致する候補は、`bar',
`baz', `bazz' ですから、これらをリストにした、'("bar"
"baz" "bazz")
が all-completions
の結果となります。これをバッファ
に表示したい時に利用するのが display-completion-list
で、
all-completions
で得られた結果をカレントバッファに表示します。
実際にはカレントバッファに候補一覧を表示しては困るので、隣に新たなバッファ を開いて表示します。この方法に関しては、ウィンドウとバッファの取り扱いを覚 えてから説明します。
Jump to: a - c - d - e - f - i - k - l - m - n - o - p - r - s - t - v - あ - い - え - か - き - け - こ - さ - し - す - せ - そ - た - ち - て - は - ふ - へ - ほ - ま - め - も - よ - ら - り - れ - ろ - わ
Jump to: $ - % - ' - ( - * - + - - - . - 1 - < - ? - \ - ^ - a - b - c - d - e - f - g - i - k - l - m - n - o - p - r - s - t - u - w - x - も - 文
This document was generated on 29 January 1998 using the texi2html translator version 1.52.