Implementing switch cases

I'd like to have command built around a switch-case environment in LaTeX, much like the example below:

\documentclass{article}

\usepackage{xstring}

\newcommand{\dothis}[1]{%
\IfStrEqCase{#1}{{a}{so you typed a}
{b}{now this is b}
{c}{you want me to do c?}}
}

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{e}

\end{document}


My problem with it is that it requires the xstring package. Is there any other way to do this? Preferably without loading additional packages and avoiding disgraceful thickets of \if-\else-\fi statements?

-
Are we allowed to use \pdfstrcmp? –  Joseph Wright Jul 20 '12 at 20:27
@JosephWright: Any ideas are welcome. :) –  Count Zero Jul 20 '12 at 20:28

The question asks to avoid packages, so while this is the method used by expl3 in \str_case:nnn I have recoded it with minimal support. The only package I've used is pdftexcmds, which is needed as the \pdfstrcmp primitive from pdfTeX is called \strcmp by XeTeX and has to be implemented in Lua for LuaTeX. This is easy enough to do without the package, but obscures the method: ask a separate question if required!

The general idea here is to set up a comparison loop in which the test is done expandably by \pdfstrcmp. The test string is passed every time, with the 'true' string inserted if there is a match by the 'tidy up' code. If there is no match at all then the 'else' code is inserted. The \romannumeral business means that it always requires exactly two expansions to do the work here:

\documentclass{article}
\usepackage{pdftexcmds}
\makeatletter
\newcommand*{\dothis}[1]{%
\stringcases
{#1}%
{%
{a}{so you typed a}%
{b}{now this is b}%
{c}{you want me to do c?}%
}%
}
\newcommand{\stringcases}[3]{%
\romannumeral
\str@case{#1}#2{#1}{#3}\q@stop
}
\newcommand{\str@case}[3]{%
\ifnum\pdf@strcmp{\unexpanded{#1}}{\unexpanded{#2}}=\z@
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\str@case@end{#3}}
{\str@case{#1}}%
}
\newcommand{\str@case@end}{}
\long\def\str@case@end#1#2\q@stop{\z@#1}
\makeatother

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{e}

\end{document}

-
Not sure which other methods you are referring to. I don't see how the loop could be avoided? Or is the \pdfstrcmp primitive particularly slow? –  Bruno Le Floch Jul 20 '12 at 23:33
@BrunoLeFloch I was thinking in particular of a non-expandable version where you you store in detokenized in a macro and use a series of baked-in \ifx tests. \ifx is fast, and \pdfstrcmp is slower, but you then have the nested \if ... \fi business we were asked to avoid. –  Joseph Wright Jul 21 '12 at 6:20
I benchmarked, and \ifx seemed just as slow as \pdfstrcmp, as long as the tokens' string representations are not too long. \ifx is only very fast for comparisons between macros let to each other (since TeX then only has to compare one number). –  Bruno Le Floch Jul 28 '12 at 8:35
@BrunoLeFloch OK, I'll edit that out then :-) –  Joseph Wright Jul 28 '12 at 8:47

The following is from catoptions package. The following \ifcasse is \cptifcasse in catoptions package. I think Count Zero should simply load an existing package for his task.

\documentclass{article}
\usepackage{pdftexcmds}
\makeatletter
\long\def\am@alltoendif#1\endif{\unexpanded{#1}}
\long\def\am@domatchcode#1#2\endif{\unexpanded{#1}}
\long\def\amifcond#1\fi{\csname @#1first\else second\fi oftwo\endcsname}
\long\def\amifstrcmp#1#2{%
\amifcond\if0\pdf@strcmp{\detokenize{#1}}{\detokenize{#2}}\fi
}
\def\ifcasse#1#2{%
\amifstrcmp{#1}\ifnone{%
\am@alltoendif
}{%
\amifstrcmp{#1}\endif{}{%
\amifstrcmp{#2}\ifnone{%
\am@alltoendif
}{%
\amifstrcmp{#2}\endif{}{\am@ifcasse{#1}{#2}}%
}%
}%
}%
}
\def\am@ifcasse#1#2#3{%
\amifstrcmp{#3}\ifnone{%
\am@alltoendif
}{%
\amifstrcmp{#3}\endif{}{%
#1{#2}{#3}\am@domatchcode{\am@ifcasse@i{#1}{#2}}%
}%
}%
}
\def\am@ifcasse@i#1#2#3{\am@ifcasse{#1}{#2}}

% The user can also define his own logical test command and pass it as the first
% argument of \ifcasse. For example, we define \ifnumtest:

\def\ifnumtest#1#2{\amifcond\ifnum#1#2\fi}

% We rewrite Count Zero's macro:
\newcommand*{\dothis}[1]{%
\par
\ifcasse\amifstrcmp{#1}%
{a}{so you typed a}
{b}{now this is b}
{c}{you want me to do c?}
\ifnone
[no match]%
\endif
}
\makeatother

\begin{document}
\dothis{a}
\dothis{b}
\dothis{c}
\dothis{e}

% Weird test that correctly gives \x as 'empty':
\edef\x{%
\ifcasse\amifstrcmp{x}
\endif
}
%\show\x

% Weird test that gives \x as 'no match':
\edef\x{%
\ifcasse\amifstrcmp{x}
\ifnone
[no match]%
\endif
}
\par\x''.

% Number test:
\edef\x{%
\ifcasse\ifnumtest{2}
{=1}{equal to 2}
{<3}{less than 3}
{>4}{greater than 4}
\ifnone
no match%
\endif
}
\par\x''.
\end{document}

-
Defining a string comparison macro which works like \pdfstrcmp is probably not reasonable (there was some code for an expandable comparison in expl3, but it dropped spaces so does not count). I was only meaning that it's not too hard to test the engine version and pick the correct name for the functionality (or to define in Lua). –  Joseph Wright Jul 21 '12 at 6:22
@JosephWright: You're right. I have seen one edge failure mode with the \strcmp command that I tried to create, but I don't have the time to fix it now. I have therefore edited my answer. –  Ahmed Musa Jul 21 '12 at 14:47

Though it's most likely too late for the OP, I just worked out my own switch and thought I'd share it here for future readers. My solution uses solely the package xifthen (ifthen suffices too, but I already had xifthen installed...).

% Switch implementation
\usepackage{xifthen}
\newcommand{\ifequals}[3]{\ifthenelse{\equal{#1}{#2}}{#3}{}}
\newenvironment{switch}[1]{\renewcommand{\case}{\ifequals{#1}}}{}

% Example: Pick color by ID
\newcommand{\incolor}[2]{
\begin{switch}{#1}
\case{1}{\color{red}}
\case{2}{\color{blue}}
\case{3}{\color{green}}
\case{4}{\color{black}}
#2
\end{switch}
}


This code compiles perfectly fine in my TeXMaker (ofc. you'll need the color-package for this example, but it's not part of the switch). The example colors a given input in a color defined by an ID I chose (Usage: \incolor{ID}{Content}). I used it for shorthand notations of lots of things (e.g. \lp1, \lp2, ... for parentheses in different colors using \newcommand{\lp}[1]{\incolor{#1}{\langle}}). Feel free to experiment ;)

I could imagine this to be expanded to a solution using only built-in control structures, but I've been too lazy for now to do so, yet \ifx and \else should do the trick.

-