2

For some reason this workaround works:

\IfStrEq{#1}{\empty}{\def\tmp{0}}{\ifnum#1>0\def\tmp{1}\else\def\tmp{0}\fi}
\ifnum\tmp=1
    TRUE
\else
    FALSE
\fi

but this combination doesn't

\ifnum\IfStrEq{#1}{\empty}{0}{\ifnum#1>0 1\else 0 \fi}=1%
    TRUE
\else
    FALSE
\fi

Now I am curious why this is so.

For the sake of completeness, here is an MVE:

\documentclass{scrbook}
\usepackage[english]{babel} 
\usepackage{xstring}

\makeatletter
\newcommand{\TFa}[1]{
    \IfStrEq{#1}{\empty}{\def\tmp{0}}{\ifnum#1>0\def\tmp{1}\else\def\tmp{0}\fi}
    \ifnum\tmp=1
        TRUE
    \else
        FALSE
    \fi
}
\newcommand{\TFb}[1]{
    \ifnum\IfStrEq{#1}{\empty}{0}{\ifnum#1>0 1\else 0 \fi}=1%
        TRUE
    \else
        FALSE
    \fi
}
% main document
\begin{document}
\TFa{}
\TFa{0}
\TFa{1}
\TFa{-1}
\TFa{99}

\TFb{}
\TFb{0}
\TFb{1}
\TFb{-1}
\TFb{99}
\end{document}
Max
  • 171
  • 1
    Because xstring's commands are not expandable, which means that when \ifnum is expanding tokens looking for a number, the \IfStrEq macro leaves things which aren't numbers, thus you get some error like missing number treated as zero or missing = inserted for \ifnum. \tmp, on the other hand, is defined to expand to a number, so it works as expected. – Phelype Oleinik Jan 11 '20 at 15:39
  • please always post full code. the fragments you gave could not be run and you gave no indication of where the non standard \IfStrEq was defined. You were lucky that Phelype recognised it but a test document that included \usepackage{xstring} would have been clearer – David Carlisle Jan 11 '20 at 15:47
  • @PhelypeOleinik, so IfStrEq doesn't always returns a boolean but e.g. an empty character if the input is also empty? – Max Jan 12 '20 at 09:00
  • 1
    @max No, not exactly. TeX doesn’t work as other languages, with “return values” for functions, so you can’t assume that. I posted an answer which (hopefully) explains why your code didn't work. Also note that you need the % in \newcommand{\TFa}[1]{% and \newcommand{\TFb}[1]{%, and the % in \else 0 \fi}=1% (first line of the definition of \TFb) shouldn't be there (see: https://tex.stackexchange.com/q/7453/134574). – Phelype Oleinik Jan 12 '20 at 15:56

3 Answers3

2

Since you have \ifnum#1>0 you seem to be assuming that #1 is empty or a valid number (not oops for example) so your tests are far more complicated than needed, you can do

 \makeatletter % if not in package code

 \ifdim#1\p@>\z@
   yes
  \else
   no
  \fi

as if #1 is empty that tests if 1pt > 0pt and if it is a number then the scaled length will be > 0pt just if the number is positive.

David Carlisle
  • 757,742
  • That is cool. I have no idea what is statement does (or what oops are), neither does it explain why my cobbled stuff works/doesn't work but yours it is a much smoother solution =) – Max Jan 12 '20 at 09:02
  • @max I mean meant literally oops (or any other string) The above code assumes #1 is a number and it returns yes or no depending on if it is positive. But it does no type checking if you go test{oops} or \test{hello max} then #1 in the above is oops so you get \ifdim oops\p@ which is a syntax error and you will get a low level tex error and not a clean yes or no result. So this form is quick if you already know by this point that the input is more or less valid, but may be a bit brutal if used in a top level command for the end user – David Carlisle Jan 13 '20 at 09:38
2

(Too long a reply to your comment) Others have already showed you how to make it work. I’ll try to explain why your approach doesn’t.

TeX doesn’t work as other languages, with “return values” for functions. TeX is a macro expansion language, and that’s basically what it does. When TeX reads your code it splits the processing in two main stages: expansion and processing. There are situations (most of them) where TeX does both of them: if there’s something to expand, it expands, if there’s something to process, it processes, and all is good. There are situations, however, sometimes called “expansion-only contexts”, where it only expands things and will not do anything else. One of this expansion-only contexts is when TeX is scanning a number, for example, after an \ifnum (there are many others, and the expansion rules slightly differ between them).

Now let us pause the explanation above for a moment to differentiate between things that expand and things that are processed. Any token (let’s focus on control sequence tokens, i.e., \<something>) can be either a macro or a primitive. A macro is anything you (or some package) defined with \def\foo{bar}, and a primitive is built into TeX (for example \ifnum). A macro will always expand (in the example above, \foo expands to bar). A primitive can either be expandable or not (that depends on the primitive; see a list here). \def, for example, is not expandable, while \ifnum is. As a rule of thumb, if the primitive does an assignment, it is not expandable. Everything else depends.

 — But why on Earth, then, isn’t \IfStrEq expandable? It’s a macro, after all, so from the definition above, it must expand, right?
 — Well, yes, it does expand, but not all the way through. What makes a macro be “safely expandable” or not depends on how it is implemented. If the implementation of certain macro contains only expandable primitives or other (expandable) macros, then that macro is said to be expandable. Otherwise it is not.

Now let’s get back to your example and the expansion-only context of \ifnum. When TeX sees \ifnum it starts expanding tokens ahead, looking for a number, then for either <, =, or >, and then another number (this imposes another restriction in the expansion rules of \ifnum: whatever you put there has to expand to these tokens). Your working example does that:

\IfStrEq{#1}{\empty}
  {\def\tmp{0}}
  {\ifnum#1>0 \def\tmp{1}\else\def\tmp{0}\fi}
\ifnum\tmp=1
  true
...

Here \IfStrEq is used in a normal (not expansion-only) context, and does its thing, defining \tmp to expand (a macro, thus it expands) to either 0 or 1. Then TeX sees \ifnum, which starts by expanding \tmp once, so it ends up like \ifnum1=1 (or \ifnum0=1, depending on #1) and all goes well.

The second code, however, puts \IfStrEq right after \ifnum:

\ifnum\IfStrEq{#1}{\empty}
  {0}
  {\ifnum#1>0 1\else 0 \fi}=1
  true
...

so TeX will expand \IfStrEq (a macro) looking for a number. After one expansion \IfStrEq becomes \xs_ifstar{<code>}{<more code>} (you can see that with \show\IfStrEq or \texttt{\meaning\IfStrEq}), so you have \ifnum\xs_ifstar<code>.... \ifnum keeps expanding things, and now \xs_ifstar (another macro) expands to \xs_ifnxttok*<more code>..., then \xs_ifnxttok (yet another macro) expands to \xs_deftok<code code>..., which finally expands to \let\xs_toksmatch=*\relax.

So after four expansion steps, \ifnum\IfStrEq becomes \ifnum\let\xs_toksmatch=*\relax, and then \ifnum sees the primitive \let (which does an assignment so it is not expandable) and stops looking for a number. But no number was found, so TeX complains about a missing number and shows you what it saw instead of the number, \let:

! Missing number, treated as zero.
<to be read again> 
                   \let 
l.28 \TFb{}

?

and that’s why \IfStrEq will not work in an expansion-only context (i.e., it’s not expandable). Different usages of the command (that is, not after a \ifnum) will break in different ways, because the expansion rules will differ, but it will break nonetheless (here, for example).

I hope this rather lengthy explanation sheds some light on how expansion works and why some commands cannot be used in certain places.

  • wow TeX is still as fascinating as it is a mystical black box to me. Thx for lighting it up a bit. I'm looking forward to find out new stuff (and run into more annoying marvels) while writing=) – Max Jan 14 '20 at 21:14
1

You can use expl3 expandable tests. The tests of xstring and \ifthen are not fully expandable.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\testforpositiveargTF}{mmm}
 {
  \bool_lazy_and:nnTF { !\tl_if_blank_p:e { #1 } } { \int_compare_p:n { #1 > 0 } }
   {% not empty and positive
    #2
   }
   {% empty or negative
    #3
   }
 }

\prg_generate_conditional_variant:Nnn \tl_if_blank:n { e } { p, T, F, TF }

\ExplSyntaxOff

\begin{document}

\testforpositiveargTF{}{true}{false} (false)

\testforpositiveargTF{ }{true}{false} (false)

\testforpositiveargTF{\space}{true}{false} (false)

\testforpositiveargTF{\empty}{true}{false} (false)

\testforpositiveargTF{0}{true}{false} (false)

\testforpositiveargTF{-2}{true}{false} (false)

\newcommand{\zero}{0}

\testforpositiveargTF{\zero}{true}{false} (false)

\testforpositiveargTF{1}{true}{false} (true)

\newcommand{\one}{1}

\testforpositiveargTF{\one}{true}{false} (true)

\end{document}

enter image description here

egreg
  • 1,121,712