count-words-in-defun 関数
¶count-words-region
関数の書き方にはいくつかの方法があることが分かりました。count-words-in-defun
を書くには、これらの形式のいずれかを適宜調整すればよいだけです。
while ループを使用しているバージョンは分かりやすいため、それを流用することにします。count-words-in-defun
はより複雑なプログラムの一部となるため、インタラクティブである必要はなく、メッセージを表示する必要もなく、単にカウントを返すだけで構いません。こうした点を考慮することで、定義が少し簡素化されます。
一方で、count-words-in-defun
は関数定義を含むバッファ内で使用されます。したがって、ポイントが関数定義内にあるときに呼び出されたかどうかを関数側で判定し、そうであればその定義のカウントを返すように求めるのは妥当でしょう。これにより定義の複雑さは増しますが、関数に引数を渡す必要がなくなります。
以上の検討を踏まえ、以下のテンプレートを作成いたします。
(defun count-words-in-defun ()
"documentation..."
(set up...
(while loop...)
return count)
いつもの通り、私たちの仕事は穴埋めだ。
まずはセットアップから。
この関数は、関数定義を含むバッファ内で呼び出されるものと想定しています。pointは関数定義内にある場合とない場合があります。count-words-in-defunを動作させるには、pointが定義の先頭に移動し、カウンタが0から開始され、かつpointが定義の末尾に達したときにカウントループが停止する必要があります。
beginning-of-defun 関数は、行頭にある ‘(’
のような開始デリミタを後方から検索し、ポイントをその位置、あるいは検索範囲の終端まで移動させます。実際には、beginning-of-defun
はポイントを囲んでいる関数定義や直前の関数定義の先頭、あるいはバッファの先頭まで移動させます。beginning-of-defun を使えば、任意の開始位置にポイントを置くことができます。
while ループでは、カウント対象の単語や記号を管理するためのカウンタが必要です。let 式を使用してこの目的のためのローカル変数を作成し、初期値として 0 を代入することができます。
end-of-defun 関数は、ポイントを定義の末尾に移動させる点を除けば、beginning-of-defun と同様に動作します。end-of-defun は、定義の末尾の位置を決定する式の一部として使用できます。
count-words-in-defun
のセットアップは迅速に形になります。まずポイントを定義の先頭に移動し、次にカウントを保持するためのローカル変数を作成し、最後に while ループがいつ終了すべきかを判断できるよう、定義の末尾の位置を記録します。
コードはこんな感じです:
(beginning-of-defun)
(let ((count 0)
(end (save-excursion (end-of-defun) (point))))
コードはシンプルです。唯一のわずかな複雑さは、おそらく end
に関するものでしょう。これは、end-of-defun
が一時的にポイントを定義の末尾に移動させた後の値を返す save-excursion
式によって、定義の末尾の位置に結び付けられています。
count-words-in-defunのセットアップに続く後半部分は、whileのループです。
ループには、pointを単語単位および記号単位で前方へジャンプさせる式と、そのジャンプ回数をカウントする別の式を含める必要があります。whileループの真偽判定は、pointが前方へジャンプすべき限りtrueとなり、pointが定義の末尾にあるときはfalseとなるべきです。これに関する正規表現はすでに再定義済みであるため、ループの実装は単純です:
(while (and (< (point) end)
(re-search-forward
"\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*" end t))
(setq count (1+ count)))
関数定義の第3部分は、単語と記号の数を返します。この部分は let
式の本体における最後の式であり、ごく単純に、評価時にその数を返すローカル変数 count
である場合があります。
まとめると、count-words-in-defun の定義は次のようになります。
(defun count-words-in-defun ()
"Return the number of words and symbols in a defun."
(beginning-of-defun)
(let ((count 0)
(end (save-excursion (end-of-defun) (point))))
(while
(and (< (point) end)
(re-search-forward
"\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*"
end t))
(setq count (1+ count)))
count))
これをどうテストすればよいでしょうか?この関数はインタラクティブではありませんが、ラッパーを被せてインタラクティブにするのは簡単です。count-words-exampleの再帰版とほぼ同じコードを使用できます:
;;; Interactive version.
(defun count-words-defun ()
"Number of words and symbols in a function definition."
(interactive)
(message
"Counting words and symbols in function definition ... ")
(let ((count (count-words-in-defun)))
(cond
((zerop count)
(message
"The definition does NOT have any words or symbols."))
((= 1 count)
(message
"The definition has 1 word or symbol."))
(t
(message
"The definition has %d words or symbols." count)))))
C-c =を便利なキーバインドとして再利用しましょう:
(keymap-global-set "C-c =" 'count-words-defun)
それでは count-words-defun を試してみましょう。count-words-in-defun と count-words-defun の両方をインストールし、キーバインドを設定します。次に、以下の内容を
Emacs Lisp バッファ(例えば *scratch*
など)にコピーし、定義内にカーソルを置いて、C-c = コマンドを実行してください。
(defun multiply-by-seven (number)
"Multiply NUMBER by seven."
(* 7 number))
⇒ 10
成功!定義には10個の単語と記号が含まれています。
次の課題は、単一のファイル内にある複数の定義に含まれる単語数と記号数を数えることです。