8

Knowing that every command declaration with conditionally optional arguments, as \foo{...} expanding into \foo[default]{...} can be defined by \newcommand\foo[1][default]{...} or the safer version of:

\newcommand\foo{\@ifnextchar[{\@foo}{\@foo[default]}}
\def\@foo[#1]#2{do something with #1 and #2}

this kind of "test" however fails if the optional argument is declared after the mandatory one. Therefore (knowing that the order of argument declaration of \@foo cannot be changed):

\newcommand\foo{\@ifnextchar[{\@foo}{\@foo{}[default]}}
\def\@foo#1[#2]{do something with #1 and #2}

will generate errors.

More generally, if \@bar is defined such that has to contain two (or more, but the general case is left aside) arguments, but only the optional #2 that doesn't appear just after the control sequence name:

\def\@bar#1[#2]{#1 and #2}

then, how is it possible to define \bar such that this condition:

\bar{a}[b] % returns "a and b"
\bar{a}    % returns "a and default"

will be met? In this case an internal command inside the definition of \bar would scan all of the argument tokens of \bar until it finds a [; if it doesn't find any, then it'll just declare \@bar#1[default].

This behaviour should be similar to \@ifnextchar (say \@iftoksexist) with the difference that it will be sufficient for char to just exist as argument token:

\documentclass{article}
\makeatletter
\def\@iftoksexist#1#2#3{...} % 
\def\mybar{%
  \@iftoksexist{[} % or \@iftoksexist[{...}{...}
  {
  %use "\@mybar#1[#2]"
  }
  {
  %use "\@mybar#1[default]"
  }%
} 
\def\@mybar#1[#2]{#1 and #2\par}
\makeatother
%
\begin{document}
%
\mybar{lorem}[ipsum]
\mybar{dolor}
\mybar{sit}[amet]   
%
\end{document}

In this case, \@iftoksexist will search for [ inside every \mybar..., yielding this output:

enter image description here

Yet again, after a sufficient number of trials, and looking this and that, I haven't had much success.

2
  • 2
    You should have a look at xparse
    – egreg
    Sep 14, 2014 at 16:30
  • @egreg Right in the weak spot... Unfortunately I haven't put my eye on its documentation for a long time, although it may save some difficulties with the plain TeX understanding of this problem.
    – TheVal
    Sep 14, 2014 at 16:32

1 Answer 1

7

Just absorb the mandatory argument and “remember” it:

\makeatletter
\newcommand{\foo}[1]{%
  \@ifnextchar[{\foo@aux{#1}}{\foo@aux{#1}[default]}%
}
\def\foo@aux#1[#2]{%
  mandatory is `#1', optional is `#2'%
}
\makeatother

of course this is not very robust and \DeclareRobustCommand should be used.

A syntax like \foo{a}[b]{c} where the optional argument has a default value can be defined in a similar way:

\makeatletter
\newcommand{\foo}[1]{%
  \@ifnextchar[{\foo@aux{#1}}{\foo@aux{#1}[default]}%
}
\def\foo@aux#1[#2]#3{%
  first mandatory is `#1', optional is `#2', second mandatory is `#3'%
}
\makeatother

Alternatively:

\usepackage{xparse}

\NewDocumentCommand{\foo}{mO{default}}{%
  mandatory is `#1', optional is `#2'%
}

which is automatically robust. But as a general advice, avoid trailing optional arguments. The \foo{a}[b]{c} syntax is just

\NewDocumentCommand{\foo}{mO{default}m}{%
  first mandatory is `#1', optional is `#2', second mandatory is `#3'%
}

Note that in the first case spaces will be ignored after \foo{a} if no optional argument is present (the reason is how \@ifnextchar works).

5
  • The [default] should be in the false branch I guess: \@ifnextchar[{\foo@aux{#1}}{\foo@aux{#1}[default]}
    – cgnieder
    Sep 14, 2014 at 16:38
  • @cgnieder You're perfectly right
    – egreg
    Sep 14, 2014 at 16:40
  • @egreg Needless to say, excellent answer as usual. I'll definitely look up xparse now that I have plenty of time ;)
    – TheVal
    Sep 14, 2014 at 20:40
  • @AndreaL. Exam session ended?
    – egreg
    Sep 14, 2014 at 20:46
  • @egreg Yeah! But I finished in July; right now I'm preparing some Linear Algebra exercise sheets for my colleagues (with the occasion, I took two birds with one stone) :-)
    – TheVal
    Sep 14, 2014 at 21:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .