\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
{}
the\csname...\endcsname
star options?\expandafter
:\expandafter\newcommand\expandafter{\csname nostar\commandname{#2}\endcsname}{<code for #2>}
. Otherwise, you are only epanding the{