如果参数不是单个标记,则命令会采取不同的行为

如果参数不是单个标记,则命令会采取不同的行为

在定义新的 LaTeX 命令时,我正在寻找一种方法来区分其参数是否为单个标记。换句话说,我希望\foo x等同于和\something{x}(而不是)。这是否可以通过某种方式实现,可能不使用恶意黑客?\foo{xy}\somethingelse{xy}\something{xy}

xparse我尝试查看和的文档etoolbox,但找不到任何有用的东西。有什么提示吗?

答案1

expl3有一个条件:\tl_if_single_token:nTF。只有当参数恰好是一个标记时,它才会返回 true。如果参数为空,或者参数是包含任意数量标记的括号组,则返回 false。 expl3还有 ,它在和 上都\tl_if_single:nTF返回 true ,而在 上返回 false。x{x}\tl_if_single_token:nTF

在此处输入图片描述

\documentclass{article}

\ExplSyntaxOn
\cs_new_eq:NN \IfSingleTokenTF \tl_if_single_token:nTF
\cs_new_eq:NN \IfSingleItemTF \tl_if_single:nTF
\ExplSyntaxOff

\newcommand\foo[1]{%
  \texttt{>#1: }%
  \IfSingleTokenTF{#1}%
    {\something}%
    {\somethingelse}{#1}}

\newcommand\something[1]{(single:#1)}
\newcommand\somethingelse[1]{(multiple:#1)}

\begin{document}
\foo{}

\foo{ }

\foo{x}

\foo{ x}

\foo{xy}

\foo{{xy}}
\end{document}

正如 Frank 在评论中指出的那样,上述代码对于 XeTeX 和 LuaTeX 来说运行良好,因为这些引擎处理 UTF-8 字符,如ä

答案2

已编辑,使其可扩展!(见补充

\documentclass{article}
\newcommand\foo[1]{\fooaux#1\endfoo}
\def\fooaux#1#2\endfoo{\if\relax\detokenize{#2}\relax
  \expandafter\fooproper\else\expandafter\fooalt\fi{#1#2}}
\newcommand\fooproper[1]{FOO! #1}
\newcommand\fooalt[1]{bar #1}
\begin{document}
\foo a

\foo{A}

\edef\z{\foo{ab}}% EXPANDABLE

\z
\end{document}

在此处输入图片描述


补充

Ulrich 正确地指出,上述简单的解决方案有几个潜在的漏洞,具体取决于 OP 期望向\foo宏抛出什么。特别是,如果参数是分隔空格,或者包含前导空格作为分隔参数的一部分,则不会检测到该空格。此外,如果参数包含宏\endfoo,同样会欺骗结果。

以牺牲可扩展性为代价,我通过调用 的功能来解决 Ulrich 对空格的担忧tokcycle,该功能可以毫无问题地识别其输入流中的空格。因此,我展示了各种示例以及可以动态识别多少内容。

唯一的限制是参数流不包含endtokcycraw由包保留的标记tokcycle

\documentclass{article}
\usepackage{tokcycle}
\newcommand\foo{\def\leadingspace{F}\futurelet\foofirst\fooA}
\newcommand\fooA{\ifx\foofirst\bgroup\expandafter\foodelim\else
  \expandafter\fooundelim\fi}
\newcounter{tokcnt}
\newcommand\foodelim[1]{%
  \setcounter{tokcnt}{0}%
  \tokcycle
  {\stepcounter{tokcnt}}%
  {\addtocounter{tokcnt}{2}\processtoks{##1}}%
  {\stepcounter{tokcnt}}%
  {\ifnum\value{tokcnt}=0\relax\def\leadingspace{T}\fi\stepcounter{tokcnt}}%
  {#1}%
  \tctestifnum{\value{tokcnt}=0}{\foonil}{%
  \tctestifnum{\value{tokcnt}=1}{\fooone}{\foomulti}}{#1}}
\newcommand\fooundelim[1]{Undelimited argument to foo: #1}
\newcommand\foonil[1]{Delimited null argument to foo}
\newcommand\fooone[1]{Delimited single token to foo: #1%
  \if T\leadingspace (Space)\fi}
\newcommand\foomulti[1]{Delimited multi-token to foo: #1%
 \if T\leadingspace\ (Leading Space)\fi}
\begin{document}
\foo a \par
\foo{} \par
\foo{ } \par
\foo{A} \par
\foo{ B} \par
\foo{C D} \par
\foo{ab}
\end{document}

在此处输入图片描述

答案3

初步说明:

  1. 参数为空/由零个标记组成的特殊情况也是参数不是单个标记的情况之一。(在下面的示例中,相应的可扩展测试是\UD@CheckWhetherNull。)

  2. 如果存在,TeX 会从宏参数中剥离最外层匹配的花括号:如果\foo是处理单个未分隔参数的宏,则\foo x\foo{x}产生相同的结果。

  3. 显式空格标记(catcode 10 和 charcode 32 的显式字符标记)在通过宏处理时需要特殊处理:当收集形成无界参数的标记时,TeX 会丢弃前面的显式空格标记。因此,显式空格标记只有在嵌套在括号中时才能作为无界宏参数处理。(在下面的示例中,相应的可扩展测试是\UD@CheckWhetherLeadingExplicitSpace.)

  4. 花括号 ({}) 也被标记为标记,作为 catcode 1(开始组)或 2(结束组)的显式字符标记:\foo{{x}}参数\foo由三个标记组成 ,和。因此,这是参数不是单个标记的情况。(在下面的示例中,相应的可扩展测试是。){1x11}2\UD@CheckWhetherBrace

在以下带有可扩展宏的示例中,\CheckWhetherSingleToken上述测试的执行是通过\romannumeral-expansion 触发的。因此,在扩展上下文中,结果是在两个扩展步骤之后获得的,例如,在\CheckWhetherSingleToken通过两次“命中”之后/在通过 “命中” 的顶层扩展\expandafter的第一个标记之后。\CheckWhetherSingleToken\expandafter

(-expansion 的要点\romannumeral是:

  1. TeX 在收集 TeX 的第一个标记时会扩展可扩展标记 -⟨数字⟩-要转换的数量\romannumeral
  2. 如果 TeX 的值⟨数字⟩- 收集到的数量不为正 TeX 将会默默地“吃掉”/丢弃形成 TeX 的 token-⟨数字⟩-数量,但不提供任何回报。

因此你可以(滥用)使用\romannumeral来欺骗 TeX 做大量的扩展和改组标记/宏参数工作,只要确保最终生成的标记序列以 TeX 开头即可。⟨数字⟩- 其值不是正值的数量。)

\errorcontextlines=10000
\documentclass[a4paper]{article}


\makeatletter    
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@PassFirstToSecond, 
%%    \UD@removespace, \UD@stopromannumeral,  \UD@CheckWhetherNull, 
%%    \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingSpace, 
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[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'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}%
  }%
}%
%%=============================================================================
\newcommand\CheckWhetherSingleToken[1]{%
  \romannumeral\UD@CheckWhetherNull{#1}{\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingExplicitSpace{#1}{%
         \expandafter\UD@CheckWhetherNull\expandafter{\UD@removespace#1}%
      }{%
         \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
      }%
    }%
  }%
  {\expandafter\UD@stopromannumeral\UD@firstoftwo}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%=============================================================================
\newcommand\something[1]{This is something with single-token-argument: (#1)}
\newcommand\somethingelse[1]{This is something else with multi-token-argument or zero-token-argument=empty argument: (#1)}
\newcommand\foo[1]{%
  \CheckWhetherSingleToken{#1}{\something}{\somethingelse}{#1}%
}%
\makeatother

\begin{document}

\noindent
\verb|\foo x| yields: \foo x\\
\verb|\foo{x}| yields: \foo{x}\\
\verb|\foo{{x}}| yields: \foo{{x}}\\
\verb|\foo{xy}| yields: \foo{xy}\\
\verb|\foo{{xy}}| yields: \foo{{xy}}\\
\verb|\foo{ }| yields: \foo{ }\\
\verb|\foo{ a}| yields: \foo{ a}\\
\verb|\foo{}| yields: \foo{}

\end{document}

在此处输入图片描述

相关内容