
¿Cómo se puede escribir una metamacro que agregue una versión destacada a un comando?
El uso previsto estaría en la línea de
\newcommand\foo[1]{foo is #1}
\addstarred\foo[2]{foo is #1, bar is #2}
Respuesta1
Ya hay un método disponible con el paquete suffix
de David Kastrup. No hace falta decir que está lleno de trucos ingeniosos.
Puedes decir
\usepackage{suffix}
\newcommand{\foo}[1]{foo is #1}
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
y puede resultar instructivo ver cómo se logra el objetivo.
Si lo hacemos \show\foo
después de la segunda instrucción, encontramos
> \foo=\protected macro:
->\WSF@suffixcheck \foo .
entonces aprendemos que suffix
requiere e-TeX (no es un problema hoy en día) y se redefine \foo
como significado \WSF@suffixcheck\foo
. Así que sumamos \makeatletter
y probamos \show\WSF@suffixcheck
, obteniendo
> \WSF@suffixcheck=macro:
#1->\begingroup \def \reserved@a {#1}\futurelet \reserved@b \WSF@suffixcheckii
entonces el argumento se guarda en \reserved@a
y
\futurelet\reserved@b\WSF@suffixcheckii
es ejecutado. Esto hace \reserved@b
que sea equivalente al token que sigue \WSF@suffixcheckii
. Si la llamada es
\foo{foo}
entonces \reserved@b
será \bgroup
; si la llamada es
\foo*{foo}{bar}
entonces \reserved@b
será *
. Ahora necesitamos saber qué \WSF@suffixcheckii
hace:
> \WSF@suffixcheckii=macro:
->\ifcsname \expandafter \SuffixName \reserved@a \reserved@b \endcsname
\expandafter \WSF@suffixcheckiii \else \expandafter \WSF@suffixcheckiv \fi .
Bien, veamos qué sucede en el \foo{foo}
caso: \reserved@a
se expande a \foo
, mientras que \reserved@b
es \bgroup
(no expandible), por lo que TeX se presenta primero con
\ifcsname\SuffixName\foo\reserved@b\endcsname
y \SuffixName
está definido por
> \SuffixName=\long macro:
#1->WSF:\string #1 \meaning .
entonces el siguiente paso es
\ifcsname WSF:\string\foo \meaning\reserved@b\endcsname
y finalmente conseguimos
\ifcsname WSF:\foo begin-group character {\endcsname
donde todos los caracteres tienen el código de categoría 12 (pero los espacios tienen 10). En el \foo*{foo}{bar}
caso de que obtuviéramos
\ifcsname WSF:\foo the character *\endcsname
El comando \csname WSF:\foo begin-group character {\endcsname
no está definido, por lo que se sigue la rama falsa, es decir
\expandafter \WSF@suffixcheckiv \fi
que simplemente deja
\WSF@suffixcheckiv{foo}
en el flujo de entrada. ahora \show\WSF@suffixcheckiv
da
> \WSF@suffixcheckiv=macro:
->\expandafter \endgroup \csname \expandafter \NoSuffixName \reserved@a \endcsname .
por lo que el grupo previamente abierto se cierra pero primero
\csname \expandafter \NoSuffixName \reserved@a \endcsname
está formado. Recuerde que \reserved@a
se expande a \foo
, por lo que obtenemos
\csname \NoSuffixName \foo \endcsname
y \NoSuffixName
es
> \NoSuffixName=macro:
->WSF:\string .
entonces finalmente obtenemos
\csname WSF:\string\foo\encsname
Bien, emitamos \expandafter\show\csname WSF:\string\foo\endcsname
:
> \WSF:\foo=\long macro:
#1->foo is #1.
es decir, esta complicada macro es una copia del original \foo
.
En el caso de \foo*{foo}{bar}
tendríamos
\ifcsname WSF:\foo the character *\endcsname
pero en este caso estoesdefinido; en efecto
\expandafter\show\csname WSF:\string\foo\space the character *\endcsname
produce
> \WSF:\foo the character *=\long macro:
#1#2->foo is #1, bar is #2.
entonces esta macro con un nombre complicado es lo que has definido como *
-variante.
Casi cualquier token se puede utilizar como sufijo con este paquete. Pero la idea esencial no es diferente de lo que has ideado; las protecciones contra la sobrescritura de posibles nombres de macros existentes son mejores. Qué hace el paquete cuando
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
se procesa es
Guarde el comando original
\foo
en\csname WSF:\string\foo\endcsname
(si esto ya existe debido a un precedente
\WithSuffix
aplicado a\foo
este paso, por supuesto se omite)Guarde la nueva definición en
\csname WSF:\string\foo\space the character *\endcsname
Utilice la interfaz abstracta descrita anteriormente para elegir entre diferentes sufijos.
Respuesta2
Mi propio intento de solución se encuentra a continuación, con mejoras proporcionadas amablemente por @egreg y @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}
Resultado:
Explicación:
- Una copia de la definición actual del comando
\foo
se almacena como\\foo@nostar
. - El comando
\foo
se redefine para buscar una estrella y llamar a\\foo@star
o\\foo@nostar
. Esto se haceedef
para que los nombres de los tokens construidos se puedan expandir en el lugar y no cada vez que se invoca el comando. - Se inicia un
\newcommand
for\\foo@star
y tomará el resto de la definición de la siguiente manera\addstarred\foo
.