覆盖用于访问 TeX 的整数/维度/胶水/..参数的控制序列

覆盖用于访问 TeX 的整数/维度/胶水/..参数的控制序列

在 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.baselineskiptex.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_intassign_glue,同时equiv保持哪个表条目将被分配。例如,如果您不重新定义原语,则控制序列(存储在区域 2 中)的等效项将baselineskip具有,以及2883 。eq_typeassign_glueequiv

当程序在输入中遇到相应的控制序列(作为其自身的命令)时,它会遵循等效的并执行相应的分配。

下面是 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指令使“新”控制序列指向与“旧”控制序列相同的位置。

相关内容