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)
和往常一样,我们的工作是填充这些占位部分。
首先是初始化部分。
我们假定该函数在包含函数定义的缓冲区中调用。光标可能位于某个函数定义内,也可能不在。
为使 count-words-in-defun 正常工作,光标需要移至定义开头,计数器从零开始,
且计数循环在光标到达定义结尾时停止。
beginning-of-defun 函数向后搜索行首的左分隔符(如 ‘(’),并将光标移至该位置,
或到达搜索边界。实际使用中,这意味着 beginning-of-defun 会将光标移至当前或前一个
函数定义的开头,或缓冲区开头。我们可以用它将光标置于统计起始位置。
while 循环需要一个计数器跟踪被统计的单词或符号。可以使用 let 表达式创建
局部变量,并将其初始值绑定为零。
end-of-defun 函数与 beginning-of-defun 类似,只是将光标移至定义结尾。
它可以用于确定定义结尾位置的表达式中。
count-words-in-defun 的初始化部分很快成型:首先将光标移至定义开头,然后创建
保存计数值的局部变量,最后记录定义结尾位置,使 while 循环知道何时停止。
代码如下:
(beginning-of-defun)
(let ((count 0)
(end (save-excursion (end-of-defun) (point))))
代码十分简洁。唯一略显复杂的地方可能与 end 有关:该变量通过 save-excursion 表达式绑定到定义的结束位置,该表达式会在 end-of-defun 临时将指针移动到定义末尾后,返回指针 point 的值。
count-words-in-defun 初始化之后的第二部分是 while 循环。
循环中需要一个表达式逐个单词、逐个符号地向前跳转光标,另一个表达式统计跳转次数。
while 循环的条件测试在需要继续跳转时为真,到达定义结尾时为假。我们已经重新定义了
对应的正则表达式,因此循环十分直观:
(while (and (< (point) end)
(re-search-forward
"\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*" end t))
(setq count (1+ count)))
函数定义的第三部分返回单词与符号的计数值。这部分是 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 个单词与符号。
下一个问题是统计单个文件中多个定义的单词与符号数量。