TransWikia.com

Braces in a @for loop

TeX - LaTeX Asked on June 9, 2021

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

defuselist#1{%
  @fortemp:=#1do{meaningtemppar}}

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

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


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

deflistwithcommasandspaces{ {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

deflistwithcommas{{Lima},Alpha,{Delta,Oscar},Tango,{Whisky,Echo,Romeo},Xray}

deflistwithcommasandspaces{ {Lima}, Alpha, {Delta,Oscar}, Tango, {Whisky,Echo,Romeo}, Xray}

newifif@isinlist
defsubtractlist#1#2{% #1:original list, #2:remove list
  gdef@subtractlist{}%
  @for@tempa:=#1do{%
    @isinlistfalse%
    @for@tempb:=#2do{ifx@tempa@tempb@isinlisttruefi}%
      if@isinlist%
        else%
          ifx@subtractlistempty%
              expandaftergdef%
              expandafter@subtractlist%
%             expandafter{@tempa}%
%%% extra pair of braces added
              expandafter{expandafter{@tempa}}%
            else%
              expandafterg@addto@macro%
              expandafter@subtractlist%
%             expandafter{expandafter,@tempa}%
%%% extra pair of braces added
              expandafter{expandafter,expandafter{@tempa}}%
          fi%
      fi%
    }%
  letcurrentlist@subtractlist}

defremovelista{Bravo,Lima}
defremovelistb{Bravo,{Delta,Oscar}}


subtractlist{listwithcommas}{removelista}
verb|subtract Lima:| meaningcurrentlist

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

bigskip

subtractlist{listwithcommasandspaces}{removelista}
verb|subtract Lima:| meaningcurrentlist

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

end{document}

Without extra braces:

enter image description here

With extra braces:

enter image description here

2 Answers

@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
defq@stop{q@stop}
defuselist#1{expandafteruselist@expandafter@empty#1,q@stop,}
defuselist@#1,{expandafteruselist@@expandafter{#1}}%
defuselist@@#1{%
  ifxq@stop#1else
    uselistcmd{#1}%
    expandafteruselist@expandafter@empty
  fi}
makeatother

defuselistcmd#1{detokenize{(#1)}par}

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

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


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

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

end{document}

Correct answer by Phelype Oleinik on June 9, 2021

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.

Answered by Don Hosek on June 9, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP