在定义新的 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
初步说明:
参数为空/由零个标记组成的特殊情况也是参数不是单个标记的情况之一。(在下面的示例中,相应的可扩展测试是
\UD@CheckWhetherNull
。)如果存在,TeX 会从宏参数中剥离最外层匹配的花括号:如果
\foo
是处理单个未分隔参数的宏,则\foo x
和\foo{x}
产生相同的结果。显式空格标记(catcode 10 和 charcode 32 的显式字符标记)在通过宏处理时需要特殊处理:当收集形成无界参数的标记时,TeX 会丢弃前面的显式空格标记。因此,显式空格标记只有在嵌套在括号中时才能作为无界宏参数处理。(在下面的示例中,相应的可扩展测试是
\UD@CheckWhetherLeadingExplicitSpace
.)花括号 (
{
或}
) 也被标记为标记,作为 catcode 1(开始组)或 2(结束组)的显式字符标记:\foo{{x}}
参数\foo
由三个标记组成 ,和。因此,这是参数不是单个标记的情况。(在下面的示例中,相应的可扩展测试是。){1
x11
}2
\UD@CheckWhetherBrace
在以下带有可扩展宏的示例中,\CheckWhetherSingleToken
上述测试的执行是通过\romannumeral
-expansion 触发的。因此,在扩展上下文中,结果是在两个扩展步骤之后获得的,例如,在\CheckWhetherSingleToken
通过两次“命中”之后/在通过 “命中” 的顶层扩展\expandafter
的第一个标记之后。\CheckWhetherSingleToken
\expandafter
(-expansion 的要点\romannumeral
是:
- TeX 在收集 TeX 的第一个标记时会扩展可扩展标记 -⟨数字⟩-要转换的数量
\romannumeral
。 - 如果 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}