4

I have a question about a strange behavior of the etoolbox package. Roughly speaking, I want to define a command for defining macros so that the user says \mynewcommand\acommand{some code} and it will define \acommand to do some setup stuff, run some code then do some cleanup. I want to use \patchcmd to turn any occurrences of \options in some code into an argument (say #1). However, I run into some difficulties with \patchcmd saying macro cannot be retokenized cleanly due to some strange difficulty with spaces.

One solution to this problem is to disable the etoolbox scan safety check by saying \def\etb@ifscanable##1{\@firstoftwo}. I don't really like this solution because it reaches into the etoolbox package.

If I could understand what is happening in the following example, I think I would be able to fix my definition without doing this. So are there any etoolbox experts out there who can tell me why the following code behaves the way it does?

\documentclass{article}
\usepackage{etoolbox}
\tracingpatches
\makeatletter
\begin{document}

% Breaks sometimes:
\def\mynewcmda#1#2{
    \gdef#1{%
        \acommand
        #2%
        \options
    }
    \patchcmd#1{\options}{\athing}
        {\message{replacement worked}}
        {\message{replacement failed}}
}

% Always works:
\def\mynewcmdb#1#2{
    \gdef#1{%
        \acommand
        #2%
        \options
    }
    \bgroup
    % Skip retokenization check, so "++ macro can be retokenized cleanly":
    \def\etb@ifscanable##1{\@firstoftwo}
    \patchcmd#1{\options}{\athing}
        {\message{replacement worked}}
        {\message{replacement failed}}
    \global\let#1#1
    \egroup
}

% Always works:
\def\mynewcmdc#1#2{
    \gdef#1{%
        \acommand{}%
        #2%
        \options
    }
    \patchcmd#1{\options}{\athing}
        {\message{replacement worked}}
        {\message{replacement failed}}
}

% Doesn't work for some mysterious reason
\mynewcmda\testa{
    do things
}
% All the rest of these work:
\mynewcmda\testaprime{%
    do things
}
\mynewcmdb\testb{
    do things
}
\mynewcmdc\testc{
    do things
}
\end{document}

The log file says:

[debug] tracing \patchcmd on input line 50
[debug] analyzing '\testa'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] -- macro cannot be retokenized cleanly
[debug] -> the macro may have been defined under a category
[debug]    code regime different from the current one
[debug] -> the replacement text may contain special control
[debug]    sequence tokens formed with \csname...\endcsname;
[debug] -> the replacement text may contain carriage return,
[debug]    newline, or similar characters
replacement failed

and then a bunch of messages saying the other \patchcmds succeeded.

  • 2
    \athing could be directly given in the macro definition instead of the detour with \options? Also, it is on purpose that the macro is defined globally (\gdef), but redefined locally (\patchcmd)? – Heiko Oberdiek Nov 23 '16 at 3:24
  • Can there be multiple occurrences of \options inside the second argument? Or at most one? – Werner Nov 23 '16 at 3:26
  • The following should always work: \def\mynewcmd#1#2{\gdef#1{\acommand {\let\options\athing #2}\options}} – Werner Nov 23 '16 at 3:35
  • @Werner Multiple occurrences of \options can happen, so I defined a helper command \patchrepeat which recursively applies \patchcommand until it fails. I'm not sure if this is the best way to do it. – Hood Chatham Nov 23 '16 at 4:00
  • 2
    This feels like an 'x-y' question: can you explain the wider context and what actually needs to be done? – Joseph Wright Nov 23 '16 at 6:46
3

It's not at all strange. The problem is that a macro cannot be safely rebuilt if the string representation of its replacement text contains something like

\macro<space><space>

because this could refer either to \macro followed by a space or to \macro<space> (with a trailing space in its name).

Defining macros with a trailing space in their name is done all the time: each \DeclareRobustCommand makes one, for instance.

Disabling the check is quite risky, because you can end up with a macro doing very different things than expected.

Where does the bad space pop out from?

\mynewcmda\testa{
    do things
}

The end-of-line after { is the culprit.

  • Thank you for the explanation. I don't want to ask the user to put a % there, so I guess the best solution is to make sure I can put an extra pair of braces or something before the user code is dumped in so that the space isn't problematic. Also, does this mean that if I ever use \protected@edef to define \somemacro, I should never expect \patchcmd to work on \somemacro? – Hood Chatham Nov 23 '16 at 14:15
  • Also, did you mean there to be only one space in \macro<space><space>? – Hood Chatham Nov 23 '16 at 14:27
  • @HoodChatham The detokenized version of \macro is the string \macro<space> – egreg Nov 23 '16 at 15:48

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.