Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I am trying to set up a macro that defines a macro that is to be executed only once.

With what I learned from Define a Macro via Macro if Given Macro is not defined I can do that via the \expandafter\newcommand syntax. And indeed the MWE as below works and produces the desired output:

enter image description here

where the subsequent \HelloWorld and \HelloDude are ignored.

What I would like to do, is to learn how to use \NewDocumentCommand (instead of \newcommand) to define this new macro via a macro. My attempt (requires you to uncomment the \def at the top of the file) fails with

Illegal parameter number in definition of \reserved@a

Furthermore, am wondering if there is a way to define repeated parameters. That is instead of {m m} can I use something similar to with tabular where one can say \begin{tabular}{r*{3}{l}r} which is the identical to \begin{tabular}{rlllr}. This would save me from having to use \IfEqCasefor each possible number of parameters. I personally only need up to 3 so am ok with the slight duplication of code below, but though I'd ask in case there was some other syntax that I am not aware of.

Notes:

  • You need to uncomment the \def at the top to get the MWE below to fail.

Refernces

Code:

%% 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}
share|improve this question
    
Out of curiosity, why don't you use the expl3 syntax? It makes much more sense when using xparse, in my opinion. I would imagine the solution would also be more concise. –  Sean Allred Jun 30 at 21:42
    
@SeanAllred: Am embarrassed to admit it, but I can't quite read expl3 syntax yet. –  Peter Grill Jun 30 at 22:34

2 Answers 2

up vote 7 down vote accepted

You could do this but it's so wrong, why mix expl3 and etoolbox tests and why all the toggle stuff, if you want \foo to just execute once define it to be

 \def\foo{hello\let\foo\@empty}

No need for a separate toggle macro.

but anyway:

%% 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}
share|improve this answer
    
Yeah, I always forget about the \def thing to stop things from happening again. Can you elaborate on the \expandafter{\romannumeral#2000} magic that is happening here. How does that become m m if #2=2? –  Peter Grill Jun 30 at 22:38
    
\romannumeral2000 expands to mm –  egreg Jun 30 at 23:07
    
@egreg: Wow, that is very clever!! An I was thinking that a loop would be needed... –  Peter Grill Jul 1 at 3:11
    
@PeterGrill yes well it was fortunate you wanted m arguments (although using romannumeral and then changing m to whatever you need is a viable technique for replicating in general) –  David Carlisle Jul 1 at 9:14

Here is a solution for this problem, using (perhaps abusing) only expl3 features. I'm not really sure about the usefulness of this code. ;-)

\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}

enter image description here

share|improve this answer
    
I read in interface3 about \use:x “Any hash characters (#) in the argument must be doubled.” What does that mean? –  Manuel Jun 30 at 22:46
    
@Manuel I'm not really using # inside \use:x, because #1 and #2 will be replaced with the arguments before \use:x is executed. Think to it as doing a \def inside a \def: if you want to use parameters to the inner \def you have to double #. –  egreg Jun 30 at 22:51
    
Okey, but then why is necessary that comment? Isn't that applied to every command? -- EDIT: Understood. –  Manuel Jun 30 at 22:53
1  
I will not upvote this question, it is scratched. :P –  Paulo Cereda Jul 1 at 10:31

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.