5

I have defined a new command \case:

\documentclass{article}
\usepackage{pgffor}
\newcommand\case[1]{
\ifx 1#1 case 1\else
\ifx 2#1 case 2\else
\ifx 3#1 case 3\else
Illegal
\fi\fi\fi
}

\begin{document}

\case{1}

\case{2}

\case{3}

\end{document}

The new command works fine:

\case{1}: case 1
\case{2}: case 2
\case{3}: case 3

But with for each result:

\foreach \x in {1,2,3}{\case{\x}}

Illegal Illegal Illegal

Any hint would be appreciated.

4
  • there is a primitive \ifcase that lets you branch on 0,1,2 Commented Dec 9, 2020 at 23:51
  • please show an example of the problem that you are asking for help with, in your final fragment neither \foreach nor \doc are defined. And you do not say what error you got. Commented Dec 9, 2020 at 23:53
  • note that with your definition \case{12} produces the text 2 case 1 Commented Dec 9, 2020 at 23:55
  • so you do not mean you got a tex error you just got the expected output illegal illegal illegal; from the foreach macro (presumably from tikz, although you do not mention that package) Commented Dec 10, 2020 at 0:10

3 Answers 3

6

The problem is unrelated to foreach you would see the same from \def\x{1} \case{\x}

You are comparing \ifx 1\x and the tokens 1 and \x are not the same so this is false.

Conversely \case{12} would test \ifx112 which would test 1 and 1 as true and then typeset 2.

You want to do a numeric test not test the unexpanded tokens, so

\documentclass{article}

\newcommand\case[1]{%
\ifcase\numexpr#1\relax
case 0\or
case 1\or
case 2\or
case 3\else
Illegal%
\fi}
\usepackage{tikz}
\begin{document}

\case{1}

\case{12}

\case{3}


\foreach \x in {1,2,3}{\case{\x}}

\end{document}
1
  • Thank you David for your solution, it works well.
    – felipeuni
    Commented Dec 10, 2020 at 0:30
4

I can propose a more flexible set of commands.

\documentclass{article}
\usepackage{pgffor}
%\usepackage{xparse} % uncomment if using LaTeX released before 2020-10-01

\ExplSyntaxOn

