
Как написать метамакрос, который добавляет к команде отмеченную звездочкой версию?
Предполагаемое использование будет осуществляться в соответствии с
\newcommand\foo[1]{foo is #1}
\addstarred\foo[2]{foo is #1, bar is #2}
решение1
Метод уже доступен в пакете suffix
Дэвида Каструпа. Само собой разумеется, он полон хитрых трюков.
Ты можешь сказать
\usepackage{suffix}
\newcommand{\foo}[1]{foo is #1}
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
и может быть поучительно посмотреть, как достигается эта цель.
Если мы сделаем это \show\foo
после второй инструкции, то обнаружим
> \foo=\protected macro:
->\WSF@suffixcheck \foo .
поэтому мы узнаем, что suffix
требует e-TeX (не проблема в наши дни) и переопределяет, \foo
чтобы означать \WSF@suffixcheck\foo
. Поэтому мы добавляем \makeatletter
и пробуем \show\WSF@suffixcheck
, получая
> \WSF@suffixcheck=macro:
#1->\begingroup \def \reserved@a {#1}\futurelet \reserved@b \WSF@suffixcheckii
поэтому аргумент сохраняется в \reserved@a
и
\futurelet\reserved@b\WSF@suffixcheckii
выполняется. Это делает \reserved@b
его эквивалентным токену, который следует за \WSF@suffixcheckii
. Если вызов
\foo{foo}
тогда \reserved@b
будет \bgroup
; если вызов
\foo*{foo}{bar}
тогда \reserved@b
будет *
. Теперь нам нужно узнать, что \WSF@suffixcheckii
делает:
> \WSF@suffixcheckii=macro:
->\ifcsname \expandafter \SuffixName \reserved@a \reserved@b \endcsname
\expandafter \WSF@suffixcheckiii \else \expandafter \WSF@suffixcheckiv \fi .
Хорошо, давайте посмотрим, что произойдет в \foo{foo}
случае: \reserved@a
расширяется до \foo
, в то время как \reserved@b
является \bgroup
(нерасширяемым), поэтому TeX сначала представляется с
\ifcsname\SuffixName\foo\reserved@b\endcsname
и \SuffixName
определяется
> \SuffixName=\long macro:
#1->WSF:\string #1 \meaning .
так что следующий шаг -
\ifcsname WSF:\string\foo \meaning\reserved@b\endcsname
и мы наконец получаем
\ifcsname WSF:\foo begin-group character {\endcsname
где все символы имеют код категории 12 (но пробелы имеют 10). В этом \foo*{foo}{bar}
случае мы получим
\ifcsname WSF:\foo the character *\endcsname
Команда \csname WSF:\foo begin-group character {\endcsname
не определена, поэтому выполняется ложная ветвь, т.е.
\expandafter \WSF@suffixcheckiv \fi
который просто оставляет
\WSF@suffixcheckiv{foo}
во входном потоке. Теперь \show\WSF@suffixcheckiv
дает
> \WSF@suffixcheckiv=macro:
->\expandafter \endgroup \csname \expandafter \NoSuffixName \reserved@a \endcsname .
поэтому ранее открытая группа закрыта, но сначала
\csname \expandafter \NoSuffixName \reserved@a \endcsname
формируется. Напомним, что \reserved@a
расширяется до \foo
, поэтому получаем
\csname \NoSuffixName \foo \endcsname
и \NoSuffixName
является
> \NoSuffixName=macro:
->WSF:\string .
так что в итоге мы получаем
\csname WSF:\string\foo\encsname
Хорошо, давайте выпустим \expandafter\show\csname WSF:\string\foo\endcsname
:
> \WSF:\foo=\long macro:
#1->foo is #1.
то есть этот сложный макрос является копией оригинала \foo
.
В случае, если бы \foo*{foo}{bar}
мы имели
\ifcsname WSF:\foo the character *\endcsname
но в этом случае этоявляетсяопределено; действительно
\expandafter\show\csname WSF:\string\foo\space the character *\endcsname
производит
> \WSF:\foo the character *=\long macro:
#1#2->foo is #1, bar is #2.
так что этот макрос со сложным именем и есть то, что вы определили как *
-variant.
Почти любой токен может быть использован в качестве суффикса с этим пакетом. Но основная идея не отличается от того, что вы придумали; защита от перезаписи возможных существующих имен макросов лучше. Что делает пакет, когда
\WithSuffix\newcommand\foo*[2]{foo is #1, bar is #2}
обработано
Сохраните исходную
\foo
команду в\csname WSF:\string\foo\endcsname
(если это уже существует, то, конечно, это опущено из-за предшествующего шага
\WithSuffix
)\foo
Сохраните новое определение в разделе
\csname WSF:\string\foo\space the character *\endcsname
Используйте абстрактный интерфейс, описанный выше, для выбора различных суффиксов.
решение2
Ниже представлена моя собственная попытка решения с улучшениями, любезно предоставленными @egreg и @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}
Результат:
Объяснение:
- Копия текущего определения команды
\foo
сохраняется как\\foo@nostar
. - Команда
\foo
переопределена для проверки наличия звезды и вызова либо ,\\foo@star
либо\\foo@nostar
. Это делается дляedef
того, чтобы сконструированные имена токенов можно было расширять на месте, а не каждый раз при вызове команды. - Начинается цикл
\newcommand
for\\foo@star
, который примет оставшуюся часть определения следующим образом\addstarred\foo
.