3

I am trying to define a macro \@ifnextchars with three arguments, the first argument being a list of characters, which expands to either the second or the third argument depending on whether one of the character of the first argument follows the command. Here follows a minimalistic example of what I have done and how I would like to use it.

\documentclass{article}

\makeatletter
\newcommand\@ifnextchars[3]{%
    \expandafter\ifx\expandafter\@empty#1\@empty%
        \def\@ifnextchars@tmp{#3}%
    \else%
        \def\@ifnextchars@tmp{%
            \expandafter\expandafter\expandafter\@ifnextchars@aux\expandafter%
                {\@car#1\@nil}{\@cdr#1\@nil}{#2}{#3}%
        }%
    \fi%
    \@ifnextchars@tmp%
}
\newcommand\@ifnextchars@aux[4]{%
    \expandafter\@ifnextchar#1{%
        #3%
    }{%
        \@ifnextchars{#2}{#3}{#4}%
    }%
}

\newcommand\whereinsentence{%
    \@ifnextchars{.!?}{%
        I am in the end of a sentence.
    }{%
        I am not in the end of a sentence.
    }%
}
\newcommand\vowel{%
    \@ifnextchars{aeiouy}{%
        Vowel:
    }{%
        Not vowel:
    }%
}
\makeatother

\begin{document}

Here is \whereinsentence a sentence\whereinsentence.

\vowel a.

\vowel b.

\end{document}

Unfortunately, it doesn’t compile:

Runaway argument?
{I am in the end of a sentence. }{\@ifnextchars {\@cdr \@cdr .!?\@nil \ETC.
! Paragraph ended before \@cdr was complete.
<to be read again> 
                   \par 
l.44 

My guess is that \@cdr didn’t expand when needed. I find this strange because I made sure to expand my \@cdr with \expandafter… So I guess that I do not understand well how \expandafter works ☹ Can someone help me?

I was inspired by this post: Generalize \@ifnextchar to consider more than one character. However, this last post was about creating a variant of \@ifnextchar looking for entire word. I am just interested in knowing whether one of the given characters are present directly afterwards.

I have made this \ifx yielding a \def to make sure that the \@ifnextchar does not react with the \fi. I do not find this very satisfying: if anyone has a better solution, I am welcoming it with pleasure ☺

Thanks in advance.

  • You probably want to look at \@addpunct defined in amsthm.sty. You're expanding \@car, but not \@cdr. – egreg Jul 23 '16 at 19:56
  • Thanks @egreg ☺ The example I gave was more a minimal example than what I really wanted to do, so \@addpunct is of few aid for what I am trying to do. But thanks ☺ – Martin Bodin Jul 25 '16 at 07:49

2 Answers2

5

You cannot expand the second argument like that. Here's a simple way with the help of eTeX:

\documentclass{article}

\makeatletter
\newcommand\@ifnextchars[3]{%
  \if\relax\detokenize{#1}\relax
    \def\@ifnextchars@tmp{#3}%
  \else
    \edef\@ifnextchars@tmp{%
      \noexpand\@ifnextchars@aux
        {\unexpanded\expandafter{\@car#1\@nil}}
        {\unexpanded\expandafter{\@cdr#1\@nil}}
        \unexpanded{{#2}{#3}}%
    }%
  \fi
  \@ifnextchars@tmp
}
\newcommand\@ifnextchars@aux[4]{%
  \@ifnextchar{#1}{%
    #3%
  }{%
    \@ifnextchars{#2}{#3}{#4}%
  }%
}

\newcommand\whereinsentence{%
  \@ifnextchars{.!?}{%
    I am in the end of a sentence.
  }{%
    I am not in the end of a sentence.
  }%
}
\newcommand\vowel{%
  \@ifnextchars{aeiouy}{%
    Vowel:
  }{%
    Not vowel:
  }%
}
\makeatother

\begin{document}

Here is \whereinsentence a sentence\whereinsentence.

\vowel a.

\vowel b.

\end{document}
Manuel
  • 27,118
  • 1
    To be safe, you should define the command with \protected\def\@ifnextchars#1#2#3 or with \newrobustcmd from etoolbox package, or just go with \DeclareRobustCommand, but I would go for the first two options. – Manuel Jul 23 '16 at 19:55
2

The main problem is that you are expanding \@car, but not \@cdr, because the chain of \expandafters doesn't reach it.

Here's an implementation in expl3 that you may enjoy studying.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\cs_new_protected:Nn \bodin_ifnext_chars:nnnn
 {
  \tl_if_in:nnTF { #1 } { #2 } { #3 } { #4 } #2
 }

\NewDocumentCommand{\ifnextchars}{mmmm}
 {
  \bodin_ifnext_chars:nnnn { #1 } { #4 } { #2 } { #3 }
 }
\ExplSyntaxOff

\newcommand\whereinsentence{%
    \ifnextchars{.!?}{%
        ``I am in the end of a sentence.''
    }{%
        ``I am not in the end of a sentence.''
    }%
}
\newcommand\vowel{%
    \ifnextchars{aeiouy}{%
        Vowel:
    }{%
        Not vowel:
    }%
}

\begin{document}

Here is \whereinsentence a sentence\whereinsentence.

\vowel a.

\vowel b.

\end{document}
egreg
  • 1,121,712
  • I am afraid that I don’t understand something about the semantics of \expandafter, then :-
    I thought that \expandafter\expandafter\expandafter\a expandafter\b\c first unfolds \c, then \b, then \a. Is because of the curly braces that it breaks?

    I was not aware about expl3. When I will have time, I shall definitely look for it ☺ Thanks!

    – Martin Bodin Jul 25 '16 at 07:53
  • @MartinBodin \expandafter eventually acts on a single token, not on a braced group. – egreg Jul 25 '16 at 07:54
  • Oh. I see. Then I guess that it would work if I make local definitions of the @car and @cdr. Is it right? I am going to test as soon as I can. Thanks ☺ – Martin Bodin Jul 25 '16 at 12:46