回答我的问题“使用 TikZ 是否可以通过预处理器传递节点内容?“,@MarkWibrow 建议使用 解决方案\scantokens
。
{\catcode`\_=13 \gdef_{\rule[-1pt]{0.75em}{1.0pt}}}
\def\pp#1{{\catcode`\_=13 \scantokens{#1\ignorespaces}}}
我从来不明白为什么\scantokens
会有用或没用,而且我看到过很多关于 的危险警告\scantokens
。 中的文档e-TeX
相当稀疏(至少稀疏得让我无法理解)。所以,我决定自己做一点实验,并编写了自己的 版本\pp
:
\def\aepp#1{{\catcode`\_=13 #1}}
但
\aepp{hello_world}
导致错误
! Missing $ inserted.
<inserted text>
$
最初,我想我可能不了解其\catcode
工作原理,并尝试了以下 MWE:
\documentclass{article}
{\catcode`\_=13 \gdef_{\rule[-1pt]{0.75em}{1.0pt}}}
\setlength\parindent{0pt}
\begin{document}
{\catcode`\_=13 hello_world}
\end{document}
编译时没有错误。
我觉得这有点令人困惑,因为
{\catcode`\_=13 hello_world}
扩展为什么\aepp{hello_world}
?
因此,虽然我对 还不太了解\scantokens
,但我更好奇为什么 的扩展\aepp{hello_world}
未能实现所\pp{hello_world}
期望的效果。
答案1
TeX 的扫描仪(眼睛)转换人物在文件中代币。这只会发生一次,宏替换文本和所有扩展处理过程标记(即 [character-code,catcode] 对)。
catcode 表影响字符到字符标记的转换。
因此,您的定义在本地生成了 13 的 catcode,_
因此如果_
特点在该范围内遇到时,它会将其标记为标记(_,13)
。但是您向其传递了一个标记列表,其中包含#1
已被标记的标记,因此永远不会参考 catcode 表,因此您对表的更改不会被使用。
\scantokens
就像将标记写入文件并产生流一样人物然后将其读回,并使用当前的 catcode 表进行重新标记。
如果没有\scantokens
经典构造,您正在寻找的是:
\documentclass{article}
{\catcode`\_=13 \gdef_{\rule[-1pt]{0.75em}{1.0pt}}}
\setlength\parindent{0pt}
\begin{document}
{\catcode`\_=13 hello_world}
\def\aepp{\bgroup\catcode`\_=13 \xaepp}
\def\xaepp#1{#1\egroup}
\aepp{hello_world}
\end{document}
_
这会改变读取参数之前的 catcode 。但是\verb
,如果嵌套在另一个命令的参数中,则此操作(并非巧合)不起作用,因为此时该参数已被标记化。
答案2
这个\scantokens
命令本身并不“危险”,但它可能会产生令人惊讶的效果。
它是如何工作的?它的参数像\write
操作一样被扫描,但根据当前类别代码,所有符号标记都被视为不可扩展;结果被放置在“伪文件”中,该文件的读入方式与\input
使用时完全相同。
这会产生各种后果;例如,在末尾添加了一个隐式空格标记,实际上是一个行尾字符,就像 TeX 对 所做的那样\input
。这个空格可以用各种方式中和,但最常见的是在 的标记列表末尾添加\empty
或。使用不在我的建议列表中:\noexpand
\scantokens
\ignorespaces
\scantokens{a\ignorespaces}X\scantokens{a\empty}X
\scantokens{a\ignorespaces} X\scantokens{a\empty} X
将输出
所以你看到的效果\ignorespaces
超越了\scantokens
。
人们可能会试图使用\scantokens
来获取命令参数中的逐字内容,但是有一个问题:
\def\test#1{\begingroup\tt\catcode`\\=12
\scantokens{#1}\endgroup}
\test{\abc\def}
将输出
(为简单起见,我使用了 Plain TeX)。空格从何而来?在 的内部表示中\write
,控制字后跟一个空格;反斜杠类别代码的更改发生得太晚了。
另一个(确实很棘手的)怪癖:
\def\test#1{\begingroup\tt\catcode`\\=12
\scantokens{#1}\endgroup}
\newlinechar`^^J
\test{a^^Jb^^J^^Jc}
将使 TeX 读取伪文件,就像
a
b
c
但结果是不是
a b\par c
正如人们所预料的那样;相反,
因为 TeX 会将两个连续的行尾转换为令牌 \par
,而不是进入控制序列\par
。这可以通过
\def\test#1{\begingroup\tt\catcode`\\=12 \edef\par{\string\par}%
\scantokens{#1}\endgroup}
\newlinechar`^^J
\test{a^^Jb^^J^^Jc}
输出
练习:解释细节。
最后的怪癖:人们可以使用\scantokens\expandafter{\foo}
并且在完成其工作\foo
之前会进行扩展:\scantokens
\def\test#1{\begingroup\tt\catcode`\\=12
\scantokens\expandafter{#1}\endgroup}
\def\foo{\abc\def}
\test\foo
\bye
会和以前一样
为了拥有\scantokens
内部\edef
,还需要一个技巧:文件末尾必须被隐藏。
\def\escantokens#1#2#3{%
\begingroup\everyeof{\noexpand}%
#3%
\edef\x{\endgroup\def\noexpand#1{\scantokens{#2}}}\x
}
\escantokens\demo{\abc\def}{\catcode`\\=12 }\show\demo
\escantokens\demo{\abc}{\def\abc{ABC}}\show\demo
\bye
第三个参数\escantokens
是一组临时分配(例如类别代码,但不仅限于此)。
终端上的输出是
> \demo=macro:
->\abc \def .
l.8 \show\demo
?
> \demo=macro:
->ABC.
l.10 ...tokens\demo{\abc}{\def\abc{ABC}}\show\demo
?