我想知道 TeX 令牌处理程序是如何处理参数arguments。看起来参数参数是逐个 token 发送的。这是真的吗?
考虑以下基于的代码wipet 的回答用于存储“数组”:
\documentclass{article}
\usepackage{fontspec}% xelatex
\newcount\itemidx
\def\setarray#1#2{\itemidx=0 \edef\tmp{\string#1}\setarrayItem#2\end}
\def\setarrayItem#1{\advance\itemidx by1
\ifx\end#1\else
\expandafter\def\csname data:\tmp:\the\itemidx\endcsname{#1}%
\expandafter\setarrayItem\fi
}
\def\getarray[#1]#2{\csname data:\string#2:#1\endcsname}
\setarray\groups{{one}{two}{three}}
\setarray\nogroups{one two three}
\begin{document}
\null
\getarray[1]\groups
\getarray[1]\nogroups
\end{document}
- 我想了解原因
\getarray[1]\groups
并\getarray[1]\nogroups
得出不同的结果。 - 递归线是否是
\expandafter\setarrayItem\fi
一种用于参数标记咀嚼的隐式循环? - 为什么
\show#1
在定义的开头加上\setarray
会产生o
和 而不是{one}
或{one} {two} {three}
。在处理参数参数时,类别代码 1 和 2 似乎会被忽略,但在\groups
和中确实会产生差异\nogroups
。
笔记
TeX Book 第 203 页提到了术语“参数标记”
TEX 还识别“参数标记”,此处用 #1 至 #9 表示。参数标记只能出现在宏的标记列表中。
答案1
tex 宏有两种参数,定界参数和非定界参数,对于非定界参数,该参数要么是单个标记,要么如果该标记是显式括号(catcode 1 的字符),则该参数是所有匹配的文本}
(catcode 2 的字符),在后一种情况下,括号不作为参数的一部分传递。所以如果你有
\def\xxx#1{...#1...}
然后之后\xxx Z
将#1
是单个 token Z
,之后\xxx {ab{c}}
将是 5 个 tokenab{c}
分隔参数类似,但匹配所有标记,直到达到指定的标记序列(]
在上面的示例中)之后
\def\yyy#1@?@{...#1...}
然后在之后\yyy abc @?@
是#1
4 个标记abc
,并且如果输入\yyy {abc }@?@
就像分隔参数仅由括号组组成一样,则会传递相同的标记,外层的括号将被剥离。
\show
只显示一个标记,因此\show #1
如果#1
与将显示和排版的标记one
相同\show one
o
ne
问题
递归线是否是
\expandafter\setarrayItem\fi
一种用于参数标记咀嚼的隐式循环?
\fi
与除了关闭测试之外的其他参数没有特别的关系,\ifx\end#1
这意味着如果#1
没有结束,宏会在这个分支中递归调用自身,当分支#1
为\end
空时,停止迭代。
答案2
参数标记#1
仅#9
在宏定义时相关,因此在思考它们时您会被误导。
宏\setarray
有两个未限定参数(因为参数标记之间没有任何分隔符)。这意味着 TeX 在扩展 时会寻找两个参数\setarray
。
当查找未分隔的参数时(再次强调,“分隔”或“未分隔”仅指宏的定义方式),TeX 会跳过空格标记,直到找到非空格标记。有两种情况:
- 非空间标记不是
<left brace>
- 非空间标记是
<left brace>
(意思是明确的 {
或任何其他类别代码为 1 的标记,但我们不要让事情复杂化)。
在第一种情况下,非空格标记将替换替换文本中的相应参数。在第二种情况下,TeX 继续扫描输入以寻找匹配项<right brace>
(从而跟踪括号嵌套)。找到匹配项后,它会剥离外部括号,并用整个吸收标记集替换相应参数。
因此,\def\foo#1#2{-#1-#2-}
有了
\foo\bar\x
\foo\bar{abc}
\foo{abc}\bar
\foo{abc}{def}
将分别实现
-\bar-\x-
-\bar-abc-
-abc-\bar-
-abc-def-
到主令牌列表进行进一步处理。
让我们看看\setarray\groups{{one}{two}{three}}
上面的规则会做什么;#1
被 替换,并被\groups
替换#2
,{one}{two}{three}
因此新的标记列表将是
\itemidx=0 \edef\tmp{\string\groups}\setarrayItem{one}{two}{three}\end
两项任务都完成了,我们仍然
\setarrayItem{one}{two}{three}\end
根据其定义,\setarrayItem
有一个参数;上面的规则说它是{one}
(但括号会被去掉),所以我们得到
\advance\itemidx by1
\ifx\end one\else
\expandafter\def\csname data:\tmp:\the\itemidx\endcsname{one}%
\expandafter\setarrayItem\fi
{two}{three}\end
(换行符和 )%
在标记列表中实际上没有意义,我只是为了清楚起见才使用它们。分配已执行并消失(\itemidx
将包含值 1)。然后\ifx
执行测试,\end
与进行比较o
;由于两个标记不同,因此直到 的标记\else
都被吞噬,因此我们保留
\expandafter\def\csname data:\tmp:\the\itemidx\endcsname{one}%
\expandafter\setarrayItem\fi
{two}{three}\end
好的,我们将建立一个象征性的\expandafter
代币\csname
;我们将剩下
\def\data:\groups:1{one}\expandafter\setarrayItem\fi{two}{three}\end
请记住,其中\data:\groups:1
是单个标记。定义完成后,我们剩下
\expandafter\setarrayItem\fi{two}{three}\end
这里\expandafter
展开\fi
(没有留下任何东西),所以我们得到
\setarrayItem{two}{three}\end
并且将重复之前的操作,从而定义\data:\groups:2
和\data:\groups:3
。在下一次迭代中,我们将得到
\setarrayItem\end
现在我们有
\advance\itemidx by1
\ifx\end\end\else
\expandafter\def\csname data:\tmp:\the\itemidx\endcsname{\end}%
\expandafter\setarrayItem\fi
计数器前进,然后\end
与 进行比较\end
:哦,测试返回 true!因此,除了测试标记之外,没有任何东西被删除,所以我们仍然保留
\else
\expandafter\def\csname data:\tmp:\the\itemidx\endcsname{\end}%
\expandafter\setarrayItem\fi
的扩展是什么\else
?它包括吞噬匹配的所有内容\fi
并使找到的所有内容消失。递归结束。概括地说,我们定义了三个宏(名称复杂)。
如果是
\setarray\nogroups{one two three}
例程将执行定义
\def\data:\nogroups:1{o}
\def\data:\nogroups:2{n}
\def\data:\nogroups:3{e}
\def\data:\nogroups:4{t}
\def\data:\nogroups:5{w}
\def\data:\nogroups:6{o}
\def\data:\nogroups:7{t}
\def\data:\nogroups:8{h}
\def\data:\nogroups:9{r}
\def\data:\nogroups:10{e}
\def\data:\nogroups:11{e}
因为根据查找无界参数的规则,e
和以及和t
之间的空格将被忽略。o
t
该宏\getarray[1]\groups
将构建名为
\data:\groups:1
及其扩展将提供one
;\getarray[1]\nogroups
控制序列
\data:\nogroups:1
建成并扩建o
。
现在您应该将以上所有内容与中的粗略实现进行比较expl3
,其中代码几乎是不言自明的(但不那么有趣):
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_protected:Npn \setarray #1 #2
{
\tl_clear_new:N #1
\tl_set:Nn #1 { #2 }
}
\cs_new:Npn \getarray [#1] #2
{
\tl_item:Nn #2 #1
}
\ExplSyntaxOff
我并不推荐这种语法\getarray[1]\groups
,因为方括号似乎与上下文无关。这甚至允许开箱即用地调用\getarray[-1]\groups
来访问数组中的最后一项。
哦,还有
\setarray\foo{{\textbf{a}}{\textit{a}}{\textsf{a}}}
\edef\baz{\getarray[1]\foo}
可以工作并存储\textbf{a}
在 中\baz
。使用 wipet 的(公认的聪明)代码尝试一下。