How can one write a meta-macro that adds a starred version to a command?
Intended use would be along the lines of
\newcommand\foo[1]{foo is #1}
\addstarred\foo[2]{foo is #1, bar is #2}
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It only takes a minute to sign up.
Sign up to join this communityHow can one write a meta-macro that adds a starred version to a command?
Intended use would be along the lines of
\newcommand\foo[1]{foo is #1}
\addstarred\foo[2]{foo is #1, bar is #2}
A method is already available with the package suffix
by David Kastrup. Needless to say, it's full of clever tricks.
You can say
\usepackage{suffix}
\newcommand{\foo}[1]{foo is #1}
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
and it may be instructive to see how the objective is achieved.
If we do \show\foo
after the second instruction, we find
> \foo=\protected macro:
->\WSF@suffixcheck \foo .
so we learn that suffix
requires e-TeX (not a problem nowadays) and redefines \foo
to mean \WSF@suffixcheck\foo
. So we add \makeatletter
and try \show\WSF@suffixcheck
, getting
> \WSF@suffixcheck=macro:
#1->\begingroup \def \reserved@a {#1}\futurelet \reserved@b \WSF@suffixcheckii
so the argument is saved in \reserved@a
and
\futurelet\reserved@b\WSF@suffixcheckii
is executed. This makes \reserved@b
to be equivalent to the token that follows \WSF@suffixcheckii
. If the call is
\foo{foo}
then \reserved@b
will be \bgroup
; if the call is
\foo*{foo}{bar}
then \reserved@b
will be *
. Now we need to know what \WSF@suffixcheckii
does:
> \WSF@suffixcheckii=macro:
->\ifcsname \expandafter \SuffixName \reserved@a \reserved@b \endcsname
\expandafter \WSF@suffixcheckiii \else \expandafter \WSF@suffixcheckiv \fi .
OK, let's see what happens in the \foo{foo}
case: \reserved@a
expands to \foo
, while \reserved@b
is \bgroup
(unexpandable), so TeX is first presented with
\ifcsname\SuffixName\foo\reserved@b\endcsname
and \SuffixName
is defined by
> \SuffixName=\long macro:
#1->WSF:\string #1 \meaning .
so the next step is
\ifcsname WSF:\string\foo \meaning\reserved@b\endcsname
and we finally get
\ifcsname WSF:\foo begin-group character {\endcsname
where all characters have category code 12 (but spaces have 10). In the \foo*{foo}{bar}
case we would get
\ifcsname WSF:\foo the character *\endcsname
The command \csname WSF:\foo begin-group character {\endcsname
is not defined, so the false branch is followed, that is
\expandafter \WSF@suffixcheckiv \fi
which simply leaves
\WSF@suffixcheckiv{foo}
in the input stream. Now \show\WSF@suffixcheckiv
gives
> \WSF@suffixcheckiv=macro:
->\expandafter \endgroup \csname \expandafter \NoSuffixName \reserved@a \endcsname .
so the group previously opened is closed but first
\csname \expandafter \NoSuffixName \reserved@a \endcsname
is formed. Recall that \reserved@a
expands to \foo
, so we get
\csname \NoSuffixName \foo \endcsname
and \NoSuffixName
is
> \NoSuffixName=macro:
->WSF:\string .
so finally we obtain
\csname WSF:\string\foo\encsname
OK, let's issue \expandafter\show\csname WSF:\string\foo\endcsname
:
> \WSF:\foo=\long macro:
#1->foo is #1.
that is, this complicated macro is a copy of the original \foo
.
In the case of \foo*{foo}{bar}
we'd have
\ifcsname WSF:\foo the character *\endcsname
but in this case this is defined; indeed
\expandafter\show\csname WSF:\string\foo\space the character *\endcsname
produces
> \WSF:\foo the character *=\long macro:
#1#2->foo is #1, bar is #2.
so this macro with a complicated name is what you have defined as *
-variant.
Almost any token can be used as a suffix, with this package. But the essential idea is no different from what you have devised; the protections against overwriting possible existing macro names are better. What the package does when
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
is processed is
Save the original \foo
command under
\csname WSF:\string\foo\endcsname
(if this already exists because of a preceding \WithSuffix
applied to \foo
this step is of course omitted)
Save the new definition under
\csname WSF:\string\foo\space the character *\endcsname
Use the abstract interface described above to choose among different suffixes.
if next-token == '*'
into a switch
-like construct selecting whatever command has been defined for a given suffix.
xparse
method.
My own attempt at a solution is below, with improvements kindly provided by @egreg and @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}
Result:
Explanation:
\foo
is stored as \\foo@nostar
.\foo
is redefined to check for a star and call either \\foo@star
or \\foo@nostar
.
This is done with edef
so that the constructed token names can be expanded in place and not every time the command is invoked.\newcommand
for \\foo@star
is started and will take the rest of the definition as follows \addstarred\foo
.\foo
in \\foo
will definitely conflict if \foo
had been defined to have an optional argument.
%
on each of the lines of the definition that doesn't have a %
and do not need a %
on the line that has one.
Sep 22, 2014 at 9:35
%
to all lines now. I assumed that, since this should appear in the preamble, extra line breaks would not matter.
\WithSuffix
? Than this answer can be usefull: tex.stackexchange.com/a/4388/3061\WithSuffix
command works?\WithSuffix
works. It does quite similarly to what you propose, by the way. The code is\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
(with\usepackage{suffix}
).