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: tex.stackexchange.com/a/4388/3061 – quinmars Sep 22 '14 at 9:05
  • Could you maybe explain how the \WithSuffix command works? – user53911 Sep 22 '14 at 9: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 9: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 9: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. – user53911 Sep 22 '14 at 10:03
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.

  • 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. – user53911 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 9:09
  • Ah, interesting. Maybe the name of the commands should have been a little less obvious. – user53911 Sep 22 '14 at 9:12
  • I have since edited the answer to be an epsilon amount less obvious. – user53911 Sep 22 '14 at 9: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 9: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. – user53911 Sep 22 '14 at 9:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy