反转扩展顺序

反转扩展顺序

是否有一个非 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是可能的,但这对我来说太脆弱了。)

相关内容