5

I have problems with macros containing ifnum. After using the command \noexpand one macro works fine, nesting two of them does not compile but causes the error message


Extra \fi. \fi


as in the following code. How can I get my nested macros working?

\documentclass{article}

\newcommand{\IFCONDA}{\noexpand{\ifnum1>0}}
\newcommand{\IFCONDB}{\noexpand{\ifnum1=0}}

\begin{document}
\IFCONDA
hello1
\else % \IFCONDA
world1

% next three lines cause trouble (nested ifnum)
\IFCONDB
hello2
\fi % \IFCONDB
\fi % \IFCONDA
\end{document}
0

4 Answers 4

7

The \noexpand token do nothing at all, because { is not expandable. Thus your code is the same as without it. Surrounding the test with braces is not good programming, by the way.

So I'll do as if \noexpand is not there. You get

{\ifnum1>0}
hello1
\else
world1
\par
\IFCONDB
hello2
\fi
\fi

(the line breaks are only for clarity). The test returns true, so you remain with

}
hello1
\else
world1
\par
\IFCONDB
hello2
\fi
\fi

The brace balances the initial one and is removed.

Now TeX sends “hello1” and a space down for typesetting and remains with

\else
world1
\par
\IFCONDB
hello2
\fi
\fi

Since the test was true, TeX discards everything from \else to the matching \fi without expansion, so \IFCONDB is discarded and the matching \fi is the first one.

You remain with the trailing \fi which matches nothing, as the test \ifnum1=0 is never seen.

It would be different if the first test was \ifnum1<0, because in this case \IFCONDB would be expanded during processing of the “false text”, but you'd get

### simple group (level 2) entered at line 14 ({)
### simple group (level 1) entered at line 8 ({)

because of the unmatched braces.

It isn't possible to say much more, because it's not clear what's your aim.

It is possible to use a conditional like style with the following changes:

\documentclass{article}

\newcommand{\CONDA}{TT\fi\ifnum1>0 }
\newcommand{\CONDB}{TT\fi\ifnum1=0 }

\begin{document}

\if\CONDA
hello1
\else % \IFCONDA
world1

% next three lines cause trouble (nested ifnum)
\if\CONDB
hello2
\fi
\fi % \IFCONDA

\end{document}

It's worth studying how it works…

3
  • This does not answer my question - it does not compile but shows the error message mentioned.
    – gschaaf
    Aug 8, 2023 at 15:07
  • 2
    @gschaaf Your approach cannot work, that’s what my answer is saying. Maybe, if you’re clearer about your aim, something better can ne said.
    – egreg
    Aug 8, 2023 at 17:39
  • @gschaaf I added a solution, but obviously it requires a change in syntax.
    – egreg
    Aug 9, 2023 at 9:34
4

You can not hide primitive \if tokens in macros (unless you take care to hide the matching\else and \fi), it is much simpler to use a syntax that takes two {} groups for true and false, such as \ifthenelse :

\documentclass{article}
\usepackage{ifthen}
\newcommand{\IFCONDA}{\ifthenelse{1>0}}
\newcommand{\IFCONDB}{\ifthenelse{1=0}}

\begin{document}
\IFCONDA
{hello1}
{%
world1

% next three lines cause trouble (nested ifnum)
\IFCONDB
{hello2}
{}%
}% \IFCONDA
\end{document}
2

While your code can never work for the reasons stated by egreg and David. It is possible to implement it approximately with a TeX macro using a cleverly chosen parameter text (see one of the following: The TeXbook chapter 20, TexbyTopic 11.5, or How TeX macros actually work: Part 4).

I provide two possible solutions, although I would note you probably dont want to do this and are better off using ifthen as suggested by David. First I show a solution where the conditions are static (e.g. 1>0) and later a more general one which can work on a more realistic conditions.

\documentclass{article}

\edef\ifconda #1\fi{\ifnum1>0 #1 \fi}
\edef\ifelseconda #1\else#2\fi{\ifnum1>0 #1 \else #2 \fi}

\edef\ifcondb #1\fi{\ifnum1=0 #1 \fi}
\edef\ifelsecondb #1\else#2\fi{\ifnum1=0 #1 \else #2 \fi}

\begin{document}

% original test case
\ifelseconda{
hello1
}\else{
world1
\ifcondb{
hello2
}\fi
}\fi

---

% more general test case 
\ifelseconda{
    \ifelsecondb{
        hello1
    }\else{
        hello2
    }\fi
}\else{
    \ifelsecondb{
        hello3
    }\else{
        hello4
    }\fi
}\fi

\ifelseconda{
    \ifelseconda{
        world1
    }\else{
        world2
    }\fi
}\else{
    \ifelseconda{
        world3
    }\else{
        world4
    }\fi
}\fi