\NewExpandableDocumentCommand{\checkcases}{mmO{}m}
 {
  \int_case:nnTF { #1 } { #2 } { #3 } { #4 }
 }

\ExplSyntaxOff

\newcommand{\mycaseswitch}[1]{%
  \checkcases{#1}{
    {1}{Case 1}
    {2}{Case 2}
    {3}{Case 3}
    {42}{Ultimate}
  }{Illegal}%
}
\newcommand{\otherswitch}[1]{%
  \checkcases{#1}{
    {-2}{Negative}
    {2}{Positive}
  }[!]{Bad?}%
}

\begin{document}

\mycaseswitch{1}: case 1

\mycaseswitch{2}: case 2

\mycaseswitch{3}: case 3

\mycaseswitch{4}: illegal

\mycaseswitch{42}: Hey!

\foreach \x in {1,2,3}{\mycaseswitch{\x}\par}

\otherswitch{-2}

\otherswitch{2}

\otherswitch{0}

\end{document}

enter image description here

The \checkcases command is just an interface to the internal expl3 function \int_case:nnTF. It has four arguments:

  1. the integer to test against the case list;
  2. the case list, in the form of pairs {<integer>}{<text>} (no need that the integers are consecutive or start from 0 like for \ifcase); spaces are ignored between pairs;
  3. (optional) text to be used after a successful match;
  4. text to be used for “no match”.
1
  • Thank you egreg for your solution and suggestions, it is very helpful.
    – felipeuni
    Commented Dec 10, 2020 at 18:56
2

With your code the \foreach-loop passes the token \x as argument to the \case-macro. \ifx (within the definition-text of \case) does compare the meaning of tokens without expanding them.

Therefore

  • via \ifx the meaning of the explicit character-token 112 is compared to the meaning of the control-sequence-token \x while the meanings differ,
  • via \ifx the meaning of the explicit character-token 212 is compared to the meaning of the control-sequence-token \x while the meanings differ,
  • via \ifx the meaning of the explicit character-token 312 is compared to the meaning of the control-sequence-token \x while the meanings differ.

Thus things end up in the "illegal"-branch.

With \foreach ensuring that control-sequences serving as loop-variable are expanded properly is up to the user.

Try: \foreach \x in {1,2,3}{\expandafter\case\expandafter{\x}}

By the way:

\case may behave in unexpected ways in case somebody provides things as #1 which contain unbalanced \else and/or \fi which match up the \ifx-expressions within the ⟨definition text⟩ of \case. Try, e.g., \case{4\fi\iftrue}.


For the sake of having fun while implementing it I can offer a macro \ExtractKthArg which does fully expandable without any additional package and without any \if..\fi and which cannot be tricked by unbalanced \else or \fi within its argument:

Syntax:

\ExtractKthArg{⟨TeX-⟨number⟩-quantity denoting an integer of value K⟩}%
              {⟨tokens in case list of undelimited arguments doesn't have a K-th argument⟩}%
              {⟨list of undelimited arguments⟩}%

In case there is no K-th argument in ⟨list of undelimited arguments⟩ :

Does deliver ⟨tokens in case list of undelimited arguments doesn't have a K-th argument⟩.

In case there is a K-th argument in ⟨list of undelimited arguments⟩ :

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

As long as the argument of your \case-macro will in any case be a valid TeX-⟨number⟩-quantity and you want TeX-⟨number⟩-quantities with values from 1 to 3 to be "legal", you can easily implement \case as follows by means of \ExtractKthArg:

\newcommand\case[1]{%
  \ExtractKthArg{#1}{illegal}{{case 1}{case 2}{case 3}}%
}%

Here comes the code:

\documentclass{article}

\makeatletter
%% Code for \ExtractKthArg
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, 
%%    \UD@CheckWhetherNull,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%-----------------------------------------------------------------------------
%% 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\z@\UD@secondoftwo}%
  {\expandafter\z@\UD@firstoftwo}%
}%
%%=============================================================================
%% Extract K-th inner undelimited argument:
%%
%% \ExtractKthArg{<TeX-<number>-quantity denoting an integer of value K>}%
%%               {<tokens in case list of undelimited arguments doesn't have a K-th argument>}%
%%               {<list of undelimited arguments>} %
%% 
%% In case there is no K-th argument in <list of undelimited arguments> : 
%%   Does deliver <tokens in case list of undelimited arguments doesn't have a K-th argument>.
%% In case there is a K-th argument in <list of undelimited arguments> : 
%%   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 
%% 
%%=============================================================================
\newcommand\ExtractKthArg[2]{%
  \romannumeral%
  % #1: <TeX-<number>-quantity denoting an integer of value K>
  % #2: <tokens in case list of undelimited arguments doesn't have a K-th argumnent>
  \expandafter\UD@ExtractKthArgCheck
  \expandafter{\romannumeral\number\number#1 000}{#2}%
}%
\newcommand\UD@ExtractKthArgCheck[3]{%
  \UD@CheckWhetherNull{#1}{% K not positive:
    \z@#2%
  }{% K positive:
    \expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
  }%
}%
\newcommand\UD@ExtractKthArgLoop[3]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#3{}.}{\z@#2}{%
    \UD@CheckWhetherNull{#1}{%
      \UD@ExtractFirstArgLoop{#3\UD@SelDOm}%
    }{%
      \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}%
      {\expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}}%
    }%
  }%
}%
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\expandafter\z@\UD@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%% End of code for \ExtractKthArg.
\makeatother

\usepackage{pgffor}

