在 TeX 中,有很多参数可以调整,以改变 TeX 的行为。
示例 1:
通过分配,\baselineskip=...
您可以更改行之间的距离。为什么当您重新定义控制序列以执行完全不同的操作
时,TeX 的段落拆分为行的机制不会中断?例如,为什么在重新定义为完全不表示粘合 时,TeX 不会打印错误消息,但仍会使用以下示例创建距离为 1.5cm 的行?\baselineskip
\baselineskip
\begingroup
\baselineskip=1.5cm\relax
\def\baselineskip{Hello, world!\hfil\break}
\noindent
\baselineskip
\baselineskip
\baselineskip
\par
\endgroup
\bye
示例 2:
通过分配,\escapechar=...
您可以更改控制序列名称前面的字符(例如,\string
应用于控制序列时)。为什么当您重新定义控制序列以执行完全不同的操作时
,TeX 的\string
原语不会受到干扰? 例如,为什么 TeX 不打印错误消息,但仍将其放在控制序列名称前面,而定义根本不提供字符的内部编码号?\escapechar
/
\escapechar
\escapechar=`\/
\def\escapechar{Hello, world!\hfil\break}
\noindent
{\tt\string\escapechar} expands to: \escapechar
\par
\bye
当以前用于访问这些参数的控制序列被重新定义时,TeX 如何还能访问这些参数的最后值?
这些控制序列是否只是用户访问某些内容的“入口”,而通过 TeX 访问某些内容则不需要这些“入口”?
\pdfprimitive
当所有以前用于访问事物的控制序列都被重新定义时,是否(除了)还有用于访问事物的“后门”?
答案1
是的,正如您推断的那样,作为原语提供的控制序列仅仅是用户的“入口”。
详细说明,
[内部参数] TeX 内部存储了所有这些参数的表格。例如,(假设)表格条目 2883 始终包含用作基线跳过的胶水,表格条目 5308 始终包含转义字符(作为整数),等等。在 TeX 操作期间,每当需要使用这些值时(例如,需要转义字符来向用户打印控制序列,如或
\string
错误消息),就会查找相应的表格条目。[控制序列] 另外,TeX 还存储了一个表(实际上是同一张表的不同部分),将控制序列映射到它们的等效项。 原始控制序列和用户定义的控制序列都被视为相同;唯一的区别是,原始控制序列由程序本身在启动(initex)期间初始化,因此它们的等效项可能恰好是用户无法设置的类型(例如“显示或修改基线跳过”)。 当您重新定义一个控制序列(比如
\baselineskip
或\escapechar
)“远离”原始序列时,唯一会发生的事情是您正在更改此映射; TeX 本身并不关心控制序列的含义,除非它遇到它作为输入。
除了pdfTeX 等添加的\pdfprimitive
或\primitive
之外,获取这些内部参数的另一种方法是使用 LuaTeX,它直接为大多数内部参数提供了 Lua 接口(如tex.baselineskip
、tex.escapechar
),这些接口不关心控制序列:
print()
function showVars()
width, stretch, shrink, stretch_order, shrink_order = node.getglue(tex.baselineskip)
mychar = tex.escapechar
print(string.format('baseline skip: %s plus %s minus %s etc', width, stretch, shrink))
print(string.format('escape char: %d = %c', mychar, mychar))
cs_baselineskip = token.get_macro('baselineskip') or 'not a macro'
cs_escapechar = token.get_macro('escapechar') or 'not a macro'
print(string.format([[\baselineskip: %s]], cs_baselineskip))
print(string.format([[\escapechar: %s]], cs_escapechar))
print()
end
\directlua{dofile('showvars.lua')}
\directlua{showVars()}
\baselineskip=1pt
\escapechar=`/
\directlua{showVars()}
\def\baselineskip{boo}
\def\escapechar{whatever}
\directlua{showVars()}
\bye
结果:
baseline skip: 786432 plus 0 minus 0 etc
escape char: 92 = \
\baselineskip: not a macro
\escapechar: not a macro
baseline skip: 65536 plus 0 minus 0 etc
escape char: 47 = /
\baselineskip: not a macro
\escapechar: not a macro
baseline skip: 65536 plus 0 minus 0 etc
escape char: 47 = /
\baselineskip: boo
\escapechar: whatever
(如果这不能回答你的问题,你仍然好奇为什么 TeX 不会崩溃,以及 TeX 内部是如何实现这一点的,那么底层细节如下:另一个答案。
答案2
(这是另一个答案。将此作为单独的答案发布,因为这会使另一个答案变得很长,而且这可能比任何人想知道的都更详细。但如果您的“为什么”和“如何”没有得到高级描述答案,并且您对实现的所有细节感兴趣……)
TeX 是一个程序。在内部,该程序维护一个称为“等价表”的表——一个数组类型的大型全局变量,称为eqtb
,定义在节253该计划:
program TEX; { ... }
var
{ ... }
eqtb: array [1 .. eqtb_size] of memory_word; {§253}
{ ... }
其中memory_word
基本上是4字节的内存块,其解释如下所示。
正如记录的那样第220条,此等价表存储:
- 控制序列的等效物(在区域 1 和 2 中),
- 各种胶水参数的值(区域 3),
- 其他各种整数参数的值(区域 4 和 5),以及
- 各种尺寸(长度)参数的值(区域 6)。
这些等价物中的每一个都存储为(§221)等级(用于保存和恢复组内数据),eq_type(命令代码,§207-210),以及实际当量,一个整数,其解释取决于类型(表索引、指向标记列表的指针……)。
每当程序需要访问其内部参数之一的值时,它都会使用表中的相应条目。例如,假设基线跳过存储在表条目 2883 中。当附加到垂直列表时,程序可能需要基线跳过(第679条),在这种情况下它使用(本质上)eqtb[2883].equiv
。
此外,该程序还提供了一些“原语”,例如\baselineskip
或\escapechar
用于设置其中一些表条目:它们只是将控制序列名称映射到设置该表条目的命令。更准确地说,在该名称的等效项中,设置eq_type
为命令代码,例如assign_int
或assign_glue
,同时equiv
保持哪个表条目将被分配。例如,如果您不重新定义原语,则控制序列(存储在区域 2 中)的等效项将baselineskip
具有,以及2883 。eq_type
assign_glue
equiv
当程序在输入中遇到相应的控制序列(作为其自身的命令)时,它会遵循等效的并执行相应的分配。
下面是 TeX 程序的微型版本,仅包含相关部分,并且为了简单起见进行了一些改动:
program TEX; { §4 ... }
var {§13}
{ ... }
cur_cmd: int; {§297}
cur_chr: int;
eqtb: array [1 .. eqtb_size] of memory_word; {§253}
{ ... }
procedure primitive(s: string, eq_type: int, equiv: int); {§264}
begin
{ ... set eqtb[hash(s)] to (level 1 and) the eq_type and equiv passed here ...}
end
procedure init_prim; {§1336}
begin {...}
primitive("baselineskip", assign_glue, 2883); {§226}
{...}
primitive("escapechar", assign_int, 5308); {§238}
{...}
end
procedure prefixed_command; {§1211}
begin {...}
case cur_cmd of
set_font: { ... §1217 ...}
def: {... §1218 ...}
{ ... }
assign_int: {§1228}
begin
p ← cur_chr; {e.g., 5308 in case of the primitive \escapechar}
scan_optional_equals;
scan_int;
word_define(p, cur_val); {...sets eqtb[p], accounting for level...}
end;
assign_glue: {...similar...}
{...}
endcases;
end;
procedure main_control; {§1030}
begin
while true do
begin
{... read a token, i.e. set cur_cmd and cur_chr ...}
case cur_cmd of
{...}
assign_int: prefixed_command; {§1210}
assign_glue: prefixed_command;
{...}
endcases;
end;
end;
{...}
begin {§1332}
{...}
init_prim;
{...}
main_control;
{... cleanup ... }
end.
答案3
重新定义\baselineskip
不会改变存储在内部跳过寄存器中的值。 同样\escapechar
,指向内部数字寄存器的 。
你只是放弃了改变这些价值观。
\baselineskip
当 TeX 将段落分割成行时,它需要知道指向的寄存器中存储的当前值,但它没有执行\the\baselineskip
,因为分段发生在比标记所在的层次更深的层次上(通常称为 TeX 的胃)。
内部指令是“查看内存位置中存储的内容基线跳跃并用它来进行所需的计算”(这只是一个虚构的名字)。当你这样做时
\baselineskip=12pt
TeX 胃中执行的指令是“存储在内存位置基线跳跃数字 786432(以比例点表示的给定值)。
使用 Knuth 的 TeX(没有第三方扩展),一旦你重新定义了一个基本元素,你就只能靠自己了,而且不恢复它的方法。当然,在组中局部重新定义基元将在组末尾恢复先前的含义。对于 pdfTeX/epTeX,可以使用 \pdfprimitive
;对于 LuaTeX/XeTeX,它被称为\primitive
。
如果在所有引擎中,你在重新定义之前用另一个名称“保存”原始对象,那么
\let\primitivebaselineskip\baselineskip
\def\baselineskip{whatever}
...
\let\baselineskip\primitivebaselineskip
原来的意义将被恢复。控制序列指向明确定义的内存位置,而\let
指令使“新”控制序列指向与“旧”控制序列相同的位置。