2

In the following MN(ot)WE, how can I fix \merge so that it merges the preceding values made automatically by \split?

The last code gives 1 | 12 | 123 | 1234 instead of 1 > 12 > 123 > 1234.

\documentclass[12pt,a4paper]{article}


\newcommand\split[1]{%
    \renewcommand\accumulator{}
    \splitacc#1\nil
}


\newcommand\accumulator{}

\def\splitacc#1#2\nil{
    \accumulator{}#1%
    \edef\old{\accumulator{}#1}
    \renewcommand\accumulator{\old{}}
    \if\relax\detokenize{#2}\relax\else
        |\splitacc#2\nil
    \fi
}

\newcommand\testbis[1]{%
    \merge#1|\nil%
}

\def\merge#1|#2\nil{
    #1%
    \if\relax\detokenize{#2}\relax\else
        $>$ \merge#2\nil%
    \fi%
}



\begin{document}

\split{1234}
% --> 1 | 12 | 123 | 1234


\testbis{1 | 12 | 123 | 1234}
% --> 1 > 12 > 123 > 1234

\testbis{\split{1234}}
% --> 1 | 12 | 123 | 1234 instead of 1 > 12 > 123 > 1234

\end{document}
5
  • 1
    Doesn’t my code for your other question already take care of the variable delimiters?
    – egreg
    Aug 4, 2020 at 12:53
  • I have updated my code. I will try with your solution but on the other hand I would like to learn what is wrong in the preceding code.
    – projetmbc
    Aug 4, 2020 at 14:26
  • I didn't check the entirety of the code, but \testbis{\split{1234}} won't work because \split is not expandable, so you can't have function-like behaviour (evaluate the argument \split{1234} then evaluate the caller \testbis). \testbis will see \split{1234}. You need to write \split to be expandable. Aug 4, 2020 at 14:39
  • How I made that ? Juste give me the general way to do that and I will do it by myself. Thanks for the explanation. I have to go and I will back in three hours so there is no emergency.
    – projetmbc
    Aug 4, 2020 at 14:40
  • @egreg I've just seen your answer now and I have added it in this post. Thanks a lot.
    – projetmbc
    Aug 4, 2020 at 22:19

2 Answers 2

3

Here's a simple implementation of what you want.

It is not very robust, as it may lose braces and spaces in the argument, and I didn't test with corner cases: basically it works if the argument consists of "normal" (non-brace and non-space) tokens. It's supposed to be instructive rather than practical. For a robust implementation I'd use expl3 to have a finer-grained control of different types of tokens and expansion.

That said: your code didn't work because your implementation of \split is not expandable (it uses assignments), so you can't have function-like behaviour (evaluate the argument, and then evaluate the caller). To have that, \split has to be expandable.

In this case it's rather easy to build a recursive \splitacc that doesn't rely on assignments. You can have it take one more argument which will be the accumulator, so you just put things there and they will carry on to further calls to the macro. I defined \splitacc{<accum>}<token><token-list>\nil so that it adds each <token> to <accum> until the end of the <token-list> is reached. When the input is over, \splitaccend loops through the separated tokens, accumulating them in the output.

Then, to have it work as argument to \testbis I defined a \exparg macro that (relies on the \expanded primitive and) fully expands the first argument to a macro. Use it as \exparg\macro{<argument>}. It is the same as expl3's \exp_args:Ne.

I also added a macro to trim spaces around the items in \testbis so that spacing is implementation-independent. You can add spaces in the separator.

\documentclass[12pt,a4paper]{article}

\makeatletter

% a quark
\def\qmark{\qmark}

% a macro to trim spaces (not very robust: may lose braces)
\def\trimspace#1{%
  \@firstofone{\expandafter\@trimspace
    \@firstofone#1 \nil\@trimspace} \nil\@@trimspace\qmark}
\def\@trimspace#1 \nil#2{#2#1\nil\@trimspace}
\def\@@trimspace#1\nil#2\qmark{#1}

% \split starts with an empty accumulator and ends
% with a \qmark to identify the end.
\newcommand\split[1]{%
  \splitacc{}#1\qmark\nil}

% \splitacc checks if the end is reached.  If so leaves the accumulator,
% otherwise recurses with #1|#2 (this adds a leading | in the first
% iteration which is removed at the end).
\def\splitacc#1#2#3\nil{%
  \ifx\qmark#2%
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
    {\splitaccend#1\qmark}% use the accumulated string (remove leading marker)
    {\splitacc{#1{#2}}#3\nil}}% add to the accumulator and loop
\def\splitaccend#1#2{%
  #1%
  \ifx\qmark#2
    \expandafter\@gobble
  \else
    |\expandafter\@firstofone
  \fi{\splitaccend{#1#2}}}

\newcommand\testbis[1]{%
  \merge#1|\nil}

\def\merge#1|#2\nil{%
  \trimspace{#1}%
  \if\relax\detokenize{#2}\relax\else
    $>$\merge#2\nil%
  \fi}

% \exparg expands one argument of a macro.  Simlar to \exp_args:Ne
\def\exparg#1#2{%
  \expandafter#1\expanded{{#2}}}

\begin{document}

\split{1234} % ---> 1 | 12 | 123 | 1234

\testbis{1 | 12 | 123 | 1234}

\exparg\testbis{\split{1234}}

\end{document}

As requested, and expl3 implementation, for didactic purposes. The idea here is to loop on the argument token list, and take a different action depending on the type of the next token (space, group of tokens, or “normal”—everything else). The entry level macro just sets the environment for the main looping macro. This looping macro looks at the next token in the input and selects one of three macros depending on the type of said token. Then, each dedicated macro does the actual work of the function.

This conditional acting allows you to process spaces and groups of tokens properly (depending on the behaviour you want). Using xparse allows you to easily define optional arguments to change what tokens are looked for and what tokens are used as replacement.

\documentclass{article}

\usepackage{xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand \split { O{|} m }
  { \mbc_split:Nn #1 {#2} }
% Entry-level function:
\cs_new:Npn \mbc_split:Nn #1 #2
  { \__mbc_split_loop:Nnw #1 { } #2 \q_recursion_tail \q_recursion_stop }
% Looping function to choose type of token:
\cs_new:Npn \__mbc_split_loop:Nnw #1 #2 #3 \q_recursion_stop
  {
    \tl_if_head_is_N_type:nTF {#3}
      { \__mbc_split_ntype:NnN }
      {
        \tl_if_head_is_group:nTF {#3}
          { \__mbc_split_group:Nnn }
          { \__mbc_split_space:Nnw }
      }
    #1 {#2} #3 \q_recursion_stop
  }
% Action for 'normal' tokens:
\cs_new:Npn \__mbc_split_ntype:NnN #1 #2 #3
  {
    \quark_if_recursion_tail_stop:N #3
    \tl_if_empty:nTF {#2}
      { \exp_not:n { #3 } }
      { \exp_not:n { #1 #2#3 } }
    \__mbc_split_loop:Nnw #1 {#2#3}
  }
% Action for grouped tokens:
\cs_new:Npn \__mbc_split_group:Nnn #1 #2 #3
  {
    \exp_not:n { #1 #2{#3} }
    \__mbc_split_loop:Nnw #1 { #2{#3} }
  }
\cs_new:Npn \__mbc_split_space:Nnw #1 #2 ~
  {
    \exp_not:n { #1 #2~ }
    \__mbc_split_loop:Nnw #1 { #2~ }
  }
%
%
\NewExpandableDocumentCommand \testbis { s D(){|} O{$>$} m }
  {
    \IfBooleanTF{#1}
      { \mbc_replace:Nne #2 {#3} {#4} }
      { \mbc_replace:Nnn #2 {#3} {#4} }
  }
\cs_generate_variant:Nn \mbc_replace:Nnn { Nne }
% Entry-level function:
\cs_new:Npn \mbc_replace:Nnn #1 #2 #3
  { \__mbc_replace_loop:Nnw #1 {#2} #3 \q_recursion_tail \q_recursion_stop }
% Looping function to choose type of token:
\cs_new:Npn \__mbc_replace_loop:Nnw #1 #2 #3 \q_recursion_stop
  {
    \tl_if_head_is_N_type:nTF {#3}
      { \__mbc_replace_ntype:NnN }
      {
        \tl_if_head_is_group:nTF {#3}
          { \__mbc_replace_group:Nnn }
          { \__mbc_replace_space:Nnw }
      }
    #1 {#2} #3 \q_recursion_stop
  }
% Action for 'normal' tokens:
\cs_new:Npn \__mbc_replace_ntype:NnN #1 #2 #3
  {
    \quark_if_recursion_tail_stop:N #3
    \token_if_eq_charcode:NNTF #1 #3
      { \exp_not:n {#2} }
      { \exp_not:n {#3} }
    \__mbc_replace_loop:Nnw #1 {#2}
  }
% Action for grouped tokens:
\cs_new:Npn \__mbc_replace_group:Nnn #1 #2 #3
  { {#3} \__mbc_replace_loop:Nnw #1 {#2} }
% Action for space tokens:
\cs_new:Npn \__mbc_replace_space:Nnw #1 #2 ~
  { ~ \__mbc_replace_loop:Nnw #1 {#2} }
\ExplSyntaxOff

\begin{document}

\split{1234}

\split{1 2{\textit{3}}4}

% * argument forces expansion
\testbis*{\split{1234}}

\testbis*{\split{1 2{\textit{3}}4}}

% ()-delimited argument is the token searched (must be a single token)
% []-delimited argument are the replacement tokens
\testbis(-)[$+$]{1-1 2-1 2{\textit {3}}4}

\end{document}
5
  • Thanks. Indeed the argument must be a basic text without any braces.
    – projetmbc
    Aug 4, 2020 at 22:20
  • @projetmbc I added an expl3 version (I was writing it at the time you posted your answer) which deals with a couple more corner cases (not necessarily useful for your application, but...). This is more or less the same approach used in expl3's case-changing functions, and ´\text_expand:n`, and others that require fine-grained expandable token analysis. Aug 4, 2020 at 22:54
  • The way to make a macro expandable in pure TeX is to use the \expandafter everywhere it is need. Am I right ?
    – projetmbc
    Aug 5, 2020 at 23:14
  • @projetmbc No. \expandafter is (very) useful, but alone it won't make your macro expandable (expandability is a tricky term in itself). For a macro to be expandable it has to be defined in terms of other expandable macros and/or expandable primitives. See the comments I just wrote under this other answer: tex.stackexchange.com/questions/557511/… hopefully they clarify things a bit. Aug 5, 2020 at 23:19
  • Thanks for the link and the explanation.
    – projetmbc
    Aug 5, 2020 at 23:34
1

Here is a expl3 solution build directly from a solution of egreg by adding the macro exparg of Phelype Oleinik.

% Sourceq
%   * https://tex.stackexchange.com/a/557325/6880
%   * https://tex.stackexchange.com/a/557377/6880

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\splitseq}{O{|}m}
 {
  \projetmbc_splitseq:nn { #1 } { #2 }
 }

\cs_new:Nn \projetmbc_splitseq:nn
 {%
  % #1 is the delimiter
  % #2 is empty if we don't want the delimiter (first cycle)
  % #3 is the accumulated material
  % #4 is the next item
  % #5 is what remains to be scanned
  \__projetmbc_splitseq:nnnw { #1 } { } { } #2 \q_nil \q_stop
 }
\cs_new:Npn \__projetmbc_splitseq:nnnw #1 #2 #3 #4 #5 \q_stop
 {
  \token_if_eq_meaning:NNF #4 \q_nil
   { #2 #3 #4 \__projetmbc_splitseq:nnnw { #1 } { #1 } { #3#4 } #5 \q_stop }
 }

\ExplSyntaxOff

\newcommand\testbis[1]{%
  \merge#1|\nil}

\def\merge#1|#2\nil{%
  #1%
  \if\relax\detokenize{#2}\relax\else
    $>$\merge#2\nil%
  \fi}
  
\def\exparg#1#2{%
  \expandafter#1\expanded{{#2}}}
  
\begin{document}

\splitseq{1234}

\exparg\testbis{\splitseq{1234}}

\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .