defin%C3%ADvel%20em%20termos%20de%20%5Couter%3F.png)
Existe um token que não pode ser afetado por \uppercase
/ \lowercase
e não (re) definível em termos de \outer
?
(Nesse caso, gostaria de usá-lo como delimitador de argumento com coisas que podem ser usadas dentro de \uppercase
/ \lowercase
.)
Responder1
Afaik não existe tal token:
Cada token pertence a pelo menos uma das duas classes de tokens a seguir – os tokens de personagem ativo pertencem a ambas as classes simultaneamente:
- Sequências de controle. (Tokens de palavras de controle, tokens de símbolos de controle, tokens de caracteres ativos.) Todas as sequências de controle são (re)definíveis em termos de
\outer
. - Tokens de caracteres explícitos. Cada token de caractere explícito é afetado por
\uppercase
/\lowercase
(desde que seu\uccode
/\lccode
seja definido adequadamente).
Em muitas situações, você pode evitar argumentos delimitados inteiramente, em vez disso, usando argumentos indelimitados e verificando se há vazios.
Por exemplo, para extrair o primeiro argumento ilimitado de uma lista de tokens com balanceamento de chaves, costumo usar algo assim:
%% \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@CheckWhetherNull
sendo definido 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}%
}%
ou como
\newcommand\UD@CheckWhetherNull[1]{%
\romanumeral\ifcat$\detokenize{#1}$%
\expandafter\expandafter\expandafter\z@\expandafter\@firstoftwo\else
\expandafter\expandafter\expandafter\z@\expandafter\@secondoftwo\fi
}%
. )
Com isso você obtém:
\romannumeral\UD@ExtractFirstArgLoop{{A}{B}CDE\UD@SelDOm}%
→ A
.
Para diminuir a quantidade de iterações necessárias para remover tudo, exceto o primeiro argumento não delimitado, usei um argumento delimitado por \UD@SelDOm
. (A quantidade de iterações necessárias é: "Quantidade dentro \UD@SelDOm
do argumento que não está aninhada entre colchetes"+1).
Se você não gosta do \UD@SelDOm
-delimiter como ele pode ser definido em termos de \outer
, então você pode dispensar o seguinte - ao custo de precisar de mais iterações até obter o resultado - a quantidade de iterações necessárias é: "Quantidade de argumentos indelimitados dentro do argumento de que eles próprios não estão aninhados entre colchetes"+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}}%
}%
Por exemplo,
\romannumeral\UD@ExtractFirstArgLoop{{A}{BC}DE{}}%
→A
Existem muitas situações em que você pode fazer coisas semelhantes para evitar totalmente argumentos delimitados.
Às vezes as pessoas usam como delimitador de argumento o macro-token que processa o argumento em questão, por exemplo, \def\macro#1\macro{...}
. Se isso é viável/razoável depende da questão de saber se \macro
poderia ser aninhado em seu próprio argumento e, portanto, corresponder erroneamente ao delimitador ou se algo semelhante \let\macrob=\macro \outer\def\macro...
poderia ocorrer.
Como você já está pensando na questão de como tornar os argumentos macro seguros, gostaria de observar que existem outras armadilhas além de \outer\def...
e \uppercase
/ \lowercase
:
Por exemplo, a questão de saber se um macromecanismo baseado em argumentos delimitados, que deve lidar com argumentos (quase) arbitrários fornecidos pelo usuário, deve funcionar dentro de um ambiente tabular/dentro de um alinhamento.
Suponha, por exemplo, uma macro
\grabdelimited
que processe um argumento delimitado e que o usuário use para reunir e destokenizar os caracteresB
e&
.\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}
A primeira linha/comentada dentro do ambiente tabular geraria um erro, a segunda não porque aqui o
&
pertencimento ao argumento delimitado está oculto entre chaves de\@firstofone
.Ainda outro problema pode ser passar
\if...
/\else
/ desequilibrados\fi
como macro-argumentos que podem corresponder erroneamente a alguns\if...
/\else
que ocorrem nas definições das macros que processam esses argumentos.O mesmo com desequilibrado
\csname
/\endcsname
.
Responder2
Comentário estendido:
Se você realmente tem medo de que seu marcador de final de argumento seja afetado por \uppercase
/ \lowercase
você só precisa ter certeza de que ele não apareça no nível superior para que nunca seja visto por \uppercase
ou \lowercase
. Isto poderia ser alcançado certificando-se de que ele seja inserido apenas em um contexto onde a expansão não possa parar, por exemplo, usando um \romannumeral
contexto de expansão. O seguinte configura uma macro que usa o caractere D
como marcador de final de argumento, mas como ele se expande ainda mais, esse marcador nunca é deixado no fluxo de entrada de forma que possa ser afetado por \lowercase
ou \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}
Então \mymacro@b
poderia processar o argumento de maneira expansível e usá-lo D
como delimitador. E você poderia encerrar o \romannumeral
contexto de expansão com \z@
. É claro que você ainda pode expandir \mymacro
uma vez e depois expandir \mymacro@a
sem \expandafter
iniciar, \romannumeral
de modo que D
possa ser afetado por \lowercase
(with \expandafter\expandafter\expandafter\lowercase\expandafter\expandafter\expandafter{\expandafter\expandafter\mymacro{}}
), mas pelo menos agora isso deve ser criado com intenções maliciosas.
Você nunca pode se proteger contra uma \outer
redefinição, mas as pessoas que redefinem os aspectos internos do código de outras pessoas parecem \outer
não querer ter um código funcional, então talvez este não seja um caso contra o qual você precise se proteger.