我正在尝试设置一个宏,该宏定义只执行一次的宏。
我从中学到如果给定的宏未定义,则通过宏定义宏我可以通过\expandafter\newcommand
语法做到这一点。事实上,如下所示的 MWE 可以工作并产生所需的输出:
其中后续的\HelloWorld
和\HelloDude
将被忽略。
我想做的是学习如何使用\NewDocumentCommand
(而不是\newcommand
)通过宏定义这个新宏。我的尝试(需要你取消注释\def
文件顶部)失败,
\reserved@a 定义中的参数数量非法
此外,我想知道是否有一种方法可以定义重复的参数。也就是说,{m m}
我可以使用类似于的东西,tabular
其中可以说与\begin{tabular}{r*{3}{l}r}
相同\begin{tabular}{rlllr}
。这将使我不必对每个可能的参数数量都使用\IfEqCase
。我个人只需要最多 3 个,所以可以接受下面的代码略微重复,但我会问一下,以防万一有其他我不知道的语法。
笔记:
- 你需要取消注释顶部
\def
的使下面的 MWE 失败。
参考文献
代码:
%% Uncomment the following \def to get the failing test case.
%\def\UseXparseForDefiningMacro{}% Works if commented out (in which case \newcommand{}{} is used to define macro)
\documentclass{article}
\usepackage{etoolbox}
\usepackage{xstring}
\usepackage{xparse}
\usepackage{xcolor}
\makeatletter
\NewDocumentCommand{\DefineMeAMacroThatExecutesOnlyOnce}{m m m}{%
% #1 = csname to use
% #2 = number of parameters
% #3 = code to execute
\newtoggle{AlreadyIssued#1}%
\togglefalse{AlreadyIssued#1}%
\ifdefined\UseXparseForDefiningMacro
\IfEqCase{#2}{%
{0}{\expandafter\NewDocumentCommand\csname#1\endcsname{ }{% No paramater version
\iftoggle{AlreadyIssued#1}{}{%
#3% Never executed this macro so go ahead an execute it,
\toggletrue{AlreadyIssued#1}% and remember that we did (so we don't do it again).
}%
}}%
{1}{\expandafter\NewDocumentCommand\csname#1\endcsname{ m }{% 1 paramater version
\iftoggle{AlreadyIssued#1}{}{%
#3% Never executed this macro so go ahead an execute it,
\toggletrue{AlreadyIssued#1}% and remember that we did (so we don't do it again).
}%
}}%
{2}{\expandafter\NewDocumentCommand\csname#1\endcsname{ m m }{% 2 paramater version
\iftoggle{AlreadyIssued#1}{}{%
#3% Never executed this macro so go ahead an execute it,
\toggletrue{AlreadyIssued#1}% and remember that we did (so we don't do it again).
}%
}}%
}%
\else% --------------------------------------------- This works!!
\expandafter\newcommand\csname#1\endcsname[#2]{%
\iftoggle{AlreadyIssued#1}{}{%
#3% Never executed this macro so go ahead an execute it,
\toggletrue{AlreadyIssued#1}% and remember that we did (so we don't do it again).
}%
}%
\fi
}
\makeatother
\DefineMeAMacroThatExecutesOnlyOnce{HelloWorld}{0}{% Does not take any parameters
Hello World!%
}
\DefineMeAMacroThatExecutesOnlyOnce{HelloDude}{1}{% Take 1 parameter
Hello \textcolor{red}{#1}.%
}
\begin{document}
%% Section 1: Works if \UseXparseForDefiningMacro is NOT defined
\HelloWorld
\HelloWorld
%% Section 2: Works if \UseXparseForDefiningMacro is NOT defined
\HelloDude{Peter}
\HelloDude{John}
\end{document}
答案1
你可以这样做,但这是错误的,为什么要混合 expl3 和 etoolbox 测试,为什么要使用所有的切换功能,如果你只想\foo
执行一次,就将其定义为
\def\foo{hello\let\foo\@empty}
无需单独的切换宏。
但不管怎么说:
%% Uncomment the following \def to get the failing test case.
\def\UseXparseForDefiningMacro{}% Works if commented out (in which case \newcommand{}{} is used to define macro)
\documentclass{article}
\usepackage{etoolbox}
\usepackage{xstring}
\usepackage{xparse}
\usepackage{xcolor}
\makeatletter
\NewDocumentCommand{\DefineMeAMacroThatExecutesOnlyOnce}{m m m}{%
% #1 = csname to use
% #2 = number of parameters
% #3 = code to execute
\newtoggle{AlreadyIssued#1}%
\togglefalse{AlreadyIssued#1}%
\ifdefined\UseXparseForDefiningMacro
\expandafter\NewDocumentCommand\csname#1\expandafter\endcsname
\expandafter{\romannumeral#2000}{%
\iftoggle{AlreadyIssued#1}{}{%
#3% Never executed this macro so go ahead an execute it,
\toggletrue{AlreadyIssued#1}% and remember that we did (so we don't do it again).
}%
}%
\else% --------------------------------------------- This works!!
\expandafter\newcommand\csname#1\endcsname[#2]{%
\iftoggle{AlreadyIssued#1}{}{%
#3% Never executed this macro so go ahead an execute it,
\toggletrue{AlreadyIssued#1}% and remember that we did (so we don't do it again).
}%
}%
\fi
}
\makeatother
\DefineMeAMacroThatExecutesOnlyOnce{HelloWorld}{0}{% Does not take any parameters
Hello World!%
}
\DefineMeAMacroThatExecutesOnlyOnce{HelloDude}{1}{% Take 1 parameter
Hello \textcolor{red}{#1}.%
}
\begin{document}
%% Section 1: Works if \UseXparseForDefiningMacro is NOT defined
\HelloWorld
\HelloWorld
%% Section 2: Works if \UseXparseForDefiningMacro is NOT defined
\HelloDude{Peter}
\HelloDude{John}
\end{document}
答案2
这是针对此问题的解决方案,仅使用(可能滥用)expl3
功能。我不太确定此代码是否有用。;-)
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\NewOnceMacro}{m m m}
{
\grill_new_once_macro:Nnn #1 { #2 } { #3 }
}
% an addition to the kernel functions
\cs_set_eq:NN \use_none: \prg_do_nothing:
\cs_new_protected:Npn \grill_new_once_macro:Nnn #1 #2 #3
{
\cs_set_eq:cc
{% the corresponding "do nothing" macro
\cs_to_str:N #1 -disabled
}
{% generate \use_none:<as many n's as #2>
use_none: \prg_replicate:nn { #2 } { n }
}
\use:x
{
\exp_not:n { \NewDocumentCommand { #1 } }
{ \prg_replicate:nn { #2 } { m } } % the right number of m's
}
{
% the first usage definition
#3
% then redefine the macro to do nothing
\cs_gset_eq:Nc #1 { \cs_to_str:N #1 -disabled }
}
}
\ExplSyntaxOff
\NewOnceMacro{\HelloWorld}{0}{Hello world}
\NewOnceMacro{\Hello}{1}{Hello #1}
\NewOnceMacro{\Foo}{2}{Your #1 is full of #2}
\begin{document}
\HelloWorld
\HelloWorld
\Hello{Peter}
\Hello{Grill}
\Foo{hovercraft}{eels}
\Foo{head}{ideas}
\end{document}