4

In a package I'm writing, I have a macro (call it \foo) which, by default takes one argument and is equivalent to \relax. Under certain circumstances, the user may wish to redefine \foo in their document so that it still takes one argument, but now prints that argument with some formatting applied. The package needs to be able to detect this redefinition and execute different code when it happens.

In the below MWE I've figured out how to do this if the formatting is a change in font attribute (family, shape, or series). However when I attempt to change the font size, the test breaks, complaining about exceeding the TeX capacity. How can I rewrite the test so that it will work in this instance too?

\documentclass[11pt]{article}

\def\foo#1{\relax}

\begin{document}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\renewcommand{\foo}[1]{{\bfseries #1}}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\renewcommand{\foo}[1]{{\ttfamily #1}}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\renewcommand{\foo}[1]{{\itshape #1}}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\renewcommand{\foo}[1]{{\small #1}}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\end{document}

Context (in case it helps): Originally \foo was defined so that it applied some default formatting to it's argument and printed it. The user would redefine it in order to change that formatting. However, this method of changing the formatting that is applied to some text is deprecated in the package. What I'm trying to do is detect when some one is using the old method so that I can warn them that they are using deprecated syntax and point them to the new syntax. In my use case, therefore, the true code would be to format the text using the new methodology while the false code would be to use the old methodology and raise a warning or error. Defining \foo to be equivalent to \relax by default is how I thought I might be able to detect such a change.

4

Wouldn't you prefer to test \foo directly, rather than through the intermediate definition of \test?

\documentclass[11pt]{article}

\def\foo#1{\relax}
\let\svfoo\foo
\def\testfoo{\ifx\svfoo\foo true\else false\fi}

\begin{document}
\testfoo

\renewcommand{\foo}[1]{{\bfseries #1}}
\testfoo

\renewcommand{\foo}[1]{{\ttfamily #1}}
\testfoo

\renewcommand{\foo}[1]{{\itshape #1}}
\testfoo

\renewcommand{\foo}[1]{{\small #1}}
\testfoo

\def\foo#1{\relax}
\testfoo
\end{document}

enter image description here

  • I had used the intermediate definition of \test because a related solution that I was inspired by (not one online) had done something similar. I didn't realize that one could comparison test commands which took arguments directly like you do in this solution. Looking at this solution, I think I actually like it better. It has the advantage of being inherently PlainTeX compatible (a preference I forgot to mention in my original question). I had made the first solution compatible by copying the definition of \protected@edef into the PlainTeX bootstrap loader for the package. – rpspringuel Aug 19 '15 at 1:06
3

Some macros are robust, while others are fragile. Robust macros protect themselves from expansion under certain conditions while fragile commands do not, and this is the cause of your current problem.

Specific to your case, \small is not robust, and when used inside an expanded definition runs into problems. Instead, use

\makeatletter
\protected@edef\test{\foo{a}}
\makeatother

which should be safe enough in most applications. Alternative, if you know what might be used in \foo, you can \robustify those macros with the help of etoolbox.

true
false
false
false
false

\documentclass{article}

\def\foo#1{\relax}
%\usepackage{etoolbox} ...or use this:
%\robustify\small

\begin{document}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\renewcommand{\foo}[1]{{\bfseries #1}}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\renewcommand{\foo}[1]{{\ttfamily #1}}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\renewcommand{\foo}[1]{{\itshape #1}}
\edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi

\makeatletter
\renewcommand{\foo}[1]{{\small #1}}
\protected@edef\test{\foo{a}}
\expandafter\ifx\test\relax true \else false \fi
\makeatother

\end{document}
  • Since the user could, feasibly, use any formatting command from any package, I think the \protected@edef method will work better for me in the long run. Tried it in context and it worked, so thanks a bunch. – rpspringuel Aug 17 '15 at 20:46
  • @rpspringuel: While my answer explains some of the inner works, Steven's answer suggests perhaps a better alternative - testing the function directly rather than creating an expansion of the redefinition as a test. – Werner Aug 17 '15 at 20:47
  • I agree, Steven's answer is the better alternative. Sorry I'm going to shift the accepted answer. You'll have to be content with an upvote. – rpspringuel Aug 19 '15 at 1:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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