我对 keyval.sty (02/2020 版本,第 65ff 行) 中的以下宏定义感到困惑:
\def\@tempa#1{%
\long\def\KV@@sp@def##1##2{%
\futurelet\KV@tempa\KV@@sp@d##2\@nil\@nil#1\@nil\relax##1}%
\def\KV@@sp@d{%
\ifx\KV@tempa\@sptoken
\expandafter\KV@@sp@b
\else
\expandafter\KV@@sp@b\expandafter#1%
\fi}%
\long\def\KV@@sp@b#1##1 \@nil{\KV@@sp@c##1}%
}
\@tempa{ }
因此\KV@@sp@b
在其参数字符串中给出了两个分隔符:即(如最后一行#1
所示,“已初始化” )和。<space>
<space>\@nil
第 3 行调用\KV@@sp@d
,最终扩展为\KV@@sp@b
。令我困惑的是\KV@@sp@b
最终在第 3 行中的用法:在我看来,如果\@tempa
调用 时使用除 之外的任何其他参数<space>
(比如\foo
),则第 3 行最终扩展为- 即永远不会遇到\KV@sp@b\foo<actualargument>\@nil\@nil\foo\@nil
第二个分隔符。相反,插入到(在我看来)应该 a 的位置。<space>\@nil
\foo
<space>
我遗漏了什么?我觉得那里有一个错误,但它似乎运行正常?
答案1
您在那段代码中看到的东西\@tempa
只是一个在定义中获取空格标记的技巧。这是必要的,因为当 TeX 读取输入时,它会忽略多字母控制序列后的空格(如\hello
和\x
,但不会像\$
或 这样的活动字符~
,假设是“通常的” catcodes),因此所有:
\def\tmp#1{}
\def \tmp #1{}
\def \tmp #1{}
\def
会做同样的事情,因为和之后的空格\tmp
会被忽略。然而,在你展示的代码片段中keyval
,TeX 会忽略一些空格。为了在这些地方有一个空格标记,一个常见的技巧是定义一个临时宏(此处\@tempa
)并#1
在你想要空格的地方使用,然后你只需要使用临时宏并以空格作为参数,所有#1
(不是 ##1
) 将被太空标记取代。
为了说明,比较一下以下输出:
1: Token is \meaning !
\def\tmpa#1{Token is \meaning#1!}
2: \tmpa{ }
在1
之后的空格字符\meaning
会被 TeX 忽略,并且它会有效地执行\meaning!
(并打印the character !
),而在2
之后插入空格,并且 TeX 会执行\meaning<space>
,并打印blank space
。
答案2
keyval.dtx 说:
\KV@@sp@def{⟨cmd⟩}{⟨token list⟩}
类似于,只是在进行分配之前删除了\def
开头或结尾的空格标记 。⟨token list⟩
我们来看看代码:
\def\@tempa#1{%
\long\def\KV@@sp@def##1##2{%
\futurelet\KV@tempa\KV@@sp@d##2\@nil\@nil#1\@nil\relax##1}%
\def\KV@@sp@d{%
\ifx\KV@tempa\@sptoken
\expandafter\KV@@sp@b
\else
\expandafter\KV@@sp@b\expandafter#1%
\fi}%
\long\def\KV@@sp@b#1##1 \@nil{\KV@@sp@c##1}%
}
\@tempa{ }
[...]
\long\def\KV@@sp@c#1\@nil#2\relax#3{\KV@toks@{#1}\edef#3{\the\KV@toks@}}
使用\@tempa
嵌套⟨space token⟩
在花括号中的 [ 来确保 .tex 输入文件中相应的空格字符在标记化过程中不会被跳过,但会产生一个明确的空格标记 = 一个字符代码为 32 的明确字符标记 - 32 是 TeX 内部字符编码方案中空格字符的代码点的数量,在传统引擎中为 ASCII,在基于 XeTeX/LuaTeX 的引擎中为 unicode - 和类别代码 10(空格) ] 作为参数产生:
\long\def\KV@@sp@def#1#2{%
\futurelet\KV@tempa\KV@@sp@d#2\@nil\@nil⟨space token⟩\@nil\relax#1}%
\def\KV@@sp@d{%
\ifx\KV@tempa\@sptoken
\expandafter\KV@@sp@b
\else
\expandafter\KV@@sp@b\expandafter⟨space token⟩%
\fi}%
\long\def\KV@@sp@b⟨space token⟩#1⟨space token⟩\@nil{\KV@@sp@c#1}%
[...]
\long\def\KV@@sp@c#1\@nil#2\relax#3{\KV@toks@{#1}\edef#3{\the\KV@toks@}}
这就是构成机制的宏的\KV@@sp@def
定义方式。
这个机制是如何\KV@@sp@def
工作的?
\KV@@sp@def{⟨cmd⟩}{⟨token list⟩}
产量:
\futurelet\KV@tempa\KV@@sp@d⟨token list⟩\@nil\@nil⟨space token⟩\@nil\relax⟨cmd⟩
\futurelet\KV@tempa
由扩展 得到的序列将/of\KV@@sp@def
的第一个标记的含义分配给控制字标记。#2
⟨token list⟩
\KV@tempa
然后\KV@@sp@d
进行。
基本\KV@@sp@d
工作原理如下:如果的含义\KV@tempa
表明的第一个标记⟨token list⟩
是⟨space token⟩
,则调用\KV@@sp@b
。否则调用。在执行之前,需要定义中的 ,以使或消失 。\KV@@sp@b⟨space token⟩
\expandafter
\KV@@sp@d
\else
\fi
\KV@@sp@b
这样,\KV@@sp@b
在任何情况下,标记后面的下一个标记都是⟨space token⟩
。它要么是 的第一个标记⟨token list⟩
,要么被添加到 之前,⟨token list⟩
因为\KV@@sp@d
没有⟨token list⟩
前导⟨space token⟩
。
换句话说:⟨token list⟩
有前导⟨space token⟩
或没有前导的情况是通过在没有前导的情况下插入前导来⟨space token⟩
解决的。\KV@@sp@d
⟨space token⟩
⟨token list⟩
⟨space token⟩
当\KV@@sp@d
及其\ifx
东西完成后,无论如何你都会得到类似这样的结果:
\KV@@sp@b⟨space token⟩⟨token list with a leading space token spliced off if at least one leading space token was present⟩\@nil\@nil⟨space token⟩\@nil\relax⟨cmd⟩
\KV@@sp@b
本身被 界定⟨space token⟩
。因此⟨space token⟩
后面的\KV@@sp@b
将被删除。
的参数\KV@@sp@b
由 界定⟨space token⟩\@nil
。
因此你可以分出两种情况:
情况1:
如果⟨token list with a leading space token spliced off if at least one leading space token was present⟩
有尾随的⟨space token⟩
,则参数分隔符将由⟨token list with a leading space token spliced off if at least one leading space token was present⟩
的尾随⟨space token⟩
和\@nil
其后紧随的组成⟨token list with a leading space token spliced off if at least one leading space token was present⟩
,您将获得:
\KV@@sp@c⟨token list with a leading space token and/or a trailing space token spliced off if at least one leading/trailing space token was present⟩\@nil⟨space token⟩\@nil\relax⟨cmd⟩
案例 2:
如果⟨token list with a leading space token spliced off if at least one leading space token was present⟩
没有尾随的,则⟨space token⟩
参数分隔符将由⟨space token⟩
第三个之前的和组成,您将获得:\@nil
\@nil
\KV@@sp@c⟨token list with a leading space token and/or a trailing space token spliced off if at least one leading/trailing space token was present⟩\@nil\@nil\relax⟨cmd⟩
在这两种情况下, 的第一个参数\KV@@sp@c
(由 分隔\@nil
)将是⟨token list with a leading space token and/or a trailing space token spliced off if at least one leading/trailing space token was present⟩
,
的第二个参数\KV@@sp@c
(由 分隔)将是第一个和\relax
之间的内容, 的第三个参数(未分隔)将由 组成。\@nil
\relax
\KV@@sp@c
⟨cmd⟩
因此,在这两种情况下,执行的\KV@@sp@c
结果都是:
\KV@toks@{⟨token list with a leading space token and/or a trailing space token spliced off if at least one leading/trailing space token was present⟩}\edef⟨cmd⟩{\the\KV@toks@}
你可能会问这样的问题:
为什么要分配给令牌寄存器\KV@toks@
而 不是?\edef⟨cmd⟩{\the\KV@toks@}
\def⟨cmd⟩{⟨token list with a leading space token and/or a trailing space token spliced off if at least one leading/trailing space token was present⟩}
原因是:
⟨token list with a leading space token and/or a trailing space token spliced off if at least one leading/trailing space token was present⟩
可能包含哈希(#
)。
这些哈希\def
可能会被错误地认为是⟨balanced text⟩
的定义中的⟨cmd⟩
表示参数的东西,而⟨parameter text⟩
的定义⟨cmd⟩
是空的。这反过来会导致错误消息。
的微妙之处\edef
在于:当\edef
通过 获取令牌寄存器的内容时,组成该内容的令牌将不会进一步扩展。除此之外,类别代码 6(参数)的每个显式字符令牌,即每个哈希(),都将被加倍,因此不会被视为的定义中的表示参数的东西。\the⟨token register⟩
#
⟨balanced text⟩
⟨cmd⟩
因此,一方面,使用“标记寄存器方式”,您可以在 的定义\edef
内将哈希值加倍。另一方面:当宏(是宏)展开时,类别代码为 6(参数)的两个连续显式字符标记,即两个连续哈希值(),会折叠成单个标记/单个哈希值( )。(这在将s 嵌套在s的 s中时很有用 。)⟨balanced text⟩
⟨cmd⟩
⟨cmd⟩
##
#
⟨definition⟩
⟨balanced text⟩
⟨definition⟩
“标记寄存器\edef
方式”确保扩展⟨cmd⟩
产生与中提供的类别代码 6(参数)/哈希的显式字符标记的数量/星座完全相同⟨token list⟩
。
就是这样。
-mechanism\KV@@sp@def
依赖于⟨token list⟩
不包含令牌\@nil
。
在定义之前,-mechanism 会从中移除恰好一个前导(如果存在)和恰好一个尾随(如果存在),即使\KV@@sp@def
存在多个前导和/或尾随。⟨space token⟩
⟨token list⟩
⟨space token⟩
⟨token list⟩
⟨cmd⟩
⟨space token⟩
如果形成的标记集⟨token list with a leading space token spliced off if at least one leading space token was present⟩
是模式,则周围的花括号将被删除。{⟨balanced text⟩}⟨space token⟩
⟨balanced text⟩
\KV@@sp@b
如果在执行\KV@@sp@b
⟨token list with a leading space token and/or a trailing space token spliced off if at least one leading/trailing space token was present⟩
模式后,最外面的一对花括号将被删除。{⟨balanced text⟩}
\KV@@sp@c
这意味着:根据前导/尾随 的存在⟨space token⟩
,最多两层周围的花括号可能会被移除/剥离。是否需要移除花括号取决于用例。
顺便一提:
有关从宏参数中删除前导⟨space token⟩
s 和尾随⟨space token⟩
s 的更多信息,请参阅以下解决方案:挑战 15(删除空格)迈克尔·唐斯 (Michael Downes)拐弯处-挑战。