# How to create a macro with two or three ;-separated arguments?

I want to create in 2D a point with \defpoint(1;2){A} and in 3D with \defpoint(1;2;-5){A}. I prefer a solution with TeX but it would be interesting to see if LaTeX3 is useful in this situation. I never tried to program with LaTeX3.

I used ; and not ,because in french I want to write : \defpoint(1,5;2,5){A} instead of \defpoint({1,5},{2,5}){A}.

There are other conditions :

I want \defpoint(12;23){A} and not \defpoint({12};{23}){A}

Example of code but it does not respect the last condition :

\documentclass{minimal}
\makeatletter
\def\defpoint(#1;#2{\@ifnextchar){\coor@ii(#1;#2}{\coor@iii(#1;#2}}
\def\coor@ii(#1;#2)#3{two coordinates: #1;#2 and #3}
\def\coor@iii(#1;#2;#3)#4{three coordinates: #1;#2;#3 and #4}
\makeatother
\begin{document}

\defpoint(1;2;3){A}

%\defpoint(12;13){B}     % here we need defpoint({12};{13}){B}

\end{document}

The next condition is a real problem also it's not necessary; I would like to avoid \defpoint({sin(45)};{cos(45)}){A} with \defpoint(sin(45);cos(45)){A}; but I know this is very difficult. With TikZ, we need in some cases to use parenthesis.

It would be interesting to evaluate expressions, also a good solution should allow to evaluate easily

-
Please use inline code formatting just for code and not for TeX, LaTeX3, TikZ, etc. Also LaTeX3 is TeX, so your second sentence doesn't make much sense. Do you mean plain-TeX or LaTeX2e? –  Martin Scharrer Dec 28 '11 at 8:57
Ok I don't know. I prefer plain-TeX but about LaTeX3, the syntax and the tools seem to be very different. –  Alain Matthes Dec 28 '11 at 9:42

\documentclass{article}
\makeatletter
\def\defpoint#1#{\expandafter\defpoint@i#1;;\@nil}
\def\defpoint@i#1;#2;#3;#4\@nil#5{%
\ifx\relax#3\relax \defpoint@ii#1;#2\@nil{#5}\else\defpoint@iii#1;#2;#3\@nil{#5}\fi}
\def\defpoint@ii(#1;#2)\@nil#3{2D~co-ordinates~-~#1;#2,~mandatory~argument~-~'#3'\\}
\def\defpoint@iii(#1;#2;#3)\@nil#4{3D~co-ordinates~-~#1;#2;#3,~mandatory~argument~-~'#4'\\}
\makeatother
\begin{document}
\noindent
\defpoint(1;2){A}
\defpoint(12;13){B}
\defpoint(sin(45);cos(45)){A}
\defpoint(1;2;-5){A}
\end{document}

-
Thanks Herbert. Instead of \ifx\relax#3\relax sometimes I see something like \ifx\\#3\\% but what is the better method ? –  Alain Matthes Dec 28 '11 at 10:33
there is no better method. It depends on what may not be possible as part of the argument. And \relax or \\ seem to be good values which may not be part of the coordinates –  Herbert Dec 28 '11 at 10:36
@Altermundus The \if\relax\detokenize{#3}\relax test for emptyness is the safest one, as it allows any token in #3 (and is fully expandable). –  egreg Dec 28 '11 at 10:57
We've discussed using 'grab to #{' inside xparse, but for 'the general case' it's not so useful. (Applied here, with a known argument situation, it works very well.) For example, something like \foo[{[bar]}] will give the wrong result unless you take additional precautions. –  Joseph Wright Dec 28 '11 at 11:05
@egreg: detokenize is overkill in grabbing values for coordinates –  Herbert Dec 28 '11 at 11:27

With eTeX, you should use \scantokens:

\documentclass{article}
\makeatletter
\def\defpoint{%
\edef\saved@catcode{\number\catcode\;}%
\catcode\;12
\begingroup
\catcode$$1 \catcode$$2 \catcode\ 9
\defpoint@i
}
\def\defpoint@i#1{%
\endgroup
\endlinechar-1 \everyeof{\noexpand}%
\edef\coord@point{\scantokens{#1}}%
\expandafter\defpoint@ii\coord@point;\@nil
}
\def\defpoint@ii#1;#2;#3\@nil#4{%
\catcode\;\saved@catcode\relax
\ifx\relax#3\relax
2 coordonn\'ees : #1 et #2
\else
3 coordonn\'ees : #1 ; #2 et \def@point@iii#3
\fi
puis le point : #4\par
}
\def\def@point@iii#1;{#1}
\makeatother
\begin{document}
\defpoint(12;13){M}
\defpoint( sin(30) ; cos(45) ; 4 ){A}
\end{document}
-
"With eTeX, you should use \scantokens", aeh, for what exactly? Please explain what exactly you are doing. Otherwise people have to study the code in order to figure it out. I personally don't get why it is an issue of the catcodes. –  Martin Scharrer Dec 28 '11 at 8:59
@unbonpetit "Bonne fin d'année :)" Very impressive, scantokens is very useful but I need to study this code to understand all the parts like  \endlinechar-1 \everyeof{\noexpand}% and how to manage the catcodes. –  Alain Matthes Dec 28 '11 at 9:02
The change of catcodes is used to grab the argument in parenthesis, but it's unnecessarily complicated, as at least one semicolon must be present, which separates the opening ( from the closing ), as Herbert's solution shows. –  egreg Dec 28 '11 at 21:11

You problem is the ;#2{ part in:

\def\defpoint(#1;#2{\@ifnextchar){\coor@ii(#1;#2}{\coor@iii(#1;#2}}

which tells to store the next token or balanced group after the ; as #2. If you don't wrap the content there in { } only the first token/character is taken. Therefore it only works when #2 is only a single character, but not in the general case. You need to read everything till the ) and then test if a ; is included in there.

The following code reads everything between the first ; and ), adds a ; behind it with some \relax, then uses a second macro which used two ;-separated parts with a \relax as end-marker. Then it checks if the added ; is still there, which indicates that there was only one coordinate. In this case the original #2 is used directly because the now read part (#3) will contain the first added \relax.

\documentclass{article}
\makeatletter
\def\defpoint(#1;#2){%
\@defpoint{#1}{#2}#2\relax;\relax\relax
}
\def\@defpoint#1#2#3;#4\relax#5{%
\ifx;#5\relax% is it the added the ; or the trailing \relax`?
\def\next{\coor@iii({#1};{#3};{#4})}%
\else
\def\next{\coor@ii({#1};{#2})}%
\fi
\next
}

\def\coor@ii(#1;#2)#3{two coordinates: #1;#2 and #3}
\def\coor@iii(#1;#2;#3)#4{three coordinates: #1;#2;#3 and #4}
\makeatother
\begin{document}

\defpoint(1;2;3){A}

\defpoint(12;23;34){B}

\defpoint(12;13){C}

\defpoint(1;3){D}

\end{document}

The code is not fully expandable, but it is possible to write it that way. However, because it seems to be a definition I didn't think it was required.

-
Yes neat code but the code of unbonpetit resolves the problem of \defpoint({sin(30)} ; {cos(30)}){B} and this is very impressive. My problem now is to understand the code. I found another solution . I put the code in an answer or inside my question like an example ? –  Alain Matthes Dec 28 '11 at 9:34
@Altermundus: Ok, I see. I overlooked the ( ) inside ( ) issue.. BTW, you only need the { } around the cos(30), i.e. in the second argument, not in the first one. –  Martin Scharrer Dec 28 '11 at 9:42
I remark the issue with { } only for the last argument because I have the same problem with a method found on the net. –  Alain Matthes Dec 28 '11 at 9:56

As a LaTeX3 solution is acceptable, I'd use xparse. The internals of xparse can deal correctly with nested optional argument brackets or similar, and so \defpoint(sin(45);cos(45)){A} is not an issue.

You don't say if \defpoint needs to be expandable. Assuming that it does not, a solution which meets the criteria is

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \defpoint
{ > { \SplitArgument { 2 } { ; } } D ( ) { 0 ; 0 } m }
{ \defpoint_aux:nnnn #1 {#2} }
\cs_new_protected:Npn \defpoint_aux:nnnn #1#2#3#4
{
\IfNoValueTF {#3}
{ 2D~co-ordinates~-~(#1;#2),~mandatory~argument~-~'#4' \\ }
{ 3D~co-ordinates~-~(#1;#2;#3),~mandatory~argument~-~'#4' \\ }
}
\ExplSyntaxOff
\begin{document}
\noindent
\defpoint(1;2){A}
\defpoint(12;13){B}
\defpoint(sin(45);cos(45)){A}
\defpoint(1;2;-5){A}
\end{document}

I need an internal function (\defpoint_aux:nnnn) here to allow me to deal with the variable number of co-ordinates. What happens is that \SplitArgument will divide up the first argument at a maximum of two ; tokens, and will always produce three <balanced text>. These are picked up by the auxiliary function as #1, #2 and #3, and so we can test for 2D versus 3D by seeing if the third argument is the special \NoValue marker.

I've not covered it above, but you could also test for whether the argument in parentheses is given at all and if it contains only one argument (i.e. no ;), again using \IfNoValueTF tests.

For an expandable approach, you need a little more work, and an up-to-date copy of xparse (nested optional arguments did not work expandability until I examined it for this question).

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand \defpoint
{ D ( ) { 0 ; 0 } m }
{ \defpoint_aux:nn {#1} {#2} }
\cs_new:Npn \defpoint_aux:nn #1#2
{ \defpoint_aux:nw {#2} #1 ; \q_nil ; \q_stop }
\cs_new:Npn \defpoint_aux:nw #1#2 ; #3 ; #4 ; #5 \q_stop
{
\quark_if_nil:nTF {#4}
{ \defpoint_aux:nnnn {#2} {#3} { \NoValue } {#1} }
{ \defpoint_aux:nnnn {#2} {#3} {#4} {#1} }
}
\cs_new:Npn \defpoint_aux:nnnn #1#2#3#4
{
\IfNoValueTF {#3}
{ 2D~co-ordinates~-~(#1;#2),~mandatory~argument~-~'#4' \\ }
{ 3D~co-ordinates~-~(#1;#2;#3),~mandatory~argument~-~'#4' \\ }
}
\ExplSyntaxOff
\begin{document}
\noindent
\defpoint(1;2){A}
\defpoint(12;13){B}
\defpoint(sin(45);cos(45)){A}
\defpoint(1;2;-5){A}
\end{document}

Much the same idea in the internals as others have suggested, except I'm using a pre-build test for a 'quark' (special marker). Again, I've not done a complete job on testing the input here, so for example an empty optional argument will cause problems.

-
Indeed, the xparse feature to allow for nested delimiter character groups is really great and very useful here. –  Martin Scharrer Dec 28 '11 at 9:43
@MartinScharrer I realise that it's not currently implemented for expandable functions. We're still not sure how useful this will be, long term, but I'll address it in the next CTAN update (probably today, if I can manage it). –  Joseph Wright Dec 28 '11 at 9:44
Thanks . I don't understand actually the code but it will be very useful later. –  Alain Matthes Dec 28 '11 at 9:59
@Altermundus I guess depending on the questions they might stand 'on their own'. Feel free to ask in comments first and I can see if there is anything I can edit in. –  Joseph Wright Dec 29 '11 at 16:27
ok thanks but I need to study LaTeX3 first ! –  Alain Matthes Dec 29 '11 at 16:58