2

While writing macros to manipulate the items of a list with a \@for loop, I have found that braced items behave differently depending on whether they have a leading space or not:

\documentclass{article}
\usepackage[T1]{fontenc}

\begin{document}
\makeatletter

\def\uselist#1{%
  \@for\temp:=#1\do{\meaning\temp\par}}

\verb|\listwithcommas{Lima,Alpha,{Delta,Oscar},Tango,{Whisky,Echo,Romeo},Xray}|

\def\listwithcommas{Lima,Alpha,{Delta,Oscar},Tango,{Whisky,Echo,Romeo},Xray}
\uselist{\listwithcommas}


\verb|\listwithcommasandspaces{ {Lima}, Alpha, {Delta,Oscar}, Tango, {Whisky,Echo,Romeo}, Xray}|

\def\listwithcommasandspaces{ {Lima}, Alpha, {Delta,Oscar}, Tango, {Whisky,Echo,Romeo}, Xray}
\uselist{\listwithcommasandspaces}


\end{document}

I would like to maintain the braces, as when there is a space before the item, after applying the macros. Is it possible to do that inside the \@for loop? The intention is that, however the list is provided (through a macro), the output list will maintain the braces. That is, both \listwithcommas and \listwithcommasandspaces should give the second output.

Another question is why the behaviour is different when there are spaces or not.

enter image description here

EDIT

Here is an example to clarify the intention.

Suppose I have a macro \subtractlist and that it is used two times. First I subtract Lima from the list and then {Delta,Oscar} from the resulting list. Since I have lost the braces after the first use, the second one will not work.

I can enclose all the items of the new formed list in braces. In that case, it will work with \listwithcommas but not with \listwithcommasandspaces, because there are double braces and spaces.

\documentclass{article}
\usepackage[T1]{fontenc}

\begin{document}
\makeatletter

\def\listwithcommas{{Lima},Alpha,{Delta,Oscar},Tango,{Whisky,Echo,Romeo},Xray}

\def\listwithcommasandspaces{ {Lima}, Alpha, {Delta,Oscar}, Tango, {Whisky,Echo,Romeo}, Xray}

\newif\if@isinlist
\def\subtractlist#1#2{% #1:original list, #2:remove list
  \gdef\@subtractlist{}%
  \@for\@tempa:=#1\do{%
    \@isinlistfalse%
    \@for\@tempb:=#2\do{\ifx\@tempa\@tempb\@isinlisttrue\fi}%
      \if@isinlist%
        \else%
          \ifx\@subtractlist\empty%
              \expandafter\gdef%
              \expandafter\@subtractlist%
%             \expandafter{\@tempa}%
%%% extra pair of braces added
              \expandafter{\expandafter{\@tempa}}%
            \else%
              \expandafter\g@addto@macro%
              \expandafter\@subtractlist%
%             \expandafter{\expandafter,\@tempa}%
%%% extra pair of braces added
              \expandafter{\expandafter,\expandafter{\@tempa}}%
          \fi%
      \fi%
    }%
  \let\currentlist\@subtractlist}

\def\removelista{Bravo,Lima}
\def\removelistb{Bravo,{Delta,Oscar}}


\subtractlist{\listwithcommas}{\removelista}
\verb|subtract Lima:| \meaning\currentlist

\subtractlist{\currentlist}{\removelistb}
\verb|subtract {Delta,Oscar}:|\meaning\currentlist

\bigskip

\subtractlist{\listwithcommasandspaces}{\removelista}
\verb|subtract Lima:| \meaning\currentlist

\subtractlist{\currentlist}{\removelistb}
\verb|subtract {Delta,Oscar}:|\meaning\currentlist

\end{document}

Without extra braces:

enter image description here

With extra braces:

enter image description here

2 Answers 2

5

\@for is really minimal, so it doesn't work too hard on removing or keeping braces and spaces. The braces are removed as part of TeX's argument grabbing, so you have to add extra safety to keep them. I added an \@empty in front of every item so that TeX won't remove braces, then I expand that \@empty to remove it before passing the item to \uselistcmd.

enter image description here

\documentclass{article}
\usepackage[T1]{fontenc}

\begin{document}

\makeatletter
\def\q@stop{\q@stop}
\def\uselist#1{\expandafter\uselist@\expandafter\@empty#1,\q@stop,}
\def\uselist@#1,{\expandafter\uselist@@\expandafter{#1}}%
\def\uselist@@#1{%
  \ifx\q@stop#1\else
    \uselistcmd{#1}%
    \expandafter\uselist@\expandafter\@empty
  \fi}
\makeatother

\def\uselistcmd#1{\detokenize{(#1)}\par}

\verb|\listwithcommas{Lima,Alpha,{Delta,Oscar},Tango,{Whisky,Echo,Romeo},Xray}|

\def\listwithcommas{Lima,Alpha,{Delta,Oscar},Tango,{Whisky,Echo,Romeo},Xray}
\uselist{\listwithcommas}


\verb|\listwithcommasandspaces{ {Lima}, Alpha, {Delta,Oscar}, Tango, {Whisky,Echo,Romeo}, Xray}|

\def\listwithcommasandspaces{ {Lima}, Alpha, {Delta,Oscar}, Tango, {Whisky,Echo,Romeo}, Xray}
\uselist{\listwithcommasandspaces}

\end{document}
6
  • Thank you. I have still not dared to start with expl3. It seems overwhelming. However, removing the braces is just what I do not want. I have edited the question to try to clarify it a bit. Dec 23, 2020 at 8:10
  • @RaoulKessels Ah, sorry, I misunderstood. I edited with a version that doesn't change the input at all (no brace nor space removal). Also no expl3 (and you can see the amount of \expandafters that pop up :) Dec 23, 2020 at 13:13
  • So, basically you have made a new \@for command. Very impressive! As per the \expandafters, don't worry. In a command for trimming spaces from a list I had to use \multiexpandafter{3} to keep the code readable. Dec 24, 2020 at 9:30
  • @RaoulKessels Kind of, yes, but both \@for and \clist_map_inline:nn have a few more features than my code. The above is just a really basic mapping over a list plus not removing braces. Use with care :) Dec 24, 2020 at 11:56
  • Thx, I will take care. Feliz Natal ;-) Dec 24, 2020 at 20:17
1

If you look closely at your output, you'll see that the output includes a space for the value of \temp which is not the case with the first set.

What happens in the first case is that TeX is parsing everything up to the comma as the argument being digested. The braces get discarded here because it's getting passed a literal argument of, e.g., {Lima} which it treats as a single argument and drops the braces, much like when you pass an argument to a macro enclosed in braces.

But in the space version, what's coming in is no longer {Lima} but {Lima} (the space doesn't get discarded because the expansion of \@for will be inserting non-letter tokens between \@for and the argument that gets digested. Now the argument is space plus a group so it keeps the braces because they're no longer at the outer level and discardable.

It's possible to do what you want (TeX is Turing-complete, after all), but it would not be simple. Enclosing every item in the list in braces and double-bracing the items you want to keep braces would work. Or you could add the spaces and parse them out of the items that you get. There might be some fancy stuff possible with token lists.

1
  • I already had tried too add additional braces in my macros, but it want work either. I have edited the question with an example. Dec 23, 2020 at 8:12

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.