我最喜欢 Plain TeX 的一个功能是能够用合理的分隔符分隔参数来编写宏。例如
\def\translate#1 = #2 (#3){...}
LaTeX 的实现方式是什么?我知道这种语法是向后兼容的,但用 as\rm
代替\textrm
它并不是惯用的。
答案1
由于 expl3 现在被大肆宣传为 LaTeX 2ε 的一部分,我认为没有理由不将 expl 语法视为 LaTeX——请注意,\cs_new:Npn
如果已定义的命令即将被覆盖,您会收到一条消息:
\documentclass{article}
\ExplSyntaxOn
\cs_new:Npn \translate #1~=~#2~(#3) {Argument~1:~#1;~Argument~2:~#2;~Argument~3:~#3.}
\ExplSyntaxOff
\begin{document}
[\translate A = B (C)]
\end{document}
在expl3之前我可能会这样做:
\documentclass{article}
\makeatletter
\@ifdefinable\translate{%
\def\translate#1 = #2 (#3){Argument 1: #1; Argument 2: #2; Argument 3: #3.}%
}%
\makeatother
\begin{document}
[\translate A = B (C)]
\end{document}
为了删除参数周围任意数量的显式空格标记,我可能结合了一个例程
\UD@TrimAllSurroundSpaceAndOneLevelOfBraces{<action>}{<argument>}
来逐个抓取分隔参数,并考虑到在抓取参数时防止删除括号:
\makeatletter
%%//////////////////////////////////////////////////////////////////////////////
%% SECTION EXPANDABLE REMOVAL OF LEADING AND TRAILING SPACES.
%%
%% The obscure case of removing several leading/trailing spaces was taken
%% into consideration.
%%
%% Removal of spaces was implemented in a way where no brace-stripping from
%% the arguments takes place.
%% Explicit-catcode-1/2-character-token-pairs remain untouched.
%%
%% Spaces interspersing the argument or hidden within braces will be left in
%% place.
%%
%% The arguments themselves do not get expanded.
%%
%% (For some obscure reason I don't remember any more I needed this in the
%% past.)
%%
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherLeadingExplicitSpace,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% 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]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%-----------------------------------------------------------------------------
%% -- Take advantage of the fact that TeX discards space tokens when
%% "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument which is to be checked is blank>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not blank>}%
\newcommand\UD@CheckWhetherBlank[1]{%
\romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}{}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has a leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a
%% leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does have a
%% leading explicit space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does not have a
%% a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral\expandafter\UD@secondoftwo
\string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
}{}%
}%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
\long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%==============================================================================
%% \UD@TrimAllLeadSpace{<action>}{<argument>}
%%..............................................................................
%% expandably removes all leading spaces from <argument> in case at least
%% one leading space is present.
%% Then
%% <action>{<argument without leading spaces>}
%% is performed.
%%==============================================================================
\newcommand\UD@TrimAllLeadSpace[2]{%
\romannumeral\UD@TrimAllLeadSpaceLoop{#2}{#1}%
}%
\newcommand\UD@TrimAllLeadSpaceLoop[2]{%
\UD@CheckWhetherLeadingExplicitSpace{#1}{%
\expandafter\UD@TrimAllLeadSpaceLoop\expandafter{\UD@removespace#1}{#2}%
}{\UD@stopromannumeral#2{#1}}%
}%
%%==============================================================================
%% \UD@TrimAllTrailSpace{<action>}{<argument>}
%%..............................................................................
%% expandably removes all trailing spaces from <argument> in case at least
%% one trailing space is present.
%% Then
%% <action>{<argument without trailing spaces>}
%% is performed.
%%==============================================================================
\newcommand\UD@TrimAllTrailSpace[2]{%
\romannumeral\UD@TrimTrailSpaceLoop{#2 }{}{#1}{}%
}%
\newcommand\UD@TrimTrailSpaceLoop[4]{%
% in a loop shuffle space-delimited arguments from #1 to #2
% until #1 is blank, i.e. empty or containing explicit space
% tokens only.
%
% #1 - the remaining argument from which space-delimited bits
% are to be shuffled to #2.
% #2 - argument without trailing spaces gathered so far.
% #3 - <action>.
% #4 - separator when appending stuff to #2; empty in 1st
% iteration, explicit space token in subsequent iterations.
%
% Shuffling requires a routine for removing the 1st space-delimited
% argument from #1 - that is "\UD@RemoveFirstSpaceDelimited#1".
%
% Shuffling also requires a routine for extracting the 1st space-
% delimited argument from #1 - that is
% "\romannumeral\UD@keepFirstSpaceDelimited{\UD@stopromannumeral#1}".
%
\UD@CheckWhetherBlank{#1}{\UD@stopromannumeral#3{#2}}{%
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{\expandafter
\UD@Exchange
\expandafter{\romannumeral\UD@keepFirstSpaceDelimited{\UD@stopromannumeral#1}}{#2#4}%
}{%
\expandafter\UD@TrimTrailSpaceLoop
\expandafter{\UD@RemoveFirstSpaceDelimited#1}%
}{#3}{ }%
}%
}%
\@ifdefinable\UD@RemoveFirstSpaceDelimited{\long\def\UD@RemoveFirstSpaceDelimited#1 {}}%
\@ifdefinable\UD@StartExtractFirstSpaceDelimitedloop{%
\long\def\UD@StartExtractFirstSpaceDelimitedloop#1 {{#1}}%
}%
\newcommand\UD@keepFirstSpaceDelimited[1]{%
\expandafter\UD@keepFirstSpaceDelimitedLoop\expandafter{\UD@StartExtractFirstSpaceDelimitedloop#1}%
}%
\newcommand\UD@keepFirstSpaceDelimitedLoop[1]{%
\expandafter\UD@CheckWhetherBlank\expandafter{\UD@firstoftwo{}#1}{%
\expandafter\UD@firstoftwo\expandafter{\UD@firstoftwo#1{}}{}%
}{%
\expandafter\UD@keepFirstSpaceDelimitedLoop\expandafter{\UD@@KeepFirstSpaceDelimited#1}%
}%
}%
\@ifdefinable\UD@@KeepFirstSpaceDelimited{\long\def\UD@@KeepFirstSpaceDelimited#1#2 {{#1}}}%
%%==============================================================================
%% \UD@TrimAllSurroundSpaceAndOneLevelOfBraces{<action>}{<argument>}
%%..............................................................................
%% expandably removes all leading and trailing spaces from <argument>
%% that are present.
%% Then removes one level of surrounding braces if present[1].
%% Then
%% <action>{<argument without leading and trailing spaces and with one level of surrounding braces removed if previously present>}
%% is performed.
%% [1] This way you can hide spaces that shall not be removed by placing
%% arguments inside curly braces.
%%==============================================================================
\newcommand\UD@TrimAllSurroundSpaceAndOneLevelOfBraces[2]{%
\romannumeral\UD@TrimAllLeadSpace{\UD@TrimAllTrailSpace{\UD@Bracecheck{#1}}}{#2}%
}%
\newcommand\UD@Bracecheck[2]{%
\UD@CheckWhetherBrace{#2}{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#2}{%
\UD@stopromannumeral#1#2%
}{\UD@stopromannumeral#1{#2}}%
}{\UD@stopromannumeral#1{#2}}%
}%
%%
%% EOF SECTION EXPANDABLE REMOVAL OF LEADING AND TRAILING SPACES.
%%//////////////////////////////////////////////////////////////////////////////
\newcommand*\translate{\grabtranslate@equal{}}%
\@ifdefinable\grabtranslate@equal{\long\def\grabtranslate@equal#1={\expandafter\grabtranslate@leftparen\expandafter{\UD@firstoftwo{}#1}{}}}%
\@ifdefinable\grabtranslate@leftparen{\long\def\grabtranslate@leftparen#1#2({\expandafter\grabtranslate@rightparen\expandafter{\UD@firstoftwo{}#2}{#1}{}}}%
\@ifdefinable\grabtranslate@rightparen{\long\def\grabtranslate@rightparen#1#2#3){\expandafter\translate@inner\expandafter{\UD@firstoftwo{}#3}{#1}{#2}{}}}%
\newcommand\translate@inner[3]{%
\UD@TrimAllSurroundSpaceAndOneLevelOfBraces{%
\UD@TrimAllSurroundSpaceAndOneLevelOfBraces{%
\UD@TrimAllSurroundSpaceAndOneLevelOfBraces{\innertranslate}{#3}%
}{#2}%
}{#1}%
}%
\newcommand\innertranslate[3]{%
\texttt{\frenchspacing\detokenize{Argument 1: |#1|; Argument 2: |#2|; Argument 3: |#3|.}}%
}%
\makeatother
\documentclass{article}
\begin{document}
\translate A = B (C)
\translate A= B (C)
\translate A =B (C)
\translate A=B(C)
\translate A=B( C )
\translate {A} = {B} ({C})
\translate {A}= {B} ({C})
\translate {A} ={B} ({C})
\translate {A}={B}({C})
\translate {A}={B}( {C} )
The spaces inside the curly braces are kept:
\translate { A } = { B } ({ C })
\translate { A }= { B } ({ C })
\translate { A } ={ B } ({ C })
\translate { A }={ B }({ C })
\translate { A }={ B }( { C } )
Test with brace-groups not surrounding arguments but as components of arguments:
\translate {A}A = {B}B ({C}C)
\translate {A}A= {B}B ({C}C)
\translate {A}A ={B}B ({C}C)
\translate {A}A={B}B({C}C)
\translate {A}A={B}B( {C}C )
Spaces surrounding arguments are removed, spaces between components of arguments are kept:
\translate {A} A = {B} B ({C} C)
\translate {A} A= {B} B ({C} C)
\translate {A} A ={B} B ({C} C)
\translate {A} A={B} B({C} C)
\translate {A} A={B} B( {C} C )
\end{document}
当然,有了这种用于抓取和预处理参数的宏机制,错误消息可能会变得更加难以理解,因为有些错误消息可能会在处理内部宏时出现,而最终用户如果不阅读相关包的注释源则可能不会意识到这些错误消息。
并且在实践中,您很少需要代码来删除任意数量的前导和尾随显式空格标记,其中使用用户提供的参数时没有标记(但根据定义的标记\outer
)是被禁止的,因为它在用户提供的参数中的出现可能会破坏机制。
实际上,代码最多删除一个前导显式空格和一个尾随显式空格,使用一些参数分隔符来表示参数的结束,而用户可能不会将其作为参数的组成部分提供,否则机制就会中断,这就足够了。