什么是“# 符号”?# 语法如何工作?

什么是“# 符号”?# 语法如何工作?

密切相关:如何实现 \expandbefore,类似于 \expandafter?

在相关问题中,引用了"# Notation"。语法如何工作?

来自另一个问题的回答:

\makeatletter
%
\newcommand\name{}%
\long\def\name#1#{\UD@innername{#1}}%
%
\newcommand\UD@innername[2]{%
  \expandafter\UD@exchange\expandafter{\csname#2\endcsname}{#1}%
}%
%
\newcommand\UD@exchange[2]{#2#1}%
%
\makeatother

参数语法(没有数字)实际上是如何工作的? \def\test#1#{\csname test#1\endcsname}? 使用此模式的良好用例有哪些?

答案1

要点\name是:

\name⟨stuff without braces before the left-brace⟩{⟨stuff within braces⟩}

TeX 应作为第一个参数 fetch⟨stuff without braces before the left-brace⟩并将其作为第二个参数 process ⟨stuff within braces⟩

⟨stuff within braces⟩视为控制序列标记的名称,可通过包装⟨stuff within braces⟩\csname..\endcsname执行结果\csname..\endcsname表达式来获得。

相反,控制序列标记将位于后面。{⟨stuff within braces⟩}⟨stuff without braces before the left-brace⟩


当执行带有分隔参数的宏时,(La)TeX 从标记流中收集形成参数的标记,并在此在标记流中搜索参数分隔符,因为参数分隔符被视为结束收集相关参数的标记过程的标记。

要搜索的分隔符应在⟨参数文本⟩的定义。

你可以表示 (La)TeX 将搜索最后一个参数的分隔符,例如,\relax通过将 a 写\relax⟨参数文本⟩

你可以表示为最后一个参数的分隔符,(La)TeX 将搜索左括号,方法是#将其写为⟨参数文本⟩

(几乎 ;-) )无论如何⟨参数文本⟩宏定义之后是⟨替换文本⟩,嵌套在括号中。
因此,在(几乎 ;-) )任何情况下⟨参数文本⟩宏定义的括号后面是⟨替换文本⟩
因此,当⟨参数文本⟩#,它后面会跟着{
这就是为什么这个表示 (La)TeX 在收集宏的最后一个参数时会搜索左括号作为分隔符的东西也称为#{-notation。

搜索\relax和搜索开括号的细微差别是:

当 (La)TeX 作为宏参数的分隔符时,例如搜索\relax并找到它,它不仅会停止收集参数的标记,而且还会删除分隔符/-token \relax

当 (La)TeX 作为宏的最后一个参数的分隔符搜索左括号并找到它时,它会停止收集最后一个参数的标记,并将该分隔符/括号留在原处,并将宏的⟨替换文本⟩在它之前。


摘自 Donald Ervin Knuth 教授的 TeXbook,第 20 章:定义(也称为宏)— Donald Ervin Knuth 教授,斯坦福大学计算机编程艺术名誉教授,是 TeX 的发明者:

现在我们已经看到了很多例子,让我们来看看控制 TeX 宏的精确规则。定义具有一般形式

\def⟨控制序列⟩⟨参数文本⟩{⟨替换文本⟩}

其中⟨参数文本⟩不包含括号,并且所有出现的{}在里面⟨替换文本⟩正确嵌套。此外,# 符号具有特殊含义: ⟨参数文本⟩,第一次出现的 # 后面必须跟 1,下一次出现时必须跟 2,依此类推;最多允许 9 个 #。在⟨替换文本⟩ 每个 # 后面必须跟一个数字,该数字出现在⟨参数文本⟩,否则 # 后面应该跟另一个 #。后一种情况代表宏展开时出现单个 # 标记;前一种情况代表插入相应的参数。

[...]

允许对这些规则进行特殊扩展:如果⟨参数文本⟩是#,因此这个#后面紧接着 {,TeX 的行为将如同{已插入参数文本和替换文本的右端。例如,如果您说
\def\a#1#{\hbox 到 #1}'
,则后续文本 '\a3pt{x}' 将扩展为 '\hbox to 3pt{x}',因为 \a 的参数由左括号分隔。

换句话说:

如果你说,后续的文本将被处理如下:\def\a#1#{\hbox to #1}\a3pt{x}

作为宏的参数,\aTeX 将从后续文本中收集一个由左括号分隔的参数:它将收集短语3pt
左括号(及其后面的所有内容)将保留在原处,同时\a⟨替换文本⟩产量:\hbox to 3pt这样整个事情将是:\hbox to 3pt{x}


简而言之:#{-notation(注意左括号!)意味着所讨论的宏的最后一个参数由左括号(类别代码 1(开始组))分隔,当(La)TeX 从标记流中收集该宏的最后一个参数时,该括号将留在原处。

值得注意的是,在这种特殊情况下,分隔符(即左括号)将保留在原处,因为这是在收集参数的过程中不会从标记流中删除参数分隔符的唯一情况。

即,如果参数分隔符是 -token,\relax例如

\def\mymacro#1\relax{The Argument#1 was delimited by relax. }

\relax从标记流中收集参数时,作为分隔符的 -token 将被删除:

\mymacro , which is nonsense,\relax...

作为\mymacro(La)TeX 的参数,它将从 token-stream 中收集序列, which is nonsense,,然后它将找到 token\relax并将该\relaxtoken 作为参数的分隔符,因此 (La)TeX 将停止为参数收集 token,并将删除分隔符。(然后它将开始收集\mymacro根据⟨参数文本⟩其定义中没有任何。但没有。)然后它将传递⟨替换文本⟩

