7

Question

How can I use \CheckCommand with robust commands?

Background

LaTeX provides \CheckCommand (only for use in the preamble) so you can ensure that a command has the expected definition:

\newcommand{\hi}{hi}
\CheckCommand{\hi}{hi}

will work fine without any warnings, but

\newcommand{\hi}{hi}
\CheckCommand{\hi}{bonjour}

will cause LaTeX to emit a warning:

LaTeX Warning: Command \hi  has changed.
           Check if current package is valid.

This warning system, however, is thwarted by the LaTeX protection mechanism for robust commands:

\DeclareRobustCommand{\hi}{hi}
\CheckCommand{\hi}{hi}

raises a warning because \hi is now \protect\hi, not hi. (See What is the difference between Fragile and Robust commands? for example.)

How can I use \CheckCommand with robust commands?

References

1
  • 1
    You can look at the documentation of xpatch or regexpatch to see something related to this problem.
    – egreg
    Jul 18, 2012 at 7:41

2 Answers 2

6

I introduce a new command \DefineRobustCommand in place of LaTeX's \DeclareRobustCommand. Why? Well, you can see the reason if you compare the definition of \DefineRobustCommand with that of \DeclareRobustCommand. \DeclareRobustCommand uses a trailing space on the parent command to define a unique auxiliary command, but this is not the issue here. One reason is that \DeclareRobustCommand isn't meant for one-letter commands/definitions like

