¿Existe un token que no se vea afectado por \uppercase/\lowercase y no (re)definible en términos de \outer?

¿Existe un token que no se vea afectado por \uppercase/\lowercase y no (re)definible en términos de \outer?

¿Existe un token que no sea afectado por \uppercase/ \lowercasey no (re)definible en términos de \outer?

(Si es así, me gustaría usarlo como delimitador de argumentos con cosas que pueden usarse dentro de \uppercase/ \lowercase).

Respuesta1

Afaik no existe tal token:

Cada token pertenece al menos a una de las siguientes dos clases de tokens; los tokens de personajes activos pertenecen a ambas clases simultáneamente:

  1. Secuencias de control. (Fichas de palabras de control, fichas de símbolos de control, fichas de caracteres activos). Todas las secuencias de control son (re)definibles en términos de \outer.
  2. Fichas de personajes explícitas. Cada token de carácter explícito se ve afectado por \uppercase/ \lowercase(siempre que su \uccode/ \lccodeesté configurado en consecuencia).

En muchas situaciones, puede evitar por completo los argumentos delimitados si utiliza argumentos no delimitados y comprueba si están vacíos.

Por ejemplo, para extraer el primer argumento ilimitado de una lista de tokens equilibrada con llaves, suelo utilizar algo como esto:

%%   \romannumeral\UD@ExtractFirstArgLoop{<argument>\UD@SelDOm}%
%%   yields <argument>'s 1st undlimited argument.
%%   <argument> must not be blank, i.e., must neither be empty nor consist
%%   only of explicit character tokens of catcode 10 and charcode 32.
%%
%%   \UD@SelDOm must not be defined in terms of \outer !
%%.............................................................................
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
  \long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
  {\expandafter\z@\@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%

( \UD@CheckWhetherNulldefinido como

%%-----------------------------------------------------------------------------
%% 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>}%
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\z@\@secondoftwo}%
  {\expandafter\z@\@firstoftwo}%
}%

o como

\newcommand\UD@CheckWhetherNull[1]{%
  \romanumeral\ifcat$\detokenize{#1}$%
  \expandafter\expandafter\expandafter\z@\expandafter\@firstoftwo\else
  \expandafter\expandafter\expandafter\z@\expandafter\@secondoftwo\fi
}%

. )

Con esto obtienes:

\romannumeral\UD@ExtractFirstArgLoop{{A}{B}CDE\UD@SelDOm}%A.

Para disminuir la cantidad de iteraciones necesarias para eliminar todo menos el primer argumento no delimitado, utilicé un argumento delimitado por \UD@SelDOm. (La cantidad de iteraciones necesarias es: "Cantidad de \UD@SelDOmdentro del argumento que no están anidadas entre llaves"+1).

Si no le gusta el \UD@SelDOmdelimitador -como podría definirse en términos de \outer, entonces puede prescindir de él de la siguiente manera, a costa de necesitar más iteraciones hasta obtener el resultado; la cantidad de iteraciones necesarias es: "Cantidad de argumentos no delimitados dentro del argumento que ellos mismos no están anidados entre llaves"+1:

% Syntax now is: \romannumeral\UD@ExtractFirstArgLoop{<argument>{}}%
\newcommand\UD@GrabFirst[2]{{#1}}%
\renewcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
  {\expandafter\z@\@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@GrabFirst#1}}%
}%

P.ej,

\romannumeral\UD@ExtractFirstArgLoop{{A}{BC}DE{}}%A

Hay muchas situaciones en las que puedes hacer cosas similares para evitar por completo discusiones delimitadas.


A veces la gente utiliza como delimitador de argumento el macro-token que procesa el argumento en cuestión, por ejemplo, \def\macro#1\macro{...}. Si esto es factible/razonable depende de la cuestión de si \macropodría anidarse dentro de su propio argumento y, por lo tanto, coincidir erróneamente con el delimitador o si \let\macrob=\macro \outer\def\macro...podría ocurrir algo similar.



Como ya estás pensando en la cuestión de cómo hacer que los argumentos macro sean seguros, me gustaría señalar que existen otras trampas además de \outer\def...y \uppercase/ \lowercase:

  1. Por ejemplo, la cuestión de si un macromecanismo basado en argumentos delimitados, que manejará argumentos (casi) arbitrarios dados por el usuario, debería funcionar dentro de un entorno tabular/dentro de una alineación.

    Supongamos, por ejemplo, una macro \grabdelimitedque procesa un argumento delimitado y que el usuario utiliza para recopilar y detokenizar los caracteres By &.

    \documentclass{article}
    \def\grabdelimited#1\delimiter{Arguments grabbed: \detokenize{#1}}%
    \begin{document}
    \grabdelimited B&\delimiter
    
    \makeatletter
    \begin{tabular}{|l|l|}
    %A&\relax\grabdelimited B&\delimiter\\
    A&\relax\expandafter\@firstofone\expandafter{\grabdelimited B&\delimiter}
    \end{tabular}
    \end{document}
    

    La primera línea/comentada dentro del entorno tabular produciría un error, la segunda no porque aquí la &pertenencia al argumento delimitado está oculta dentro de las llaves de \@firstofone.

  2. Otro problema más podría ser pasar \if.../ \else/ desequilibrados \ficomo macroargumentos que podrían coincidir erróneamente con algunos \if.../ \elseque aparecen en las definiciones de las macros que procesan esos argumentos.

    Lo mismo con \csname/ desequilibrado \endcsname.

Respuesta2

Comentario extendido:

Si realmente tienes miedo de que tu marcador de fin de argumento pueda verse afectado por \uppercase/ \lowercasesolo tienes que asegurarte de que no aparezca en el nivel superior para que nunca sea visto por \uppercaseo \lowercase. Esto podría lograrse asegurándose de que sólo se inserte en un contexto donde la expansión no pueda detenerse en el medio, por ejemplo, usando un \romannumeralcontexto de expansión. Lo siguiente configura una macro que utiliza el carácter Dcomo marcador de fin de argumento, pero como se expande aún más, ese marcador nunca se deja en el flujo de entrada de modo que pueda verse afectado por \lowercaseo \uppercase:

% first insert a \romannumeral such that the following is expanded as far as possible
\newcommand*\mymacro{\romannumeral\mymacro@a}
% only after \romannumeral has started input the delimiter
\newcommand\mymacro@a[1]{\mymacro@b #1D}

Luego \mymacro@bpodría procesar el argumento de manera expandible y usarlo Dcomo delimitador. Y podrías finalizar el \romannumeralcontexto de expansión con \z@. Por supuesto, aún puede expandirse \mymacrouna vez y luego expandirse \mymacro@asin \expandaftercomenzar \romannumeralde modo que Dpueda verse afectado por \lowercase(con \expandafter\expandafter\expandafter\lowercase\expandafter\expandafter\expandafter{\expandafter\expandafter\mymacro{}}), pero al menos ahora esto debe crearse con intenciones maliciosas.

Nunca podrá protegerse contra una \outerredefinición, pero las personas que redefinen los aspectos internos del código de otras personas parecen \outerno querer tener un código que funcione, por lo que tal vez este no sea un caso contra el que deba protegerse.

información relacionada