我想构建一个 TeX 宏,通过将每个数字扩展为字母序列(始终是同一个字母)来转换其参数。例如3(2)2(1)
应该生成AAA(AA)AA(A)
。
A
我已经找到了一种从一个数字中产生任意数字的方法:
\newcommand\expandtoA[1]{%
\foreach \index in {1, ..., #1} {A}
}
因此,\expandtoA{10}
将扩展为AAAAAAAAAA
。
但是,我正在寻找一种方法来找到参数中的所有数字,并用它所代表的 A 的数量替换每个数字。
答案1
这确实可以通过扩展来实现,如图所示,它\typeout
与排版段落一样有效。
\documentclass{article}
\def\zz#1{\zzz#1(\relax)}
\def\zzz#1(#2){\zzA{#1}\ifx\relax#2\else(\zzA{#2})\expandafter\zzz\fi}
\def\zzA#1{\ifnum\numexpr0#1\relax>0 A\expandafter\zzA\expandafter{\the\numexpr#1-1\relax}\fi}
\begin{document}
\zz{3(2)2(1)}
\typeout{\zz{3(2)2(1)}}
\end{document}
放置
AAA(AA)AA(A)
在终端上。
答案2
这是一个使用 的可扩展方法expl3
。 \Replicate[<tokens>]{<token list>}
它将循环遍历您的输入<token list>
,寻找任何数字序列,并用输入的重复次数替换它们<tokens>
(默认情况下A
)。除数字之外的所有内容都将转发到输出,因此您可以得到类似
\Replicate{3(2)2(1)}
\Replicate[Z]{3(2)2(1)}
\Replicate[(Z)]{3(2)2(1)}
\Replicate[Z]{3 \textbf{(2) 2}( 1 )}
\edef\zzzzzzzzzzzz{\Replicate[Z]{3\textbf{(2)2}(1)}}
\texttt{\meaning\zzzzzzzzzzzz}
生产:
代码如下:
\documentclass{article}
\usepackage{xparse}
\pagestyle{empty}
\ExplSyntaxOn
\NewExpandableDocumentCommand \Replicate { O{A} m }
{ \perror_replicate:nn {#1} {#2} }
\cs_new:Npn \perror_replicate:nn #1 #2
{
\__perror_replicate_loop:w #2
\q_recursion_tail \q_recursion_stop {#1} { }
}
\cs_new:Npn \__perror_replicate_loop:w #1 \q_recursion_stop
{
\tl_if_head_is_N_type:nTF {#1}
{ \__perror_replicate_parse_token:N }
{
\tl_if_head_is_group:nTF {#1}
{ \__perror_replicate_nested:n }
{ \__perror_replicate_output_space:w }
}
#1 \q_recursion_stop
}
\use:nn { \cs_new:Npn \__perror_replicate_output_space:w } { ~ }
{ \__perror_replicate_output:nw { ~ } }
\cs_new:Npn \__perror_replicate_output:nw #1 #2 \q_recursion_stop #3 #4
{ \__perror_replicate_loop:w #2 \q_recursion_stop {#3} { #4 #1 } }
\cs_new:Npn \__perror_replicate_nested:n #1 #2 \q_recursion_stop #3
{
\exp_args:Ne \__perror_replicate_output:nw
{ { \perror_replicate:nn {#3} {#1} } }
#2 \q_recursion_stop {#3}
}
\cs_new:Npn \__perror_replicate_end:nn #1 #2 { \exp_not:n {#2} }
\cs_new:Npn \__perror_replicate_parse_token:N #1
{
\quark_if_recursion_tail_stop_do:Nn #1
{ \__perror_replicate_end:nn }
\__perror_replicate_if_digit:NTF #1
{ \__perror_replicate_collect_number:nw }
{ \__perror_replicate_output:nw }
{#1}
}
\prg_new_conditional:Npnn \__perror_replicate_if_digit:N #1 { TF }
{
\if_int_compare:w 10 < 9 \token_to_str:N #1 \exp_stop_f:
\prg_return_true:
\else:
\prg_return_false:
\fi:
}
\cs_new:Npn \__perror_replicate_collect_number:nw #1 #2 \q_recursion_stop
{
\tl_if_head_is_N_type:nTF {#2}
{ \__perror_replicate_collect_number:nN }
{ \__perror_replicate_finish_number:nw }
{#1} #2 \q_recursion_stop
}
\cs_new:Npn \__perror_replicate_collect_number:nN #1 #2
{
\quark_if_recursion_tail_stop_do:Nn #2
{
\__perror_replicate_finish_number:nw {#1}
\q_recursion_tail \q_recursion_stop
}
\__perror_replicate_if_digit:NTF #2
{ \__perror_replicate_collect_number:nw { #1 #2 } }
{ \__perror_replicate_finish_number:nw {#1} #2 }
}
\cs_new:Npn \__perror_replicate_finish_number:nw #1 #2 \q_recursion_stop #3
{
\exp_args:Ne \__perror_replicate_output:nw
{ \prg_replicate:nn {#1} {#3} }
#2 \q_recursion_stop {#3}
}
\ExplSyntaxOff
\begin{document}
\Replicate{3(2)2(1)}
\Replicate[Z]{3(2)2(1)}
\Replicate[(Z)]{3(2)2(1)}
\Replicate[Z]{3 \textbf{(2) 2}( 1 )}
\edef\zzzzzzzzzzzz{\Replicate[Z]{3\textbf{(2)2}(1)}}
\texttt{\meaning\zzzzzzzzzzzz}
\end{document}
答案3
使用 LuaLaTeX 非常简单(但是,只适用于 LuaLaTeX):
\documentclass{standalone}
\usepackage{luacode}
\newcommand\myexpand[2]{%
\directlua{local function myexpand(x,s) return (x:gsub("(\csstring\%d+)", function(u) return s:rep(math.floor(u)) end)) end tex.sprint(myexpand(\luastring{#1},\luastring{#2}))}}
\begin{document}
\myexpand{(3)2(2)1}{A}
\end{document}
答案4
如果你对某种语法错误管理感兴趣——我可能会不是只要用户遵守规则,David Carlisle 的代码就会变得非常短且快;-) — 我可以提供一个相当慢的\romannumeral
基于扩展的例程
\expandtoA{⟨sequence of numbers denoted by digits and separated from each other by properly matched not nested parentheses⟩}{⟨tokens in case of error⟩}
它无需任何 TeX 扩展,而是经过两个扩展步骤/两次“命中”后即可给出结果\expandafter
。
第一个参数中的数字和括号必须是类别代码 12(其他)的明确字符标记。
允许出现匹配的、不嵌套的空括号对。
不允许有空格。
如果第一个参数不符合描述的模式,⟨发生错误时的标记⟩将被交付,而不是“转换”为“A”。
您可以使用⟨发生错误时的标记⟩用于触发 LaTeX 错误消息或其他内容。
肯定还有改进/捷径的空间。
\documentclass{article}
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@PassFirstToSecond,
%% \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherLeadingTokens, \UD@replicate
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific
%% token-sequence that does neither contain explicit character tokens of
%% category code 1 or 2 nor contain tokens of category code 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%% {<a <token sequence> without explicit
%% character tokens of category code
%% 1 or 2 and without tokens of
%% category code 6>}%
%% {<internal token-check-macro>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked> has
%% <token sequence> as leading tokens>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked>
%% does not have <token sequence> as
%% leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[3]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral
\expandafter\UD@secondoftwo\string{\expandafter\UD@@CheckWhetherLeadingTokens#3{\relax}#1#2}{}}{}%
}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%% {<token sequence>}%
%% Defines <internal token-check-macro> to snap everything
%% until reaching <token sequence>-sequence and spit that out
%% nested in braces.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[2]{%
\@ifdefinable#1{\long\def#1##1#2{{##1}}}%
}%
%------------------------------------------------------------------------------
% \UD@replicate{<number>}{<tokens>}
%------------------------------------------------------------------------------
\newcommand\UD@replicateloop[3]{%
\if m#3\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@replicateloop{#1}{#2#1}}{\UD@stopromannumeral#2}%
}%
\newcommand\UD@replicate[2]{%
\romannumeral
\expandafter\UD@Exchange\expandafter{\romannumeral\number\number#1 000}%
{\UD@replicateloop{#2}{}}\relax
}%
%------------------------------------------------------------------------------
\UD@internaltokencheckdefiner{\UD@CheckLeftParen}{(}%
\UD@internaltokencheckdefiner{\UD@CheckRightParen}{)}%
\UD@internaltokencheckdefiner{\UD@CheckZero}{0}%
\UD@internaltokencheckdefiner{\UD@CheckOne}{1}%
\UD@internaltokencheckdefiner{\UD@CheckTwo}{2}%
\UD@internaltokencheckdefiner{\UD@CheckThree}{3}%
\UD@internaltokencheckdefiner{\UD@CheckFour}{4}%
\UD@internaltokencheckdefiner{\UD@CheckFive}{5}%
\UD@internaltokencheckdefiner{\UD@CheckSix}{6}%
\UD@internaltokencheckdefiner{\UD@CheckSeven}{7}%
\UD@internaltokencheckdefiner{\UD@CheckEight}{8}%
\UD@internaltokencheckdefiner{\UD@CheckNine}{9}%
%------------------------------------------------------------------------------
\newcommand\expandtoA[2]{%
\romannumeral\expandtoALoop{#1}{#2}{}{}{}%
}%
%------------------------------------------------------------------------------
\newcommand\expandtoALoop[5]{%
% #1 Remaining argument to examine
% #2 Tokens in case of error
% #3 Flag denoting how next number must terminate:
% Empty -> next number must terminate due to opening parenthesis or emptiness of remaining argument to examine.
% Not empty -> next number must terminate due to closing parenthesis.
% #4 Digits of next number gathered so far.
% #5 Result gathered so far.
\UD@CheckWhetherNull{#1}{%
\UD@CheckWhetherNull{#3}{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@replicate{#4}{A}}%
{\UD@stopromannumeral#5}%
}{\UD@stopromannumeral#2}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{(}{\UD@CheckLeftParen}{%
\UD@CheckWhetherNull{#3}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@replicate{#4}{A}}{\UD@stopromannumeral#5}%
}%
{\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{\relax}{}}%
}{\UD@stopromannumeral#2}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{)}{\UD@CheckRightParen}{%
\UD@CheckWhetherNull{#3}{\UD@stopromannumeral#2}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@replicate{#4}{A}}{\UD@stopromannumeral#5(})%
}%
{\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{}{}}%
}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{0}{\UD@CheckZero}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#40}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{1}{\UD@CheckOne}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#41}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{2}{\UD@CheckTwo}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#42}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{3}{\UD@CheckThree}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#43}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{4}{\UD@CheckFour}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#44}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{5}{\UD@CheckFive}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#45}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{6}{\UD@CheckSix}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#46}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{7}{\UD@CheckSeven}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#47}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{8}{\UD@CheckEight}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#48}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{9}{\UD@CheckNine}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#49}{#5}%
}{%
\UD@stopromannumeral#2%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
%------------------------------------------------------------------------------
\makeatother
\begin{document}
\message{\detokenize\expandafter\expandafter\expandafter{\expandtoA{1(2)3(4)5(6)7(8)9(10)()(11)}{ErrorText}}}%
\message{\detokenize\expandafter\expandafter\expandafter{\expandtoA{1(2)3(4)5(6)7(89(10)()(11)}{ErrorText}}}%
\end{document}
上述代码传递了消息
A(AA)AAA(AAAA)AAAAA(AAAAAA)AAAAAAA(AAAAAAAA)AAAAAAAAA(AAAAAAAAAA)()(AAAAAAAAAAA)
和
ErrorText
到终端。(第二次调用时出现 ErrorText,因为这里的括号没有正确匹配。)