# How do I write a macro having comma-separated and variable number of arguments?

I want to write a macro in LaTeX2e that can pass \textcolor, which is embedded in another macro, the values of the colour model gray, rgb, or cmyk. The number of arguments specified implies the colour model to be used; 1 argument for gray, 3 for the rgb, and 4 for the cmyk model. I was able to write a macro that does what I want, but with the arguments in the standard manner in braces. This is the code I was able to write by modifying the answers here.

\makeatletter
\def\setmycolour#1{%
\@ifnextchar\bgroup%
{\docolour{#1}}
{\dogray{#1}}
}
\def\dogray#1{This is gray hue #1.}
\def\docolour#1#2#3{%
\@ifnextchar\bgroup%
{\docmyk{#1}{#2}{#3}}
{\dorgb{#1}{#2}{#3}}
}
\def\dorgb#1#2#3{This is rgb colour #1,#2,#3.}
\def\docmyk#1#2#3#4{This is cmyk colour #1,#2,#3,#4.}
\makeatother


I use the macro as

\setmycolour{0.85}\\
\setmycolour{1}{0}{0}\\
\setmycolour{1}{0}{0}{0}\\


I want to use the macro, for example, as \setmycolour{1,0,0} or \setmycolour{0.85}. How do I parse the arguments in the macro definition to do this? Is the above code the best way to get the effect I want?

-

The comments what color we have is only for demonstration here.

\documentclass{article}
\usepackage[T1]{fontenc}

\makeatletter
\def\setmycolour#1{\expandafter\setmycolour@i#1,,,,\@nil}
\def\setmycolour@i#1,#2,#3,#4,#5\@nil{%
\ifx$#2$ we have gray => #1 \else
\ifx$#3$ we have a wrong color setting \else
\ifx $#4$ we have a rgb setting => #1,#2,#3\else
we have a cmyk setting =>#1,#2,#3,#4
\fi
\fi
\fi
}
\makeatother

\begin{document}

\setmycolour{0.5}\par
\setmycolour{0.5,0.6}\par
\setmycolour{0.5,0.6,0.7}\par
\setmycolour{0.5,0.6,0.7,0.8}\par

\end{document}

-
Thanks Herbert, this works fine. I had to add another \if to disallow more than 5 arguments (I'm just splitting hairs now :-)). –  Luis Costa Apr 13 '11 at 14:44
Could you explain the solution? I assume you trickily pass four commas to setmycolour@i to ensure that this macro call always succeeds but depending on the number of commas in #1, you might pass too many (which does not hurt). –  Christian Lindig Apr 13 '11 at 15:27
\def\setmycolour@i#1,#2,#3,#4,#5\@nil expects at least 4 commas, the reason why i have t add these ones if there is only one argument, then #2#3#4#5 are all empty. When there are four comma separated arguments given, #5 collects all trhe rest, in this case three commas. #5 is like a garbage can. The other cases are between these two ones. –  Herbert Apr 13 '11 at 15:34
What about \ifx$#2$? This seems tricky as well. Is this evaluated as \ifx (true) when #2 is empty? But why does it evaluate to false when #2 is not empty? Could you have used something other than $? – Christian Lindig Apr 13 '11 at 18:10 @Christian: I guess you could put any character (a-z or A-Z) in place of$. The key, I think, is that two same tokens are place around the argument being tested, say #2. For example, you write \ifx a#2a with "a" in place of "\$". So when #2 is empty \ifx will test left-hand "a" and right-hand "a" and evaluate to true and to false when testing left-hand "a" and "contents-of-#2" when #2 isn't empty whence the right-hand "a" is ignored, now it being part of the 'then' execution. –  Luis Costa Apr 18 '11 at 14:52
\documentclass{minimal}

\makeatletter

\def \setmycolour #1{
\newcount \n
\n = 0
\setmycolour@ #1,\stopmarker ,
\ifnum \n = 1
This is gray hue (#1).
\else \ifnum \n = 3
This is rgb colour (#1).
\else \ifnum \n = 4
This is cmyk colour (#1).
\else
\message{Wrong number of values.}
\fi\fi\fi
}

\def \stopmarker{EOV}

\def \setmycolour@ #1,{
\edef \colorvalue{#1}
\ifx \colorvalue \stopmarker
\let \next = \relax
\else
\let \next = \setmycolour@
\fi
\next
}
\makeatother

\begin{document}

\setmycolour{0.5}\par
\setmycolour{0.5,0.6}\par
\setmycolour{0.5,0.6,0.7}\par
\setmycolour{0.5,0.6,0.7,0.8}\par

\end{document}

-
This solution is good and can be improved by using \ifcase\n rather than the multiple \ifnum tests. It will scale nicely. –  Bruno Le Floch Apr 13 '11 at 20:21
@Bruno: after posting this solution, I've tried to rewrite it using ifcase. Unfortunately, I found it looking awkward, because - as far as I know - there's no possibility to explicitly set constants for ifcase, so you have to write multiple redundant ors. –  Dmitry Volosnykh Apr 13 '11 at 20:33
It seems that \ifcase\n \or "gray" \or \ERROR \or "rgb" \or "cmyk" \else \ERROR \fi is shorter than what you have. But in general, it is true that there is no way of doing anything else than contiguous cases with \ifcase. –  Bruno Le Floch Apr 13 '11 at 23:26
This is a very clear and readable solution. –  Luis Costa Apr 18 '11 at 9:23
@DmitryF.Volosnykh: \value is a LaTeX2e command. It is dangerous to redefine primitives or kernel commands outside local groups. –  Ahmed Musa Oct 4 '12 at 11:14

A LaTeX3 solution:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\setmycolour}{ m }
{
\prg_case_int:nnn { \clist_length:n { #1 } }
{
{1}{ \dogray{#1} }
{3}{ \dorgb{#1} }
{4}{ \docmyk{#1} }
}
{OOPS}
}
\ExplSyntaxOff

\def\dogray#1{This is gray hue #1.}
\def\dorgb#1{This is rgb colour #1.}
\def\docmyk#1{This is cmyk colour #1.}

\begin{document}
\setmycolour{0.85}\\
\setmycolour{1,0,0}\\
\setmycolour{1,0,0,0}

\end{document}


Customizations of the commands performed in the admissible cases are, of course, possible.

## Important change

Due to the changes made to expl3 in Summer 2012, the functions

\prg_case_int:nnn
\clist_length:n


should be changed into

\int_case:nnn
\clist_count:n


with the same syntax.

-

A variant from the Dmitry's code without counter

\documentclass{minimal}

\makeatletter
\def\expr@{This is gray hue}
\def\expr@@{Error with}
\def\expr@@@{This is rgb colour}
\def\expr@@@@{This is cmyk colour}
\def\expr@@@@@{Error with}

\def \setmycolour #1{
\def\tmp{}
\setmycolour@ #1,\stopmarker ,
\@nameuse{expr\tmp} (#1)
}

\def \stopmarker{EOV}

\def \setmycolour@ #1,{
\edef \value{#1}
\ifx \value \stopmarker
\let \next = \relax
\else
\expandafter\def\expandafter\tmp\expandafter{\tmp @}%
\let \next = \setmycolour@
\fi
\next
}
\makeatother

\begin{document}
\setmycolour{0.5}\par
\setmycolour{0.5,0.6}\par
\setmycolour{0.5,0.6,0.7}\par
\setmycolour{0.5,0.6,0.7,0.8}\par
\setmycolour{0.5,0.6,0.7,0.8,0.9}\par
\end{document}

-

Just for fun, a solution with the lpeg parser included in luaTeX.

\documentclass{standalone}
\usepackage{luacode}

\begin{luacode*}
lpeg = require('lpeg')

digit = lpeg.R('09')
dot = lpeg.P('.')
number = (digit^1 * dot * digit^1) + digit^1
comma = lpeg.P(',')

csv = lpeg.Ct(lpeg.C(number) * (comma * lpeg.C(number))^0) / function (t)
if #t == 1 then
return '\\dogray{' .. tostring(t[1]) .. '}'
else if #t == 3 then
return '\\dorgb{' .. tostring(t[1]) .. '}{' .. tostring(t[2]) .. '}{' .. tostring(t[3]) .. '}'
else if #t == 4 then
return '\\docmyk{' .. tostring(t[1]) .. '}{' .. tostring(t[2]) .. '}{' .. tostring(t[3]) .. '}{'  .. tostring(t[4]) .. '}'
end
end
end
end

function parse_and_make(s)
tex.sprint(lpeg.match(csv,s))
end
\end{luacode*}

\def\dogray#1{This is gray hue #1.}
\def\dorgb#1#2#3{This is rgb colour #1,#2,#3.}
\def\docmyk#1#2#3#4{This is cmyk colour #1,#2,#3,#4.}

\def\setmycolor#1{%
\directlua{parse_and_make("#1")}}

\begin{document}

\setmycolor{0.85}\\
\setmycolor{1,0,0}\\
\setmycolor{1,0,0,0}\\

\end{document}

-