笔记

笔记

我想知道 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}
  1. 我想了解原因\getarray[1]\groups\getarray[1]\nogroups得出不同的结果。
  2. 递归线是否是\expandafter\setarrayItem\fi一种用于参数标记咀嚼的隐式循环?
  3. 为什么\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 @?@#14 个标记abc ,并且如果输入\yyy {abc }@?@就像分隔参数仅由括号组组成一样,则会传递相同的标记,外层的括号将被剥离。

\show只显示一个标记,因此\show #1如果#1与将显示和排版的标记one相同\show oneone


问题

递归线是否是\expandafter\setarrayItem\fi一种用于参数标记咀嚼的隐式循环?

\fi与除了关闭测试之外的其他参数没有特别的关系,\ifx\end#1这意味着如果#1没有结束,宏会在这个分支中递归调用自身,当分支#1\end空时,停止迭代。

答案2

参数标记#1#9在宏定义时相关,因此在思考它们时您会被误导。

\setarray有两个未限定参数(因为参数标记之间没有任何分隔符)。这意味着 TeX 在扩展 时会寻找两个参数\setarray

当查找未分隔的参数时(再次强调,“分隔”或“未分隔”仅指宏的定义方式),TeX 会跳过空格标记,直到找到非空格标记。有两种情况:

  1. 非空间标记不是<left brace>
  2. 非空间标记是<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之间的空格将被忽略。ot

该宏\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 的(公认的聪明)代码尝试一下。

相关内容