使用 \edef 定义的宏,其主体仅由不可扩展的标记组成

使用 \edef 定义的宏,其主体仅由不可扩展的标记组成

\pgfoo@inherit@methods以下纯 TeX 代码是根据 pgf/tikz 源代码中的类似代码行(靠近中的宏的末尾<pgf/tikz installation dir>/modules/pgfmoduleoo.code.tex)建模的:

\edef\c{%
    \noexpand\let\expandafter\noexpand\csname a\endcsname%
    \expandafter\noexpand\csname b\endcsname%
%}

当我打印时\c\meaning得到:

macro:->\let \a \b

这不是我所期望的。我期望的是

macro:->\noexpand \let \expandafter \noexpand ... \csname b\endcsname

换句话说,我期望使用与定义 完全相同的标记列表\c。这种期望的原因是\edef应该只扩展宏,但 的定义中的任何标记都不\c是宏。

为什么会\c这样\meaning呢?

答案1

宏,即用\def\edef或定义的标记\gdef都是\xdef可扩展的。每个\let可扩展标记的标记都是可扩展的。

基元可以是可扩展的;可扩展基元包括

  • \csname
  • \else
  • \expandafter
  • \fi
  • \if...(任何原始条件)
  • \input
  • \meaning
  • \noexpand
  • \number
  • \or
  • \romannumeral
  • \string
  • \the

e-TeX 增加了一些条件和可扩展原语

  • \detokenize
  • \eTeXrevision
  • \scantokens
  • \unexpanded
  • \unless

请注意,e-TeX 还添加了\protected;用 定义的宏将不会在或 的\protected\def一般文本中扩展。\edef\write

pdftex和引擎添加了更多可xetex扩展luatex原语;请查看它们的文档。

在您编写的代码中,第一个\noexpand什么都不做。实际上,的作用\noexpand是使以下标记\relax在下次使用时等同于(因此不可扩展);\noexpand在完成其任务后,的扩展为空。但是,\let一开始就不可扩展,因此在期间不会对其进行任何操作\edef

类似地,\expandafter在扩展了下一个令牌之后(如果可以扩展的话),它将扩展为无。

因此你的代码\edef\c 就变成了

\let\expandafter\noexpand\csname a\endcsname\expandafter\noexpand\csname b\endcsname

\let相当于\relax

\let\noexpand\a\expandafter\noexpand\csname b\endcsname

\let\a\noexpand\b

\a相当于\relax

\let\a\b

\b相当于\relax

\c展开时,\let \a\b不再等同于\relax

答案2

\def好的,按照要求,下面是 TeX 程序在和情况下发生的情况的概述\edef。对于这个问题,它完全没有增加任何基本点(即\noexpand\expandafter确实是“可扩展的”),但无论如何它可能对你来说很有趣。

你可能想参考TeX 程序正如您阅读下面的内容一样。

TeX 的main_control程序(第 1030 节)的作用是读取一个标记(get_x_token),(本质上)循环,然后决定要做什么。内循环针对水平模式下的常规字符进行了严格优化,但诸如\hrule或 之类的东西\def不是它的一部分(→第 1045 节),事实上\def\edef是模式独立处理的一部分(→第 1210 节),并导致调用该程序prefixed_command(第 1211 节)。此程序对许多基元/命令来说都是通用的,但对于 和\def\edef它的调用方式如下:

  • cur_cmd= [表示“def”的代码],并且
  • cur_chr= [0 为\def, 1 为\gdef, 2 为\edef, 3 为\xdef]

(正如你可能从这四个数字中猜到的那样,Knuth 随后检查“ odd(cur_chr)”来决定当前定义是否应该是全局的,并检查“ e = (cur_chr >= 2)”来决定是否扩展!)

无论如何,在此prefixed_command程序(第 1211 节)中,执行落入第 1217 节,然后是第 1218 节,即:

§1218 定义

假设您的输入有\def\foo{\bar}\edef\foo{\bar}。那么在上面,get_r_token; p ← cur_cs;将设置p为(基本上)\foo,然后scan_toks(true, e)被调用。

这里, 的两个参数中scan_toks,第一个参数(macro_def,这里传递为true)表示要扫描的标记列表是宏定义的标记列表(而不是\mark\output\message\everypar等的标记列表),第二个布尔值(xpand,这里传递为e)决定是否扩展。

那么让我们深入研究scan_toks(第 473 节)。

§473 <code>扫描_toks</code>

首先,当我们进入时macro_def,它会进入⟨Scan and build the parameter part of the macro definition⟩第 474 节。在我们的示例(无参数宏)中,所发生的一切就是get_token获取标记开始组字符{,因此这部分只是以end_match_token添加到标记列表而结束。定义实际主体的标记列表在第 477 节中进行扫描。

这个基本上是逐个获取标记并将它们添加到正在构建的标记列表中(用于定义主体),关键的区别在于,在\edef或的情况下\xdef,标记会随着我们的进行而扩展:

§ 477 扫描主体标记列表

  • 在 的情况下,\def\foo{\bar}不存在扩展的复杂性,并且这部分标记列表基本上只包含标记\bar;因此在scan_toks返回后, 的定义\foo被保存为包含两个代币end_match\bar

  • 在 的情况下\edef\foo{\bar},此部分(记住我们仍在 内)一次scan_toks从输入流(当前包含 )读取一个标记,每次扩展它看到的任何标记。具体来说,此处的扩展发生在第 478 节对过程(第 366 节)的调用中。此过程的工作方式是,每次调用它时,它(本质上)只是销毁输入流中的第一个标记,并将其替换为已移除标记的“扩展”(无论什么合适的)。这样,下一次对 的调用(比如说)将拾取替换的标记。\barexpandget_token

如您所见,这里的“expand” / “expansion” / “expandable”基本上表示任何具有替换的东西(即应该以某种方式改变),它不仅仅意味着宏,还包括以下\noexpand内容\expandafter

§ 366,扩展,第 1 部分 §366,扩展,第2部分 §366,扩展,第3部分

(不要试图直接从变量代码的名称推断可扩展的 TeX 原语;而是参见 egreg 的回答。例如,原语\number\romannumeral\string\meaning、都是可扩展的,因为它们都会导致上面代码列表中的命令代码\fontname。)\jobnameconvert

相关内容