7

In the xparse package, there is the g type of argument which captures things inside a pair of TeX group tokens. This makes it possible to define commands \foo that behaves differently for \foo{a} and \foo a. I am interested in whether such type of macro is possible in plain TeX (I guess yes) and if it is possible, how can it be implemented. I am new to plain TeX and I appreciate detailed explanation of the workflow of such a macro. I would also be happy to learn about other possibilities such as in e-TeX instead of plain TeX.

Edit

Only after reading the answers did I realize that it was impossible at first look because all I was thinking was \def\foo#1... (and indeed it is impossible if this form is used).

  • It's possible, but it's bad syntax. Under normal TeX conventions, \foo a and \foo{a} should be considered equivalent (when the argument consists of a single token as in this case). – egreg Apr 6 at 21:18
  • Thank you for your quick reply. I know it's bad syntax otherwise xparse would not declare it as obsolete, but it is (extensively) used in e.g. physics package. I am just not sure about whether it can be done in plain, or it requires some features of the engine. – Weijun Zhou Apr 6 at 21:22
  • 1
    It's indeed used in physics. My opinion about the package is that it has good ideas, but I can't recommend its usage. The weird syntax is just one among the several reasons for not recommending it. – egreg Apr 6 at 21:23
  • 1
    Due to the weird syntax I end up adding \relax here and there ... but I guess I will still use it. – Weijun Zhou Apr 6 at 21:31
6

You can use \futurelet

