5

I have this code with nested ifx (plain TeX):

\def\test#1#2%
{
  \edef\cmpa{#1}
  \edef\cmpb{x}
  \ifx\cmpa\cmpb
    it is x
    \edef\cmpa{#2}
    \edef\cmpb{1}
    \ifx\cmpa\cmpb
      and 1
    \else
      and something else
    \fi
  \else
    it is something else
  \fi
}

\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % prints: it is something else

It works fine but is much code in \test for what it is doing.

I tried to tidy this like:

\def\ifEq#1#2%
{
  \edef\cmpa{#1}
  \edef\cmpb{#2}
  \ifx\cmpa\cmpb%
}

\def\test#1#2%
{
  \ifEq{#1}{x}%
    it is x
    \ifEq{#2}{1}%
      and 1
    \else
      and something else
    \fi
  \else
    it is something else
  \fi
}

\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error

The code of \test is much easier, but it produces an error for y:

! Extra \else.
\test ...and 1 \else and something else \fi \else 
                                                  it is something else \fi 
l.54 \test{y}{1}

Why does the first work but not the second one?

(I have to notice, that the parameters for \test and the tested texts can be longer texts, not only one letter; parameters for \test can be macros)

Thanks, Peter

Edit:

Thank you all for your answers! The most important hint is this: „TeX does not expand macros when searching for \else or \fi in false case.“ The solution in the accepted answer seems to be the easiest and smartest :-)

Edit 2: (please ignore, was nonsense)

I just see, i cannot do an if .. else .. if like this:

\def\Eq#1#2%
{%
  TT\fi%
  \edef\cmpa{#1}%
  \edef\cmpb{#2}%
  \ifx\cmpa\cmpb%
}

\def\test#1#2%
{
  \if\Eq{#1}{x}%
    it is x
    \if\Eq{#2}{1}%
      and 1
    \else
      and something else
    \fi
  \else
  \if\Eq{#1}{y}%
    it is y
    \if\Eq{#2}{1}%
      and 1
    \else
      and something else
    \fi
  \fi
}

An error is thrown by \test{x}{1} deep inside the following code of my project. Seems there is an if unclosed now.

3
  • 2
    TeX does not expand macros when searching for \else or \fi. Therefore the \ifx in the second \ifEq is not found and the first \else is already interpreted as corresponding to the \ifx of the first \ifEq. So you should not expand a macro to a TeX conditional like \iftrue, \iffalse, \if, \ifx, \ifcase. I'm almost sure, there are already questions about this.
    – cabohah
    Commented Mar 7 at 10:11
  • your edit 2 final code block has a missing \fi at the end (4 \if, 3 \fi) Commented Mar 7 at 12:12
  • yes, you are right. I wanted to create a if-elseif-elself-fi-construct. But there is no \elseif in TeX. So Edit 2 was nonsense, sorry
    – coonlight
    Commented Mar 7 at 12:25

6 Answers 6

6

With \test{y}{1} you get

  \ifEq{y}{x}%
    it is x
    \ifEq{1}{1}%
      and 1
    \else
      and something else
    \fi
  \else
    it is something else
  \fi

which then becomes

  \edef\cmpa{y}
  \edef\cmpb{x}
  \ifx\cmpa\cmpb%
    it is x
    \ifEq{1}{1}%
      and 1
    \else
      and something else
    \fi
  \else
    it is something else
  \fi

The assignments are performed and the \ifx is evaluated to false, so everything up the matching \else is discarded without any macro expansion; only conditionals are taken into account, but there isn't any up to the first \else. So you remain with

      and something else
    \fi
  \else
    it is something else
  \fi

which shows the “Extra \else”.

Solution: define \Eq instead.

\def\Eq#1#2{%
  TT\fi
  \edef\cmpa{#1}%
  \edef\cmpb{#2}%
  \ifx\cmpa\cmpb
}

\def\test#1#2{%
  \if\Eq{#1}{x}%
    it is x
    \if\Eq{#2}{1}%
      and 1
    \else
      and something else
    \fi
  \else
    it is something else
  \fi
}

\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % prints: it is something else

\bye

Comment

Why TT\fi and not just \fi?

When the \if preceding \Eq is skipped, the expansion of \Eq is not looked at. If it's not skipped, then TeX expands \Eq and finds TT\fi. Any two unexpandable tokens can be used here, actually. What if we omit them? We're still evaluating \if and

\if\fi

is an unfinished conditional, so TeX inserts a “frozen \relax” token. Now it looks at

\if\relax\fi

which is still unfinished, so another “frozen \relax” is added. Now

\if\relax\relax\fi

is complete and disappears. With TT\fi the conditional is complete to begin with.

See https://tex.stackexchange.com/a/57417/4427 for information about the frozen \relax.

Alternative

If you use pdftex, you can also make this equality text fully expandable:

\def\Eq#1#2{%
  TT\fi
  \ifnum\pdfstrcmp{#1}{#2}=0
}

\def\test#1#2{%
  \if\Eq{#1}{x}%
    it is x
    \if\Eq{#2}{1}%
      and 1
    \else
      and something else
    \fi
  \else
    it is something else
  \fi
}

\test{x}{1} % prints: it is x and 1

\test{x}{2} % prints: it is x and something else

\test{y}{1} % prints:

\bye
5
  • The TT\fi bit could be just \fi. The point of it is to give a matching \fi for the preceding visible \if, so that the \if does nothing beyond balancing the books. Instead the internal \ifx does the comparison. When being skippied, one \if(x) and one \fi are hidden within the macro, so the balance is preserved. Commented Mar 7 at 10:52
  • Yes, your solution it works fine. With and without the TT. Thank you very much for your explanation and the new code! :-)
    – coonlight
    Commented Mar 7 at 10:57
  • @DonaldArseneau I've tidied up your comment :) Backticks always make code markup in Markdown
    – Joseph Wright
    Commented Mar 7 at 11:29
  • Oh, i see, an if-else-if-construct is not possible with your solution. See my Edit 2.
    – coonlight
    Commented Mar 7 at 11:42
  • @DonaldArseneau I prefer not asking TeX to supply two “frozen \relax” tokens.
    – egreg
    Commented Mar 7 at 11:46
4

TeX does not expand macros when searching for \else or \fi in false case. In the example with y, \ifx xy is false. So TeX searchs for the next \else or \fi at the same level without expansion. So the second \ifEq is not expanded and TeX does not know, that there would be a \ifx inside. Because of this, the next \else, that should be related to the inner \ifEq is still the one TeX finds as the \else of the first \ifEq. Therefore the second \else, which would be the one, you expect to be related to the first \ifEq is extra.

To avoid this, you should never start a low level TeX condition like \if, \ifx, \ifcase, \ifnum, \iftrue or \iffalse inside a macro, but finish it outside the macro. Instead either define a test macro that lets a (predefined) conditional be \iffalse or \iftrue:

\let\iftestresult\iffalse
\def\testresulttrue{\let\iftestresult\iftrue}
\def\testresultfalse{\let\iftestresult\iffalse}

\def\isEq#1#2%
{
  \edef\cmpa{#1}%
  \edef\cmpb{#2}%
  \ifx\cmpa\cmpb
    \testresulttrue
  \else
    \testresultfalse
  \fi
}

\def\test#1#2%
{
  \isEq{#1}{x}%
  \iftestresult
    it is x
    \isEq{#2}{1}%
    \iftestresult
      and 1
    \else
      and something else
    \fi
  \else
    it is something else
  \fi
}

\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error
\bye

or define the test with arguments:

\def\ifEq#1#2%
{%
  \edef\cmpa{#1}%
  \edef\cmpb{#2}%
  \ifx\cmpa\cmpb
    \expandafter\firstoftwo
  \else
    \expandafter\secondoftwo
  \fi
}

\long\def\firstoftwo#1#2{#1}
\long\def\secondoftwo#1#2{#2}

\def\test#1#2%
{
  \ifEq{#1}{x}{it is x \ifEq{#2}{1}{and 1}{and something else}}{is is
  something else}
}

\test{x}{1} % prints: it is x and 1
\test{x}{2} % prints: it is x and something else
\test{y}{1} % -> error
\bye
1
  • Thank you very much! The most important words were: „TeX does not expand macros when searching for \else or \fi in false case.“ This opened my eyes :-)
    – coonlight
    Commented Mar 7 at 11:03
4

The reason is that TeX doesn't see the \if hidden inside a macro when it is skipping text, so it loses track of the matching \if ... \fi. You need to define your test macro so it exposes a primitive conditional when used.

I was making a two-option answer when egreg posted the first and more normal method, so I'll just do the second method. (And comment on his.)

One way is to include an \if-thing as macro parameter syntax in your definition

\let\then=\iftrue
\def\ifEq#1#2\then
{% no spaces
  \edef\cmpa{#1}%
  \edef\cmpb{#2}%
  \ifx\cmpa\cmpb
}

Use like

\ifEq{#1}{yes}\then Great!\else Sorry\fi

When being skipped, the \then is seen as an \if and matches a following \fi. When executing, the \then is absorbed and discarded, but the internal \ifx is expanded and matches some later \fi.

1
  • Another cool and smart way :-) Thank you very much!
    – coonlight
    Commented Mar 7 at 11:05
2

I show you the concept used in OpTeX:

\def\isEq#1#2#3{%
   \def\tmpa{#1}\def\tmpb{#2}%
   \ifx\tmpa\tmpb \else \expandafter \unless \fi
   #3%
}

\isEq xx \iftrue Yes \else No \fi   % prints Yes
\isEq xx \iffalse No \else Yes \fi  % prints Yes
\isEq xy \iftrue Yes \else No \fi   % prints No
\isEq xy \iffalse No \else Yes \fi  % prints No

\bye
0
0

As long as you are comparing only single tokens, you can avoid the pitfalls of the \ifx...\else...\fi syntax by creating your test with the alternate syntax \tctestifx{tokens to compare}{true-condition}{false-condition}. Such syntax is already present in the tokcycle package, or else, as shown in the MWE, it can be explicitly recreated.

With this approach, nesting is not an issue.

\documentclass[]{article}
%\usepackage{tokcycle}% OR ELSE
\makeatletter
\long\def\tc@exfirst#1#2{#1}
\long\def\tc@exsecond#1#2{#2}
\long\def\tctestifcon#1{#1\expandafter\tc@exfirst\else\expandafter\tc@exsecond\fi}
\long\def\tctestifx#1{\tctestifcon{\ifx#1}}
\makeatother

\begin{document}

\def\test#1#2%
{
  \tctestifx{#1x}%
    {it is x%
    \tctestifx{#21}%
      { and 1}%
      { and something else}%
    }%
    {it is something else}%
}

\test{x}{1} % prints: it is x and 1

\test{x}{2} % prints: it is x and something else

\test{y}{1} % -> error
\end{document}

enter image description here

The tokcycle package works in Plain Tex, as well:

\input tokcycle.tex% OR ELSE
%\catcode`\@=11
%\long\def\tc@exfirst#1#2{#1}
%\long\def\tc@exsecond#1#2{#2}
%\long\def\tctestifcon#1{#1\expandafter\tc@exfirst\else\expandafter\tc@exsecond\fi}
%\long\def\tctestifx#1{\tctestifcon{\ifx#1}}
%\catcode`\@=12

\def\test#1#2%
{
  \tctestifx{#1x}%
    {it is x%
    \tctestifx{#21}%
      { and 1}%
      { and something else}%
    }%
    {it is something else}%
}

\test{x}{1} % prints: it is x and 1

\test{x}{2} % prints: it is x and something else

\test{y}{1} % -> error

\bye
0

For the sake of having fun you can do the test by means of delimited macro arguments instead of whatsoever \if..\else..\fi-nesting.

%%=============================================================================
%% PARAPHERNALIA:
%% \stopromannumeral, \firstoftwo, \secondoftwo, \CheckWhetherNull,
%%=============================================================================
\chardef\stopromannumeral=`\^^00 %
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \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>
\long\def\CheckWhetherNull#1{%
  \romannumeral\expandafter\secondoftwo\string{\expandafter
  \secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}\expandafter\stopromannumeral\secondoftwo}{%
  \expandafter\stopromannumeral\firstoftwo}%
}%
%%=============================================================================
%%
%% The gist of checking via a delimited macro argument is:
%% 
%% Check whether ! is a component of the user-provided argument. 
%% If so, the user-provided argument is not the token x. 
%% If not so, the user-provided argument can safely be inserted into a
%% token pattern to which in turn a macro is applied which beneath
%% other things processes a delimited argument where !x! is a component
%% of the delimiter so that forking is implemented by having two
%% possibilities where the delimiter might be found.
%% 
%% If you do not crank out the case of ! being a component of the
%% user-provided argument, the case of the user-provided argument
%% containing the tokens !x!, i.e., the entire delimiter, would fool
%% the test.
%% 
%% Checking whether ! is a component of the user-provided argument is
%% done by appending ! and then removing everything till the 1st !
%% and checking whether the result thereof is emptiness.
%%
%%-----------------------------------------------------------------------------
\long\def\RemoveTillFirstExclam#1!{}%
%%-----------------------------------------------------------------------------
\long\def\xfork#1!x!#2#3!!!{#2}%
\long\def\Onefork#1!1!#2#3!!!{#2}%
\long\def\test#1#2{%
  \begingroup
  \edef\test{\endgroup\noexpand\testexpanded{#1}{#2}}%
  \test
}%
\long\def\testexpanded#1#2{%
  \expandafter\CheckWhetherNull\expandafter{\RemoveTillFirstExclam#1!}{%
    \xfork!#1!{\firstoftwo}!x!{\secondoftwo}!!!%
  }{\secondoftwo}%
  {%
    \expandafter\CheckWhetherNull\expandafter{\RemoveTillFirstExclam#2!}{%
      \Onefork!#2!{\firstoftwo}!1!{\secondoftwo}!!!%
    }{\secondoftwo}%
    {it is x and 1}%
    {it is x and something else}%
  }{%
    it is something else%
  }%
}%

%%-----------------------------------------------------------------------------

\def\Nothing{}%
\def\Foo{\Nothing\Bar\Nothing}%
\def\Bar{x}%
\def\Baz{\Nothing\Bat\Nothing}%
\def\Bat{1}%

\test{x}{1} % prints: it is x and 1

\test{x}{2} % prints: it is x and something else

\test{\Foo}{1} % prints: it is x and 1

\test{\Foo}{\Baz} % prints: it is x and 1

\test{\Foo}{2} % prints: it is x and something else

\test{x}{!} % prints: it is x and something else

\test{y}{1} % prints: it is something else

\test{!}{1} % prints: it is something else

\bye

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .