count-words-example 中的空白字符错误 ¶上一节介绍的 count-words-example 命令存在两处错误,更准确地说是同一错误的两种表现。第一,若在文本中间选中一段仅含空白的区域,count-words-example 会提示该区域包含一个单词!第二,若在缓冲区末尾或窄化缓冲区可访问部分末尾选中一段仅含空白的区域,命令会抛出如下错误:
Search failed: "\\w+\\W*"
如果你正在 GNU Emacs 的 Info 中阅读本文,可以亲自测试这些错误。
首先,按常规方式求值函数以完成加载。
如需,还可求值以下代码绑定快捷键:
(keymap-global-set "C-c =" 'count-words-example)
进行第一次测试:将标记与光标分别置于下一行开头与结尾,然后键入 C-c =(若未绑定快捷键则使用 M-x count-words-example):
one two three
Emacs 会正确提示该区域有三个单词。
重复测试,但将标记置于行首,光标放在单词 ‘one’ 前方。再次执行命令 C-c =(或 M-x count-words-example)。Emacs 本应提示该区域无单词,因为只有行首空白,但却提示有一个单词!
第三次测试:将示例行复制到 *scratch* 缓冲区末尾,并在行尾添加若干空格。将标记放在单词 ‘three’ 之后,光标置于行尾(即缓冲区末尾)。再次键入 C-c =(或 M-x count-words-example)。Emacs 本应提示无单词,却抛出 ‘Search failed’ 错误。
两处错误源于同一问题。
先看第一种错误表现:命令提示行首空白处有一个单词。过程如下:M-x count-words-example 将光标移至区域起始,while 判断光标位置小于 end,条件成立。随后正则表达式搜索找到第一个单词,将光标移至单词后,count 设为 1。while 循环再次判断时,光标位置已大于 end,循环退出,函数提示区域内有一个单词。简言之,正则表达式搜索超出选中区域找到了单词。
第二种错误表现:区域为缓冲区末尾的空白。Emacs 提示 ‘Search failed’。原因是 while 循环条件成立,执行搜索表达式,但缓冲区已无单词,搜索失败。
两种错误中,搜索均超出或试图超出区域范围。
解决方案是将搜索限制在区域内——这一操作看似简单,但实际并非如想象中直接。
如前所述,re-search-forward 函数第一个参数为搜索模式,此外还接受三个可选参数:第二个可选参数限定搜索边界;第三个可选参数若为 t,搜索失败时返回 nil 而非抛出错误;第四个可选参数为重复次数。(在 Emacs 中,可通过 C-h f 加函数名并回车查看函数文档。)
在 count-words-example 定义中,区域结束位置保存在变量 end 中并作为参数传入函数。因此可将 end 加入正则表达式搜索参数:
(re-search-forward "\\w+\\W*" end)
但若仅对 count-words-example 做此修改并在空白区域测试,仍会收到 ‘Search failed’ 错误。
原因是:搜索被限制在区域内,且区域内无单词构成字符,因此按预期失败并抛出错误。但我们此时不希望报错,而希望显示 “该区域内没有任何单词。”
解决方法是为 re-search-forward 提供第三个参数 t,使其搜索失败时返回 nil 而非抛出错误。
但若仅做此修改,你会看到 “Counting words in region ...”(正在统计区域内单词 ...) 并一直停留,直到键入 C-g(keyboard-quit)。
原因是:搜索仍限制在区域内,且因无单词构成字符失败,re-search-forward 返回 nil,未执行任何其他操作,尤其没有像搜索成功时那样移动光标。re-search-forward 返回 nil 后,while 循环内下一个表达式执行,计数器自增,循环再次判断。由于光标位置仍小于 end,条件持续成立,循环无限执行。
count-words-example 还需进一步修改,使 while 循环条件在搜索失败时为假。换句话说,计数器自增前必须同时满足两个条件:光标仍在区域内,且搜索成功找到单词。
由于两个条件需同时成立,可将区域判断与搜索表达式用 and 特殊形式结合,作为 while 循环条件:
(and (< (point) end) (re-search-forward "\\w+\\W*" end t))
re-search-forward 搜索成功时返回 t 并移动光标,因此找到单词时光标会在区域内推进。当搜索无法找到更多单词或光标到达区域末尾时,循环条件为假,while 循环退出,count-words-example 显示对应提示。
加入最终修改后,count-words-example 可正常运行(至少未发现已知错误)。完整代码如下:
;;; 最终版: while 实现
(defun count-words-example (beginning end)
"打印区域内单词数量。"
(interactive "r")
(message "Counting words in region ... ")
;;; 1. 设置合适条件
(save-excursion
(let ((count 0))
(goto-char beginning)
;;; 2. 运行 while 循环 (while (and (< (point) end) (re-search-forward "\\w+\\W*" end t)) (setq count (1+ count)))
;;; 3. 向用户输出信息
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message
"The region has 1 word."))
(t
(message
"The region has %d words." count))))))