\uppercase
是否存在不受/影响\lowercase
且不能(重新)定义的标记\outer
?
(如果是这样,我想将它用作可以在\uppercase
/内部使用的内容的参数分隔符\lowercase
。)
答案1
据我所知,没有这样的标记:
每个标记至少属于以下两类标记中的一类 - 活动角色标记同时属于这两类:
- 控制序列。(控制字标记、控制符号标记、活动字符标记。)所有控制序列都可以根据 进行(重新)定义
\outer
。 - 显式字符标记。每个显式字符标记都会受到
\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)。
如果您不喜欢 -delimiter \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...
and \uppercase
/之外还有其他陷阱\lowercase
:
例如,基于分隔参数的宏机制是否应该在表格环境/对齐内工作,该机制应处理用户给出的(几乎)任意参数。
例如,假设一个宏
\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
。另一个问题可能是将不平衡的
\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
不想拥有有效的代码,所以也许这不是您需要保护自己的情况。