8

In another thread the question arose if it is possible to define a control sequence, or better control word, after that a space character changes the meaning of the command. That is, the two calls \foo{} and \foo {} should give different results.

I was told that it's possible using the g parameter option of the xparse package, but I wasn't able to find a solution that works. The best I could find is a version that handles a space as the second parameter:

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\foo}{gg}{%
    \IfNoValueTF{#1}{--}{(#1)}%
    \IfNoValueTF{#2}{--}{(#2)}%
}

\begin{document}
\foo{}{}xy

\foo{} {}xy
\end{document}

which results in

()()xy
()-xy


So the question is, is there any way to define such a command? IMHO, it's not possible at all, because of the way TeX's input processor works. After a control word has been built, TeX switches into state "skipping spaces" which gobbles all space characters (and possibly the line end character). That would mean, no matter what black magic was used to define \foo, the information about the space is already gone when \foo is being expanded.

Note that solutions using control characters (like \f {}), or changing catcodes before the macro is expanded (like \catcode`\ =12 \foo {}) do not count as legal solutions to this question.

2
  • you could only do it by changing the catcode of space not to be 10 (which would mean that you would have to do an awful lot of white space handling "by hand") Nov 10, 2016 at 11:10
  • 1
    do not always believe what you are told:-) Nov 10, 2016 at 11:13

7 Answers 7

4

Following the suggestion of David Carlisle to change the catcode of space, you may try this:

{\catcode`\ =12\relax%
\global\let\otherspace= %
\gdef\xfoosp {\foosp}}
\def\foo{\begingroup\catcode`\ =12\futurelet\nexttok\xfoo}
\def\xfoo{%
  \ifx\nexttok\otherspace
    \endgroup\expandafter\xfoosp
  \else
    \endgroup\expandafter\foonosp
  \fi}
\def\foosp#1{foo with space: arg=#1}
\def\foonosp#1{foo without space: arg=#1}

You may define \foosp and \foonosp as you like, even with different number of arguments.

Alternatively, the first 3 lines may be replaced with

\begingroup\lccode`+=`\ %
\lowercase{\endgroup
\let\otherspace=+
\def\xfoosp+{\foosp}}

which keeps all definitions local.

9
  • I recall when I first realized a control sequence could change the catcode of the space immediately following it and how shocked I was (as this contradicted how I had pictured TeX's parsing so far). Of course the main objection here is that this will not be usable inside macro arguments.
    – user4686
    Nov 10, 2016 at 18:27
  • @Eric Wow, that is really astonishing! I understand how your code works, but I still didn't expect that to work with space tokens. Even the TeXbook doesn't seem to have a clear explanation for why this works. Can you explain why the space isn't already skipped when the control sequence is being built?
    – siracusa
    Nov 11, 2016 at 5:57
  • 1
    @siracusa The phrase "TeX ignore spaces after a control word" is actually a bit simplistic. The truth is (as explained in chap. 8 of the TeXbook) that TeX enters state S (skipping blanks) after a control word or a space. The trick is to assign a catcode 12 to the space after \foo with \futurelet. It is not a real space anymore and is not skipped. By the way, it works repeatedly. If you say def\foosp{<sp>\foo}\def\foonosp#1{foonosp:#1} and type \foo x (3 spaces), you get <sp><sp><sp>x. This means that even the rule "several spaces equal one space" is not completely true. Nov 11, 2016 at 9:52
  • Oops. You get <sp><sp><sp>foonosp:x. Nov 11, 2016 at 9:53
  • TeX by Topic states "If a character token has been \futurelet to a control sequence, its category code is fixed." I thought this were the magic trick here to make the spaces not disapear. But \foo x even works if we skip the \futurelet and use the simpler definition \def\foo{\begingroup\catcode`\ =12\relax\expandafter\endgroup\xfoosp}. So there's actually no magic going on here, it's just that my understanding of when the space skipping happens was completely wrong. Many thanks for the rectification!
    – siracusa
    Nov 11, 2016 at 11:52
4

For the reasons that you state in the question this can't be done.

What you might have seen re xparse is discussion of whether xparse should or should not skip over spaces while looking for arguments. This makes no difference in your \foo {} case but does make a difference in \\{} or \\ {} (spaces not dropped after a command symbol with a name being a single letter of catcode other than 11) or in \foo{} {} spaces not dropped after } so affecting arguments other than the first.

LaTeX's \@ifnextchar which is used to look ahead for * forms and optional [] arguments and other things is a wrapper around \futurelet mainly to skip spaces. this means that \\[2pt] and \\ [2pt] are the same, unless amsmath is loaded which changes this so that white space is significant so that common cases like

\begin{align}
zzzaa\\
[1,2] \in zzzz
\end{align}

The white space after \\ allows the [1,2] to be taken as characters to be typeset rather than an argument to \\ (which would generate an error as it is not a legal length syntax as required by that argument).

So in short, when defining an argument parser you have a choice to discard or keep space tokens that are before any arguments that you are picking up, but you can not keep things that are not there, and there is no space token after \foo If yo do define \foo in a way that a following space token is significant then you would have to call it via

\def\zzz#1{#1}
\zzz{\foo} {}

or

\expandafter\foo\space{}

or some other syntax that produced a space token at that point.

4

By applying the #{-notation, you can define macros whose last argument is delimited by an opening brace. Unlike with other argument delimiters that get removed when gathering arguments, TeX will leave a delimiting opening brace in place.
(Actually with TeX-releases prior to TeX Version 3.141592653—things were changed in the 2021 TeX-TuneUp—the mechanism isn't restricted to opening brace character tokens. With TeX-releases prior to TeX Version 3.141592653 you can use any token whose category code is 1 at definition time. Could as well be #\WeIrd after \let\WeIrd={  .)
Delimited arguments can be empty.

Therefore for obtaining a control sequence token from a set of character tokens that form the name of the control sequence token in question both for defining and for calling that control sequence token, you can (by applying the #{-notation) invent a single control sequence \name which processes a brace delimited argument trailed by an undelimited argument (which is nested in braces). After having TeX fetch the arguments, you can have TeX whirl them around and apply \csname..\endcsname to the argument supplied inside braces. The name of the control sequence token in question can contain space tokens as well.

\makeatletter
%
\newcommand\name{}%
\long\def\name#1#{\UD@innername{#1}}%
%
\newcommand\UD@innername[2]{%
  \expandafter\UD@exchange\expandafter{\csname#2\endcsname}{#1}%
}%
%
\newcommand\UD@exchange[2]{#2#1}%
%
\makeatother

\name foo{bar} → expansion step 1:
\UD@innername{foo}{bar} → expansion step 2:
\expandafter\UD@exchange\expandafter{\csname bar\endcsname}{foo} → expansion step 3:
\UD@exchange{\bar}{foo} → expansion step 4:
foo\bar  .

In expansion contexts you would need four \expandafter-chains for obtaining the result.

As \romannumeral does not produce any token when encountering a non-positive number, you can add a bit of \romannumeral-expansion in order to reduce the amount of \expandafter-chains.

Either do \romannumeral\name0 foo{bar}. This way only one \expandafter-chain hitting the \romannumeral-token is needed.

Or have the \romannumeral-expansion "hardcoded" within the definition - this way two \expandafter-chains are needed. The first one for obtaining the topl-level-expansion of \name. The second one for inducing \romannumeral-expansion.

\makeatletter
%
\newcommand\name{}%
\long\def\name#1#{\romannumeral0\UD@innername{#1}}%
%
\newcommand\UD@innername[2]{%
  \expandafter\UD@exchange\expandafter{\csname#2\endcsname}{ #1}%
}%
%
\newcommand\UD@exchange[2]{#2#1}%
%
\makeatother

With such a macro you are not bound to specific definition commands:

\name{foo}\foo  .

\name\newcommand{foo}\newcommand\foo  .

\name\DeclareRobustCommand{foo}\DeclareRobustCommand\foo  .

\name\global\long\outer\def{foo}\global\long\outer\def\foo  .

\name\expandafter{foo}\bar\expandafter\foo\bar  .

\name\let{foo}=\bar\let\foo=\bar  .

\name\string{foo}\string\foo  .

\name\meaning{foo}\meaning\foo  .

You can as well use such a macro for defining/calling macros whose names contain spaces:

\name{foo }\foo␣  .

\name\newcommand{foo }\newcommand\foo␣  .

\name\DeclareRobustCommand{foo }\DeclareRobustCommand\foo␣  .

\name\global\long\outer\def{foo }\global\long\outer\def\foo␣  .

\name\expandafter{foo }\bar\expandafter\foo␣\bar  .

\name\let{foo }=\bar\let\foo␣=\bar  .

\name\string{foo }\string\foo␣  .

\name\meaning{foo }\meaning\foo␣  .

You can also nest the calls of \name:

Example 1:

   \name\name\expandafter{f o o }{b a r }

Processing the first \name yields:
   \name\expandafter\f␣o␣o␣{b a r }  .

Processing the second \name yields:
   \expandafter\f␣o␣o␣\b␣a␣r␣  .

(Analogously: \name\name\let{f o o }={b a r }\let\f␣o␣o␣=\b␣a␣r␣.)

Example 2:

   \name\name\name\expandafter\expandafter\expandafter{f o o }\expandafter{b a r }{c r a z y }

Processing the first \name yields:
   \name\name\expandafter\expandafter\expandafter\f␣o␣o␣\expandafter{b a r }{c r a z y }  .

Processing the second \name yields:
   \name\expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣{c r a z y }  .

Processing the third \name yields:
   \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣\c␣r␣a␣z␣y␣  .

Example 3:

In expansion contexts you can use \romannumeral-expansion in order to keep things going.

   \romannumeral\name\name\name0 \expandafter\expandafter\expandafter{f o o }\expandafter{b a r }{c r a z y }

\romannumeral keeps expanding until it has found some number. In the end it will find the number0 while with non-positive numbers \romannumeral will not deliver any token:
   %\romannumneral-expansion in progress
   \name\name\name0 \expandafter\expandafter\expandafter{f o o }\expandafter{b a r }{c r a z y }

Processing the first \name yields:
   %\romannumneral-expansion in progress
   \name\name0 \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter{b a r }{c r a z y }  .

Processing the second \name yields:
   %\romannumneral-expansion in progress
   \name0 \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣{c r a z y }  .

Processing the third \name yields:
   %\romannumneral-expansion in progress
   0 \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣\c␣r␣a␣z␣y␣  .

Now \romannumeral finds the number 0 . Therefore \romannumeral-expansion gets aborted and \romannumeral won't deliver any token:
   \expandafter\expandafter\expandafter\f␣o␣o␣\expandafter\b␣a␣r␣\c␣r␣a␣z␣y␣  .

Be aware that \name internally applies \csname while applying \csname as a side effect yields assigning the control sequence in question the meaning of the \relax-primitive in case the control sequence in question was undefined before applying \csname. That assignment will be restricted to the current scope even if the \globaldefs-parameter had a positive value at the time of applying \csname.

%%\errorcontextlines=1000
\documentclass[a4paper]{article}
\usepackage{textcomp}%

\makeatletter
\newcommand\name{}%
\long\def\name#1#{\romannumeral0\UD@innername{#1}}%
\newcommand\UD@innername[2]{%
  \expandafter\UD@exchange\expandafter{\csname#2\endcsname}{ #1}%
}%
\newcommand\UD@exchange[2]{#2#1}%
\makeatother


%\newcommand\foo[2]{%
\name\newcommand{foo}[2]{%
  \noindent
  Control sequence whose name does not contain any space.\\
  Argument 1: \textit{\textlangle#1\textrangle}\\
  Argument 2: \textit{\textlangle#2\textrangle}\\
}%

\name\newcommand{foo }[2]{%
  \noindent
  Control sequence whose name has a trailing space.\\
  Argument 1: \textit{\textlangle#1\textrangle}\\
  Argument 2: \textit{\textlangle#2\textrangle}\\
}%

\name\newcommand{ f o o }[2]{%
  \noindent
  Control sequence whose name is interspersed with spaces.\\
  Argument 1: \textit{\textlangle#1\textrangle}\\
  Argument 2: \textit{\textlangle#2\textrangle}\\
}%


\begin{document}

\name{foo}{Arg 1}{Arg 2}

\name{foo }{Arg 1}{Arg 2}

\name{ f o o }{Arg 1}{Arg 2}

Nesting \texttt{\string\name}:

\name\expandafter\newcommand\expandafter*\expandafter{C o N f u SiO n}\expandafter{%
  \romannumeral\name\name\name0 %
  \expandafter\expandafter\expandafter{F O O}\expandafter{B A R}{C R A Z Y}%
}%
\texttt{\name\string{C o N f u SiO n} is \name\meaning{C o N f u SiO n}}%
\\

Playing around with grouping:

%Be aware that \texttt itself opens up a new scope for typesetting its argument.

%\globaldefs=1\relax

\texttt{%
  \begingroup\name\string{w e i r d } is  \name\endgroup\meaning{w e i r d }%
}%

\texttt{%
  \name\string{w e i r d } is  \name\meaning{w e i r d }%
}%

\end{document}
2
2

Do you need to distinguish the case where the space is wrapped into braces from the case where the space is not wrapped into braces? I. e., \foo{A}{B} versus \foo {A}{B} versus \foo{ }{A}{B} ?

You can use the #{-notation in order to define a macro where the control word itself is delimited by a non letter character token and where the first argument is delimited by an opening brace.

Due to the non-letter-character TeX's reading apparatus will switch to state m (middle of line) where blanks won't be skipped before tokenizing the brace-delimited argument. Unlike with undelimited arguments where preceding space tokens will be omitted, space tokens will not be omitted with delimited arguments. Therefore you can have TeX check whether that first brace-delimited argument contains a leading space and fork accordingly.

%%\errorcontextlines=1000
\documentclass[a4paper]{article}
\usepackage{textcomp}
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange
%%    \UD@CheckWhetherNull, \UDAtCheckWhetherLeadingSpace, \UD@gobblespace
%%=============================================================================
\makeatletter
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@gobblespace[1]{%
  \def\UD@gobblespace#1{}%
}\UD@gobblespace{ }%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
%% A concern in his posting is that the argument is hit with \string
%% after some expansions which in edge cases might result in unbalancing
%% surrounding \if..\fi-constructs if the macro is used inside of such
%% \if..\fi-constructs.
%%
%% That challenging concern sickened me. ;-)
%%
%% Therefore I decided to implerment a variant where this cannot happen
%% as expansion is forced by \romannumeral:
%%
%% After the first expansion-step, \string is not applied yet.
%% After the second expansion-step, any possibly disturbing remainders
%% are already removed due to \romannumeral-expansion.
%%
%% No eTeX- or whatsoever extensions. No \if.. .Only \romannumeral,
%% digit 0, space token for terminating \romannumeral-expansion,
%% \string, \expandafter, \UD@firstoftwo, \UD@secondoftwo, {, }.
%%
%% May 20, 2016
%%
%% Ulrich Diez (e-mail: [email protected])
%%
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%=============================================================================
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UDAtCheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                         {<Tokens to be delivered in case <argument
%%                            which is to be checked>'s 1st token is a
%%                            space-token>}%
%%                         {<Tokens to be delivered in case <argument
%%                           which is to be checked>'s 1st token is not
%%                           a space-token>}%
\newcommand\UDAtCheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%=============================================================================
%% \foo.{<arg1>}{<arg2>}  or  \foo.<space token>{<arg1>}{<arg2>}
%%
%% !!Be aware that there must be the dot and that the first and/or the 
%% !!second argument must be nested in braces.!!
%%.............................................................................
\newcommand\foo{}%
\long\def\foo.#1#{%
  \UDAtCheckWhetherLeadingSpace{#1}{%
   \expandafter\UD@Exchange\expandafter{\UD@gobblespace#1}{\innerfoo{There was a leading space}}%
  }{%
     \innerfoo{There was no leading space}#1%  
  }%
}%
\newcommand\innerfoo[3]{%
  \noindent
  First Argument: \textit{\textlangle#1\textrangle}\\%
  Second Argument: \textit{\textlangle#2\textrangle}\\%
  Third Argument: \textit{\textlangle#3\textrangle}%  
}%
\makeatother


\begin{document}

\foo.{Arg1}{Arg2}

\foo.  {Arg1}{Arg2}

\foo.{Arg1}2

\foo.  {Arg1}2

\foo.1{Arg2}

\foo.  1{Arg2}

\end{document}

If you don't need an expandable solution, you can try with \futurelet. But this can be fooled.

%%\errorcontextlines=1000
\documentclass[a4paper]{article}
\usepackage{textcomp}
\makeatletter
%%=============================================================================
%% \foo.{<arg1>}{<arg2>}  or  \foo.<space token>{<arg1>}{<arg2>}
%%
%% !!Be aware that there must be the dot and that the first and/or the
%% !!second argument must be nested in braces.!!
%%.............................................................................
\newcommand\foo{}%
\long\def\foo.{%
   \begingroup
   \futurelet\fooA\fooB
}%
\expandafter\newcommand\expandafter\fooB\expandafter{%
  \@firstofone{\expandafter\endgroup\ifx} \fooA %<-There will be space-token behind \ifx
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\innerfoo{There was a space token}}%
  {\innerfoo{There was no space token}}%
}%
\newcommand\innerfoo[3]{%
  \noindent
  First Argument: \textit{\textlangle#1\textrangle}\\%
  Second Argument: \textit{\textlangle#2\textrangle}\\%
  Third Argument: \textit{\textlangle#3\textrangle}%
}%
\makeatother


\begin{document}

\foo.{Arg1}{Arg2}

\foo.  {Arg1}{Arg2}

\foo.{Arg1}2

\foo.  {Arg1}2

\foo.1{Arg2}

\foo.  1{Arg2}

Now fooling around:

% Assign the control-sequence \myspace the meaning of a
% space-token (character code 32, category code 10).
% Due to the synopsis of \let it must be ensured that <optional space> 
% is provided behind equal-sign when \letting a cs equal to a space token.
\csname @firstofone\endcsname{\let\myspace= } %

\texttt{\string\myspace=\meaning\myspace}%

\foo.\myspace{Arg1}{Arg2}

\foo.\myspace{Arg1}2

\foo.\myspace1{Arg2}

\end{document}
1

How about the xspace package?

\documentclass{standalone}
\usepackage{xspace}
\newcommand{\hello}{Hello\xspace}
\begin{document}
\hello world!
\end{document}

example

1
  • 4
    xspace does not distinguish \foo{} and \foo {} as requested in the question. Nov 10, 2016 at 11:12
1

I don't know the right TeX terminology here, but single letter commands such as \1, \%, etc. behave differently from regular commands. For example (ConTeXt code, but the same should work with other flavors of TeX):

\def\1{\futurelet\nexttoken\doWithNextChar}
\def\doWithNextChar%
    {\ifx\nexttoken\blankspace
       \expandafter\WithSpace
     \else
       \expandafter\WithoutSpace
     \fi}

\def\WithSpace#1{Command with space: «#1»}
\def\WithoutSpace#1{Command without space: «#1»}

\let\foo\1

\starttext
\startlines
\1 12
\11 2
\stoplines
\stoptext

which gives:

enter image description here

2
  • single letter commands with the "letter" being a "non-letter" ;-)
    – user4686
    Nov 15, 2016 at 18:00
  • 1
    Kinds of control sequences: 1. Control word tokens. 2. Control symbol tokens. 3. Active character tokens. Names of control symbols consist of a single character whose catcode is not 11. Thus by changing the catcode of the character in question you can change whether TeX treats the thing as a control word or as a control symbol: When reading from file, spaces behind control words do not get tokenized while spaces behind control symbols do get tokenized. TeX will attach a space when unexpanded-writing a control word to text-file while it does not do so when unexpanded-writing a control-symbol. Nov 15, 2016 at 18:32
0

Defining it is not hard. Rather, using it is the hard part because, natively, latex will not see the difference between \foo and \foo . One way to do this is using commands from the etoolbox package:

\documentclass{article}
\usepackage{etoolbox}
\csdef{foo }{Foo with space}
\csdef{foo}{Foo witohut space}
\begin{document}

    \foo

    \csuse{foo }
\end{document}

Note that \csuse{foo } explicitly adds the space to the command name.

You must log in to answer this question.

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