15

I am trying to define a macro with variable number of arguments. For example I want to define a macro with two arguments that can work with one argument as well. So the same macro works in two ways.

\textcolor{red}{sample text}

This would limit the red color to the sample text and whereas just calling \textcolor{red} would turn on the red color until the end of the document. So the second way of using the macro would be

\textcolor{red}

I was trying to implement the macro using \@ifnextchar. I am not having much success. Could you please give me some hints?

Based on @Joseph's suggestion below I modified my toy code and it works. I still have problem understanding the actual execution flow in the code. Here is my code.

\documentclass[12pt]{article}
\makeatletter
\def\oneortwoargs#1{%
\@ifnextchar\bgroup%
   {\twoargs@aux{#1}}
   {\onearg@only{#1}}
}

\def\onearg@only#1{%
   Only argument provided: #1 %
}

\def\twoargs@aux#1#2{%
   Two arguments are provided. \\%
   First argument: #1 \\ %
   Second argument: #2%
}
\makeatother

\begin{document}
\oneortwoargs{gg}{fff}
\oneortwoargs{qq}
\end{document}

As @Joseph explained below one token is left in the input stream. What is confusing me is that \onearg@only and \twoargs@aux are invoked the same way with only one argument.

1
  • 3
    Since you have some responses below that seem to answer your question, please mark one of them as ‘Accepted’ by clicking on the tickmark below their vote count. This moves the question off the ‘unanswered’ list and assigns them (and you!) reputation points. Feb 7, 2011 at 7:23

2 Answers 2

23

I'd use the xparse package to do this, as everything is then 'pre-packaged':

\documentclass{article}
\usepackage{color,xparse}
\NewDocumentCommand\MyTextColor{m+g}{%
  \IfNoValueTF{#2}
    {\color{#1}}
    {\textcolor{#1}{#2}}%
}
\begin{document}
\MyTextColor{green}{stuff}

\MyTextColor{red} Some text
\end{document} 

The same can of course be done using \@ifnextchar

\documentclass{article}
\usepackage{color}
\makeatletter
\newcommand*\MyTextColor[1]{%
  \@ifnextchar\bgroup
    {\textcolor{#1}}
    {\color{#1}}%
}
\makeatother
\begin{document}
\MyTextColor{green}{stuff}

\MyTextColor{red} Some text
\end{document} 

The key point is that you need to look for { using \bgroup rather than trying to use it directly (as it is the begin-group character).


To explain how the \@ifnextchar part deals with the need for either 1 or 2 arguments, what happens in my second approach is that

\newcommand*\MyTextColor[1]{%

defines a macro which absorbs one argument. So:

\MyTextColor{red}{stuff}

absorbs red as #1 and leaves {stuff} in the input stream, while

\MyTextColor{red} other stuff

also absorbs red as #1 and leaves other stuff in the input stream. We now apply \@ifnextchar, which 'peeks' at the next token without absorbing it. The test reads

  \@ifnextchar\bgroup
    {\textcolor{#1}}
    {\color{#1}}%

and so depending on the result, either \textcolor{red} or \color{red} is inserted into the input stream. Thus we end up with either

\textcolor{red}{stuff}

or

\color{red} other stuff

which means that \textcolor will get the required two arguments, while \color only gets one. The key is that {red} is reinserted into the input after either \textcolor or \color.


Although it is not directly related to the question, a few notes on the xparse approach may be useful. When using the xparse package, arguments are described by letter, with m representing a mandatory argument and g representing an optional argument in braces (i.e. an optional TeX group). A + before the letter allows that argument to accept paragraph tokens, so in my definition the first argument cannot (it's supposed to be the name of a colour), while the second one can (it is arbitrary text).

The g argument returns a special token (\NoValue) if the optional argument was not present at all. If a default value was wanted, I'd have used G{<default>}. The same approach applies to standard LaTeX optional arguments, which are represented as o or O, depending on whether a default value is required. I have tested for \NoValue with the test \IfNoValueTF; \IfNoValueT and \IfNoValueT are also available.

9
  • Thank you very much for the quick response. I should have used the \bgroup!
    – mahes25
    Feb 6, 2011 at 17:07
  • 1
    @mahes25. Like many things, it is obvious once you know :-)
    – Joseph Wright
    Feb 6, 2011 at 17:09
  • 1
    Note, however, that \MyTextColor{blue} \bgroup ... is likely to lead to unexpected behaviour in the second solution. Also, remember that \@ifnextchar gobbles spaces, so if you want a left brace after a single argument invocation of \MyTextColor then you'd better insert a \relax in between. This sort of requirement is likely to confuse users of your macro, so you'd better keep it to yourself. 8-) Feb 6, 2011 at 17:16
  • 1
    @mahes25: I've only absorbed one argument in \MyTextColor, so if \@ifnextchar finds a second one it is still in the input.
    – Joseph Wright
    Feb 6, 2011 at 17:35
  • 3
    @Joseph: Great answer. Could you please expand a bit on the m+g? Feb 7, 2011 at 13:15
8

In ConTeXt, you can define such macros using \dodoublegroupempty. For example:

\def\TwoArgMacro
  {\dodoublegroupempty\doTwoArgMacro}

\def\doTwoArgMacro#1#2%
  {\ifsecondargument
      Two arguments were passed: #1 and #2
    \else
      One argument was passed: #1
    \fi}

This works by checking if the next character is equal to \bgroup.

You must log in to answer this question.

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