Here is a variant of the code of Bruno Le Floch which does without brace-hacking:
The result is delivered by triggering two expansion-steps on \firstofmany:
\begingroup
\catcode`@=11
\long\gdef\firstofmany#1{\unexpanded\expandafter\fom@test\expandafter{\fom@grab #1{}abc}}
\long\gdef\fom@test#1{%
\ifcat$\detokenize\expandafter{\fom@gobble#1}$%
\expandafter\fom@i
\else
\expandafter\fom@ii
\fi
{#1}% #1 is first item wrapped in braces and after \unexpanded-driven
% expansion forms <left brace>, <balanced text> and <right brace>
% of \unexpanded's <general text>
{\expandafter\fom@test\expandafter{\fom@grab #1}}%
}
\long\gdef\fom@grab#1#2abc{{#1}}
\long\gdef\fom@gobble#1{}
\long\gdef\fom@i#1#2{#1}
\long\gdef\fom@ii#1#2{#2}
\endgroup
\long\def\test#1%
{\message{|\unexpanded\expandafter\expandafter\expandafter{#1}|}}
\test{\firstofmany{ a bc}}
\test{\firstofmany{ {a\a} bc}}
\test{\firstofmany{ {a\a} abc abc abc}}
\test{\firstofmany{ }}
\test{\firstofmany{ { }Where is the space gone?}}
\csname stop\endcsname
\csname bye\endcsname
\endinput
Here is another variant—as delimiter instead of abc a frozen-\relax is used (frozen-\relax neither can be affected by \uppercase/\lowercase nor can be (re)defined in terms of \outer) and expansion is driven by \romannumeral instead of \unexpanded's expansion while scanning for the ⟨left brace⟩ of \unexpanded's ⟨general text⟩.
As the loop is driven by \romannumeral-expansion, you obtain the result by triggering two expansion-steps.
You can, e.g., do
\unexpanded\expandafter\expandafter\expandafter{\ExtractFirstArg{{first}more stuff}}
for obtaining something like \unexpanded{first}.
As with the code of Bruno Le Floch the gist of the mechanism is:
Append a sequence {}⟨Delimiter⟩ to the argument passed by the user, then recursively apply a "removal-macro" with parameter text #1#2⟨Delimiter⟩ which delivers {#1} until only a single non-delimited argument is left.
(In the example below, the removal-macro is called \UD@RemoveTillFrozenrelax and the ⟨Delimiter⟩ is a frozen-\relax.)
( {} before ⟨Delimiter⟩ for ensuring that the removal-macro finds a non-delimited argument even in case of the argument passed by the user being empty or blank (blank = consisting of explicit space tokens only). )
If the user passes, e.g., the sequence
{1}{2}{3}, then after appending you have the sequence
{1}{2}{3}{}⟨Delimiter⟩.
Applying the removal-macro with its first non-delimited and its second delimited argument yields that the removal macro's first argument will be 1 and the removal-macro's second argument will be {2}{3}{}. When the removal-macro delivers its first argument wrapped in curly braces, you have the sequence {1} - the condition of there being only a single non-delimited argument is now fulfilled.
If the user passes, e.g., the sequence
{1}{2}{3}⟨Delimiter⟩{4}, then after appending you have the sequence
{1}{2}{3}⟨Delimiter⟩{4}{}⟨Delimiter⟩.
Applying the removal-macro with its first non-delimited and its second delimited argument yields that the removal-macro's first argument will be 1 and the removal-macro's second argument will be {2}{3}. When the removal-macro delivers its first argument wrapped in curly braces, you have the sequence {1}{4}{}⟨Delimiter⟩ - the condition of there being only a single non-delimited argument is not fulfilled yet, thus do another iteration with the removal-macro:
Applying the removal-macro with its first non-delimited and its second delimited argument yields that the removal-macro's first argument will be 1 and the removal-macro's second argument will be {4}{}. When the removal-macro delivers its first argument wrapped in curly braces, you have the sequence {1} - the condition of there being only a single non-delimited argument is now fulfilled.
If the user passes an empty argument, then after appending you have the sequence
{}⟨Delimiter⟩.
Applying the removal-macro with its first non-delimited and its second delimited argument yields that {} will be taken for the removal-macro's first argument and therefore the removal-macro's first argument will be empty. The removal-macro's second argument will be empty, too, because there are no other tokens before ⟨Delimiter⟩. When the removal-macro delivers its first argument wrapped in curly braces, you have the sequence {} - the condition of there being only a single non-delimited argument is now fulfilled.
As this mechanism is based on macros it cannot be applied for accessing the first item of a token-list that contains tokens defined in terms of \outer.
This mechanism extracts single/unbalanced \if/\else/\fi in case such things form the first item of the token-list in question.
Every user-provided argument is wrapped between curly braces while the expansion-cascade is running, thus this mechanism should also work out when extracting & and the like inside alignments/tabular-environments etc.
Using this mechanism, you cannot distinguish by "looking" at the result of \ExtractFirstArg the case of the argument passed by the user being empty or blank (blank = consisting of explicit space-tokens only) from the case of the argument passed by the user having a first component which is an empty argument.
I.e., the results of \ExtractFirstArg{} and \ExtractFirstArg{ } will be the same as the result of, e.g., \ExtractFirstArg{{}More stuff}: In all these cases \ExtractFirstArg will not return any token at all.
But you can easily distinguish these cases by checking the emptiness/blankness of the argument passed by the user before applying \ExtractFirstArg to it.
For checking the emptiness of an argument you can, e.g., use the \UD@CheckWhetherNull-macro of the example below.
For checking the "emptiness or blankness" of an argument you can, e.g., use the same macro when doing something which takes into account that TeX discards space-tokens that precede non-delimited arguments—just do something like:
\expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo#1{}.}%
{<#1 is empty or blank (blank = consists only of explicit space-tokens).>}%
{<#1 is not empty and not blank (but consists of something else than only explicit space-tokens).>}%
For checking the "blankness" put the pieces together:
\UD@CheckWhetherNull{#1}{%
<#1 is not blank (but empty).>
}{%
\expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo#1{}.}%
{<#1 is blank (blank = consists only of explicit space-tokens).>}%
{<#1 is not blank and not empty (and consists of something else than only explicit space-tokens).>}%
}%
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@PassFirstToSecond,
%% \UD@stopromannumeral, \UD@CheckWhetherNull, \UD@ExtractFirstArg,
%% \UD@TrimLeadingTokens, \UD@TrimTrailingTokens
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% 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\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \ExtractFirstArg{ABCDE} yields {A}
%%
%% \ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps/after "hitting" \ExtractFirstArg with \expandafter
%% twice.
%%
%% \ExtractFirstArg's argument must not be blank.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
{\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\expandafter
\UD@PassFirstToSecond\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1{}}%
}{%
\UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
}%
}{%
\newcommand\ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\expandafter\UD@firstoftwo\expandafter{\expandafter\UD@stopromannumeral\UD@secondoftwo{}#1}{}}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
\makeatother
\documentclass{article}
\parindent=0ex
\parskip=.5\baselineskip
\topsep=0ex
\partopsep=0ex
\pagestyle{empty}
\makeatletter
% A loop for replacing \FOO by frozen-\relax:
@ifdefinable\ReplaceFOOByFrozenRelax{%
\long\def\ReplaceFOOByFrozenRelax#1#{%
\romannumeral@ReplaceFOOByFrozenRelax{#1}%
}%
}%
\newcommand@ReplaceFOOByFrozenRelax[2]{%
\ReplaceFOOByFrozenRelaxLoop{#2}{#1}%
}%
\newcommand\ReplaceFOOByFrozenRelaxLoop[2]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletoFOO#1\FOO}{%
\UD@stopromannumeral#2{#1}%
}{%
\expandafter\expandafter\expandafter\ReplaceFOOByFrozenRelaxLoop
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo\expandafter{\expandafter}\UD@replaceFOO{{}}#1%
}{#2}%
}%
}%
@ifdefinable\UD@gobbletoFOO{\long\def\UD@gobbletoFOO#1\FOO{}}%
\begingroup
\def\UD@replaceFOO#1{%
\endgroup
@ifdefinable\UD@replaceFOO{\long\def\UD@replaceFOO##1\FOO{##1#1}}%
}%
\expandafter\expandafter\expandafter\UD@replaceFOO
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
\makeatother
\begin{document}
\enlargethispage{2in}%
\null\kern-1.5in
Test 1: \verb|\ExtractFirstArg{ABCDE}|
yields:
\ExtractFirstArg{ABCDE}
Test 2: \verb|\ExtractFirstArg{{AB}CDE}|
yields:
\ExtractFirstArg{{AB}CDE}
The argument can contain frozen-\verb|\relax| although it is used as delimiter in some place,
this just increases the amount of iterations:
Test 3:
\begin{verbatim}
\ReplaceFOOByFrozenRelax\ExtractFirstArg{AB\FOO C\FOO DE}
\end{verbatim}
yields:
\ReplaceFOOByFrozenRelax\ExtractFirstArg{AB\FOO C\FOO DE}
Test 4:
\begin{verbatim}
\ReplaceFOOByFrozenRelax\ExtractFirstArg{{AB}\FOO C\FOO DE}
\end{verbatim}
yields:
\ReplaceFOOByFrozenRelax\ExtractFirstArg{{AB}\FOO C\FOO DE}
The result is delivered by triggering two expansion-steps:
Test 5:
\begin{verbatim}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\ExtractFirstArg{ABCDE}%
}
\end{verbatim}
yields:
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\ExtractFirstArg{ABCDE}%
}
\texttt{\string\temp: \meaning\temp}%
Test 6:
\begin{verbatim}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\ExtractFirstArg{{AB}CDE}%
}
\end{verbatim}
yields:
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\ExtractFirstArg{{AB}CDE}%
}
\texttt{\string\temp: \meaning\temp}%
\makeatletter
Test 7:
\begin{verbatim}
\expandafter\def\expandafter\temp\expandafter{\romannumeral
\ReplaceFOOByFrozenRelax\expandafter\expandafter\expandafter\UD@stopromannumeral
\ExtractFirstArg{{AB}C\FOO D\FOO E}%
}
\end{verbatim}
yields:
\expandafter\def\expandafter\temp\expandafter{\romannumeral
\ReplaceFOOByFrozenRelax\expandafter\expandafter\expandafter\UD@stopromannumeral
\ExtractFirstArg{{AB}C\FOO D\FOO E}%
}
\texttt{\string\temp: \meaning\temp}%
Test 8:
\begin{verbatim}
\expandafter\def\expandafter\temp\expandafter{\romannumeral
\ReplaceFOOByFrozenRelax\expandafter\expandafter\expandafter\UD@stopromannumeral
\ExtractFirstArg{\FOO{AB}C\FOO D\FOO E}%
}
\end{verbatim}
yields:
\expandafter\def\expandafter\temp\expandafter{\romannumeral
\ReplaceFOOByFrozenRelax\expandafter\expandafter\expandafter\UD@stopromannumeral
\ExtractFirstArg{\FOO{AB}C\FOO D\FOO E}%
}
\texttt{\string\temp: \meaning\temp}%
\makeatother
Test 9:
\begin{verbatim}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\ExtractFirstArg{}%
}
\end{verbatim}
yields:
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\ExtractFirstArg{}%
}
\texttt{\string\temp: \meaning\temp}%
\end{document}

l3checkfor LaTeX3),\pdfstrcmpis available, so this works. I'll probably make that a bit faster by first removing stuff until a marker (in most cases, that's enough), then applying your technique. It would have been nice to get a pure TeX or eTeX solution, because the LuaTeX\pdf@strcmp(provided by Heiko) is 10x slower than pdfTeX's. – Bruno Le Floch Sep 03 '12 at 01:23\elsein the solution playing any role? – Ahmed Musa Sep 03 '12 at 04:06\elsehas no purpose here, you can omit it. – Heiko Oberdiek Sep 03 '12 at 05:02\@gobbletoand\@gobbletwoneed\longto support\parinside the arbitrary token list. – Heiko Oberdiek Sep 03 '12 at 05:04\elsehad the purpose of me not having finished designing the code by the time I started the\if:-) – David Carlisle Sep 03 '12 at 09:12\romannumeral-based expansion (LaTeX3 'f-type' expansion), which I can't find a way to do with Sašo Živanović's or Ryan Reich's solutions. On the other hand, it's slower as there is a loop involved. – Joseph Wright Sep 04 '12 at 08:31