# Why are some macros undefined inside an if case?

I'm trying to check if a string is only of one char, and I ran into this problem: LaTeX complains that \StrLeft in the comparison takes and additional argument:

! Argument of \@secondoftwo has an extra }.
<inserted text>
\par
l.11 \IfStrEq{A}{\StrLeft{AB}{1}}
{True}{False}

However, it doesn't in the first one.

\documentclass[english]{article}
\usepackage[T1]{fontenc}
\usepackage[latin9]{inputenc}
\usepackage{xstring}
\begin{document}

\StrLeft{AB}{1}

\IfStrEq{A}{A}{True}{False}

\IfStrEq{A}{\StrLeft{AB}{1}}{True}{False}

\end{document}

I know I could take an additional third input and set a variable, but why is it not possible to use it like this?

-
Using it as such with the last optional parameter to allow you to store the result: \StrLeft{AB}{1}[\LeftChar] \IfStrEq{A}{\LeftChar}{True}{False}. Alternatively, if you just want to check the first char you could use \IfBeginWith. –  Peter Grill Apr 25 '12 at 18:19
Yes, but why doesn't it work in that special case? –  Henrik Hansen Apr 25 '12 at 18:23
Actually I want to check if the string is only of one char –  Henrik Hansen Apr 25 '12 at 18:24
Best way to check if it is one char would be: \StrLen{AB}[\length] \IfEq{\length}{1}{one char}{not one char}. I can't properly explain they "why" part, so will leave that to the real experts here. –  Peter Grill Apr 25 '12 at 18:27
I guess \IfStrEq requires expandable input and \StrLeft isn't fully expandable. Therefore it breaks and the error is caused. –  Martin Scharrer Apr 25 '12 at 18:29

The "real" reason why

\IfStrEq{A}{\StrLeft{AB}{1}}{True}{False}

doesn't work is that \StrLeft{AB}{1} produces the set of instructions necessary to extract the first token from the string AB, not the first token itself, while \IfStrEq needs to see the token.

Such instructions aren't made only by "expandable macros", because some computations need to be performed and this involves assignment of values to registers, thus cannot be evaluated by \IfStrEq. This is essentially the reason why all xstring commands have an optional last argument where to store the result of an extraction or substitution.

In your case a two step procedure must be used:

\StrLeft{AB}{1}[\temp]
\IfStrEq{A}{\temp}{True}{False}

will work, because \IfStrEq normally does expansion and \temp just contains the first token got from the string AB.

-

As Martin Scharrer said, you are using non expandable constructs so they do not work in all contexts, even if an error is avoided you need to decide if \def\temp{a}\temp is "equal" to "a" (and so have string length 1) or not. Either answer is reasonable but the coding required is completely different depending on what strings you want to consider, and what definition of equality you want.

Here's a simple expandable test just using TeX primitives that gives one definition of a test for string length 1:

\$ tex one
This is TeX, Version 3.1415926 (Web2C 2010)
(./one.tex
: length 0
a: length 1
abc: longer than 1
abcd: longer than 1
)
No pages of output.

produced by:

\def\isone#1{\immediate\write20{#1: \xisone#1{}{}\isone}}

\def\xisone#1#2#3\isone{%
\ifx\relax#1\relax
length 0%
\else
\ifx\relax#2\relax
length 1%
\else
longer than 1%
\fi
\fi}

\isone{}

\isone{a}

\isone{abc}

\isone{abcd}

\bye
-