Существует ли токен, на который не влияет \uppercase/\lowercase и который не (пере)определяется в терминах \outer?

Существует ли токен, на который не влияет \uppercase/\lowercase и который не (пере)определяется в терминах \outer?

Существует ли токен, на который нельзя повлиять с помощью \uppercase/ \lowercaseи который нельзя (пере)определить в терминах \outer?

(Если так, то я бы хотел использовать его в качестве разделителя аргументов с вещами, которые могут использоваться внутри \uppercase/ \lowercase.)

решение1

Насколько мне известно, такого токена не существует:

Каждый токен принадлежит по крайней мере к одному из следующих двух классов токенов — токены активных персонажей принадлежат к обоим классам одновременно:

  1. Управляющие последовательности. (Управляющие словарные токены, управляющие символьные токены, активные символьные токены.) Все управляющие последовательности (пере)определяемы в терминах \outer.
  2. Явные токены символов. На каждый явный токен символов можно воздействовать с помощью \uppercase/ \lowercase(при условии, что его \uccode/ \lccodeустановлено соответствующим образом).

Во многих ситуациях можно полностью избежать использования аргументов с разделителями, используя вместо этого неразделенные аргументы и проверяя их на пустоту.

Например, для извлечения первого неограниченного аргумента из списка токенов, сбалансированного по фигурным скобкам, я часто использую что-то вроде этого:

%%   \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определяется как

%%-----------------------------------------------------------------------------
%% 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}%
}%

или как

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

. )

При этом вы получаете:

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

Чтобы уменьшить количество итераций, необходимых для удаления всего, кроме первого неразделенного аргумента, я использовал аргумент, разделенный на \UD@SelDOm. (Количество необходимых итераций равно: «Количество \UD@SelDOmвнутри аргумента, которые не вложены в фигурные скобки»+1).

Если вам не нравится разделитель \UD@SelDOm-, как он может быть определен в терминах \outer, то вы можете обойтись без него следующим образом — ценой необходимости дополнительных итераций для получения результата — необходимое количество итераций равно: «Количество неразделенных аргументов внутри аргумента, которые сами по себе не заключены в фигурные скобки»+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}}%
}%

Например,

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

Существует множество ситуаций, в которых вы можете поступить аналогичным образом, чтобы полностью избежать разграничительных аргументов.


Иногда люди используют в качестве разделителя аргумента макрос-токена, который обрабатывает рассматриваемый аргумент, например, \def\macro#1\macro{...}. Осуществимо/разумно ли это, зависит от вопроса, \macroможет ли он быть вложен в свой собственный аргумент и, таким образом, ошибочно соответствовать разделителю или может ли произойти что-то подобное \let\macrob=\macro \outer\def\macro....



Поскольку вы уже думаете над вопросом, как сделать макроаргументы безопасными, я хотел бы отметить, что существуют и другие ловушки, помимо \outer\def...и \uppercase/ \lowercase:

  1. Например, вопрос о том, должен ли макромеханизм, основанный на разделенных аргументах, который должен обрабатывать (почти) произвольные аргументы, заданные пользователем, работать внутри табличной среды/в пределах выравнивания.

    Предположим, например, что макрос \grabdelimitedобрабатывает аргумент с разделителями и используется пользователем для сбора и детокенизации символов Bи &.

    \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}
    

    Первая/закомментированная строка внутри табличного окружения приведет к ошибке, вторая — нет, поскольку здесь &принадлежность к разделенному аргументу скрыта внутри фигурных скобок \@firstofone.

  2. Еще одной проблемой может быть передача несбалансированных \if.../ \else/ \fiв качестве макроаргументов, которые могут ошибочно совпадать с некоторыми \if.../ \else, встречающимися в определениях макросов, обрабатывающих эти аргументы.

    То же самое с несбалансированным \csname/ \endcsname.

решение2

Расширенный комментарий:

Если вы действительно боитесь, что ваш маркер конца аргумента может быть затронут \uppercase/, \lowercaseвам просто нужно убедиться, что он не отображается на верхнем уровне, чтобы его никогда не увидели \uppercaseили \lowercase. Этого можно добиться, убедившись, что он вставляется только в контекст, где расширение не может остановиться между ними, например, с помощью \romannumeralконтекста расширения. Ниже настраивается макрос, который использует символ Dв качестве маркера конца аргумента, но поскольку он расширяется дальше, этот маркер никогда не остается во входном потоке, так что на него может повлиять \lowercaseили \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}

Затем \mymacro@bможно обработать аргумент расширяемым способом и использовать Dв качестве разделителя. И вы можете закончить \romannumeralконтекст расширения с помощью \z@. Конечно, вы все еще можете расширить \mymacroодин раз, а затем расширить \mymacro@aс помощью \expandafterбез начала \romannumeral, так что Dможет быть затронуто \lowercase(с помощью \expandafter\expandafter\expandafter\lowercase\expandafter\expandafter\expandafter{\expandafter\expandafter\mymacro{}}), но по крайней мере теперь это должно быть создано со злым умыслом.

Вы никогда не сможете защитить себя от \outerпереопределения, но люди, переопределяющие внутреннюю часть чужого кода, \outerпохоже, не хотят иметь работающий код, так что, возможно, это не тот случай, от которого вам нужно защищаться.

Связанный контент