我遇到了以下检查空参数的代码:
\catcode`\@=11 % as in plain.tex
\long\def\blank#1{\bl@nk#1@@..\bl@nk}%
\long\def\bl@nk#1#2@#3#4\bl@nk{#3#4}
\catcode`\@=12
\long\def\test#1{\begingroup \toks0{[#1]}%
\newlinechar`\/\message{/\the\toks0:
\if\blank {#1}EMPTY\else NOT empty\fi%
有人可以解释一下它是如何工作的吗?
尤其是这部分我不太理解。如果用vs\if\blank {#1}
调用,它会扩展成什么?\test{}
\test{Test}
答案1
\catcode`\@=11 % as in plain.tex
\long\def\blank#1{\bl@nk#1@@..\bl@nk}%
\long\def\bl@nk#1#2@#3#4\bl@nk{#3#4}
\catcode`\@=12
\long\def\test#1{\begingroup \toks0{[#1]}%
\newlinechar`\/\message{/\the\toks0:
\if\blank {#1}EMPTY\else NOT empty\fi%
在上面的代码中,你会发现短语“blank”:
在口语化的 TeX 语言中,如果宏参数根本不包含任何标记,即为空,或者仅由类别代码 10(空格)和字符代码 32 的显式字符标记组成,即仅由显式空格标记组成,则宏参数被称为“空白”。 (字符标记的字符代码是指字符在 TeX 的内部字符表示方案中的数字,在传统的 TeX 引擎中为 ASCII,而在 XeTeX/LuaTeX 中为 Unicode,其中 ASCII 是其严格子集。)
测试是为了查明是否\if\blank{1⟨\blank's argument⟩}2E11M11P11T11Y11\elseN11O11T11explicit space token10e11m11p11t11y11\fi
⟨\blank 的论点⟩为空或仅由明确的空格标记组成(字符代码 32、类别代码 10)。
测试依赖于\if\blank{1⟨\blank's argument⟩}2E11M11P11T11Y11\elseN11O11T11explicit space token10e11m11p11t11y11\fi
⟨\blank 的论点⟩不包含标记或标记,因为这些标记由 -macro 传递并用作参数分隔符和来自用户输入/来自的标记@11
\bl@nk
\blank
\bl@nk
⟨\blank 的论点⟩不应错误地匹配这些分隔符。
TeX 工作方式的两个微妙方面被利用:
\if
TeX 在收集-comparison=character-code-comparison的两个标记时,会不断扩展可扩展的内容。- 当 TeX 收集属于未限定参数的标记时,将丢弃该参数最外层之前/形成该参数的单个标记之前的所有显式空格标记。
{1
\blank{1⟨\blank's argument⟩}2
产量:
\bl@nk⟨\blank's argument⟩@11@11.12.12\bl@nk
。
测试的要点是关于 TeX 如何收集参数\bl@nk
:
如果为⟨\blank's argument⟩
空白,
- 后面的第一个将被视为 的第一个(未限定的)参数。
@11
⟨\blank's argument⟩
\bl@nk
- 后面的第二个将被视为 的第二个( -delimited)参数的分隔符,该参数为空。
@11
⟨\blank's argument⟩
\bl@nk
@11
- 后面第一个将被视为 的第三个(未限定)参数。
.12
⟨\blank's argument⟩
\bl@nk
- 后面的第二个将用作 的第四个( -delimited)参数。
.12
⟨\blank's argument⟩
\bl@nk
\bl@nk
\bl@nk
的第三和第四个参数被传递,因此你有类似
\if.12.12E11M11P11T11Y11\elseN11O11T11explicit space token10e11m11p11t11y11\fi
将 的字符代码与 的字符代码进行比较,因此采用 -branch/true-branch 并丢弃 -branch/false-branch。.12
.12
\if
\else
如果⟨\blank's argument⟩
不是空白,
- 第一个不是显式空格标记的标记(即,不是字符代码 32 和类别代码 10(空格)的显式字符标记)或第一个-group 的标记将被视为 的第一个(未分隔的)参数。
{1…}2
⟨\blank's argument⟩
\bl@nk
- 后面的第一个将作为 的第二个( -delimited)参数的分隔符,该分隔符保存了 的余数。
@11
⟨\blank's argument⟩
\bl@nk
@11
⟨\blank's argument⟩
- 后面的第二个将被视为 的第三个(未限定)参数。
@11
⟨\blank's argument⟩
\bl@nk
- 后面的标记序列将用作 的第4个(分隔的)参数。
.12.12
⟨\blank's argument⟩
\bl@nk
\bl@nk
\bl@nk
的第三和第四个参数被传递,因此你有类似
\if@11.12.12E11M11P11T11Y11\elseN11O11T11explicit space token10e11m11p11t11y11\fi
将 的字符代码与 的字符代码进行比较,因此-branch/true-branch 被丢弃并且-branch/false-branch 被采用。@11
.12
\if
\else
答案TeX 如何查找分隔参数?你可能会感兴趣
答案空标记列表的可扩展测试——方法、性能和稳健性你可能会感兴趣
答案2
如果#1
为空则
\if\blank{}
是
\if\bl@nk @@..\bl@nk
即
\if ..
如此真实
如果#1
非空,!
则说
\if\blank{|}
是
\if\bl@nk !@@..\bl@nk
即
\if @..
这是错误的,所以.
跳到\else
请注意,此测试并不完全安全
\catcode`\@=11 % as in plain.tex
\long\def\blank#1{\bl@nk#1@@..\bl@nk}%
\long\def\bl@nk#1#2@#3#4\bl@nk{#3#4}
\if\blank{@@}EMPTY\else Not empty\N\fi
\bye
将按..EMPTY
内部分隔参数的格式进行排版,并假设被测试的参数从不包含 catcode 11 @
。
答案3
要记住的第一件事是,\if
将进行递归宏扩展,直到找到两个不可扩展的标记。
第二个重要事实是,TeX 在寻找未限定的宏参数时会忽略显式的空格标记。
让我们看看调用时发生了什么
\if\blank{a}<true>\else<false>\fi
\if\blank{}<true>\else<false>\fi
\if\blank{ }<true>\else<false>\fi
\if\blank{a}<true>\else<false>\fi
TeX 将会扩展\blank
,因此#1
将会a
得到
\if\bl@nk a@@..\bl@nk•<true>\else<false>\fi
(第一个 之后没有空格,我使用它只是为了查看标记的结束位置)。现在将扩展\bl@nk
宏。它会查找未定界的参数,然后查找由 定界的参数,然后查找未定界的参数,最后查找由 定界的参数。在本例中,\bl@nk
@
\bl@nk
#1 <- a
#2 <- (empty)
#3 <- @
#4 <- ..
并将替换文本,因此我们得到
\if @..<true>\else<false>\fi
由于@
和.
没有相同的字符代码,测试将返回 false,因此
<false>\fi
将保留在输入流中。
\if\blank{a}
如果我们有而不是 ,结果会类似\if\blank{abc}
,因为在这种情况下我们会得到
#1 <- a
#2 <- bc
#3 <- @
#4 <- ..
\if\blank{}<true>\else<false>\fi
第一步我们得到
\if\bl@nk @@..\bl@nk<true>\else<false>\fi
(再次强调,第一个空格之后的空格\bl@nk
不存在)。现在\bl@nk
寻找它的参数:
#1 <- @
#2 <- (empty)
#3 <- .
#4 <- .
因此输入流将具有
\if..<true>\else<false>\fi
现在测试返回 true。
\if\blank{ }<true>\else<false>\fi
与前一种情况相同:我们得到
\if\bl@nk @@..\bl@nk<true>\else<false>\fi
但现在后面的空格\bl@nk
是从 的参数中获得的\blank
。没什么大不了的!上述第二条规则适用,该空格被忽略,其余内容与第二种情况完全相同。
笔记
正如 David Carlisle 指出的那样,这并不是特别稳健,因为如果在类别代码为 11 的上下文中使用@
,即使参数不为空,测试也可能返回 true,如果我们调用\if\blank{@@}
。事实上,在这种情况下,第一步会产生
\if\bl@nk @@@@..\bl@nk<true>\else<false>\fi
我们有
#1 <- @
#2 <- (empty)
#3 <- @
#4 <- @..
所以我们最终会得到
\if @@..<true>\else<false>\fi
测试将返回 true。两个句点将出现在输入流中。
更安全的测试是很奇怪字符,例如Q
通常找不到的类别代码为 3 的字符。