为了更深入地了解 TeX,我关注了TeX 编程指南. 它包含有关宏扩展的示例,例如这个
\def\point(#1,#2){we do something with #1 and #2}
\def\temp{(42,1234)}
\expandafter\point\temp
这让我开始尝试从它的定义中重建一个宏。
以下是我的方法:
% definition of macro
\def\macro #1{I am a macro called with #1}
% storing it in toks1
\toks1={\meaning\macro}
% define reconstruction of arguments/pattern from \meaning
\def\args #1:#2->#3{#2}%
% define reconstruction of definition from \meaning
\def\definition #1:#2->#3{#3}%
% define reconstruction itself
\def\reconstruct(#1,#2){\expandafter\def\csname#1\endcsname \args #2 {\definition #2}}%
% reconstruct contents of toks1
\reconstruct(reconstructed,\expandafter\expandafter\null\the\toks1)
% print its meaning
\message{\meaning\reconstructed}
\end
然而,这并没有导致我想要的宏被重建。我无法以预期的方式使用它,它的含义也没有镜像原始宏。
我尝试了 \expandafter 的各种不同放置方式,但这最终是最合理的方式(至少对我来说)。
仅使用 TeX 执行此操作的正确方法是什么?
我认为上述扩展将导致类似这样的结果
\def\mymacoro #1{\def\mymacro2 #1{#1}}
这显然是一个问题;我能帮助那里的一个小组吗,或者# 寄存器没有被遮蔽?
答案1
你的方法有几个问题。你应该先存储含义,而不是\meaning\macro
,所以
\toks0=\expandafter{\meaning\macro}
(奇数的临时寄存器应该保留用于全局分配,但这是一个小问题)。现在,\showthe\toks0
将打印
> macro:#1->I am a macro called with #1.
但你会绝不能够\macro
从这些数据中重建,除非你将结果写入文件并读取它,或者等效地使用\scantokens
仅适用于 e-TeX 引擎(因此不适用于 Knuth TeX)。
问题在于的结果\meaning\macro
由 12 个类别字符标记组成(但空格有类别代码 10)。
这适用于具有无分隔参数的宏(对替换文本有限制,不能包含\replacement
)。
它可能也适用于分隔参数。
\newwrite\reconstructout
\newread\reconstructin
\def\macro #1{I am a macro called with #1}
\immediate\openout\reconstructout=\jobname.rcn
\immediate\write\reconstructout{\meaning\macro}
\immediate\closeout\reconstructout
\openin\reconstructin=\jobname.rcn
\chardef\remember=\endlinechar
\endlinechar=-1
\read\reconstructin to \reconstruct
\closein\reconstructin
\endlinechar=\remember
\def\args #1:#2->#3\args{#2}
\def\replacement #1:#2->#3\replacement{#3}
\toks0=\expandafter\expandafter\expandafter{\expandafter\args\reconstruct\args}
\toks2=\expandafter\expandafter\expandafter{\expandafter\replacement\reconstruct\replacement}
\begingroup\edef\x{\endgroup
\def\noexpand\mymacro\the\toks0{\the\toks2}%
}\x
\ifx\mymacro\macro\message{SUCCESS!!!}\fi
这是控制台输出:
This is TeX, Version 3.141592653 (TeX Live 2021) (preloaded format=tex)
(./reconstruct.tex SUCCESS!!!)
如果你这样做
\def\point(#1,#2){we do something with #1 and #2}
并将\macro
上面的代码替换为\point
。
答案2
\def\macro #1{I am a macro called with #1}
% storing it in toks1
\toks1={\meaning\macro}
此时toks1
包含两个标记,我\meaning\macro
认为你想让它保存字符串\meaning
\toks1=\expandafter{\meaning\macro}
但请注意,这\meaning
是一个“逐字”字符串,所有内容都是 catcode 12(标点符号)或 10(空格),因此特别是文本中的字母不是 catcode 11(字母),并且 in#
是#1
正常标点符号#
,而\#
不是宏参数。
在经典 TeX 中,为了重新解析此字符串,假设正常的 catcodes 您可以将其写入文件然后读回,e-tex 提供\scantokens
重新解析(内部它所做的与文件写入后跟文件读取大致完全相同)。
相似地
\expandafter\expandafter\null\the\toks1
第一个\expandafter
扩展\null
如下
\expandafter\hbox{}\the\toks1
所以第二个\expandafter
是可扩展的{
,它是不可扩展的,所以这是
\hbox{}\the\toks1
这不是你想要的。(不清楚你想做什么,\null
无论如何它都会添加一个不需要的框)
答案3
为了获得更深的理解关于 TeX,我建议阅读 TeXbook,阅读时要挑剔每个单词的上下文,并严格遵守正确使用该书中介绍的术语。
这您参考的编程指南使用自己发明的术语,缺乏精确性,因此可能会误导新手。
例如,你会发现这样的解释
但是,既没有解释“宏的内容”这个短语中的“内容”是什么意思,也没有解释“TeX 所见”是什么意思。
除此之外,\meaning
实际应用并不局限于宏。\meaning
可以应用于⟨代币⟩这不是一个⟨宏⟩同样:您可以用来\meaning
了解原语、显式字符标记、隐式字符标记、\chardef
-标记、\toksdef
-标记、\dimendef
-标记、任何\...def
-标记的含义,...
新手无法推断出在 .tex 输入文件中出现的字符可能在对\meaning
要传递的宏的定义进行标记时被丢弃,因为当时这些字符属于类别代码 10(空格),因此在读取设备处于 S 状态(跳过空格)或 N 状态(新行)时被丢弃。
新手无法推断它只\meaning
传递了类别 12(其他)的显式字符标记序列,空格是唯一的例外:空格将属于类别 10(空格)。即,它\meaning
不会向您提供线索,让您知道它传递的哪些字符标记来自控制字标记或控制符号标记,以及它传递的哪些字符标记来自显式字符标记。除此之外,使用显式字符标记会丢失有关类别的信息。修补宏的例程实际上\meaning
只是进行某种“非常有根据的猜测”。
新手无法推断的结果\meaning
会受到整数参数的值的影响\escapechar
。
新手无法推断的结果\meaning
可能会受到执行时当前的类别代码的影响\meaning
:当\meaning
传递控制符号标记的符号表示时,不会附加任何空格标记。当\meaning
传递控制字标记的符号表示时,会附加一个空格标记。名称由单个字符组成的控制序列标记是被视为控制符号标记还是控制字标记取决于执行时该字符的当前类别代码\meaning
:
\def\test{\z\z W\z}
\tt
\string\test=\meaning\test
\catcode`\z=12
\string\test=\meaning\test
\escapechar=`\A
\catcode`\z=11
\string\test=\meaning\test
\catcode`\z=12
\string\test=\meaning\test
\bigskip
\hrule
\bigskip
\escapechar=`\\
\catcode`\z=11
\def\testA{\z\zW \z}
\def\testB{\z\z W \z}
\catcode`\z=12
\string\testA=\meaning\testA
\string\testB=\meaning\testB
meanings are \ifx\testA\testB equal\else different\fi
\bye
TeXbook,第 7 章:TeX 如何读取您输入的内容明确指出:
在迄今为止的例子中,
\string
已将控制序列转换为以 开头的标记列表。但这个反斜杠标记实际上并不是硬连线到 TeX 中的;\12
有一个名为的参数\escapechar
,它指定在将控制序列输出为文本时应使用什么字符。的值\escapechar
通常是 TeX 的反斜杠内部代码,但如果需要其他约定,则可以更改它。
这一段很好地说明了为什么我建议在阅读本书时要非常挑剔:尽管在开头这段话是关于但\string
并没有说这\string
是唯一一个以文本形式输出控制序列起作用的例程。
\meaning
,也是这样的例程。
TeXbook,第 20 章:定义(也称为宏)明确指出:
\meaning⟨token⟩
。TeX 将其扩展为命令 ' ' 将显示在终端上的字符序列。例如,' ' 通常扩展为 ' ';' ' 之后的 ' ' 扩展为 ' '。 [...] 在迄今为止列出的所有情况下,产生的结果都是 ASCII 字符标记序列。\let\test=⟨token⟩ \show\test
\meaning A
the letter A
\meaning\A
\def\A#1B{\C}
macro:#1B->\C
\the
每个标记都分配有类别代码 12(“其他”),但字符代码 32 属于类别 10(“空格”)。同样的规则用于为由\number
、\romannumeral
、\string
、\meaning
、\jobname
和产生的标记分配类别代码\fontname
。
TeXbook,第 21 章:制作盒子明确指出:
每个
\write
命令都会产生输出TeX 总是使用符号形式显示标记列表:字符代表自身(除非像##
宏参数字符那样获得重复的字符);不可扩展的控制序列标记产生它们的名称,前面是\escapechar
,后面跟着一个空格(除非名称是一个活动字符或由单个非字母形成的控制序列)。
另一个例子是,阅读时挑剔是件好事:虽然段落开头是这么说的,但\write
并没有说这\write
是唯一一个以符号方式显示标记列表的例程。
\meaning
,也是这样的例程。
\meaning
但是, 不会复制宏参数字符。
也许“except”这个词是为了表明这种重复是使用符号显示标记列表形式的一种偏差,这种偏差发生在用于\write
写入内容(到文本文件或终端)时。