\newcommand\case[1]{%
  \ExtractKthArg{#1}{illegal}{{case 1}{case 2}{case 3}}%
}%

\pagestyle{empty}

\begin{document}

\verb|\case{1}|: \case{1}

\verb|\case{2}|: \case{2}

\verb|\case{3}|: \case{3}

\verb|\case{-1}|: \case{-1}

\verb|\case{4}|: \case{4}

The \verb|\foreach|-loop:

\verb|\foreach \x in {1,2,3}{\case{\x}}|: 

\foreach \x in {1,2,3}{\case{\x}}

\end{document}     

enter image description here

In case it is not ensured that the argument of your \case-macro will in any case be a valid TeX-⟨number⟩-quantity, things are more complicated:

Strictly spoken it is not possible to test whether an arbitrary macro argument yields a valid TeX-⟨number⟩-quantity: TeX is said to be Turing-complete. An arbitrary macro argument forms an arbitrary expansion-based algorithm. Checking whether an arbitrary expansion-based algorithm yields a valid TeX-⟨number⟩-quantity implies checking whether the arbitrary expansion-based algorithm in question (carried out by a system which is Turing-complete) terminates at all. This is the halting-problem. Alan Turing proved in 1936 that a general algorithm to solve the halting problem for all possible program-input pairs cannot exist.

But if you restrict to a -eh- bulletable list of constellations of tokens as "legal" user-input, then you can implement mapping from the single "legal" constellations to numbers and use that mapping when calling \ExtractKthArg:

Assume you wish only the token-constellation A11112 and the token-constellation a11112 and the token-constellation B11212 and the token-constellationb11212 and the token-constellation C11312 and the token-constellation c11312 to be allowed as legal user-input,
both A11112 and a11112 yielding the phrase case a1 or A1,
both B11212 and b11212 yielding the phrase case b2 or B2,
both C11312 and c11212 yielding the phrase case c3 or C3.
You can implement forking/mapping by means of delimited arguments as follows:

The token(-sequence) !12 is not a component of any of the token-constellations which are considered legal user-input. Therefore this token(-sequence) can be used as component of an argument-delimiter. But you need to test whether the argument passed by the user contains this token(-sequence) in order to ensure that no weird things happen in case the user provided something that would match up the argument-delimiter in some erroneous way:

% Code for a mechanism which forks illegal user-input and the
% different cases of legal user-input and maps these cases to 
% numbers:
\@ifdefinable{\GobbleToExclam}{\long\def\GobbleToExclam#1!{}}%
\@ifdefinable\CaseSelect{%
  \long\def\CaseSelect#1!a1!A1!b2!B2!c3!C3!#2#3!!!{#2}%
}%
\newcommand\MapCasesToNumbers[1]{%
  % Check whether an argument contains no exclamation-mark of
  % catcode 12 which is not nested in braces:
  \expandafter\UD@CheckWhetherNull\expandafter{\GobbleToExclam#1!}{%
    % Something without exclamation-mark, so let's select a case by
    % means of \CaseSelect where exclamation-mark is a component of
    % the argument-delimiter:
    \CaseSelect
    !#1!A1!b2!B2!c3!C3!{1}% <- Case #1 = a1
    !a1!#1!b2!B2!c3!C3!{1}% <- Case #1 = A1
    !a1!A1!#1!B2!c3!C3!{2}% <- Case #1 = b2
    !a1!A1!b2!#1!c3!C3!{2}% <- Case #1 = B2
    !a1!A1!b2!B2!#1!C3!{3}% <- Case #1 = c3
    !a1!A1!b2!B2!c3!#1!{3}% <- Case #1 = C3
    !a1!A1!b2!B2!c3!C3!{4}% <- Case #1 = s.th. else without exclamation-mark
    !!!%
  }{4}% <- Case #1 = s.th. else with exclamation-mark
}%
% End of code for a mechanism which forks illegal user-input
% and the different cases of legal user-input and maps these
% cases to numbers.

If you do it this way, ensuring that, e.g., macros serving as loop-variable of \pgffor are expanded properly before the forking-mechanism is applied is up to you.

Putting the pieces together:

\documentclass{article}

\makeatletter
%% Code for \ExtractKthArg
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, 
%%    \UD@CheckWhetherNull,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%-----------------------------------------------------------------------------
%% 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\z@\UD@secondoftwo}%
  {\expandafter\z@\UD@firstoftwo}%
}%
%%=============================================================================
%% Extract K-th inner undelimited argument:
%%
%% \ExtractKthArg{<TeX-<number>-quantity denoting an integer of value K>}%
%%               {<tokens in case list of undelimited args doesn't have a K-th argument>}%
%%               {<list of undelimited arguments>} %
%% 
%% In case there is no K-th argument in <list of undelimited arguments> : 
%%   Does deliver <tokens in case list of undelimited arguments doesn't have a K-th argument>.
%% In case there is a K-th argument in <list of undelimited arguments> : 
%%   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 
%% 
%%=============================================================================
\newcommand\ExtractKthArg[2]{%
  \romannumeral%
  % #1: <TeX-<number>-quantity denoting an integer of value K>
  % #2: <tokens in case list of undelimited arguments doesn't have a K-th argument>
  \expandafter\UD@ExtractKthArgCheck
  \expandafter{\romannumeral\number\number#1 000}{#2}%
}%
\newcommand\UD@ExtractKthArgCheck[3]{%
  \UD@CheckWhetherNull{#1}{% K not positive:
    \z@#2%
  }{% K positive:
    \expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
  }%
}%
\newcommand\UD@ExtractKthArgLoop[3]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#3{}.}{\z@#2}{%
    \UD@CheckWhetherNull{#1}{%
      \UD@ExtractFirstArgLoop{#3\UD@SelDOm}%
    }{%
      \expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}%
      {\expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}}%
    }%
  }%
}%
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\expandafter\z@\UD@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%% End of code for \ExtractKthArg.
\makeatother

