使用 xparse 语法通过宏定义新宏

使用 xparse 语法通过宏定义新宏

我正在尝试设置一个宏,该宏定义只执行一次的宏。

我从中学到如果给定的宏未定义,则通过宏定义宏我可以通过\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}

在此处输入图片描述

相关内容