11

I'd like to test whether two custom control sequences have the same name, regardless of what they expand to or whether they're actually defined. For instance,

\ifsamecsname\Foo\Foo

should be equivalent to "true" (\iftrue?), but

\ifsamecsname\Foo\Bar

should be equivalent to "false" (\iffalse?).

A few requirements:

  • Only TeX (incl. plain TeX and e-TeX) is allowed,
  • (edit) no intermediate definitions should be used.

My initial thought for implementing \ifsamecsname was to apply \string to both control sequences and then compare the resulting lists of tokens. However, I'm wondering whether a better, more efficient approach exist, one that would preserve each of the two control sequences as one token...

  • I'd have done something with \string to solve this. Is there a particular reason not to use \string? – A.Ellett May 19 '15 at 23:18
  • After \def\a{\foo}\def\b{\foo}, an\ifx\a\b will evaluate to true, but after \def\a{\foo}\def\b{\bar} it will evaluate to false. Couldn't you simply exploit this? – GuM May 19 '15 at 23:25
  • @GustavoMezzetti you should make that an answer:-) – David Carlisle May 20 '15 at 0:00
  • 1
    Thanks, but your suggestion made me realise that I left out an important requirement: no intermediate definitions. – jub0bs May 20 '15 at 6:55
  • 1
    @GustavoMezzetti In all cases I can think of, I'll be passing a control sequence as a parameter to another one and compare it to a "constant" one. However, I'd rather avoid assignments, because I don't want to run the risk of shadowing an existing control sequence defined at a wider scope. – jub0bs May 20 '15 at 9:01
11

Taking your requirements at face value, we can't use \pdfstrcmp or equivalent as it's not part of e-TeX (it's a pdfTeX primitive available in XeTeX under a different name and in LuaTeX using Lua emulation). What we can do is a token-by-token comparison of the \string versions of the two macro names. This will work provided there are no spaces in the names. There used to be a clever version of this approach in expl3, but it's no longer there and I haven't looked back to find it. As such, the following is rather brute-force:

\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

The idea here is simple enough. We turn the two macro names into strings, then iterate over both in parallel and compare the characters. The loop stops if there is a mismatch or if the lengths don't match (if we hit \end for one but not the other). If on the other hand both end at the same point the two match.

  • Because I'm at the prototype phase, I'm not too bothered about the pdfTeX dependency, right now. egreg's approach seems to do the trick for me, at least for now. If my experiment proves successful, and when the time comes to make my implementation more portable, I will revisit your post. Thanks. – jub0bs May 20 '15 at 9:17
  • Probably it would be possible to tackle spaces here (as we are dealing with strings), though it would be tedious! – Joseph Wright May 20 '15 at 9:28
  • Spaces are not a problem, in my case. The names of the control sequences involved do not contain spaces. – jub0bs May 20 '15 at 9:29
8

If \pdfstrcmp is allowed (or its friends in other TeX compilers like \strcmp in XeTeX), it's quite simple:

Example for plain-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

However, a macro can never be equivalent to \iftrue. Therefore \someIf cannot be nested inside other \if commands. Otherwise TeX will only see \else and \fi, but not the inner \ifnum and TeX is fooled when skipping branches.

A different syntax is more safe in this regard:

\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}}

The command token arguments are prevented from expansion by \noexpand. Alternatives are \string or putting it into e-TeX's \detokenize.

  • Thanks for this. I do need nested if tests, so I think the second approach is probably what I need. – jub0bs May 20 '15 at 7:35
7

No, you cannot compare two control sequence names in expandable way unless you turn them into strings. The available test is \ifx, which compares the meaning.

This requires e-TeX, runs with pdftex, xetex and 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

Output

<...>
(/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

If the restriction that no assignments (in particular, no definitions) must intervene in the test for equality can be partially lifted, as one of the comments seems to permit, in the sense that they may be allowed in a “preliminary” phase in which one sets a “constant” name against which subsequent tests for equality will be made using expansion alone, the following solution, that exploit TeX’s machinery for determining delimited macro arguments, could be acceptable. Although the following example uses LaTeX, it essentially depends on means available in all formats and under every TeX engine.

\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}

The above listing includes, in the comments, some diagnostic code that can help to understand what’s going on. The way in which TeX uses control sequences as argument delimiters is exactly what is wanted: “control sequence names must be the same” (The TeXbook, page 203, second-last line). That’s why the method works.

Of course, refinements and amendments are always possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.