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
将点移到封闭或前置函数定义的开头,或者移到缓冲区的开头。我们可以使用 beginning-of-defun
将点放置在我们想要开始的位置。
while
循环需要一个计数器来跟踪要计数的单词或符号。可以使用 let
表达式创建一个用于此目的的局部变量,并将其绑定到零的初始值。
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
:它被绑定到通过 save-excursion
表达式返回的点的位置,该表达式在 end-of-defun
暂时将其移动到定义的末尾后返回其值。
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 () "返回 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
相同的代码:
;;; 交互版本。
(defun count-words-defun ()
"函数定义中的单词和符号数。"
(interactive)
(message
"正在计算函数定义中的单词和符号数... ")
(let ((count (count-words-in-defun))) (cond ((zerop count) (message "该定义没有任何单词或符号。"))
((= 1 count) (message "该定义有 1 个单词或符号。")) (t (message "该定义有 %d 个单词或符号。" count)))))
让我们重新使用 C-c = 作为一个方便的键绑定:
(global-set-key "\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) "将 NUMBER 乘以七。" (* 7 number)) ⇒ 10
成功!该定义有 10 个单词和符号。
下一个问题是计算单个文件中多个定义中的单词和符号数。