用于添加带星号版本的命令的宏

用于添加带星号版本的命令的宏

如何编写一个元宏来为命令添加带星号的版本?

预期用途如下

\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}

已处理

  1. 保存原始\foo命令

    \csname WSF:\string\foo\endcsname
    

    (如果这已经存在,因为前面的\WithSuffix应用\foo当然会被省略)

  2. 保存新定义

    \csname WSF:\string\foo\space the character *\endcsname
    
  3. 使用上面描述的抽象接口在不同的后缀中进行选择。

答案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}

结果:

MWE 输出

解释:

  • 该命令的当前定义的副本\foo存储为\\foo@nostar
  • 该命令\foo被重新定义为检查星号并调用 或\\foo@star\\foo@nostar这样做是edef为了构造的标记名称可以在适当位置扩展,而不是每次调用命令时都扩展。
  • for\newcommand\\foo@star启动并将采用如下其余定义\addstarred\foo

相关内容