为了实现比此处所示更复杂的目的,我想测试某些命令,这些命令既有普通形式,也有星号形式,后者使用包构建suffix
。为此,我将每个形式作为参数传递给\try
包含\csname
...\endcsname
表达式的宏。
例如:
\documentclass{article}
\usepackage{suffix}
\newcommand{\toward}{\rightarrow}
\WithSuffix\newcommand\toward*{\Rightarrow}
\newcommand{\try}[1]{$a \csname#1\endcsname b$}
\begin{document}
OK: $a \toward b$ and $a \toward* b$
OK---\verb!\try{toward}! works: \try{toward}
BAD--\verb!\try{toward*}! gobbles up the arrow: \try{toward*}
\end{document}
如下所示,的结果a \Rightarrow b
从的输出中消失\try{toward*}
该怎么解决呢?
答案1
问题\toward*
是二代币,不是一个,而\csname toward*\endcsname
只是构建一令牌。
你可以\csname...\endcsname
模拟\scantokens
:
\documentclass{article}
\usepackage{suffix}
\newcommand{\toward}{\rightarrow}
\WithSuffix\newcommand\toward*{\Rightarrow}
\makeatletter
\edef\bschar{\expandafter\@gobble\string\\}
\makeatother
\newcommand{\try}[1]{%
$a%
\scantokens\expandafter{\bschar#1}%
b$%
}
\begin{document}
OK: $a \toward b$ and $a \toward* b$
OK---\verb!\try{toward}! works: \try{toward}
OK---\verb!\try{toward*}! gobbles up the arrow: \try{toward*}
\end{document}
一种不同的解决方案,它提供了可扩展性\try
,同时还避免了suffix
。
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\toward}{s}{%
\IfBooleanTF{#1}{\Rightarrow}{\rightarrow}%
}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\try}{m}
{
\str_if_eq:eeTF
{ \str_range:nnn { #1 } { -1 } { -1 } } % get the last character
{ * }
{ $ a \use:c { \str_range:nnn { #1 } { 1 } { -2 } } * b $ }
{ $ a \use:c { #1 } b $ }
}
\ExplSyntaxOff
\begin{document}
OK: $a \toward b$ and $a \toward* b$
OK---\verb!\try{toward}! works: \try{toward}
OK---\verb!\try{toward*}! works: \try{toward*}
\end{document}
对 的参数\try
进行字符串化和检查:如果字符串中的最后一项是*
,那么我们将其分离并放在命令后面。
答案2
\try{toward*}
由于没有命令,所以的结果消失\csname toward*\endcsname
。\csname...\endcsname
对于未定义的命令,结果变为\relax
(不打印任何内容)。
如果你想使用\try{toward*}
,但也有\toward*
工作作为一个常规已加星标宏,那么您只需将其定义\csname toward*\endcsname
为与\toward*
(或\Rightarrow
)相同即可:
\documentclass{article}
\usepackage{suffix}
\newcommand{\toward}{\rightarrow}
\WithSuffix\newcommand\toward*{\Rightarrow}% This defines \toward to have a starred version
\expandafter\newcommand\csname toward*\endcsname{\Rightarrow}% This defines \towards* ...
% ... which is different
% from what is defined above
\newcommand{\try}[1]{$a \csname#1\endcsname b$}
\begin{document}
OK: $a \toward b$ and $a \toward* b$
OK---\verb!\try{toward}! works: \try{toward}
OK--\verb!\try{toward*}! gobbles up the arrow: \try{toward*}
\end{document}
相关内容TeXbook(第 40 页):
... 您可以通过说 ' ' 从字符标记列表转到控制序列。出现在和
\csname<tokens>\endcsname
之间的此构造中的标记可能包含其他控制序列,只要这些控制序列最终扩展为字符而不是 TeX 基元;最终的字符可以是任何类别,不一定是字母。例如,' ' 本质上与 ' ' 相同;但 ' ' 是非法的,因为它扩展为包含基元的标记。此外,' ' 将产生不寻常的控制序列 '\csname
\endcsname
\csname TeX\endcsname
\TeX
\csname\TeX\endcsname
\TeX
\kern
\csname\string\TeX\endcsname
\\TeX
',IE,即标记\TeX
,通常不能写入。
答案3
如果您不想使用 eTeX 并且只使用单个尾随星号作为后缀,我可以提供一个宏\UD@SplitTrailingStar
。
句法:\UD@SplitTrailingStar{⟨sequence⟩}{⟨prepend⟩}{⟨append⟩}
如果⟨sequence⟩
最后一个标记没有星号,则产生:
⟨prepend⟩⟨sequence⟩⟨append⟩
如果⟨sequence⟩
最后一个标记有星号,则会产生以下结果:
⟨prepend⟩⟨sequence without the last star⟩⟨append⟩*
⟨prepend⟩
例如可以是\csname
。
⟨append⟩
例如可以是\endcsname
。
由于\romannumeral
-扩展,结果经过两个扩展步骤后得出。
\documentclass{article}
\usepackage{suffix}
\makeatletter
%%----------------------------------------------------------------------
%% Paraphernalia
%%----------------------------------------------------------------------
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
%%----------------------------------------------------------------------
%% 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]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%----------------------------------------------------------------------
%% \UD@SplitTrailingStarLoop{<sequence>*}{}{}{<prepend>}{<append>}
%% In case <sequence> doesn't have a star as last token yields:
%% <prepend><sequence><append>
%% In case <sequence> does have a star as last token yields:
%% <prepend><sequence without the last star><append>*
%%----------------------------------------------------------------------
\newcommand\UD@GobbleToExclam{}\long\def\UD@GobbleToExclam#1!{}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
\newcommand\UD@gobbletostar{}\long\def\UD@gobbletostar#1*{}%
\newcommand\UD@keeptostar{}\long\def\UD@keeptostar#1*{#1}%
\newcommand\UD@RemoveStarDelimitedTillUD@nil{}%
\long\def\UD@RemoveStarDelimitedTillUD@nil#1*#2\UD@nil{#1*}%
\newcommand\UD@ExtractFirstStarDelimitedLoop[2]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletostar#1}%
{\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo\expandafter{\expandafter}%
\UD@keeptostar#1}{ #2}}%
{%
\expandafter\UD@ExtractFirstStarDelimitedLoop
\expandafter{\UD@RemoveStarDelimitedTillUD@nil#1}{#2}%
}%
}%
\newcommand\UD@starfork{}\long\def\UD@starfork#1!!*!#2#3!!!!{#2}%
\newcommand\UD@SplitTrailingStarLoop[5]{%
\UD@CheckWhetherNoExclam{#1}{%
\UD@starfork!#1!*!{\UD@firstoftwo{#4#2#5}}% #1 empty.
!!#1!{\UD@firstoftwo{#4#2#5*}}% #1 single star.
!!*!{\UD@secondoftwo{}}%
!!!!%
}{\UD@secondoftwo{}}{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter{%
\expandafter\UD@Exchange\expandafter{%
\romannumeral0\UD@ExtractFirstStarDelimitedLoop{.#1\UD@nil}{}}{#2#3}}}%
{\expandafter\UD@SplitTrailingStarLoop
\expandafter{\UD@gobbletostar#1}}%
{*}{#4}{#5}%
}%
}%
%%----------------------------------------------------------------------
%% \UD@SplitTrailingStar{<sequence>}{<prepend>}{<append>}
%% In case <sequence> doesn't have a star as last token yields:
%% <prepend><sequence><append>
%% In case <sequence> does have a star as last token yields:
%% <prepend><sequence without the last star><append>*
%%
%% Due to \romannumeral-expansion, the result is delivered after two
%% expansion-steps.
%% \UD@SplitTrailingStar is a wrapper for \UD@SplitTrailingStarLoop.
%%----------------------------------------------------------------------
\newcommand\UD@SplitTrailingStar[2]{%
\romannumeral0\UD@SplitTrailingStarLoop{#1*}{}{}{ #2}%
}%
\newcommand{\try}[1]{$a\UD@SplitTrailingStar{#1}{\csname}{\endcsname}b$}%
\newcommand{\toward}{\rightarrow}
\WithSuffix\newcommand\toward*{\Rightarrow}
\makeatother
\parskip=\baselineskip
\parindent=0ex
\begin{document}
\verb!$a\csname toward\endcsname b$!: $a\csname toward\endcsname b$
\verb!$a\csname toward\endcsname*b$!: $a\csname toward\endcsname*b$
\verb!\try{toward}!: \try{toward}
\verb!\try{toward*}!: \try{toward*}
\hrulefill
% something more complex:
\makeatletter
\newcommand\name{}\long\def\name#1#{\romannumeral0\innername{#1}}%
\newcommand\innername[2]{%
\expandafter\UD@Exchange\expandafter{\csname#2\endcsname}{ #1}%
}%
\makeatother
\name\newcommand{*{ma}*ny**star}{\rightarrow}
\name\WithSuffix\newcommand{*{ma}*ny**star}*{\Rightarrow}
\verb!$a\csname*{ma}*ny**star\endcsname b$!: $a\csname*{ma}*ny**star\endcsname b$
\verb!$a\csname*{ma}*ny**star\endcsname*b$!: $a\csname*{ma}*ny**star\endcsname*b$
\verb!\try{*{ma}*ny**star}!: \try{*{ma}*ny**star}
\verb!\try{*{ma}*ny**star*}!: \try{*{ma}*ny**star*}
\hrulefill
\name\newcommand{*{mo}*re**stars*}{\rightarrow}
\name\WithSuffix\newcommand{*{mo}*re**stars*}*{\Rightarrow}
\verb!$a\csname*{mo}*re**stars*\endcsname b$!: $a\csname*{mo}*re**stars*\endcsname b$
\verb!$a\csname*{mo}*re**stars*\endcsname*b$!: $a\csname*{mo}*re**stars*\endcsname*b$
\verb!\try{*{mo}*re**stars**}!: \try{*{mo}*re**stars**}
But Problem:
\verb!\try{*{mo}*re**stars*}!: \try{*{mo}*re**stars*}
Reason:
\verb|\UD@SplitTrailingStarLoop| cannot "know" that this time the trailing star should not be treated as
a suffix but as a part of the macro-name.
\verb!\try{*{mo}*re**stars*}!\\
yields:\\
\verb!$a\csname*{mo}*re**stars\endcsname*b$!\\
while the control-word-token \verb|\*{mo}*re**stars| at the time of performing \verb|\csname|-expansion
is undefined and thus within the current scope gets assigned the meaning of the \verb|\relax|-primitive.
\end{document}