46

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?}}
    [nada]
}

\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?

4
  • Are we allowed to use \pdfstrcmp?
    – Joseph Wright
    Jul 20, 2012 at 20:27
  • @JosephWright: Any ideas are welcome. :)
    – Count Zero
    Jul 20, 2012 at 20:28
  • After various troubles with alternatives, I ended up using the \str_case:nnF construct that expl3 makes available through e.g. \usepackage{xparse}. I was initially put off by the unusual characters in the macro name, but that was a mistake — it just has to be surrounded by \ExplSyntaxOn, \ExplSyntaxOff to make that syntax valid. Feb 17, 2016 at 10:19
  • Just a note that \str_case:nnF is documented in interface3.pdf. Feb 17, 2016 at 10:33

9 Answers 9

27

The question asks to avoid packages, so while this is the method used by expl3 in \str_case:nnF 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?}%
    }%
    {[nada]}%
}
\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}
4
  • 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? Jul 20, 2012 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, 2012 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). Jul 28, 2012 at 8:35
  • @BrunoLeFloch OK, I'll edit that out then :-)
    – Joseph Wright
    Jul 28, 2012 at 8:47
30

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}{}}
\newcommand{\case}[2]{#1 #2} % Dummy, so \renewcommand has something to overwrite...
\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.

3
  • 1
    Unfortunately there is no easy way to provide a default base case if nothing matches, with this approach. Feb 8, 2016 at 9:49
  • You can delete the line \newcommand{\case}[2]{#1 #2} and simply use \newcommand when defining the switch environment.
    – Paul Wintz
    Apr 23, 2022 at 19:48
  • 1
    Ah, I see why \renewcommand is better than \newcommand. If you want to use nested switch environments, then using \newcommand will cause an error in the inner switch environment because the \case command will already be defined in the outer environment.
    – Paul Wintz
    Apr 23, 2022 at 20:50
11

There is the built-in \ifcase which works like a switch-case that interpret numbers. Because of this, I translated the strings 'a', 'b', 'c' to numbers 0, 1, 2. Note that the first case is 0. It don't need packages and is very simple.

\documentclass{article}
\newcommand{\dothis}[1]{%
\ifcase#1\relax so you typed a % Case 0.

\or now this is b % Case 1.

\or you want me to do c? % Case 2.

\else Default case.
\fi
}
\begin{document}
\dothis{0}

\dothis{1}

\dothis{2}

\dothis{3}
\end{document}
0
7

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}
2
  • 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, 2012 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, 2012 at 14:47
7

Here is a solution via macro definitions (a single test makes the choice):

\documentclass{article}
\makeatletter
\newcommand\addcase[3]{\expandafter\def\csname\string#1@case@#2\endcsname{#3}}
\newcommand\makeswitch[2][]{%
  \newcommand#2[1]{%
    \ifcsname\string#2@case@##1\endcsname\csname\string#2@case@##1\endcsname\else#1\fi%
  }%
}
\makeatother

\makeswitch[nada]\dothis
\addcase\dothis{a}{so you typed a}
\addcase\dothis{b}{so you typed b}
\addcase\dothis{c}{you want me to do c?}

\begin{document}
\dothis{a}

\dothis{b}

\dothis{c}

\dothis{e}

\end{document}
1
  • 1
    I was just looking to see if this type of approach was represented. I find it the most excellent of approaches. (+1) Jan 18, 2017 at 17:06
4

Based on the great answer by Thev and updated by MaxD I have implemented a \default command for switch. I used ifthen package:

\newboolean{default}
\newcommand{\case}{}
\newcommand{\default}{}

\newenvironment{switch}[1]{%
    \setboolean{default}{true}
    \renewcommand{\case}[2]{\ifthenelse{\equal{#1}{##1}}{%
        \setboolean{default}{false}##2}{}}%
    \renewcommand{\default}[1]{\ifthenelse{\boolean{default}}{##1}{}}
}{}

Then implement it as such:

  \begin{switch}{\program}  
    \case{2}{\twodayprogram}  
    \case{4}{\fourdayprogram}  
    \default{\first\ \last\ missing program\pagebreak}  
  \end{switch}

in case anyone is curious, this was a program for an off-season sports conditioning program and was being looped with datatool, the commands \twodayprogram and \fourdayprogram implement the program for a particular athlete

3

This is a simple extension to Thev's answer, adding support for a default case.

% Switch implementation
\usepackage{xifthen}
\newcommand{\ifequals}[4]{\ifthenelse{\equal{#1}{#2}}{#3}{#4}}
\newcommand{\case}[2]{#1 #2} % Dummy, so \renewcommand has something to overwrite...
\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}}{
        \color{cyan}
        }}}}
    \end{switch}
    #2
}
2

Sorry for being late to the party. :-)

expl3/xparse/ltcmd bring along nice tools for case-forking. Since the 2020-10-01 release expl3 and xparse (the latter re-branded as ltcmd and deprived of things that are not considered good practice any more) are included in the LaTeX 2e-format. So nowadays you could use expl3/ltcmd "out of the box" without loading additional packages. But in 2012, when the question was asked, xparse/expl3 needed to be loaded as additional packages while the question requests things to be done without loading additional packages. Thus I assume that the focus of the question is on "old school code".


You can easily avoid any \if..\else..\fi-thingie without loading whatsoever additional packages if you do things in terms of delimited arguments:

\makeatletter
\errorcontextlines=10000
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@stopromannumeral, \UD@CheckWhetherNull,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@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>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%=============================================================================
\@ifdefinable\UD@GobbleToExclam{\long\def\UD@GobbleToExclam#1!{}}%
\@ifdefinable\UD@abcFork{%
  \long\def\UD@abcFork#1!a!b!c!#2#3!!!!{\UD@stopromannumeral#2}%
}%
\newcommand\dothis[1]{%
  \romannumeral\expandafter\UD@CheckWhetherNull
               \expandafter{\UD@GobbleToExclam#1!}{%
    \UD@abcFork
    !#1!b!c!{so you typed a}% <-this is \UD@abcFork's 2nd arg if #1 = a
    !a!#1!c!{now this is b}% <-this is \UD@abcFork's 2nd arg if #1 = b
    !a!b!#1!{you want me to do c?}% <-this is \UD@abcFork's 2nd arg if #1 = c
    !a!b!c!{[nada]}%  <-this is \UD@abcFork's 2nd arg if #1 neither a nor b nor c
    !!!!% <-this is the delimiter of \UD@abcFork's 3rd argument
  }{\UD@stopromannumeral[nada]}% <-This is done if #1 contains ! and thus could erroneously match the delimiter !a!b!c!
}%
\makeatother

\documentclass{article}

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{e}

\dothis{!e!}

\end{document}

enter image description here


If you wish to combine cases, e.g. "You typed a or d", you can combine the above forking-technique with a routine for extracting the K-th argument from a list of undelimited arguments so that the number K is provided by the forking-mechanism:

\makeatletter
\errorcontextlines=10000
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond,
%% \UD@stopromannumeral, \UD@CheckWhetherNull,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@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>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%=============================================================================
%%
%% Extract K-th inner undelimited argument:
%%
%% \ExtractKthArg{<integer K>}%
%%               {<tokens in case list of undelimited args doesn't have a k-th argumnent>}%
%%               {<list of undelimited args>} %
%% 
%% In case there is no K-th argument in <list of indelimited args> : 
%%   Does deliver <tokens in case list of undelimited args doesn't have a k-th argumnent.
%% In case there is a K-th argument in <list of indelimited args> : 
%%   Does deliver that K-th argument with one level of braces removed.
%%
%% Examples:
%%
%%   \ExtractKthArg{0}{not available}{ABCDE} yields: not available
%%
%%   \ExtractKthArg{3}{not available}{ABCDE} yields:  C
%%
%%   \ExtractKthArg{3}{not available}{AB{CD}E} yields:  CD
%%
%%   \ExtractKthArg{4}{not available}{{001}{002}{003}{004}{005}} yields: 004
%%
%%   \ExtractKthArg{6}{not available}{{001}{002}{003}} yields: not available 
%% 
%% Due to \romannumeral-expansion the result can be obtained by triggering
%% two expansion-steps.
%%=============================================================================
\newcommand\ExtractKthArg[2]{%
  \romannumeral%
  % #1: <integer number K>
  % #2: <action if there is no K-th argument>
  \expandafter\UD@ExtractKthArgCheck
  \expandafter{\romannumeral\number\number#1 000}{#2}%
}%
\newcommand\UD@ExtractKthArgCheck[3]{%
  \UD@CheckWhetherNull{#1}{\UD@stopromannumeral#2}{% empty
    \expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
  }%
}%
\begingroup
\def\UD@ExtractFirstArgLoop#1{%
  \endgroup
  \@ifdefinable\UD@RemoveTillFrozenrelax{%
    \long\def\UD@RemoveTillFrozenrelax##1##2#1{{##1}}%
  }%
  \newcommand\UD@ExtractKthArgLoop[3]{%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo##3{}.}{\UD@stopromannumeral##2}{%
      \UD@CheckWhetherNull{##1}{%
        \UD@ExtractFirstArgLoop{##3#1}%
      }{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}##3}%
        {\expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}##1}{##2}}%
      }%
    }%
  }%
}%
\expandafter\expandafter\expandafter\UD@ExtractFirstArgLoop
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
%% Usage of frozen-\relax as delimiter is for speeding things up by reducing the
%% amount of iterations needed. I chose frozen-\relax because David Carlisle 
%% pointed out in   <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\expandafter\UD@stopromannumeral\UD@firstoftwo#1{}}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%% End of code for \ExtractKthArg.
%%=============================================================================
\@ifdefinable\UD@GobbleToExclam{\long\def\UD@GobbleToExclam#1!{}}%
\@ifdefinable\UD@abcdeFork{%
  \long\def\UD@abcdeFork#1!a!b!c!d!e!#2#3!!!!{#2}%
}%
\newcommand\dothis[1]{%
  \romannumeral\expandafter\UD@secondoftwo
  \ExtractKthArg{%
    %==\ExtractKthArg's 1st argument=<integer K>===============================
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}{%
      \UD@abcdeFork
      !#1!b!c!d!e!{1}% <- #1 = a: deliver 1st argument
      !a!#1!c!d!e!{2}% <- #1 = b: deliver 2nd argument
      !a!b!#1!d!e!{3}% <- #1 = c: deliver 3rd argument
      !a!b!c!#1!e!{1}% <- #1 = d: deliver 1st argument
      !a!b!c!d!#1!{4}% <- #1 = e: deliver 4th argument
      !a!b!c!d!e!{5}% <- #1 doesn't contain ! and is neither a nor b nor c nor d nor e: deliver 5th argument
      !!!!%
    }{5}% <- #1 does contain ! and thus is neither a nor b nor c nor d nor e: deliver 5th argument
    %==End of \ExtractKthArg's 1st argument====================================
  }{%==\ExtractKthArg's 2nd argument=<tokens in case list of undelimited args
    %   doesn't have a k-th argumnent> It is left empty as a K-th argument is
    %   in the list for each value K that can occur.
  }{%==\ExtractKthArg's 3rd argument=<list of undelimited arguments>===========
    {You typed a or you typed d.}% - 1st argument
    {You typed b.}% - 2nd argument
    {You typed c.}% - 3rd argument
    {You typed e.}% - 4th argument
    {You typed something which is neither a nor b nor c nor d nor e.}% - 5th argument
  }%
}%
%%=============================================================================
\makeatother

\documentclass{article}

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{d}

\dothis{e}

\dothis{f}

\dothis{!f!}

\end{document}

enter image description here


If you need to fork many different cases so that a single forking-macro would require a delimiter which is cluttered, you can use more than one forking macro.

E.g., instead of \UD@abcdeFork you can combine usage of \UD@abcFork and \UD@deFork:

\makeatletter
\errorcontextlines=10000
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond,
%% \UD@stopromannumeral, \UD@CheckWhetherNull,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@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>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%=============================================================================
%%
%% Extract K-th inner undelimited argument:
%%
%% \ExtractKthArg{<integer K>}%
%%               {<tokens in case list of undelimited args doesn't have a k-th argumnent>}%
%%               {<list of undelimited args>} %
%% 
%% In case there is no K-th argument in <list of indelimited args> : 
%%   Does deliver <tokens in case list of undelimited args doesn't have a k-th argumnent.
%% In case there is a K-th argument in <list of indelimited args> : 
%%   Does deliver that K-th argument with one level of braces removed.
%%
%% Examples:
%%
%%   \ExtractKthArg{0}{not available}{ABCDE} yields: not available
%%
%%   \ExtractKthArg{3}{not available}{ABCDE} yields:  C
%%
%%   \ExtractKthArg{3}{not available}{AB{CD}E} yields:  CD
%%
%%   \ExtractKthArg{4}{not available}{{001}{002}{003}{004}{005}} yields: 004
%%
%%   \ExtractKthArg{6}{not available}{{001}{002}{003}} yields: not available 
%% 
%% Due to \romannumeral-expansion the result can be obtained by triggering
%% two expansion-steps.
%%=============================================================================
\newcommand\ExtractKthArg[2]{%
  \romannumeral%
  % #1: <integer number K>
  % #2: <action if there is no K-th argument>
  \expandafter\UD@ExtractKthArgCheck
  \expandafter{\romannumeral\number\number#1 000}{#2}%
}%
\newcommand\UD@ExtractKthArgCheck[3]{%
  \UD@CheckWhetherNull{#1}{\UD@stopromannumeral#2}{% empty
    \expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
  }%
}%
\begingroup
\def\UD@ExtractFirstArgLoop#1{%
  \endgroup
  \@ifdefinable\UD@RemoveTillFrozenrelax{%
    \long\def\UD@RemoveTillFrozenrelax##1##2#1{{##1}}%
  }%
  \newcommand\UD@ExtractKthArgLoop[3]{%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo##3{}.}{\UD@stopromannumeral##2}{%
      \UD@CheckWhetherNull{##1}{%
        \UD@ExtractFirstArgLoop{##3#1}%
      }{%
        \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}##3}%
        {\expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}##1}{##2}}%
      }%
    }%
  }%
}%
\expandafter\expandafter\expandafter\UD@ExtractFirstArgLoop
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
%% Usage of frozen-\relax as delimiter is for speeding things up by reducing the
%% amount of iterations needed. I chose frozen-\relax because David Carlisle 
%% pointed out in   <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\expandafter\UD@stopromannumeral\UD@firstoftwo#1{}}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%% End of code for \ExtractKthArg.
%%=============================================================================
\@ifdefinable\UD@GobbleToExclam{\long\def\UD@GobbleToExclam#1!{}}%
\@ifdefinable\UD@abcFork{%
  \long\def\UD@abcFork#1!a!b!c!#2#3!!!!{#2}%
}%
\@ifdefinable\UD@deFork{%
  \long\def\UD@deFork#1!d!e!#2#3!!!!{#2}%
}%
\newcommand\dothis[1]{%
  \romannumeral\expandafter\UD@secondoftwo
  \ExtractKthArg{%
    %==\ExtractKthArg's 1st argument=<integer K>===============================
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}{%
      \UD@abcFork
      !#1!b!c!{1}% <- #1 = a: deliver 1st argument
      !a!#1!c!{2}% <- #1 = b: deliver 2nd argument
      !a!b!#1!{3}% <- #1 = c: deliver 3rd argument
      !a!b!c!{%  <- #1 does not contain ! and is neither a nor b nor c / in this case number is delivered by \UD@deFork
        \UD@deFork
        !#1!e!{1}% <- #1 = d: deliver 1st argument
        !d!#1!{4}% <- #1 = e: deliver 4th argument
        !d!e!{5}% <- #1 (doesn't contain ! and) is neither (a nor b nor c nor) d nor e: deliver 5th argument
        !!!!%
      }%
      !!!!%
    }{5}% <- #1 does contain ! and thus is neither a nor b nor c nor d nor e: deliver 5th argument
    %==End of \ExtractKthArg's 1st argument====================================
  }{%==\ExtractKthArg's 2nd argument=<tokens in case list of undelimited args
    %   doesn't have a k-th argumnent> It is left empty as a K-th argument is
    %   in the list for each value K that can occur.
  }{%==\ExtractKthArg's 3rd argument=<list of undelimited arguments>===========
    {You typed a or you typed d.}% - 1st argument
    {You typed b.}% - 2nd argument
    {You typed c.}% - 3rd argument
    {You typed e.}% - 4th argument
    {You typed something which is neither a nor b nor c nor d nor e.}% - 5th argument
  }%
}%
%%=============================================================================
\makeatother

\documentclass{article}

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{d}

\dothis{e}

\dothis{f}

\dothis{!f!}

\end{document}

enter image description here

2
  • 1
    Well, with a recent LaTeX kernel, there's no need to load packages in order to have expl3.
    – egreg
    Jan 7, 2022 at 22:48
  • @egreg That's right. I edited my preliminary comment accordingly. Jan 7, 2022 at 23:36
1

In LuaTeX you can use Lua tables to emulate a switch statement.

% arara: lualatex
\documentclass{article}

\newcommand*\dothis[1]{%
  \directlua{
    local cases = {
        a = "so you typed a",
        b = "now this is b",
        c = "you want to do c?"
    }

    if cases["\luaescapestring{#1}"] \string~= nil then
        tex.sprint(cases["\luaescapestring{#1}"])
    else
        tex.sprint("[nada]")
    end
  }
}

\begin{document}

\dothis{a}

\dothis{b}

\dothis{c}

\dothis{d}

\end{document}

enter image description here

You must log in to answer this question.

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