Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

TeX refuses to strip outer braces in argument

117 views
Skip to first unread message

Ahmed Musa

unread,
Nov 10, 2010, 5:30:31 PM11/10/10
to

I wanted braces stripped from some tokens/strings. So I created the
following \stripbrace command. It works for all simple cases, but
fails in some complicated cases for which I can't reproduce minimal
examples (the complete code is too long for posting). It fails by
repeating the loop ad infinitum, each time refusing to strip the
outermost braces. Can anyone tell me the circumstances under which TeX
may refuse to strip braces in arguments of macros? Perhaps someone
will suggest an entirely different approach.

\def\afterelsefi#1\else#2\fi{\fi#1}
\def\stripbrace{%
\begingroup
\catcode`\\\z@
\catcode`\{\@ne
\catcode`\}\tw@
\@stripbrace
}
\def\@stripbrace#1\@nil#2{%
\def\tempa{\futurelet\next\tempb}%
\def\tempb##1\@nil{%
\ifx\next\bgroup
\afterelsefi\tempa##1\@nil
\else
\toks@{\edef#2{\unexpanded{##1}}}%
\fi
}%
\tempa#1\@nil
\expandafter\endgroup\the\toks@
}

% Works here but fails in some cases that can't reproduced compactly:
\def\tempa#1{\stripbrace#1\@nil\x}
\tempa{{x,y}}

Heiko Oberdiek

unread,
Nov 10, 2010, 8:49:52 PM11/10/10
to
Ahmed Musa <ahmedm...@gmail.com> wrote:

> I wanted braces stripped from some tokens/strings. So I created the
> following \stripbrace command. It works for all simple cases, but
> fails in some complicated cases for which I can't reproduce minimal
> examples (the complete code is too long for posting). It fails by
> repeating the loop ad infinitum, each time refusing to strip the
> outermost braces. Can anyone tell me the circumstances under which TeX
> may refuse to strip braces in arguments of macros? Perhaps someone
> will suggest an entirely different approach.
>
> \def\afterelsefi#1\else#2\fi{\fi#1}
> \def\stripbrace{%
> \begingroup
> \catcode`\\\z@
> \catcode`\{\@ne
> \catcode`\}\tw@
> \@stripbrace
> }
> \def\@stripbrace#1\@nil#2{%
> \def\tempa{\futurelet\next\tempb}%
> \def\tempb##1\@nil{%
> \ifx\next\bgroup

The end test is faulty:
* The first token might be \bgroup itself.
* The token list can start with a group: {a}b.
The first brace is detected, but it's not part
of surrounding braces and therefore doesn't
get stripped.

> \afterelsefi\tempa##1\@nil
> \else

##1 inside the branches of an \if is asking for
trouble. ##1 might contain unmatched \if, \else
\or, \fi.

> \toks@{\edef#2{\unexpanded{##1}}}%
> \fi
> }%
> \tempa#1\@nil
> \expandafter\endgroup\the\toks@
> }
>
> % Works here but fails in some cases that can't reproduced compactly:
> \def\tempa#1{\stripbrace#1\@nil\x}
> \tempa{{x,y}}

% Setup for iniTeX
\catcode`\{=1
\catcode`\}=2
\catcode`\#=6
\catcode`\@=11

