1

I want to define a command, let's call it \commander, for defining starred and non-starred versions of a new command. \commander would be called as follows:

\commander[<options>]{\thenewcommand}

This creates the commands \thenewcommand and \thenewcommand*, with slightly different instructions. (The specific definitions of those commands are of no importance for this question. I've already worked them out.) The way I tried to define \commander depends on a one-argument command called \commandname, which returns the string of the name of a command, but without its backslash. For example:

  1. \commandname{\empty} returns the string empty
  2. \commandname{\color} returns the string color
  3. \commandname{\thenewcommand} returns the string thenewcommand
  4. And so forth...

I don't think that there's any problem with \commandname since it has worked perfectly up until my attempt of defining \commander. But just in case, here is its definition:

\newcommand{\commandname}[1]{%
  \ifcat\relax\noexpand#1%
  \expandafter\expandafter\expandafter%
  \@gobble\expandafter\string\fi#1}

(This was poposed by Ashok.)

Any suggestion for improving or simplifying this \commandname is welcome.

\commander is then defined roughly as follows:

\newcommand{\commander}[2][]{%
  \expandafter\newcommand{#2}{%
    \@ifstar%
      \csname star\commandname{#2}\endcsname%
      \csname nostar\commandname{#2}\endcsname}%
  %
  %DEFINE THE #2 COMMAND
  \expandafter\newcommand{\csname nostar\commandname{#2}\endcsname}%
    {<code for #2>}
  %
  %DEFINE THE #2* COMMAND
  \expandafter\newcommand{\csname star\commandname{#2}\endcsname}%
    {<code for #2*>}%
}

But whenever I attempt to call \commander, it returns the following error messages:

Command \csname already defined. \commander{\thenewcommand}
Extra \endcsname. \commander{\thenewcommand}

I have previously used a similar structure for recursively defining commands, so I suppose the problem is in the simultaneous use of \@ifstar and \csname.

I would strongly prefer that the solution be as TeX primitive as possible because I would like to use it in several projects. In my current project, though, I am using the packages everypage and pgf/tikz. So a solution depending on those packages is also welcome.

Thanks in advance for your help.

  • 1
    what happens if you embrace {} the \csname...\endcsname star options? – Steven B. Segletes Sep 16 at 20:56
  • 1
    Thanks @StevenB.Segletes. I've just tried it, but no difference: the same error messages. – lfba Sep 16 at 20:57
  • 2
    You need an extra \expandafter: \expandafter\newcommand\expandafter{\csname nostar\commandname{#2}\endcsname}{<code for #2>}. Otherwise, you are only epanding the { – Steven B. Segletes Sep 16 at 21:01
  • 2
    Your question (as true for most questions) would be much easier to answer if you provided a small complete document that produces the error rather than just posting disconnected fragments. – David Carlisle Sep 16 at 21:24
  • 2
    if you don't provide a test file you force anyone answering to guess how to put the fragments together and make their own test file as Skillmon had to do here, but that makes answering harder and if the guess is wrong the effort is wasted. – David Carlisle Sep 16 at 21:33
1

You don't need to extract the name without the backslash for the \csname. You could use something like the following:

\documentclass[]{article}

\makeatletter
\newcommand\commander[2][]
  {%
    \@ifdefinable#2%
      {%
        \protected\edef#2%
          {%
            \noexpand\@ifstar
              \expandafter\noexpand\csname\string#2star\endcsname
              \expandafter\noexpand\csname\string#2nostar\endcsname
          }%
        \expandafter\newcommand\csname\string#2star\endcsname{.star.}%
        \expandafter\newcommand\csname\string#2nostar\endcsname{no.star.}%
      }%
  }
\makeatother

\commander\thecommand

\begin{document}
\thecommand

\thecommand*
\end{document}
  • Thanks @Skillmon. What is the \@ifdefinableconditional for? – lfba Sep 16 at 21:14
  • 1
    @lfba checks whether the macro is already defined, if it is, it throws an error just as \newcommand would. If it isn't the second argument is executed. I then use \protected\edef instead of \newcommand so that the \csnames are only evaluated once and not on every call, also the macro has to be robust (hence the \protected), as \@ifstar isn't expandable. – Skillmon Sep 16 at 21:19
  • 1
    @lfba if you used \newcommand or \def instead of \edef you'd have to use braces around the \csnames after the \@ifstar, and you wouldn't need \expandafter\noexpand before the \csnames. – Skillmon Sep 16 at 21:21
  • Thanks, @Skillmon. It has been very instructive. – lfba Sep 16 at 21:24
  • 1
    @lfba \string can be used anywhere. Just try \string\foo in the document body (you'll need to use \texttt{\string\foo} for the backslash to be printed as such). – Skillmon Sep 17 at 6:14
3
  \expandafter\newcommand{\csname nostar\commandname{#2}\endcsname}%

applies \expandafter to { which is a non-expandable token.

You intended

  \expandafter\newcommand\csname nostar\commandname{#2}\endcsname

to apply \expandafter to \csname.

2
\newcommand{\commandname}[1]{%
  \ifcat\relax\noexpand#1%
  \expandafter\expandafter\expandafter%
  \@gobble\expandafter\string\fi#1}

Any suggestion for improving or simplifying this \commandname is welcome.

You asked, so let's do some nitpicking: ;-)

Your routine \commandname seems to be intended to do the following things:

Detect whether the argument consists of a single ⟨control sequence token⟩.
If so, apply \string and remove the leading backslash from the result for obtaining something that can be "fed" to \csname..\endcsname.
If not so, take the token sequence which forms the argument for something that can be "fed" to \csname..\endcsname.

Issue 1:

There are some edge-cases:

How to handle, e.g., the situation that one wishes to define a macro \SantaClaus from macro \Santa and macro \CLaus while \Santa is defined as \def\Santa{Santa} and \Claus is defined as \def\Claus{Claus}?

In this scenario, one could do \expandafter\edef\csname\Santa\Claus\endcsname{\Santa\noexpand\nobreak\ \Claus}.

The crucial point here is: You can "feed" macros to \csname. Expandable tokens will be expanded while (La)TeX searches the matching \endcsname.

Let's look at \commandname{\Santa\Claus}—this yields:

\ifcat\relax\noexpand\Santa\Claus
\expandafter\expandafter\expandafter
\@gobble\expandafter\string\fi\Santa\Claus

\Claus Santa\Claus

I suppose, this is not intended.

Issue 2:

\commandname may fail with control sequences that are implicit character tokens:

E.g., try

\let\CmdCharA=A
\commandname{\CmdCharA}

The problem is:

Here \CmdCharA has the meaning of the character token A whose category code is 11(letter) and whose character code is 65.

Therefore the \ifcat-check will yield that \CmdCharA's catcode (which is 11(letter)) will differ from what by \ifcat internally is taken for the catcode of the \relax-primitive.

Therefore stringification and gobbling will not take place.

Issue 3:

In TeX there is an integer-parameter \escapechar which denotes the number of the code point in the TeX engine's internal encoding of the chacter which shall be prepended in case \string delivers the name of a ⟨control sequence token⟩. Usually the value of \escapechar is 92. 92 is the number of the code point of the backslash character both in ASCII-encoding and in Unicode while the internal encoding with traditional TeX engines is ASCII and with XeTeX- or LuaTeX-based TeX engines is Unicode. In case the value of \escapechar is negative, no character will be prepended at all. In case the value of \escapechar is greater than the largest code point number which is possible in the TeX engine's internal encoding, the situation is as follows: With traditional TeX engines no character will be prepended. With most recent XeTeX- or LuaTeX-based engines no character will be prepended. With LuaTeX-based engines prior to luatex 1.10.0, you can get unexpected results. (See, e.g., my question \string-primitive works in unexpected ways in LuaTeX — Where can I find exact documentation of the \string-primitive in LuaTeX? and David Carlisle's answer to that question.)

Try e.g.,

\newlinechar=`^^J
\escapechar=92 %
\def\stringifyrelaxwithescapechar#1{%
  \message{^^J^^J}%
  \message{When \string\escapechar=\number#1, then (\string\string\string\relax) yields:}%
  \escapechar=#1\relax
  \message{(\string\relax)}%
  \escapechar=92 %
}%
\message{^^J^^J}%
\message{The great \string\string\space and \string\escapechar\space test:}%
\message{^^J}%
\message{=======================================}%
\stringifyrelaxwithescapechar{92}%
\stringifyrelaxwithescapechar{`\A}%
\stringifyrelaxwithescapechar{`\B}%
\stringifyrelaxwithescapechar{`\C}%
\stringifyrelaxwithescapechar{`\Z}%
\stringifyrelaxwithescapechar{`\7}%
\stringifyrelaxwithescapechar{`\/}%
\stringifyrelaxwithescapechar{`\?}%
\stringifyrelaxwithescapechar{`\:}%
\stringifyrelaxwithescapechar{-1}%
\stringifyrelaxwithescapechar{32}%
\message{^^J^^J}%
\csname stop\endcsname
\bye

By the way: Character tokens delivered due to \string usually have the category code 12(other). An exception is the space character. If \string delivers a space character token, that token will have category code 10(space), i.e., it will be a space token. When the value of \escapechar is 32, then applying \string to a ⟨control sequence token⟩ yields a token sequence with a leading space token. You cannot have that space token removed via \@gobble because \@gobble will process/remove the following non-delimited argument while (La)TeX silently discards space tokens while searching the very first token that belongs to a non-delimited argument. Therefore in this edge case the leading space and the following non-space-character would be removed.

Summa summarum: When applying \string you may need to take the value of \escapechar into account when deciding whether a leading backslash (or whatever) needs to be removed.

I suggest a different approach:

As the mechanism is intended for defining macros, there is no need for it to work in pure-expansion-contexts as well.

Thus you can temporarily assign, e.g., the value 92 (92 is the code-point number of the backslash-character in (La)TeX's internal encoding) to \escapechar.

Besides this, don't let the mechanism try to find out whether the user specified a ⟨control sequence token⟩ or a token sequence that needs to be "fed" to \csname..\endcsname but let the users specify that themselves.

I sometimes use a macro \name which processes an argument which is delimited by a left curly brace ({) and an argument which is nested in braces.
The argument nested in braces is taken for the name of a ⟨control sequence token⟩ which is to be constructed via \csname..\endcsname. In the ⟨parameter text⟩ of a macro-definition you can use #{-notation for a macro whose last argument will be delimited by a { which will (unlike other argument-delimiters) be left in place/be re-insered:

\newcommand\exchange[2]{#2#1}%
\@ifdefinable\name{\long\def\name#1#{\romannumeral0\innername{#1}}}%
\newcommand\innername[2]{\expandafter\exchange\expandafter{\csname#2\endcsname}{ #1}}%

Some usage-examples:

\name{foo}\foo
\name\string{foo}\string\foo
\name\meaing{foo}\meaning\foo
\name\global\long\def{foo}...\global\long\def\foo...
\name\newcommand*{foo}...\newcommand*\foo...
\name\name\global\let{foo}={bar}\name\global\let\foo={bar}\global\let\foo=\bar

Putting the pieces together, assuming that the "starred" form of the command shall always process the same set of arguments/shall (apart from the star) have the same ⟨parameter text⟩ as the "non-starred" form, I would probably do something like this:

\makeatletter

\newcommand\exchange[2]{#2#1}%
\@ifdefinable\name{\long\def\name#1#{\romannumeral0\innername{#1}}}%
\newcommand\innername[2]{\expandafter\exchange\expandafter{\csname#2\endcsname}{ #1}}%

% \myUnexpandableStringifier{<tokens to prepend after stringification>}%
%                           {<control sequence token to stringify>}%
% ->
% <tokens to prepend after stringification>{<stringified control sequence token without leading escapechar>}
\newcommand\myUnexpandableStringifier[2]{%
  \begingroup
  \escapechar=92 %
  \expandafter\expandafter\expandafter\exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter{%
  \expandafter\@gobble\string#2}}{\endgroup#1}%
}%
\@ifdefinable\commander{%
  \DeclareRobustCommand\commander[1]{%
     \myUnexpandableStringifier{\innercommander}{#1}%
  }%
}%
\@ifdefinable\innercommander{%
  \long\def\innercommander#1#2#{%
    % #1 = <name of control sequence>
    % #2 = <(same) parameter-text both for starred variant and non-starred variant>
    \name\name\name\innerinnercommander{#1}{#1atnostar}{#1atstar}{#2}%
  }%
}%
\newcommand\innerinnercommander[6]{%
   % #1 = <control sequence>
   % #2 = <control sequence at nostar>
   % #3 = <control sequence at star>
   % #4 = <(same) parameter-text both for starred variant and non-starred variant>
   % #5 = <<balanced text> of the <definition text> of the unstarred definition>
   % #6 = <<balanced text> of the <definition text> of the starred definition>
  \@ifdefinable{#1}{%
    \DeclareRobustCommand*#1{\@ifstar{#3}{#2}}%
    \newcommand#2#4{#5}%
    \newcommand#3#4{#6}%
  }%
}%

\commander\Foo[3][optional]{%
  This is Foo-without-star's optional argument: #1%
  This is Foo-without-star's first non-optional argument: #2%
  This is Foo-without-star's second non-optional argument: #3%
}{%
  This is Foo-with-star's optional argument: #1%
  This is Foo-with-star's first non-optional argument: #2%
  This is Foo-with-star's second non-optional argument: #3%
}%
\typeout{Meanings of macros related to \string\Foo}
\typeout{==================================}
\typeout{\string\Foo: \meaning\Foo}
\typeout{\name\string{Foo }: \name\meaning{Foo }}
\typeout{\string\Fooatnostar: \meaning\Fooatnostar}
\typeout{\name\string{\string\Fooatnostar}: \name\meaning{\string\Fooatnostar}}
\typeout{\string\Fooatstar: \meaning\Fooatstar}
\typeout{\name\string{\string\Fooatstar}: \name\meaning{\string\Fooatstar}}
\typeout{^^J}

\name\commander{Bar}[2]{%
  This is Bar-without-star's first non-optional argument: #1%
  This is Bar-without-star's second non-optional argument: #2%
}{%
  This is Bar-with-star's first non-optional argument: #1%
  This is Bar-with-star's second non-optional argument: #2%
}%

\typeout{Meanings of macros related to \string\Bar}
\typeout{==================================}
\typeout{\string\Bar: \meaning\Bar}
\typeout{\name\string{Bar }: \name\meaning{Bar }}
\typeout{\string\Baratnostar: \meaning\Baratnostar}
\typeout{\string\Baratstar: \meaning\Baratstar}
\typeout{^^J}

\stop
1

The error is in bracing \csname...\endcsname after \newcommand.

However, I think you can do better by avoiding \commander altogether. If you insist on it, you can use xparse:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\commander}{mmo}
 {% #1 = command to define, #2 = code for the main, #3 = code for *-version
  \IfNoValueTF{#3}
   {
    \NewDocumentCommand{#1}{}{#2}
   }
   {
    \NewDocumentCommand{#1}{s}
     {
      \IfBooleanTF{##1}{#3}{#2}
     }
   }
 }
\ExplSyntaxOff

\commander{\foo}{This is foo, it has no *-variant}
\commander{\baz}{This is baz, normal variant}[This is baz, *-variant]

\begin{document}

\foo

\baz

\baz*

\end{document}

enter image description here

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.