0

Goal: Have an active character not expanded when writing to file

MWE

\documentclass{article}

\makeatletter
% == Package code -- untouchable
\def\foo{\@ifnextchar[{\@foo}{\@foo[x]}}
\DeclareRobustCommand\fooaux[1]{\message{#1}}

\def\@foo[#1]{%
  \@foo@delegate{#1}}

\def\@foo@delegate#1{
  (#1)
  \@foo@write
}

\def\@foo@write#1{%
  \protected@write\@mainaux{}{\fooaux{#1}}
}

% == User code -- can manipulate at will

\let\foo@orig\foo
\def\foo@prep{\begingroup
% 1st attempt -- builds but active char expanded in AUX
  \catcode`\*=12%
% 2nd attempt -- error: ! Missing control sequence inserted. l.32 \foo@prep
%  \catcode`\*=13%
%  \def\@dettac{\char46}%
%  \let*=\@dettac
  \global\def\foo{\@ifnextchar[{\foo@ii}{\foo@ii[]}}%
  \global\def\foo@ii[##1]##2{%
    \foo@orig[##1]{##2}%
% 3rd attempt -- error: ! File ended while scanning definition of \@foo@arg@ii.
%     \edef\@foo@arg@ii{\scantokens{##2}}%
%     \foo@orig[##1]{\@foo@arg@ii}%
% 4th attempt -- ! Use of \foo@ii doesn't match its definition.
%   \edef\@foo@arg@ii{
%     \toks0={##2}
%     \immediate\openout14=foo.tex
%     \immediate\write14{\the\toks0}
%     \immediate\closeout14
%     \input foo.tex
%   }%
%   \foo@orig[##1]{\@foo@arg@ii}%
  }%
\endgroup
}

\foo@prep

\def\@catted{
  !!
}
\catcode`\*=13
\let*=\@catted

\makeatother

\begin{document}

Works: *

Work \foo{normal} \& \foo[ok]{works}

Do not \foo{five*four} \& \foo[is expanded in aux]{x*x}

\end{document}

The code marked Package code is from a third party and cannot be changed. I can only change User code.

The package defines \foo[#1]{#2} which eventually writes to some file. (AUX here for demonstration purposes.)

The example redefines * to be active and expand to !!. This works.

The preamble stores the pristine definition of foo into foo@orig and attempts to cleverly rebuild foo, using an outer macro foo@prep to provide proper catcode redefinition of * to 12 (other) and redefine foo within this context.

The MWE contains three attempts.

Attempt 1

Redefine catcode of * to 12, and redefine foo macros, passing the arguments unaltered. This does not yield errors, but expands the * as if it were still active.

Actual lines in AUX file

\fooaux  {normal }
\fooaux  {works }
\fooaux  {five !! four }
\fooaux  {x !! x }

Expected lines in AUX file

\fooaux  {normal }
\fooaux  {works }
\fooaux  {five * four }
\fooaux  {x * x }

Attempt 2

This attempt leaves * an active character and tries to redefine it to a primitive using char42. However, this yields an error

! Missing control sequence inserted.
l.33 \foo@prep

Attempt 3

Then I tried out the eTeX \scantokens command by \edefing the macro argument to \@foo@arg@ii representing an expanded, properly catcoded variant of its argument.

But this also yields an error

! Extra \endgroup.
l.33 \endgroup

which I cannot make any sense of.

[Edit fixed syntax error and added attempt 4]

Attempt 4

Used token list instead of \scantokens. This one yields

! Use of \foo@ii doesn't match its definition.
\@ifnextchar ... \reserved@d =#1\def \reserved@a {
                                                  #2}\def \reserved@b {#3}\f...
l.63 Work \foo{normal}
                       \& \foo[ok]{works}

Interestingly, this resembles the error I get in on the real document most.

Can somebody guide me how to "deactivate" active characters passed as macro arguments?

2
  • Why not: \newcommand\foo[2][x]{(#1)\protected@write\@mainaux{}{\fooaux{#2}}}? || The non-optional argument of \foo seems to be used for writing only. So why not applying \string to *: \foo{five\string*four} \& \foo[is expanded in aux]{x\string*x}? || Do you need expansion-protection for * also when \fooaux/\message-command written to aux-file is executed? Commented Apr 7, 2023 at 8:16
  • The package code is shortened and does not show processing not essential for the MWE. Therefore, I could not simply inline the package code. Applying \string* is a clever idea but becomes burdensome if you have hundreds of them.
    – aker
    Commented Apr 8, 2023 at 14:32

1 Answer 1

3

Since several years, LaTeX runs TeX with e-TeX extensions and only very old distributions wouldn't know about \protected.

\documentclass{article}

\makeatletter
% == Package code -- untouchable
\def\foo{\@ifnextchar[{\@foo}{\@foo[x]}}
\DeclareRobustCommand\fooaux[1]{\message{#1}}

\def\@foo[#1]{%
  \@foo@delegate{#1}}

\def\@foo@delegate#1{
  (#1)
  \@foo@write
}

\def\@foo@write#1{%
  \protected@write\@mainaux{}{\fooaux{#1}}
}

% == User code -- can manipulate at will

\protected\def\@catted{
  !!
}
\catcode`\*=13
\let*=\@catted

\makeatother

\begin{document}

Works: *

Work \foo{normal} \& \foo[ok]{works}

Do not \foo{five*four} \& \foo[is expanded in aux]{x*x}

\end{document}

enter image description here

Contents of the .aux file

\relax
\fooaux  {normal}
\fooaux  {works}
\fooaux  {five*four}
\fooaux  {x*x}
\gdef \@abspage@last{1}

Note: I left the unprotected endlines in the definition of \@catted and elsewhere. Please, remove them.

1
  • Thank you! This works like a charm! Didn't know that \def could be \protected
    – aker
    Commented Apr 6, 2023 at 20:37

You must log in to answer this question.

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