It is not possible to get a full solution expandably. The code below gives correct results as long as the escape character is printable (\escapechar between 0 and 255 inclusive). It relies on the fact that in that case, applying \string to our argument will give more than one character. In fact, we treat the case \escapechar=32 (space) separately, because TeX ignores spaces when grabbing an undelimited argument.
The weird-looking \noexpand at various places are needed to cater for \outer macros: it lets them to \relax for long enough to grab them in an argument. Note that #1 never appears in text that may be skipped in a conditional (because it may be \outer).
In the case where the escape character is unprintable, and \string#1 gives a single character, then we need some more investigation to distinguish usual cases (but the solution cannot be complete).
\makeatletter \newcommand{\ifcs}{\expandafter\ifcs@i\noexpand} \newcommand{\ifcs@T}[3]{#2} \newcommand{\ifcs@F}[3]{#3} % "normal" escapechar \newcommand{\ifcs@i}[1] {% \ifcat$\ifcat*\string#1\fi$% \expandafter \expandafter \expandafter \ifcs@test \else \expandafter \expandafter \expandafter \ifcs@T \fi \noexpand #1% }
We could be done here if we didn't care about special cases for \escapechar: just replace the end of the conditional by \expandafter \@secondoftwo \else \expandafter \@firstoftwo \fi (removing \noexpand #1 as well). But it's not too expensive to distinguish between various escapechars (I doubt that the test I give here is anywhere close to optimal).
\newcommand{\ifcs@test} {% \ifcase \expandafter\@gobble\string\2 % (space) \ifcat\@sptoken\string\1 \else 0 \fi \expandafter \ifcs@unprintable \or \expandafter \ifcs@space \else \expandafter \ifcs@F \fi } % \escapechar=32 \newcommand{\ifcs@space}[1] {% \unless\ifcat\@sptoken\string#1% \expandafter \expandafter \expandafter \ifcs@F \else \expandafter \expandafter \expandafter \ifcs@space@i \fi \noexpand #1% } \newcommand{\ifcs@space@i}[1]% \string#1 starts with space {% \ifcat$\romannumeral-`0\string#1$% \expandafter \@secondoftwo \else \expandafter \@firstoftwo \fi }
Case \escapechar < 0 or > 255. This I didn't test much, but it is not possible to provide a full solution, since for instance the control sequence \a, and an active a let to one another and let to a character are indistinguishable expandably. (Identical for \ifcat, \if, \ifx, \meaning, and \string)
\newcommand{\ifcs@unprintable}[1] {% \ifcat\relax\noexpand#1% \expandafter \expandafter \expandafter \ifcs@T \else \expandafter \expandafter \expandafter \ifcs@unprintable@i \fi \noexpand #1% } \newcommand{\ifcs@unprintable@i}[1] {% \expandafter\ifx\csname \string#1\endcsname#1% \expandafter \@firstoftwo % can be wrong \else \expandafter \@secondoftwo \fi } \def\test{\expandafter\test@\noexpand} \def\test@#1{\ifcs#1{\message{T}}{\message{F}} \outer\def\foo{} \escapechar=-1\relax \test\foo \test\a \test\ % \expandafter\test\csname \space a\endcsname \test a \test ^ \test $
\topskip? how about\normalbaselineskip? The texbook defines "control words" as catcode==0 followed by one or more catcode==11, ending just before the next catcode!=11 or eol.\letters) and control symbols (\'). I most likely wrongly remembered command instead of control because LaTeX calls them this way (e.g.\newcommand).\def,\relax,\empty,\',\topskip,\undefined,\normalbaselineskip? And false for\topskip=2pt,some text,\csname relax\endcsname,\relax(note trailing space),{\relax}?\undefinedas true or false?