Compreendendo a expansão em \addtocontents

Compreendendo a expansão em \addtocontents

Estou confuso sobre como a expansão funciona \addtocontents, ou mais precisamente, em \protected@write. Pelo que entendi, \addtocontentsdeveria essencialmente expandir seu segundo argumento \protected@edefe gravar o resultado no arquivo aux. Com esse entendimento, eu esperaria que o comportamento fosse (até a \protected@) o mesmo que

\iow_shipout:Ne \@auxout
  {
    \exp_not:N \@writefile { #1 } { #2 }
  }

Mas, como mostra o MWE a seguir, eles se comportam de maneira diferente ao tentar impedir a expansão dentro de seu argumento com \exp_not:n.

\documentclass{article}

\makeatletter

\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
  {
    \iow_shipout:Ne \@auxout
      {
        \exp_not:N \@writefile { #1 } { #2 }
      }
  }
\ExplSyntaxOff

\def\abc{some text}

\begin{document}

bla

\ExplSyntaxOn

% this does what I expect
\protected@edef \l_tmpa_tl { \exp_not:n { \abc } }
\tl_show:N \l_tmpa_tl

% so does this
\naiveaddtocontents{ lof }{ \exp_not:n { \abc } }

% this does not
\addtocontents{ lof }{ \exp_not:n { \abc } }

\ExplSyntaxOff

\end{document}

O arquivo aux se parece com

\relax 
\@writefile {lof}{\abc }
\@writefile{lof}{some text}
\gdef \@abspage@last{1}

O mesmo \naiveaddtocontentsacontece com a prevenção da expansão \exp_not:n, mas \addtocontentsnão é. Para o meu caso de uso em que não quero que o argumento seja expandido, posso simplesmente usar this \naiveaddtocontents. Mas por que não \exp_not:nfunciona como eu esperava incorretamente \addtocontents?

Responder1

O comando \addtocontentsusa \protected@write. Abaixo está a definição de \protected@writefrom source2e:

\long\def \protected@write#1#2#3{%
      \begingroup
       \let\thepage\relax
       #2%
       \let\protect\@unexpandable@protect
       \edef\reserved@a{\write#1{#3}}%
       \reserved@a
      \endgroup
      \if@nobreak\ifvmode\nobreak\fi\fi
}

A parte relevante é \edef\reserved@a{\write#1{#3}}%. Se a definição acima \protected@writefor adicionada no documento e na linha anterior, \edefé substituída por \defentão \addtocontents{ lof }{ \exp_not:n { \abc } }\@writefile{lof}{\abc }no arquivo .aux.

Porém com \edef, ocorre uma expansão adicional que \addtocontents{ lof }{ \exp_not:n { \abc } }\@writefile{lof}{some text}no arquivo .aux.

Portanto com \edef, o exemplo funciona se \exp_not:nfor adicionado um adicional: \addtocontents{ lof }{ \exp_not:n { \exp_not:n { \abc } } }\@writefile{lof}{\abc }no arquivo .aux.

Responder2

Vejamos a definição de \addtocontents:

% latex.ltx, line 14289:
\long\def\addtocontents#1#2{%
  \protected@write\@auxout
      {\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
      {\string\@writefile{#1}{#2}}}

Com sua definição ingênua, você teria grandes problemas se tivesse um \labelno segundo argumento para \addtocontents. Mas vamos deixar isso de lado, porque é um detalhe técnico.

Suponha que você queira \addtocontents{toc}{Hey, this is \textbf{boldface}}.

\documentclass{article}

\makeatletter
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
  {
    \iow_shipout:Ne \@auxout
      {
        \exp_not:N \@writefile { #1 } { #2 }
      }
  }
\ExplSyntaxOff
\makeatother

\begin{document}

Some text

\addtocontents{toc}{Hey, this is \textbf{boldface}}

\naiveaddtocontents{toc}{Hey, this is \textbf{boldface}}

\end{document}

O console irá imprimir

(\end occurred when \ifx on line 21 was incomplete)
(\end occurred when \ifx on line 21 was incomplete)
(\end occurred when \ifx on line 21 was incomplete)
(\end occurred when \ifmmode on line 21 was incomplete)</usr/local/texlive/2023
/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb>

e o auxarquivo terá

\relax
\@writefile{toc}{Hey, this is \textbf  {boldface}}
\@writefile {toc}{Hey, this is \protect \unhbox \voidb@x \bgroup \edef l3backend-pdftex.def{boldface}\let \futurelet \@let@token \let \protect \relax \edef cmr{cmr}\edef cmss{cmss}\edef cmtt{cmtt}\def ##1,b,{}\series@check@toks {,ulm,elm,lm,slm,mm,sbm,bm,ebm,ubm,muc,mec,mc,msc,msx,mx,mex,mux,{}{},b,}\edef {}\edef b{b}\def ##1,m,{}\series@check@toks {,ulm,elm,lm,slm,mm,sbm,bm,ebm,ubm,muc,mec,mc,msc,msx,mx,mex,mux,{}{},m,}\edef {}\edef m{m}\protect \let }
\gdef \@abspage@last{1}

Não é realmente o que você quer ver, não é? Vamos consertar isso.

\documentclass{article}

\makeatletter
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
  {
    \iow_shipout:Ne \@auxout
      {
        \exp_not:N \@writefile { #1 } { \text_expand:n { #2 } }
      }
  }
\ExplSyntaxOff
\makeatother

\newcommand{\abc}{some text}

\begin{document}

Some text

\addtocontents{toc}{Hey, this is \textbf{boldface} and \abc}

\naiveaddtocontents{toc}{Hey, this is \textbf{boldface} and \abc}

\end{document}

Agora o auxarquivo terá

\relax
\@writefile{toc}{Hey, this is \textbf  {boldface} and some text}
\@writefile {toc}{Hey, this is \textbf {boldface} and some text}
\gdef \@abspage@last{1}

Basicamente, \text_expand:nfará aproximadamente o mesmo \protected@edef, mas deixando a lista de tokens resultante envolvida em \unexpanded.

Se você fizer isso \exp_not:n(ou seja, \unexpanded), o TeX faránãoexpansão, então você obtém \abce não sua expansão.

Sem \makeatlettere \makeatother:

\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
  {
    \iow_shipout:ce { @auxout }
      {
        \token_to_str:c { @writefile } { #1 } { \text_expand:n { #2 } }
      }
  }
\ExplSyntaxOff

Em vez de \token_to_str:cvocê pode usar\exp_not:c

informação relacionada