可扩展的令牌完整扩展,保留 catcodes

可扩展的令牌完整扩展,保留 catcodes

是否可以以可扩展的方式完全扩展标记并保留类别代码?我想仅使用 pdfTeX 来做到这一点。

我正在寻找可以像这样工作的东西:

\def\foo{foo}
\fullyexpand{\foo bar baz}

它将扩展为 10 个标记“foobar baz”并保留类别代码。

如果我们放弃可扩展性的要求,那么这很容易。

\def\fullyexpand#1{\edef\fetemp{#1}\fetemp}

如果我们放弃保留 catcode 的要求,这是可行的,但不太直接。这是我能想到的最好的办法。

\def\gobbleprefix#1#2\femarker{%
        \ifnum\escapechar<0
                #1%
        \else\ifnum\escapechar>255
                #1%
        \fi\fi
        #2%
}
\def\fullyexpand#1{%
        \expandafter\expandafter\expandafter\gobbleprefix
        \expandafter\string\csname#1\endcsname\femarker
}

我希望结合ε-TeX 扩展名\scantokens与上面的相同(通过对 进行适当的、简单的修改\gobbleprefix),但当然,这是行不通的。\scantokens的扩展名是空的,而 pdfTeX 只是表现得像打开了一个新文件一样。

(顺便说一句,让我感到有趣的是,不扩展令牌是可扩展的——使用\unexpanded——但扩展令牌却不可扩展,至少不是很明显。)

答案1

你试过使用吗\romannumeral?这在这种事情中用得很多(例如,\exp_args:Nf参见expl3):

\def\fullyexpand#1{\romannumeral - `0#1}

这种方法之所以有效,是因为 TeX 会不断扩展#1以寻找数字,而这个数字总是负数,所以罗马数字会消失。请注意,此解决方案会在第一个不可扩展的标记处停止,而不像 an\edef会继续下去。

可以构建一个可以使用\romannumeral“around”不可扩展标记进行扩展的函数。例如,以下代码将运行得相当好:

\long\def\fullyexpand#1{%
  \csname donothing\fullyexpandauxi{#1}{}%
}
\long\def\fullyexpandauxi#1{%
  \expandafter\fullyexpandauxii\romannumeral -`0#1\fullyexpandend
}
\long\def\fullyexpandauxii#1#2\fullyexpandend#3{%
  \ifx\donothing#2\donothing
    \expandafter\fullyexpandend
  \else
    \expandafter\fullyexpandloop
  \fi
  {#1}{#2}{#3}%
}
\long\def\fullyexpandend#1#2#3{\endcsname#3#1}
\long\def\fullyexpandloop#1#2#3{%
  \fullyexpandauxi{#2}{#3#1}%
}
\def\donothing{}

但是,由于一些原因,这与 不同\expanded。首先,我的实现将删除参数中的空格(因为它执行循环,而 TeX 将跳过空格)。括号也会被删除。一些测试还表明\romannumeral 将要在这里扩展\protected函数,而\expanded没有。我还注意到上面的代码需要为空白(空或全部空格)参数添加一些保护,因为目前在这些情况下会失败。


在当前版本的 LuaTeX 中,可以使用\expanded,它的功能与 大致相同,\edef但可扩展(它也不需要双倍#标记)。此原语将出现在 TeX Live 2019 pdfTeX/e-pTeX/e-upTeX 中,并有望出现在 XeTeX 中(尚未确认)。作为它的前身,expl3有一个基于宏的仿真,速度慢但可以工作,它可以逐个标记地检查并允许“ e-type”扩展。


另外,可以使用可\scantokens扩展的,但您可能已经发现这可能很棘手,通常需要\everyeof首先进行(不可扩展的)更改。LuaTeX\scantextokens使用原语解决了这个问题,它将这个文件结尾的内容直接组合到原语中。当然,如果您使用的是 LuaTeX,那么原始问题无论如何都是可以解决的,因为是\expanded可用的。

答案2

在论坛上学习了许多技巧后,我提出了以下解决方案。我认为它实现了@TH. 想要的功能,即它扩展所有内容,当遇到不可扩展的标记时,它只是存储它并继续。它使用了我的未来包,其代码目前可以找到在线的,或者作为我的回答这个问题。此代码在下面第一行输入。设置您想要的文件名。

请注意,我没有小心地将 catcodes 重置为默认值ULcase.sty,因此这里我们不需要\catcode`:=11\catcode`_=11\relax。总有一天,我会清理它。

% Code based on the extended Upper- and Lower-casing code found
% in the ULcase package.
\input ULcase.sty\relax

% ============ Table |fullyexpand|, |\fullyexpand|
% The |fullexpand| table expands every token that is expandable
% according to the test \FE_token_if_expandable:NTF.
% 
% Then |\fullyexpand| is basically changing the case using a special
% "case table", |ULfullyexpand|. Here, |\MEA_trigger:f| is |\romannumeral|
% in disguise, forcing the full expansion of |\UL_to_case_aux:nn|
% (This is necessary for technical reasons.)
%
\def\fullyexpand{\MEA_trigger:f\UL_to_case_aux:nn{ULfullyexpand}}


% A few tests, building up to \FE_token_if_expandable:NTF.
%
\long\gdef\FE_token_if_expandable:NTF#1{%
  \FE_token_if_defined:NTF#1%
  {%
    \FE_token_if_eq_noexpand_self:NTF#1%
    {\use_ii:nn}%
    {\FE_token_if_protected:NTF#1{\use_ii:nn}{\use_i:nn}}%
  }%
  {\use_ii:nn}%
}
\long\gdef\FE_token_if_defined:NTF#1{%
  \ifdefined #1%
    \expandafter\use_i:nn%
  \else%
    \expandafter\use_ii:nn%
  \fi%
}%
\long\gdef\FE_token_if_eq_noexpand_self:NTF#1{%
  \expandafter\ifx\noexpand#1#1%
    \expandafter\use_i:nn%
  \else%
    \expandafter\use_ii:nn%
  \fi%
}
% |\expandsome| only expands the tokens following |\expandthis|.
\expandsome{%
  \long\gdef\FE_token_if_protected:NTF#1{%
    \expandafter\FE_token_if_protected_aux:w\meaning#1%
    \expandthis\string\protected\q_stop}%
}
% |\expandafter:nw{...}\foo| expands |\foo| before |...|.
\expandafter:nw{\long\gdef\FE_token_if_protected_aux:w#1}%
\string\protected#2\q_stop{%
  \UL_if_empty:nTF{#1}%
}%

% ===== Building the table.
% We copy the standard definitions (in particular for braces)
% NB: maybe problem with \NoCaseChange.
\UL_new_table:n{ULfullyexpand}
% 
% Spaces are just kept:
\UL_setup:nnn{ULfullyexpand}{ }{ }
% 
% The default action is to check if #3 (the next token) is expandable.
% If it is, we expand it. Otherwise, we output it. "#2{#3}" is responsible
% for continuing the loop.
\long\gdef\UL_table_ULfullyexpand_default#1#2#3{%
  \FE_token_if_expandable:NTF#1{% 
    \expandafter:nw{#2{#3}}#1%
  }{%
    \UL_to_case_output:n{#1}#2{#3}%
  }%
}%
%
% Define |\noexpand|
\long\expandafter\gdef\csname UL_table_ULfullyexpand_%
  \detokenize{\noexpand}\endcsname#1#2#3{%
    \UL_to_case_output:n{#3}#1{#2}}
%
% Define |\detokenize|
\long\expandafter\gdef\csname UL_table_ULfullyexpand_%
  \detokenize{\detokenize}\endcsname#1#2{%
    \expandafter:nw{\FE_detok_unexp_aux:nnNn{#1}{#2}\detokenize}%
    \romannumeral-`\0}
