如何编写一个元宏来为命令添加带星号的版本?
预期用途如下
\newcommand\foo[1]{foo is #1}
\addstarred\foo[2]{foo is #1, bar is #2}
答案1
David Kastrup 的软件包中已经提供了一种方法suffix
。不用说,它充满了巧妙的技巧。
你可以说
\usepackage{suffix}
\newcommand{\foo}[1]{foo is #1}
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
了解如何实现这一目标可能会很有启发。
如果我们\show\foo
在第二条指令之后执行此操作,我们会发现
> \foo=\protected macro:
->\WSF@suffixcheck \foo .
因此,我们知道suffix
需要 e-TeX(现在不是问题)并重新定义\foo
为\WSF@suffixcheck\foo
。因此,我们添加\makeatletter
并尝试\show\WSF@suffixcheck
,得到
> \WSF@suffixcheck=macro:
#1->\begingroup \def \reserved@a {#1}\futurelet \reserved@b \WSF@suffixcheckii
因此参数被保存\reserved@a
在
\futurelet\reserved@b\WSF@suffixcheckii
执行。这使得\reserved@b
等同于 后面的标记\WSF@suffixcheckii
。如果调用
\foo{foo}
那么\reserved@b
将是\bgroup
;如果调用
\foo*{foo}{bar}
那么\reserved@b
将是*
。现在我们需要知道\WSF@suffixcheckii
:
> \WSF@suffixcheckii=macro:
->\ifcsname \expandafter \SuffixName \reserved@a \reserved@b \endcsname
\expandafter \WSF@suffixcheckiii \else \expandafter \WSF@suffixcheckiv \fi .
好的,让我们看看在这种\foo{foo}
情况下会发生什么:\reserved@a
扩展为\foo
,而\reserved@b
是\bgroup
(不可扩展),因此 TeX 首先呈现的是
\ifcsname\SuffixName\foo\reserved@b\endcsname
并\SuffixName
定义为
> \SuffixName=\long macro:
#1->WSF:\string #1 \meaning .
所以下一步是
\ifcsname WSF:\string\foo \meaning\reserved@b\endcsname
我们最终得到了
\ifcsname WSF:\foo begin-group character {\endcsname
其中所有字符的类别代码为 12(但空格的类别代码为 10)。在这种\foo*{foo}{bar}
情况下,我们将得到
\ifcsname WSF:\foo the character *\endcsname
该命令\csname WSF:\foo begin-group character {\endcsname
未定义,因此遵循错误分支,即
\expandafter \WSF@suffixcheckiv \fi
这使得
\WSF@suffixcheckiv{foo}
在输入流中。现在\show\WSF@suffixcheckiv
给出
> \WSF@suffixcheckiv=macro:
->\expandafter \endgroup \csname \expandafter \NoSuffixName \reserved@a \endcsname .
因此之前开设的小组已关闭,但首先
\csname \expandafter \NoSuffixName \reserved@a \endcsname
形成。回想一下,\reserved@a
展开为\foo
,所以我们得到
\csname \NoSuffixName \foo \endcsname
并且\NoSuffixName
是
> \NoSuffixName=macro:
->WSF:\string .
所以最后我们得到
\csname WSF:\string\foo\encsname
好的,让我们发布\expandafter\show\csname WSF:\string\foo\endcsname
:
> \WSF:\foo=\long macro:
#1->foo is #1.
也就是说,这个复杂的宏是原始宏的副本\foo
。
如果\foo*{foo}{bar}
我们有
\ifcsname WSF:\foo the character *\endcsname
但在这种情况下是已定义;确实
\expandafter\show\csname WSF:\string\foo\space the character *\endcsname
生产
> \WSF:\foo the character *=\long macro:
#1#2->foo is #1, bar is #2.
所以这个具有复杂名称的宏就是您定义为*
-variant的。
使用此包,几乎任何标记都可以用作后缀。但基本思想与你所设计的没有什么不同;对覆盖可能存在的宏名称的保护更好。包在什么时候做什么
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
已处理
保存原始
\foo
命令\csname WSF:\string\foo\endcsname
(如果这已经存在,因为前面的
\WithSuffix
应用\foo
当然会被省略)保存新定义
\csname WSF:\string\foo\space the character *\endcsname
使用上面描述的抽象接口在不同的后缀中进行选择。
答案2
以下是我自己尝试的解决方案,并由@egreg 和@DavidCarlisle 提供改进。
\documentclass{standalone}
\makeatletter
\newcommand\addstarred[1]{%
\expandafter\let\csname\string#1@nostar\endcsname#1%
\edef#1{\noexpand\@ifstar\expandafter\noexpand\csname\string#1@star\endcsname\expandafter\noexpand\csname\string#1@nostar\endcsname}%
\expandafter\newcommand\csname\string#1@star\endcsname%
}
\makeatother
\newcommand\foo[1]{foo is #1}
\addstarred\foo[2]{foo is #1, bar is #2}
\begin{document}
\foo{red} --- \foo*{red}{green}
\end{document}
结果:
解释:
- 该命令的当前定义的副本
\foo
存储为\\foo@nostar
。 - 该命令
\foo
被重新定义为检查星号并调用 或\\foo@star
。\\foo@nostar
这样做是edef
为了构造的标记名称可以在适当位置扩展,而不是每次调用命令时都扩展。 - for
\newcommand
已\\foo@star
启动并将采用如下其余定义\addstarred\foo
。