语境:
我正在尝试编写一个行为类似于 Cprintf
函数系列的宏。例如,以下 C 代码:
#include <stdio.h>
int main()
{
printf("Hello %s! Today's a good day to write obscure %s macros.\n",
"World","TeX");
}
印刷
Hello World! Today's a good day to write obscure TeX macros.
将第一个替换%s
为"World"
,将第二个%s
替换为"TeX"
。
我正在尝试提供类似的语法:
\Printf{Hello \%! Today's a good day to write obscure \%\ macros.}
{world,\TeX}
这应该会在 TeX 中打印相同的句子。现在我失败了,因为 TeX 不是 C:)
问题:
我找不到方法来传递第二个参数中的项目,\Printf
因为\%
宏\%
可以隐藏在文本中间的任何位置。
\Printf
和 的简单定义\%
是:
\def\Printf#1#2{%
#1% What do I do with #2?
}
\def\%{% How do I define this macro?
:(%
}
\Printf{Hello \%! Today's a good day to write obscure \%\ macros.}
{world,\TeX}
\bye
问题是:我该如何定义\Printf
并\%
使其打印正确的句子?
要求(因为我很挑剔:P
):
最强烈的要求是我希望该
\Printf
功能可扩展,以便:\edef\tempa{\Printf{Obscure \%\space Macros.}{TeX}} \show\tempa
将打印:
> \tempa=macro: ->Obscure TeX Macros..
它必须在括号组内工作。我尝试了一种分隔宏方法,这种方法有点奏效,但如果出现
\%
在括号对1内,就会失败。我还希望引擎独立。
另一方面,语法可以稍微改变。也就是说
\Printf{text}{replacement}
。我想保留这个\%
东西:)
我尝试过的:
1这是我使用\%
分隔宏的尝试。我更喜欢这种方法,因为我不必\%
在任何地方重新定义控制字符。缺点是当\%
标记出现在括号组内时,它会失败。多么大胆...
\input expl3-generic.tex
\ExplSyntaxOn
\cs_generate_variant:Nn \clist_item:nn { nf }
\cs_set_eq:NN \clistItem \clist_item:nf
\cs_new:Npn \Printf #1 #2
{ \__printf_step:nnn { 0 } { #2 } { #1 } }
\cs_new:Npn \__printf_step:nnn #1 #2 #3
{ \__printf_step:nnw { #1 } { #2 } #3 \% \q_nil }
\cs_new:Npn \__printf_step:nnw #1 #2 #3 \% #4
{
#3
\quark_if_nil:NF #4
{
\exp_args:Nf \__printf_percent:nn
{ \int_eval:n { #1 + 1 } } { #2 }
\exp_args:Nf \__printf_step:nnw
{ \int_eval:n { #1 + 1 } } { #2 } #4
}
}
\cs_new:Npn \__printf_percent:nn #1 #2
{ \clist_item:nn { #2 } { #1 } }
\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
这将打印:
答案1
当然,这完全是疯了,但可行!在这里,我使用与例如相同的方法\text_lowercase:n
。我们逐步检查标记,检查每个标记。有三种情况,一个括号组、一个空格和一个“正常”标记。在后一种情况下,我们将\%
和其他任何东西分开。唯一棘手的部分是跟踪括号组内的替换号。我通过将跟踪号保持在“顶层”并在将括号组添加到“输出”之前从任何括号组内“传回”该数字来实现这一点。
\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
答案2
常用表达是你的朋友!你可以逐步检查clist
参数,并使用\regex_replace_once:nnN
在标记列表中依次替换每个参数,以生成打印的文本:
这种方法也更短!代码如下:
\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
嗯,虽然这可能不符合可扩展性要求……