The Argument, which is nonsense, was delimited by relax. 

⟨替换文本⟩在标记流中后面会跟着三个点,因此现在标记流将包含:

The Argument, which is nonsense, was delimited by relax. ...

但如果定义是:

\def\mymacro#1#{The Argument#1 was delimited by a left-brace. }

你说

\mymacro , which is nonsense,{...

, 你会得到

The Argument, which is nonsense, was delimited by a left-brace. {...

因为与前一种情况不同,前一种情况中\relax分隔参数的 会被删除,而在这种情况下,分隔参数的左括号不会被删除。


词组

如果⟨参数文本⟩是#,因此这个#后面紧接着 {,TeX 的行为将如同{已插入到参数文本和替换文本的右端。

方法:

定义通常是模式

\def⟨控制序列⟩⟨参数文本⟩{⟨替换文本⟩}

假设,您希望定义一个宏\macro,该宏处理两个参数,其中第一个参数未限定,第二个参数由序列限定,\foo\bar并且只是“吐出”参数。

在这种情况下,您可以创建一个如下表达式:

\def\macro#1#2\foo\bar{argument 1: #1 argument 2: #2}

⟨控制序列⟩=\macro

⟨参数文本⟩=#1#2\foo\bar

⟨替换文本⟩=argument 1: #1 argument 2: #2

当你观察这个表情时,你会发现在右端⟨参数文本⟩,其中将包含构成最后一个参数的分隔符的标记,即 token \foo\bar。它们属于构成⟨参数文本⟩。在它们后面,你可以看到围绕着⟨替换文本⟩
因此,在这种情况下,在⟨参数文本⟩找到界定最后一个参数的标记,并找到包围⟨替换文本⟩

序列

\macro{A}B\foo\bar

产量:

argument 1: A argument 2: B

如您所见,\foo\bar在收集属于参数的标记时,分隔标记已被删除。

如果你定义:

\def\macro#1#2\foo\bar{argument 1: #1 argument 2: #2\foo\bar}

⟨控制序列⟩=\macro

⟨参数文本⟩=#1#2\foo\bar

⟨替换文本⟩=argument 1: #1 argument 2: #2\foo\bar

即,如果你\foo\bar⟨替换文本⟩, 序列

\macro{A}B\foo\bar

产量

argument 1: A argument 2: B\foo\bar

就像之前的定义中分隔符被保留了一样

如果您希望定义这样的东西但分隔符不是序列\foo\bar而是左括号,该怎么办?

定义仍应是模式

\def\macro#1#2⟨delimiter⟩{argument 1: #1 argument 2: #2⟨delimiter⟩}

{但出于多种原因你不能⟨分隔符⟩和写

\def\macro#1#2{{argument 1: #1 argument 2: #2{}

并特此#1#2{认为⟨参数文本⟩以及argument 1: #1 argument 2: #2{⟨替换文本⟩

  1. 在许多情况下,由于括号不平衡,无法轻松插入单个左括号而不会出现错误。

  2. 这种情况会很模糊,因为 (La)TeX 必须猜测在⟨参数文本⟩是一个参数分隔符,或者它是否属于围绕⟨替换文本⟩
    (La)TeX 还必须猜测右括号是否应该与末尾的左括号匹配⟨替换文本⟩或者它是否应该标志着⟨替换文本⟩

因此,作为一种语法解决方法,#{发明了 -notation:

\def\macro#1#2#{argument 1: #1 argument 2: #2}

⟨控制序列⟩= \macro

⟨参数文本⟩=#1#2#

⟨替换文本⟩=argument 1: #1 argument 2: #2

, 这⟨参数文本⟩没有以{界定最后一个参数的标记结尾,但是以 结尾#

#用于表示 (La)TeX 在扩展过程中从标记流中\macro收集最后一个参数的标记时,应搜索左括号作为参数分隔符。这也表示找到的作为分隔符的左括号应保留在原处,以便\macro#⟨替换文本⟩\macro在它之前。

换句话说:

#导致 (La)TeX 的行为就像在⟨参数文本⟩

它还会导致 (La)TeX 的行为就像定义中给出的最后一个标记一样⟨替换文本⟩是左括号。


对于您的评论:

嗯。我不认为你能编造出什么?猜测一下到底发生了什么?让我重新表述一下,在你的另一个例子中,如果你在所有这些内容中甚至有 cat code 1 字符。推断这种“原始行为”发生在读取文件的输入阶段是否正确?(抱歉,我正在编造术语……我说的是process_input_buffer事件处理程序。

LaTeX 处理的第一个阶段是读取输入(来自文件或控制台),并将其作为一组指令来输入代币进入标记流。(字符标记/控制序列标记)。在后续阶段,标记流中的标记得到处理。在这些阶段的较早阶段中,可扩展标记(例如宏)得到扩展。即,它们被构成其定义的替换文本的标记替换。在此阶段,会收集宏参数的标记。因此,该行为不会在读取文件时发生,而是在读取和标记化之后发生,即在扩展可扩展标记并由此收集其他标记的阶段代币作为他们的论点。


我在回答这个问题时试图详细阐述 (La)TeX 如何收集和处理宏参数TeX 如何查找分隔参数?

\expandafter我在回答这个问题时试图详细阐述如何避免这种情况如何知道附加到 csname 宏时的 expandafter 数量?

\name我试图在回答问题时详细阐述宏定义控制序列,之后留有空格

相关内容