I think a reliable algorithm for expanding outgoing from an arbitrary set of tokens at most k times is not possible:
Expanding at most one time is already a problem:
You'd need an algorithm which recursively iterates on a ⟨list of not yet expanded tokens⟩ and maintains a ⟨list of already expanded tokens⟩ as follows:
Step 1:
Check whether the ⟨list of not yet expanded tokens⟩ is empty.
If so: Deliver the ⟨list of already expanded tokens⟩.
If not so:
In case the first element/the first token of the ⟨list of
not yet expanded tokens⟩ is not expandable, remove it from
the ⟨list of not yet expanded tokens⟩ and add it
at the end of the ⟨list of already expanded
tokens⟩.
In case the first element/the first token of the
⟨list of not yet expanded tokens⟩ is expandable,
expand it and remove the result of that expansion from the
⟨list of not yet expanded tokens⟩ and add the
result of that expansion at the end of the ⟨list of already
expanded tokens⟩
- Repeat Step 1.
One crucial point hereby is "removing the result of that expansion from the ⟨list of not yet expanded tokens⟩ and adding the result of that expansion at the end of the ⟨list of already expanded tokens⟩".
That point is crucial because it implies that after expansion you need to detect which tokens belong to the result of that expansion and which tokens were already in the ⟨list of not yet expanded tokens⟩ before that expansion took place.
Assume, e.g., a macro which is defined as follows: \def\macro#1#2#3{\A\B\C}
, while the ⟨list of not yet expanded tokens⟩ holds the following content:
\macro \A\B\C Whatsoever
Now you need to have \macro
expanded once. After that, the ⟨list of not yet expanded tokens⟩ holds the following content:
\A\B\C Whatsoever
.
Now you need to move the result from expanding \macro
towards the ⟨list of already expanded tokens⟩.
In this case the result of expanding \macro
is formed by the tokens \A\B\C
.
How to detect that these \A\B\C
are the replacement-text/the expansion-result from expanding \macro
and thus are not the same as the \A\B\C
that before expansion were in the ⟨list of not yet expanded tokens⟩?
(Parsing the result of \meaning
with every expandable token for finding out whether it is a macro that processes arguments does not deliver reliable information about the parameter-texts of macros because with \meaning
you don't have reliable information about the category-codes of tokens / about whether a set of delivered characters denotes a set of character tokens (of whatsoever category code) or a control sequence...)
Another crucial point is that you'd need to iterate "token-wise" while macros usually work "argument-wise".
In your special case, where you already know about definitions and tokens delivered by expansion, you can, e.g., do:
\def\a{3}
\def\b{2\a}
\def\c{1\b}
% You wish to obtain a macro containing "12\a" from using only \c
% \expandaf-% \expandaf-%
% ter- % ter- %
% chain 1 % chain 2 %
% | % | %
\expandafter\expandafter
\expandafter \def
\expandafter\expandafter
\expandafter \expandedTwice
\expandafter\expandafter
\expandafter {%
\expandafter\expandafter
\c}
\expandafter
-chain 1 delivers:
% \expandaf-%
% ter- %
% chain 2 %
% | %
\expandafter
\def
\expandafter
\expandedTwice
\expandafter
{%
\expandafter
1\b}
\expandafter
-chain 2 delivers:
\def
\expandedTwice
{%
12\a}
(In case you are not familiar to \expandafter
:
\expandafter
is an expandable primitive which works on the next and on the next but one token:
In case the next but one token is expandable, the replacement-text of
\expandafter⟨next token⟩⟨next but one token⟩
will be
⟨next token⟩⟨top-level-expansion of next but one token⟩
.
In case the next but one token is not expandable, the replacement-text of
\expandafter⟨next token⟩⟨next but one token⟩
will be
⟨next token⟩⟨next but one token⟩
.
In other words: (La)TeX considers the \expandafter
-work done and removes the \expandafter
from the token-stream when top-level-expanding the next-but-one token is finished.
That's why you can have \expandafter
-chains for "hopping" over tokens that shall not be expanded.
E.g., if you have \def\foo{bar}
, and do
\expandafter 1\expandafter 2\expandafter 3\foo
, you will get
123bar
:
Carrying out the first \expandafter
causes the second \expandafter
to be carried out. Hereby "carrying out the second \expandafter
" is considered an aspect of carrying out the first \expandafter
.
Carrying out the second \expandafter
in turn causes the third \expandafter
to be carried out. Hereby "carrying out the third \expandafter
" is considered an aspect of carrying out the second \expandafter
.
The third \expandafter
causes \foo
to be top-level-expanded.
When the top-level-expansion of \foo
is delivered, (La)TeX will consider the expansion-work of the third \expandafter
done.
This expansion-work was initiated by the second \expndafter
.
As the expansion-work initiated by the second \expandafter
is done, now the expansion-work of the second \expandafter
is done.
The expansion-work of the second \expandafter
was initiated by the first \expandafter
.
As the expansion-work initiated by the first \expandafter
is done, now the expansion-work of the first \expandafter
is done.)
But if you have, e.g.,
\catcode`\(=1 %
\catcode`\)=2 %
\def\a{3}
\def\b{2\a}
\def\c{11111(1)11111\b}
, and wish to obtain 11111(1)111112\a
—parentheses still of catcode 1 / 2 —outgoing from \c
, this will probably turn out an interesting task.
By the way 1: The methods of choice for obtaining the result of expansion highly depend on the context:
Within a "pure expansion context", i.e., e.g., within \csname..\endcsname
or within \write{...}
or within \edef{..}
you cannot have LaTeX define temporary macros/you cannot have LaTeX perform whatsoever assignments (with the exception of the result of \csname..\endcsname
locally assigning the meaning of the \relax
-primitive in case the control-sequence-token constructed is undefined).
By the way 2: \edef
/ \xdef
is not reliable in all situations.
E.g., look at:
\edef\test{%
Where does the assignment end? Here? \iffalse{\fi}%
{\iffalse}\fi Or here?%
}%
\par
\meaning\test
\expandafter\expandafter\expandafter\c
but1
isn't expandable so that gives you1\b
you say you want12\a
but do you want to expand all tokens in\c
by one step, or just the first expandable token. (clearly you don't mean the first token, as I note above.) what would you want if the definition of\c
was\def\c{1\b1\b}
?\c
by one step though.\def\c{1\b1\b}
, I then guess I'd want the result to be12\a12\a
because then, since 1 can't be expanded further, it stays as a1
and\b
is2\a
after one expansion.