6

I would like to be able to insert code into the definitions of my custom macros, without having to modify each macro definition. This seemed like a simple case of redefining \newcommand, but this turns out not to be so easy (well for me anyway). I end up with:

Illegal parameter number in definition of \MacroB.
to be read again
1 l.45 \newcommand*{\MacroB}[1]{b#1b}

My guess is that somewhere I need to double up the # but I don't understand why, and guessing which ones need to be doubled up did not work. So perhaps that is not the problem.

Notes:

  • The MWE below compiles fine -- one needs to uncomment the \def\EnableTrace{} to see the problem.
  • The goal of this is to build up a stack trace of macros as they are being called.

Code:

%\def\EnableTrace{}
\documentclass{article}

%% All packages included here... \usepackage{xparse} \usepackage{letltxmacro}

%% Start of custom macros... \ifdefined\EnableTrace \newcounter{NestingDepth} \newcommand{\StartMacro}[1]{% \typeout{*** DEBUG: (Depth=\arabic{NestingDepth}) Started macro "\string#1".}% \stepcounter{NestingDepth}% }% \newcommand{\EndMacro}[1]{% \addtocounter{NestingDepth}{-1}% \typeout{*** DEBUG: (Depth=\arabic{NestingDepth}) Completed macro "\string#1".}% }% % -------------- \LetLtxMacro{\OldNewcommand}{\newcommand}% \RenewDocumentCommand{\newcommand}{% s% #1 = * (ignored for now to keep the code below simple) m% #2 = macro name O{1}% #3 = number of paramaters o% #4 = default value for first optional parameter (if there is one) m% #5 = code to execute }{% % \ifnum#3=1\relax \OldNewcommand{#2}{\StartMacro{#2}#5\EndMacro{#2}}% \else \IfBooleanTF{#4}{% % first parameter of macro being defined is optional, as default value is provided \OldNewcommand{#2}[#3][#4]{\StartMacro{#2}#5\EndMacro{#2}}% }{% % first parameter of macro being defined is mandatory \OldNewcommand{#2}[#3]{\StartMacro{#2}#5\EndMacro{#2}}% }% \fi }% \fi

\newcommand{\MacroB}[1]{b#1b}% \newcommand{\MacroA}[1]{a\MacroB{#1}a}%

\begin{document} \MacroA{XXX}

\MacroB{YYY} \end{document}

Peter Grill
  • 223,288

2 Answers2

4

There are strange conditionals in your code. Moreover, \IfBooleanTF is for arguments of type s or t, not for o.

\documentclass{article}
\usepackage{xparse}

\newif\ifEnableTrace
\newcounter{NestingDepth}
\let\latexnewcommand\newcommand

\newcommand{\StartMacro}[1]{%
  \ifEnableTrace
    \typeout{**** DEBUG: (Depth=\arabic{NestingDepth}) Started macro `\string#1'.}%
    \stepcounter{NestingDepth}%
  \fi
}
\newcommand{\EndMacro}[1]{%
  \ifEnableTrace
    \addtocounter{NestingDepth}{-1}%
    \typeout{**** DEBUG: (Depth=\arabic{NestingDepth}) Completed macro `\string#1'.}%
  \fi
}

\NewDocumentCommand{\NewCommand}{smO{0}om}{%
  \IfBooleanTF{#1}
    {\IfNoValueTF{#4}{\latexnewcommand*{#2}[#3]}{\latexnewcommand*{#2}[#3][#4]}}%
    {\IfNoValueTF{#4}{\latexnewcommand{#2}[#3]}{\latexnewcommand{#2}[#3][#4]}}%
  {\StartMacro{#2}#5\EndMacro{#2}}%
}

% if you want to enable tracing
\let\newcommand\NewCommand

\newcommand*{\MacroB}[1]{b#1b}
\newcommand*{\MacroA}[1]{a\MacroB{#1}a}

\begin{document}

\MacroA{XXX}

\MacroB{YYY}

\EnableTracetrue

\MacroA{XXX}

\MacroB{YYY}

\end{document}

Here's the terminal output

**** DEBUG: (Depth=0) Started macro `\MacroA'.
**** DEBUG: (Depth=1) Started macro `\MacroB'.
**** DEBUG: (Depth=1) Completed macro `\MacroB'.
**** DEBUG: (Depth=0) Completed macro `\MacroA'.
**** DEBUG: (Depth=0) Started macro `\MacroB'.
**** DEBUG: (Depth=0) Completed macro `\MacroB'.
egreg
  • 1,121,712
  • As always, your solution works great. Two questions: 1. Why is \LetLtxMacro not needed here -- \newcommand has an optional parameter. 2. Why the \ifEnableTrace in \StartMacro and \EndMacro? Seems that it should just be around the \let\newcommand\NewCommand. – Peter Grill Aug 06 '14 at 04:44
  • @PeterGrill (2) at least that's an extra since you can disable tracing at some point in the document, and reenable it again whenever you want. – Manuel Aug 06 '14 at 06:31
  • @PeterGrill The first level expansion of \newcommand is \@star@or@long\new@command; no arguments. I added two levels for EnableTrace: you can enable tracing globally and then disable/enable it locally. – egreg Aug 06 '14 at 08:29
2

You use

\ifnum#3=1\relax
  \OldNewcommand{#2}{\StartMacro{#2}#5\EndMacro{#2}}%
\else

which… after checking you pass \newcommand\…[1] gives you something like \newcommand\…[0].

\ifnum#3=1\relax
  \OldNewcommand{#2}[1]{\StartMacro{#2}#5\EndMacro{#2}}%
\else

works.

By the way… why is it necessary the \ifnum at all? Just leaving the “else” branch of that \ifnum is enough:

%\def\EnableTrace{}
\documentclass{article}

%% All packages included here...
\usepackage{xparse}
\usepackage{letltxmacro}


%% Start of custom macros...
\ifdefined\EnableTrace
    \newcounter{NestingDepth}
    \newcommand*{\StartMacro}[1]{%
        \typeout{**** DEBUG: (Depth=\arabic{NestingDepth}) Started macro "\string#1".}%
        \stepcounter{NestingDepth}%
    }%
    \newcommand*{\EndMacro}[1]{%
        \addtocounter{NestingDepth}{-1}%
        \typeout{**** DEBUG: (Depth=\arabic{NestingDepth}) Completed macro "\string#1".}%
    }%
    % --------------
    \LetLtxMacro{\OldNewcommand}{\newcommand}%
    \RenewDocumentCommand{\newcommand}{%
        s%    #1 = * (ignored for now to keep the code below simple)
        m%    #2 = macro name
        O{1}% #3 = number of paramaters
        o%    #4 = default value for first optional parameter (if there is one)
        m%    #5 = code to execute
    }{%
        %
%       \ifnum#3=1\relax
%           \OldNewcommand{#2}{\StartMacro{#2}#5\EndMacro{#2}}%
%       \else
            \IfBooleanTF{#4}{%
                % first parameter of macro being defined is optional, as default value is provided
                \OldNewcommand{#2}[#3][#4]{\StartMacro{#2}#5\EndMacro{#2}}%
            }{%
                % first parameter of macro being defined is mandatory
                \OldNewcommand{#2}[#3]{\StartMacro{#2}#5\EndMacro{#2}}%
            }%
%       \fi
    }%
\fi

\newcommand*{\MacroB}[1]{b#1b}%
\newcommand*{\MacroA}[1]{a\MacroB{#1}a}%

\begin{document}
\MacroA{XXX}

\MacroB{YYY}
\end{document}

By the way, why not default the number of arguments to [0] so it works exactly as the original \newcommand?

Manuel
  • 27,118
  • Hmmm... You are right, it should have been \ifnum#3=0 and the the default should be '0'. Those two changes fix this MWE. SO, then I need both the ifnum#3=0 and else branches. – Peter Grill Aug 06 '14 at 04:37
  • The \IfBooleanTF (which as noted by egreg should be \IfValueTF) is enough (after defaulting to [0] parameters). \newcommand\foo[0]{…} is the same as \newcommand\foo{…}. – Manuel Aug 06 '14 at 06:54