
如何編寫一個元宏來將帶有星號的版本加入到命令中?
預期用途如下
\newcommand\foo[1]{foo is #1}
\addstarred\foo[2]{foo is #1, bar is #2}
答案1
suffix
David Kastrup 的軟體包中已經提供了一種方法。不用說,它充滿了巧妙的技巧。
你可以說
\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
and
\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
is \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
未定義,因此遵循 false 分支,即
\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
為了可以就地擴展構造的令牌名稱,而不是每次呼叫命令時都會擴展。 - for開始,並將採用如下定義的其餘
\newcommand
部分。\\foo@star
\addstarred\foo