Tail-recursion, i.e., macros that call themselves again, and a lot of exchanging of arguments beforehand, might do the trick. ;-)
If I get it right, \mxm
is to process a list of optional arguments whereof each optional argument itself holds a *
-separated list of optional arguments.
You did not specify what to do in case not all *
-separated lists of optional arguments contain the same amount of elements.
Therefore I took the freedom to implement things so that [NULL]
is provided wherever *
-separated elements are missing due to lists being of different length or arguments between *
being specified empty.
\documentclass{article}
\usepackage{xparse}
\makeatletter
%%=============================================================================
%% Little helpers:
%%.............................................................................
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% 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>}%
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\expandafter\z@\@secondoftwo}%
{\expandafter\z@\@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% \romannumeral\UD@ExtractFirstArgLoop{<argument>\UD@SelDOm}%
%% yields <argument>'s 1st undelimited argument.
%% <argument> must not be blank, i.e., must neither be empty nor consist
%% only of explicit character tokens of catcode 10 and charcode 32.
%%
%% \UD@SelDOm must not be defined in terms of \outer !
%%.............................................................................
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
{\expandafter\z@\@secondoftwo{}#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \mxm starts a loop for collecting an arbitrary amount of optional arguments.
%%.............................................................................
\NewDocumentCommand\mxm{}{\innermxm{}}%
%%-----------------------------------------------------------------------------
%% \innermxm checks if another optional argument is present.
%% If so it will be collected and \innermxm is called again.
%% Otherwise the loop for forming the calls to \x is started.
%%.............................................................................
\NewDocumentCommand\innermxm{m >{\SplitList{*}}o}{%
% #1 - list of brace-nested optional arguments collected so far.
% #2 - either the no-value-marker or the next optional argument:
\IfNoValueTF{#2}{%
% Start the loop for forming the calls to \x/for re-arranging things:
% \romannumeral is not really needed here, but while writing
% this I wanted the missing-number-error in case of messing up
% the tail-recursive \mxmloop's flipping-around/exchanging
% of arguments. ;-)
\romannumeral\mxmloop{\x}{}{}{#1}{}%
}{%
% Add the current optional argument to the list #1 and check if
% another optional argument is present...
\innermxm{#1{#2}}%
}%
}%
\newcommand\mxmloop[5]{%
%#1 - token-list produced so far forming current new call to \x
%#2 - new list of lists
%#3 - indicator if all elements of current list of lists were empty.
%#4 - current list of lists
%#5 - list of calls to \x
\UD@CheckWhetherNull{#4}{%
\UD@CheckWhetherNull{#3}{%
\z@#5% <- this \z@ terminates the (actually not needed)
% \romannumeral-expansion started by \innermxm.
% It denotes a non-positive number and therefore
% gets removed silently.
}{%
\mxmloop{\x}{}{}{#2}{#5#1}%
}%
}{%
\UD@PassFirstToSecond{#5}{%
\expandafter\UD@PassFirstToSecond\expandafter{\@firstoftwo{}#4}{%
\expandafter\UD@CheckWhetherNull\expandafter{\romannumeral\UD@ExtractFirstArgLoop{#4\UD@SelDOm}}{%
\UD@PassFirstToSecond{#3}{%
\UD@PassFirstToSecond{#2{}}{%
\UD@PassFirstToSecond{#1[NULL]}{\mxmloop}%
}%
}%
}{%
\UD@PassFirstToSecond{m}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\expandafter\@firstoftwo\expandafter{\expandafter}%
\romannumeral\UD@ExtractFirstArgLoop{#4\UD@SelDOm}}{\z@#2}%
}{%
\expandafter\UD@CheckWhetherNull\expandafter{%
\romannumeral\expandafter
\UD@ExtractFirstArgLoop\expandafter{%
\romannumeral\UD@ExtractFirstArgLoop{#4\UD@SelDOm}%
\UD@SelDOm}%
}{%
\UD@PassFirstToSecond{#1[NULL]}{\mxmloop}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\UD@Exchange\expandafter{%
\romannumeral\expandafter
\UD@ExtractFirstArgLoop\expandafter{%
\romannumeral\UD@ExtractFirstArgLoop{#4\UD@SelDOm}%
\UD@SelDOm}%
}{\z@#1}%
}{\mxmloop}%
}%
}%
}%
}%
}%
}%
}%
}%
%%=============================================================================
% Let's define \x to collect an arbitrary amount of optional arguments and to display them:
\NewDocumentCommand\x{}{\innerx{}}%
\NewDocumentCommand\innerx{mo}{%
\IfNoValueTF{#2}{\par\noindent\texttt{\detokenize\expandafter{\string\x#1}}}{\innerx{#1[#2]}}%
}%
\makeatother
\begin{document}
\footnotesize
\noindent Test 1:
\mxm
[ [3,2]*[-3, 0]*[ 1,-2] ]
[ [1,1]*[ 0,-2]*[-1, 3] ]
[ [3,5]*[ 6,-7]*[-8, 9] ]
\bigskip\hrule\bigskip
\noindent Test 2:
\mxm
[ [3,2]*[-3, 0] ]
[ [1,1]*[ 0,-2]*[-1, 3] ]
[ [3,5]*[ 6,-7]*[-8, 9] ]
\bigskip\hrule\bigskip
\noindent Test 3:
\mxm
[ [3,2]*[-3, 0]*[ 1,-2]*[2,8] ]
[ [1,1]*[ 0,-2]* * ]
[ [3,5]*[ 6,-7]*[-8, 9]* ]
\bigskip\hrule\bigskip
\noindent Test 4:
\mxm
[ [1,1]*[ 0,-2]]
[ [3,2]*[-3, 0]*[ 1,-2]*[2,8]*[17,4] ]
[ [3,5]*[ 6,-7]*[-8, 9]* ]
\bigskip\hrule\bigskip
\noindent Test 5:
\mxm
[ [3,2]*[-3, 0]*[ 1,-2]*[2,8] ]
[ ]
[ [3,5]*[ 6,-7]*[-8, 9]* ]
\bigskip\hrule\bigskip
\noindent Test 6:
\mxm
[ [3,2]*[-3, 0] ]
[ [1,1]*[ 0,-2] ]
[ [3,5]*[ 6,-7] ]
[ [2,7]*[ 7,-0] ]
[ [8,4]*[ 6,-0] ]
\bigskip\hrule\bigskip
\noindent Test 7:
\mxm
[ [3,2]*[-3, 0]*[1,1]*[ 0,-2]*[3,5]*[ 6,-7] ]
\bigskip\hrule\bigskip
\noindent Test 8:
\mxm
[ [3,2] ]
\bigskip\hrule\bigskip
\noindent Test 9:
\mxm
[ ]
\bigskip\hrule\bigskip
\noindent Test 10:
\mxm
\noindent bla
\end{document}

A variation of this can be used to create matrices:
\documentclass{article}
\usepackage{xparse}
\usepackage{amsmath}
\makeatletter
%%=============================================================================
%% Little helpers:
%%.............................................................................
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% 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>}%
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\expandafter\z@\@secondoftwo}%
{\expandafter\z@\@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% \romannumeral\UD@ExtractFirstArgLoop{<argument>\UD@SelDOm}%
%% yields <argument>'s 1st undlimited argument.
%% <argument> must not be blank, i.e., must neither be empty nor consist
%% only of explicit character tokens of catcode 10 and charcode 32.
%%
%% \UD@SelDOm must not be defined in terms of \outer !
%%.............................................................................
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
{\expandafter\z@\@secondoftwo{}#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \mxm starts a loop for collecting an arbitrary amount of optional arguments.
%%.............................................................................
\NewDocumentCommand\mxm{s}{%
\IfBooleanTF{#1}{\innermxm{\x*}{}}{\innermxm{\x}{}}%
}%
%%-----------------------------------------------------------------------------
%% \innermxm checks if another optional argument is present.
%% If so it will be collected and \innermxm is called again.
%% Otherwise the loop for forming the calls to \x is started.
%%.............................................................................
\NewDocumentCommand\innermxm{mm >{\SplitList{*}}o}{%
% #1 - command for creating matrices
% #2 - list of brace-nested optional arguments collected so far.
% #3 - either the no-value-marker or the next optional argument:
\IfNoValueTF{#3}{%
% Start the loop for forming the calls to \x/for re-arranging things:
% \romannumeral is not really needed here, but while writing
% this I wanted the missing-number-error in case of messing up
% the tail-recursive \mxmloop's flipping-around/exchanging
% of arguments. ;-)
\romannumeral\mxmloop{#1}{}{}{}{#2}{}%
}{%
% Add the current optional argument to the list #2 and check if
% another optional argument is present...
\innermxm{#1}{#2{#3}}%
}%
}%
\newcommand\mxmloop[6]{%
%#1 - command for creating matrices
%#2 - token-list produced so far forming current command for creating matrices
%#3 - new list of lists
%#4 - indicator if all elements of current list of lists were empty.
%#5 - current list of lists
%#6 - list of calls for creating matrices
\UD@CheckWhetherNull{#5}{%
\UD@CheckWhetherNull{#4}{%
\z@#6% <- this \z@ terminates the (actually not needed)
% \romannumeral-expansion started by \innermxm.
% It denotes a non-positive number and therefore
% gets removed silently.
}{%
\mxmloop{#1}{}{}{}{#3}{#6#1#2}%
}%
}{%
\UD@PassFirstToSecond{#6}{%
\expandafter\UD@PassFirstToSecond\expandafter{\@firstoftwo{}#5}{%
\expandafter\UD@CheckWhetherNull\expandafter{\romannumeral\UD@ExtractFirstArgLoop{#5\UD@SelDOm}}{%
\UD@PassFirstToSecond{#4}{%
\UD@PassFirstToSecond{#3{}}{%
\UD@PassFirstToSecond{#2%[NULL]
}{\mxmloop{#1}}%
}%
}%
}{%
\UD@PassFirstToSecond{m}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\expandafter\@firstoftwo\expandafter{\expandafter}%
\romannumeral\UD@ExtractFirstArgLoop{#5\UD@SelDOm}}{\z@#3}%
}{%
\expandafter\UD@CheckWhetherNull\expandafter{%
\romannumeral\expandafter
\UD@ExtractFirstArgLoop\expandafter{%
\romannumeral\UD@ExtractFirstArgLoop{#5\UD@SelDOm}%
\UD@SelDOm}%
}{%
\UD@PassFirstToSecond{#2%[NULL]
}{\mxmloop{#1}}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\UD@Exchange\expandafter{%
\romannumeral\expandafter
\UD@ExtractFirstArgLoop\expandafter{%
\romannumeral\UD@ExtractFirstArgLoop{#5\UD@SelDOm}%
\UD@SelDOm}%
}{\z@#2}%
}{\mxmloop{#1}}%
}%
}%
}%
}%
}%
}%
}%
}%
%%=============================================================================
% Let's define \x to collect an arbitrary amount of optional arguments and to create a matrix of them:
\NewDocumentCommand\x{s}{%
\IfBooleanTF{#1}{\innerx{}{}{pmatrix}}{\innerx{}{}{bmatrix}}%
}%
\NewDocumentCommand\innerx{mmm >{\SplitList{,}}o}{%
% #1 - matrix-content created so far
% #2 - things to prepend to matrix-row to create (empty with 1st row, \\ otherwise)
% #3 - name of matrix-environment
% #4 - optional argument from which next matrix-row is to be created
\IfNoValueTF{#4}%
{\UD@CheckWhetherNull{#1}{}{\begin{#3}#1\end{#3}}}%
{%
\expandafter\innerx
\expandafter{\romannumeral\expandafter\UD@Exchange\expandafter{\romannumeral\intersperseloop{#4}{}{}}{\z@#1#2}}%
{\\}%
{#3}%
}%
}%
\newcommand\intersperseloop[3]{%
%#1 - argument list
%#2 - interspersed list
%#3 - token to prepend (empty with 1st element, & otherwise)
\UD@CheckWhetherNull{#1}{\z@#2}{%
\UD@PassFirstToSecond{&}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\expandafter\UD@Exchange\expandafter{\romannumeral\UD@ExtractFirstArgLoop{#1\UD@SelDOm}}{\z@#2#3}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\@firstoftwo{}#1}{%
\intersperseloop
}%
}%
}%
}%
}%
\makeatother
\begin{document}
\footnotesize
\noindent Test 1:
$$\mxm
[ [3,2]*[-3, 0]*[ 1,-2] ]
[ [1,1]*[ 0,-2]*[-1, 3] ]
[ [3,5]*[ 6,-7]*[-8, 9] ]$$
\bigskip\hrule\bigskip
\noindent Test 2:
$$\mxm*
[ [3,2]*[-3, 0] ]
[ [1,1]*[ 0,-2]*[-1, 3] ]
[ [3,5]*[ 6,-7]*[-8, 9] ]$$
\bigskip\hrule\bigskip
\noindent Test 2a:
$$\mxm
[ [3,2]* *[-3, 0] ]
[ [1,1]*[ 0,-2]*[-1, 3] ]
[ [3,5]*[ 6,-7]*[-8, 9] ]$$
\bigskip\hrule\bigskip
\noindent Test 3:
$$\mxm
[ [3,2] * [-3, 0] * [ 1,-2] * [2,8] ]
[ [1,1] * [ 0,-2] * * ]
[ [3,5] * [ 6,-7] * [-8, 9] * ]$$
\bigskip\hrule\bigskip
\noindent Test 4:
$$\mxm
[ [1,1]*[ 0,-2]]
[ [3,2]*[-3, 0]*[ 1,-2]*[2,8]*[17,4] ]
[ [3,5]*[ 6,-7]*[-8, 9]* ]$$
\bigskip\hrule\bigskip
\noindent Test 5:
$$\mxm
[ [3,2]*[-3, 0]*[ 1,-2]*[2,8] ]
[ ]
[ [3,5]*[ 6,-7]*[-8, 9]* ]$$
\bigskip\hrule\bigskip
\noindent Test 6:
$$\mxm*
[ [3,2]*[-3, 0] ]
[ [1,1]*[ 0,-2] ]
[ [3,5]*[ 6,-7] ]
[ [2,7]*[ 7,-0] ]
[ [8,4]*[ 6,-0] ]
[ [3,2]*[-3, 0] ]
[ [1,1]*[ 0,-2] ]
[ [3,5]*[ 6,-7] ]
[ [2,7]*[ 7,-0] ]
[ [8,4]*[ 6,-0] ]
[ [3,2]*[-3, 0] ]
[ [1,1]*[ 0,-2] ]
[ [3,5]*[ 6,-7] ]
[ [2,7]*[ 7,-0] ]
[ [8,4]*[ 6,-0] ]$$
\bigskip\hrule\bigskip
\noindent Test 6b:
$$\mxm
[ [3,2] * [-3, 0] * ]
[ [1,1] * * [ 0,-2] ]
[ [3,5] * * [ 6,-7] ]
[ [2,7] * * [ 7,-0] ]
[ [8,4] * * [ 6,-0] ]
[ * * [ 6,-0] ]$$
\bigskip\hrule\bigskip
\noindent Test 7:
$$\mxm
[ [3,2]*[-3, 0]*[1,1]*[ 0,-2]*[3,5]*[ 6,-7] ]$$
\bigskip\hrule\bigskip
\noindent Test 8:
$$\mxm
[ [3,2] ]$$
\bigskip\hrule\bigskip
\noindent Test 9:
$$\mxm
[ ]$$
\bigskip\hrule\bigskip
\noindent Test 10:
$$\mxm$$
\noindent bla
\end{document}

\mxm
might have up to eight optional arguments? Some more words about the real aim would be helpful.\mxm
would have to take that many optional arguments so that it can provide them to\x
. I'll try to make that more clear. I don't often ask the questions so I appreciate the advice!