尝试理解 keyval.sty (特别是 \KV@@sp@def)

尝试理解 keyval.sty (特别是 \KV@@sp@def)

我对 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)拐弯处-挑战。

相关内容