I notice some issues with Phelype Oleinik's answer, so I try to find out a better solution.
There's an implementation below. However absolutely do not try to reverse engineer what the code does. Read the English description instead, or see other options below.
Stage 0. Preparation
How to take a detokenized token from the input stream
(token equal to its own \string
, i.e. either explicit space or non-space with other catcode)
(handled by the remove_next_stringified_character
function.)
Apply \meaning
to that token, then the following content is either blank space
or the character ⟨non-space token⟩
.
An alternative (which should be faster), if e-TeX is available, is to expand \unless \if \relax ⟨the character to be removed⟩
once,
then expand \fi
.
Expand the following tokens in the input stream while holding data
(implemented into the compiler engine)
This part is just standard tricks. If we hold a number instead there are 2 ways
- note that
\number ⟨some number⟩ \expandafter ⟨explicit space token⟩ ⟨some other tokens⟩
expands in 1 step to ⟨some number⟩ ⟨once expansion of these other tokens⟩
.
- wrap the number into a
\csname
, do the necessary expansion, then \cs_to_str:N
it back. (issue: pollute the hash table)
- write some code to insert one
\expandafter
token before each token (it's easy because it's just pure numbers), then do an expansion. (okay this is the third way.)
Stage 1. Handle the input size
(implemented in the count_string_len
function)
Background
This algorithm is multiple-pass, it needs to take the container macro as input.
First, there's a little problem with \meaning
.
Let's say we know \escapechar=-1
and the following token is either...
- char
c
cat other
- the token
\the
, followed by either c
or some outer token
- some outer token
- char
e
cat }
- the primitive
\end
The only way(*) to proceed in this situation is to \meaning
the next token, but then the following token might be...
end-group character e
comes from case 4 or 5 above
the character c
comes from case 1 or 2 above
the ⟨some outer token⟩
comes from case 2 above
end-⟨some outer token⟩
comes from case 4 or 5 above
The first 2 bullet points make it hard enough to distinguish already, then there's still the possibility that some following token might be outer
so we need to \meaning
it again, etc..
(*) (I believe it's possible to apply \noexpand
before applying \meaning
. That approach could work as well, but it appears to be more complex.)
Side note, LuaTeX appears to have some bug where the \meaning
of a \noexpand
token is [unknown command code! (0, 1)]
instead of the usual \relax
. So the following description assumes the meaning of a noexpand token is ⟨escape character⟩relax
.
We want to use \string
here, as repeatedly apply \string
to something will not increase its size unbounded i.e. \string
is idempotent.
Approach
We will compute the total length of the stringified representation of all tokens.
First, put ⟨token list⟩A
in the input stream, then repeatedly
- stringify the next token. The stringfication must be nonempty.
- absorb one stringified token.
This will eventually absorb the whole token list.
Let's say after 50 characters, we hit A
as the 51th character. There are two possibilities
- either we reached the end of the
⟨token list⟩
, in that case we report the result is 50,
- or there's some
A
within the stringfication of some token in the ⟨token list⟩.
To double check, we put ⟨token list⟩B
in the input stream (note that if the second case is true, there will remain some garbage in the input stream which we will clean up later),
then stringify-grab exactly 50 tokens, stringify the next one, then check if it's A
or B
.
If it's A
, We have not reached the end of the token list yet, so we recursively call the function knowing that the length is at least 51 characters long.
The inner function would initially absorb 51 characters, before checking for any A
or B
; after some steps it should finally return the result.
Assuming it's determined that the actual length is 100, then the outer function would need to clean up about 50 remaining tokens after stringifying them. (the exact number is left as an exercise for the implementor.)
Stage 2. Apply \meaning
to one token
(also handled by count_string_len
, by passing suitable preprocess
values this can be done)
Let's say, at this point we know that the concatenated stringfication of all tokens in the ⟨token list⟩ has length 100.
We iterate from 0 to 99, for each number X:
we would stringify/remove first X tokens, apply \meaning
to the following token (without \string
ify it), then use the A/B flip idea above to grab the remaining tokens.
That way, we can compute the total length of the stringfication of all tokens, minus the length of the token that is generated after first X stringify/remove, plus the length of its \meaning
.
Do the same as the above, but instead of \meaning
we apply \noexpand
followed by \meaning
.
Note that, in this way there are 2 important properties
- only N-type tokens will have the length changed (the meaning of
{
, }
,
are unchanged under \noexpand
)
- all outer tokens will have the length changed (because
outer macro
is strictly longer than \relax
, regardless of the value of \escapechar
)
Stage 3. Compute the result
Given the above, while iterating over values X, after stringify/removing first X tokens, we can simply \noexpand
the next token, grab it as an argument, then it's not difficult to process it to determine whether or not it's \outer
.
When I said "simply" here, I mean you need to do the following...
- first check if the string "macro:" appear in the meaning.
- if it is, then check if "mark" appear before the first occurrence of "macro".
- if it isn't, then check if "outer" appear before the first occurrence of "macro".
Reference: Every possible \meaning that a token can have
We also know the total stringfication length, so if we "copy" instead of absorb the token, we can continue stringifying/grabbing N-X
remaining tokens after checking.
Notes
- Instead of
\numexpr
, this code stores "numbers" in unary. Overall the time complexity is the same anyway (this method might even be faster because of various reasons).
- As far as I can see, this method is completely robust. There's only minimal test suite below, but see the tests in the source file linked below.
Alternative approach
If you have \unexpanded
available, simply do the normal "tail recursion" thing to process the token list and \unexpanded
the argument after every step.
This requires the arguments, if given as a token list instead of the container macro, to be initially wrapped in two layers of braces (one for \unexpanded
, the other for absorbing the token list).
Actually the alternative approach won't work, \unexpanded
doesn't accept outer tokens in its input as well. The main approach does work though.
Actual implementation here.
%! TEX program = pdflatex
\documentclass{article}
\usepackage{filecontentsdef}
\begin{document}
\ExplSyntaxOn
% ======== some auxiliary macros ========
\def\__process_char #1 #2 {
%\prettye:n{\expandafter \expandafter \expandafter \noexpand \char_generate:nn {`#2} {"#1}}
\expandafter \expandafter \expandafter \noexpand \char_generate:nn {`#2} {"#1}
\__process_s
}
\def\__process_space_other_cat #1 {
\expandafter \expandafter \expandafter \noexpand \char_generate:nn {32} {"#1}
\__process_s
}
\def\__process_cs #1 / {
\expandafter \noexpand \csname #1 \endcsname
\__process_s
}
\def\__process_s#1{
\token_if_eq_charcode:NNTF #1 0 { % 0 <name> / → the control sequence
\__process_cs
} {
\token_if_eq_charcode:NNTF #1 s { ~ \__process_s
} {
\token_if_eq_charcode:NNTF #1 S { % S <cat> → a space
\__process_space_other_cat
} {
\token_if_eq_charcode:NNF #1 . { % . → end
\__process_char #1
}
}
}
}
}
% main handler function, will exec the resulting token list.
\def\__process_all#1{
\begingroup \exp_last_unbraced:Nx \endgroup {\__process_s #1}
}
\ExplSyntaxOff
\begin{filecontentsdefmacro}{\data}
0def/0ifescapenull/1{0texconditional/1{0ifnum/C0C>0escapechar/2}2}0def/0remove_next_stringified_character/1{0expandafter/0stzz31/0meaning/2}0def/0stzz31/6#6#C11{0if/Bb6#6#C10use_ii_to_i:w/0fi/0use_ii:nn/0stzz33/0stzz35/2}0def/0stzz35/ChCesCcChCaCrCaCcCtCeCrs6#6#C11{0exp_end:/2}0def/0stzz33/ClCaCnCksCsCpCaCcCess1{0exp_end:/2}0def/0stringify_until_A/1{0expandafter/0stzz56/0string/2}0def/0stzz56/1{0expandafter/0stzz57/0meaning/2}0def/0stzz57/6#6#C11{0ifx/6#6#C1Cb0use_ii_to_i:w/0fi/0use_ii:nn/0stzz59/0stzz61/2}0def/0stzz61/ChCesCcChCaCrCaCcCtCeCrs6#6#C11{0ifx/6#6#C1CA0use_ii_to_i:w/0fi/0use_ii:nn/0stzz63/0stzz67/2}0def/0stzz63/1{0exp_end:/BA2}0def/0stzz59/ClCaCnCksCsCpCaCcCess1{0expandafter/0exp_end:/0expandafter/C.0romannumeral/0stringify_until_A/2}0def/0stzz67/1{0expandafter/0exp_end:/0expandafter/C.0romannumeral/0stringify_until_A/2}0def/0replicate_helper/6#6#C11{0stzz80/6#6#C12}0def/0stzz80/6#6#C16#6#C21{0stzz83/6#6#C26#6#C12}0def/0stzz83/6#6#C16#6#C21{0ifx/6#6#C10relax/0use_ii_to_i:w/0fi/0use_ii:nn/0stzz85/1{0stzz88/6#6#C22}2}0def/0stzz88/6#6#C11{0expandafter/0stzz103/0expandafter/6#6#C10exp:w/0replicate_helper/6#6#C12}0def/0stzz85/1{0exp_end:/0relax/2}0def/0replicate/6#6#C16#6#C21{0expandafter/0stzz102/0exp:w/0replicate_helper/6#6#C26#6#C10relax/2}0def/0stzz102/6#6#C10relax/1{0exp_end:/6#6#C12}0def/0stzz103/6#6#C11{0exp_end:/6#6#C12}0def/0stringify_remove_next_repeated/1{0expandafter/0stzz125/0exp:w/2}0def/0stzz125/1{0expandafter/0remove_next_stringified_character/0string/2}0def/0stringify_remove_next_repeated_count/6#6#C11{0expandafter/0stzz149/0expandafter/1{0exp:w/0replicate/1{6#6#C12}0stringify_remove_next_repeated/2}2}0def/0remove_next_repeated/1{0expandafter/0stzz143/0exp:w/2}0def/0stzz143/6#6#C11{0exp_end:/2}0def/0remove_next_repeated_count/6#6#C11{0expandafter/0stzz149/0expandafter/1{0exp:w/0replicate/1{6#6#C12}0remove_next_repeated/2}2}0def/0stzz149/6#6#C11{0expandafter/0exp_end:/0romannumeral/6#6#C10exp_end:/2}0def/0exp_forward/1{0expandafter/0stzz157/0exp:w/2}0def/0stzz157/1{0exp_end:/0exp_forward/2}0def/0exp_forward_end/1{0expandafter/0stzz164/2}0def/0stzz164/1{0exp_end:/0exp_forward_end/2}0def/0r_function_do_nothing/1{0exp_end:/2}0def/0r_function_meaning_noexpand/1{0expandafter/0r_function_meaning/0noexpand/2}0def/0r_function_meaning/1{0expandafter/0exp_end:/0meaning/2}0def/0stzz191/6#6#C1CmCaCcCrCoC:6#6#C20relax/1{1{6#6#C12}1{6#6#C22}2}0def/0stzz205/6#6#C1CmCaCrCk1{2}0def/0stzz209/6#6#C1CoCuCtCeCr1{2}0def/0check_outer_aux/1{0expandafter/0stzz184/0noexpand/2}0def/0stzz184/6#6#C11{0expandafter/0stzz187/0expandafter/1{0meaning/6#6#C12}2}0def/0stzz187/6#6#C11{0expandafter/0stzz195/0expandafter/1{0stzz191/6#6#C1CmCaCcCrCoC:0relax/2}6#6#C12}0def/0stzz195/6#6#C11{0stzz195a/6#6#C12}0def/0stzz195a/6#6#C16#6#C21{0ifempty_simple/1{6#6#C22}0exp_end:/1{0stzz205b/1{6#6#C12}2}2}0def/0stzz205b/6#6#C11{0expandafter/0stzz206/0expandafter/1{0stzz205/6#6#C1CmCaCrCk2}1{6#6#C12}2}0def/0stzz206/6#6#C16#6#C21{0ifempty_simple/1{6#6#C12}1{0stzz206b/1{6#6#C22}2}0exp_end:/2}0def/0stzz206b/6#6#C11{0expandafter/0stzz212/0expandafter/1{0stzz209/6#6#C1CoCuCtCeCr2}2}0def/0stzz212/6#6#C11{0ifempty_simple/1{6#6#C12}0exp_end:/0stzz215/2}0def/0stzz215/1{0exp_end:/C12}0def/0count_string_len/6#6#C16#6#C26#6#C36#6#C41{0stzz232/6#6#C11{6#6#C22}1{6#6#C42}1{6#6#C32}2}0def/0stzz232/6#6#C16#6#C26#6#C36#6#C41{0stzz233/6#6#C41{6#6#C22}1{6#6#C32}6#6#C12}0def/0stzz233/6#6#C16#6#C26#6#C36#6#C41{0stzz234/6#6#C3C*1{6#6#C22}6#6#C16#6#C42}0def/0stzz234/6#6#C1C*6#6#C26#6#C36#6#C41{0stzz236/6#6#C1C*6#6#C46#6#C36#6#C2C*2}0def/0stzz236/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0stzz237/6#6#C26#6#C4C*6#6#C36#6#C1C*6#6#C2BA2}0def/0stzz237/6#6#C16#6#C2C*6#6#C36#6#C4C*1{0expandafter/0stzz240/0expandafter/6#6#C10exp:w/6#6#C20expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz240/6#6#C16#6#C2C*6#6#C36#6#C4C*1{0stzz240a/6#6#C2C*6#6#C16#6#C4C*6#6#C30exp:w/0stringify_remove_next_repeated_count/1{6#6#C22}2}0def/0stzz240a/6#6#C1C*6#6#C26#6#C3C*6#6#C41{0expandafter/0stzz241/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20exp:w/6#6#C30expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C42}0def/0stzz241/6#6#C1C*6#6#C26#6#C3C*6#6#C41{0stzz241a/6#6#C46#6#C26#6#C3C*6#6#C1C*0exp:w/6#6#C42}0def/0stzz241a/6#6#C16#6#C26#6#C3C*6#6#C4C*1{0expandafter/0stzz242/0expandafter/6#6#C10expandafter/6#6#C20exp:w/6#6#C30expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz242/6#6#C16#6#C26#6#C3C*6#6#C4C*1{0stzz242a/6#6#C3C*6#6#C26#6#C16#6#C4C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C32}2}0def/0stzz242a/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0expandafter/0stzz246/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz246/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0stzz246a/6#6#C1C*6#6#C26#6#C36#6#C4C*0exp:w/0stringify_until_A/2}0def/0stzz246a/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0expandafter/0stzz249/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz249/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5BA1{0stzz252/6#6#C4C*6#6#C1C*6#6#C26#6#C31{6#6#C52}2}0def/0stzz252/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C51{0expandafter/0stzz253/0expandafter/1{0exp:w/0replicate/1{6#6#C52}0exp_forward/2}6#6#C2C*6#6#C36#6#C46#6#C1C*2}0def/0stzz253/6#6#C16#6#C2C*6#6#C36#6#C46#6#C5C*1{0stzz256/6#6#C5C*6#6#C2C*6#6#C36#6#C46#6#C1C*2}0def/0stzz256/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0stzz257/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*6#6#C3BB2}0def/0stzz257/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0expandafter/0stzz261/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C20expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C30expandafter/6#6#C40exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz261/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0stzz261a/6#6#C1C*6#6#C46#6#C36#6#C2C*6#6#C5C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C12}2}0def/0stzz261a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz262/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz262/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz262a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*0exp:w/6#6#C22}0def/0stzz262a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz263/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz263/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz263a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C46#6#C52}2}0def/0stzz263a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz265/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz265/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz265a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*0string/2}0def/0stzz265a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz271/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz271/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*6#6#C61{0stzz272/1{6#6#C62}6#6#C26#6#C1C*6#6#C36#6#C4C*6#6#C5C*2}0def/0stzz272/6#6#C16#6#C26#6#C3C*6#6#C46#6#C5C*6#6#C6C*1{0ifx/6#6#C1CA0use_ii_to_i:w/0fi/0use_ii:nn/1{0stzz284c/6#6#C3C*6#6#C26#6#C46#6#C5C*2}0stzz284/6#6#C6C*2}0def/0stzz284/6#6#C1C*1{0exp_end:/6#6#C12}0def/0stzz284c/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz274/6#6#C1C*6#6#C4C*6#6#C36#6#C26#6#C5C*2}0def/0stzz274/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0expandafter/0stzz275/0expandafter/1{0exp:w/0count_string_len/6#6#C31{6#6#C12}6#6#C41{6#6#C26#6#C50exp_forward/2}2}6#6#C5C*2}0def/0stzz275/6#6#C16#6#C2C*1{0stzz280/6#6#C1C*6#6#C2C*2}0def/0stzz280/6#6#C1C*6#6#C2C*1{0stzz280a/6#6#C1C*6#6#C2C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C16#6#C10exp_forward/0exp_forward/2}2}0def/0stzz280a/6#6#C1C*6#6#C2C*1{0expandafter/0stzz281/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C20expandafter/0exp_end:/0expandafter/C*2}0def/0stzz281/6#6#C1C*6#6#C2C*1{0exp_end:/6#6#C20exp_forward/6#6#C12}0def/0ifempty_simple/6#6#C11{0ifx/0relax/6#6#C10relax/0expandafter/0use_i:nn/0else/0expandafter/0use_ii:nn/0fi/2}0def/0ifnotempty_simple/6#6#C11{0ifx/0relax/6#6#C10relax/0expandafter/0use_ii:nn/0else/0expandafter/0use_i:nn/0fi/2}0def/0iflenequal_simple/6#6#C16#6#C21{0expandafter/0stzz312/0exp:w/0remove_next_repeated_count/1{6#6#C22}6#6#C1C?6#6#C2C+2}0def/0stzz312/6#6#C16#6#C2C+1{0texconditional/1{0ifx/6#6#C1C?2}2}0def/0if_outer/6#6#C11{0stzz318/6#6#C12}0def/0stzz318/6#6#C11{0expandafter/0stzz321/0expandafter/1{0exp:w/0count_string_len/6#6#C11{2}0r_function_do_nothing/1{2}2}6#6#C12}0def/0stzz321/6#6#C16#6#C21{0expandafter/0stzz326/0expandafter/1{0use_none:n/6#6#C12}1{6#6#C22}2}0def/0stzz322a/6#6#C16#6#C21{0expandafter/0stzz326/0expandafter/1{0use_none:n/6#6#C22}1{6#6#C12}2}0def/0stzz326/6#6#C16#6#C21{0expandafter/0stzz327/0expandafter/1{0exp:w/0count_string_len/6#6#C21{6#6#C12}0r_function_meaning_noexpand/1{2}2}1{6#6#C22}1{6#6#C12}2}0def/0stzz327/6#6#C16#6#C26#6#C31{0expandafter/0stzz328/0expandafter/1{0exp:w/0count_string_len/6#6#C21{6#6#C32}0r_function_meaning/1{2}2}1{6#6#C22}1{6#6#C32}1{6#6#C12}2}0def/0stzz328/6#6#C16#6#C26#6#C36#6#C41{0iflenequal_simple/1{6#6#C42}1{6#6#C12}1{0stzz336f/1{6#6#C32}1{6#6#C22}2}1{0stzz332/1{6#6#C22}1{6#6#C32}1{6#6#C12}2}2}0def/0stzz332/6#6#C16#6#C26#6#C31{0expandafter/0stzz333/0expandafter/1{0exp:w/0count_string_len/6#6#C11{6#6#C22}0check_outer_aux/1{2}2}1{6#6#C12}1{6#6#C22}1{6#6#C32}2}0def/0stzz333/6#6#C16#6#C26#6#C36#6#C41{0iflenequal_simple/1{6#6#C12}1{6#6#C42}1{0stzz336f/1{6#6#C32}1{6#6#C22}2}0use_i:nn/2}0def/0stzz336f/6#6#C16#6#C21{0ifnotempty_simple/1{6#6#C12}1{0stzz322a/1{6#6#C22}1{6#6#C12}2}0use_ii:nn/2}0def/0debug/C1C21{0stzz352/2}0def/0stzz352/C3C41{0exp_end:/2}.
\end{filecontentsdefmacro}
\ExplSyntaxOn
\exp_args:NV \__process_all \data
\def \use_ii_to_i:w \fi \use_ii:nn { \fi \use_i:nn }
\def \texconditional #1 {#1 \use_ii_to_i:w \fi \use_ii:nn}
\ExplSyntaxOff
% -----
% Tests
% -----
\def\rain{My bald head is still wet.}
\def\unfold{\umbrella My bald {head always} dries so slowly.}
\outer\def\umbrella{No raindrops on my head, please! }
\newcount\abc
\tt
3\csname if_outer\endcsname\unfold{T}{F} (T)\par
4\csname if_outer\endcsname\rain{T}{F} (F)\par
7\edef\tmpa{\csname if_outer\endcsname\unfold{T}{F}}\meaning\tmpa (T)\par
\end{document}
It's compatible with all engines, I think.
As mentioned above. Absolutely do not try to reverse engineer what the code does.
I compiled that code from the source code at https://github.com/user202729/TeXlib/blob/main/test_imperative2.tex using one of my unreleased libraries.
This one (imperative
, name might change later subject to CTAN maintainers) is made to make expandable programming/input stream parsing macros easier to write, and reduce the number of meaningless _aux
, _auxi
, _auxii
etc. macro. (or worse, \macro@
, \macro@@
, \m@cro
, m@cr@
)
No documentation so far, unfortunately...
\def\outer{}
as soon as you have the catcodes of{}
set, but I suppose that isn't the answer you want:-)\suppressoutererror = 1
and then\outer
tokens do not generate an error.