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 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}
share|improve this question
    
I don't understand what does \ifnum#3=1… do. After checking the number of parameters is 1, you call \OldNewCommand{#2}{…} with 0 arguments. –  Manuel Aug 5 at 21:01
    
If you delete everything between \ifnum and \else and also the corresponding \fi (i.e., leaving only the \IfBooleanTF… it compiles fine here. –  Manuel Aug 5 at 21:08
    
Does reading #5 with v instead of m help? (I can't try it myself for I am on my phone) –  Henri Menke Aug 5 at 21:09
    
For an o argument, the test is not \IfBooleanTF, but \IfNoValueTF or \IfValueTF. Of course, \LetLtxMacro is not needed for \newcommand, that has no argument. –  egreg Aug 5 at 21:22

2 Answers 2

up vote 4 down vote accepted

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'.
share|improve this answer
    
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 6 at 4: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 6 at 6: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 6 at 8:29

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?

share|improve this answer
    
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 6 at 4: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 6 at 6:54

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.