我想定义一个宏,其中其名称将用作逻辑参考。
例如,如果宏名称是\mycmdAAA
,它将键入AAA
;如果宏名称是\mycmdBBB
,它将键入BBB
;如果宏名称是\mycmdCCC
,它将键入CCC
。
也就是说,在宏的定义部分可以使用宏本身的名字。
这个功能如何实现呢?
答案1
我仍然不太确定需要什么,但这对于评论来说太长了,可能会回答这个问题。
在对 OP 的评论中,有人问是否可以在第一次使用命令时让命令自行定义。据我所知,这是不可能的,但是,可以使用以下命令一次性定义一大堆类似的命令。
\documentclass{article}
\usepackage{etoolbox}
\newcommand\BuildCommands[1]{% usage: \BuildCommands{comma separated list}
\renewcommand*\do[1]{\csdef{mycmd##1}{##1}}%
\docsvlist{#1}%
}
\BuildCommands{AAA,BBB,CCC,DDD,EEE}
\begin{document}
\mycmdAAA
\mycmdBBB
\mycmdCCC
\mycmdDDD
\mycmdEEE
\end{document}
诀窍是\docsvlist
来自电子工具箱包,它循环遍历逗号分隔的列表来定义来自 CSV 的形式的\mycmdXXX
命令XXX
。
也许这满足了 OP 的要求?如果这确实回答了问题,那么正如 @siracusa 上面的评论一样,我不清楚为什么使用类似的东西是不够的:
\newcommand\mycmd[1]{#1}
\mycmd{AAA} \mycmd{BBB} ...
编辑
要回答评论中的问题,只需对上面的代码进行一点小改动\BuildCommands
,即可定义\mycmd
宏并将宏名称插入文本。但请注意,现在\BuildCommands
只能在之后使用,\begin{document}
因为它是插入文本。以下是修改后的代码:
\documentclass{article}
\usepackage{etoolbox}
\newcommand\BuildCommands[1]{% usage: \BuildCommands{comma separated list}
\renewcommand*\do[1]{\csdef{mycmd##1}{##1}##1}%
\docsvlist{#1}%
}
\begin{document}
\BuildCommands{AAA,BBB,CCC,DDD,EEE}
\mycmdAAA
\mycmdBBB
\mycmdCCC
\mycmdDDD
\mycmdEEE
\end{document}
答案2
代码可以执行您想要的操作,也可以处理异常。在示例中,我定义了两个命令系列,一个命令有“异常”,另一个命令没有。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\BuildCommands}{mmO{}}
{% #1 = prefix,
% #2 = list,
% #3 (optional) = exceptions
\clist_map_inline:nn { #2 }
{
\cs_new:cpx { #1##1 } { \str_case:nnF {##1}{#3}{##1} }
}
}
\ExplSyntaxOff
\BuildCommands{my}{AAA,BBB,table,foo}[
{table}{figure}
{foo}{baz}
]
\BuildCommands{our}{AAA,BBB,table,foo}
\begin{document}
\myAAA \par
\myBBB \par
\mytable \par
\myfoo \par
\ourAAA \par
\ourBBB \par
\ourtable \par
\ourfoo \par
\end{document}
答案3
如果宏/命令可以评估其自身的名称,那么就会出现以下问题:
\mycmdDDD
当通过\let\mycmdDDD=\mycmdCCC
创建一个控制序列\mycmdDDD
,其含义等于的含义时,您希望出现什么行为\mycmdCCC
?应该像这样\mycmdDDD
输入还是应该输入?CCC
\mycmdCCC
DDD
这里是用纯 TeX 实现的循环,用于定义许多类似的命令。
您可以通过添加宏\Exceptionfork
和 来指定例外\ForkExceptions
。
假定表示宏名称和异常的可区分部分的字符串不包含标记\dLm
。
您不必拘泥于标记\dlm
。作为分隔符,您可以选择任何一组标记,这些标记不会出现在表示宏名称和异常的可区分部分的任何字符串中。
这是一种快速而粗糙的事情,我可能会在创建文档时这样做,因为除了我之外没有人会获得 .tex 源,但是当我为要向公众发布的包或类似的东西编写代码时,我不会这样做。;-)
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\exchange#1#2{#2#1}%
\long\def\passfirsttosecond#1#2{#2{#1}}%
%%-------------------------------------------------------------------------
%% Check whether argument is empty:
%%.........................................................................
%% \CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is empty>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is not empty>}%
\long\def\CheckWhetherNull#1{%
\romannumeral0\expandafter\secondoftwo\string{\expandafter
\secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\secondoftwo}{\expandafter\expandafter\firstoftwo{ }{}\firstoftwo}%
}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%-------------------------------------------------------------------------
%% Check whether argument contains no token \dLm on top-brace-level:
%%.........................................................................
%% \CheckWhetherNoDelimiter{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked does not contain \dLm>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked does contain \dLm>}%
\long\def\RemoveToDelimiter#1\dLm{}%
\long\def\CheckWhetherNoDelimiter#1{%
\expandafter\CheckWhetherNull\expandafter{\RemoveToDelimiter#1\dLm}%
}%
%%-------------------------------------------------------------------------
%% Create control-sequence-token from macro name:
%% \name{foo} -> \foo
%% \name\newcommand{foo}... -> \newcommand\foo....
%%-------------------------------------------------------------------------
\long\def\name#1#{\romannumeral0\innername{#1}}%
\long\def\innername#1#2{%
\expandafter\exchange\csname#2\endcsname{ #1}%
}%
%%-------------------------------------------------------------------------
%% Tail-recursive loop for defining similar commands:
%%-------------------------------------------------------------------------
%% \dlm denotes the end of the comma-list:
\long\def\Definecommands#1{\Definecommandsloop#1,\dLm,}%
%%
\long\def\Definecommandsloop#1,{%
\CheckWhetherNoDelimiter{#1}%
{%
\expandafter\expandafter\expandafter\passfirsttosecond
\expandafter\expandafter\expandafter{%
\ForkExceptions{#1}}%
{\name\long\def{Mycmd#1}}%
\Definecommandsloop
}{}%
}%
%%-------------------------------------------------------------------------
\long\def\Exceptionfork#1\dLm\dLm DDD\dLm EEE\dLm#2#3\dLm\dLm\dLm\dLm{ #2}% The space before #2 stops \romannumeral-expansion.
\long\def\ForkExceptions#1{%
\romannumeral0%
\CheckWhetherNoDelimiter{#1}{%
\Exceptionfork
\dLm#1\dLm DDD\dLm EEE\dLm{Definition text for case emptiness}%<- Definition text in case of empty argument
\dLm \dLm #1\dLm EEE\dLm{Definition text for exceptional case DDD}%<-Definition text in case DDD
\dLm \dLm DDD\dLm #1\dLm{Definition text for exceptional case EEE}%<-Definition text in case EEE
\dLm \dLm DDD\dLm EEE\dLm{#1}%<- Definition text for other cases without \dLm
\dLm\dLm\dLm\dLm
}{ #1}%<- Definition text for other cases with \dLm. The space before #1 stops \romannumeral-expansion.
}%
\tt\frenchspacing
\string\Definecommands\string{AAA,BBB,CCC,DDD,EEE,FFF,,!!!,\string{,,,\string}\string}
\Definecommands{AAA,BBB,CCC,DDD,EEE,FFF,,!!!,{,,,}}
\bigskip
\string\MycmdAAA: \meaning\MycmdAAA
\string\MycmdBBB: \meaning\MycmdBBB
\string\MycmdCCC: \meaning\MycmdCCC
\string\MycmdDDD: \meaning\MycmdDDD
\string\MycmdEEE: \meaning\MycmdEEE
\string\MycmdFFF: \meaning\MycmdFFF
\string\Mycmd: \meaning\Mycmd
\name\string{Mycmd!!!}: \name\meaning{Mycmd!!!}
\name\string{Mycmd,,,}: \name\meaning{Mycmd,,,}
\bye
当然,您也可以使用前面例子中的分叉技术来定义一个宏,该宏处理一个参数并根据该参数进行分叉。
在下面的例子中,我将上一个例子中的分叉技术与选择 L 个连续无分隔参数中的第 K 个参数的例程相结合。
%%=============================================================================
%% "Paraphernalia":
%%=============================================================================
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\exchange#1#2{#2#1}%
\long\def\passfirsttosecond#1#2{#2{#1}}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
%% \CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is empty>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is not empty>}%
\long\def\CheckWhetherNull#1{%
\romannumeral0\expandafter\secondoftwo\string{\expandafter
\secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\secondoftwo}{\expandafter\expandafter\firstoftwo{ }{}\firstoftwo}%
}%
%%=============================================================================
%% Keep only the K-th of L consecutive undelimited arguments.
%% ( IF K < 1 OR K > L just remove L consecutive undelimited arguments. )
%%=============================================================================
%% \KeepKthOfLArguments{<integer number K>}%
%% {<integer number L>}%
%% <sequence of L consecutive undelimited arguments>
%%
%% If L < 1 yields nothing.
%% Else:
%% If K >= 1 and K <= L yields:
%% <K-th undelimited argument from <sequence of L consecutive undelimited
%% arguments>>
%% If K < 1 or K > L
%% (-> there is no K-th argument in the
%% <sequence of L consecutive undelimited arguments> )
%% yields nothing but removal of <sequence of L consecutive
%% undelimited arguments>
\long\def\KeepKthOfLArguments#1#2{%
\romannumeral0%
% #1: <integer number K>
% #2: <integer number L>
\expandafter\UDKeepKthOfLArgumentsKSmallerOneFork
\expandafter{\romannumeral\number\number#1 000\expandafter}%
\expandafter{\romannumeral\number\number#2 000}%
}%
%%-----------------------------------------------------------------------------
\long\def\UDKeepKthOfLArgumentsKSmallerOneFork#1#2{%
% #1: <K letters m>
% #2: <L letters m >
\CheckWhetherNull{#1}{% K is smaller than one:
\UDKeepKthOfLArgumentsRemoveNArguments{#2}{ }{}%
}{% K is not smaller than one:
\expandafter\passfirsttosecond
\expandafter{%
\firstoftwo{}#1%
}{%
\UDKeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop{#1}{#2}%
}{#2}%
}%
}%
%%-----------------------------------------------------------------------------
\long\def\UDKeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop#1#2#3#4{%
% #1: <K letters m>
% #2: <L letters m>
% (For detecting whether K>L or K<=L, during the loop letters m will
% be removed both from #1 and #2 until at least one of these arguments
% is empty.
% When the loop terminates with 0<K<=L, #1 will be empty and #2
% will hold an amount of letters m corresponding to the the
% difference L-K.
% When the loop terminates with K>L, #1 will not be empty and #2
% will be empty.
% )
% #3: <K-1 letters m>
% #4: <L letters m>
% (#3 and #4 will be left untouched during the loop so they can be
% used for performing appropriate action when loop terminates as
% it is known whether K>L.)
\CheckWhetherNull{#1}{% We have K<=L:
\UDKeepKthOfLArgumentsRemoveNArguments{%
#3%
}{%
\UDKeepKthOfLArgumentsRemoveNArguments{#2}{ }%
}{}%
}{%
\CheckWhetherNull{#2}{% We have K>L:
\UDKeepKthOfLArgumentsRemoveNArguments{#4}{ }{}%
}{% We don't know yet whether K<=L or K>L, thus remove letters m and
% do another iteration:
\expandafter\passfirsttosecond
\expandafter{%
\firstoftwo{}#2%
}{%
\expandafter\UDKeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop
\expandafter{%
\firstoftwo{}#1%
}%
}{#3}{#4}%
}%
}%
}%
%%-----------------------------------------------------------------------------
%% \UDKeepKthOfLArgumentsRemoveNArguments{<N letters m>}%
%% {<argument 1>}%
%% {<argument 2>}%
%% <sequence of consecutive
%% undelimited arguments>
%%.............................................................................
%% Removes the first N undelimited arguments from the <sequence of
%% consecutive undelimited arguments>, then inserts
%% <argument 1><argument 2>
%%
%% On the one hand when providing <argument 2> empty, you can use
%% <argument 1> for nesting calls to \UD@KeepKthOfLArgumentsRemoveNArguments.
%% On the other hand you can provide a <space token> for stopping
%% \romannumeral-expansion as <argument 1> and have the
%% macro grab the <K-th undelimited argument> from the <sequence of L
%% consecutive undelimited arguments> as <argument 2>.
%%
\long\def\UDKeepKthOfLArgumentsRemoveNArguments#1#2#3{%
%% #1: <N letters m>
%% #2: <Argument 1>
%% #3: <Argument 2>
\CheckWhetherNull{#1}{#2#3}{%
\firstoftwo{%
\expandafter\UDKeepKthOfLArgumentsRemoveNArguments
\expandafter{%
\firstoftwo{}#1%
}{#2}{#3}%
}%
}%
}%
%%=============================================================================
%% Forking-mechanism:
%%=============================================================================
%% Check whether argument contains no token \dLm on top-brce-level:
%%
%% \CheckWhetherNoDelimiter{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked does not contain \dLm>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked does contain \dLm>}%
\long\def\RemoveToDelimiter#1\dLm{}%
\long\def\CheckWhetherNoDelimiter#1{%
\expandafter\CheckWhetherNull\expandafter{\RemoveToDelimiter#1\dLm}%
}%
%% Fork via delimited arguments:
\long\def\ForkMyCmd#1\dLm\dLm DDD\dLm EEE\dLm#2#3\dLm\dLm\dLm\dLm{#2}%
\long\def\MycmdFork#1{%
\KeepKthOfLArguments{%
% \KeepKthOfLArguments does evaluate its number-arguments via
% \romannumeral/\number-expansion.
% Thus we can here use the forking technique for selecting the
% appropriate value K for laterwards picking the K-th argument:
\CheckWhetherNoDelimiter{#1}{%
\ForkMyCmd
\dLm#1\dLm DDD\dLm EEE\dLm{2}%<-argument to pick in case of empty argument
\dLm \dLm #1\dLm EEE\dLm{3}%<-argument to pick in case DDD
\dLm \dLm DDD\dLm #1\dLm{4}%<-argument to pick in case EEE
\dLm \dLm DDD\dLm EEE\dLm{1}%<-argument to pick for other cases without \dLm
\dLm\dLm\dLm\dLm
}{1}%<- argument to pick for other cases with \dLm
}{4}% We will have L=4 cases/arguments
}%
\long\def\Mycmd#1{%
\MycmdFork{#1}% argument to check.
{#1}% text in all other cases.
{text in exception-case argument is empty}%
{text in exception-case argument is DDD}%
{text in exception-case argument is EEE}%
}%
\tt\frenchspacing
\string\Mycmd\string{AAA\string}: \Mycmd{AAA}
\string\Mycmd\string{BBB\string}: \Mycmd{BBB}
\string\Mycmd\string{CCC\string}: \Mycmd{CCC}
\string\Mycmd\string{DDD\string}: \Mycmd{DDD}
\string\Mycmd\string{EEE\string}: \Mycmd{EEE}
\string\Mycmd\string{FFF\string}: \Mycmd{FFF}
\string\Mycmd\string{\string}: \Mycmd{}
\string\Mycmd\string{!!!\string}: \Mycmd{!!!}
\string\Mycmd\string{,,,\string}: \Mycmd{,,,}
\bye
答案4
一种listofitems
方法。
\documentclass{article}
\usepackage{listofitems}
\newcommand\BuildCommands[1]{%
\readlist*\bldargs{#1}%
\foreachitem\x\in\bldargs[]{%
\expandafter\def\csname mycmd\x\expandafter\endcsname\expandafter{\x}%
}%
}
\BuildCommands{AAA,BBB,CCC,DDD,EEE}
\begin{document}
\mycmdAAA
\mycmdBBB
\mycmdCCC
\mycmdDDD
\mycmdEEE
\end{document}