% The macros
\long\def\GobbleTwo#1#2{}
\long\def\StripOuterBraces#1#2{%
\edef#2{\unexpanded{#1}}%
\@StripOuterBraces#1\@StripOuterBraces@NIL#2%
}
\long\def\@StripOuterBraces#1\@StripOuterBraces@NIL#2{%
\edef\@StripOuterBraces@temp{\unexpanded{#1}}%
\ifx#2\@StripOuterBraces@temp
\expandafter\GobbleTwo
\else
\expandafter\StripOuterBraces
\fi
{#1}#2%
}

% Testing
\def\msg#{\immediate\write16}
\long\def\Test#1#2{%
\edef\InputString{\unexpanded{#1}}%
\msg{ Input: [\meaning\InputString]}%
\edef\Expect{\unexpanded{#2}}%
\msg{Expect: [\meaning\Expect]}%
\StripOuterBraces{#1}\Result
\msg{Result: [\meaning\Result]}%
\ifx\Expect\Result
\msg{==> OK}%
\else
\msg{!!! FAILED !!!}%
\fi
}
\Test{}{}
\Test{{}}{}
\Test{{{{}}}}{}
\Test{a}{a}
\Test{ab}{ab}
\Test{{a}}{a}
\Test{{{{a}}}}{a}
\Test{{a}b}{{a}b}
\Test{a{b}}{a{b}}
\Test{\else}{\else}
\Test{{\else}}{\else}
\Test{{{\else}}}{\else}
\Test{\fi}{\fi}
\Test{{\fi}}{\fi}
\Test{{{\fi}}}{\fi}
\Test{\par}{\par}
\Test{{\par}}{\par}
\Test{#}{#}
\Test{{#}}{#}
\Test{{{#}}}{#}

% End job
\csname @@end\endcsname\end

--
Heiko Oberdiek

Ulrich D i e z

unread,
Nov 10, 2010, 9:30:28 PM11/10/10
to
Ahmed Musa wrote:

> I wanted braces stripped from some tokens/strings. So I created the
> following \stripbrace command. It works for all simple cases, but
> fails in some complicated cases for which I can't reproduce minimal
> examples (the complete code is too long for posting). It fails by
> repeating the loop ad infinitum, each time refusing to strip the
> outermost braces. Can anyone tell me the circumstances under which TeX
> may refuse to strip braces in arguments of macros?

TeX does not refuse stripping braces from arguments.

The code yields an endless loop in case the argument
consists - after stripping all brace-levels that surround
the _entire_ argument - either of a leading \bgroup-token
(or something let equal to \bgroup) or of several
components whereof the first component is nested into
braces.

E.g., the following argument-patterns yield an endless loop:

- {{{first part}more stuff}}
- {\bgroup more stuff}

You might wish to alter your loop so that it also checks whether
applying \@gobble to the entire argument yields emptiness.

If so, you need to be careful about leading-space-tokens as
leading space-tokens get silently swallowed when \@gobble
fetches its (undelimited) argument.

> Perhaps someone
> will suggest an entirely different approach.
>
> \def\afterelsefi#1\else#2\fi{\fi#1}
> \def\stripbrace{%
> \begingroup
> \catcode`\\\z@
> \catcode`\{\@ne
> \catcode`\}\tw@
> \@stripbrace
> }
> \def\@stripbrace#1\@nil#2{%
> \def\tempa{\futurelet\next\tempb}%
> \def\tempb##1\@nil{%
> \ifx\next\bgroup
> \afterelsefi\tempa##1\@nil
> \else
> \toks@{\edef#2{\unexpanded{##1}}}%
> \fi
> }%
> \tempa#1\@nil
> \expandafter\endgroup\the\toks@
> }
>
> % Works here but fails in some cases that can't reproduced compactly:
> \def\tempa#1{\stripbrace#1\@nil\x}
> \tempa{{x,y}}

In case not the entire argument of \tempb, but only a first/
leading component thereof is nested into braces or starts
with a token equal to \bgroup, the \ifx-comparison within
\@stripbrace will end up in the "true" branch although not
the entire argument is nested into braces and thus no
more brace-levels will be removed from the entire
\@nil-delimited argument.
This will happen again and again -> endless loop.

By the way: As \tempa within \@stripbrace doesn't process
any argument, you don't need it and could instead have
\@stripbrace iterate directly:

\catcode`\@=11


\def\stripbrace{%
\begingroup
\catcode`\\\z@
\catcode`\{\@ne
\catcode`\}\tw@

\futurelet\next\@stripbrace
}
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\@gobble#1{}
\def\@stripbrace#1\@nil#2{%
\ifx\next\bgroup
\expandafter\ifx\expandafter$\detokenize\expandafter{\@gobble#1}$%
\expandafter\expandafter\expandafter\@firstoftwo
\else
\expandafter\expandafter\expandafter\@secondoftwo
\fi
\else
\expandafter\@secondoftwo
\fi
{\futurelet\next\@stripbrace#1\@nil#2}%
{%
\toks@{\endgroup\edef#2{\unexpanded{#1}}}%
\the\expandafter\toks@
}%
}
% Works here


\def\tempa#1{\stripbrace#1\@nil\x}
\tempa{{{{x,y}}}}

\show\x
\tempa{{{{x},y}}}
\show\x
\tempa{{{ {x},y}}}
\show\x
\csname stop\endcsname \bye


But this code still might also behave in an undesired way in case
the argument has a leading \bgroup-control-sequence-token or
something let equal to a leading \bgroup-control-sequence-token.
Within this code it is relied on \@nil not appearing in the argument.
The code still requires a lot of \futurelet-assignments and it also
does not take into consideration other combinations of
catcode-1/2-character-tokens.
Therefore I suggest a completely different expandable method
for the brace-stripping-loop:

\catcode`\@=11
%%---------------------------------------------------------------
%% Stopper for \romannumeral-Expansion:
%%...............................................................
\long\def\@rmstop{0 }
%%---------------------------------------------------------------
%% Nice stuff:
%%...............................................................
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\@firstofone#1{#1}
\long\def\@gobble#1{}
\newtoks\@temptokena
%%---------------------------------------------------------------
%% Check if argument is null:
%% The \@ifnull-macro is derived from
%% Robert R Schneck's \ifempty-macro
%% [ news:3eef1...@corp.newsgroups.com
%% Newsgroups: comp.text.tex,
%% Subject:
%% Re: \ifempty solution (was Macro puzzle: maximally general \ifempty)
%% Message-ID: <3eef1...@corp.newsgroups.com>
%% Date: 17 Jun 2003 08:42:50 -0500 ].
%%...............................................................
\long\def\@ifnull#1{%
\romannumeral\expandafter\@firstofone\expandafter{\expandafter
\expandafter\expandafter\@rmstop\csname @\expandafter\@gobble\string{%
\expandafter\@secondoftwo\expandafter{\expandafter{\string#1}%
{}\expandafter\expandafter\expandafter\@gobble\expandafter
\@gobble\string}{}\expandafter\expandafter\expandafter
\@firstoftwo\expandafter\expandafter\expandafter{%
\expandafter\@gobble\string}second}{first}oftwo\endcsname}%
}
%%---------------------------------------------------------------
%% Check if brace-balanced argument starts with a catcode-1-
%% character-token:
%%...............................................................
\long\def\@ifbrace#1{%
\romannumeral\expandafter\@firstofone\expandafter{%
\expandafter\expandafter\expandafter\@rmstop
\csname @%
\expandafter\@gobble\string{%
\expandafter\@gobble
\expandafter{%
\expandafter{%
\string#1}%
\expandafter\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\expandafter\expandafter
\@firstoftwo\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter\@gobble\expandafter
\expandafter\expandafter\@gobble\expandafter\expandafter
\expandafter{%
\expandafter\string
\expandafter}%
\string}%
\expandafter\@gobble\string}%
\@secondoftwo
{first}{second}oftwo\endcsname
}%
}
%%---------------------------------------------------------------
%% \@stripbrace{#1} spits out #1 with all surrounding
%% brace-levels removed - takes at most 2 expansion-steps:
%%...............................................................
\long\def\@stripbrace#1{%
\romannumeral\@ifbrace{#1}{%
\expandafter\@ifnull
\expandafter{\@gobble#1}{%
\expandafter\@gobble\@stripbrace#1%
}{\@rmstop#1}%
}{\@rmstop#1}%
}
%%---------------------------------------------------------------
%% \stripbrace{#1}{#2} defines #2 to expand to #1 with all
%% surrounding brace-levels removed:
%%...............................................................
\ifx\unexpanded\UnDeDFinEd
\long\def\stripbrace#1#2{%
\toks@\expandafter{\expandafter\toks@\expandafter{\the\toks@}}%
\begingroup
\@temptokena{%
\toks@\expandafter\expandafter\expandafter{\@stripbrace{#1}}%
\edef#2{\the\toks@}%
}%
\toks@\expandafter{\expandafter\endgroup
\the\expandafter\@temptokena\the\toks@}%
\the\toks@
}%
\else
\long\def\stripbrace#1#2{%
\edef#2{%
\unexpanded\expandafter\expandafter\expandafter{\@stripbrace{#1}}%
}%
}%
\fi
%%---------------------------------------------------------------
%% Testing:
%%...............................................................
\stripbrace{{{{x,y}}}}{\x}
\show\x
\stripbrace{{{{x},y}}}{\x}
\show\x
\stripbrace{{{ {x},y}}}{\x}
\show\x
\stripbrace{{{{\bgroup,y}}}}{\x}
\show\x
\stripbrace{{{{\bgroup,\@nil y}}}}{\x}
\show\x
\stripbrace{{{{ x,y}}}}{\x}
\show\x
% Be aware of the space in front of the innermost surrounding braces -
% neither the space nor the braces wil be removed.
\stripbrace{{{ {x,y}}}}{\x}
\show\x
\stripbrace{{{#1{x,y}}}}{\x}
\show\x
\catcode`\X=13 \letX={
\catcode`\Y=13 \letY=}
\stripbrace{{{X{x,y}Y}}}{\x}
\show\x
\catcode`\X=1
\catcode`\Y=2
\stripbrace{{{X{x,y}Y}}}{\x}
\show\x
\csname stop\endcsname \bye


Ulrich

Message has been deleted

Ulrich D i e z

unread,
Nov 11, 2010, 4:56:33 AM11/11/10
to
This posting supersedes my posting
news:ibfln9$afl$1...@news.albasani.net

I overlooked in my prior postings that for some
reason you have switched catcodes of \, { and } before
starting brace-removal-loop.

So I turned \@stripbrace into \@@stripbrace
and \stripbrace into \@stripbrace
and introduced another macro \stripbrace
which ensures that catcodes are switched before
it comes to reading \@stripbrace's arguments.

Ulrich

%% \@@stripbrace{#1} spits out #1 with all surrounding


%% brace-levels removed - takes at most 2 expansion-steps:
%%...............................................................

\long\def\@@stripbrace#1{%


\romannumeral\@ifbrace{#1}{%
\expandafter\@ifnull
\expandafter{\@gobble#1}{%

\expandafter\@gobble\@@stripbrace#1%


}{\@rmstop#1}%
}{\@rmstop#1}%
}
%%---------------------------------------------------------------

%% \@stripbrace{#1}{#2} defines #2 to expand to #1 with all


%% surrounding brace-levels removed:
%%...............................................................
\ifx\unexpanded\UnDeDFinEd

\long\def\@stripbrace#1#2{%


\toks@\expandafter{\expandafter\toks@\expandafter{\the\toks@}}%
\begingroup
\@temptokena{%

\endgroup
\toks@\expandafter\expandafter\expandafter{\@@stripbrace{#1}}%


\edef#2{\the\toks@}%
}%

\toks@\expandafter{\the\expandafter\@temptokena\the\toks@}%
\the\toks@
}%
\else
\long\def\@stripbrace#1#2{%
\edef#2{%
\unexpanded\expandafter\expandafter\expandafter{\@@stripbrace{#1}}%
}%
}%
\fi
%%---------------------------------------------------------------
%% \stripbrace{#1}{#2} switches catcodes of \,{,} before reading
% argument #1 and #2 and then defines #2 to expand to #1 with all


%% surrounding brace-levels removed:
%%...............................................................

\def\stripbrace{%
\begingroup
\catcode`\\\z@
\catcode`\{\@ne
\catcode`\}\tw@

\expandafter\endgroup\@stripbrace
}
%%...............................................................

%%---------------------------------------------------------------
%% Testing:
%%...............................................................
\toks@{content of toks@}
\@temptokena{content of temptokena}

\stripbrace{{{X{}Y}}}{\x}
\show\x
\stripbrace{}{\x}
\show\x
\catcode`\{=12


\stripbrace{{{{x,y}}}}{\x}
\show\x

\showthe\catcode`\{
\showthe\toks@
\showthe\@temptokena
\csname stop\endcsname \bye

Ahmed Musa

unread,
Nov 11, 2010, 1:38:22 PM11/11/10
to
On Nov 11, 9:56 am, Ulrich D i e z <eu_angel...@web.de> wrote:
> This posting supersedes my postingnews:ibfln9$afl$1...@news.albasani.net
> %%     Message-ID: <3eef1ad...@corp.newsgroups.com>

Many thanks for the contributions from Heiko and Ulrich. I will study
your responses after office hours tomorrow. In the meantime I can
again see Ulrich's fondness for \expandafter.

Ulrich D i e z

unread,
Nov 11, 2010, 2:34:40 PM11/11/10
to
Ahmed Musa wrote:

> Many thanks for the contributions from Heiko and Ulrich. I will study
> your responses after office hours tomorrow. In the meantime I can
> again see Ulrich's fondness for \expandafter.

I'm not fond of \expandafter. ;-)
Maybe I use that token here and there, but "fondness" definitely
is not a reason for doing so.

I just didn't see how to get a reliable solution with underlying
_expandable_ macros that are \if..\else..\fi-proof and also
might work within alignments without \expandafter.

For eample you can use \@@stripbrace within an \edef
or within a \csname.
E.g., \csname @fi\@@stripbrace{{{rstof}}}one\endcsname
yields the token \@firstofone .
Afaik such things are not possible with the other solutios
presented so far.

I use \romannumeral-expansion and \expandafter in order
to create macros where on user-level you don't have to
use \expandafter that often in situations where you need to
have control over the moment when you obtain the result.
Due to \romannumeral-expansion you know that exactly
two expansion-steps / \expandafter-chains are needed for
obtaining the result.

Ulrich

Robin Fairbairns

unread,
Nov 12, 2010, 4:56:04 AM11/12/10
to
Ulrich D i e z <eu_an...@web.de> writes:

> Ahmed Musa wrote:
>
>> Many thanks for the contributions from Heiko and Ulrich. I will study
>> your responses after office hours tomorrow. In the meantime I can
>> again see Ulrich's fondness for \expandafter.
>
> I'm not fond of \expandafter. ;-)
> Maybe I use that token here and there, but "fondness" definitely
> is not a reason for doing so.

i agree. \futurelet is a far more exciting name -- one imagines it
echoing down a dusty hall that's seen better days ... and then the doors
are flung open ... in next to no time it's as starkly elegant as it ever
was in mediaeval days.
--
Robin Fairbairns, Cambridge

Message has been deleted

Ulrich D i e z

unread,
Nov 12, 2010, 2:17:44 PM11/12/10
to
This posting supersedes my posting
news:ibk316$c9g$1...@news.albasani.net

Ahmed Musa wrote:

> Many thanks for the contributions from Heiko and Ulrich. I will study
> your responses after office hours tomorrow. In the meantime I can
> again see Ulrich's fondness for \expandafter.

I think (<-uh, uh) that -- not due to "fondness for \expandafter" but
due to my silliness -- I overloked in my previous postings that
both \@ifbrace and \@ifnull can be shortened.

Ulrich

\catcode`\@=11
%%---------------------------------------------------------------
%% Stopper for \romannumeral-Expansion:
%%...............................................................
\long\def\@rmstop{0 }
%%---------------------------------------------------------------
%% Nice stuff:
%%...............................................................
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\@firstofone#1{#1}
\long\def\@gobble#1{}
\newtoks\@temptokena
%%---------------------------------------------------------------
%% Check if argument is null:
%% The \@ifnull-macro is derived from
%% Robert R Schneck's \ifempty-macro
%% [ news:3eef1...@corp.newsgroups.com
%% Newsgroups: comp.text.tex,
%% Subject:
%% Re: \ifempty solution (was Macro puzzle: maximally general \ifempty)

%% Message-ID: <3eef1...@corp.newsgroups.com>


%% Date: 17 Jun 2003 08:42:50 -0500 ].
%%...............................................................
\long\def\@ifnull#1{%
\romannumeral\expandafter\@firstofone\expandafter{\expandafter
\expandafter\expandafter\@rmstop\csname @\expandafter\@gobble\string{%
\expandafter\@secondoftwo\expandafter{\expandafter{\string#1}%

\expandafter\@gobble\string}second\expandafter\expandafter\expandafter
\@gobble\expandafter\@gobble\string}{first}oftwo\endcsname}%


}%
%%---------------------------------------------------------------
%% Check if brace-balanced argument starts with a catcode-1-
%% character-token:
%%...............................................................
\long\def\@ifbrace#1{%

\romannumeral\expandafter\@firstofone\expandafter{\expandafter
\expandafter\expandafter\@rmstop\csname @\expandafter

\@secondoftwo\expandafter{\expandafter{\string#1.}first%


\expandafter\expandafter\expandafter\@gobble\expandafter

\@gobble\string}{second}oftwo\endcsname}%

\expandafter\show
\csname @fi\csname @@stripbrace\endcsnameXXXXXXXrsto}}}}}}}fone\endcsname

Heiko Oberdiek

unread,
Nov 12, 2010, 5:42:28 PM11/12/10
to
Ulrich D i e z <eu_an...@web.de> wrote:

> %% Check if argument is null:
> %% The \@ifnull-macro is derived from
> %% Robert R Schneck's \ifempty-macro
> %% [ news:3eef1...@corp.newsgroups.com
> %% Newsgroups: comp.text.tex,
> %% Subject:
> %% Re: \ifempty solution (was Macro puzzle: maximally general \ifempty)
> %% Message-ID: <3eef1...@corp.newsgroups.com>
> %% Date: 17 Jun 2003 08:42:50 -0500 ].
> %%...............................................................
> \long\def\@ifnull#1{%
> \romannumeral\expandafter\@firstofone\expandafter{\expandafter
> \expandafter\expandafter\@rmstop\csname @\expandafter\@gobble\string{%
> \expandafter\@secondoftwo\expandafter{\expandafter{\string#1}%
> \expandafter\@gobble\string}second\expandafter\expandafter\expandafter
> \@gobble\expandafter\@gobble\string}{first}oftwo\endcsname}%
> }%

Nice.

I have now added \ltx@ifempty to ltxcmds:
http://www.informatik.uni-freiburg.de/~oberdiek/tmp/ltxcmds.pdf
(location is temporarily until next bundle update)

\catcode`\{=1
\catcode`\}=2
\catcode`\#=6
\catcode`\@=11

\input ltxcmds.sty\relax
\ltx@ifpackagelater{ltxcmds}{2010/11/12}{%
}{%
\long\def\ltx@firstofthree#1#2#3{#1}%
\def\LTXcmds@temp#1{%
\long\def\ltx@ifempty##1{%
\romannumeral0%
\iffalse{\fi
\expandafter\ltx@gobble\expandafter{%
\expandafter{\string##1}%
\expandafter\ltx@gobble\string
}%
\expandafter\ltx@firstofthree\expandafter
{\iffalse}\fi
\expandafter#1\ltx@secondoftwo
}%
\expandafter#1\ltx@firstoftwo
}%
}%
\LTXcmds@temp{ }%
}

\def\msg#{\immediate\write16}
\long\def\test#1{%
\begingroup
\def\Stuff{#1}%
\ifx\Stuff\ltx@empty
\def\StuffEmpty{\Y}%
\else
\def\StuffEmpty{\N}%
\fi
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\TestEmpty
\expandafter\expandafter\expandafter{%
\ltx@ifempty{#1}{\Y}{\N}%
}%
\ifx\StuffEmpty\TestEmpty
\msg{* Test OK}%
\else
\errmessage{Test failed!}%
\fi
\endgroup
}
\test{}
\test{a}
\test{ab}
\test{{}}
\test{{a}}
\test{ }
\test{\par}
\test{{\par}}
\test{\if}
\test{{\if}}
\test{\else}
\test{{\else}}

Ahmed Musa

unread,
Nov 15, 2010, 12:44:26 PM11/15/10
to
On Nov 12, 10:42 pm, Heiko Oberdiek <heiko.oberd...@googlemail.com>
wrote:

> Ulrich  D i e z <eu_angel...@web.de> wrote:
>
>
>
>
>
> > %% Check if argument is null:
> > %%   The \@ifnull-macro is derived from
> > %%   Robert R Schneck's \ifempty-macro
> > %%   [news:3eef1...@corp.newsgroups.com
> > %%     Newsgroups: comp.text.tex,
> > %%     Subject:
> > %%      Re: \ifempty solution (was Macro puzzle: maximally general \ifempty)
> > %%     Message-ID: <3eef1ad...@corp.newsgroups.com>
> Heiko Oberdiek- Hide quoted text -
>
> - Show quoted text -


The \@ifnull test is testing for empty or blank with a truckload of
\expandafter's. The compact and simple \ifblank test by Donald
Arseneau is also expandable and accepts unbalanced conditionals. Is
there a difference between the two?


-----------------------------------------------------------

I have a problem related to the previous one, as follows. How do I
propagate curly braces in arguments when using LaTeX's \@for, \@tfor,
or kvsetkeys's \comma@parse? In the past I used \futurelet but I have
observed this to be flawed in general. I am now tempted to try making
braces innocent before argument grabbing, and then retokenizing the
argument afterwards, but before I try it I would like to know what
experienced TeXnicians think.

EXAMPLE 1

Braces around {y} are stripped in the following:

\@tempcnta\z@
\def\do#1{%
\advance\@tempcnta\@ne
\@namedef{x@\romannumeral\@tempcnta}{#1}%
}
\comma@parse{x, , {y},, z}\do
\show\x@ii


EXAMPLE 2

Some definitions:

\def\@ifx#1#2{%
\@nameuse{@\ifx#1#2first\else second\fi oftwo}%
}
\def\@ifcat#1\cat@nil{%
\@nameuse{@\ifcat#1first\else second\fi oftwo}%
}
\def\defsec@fir#1#2{\edef#2{\unexpanded{#1}}}
\def\brace@a{%
\begingroup
% Don't confuse \bgroup as argument delimiter brace:
\let\@bgroup\bgroup
\let\bgroup\@undefined
\futurelet\next
}
\def\brace@b#1\@nil#2{%
\@ifx\next\@bgroup{%
\@ifcat$\detokenize\expandafter{\@cdr#1\@nil}$\cat@nil
\@firstoftwo\@secondoftwo
}{%
\@secondoftwo
}%
{\endgroup\defsec@fir{{#1}}#2}{\endgroup\defsec@fir{#1}#2}%
}

Now how do I distinguish between the following cases (both of which
begin with a left brace) and retain the outer braces in case 1 when
defining \myitem? In case 2, the token list '{\ifx}\bgroup' should, of
course, not be braced when defining \myitem.

case1:

\brace@a\brace@b {{\bgroup}\ifx}\@nil\myitem

case 2:

\brace@a\brace@b {\ifx}\bgroup\@nil\myitem


Sincerely

Ulrich D i e z

unread,
Nov 15, 2010, 2:17:50 PM11/15/10
to
Ahmed Musa wrote:

> The \@ifnull test is testing for empty or blank with a truckload of
> \expandafter's.

\@ifnull does not test for blankness but for emptiness.
"blankness" means either empty or containing only space-tokens.
You can easily implement \@ifblank by means of \@ifnull:

\long\def\@ifblank#1{%
\romannumeral\expandafter\expandafter
\expandafter \@gobble
\expandafter\@ifnull
\expandafterl{%
\@gobble#1.}%
}%


> The compact and simple \ifblank test by Donald
> Arseneau is also expandable

In http://www.ctan.org/tex-archive/info/aro-bend/answer.002 I found the following:

[...]
| >>Solution 1 [Donald Arseneau]:
| \catcode`\@=11 % as in plain.tex
| \let\then\iftrue
| \long\def\ifblank#1\then{\Ifbl@nk#1@@..\then}%
| \long\def\Ifbl@nk#1#2@#3#4\then{\if#3#4}
| \catcode`\@=12
[...]
| >>Solution 3 [Arseneau/Knuth]:
| % Usage: \if\blank{#1}...\else...\fi
| \catcode`\@=11 % as in plain.tex
| \long\def\blank#1{\bl@nk#1@@..\bl@nk}%
| \long\def\bl@nk#1#2@#3#4\bl@nk{#3#4}
| \catcode`\@=12
[...]

In ifmtarg.sty I found the following:

| \NeedsTeXFormat{LaTeX2e}
| \ProvidesPackage{ifmtarg}[2009/09/02 v1.2a check for an empty argument]
| \begingroup
| \catcode`\Q=3
| \long\gdef\@ifmtarg#1{\@xifmtarg#1QQ\@secondoftwo\@firstoftwo\@nil}
| \long\gdef\@xifmtarg#1#2Q#3#4#5\@nil{#4}
| \long\gdef\@ifnotmtarg#1{\@xifmtarg#1QQ\@firstofone\@gobble\@nil}
| \endgroup
| \endinput


> and accepts unbalanced conditionals.

So does \@ifnull.

> Is there a difference between the two?

Yes. The definitions look different ;->

Another difference is that Donald Arseneau's \ifblank-tests
internally call macros that process delimited arguments
while \@ifnull does not process delimited arguments.
- In case the delimiter occurs in what you wish to test for
emptiness, the \ifblank-macro might not behave as
expected.
- Besides this, with \ifblank it is not ensured that arguments
are nested into braces during evaluation. I think this might
cause trouble, e.g., in case \ifblank is used inside an
alignment / tabular-environment for testing an argument
which also contains &-character-tokens.
In order to resolve this, some \expandafter might be
required.

Ulrich

Ulrich D i e z

unread,
Nov 15, 2010, 3:52:23 PM11/15/10
to
Ahmed Musa wrote:

In case you work with delimited arguments, you probably
don't need to distinguish the cases as with delimited
arguments braces are stripped only in case they
surround the entire argument. Thus with delimited arguments
you can avoid brace-stripping in general by having added
something in front of what will be the delimited argument,
and have it gobbled later - this way it is ensured that
not the entire argument is surrounded by braces so that
no braces get removed:

\catcode`\@=11
\long\def\@gobble#1{}%


\def\defsec@fir#1#2{\edef#2{\unexpanded{#1}}}

% Add the dot:
\def\BracePreserveDef{\@BracePreserveDef.}
\def\@BracePreserveDef#1\@nil#2{%
% Gobble the dot - sorry, cannot avoid \expandafter here :
\expandafter\defsec@fir\expandafter{\@gobble#1}#2%
% Voila - braces were not stripped.
}

\BracePreserveDef {{\bgroup}\ifx}\@nil\myitem

\show\myitem

\BracePreserveDef {\ifx}\bgroup\@nil\myitem

\show\myitem

\csname stop\endcsname \bye

To increase the likelihood of getting a comprehensible
error-message in case something doesn't work out as
expected, you can also define:


\catcode`\@=11
\long\def\@gobbledot.{}%


\def\defsec@fir#1#2{\edef#2{\unexpanded{#1}}}

% Add the dot:
\def\BracePreserveDef{\@BracePreserveDef.}
\def\@BracePreserveDef#1\@nil#2{%
% Gobble the dot - sorry, cannot avoid \expandafter here :
\expandafter\defsec@fir\expandafter{\@gobble#1}#2%
% Voila - braces were not stripped.
}

\BracePreserveDef {{\bgroup}\ifx}\@nil\myitem

\show\myitem

\BracePreserveDef {\ifx}\bgroup\@nil\myitem

\show\myitem

Heiko Oberdiek

unread,
Nov 15, 2010, 5:15:32 PM11/15/10
to
Ahmed Musa <ahmedm...@gmail.com> wrote:

> On Nov 12, 10:42 pm, Heiko Oberdiek <heiko.oberd...@googlemail.com>
> wrote:
> > Ulrich  D i e z <eu_angel...@web.de> wrote:

> > > \long\def\@ifnull#1{%

> >     \long\def\ltx@ifempty##1{%

> The \@ifnull test is testing for empty or blank with a truckload of


> \expandafter's. The compact and simple \ifblank test by Donald
> Arseneau is also expandable and accepts unbalanced conditionals. Is
> there a difference between the two?

A huge, the space stands inbetween them.
\@ifnull or \...@ifempty consider the space as something that is not
empty. \ifblank, however, accepts the space in the "blank" case.

> I have a problem related to the previous one, as follows. How do I
> propagate curly braces in arguments when using LaTeX's \@for, \@tfor,
> or kvsetkeys's \comma@parse?

Nothing special in \comma@parse, because it preserves braces
that are part of the entry. That is part of the specification.
Except for the outmost braces:
"abc,def" is equivalent to ",{abc},{def}". Otherwise it would be
difficult to put the comma into an entry.

I other cases you have to check the documentation and/or the
implementation.

LaTeX's \@for:
* Spaces are not removed.
* One level of outmost braces are removed.
* The first token of the list is expanded once.
(This can be prevented by adding \@empty in front
of the list.)
* Empty entries are not ignored.

In LaTeX's \@tfor the entries are parsed as undelimited arguments:
* One level of outmost braces are removed.
* When looking for the next entry, spaces are ignored.
* The first token is not expanded (contrary to \@for).
* Empty entries are not ignored.
* Entries are scanned as undlimite

> In the past I used \futurelet but I have
> observed this to be flawed in general. I am now tempted to try making
> braces innocent before argument grabbing, and then retokenizing the
> argument afterwards,

IMHO that procedure has much more disadvantages. The information
about catcodes are lost:

\mymacro{~}

\begingroup
\@makeother\~%
\@firstofone{\endgroup
\mymacro{~}%
}

In the first case \mymacro the argument "~" is an active character.
In the latter case "~" is an character with catcode `other' (12).
But you cannot distinguish this cases if the argument get detokenized
and reread via \scantokens.

> EXAMPLE 1
>
> Braces around {y} are stripped in the following:
>
> \@tempcnta\z@
> \def\do#1{%
> \advance\@tempcnta\@ne
> \@namedef{x@\romannumeral\@tempcnta}{#1}%
> }
> \comma@parse{x, , {y},, z}\do
> \show\x@ii

Yes. \comma@parse first removes spaces around commas
and "normalizes" the list:
,x,{y},z,
Then an entry is read as (delimited) TeX macro argument:
entry 1: "x"
entry 2: "y"
entry 3: "z"

If the braces should be part of the value or the value contains
the comma outside of braces, then you need argument braces:
x,{{y}},z

If comma@parse woud remove more than one set of outer
braces, then it would be a bug. I think this is a very important
feature. Consider a list as value:
x,{{a,b,c}},x
Then the inner list contains of one entry "a,b,c". However, if the
first parser removes more levels of braces, then the second
parser might see a list with three entries "a", "b", and "c".

> EXAMPLE 2

> [...]
> \futurelet\next

> Now how do I distinguish between the following cases (both of which
> begin with a left brace) and retain the outer braces in case 1 when
> defining \myitem?

If \next is \bgroup, then
save the unprocessed list, grab the argument and the rest of the list.
If the concatenation of argument and list is the original list, then
no braces are stripped, otherwise add outmost braces to the argument.

--
Heiko Oberdiek

Heiko Oberdiek

unread,
Nov 15, 2010, 5:29:46 PM11/15/10
to
Ulrich D i e z <eu_an...@web.de> wrote:

> Ahmed Musa wrote:
>
> > The \@ifnull test is testing for empty or blank with a truckload of
> > \expandafter's.
>
> \@ifnull does not test for blankness but for emptiness.
> "blankness" means either empty or containing only space-tokens.
> You can easily implement \@ifblank by means of \@ifnull:
>
> \long\def\@ifblank#1{%
> \romannumeral\expandafter\expandafter
> \expandafter \@gobble
> \expandafter\@ifnull
> \expandafterl{%
> \@gobble#1.}%
> }%

You mean something between the following variants?

\long\def\@ifblank#1{%
\expandafter\@ifnull\expandafter{\@gobble#1.}%
}

\long\def\@ifblank#1{%
\romannumeral0%


\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter

\space\expandafter
\@ifnull\expandafter{\@gobble#1.}%
}

--
Heiko Oberdiek

Ahmed Musa

unread,
Nov 15, 2010, 5:48:52 PM11/15/10
to
> Ulrich- Hide quoted text -

>
> - Show quoted text -

Many thanks for this clever idea.

>\catcode`\@=11
>\long\def\@gobbledot.{}%
>\def\defsec@fir#1#2{\edef#2{\unexpanded{#1}}}
>% Add the dot:
>\def\BracePreserveDef{\@BracePreserveDef.}
>\def\@BracePreserveDef#1\@nil#2{%
> % Gobble the dot - sorry, cannot avoid \expandafter here :
> \expandafter\defsec@fir\expandafter{\@gobble#1}#2%
> % Voila - braces were not stripped.
>}


> % Gobble the dot - sorry, cannot avoid \expandafter here.

You don't have to bother: I now know why you often call on
\expandafter. It's not all about fondness after all.


> \expandafter\defsec@fir\expandafter{\@gobble#1}#2%

I think you meant to write \@gobbledot here.

Ahmed Musa

unread,
Nov 15, 2010, 5:51:48 PM11/15/10
to
On Nov 15, 10:15 pm, Heiko Oberdiek <heiko.oberd...@googlemail.com>
wrote:

Heiko, many thanks. I don't seem to understand every line of your
thought very well but, all the same, it has led me to something
similar to your suggestion (although I have the following reservations
about the approach I have in mind).

If \next is \bgroup, I will match every item of \@for with the
original list. If it exists in the original list, then no brace has
been stripped, otherwise braces have been stripped. I am assuming that
a braced item in the original list will not match its unbraced
counterpart. But braces (of any kind) in the following <item> will
cause problems in argument matching in <list>. And if we were to
detokenize both <item> and <list> before the test, the significance of
outer braces will be lost and a false <true> will certainly emerge.

\in@<item><list>

Message has been deleted

Ulrich D i e z

unread,
Nov 15, 2010, 5:58:55 PM11/15/10
to
Ahmed Musa wrote:

Ouch! Actually I don't remember any more whether I
meant to but in any case I should have done so. ;-)
I don't know what I was thinking! Sorry.

Ulrich

Ulrich D i e z

unread,
Nov 15, 2010, 6:26:25 PM11/15/10
to
This posting supersedes my posting
news:ibsdnd$q3v$1...@news.albasani.net .

Heiko Oberdiek wrote:
.


> Ulrich D i e z <eu_an...@web.de> wrote:
>
> > Ahmed Musa wrote:
> >
> > > The \@ifnull test is testing for empty or blank with a truckload of
> > > \expandafter's.
> >
> > \@ifnull does not test for blankness but for emptiness.
> > "blankness" means either empty or containing only space-tokens.
> > You can easily implement \@ifblank by means of \@ifnull:
> >
> > \long\def\@ifblank#1{%
> > \romannumeral\expandafter\expandafter
> > \expandafter \@gobble
> > \expandafter\@ifnull
> > \expandafterl{%

That "l" above is spurious/too much and needs to be removed.

> > \@gobble#1.}%
> > }%
>
> You mean something between the following variants?

[...]

\@ifnull provided in my previous postings starts with
\romannumeral:

| \long\def\@ifnull#1{%
| \romannumeral\expandafter\@firstofone\expandafter{\expandafter
| \expandafter\expandafter\@rmstop\csname @\expandafter\@gobble\string{%
| \expandafter\@secondoftwo\expandafter{\expandafter{\string#1}%
| \expandafter\@gobble\string}second\expandafter\expandafter\expandafter
| \@gobble\expandafter\@gobble\string}{first}oftwo\endcsname}%
| }%

Thus I thought having gobbled that \romannumeral
for the sake of starting \@ifblank with \romannumeral
in order to reduce the amount of required expansion-steps/
\expandafter-chains to 2 for \@ifblank might be a nice
idea...

I meant:

\long\def\@ifblank#1{%
\romannumeral\expandafter\expandafter
\expandafter \@gobble
\expandafter\@ifnull

\expandafter{%
\@gobble#1.}%
}%


The trailing dot gets gobbled as \@gobble's _undelimited_
argument only in case #1 is either blank or empty.

Ulrich

Ahmed Musa

unread,
Nov 16, 2010, 2:23:27 PM11/16/10
to
On Nov 15, 11:26 pm, Ulrich D i e z <eu_angel...@web.de> wrote:
> This posting supersedes my postingnews:ibsdnd$q3v$1...@news.albasani.net.
> Ulrich- Hide quoted text -

>
> - Show quoted text -

What of the following much shorter form? Any pitfalls? I haven't
observed any.

\long\def\@ifnull#1{%
\csname @\iffalse{\fi


\expandafter\@secondoftwo\expandafter{\expandafter{\string#1}}%

{}\expandafter\@firstoftwo\expandafter{\iffalse}\fi
second}{first}oftwo\endcsname
}

\long\def\@ifblank#1{%
\expandafter\@ifnull\expandafter{\@gobble#1.}%
}

\@ifnull{xx}{\def\x{x}}{\def\y{y}}
\@ifnull{}{\def\x{x}}{\def\y{y}}
\@ifblank{ }{\def\x{x}}{\def\y{y}}
\@ifblank{ \ifx}{\def\x{x}}{\def\y{y}}
\@ifblank{ \fi}{\def\x{x}}{\def\y{y}}

Message has been deleted

Ulrich D i e z

unread,
Nov 16, 2010, 9:01:39 PM11/16/10
to
This posting supersedes my posting
news:ibv4e4$rl4$1...@news.albasani.net

Ahmed Musa wrote:

> What of the following much shorter form? Any pitfalls? I haven't
> observed any.
>
> \long\def\@ifnull#1{%
> \csname @\iffalse{\fi
> \expandafter\@secondoftwo\expandafter{\expandafter{\string#1}}%
> {}\expandafter\@firstoftwo\expandafter{\iffalse}\fi
> second}{first}oftwo\endcsname
> }

In case the argument which is to be tested for emptiness
or the leading part of the argument which is to be tested
for emptiness is wrapped into braces, your current variant
delivers the error

! Argument of \@secondoftwo has an extra }.

You can test the following snippet:

----------------------------------------------------------------

\catcode`\@=11


\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}

\long\def\@ifnull#1{%


\csname @\iffalse{\fi
\expandafter\@secondoftwo\expandafter{\expandafter{\string#1}}%
{}\expandafter\@firstoftwo\expandafter{\iffalse}\fi
second}{first}oftwo\endcsname
}

\message{\@ifnull{{huh?}}{Argument is null}{Argument is not null}}

\csname stop\endcsname \bye

----------------------------------------------------------------

Expansion-cascade is:

Step 1:

\@ifnull{{huh?}}{Argument is null}{Argument is not null}

Step 2:

\csname % \csname-expansion in progress
@\iffalse{\fi
\expandafter\@secondoftwo\expandafter{\expandafter{\string{huh?}}}%


{}\expandafter\@firstoftwo\expandafter{\iffalse}\fi
second}{first}oftwo\endcsname

Step 3:

\csname % \csname-expansion in progress
@\expandafter\@secondoftwo\expandafter{\expandafter{\string{huh?}}}%


{}\expandafter\@firstoftwo\expandafter{\iffalse}\fi
second}{first}oftwo\endcsname

Step 4: In order to indicate that \string{ delivers something
that has not catcode 1, I'll use the symbol X:

\csname % \csname-expansion in progress
@\@secondoftwo{{Xhuh?}}}%


{}\expandafter\@firstoftwo\expandafter{\iffalse}\fi
second}{first}oftwo\endcsname

Problem: The first argument to \@secondoftwo might be "{Xhuh?}"
but the unbalanced trailing closing-brace is not accepted as
second argument to \@secondoftwo, thus the error-message.

The circumstance of the code presented by you
delivering error-messages for some cases doesn't
imply that the code presented in my postings could
not be shortened. Maybe I just don't see the obvious.

> \long\def\@ifblank#1{%
> \expandafter\@ifnull\expandafter{\@gobble#1.}%
> }

Assume that obtaining the result of \@ifnull takes exactly
one expansion-step. Then obtaining the result of \@ifblank
takes at least 3 expansion-steps with your macro.
That means you need 3 \expandafter-chains in situations/
expansion-contexts were you need control about the moment
in time, when the result is delivered.

Start: \@ifblank{argument}{case A}{case B}

Step 1
yields: \expandafter\@ifnull\expandafter{\@gobble argument.}{case A}{case B}

Step 2
yields: \@ifnull{rgument.}{case A}{case B}

Step 3
yields: {case B}

Of course your \@ifblank-macro is shorter. The gist is the
same as with previously presented approaches:
The trailing dot will only be gobbled in case the argument
is blank or empty...

The point is: You don't seem to be fond of \expandafter but
in expandable contexts your \@ifblank-variant requires
plenty (more than 2) expansion-steps/plenty (more than
2) \expandafter-chains until you obtain the result, while
wrapping the thing into \romannumeral-expansion
ensures that in any case obtaining the result requires
exactly 2 expansion-steps / exactly 2 \expandafter-chains.

Wrapping the whole thing into \romannumeral-expansion
increases the size of the definition-text but is done as a
favour to users who are not fond of \expandafter and thus
don't want to apply too many \expandafter-tokens in order
to obtain the result.

Besides this, both \romannumeral-expansion and \csname-
expansion can be used in order to "equalize" the amount
of expansion-steps / \expandafter-chains required for
obtaining the result with macros where internally forking
takes place between branches which initiate
expansion-cascades of different length.

I think with \romannumeral-expansion you can tweak
things so that at most 2 expansion-steps/\expandafter-
chains are required for obtaining the result
(The first step "peels" out the \romannumeral.
The second step carries out \romannumeral-expansion
which in turn carries out anything until \romannumeral
is "allowed to know" that no number that might be
transformed into roman "digits" follows.)
, while with \csname-expansion alone you will in any case
need at least 3 expansion-steps / \expandafter-chains.
(The first step "peels" out the \csname.
The second step carries out \csname-expansion
which in turn carries out anything until \csname
is "allowed to form and deliver a token", e.g., \@firstoftwo.
The third step (and probably further steps) carries (carry)
out the token delivered by \csname.)

Ulrich

Ahmed Musa

unread,
Nov 17, 2010, 4:12:06 PM11/17/10
to
On Nov 17, 2:01 am, Ulrich D i e z <eu_angel...@web.de> wrote:
> This posting supersedes my postingnews:ibv4e4$rl4$1...@news.albasani.net

Yes, you're right. It is possible to correct it for the case of '{x}',
but Heiko's solution is short enough. I therefore simply modified it
for use: both \@firstoffour and \@rmstopper are already in my files.

\long\def\@firstoffour#1#2#3#4{#1}
\def\@rmstopper{0 }
\long\def\ifnull#1{%
\romannumeral
\iffalse{\fi
\expandafter\@gobble\expandafter{%


\expandafter{\string#1}%
\expandafter\@gobble\string

}%
\expandafter\@firstoffour\expandafter
{\iffalse}\fi
\expandafter\@rmstopper\@secondoftwo
}%
\expandafter\@rmstopper\@firstoftwo
}

\long\def\ifblank#1{%
\romannumeral\expandafter\expandafter
\expandafter\@gobble\expandafter\ifnull
\expandafter{\@gobble#1.}%
}

\long\def\test#1#2{%
\begingroup
\ifx#1\ifblank
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
{\edef\arg{\detokenize{#2}}}%
{\edef\arg{\expandafter\@gobble\detokenize{#2}.}}%
\ifx\arg\@empty
\def\isargempty{\Y}%
\else
\def\isargempty{\N}%


\fi
\expandafter\expandafter\expandafter\def

\expandafter\expandafter\expandafter\testresult
\expandafter\expandafter\expandafter{%
#1{#2}{\Y}{\N}%
}%
\noindent
{\tt Argument `\arg' -> Test of `\string#1' is
\ifx\testresult\isargempty OK\else bullshit\fi!}%
\newline
\endgroup
}


\begin{document}

\test\ifnull{}
\test\ifnull{a}
\test\ifnull{#1ab}
\test\ifnull{{}}
\test\ifnull{{{ }}}
\test\ifnull{{a}}
\test\ifnull{ }
\test\ifnull{\par}
\test\ifnull{{\par}}
\test\ifnull{\if}
\test\ifnull{{\if}}
\test\ifnull{\else}
\test\ifnull{{\else}}

\test\ifblank{x}
\test\ifblank{a}
\test\ifblank{#1ab}
\test\ifblank{{}}
\test\ifblank{{{ }}}
\test\ifblank{{a}}
\test\ifblank{ }
\test\ifblank{\par}
\test\ifblank{{\par}}
\test\ifblank{\if}
\test\ifblank{{\if}}
\test\ifblank{\else}
\test\ifblank{{\else}}

\end{document}

Ulrich D i e z

unread,
Nov 17, 2010, 7:06:49 PM11/17/10
to
Ahmed Musa wrote:

> Heiko's solution is short enough. I therefore simply modified it
> for use: both \@firstoffour and \@rmstopper are already in my files.

Oops! Although both Heiko and you already pointed it out in
your examples, I overlooked that that \@firstofone-thingie
of mine is not needed as in any case #1 is argument to \@gobble
and thus is in braces and thus is "alignment-proof".

Besides this there are far too many helper-macros in use.
You can accomplish Heiko's solution with \@firstoftwo
and \@secondoftwo as only additional helper-macros:

\catcode`\@=11

\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}

\long\def\@ifnull#1#2#3{%
\romannumeral\iffalse{\fi


\expandafter\@secondoftwo\expandafter{\expandafter{\string#1}%

\expandafter\@secondoftwo\string}\expandafter\@firstoftwo
\expandafter{\iffalse}\fi\@secondoftwo}\@firstoftwo{0 #2}{0 #3}%
}%

\long\def\@ifblank#1{%
\romannumeral\expandafter\expandafter
\expandafter\@secondoftwo\expandafter\@ifnull
\expandafter{\@secondoftwo#1.{}}%
}

%-----

\newtoks\toks@


\long\def\test#1#2{%
\begingroup

\toks@{Testing #1{#2}{Y}{N} yields: }%
\message{^^J\the\toks@ #1{#2}{Y}{N}}%
\endgroup
}

\test\@ifnull{}
\test\@ifnull{a}
\test\@ifnull{#1ab}
\test\@ifnull{{}}
\test\@ifnull{{{ }}}
\test\@ifnull{{a}}
\test\@ifnull{ }
\test\@ifnull{\par}
\test\@ifnull{{\par}}
\test\@ifnull{\if}
\test\@ifnull{{\if}}
\test\@ifnull{\else}
\test\@ifnull{{\else}}

\test\@ifblank{x}
\test\@ifblank{a}
\test\@ifblank{#1ab}
\test\@ifblank{{}}
\test\@ifblank{{{ }}}
\test\@ifblank{{a}}
\test\@ifblank{ }
\test\@ifblank{\par}
\test\@ifblank{{\par}}
\test\@ifblank{\if}
\test\@ifblank{{\if}}
\test\@ifblank{\else}
\test\@ifblank{{\else}}

Ulrich D i e z

unread,
Nov 18, 2010, 5:52:51 AM11/18/10
to
% Oops! Although both Heiko and you already pointed it out in
% your examples, I overlooked that that \@firstofone-thingie
% of mine is not needed as in any case #1 is argument to \@gobble
% and thus is in braces and thus is "alignment-proof".

% Besides this there are far too many helper-macros in use.
% You can accomplish Heiko's solution with \@firstoftwo
% and \@secondoftwo as only additional helper-macros:

\catcode`\@=11

%%---------------------------------------------------------------

\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}

\long\def\@ifnull#1#2#3{%
\romannumeral\iffalse{\fi\expandafter\@secondoftwo\expandafter

{\expandafter{\string#1}\expandafter\@secondoftwo\string}%
\expandafter\@firstoftwo\expandafter{\iffalse}\fi0 #3}{0 #2}%
}%
% in case of eTeX you can use \detokenize for the \@ifnull-test

\long\def\@ifblank#1{%
\romannumeral\expandafter\expandafter\expandafter\@secondoftwo
\expandafter\@ifnull\expandafter{\@secondoftwo#1.{}}%
}

\long\def\@ifbrace#1#2#3{%
\romannumeral\expandafter\@secondoftwo\expandafter{\expandafter
{\string#1.}\expandafter\@firstoftwo\expandafter{\iffalse}\fi


0 #2}{0 #3}%
}%

%%---------------------------------------------------------------
%% \@stripbrace{#1} spits out #1 with all surrounding


%% brace-levels removed - takes at most 2 expansion-steps:
%%...............................................................

\long\def\@stripbrace#1{%


\romannumeral\@ifbrace{#1}{%
\expandafter\@ifnull

\expandafter{\@secondoftwo#1{}}{%
\expandafter\@secondoftwo\@stripbrace#1%
}{0 #1}%
}{0 #1}%
}
%%---------------------------------------------------------------
%% \stripbrace{#1}{#2} defines #2 to expand to #1 with all


%% surrounding brace-levels removed:
%%...............................................................

\newtoks\toks@
\newtoks\@temptokena
%%...............................................................
\ifx\unexpanded\UnDeFInEd
\long\def\stripbrace#1#2{%


\toks@\expandafter{\expandafter\toks@\expandafter{\the\toks@}}%
\begingroup
\@temptokena{%
\endgroup

\toks@\expandafter\expandafter\expandafter{\@stripbrace{#1}}%


\edef#2{\the\toks@}%
}%
\toks@\expandafter{\the\expandafter\@temptokena\the\toks@}%
\the\toks@
}%
\else

\long\def\stripbrace#1#2{%
\edef#2{%
\unexpanded\expandafter\expandafter\expandafter{\@stripbrace{#1}}%
}%
}%
\fi

%%---------------------------------------------------------------
%% Testing:
%%...............................................................


\long\def\test#1#2{%
\begingroup
\toks@{Testing #1{#2}{Y}{N} yields: }%
\message{^^J\the\toks@ #1{#2}{Y}{N}}%
\endgroup
}

\test\@ifnull{}
\test\@ifnull{a}
\test\@ifnull{#1ab}
\test\@ifnull{{}}
\test\@ifnull{{{ }}}
\test\@ifnull{{a}}
\test\@ifnull{ }
\test\@ifnull{\par}
\test\@ifnull{{\par}}
\test\@ifnull{\if}
\test\@ifnull{{\if}}
\test\@ifnull{\else}
\test\@ifnull{{\else}}

\test\@ifblank{}


\test\@ifblank{a}
\test\@ifblank{#1ab}
\test\@ifblank{{}}
\test\@ifblank{{{ }}}
\test\@ifblank{{a}}
\test\@ifblank{ }
\test\@ifblank{\par}
\test\@ifblank{{\par}}
\test\@ifblank{\if}
\test\@ifblank{{\if}}
\test\@ifblank{\else}
\test\@ifblank{{\else}}

\test\@ifbrace{}
\test\@ifbrace{a}
\test\@ifbrace{#1ab}
\test\@ifbrace{{}}
\test\@ifbrace{{{ }}}
\test\@ifbrace{{a}}
\test\@ifbrace{ }
\test\@ifbrace{\par}
\test\@ifbrace{{\par}}
\test\@ifbrace{\if}
\test\@ifbrace{{\if}}
\test\@ifbrace{\else}
\test\@ifbrace{{\else}}


\long\def\test#1#2#3{%
\begingroup
\toks@{Testing #1{#2}{#3} yields: #3= }%
#1{#2}{#3}%
\message{^^J\the\toks@\meaning#3}%
\endgroup
}

\test\stripbrace{{{{x,y}}}}{\x}
\test\stripbrace{{{{x},y}}}{\x}
\test\stripbrace{{{ {x},y}}}{\x}
\test\stripbrace{{{{\bgroup,y}}}}{\x}
\test\stripbrace{{{{\bgroup,\@nil y}}}}{\x}
\test\stripbrace{{{{ x,y}}}}{\x}


% Be aware of the space in front of the innermost surrounding braces -
% neither the space nor the braces wil be removed.

\test\stripbrace{{{ {x,y}}}}{\x}
\test\stripbrace{{{#1{x,y}}}}{\x}


\catcode`\X=13 \letX={
\catcode`\Y=13 \letY=}

\test\stripbrace{{{X{x,y}Y}}}{\x}
\catcode`\X=1
\catcode`\Y=2
\test\stripbrace{{{X{x,y}Y}}}{\x}

Ahmed Musa

unread,
Nov 18, 2010, 3:43:22 PM11/18/10
to

It's interesting that most of the helper macros are gone.

Aah, Joseph Wright ever said to me that \expandafter was fun. So I
thought someone who likes building expandable macros, like Ulrich
does, could be fond of it.

Ulrich D i e z

unread,
Nov 18, 2010, 7:46:13 PM11/18/10
to
Ahmed Musa wrote:

> someone who likes building expandable macros, like Ulrich
> does

Seems I'm not very good at it. Due to my clumsiness I
presented a lot of code which carries out the task in
rather cumbersome ways.
E.g., it took me a long time until I realized that I could
have learned earlier both from Heiko and from you that
both the \csname-thingie and the \@firstofone-thingie
in \@ifnull / \@ifbrace were unnecessary...
It's embarrassing that this thread took me so many
postings and so many "supersedes".
You were absolutely right being sceptic about me
posting a "truckload" of \expandafter.

Sincerely

Ulrich

Ahmed Musa

unread,
Dec 3, 2010, 2:47:30 PM12/3/10
to
On Nov 18, 10:52 am, Ulrich D i e z <eu_angel...@web.de> wrote:
> % Oops! Although both Heiko and you already pointed it out in
> % your examples, I overlooked that that \@firstofone-thingie
> % of mine is not needed as in any case #1 is argument to \@gobble
> % and thus is inbracesand thus is "alignment-proof".
> % neither the space nor thebraceswil be removed.

> \test\stripbrace{{{ {x,y}}}}{\x}
> \test\stripbrace{{{#1{x,y}}}}{\x}
> \catcode`\X=13 \letX={
> \catcode`\Y=13 \letY=}
> \test\stripbrace{{{X{x,y}Y}}}{\x}
> \catcode`\X=1
> \catcode`\Y=2
> \test\stripbrace{{{X{x,y}Y}}}{\x}
>
> \csname stop\endcsname \bye
>
> Ulrich

Please is there any trap with the following definitions? If no flaw,
they are definitely shorter than Ulrich's.

\newcommand\ifstrempty[1]{%
\@nameuse{@\ifnum\pdfstrcmp{}%
{\detokenize{#1}}=\z@ first\else second\fi oftwo}%
}

\newcommand\ifblank[1]{%
\expandafter\ifstrempty\expandafter{\@gobble#1.}%
}

Heiko Oberdiek

unread,
Dec 3, 2010, 4:46:30 PM12/3/10
to
Ahmed Musa <ahmedm...@gmail.com> wrote:

> Please is there any trap with the following definitions? If no flaw,
> they are definitely shorter than Ulrich's.
>
> \newcommand\ifstrempty[1]{%
> \@nameuse{@\ifnum\pdfstrcmp{}%
> {\detokenize{#1}}=\z@ first\else second\fi oftwo}%
> }

Good idea.
Some variants:

\usepackage{pdftexcmds}
% The package provides \pdf@strcmp from
% pdfTeX >= 1.30.0: \pdfstrcmp
% XeTeX: \strcmp
% LuaTeX: lua code

\newcommand\ifstrempty[1]{%
\csname @\ifcase\pdf@strcmp{}%
{\detokenize{#1}} first\else second\fi oftwo\endcsname
}

Expandable in exact two expansion steps:

\newcommand\ifstrempty[1]{%
\romannumeral
\ifcase\pdf@strcmp{}{\detokenize{#1}} %
\expandafter\expandafter\expandafter\z@
\expandafter\@firstoftwo
\else
\expandafter\expandafter\expandafter\z@
\expandafter\@secondoftwo
\fi
}

or

\newcommand\ifstrempty[1]{%
\romannumeral\expandafter\expandafter\expandafter\z@
\csname @\ifcase\pdf@strcmp{}{\detokenize{#1}} %
first\else second\fi
oftwo%
\endcsname
}

or

\long\expandafter
\def\csname @ifstrempty0\endcsname#1#2{\z@#1}
\long\expandafter
\def\csname @ifstrempty1\endcsname#1#2{\z@#2}
\long\expandafter
\def\csname @ifstrempty-1\endcsname#1#2{\z@#2}
\newcommand\ifstrempty#1{%
\romannumeral
\csname
@ifstrempty%
\pdf@strcmp{}{\detokenize{#1}}%
\endcsname
}

Of course neither \pdfstrcmp nor \detokenize are available
in vanilla TeX.

--
Heiko Oberdiek

GL

unread,
Dec 3, 2010, 4:42:05 PM12/3/10
to
Le 10/11/2010 23:30, Ahmed Musa a écrit :
>
> I wanted braces stripped from some tokens/strings. So I created the
> following \stripbrace command. It works for all simple cases, but
> fails in some complicated cases for which I can't reproduce minimal
> examples (the complete code is too long for posting). It fails by
> repeating the loop ad infinitum, each time refusing to strip the
> outermost braces. Can anyone tell me the circumstances under which TeX
> may refuse to strip braces in arguments of macros? Perhaps someone
> will suggest an entirely different approach.

There is also:

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{etex,xstring}

\begin{document}

\exploregroups
\StrRemoveBraces{cou{cou}tra{tra{tra}}}[\result]

\meaning\result

\stop

It's good for occasional use, not very efficient in a package,
if it have to be expanded often...

Yours sincerely.

Ulrich D i e z

unread,
Dec 3, 2010, 4:40:23 PM12/3/10
to
Ahmed Musa wrote:

> Please is there any trap with the following definitions? If no flaw,
> they are definitely shorter than Ulrich's.
>
> \newcommand\ifstrempty[1]{%
> \@nameuse{@\ifnum\pdfstrcmp{}%
> {\detokenize{#1}}=\z@ first\else second\fi oftwo}%
> }

I don't see flaws/traps.

In news:ic30n3$hnh$1...@news.albasani.net ,
Subject: Re: TeX refuses to strip outer braces in argument,
Date: Thu, 18 Nov 2010 11:52:51 +0100,
I stated:


| % in case of eTeX you can use \detokenize for the \@ifnull-test

Afaik
- \detokenize is an eTeX-primitive.
- \pdfstrcmp is a pdfTeX-primitive.
Both tokens might not be available on old TeX-platforms.

[E.g., MiKTeX 2.4 is the last MiKTeX-distribution which
also runs under Win95. With MiKTeX 2.4 there is
no \pdfstrcmp available. I still often use MiKTeX 2.4 as
one of my computers is old. (No, I don't use edlin any
more for writing text-files ;-) ]

On the one hand, there are those who wish to "preserve
portability" and on the other hand there are those who
think that new features were not invented in order to be
avoided for the sake of code being portable to every
outdated and obsolete platform.

You can avoid \pdfstrcmp - thus only eTeX-support
but not pdfTeX-suport will be required - sorry for the
two \expandafter :

\newcommand\ifstrempty[1]{%
\@nameuse{@\expandafter\ifx\expandafter
$\detokenize{#1}$first\else second\fi oftwo}%
}

Due to omitting wrapping into \romannumeral or the
like for the sake of representing clearly the gist of
the definitions, our macros take 4 expansion-steps.
(1. expand \ifstrempty
2. expand \@nameuse
3. expand \csname
4. expand the token delivered by expanding \csname)

Sincerely

Ulrich

zappathustra

unread,
Dec 3, 2010, 5:00:08 PM12/3/10
to
Le 03/12/2010 22:46, Heiko Oberdiek a écrit :
> Ahmed Musa<ahmedm...@gmail.com> wrote:
>
>> Please is there any trap with the following definitions? If no flaw,
>> they are definitely shorter than Ulrich's.
>>
>> \newcommand\ifstrempty[1]{%
>> \@nameuse{@\ifnum\pdfstrcmp{}%
>> {\detokenize{#1}}=\z@ first\else second\fi oftwo}%
>> }
> Good idea.
> Some variants:

[Snip Heiko's variants.]

Since it's \ifempty fest, here's what I do:

\expandafter\let\csname A totally empty string\endcsname\relax
\def\ifempty#1{%
\ifcsname A totally empty string\detokenize{#1}\endcsname
\expandafter\first \else \expandafter\second \fi
}

(With \first and \second as usual.)
Oh, it might happen than \csname A totally empty
string<material>\endcsname is defined and one tests for
\ifempty{<material>}, in which case the test is wrong, but I try not to
be so paranoid. And the macro doesn't require pdfTeX, although it does
require eTeX.

Best,
Paul

Ulrich D i e z

unread,
Dec 3, 2010, 5:11:05 PM12/3/10
to
Heiko Oberdiek wrote:

What's the reason for not making \ifstrempty fetch 3
arguments?

\newcommand\ifstrempty[3]{%
\romannumeral


\csname @\ifcase\pdf@strcmp{}{\detokenize{#1}} %

first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
}

\newcommand\ifstrempty[3]{%
\romannumeral
\csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
}

Ulrich

Heiko Oberdiek

unread,
Dec 3, 2010, 6:26:18 PM12/3/10
to
Ulrich D i e z <eu_an...@web.de> wrote:

> You can avoid \pdfstrcmp - thus only eTeX-support
> but not pdfTeX-suport will be required - sorry for the
> two \expandafter :
>
> \newcommand\ifstrempty[1]{%
> \@nameuse{@\expandafter\ifx\expandafter
> $\detokenize{#1}$first\else second\fi oftwo}%
> }
>
> Due to omitting wrapping into \romannumeral or the
> like for the sake of representing clearly the gist of
> the definitions, our macros take 4 expansion-steps.
> (1. expand \ifstrempty
> 2. expand \@nameuse
> 3. expand \csname
> 4. expand the token delivered by expanding \csname)

Instead of \@nameuse you can use \csname directly.
That makes three expansion steps. And it can be
made to two expansion steps using the usual
\romannumeral trick.

--
Heiko Oberdiek

Ahmed Musa

unread,
Dec 3, 2010, 6:34:37 PM12/3/10
to
> Ulrich- Hide quoted text -
>
> - Show quoted text -

> \newcommand\ifstrempty[3]{%


> \romannumeral
> \csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
> }

What of the case of '$' getting catcode 12, say in a make-innocent
setup?

Heiko Oberdiek

unread,
Dec 3, 2010, 6:51:41 PM12/3/10
to
Ulrich D i e z <eu_an...@web.de> wrote:

> What's the reason for not making \ifstrempty fetch 3
> arguments?
>
> \newcommand\ifstrempty[3]{%
> \romannumeral
> \csname @\ifcase\pdf@strcmp{}{\detokenize{#1}} %
> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
> }

\detokenize{} isn't necessary for \pdfstrcmp.

> \newcommand\ifstrempty[3]{%
> \romannumeral
> \csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
> }

Thanks, it's now my favorite for \ltx@ifstrempty (for the case
\detokenize is available).

--
Heiko Oberdiek

Heiko Oberdiek

unread,
Dec 3, 2010, 7:17:23 PM12/3/10
to
Ahmed Musa <ahmedm...@gmail.com> wrote:

That's the reason for my "catcode paranoia" at the beginning
of my packages.

The catcode of $ must be 3, 4, 7, 8, 11, or 13 (inefficient)
during the definition of \ifstrempty. Afterwards the category
code does not matter, because the definition text is
already tokenized and the tokens inside will not change
the category code afterwards.

Generating the catcode list I stumbled across 10.
\ifx yields true if two tokens have the catcode 10,
but different character codes.

\endlinechar=-1
\catcode`\{=1\relax
\catcode`\}=2\relax
\catcode`\#=6\relax
\catcode`\ =10\relax
\catcode`\$=10\relax
\def\msg#{\immediate\write16}
\def\test#1#2{\ifx#1#2\msg{true/unexpected)}\else\msg{false/expected}\fi}
\test{ }{$}
\csname @@end\endcsname\end

"The TeXbook" says about \ifx:

| \ifx<token1><token2> (test if tokens agree)
|
| In this case, TeX does not expand control sequences when it looks at
| the two tokens. The condition is true if
| (a) the two tokens are not macros, and they both represent the same
| (character code, category code) pair or the same TeX primitive
| or the same \font or \chardef or \countdef, etc.; or if
| (b) the two tokens are macros, [...]

What I am missing?

--
Heiko Oberdiek

Heiko Oberdiek

unread,
Dec 3, 2010, 8:09:51 PM12/3/10
to
Heiko Oberdiek <heiko.o...@googlemail.com> wrote:

> Generating the catcode list I stumbled across 10.
> \ifx yields true if two tokens have the catcode 10,
> but different character codes.
>
> \endlinechar=-1
> \catcode`\{=1\relax
> \catcode`\}=2\relax
> \catcode`\#=6\relax
> \catcode`\ =10\relax
> \catcode`\$=10\relax
> \def\msg#{\immediate\write16}
> \def\test#1#2{\ifx#1#2\msg{true/unexpected)}\else\msg{false/expected}\fi}
> \test{ }{$}
> \csname @@end\endcsname\end
>
> "The TeXbook" says about \ifx:
>
> | \ifx<token1><token2> (test if tokens agree)
> |
> | In this case, TeX does not expand control sequences when it looks at
> | the two tokens. The condition is true if
> | (a) the two tokens are not macros, and they both represent the same
> | (character code, category code) pair or the same TeX primitive
> | or the same \font or \chardef or \countdef, etc.; or if
> | (b) the two tokens are macros, [...]
>
> What I am missing?

"Chapter 8: The Characters You Type":

| If TeX sees a character of category 10 (space), [...]
| the character is converted to a token of category 10
| whose character code is 32, [...]. the character code
| in a space token is always 32.

--
Heiko Oberdiek

Ulrich D i e z

unread,
Dec 4, 2010, 4:43:45 AM12/4/10
to
Heiko Oberdiek wrote:

> Generating the catcode list I stumbled across 10.
> \ifx yields true if two tokens have the catcode 10,
> but different character codes.
>
> \endlinechar=-1
> \catcode`\{=1\relax
> \catcode`\}=2\relax
> \catcode`\#=6\relax
> \catcode`\ =10\relax
> \catcode`\$=10\relax
> \def\msg#{\immediate\write16}
> \def\test#1#2{\ifx#1#2\msg{true/unexpected)}\else\msg{false/expected}\fi}
> \test{ }{$}
> \csname @@end\endcsname\end
>
> "The TeXbook" says about \ifx:
>
> | \ifx<token1><token2> (test if tokens agree)
> |
> | In this case, TeX does not expand control sequences when it looks at
> | the two tokens. The condition is true if
> | (a) the two tokens are not macros, and they both represent the same
> | (character code, category code) pair or the same TeX primitive
> | or the same \font or \chardef or \countdef, etc.; or if
> | (b) the two tokens are macros, [...]
>
> What I am missing?

The $ has catcode 10 (space) at the time of reading/tokenizing the
input-line "\test{ }{$}". According to TeXBook "Chapter 8: The
Characters You Type", characters in the input(-file) whose catcode
is 10 get always tokenized as character-tokens with category-code
10 and character-code 32 (space tokens) (unless TeX encounters
them while gathering them as the name of a control-symbol).
Therefore tokenizing the input-sequence "{ }" yields the same
sequence of tokens as tokenizing the input-sequence "{$}" .

You can use \lowercase in order to get a character-token (funny
space) with category-code 10 and with character-code differing from
32. \ifx-comparison will deliver the "expected" result.

Ulrich

\endlinechar=-1
\catcode`\{=1\relax
\catcode`\}=2\relax
\catcode`\#=6\relax
\catcode`\ =10\relax

\lccode`\ =`\A\relax
\def\msg#{\immediate\write16}
\def\test#1#2{%
\ifx#1#2%
\msg{|#1|=|#2| - true/unexpected)}%
\else
\msg{|#1|=/=|#2| - false/expected}%
\fi
}
\def\inlctest{\test{ }}%
\lowercase{\inlctest{ }}%
\csname @@end\endcsname\end

Ulrich D i e z

unread,
Dec 4, 2010, 4:44:45 AM12/4/10
to
Ahmed Musa wrote:

>> \newcommand\ifstrempty[3]{%
>> \romannumeral
>> \csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
>> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
>> }
>
> What of the case of '$' getting catcode 12, say in a make-innocent
> setup?

You can use a non-outer control-sequence instead of a
character-token.

\newcommand\ifstrempty[3]{%
\romannumeral

\csname @\expandafter\ifx\expandafter\relax\detokenize{#1}\relax


first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
}

Ulrich

Message has been deleted

Ulrich D i e z

unread,
Dec 4, 2010, 5:12:45 AM12/4/10
to
Heiko Oberdiek wrote:
.

> Ulrich D i e z <eu_an...@web.de> wrote:

> > Due to omitting wrapping into \romannumeral or the
> > like for the sake of representing clearly the gist of
> > the definitions, our macros take 4 expansion-steps.

[...]


> Instead of \@nameuse you can use \csname directly.
> That makes three expansion steps.

Sure.

> And it can be
> made to two expansion steps using the usual
> \romannumeral trick.

That's why I wrote "Due to omitting wrapping into
\romannumeral ..." ;-)

Sincerely

Ulrich

Ulrich D i e z

unread,
Dec 4, 2010, 5:17:28 AM12/4/10
to
This posting supersedes my posting
news:idd48i$qvb$1...@four.albasani.net

Heiko Oberdiek wrote:
.


> Ulrich D i e z <eu_an...@web.de> wrote:
>
> > What's the reason for not making \ifstrempty fetch 3
> > arguments?
> >
> > \newcommand\ifstrempty[3]{%
> > \romannumeral
> > \csname @\ifcase\pdf@strcmp{}{\detokenize{#1}} %
> > first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
> > }
>
> \detokenize{} isn't necessary for \pdfstrcmp.

Thanks for pointing this out.



> > \newcommand\ifstrempty[3]{%
> > \romannumeral
> > \csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
> > first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
> > }
>
> Thanks, it's now my favorite for \ltx@ifstrempty (for the case
> \detokenize is available).

By the way - in case of \romannumeral-expansion you can
omit \csname. Also I overlooked that "0" can trail
\romannumeral as \romannueral will keep expanding anyway
in order to find out about following digits/tokens.

\newcommand\ifstrempty[3]{%
\romannumeral0\expandafter\ifx\expandafter$%
\detokenize{#1}$\expandafter\@firstoftwo\else
\expandafter\@secondoftwo\fi{ #2}{ #3}%
}

Ulrich

Heiko Oberdiek

unread,
Dec 4, 2010, 6:23:03 AM12/4/10
to

A control-sequence has the weakness that it could be redefined
later. Especially \outer or conditionals will break the code.
Of course redefining \relax this way would break other things
as well.

--
Heiko Oberdiek

Heiko Oberdiek

unread,
Dec 4, 2010, 6:23:02 AM12/4/10
to

Thanks.

--
Heiko Oberdiek

David Kastrup

unread,
Dec 4, 2010, 6:28:41 AM12/4/10
to
Heiko Oberdiek <heiko.o...@googlemail.com> writes:

> Ulrich D i e z <eu_an...@web.de> wrote:
>
>> Ahmed Musa wrote:
>>
>> >> \newcommand\ifstrempty[3]{%
>> >> \romannumeral
>> >> \csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
>> >> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
>> >> }
>> >
>> > What of the case of '$' getting catcode 12, say in a make-innocent
>> > setup?
>>
>> You can use a non-outer control-sequence instead of a
>> character-token.
>>
>> \newcommand\ifstrempty[3]{%
>> \romannumeral
>> \csname @\expandafter\ifx\expandafter\relax\detokenize{#1}\relax
>> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
>> }
>
> A control-sequence has the weakness that it could be redefined
> later.

Well, take \csname as a control sequence token then. I doubt that you
can redefine it in a manner that would break the above. Or \expandafter
(redefining that while keeping it functioning should be really hard).
Anyway, I consider the use of \csname above a bad choice since it costs
lots of tokens and incurs a hash table lookup at runtime.

\newcommand\ifstrempty[3]{%
\romannumeral0%
\expandafter\ifx\expandafter\expandafter\detokenize{#1}\expandafter


\expandafter\@firstoftwo\else \expandafter\@secondoftwo\fi { #2}{ #3}%
}

> Especially \outer or conditionals will break the code. Of course


> redefining \relax this way would break other things as well.

--
David Kastrup
UKTUG FAQ: <URL:http://www.tex.ac.uk/cgi-bin/texfaq2html>

David Kastrup

unread,
Dec 4, 2010, 6:31:33 AM12/4/10
to
Heiko Oberdiek <heiko.o...@googlemail.com> writes:

> Ulrich D i e z <eu_an...@web.de> wrote:
>
>> Ahmed Musa wrote:
>>
>> >> \newcommand\ifstrempty[3]{%
>> >> \romannumeral
>> >> \csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
>> >> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
>> >> }
>> >
>> > What of the case of '$' getting catcode 12, say in a make-innocent
>> > setup?
>>
>> You can use a non-outer control-sequence instead of a
>> character-token.
>>
>> \newcommand\ifstrempty[3]{%
>> \romannumeral
>> \csname @\expandafter\ifx\expandafter\relax\detokenize{#1}\relax
>> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
>> }
>
> A control-sequence has the weakness that it could be redefined
> later.

Well, take \csname as a control sequence token then. I doubt that you


can redefine it in a manner that would break the above. Or \expandafter
(redefining that while keeping it functioning should be really hard).
Anyway, I consider the use of \csname above a bad choice since it costs
lots of tokens and incurs a hash table lookup at runtime.

\newcommand\ifstrempty[3]{%
\romannumeral\expandafter


\ifx\expandafter\expandafter\detokenize{#1}\expandafter \expandafter

\@firstoftwo\else \expandafter\@secondoftwo\fi {\z@#2}{\z@#3}%
}

> Especially \outer or conditionals will break the code. Of course
> redefining \relax this way would break other things as well.

--

Heiko Oberdiek

unread,
Dec 4, 2010, 6:51:40 AM12/4/10
to
David Kastrup <d...@gnu.org> wrote:

> Heiko Oberdiek <heiko.o...@googlemail.com> writes:
>
> > Ulrich D i e z <eu_an...@web.de> wrote:
> >
> >> Ahmed Musa wrote:
> >>
> >> >> \newcommand\ifstrempty[3]{%
> >> >> \romannumeral
> >> >> \csname @\expandafter\ifx\expandafter$\detokenize{#1}$%
> >> >> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
> >> >> }
> >> >
> >> > What of the case of '$' getting catcode 12, say in a make-innocent
> >> > setup?
> >>
> >> You can use a non-outer control-sequence instead of a
> >> character-token.
> >>
> >> \newcommand\ifstrempty[3]{%
> >> \romannumeral
> >> \csname @\expandafter\ifx\expandafter\relax\detokenize{#1}\relax
> >> first\else second\fi oftwo\endcsname{0 #2}{0 #3}%
> >> }
> >
> > A control-sequence has the weakness that it could be redefined
> > later.
>
> Well, take \csname as a control sequence token then. I doubt that you
> can redefine it in a manner that would break the above.

The point was `$' vs. `\relax' in the comparison of \ifx.

\newcommand\ifstrempty[3]{%
\romannumeral
\csname @\expandafter\ifx\expandafter
\relax\detokenize{#1}\relax
first\else second\fi oftwo\endcsname
{0 #2}{0 #3}%
}

\typeout{\ifstrempty{X}{empty}{full}}
\let\relax\fi
\typeout{\ifstrempty{X}{empty}{full}}
\csname @@end\endcsname\end

Breaks with error "Extra \else".

--
Heiko Oberdiek

GL

unread,
Dec 4, 2010, 6:59:22 AM12/4/10
to

Either a \space or \z@ stops \romannumeral.

The .log is lighter with \csname and if you wish a minimal
number of \expandafter(s) :

\newcommand\ifstrempty[3]{\romannumeral0%
\csname ltx@\if\relax\detokenize{#1}\relax
first\else second\fi oftwo\endcsname{ #2}{ #3}}

18 lines of .log, I don't think shorter even exists...


\documentclass{minimal}
\usepackage[T1]{fontenc}
\usepackage{etex,ltxcmds}
\begin{document}

\def\YES{YES}\def\NO{NO}

\makeatletter
\newcommand\ifstrempty[3]{\romannumeral0%
\csname ltx@\if\relax\detokenize{#1}\relax
first\else second\fi oftwo\endcsname{ #2}{ #3}}

\loggingall
\toks@\expandafter\expandafter\expandafter{\ifstrempty{}\YES \NO}

\detokenize\expandafter{\the\toks@}


\toks@\expandafter\expandafter\expandafter{\ifstrempty{ }\YES \NO}

\detokenize\expandafter{\the\toks@}

\stop

GL

unread,
Dec 4, 2010, 7:03:47 AM12/4/10
to

Then \ifcat $\detokenize{#1}$ economizes \expandafters,
(provided $ is the math shift at the time of the definition...)

Yours sincerely.

Heiko Oberdiek

unread,
Dec 4, 2010, 9:16:44 AM12/4/10
to
GL <goua...@gmail.com> wrote:

> The .log is lighter with \csname and if you wish a minimal
> number of \expandafter(s) :
>
> \newcommand\ifstrempty[3]{\romannumeral0%
> \csname ltx@\if\relax\detokenize{#1}\relax
> first\else second\fi oftwo\endcsname{ #2}{ #3}}
>
> 18 lines of .log, I don't think shorter even exists...

Dangerous ;-)



> \newcommand\ifstrempty[3]{\romannumeral0%
> \csname ltx@\if\relax\detokenize{#1}\relax
> first\else second\fi oftwo\endcsname{ #2}{ #3}}

The relevant .log lines (\loggingall):

| \ifstrempty #1#2#3->\romannumeral 0\csname ltx@\if \relax \detokenize {#1}\rela
| x first\else second\fi oftwo\endcsname { #2}{ #3}
| #1<-
| #2<-\YES
| #3<-\NO
| {\romannumeral}
| {\csname}
| {\if: (level 1) entered on line 27}
| {\detokenize}
| {true}
| {\else: \if (level 1) entered on line 27}
| {\fi: \if (level 1) entered on line 27}
|
| \ltx@firstoftwo #1#2->#1
| #1<- \YES
| #2<- \NO

(16 lines)

Even less lines are possible with:

\newcommand*\ifstrempty[1]{%
\romannumeral
\csname @ifstrempty@%
\ifcat$\detokenize{#1}$y\fi
\endcsname
}
\long\def\@ifstrempty@y#1#2{0 #2}%
\long\def\@ifstrempty@#1#2{0 #1}%

The .log lines:

| \ifstrempty #1->\romannumeral \csname @ifstrempty@\ifcat $\detokenize {#1}$y\fi
| \endcsname
| #1<-
| {\romannumeral}
| {\csname}
| {\ifcat: (level 1) entered on line 27}
| {\detokenize}
| {true}
| {\fi: \ifcat (level 1) entered on line 27}
|
| \@ifstrempty@y #1#2->0 #2
| #1<-\YES
| #2<-\NO

(13 lines)

--
Heiko Oberdiek

GL

unread,
Dec 4, 2010, 10:09:57 AM12/4/10
to
Le 04/12/2010 15:16, Heiko Oberdiek a écrit :
> GL<goua...@gmail.com> wrote:
>
>> The .log is lighter with \csname and if you wish a minimal
>> number of \expandafter(s) :
>>
>> \newcommand\ifstrempty[3]{\romannumeral0%
>> \csname ltx@\if\relax\detokenize{#1}\relax
>> first\else second\fi oftwo\endcsname{ #2}{ #3}}
>>
>> 18 lines of .log, I don't think shorter even exists...
>
> Dangerous ;-)

Dangerous Bend ? ;-)

I like .log like this.

> [...]


> (16 lines)
>
> Even less lines are possible with:
>

>[...]


> The .log lines:
>
> | \ifstrempty #1->\romannumeral \csname @ifstrempty@\ifcat $\detokenize {#1}$y\fi
> | \endcsname
> | #1<-
> | {\romannumeral}
> | {\csname}
> | {\ifcat: (level 1) entered on line 27}
> | {\detokenize}
> | {true}
> | {\fi: \ifcat (level 1) entered on line 27}
> |
> | \@ifstrempty@y #1#2->0 #2
> | #1<-\YES
> | #2<-\NO
>
> (13 lines)
>

Good ! It would just worth to give to \@ifstrempty@(y) a more
eloquent name. Any idea ?

\ifstrempty_result:true
\ifstrempty_result:false

Regards.

Ulrich D i e z

unread,
Dec 4, 2010, 11:27:57 AM12/4/10
to
GL wrote:

> Then \ifcat $\detokenize{#1}$ economizes \expandafters,
> (provided $ is the math shift at the time of the definition...)

Thanks:

\newcommand\ifstrempty[3]{%
\romannumeral0\ifcat$\detokenize{#1}$\expandafter
\@firstoftwo\else\expandafter\@secondoftwo\fi{ #2}{ #3}%
}

Do you see possibilities for shortening the "vanilla-code" below
while sticking to vanilla TeX?

\newcommand\ifstrempty[3]{%


\romannumeral\iffalse{\fi\expandafter\@secondoftwo\expandafter
{\expandafter{\string#1}\expandafter\@secondoftwo\string}%
\expandafter\@firstoftwo\expandafter{\iffalse}\fi0 #3}{0 #2}%
}%

Ulrich

Heiko Oberdiek

unread,
Dec 4, 2010, 11:47:05 AM12/4/10
to
GL <goua...@gmail.com> wrote:

> > | \ifstrempty #1->\romannumeral \csname @ifstrempty@\ifcat $\detokenize {#1}$y\fi
> > | \endcsname

> Good ! It would just worth to give to \@ifstrempty@(y) a more


> eloquent name. Any idea ?

In ltxcmds I am now using \LTXcmds@ifstrempty and
\LTXcmds@ifstrempty@.

> \ifstrempty_result:true
> \ifstrempty_result:false

Then the \else branch must be introduced again.
Without \else the name for "false" is a substring
of the name for "true".

--
Heiko Oberdiek

GL

unread,
Dec 4, 2010, 1:24:23 PM12/4/10
to
Le 04/12/2010 17:47, Heiko Oberdiek a écrit :
> GL<goua...@gmail.com> wrote:
>
>>> | \ifstrempty #1->\romannumeral \csname @ifstrempty@\ifcat $\detokenize {#1}$y\fi
>>> | \endcsname
>
>> Good ! It would just worth to give to \@ifstrempty@(y) a more
>> eloquent name. Any idea ?
>
> In ltxcmds I am now using \LTXcmds@ifstrempty and
> \LTXcmds@ifstrempty@.

What will be the name of the user command ?
\ltx@ifempty ?
\ltx@ifstrempty ?

I remember \ifstrempty is defined in etoolbox...

Ulrich D i e z

unread,
Dec 4, 2010, 2:27:47 PM12/4/10
to
GL wrote:

> What will be the name of the user command ?
> \ltx@ifempty ?
> \ltx@ifstrempty ?

I think "str" is an abbreviation for "string".

What does the term "string" mean in conjunction with TeX?

Some people use that term in order to refer to token-sequences
consisting only of (non-expandable / non-active) character-
tokens of catcode 11 or 12 (plus the ordinary space-token).

The macros in question are to be applicable not just to
such token-sequences but to any sequence of tokens
unless that sequence contains \outer-tokens.

Ulrich

Heiko Oberdiek

unread,
Dec 4, 2010, 4:30:21 PM12/4/10
to
GL <goua...@gmail.com> wrote:

> Le 04/12/2010 17:47, Heiko Oberdiek a écrit :
> > GL<goua...@gmail.com> wrote:
> >
> >>> | \ifstrempty #1->\romannumeral \csname @ifstrempty@\ifcat $\detokenize {#1}$y\fi
> >>> | \endcsname
> >
> >> Good ! It would just worth to give to \@ifstrempty@(y) a more
> >> eloquent name. Any idea ?
> >
> > In ltxcmds I am now using \LTXcmds@ifstrempty and
> > \LTXcmds@ifstrempty@.

s/ifstrempty/ifempty/g;

> What will be the name of the user command ?
> \ltx@ifempty ?
> \ltx@ifstrempty ?

\ltx@ifempty

> I remember \ifstrempty is defined in etoolbox...

Therefore I have prefixed all with "ltx@".

--
Heiko Oberdiek

0 new messages