有时,更复杂的 (La)TeX 宏会测试下一个输入标记并根据其类型进行分支。我知道如何测试 catcode 和字符代码,但有时我喜欢以特殊方式处理控制序列(宏、基元,即\
后跟字母(catcode 11)以及的乘积\csname ...\endcsname
)。
是否有合适的测试来判断作为宏参数读取的标记是否是控制序列?标记不能事先扩展。我喜欢避免使用\string
和寻找前导\
,因为这取决于 的值\escapechar
可能在本地更改。使用\meaning
和测试前导macro
不适用于 这样的原语\relax
。
所以基本上我正在寻找一个\@ifcontrolsequence{<token>}{<true>}{<false>}
宏,它可以提供真的例如\relax
,对于\empty
,\custommacro
对于任何其他标记,则为 false。活动字符可以被视为例外,并以任何方式处理。宏当然不应该在任何特殊标记上中断。
答案1
\makeatletter
\def\@ifismacro#1{%
\begingroup\escapechar=-1
\edef\x{\endgroup\def\noexpand\first{\string#1}}\x
\begingroup\escapechar=`\\
\edef\x{\endgroup\def\noexpand\second{\string#1}}\x
\ifnum\pdfstrcmp{\first}{\second}=\z@
\expandafter\@secondoftwo % no backslash in front
\else
\expandafter\@firstoftwo % backslash in front
\fi}
\def\report#1{\@ifismacro{#1}{\message{CS}}{\message{NON CS}}}
\makeatother
\report{A}
\report{\"}
\let\pippo=a
\report{\pippo}
这种方法的问题在于不是完全可扩展的,因为它依赖于对的分配\escapechar
,同时与执行测试时的值无关。
此测试区分了最后一种情况,对于 来说这是不可能的\ifcat
。对于\ifcsmacro
来说也不可能电子工具箱, 它似乎。
答案2
无法通过扩展获得完整的解决方案。只要转义字符可打印(\escapechar
介于 0 和 255 之间,含 0 和 255),下面的代码就会给出正确的结果。它依赖于这样一个事实:在这种情况下,应用于\string
我们的参数将给出多个字符。事实上,我们\escapechar=32
单独处理大小写(空格),因为 TeX 在抓取未分隔的参数时会忽略空格。
各个地方看起来怪异\noexpand
的地方需要满足\outer
宏的要求:它允许它们停留
\relax
足够长的时间以在参数中抓取它们。请注意,#1
永远不会出现在条件中可能被跳过的文本中(因为它可能是\outer
)。
如果转义字符不可打印,并且\string#1
只给出一个字符,那么我们需要进行更多的调查来区分通常的情况(但解决方案并不完整)。
\makeatletter
\newcommand{\ifcs}{\expandafter\ifcs@i\noexpand}
\newcommand{\ifcs@T}[3]{#2}
\newcommand{\ifcs@F}[3]{#3}
% "normal" escapechar
\newcommand{\ifcs@i}[1]
{%
\ifcat$\ifcat*\string#1\fi$%
\expandafter \expandafter
\expandafter \ifcs@test
\else
\expandafter \expandafter
\expandafter \ifcs@T
\fi
\noexpand #1%
}
如果我们不关心 的特殊情况,我们可以在这里完成\escapechar
:只需将条件的结尾替换为
\expandafter \@secondoftwo \else \expandafter \@firstoftwo \fi
(也删除\noexpand #1
)。但区分各种转义字符并不太昂贵(我怀疑我在这里给出的测试是否接近最佳)。
\newcommand{\ifcs@test}
{%
\ifcase \expandafter\@gobble\string\2 % (space)
\ifcat\@sptoken\string\1 \else 0 \fi
\expandafter \ifcs@unprintable
\or
\expandafter \ifcs@space
\else
\expandafter \ifcs@F
\fi
}
% \escapechar=32
\newcommand{\ifcs@space}[1]
{%
\unless\ifcat\@sptoken\string#1%
\expandafter \expandafter
\expandafter \ifcs@F
\else
\expandafter \expandafter
\expandafter \ifcs@space@i
\fi
\noexpand #1%
}
\newcommand{\ifcs@space@i}[1]% \string#1 starts with space
{%
\ifcat$\romannumeral-`0\string#1$%
\expandafter \@secondoftwo
\else
\expandafter \@firstoftwo
\fi
}
案例\escapechar < 0
或> 255
。我对此没有进行太多测试,但无法提供完整的解决方案,因为例如控制序列\a
和活动a
let 彼此以及 let 到字符在扩展上无法区分。(
\ifcat
、\if
、\ifx
、\meaning
和相同\string
)
\newcommand{\ifcs@unprintable}[1]
{%
\ifcat\relax\noexpand#1%
\expandafter \expandafter
\expandafter \ifcs@T
\else
\expandafter \expandafter
\expandafter \ifcs@unprintable@i
\fi
\noexpand #1%
}
\newcommand{\ifcs@unprintable@i}[1]
{%
\expandafter\ifx\csname \string#1\endcsname#1%
\expandafter \@firstoftwo % can be wrong
\else
\expandafter \@secondoftwo
\fi
}
\def\test{\expandafter\test@\noexpand}
\def\test@#1{\ifcs#1{\message{T}}{\message{F}}
\outer\def\foo{}
\escapechar=-1\relax
\test\foo
\test\a
\test\ %
\expandafter\test\csname \space a\endcsname
\test a
\test ^
\test $
答案3
看看电子工具箱包装及其\ifcsmacro
测试。
\documentclass{article}
\usepackage{etoolbox}
\begin{document}
% \def\custommacro{foo}
% \def\custommacro{\empty}
% \def\custommacro{\relax}
% \let\custommacro\box
\ifcsmacro{box}{True}{False}
\ifcsmacro{custommacro}{True}{False}
\end{document}
答案4
你想要的\ifiscs
是etextools
包。我发现有一个 bug 需要测试#
。
例子:
\documentclass{article}
\usepackage{etextools}
\begin{document}
% yes
\ifiscs{\fi}{yes}{no}\par
\ifiscs{\undefined}{yes}{no}\par
\ifiscs{\,}{yes}{no}\par
% no
\ifiscs{}{yes}{no}\par
\ifiscs{ }{yes}{no}\par
\ifiscs{
}{yes}{no}\par
\ifiscs{~}{yes}{no}\par
\ifiscs{_}{yes}{no}\par
\ifiscs{^}{yes}{no}\par
\ifiscs{&}{yes}{no}\par
\ifiscs{$}{yes}{no}\par
\ifiscs{汉}{yes}{no}\par
\ifiscs{foo}{yes}{no}
% wrong: should be no, but get yes
\ifiscs{#}{yes}{no} is wrong
% I cannot test braces { }, comment char % and escape char \
\makeatletter
% yes
\ifiscs{\@sptoken}{yes}{no}
\catcode`\@=0
% yes
\ifiscs{@asdf}{yes}{no}
\end{document}