我想测试两个自定义控制序列是否具有相同的名称,无论它们扩展为什么或是否实际定义。例如,
\ifsamecsname\Foo\Foo
应该相当于“true”(\iftrue
?),但是
\ifsamecsname\Foo\Bar
应等同于“false”(\iffalse
?)。
一些要求:
e-TeX
仅允许使用TeX(包括纯 TeX 和),- (编辑) 不应使用中间定义。
我最初的实施想法\ifsamecsname
是将其应用于\string
两个控制序列,然后比较生成的标记列表。但是,我想知道是否存在更好、更有效的方法,将两个控制序列中的每一个都保留为一个标记...
答案1
从表面上看,我们不能使用\pdfstrcmp
或等效于 e-TeX 的一部分(它是 XeTeX 中以不同名称提供的 pdfTeX 原语,在 LuaTeX 中使用 Lua 仿真)。我们可以做的是逐个标记比较\string
两个宏名称的版本。只要名称中没有空格,这种方法就会起作用。以前在 中有这种方法的一个巧妙版本expl3
,但它不再存在,我也没有回头去找它。因此,以下内容是相当蛮力的:
\catcode`\@=11 %
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\comparemacros#1#2{%
\expandafter\expandafter\expandafter\comparemacros@auxi
\expandafter\expandafter\expandafter
{\expandafter\string\expandafter#1\expandafter}\expandafter
{\string#2}%
}
\def\comparemacros@auxi#1#2{%
\comparemacros@loop#1\end\relax#2\end\relax
}
\def\comparemacros@loop#1#2\relax#3#4\relax{%
\if#1#3%
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{%
\ifnum0%
\ifx\end#21\fi
\ifx\end#41\fi
>0 %
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{%
\ifnum0%
\ifx\end#21\fi
\ifx\end#41\fi
=11 %
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}%
{\comparemacros@loop#2\relax#4\relax}%
}%
{\@secondoftwo}%
}
\catcode`\@=12 %
\def\foo{a}
\def\bar{a}
{%
\comparemacros\foo\bar{TRUE}{FALSE}}
\comparemacros\foo\fo{TRUE}{FALSE}}
\comparemacros\foo\foo{TRUE}{FALSE}}
\bye
这里的想法很简单。我们将两个宏名称转换为字符串,然后并行迭代两者并比较字符。如果不匹配或长度不匹配(如果我们命中\end
一个而没有命中另一个),则循环停止。另一方面,如果两者在同一点结束,则两者匹配。
答案2
如果\pdfstrcmp
允许(或在其他 TeX 编译器中允许,如\strcmp
XeTeX),则非常简单:
纯 TeX 示例:
\input pdftexcmds.sty\relax
\catcode`\@=11
\long\def\someIf#1#2{%
\ifnum\pdf@strcmp{\noexpand#1}{\noexpand#2}=0 %
}
% Testing
\def\msg#{\immediate\write16}
\someIf\foo\bar
\msg{equal}%
\else
\msg{not equal}%
\fi
\def\msg#{\immediate\write16}
\someIf\foo\foo
\msg{equal}%
\else
\msg{not equal}%
\fi
\csname @@end\endcsname\end
但是,宏可以绝不相当于\iftrue
。因此\someIf
不能嵌套在其他\if
命令中。否则 TeX 只会看到\else
和\fi
,而不会看到内部\ifnum
,并且 TeX 在跳过分支时会被欺骗。
在这方面,不同的语法更安全:
\long\def\@firstoftwo#1#2{#1}% already defined in LaTeX
\long\def\@secondoftwo#1#2{#2}% already defined in LaTeX
\long\def\someIf#1#2{%
\ifnum\pdf@strcmp{\noexpand#1}{\noexpand#2}=0 %
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
% The code for the cases go into arguments 3 and 4:
\someIf\foo\bar{\msg{equal}}{\msg{not equal}}
阻止命令标记参数扩展\noexpand
。替代方法是\string
或将其放入 e-TeX 的\detokenize
。
答案3
不,您无法以可扩展的方式比较两个控制序列名称,除非将它们转换为字符串。可用的测试是\ifx
,它比较意义。
这需要e-TeX
,与 一起运行pdftex
,xetex
并且luatex
:
\input pdftexcmds.sty
\catcode`@=11
\def\STRINGEQ#1#2{TT\fi
\ifnum\pdf@strcmp{\detokenize{#1}}{\detokenize{#2}}=\z@
}
\catcode`@=12
\if\STRINGEQ\Foo\Foo
\message{EQUAL}%
\else
\message{DIFFERENT}%
\fi
\if\STRINGEQ\Foo\Bar
\message{EQUAL}%
\else
\message{DIFFERENT}%
\fi
\bye
输出
<...>
(/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/pdftexcmds.sty
(/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/infwarerr.sty)
(/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/ifluatex.sty)
(/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/ltxcmds.sty)
(/usr/local/texlive/2014/texmf-dist/tex/generic/oberdiek/ifpdf.sty))
EQUAL
DIFFERENT
<...>
答案4
如果可以部分取消在相等性测试中不得有任何赋值(特别是定义)的限制,正如其中一条评论似乎允许的那样,即在“初步”阶段允许这样做,在该阶段,设置一个“常量”名称,随后将仅使用扩展对其进行相等性测试,则以下解决方案(利用 TeX 的机制来确定分隔的宏参数)可能是可以接受的。虽然以下示例使用的是 LaTeX,但它本质上依赖于所有格式和每个 TeX 引擎下可用的方法。
\documentclass[a4paper]{article}
\makeatletter
\@ifdefinable\CSNameIsEqualToSaved{}
\@ifdefinable\@CSNameIsEqualToSaved{}
\newcommand*\DefineComparisonName[1]{%
\def\CSNameIsEqualToSaved##1{%
TT\fi
\@CSNameIsEqualToSaved##1#1@%
}%
\def\@CSNameIsEqualToSaved##1#1##2@{%
\ifx @##1@%
}%
}
% \newcommand*\checkdefinitions{%
% \show\CSNameIsEqualToSaved
% \show\@CSNameIsEqualToSaved
% }
\makeatother
\newcommand*{\domytest}[1]{%
\par\noindent
Testing \texttt{\string #1}: names
% \begingroup
% \tracingmacros = 1
\if\CSNameIsEqualToSaved #1%
ARE%
\else
are NOT%
\fi
% \endgroup
\space equal.
}
\begin{document}
Let us check some obvious facts.
\section{It works with undefined control sequences}
\DefineComparisonName{\foo}
% \checkdefinitions
\domytest{\foo}
\domytest{\bar}
\section{It does not consider the meaning}
\def\foo{x}
\let\bar=\foo
\domytest{\foo}
\domytest{\bar}
\section{It works with control symbols}
\DefineComparisonName{\!}
% \checkdefinitions
\domytest{\!}
\domytest{\@}
\end{document}
上面的列表在注释中包含了一些诊断代码,可以帮助理解发生了什么。TeX 使用控制序列作为参数分隔符的方式正是我们想要的:“控制序列名称必须相同”(TeXbook,第 203 页,倒数第二行)。这就是该方法有效的原因。
当然,改进和修正总是可能的。