Passing arguments to a macro hidden in the text
Totally insane, of course, but doable! Here, I use the same approach as in for example \text_lowercase:n
. We step through the tokens, examining each one. There are three cases, a brace group, a space, and a 'normal' token. In the latter case, we split between \%
and anything else. The only tricky part is tracking the substitution number inside brace groups. I do that by keeping the tracking number at the 'top level', and 'passing back' the number from inside any brace groups before they are added to the 'output'.
\input expl3-generic.tex
\ExplSyntaxOn
\cs_new:Npn \Printf #1#2
{
\exp:w \exp_not:o
{
\exp_after:wN \exp_end: \exp:w
\exp_args:Nf \__printf_outer:n { \__printf:nnn { 1 } {#1} {#2} }
}
}
\cs_new:Npn \__printf_outer:n #1
{ \__printf_outer:nn #1 }
\cs_new:Npn \__printf_outer:nn #1#2
{
\exp_end:
#1
}
\cs_new:Npn \__printf:nnn #1#2#3
{
\group_align_safe_begin:
\__printf_loop:w
#2 \q_recursion_tail \q_recursion_stop {#3}
\__printf_result:nn { } {#1}
}
\cs_new:Npn \__printf_loop:w #1 \q_recursion_stop
{
\tl_if_head_is_N_type:nTF {#1}
{ \__printf_N_type:N }
{
\tl_if_head_is_group:nTF {#1}
{ \__printf_group:nw }
{ \__printf_space:w }
}
#1 \q_recursion_stop
}
\cs_new:Npn \__printf_N_type:N #1
{
\quark_if_recursion_tail_stop_do:Nn #1
{ \__printf_end:w }
\token_if_eq_meaning:NNTF #1 \%
{ \__printf_N_type:w }
{
\__printf_output:nw {#1}
\__printf_loop:w
}
}
\cs_new:Npn \__printf_N_type:w #1 \q_recursion_stop #2 \__printf_result:nn #3#4
{
\exp_args:Nff \__printf_N_type:nnnnn
{ \clist_item:nn {#2} {#4} }
{ \int_eval:n { #4 + 1 } }
{#1} {#2} {#3}
}
\cs_new:Npn \__printf_N_type:nnnnn #1#2#3#4#5
{
\__printf_loop:w #3 \q_recursion_stop
{#4}
\__printf_result:nn { #5 #1 } {#2}
}
\cs_new:Npn \__printf_group:nw #1#2 \q_recursion_stop #3 \__printf_result:nn #4#5
{
\exp_args:Nf \__printf_group:nnnn
{ \__printf:nnn {#5} {#1} {#3} }
{#2} {#3} {#4}
}
\cs_new:Npn \__printf_group:nnnn #1#2#3#4
{ \__printf_group:nnnnn #1 {#2} {#3} {#4} }
\cs_new:Npn \__printf_group:nnnnn #1#2#3#4#5
{
\__printf_loop:w #3 \q_recursion_stop
{#4}
\__printf_result:nn { #5 {#1} } {#2}
}
\exp_last_unbraced:NNo \cs_new:Npn \__printf_space:w \c_space_tl
{
\__printf_output:nw { ~ }
\__printf_loop:w
}
\cs_new:Npn \__printf_output:nw #1#2 \__printf_result:nn #3
{ #2 \__printf_result:nn { #3 #1 } }
\cs_new:Npn \__printf_end:w #1 \__printf_result:nn
{
\group_align_safe_end:
}
\ExplSyntaxOff
\Printf{Hello \%! Today's a good day to write obscure \%\ macros.}
{world,\TeX}
\Printf{Oh, \%, my macro doesn't work {\bf with \%\ text}. What a \%\dots}
{drat,bold,shame}
\bye
Regular expressions are your friend! You can step through the clist
of arguments and use \regex_replace_once:nnN
to replace each one in turn in a token list for the printed text to produce:
This approach is shorter too! Here's the code:
\input expl3-generic.tex
\ExplSyntaxOn
\clist_new:N \l_printf_args_clist
\tl_new:N \l_printf_tl
\cs_new:Npn \Printf #1 #2
{
\tl_set:Nn \l_printf_tl {#1}
\clist_set:Nn \l_printf_args_clist {#2}
\clist_map_inline:Nn \l_printf_args_clist {
\regex_replace_once:nnN { \c{\%} } {##1} \l_printf_tl
}
\tl_use:N \l_printf_tl
}
\ExplSyntaxOff
\Printf{Hello \%! Today's a good day to write obscure \%\ macros.}
{world,\TeX}
\Printf{Oh, \%, my macro doesn't work {\bf with \%\ text}. What a \%\dots}
{drat,bold,shame}
\bye
Hmm, although this may not fit the expandability requirement...