Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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.

share|improve this question
    
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
add comment

4 Answers

up vote 7 down vote accepted

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.

share|improve this answer
    
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
add comment
\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} 
share|improve this answer
    
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
add comment

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?

share|improve this answer
    
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
1  
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
1  
@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
add comment

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}
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.