\DeclareRobustCommand~[1][]{\def\x{#1}}

With

\expandafter\@gobble\string#1

as seen in the definition of \DeclareRobustCommand, auxiliary commands defined by \DeclareRobustCommand can't distinguish between command symbols.

If you want to "check" a defined command, use \DefineRobustCommand instead of \DeclareRobustCommand. You can use \VerifyCommand to check all LaTeX-defined commands, except those defined by \DeclareRobustCommand.

\VerifyCommand will issue a warning if the command doesn't match previous definition, while \XVerifyCommand will issue an error in that case.

\documentclass{article}
\makeatletter
\newtoks\amtemptoks
\def\iffurcate#1\fi{%
  #1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\def\ifcmddef#1{%
  \iffurcate\ifdefined#1\fi{%
    \iffurcate\ifx#1\relax\fi\@secondoftwo\@firstoftwo
  }{%
    \@secondoftwo
  }%
}
\def\ifnamedef#1{\expandafter\ifcmddef\csname#1\endcsname}
\def\DefineRobustCommand{\@star@or@long\define@robustcommand}
\def\define@robustcommand#1{%
  \ifx#1\@undefined\else\ifx#1\relax\else
    \@latex@info{Redefining \string#1}%
  \fi\fi
  \edef\am@tempa{\string#1}%
  \def\am@tempb{#1}%
  \edef\am@tempb{\expandafter\strip@prefix\meaning\am@tempb}%
  \edef#1{%
    \ifx\am@tempa\am@tempb\unexpanded{\am@xprotect#1}\fi
    \noexpand\protect
    \expandafter\noexpand\csname\string#1@RoB\endcsname
  }%
  \let\@ifdefinable\@rc@ifdefinable
  \expandafter\new@command\csname\string#1@RoB\endcsname
}
\def\am@xprotect#1#2#3{%
  \iffurcate\ifx\protect\@typeset@protect\fi{#3}{\protect#1}%
}
\def\VerifyCommand{\def\am@switch{0}\@star@or@long\am@checkcommand}
\def\XVerifyCommand{\def\am@switch{1}\@star@or@long\am@checkcommand}
\def\am@checkcommand#1#2#{\am@ch@ckcommand#1{#2}}
\long\def\am@ch@ckcommand#1#2#3{%
  \let\am@resa\relax
  \expandafter\let\csname\string\am@resa\endcsname\relax
  \new@command\am@resa#2{#3}%
  \amtemptoks{%
    \ifnamedef{\@backslashchar\string#1@RoB}{%
      \expandafter\let\csname\string\am@resa@RoB\endcsname\relax
      \expandafter\let\csname\@backslashchar\string\am@resa@RoB\endcsname\relax
      \define@robustcommand\am@resa#2{#3}%
      \expandafter\am@checkeqcmd
      \csname\@backslashchar\string\am@resa@RoB\expandafter\endcsname
      \csname\@backslashchar\string#1@RoB\endcsname
      #1%
    }{%
      \am@checkeqcmd#1\am@resa#1%
    }%
  }%
  \ifnamedef{\string#1}{%
    \@expandtwoargs\in@{\detokenize{\@protected@testopt}}
      {\expandafter\meaning\csname\string#1\endcsname}%
    \ifin@
      \expandafter\am@checkeqcmd
      \csname\string\am@resa\expandafter\endcsname
      \csname\string#1\endcsname#1%
    \else
      \the\amtemptoks
    \fi
  }{%
    \the\amtemptoks
  }%
}
\def\am@checkeqcmd#1#2#3{%
  \def\am@ch@ckeqcmd{Command \noexpand#3 has changed.
    \MessageBreak Check that there is nothing wrong}%
  \ifx#1#2\else
    \if1\am@switch
      \@latexerr\am@ch@ckeqcmd\@ehd
    \else
      \@latex@warning@no@line\am@ch@ckeqcmd
    \fi
  \fi
}
\makeatother

% Tests
\newcommand\hy{Hi}
\VerifyCommand\hy{Hi}
\XVerifyCommand\hy{Hi}

\newcommand\hiy[2]{Hi #1, #2}
\XVerifyCommand\hiy[2]{Hi #1, #2}
% \XVerifyCommand\hiy{Hi, -changed-}

\DefineRobustCommand\hiz[2][x]{Hi (#1***#2)}
\VerifyCommand\hiz[2][x]{Hi (#1***#2)}
% \VerifyCommand\hiz[2][x]{Hi-changed [#1***#2]}
\XVerifyCommand\hiz[2][x]{Hi (#1***#2)}
% \XVerifyCommand\hiz[2][x]{Hi-changed [#1***#2]}

% \| is defined as a delimiter. Defining | as a robust command 
% will not change that:
\begingroup
\lccode`~=`|
\lowercase{\endgroup
  \DefineRobustCommand~[2][x]{\def\x{#1***#2}}
}
{\catcode`|=13
  \XVerifyCommand|[2][x]{\def\x{#1***#2}}
  % \XVerifyCommand|[2][x]{-changed-\def\x{#1***#2}}
  %|{y}
}
\begin{document}
x
\end{document}
2
  • Thank Ahmed. I will have to look a bit more closely at this. Could you please let me know how this code came to be: is it just a simple hacked modification of the LaTeX internals, or a more sophisticated rewrite? I was hoping for something a little more "standard", but maybe it is not possible. Will wait a bit to see if a simpler solution comes up before accepting.
    – mforbes
    Jul 18, 2012 at 5:17
  • 1
    \DefineRobustCommand may qualify as a hack, but not \VerifyCommand and \XVerifyCommand. What you wanted doesn't exist in the LaTeX kernel. I personally would have preferred eTeX's \protected for defining robust commands these days. There are issues with LaTeX's robust commands. For example, they expand under \edef or equivalent milieus. LaTeX resorted to \protect because there wasn't eTeX in the early days of TeX. You can still access optional arguments with \protected if you are familiar with \@testopt.
    – Ahmed Musa
    Jul 18, 2012 at 18:01
5

As you probably know by now, \DeclareRobustCommand{\cs}{...} defines two commands: essentially it does

\def\cs{\protect\cs•}
\def\cs•{...}

where I denote with a space in the command's name (which is ordinarily not possible). When LaTeX finds this in normal typesetting, \protect is equivalent to \relax, so the expansion of \cs• does that's requested. When LaTeX is writing into auxiliary files, \protect is \noexpand, so the effect is of writing the string \cs followed by a space (when the auxiliary file will be input, that space will not be considered part of the name, but that's irrelevant).

So, in order to check a robust command, you have to look at the expansion of \cs•, but you must know in advance whether the command is robust.

The situation is further complicated by the fact that the expansion of \1 after \DeclareRobustCommand\1{...} is

\x@protect\1\protect\1•

(again • denotest a space in the name).

In the packages xpatch and regexpatch I do a check on the various possibilities of the replacement text with the macro \xpatch_main:NN (xpatch) or \xpatch_main_check:N (regexpatch). You can see that the list is quite long.

If you know that the command has been defined with \DeclareRobustCommand then

\def\CheckRobustCommand#1{%
  \expandafter\CheckCommand\csname\expandafter\@gobble\string#1\space\endcsname
}% (AM)

to be used just like \CheckCommand might be what you want.

A simple-minded version that only checks whether the "command with a trailing space" is defined can be

\makeatletter
\def\CheckRobustCommand#1{%
  \expandafter\CheckCommand\csname\expandafter\@gobble\string#1\space\endcsname
}% (AM)

\def\xCheckCommand#1{%
  \ifcsname\expandafter\@gobble\string#1\space\endcsname
    \expandafter\CheckRobustCommand
  \else
   \expandafter\CheckCommand
  \fi
}
\makeatother

The command \xCheckCommand can be used for commands defined by both \newcommand and \DeclareRobustCommand. (AM)

1
  • @AhmedMusa Thanks for spotting the missing braces.
    – egreg
    Jul 18, 2012 at 18:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .