如何防止带有 \csname 的宏吞噬带星号的参数?

如何防止带有 \csname 的宏吞噬带星号的参数?

为了实现比此处所示更复杂的目的,我想测试某些命令,这些命令既有普通形式,也有星号形式,后者使用包构建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*}

该怎么解决呢?

Unwanted gobbling up of starred form of argument

答案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进行字符串化和检查:如果字符串中的最后一项是*,那么我们将其分离并放在命令后面。

enter image description here

答案2

\try{toward*}由于没有命令,所以的结果消失\csname toward*\endcsname\csname...\endcsname对于未定义的命令,结果变为\relax(不打印任何内容)。

如果你想使用\try{toward*},但也有\toward*工作作为一个常规已加星标宏,那么您只需将其定义\csname toward*\endcsname为与\toward*(或\Rightarrow)相同即可:

enter image description here

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

enter image description here

相关内容