- TeX 术语中,“参数”和“实参”有什么区别?
- 最外层包含匹配的无界参数花括号对是否应被视为这些参数的组成部分?
- 分隔参数的分隔符是否应被视为这些参数的组成部分?
答案1
有了这个答案
- 词组 ”⟨替换文本⟩“(以角度表示)表示⟨替换文本⟩宏的⟨定义⟩在定义该宏时。例如⟨替换文本⟩可能包含参数(
#1
或#2
或 ...)。 - 短语“替换文本”(不带角度)是指在扩展宏实例时获得的(可能为空的)标记集合,该集合由该宏的⟨替换文本⟩但是,其中的参数(
#1
,#2
等)被从标记流中收集的参数所取代。
回答:1. TeX 术语中“参数”和“实参”有什么区别?
在 TeXbook 中,“参数”一词出现在以下几种情况下:
术语“参数”(与术语“自变量”)出现在宏编程的上下文中。这值得特别注意。因此,在对 1 的回答的最后一节中单独讨论它。
字符标记可以具有的类别之一称为“参数”。该类别由类别代码 6 表示。通常,井号 (
#
) 属于类别 6(参数)。字体/字体指标的数量被称为“字体参数”。
有五种所谓的“内部参数”,它们的值可以通过执行分配来改变,并且这些值会影响/调整硬编码到 TeX 程序中的算法的工作方式:整数参数、维度参数、粘合参数、muglue 参数和标记参数。
例如,控制字标记
\parskip
通常表示影响文本段落之间垂直空间量的粘合参数。例如,控制字标记
\parindent
通常表示一个维度参数,该参数影响段落文本第一行左侧的水平缩进空间量。例如,控制字标记
\escapechar
通常表示一个整数参数,用于指定 TeX 用于表示控制序列标记的字符- 当
\scantokens
或\detokenize
导致将未展开的标记虚拟地写入虚拟文本文件时(以便随后虚拟地读回虚拟文本文件并由此(重新)标记和处理虚拟文本文件的内容;使用-catcode-régime\detokenize
进行(重新)标记\string
,即,所有内容都被标记为显式字符标记,除空格外的每个字符都有类别代码 12(其他),空格(字符代码 32)都有类别代码 10(空格)),或 - 当实际将令牌未扩展地写入屏幕 / 控制台 / 终端或文本文件时,例如通过、、,
\meaning
或者以其他方式创建 .log 文件的条目或在屏幕 / 控制台 / 终端上显示的消息时,或者\show
\message
\write
- 当通过
\string
获取控制序列标记的字符串化时。
关于内部参数的补充说明: 对于内部参数,表示它们的控制序列可看作是用于在进行某些编程工作时/在让 TeX 程序执行 TeX 语言中给出的指令时更改它们的值并检索有关其当前值的信息的“访问点”:
如果您说\let\tnednirap=\parindent
,那么您就有两个对同一参数的访问点,我们通俗地将该参数称为段落缩进参数:\show\parindent
揭示> \parindent=\parindent.
并\show\tnednirap
揭示> \tnednirap=\parindent.
这意味着在两种情况下, 左侧表示的控制序列标记=
都充当原语,通常在 TeX 程序的初始状态下,通过控制字标记可以获得没有重新定义任何内容等的原语\parindent
。
如果您这样做\parindent=20pt
,则\showthe\parindent
和\showthe\tnednirap
都会显示:> 20.0pt
。
如果您这样做\tnednirap=25pt
,则\showthe\parindent
和\showthe\tnednirap
都会显示:> 25.0pt
。
如果您另外执行了\def\parindent{now parindent is a macro}
,那么您将无法再执行\parindent = ...
或\showthe\parindent
或\the\parindent
成功操作来调整段落缩进参数的值,或为任何 TeX 编程目的检索有关段落缩进参数的当前值的信息。
因为现在控制序列标记的目的\parindent
已经改变:它现在是一个宏。
但您仍可以执行\tnednirap = ...
来更改段落缩进参数的值。您仍可以执行\showthe\tnednirap
或\the\tnednirap
来检索有关段落缩进参数值的信息。
如果您另外做了\def\tnednirap{now tnednirap is a macro, too}
,那么您也不能再做\tnednirap = ...
等等。
然后,您就无法再通过 TeX 语言形式的输入来访问段落缩进参数。您无法再更改该参数的值。您无法再通过/\the
检索有关其当前值的信息。\showthe
但段落缩进参数仍然存在:
TeX 不断缩进段落的第一行。
根据分配给段落缩进参数的最后一个值,取消所有用于修改该值的访问点。
(当然,依赖于控制字标记之一\parindent
/\tnednirap
用于访问段落缩进参数以更改和/或检索其值的代码现在将失败。)\year
、\month
和\day
表示\time
TeX 中的普通内部参数。在 TeX 运行开始时,它们的值会根据计算机平台的时钟进行初始化,仅此而已。初始化后,TeX 本身不会再更改它们的值,尽管时钟仍在继续滴答作响。通过 .tex-input 中的赋值\year=...
/\month=...
/\day=...
/,\time=...
您可以在处理 .tex-input 的 TeX 运行期间随时更改它们的值。- 当
分配并用于影响以纯 TeX 格式实现的例程(例如纯 TeX 格式的输出例程)行为的寄存器称为“伪参数”。可通过伪参数调整的行为不是硬编码到 TeX 程序本身中,而是使用 TeX 语言以格式实现。
关于“争论”一词:
在 TeXbook 的“第 24 章:垂直模式摘要”中,你会发现这样的陈述:
有些命令带有参数。换句话说,命令后面的一个或多个标记可能用于修改该命令的行为,而这些标记本身不被视为命令。例如,当 TeX 处理对应于 '
\dimen2=2.5pt
' 的标记序列时,它只将第一个标记 '\dimen
' 视为命令;接下来的标记将作为操作的一部分被清除,因为 TeX 需要知道要将哪个 \dimen 寄存器设置为等于 ⟨尺寸⟩ 价值。
在宏编程中,“参数”和“参数”这两个术语值得特别注意:
在 TeXbook 的“第 20 章:定义(也称为宏)”中,你可以找到以下语句:
好吧,TeX 有个好消息要告诉你:控制序列可以根据参数来定义,并且你可以提供将要替换参数的参数。
看起来,
- 参数(
#1
或#2
或...)出现在⟨参数文本⟩ 也可能在 ⟨替换文本⟩ 的 ⟨定义⟩ 宏。 - 在 TeX 术语中,参数是在 ⟨ 中展开宏实例时收集的标记集合(可能为空)。替换文本⟩ 宏的 ⟨定义⟩ 替换相应参数的每个实例(
#1
或#2
或...中的一个特定实例)。
因此从某种意义上来说你可以说
- 在基于扩展的宏编程中,参数是单步例程的“变量”(在定义单步例程时,单步例程用 ⟨控制序列⟩ 在定义过程中被转化为宏标记),
- 而此上下文中的参数可以被视为在扩展阶段执行由所讨论的宏标记表示的单步例程时变量的值。
但是,当“变量”一词与 TeX 有关而与 expl3 无关时,在使用时也许采取一定的克制和谨慎态度,可能有助于避免产生歧义:
- “变量”一词在 expl3 术语中正式使用,而 expl3 术语可以被视为 TeX 术语的严格超类 / 而 expl3 术语包含 TeX 术语。但在 expl3 术语中,“变量”一词不一定指“定义阶段的宏参数”概念,而“变量值”一词不一定指“扩展阶段的宏参数”概念。在 expl3 术语和 TeX 术语中使用术语“变量”和“变量值”,但在涉及宏扩展时不一定在 expl3 术语中使用术语“变量”和“变量值”,可能会导致对这些术语的理解产生歧义。
- 在 expl3 术语之外使用术语“变量”可能会误导人们混淆面向对象/面向过程/面向功能的计算机编程语言(如 Pascal 或 C)的编程范式与基于宏的排版语言 TeX 的编程范式,在扩展阶段,首先要做的就是用(其他/可能是空的)标记集合替换标记集合。
- 在 TeX 中,“变量”/“变量的值”可能不仅仅适用于宏编程,因为宏编程的执行重点在于扩展阶段。
在 TeX 中,“变量”/“变量的值”可能同样适用于执行赋值的处理阶段——例如通过\def
、\edef
、\xdef
、\xdef
等定义宏\chardef
,通过 执行赋值\let
,为寄存器赋值等,其中即将改变含义的标记将是“变量”,当前含义或例如无参数宏,⟨替换文本⟩ 与宏标记一起构成当前含义的一部分,将形成“该变量的值”,或者要改变内容的寄存器将成为“变量”,而该寄存器的当前内容将形成“该变量的值”。
回答:2. 最外层包含匹配的无界参数花括号对是否应被视为这些参数的组成部分?
回答
:3. 有界参数的分隔符是否应被视为这些参数的组成部分?
#{
暂时将 -notation的边缘情况(见下文)放在一边,在扩展宏实例时形成该实例的分隔参数的分隔符组件的标记在收集分隔参数时将被丢弃。在扩展包含该实例的未限定参数的宏实例时,类别 1/2 的最外层匹配的显式字符标记对在收集参数时被剥离/丢弃。
(您没有问到这个问题,但我们还是提一下:
在扩展包含整个分隔参数的宏实例时,类别 1/2 的最外层匹配的显式字符标记对,使得类别 2 的最外层封闭显式字符标记位于参数分隔符的第一个标记的左侧 ;-),也被剥离/丢弃。
当 TeX 搜索未限定参数的第一个标记时,显式空格标记(即字符代码 32 和类别 10(空格)的显式字符标记)将被丢弃。)
无论如何,这样的代币都不能成为 ⟨ 的替代品替换文本⟩ 的参数,而上面提到,在 TeX 术语中,参数似乎是一组(可能为空的)标记集合,在展开宏实例时,它会替代出现在 ⟨中的对应参数(#1
或或......)的每个实例#2
替换文本⟩ 宏的 ⟨定义⟩.
因此,严格来说,似乎这样的标记不应被视为参数的组成部分。
但是在扩展宏的一个实例时,最外层参数包围的显式花括号字符标记和属于参数分隔符的标记是确定此实例的参数的方法=是确定那些确实成为相应参数的替代品的标记集合的方法⟨替换文本⟩ 该宏的 ⟨定义⟩.
一些边缘情况值得特别注意:
应用
\def\foo#1\relax{\relax}
显示\show\foo
:> \foo=macro: #1\relax ->\relax .
正如所见在编辑/扩展此答案之前收到的评论,可能会出现这样的问题:在扩展的实例时,到底发生了什么
\foo
:- 是否会出现这样的情况:定界符
\relax
留在标记流中的原处,并且该实例的替换文本\foo
插入到其正前方,\relax
从而插入不会带来标记\relax
? \relax
在收集分隔参数的过程中,分隔符是否从标记流中删除,以及\relax
来自\foo
⟨ 的标记定义⟩ 的 ⟨替换文本⟩ 与该实例的其余替换文本一起插入\foo
?
后者是这样的:
你有一个宏参数,根据 ⟨参数文本⟩ 的
\foo
⟨定义⟩ 由标记 分隔\relax
。让我们意识到这一点⟨定义⟩ 这个 token
\relax
肯定也出现在 ⟨定义⟩ 的 ⟨替换文本⟩.\relax
因此,在扩展实例之后出现的标记\foo
不是由于\relax
在收集分隔参数的过程中参数分隔标记留在原处,而是由于 ⟨定义⟩ 的 ⟨替换文本⟩ 在扩展该实例时导致插入包含标记的替换文本\relax
。\relax
在任何情况下,扩展实例后出现的标记\foo
与替换的参数无关\foo
⟨定义⟩ 的 ⟨替换文本⟩.
因此,从标记\relax
的存在来看,不能断定那\relax
是宏参数的组成部分。- 是否会出现这样的情况:定界符
-notation的边界情况
#{
:当你这样做时,例如,
\def\foo#1#{}
然后\show\foo
产生:> \foo=macro: #1{->{.
这里还出现了一个问题,在扩展一个实例期间发生了什么
\foo
:- 是否会出现这样的情况:定界符
{
留在标记流中的原处,并且该实例的替换文本\foo
插入到其正前方,{
从而插入不会带来标记{
? {
在收集分隔参数的过程中,分隔符是否从标记流中删除,以及{
来自\foo
⟨ 的标记定义⟩ 的 ⟨替换文本⟩ 与该实例的其余替换文本一起插入\foo
?
与前一种情况的细微差别在于,在这种情况下,(不平衡的)标记
{
不在⟨替换文本⟩ 的\foo
⟨定义⟩.在 TeXbook 的“第 20 章:定义(也称为宏)”中,在其中一个双重危险弯曲段落中你会发现:
允许对这些规则进行特殊扩展:如果 ⟨参数文本⟩ 是
#
,因此#
紧接着{
,TeX 的行为就像{
已插入到参数文本和替换文本的右端一样。首先乌尔里希·迪茨,截至 2023 年 9 月 18 日,此答案版本的作者将“好像...已被插入”这句话弄错了:
对“好像...已经插入”这句话的错误理解是,它表示插入实际上并没有发生,反过来表示它并
{
没有插入,而是因为留在原处而存在。根据这种错误的理解,由于 token 一直留在原处,因此在宏扩展之后它仍然留在原处与它作为 ⟨ 的替代品的组件传递无关。定义⟩ 的 ⟨替换文本⟩ 的参数。
因此,这样的标记不能被视为参数的组成部分。尊敬的用户斯基尔蒙在一条评论在 Knuth 的 TeX 程序的注释源代码中,友好地给出了指向“结束匹配”的指针,以及用于收集参数标记的例程,直到由 ⟨ 表示的标准定义⟩ 对应参数都满足,并解释了“好像
{
在参数文本和替换文本的右端都插入了 ”这句话的正确理解为“好像在定义参数文本的右端都插入了 ,并且在{
定义替换文本的右端都{
插入了 ”。因此,有必要研究 Knuth 的 TeX 程序(用 WEB 编写)的注释源代码。注释源代码可以作为一本书获得,书名为“TeX 程序”。CTAN 上有一个略微精简的版本,可作为 .pdf 文件使用:https://mirrors.ctan.org/info/knuth-pdf/tex/tex.pdf。
在文本.pdf,你会发现§291:
- [...]
下面是一个说明这些约定的示例宏定义。在 TeX 处理文本
\def\mac a#1#2 \b {#1\−a ##1#2 #2}之后
, \mac 的定义表示为一个标记列表,其中包含
(引用计数)、字母 a、匹配 #、匹配 #、spacer、\b、结束匹配、
输出参数 1、\−、字母 a、spacer、mac 参数 #、其他字符 1、
输出参数 2、spacer、输出参数 2。
[...]
如果定义的参数匹配部分以 '#{' 结尾,则相应的标记列表在 '结束匹配' 之前和最后都会有 '{'。第一个 '{' 用于分隔参数;第二个 '{' 防止第一个消失。
您需要对代码进行更多的深入研究,但这是了解 -notation 的起点
#{
。因此,看起来,后者是这样的:在扩展时,你会在内部获得一个标记列表,在该列表中,你可以找到抓取参数标记的指令,直到找到 -delimiter
{
,并且将 a 作为最后一项{
放置,就好像 a{
是作为 ⟨ 的最后一项提供的一样定义⟩ 的 ⟨替换文本⟩.扩展后仍然
{
存在于该位置与作为替代的组件交付无关⟨定义⟩ 的 ⟨替换文本⟩ 的参数。
因此,这样的标记不能被视为参数的组成部分。- 是否会出现这样的情况:定界符