是否有一个非 expl3 可扩展函数,用于反转任意数量的标记或宏的扩展顺序?例如,
\reverseexpansion<n>\a\b
\reverseexpansion<n>\a\b\c
\reverseexpansion<n>\a\b\c\d
\reverseexpansion<n>\a\b\c\d\e ... \m
其中<n>
是需要逆向扩展的前导标记的数量。
此外,的可扩展版本\expandallonce
将很有用,它仅将其参数中的所有宏扩展一次:
\expandallonce{\a\b}
\expandallonce{\a\b\c}
\expandallonce{\a\b\c\d}
\expandallonce{\a\b\c\d\e ... \n}
我应该补充一点,标记可能包含匹配的括号/参数。比如说,
\reverseexpansion 3\a{\b\c}\d
\expandallonce{\a{\b\c}\d}
支架不可伸缩,但运行后应保留。
实际上,我已经为这两个任务找到了不可扩展的笨拙解决方案。我认为过去有人已经解决了这些问题,而不是花时间寻找可扩展的解决方案。
答案1
编辑:在这篇文章的末尾,我附上了相关部分,
ULcase.sty
以便这篇文章是一个独立的部分。
在下面的代码中我使用UL案例。我觉得代码里的注释应该够理解了。
最简单的是\expandallonce
,尽管当令牌接受一个参数时你想要什么?(目前,它会中断。)我们只需定义一个默认扩展一次的大写表,然后读取标记。
% Code based on the extended Upper- and Lower-casing code found
% in the ULcase package.
\input ULcase.sty\relax
% ============ Table |expandallonce|
% Just as uppercasing is changing "a->A", "b->B" etc, and applying
% |\donothing| to all other tokens, we define the table of case change
% |expandallonce| to expand the token once before outputting it. This
% will fail in case the token takes an argument (I don't know what the
% expected behaviour would be, anyways.)
%
\long\gdef\expandallonce#1{%
\UL_to_case:nn{expandallonce}{#1}}
% The default action is to output the given token expanded once.
\long\gdef\UL_table_expandallonce_default#1{%
\expandafter\UL_to_case_output:n\expandafter{#1}}
% Braces do nothing special, just as in the ULnil table.
\long\gdef\UL_table_expandallonce_braces#1#2{%
\expandafter\expandafter\expandafter\UL_to_case_output:n%
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter{\UL_to_case:nn{#1}{#2}}%
}%
}
% ===== Tests
\def\0{\1}\def\1{\2}\def\2{\3}\def\3{\4}\def\4{\5}
\long\gdef\a{\expandallonce{\0{{} {\2} {\3}\0\2}\2}}
\expandonce\a\show\a
\expandonce\a\show\a
\expandonce\a\show\a
为了\reverseexpand
,我们首先插入来自这个问题关于建立一个多\expandafter
在每个标记之前。下一步扩展将触发多重扩展,这将执行您想要的操作。请注意,我们扩展的宏可以顺利地接受各种参数,因为我们在当前扩展的标记之后没有在输入流中留下任何内容。
% Code based on the extended Upper- and Lower-casing code found
% in the ULcase package.
\input ULcase.sty\relax
% ============ Table |reverseexpand|
% With the definition below, one step of expansion on
% |\romannumeral\eatwice| is the same as two steps of |\expandafter|.
\gdef\eatwice{0\expandafter\expandafter\expandafter\space%
\expandafter\expandafter\expandafter}
% We insert |\romannumeral\eatwice| in front of every token,
% and add |\empty\empty| at the end, to stop the extra |\expandafter|.
%
% The full expansion requires 5 steps.
\long\gdef\reverseexpand#1{%
\UL_to_case:nn{reverseexpand}{#1}\empty\empty}
% The default action is to output the given token plus the extra text.
\long\gdef\UL_table_reverseexpand_default#1{%
\UL_to_case_output:n{\romannumeral\eatwice#1}}
% The chain of |\expandafter| triggers the full expansion of
% |\romannumeral\UL_to_case_aux:nn{#1}{#2}|, which adds the
% tokens |\romannumeral\eatwice| before each token in the group.
%
\long\gdef\UL_table_reverseexpand_braces#1#2{%
\expandafter\UL_to_case_output:n\expandafter{%
\expandafter\romannumeral\expandafter\eatwice\expandafter{%
\romannumeral\UL_to_case_aux:nn{#1}{#2}%
\romannumeral\eatwice%
}%
}%
}
% ===== Tests
\def\0{\1}\def\1{\2}\def\2{\3}\def\3{\4}\def\4{\5}
\def\5{\6}\def\6{\7}\def\7{\8}\def\8{\9}\def\9{\0}
\def\foo#1{{Foo=#1.}}
\def\double#1{(#1,#1)}
\long\def\a{\reverseexpand{\double\foo{ \4\6}\8}}
\expandonce\a\show\a
\expandonce\a\show\a
\expandonce\a\show\a
\expandonce\a\show\a
\expandonce\a\show\a
ULcase 的相关部分(应该放在其他代码的顶部,但这不应该是关键点)。
\catcode`\_=11\relax
\catcode`\:=11\relax
% ======================== Generic macros
% Note on LaTeX3's naming convention: letters after ":" in macro names
% indicate the arguments that the command takes.
% "n" = braced
% "N" = single token
% "w" = weird
% and plenty of others.
%
% A few standard commands to manipulate arguments
\long\gdef\use_i:nn#1#2{#1}
\long\gdef\use_ii:nn#1#2{#2}
% What expl3 calls "quarks", useful for |\ifx| comparisons.
\gdef\q_stop{\q_stop}
\gdef\q_mark{\q_mark}
\long\gdef\use_none_until_q_stop:w#1\q_stop{}
% Two tests
\long\gdef\UL_if_empty:nTF#1{%
\expandafter\ifx\expandafter\q_mark\detokenize{#1}\q_mark%
\expandafter\use_i:nn%
\else%
\expandafter\use_ii:nn%
\fi}
\expandafter\long\expandafter\gdef\expandafter\UL_if_detok_qmark:wTF%
\expandafter#\expandafter1\detokenize{\q_mark}#2\q_stop{%
\UL_if_empty:nTF{#1}}
% ======================== Main command: |\UL_to_case:nn|
% Usage: |\UL_to_case:nn{<table>}{<text>}|
% Expands in: 2 steps.
\long\gdef\UL_to_case:nn{\romannumeral\UL_to_case_aux:nn}
\long\gdef\UL_to_case_aux:nn#1#2{-`\0% almost stops \romannumeral
\UL_brace_check:nw{#1}#2{\q_mark} \q_stop\UL_to_case_end:n{}}%
% |\UL_to_case_output:n| appends its argument to the argument of
% |\UL_to_case_end:n|.
\long\gdef\UL_to_case_output:n#1#2\UL_to_case_end:n#3{%
#2\UL_to_case_end:n{#3#1}}
\long\gdef\UL_to_case_end:n#1{ #1}
% And |\UL_to_case_end:n| expands to
% - a space, which stops the expansion of |\romannumeral-`\0|,
% - followed by its argument, which is the result we want.
% First, we check whether the next token is a brace.
\long\gdef\UL_brace_check:nw#1#2#{%
\UL_if_empty:nTF{#2}%
{\UL_brace_yes:nn{#1}}%
{\UL_space_check:nw{#1}#2}%
}
% If there is a brace, we might have reached {\q_mark}.
\long\gdef\UL_brace_yes:nn#1#2{%
\expandafter\UL_if_detok_qmark:wTF \detokenize{#2 \q_mark}\q_stop{%
% Note the space before \q_mark!
\use_none_until_q_stop:w%
}{% Otherwise, we have a brace group, and we can act on it.
\csname UL_table_#1_braces\endcsname{#1}{#2}%
\UL_brace_check:nw{#1}%
}%
}
% Then check whether the next token is a space.
\long\gdef\UL_space_check:nw#1#2 {%
\UL_if_empty:nTF{#2}%
{\UL_convert_token:nn{#1}{ }}%
{\UL_convert_token:nn{#1}#2 }% put the space back!
}
\long\gdef\UL_convert_token:nn#1#2{%
\ifcsname UL_table_#1_\detokenize{#2}\endcsname%
\expandafter\use_i:nn%
\else%
\expandafter\use_ii:nn%
\fi%
{\csname UL_table_#1_\detokenize{#2}\endcsname}%
{\csname UL_table_#1_default\endcsname{#2}}%
\UL_brace_check:nw{#1}% Do the next token.
}
% For tests.
\long\gdef\expandonce#1{% redefines #1 as #1 expanded once.
\long\xdef#1{\unexpanded\expandafter\expandafter\expandafter{#1}}}
答案2
\reverseexpansion
我将跳过这个问题,因为我认为你问的问题可能和给出的答案一样难,甚至更难这个问题,我不清楚这些扩展抽象在实践中是否真的有用。(而且我的时间不够了。)
\expandallonce
这个想法是逐个标记映射列表并应用于\unexpanded\expandafter{#1}
每个标记。但无论如何你都会得到一个 expl3 解决方案,因为我厌倦了一遍又一遍地重新编写相同的代码:)
\usepackage{expl3}
\ExplSyntaxOn
\def\expandallonce#1{
\tl_map_function:nN {#1} \tl_nested_exp_not:n
}
\cs_set:Nn \tl_nested_exp_not:n {
\tl_if_single:nTF {#1}
{ \exp_not:o #1 }
{ { \expandallonce {#1} } }
}
\ExplSyntaxOff
\def\a{\aa}\def\b{\bb}\def\c{\cc}\def\d{\dd}
\edef\1{\expandallonce{\a{\b\c}\d}}
\show\1
如果您想在里面放入任意 TeX 代码\expandallonce
,这并不完全可靠,但我认为这不是问题。(例如,您不能在里面放入条件。)请注意,expl3
目前没有通过括号嵌套的映射函数,因此(在更新的答案中)我已经通过递归实现了类似的效果。
请注意,除非括号内只有一个标记(例如 ),否则此嵌套映射会尊重括号。abc{d}efg
我不知道有任何方法可以解决这个问题并使代码保持可扩展。(也许使用\detokenize
和\scantokens
是可能的,但这对我来说太脆弱了。)