let* 表达式

forward-paragraph 函数的下一行开始一个 let* 表达式(see let* introduced),Emacs 在其中总共绑定了七个变量:opointfill-prefix-regexpparstartparsepsp-parstartstartfound-startlet* 表达式的前半部分如下:

(let* ((opoint (point))
       (fill-prefix-regexp
        (and fill-prefix (not (equal fill-prefix ""))
             (not paragraph-ignore-fill-prefix)
             (regexp-quote fill-prefix)))
       ;; Remove ^ from paragraph-start and paragraph-sep if they are there.
       ;; These regexps shouldn't be anchored, because we look for them
       ;; starting at the left-margin.  This allows paragraph commands to
       ;; work normally with indented text.
       ;; This hack will not find problem cases like "whatever\\|^something".
       (parstart (if (and (not (equal "" paragraph-start))
                          (equal ?^ (aref paragraph-start 0)))
                     (substring paragraph-start 1)
                   paragraph-start))
       (parsep (if (and (not (equal "" paragraph-separate))
                        (equal ?^ (aref paragraph-separate 0)))
                   (substring paragraph-separate 1)
                 paragraph-separate))
       (parsep
        (if fill-prefix-regexp
            (concat parsep "\\|"
                    fill-prefix-regexp "[ \t]*$")
          parsep))
       ;; This is used for searching.
       (sp-parstart (concat "^[ \t]*\\(?:" parstart "\\|" parsep "\\)"))
       start found-start)
  ...)

变量 parsep 出现了两次:第一次用于移除 ‘^’ 实例,第二次用于处理填充前缀。

变量 opoint 就是光标 point 的值。你可以猜到,它和在 forward-sentence 中一样,用于 constrain-to-field 表达式。

变量 fill-prefix-regexp 被设为对以下列表求值后返回的值:

(and fill-prefix
     (not (equal fill-prefix ""))
     (not paragraph-ignore-fill-prefix)
     (regexp-quote fill-prefix))

这是一个以 and 特殊形式为第一个元素的表达式。

如前所述(see The kill-new function),and 特殊形式会依次对每个参数求值,直到某个参数返回 nil,此时整个 and 表达式返回 nil;如果所有参数均未返回 nil,则返回最后一个参数的求值结果。(由于该结果非 nil,在 Lisp 中被视为真。)换句话说,and 表达式仅在所有参数均为真时返回真值。

在本例中,仅当以下四个表达式求值均为真(非 nil)时,变量 fill-prefix-regexp 才会绑定到非 nil 值;否则 fill-prefix-regexp 绑定为 nil

fill-prefix

对该变量求值时,会返回填充前缀的值(若存在)。若无填充前缀,该变量返回 nil

(not (equal fill-prefix ""))

该表达式检查已存在的填充前缀是否为空字符串(即不包含任何字符的字符串)。空字符串不是有效的填充前缀。

(not paragraph-ignore-fill-prefix)

如果变量 paragraph-ignore-fill-prefix 被设为真值(如 t)而开启,该表达式返回 nil

(regexp-quote fill-prefix)

这是 and 特殊形式的最后一个参数。如果 and 的所有参数均为真,该表达式的求值结果会由 and 表达式返回,并绑定到变量 fill-prefix-regexp

and 表达式成功求值的结果是:fill-prefix-regexp 会绑定到经 regexp-quote 函数处理后的 fill-prefix 值。regexp-quote 的作用是读取一个字符串,并返回一个能精确匹配该字符串且不匹配其他内容的正则表达式。这意味着,如果填充前缀存在,fill-prefix-regexp 会被设为能精确匹配该填充前缀的值;否则该变量设为 nil

let* 表达式中的接下来两个局部变量用于从 parstartparsep(分别表示段落起始和段落分隔的局部变量)中移除 ‘^’ 实例。随后的表达式再次设置 parsep,用于处理填充前缀。

这也是需要使用 let* 而非 let 定义的原因。if 的真假测试依赖于变量 fill-prefix-regexp 求值为 nil 还是其他值。

如果 fill-prefix-regexp 没有值,Emacs 会执行 if 表达式的 else 分支,并将 parsep 绑定到其局部值。(parsep 是一个匹配段落分隔内容的正则表达式。)

但如果 fill-prefix-regexp 有值,Emacs 会执行 if 表达式的 then 分支,并将 parsep 绑定到一个包含 fill-prefix-regexp 作为模式一部分的正则表达式。

具体来说,parsep 被设为原段落分隔正则表达式的值,与一个备选表达式拼接而成:该备选表达式由 fill-prefix-regexp 后跟行尾可选空白字符组成。空白字符由 "[ \t]*$" 定义。‘\\|’ 将这部分正则表达式定义为 parsep 的备选。

根据代码中的注释,下一个局部变量 sp-parstart 用于搜索;最后两个变量 startfound-start 被设为 nil

现在我们进入 let* 的主体。主体第一部分处理函数接收负参数并因此反向移动的情况,我们略过这一节。