是否可以以可扩展的方式完全扩展标记并保留类别代码?我想仅使用 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}