I can offer a macro
\SplitAtSquareBracketAndPassToMacro{⟨macro A⟩}{⟨macro B⟩}{⟨tokens⟩}
which works as follows:
If ⟨tokens⟩
doesn't contain any square bracket of category code 12(other) which is not nested in curly braces, then
⟨macro B⟩{⟨tokens⟩}
is delivered.
If ⟨tokens⟩
does contain at least one square bracket of category code 12(other) which is not nested in curly braces, then
⟨macro A⟩{⟨tokens before first square bracket⟩}{⟨square bracket⟩}{⟨tokens behind first square bracket⟩}
is delivered.
First in a \romannumeral
-expansion-driven tail-recursive loop it is tested whether the ⟨remaining tokens⟩
-argument
- is empty
- or has a leading
[12
or ]12
- or has a leading explicit space token.
If it is empty, then ⟨macro B⟩{⟨tokens⟩}
is delivered.
If it has a leading [12
or ]12
, then a macro processing a [12
-delimited argument respective ]12
-delimited argument is called for splitting ⟨tokens⟩
accordingly.
If it has a leading explicit space token, then that is removed and another iteration of the loop is done.
If neither is the case, then a non-delimited argument is removed and another iteration of the loop is done.
As ⟨macro A⟩
-argument you can pass \mymacro
to \SplitAtSquareBracketAndPassToMacro
.
\documentclass{article}
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo,
%% \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherLeadingTokens,
%% \UD@ExtractFirstArgLoop
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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\z@\UD@secondoftwo}%
{\expandafter\z@\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific
%% token-sequence that does neither contain explicit character tokens of
%% category code 1 or 2 nor contain tokens of category code 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%% {<a <token sequence> without explicit
%% character tokens of category code
%% 1 or 2 and without tokens of
%% category code 6>}%
%% {<internal token-check-macro>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked> has
%% <token sequence> as leading tokens>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked>
%% does not have <token sequence> as
%% leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[3]{%
\romannumeral\UD@CheckWhetherNull{#1}{\expandafter\z@\UD@secondoftwo}{%
\expandafter\UD@secondoftwo\string{\expandafter
\UD@@CheckWhetherLeadingTokens#3{\relax}#1#2}{}}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\z@\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \romannumeral\UD@ExtractFirstArgLoop{ABCDE\UD@SelDOm} yields {A}
%%
%% \romannumeral\UD@ExtractFirstArgLoop{{AB}CDE\UD@SelDOm} yields {AB}
%%.............................................................................
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\z@#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%% {<token sequence>}%
%% Defines <internal token-check-macro> to snap everything
%% until reaching <token sequence>-sequence and spit that out
%% nested in braces.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[2]{%
\@ifdefinable#1{\long\def#1##1#2{{##1}}}%
}%
%%=============================================================================
%% Supplementary macros for \SplitAtSquareBracketAndPassToMacro
%% and \SplitAtSquareBracketAndPassToMacro
%%=============================================================================
\UD@internaltokencheckdefiner{\UD@InternalExplicitSpaceCheckMacro}{ }%
\UD@internaltokencheckdefiner{\UD@InternalLeftSquaeBracketCheckMacro}{[}%
\UD@internaltokencheckdefiner{\UD@InternalRightSquaeBracketCheckMacro}{]}%
\@ifdefinable\UD@SplitAtLeftSquareBracket{%
\long\def\UD@SplitAtLeftSquareBracket#1[{\expandafter\z@\expandafter{\UD@firstoftwo{}#1}{[}}%
}%
\@ifdefinable\UD@SplitAtRightSquareBracket{%
\long\def\UD@SplitAtRightSquareBracket#1]{\expandafter\z@\expandafter{\UD@firstoftwo{}#1}{]}}%
}%
\newcommand\UD@SplitAtSquareBracket[3]{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\expandafter\expandafter\z@\expandafter\UD@firstoftwo\expandafter{\expandafter}%
\romannumeral
\expandafter\expandafter\expandafter\z@\expandafter\UD@firstoftwo\expandafter{\expandafter}%
\romannumeral#3#1%
}{%
\expandafter\UD@PassFirstToSecond
\romannumeral\expandafter\expandafter\expandafter\UD@ExtractFirstArgLoop
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo\expandafter{\expandafter}%
\romannumeral#3#1\UD@SelDOm}{%
\expandafter\UD@PassFirstToSecond
\romannumeral\expandafter\UD@ExtractFirstArgLoop\expandafter{%
\romannumeral#3#1\UD@SelDOm}{%
\z@#2%
}%
}%
}%
}%
\newcommand\SplitAtSquareBracketAndPassToMacro[3]{%
\romannumeral\UD@SplitAtSquareBracketAndPassToMacroLoop{#3}{#3}{#1}{#2}%
}%
\newcommand\UD@SplitAtSquareBracketAndPassToMacroLoop[4]{%
% #1 = <remaining tokens>
% #2 = <tokens>
% #3 = <macro A>
% #4 = <macro B>
\UD@CheckWhetherNull{#1}{\z@#4{#2}}{%
\UD@CheckWhetherLeadingTokens{#1}{ }{\UD@InternalExplicitSpaceCheckMacro}{%
\expandafter\UD@SplitAtSquareBracketAndPassToMacroLoop\expandafter{\UD@removespace#1}{#2}{#3}{#4}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{[}{\UD@InternalLeftSquaeBracketCheckMacro}{%
\UD@SplitAtSquareBracket{.#2}{#3}{\UD@SplitAtLeftSquareBracket}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{]}{\UD@InternalRightSquaeBracketCheckMacro}{%
\UD@SplitAtSquareBracket{.#2}{#3}{\UD@SplitAtRightSquareBracket}%
}{%
\expandafter\UD@SplitAtSquareBracketAndPassToMacroLoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#4}%
}%
}%
}%
}%
}%
\makeatother
%%=============================================================================
%% \mymacro{<tokens 1>}{<tokens 2>}{<tokens 3>}
%% Whan arguments are passed to \mymacro from
%% \SplitAtSquareBracketAndPassToMacro, then
%% - <tokens 1> is the things before the first [ respective ] .
%% - <tokens 2> is either [ or ] .
%% - <tokens 3> is the things behind the first [ respective ] .
%%=============================================================================
\newcommand\mymacro[3]{%
\noindent
\scantokens\expandafter\expandafter\expandafter{%
\expandafter\string\expandafter\verb\expandafter|\string\mymacro|:%
}%
Argument 1 is: \scantokens\expandafter{\string\verb|#1|.}%
Argument 2 is: \scantokens\expandafter{\string\verb|#2|.}%
Argument 3 is: \scantokens\expandafter{\string\verb|#3|.}%
}%
%%=============================================================================
%% macro in case there was no square bracket
%%=============================================================================
\newcommand\nosquarebracketsmacro[1]{%
\noindent
\scantokens\expandafter\expandafter\expandafter{%
\expandafter\string\expandafter\verb\expandafter|%
\string\nosquarebracketsmacro|:%
}%
Argument is: \scantokens\expandafter{\string\verb|#1|.}%
}%
\parindent=0ex
\begin{document}
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB]CDE}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB]CDE}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB[CDE}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB[CDE}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{ABCDE}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{ABCDE}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}]CDE}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}]CDE}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}[CDE}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}[CDE}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}CDE}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}CDE}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB]{CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB]{CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB[{CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB[{CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB{CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB{CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}]{CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}]{CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}[{CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}[{CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}{CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB}{CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB]CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB]CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB[CDE}}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{AB[CDE}}
\vfill
\verb|\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{AB]CDE}|:\\
\SplitAtSquareBracketAndPassToMacro{\mymacro}{\nosquarebracketsmacro}{{ABCDE}}
\end{document}

As far as the task/macro-writing-challenge in the question is concerned, I admittedly cheated a bit:
By defining the macro \SplitAtSquareBracketAndPassToMacro
in such a way that the tokens to be split up are passed to it as a macro argument, I have dodged the need to "fish" these tokens out of the token stream directly.
I did this because tokens can be "fished out" of the token stream in principle only as macro arguments.
For example, undelimited macro arguments consisting of several tokens are nested in a token pair consisting of an explicit character token of category code 1 (begin group) and an explicit character token of category code 2 (end group). This pair of tokens forms the so-called argument-group.
Usually, argument-groups are formed by the character-tokens {1
and }2
, but it is not excluded that for some obscure reason the category code régime is different and other characters of these category-codes are in use.
I don't know of any reliable method to fish undelimited macro arguments out of the token stream in a way where the braces or category-code-1- and -2-character-tokens forming the argument-group in which the undelimited arguments are nested are preserved exactly instead of being replaced by some "hard-coded" pair of explicit character tokens of category code 1/2.
You can have TeX "look" via \futurelet
and \ifcat
for the category code of the next token in the token stream. But firstly a category-code-1-character-token denoting the begin of the argument-group is not fished out from just "looking", and secondly the category-code-2-character-token denoting the end of the argument-group is not the next token in the token stream...
..]..[..
or..[..]..
or could you have cases with just one? – David Carlisle Nov 30 '20 at 22:33\ifnextchar[
– John Kormylo Dec 1 '20 at 0:16\def
) statement? – Joseph Wright♦ Dec 1 '20 at 12:02\mymacro bla{[bla bla]}bla[ lorem ipsum
or\mymacro bla{]bla bla]}bla] lorem ipsum
? I.e., what about square brackets nested inside curly-brace-groups ? – Ulrich Diez Dec 1 '20 at 15:18