9

I have a macro that defines some internal macros and does so locally as to not clutter the global namespace. However, \caption seems to expand the macro's argument too early. Here is a minimal example.

\documentclass{article}
\begin{document}
\DeclareRobustCommand\aaaa[1]{%
  \bgroup%
  \def\bbbb{bbbb}% exists only inside \aaaa (as not to clutter global namespace)
  #1% 
  \egroup%
}
\aaaa{\bbbb}% ok
\begin{figure}
  \caption{long \aaaa{\bbbb}}% not ok
\end{figure}

What is causing this? Is there a way to work around that issue? (A LaTeX3 solution for my macro would be fine as well.)

  • Note that \bbbb doesn't exist when you call \caption. One way to avoid premature expansion is to use \aaaa{\protect\bbbb}. – Werner Feb 13 '14 at 19:17
  • @Werner, my point of view is that \aaaa's argument should only be expanded by \aaaa itself and \caption should not alter it in any way. – mh543 Feb 13 '14 at 19:55
  • Then you have to \protect certain components that you want to not expand, since \caption does some legwork in the background to check for the size/width of the caption and whether or not to center/justify it. – Werner Feb 13 '14 at 20:13
  • It's not clear why you would do this. Can you explain? – egreg Feb 13 '14 at 21:18
  • 1
    @user46141 Why not making an example with \ce? This would make the problem much clearer. – egreg Feb 18 '14 at 0:16
4
+50

LaTeX's protection mechanism only protects the macro, not its arguments. In LaTeX macro can be used/executed in different contexts:

  • Normal typesetting context (\set@typeset@protect).
    \protect has the meaning of \relax. It does not touch the following macro which is executed normally.

  • Display context (\set@display@protect).
    Macros are displayed inside messages, errors, warnings, or \typeout. \protect becomes \string that converts the following macro into simple character tokens that cannot be executed anymore.

  • Expansion context (\@unexpandable@protect).
    \protect is \noexpand here that prevents the following macro from the next expansion.

\string and \noexpand only affects the next token. They do not protect whole arguments with many tokens. Nowadays most TeX engines support e-TeX, which comes with \detokenize and \unexpanded. Both work on token lists and can be used to protect arguments.

To some degree the definition of \aaaa can be extended to support the protection of its argument, e.g.:

\documentclass{article}

\makeatletter
\newcommand*{\aaaa}{%
  \ifx\protect\@typeset@protect
    \csname aaaa \expandafter\endcsname
  \else
    \ifx\protect\@unexpandable@protect
      \protect@unexpand@cmd@arg\aaaa
    \else
      \ifx\protect\string
        \protect@string@cmd@arg\aaaa
      \else
        \expandafter\protect@unknown@cmd@arg
        \csname aaaa \endcsname
      \fi
    \fi
  \fi
}
\expandafter\newcommand\csname aaaa \endcsname[1]{%
  \begingroup
    \def\bbbb{bbbb}%
    #1%
  \endgroup
}
% unexpanded protect
\def\protect@unexpand@cmd@arg#1\else#2\fi\fi\fi#3{%
  \fi\fi
  \ifx\thepage\relax 
    \detokenize
  \else
    \unexpanded
  \fi
  {#1{#3}}%
}
% display protect
\def\protect@string@cmd@arg#1\else#2\fi\fi\fi#3{%
  \fi\fi\fi
  \detokenize{#1{#3}}%
}
% unknown protect
\def\protect@unknown@cmd@arg#1\fi\fi\fi{%
  \fi\fi\fi
  \protect#1%
}
\makeatother

\begin{document}
\listoffigures

% test typeset context
\aaaa{\bbbb}

%
\makeatletter
\protected@edef\x{\aaaa{\bbbb}}
\typeout{\meaning\x}
\makeatother

\typeout{\aaaa{\bbbb}}

\begin{figure}
  \caption{long \aaaa{\bbbb}}
\end{figure}
\end{document}

However a serious problem remains with deferred writing, which is used for the index, for example. If an index entry is written, \thepage is prevented from expanding; the write node will therefore contain the macro \thepage instead of the current page number and may thus be wrong. At shipout time, the write node expands its contents and flushes it to the file. At this time the page number is known and \thepage expands to the right page number.

Unfortunately this causes an ambiguity for \aaaa:

  • If it is written to a file, then it should use \detokenize for itself and its argument to prevent any early expansions.

  • It should use \unexpanded if it is used inside a macro definition by \protected@edef and the macro is not written to a file, but executed later.

Unfortunately there is no safe and robust method to distinguish both cases from inside the macro \aaaa. The example above uses a heuristics and assumes that if \thepage is disabled from expansion (via \let\thepage\relax), then the macro is probably written to a file.

  • Okay, first thing I learned: This is because shipping code to the .aux file. Therefore it is not limited to \caption. The solution is a little bit more complex than I hoped for. Self-modifying and control-structure-busting macros! Wow! I am tempted to change the mhchem syntax just to avoid all of this. And I somehow feel that I should not include things into my package that I do not completely understand. Heiko, would it be possible to move this into a package of the Oberdiek bundle? – mh543 Feb 23 '14 at 19:47

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.