中有以下代码片段pgfutil-common.tex
(与 pgfmathutil.code.tex` 中的代码片段非常相似):
{%
\def\:{\global\let\pgfutil@sptoken= } \:
\def\:{\pgfutil@xifnch} \expandafter\gdef\: {\futurelet\pgfutil@let@token\pgfutil@ifnch}
}
我一直试图通过编写自己的版本来理解这一点:
\documentclass{article}
\makeatletter
\newcommand\aetest{\ae@test}
\def\ae@test(#1){%%
\textsf{#1}%%
\ae@ifnextchar H%%
{\ae@true}{\ae@false}}
\def\ae@true#1;{ \textbf{(true):#1}}
\def\ae@false#1;{ \textbf{(false):#1}}
\long\def\ae@ifnextchar#1#2#3{%%
\let\ae@reserved@d=#1%%
\def\ae@reserved@a{#2}%%
\def\ae@reserved@b{#3}%%
\futurelet\ae@let@token\ae@ifnch}
\def\ae@ifnch{%%
\ifx\ae@let@token\ae@sptoken
\let\ae@reserved@c\ae@xifnch
\else
\ifx\ae@let@token\ae@reserved@d
\let\ae@reserved@c\ae@reserved@a
\else
\let\ae@reserved@c\ae@reserved@b
\fi
\fi
\ae@reserved@c}
{%%
\def\:{\global\let\ae@sptoken= } \:
\def\:{\ae@xifnch} \expandafter\gdef\: {\futurelet\ae@let@token\ae@ifnch}%%
}
\makeatother
\begin{document}
\aetest(This test is) Hello;
\aetest(This test is) Ciao;
\end{document}
上述代码编译正确,符合预期。
上述代码片段已重写为:
{%%
\def\:{\global\let\ae@sptoken= } \:
\def\:{\ae@xifnch} \expandafter\gdef\: {\futurelet\ae@let@token\ae@ifnch}%%
}
我相信我明白这个片段应该做什么:
(a)\let
将令牌\ae@sptoken
转换为空间令牌
(b)向前扫描标记列表中的任何空间标记到下一个非空间标记。
我有两个问题:
(1)为什么需要使用它\:
来代替其他控制序列?
\expandafter\gdef\:
(2)为什么扩展之后后面的空间没有被吃掉?
在《TeXBook》第 39 页,Knuth 写道:
.... 标记列表内的控制序列后的空格不会被忽略;忽略空格规则仅适用于输入文件,在字符串被标记期间。
因此,我认为可以将此代码片段的第一行重写为:
\def\ae@colon{\global\let\ae@sptoekn= }\ae@colon
我的印象是,由于等号后面的空格已经被标记化,所以它不会丢失。但似乎正在发生的事情是,当\ae@colon
扩展时,这个标记化的空间是无关紧要的。相反,
\ae@sptoken
是\let
到\def
。虽然我不确定如何验证这一点,但 LaTeX 只是抱怨\ae@xifnch
未定义。因此我的第一个问题。
关于\expandafter\gdef\:
以下空间:我认为这个空间应该丢失,因为\:
在空间被标记之前它被扩展了。因此,我认为以下方法之一可能有效:
\expandafter\gdef\:{....} %%<-- no space following "\:"
或者
\gdef\ae@xifnch{....}
或者
\gdef\ae@xifnch {...} %%<-- space after \ae@xifnch
但两者都不起作用。相反,LaTeX 挂起了。我认为这是因为它一直在解析相同的空格标记,并且无法将其从列表中删除。
因此,当\expandafter\gdef\:
扩展时,以下空间会保留下来。因此我的第二个问题是。
关于冒号的选择,我知道 没什么特别的\:
。我尝试过\!
和\,
。两者都工作得很好。所以,这里的技巧与控制符号(由一个非字母组成的控制序列)的工作方式有关。但是,我不知何故不知道如何\:
在这里实现预期的效果。
答案1
我想我现在明白这里发生了什么。
语法\let
如下
\let<control sequence><equal><one optional space><token>
如果我写,其中*
表示空格
\let\ae@sptoken=*
然后*
就被解释为<one optional space>
并且下一个标记被抓取用于\let
ting。
在 的情况下\def\:{\let\ae@sptoken= }
, 后面的空格=
是<one optional space>
。现在由于\:
是命令符号,其后面的空格将转换为空格标记。假设当 LaTeX 到达行末时,它会将行末字符转换为空格(前提是 LaTeX 处于 状态M
)。这意味着
\:
扩展为
\let\ae@sptoken=**
(我再次使用*
来表示空间)。空间 也是\ae@sptoken
如此。\let
通过以下方式可以达到类似的效果:
\def\ae@colon{\let\ae@sptoken= }
\expandafter\ae@colon\space
这里的诀窍是找到一种方法来在紧随之后的两个相邻空间中走私=
。
关于
\expandafter\gdef\: {....}
我误解了空格转换为标记的时间。转换发生在扩展之前\expandafter
。因此,当上面的行扩展时,它变成
\gdef\ae@xifnch*{....}
其中*
表示空间,现在它是一个标记,不会被命令字符串吞噬。
通过以下方式可以达到类似的效果:
\expandafter\gdef\expandafter\ae@sptoken\space{....}