Explode arguments in argument list

You can use another separator different than @ that doesn't change catcodes, or use

\begingroup\lccode`;=`@ \lowercase{\endgroup
  \def\@explode#1;#2;#3\@nil}{\edef\@pOne{#1}\edef\@pTwo{#2}}

instead of

\def\@explode#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}

but note that you won't be able to use it in a \makeatletter part of the code.


And the full game

\begingroup\lccode`;=`@ \lowercase{\endgroup
  \def\@explodeother#1;#2;#3\@nil}{\edef\@pOne{#1}\edef\@pTwo{#2}}
\def\@explodeletter#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}

\def\explode{\ifnum\catcode`@=11 \expandafter\explodeletter\else\expandafter\explodeother\fi}
\begingroup\lccode`;=`@ \lowercase{\endgroup
  \def\explodeother#1{\expandafter\@explodeother#1;;\@nil}}
\def\explodeletter#1{\expandafter\@explodeletter#1@@\@nil}

\newcommand{\fun}[1][a@b,c@d,e@f,g,]{%
  \@for\elem:=#1\do{\explode{\elem}1:\@pOne-2:\@pTwo, }%
}

This last one checks the catcode of @ and uses the letter-@ division or other-@ division accordingly.


It is a category code problem of @. The definition used category code "letter", whereas the category code of @ is usually "other" in the main document, example:

\documentclass{article}

% \FunLetter using @ with category code "letter"

\makeatletter
\def\@explode@letter#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
\newcommand*{\ExplodeLetter}[1]{\expandafter\@explode@letter#1@@\@nil}

\newcommand{\FunLetter}[1][a@b,c@d,e@f,g,]{%
    \@for\elem:=#1\do{\ExplodeLetter{\elem}1:\@pOne-2:\@pTwo, }%
}
\makeatother

% \FunOther using @ with category code "other"

\makeatletter
\begingroup
  \lccode`9=`@
\lowercase{%
  \endgroup
  \def\@explode@other#19#29#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
  \newcommand*{\ExplodeOther}[1]{\expandafter\@explode@other#199\@nil}

  \newcommand{\FunOther}[1][a9b,c9d,e9f,g,]{%
      \@for\elem:=#1\do{\ExplodeOther{\elem}1:\@pOne-2:\@pTwo, }%
  }
}
\makeatother

\begin{document}
\setlength{\parindent}{0pt}

\verb|\makeatother|\makeatother\\
\begin{tabular}{@{}l@{ }l@{}}
\verb|\FunLetter|:& \FunLetter\\
\verb|\FunLetter[a@b,c@d,e@f,g,]|:& \FunLetter[a@b,c@d,e@f,g,]\\
\verb|\FunOther|:& \FunOther\\
\verb|\FunOther[a@b,c@d,e@f,g,]|:& \FunOther[a@b,c@d,e@f,g,]
\end{tabular}

\medskip
\verb|\makeatletter|\makeatletter\\
\begin{tabular}{@{}l@{ }l@{}}
\verb|\FunLetter|:& \FunLetter\\
\verb|\FunLetter[a@b,c@d,e@f,g,]|:& \FunLetter[a@b,c@d,e@f,g,]\\
\verb|\FunOther|:& \FunOther\\
\verb|\FunOther[a@b,c@d,e@f,g,]|:& \FunOther[a@b,c@d,e@f,g,]
\end{tabular}
\end{document}

Result

Workarounds/solutions:

  • Checking for both @ tokens with category code "letter" and "other".
  • Using a different separator character. For example, there are lots of key value parser, if the equals sign = is uses as separating character, e.g. package kvsetkeys.

As already explained, the problem is that @ has category code 11 at definition time, but category code 12 at usage time.

Here's an implementation with xparse; the \SplitArgument processor pushes -NoValue- when the argument hasn't the indicated number of tokens to split at, so it's necessary to use \IfValueT in order to print the second part when existing.

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\fun}{>{\SplitList{,}}O{a@b,c@d,e@f,g,}}{%
  \ProcessList{#1}{\explode}%
}

\NewDocumentCommand{\explode}{>{\SplitArgument{1}{@}}m}{%
  \doexplosion#1, %
}

\NewDocumentCommand{\doexplosion}{mm}{%
  1:#1-\IfValueT{#2}{#2}% No @ pushes -NoValue-
}

\begin{document}

\fun

\fun[a@b,c@d,e@f,g,]

\end{document}

enter image description here