11

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}
  • 1
    Something like \WithSuffix? Than this answer can be usefull: http://tex.stackexchange.com/a/4388/3061 – quinmars Sep 22 '14 at 09:05
  • Could you maybe explain how the \WithSuffix command works? –  Sep 22 '14 at 09:11
  • Sorry I have no idea how it works internally. But a possible use case is shown in the linked answer. – quinmars Sep 22 '14 at 09:13
  • This is definitely a duplicate. Maybe you want to turn it into a question about how \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}). – egreg Sep 22 '14 at 09:44
  • I do not actually ask how to define starred versions, I can do that quickly enough with three separate commands. Being a programmer, I am curious how to do macro-writing macros in LaTeX. And I figured this is an interesting case that is actually useful. –  Sep 22 '14 at 10:03
  • @Nicolas Are we to assume that \foo is defined before the use of \addstarred\foo? Do we have to allow for LaTeX 'robust' commands (I assume we do)? What is expected if \foo is redefined after \addstarred or if \addstarred is applied multiple times? – Joseph Wright Sep 22 '14 at 10:56
  • The only way that seemed possible to me was to assume that \foo was already defined and to add the \@ifstar switch to its definition. I have mostly asked to find out whether there are other, more natural possibilities. The answer "from the Book" would probably be a simple \newcommand\foo* that is impossible to break. –  Sep 22 '14 at 11:06

2 Answers2

9

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

  1. 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)

  2. Save the new definition under

    \csname WSF:\string\foo\space the character *\endcsname
    
  3. Use the abstract interface described above to choose among different suffixes.

egreg
  • 1,121,712
  • Great explanation. If I understood the method well, this basically turns the simplistic if next-token == '*' into a switch-like construct selecting whatever command has been defined for a given suffix. –  Sep 22 '14 at 12:54
  • @Nicolas Yes, and in a clever way for transforming the suffix into a string and avoiding expansion problems. However I much prefer the xparse method. – egreg Sep 22 '14 at 13:18
4

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:

MWE output

Explanation:

  • A copy of the current definition of the command \foo is stored as \\foo@nostar.
  • The command \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.
  • A \newcommand for \\foo@star is started and will take the rest of the definition as follows \addstarred\foo.
  • Storing \foo in \\foo will definitely conflict if \foo had been defined to have an optional argument. – egreg Sep 22 '14 at 09:09
  • Ah, interesting. Maybe the name of the commands should have been a little less obvious. –  Sep 22 '14 at 09:12
  • I have since edited the answer to be an epsilon amount less obvious. –  Sep 22 '14 at 09:21
  • You need a % on each of the lines of the definition that doesn't have a % and do not need a % on the line that has one. – David Carlisle Sep 22 '14 at 09:35
  • @DavidCarlisle I have added the % to all lines now. I assumed that, since this should appear in the preamble, extra line breaks would not matter. –  Sep 22 '14 at 09:45
  • newcommands don't have to appear in the preamble (and there was a time that saving three bytes mattered:-) for similar reasons I'd do \edef#1{\@ifstar\expandafter\noexpand\csname\string#1@star\endcsname\expandafter\noexpand\csname\string#1@nostar\endcsname}}% so that the defined command just had two tokens after ifstar with the new command names made at define time – David Carlisle Sep 22 '14 at 10:13
  • @DavidCarlisle Thank you, these tips are the exact reason why I asked this question. –  Sep 22 '14 at 10:37