如何检查两个控制序列是否具有相同的名称?

如何检查两个控制序列是否具有相同的名称?

我想测试两个自定义控制序列是否具有相同的名称,无论它们扩展为什么或是否实际定义。例如,

\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 编译器中允许,如\strcmpXeTeX),则非常简单:

纯 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,与 一起运行pdftexxetex并且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 页,倒数第二行)。这就是该方法有效的原因。

当然,改进和修正总是可能的。

相关内容