![在符合“用户空间”宏命名方案的宏中存储/收集项目](https://linux22.com/image/338203/%E5%9C%A8%E7%AC%A6%E5%90%88%E2%80%9C%E7%94%A8%E6%88%B7%E7%A9%BA%E9%97%B4%E2%80%9D%E5%AE%8F%E5%91%BD%E5%90%8D%E6%96%B9%E6%A1%88%E7%9A%84%E5%AE%8F%E4%B8%AD%E5%AD%98%E5%82%A8%2F%E6%94%B6%E9%9B%86%E9%A1%B9%E7%9B%AE.png)
我在这里寻找一个有用的答案。如果我想附加到宏,我有几个选项,每个选项都取决于我是否希望项目完全展开、部分展开(一定次数)或不展开,对吗?
今天我选择使用满载\expandafter
的方法。
在符合“用户空间”宏命名方案的宏中存储/收集项目
没有数字、没有符号、没有空格
\def\appendtolist#1#2{% #1 = Macro Name (list) #2=New Item to Append
\ifx#1\undefined \def#1{#2}\else% Macro Existence Check
\expandafter\def\expandafter#1\expandafter{#1,#2}\fi% #1=Expand Macro List, #2=New Item
}%
使用以下宏定义存储/收集项目\csname
包含任何有权衡的东西,你必须使用大量\expandafter
的
\def\appendtolist#1#2{% #1 = Macro Name (list) #2=New Item to Append
\expandafter\ifx\csname#1\endcsname\relax\expandafter\def\csname#1\endcsname{#2}\else% Macro Existence Check
%\expandafter\def\expandafter#1\expandafter{#1,#2}\fi% Headache-causing line when trying to surround #1 with \csname
%\expandafter\def\expandafter\csname #1-names\endcsname% I became lost at this point
}%
重新定义问题
参见上面令人头痛的一行。我该如何知道\expandafter
无需使用反复试验的方法,就能毫无疑问地得到正确的 s 数量?
直到停下来之前我的想法是:
- 好的,我需要
\expandafter
扩展\csname
为宏 - 然后我
\expandafter
在用 TeX 定义该宏之前扩展它的新定义。
笔记
- 这里很好地描述了 为什么我使用
\relax
代替:\undefined
csname
\ifx\somecommand\undefined 和 \ifdefined\somecommand\else 之间有什么区别? - 我在 egreg 上看到了一个公式,例如
2^n-1
(n
需要扩展的数量)。我希望找到该链接并将其放在这里。
答案1
该原语\expandafter
只执行一次扩展,并将其结果保留在输入流中。因此
\expandafter\def\csname #1\endcsname
经过一次扩展后#1
,相当于foo
\def\foo
为了确保一切正确,您必须跟踪需要多少扩展,并了解所需的命令数量\expandafter
(分别对应 1、2、3、... 扩展,1、3、7、...)。我们还提供一些“技巧”。
在
\def\appendtolist#1#2{% #1 = Macro Name (list) #2=New Item to Append
....
需要先进行存在性测试(您已经进行了测试),然后在两个地方扩展\csname #1\endcsname
为命令名称。最好这样做
\expandafter\def\csname#1\expandafter\expandafter\expandafter\endcsname
\expandafter\expandafter\expandafter
{\csname #1\endcsname,#2}%
这里有个“技巧”。我们首先展开过去,\def
开始构造我们想要存储的宏的名称。在构造的末尾\csname ...
,我们需要三个\expandafter
s(和两个集合,因为有一个括号要跳过)。情况就是这样,因为我们需要进行两次扩展:第一次扩展\csname #1\endcsname
为宏名称(例如\foo
),第二次扩展\foo
为 的内容\foo
。
扩展就是准确跟踪输入流中的内容,并了解所需的步骤数。
答案2
\expandafter
需要插入的量的公式。
如果您希望让 TeX “跳过” L 个标记,以便让 TeX 预先生成从第 (L+1)个标记传出的 K 级扩展,则需要在每个要“跳过”的 L 个标记前面插入(2 K -1)个标记。\expandafter
因此:
如果您希望让 TeX “跳过” L 个标记,以便让 TeX 预先生成从第 (L+1)个标记传出的 K 级扩展,则需要将 L·(2 K -1)插入\expandafter
到 .tex 输入中。
基本过程是连续插入\expandafter
-chains。
每个\expandafter
-chain 产生另一层扩展。插入到倒数第二条 -chain 将提供第一层扩展。
插入到倒数第二条 -chain将提供第二层扩展。... 插入到倒数第二条 (K-1) 的 -chain将提供 K 层扩展。\expandafter
\expandafter
\expandafter
此处的要点是:
添加另一个-chain 意味着在每个要添加的 -chain应该“跳过”的 token 前面\expandafter
插入一个-token。 因此,您需要跟踪添加新 -chain 时应该“跳过”的 token 数量。\expandafter
\expandafter
\expandafter
例子:
为了让 TeX 预先生成从第 5 个标记开始的 2 级扩展,让 TeX “跳过” 四个标记需要插入 L·(2 K -1) ,其中 L=4 且 K=2,因此需要在 .tex 输入中\expandafter
插入 4·(2 2 -1) \expandafter
= 12 :\expandafter
%|the \expand|the \expand|
%|after-chain|after-chain|
%|in this co-|in this co-|
%|lumn deli- |lumn deli- |
%|vers |vers |
%|1-level-ex-|2-level-ex-|
%|pansion out|pansion out|
%|-going from|-going from|
%|\Fifth |\Fifth |
%| | | | |
\expandafter\expandafter
\expandafter \First % _________ 1x(2^K-1)=1x(2^2-1) \expandafter inserted in front of \First
\expandafter\expandafter
\expandafter \Second % _________ 1x(2^K-1)=1x(2^2-1) \expandafter inserted in front of \Second
\expandafter\expandafter
\expandafter \third % _________ 1x(2^K-1)=1x(2^2-1) \expandafter inserted in front of \Third
\expandafter\expandafter
\expandafter \Fourth % _________ 1x(2^K-1)=1x(2^2-1) \expandafter inserted in front of \Fourth
\Fifth %A total of Lx(2^K-1)=4x(2^2-1) \expandafter inserted into the .tex-input
让我们看一下连续添加链的场景\expandafter
,以便让 TeX 预先生成从第 (1+1)个标记(= 第 2个标记)传出的 K 级扩展。
要求在.tex 输入中插入 1·(2 K -1) \expandafter
=2 K -1 。\expandafter
(假设\First
是插入 之前的第一个标记,\expandafter
并且\Second
是插入 之前的第二个标记。\expandafter
)
案例 K=0— 让 TeX 产生来自以下项的 0 级扩展\Second
:
(从可扩展令牌传出的 0 级扩展 = 从该令牌传出的扩展不会被触发。)
前面的 0 个标记\Second
应该被“跳过”。前面
有 2 0 个\Second
标记 = 1 个标记。其中 1 个不是\expandafter
-token。
您需要插入 2 0 -1 \expandafter
= 0 \expandafter
:
\First\Second
案例 K=1— 让 TeX 生成从 传出的 1 级扩展\Second
:
为了获得另一级扩展,\expandafter
需要在为实现 K=0 情况而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。
从 K=0 情况我们知道前面有 2 0 个\Second
标记 应该“跳过”。在每个标记前面
插入一个-token 会在 前面产生 2·(2 0 ) 个标记=2 1 个标记。其中 1 个不是 -token 。 您需要插入 2 1 -1 :\expandafter
\Second
\expandafter
\expandafter
%|the \expand|
%|after-chain|
%|in this co-|
%|lumn deli- |
%|vers |
%|1-level-ex-|
%|pansion out|
%|-going from|
%|\Second |
%| | |
\expandafter\First
\Second
案例 K=2— 让 TeX 生成从 传出的 2 级扩展\Second
:
为了获得另一级扩展,\expandafter
需要在为实现案例 K=1 而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。
从案例 K=1 我们知道前面有 2 1 个\Second
标记 应该“跳过”。在每个标记前面
插入一个-token 会在 前面产生 2·(2 1 ) 个标记=2 2 个标记。其中 1 个不是 -token 。 您需要插入 2 2 -1 :\expandafter
\Second
\expandafter
\expandafter
%|the \expand|the \expand|
%|after-chain|after-chain|
%|in this co-|in this co-|
%|lumn deli- |lumn deli- |
%|vers |vers |
%|1-level-ex-|2-level-ex-|
%|pansion out|pansion out|
%|-going from|-going from|
%|\Second |\Second |
%| | | | |
\expandafter\expandafter
\expandafter \First
\Second
案例 K=3— 让 TeX 生成从 传出的 3 级扩展\Second
:
为了获得另一级扩展,\expandafter
需要在为实现 K=2 情况而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。从 K=2 情况我们知道前面
有 2 2 个\Second
标记 应该“跳过”。在每个标记前面
插入一个-token 会在 前面产生 2·(2 2 ) 个标记=2 3 个标记。其中 1 个不是 -token 。 您需要插入 2 3 -1 :\expandafter
\Second
\expandafter
\expandafter
%|the \expand|the \expand|the \expand|
%|after-chain|after-chain|after-chain|
%|in this co-|in this co-|in this co-|
%|lumn deli- |lumn deli- |lumn deli- |
%|vers |vers |vers |
%|1-level-ex-|2-level-ex-|3-level-ex-|
%|pansion out|pansion out|pansion out|
%|-going from|-going from|-going from|
%|\Second |\Second |\Second |
%| | | | | | |
\expandafter\expandafter
\expandafter \expandafter
\expandafter\expandafter
\expandafter \First
\Second
案例K=4— 让 TeX 生成从 传出的 4 级扩展\Second
:
为了获得另一级扩展,\expandafter
需要在为实现案例 K=3 而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。从案例 K=3 我们知道前面
有 2 3 个\Second
标记 应该“跳过”。在每个标记前面
插入一个-token 会在 前面产生 2·(2 3 ) 个标记=2 4 个标记。其中 1 个不是-token。 您需要插入 2 4 -1 :\expandafter
\Second
\expandafter
\expandafter
%|the \expand|the \expand|the \expand|the \expand|
%|after-chain|after-chain|after-chain|after-chain|
%|in this co-|in this co-|in this co-|in this co-|
%|lumn deli- |lumn deli- |lumn deli- |lumn deli- |
%|vers |vers |vers |vers |
%|1-level-ex-|2-level-ex-|3-level-ex-|4-level-ex-|
%|pansion out|pansion out|pansion out|pansion out|
%|-going from|-going from|-going from|-going from|
%|\Second |\Second |\Second |\Second |
%| | | | | | | | |
\expandafter\expandafter
\expandafter \expandafter
\expandafter\expandafter
\expandafter \expandafter
\expandafter\expandafter
\expandafter \expandafter
\expandafter\expandafter
\expandafter \First
\Second
案件 ...
看到图片了吗?
顺便一提:
引入了一些缩进,以提供可见的印象,其中事物被分成几列。
一列是一条\expandafter
-链,提供一层扩展。如果从最右边的列到最左边的列计算每列/每条-链
的数量,则得出: 1+2+4+8+... = ∑ i=1..K {2 (i-1) }=2 K -1。\expandafter
\expandafter
如果获得 4 级扩展传出,则\Second
有 4 列\expandafter
保持 1+2+4+8 \expandafter
= ∑ i=1..4 {2 (i-1) } \expandafter
= 2 4 -1 \expandafter
。
添加另一个\expandafter
-chain 以获得 5 级扩展传出,意味着在 token 前面\Second
添加一个,并在已经存在的1+2+4+8 前面添加一个。 这产生 1+2·(1+2+4+8) =1+2+4+8+16 = ∑ i=1..5 {2 (i-1) } = 2 5 -1 。\expandafter
\First
\expandafter
\expandafter
\expandafter
\expandafter
\expandafter
\expandafter
添加另一个\expandafter
-chain 以获得 6 级扩展传出,意味着在 token 前面\Second
添加一个,并在已经存在的1+2+4+8+16 前面添加一个。 这得到 1+2·(1+2+4+8+16) =1+2+4+8+16+32 = ∑ i=1..6 {2 (i-1) } = 2 6 -1 。\expandafter
\First
\expandafter
\expandafter
\expandafter
\expandafter
\expandafter
\expandafter
现在让我们思考一下连续添加链的场景\expandafter
,以便让 TeX 预先生成从第 (L+1)个标记传出的 K 级扩展。
(插入 之后\expandafter
,第 (L+1)个标记将不再是第 (L+1)个标记。因此,插入 之前的第 (L+1)个\expandafter
标记将被称为“(L+1)-token”——在某些地方“(L+1)”是序数,在其他地方它是一个名义数。)
案例 K=0— 让 TeX 在处理扩展结果之前的 L 个 token 之前,先从 (L+1) 个 token 生成 0 级扩展:
(从可扩展令牌传出的 0 级扩展 = 从该令牌传出的扩展不会被触发。)
(L+1) 标记是标记流中的第
(L+1) 个标记。 (L+1) 标记前面有 0 个标记应该被“跳过”。 (L+1) 标记前面
有 L 个标记 = L·(2 0 ) 个标记。其中 L 个不是\expandafter
标记。
您需要插入 L·(2 0 )-L \expandafter
=L·(2 0 -1) \expandafter
=0 \expandafter
。
案例 K=1— 让 TeX 在处理扩展结果前面的 L 个标记之前,从 (L+1) 个标记生成 1 级扩展:
为了获得另一级扩展,\expandafter
需要在为实现 K=0 情况而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。
从 K=0 情况我们知道 (L+1) 个标记前面有 L·(2 0 ) 个应该“跳过”的标记。在每个标记前面
插入一个-token 会在 (L+1) 个标记前面产生 2·(L·(2 0 )) 个标记 = L·(2 1 ) 个标记。其中 L 个不是-token。 您需要插入 L·(2 1 )-L =L·(2 1 -1) 。\expandafter
\expandafter
\expandafter
\expandafter
案例 K=2— 让 TeX 在处理扩展结果前面的 L 个标记之前,从 (L+1) 个标记生成 2 级扩展:
为了获得另一个扩展级别,\expandafter
需要在为实现情况 K=1 而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。
从情况 K=1 我们知道在 (L+1) 个标记前面有 L·(2 1 ) 个应该“跳过”的标记。在每个标记前面
插入一个-token 会在 (L+1) 个标记前面产生 2·(L·(2 1 )) 个标记 = L·(2 2 ) 个标记。其中 L 不是-token。 您需要插入 L·(2 2 )-L =L·(2 2 -1) 。\expandafter
\expandafter
\expandafter
\expandafter
案例 K=3— 让 TeX 在处理扩展结果前面的 L 个标记之前,从 (L+1) 个标记生成 3 级扩展:
为了获得另一级扩展,\expandafter
需要在为实现 K=2 情况而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。
从 K=2 情况我们知道 (L+1) 个标记前面有 L·(2 2 ) 个应该“跳过”的标记。在每个标记前面
插入一个-token 会在 (L+1) 个标记前面产生 2·(L·(2 2 )) 个标记 = L·(2 3 ) 个标记。其中 L 个不是-token。 您需要插入 L·(2 3 )-L =L·(2 3 -1) 。\expandafter
\expandafter
\expandafter
\expandafter
案例K=4— 让 TeX 在处理扩展结果前面的 L 个标记之前,从 (L+1) 个标记生成 4 级扩展:
为了获得另一级扩展,\expandafter
需要在为实现 K=3 情况而生成的代码中添加另一个 -chain。这意味着\expandafter
在每个应该“跳过”的标记前面插入一个 -token。
从 K=3 情况中,我们知道 (L+1) 个标记前面有 L·(2 3 ) 个应该“跳过”的标记。在每个标记前面
插入一个-token 会在 (L+1) 个标记前面产生 2·(L·(2 3 )) 个标记 = L·(2 4 ) 个标记。其中 L 个不是-token。 您需要插入 L·(2 4 )-L =L·(2 4 -1) 。\expandafter
\expandafter
\expandafter
\expandafter
案件 ...
有一些技巧可以减少的数量\expandafter
。
\if
一些技巧与扩展由诸如和\ifcat
和\romannumeral
和之类的标记触发的事实有关\csname
。
其他技巧与使用 TeX 翻转宏参数有关。
例如,使用 TeX 的#{
-notation,你可以定义一个宏\name
它确实在参数的第一个左括号之前获取标记,并导致另一个(内部)宏获取嵌套在第一个左括号和相应的右括号中的标记,用于另一个/第二个参数。
这样,您不需要编写\expandafter
-chains 来启动\csname
-expansion。
我详细阐述了\name
-macro2016 年 11 月 10 日在 TeX - LaTeX StackExchange 上发起的线程“定义一个控制序列,其后有一个空格”。
基本上,宏\name
是有关让 TeX 翻转宏参数的东西之一。
让我们看一下如何利用\romannumeral
-expansion。
\romannumeral
触发扩展,直到明确需要触发错误消息,或者 TeX 找到一组形成有效 TeX-⟨数字⟩-数量。
在 TeX- 表示的数字中,⟨数字⟩-quantity 发现为正数,TeX 会提供一组明确的字符标记,类别 12(其他),以小写罗马数字表示该数字。
在更有趣的情况下,TeX-⟨数字⟩-如果发现数量不为正,TeX 不会默默地传递任何 token,而是吞下/移除/丢弃形成 TeX- 的 token⟨数字⟩-数量。
因此\romannumeral
,只要扩展级联结果中的前导标记最终形成有效的 TeX-,就可以(滥用?)用于触发大量的扩展工作⟨数字⟩-quantity 表示非正数。
\Expandtimes{<number K>}
您可以创建一个利用 -expansion 的宏\romannumeral
,使得序列
\romannumeral\Expandtimes{<number K>}
只需要一个“命中”\expandafter
就可以产生 K 个“命中” \expandafter
。
\romannumeral\Expandtimes{<number K>}
\expandafter
将所需的链数量减少到仅一个\expandafter
链。
句法:
\romannumeral\Expandtimes{<number K>}<token sequence>
→K
次 的前导标记<token sequence>
将被 命中\expandafter
。
在扩展上下文中,前导\romannumeral
被 命中一次\expandafter
就足以获得 的前导标记的这些K
“命中” 。\expandafter
<token sequence>
例如,
\def\top{\first 0 }
\def\first{\second1 }
\def\second{\third2 }
\def\third{\fourth3 }
\def\fourth{\fifth4 }
\def\fifth{\sixth5 }
\def\sixth{6 }
\expandafter\string\romannumeral\Expandtimes{0}\top
→\string\top
\expandafter\string\romannumeral\Expandtimes{1}\top
→\string\first0
\expandafter\string\romannumeral\Expandtimes{2}\top
→\string\second1 0
\expandafter\string\romannumeral\Expandtimes{3}\top
→\string\third2 1 0
\expandafter\string\romannumeral\Expandtimes{4}\top
→\string\fourth3 2 1 0
\expandafter\string\romannumeral\Expandtimes{5}\top
→\string\fifth4 3 2 1 0
\expandafter\string\romannumeral\Expandtimes{6}\top
→\string\sixth5 4 3 2 1 0
\expandafter\string\romannumeral\Expandtimes{7}\top
→\string6 5 4 3 2 1 0
实现的方法有很多种\Expandtimes
。
(一种无聊的方法是:
\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\chardef\stopromannumeral=`\^^00
% A check is needed for finding out if an argument is catcode-11-"d" while there are only
% the possibilities that the argument is either a single catcode-11-"d"
% or a single catcode-12-"m":
\def\innerdfork#1d#2#3dd{#2}%
\def\dfork#1{\innerdfork#1{\firstoftwo}d{\secondoftwo}dd}%
% By means of \romannumeral create as many catcode-12-characters m as expansion-steps are to take place.
% Then by means of recursion for each of these m double the amount of `\expandafter`-tokens and
% add one `\expandafter`-token within \innerExp's first argument.
\def\Expandtimes#1{\expandafter\innerExp\expandafter{\expandafter}\romannumeral\number\number#1 000d}
\def\innerExp#1#2{\dfork{#2}{#1\stopromannumeral}{\innerExp{#1#1\expandafter}}}
Now testing \tt\string\Expandtimes:
\def\top{\first 0 }
\def\first{\second1 }
\def\second{\third2 }
\def\third{\fourth3 }
\def\fourth{\fifth4 }
\def\fifth{\sixth5 }
\def\sixth{6 }
\expandafter\string\romannumeral\Expandtimes{0}\top
\expandafter\string\romannumeral\Expandtimes{1}\top
\expandafter\string\romannumeral\Expandtimes{2}\top
\expandafter\string\romannumeral\Expandtimes{3}\top
\expandafter\string\romannumeral\Expandtimes{4}\top
\expandafter\string\romannumeral\Expandtimes{5}\top
\expandafter\string\romannumeral\Expandtimes{6}\top
\expandafter\string\romannumeral\Expandtimes{7}\top
\bye
)
此答案底部的示例展示了另一种实现方式。
(\Expandtimes
此答案底部示例中的 -variant 确实触发了大量\csname
-expansion,这反过来又会影响与语义嵌套大小相关的内存分配。)
例如,如果您希望宏\top
被“命中”\expandafter
六次,而\top
插入之前是标记流中的第\expandafter
16 个标记,则需要插入六个链以绕过 15 个标记。这很多。 (根据公式,这将是 15·(2 6 -1) = 945。)\expandafter
\expandafter
\expandafter
\expandafter
使用\romannumeral\Expandtimes{<number K>}<token sequence>
-thingie,你可以轻松地减少到只需要一个\expandafter
-chain 来绕过 15 个 token。(根据公式,这将是 15·(2 1 -1) \expandafter
= 15 \expandafter
。
这相差 930 \expandafter
。)
这看起来像这样:
\expandafter\tokA
\expandafter\tokB
\expandafter\tokC
\expandafter\tokD
\expandafter\tokE
\expandafter\tokF
\expandafter\tokG
\expandafter\tokH
\expandafter\tokI
\expandafter\tokJ
\expandafter\tokK
\expandafter\tokL
\expandafter\tokM
\expandafter\tokN
\expandafter\tokO
\romannumeral\Expandtimes{6}\top
但 15 仍然很多\expandafter
。
到目前为止,只应用了\expandafter
使用 -thingie 减少到只需要一个 -chain的技巧。 现在只有一个-chain。 但那条链很长。\romannumeral\Expandtimes{<number K>}
\expandafter
\expandafter
通过让 TeX 翻转宏参数来保持链简短。
另一个减少代码量的技巧\expandafter
可以在很多(但不是全部)情况下应用,那就是\expandafter
通过让 TeX 翻转宏参数/简单地通过让 TeX 交换宏参数来保持 -chains 简短:
\long\def\exchange#1#2{#2#1}%
\expandafter\exchange
\expandafter{%
\romannumeral\Expandtimes{6}\top
}{%
\tokA\tokB\tokC\tokD\tokE
\tokF\tokG\tokH\tokI\tokJ
\tokK\tokL\tokM\tokN\tokO
}%
(我写了“但不是全部!!”因为交换参数的技巧不能应用于由扩展级联触发的\Expandtimes
旨在执行某些括号删除黑客攻击的情况,因此扩展级联结果中的花括号 catcode1/2 括号不平衡。)
现在从需要 945 减少\expandafter
到需要 2 \expandafter
。
差异为 943 \expandafter
。
但有一个细微的差别:
使用\romannumeral\Expandtimes{6}
通过 15 个标记绕过 15 个标记的方法\expandafter
,仅需触发一个扩展步骤(“命中”链\expandafter
中的第一个\expandafter
)即可获得 的 6 级扩展\top
。
\romannumeral\Expandtimes{6}
对于将 -thingie 与 -ing 参数组合的方法\exchange
,需要触发两个扩展步骤。一个扩展步骤用于“命中”仅由两个组成的 -chain\expandafter
中的第一个。另一个扩展步骤用于开始执行其交换参数的工作。\expandafter
\expandafter
\exchange
如果需要的话,您可以再次应用旧的\romannumeral
-expansion 来减少仅需要触发一个扩展步骤:
\chardef\stopromannumeral=`\^^00
\long\def\exchange#1#2{#2#1}%
\romannumeral%<-\romannumeral gathers tokens for the <number>-quantity keeps expanding expandable tokens.
\expandafter\exchange
\expandafter{%
\romannumeral\Expandtimes{6}\top
}{\stopromannumeral
% After exchanging \stopromannumeral is right behind
% \romannumeral and causes \romannumeral to
% terminate. It gets removed by \romannumeral which in turn does not
% deliver any tokens as \stopromannumeral denotes the value 0 is not a positive number.
\tokA\tokB\tokC\tokD\tokE
\tokF\tokG\tokH\tokI\tokJ
\tokK\tokL\tokM\tokN\tokO
}%
使用此技术, 的数量\expandafter
与要“跳过”的令牌数量无关。
当要“跳过”的令牌数量不可预测时,此技术非常有用,因为这些令牌是通过宏参数传递的。
现在举一个例子,将各种技巧结合起来,将标记附加到一个列表,该列表存储为一个需要指定名称的宏:
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\exchange#1#2{#2#1}%
\long\def\name#1#{\innername{#1}}%
\long\def\innername#1#2{%
\expandafter\exchange\expandafter{\csname#2\endcsname}{#1}%
}%
\chardef\stopromannumeral=`\^^00%
\def\Expandtimes#1{%
\csname stopromannumeral\expandafter\Expandtimesloop
\romannumeral\number\number#1 000D\endcsname
}%
\def\Expandtimesloop#1{%
\if m\noexpand#1%
\expandafter\expandafter\csname endcsname\expandafter\Expandtimesloop\fi
}%
% The most frugal and most boring thingie without \name and without \Expandtimes
% and without \romannumeral.
\long\def\appendtolist#1#2{%
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{\expandafter\def\csname#1\endcsname{#2}}%
{%
\expandafter\def\csname#1\expandafter\expandafter\expandafter\endcsname
\expandafter\expandafter\expandafter{\csname#1\endcsname,#2}%
}%
}%
% Another thingie without \name and without \Expandtimes.
% This time a \romannumeral-trick is used for eliminating the need of
% having \csname launch two \expandafter-chains.
\long\def\otherappendtolist#1#2{%
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{\expandafter\def\csname#1\endcsname{#2}}%
{%
\expandafter\def\csname#1\expandafter\endcsname\expandafter{%
\romannumeral\expandafter\expandafter\expandafter\stopromannumeral
\csname#1\endcsname,#2}%
}%
}%
% One more thingie. This time \name and \exchange and \romannumeral-expansion
% are used. On the first glimpse it is confusing. Therefore it is one of my
% favorites.
\long\def\AndOneMoreAppendtolist#1#2{%
\name\ifx{#1}\relax\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{\name\def{#1}{#2}}%
{\expandafter\exchange\expandafter{\expandafter{\romannumeral\name\stopromannumeral{#1},#2}}%
{\name\expandafter\def\expandafter{#1}\expandafter}%
}%
}%
% Yet another thingie.
% This time the helper-macro \Expandtimes is used for eliminating the need of
% having \csname launch two \expandafter-chains.
\long\def\yetotherappendtolist#1#2{%
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{\expandafter\def\csname#1\endcsname{#2}}%
{%
\expandafter\def\csname#1\expandafter\endcsname
\expandafter{\romannumeral\Expandtimes{2}\csname#1\endcsname,#2}%
}%
}%
% A thingie where you can specify the level of expansion before appending.
\long\def\AppendLevelExpandedTolist#1#2#3{%
\expandafter\def
\csname#1%
\expandafter\endcsname
\expandafter{%
\romannumeral
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{}{%
\Expandtimes{2}\csname#1%
\expandafter\endcsname
\expandafter,%
\romannumeral
}%
\Expandtimes{#2}#3%
}%
}%
\tt
\appendtolist{mylist}{element1}
\name\string{mylist}\name\meaning{mylist}
\appendtolist{mylist}{element2}
\name\string{mylist}\name\meaning{mylist}
\otherappendtolist{mylist}{element3}
\name\string{mylist}\name\meaning{mylist}
\AndOneMoreAppendtolist{mylist}{element4}
\name\string{mylist}\name\meaning{mylist}
\yetotherappendtolist{mylist}{element5}
\name\string{mylist}\name\meaning{mylist}
\hrule
Now testing \string\Expandtimes:
\def\top{\first 0 }
\def\first{\second1 }
\def\second{\third2 }
\def\third{\fourth3 }
\def\fourth{\fifth4 }
\def\fifth{\sixth5 }
\def\sixth{6 }
\expandafter\string\romannumeral\Expandtimes{0}\top
\expandafter\string\romannumeral\Expandtimes{1}\top
\expandafter\string\romannumeral\Expandtimes{2}\top
\expandafter\string\romannumeral\Expandtimes{3}\top
\expandafter\string\romannumeral\Expandtimes{4}\top
\expandafter\string\romannumeral\Expandtimes{5}\top
\expandafter\string\romannumeral\Expandtimes{6}\top
\expandafter\string\romannumeral\Expandtimes{7}\top
\hrule
Now testing \string\AppendLevelExpandedTolist:
\AppendLevelExpandedTolist{myotherlist}{0}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\AppendLevelExpandedTolist{myotherlist}{1}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\AppendLevelExpandedTolist{myotherlist}{2}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\AppendLevelExpandedTolist{myotherlist}{3}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\AppendLevelExpandedTolist{myotherlist}{4}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\AppendLevelExpandedTolist{myotherlist}{5}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\AppendLevelExpandedTolist{myotherlist}{6}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\AppendLevelExpandedTolist{myotherlist}{7}{\top}
\name\string{myotherlist}\name\meaning{myotherlist}
\bye
答案3
附加到现有(无参数)宏只需要一个链:
\expandafter\def\expandafter\foo\expandafter{\foo<material to add>}
如果按名称调用无参数宏,因此需要\csname
,问题会稍微复杂一些,需要\expandafter
分散在各个地方的七个标记。
您可以使用各种方法更轻松地完成此操作。
\makeatletter
\newcommand{\appendtolist}[2]{%
\@ifundefined{#1}
{\@namedef{#1}{#2}}% not yet defined
{\append@to@list{#1}{#2}}%
}
\newcommand{\append@to@list}[2]{%
\toks0=\expandafter\expandafter\expandafter{\csname #1\endcsname}%
\toks2={,#2}%
\expandafter\edef\csname#1\endcsname{\the\toks0 \the\toks2}
}
\makeatother
使用三元组\expandafter
我们到达{
并扩展两次\csname #1\endcsname
;第一次我们获得宏名称,第二次获得其扩展。我利用\the\toks<n>
仅在 中扩展一次的事实\edef
。
不过,你可以做得更好。首先定义\append@to@list
案例
\append@to@list{\foo}{bar}
这很容易(而且你已经这样做了)
\makeatletter
\newcommand{\append@to@list}[2]{%
\ifx#1\undefined
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\def#1{#2}}%
{\expandafter\def\expandafter#1\expandafter{#1,#2}}%
}
\newcommand{\appendtolist}[2]{%
\expandafter\append@to@list\csname#1\endcsname{#2}%
}
看出其中的窍门了吗?扩展一次得到的 token\csname#1\endcsname
被传递给\append@to@list
。
etoolbox
这与使用其\appto
和宏的方法非常相似\csappto
。但是,虽然上面的代码不依赖于 e-TeX 基元,但中的代码却etoolbox
依赖它。使用etoolbox
它非常简单:
\newcommand{\appendtolist}[2]{%
\ifcsundef{#1}
{\csappto{#1}{#2}}
{\csappto{#1}{,#2}}%
}
最后但并非最不重要的一点:使用expl3
:\expandafter
根本不需要,所以不需要计算它们。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\appendtolist}{mm}
{% #1 = list name, #2 = tokens to append
\tl_if_exist:cTF { komar_list_#1_tl }
{
\tl_put_right:cn { komar_list_#1_tl } { ,#2 }
}
{
\tl_new:c { komar_list_#1_tl }
\tl_set:cn { komar_list_#1_tl } { #2 }
}
}
\NewExpandableDocumentCommand{\uselist}{m}
{% #1 = list name
\tl_use:c { komar_list_#1_tl }
}
\ExplSyntaxOff
\begin{document}
\appendtolist{test}{a} \uselist{test}
\appendtolist{test}{b} \uselist{test}
\end{document}
对于这种情况,最好使用 clist 变量。
答案4
我想添加另一个答案,因为到目前为止提供的所有答案(2020 年 1 月 19 日,16:10:00 UTC+0100)都没有明确提到扩展宏会导致其包含的连续哈希值数量减半这一事实⟨definition-text⟩
。
假设您已定义宏\appendtolist
,其中保存列表的宏被扩展,以便扩展结果形成新的一部分⟨definition-text⟩
。在扩展期间,作为⟨definition-text⟩
保存列表的宏的一部分存储的连续哈希值的数量将减半。每当您将 附加到列表时,都会发生这种情况:
\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\def\innerdfork#1d#2#3dd{#2}%
\def\dfork#1{\innerdfork#1{\firstoftwo}d{\secondoftwo}dd}%
\def\Expandtimes#1{0\expandafter\innerExp\expandafter{\expandafter}\romannumeral\number\number#1 000d}
\def\innerExp#1#2{\dfork{#2}{#1 }{\innerExp{#1#1\expandafter}}}
% A thingie where you can specify the level of expansion before appending.
\long\def\AppendLevelExpandedTolist#1#2#3{%
\expandafter\def
\csname#1%
\expandafter\endcsname
\expandafter{%
\romannumeral
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{}{%
\Expandtimes{2}\csname#1%
\expandafter\endcsname
\expandafter,%
\romannumeral
}%
\Expandtimes{#2}#3%
}%
}%
\def\top{\first 0 }
\def\first{\second1 }
\def\second{\third2 }
\def\third{\fourth3 }
\def\fourth{\fifth4 }
\def\fifth{\sixth5 }
\def\sixth{6 }
\def\mylist{SomethingWithHashes################}
\AppendLevelExpandedTolist{mylist}{4}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{2}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{1}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{3}{\top}
\show\mylist
\bye
使用此方法的终端输出是
> \mylist=macro:
->SomethingWithHashes########,\fourth 3 2 1 0 .
l.38 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes####,\fourth 3 2 1 0 ,\second 1 0 .
l.41 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes##,\fourth 3 2 1 0 ,\second 1 0 ,\first 0 .
l.44 \show\mylist
?
! Illegal parameter number in definition of \mylist.
<to be read again>
您是否注意到短语后面的哈希数量SomethingWithHashes
逐渐减少?
目前有两种方法可以解决这个问题:
使用传统的 TeX,您可以将内容放入标记寄存器,然后\edef
应用于。\the⟨token-register⟩
使用可用的 ε-TeX 扩展,您可以使用\unexpanded{...}
而不是。\the⟨token-register⟩
原因是:
带有和带有哈希的值都会加倍,并且进一步的扩展会受到抑制。\edef⟨macro⟩{\the⟨token-register⟩}
\edef⟨macro⟩{\unexpanded{⟨stuff where hashes shall not appear reduced⟩}}
当 ε-TeX 扩展不可用时,您可以执行以下操作:
\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\def\exchange#1#2{#2#1}%
\def\innerdfork#1d#2#3dd{#2}%
\def\dfork#1{\innerdfork#1{\firstoftwo}d{\secondoftwo}dd}%
\def\Expandtimes#1{0\expandafter\innerExp\expandafter{\expandafter}\romannumeral\number\number#1 000d}
\def\innerExp#1#2{\dfork{#2}{#1 }{\innerExp{#1#1\expandafter}}}
\newtoks\myscratchtoks
% A thingie where you can specify the level of expansion before appending.
\long\def\AppendLevelExpandedTolist#1#2#3{%
\expandafter\exchange\expandafter{%
\expandafter\myscratchtoks\expandafter{\the\myscratchtoks}%
}{%
\myscratchtoks\expandafter{%
\romannumeral
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{}{%
\Expandtimes{2}\csname#1%
\expandafter\endcsname
\expandafter,%
\romannumeral
}%
\Expandtimes{#2}#3%
}%
\expandafter\edef\csname#1\endcsname{\the\myscratchtoks}%
}%
}%
\def\top{\first 0 }
\def\first{\second1 }
\def\second{\third2 }
\def\third{\fourth3 }
\def\fourth{\fifth4 }
\def\fifth{\sixth5 }
\def\sixth{6 }
\def\mylist{SomethingWithHashes################}
\AppendLevelExpandedTolist{mylist}{4}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{2}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{1}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{3}{\top}
\show\mylist
\bye
终端输出为:
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 .
l.43 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 ,\second 1 0 .
l.46 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 ,\second 1 0 ,\first 0 .
l.49 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 ,\second 1 0 ,\first 0 ,\
third 2 1 0 .
l.52 \show\mylist
采用这种方法,哈希值不会减少。
使用可用的 ε-TeX 扩展,您可以执行以下操作:
\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\def\innerdfork#1d#2#3dd{#2}%
\def\dfork#1{\innerdfork#1{\firstoftwo}d{\secondoftwo}dd}%
\def\Expandtimes#1{0\expandafter\innerExp\expandafter{\expandafter}\romannumeral\number\number#1 000d}
\def\innerExp#1#2{\dfork{#2}{#1 }{\innerExp{#1#1\expandafter}}}
% A thingie where you can specify the level of expansion before appending.
\long\def\AppendLevelExpandedTolist#1#2#3{%
\expandafter\edef\csname#1\endcsname{%
\expandafter\ifx\csname #1\endcsname\relax
\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{}{%
\unexpanded\expandafter{\romannumeral\Expandtimes{2}\csname#1\endcsname,}%
}%
\unexpanded\expandafter{\romannumeral\Expandtimes{#2}#3}%
}%
}%
\def\top{\first 0 }
\def\first{\second1 }
\def\second{\third2 }
\def\third{\fourth3 }
\def\fourth{\fifth4 }
\def\fifth{\sixth5 }
\def\sixth{6 }
\def\mylist{SomethingWithHashes################}
\AppendLevelExpandedTolist{mylist}{4}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{2}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{1}{\top}
\show\mylist
\AppendLevelExpandedTolist{mylist}{3}{\top}
\show\mylist
\bye
终端输出为:
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 .
l.31 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 ,\second 1 0 .
l.34 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 ,\second 1 0 ,\first 0 .
l.37 \show\mylist
?
> \mylist=macro:
->SomethingWithHashes################,\fourth 3 2 1 0 ,\second 1 0 ,\first 0 ,\
third 2 1 0 .
l.40 \show\mylist
采用这种方法,哈希值不会减少。