14.3 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-defuncount-words-defun,并设置快捷键。然后将以下代码复制到 Emacs Lisp 缓冲区 (如 *scratch*),将光标置于定义内,使用 C-c = 命令。

(defun multiply-by-seven (number)
  "Multiply NUMBER by seven."
  (* 7 number))
     ⇒ 10

成功!该定义包含 10 个单词与符号。

下一个问题是统计单个文件中多个定义的单词与符号数量。