\makeatletter    
% Code for a mechanism which forks illegal user-input and the
% different cases of legal user-input and maps these cases to 
% numbers:
\@ifdefinable{\GobbleToExclam}{\long\def\GobbleToExclam#1!{}}%
\@ifdefinable\CaseSelect{%
  \long\def\CaseSelect#1!a1!A1!b2!B2!c3!C3!#2#3!!!{#2}%
}%
\newcommand\MapCasesToNumbers[1]{%
  % Check whether an argument contains no exclamation-mark of
  % catcode 12 which is not nested in braces:
  \expandafter\UD@CheckWhetherNull\expandafter{\GobbleToExclam#1!}{%
    % Something without exclamation-mark, so let's select a case by
    % means of \CaseSelect where exclamation-mark is a component of
    % the argument-delimiter:
    \CaseSelect
    !#1!A1!b2!B2!c3!C3!{1}% <- Case #1 = a1
    !a1!#1!b2!B2!c3!C3!{1}% <- Case #1 = A1
    !a1!A1!#1!B2!c3!C3!{2}% <- Case #1 = b2
    !a1!A1!b2!#1!c3!C3!{2}% <- Case #1 = B2
    !a1!A1!b2!B2!#1!C3!{3}% <- Case #1 = c3
    !a1!A1!b2!B2!c3!#1!{3}% <- Case #1 = C3
    !a1!A1!b2!B2!c3!C3!{4}% <- Case #1 = s.th. else without exclamation-mark
    !!!%
  }{4}% <- Case #1 = s.th. else with exclamation-mark
}%
% End of code for a mechanism which forks illegal user-input
% and the different cases of legal user-input and maps these
% cases to numbers.
\makeatother

\usepackage{pgffor}

\newcommand\case[1]{%
  \ExtractKthArg{\MapCasesToNumbers{#1}}{illegal case}{{case a1 or A1}{case b2 or B2}{case c3 or C3}}%
}%

\pagestyle{empty}

\begin{document}

\verb|\case{a1}|: \case{a1}

\verb|\case{A1}|: \case{A1}

\verb|\case{b2}|: \case{b2}

\verb|\case{B2}|: \case{B2}

\verb|\case{c3}|: \case{c3}

\verb|\case{C3}|: \case{C3}

\verb|\case{Something Weird \relax \csname \fi}|: \case{Something Weird \relax \csname \fi}

\verb|\case{D4}|: \case{D4}

The \verb|\foreach|-loop---you need to ensure that the loop-variable \verb|\x| is expanded properly:

\verb|\foreach \x in {a1,A1,b2,B2,c3,C3,Weird}{\expandafter\case\expandafter{\x}}|:

\foreach \x in {a1,A1,b2,B2,c3,C3,Weird}{\expandafter\case\expandafter{\x}}

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