\end{document}

Typesets:

hello1
---
hello2
world1

Note that this will only work if the condition can and should be evaluated when defining \ifconda etc.... As an \edef will expand its replacement text when defined (so \edef\ifelseconda #1\else#2\fi{\ifnum1>0 #1 \else #2 \fi} is expanded to \edef\ifelseconda #1\else#2\fi{#1} for example).

If you dont want the condition to be evaluated at definition time you can use a plain \def but we will need to change the spelling of \fi to avoid the need for excessive token juggling (which is probably possible but beyond what I want to reason through).

\documentclass{article}
\def\ifconda #1\fii{\ifnum1>0 #1 \fi}
\def\ifelseconda #1\else#2\fii{\ifnum1>0 #1 \else #2 \fi}

\def\ifcondb #1\fii{\ifnum1=0 #1 \fi}
\def\ifelsecondb #1\else#2\fii{\ifnum1=0 #1 \else #2 \fi}

\begin{document}

% original test case
\ifelseconda{
hello1
}\else{
world1
\ifcondb{
hello2
}\fii
}\fii

...
1
  • Your code introduces many unwanted spaces... You shouldn't put a space between #1 and \fi for instance. And the spaces around your arguments in the usage are wrong as well, you shoukd comment out the line endings (so use {% at the end of a line instead of the variant without %).
    – Skillmon
    Aug 9, 2023 at 9:47
0

\if..\else..\fi-matching only works out with TeX's \if.., \else and \fi-primitives.
\if..\else..\fi-matching does not apply to macro-tokens which have a phrase if or IF in their name.

With \if..\else..\fi-matching expansion of expandable tokens usually only takes places with tokens considered in branches which denote the actual case and which therefore do not just get discarded.

For the sake of clarity in your code let's rename \IFCONDA to \MACROA and \IFCONDB to \MACROB:

\documentclass{article}

\newcommand{\MACROA}{\noexpand{\ifnum1>0}}
\newcommand{\MACROB}{\noexpand{\ifnum1=0}}

\begin{document}
\MACROA
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi
\end{document}

So when the preamble and \begin{document} is processed, you have this:

\MACROA
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi

Now \MACROA is expanded and you have s.th. like this:

\noexpand{\ifnum1>0}%
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi

Now \noexpand "tells" TeX not to expand the subsequent token if that is expandable. But the subsequent token is { which is not expandable, so \noexpand actually has no effect:

{\ifnum1>0}%
hello1
\else
world1

% next three lines cause trouble (nested ifnum)
\MACROB
hello2
\fi
\fi

Now due to { TeX opens up a local scope, then encounters \ifnum1>0. Be aware that brace-nesting ({...}) is independent from \if..-\else-\fi-nesting! \ifnum1>0 is true, so TeX processes everything until finding a matching \fi or a matching \else. (In case a matching \fi is encountered, it gets removed. In case a matching \else is found, that \else and everything between that \else and the matching \fi and the matching \fi itself gets removed.) So TeX now encounters } and closes the local scope. Then TeX encounters the character tokens h, e, l, l, o, 1 and a space token. (The space token comes into being because usually TeX appends a space token to the token stream when in the .tex-input-file finding an end of a line of input while the last token appended to the token stream was not a control word token. 1 is not a control word token but is a character token.) These tokens are processed further, thus they may affect the result of typesetting/creating a .pdf-file. Then the matching \else is found. Thus that \else and everything between it and the matching \fi and the matching \fi itself gets discarded. The first \fi is considered the matching \fi. The second \fi is considered an extra \fi.


In order to avoid confusion when it comes to \if..-\else-\fi-nesting, in many situations you can do s.th. like this:

\documentclass{article}

\newcommand\FirstOfTwo[2]{#1}%
\newcommand\SecondOfTwo[2]{#2}%
\newcommand\CheckWhetherConditionA{\ifnum1>0 \expandafter\FirstOfTwo\else\expandafter\SecondOfTwo\fi}%
\newcommand\CheckWhetherConditionB{\ifnum1=0 \expandafter\FirstOfTwo\else\expandafter\SecondOfTwo\fi}%

\begin{document}
\CheckWhetherConditionA{hello1 }{world1\par\CheckWhetherConditionB{hello2}{}}%
\end{document}

(I said "in many situations" because the \FirstOfTwo/\SecondOfTwo-method does not work out for choosing one of two instances of a \verb|...|-command or a verbatim-environment or the like where it is relied on the changes of the category-code-régime introduced by these commands/environments being in effect when reading from the .tex-input-file and tokenizing their arguments/contents.)

You must log in to answer this question.

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