while 循环

接下来是两个 while 循环。第一个 while 的真假测试会在 forward-sentence 的前缀参数为负数时成立,用于实现反向移动。该循环的主体与第二个 while 子句的主体类似,但并不完全相同。我们略过这个 while 循环,重点讲解第二个。

第二个 while 循环用于向前移动光标。其结构如下:

(while (> arg 0)            ; true-or-false-test
  (let varlist
    (if (true-or-false-test)
        then-part
      else-part
  (setq arg (1- arg))))     ; while loop decrementer

while 循环属于递减型循环。(See A Loop with a Decrementing Counter。)只要计数器(此处为变量 arg)大于 0,真假测试就成立;并且每次循环重复时,递减器都会将计数器的值减 1。

如果调用 forward-sentence 时未指定前缀参数(这是最常用的方式),该 while 循环会执行一次,因为 arg 的值为 1。

while 循环的主体由一个 let 表达式构成,该表达式创建并绑定一个局部变量,其内部主体为一个 if 表达式。

while 循环的主体如下:

(let ((par-end
       (save-excursion (end-of-paragraph-text) (point))))
  (if (re-search-forward sentence-end par-end t)
      (skip-chars-backward " \t\n")
    (goto-char par-end)))

let 表达式创建并绑定局部变量 par-end。稍后我们会看到,该局部变量用于为正则表达式搜索提供边界或限制。如果在段落内未能找到合法的句子结尾,搜索会在到达段落末尾时停止。

首先,我们来看 par-end 如何绑定到段落结尾的值。let 会将 par-end 的值设为 Lisp 解释器对以下表达式求值后返回的结果:

(save-excursion (end-of-paragraph-text) (point))

在该表达式中,(end-of-paragraph-text) 将光标移动到段落结尾,(point) 返回当前光标位置的值,随后 save-excursion 将光标恢复到原始位置。因此,letpar-end 绑定到 save-excursion 表达式的返回值,即段落结尾的位置。(end-of-paragraph-text 函数使用了 forward-paragraph,我们稍后会讲解。)

接下来 Emacs 对 let 的主体求值,该主体是一个 if 表达式,如下所示:

(if (re-search-forward sentence-end par-end t) ; if-part
    (skip-chars-backward " \t\n")              ; then-part
  (goto-char par-end)))                        ; else-part

if 会测试其第一个参数是否为真,若为真则执行 then 分支;否则 Emacs Lisp 解释器会执行 else 分支。该 if 表达式的真假测试即为正则表达式搜索。

forward-sentence 函数的核心逻辑写在这里看起来有些奇怪,但这是 Lisp 中实现此类操作的常用方式。