\def\exchange#1#2{#2#1}
\def\split#1{\splitloop{}{}#1\end}
\def\splitloop#1#2#3{%
% #1 - separator in this iteration
% #2 - digits accumulated so far
% #3 - digit or \end collected in this iteration
\ifx\end#3\else\exchange{#1#2#3\splitloop{ | }{#2#3}}\fi
}
\tt
(\split{})
(\split{1})
(\split{12})
(\split{123})
(\split{1234})
\bye

A variant where the result is delivered after triggering two expansion-steps/where the result is delivered, e.g., after \split
being "hit" by \expandafter
twice - this might be useful in situations where you need to control expansion/where you need to know the exact amount of expansion-steps until obtaining the result - the gist is:
\romannumeral
- triggers expansion while gathering digits belonging to the number that is to be represented in roman notation.
- silently discards a space-token ending the digit-sequence which forms that number.
- in any case swallows the token-sequence/digit-sequence which forms that number. In case the number found not being positive silently no tokens will be delivered at all.
(A formal presentation of TeX's ⟨number⟩-quantities in terms of Backus/Naur-notation of TeX's grammar can be found in the TeXbook, Chapter 24: Summary of Vertical Mode.)
Thus \romannumeral
can be used for triggering a lot of expansion- and macro-argument-exchanging-work as long as it is ensured that in the end \romannumeral
will have gathered a number which is not positive.
With \split
as implemented below one expansion-step needs to be triggered for obtaining from the toplevel-expansion of \split
a token sequence which begins with the tokens \romannumeral0
.
Then another expansion-step needs to be triggered for obtaining the result of \romannumeral
. \romannumeral
will first initiate the process of gathering the token(s) which form the number to represent in roman notation. The first token is the digit "0", thus the process of gathering turns into the process of gathering either more digits or a token which terminates the digit-sequence and therefore also terminates the process of gathering. During that process of gathering - this is the desired side-effect - a lot of expansion- and argument-exchanging-work is done until encountering a token which terminates the process of gathering digits. If that token is a space-token, it will silently be discarded. In the example below the "expansion- and argument-exchanging-work" is focused on tokens which form an expansion-based loop \splitloop
. \splitloop
via calling itself recursively manages things in terms of its macro-arguments. When the loop terminates, the macro-argument which holds the result will be delivered plus a leading space-token. This leading space token will be discarded and will terminate \romannumeral
's process of gathering digits. Thus \romannumeral
will find the number "0" which is not positive. As that number is not positive, \romannumeral
will silently not deliver any roman digits/any tokens at all. But you got the expansion- and argument-exchanging work done.
\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\def\split#1{\romannumeral0\splitloop{}{}{}#1\end}
\def\splitloop#1#2#3#4{%
% #1 - result collected so far
% #2 - separator in this iteration
% #3 - digits accumulated so far
% #4 - digit or \end collected in this iteration
\ifx\end#4\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{ #1}{\splitloop{#1#2#3#4}{ | }{#3#4}}%
}
\tt
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\macro
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter(%
\split{}%
)%
}%
\string\macro: \meaning\macro
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\macro
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter(%
\split{1}%
)%
}%
\string\macro: \meaning\macro
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\macro
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter(%
\split{12}%
)%
}%
\string\macro: \meaning\macro
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\macro
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter(%
\split{123}%
)%
}%
\string\macro: \meaning\macro
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\macro
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter(%
\split{1234}%
)%
}%
\string\macro: \meaning\macro
% Of course shorter would be:
% \expandafter\def\expandafter\macro\expandafter{\expandafter(\romannumeral0\splitloop{}{}{}1234\end)}
% The point is that the amount of triggers for expansion-steps needed
% for obtaining the result is constant.
\bye

\edef\old{\accumulator{}#1}
instead of\def
.