# Generalize \@ifnextchar to consider more than one character.

I am looking for a clever way to write a switch statement based on the next input characters, ideally, one could write:

\@switchnextchars
\@case{abc}{\typeout{You gave me an abc}}%
\@case{ab+}{\typeout{You gave me an ab+}}%
\@default{\typeout{Nothing that I heard of!}%
\@endswitch


but also

\@ifnextchars{abc}{yes code}{no code}


or even

\@ifnexttwochars{ab}{yes code}{no code}


would be useful.

-
Something like \@ifnextchars got already implemented, I think even twice, but I can't recall where right now. Was it beamer, pgf/tikz or koma script? –  Martin Scharrer Feb 7 '11 at 20:23

One possible approach to construct a loop of \@ifnextchar tests, for example

\makeatletter
\newtoks\my@toksa
\newtoks\my@toksb
\newcommand\@ifnextchars[3]{%
\def\my@tempa{#1}%
\def\my@tempb{#2}%
\def\my@tempc{#3}%
\def\my@tempd{}%
\@ifnextchars@aux@i
}
\newcommand*\@ifnextchars@aux@i{%
\def\my@tempe{\expandafter\my@tempb\my@tempd}%
\ifx\@empty\my@tempa\@empty
\else
\def\my@tempe{\@ifnextchars@aux@ii}%
\fi
\my@tempe
}
\newcommand*\@ifnextchars@aux@ii{%
\my@toksa\expandafter\expandafter\expandafter
{\expandafter\@car\my@tempa\@nil}%
\expandafter\@ifnextchar\the\my@toksa
{%
\expandafter\def\expandafter\@ifnextchars@aux@iii\the\my@toksa{%
\my@toksb\expandafter{\my@tempd}%
\edef\my@tempd{\the\my@toksb\the\my@toksa}%
\my@toksa\expandafter\expandafter\expandafter
{\expandafter\@cdr\my@tempa\@nil}%
\edef\my@tempa{\the\my@toksa}%
\@ifnextchars@aux@i
}%
\@ifnextchars@aux@iii
}
{\expandafter\my@tempc\my@tempd}%
}
\makeatother


(This may well not be the most efficient implementation for this.) As you can see, this is non-trivial as TeX only provides \futurelet (on which \@ifnextchar is built), and this only deals with one token at a time. The 'cases' system is doable but will not be pretty, as each case would have to be saved and the tests done one at a time with token-reinsertion.

-
This is a clever solution. Thanks. But have you considered catcode changes between your command definition and later use/argument grabbing? Also, can we avoid repeat calls to \@ifnextchar for each character in the test set? I await your comment before having a go at it. –  Ahmed Musa Feb 8 '11 at 18:05
@Pluto: not really clever, more brute force. As you probably known, \@ifnextchar does an \ifx test, and so only gives a positive result if category codes are unchanged. So it seems to make logical sense if an extended \@ifnextchars behaves in the same way. You could implement \my@ifnextchar using an \if test to use only character code, then use that function in my solution to do this for multiple characters. –  Joseph Wright Feb 8 '11 at 19:16
@Pluto. On the question of multiple uses of \@ifnextchar in the above, remember that we are using \futurelet to 'see' the next token. If we want to 'see' several tokens, this does mean that we have to remove them from the input stream. The approach of absorbing them all in one go first fails with something like {a}bc (i.e. the test will return true when it should be false). So doing the job one token at a time is the only safe way: sorry. –  Joseph Wright Feb 8 '11 at 19:32
I will prefer the \my@ifnextchar to avoid possible changes/differences in catcode between the test set and what is found on the input stack. \@ifnextchar seems to be more expensive than simply grabbing the next character and making a comparison. See my attempt below. Not really tested yet. –  Ahmed Musa Feb 8 '11 at 20:11
\documentclass{article}
\usepackage{catoptions}
\makeatletter
\def\special@car{\futurelet\next\special@car@a}
\long\def\special@car@a#1#2\car@nil{%
\ifxTF\next\temp@bgroup{\toks@{{#1}}}{\toks@{#1}}%
}
\long\def\@ifnextchars#1#2#3{%
\begingroup
\let\temp@bgroup\bgroup
\let\bgroup\@undefined
\def\@tempa{#1}\def\@tempb{#2}%
\def\@tempc{#3}\def\@tempd{}%
\@ifnextchars@a
}
\def\@ifnextchars@a{\futurelet\next\@ifnextchars@b}
\def\@ifnextchars@b{%
\ifxTF\next\@sptoken{\afterassignment\@ifnextchars@a\let\next= }%
\@ifnextchars@c
}
\def\@ifnextchars@c#1{%
\ifxTF\next\temp@bgroup{\toks1{{#1}}}{\toks1{#1}}%
\expandafter\special@car\@tempa\car@nil
\edef\x{{\the\toks@}{\the\toks1}}%
\expandafter\ifstrcmpTF\x{%
\edef\@tempd{\expandcsonce\@tempd\the\toks@}%
\toks@\cptthreexp{\expandafter\cpt@cdr\@tempa\cdr@nil}%
\edef\@tempa{\the\toks@}%
\ifcsemptyTF\@tempa{\cptthreexp\endgroup\expandafter\@tempb\@tempd}%
\@ifnextchars@a
}{%
\edef\@tempd{\expandcsonce\@tempd\the\toks1}%
\cptthreexp\endgroup\expandafter\@tempc\@tempd
}%
}

\begin{document}

{\tt
abc: \@ifnextchars{abc}{yes -> }{no -> } abc

\detokenize{{a}bc}: \@ifnextchars{{a}bc}{yes -> }{no -> } {a}bc

\detokenize{a{b}c}: \@ifnextchars{a{b}c}{yes -> }{no -> } a{b}c

abc: \@ifnextchars{abc}{yes -> }{no -> } {c}ba
}

\end{document}

-
This is a tangential comment, but I notice that you have basically a whole 'nother API based around macro programming inside catoptions. Have you looked into using expl3 or etoolbox before? The chances of running into problems using tested code is much lower. (Also, TeX gets slower the more control sequences there are, so defining many redundant commands tends towards inefficiency.) –  Will Robertson Feb 9 '11 at 2:48
@Will: The catoptions package provides an email address that can be used for forwarding comments to its author. I think that is where your comments on catoptions should go. I am sure the author will welcome your comments. –  Ahmed Musa Feb 9 '11 at 3:04
are you saying you're not the author of that package? But you're right this isn't the best place to discuss it. –  Will Robertson Feb 9 '11 at 3:27

Here is a variation on Joseph Wright's solution. \ifstrcmpTF compares strings of characters irrespective of their catcodes.

\makeatletter
\usepackage{catoptions}
\newcommand\@ifnextchars[3]{%
\def\@tempa{#1}\def\@tempb{#2}%
\def\@tempc{#3}\def\@tempd{}%
\@ifnextchars@a
}
\def\@ifnextchars@a#1{%
\toks@\cptthreexp{\expandafter\@car\@tempa\@nil}%
\expandafter\ifstrcmpTF\expandafter{\the\toks@}{#1}{%
\edef\@tempd{\expandcsonce\@tempd\the\toks@}%
\toks@\cptthreexp{\expandafter\@cdr\@tempa\@nil}%
\edef\@tempa{\the\toks@}%
\ifcsemptyTF\@tempa{\expandafter\@tempb\@tempd}%
\@ifnextchars@a
}{%
\edef\@tempd{\expandcsonce\@tempd#1}%
\expandafter\@tempc\@tempd
}%
}

\begin{document}
% Tests
\@ifnextchars{abc}{yes}{no} abc

\@ifnextchars{abc}{yes}{no} acb

\@ifnextchars{abc}{yes}{no} cba
\end{document}


There is an expandable form of \@ifnextchar, by Chervet Florent. Can we think of an expandable form of \@ifnextchars?

-
You know, I assume, that an expandable test for characters is more restricted than one using \futurelet (for example, try \@ifnextchars{abc}{yes}{no}{a}bc in your implementation). –  Joseph Wright Feb 8 '11 at 20:35
@Joseph: I can see your argument. Thanks. My code will wrongly yield true in this case. Only \futurelet can save the situation, but how often do we need to test abc against {a}bc? Well, I (like you) normally prefer to consider all bizarre possibilities. –  Ahmed Musa Feb 8 '11 at 21:36
As I'm on the LaTeX3 Team, these edge cases worry me (or at least, they worry Frank M., who then worries me!). I mention it because we had some discussion about xparse, and what was and was not possible for expandable 'look ahead' functions in TeX. The documentation for that package lists various limitations of an expandable look-ahead, including the fact that you can't do a definition with a terminal optional argument and have it work expandably. So for robustness it's necessary to stick with \futurelet-based code. –  Joseph Wright Feb 8 '11 at 21:42
@Joseph: You’re right! But I have found some good and robust applications for expandable \@ifnextchar; not for terminal optional arguments anyway. Already I am able to preserve outer braces in argument grabbing, which is the source of your trepidation about my solution. Do you have further concerns with that solution? –  Ahmed Musa Feb 8 '11 at 23:05
@Joseph: OK, \futurelet is inevitable in this game. But your solution fails for \@ifnextchars{{a}bc}{yes -> }{no -> } {a}bc. See my next rather acrobatic (but cheap) solution, which works in all cases I have tested. –  Ahmed Musa Feb 9 '11 at 2:20

xstring package provides some functions for string operation, \IfStrEqCase can be used for this purpose. Using pdfTeX (latex command is often pdfTeX), you can also use \pdfstrcmp for comparation. This does not work as \@ifnextchar, but may be helpful.

\documentclass{article}
\usepackage{xstring}
\begin{document}
\newcommand\test[1]{%
\IfStrEqCase{#1}{%
{a}{I got a'}
{bb}{I got bb'}
{ccc}{I got ccc'}}%
[I got unknown string]\par}

\test{a}     % I got a'
\test{bb}    % I got bb'
\test{ccc}   % I got ccc'
\test{other} % I got unknown string
\end{document}


Besides, it is difficult to generalize \@ifnextchar for alphabets. TeX cannot distinguish \foo + ab from a single command \fooab.

You can define several commands begin with \foo, but each one should ends with alphabets.

\documentclass{article}
\begin{document}
\makeatletter
\def\strlist{a,bb,ccc}
\@for\curstr:=\strlist\do{%
\long\expandafter\edef \csname test\curstr\endcsname
{I got \curstr'.\par}%
}
\makeatother

\testa   % I got a'
\testbb  % I got bb'
\testccc % I got ccc'

\end{document}


Using etextools, you can partially generalize \@ifnextchar to multiple charcter (non-alphabets or alphabets are allowed). This is the closest one.

\documentclass{article}
\usepackage{etextools}
\usepackage{xstring}

\begin{document}
\def\test{%
\futuredef[-+ab]\nexttokens{%
\IfStrEqCase{\nexttokens}{%
{-}{I got -'}
{-+}{I got -+'}
{+ab}{I got +ab'}}%
[I got unknown string]\par}}

\test-   % I got -'
\test-+  % I got -+'
\test+ab % I got +ab'
\test++ab  % I got unknown string
\test a  % I got unknown string

\end{document}

-