当输入处理器遇到连续两个类别代码为 5 的字符(换句话说,一个空白行)时,它会插入宏\par
。
当\vbox{Abc.}
结束时,TeX 会结束当前段落,但不会插入宏\par
。TeX 似乎插入的是\par
原始内容。我理解 TeX 在这种情况下在做什么是否正确?TeX 插入的其他地方呢\par
?什么时候是宏\par
,什么时候是原始内容?
\catcode`@=11
\let\@@par\par
\def\par{\typeout{Macro!}\@@par}
Abc.
\vbox{Abc.\tracingall}
答案1
TeX 程序中只有 7 个地方 TeX 内部执行段落构建器,即将水平列表(如果正在构建)转换为段落。这种情况发生不是通过\par
在输入流中插入一个标记,而是通过执行过程结束在 TeX 程序的模块§1096 中实现。
如果 TeX 不是水平模式,则此过程不执行任何操作;如果 TeX 处于水平模式但列表为空(TeX 会忽略空段落),则此过程基本不执行任何操作,然后执行该过程越线否则(并且那个人做了所有关于添加 parfillskip 惩罚等的神奇事情)。
这七个地方是
- 在内部垂直结构的末尾,如右括号“
\vbox
但”\noalign
或\vcenter
“对齐单元” - 当原始par_end被感知(最初在宏观层面上可用作 token 的含义
\par
) - 输出例程(OR)结束后立即开始(因此,任何在 OR 中开始的水平列表都不会继续使用样稿中的材料,而是形成自己的段落)
在所有这些情况下,都没有\par
插入标记(可能会重新定义);相反,结束程序已执行!
\par
只有在水平模式下,遇到与水平模式不兼容的基元(如\vskip
、\hrule
、... 中的完整列表在 TeX 代码中的 §1094)时,才会插入标记。当然,在标记化过程中,TeX 会用 替换两个行尾字符\par
(即,使空行等同于\par
)。
答案2
根据TeXbook,行尾规则如下
如果 TeX 遇到行尾字符(类别 5),它会丢弃当前行中可能保留的任何其他信息。然后如果 TeX 处于状态否(换行符),行尾字符将转换为控制序列标记
\par
(段落结束);如果 TeX 处于状态米(中间行),行尾字符被转换为类别 10(空格)的字符 32( )的标记;并且如果 TeX 处于状态年代(跳过空格),行尾字符被删除。
这里,需要注意的是 TeX 插入的是 token \par
,而不是\par
原语。这意味着\par
在任何情况下都必须定义。Knuth 在讨论强制水平模式的命令时说明了这一点:
出现<垂直命令> 在受限水平模式下是被禁止的,但在常规水平模式下它会导致 TeX 将标记插入
\par
到输入中;在读取并扩展此\par
标记后,TeX 将看到 <垂直命令\par
> 再次标记。(将使用 控制序列的当前含义;\par
可能不再代表 TeX 的\par
原语。)
现在,似乎没有任何地方提到内部垂直模式结束时会发生什么。显然,上述规则不允许插入标记,\par
因为没有行尾标记需要处理。(如果在最后一个材料和框末尾\par
之间有一个空白行,则确实会插入一个。)\vbox
读取跟踪输出,没有提到\par
框末尾的原语,但 shipout 确实显示
....\penalty 10000
....\glue(\parfillskip) 0.0 plus 1.0fil
当然,显然已经构建了一个段落。所以我的结论是,内部垂直模式的结尾隐式插入了一个\par
原语,从而插入了通常的段落结尾材料,然后运行段落构建器。
答案3
\par
TeX 将 的“原始含义”放在 的末尾是有原因的\vbox
:考虑以下愚蠢的输入
\vbox{\let\par\empty a}
当 TeX 找到 时}
,它会将其备份,通过插入“原始\par
”来终止水平模式,然后重新读取}
,关闭内部垂直模式。
我们可以模拟如果 TeX在\par
重新读取之前插入当前的含义会发生什么}
\vbox{\let\par\empty a\vskip0pt}
因为\vskip
插入了一个 (当前)\par
并且 TeX 重新读取了\vskip
。结果就是无限循环!