\let\leftbracechar={
\def\foo{%
  \begingroup
  \futurelet\footemp\innerfoo
}%
\def\innerfoo{%
  \expandafter\endgroup
  \ifx\footemp\leftbracechar
  \expandafter\fooatleftbrace
  \else
  \expandafter\fooatnoleftbrace
  \fi
}%
\def\fooatleftbrace#1{Argument in braces is: {\bf #1}}
\def\fooatnoleftbrace#1{Argument without braces is: {\bf #1}}


\foo a

\foo{a}

\bye

enter image description here

, but be aware that this can be confused by implicit characters, i.e., by things like \foo\bgroup huh?...

Besides this, the check is only about tokens (be they explicit or implicit character tokens) where the category code is 1 (begin group) and the character code equals the character code of the curly-opening-brace-character. The check does not work out with character tokens where the category code is 1 (begin group) but the character code is different.

But you can implement a full expandable check which tells you whether the first token inside a macro argument is an explicit character token of category code 1 (begin group) no matter what its character code might be:

%%-----------------------------------------------------------------------------
%% Check whether argument's first token is an explicit catcode-1-character
%%.............................................................................
%% \UDCheckWhetherBrace{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked has leading
%%                       catcode-1-token>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked has no leading
%%                       catcode-1-token>}%
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\UDCheckWhetherBrace#1{%
  \romannumeral0\expandafter\secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
  \firstoftwo}{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
}%

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{{}Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{{Test}}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\leavevmode\hrulefill\null

% Now let's have some fun: Give [ the same functionality as {:
\catcode`\[=\the\catcode`\{

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{[}Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{[Test}}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\leavevmode\hrulefill\null

% Now let's see that the test on explicit characters is not fooled by implicit characters:
\let\bgroup={

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{\bgroup\egroup Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{\bgroup Test\egroup}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\leavevmode\hrulefill\null    

% The test is also not fooled by implicit active characters:
\catcode`\X=13
\let X={

\UDCheckWhetherBrace{Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{X\egroup Test}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%

\UDCheckWhetherBrace{X Test\egroup}%
                    {The first token of the arg is an explicit catcode 1 char.}%
                    {The first token of the arg is not an explicit catcode 1 char.}%


\bye

enter image description here


In order to see how \UDCheckWhetherBrace works, let's write it with different line-breaking and different indentation:

The gist is: Have the argument's first token hit by \string and use TeX's catching of brace-balanced arguments for finding out whether an opening brace or something else was neutralized/was turned into a catcode-12-sequence:

\long\def\UDCheckWhetherBrace#1{%
  \romannumeral0%
  \expandafter
  \secondoftwo%← This is the interesting \secondoftwo.
  \expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
  \expandafter{%
  \string#1.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
             %  , which itself must be brace-balanced, had an opening-brace as first token.
  \expandafter\firstoftwo\expandafter{\expandafter
  \secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
  \firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
              %  did not have an opening-brace as first token
  {\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
}%

For example

\UDCheckWhetherBrace{test}{brace}{no brace}%

yields:

\romannumeral0%
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string test.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
              %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string test.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
              %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields carrying out the \expandafter-chain and \string and thus stringifying "t" from the phrase "test":

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\secondoftwo%← This is the interesting \secondoftwo.
{%← This is the interesting \secondoftwo's first argument's opening brace.
{%
⟨character t, due to \string now of category code 12 (other)⟩est.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument
                                                                  %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields carrying out the interesting \secondoftwo:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter\expandafter\firstoftwo{ }{}\secondoftwo%
{brace}{no brace}%

yields carrying out \expandafter and \firstoftwo and thus delivering the space that was inside braces:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter⟨space token⟩\secondoftwo%
{brace}{no brace}%

yields carrying out \expandafter and \secondoftwo:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
⟨space token⟩no brace

Now \romannumeral finds the space token and discards it and aborts gathering digits. As by now it has only found the digit "0" which does not form a positive number, it does silently not deliver any token:

%\romannumeral expansion done:
no brace

For example

\UDCheckWhetherBrace{{test}}{brace}{no brace}%

yields:

\romannumeral0%
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string{test}.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
               %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter
\secondoftwo%← This is the interesting \secondoftwo.
\expandafter{%← This is the interesting \secondoftwo's first argument's opening brace.
\expandafter{%
\string{test}.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
               %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields carrying out the \expandafter-chain and \string and thus stringifying the left curly brace/the openening curly brace from the phrase "{test}":

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\secondoftwo%← This is the interesting \secondoftwo.
{%← This is the interesting \secondoftwo's first argument's opening brace.
{%
⟨character {, due to \string now of category code 12 (other)⟩test}.}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument
                                                                    %  , which itself must be brace-balanced, had an opening-brace as first token.
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields carrying out the interesting \secondoftwo:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields carrying out the \expandafter-chain and \string:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\firstoftwo{%
\secondoftwo⟨character }, due to \string now of category code 12 (other)⟩\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo}%← This is the interesting \secondoftwo's first argument's closing brace in case the argument 
            %  did not have an opening-brace as first token
{\expandafter\expandafter\firstoftwo{ }{}\secondoftwo}%
{brace}{no brace}%

yields carrying out \firstoftwo:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\secondoftwo⟨character }, due to \string now of category code 12 (other)⟩\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo
{brace}{no brace}%

yields carrying out \secondoftwo:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter\expandafter\firstoftwo{ }{}%
\firstoftwo
{brace}{no brace}%

yields carrying out \expandafter and \firstoftwo and thus delivering the space that was inside braces:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
\expandafter⟨space token⟩\firstoftwo%
{brace}{no brace}%

yields carrying out \expandafter and \firstoftwo:

%\romannumeral expansion in progress as by now \romannumeral only found the digit 0 and searches for more digits:
⟨space token⟩brace

Now \romannumeral finds the space token and discards it and aborts gathering digits. As by now it has only found the digit "0" which does not form a positive number, it does silently not deliver any token:

%\romannumeral expansion done:
brace
  • This is much more extensive. I will spend some time understanding and learning from it. I think the idea of checking catcode instead of char code is very interesting and useful. It would be great if some more explanation can be added for the latter case. – Weijun Zhou Apr 6 at 22:08
  • Thank you very much for your effort explaining how it works. – Weijun Zhou Apr 6 at 23:40
8

Fundamentally you just need to use \futurelet as you do for any other look ahead

\def\foo{\futurelet\footoken\fooaux}
\def\fooaux{%
  \ifx\footoken\bgroup
     % Brace group
   \else
     % Something else
   \fi
}

The only reason this 'looks different' to other peek ahead situations is that you can't use an explicit {, but rather the implicit token \bgroup.

  • That's clear enough and much simpler than I originally imagined. – Weijun Zhou Apr 6 at 21:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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