编组
编组可以理解成计算机编程中的模块结构。编组发生作用是在编程语言层次上的,也就是每经过一个编组,编译器就回复到编组之前的正常规则。
ex5.1 得到ff不连在一起的shelfful.
方法是输入shelf{ful}.也就是在f中间加上一个编组。
ex5.2 不用转义的空格,怎样在一排中得到三个空格。
使用{ }{ }{ }吧。或者{ } { },注意编组后面的空格不会被自动忽略。
为了使控制序列作用在特定的文本上,我们也使用\centerline{}这样的结构。
虽然编组的功能很强大,但是也容易使文档变得难读。幸好在普通文稿当中不必使用太多的编组。
括号的作用有多个,一个是把多个单词看成一个结构,另一种是提供了一个局部模块结构。不应当认为大括号仅有一个作用。
ex5.3 键入下列内容会得到什么结果?
\centerline{This information should be {centered}.}
\centerline So should this.它的结果是,第一条命令产生一个居中的文本块,但是第二行将只有一个单词S。第三行变成了一o开头。
注意第二行不是一个单词,因为TeX控制系列默认将后面的一个字母而不是单词视为一个单独的参数。
ex5.5 定义一个控制系列\ital,使得用户键入\ital{text}来代替{\it text\/}.与\it相比它有什么优缺点。
显然使用\def\ital#1{ {\it #1\/} }它的不足只是放在后面,并且总是有一个斜体校正,它的所有的特点都跟它的封装形式有关。
TeX的宏定义与m4差不多,过多的括号里面,只有最外层的被去掉,内层的括号得到了保留。
TeX的参数的处理
与C语言不同,在编组当中改变即使是全局变量,当走出编组的时候全局变量也会恢复成原来的样子(这有一点像是有硬堆栈变量的计算机)。使用\global命令可以做到这一点。比如将计数器加一的操作,显然通过普通方法是不行的,在块里面需要使用
\global\advance\count0 by 1其中的advance表示对指定的计数器加上特定的值。\count0是TeX内部的一个计数器。
分组
另一个分组的命令是\begingroup{GRPNAME}...\endgroup{GRPNAME}.
这个可以看成是LaTeX环境的基础。如果使组正常工作,那么我们就应当避免Sa,Sb,Ea,Eb这样的情况,方法其实很简单,在Sa的时候定义一个\blockname{}名子与本环境的名子相同,Sb也同样这种做法。这样在Ea的时候,Sb定义的blockname就会消失,在读到Eb的时候检查一下b有没有被定义就可以了。
\def\beginthe#1{\begingroup\def\blockname{ #1}}
\def\endthe#1{\def\test{ #1}%
\ifx\test\blockname\endgroup
\else\errmessage{You should have said
\string\endthe{\blockname}}\fi}(具名编组的概念,有些像是Pascal语言的end for语句之类的扩展)。
从外壳中键入tex命令,出现双星号提示符,要求输入文件名,这个时候我们可以代之以输入\relax命令,进入到一个星号的状态。之后就进入排版了。
在输入完成内容后,键入\end可以结束会话,TeX会把文件输出到当前目录里面。
在第一篇TeX当中,可能\hskip, \vskip 以及后面的\hrule 我们比较常用吧。
直接的波浪线表示告诉TeX前后两个文本是连在一起的,不要在此处断行。\vfill表示用空白将本页剩下的部分用空白填满,然后用\eject把它送到输出文件。
在出现**提示符的时候,我们可以以&开头表示以特定的宏格式处理文件,比如 &plain \input story表示使用plainTeX的格式处理文件。一般来说,还可以在从外壳调用它的时候使用tex &plain story或tex \relax这样的形式直接跳过双星号提示。
\eject 表示本页处理完毕,不代表所有的页都处理完成了。我们仍然需要使用\end来结束文本的输入。
在TeX中,由\hsize=控制本行的排版宽度(不是页面宽度)。
如果TeX发出警告,那么每段文本后面最后有一个|表示在输出的时候TeX加了一个黑色的方块。以产生警告作用。
在输出文件的时候,TeX总会产生log日志。
TeX通过给定的信息以断词。比如通过\hyphenation{gal-axy},中间的一个-让TeX知道这是一个可以断词的位置。
错误信息与处理
TeX的错误信息是以感叹号!开始的。并提供了错误上下文。上文是读出的内容,下文是要读的内容。并在上文的最左边给出了错误的行号(X.Y中Y表示行号)。
这个时候可以键入回车,表示试图忽略本次错误,或者输入在TeX当中,这些命令改变了交互的等级。S,R,Q分别对应于\scrollmode,\nonstopmode,\batchmode.使用\errorstopmode转成正常的模式。这些命令是全局作用的,无论它们出现在哪一个组当中。
调用宏的时候产生的错误就像编程语言里面函数调用栈的错误一样,我们可以设置\errorcontextlines设置显示出来的调用栈的深度。
TeX的工作原理
在TeX中有如下约定,一个回车类似于一个空格,一行中的两个连续空格被视为一个,无论出现在哪一个位置,并且使用一个空行表示一段的结束。
TeX的字符所属的类
256个字符被分成16类。数字编号从0到15.TeX在读入文件的时候每次读取一个记号,并对比当前记号所在的类,以决定所产生的行为。这些类及作用为:
| 类别 | 意义 | 默认字符 |
|---|---|---|
| 0 | 转义符 | 反斜杠 |
| 1 | 组开始 | { |
| 2 | 组结束 | } |
| 3 | 数学环境 | $ |
| 4 | 表格对齐 | & |
| 5 | 换行 | |
| 6 | 位置参数 | # |
| 7 | 上标 | ^ |
| 8 | 下标 | _ |
| 9 | 可忽略字符 | |
| 10 | 空格 | |
| 11 | 字母 | [a-zA-Z] |
| 12 | 其它字符 | 无 |
| 13 | 活动符 | ~ |
| 14 | 注释符 | % |
| 15 | 无用符 |
其中每类都可以包含若干个字符。当使用plainTeX格式的时候,$,%,&,#,_要转义使用。此外,一些词在转义时有其它的含义,比如\~得到的是波浪重音。
在TeX工作的时候源文件中所有的部分被分成一个一个的记号,它们或者是指定类代码中的单个字符,或者是一个控制序列。也就是在读内容的时候,TeX先给字符分类,根据分类的情况作进一步的处理:
\hskip 36 pt
\\[1]skip3[12]6[12] [10]p[11]t[11]TeX有一个叫INITEX的版本。这个版本里面气有的字母都被认为是12,除了字母,回车,空格,delete,注释与反斜杠符号之外。
一个明显的特点是,编组符号是不能使用的。
使用\catcodeSYMBOL=N`来将字符更改到指定的类代码当中。
catcode的作用域是局部的。使用\string命令可以将控制字符分成单个的对象。使用\csnameXXXX\endcsname可以将一个字符记号列表变成一个单独的控制系列。只要它不与TeX的原始控制序列相冲突。这个时候,控制系列的名子完全来自于后面的字符,比如 \csname TeX\endcsname 我们不能使用\csname\TeX\endcsname这样的格式,因为\TeX在被调用的时候会被宏展开成其它的东西。
\string宏可以看成是读取后面的非空白字符并按照原样显示,它可以用在我们的\message{\stringXXX}命令当中,以便正确显示出向用户提示的信息。
注: 当\csname用来第一次定义控制系列的时候,那个控制系列在重新被定义之前等价于\relax命令产生的效果。
使用\let命令可以给一个记号赋值,使用
\ifundefined{TOKEN}<if_true>\else<if_false>\fi可以完成一个复杂的判断逻辑。比如
\ifundefined{TeX}<true_text>\else<false_text>\fi表示在\TeX宏未被定义或者为\relax的时候被展开成
\let的语法是\let\TOKEN=\VALUE
除了\string用于得到记号外,还可以使用\numberNUM用于得到一个小数,使用\romannumeralNUMBER得到一个用小写罗马数字表示的数(如\romannumeral24得到xxiv)。并且可以使用\numberREG来得到一个内部寄存器里面的数字。
\uppercase{token_list},\lowercase{token_list}用于完成记号的转换。
ex7.8 想一想,\uppercase{a\lowercase{bC}}得到的记号列表是什么
这可能是TeX语法最为特殊的一点了吧。它的展开不是从内到外逐渐展开的,而是从外到内按照记号逐个分析出来的。发生的过程是,首先\uppercase宏看到了它的一系列参数。这些参数被识别成记号的列表,a, \lowercase, {, b, C, },各自都是完整的单位。这个时候,每一个列表被分类。被uppercase宏作用,就变成了A\lowercase{BC}。然后继续向前读入字符并排版,转成Abc.
这个过程可能很特殊吧。仔细想一想,它跟m4宏还是很相像的,不过m4宏把它们都当成字符,而TeX还分类处理这些记号。
ex7.9 TeX有一个内部整数参数叫做year,它等于任务开始时当前年的数字,看看怎样用\year以及\romannumeral和\uppercase对所有运行在2003的任务给出后面的版权表示。
根据前面的介绍\uppercase{\romannumeral\year}显然是不行的,因为uppercase在展开的时候,首先得到它的参数,而它的参数还没有被展开,为了解决这个问题,必须使用\expandafter宏,让里面的宏先展开,让uppercase得到宏展开后的参数。
正确的答案是
\copyright\ \uppercase\expandafter{\romannumeral\year}TeX的语法的特殊性现在有一点了解了。但是还差很多基础的知识,等看到后面更多的控制系列之后再看它的语法结构吧。
字符输入
很少有键盘能够提供256个字符,而且有的字符已经有其它的用处,所以我们使用下面的char命令得到任意的字符。格式是: \char<NUMBER>得到当前字体的相应字符。注意是当前字体中的字符。
在TeX刚设计的时候,一个字体文件就最多只有256个字符。在TeX内部,字体的代码总是以ASCII格式被调用的。b的内部代码,这样就总是98.
Knuth喜欢使用斜体表示八进制数,使用打字机字体表示十六进制数。个人的习惯不同吧。我觉得都使用正常字体就行了,Knuth的字体使用风格有些复杂。
八进制数使用右单引号引导,而十六进制数使用"引导。比如\char'142与\char"62与\char98是等效的。
定义char还有一个比较方便的方式,这时候不用使用\def<SEQ>{\char<NUM>}这样复杂的形式,而是直接使用\chardef<SEQ>=<NUM>。
有些人的键盘上直接有\(\leq\),\(\geq\)这些字符,它们的生活还真是比较方便啊。
像回车等换行字符的ASCII可以使用^^M这样的形式来表示。^^后面实际上还可以跟两位十六进制数字,用于表示指字代码的ASCII字符。
TeX系统读入的规则
TeX的输入是一系列的“行”,当TeX从里面读入文本的时候,它就处于三个状态中的三歧的一个: 新行N,行中间M,跳过空格S.
在每行的开头它处于状态N,但是大部分的时间它处在状态M,当读入一个控制词或者一个空格之后它进入状态S,这个时候再遇到空格就会被忽略。
出现在行尾的任何空格都会被删除。当TeX在任意的状态读到一个第零类的字符的时候,它就会搜索这个控制序列的名子。如果行中没有其它的字符,那么定义这个序列的名子为空。如果接下来的一个字符不是属于第11类,那么它读入一个字符作为控制系列的名子。如果是属于第十一类,那么继续读,直到读到一个非第11类的字母为止。作为控制序列的一个记号。在读完记号后进入到处理第十类字符的状态S,如果读的记号为空,那么进入状态M当中。
TeX在任意状态遇到第7类字符,并且接下来还是一个第7类字符,也就是遇到了^^,如果后面跟一个小于128的字符,那么^^被去掉,并且从c中加上或减去64.于是^^A就被解释成ASCII为1的字符。但是如果后面跟的是0123456789abcdef,那么读取两位字符,然后使用十六进制代码对应的符号替换^^xx.当替换完成后,TeX返回到原来的位置继续开始。
比如,使用\T65X得到的结果与\TeX结果相同,因为65代表的就是字母e.
在上面的状态下,如果遇到的不是三字符或者四字符,采用另外的处理办法。在读入一个^后,下面的字符如果是1,2,3,4,6,8,11,12,13类中的一个字符,或者第七类的字符,但是不能理解成上面的三字符或者四字符,那么它给字符一个类代码并且进入状态M.
如果遇到的是第5类(行尾字符),那么它就放弃本行剩下的所有的内容但是如果在状态N的时候遇到了行尾字符,那么行尾字符就被转换到控制系列\par.如果处于状态M,那么行尾字符转换成第10类(空白)记号。如果处于状态S的时候遇到了行尾符,那么行尾的字符就被忽略。(同时转到状态N?)
如果遇到第9类字符,那么直接忽跳过它,就好像没有它一样。
遇到第10类字符的处理: N或S时,第10类字符被直接跳过,并且保持当前状态,处在状态M的时候,字符被转换成第10类的记号,并设此记号代码为32.然后进入状态S.空格记号当中,字符代码总是32.
我们不妨看成这就是TeX程序的词法分析。在词法分析的时候它得到了记号的类型与记号的值。
如果TeX遇到了注释符,它就不再读入当前行剩下的内容。
遇到无用符(第15类)的时候,跳过此字符,打印出错误消息,并仍保持当前的状态不发生改变。
如果当前行没有要读入的内容了,就进入下一行并且进入状态S.但是如果是\input文件结束,或者\input的文件给出了\endinput,那么TeX返回到\input文件读取结束后的状态,就好像是从读完input的内容一样。
ex8.2 回答下面简短问题,看看你对TeX的读入规则的理解程度: a.第5类和第14类的不同在什么地方 b.第3类和第4类的不同在什么地方 c.第11类和第12类的不同在什么地方 d.活动符后面的空格要忽略掉么 e.当一行以注释符%结尾的时候,在下一行开头的空格被忽略了么? f.一个可被忽略的字符能出现在控制系列的名子中间吗?
答:a.第5类是换行,第14类是注释,两者都结束了当前行,但是换行会被转成第10类(空白符)的代码或者一个\par控制系列。但是第14类的字符永远不产生任何记号。
b.第3类是数学类,第四类是表格类。它们产生不同类型的代记号。在后续的处理当中它们是完全不同的。
c.类似于b.第11类是字母,第12类是其它符号。但是只有第11类的字母有组成较长的控制系列的能力。
d.不能被忽略掉。活动符是第十三类,读完它后处于状态M,后面的空格不能被忽略。
e.是的,当注释符里面的内容结束后,TeX进入状态N,任何位于行首的空白字符都会被删除的。
f.不可以。控制系列是连续读取在第11类中的单词,遇到其它类就是结束对控制系列名字的读取。从而起到了分割的作用。
ex8.3 再次看看有关\vship的错误信息,当TeX报告说\vship是一个未定义的控制序列的时候,它输出了两行上下文,表明它正在读入文件story的第二行的中间部分,在遇见错误的时候,TeX处于什么状态,它下一个要读入什么字符。
我们可以按照正确的路向分析。它读入一个控制系列后处于状态S,会忽略掉后面的空白的字符(第10类),因此它下一个要读取的是非空白字符。
ex8.4 给定plainTeX格式的类代码后,从输入行$x^2$~ \TeX ^^62^^6得到什么样的记号?
这得好好分析。首先数学模式正常排版,读完$后进入M状态,活动符是有效的,所以波浪线后面的空格不能被忽略。接下来读\TeX,在读完\TeX之后,进入状态S,因此后面的空格都被忽略,接下来读^^62,得到字母b,再接下来读记号^^6,得到字母v.
我们完全可以把这种分析过程理解成词法分析,分析之后形成了记号的类型与值的线性序列。
ex8.5 想想正好有一个三行的输入文件,第一行是Hi!,其它两行都是空的,当TeX读入这个文件的时候,按照plainTeX的类代码将会得到什么记号?
这个时候处理的方式是,处理成[11]H[11]i[12]![10] [0]\[S]par.空格是第一行的回车引起的。
据Knuth自己的表述,类代码写成数值格式主要是为了防止人们过度使用\catcode.