我以为我终于理解了扩展,但是当我写了一些类似下面的东西时,我却有点困惑。
\documentclass{article}
\usepackage{xcolor}
\usepackage{etoolbox}
\makeatletter
\def\ae@test#1{\textcolor{red}{\fbox{#1}}}
\edef\aetest#1{<a bunch of expandable material>\expandonce\ae@test{#1}<more expandable material>}
\makeatother
\begin{document}
\aetest{Hello}
\end{document}
我怀疑这个问题与需要提前考虑论点有关,但我甚至不知道如何检验这个理论。
因此,我有两个问题:
- 究竟发生了什么,导致扩展没有像我想象的那样发挥作用?
我该如何编写宏
\aetest
才能让它执行我想要的操作?即,\aetest{<arg>}->\textcolor{red}{\fbox{<arg>}}
我想我可以使用令牌来做一些技巧(因为令牌内的令牌\edef
只扩展一次),但是我对这种方法有点不清楚,因为我没有用过太多令牌。
顺便说一句,我\ae@test
这样写是因为我对以下情况不感兴趣:
\def\ae@test#1{\textcolor{red}{#1}}
以下\aetest
内容可以实现我想要的
\edef\aetest#1{\expandafter\noexpand\ae@test{#1}}
问题是我想扩展一个可能包含几个脆弱元素的宏。
我确实尝试了以下方法
\edef\aetest#1{\unexpanded\expandafter{\ae@test{#1}}}
以为这样\ae@test
就能抓住它的论点,但问题仍然是相同的。
答案1
在里面会#
加倍\unexpanded
。解决方法是\expandafter
:
\documentclass{article}
\usepackage{xcolor}
\makeatletter
\def\ae@test#1{\textcolor{red}{\fbox{#1}}}
\expandafter\def\expandafter\aetest\expandafter#\expandafter1\expandafter{%
\ae@test{#1}%
}
\makeatother
\begin{document}
\aetest{Hello}
\end{document}
则\aetest
有如下定义:
macro:#1->\textcolor {red}{\fbox {#1}}
当然在这种情况下,简单的表达\let
更容易表达同样的意思:
\let\aetest\ae@test
#1
一种相当复杂的方法是用不同的令牌替换,然后进行交换:
\usepackage{etoolbox}
\def\ae@test#1{\textcolor{red}{\fbox{#1}}}
\let\myarg\relax
\edef\aetest#1{\expandonce{\ae@test{\myarg/}}}
\patchcmd\aetest{\myarg/}{#1}{}{%
\errmessage{Patching \noexpand\aetest failed}%
}
#1
最后一步需要重复进行,直到在 的定义文本中出现 的次数\ae@test
。如果次数未知,则可以使用循环,在第一次出现错误时中止:
\let\myarg\relax
\edef\aetest#1{\expandonce{\ae@test{\myarg/}}}
\count@=\@ne
\@whilenum\count@>\z@\do{%
\edef\process@me{%
\noexpand\patchcmd\noexpand\aetest{\noexpand\myarg/}{\string#1}%
}\process@me{}{\count@=\z@}%
}
临时计数器\count@
表示修补成功。如果将其设置为 1,则尝试替换\myarg
。#1
如果修补失败,因为\myarg
无法再找到,则将其\count@
设置为零并中止循环。但如果修补失败有其他原因,则不会检测到。
的规避\process@me
是一种技巧,可以防止的#
第三个参数中的重复\patchcmd
,因为它被用作另一个命令的参数(\@whilenum
)。
评论:
\myarg/
用于#1
,否则\patchcmd
不高兴,如果后面有空格#1
。
改进(由 egreg 完成)
软件包regexpatch
提供了\xpatchcmd
一个星号形式,可以替换所有匹配,而无需手动编写循环:
\documentclass{article}
\usepackage{xcolor}
\usepackage{etoolbox}
\usepackage{regexpatch}
\makeatletter
\def\ae@test#1{%
\typeout{\detokenize{** #1 **}}%
\textcolor{red}{\fbox{#1}}%
}
\let\myarg\relax
\edef\aetest#1{\expandonce{\ae@test{\myarg/}}}
\xpatchcmd*{\aetest}{\myarg/}{#1}{}{%
\errmessage{Patching \noexpand\aetest failed}%
}
\makeatother
\begin{document}
\aetest{Hello}
\end{document}
\myarg/
也需要\myarg
来代替,因为后面\xpatchcmd
有一个空格。#1
\ae@test
答案2
您可以使用\unexpanded
以下代码从 eTeX 使用:
\def\aeAtest#1{\textcolor{red}{\fbox{#1}}}
\def\eonce#1{\unexpanded\expandafter{#1{##1}}}
\edef\tmp{\def\noexpand\OUT##1{expanded before \eonce{\aeAtest} after.}}\tmp
\message{\meaning\OUT}
% \OUT#1 -> expanded before \textcolor {red}{\fbox {#1}} after.
编辑相同思想的另一种用法定义\oedef
宏:
\def\eonce#1#2{\unexpanded\expandafter{#1{#2}}}
\def\eodef#1#2{\edef\tmp{\def\noexpand#1####1{#2}}\tmp}
\def\aeAtest#1{\textcolor{red}{\fbox{#1},#1}}
\eodef\aetest{expanded ##1+y before \eonce\aeAtest{#1+x} after.}
\message{\meaning\aetest}
% \aetest#1 -> expanded #1+y before \textcolor {red#1}{\fbox {#1+x},#1+x} after.
不幸的是,该\eodef
宏有一个奇怪的语法:参数不能出现在参数掩码中,并且该参数必须像##1
在“正常”位置一样输入,但像#1
在的参数中一样输入\eonce
。
答案3
为了解决@HeikoOberdiek 指出的扩展问题,我提出了以下解决方法
\edef\aetest#1{\noexpand\def\noexpand\ae@tmp{#1}%%
\unexpanded\expandafter{\ae@test{\ae@tmp}}}
不过我必须进行一些测试才能确定它确实能实现我想要的效果。
答案4
我怀疑这对于评论来说太长了,所以我就把它放在这里。下面这段话对我帮助很大,但我没有看到任何地方提到它。它可能非常明显,但也可能不是。
考虑以下情况。你有两个宏,一个要扩展,另一个必须保存扩展的定义:
\def\expandthis#1{\textcolor{red}{\fbox{#1}}}
\def\macrowithexpandeddefinition#1{\expandthis{#1}}
为了得到你想要的东西,你必须逆向而行(你很快就会明白我的意思)。这需要一些关于标记的概念的知识,我假设你对此很熟悉。这个诡计必须在 行中发生。\def\macrowithexpandeddefinition#1
为了扩展\expandthis
一次,我们只需到达\
该宏的转义字符,而无需扩展之前的任何内容。
步骤 1:跳过 之前的最后一个标记\expandthis
,即大括号{
:
\def\macrowithexpandeddefinition#1\expandafter{\expandthis{#1}}
^^^^^^^^^^
第 2 步:现在我们必须到达第一个\expandafter
。它之前的最后一个标记是1
:
\def\macrowithexpandeddefinition#\expandafter1\expandafter{\expandthis{#1}}
^^^^^^^^^^
步骤 3 及后续:在此之前的最后一个标记是#
(哈希)标记,继续:
\def\macrowithexpandeddefinition\expandafter#\expandafter1\expandafter{\expandthis{#1}}
^^^^^^^^^^
并分别对最后两个标记\macrowithexpandeddefinition
和重复\def
:
\expandafter\def\expandafter\macrowithexpandeddefinition\expandafter#\expandafter1\expandafter{\expandthis{#1}}
您可以对任何您喜欢的宏重复这些步骤,以实现细粒度扩展控制,但这不会使代码非常易读。另请注意,我最终得到的结果与 Heiko 的“解决方法”完全相同。