如何精确扩展一次带有参数的宏

如何精确扩展一次带有参数的宏

我以为我终于理解了扩展,但是当我写了一些类似下面的东西时,我却有点困惑。

\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 的“解决方法”完全相同。

相关内容