Écriture immédiate de @ifnextchar

Oct 13 2020

Lors de l'écriture d'un test pour l3build, j'ai rencontré un problème lors de la sortie du résultat d'une commande utilisée @ifnextchar. Concrètement, a \TYPE{\command}donné une erreur de compilation. Je l'ai réduit au MWE suivant:

\documentclass{article}

\begin{document}
\makeatletter
\DeclareRobustCommand\lookahead{\@ifnextchar{z}{hello}{goodbye}}
\immediate\write128{\lookahead z}
\makeatother
\end{document}

Sur ma machine (pdflatex, TeX Live 2020 / Debian) le code précédent donne l'erreur: Argument of \reserved@a has an extra }. Je suis un peu hors de ma profondeur ici, donc après quelques recherches, j'ai décidé qu'il serait préférable de demander de l'aide. Savez-vous pourquoi il donne cette erreur et pourrait-on faire quelque chose pour la résoudre? Notez que la méthode de sortie ne doit pas être modifiée, car c'est essentiellement celle utilisée par l3build .

Merci beaucoup!

Réponses

1 UlrichDiez Oct 13 2020 at 19:41

Je peux proposer un mécanisme extensible \UD@CheckWhetherLeadingTokensau moyen duquel vous pouvez demander à LaTeX de vérifier au moyen de macros qui traitent des arguments délimités si les jetons principaux d'un argument de macro forment un ensemble spécifique de jetons.

\UD@CheckWhetherLeadingTokensest différent de \@ifnextchar/ \kernel@ifnextcharsous plusieurs aspects:

  • \UD@CheckWhetherLeadingTokens est extensible.
  • \UD@CheckWhetherLeadingTokensne "regarde pas en avant" le jeton suivant dans le flux de jetons. Au lieu de cela, il "regarde" les premiers jetons d'un macro-argument.

(Avec \@ifnextchar/ \kernel@ifnextcharet avec, \UD@CheckWhetherLeadingTokensvous devrez peut-être faire attention lorsque \uppercase/ \lowercase/ \MakeUppercase/ \MakeLowercaseet autres jouent un rôle.)

\documentclass{article}
\makeatletter
%==========[code for checking leading token-sequences in arguments]============
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
  {\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Exchange two arguments. (From each argument an outermost level of 
%% surrounding braces will be removed if present.)
%%-----------------------------------------------------------------------------
\newcommand\UD@Exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific 
%% token-sequence that does not contain explicit character tokens of 
%% category code 1 or 2:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%%                              {<a <token sequence> without explicit 
%%                                character tokens of category code
%%                                1 or 2>}%
%%                              {a <single non-space token> that does 
%%                                _not_ occur in <token sequence> >}%
%%                              {<internal token-check-macro>}%
%%                              {<tokens to be delivered in case
%%                                <argument which is to be checked> has
%%                                <token sequence> as leading tokens>}%
%%                              {<tokens to be delivered in case 
%%                                <argument which is to be checked>
%%                                does not have <token sequence> as
%%                                leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[4]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\UD@Exchange{ }\expandafter\@secondoftwo}%
  {\expandafter\@secondoftwo\string{\expandafter
   \UD@@CheckWhetherLeadingTokens#4#3#1#2}{}}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
  {\UD@Exchange{\@firstoftwo}}{\UD@Exchange{\@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%%                              {<token sequence>}%
%% Defines <internal token-check-macro> to snap everything 
%% until reaching <token sequence>-sequence and spit that out
%% nested in braces.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[2]{%
  \@ifdefinable#1{\long\def#1##1#2{{##1}}}%
}%
%=======[end of code for checking leading token-sequences in arguments]=========

\UD@internaltokencheckdefiner{\zcheck}{z}%
\newcommand\lookahead[1]{%
  \UD@CheckWhetherLeadingTokens{#1}{z}{.}{\zcheck}{hello}{goodbye} #1%
}%
\makeatother

\begin{document}

\immediate\write128{ble ble \lookahead{y bla} blu blu}

\immediate\write128{ble ble \lookahead{{z} bla} blu blu}

\immediate\write128{ble ble \lookahead{z bla} blu blu}

\end{document}

Avec l'exemple ci-dessus, j'obtiens ceci sur le terminal:

ble ble goodbye y bla blu blu
ble ble goodbye {z} bla blu blu
ble ble hello z bla blu blu

Une autre approche pourrait être d' \lookaheadeffectuer un hack d'accolade pour supprimer l'accolade ouvrante avant d'appeler une autre macro \lookaheadbpour effectuer réellement le lookahead via \@ifnextcharet d' \@ifnextchareffectuer un autre hack d'accolade pour ajouter une accolade d'ouverture et appeler \immediate\writel'argument:

\documentclass{article}

\makeatletter
\newcommand\lookahead{%
  \expandafter\expandafter\expandafter\lookaheadb\expandafter\@gobble\string
}%
\newcommand\lookaheadb{%
  \@ifnextchar{z}%
  {\immediate\write128\expandafter\expandafter\expandafter{\expandafter\@gobble\string}hello }%
  {\immediate\write128\expandafter\expandafter\expandafter{\expandafter\@gobble\string}goodbye }%
}%
\makeatother

\begin{document}

\lookahead{y bla bla bla}

\lookahead{{z} bla bla bla}

\lookahead{z bla bla bla}

\end{document}

Avec l'exemple ci-dessus, j'obtiens ceci sur le terminal:

goodbye y bla bla bla
goodbye {z} bla bla bla
hello z bla bla bla

Avec cette approche, \lookaheadne peut pas être imbriqué dans la \writecommande, mais appelle la \writecommande-.