有人可以进一步阐明扩展、catcodes 和 scantokens……吗?

有人可以进一步阐明扩展、catcodes 和 scantokens……吗?

回答我的问题“使用 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

? 

相关内容