7

I want to execute particular command for some space separated list as argument. And then execute the same command for the same list without last element and do it until list is empty. As extra point store cutted out element to the separate list.

What I have so far:

\documentclass{standalone}

\makeatletter

\def\split#1{
  \typeout{split:#1}
  \def\init{}
  \def\last{}
  \@split#1 \@empty
}

\def\@split#1 #2{%
  \typeout{1:#1}
  \typeout{2:#2}
  \ifx #2\@empty
    \def\last{#1}
  \else
    \ifx\init\@empty
      \expandafter\expandafter\expandafter\def
      \expandafter\expandafter\expandafter\init
      \expandafter\expandafter\expandafter{\expandafter\init#1}
    \else
      \expandafter\expandafter\expandafter\def
      \expandafter\expandafter\expandafter\init
      \expandafter\expandafter\expandafter{\expandafter\init\space#1}
    \fi
    \expandafter\@split
  \fi
  #2%
}

\def\writer#1{%
  \typeout{writer:#1}
  \split{#1}
  \typeout{writer-init:\init}
  \typeout{writer-last:\last}
  \ifx \init\@empty\else\writer{\init}\fi
}

\makeatother

\begin{document}

\writer{Takes several words as argument}

\end{document}

The problem is that on the second iteration @split receive empty arguments. I use \typeout{} command to see what arguments every command receives. Console output is follow:

writer:Takes several words as argument
split:Takes several words as argument
1:Takes
2:s
1:several
2:w
1:words
2:a
1:as
2:a
1:argument
2:
writer-init:Takes several words as
writer-last:argument
writer:Takes several words as
split:Takes several words as
1:
2:
writer-init:
writer-last:

As you can see \split itself receive correct data, but \@split command can't parse arguments. I am sure I made mistake in the passing data to the \@split, but have no idea how to fix it.

I'm very appreciate for any suggestions.

7

You're missing a couple of \expandafter's:

\ifx\init\@empty\else\expandafter\writer\expandafter{\init}\fi

If I change the corresponding line, I get this output to the terminal:

writer:Takes several words as argument
split:Takes several words as argument
1:Takes
2:s
1:several
2:w
1:words
2:a
1:as
2:a
1:argument
2:
writer-init:Takes several words as
writer-last:argument
writer:Takes several words as
split:Takes several words as
1:Takes
2:s
1:several
2:w
1:words
2:a
1:as
2:
writer-init:Takes several words
writer-last:as
writer:Takes several words
split:Takes several words
1:Takes
2:s
1:several
2:w
1:words
2:
writer-init:Takes several
writer-last:words
writer:Takes several
split:Takes several
1:Takes
2:s
1:several
2:
writer-init:Takes
writer-last:several
writer:Takes
split:Takes
1:Takes
2:
writer-init:
writer-last:Takes

Here's a perhaps better implementation, which exploits token registers instead of those complex chain of \expandafter's or, in alternative, \unexpanded which is from e-TeX. Note that all recent TeX distributions use e-TeX when LaTeX is called.

\documentclass{article}

\makeatletter

\def\split#1{%
  \typeout{split:#1}%
  \def\init{}%
  \def\last{}%
  \@split#1 \@empty
}

\def\@split#1 #2{%
  \typeout{1:#1}%
  \typeout{2:\noexpand#2}% \noexpand for showing also `\@empty`
  \ifx #2\@empty
    \def\last{#1}%
  \else
    \ifx\init\@empty
      \def\init{#1}%
    \else
      % with e-TeX use the following line
      \edef\init{\unexpanded\expandafter{\init}\space\unexpanded{#1}}%
      % without e-TeX use the following two lines
      %\toks@=\expandafter\expandafter\expandafter{\expandafter\init\space#1}%
      %\edef\init{\the\toks@}%
    \fi
    \expandafter\@split
  \fi
  #2%
}

\def\writer#1{%
  \typeout{writer:#1}%
  \split{#1}%
  \typeout{writer-init:\init}%
  \typeout{writer-last:\last}%
  \ifx\init\@empty\else\expandafter\writer\expandafter{\init}\fi
}

\makeatother

\begin{document}

\writer{Takes several words as argument}

\end{document}

Here's a LaTeX3 implementation of what you seem to want. Note that this is rather straightforward and also avoids your mixing of \split and \writer.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\bronislavsplit}{m}
 {
  \bronislav_split:n { #1 }
 }

\seq_new:N \l_bronislav_input_seq
\tl_new:N \l_bronislav_last_tl

\cs_new_protected:Npn \bronislav_split:n #1
 {
  % split the input at spaces
  \seq_set_split:Nnn \l_bronislav_input_seq { ~ } { #1 }
  % start the recursion
  \__bronislav_iterate:
 }

\cs_new_protected:Npn \__bronislav_iterate:
 {
  \seq_if_empty:NF \l_bronislav_input_seq
   {% this is executed only if the sequence is not empty
    % for debugging, show the what's in the sequence
    \typeout{writer: ~ \seq_use:Nn \l_bronislav_input_seq { ~ }}
    % split off the last element
    \seq_pop_right:NN \l_bronislav_input_seq \l_bronislav_last_tl
    \seq_map_inline:Nn \l_bronislav_input_seq
     {
      % do something with each item
      \bronislav_do_item:n { ##1 }
     }
    % do something with the last item
    \bronislav_do_last_item:V \l_bronislav_last_tl
    % keep on the recursion
    \__bronislav_iterate:
   }
 }

\cs_new_protected:Npn \bronislav_do_item:n #1
 {
  \typeout { item: ~ #1 }
 }
\cs_new_protected:Npn \bronislav_do_last_item:n #1
 {
  \typeout { last ~ item: ~ #1 }
 }
% generate a variant because a variable is passed
\cs_generate_variant:Nn \bronislav_do_last_item:n { V }
\ExplSyntaxOff

\begin{document}

\bronislavsplit{Takes several words as argument}

\end{document}

Here's the output on the terminal:

writer: Takes several words as argument
item: Takes
item: several
item: words
item: as
last item: argument
writer: Takes several words as
item: Takes
item: several
item: words
last item: as
writer: Takes several words
item: Takes
item: several
last item: words
writer: Takes several
item: Takes
last item: several
writer: Takes
last item: Takes

Of course you want to do something more useful with the last two commands.

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.