# Calling a command with a generated optional argument

I'm working on a macro that should save me a lot of time in the future. I've currently got one of the commands working that I wanted but I am having trouble with getting the second part to work. That first command is defined as follows:

\NewDocumentCommand\x
{
s
>{\SplitList{,}}o
>{\SplitList{,}}o
>{\SplitList{,}}o
>{\SplitList{,}}o
>{\SplitList{,}}o
>{\SplitList{,}}o
>{\SplitList{,}}o
>{\SplitList{,}}o
}{
% Full definition of the command
}


This command takes eight optional arguments, each one is a comma-separated list, usually numbers though that is not required. I might call it like \x[-3,0][0,-2] or \x[1,2,3,0][4,5,6,0][7,8,9,0]. Each list will always be the same length.

I am trying to create a second command which acts a bit like a transpose. So it takes up to eight lists as well but these lists are separated with stars and the elements are lists from the \x command. It's easiest to show with an example how it works:

\mxm
[ [3,2]*[-3, 0]*[ 1,-2] ]
[ [1,1]*[ 0,-2]*[-1, 3] ]


would be equivalent to:

\x[3,2][1,1]
\x[-3,0][0,-2]
\x[1,-2][-1,3]


Each of these star lists would have the same number of elements. If they had different numbers of elements or elements that were not arguments of the \x command then anything could happen, an error, insert some kind of null element, truncate to the shortest list, etc. It won't be called that way so it doesn’t matter.

The length of these lists is how many \x calls there are. In this example, the length is three so there are three calls to \x but if the length was longer or shorter there would be a different number. The number of lists is how many arguments there are to each call of \x. In this example, there are two but there could be up to 8.

If possible, I would like to be able to pass on a star from \mxm* to \x* but if that is too difficult then I have an alternate solution since it shouldn't come up very often. Here's what that would look like:

\mxm[[1,3,1]*[-1, 0,3]]
[[2,2,1]*[ 0,-2,2]]
[[3,1,2]*[ 1,-2,1]]


would be equivalent to:

\x*[1,3,1]
[2,2,1]
[3,1,2]
\x*[-1, 0,3]
[ 0,-2,2]
[ 1,-2,1]


Here's an MWE with an example \x (just a placeholder, may not be called \x). It also includes how far I got though I can't imagine it is much help.

• Am I right in thinking that also \mxm might have up to eight optional arguments? Some more words about the real aim would be helpful. – egreg Nov 10 '20 at 17:09
• Yes, \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! – Bryce Nov 10 '20 at 20:59

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}


• This is definitely the closest to what I was trying to do though clearly, I couldn't have figured this out on my own. Now I need to figure out how this works so that I can do similar things in the future. Having the * separated lists be different lengths was undefined behaviour so I replacing them will NULL is a good solution. Thank you so much for your help! – Bryce Nov 10 '20 at 21:14
• Thanks for letting me know! I thought three answers were probably good but I can un-accept it for now. I don't ask a lot of questions here so I don't fully know the etiquette. – Bryce Nov 10 '20 at 21:16
• @Bryce Feel free to specify as precisely as possible what you need. The gist of how this works is: \mxmloop -> \PassFirstToSecond{#5}{\PassFirstToSecond{#4}{\PassFirstToSecond{#3}{\PassFirstToSecond{#2}{\PassFirstToSecond{#1}{\mxmloop}}}}}, but with \expandafter-trickery inserted for modifying #5 / #4 / #3 / #2 / #1 before carrying out \mxmloop again. ... – Ulrich Diez Nov 10 '20 at 21:18
• @Bryce ... \PassFirstToSecond "throws" its first argument (nested in curly braces) behind everything else. You can go at the first argument for modifying it before "throwing" it behind everything else via \expandafter\PassFirstToSecond\expandafter{... . You can use \romannumeral for keeping expansion (and thus flipping around arguments) going by ensuring that in the end \romannumeral finds a non-positive number which is silently "eaten" without delivering tokens in return. – Ulrich Diez Nov 10 '20 at 21:24
• @Bryce ...Conclusion:... You can (ab?)use \romannumeral for triggering a lot of expansion- and macro-argument-flipping/modification-work as long as in the end the leading tokens of the resulting token-sequence form a <number>-quantity with a non-positive value that will be eaten silently. – Ulrich Diez Nov 10 '20 at 22:03

If you want to create a \mxm:

\mxm
[ [3,2]*[-3, 0]*[ 1,-2] ]
[ [1,1]*[ 0,-2]*[-1, 3] ]


to do

\x[3,2][1,1]
\x[-3,0][0,-2]
\x[1,-2][-1,3]


then you can define

\def\mxm [ [#1]*[#2]*[#3] ] [ [#4]*[#5]*[#6] ]{%
\x[#1][#4]
\x[#2][#5]
\x[#3][#6]
}


Edit: If I understand your demand, then you need to transpose the parameters. When the \mxm is given with arbitrary rows and columns, then you need to convert the

\mxm
[ [A1]*[A2]*[A3]*[A4] ]
[ [B1]*[B2]*[B3]*[B4] ]
[ [C1]*[C2]*[C3]*[C4] ]


into a series of calls:

\x[A1][B1][C1]
\x[A2][B2][C2]
\x[A3][B3][C3]
\x[A4][B4][C4]


You can test the following code:

\newcount\numrows \newcount\tmpnum
\def\mxm {\numrows=0 \mxmA}
\def\mxmA [ #1 ] {\advance\numrows by1 \sdef{r:\the\numrows}{#1}%
\futurelet\next\mxmB}
\def\mxmB {\ifx\next[\expandafter\mxmA \else \expandafter \mxmC\fi}
\def\mxmC {\expandafter\ifx \csname r:1\endcsname \empty \else
\tmpnum=0 \def\xparams{}%
\loop
\expandafter \expandafter \expandafter \mxmD
\csname r:\the\tmpnum\endcsname \end
\ifnum\tmpnum<\numrows \repeat
\expandafter \x \xparams \relax
\expandafter \mxmC \fi
}

\def\sdef#1{\expandafter\def\csname#1\endcsname}

% Just for testing:
\def\x #1\relax{\message{running \string\x #1}}

\mxm
[ [A1]*[A2]*[A3]*[A4] ]
[ [B1]*[B2]*[B3]*[B4] ]
[ [C1]*[C2]*[C3]*[C4] ]

\bye


The message result of this code (after \pdftex testfile) is:

running \x[A1][B1][C1] running \x[A2][B2][C2]
running \x[A3][B3][C3] running \x[A4][B4][C4]


I don't think this is a good approach, particularly in the eight optional arguments. But the customer's always right.

\documentclass{article}
\usepackage{amsmath}

\ExplSyntaxOn

\NewDocumentCommand\makematrix{ s o o o o o o o o }
{
\begin{ \IfBooleanTF { #1 } { p } { b } matrix }
\bryce_make_matrix:nnnnnnnn { #2 } { #3 } { #4 } { #5 } { #6 } { #7 } { #8 } { #9 }
\end{ \IfBooleanTF { #1 } { p } { b } matrix }
}
\NewDocumentCommand{\mxm} { s o o o o o o o o }
{
\group_begin: % localize the setting of the sequences
\IfValueT{#2}
{
\seq_set_split:Nnn \l__bryce_mxm_i_seq { * } { #2 }
}
\IfValueT{#3}
{
\seq_set_split:Nnn \l__bryce_mxm_ii_seq { * } { #3 }
}
\IfValueT{#4}
{
\seq_set_split:Nnn \l__bryce_mxm_iii_seq { * } { #4 }
}
\IfValueT{#5}
{
\seq_set_split:Nnn \l__bryce_mxm_iv_seq { * } { #5 }
}
\IfValueT{#6}
{
\seq_set_split:Nnn \l__bryce_mxm_v_seq { * } { #6 }
}
\IfValueT{#7}
{
\seq_set_split:Nnn \l__bryce_mxm_vi_seq { * } { #7 }
}
\IfValueT{#8}
{
\seq_set_split:Nnn \l__bryce_mxm_vii_seq { * } { #8 }
}
\IfValueT{#9}
{
\seq_set_split:Nnn \l__bryce_mxm_viii_seq { * } { #9 }
}
\cs_set_protected:Nx \__bryce_mxm:
{
\makematrix \IfBooleanT { #1 } { * }
}
\__bryce_mxm_do:
\group_end:
}

\cs_new_protected:Nn \bryce_make_matrix:nnnnnnnn
{
\tl_if_novalue:nF { #1 } { \__bryce_make_row:n { #1 } }
\tl_if_novalue:nF { #2 } { \__bryce_make_row:n { #2 } }
\tl_if_novalue:nF { #3 } { \__bryce_make_row:n { #3 } }
\tl_if_novalue:nF { #4 } { \__bryce_make_row:n { #4 } }
\tl_if_novalue:nF { #5 } { \__bryce_make_row:n { #5 } }
\tl_if_novalue:nF { #6 } { \__bryce_make_row:n { #6 } }
\tl_if_novalue:nF { #7 } { \__bryce_make_row:n { #7 } }
\tl_if_novalue:nF { #8 } { \__bryce_make_row:n { #8 } }
}

\cs_new_protected:Nn \__bryce_make_row:n
{
\clist_set:Nn \l__bryce_row_clist { #1 }
\clist_use:Nn \l__bryce_row_clist { & }
\\
}

\seq_new:N \l__bryce_mxm_i_seq
\seq_new:N \l__bryce_mxm_ii_seq
\seq_new:N \l__bryce_mxm_iii_seq
\seq_new:N \l__bryce_mxm_iv_seq
\seq_new:N \l__bryce_mxm_v_seq
\seq_new:N \l__bryce_mxm_vi_seq
\seq_new:N \l__bryce_mxm_vii_seq
\seq_new:N \l__bryce_mxm_viii_seq
\int_new:N \l__bryce_mxm_int

\cs_new_protected:Nn \__bryce_mxm_do:
{
\int_step_inline:nn { \seq_count:N \l__bryce_mxm_i_seq }
{
\int_set:Nn \l__bryce_mxm_int { ##1 }
\exp_last_unbraced:Ne \__bryce_mxm: \__bryce_mxm_args:
}
}
\cs_new:Nn \__bryce_mxm_args:
{
\int_step_function:nN { 8 } \__bryce_mxm_args_aux:n
}
\cs_new:Nn \__bryce_mxm_args_aux:n
{
\seq_item:cn { l__bryce_mxm_\int_to_roman:n { #1 }_seq } { \l__bryce_mxm_int }
}

\ExplSyntaxOff

\begin{document}

$\makematrix[3,2][1,1] \makematrix[-3, 0][ 0,-2] \makematrix[ 1,-2][-1, 3]$

$\mxm [ [3,2]*[-3, 0]*[ 1,-2] ] [ [1,1]*[ 0,-2]*[-1, 3] ]$

$\makematrix*[3,2][1,1] \makematrix*[-3, 0][ 0,-2] \makematrix*[ 1,-2][-1, 3]$

$\mxm* [ [3,2]*[-3, 0]*[ 1,-2] ] [ [1,1]*[ 0,-2]*[-1, 3] ]$

\end{document}


• "... But the customer's always right." - YMMD! :-)))) – Ulrich Diez Nov 10 '20 at 21:42

Things would be a lot more easier if you don't mind using some braces.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{expl3}
\usepackage{xparse}
\usepackage{amsmath}

\ExplSyntaxOn

\tl_new:N \l_doc_tmpa_tl
\tl_new:N \l_doc_tmpb_tl
\tl_new:N \l_doc_tmpc_tl

\int_new:N \l_doc_tmpa_int
\int_new:N \l_doc_tmpb_int
\int_new:N \l_doc_tmpc_int
\clist_new:N \l_doc_tmpa_clist

\seq_new:N \l_doc_tmpa_seq
\seq_new:N \l_doc_tmpb_seq
\seq_new:N \l_doc_tmpc_seq
\seq_new:N \l_doc_tmpd_seq
\seq_new:N \l_doc_tmpe_seq

\cs_set:Npn \doc_extract_square_bracket:nN #1#2 {
\tl_set:Nn \l_doc_tmpa_tl {#1}
\seq_clear:N #2
\int_set:Nn \l_doc_tmpa_int {0}
\tl_clear:N \l_doc_tmpc_tl

\bool_do_until:nn {\tl_if_empty_p:N \l_doc_tmpa_tl} {
\tl_set:Nx \l_doc_tmpa_tl {\tl_tail:N \l_doc_tmpa_tl}

\exp_args:NV \str_case:nnF \l_doc_tmpb_tl {
{[} {
\int_compare:nNnT {\l_doc_tmpa_int} > {0} {
\tl_put_right:NV \l_doc_tmpc_tl \l_doc_tmpb_tl
}
\int_incr:N \l_doc_tmpa_int
}
{]} {
\int_decr:N \l_doc_tmpa_int
\int_compare:nNnTF {\l_doc_tmpa_int} > {0} {
\tl_put_right:NV \l_doc_tmpc_tl \l_doc_tmpb_tl
}{
\seq_put_right:NV #2 \l_doc_tmpc_tl
\tl_clear:N \l_doc_tmpc_tl
}
}
} {
\int_compare:nNnT {\l_doc_tmpa_int} > {0} {
\tl_put_right:NV \l_doc_tmpc_tl \l_doc_tmpb_tl
}
}
}
}

\newcommand{\x}[1]{
\seq_clear:N \l_doc_tmpc_seq
\doc_extract_square_bracket:nN {#1} \l_doc_tmpb_seq
\seq_map_variable:NNn \l_doc_tmpb_seq \l_doc_tmpa_tl {
\clist_set:NV \l_doc_tmpa_clist \l_doc_tmpa_tl
\seq_put_right:Nx \l_doc_tmpc_seq {\clist_use:Nn \l_doc_tmpa_clist {\c_alignment_token}}
}
\begin{bmatrix}
\seq_use:Nn \l_doc_tmpc_seq {\\}
\end{bmatrix}
}

\cs_set:Npn \doc_temp_seq_name:n #1 {
l__doc_mat_\int_to_alph:n {#1}_seq
}

\newcommand{\mxm}[1]{
\doc_extract_square_bracket:nN {#1} \l_doc_tmpd_seq

\seq_get_left:NN \l_doc_tmpd_seq \l_doc_tmpa_tl
\exp_args:NV \doc_extract_square_bracket:nN \l_doc_tmpa_tl \l_doc_tmpe_seq
\int_set:Nn \l_doc_tmpb_int {\seq_count:N \l_doc_tmpe_seq}
\seq_show:N \l_doc_tmpd_seq
\int_step_inline:nn {\l_doc_tmpb_int} {
\seq_clear:c {\doc_temp_seq_name:n {##1}}
}

\seq_map_variable:NNn \l_doc_tmpd_seq \l_doc_tmpa_tl {
\exp_args:NV \doc_extract_square_bracket:nN \l_doc_tmpa_tl \l_doc_tmpe_seq
\int_set:Nn \l_doc_tmpc_int {1}
\seq_map_variable:NNn \l_doc_tmpe_seq \l_doc_tmpb_tl {
\tl_clear:N \l_doc_tmpc_tl
\tl_put_right:Nn \l_doc_tmpc_tl {[}
\tl_put_right:NV \l_doc_tmpc_tl \l_doc_tmpb_tl
\tl_put_right:Nn \l_doc_tmpc_tl {]}
\seq_put_right:cV {\doc_temp_seq_name:n {\l_doc_tmpc_int}} \l_doc_tmpc_tl
\int_incr:N \l_doc_tmpc_int
}
}

\int_step_inline:nn {\l_doc_tmpb_int} {
\tl_set:Nx \l_doc_tmpa_tl {\exp_not:N\x{\seq_use:cn {\doc_temp_seq_name:n {##1}} {}}}
\tl_use:N \l_doc_tmpa_tl
}
}

\ExplSyntaxOff

\begin{document}
$$\x{[1,2,3][4,5,6][7,8,9]}$$
$$\x{[1,2,3,4,5,6][a,b,c,d,e,f][6,5,4,3,2,1]}$$
$$\x{[1,2][3,4][5,6][7,8][9,10][11,12]}$$
$$\mxm{ [ [3,2]*[-3, 0]*[ 1,-2] ] [ [1,1]*[ 0,-2]*[-1, 3] ] }$$
$$\mxm{ [ [3,2]+[-3, 0]+[ 1,-2] ] [ [1,1]+[ 0,-2]+[-1, 3] ] }$$
$$\mxm{ [ [3,2][-3, \alpha][ 1,-2][5,6] ] [ [1,1][ 0,-2][-1, 3][5,6] ] [ [1,1][ 0,-2][-1, 3][5,6] ] [ [1,1][ 0,-2][-1, 3][5,6] ] [ [1,1][ 0,-2][-1, 3][5,6] ] [ [1,1][ 0,-2][\frac{3}{2}, 3][5,6] ] }$$
\end{document}