%
% Define |\unexpanded|
\long\expandafter\gdef\csname UL_table_ULfullyexpand_%
  \detokenize{\unexpanded}\endcsname#1#2{%
    \expandafter:nw{\FE_detok_unexp_aux:nnNn{#1}{#2}\unexpanded}%
    \romannumeral-`\0}
% 
% A helper for |\detokenize| and |\unexpanded|.
\long\gdef\FE_detok_unexp_aux:nnNn#1#2#3#4{%
  \expandafter\UL_to_case_output:n\expandafter{#3{#4}}%
  #1{#2}}%


% ===== Tests
\long\gdef\fooA#1{\fooC{\noexpand\fooA got #1} \fooB{a nice #1}}
\long\gdef\fooB#1{fooB\space got <#1>}
\protected\def\fooC#1{Not expanded.}
\long\gdef\a{\fullyexpand{Text: \fooA{argument}}}
\expandonce\a\expandonce\a
\long\gdef\b{Text: \fooC {\fooA got argument} fooB got <a nice argument>}
\checkoutput

\long\gdef\a{\fullyexpand{Text:%
    \detokenize\expandafter{\fooA{argument}\fooA{hi}}}}
\long\xdef\b{Text:\detokenize{\fooC {\noexpand \fooA got argument} %
    \fooB {a nice argument}\fooA {hi}}}
\expandonce\a\expandonce\a
\checkoutput

\long\gdef\a{\fullyexpand{Text:%
    \unexpanded\expandafter{\fooA{argument}\fooA{hi}}}}
\long\xdef\b{Text:\unexpanded{\fooC {\noexpand \fooA got argument} %
    \fooB {a nice argument}\fooA {hi}}}
\expandonce\a\expandonce\a
\checkoutput


\long\gdef\foo{foo}
\long\gdef\a{\fullyexpand{a\foo \ifnum1=1\number2 yes\else no\fi}}
\expandonce\a\expandonce\a
\long\gdef\b{afoono}

\endinput

答案3

\edef使用 pdfTeX 1.40 无法以可扩展的方式进行详尽(类似)扩展。所有建议的方法在行为上都不同于\edef

  • \csname只要穷举扩展包含非字符标记(例如原语或受保护的宏),使用就会失败
  • 正如约瑟夫所解释的那样,使用\romannumeral只会扩展到第一个不可扩展的标记。

答案4

这个怎么样?

\catcode `@=13
\def\at{`@}
\def\fullyexpand#1{\the\catcode#1}
\fullyexpand{\at}

相关内容