3

This is similar, but not a duplicate of Special behavior if optional argument is not passed and Different macro behavior when supplied an argument.

I want to define a macro \XXX such that \XXX{1} and \XXX1 behave the same (as they would if I used \newcommand{\XXX}[1]{...} to define the macro), but such that \XXX by itself would behave differently. In order words, something like

\newcommand{\XXX}[1]{Hello #1}
\newcommand{\XXX}{Bye}

so that \XXX1 and \XXX2 produce "Hello 1" and "Hello 2" respectively, but \XXX produces "Bye".

The problem with \@ifnextchar is that it does not test for spaces, and I cannot test just for braces. I do not see a way to achieve this with xparse package either.

Clarification: I do not care how \XXX 1 behaves.

Added: After reading the comments and additional reflection, I decided that my intended syntax was not good. Thanks for all the help.

  • If you could agree on using [1] and [2] instead of 1 and 2, you could do \documentclass{article} \begin{document} \newcommand{\XXX}[1][\empty]{\ifx#1\empty Bye \else Hello #1 \fi} \XXX[1] \XXX \end{document}. – marmot Feb 11 at 22:29
  • Can you show how you would use \XXX in general? If you want the "mandatory" argument to be conditional, then there might be instances where you're forced to introduce some weird syntax so as to not grab content. For example, will \XXX only accept numbers, or characters as well? If numbers, single or double digits or higher order digits? Will you use input like \XXX1, \XXX{1} and \XXX 1 interchangeably? – Werner Feb 11 at 22:39
  • 1
    @marmot oh sorry I just noticed your comment, which has some similarities to my answer:-) – David Carlisle Feb 11 at 22:57
  • 1
    what do you mean by \XXX by itself? \XXX1 is the same as \XXX 1 so what input following \XXX would you consider as terminating the construct with no argument? – David Carlisle Feb 11 at 23:01
  • @marmot I can't guess what you mean about \else , but try \XXX[11] ... – David Carlisle Feb 11 at 23:06
6

There will be no space tokens after \XXX so you could use \@ifnextchar\lbrace (or you could use xparse g argument type) But either are totally against latex syntax. The argument is optional so should use [..] not {..}.

A syntax that follows LaTeX syntax conventions would be

\newcommand\XXX[1][\relax]{\ifx\relax#1 bye \else hello #1\fi}

\XXX

\XXX[1]
  • 1
    note that if you make an optional argument use {} delimiters then you need to make the {} mandatory, you can't really have that and have \XXX 1 being the same as \XXX {1} as they are conflicting requirements. – David Carlisle Feb 11 at 23:03
3
\documentclass{article}

\makeatletter
% If #1 is empty or does hold space-tokens only, yields "bye", 
% otherwise yields "hello #1".
% Requires an engine with eTeX-extensions.
\newcommand\XXX[1]{%
  \if\relax\detokenize\expandafter{\@firstofone#1{}}\relax bye \else hello #1\fi
}
\makeatother

\begin{document}

\XXX{}

\XXX{ }

\XXX{1}

\end{document}
2

You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of \kernel@ifnextchar (which itself is based on \futurelet) check the next token in the token-stream.


Some days ago, in my answer to the question Space after LaTeX commands, I tried to explain the drawbacks of the approach of having LaTeX "look ahead" at the next token:

The major drawback of this method is that it relies on the next token in the token-stream coming into being by reading and tokenizing tex-input from the tex-source-code while the temporary changes of these catcodes are effective.

But tokens can also get into the token-stream not by reading and tokenizing tex-input from the tex-source-code but as a result of expanding, e.g., a macro-token where both the replacement-text and the arguments got tokenized at points in time when these category-codes were not changed.

Commands that temporarily change the category-code-régime and rely on the changed category-code-régime being in effect when tokens which they shall process get tokenized cannot be used in circumstances where the things they shall process will already have been tokenized under the unchanged category-code-régime as would be the case, e.g., when they get their arguments passed by other macros as a result of expanding these other macros.

Therefore with the example below, \XXX is defined in terms of \outer for ensuring as good as possible that it will not be used within the definition-texts or the arguments of other macros.


You need another variant of \kernel@ifnextchar because you cannot safely use \kernel@ifnextchar as \kernel@ifnextchar is definitely not 100%ly reliable:

The commented sources of LaTeX 2e as a pdf-file whose name is source2e.pdf can be found at http://mirrors.ctan.org/macros/latex/base/source2e.pdf.

\kernel@ifnextchar in the LaTeX 2e sources is defined as follows—File d: ltdefns.dtx Date: 2018/09/26 Version v1.5e :

321 \long\def\@ifnextchar#1#2#3{%
322   \let\reserved@d=#1%
323   \def\reserved@a{#2}%
324   \def\reserved@b{#3}%
325   \futurelet\@let@token\@ifnch}

326 \let\kernel@ifnextchar\@ifnextchar

327 \def\@ifnch{%
328   \ifx\@let@token\@sptoken
329     \let\reserved@c\@xifnch
330   \else
331     \ifx\@let@token\reserved@d
332       \let\reserved@c\reserved@a
333     \else
334       \let\reserved@c\reserved@b
335     \fi
336   \fi
337   \reserved@c}

338 \def\:{\let\@sptoken= } \:  % this makes \@sptoken a space token

339 \def\:{\@xifnch} \expandafter\def\: {\futurelet\@let@token\@ifnch}
  • E.g., you said you cannot test for a space. That's true. There was extra effort for implementing a loop that does remove spaces when implementing \kernel@ifnextchar/\@ifnextchar.

    This loop leads to error-messages with things like:

    \kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}\@sptoken

    That has to do with the fact that knowing whether tokens have the same meaning does not imply knowing whether they are the same tokens.

  • When the token trailing the arguments of \kernel@ifnextchar has the meaning of the token \reserved@d, i.e., when you have something like

    \kernel@ifnextchar{⟨char⟩}{The next thing is ⟨char⟩}{The next thing is not ⟨char⟩}\reserved@d

    , you get \kernel@ifnextchar's first argument no matter what it is.

    (That's why things like \reserved@d are reserved. ;-) )

    See, e.g., what you get from

    \documentclass{article}
    \makeatletter
    \begin{document}
    % This is nice:
    
    \kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}\reserved@d
    
    \kernel@ifnextchar{Y}{The next thing is Y}{The next thing is not Y}\reserved@d
    
    \kernel@ifnextchar{Z}{The next thing is Z}{The next thing is not Z}\reserved@d
    
    \kernel@ifnextchar{\LaTeX}{The next thing is \LaTeX}{The next thing is not \LaTeX}\reserved@d
    
    % This raises nice errors.
    % \kernel@ifnextchar{X}{The next thing is X}{The next thing is not X}\@sptoken X
    
    \end{document}
    

    enter image description here

  • Also \kernel@ifnextchar does not distinguish implicit characters from explicit characters.
    See, e.g., what you get from

    \documentclass{article}
    \makeatletter
    \let\implicitA=A
    \begin{document}
    \kernel@ifnextchar{A}{We have an }{We don't have an }\implicitA
    
    \kernel@ifnextchar{\implicitA}{We have an }{We don't have an }A
    \end{document}
    

    enter image description here

Above it was said:

You can, e.g., create a command which has LaTeX temporarily change the catcode of space, tab, return and % (% is used for commenting) and via a variant of \kernel@ifnextchar (which itself is based on \futurelet) check the next token in the token-stream.

If the catcode of these characters is switched to 12(other) and checking only for these characters is of interest, you can have LaTeX peek at the meaning of the next token via \futurelet while leaving that token in place.

In case the meaning of the next token equals the meaning of one of these explicit catcode-12-character-tokens, you can safely have LaTeX grab that token as an undelimited macro argument. Thus in this special case of checking for explicit catcode-12-characters you can have LaTeX "grab" the token itself for defining a temporary macro that expands to that token. If there also is already defined another temporary argument that expands to your \kernel@ifnextchar-variant's first argument, LaTeX can do an \ifx-comparison with these temporary macros for making sure that the two tokens in question do not just have the same meanings but are really the same tokens. (When grabbing the next token as argument, LaTeX should probably put it back in the right moment...)

In the example below I tried to implement a routine \UD@ifnextcharForOtherTokens which causes LaTeX to do these things. At least I hope it does. ;-)

\documentclass{article}

\makeatletter

% Patch verbatim to also display horizontal tabs:
% The patch is needed for this example only.
\usepackage{amssymb}
\g@addto@macro\dospecials{\keystroketab}
\newbox\UD@tempbox
\begingroup
\catcode`\^^I=13\relax
\@firstofone{%
  \endgroup
  \newcommand\keystroketab{%
    \setbox\UD@tempbox\hbox{\verbatim@font\char32}%
    \catcode`\^^I=13\relax
    \def^^I{\mbox{\hbox to 3\wd\UD@tempbox{\null\hfill$\leftrightarrows$\hfill\null}}}%
  }%
}%
%verbatim-patch done.

% \UD@ifnextcharForOtherTokens peeks at the following token and
% compares it with its first argument  w h i c h   m u s t   b e 
% a   s i n g l e    t o k e n    a n d   w h i c h   i n   c a s e 
% o f   b e i n g   a   c h a r a c t er   t o k e n ---  b e 
% i t   e x p l i c i t   o r   i m p l i c i t ---  m u s t
% b e   a   c h a r a c t e r   t o k e n   w h o s e   e x p l i c i t
% v a r i a n t   c a n   b e   s a f e l y   g r a b b e d   a s
% u n d e l i m i t e d   a r g u m e n t!!!   T h i s   i s   t h e 
% c a s e,   e. g.,   w i t h   c h a r a c t er   t o k e n s   o f
% c a t e g o r y   c o d e 12(other)!
% If both are the same it executes its second argument, otherwise
% its third.
\newcommand\UD@reserved@a{}%
\newcommand\UD@reserved@b{}%
\newcommand\UD@reserved@c{}%
\newcommand\UD@reserved@d{}%
\newcommand\UD@let@token{}%
\newcommand\UD@ifnextcharForOtherTokens[3]{%
  \begingroup
  \def\UD@reserved@d{#1}%
  \def\UD@reserved@a{#2}%
  \def\UD@reserved@b{#3}%
  \futurelet\UD@let@token\UD@ifnch
}%
\newcommand\UD@ifnch{%
  \expandafter\ifx\expandafter\UD@let@token\UD@reserved@d
    \expandafter\UD@ifnchsnapnexttoken
  \else
    \expandafter\expandafter\expandafter
    \endgroup\expandafter\UD@reserved@b
  \fi
}%
\newcommand\UD@ifnchsnapnexttoken[1]{%
  \def\UD@reserved@c{#1}%
  \ifx\UD@reserved@c\UD@reserved@d
    \expandafter\expandafter\expandafter
    \endgroup\expandafter\UD@reserved@a
  \else
    \expandafter\expandafter\expandafter
    \endgroup\expandafter\UD@reserved@b
  \fi
  #1%
}%
\begingroup
% -------------------------------------------------
% Don't indent the following code!
% Each line of the following code must end with ^^A 
% vwhich serves as comment-char!
\catcode`\^^A=14\relax%
\catcode`\ =12\relax^^A
\catcode`\^^I=12\relax^^A
\catcode`\^^M=12\relax^^A
\catcode`\%=12\relax^^A
\@firstofone{^^A
\endgroup^^A
\newcommand\UD@removeotherspace{}^^A
\long\def\UD@removeotherspace#1 {#1}^^A
\newcommand\UD@removeotherreturn{}^^A
\long\def\UD@removeotherreturn#1^^M{#1}^^A
\newcommand\UD@removeothertab{}^^A
\long\def\UD@removeothertab#1^^I{#1}^^A
\newcommand\UD@removecomment{}^^A
\long\def\UD@removecomment#1#2%#3^^M{\endgroup\UD@removecommentloop{#1}{#2}}^^A
\begingroup^^A
\newcommand\UD@CheckWhetherSourceSpace[1]{^^A
\endgroup^^A
\newcommand\UD@removecommentloop[2]{^^A
\UD@ifnextcharForOtherTokens{ }{\UD@removeotherspace{\UD@removecommentloop{##1}{##2}}}{^^A
\UD@ifnextcharForOtherTokens{^^I}{\UD@removeothertab{\UD@removecommentloop{##1}{##2}}}{^^A
\UD@ifnextcharForOtherTokens{%}{\begingroup\catcode`\^=12#1\UD@removecomment{##1}{##2}}{^^A
\UD@ifnextcharForOtherTokens{^^M}{\UD@removeotherreturn{\endgroup##1{\par}}}{\endgroup##2}^^A
}}}}^^A
\newcommand\UD@CheckWhetherSourceSpace[2]{^^A
\begingroup^^A
\catcode`\ =12\relax^^A
\catcode`\^^I=12\relax^^A
\catcode`\^^M=12\relax^^A
\catcode`\%=12\relax^^A
\UD@ifnextcharForOtherTokens{%}{\begingroup\catcode`\^=12#1\UD@removecomment{##1}{##2}}{^^A
\UD@ifnextcharForOtherTokens{ }{\endgroup\UD@removeotherspace{##1{#1\ignorespaces}}}{^^A
\UD@ifnextcharForOtherTokens{^^M}{\endgroup\UD@removeotherreturn{##1{#1\ignorespaces}}}{^^A
\UD@ifnextcharForOtherTokens{^^I}{\endgroup\UD@removeothertab{##1{#1\ignorespaces}}}{\endgroup##2}^^A
}}}}}^^A
}%<- closing brace of \@firstofone's argument
\UD@CheckWhetherSourceSpace{ }%
%
% Now we are back to normal circumstances. ;-)
% -------------------------------------------------
%
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% Commands that call \UD@CheckWhetherSourceSpace must be defined in
% terms of \outer.
% They may be used only in circumstances where their arguments get
% read and tokenized _while_ they are carried out.
% They must not be used in circumstances where the tokens probably
% forming their arguments already got tokenized and then were passed
% on to them via "spitting out" some macro-definition or 
% macro-argument or the like.
% Be aware that defining in terms of \outer does not prevent 
% erroneous usage to 100%,
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% 
% The first argument of \UD@CheckWhetherSourceSpace is a macro that
% performs the action in case no argument is present. It seems
% confusing but it must nonetheless process one argument.
% That argument is deliveredby \UD@CheckWhetherSourceSpace as a means
% of providing info about whether a space token or a \par-token
% needs to be appended after the action.
% The second argument of \UD@CheckWhetherSourceSpace is a macro that
% performs the action in case an argument is present.
\newcommand\XXX{}
\outer\def\XXX{%
  \UD@CheckWhetherSourceSpace{\XXX@Space}{\XXX@Arg}%
}%
\newcommand\XXX@Space[1]{\fbox{Bye}#1}%
\newcommand\XXX@Arg[1]{\fbox{Hello #1}}
\makeatother

\parindent=-.66cm

\begin{document}

{\bfseries Linebreak between \verb|\XXX| and following stuff:}

\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX
1 A
\end{verbatim*}%
\endgroup
\emph{yields:}\\
text \XXX
1 A

\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX
{1} A
\end{verbatim*}
\endgroup
\emph{yields:}\\
text \XXX
{1} A

\vfill

{\bfseries Comment and linebreak between \verb|\XXX| and following stuff:}

\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX%
   % Comment ^^M^^M Comment
    % Comment 
    1 A
\end{verbatim*}
\endgroup
\emph{yields:}\\    
text \XXX%
   % Comment ^^M^^M Comment
    % Comment 
    1 A

\vfill

{\bfseries Comment and linebreak and linebreak between \verb|\XXX| and following stuff:}

\emph{The code}
\begingroup\topsep=0ex\partopsep=0ex
\begin{verbatim*}
text \XXX%
   % Comment ^^M^^M Comment
    % Comment 

1 A
\end{verbatim*}
\endgroup
\emph{yields:}\\
\begingroup\parindent=0ex  
text \XXX%
   % Comment ^^M^^M Comment
    % Comment 

1 A
\endgroup

\vfill

{\bfseries Space between \verb|\XXX| and following stuff:}

\verb*|text \XXX 1 A|: text \XXX 1 A

\verb*|text \XXX {1} A|: text \XXX {1} A

\vfill

{\bfseries Horizontal tab between \verb|\XXX| and following stuff:}

\verb*|text \XXX    1 A|: text \XXX 1 A

\verb*|text \XXX    {1} A|: text \XXX   {1} A

\vfill

{\bfseries Nothing between \verb|\XXX| and following stuff:}

\verb*|text \XXX1 A|: text \XXX1 A

\verb*|text \XXX{1} A|: text \XXX{1} A

\verb*|text \XXX123 A|: text \XXX123 A

\verb*|text \XXX{123} A|: text \XXX{123} A

\vfill\vfill\vfill\vfill\vfill\vfill

\end{document}

enter image description here

For some reason unknown to me horizontal-tabs in examples get transformed into four spaces when pasting code into the "Answer"-window of StackExchange.
Thus the horizontal-tabs I typed into the example before compiling it may during the copy-paste-process have been transformed to spaces which implies that the result of copy-pasting the example from StackExchange and compiling may have a look which is slightly different from the look of the image above.


I state clearly and explicitly that I do not recommend this approach. It has too many drawbacks and restrictions. The example is intended to show how confusing (La)TeX-programming can be. ;-)

One restriction is already mentioned: You can't use \XXX within macro-definitions or within macro-arguments. You'd better not have other macros deliver \XXX's argument or tokens that shall be behind \XXX but not be taken for \XXX's argument.

Some other restrictions and drawbacks are:

  • \XXX performs a lot of temporary assignments. Thus \XXX is not fully expandable and cannot be used safely in pure-expansion-contexts.
  • A thing like \XXX could not be used safely within moving-arguments, i.e., within things that, e.g., get written to temporary files so that in future LaTeX-runs they can pop up in the table of contents or within \label-\ref-cross-references also. One of the reasons for this is that LaTeX does always attach a space character when unexpanded-writing a control-word-token to an external text-file.

    E.g.,

    \newwrite\mywrite
    \immediate\openout\mywrite experiment.tex %
    \immediate\write\mywrite{\noexpand\XXX{Something}}%
    \immediate\closeout\mywrite
    

    yields a file experiment.tex whose content is:

    \XXX{Something}

    Note the space character between \XXX and {Something}.

  • Thanks. As I said in the update to my question: I was dissuaded from this approach. However, it is still interesting that this is possible, and see a way to do so. – Boris Bukh Feb 12 at 20:45

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.