使用 Knuth 的 WEB 文字编程语言编写的相当简单、指导性、教育性和说明性的网络程序源代码?

使用 Knuth 的 WEB 文字编程语言编写的相当简单、指导性、教育性和说明性的网络程序源代码?

有人可以在 DEK 的 WEB 上提供(或编写)这样的程序吗?

我希望他们测试 Jim Fowler 的 WEB/TeX 的 pascal 到 javascript 编译器 web2js而且我觉得这样的合集对于DEK的WEB编程也是一个很好的介绍。

我请求尽可能多的建议。

答案1

这是一个很小的hello.web,它没有使用太多的 WEB 功能:

@* Introduction.
This program takes an integer $n$ as input, and prints ``Hello world'' $n$ times.

@p program HELLO(input, output);
var
   n: integer;
   i: integer;
begin
   read(n);
   for i := 1 to n do
   begin
      writeln('Hello world');
   end;
end.

@* Index. Not much to it. Everything occurs in section 1.

紧接着weave hello.webtex hello.tex最终的排版输出如下:

你好的第一页


有关详细信息,请阅读 WEB 手册(随texdoc webman或提供)在线的


除了“Hello World”,我们还可以用一个专门用于说明 WEB 的小程序来代替它吗?

1981 年 11 月问题拖船克努斯 提及他正在开发一个名为 WEB 的系统。然后在下一期(1982 年 3 月),他发布“定点粘合设置:WEB 示例”,说

我很快就会发布一本关于 WEB 的完整手册,但与此同时,我认为用“web”形式编写一段相当短的代码示例会很有用。因此,我准备了附带的程序,它还具有另一个功能:...

您可以使用以下方式阅读程序本身texdoc glue,或者这里。 这glue.web你需要的是这里

glue.web我刚刚用另一个 Pascal 编译器 (fpc)​​ 对此进行了测试,首先运行tangle glue.web以生成glue.p,然后添加{$mode ISO}到顶部glue.p(这样当程序说“整数”时,使用 32 位类型而不是 16 位类型),然后运行fpc glue.p以生成glue二进制文件。 (我尝试了 web2js,但它崩溃了。)

然后,为了测试二进制文件,请将以下输入(由 7 个测试用例(成对的行)后跟 0 组成)放入文件中(或在终端中输入)。此输入与 Knuth 使用的输入相同。

200000
30000 40000 50000 60000 0
2000
30000 40000 50000 60000 0
1000000000
8000000 -9000000 8000000 4000 7000000 0
100
8000000 -9000000 8000000 4000 7000000 0
1000000000
800 -900 800 400 700 0
1000000000
800 -900 800 400 -700 0
65555 
-200 199 0
1
60000 -59999 90000 0
0

输出应与在拖船):

Test data set number 1:
  Glue ratio is 1.1111 (0,14,18205)
               30000          33334
               40000          44445
               50000          55557
               60000          66668
 Totals       180000         200004 (versus 200000)
Test data set number 2:
  Glue ratio is 0.0111 (0,21,23302)
               30000            333
               40000            444
               50000            555
               60000            666
 Totals       180000           1998 (versus 2000)
Test data set number 3:
  Glue ratio is 71.4101 (8,0,18281)
             8000000      571281250
            -9000000     -642686836
             8000000      571281250
                4000         274215
             7000000      499857383
 Totals     14004000     1000007262 (versus 1000000000)
Test data set number 4:
  Glue ratio is 0.0000 (8,24,30670)
             8000000             57
            -9000000            -64
             8000000             57
                4000              0
             7000000             49
 Totals     14004000             99 (versus 100)
Test data set number 5:
  Glue ratio is 2x2x2x2x2x2x8681.0000 (-6,1,17362)
                 800      444467200
                -900     -500025600
                 800      444467200
                 400      222233600
                 700      388908800
 Totals         1800     1000051200 (versus 1000000000)
Test data set number 6:
! Excessive glue.
  Glue ratio is 2x2x2x2x2x2x2x0.0000 (-6,0,0)
                 800              0
                -900              0
                 800              0
                 400              0
                -700              0
 Totals          400              0 (versus 1000000000)
Test data set number 7:
Invalid data (nonpositive sum); this set rejected.
Test data set number 8:
  Glue ratio is 0.0000 (1,30,23861)
               60000              0
              -59999              0
               90000              1
 Totals        90001              1 (versus 1)

答案2

为了更好地说明 WEB,这里有一个相当复杂的 hello-world 程序版本,它使用了 WEB 的大部分主要特性:命名和未命名部分;简单、数字和参数宏;一些格式和索引控件;以及最奇特的字符串池。

该程序所做的一切就是在读取数字 N 后打印“Hello world”N 次(因此与 GLUE 或 PRIMES 程序不同,在数学或算法上没有什么不平凡的事情来增加难度),但它被分成多个模块,并使用双引号字符串(TANGLE 写入)程序从字符串池文件读取,将字符串维护在字符数组中,就像 TeX 和其他 Knuth 程序一样。

这是程序(也上传了这里):

% A Hello-world program.
\def\title{Hello}
% Hack to change link colours (if used with pdfwebmac)
\def\BlueGreen{\pdfsetcolor{\cmykRed}}


@* Introduction.
This program takes an integer $n$ as input and prints ``Hello world'' $n$ times.
(There is no Pascal code in this section.)



@ We give part of the program here, and it will continue later.

@p
@<Compiler directives@>
program HELLO(input, output);
var
  @<global variables@>



@ What global variables do we need? For one thing, we need the $n$.

@<global...@>=
  @!n: integer;



@ For |integer| variables to be treated as 32-bit by the Pascal compiler,
on FPC we need a special compiler directive.

@<Compiler di...@> = @{@&$mode iso@}
@^system dependencies@>

@* The string pool and file I/O.
The WEB feature of string pools was designed at a time when Pascal compilers
did not have good support for strings. Now it may be no longer necessary, but to
illustrate the feature we will maintain a string pool.

More specifically, we will maintain a large array of characters, named |str|.
All characters of all strings from the string pool go into this array: the $n$th
string occupies the positions from |str_start[n]| to |str_start[n+1] - 1|
(inclusive) in this array, where |str_start| is an auxiliary array of integers.
Also, the number of strings currently in the string pool is stored in an integer
variable called |str_count|.

By convention, the first $256$ strings are the one-character (one-byte) strings.
For this program we don't need too many additional strings. In fact we need just
a few strings, but we'll support $10$ strings with a total of $1000$ characters.

@d max_strings = 256 + 10
@d max_total_string_length = 1000

@<global var...@>=
  @!str: array[0..max_total_string_length-1] of char;
  @!str_start: array[0..max_strings-1] of integer;
  @!str_count: integer;



@ To use this string pool, we have a procedure that reads out characters from it
one-by-one. Specfically, |print(k)| prints the $k$th string, and |println| and
|printnl| are convenience macros.

@d println(#) == begin print(#); writeln; end
@d printnl(#) == begin writeln; print(#); end
@p
procedure print(n: integer);
var
  i: integer;
begin
  @{ writeln('For ', n, ' will print characters from ', str_start[n], ' to ', str_start[n + 1] - 1); @}
  for i := str_start[n] to str_start[n + 1] - 1 do
  begin
    write(str[i]);
  end;
end;



@ We'll have a procedure to populate this array by reading from the pool file,
but unfortunately that means we need to figure out file input. How this is done
depends on the Pascal compiler. In FPC, a file of characters can be declared as
a variable of type |TextFile|, initialized with |Assign| and |Reset|, then read
with |read|.
@^system dependencies@>

@p
procedure initialize_str_array;
var
  pool_file: TextFile;
  x, y: char;  { for the first two digits on each line}
  @!length: integer;
  i: integer;
begin
  str_count := 0;
  str_start[0] := 0;
  for i := 0 to 255 do
  begin
    str[i] := chr(i);
    str_start[i + 1] := str_start[i] + 1;
    str_count := str_count + 1;
  end;
  Assign(pool_file, 'hello.pool');
  Reset(pool_file);
  while not eof(pool_file) do
  begin
    read(pool_file, x, y);
    if x = '*' then @<check pool checksum@>
    else begin
      length := 10 * (ord(x) - "0") + ord(y) - "0";
      str_start[str_count + 1] := str_start[str_count] + length;
      for i := str_start[str_count] to str_start[str_count + 1] - 1 do
      begin
        read(pool_file, str[i]);
      end;
      readln(pool_file);
      str_count := str_count + 1;
    end
  end;
end;



@ To ensure that the pool file hasn't been modified since tangle was run, we can
use the @@\$ (= |@t\AT!\$@>| = at-sign, dollar-sign) feature. We can reuse
(abuse?) the |y| and |length| variables for reading characters and maintaining
the checksum read from the file.

@<check pool...@> =
begin
  length := ord(y) - "0";
  while not eof(pool_file) do
  begin
    read(pool_file, y);
    if ("0" <= ord(y)) and (ord(y) <= "9") then
      length := length * 10 + (ord(y) - "0");
  end;
  if length <> @$ then
  begin
     writeln('Corrupted pool file: got length: ', length : 1, '; rerun tangle and recompile.');
     Halt(1);
  end
end



@* Main program.
Apart from |n|, we also need an |i| to loop over.

@<glob...@> =
  i: integer;



@ Here finally is the ``main'' block of the program.

@p
begin
  initialize_str_array;
  print("How many times should I say hello? ");
  read(n);
  printnl("OK, here are your "); write(n : 1); println(" hellos: ");
  for i := 1 to n do
  begin
    println("Hello, world!");
  end;
  print("There, said hello "); write(n : 1); println(" times.");
end.



@* Index. If you're reading the woven output, you'll see the index here.

跑步tangle(为了得到hello.phello.pool),然后 Pascal 编译器显示程序运行正常:

% tangle hello.web                                                     
This is TANGLE, Version 4.5 (TeX Live 2018)
*1*5*9*11
Writing the output file
Done.
6 strings written to string pool file.
(No errors were found.)

% cat hello.pool                                    
35How many times should I say hello? 
18OK, here are your 
09 hellos: 
13Hello, world!
18There, said hello 
07 times.
*332216284

% fpc hello.p                      
Free Pascal Compiler version 3.0.4 [2018/10/02] for x86_64
Copyright (c) 1993-2017 by Florian Klaempfl and others
Target OS: Darwin for x86_64
Compiling hello.p
Assembling (pipe) hello.s
Linking hello
26 lines compiled, 0.1 sec

% echo 5 | ./hello 
How many times should I say hello? 
OK, here are your 5 hellos: 
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
There, said hello 5 times.

并运行weave

weave hello && sed -i=.bak "s/webmac/pdfwebmac/" hello.tex && pdftex hello.tex

结果是 7 页排版PDF这是通常“应该”读取的程序版本。

答案3

一些额外的信息来源。

1987 年,Knuth 举办了一系列关于数学写作的讲座;这些讲座也经常讨论计算机科学写作。他用两场讲座来介绍文学编程,自然而然地使用了WEBCWEB也是在 1987 年开发的,但这些讲座可能早于它)。

虽然整个系列由 21 个讲座组成,但它们按主题分组并且相对独立;您无需观看所有讲座即可了解这里相关的两个内容:

文学编程(1) -https://www.youtube.com/watch?v=U8LttJ1rvWI
文学编程(2) -https://www.youtube.com/watch?v=ObxmXC2NCMA

他仔细研究了学生编写的几个WEB程序并对其进行了批评,同时讨论了WEB文学编程的各种特点和一般情况。


但这还不是全部。1982 年有一系列关于“TeX82 的内部细节”的讲座;其中前几个讲座经常涉及WEB,但第一的是最相关的。如果你对 TeX82 的内部细节感兴趣,我建议你看完整个系列。
(但请注意一些时代错误;虽然大多数细节没有改变,但讲座中讨论的 TeX82 和我们今天所知道的 TeX82 之间至少有一个很大的差异,即是否存在一个原语。这个单一的原语被用于现在将使用或使用的\chcode地方(参见\catcode\mathcode错误日志)。哦,计算机显示的记录大部分都难以辨认,但如果你熟悉WEBTeX,那么你仍然应该能够跟上。


@ShreevatsaR指出一条评论讲座中提到的 TeX82 和我们所知的 TeX82 之间的差异并不一定是微不足道的,并且与tex.web版本(banner称为version -.25)。我去阅读了其中的一些内容,并意识到,即使您对 TeX 有点熟悉,使用源代码(尤其是没有编织的排版版本)WEB来跟进也是非常困难的。WEB

因此,让我们浏览一下其中的一些部分,并将其与 TeX 版本 3.14159265 进行比较(此后,“OldTeX”是讲座中介绍的版本 -0.25,“TeX”是现代形式)。至于行号,读者应该从网站上复制并粘贴代码,而不是保存整个页面;后者需要从代码中删除所有 HTML 实体和标记(并且有大量代码)。

首先,当然是前 61 行:

COMMENT ⓧ   VALID 00057 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00006 00002  % This program is copyright 1982 by D. E. Knuth all rights are reserved.
C00010 00003  @* \[1] Introduction.
C00042 00004  @* \[2] The character set.
C00056 00005  @* \[3] Input and output.
C00079 00006  @* \[4] String handling.
C00095 00007  @* \[5] On-line and off-line printing.
C00111 00008  @* \[6] Reporting errors.
C00133 00009  @* \[7] Arithmetic with scaled dimensions.
C00148 00010  @* \[8] Packed data.
C00158 00011  @* \[9] Dynamic memory allocation.
C00175 00012  @* \[10] Data structures for boxes and their friends.
C00211 00013  @* \[11] Memory layout.
C00224 00014  @* \[12] Displaying boxes.
C00243 00015  @* \[13] Destroying boxes.
C00247 00016  @* \[14] Copying boxes.
C00253 00017  @* \[15] The command codes.
C00267 00018  @* \[16] The semantic nest.
C00280 00019  @* \[17] The table of equivalents.
C00332 00020  @* \[18] The hash table.
C00351 00021  @* \[19] Saving and restoring equivalents.
C00372 00022  @* \[20] Token lists.
C00384 00023  @* \[21] Introduction to the syntactic routines.
C00391 00024  @* \[22] Input stacks and states.
C00423 00025  @* \[23] Maintaining the input stacks.
C00430 00026  @* \[24] Getting the next token.
C00459 00027  @* \[25] Expanding user macros.
C00481 00028  @* \[26] Basic scanning subroutines.
C00531 00029  @* \[27] Building token lists.
C00544 00030  @* \[28] File names.
C00572 00031  @* \[29] Font metric data.
C00628 00032  @* \[30] Device-independent file format.
C00665 00033  @* \[31] Shipping pages out.
C00721 00034  @* \[32] Packaging.
C00749 00035  @* \[33] Data structures for math mode.
C00778 00036  @* \[34] Subroutines for math mode.
C00799 00037  @* \[35] Typesetting math formulas.
C00853 00038  @* \[36] Alignment.
C00902 00039  @* \[37] Breaking paragraphs into lines.
C00963 00040  @* \[38] Breaking paragraphs into lines, continued.
C00992 00041  @* \[39] Pre-hyphenation.
C01004 00042  @* \[40] Post-hyphenation.
C01020 00043  @* \[41] Hyphenation.
C01038 00044  @* \[42] Initializing the hyphenation tables.
C01067 00045  @* \[43] Breaking vertical lists into pages.
C01084 00046  @* \[44] The page builder.
C01131 00047  @* \[45] The chief executive.
C01157 00048  @* \[46] Building boxes and lists.
C01218 00049  @* \[47] Building math lists.
C01267 00050  @* \[48] Conditional processing.
C01281 00051  @* \[49] Mode-independent processing.
C01325 00052  @* \[50] Dumping and undumping the tables.
C01350 00053  @* \[51] The main program.
C01362 00054  @* \[52] Debugging.
C01367 00055  @* \[53] Extensions.
C01389 00056  @* \[54] System-dependent changes.
C01390 00057  @* \[55] Index.
C01391 ENDMK
Cⓧ;

在 SAIL,主系统文本编辑器(在讲座中经常提到)是面向页面;还有一个具有类似 ex 功能的行编辑器。页面标有进纸器字符,可能显示为带有 的方块FF,可以使用 control-L 在终端中输入(clear如果在 shell 中输入,它通常用作命令)。有趣的是, GNU C 编码风格指南 建议用换页符来划分源代码,但我个人从未在 1990 年以后的任何代码中见过这种情况。

无论如何,这个“标题”仅列出了文件的页面及其第一行。在支持分页的编辑器中,可以通过跳转到第 32 页轻松跳转到第 30 部分。可以忽略它。您可以安全地用空字符替换所有换页符,而不会更改任何重要内容。

接下来是 limbo 部分,我会将其分成几个部分。

% This program is copyright 1982 by D. E. Knuth; all rights are reserved.
% Please don't make any changes to this file unless you are D. E. Knuth!
% Version 0 is fully implemented but not yet fully tested, so beware of bugs.

% Here is TeX material that gets inserted after \input webhdr
\def\hang{\hangindent 3em\ \unskip\!}
\def\textindent#1{\hangindent 2.5em\noindent\hbox to 2.5em{\hss#1 }\!}
\def\at{@@} % use for an at sign
\chcode@@=13 \def@@{\penalty999\ } % ties words together
\def\TeX{T\hbox{\hskip-.1667em\lower.424ex\hbox{E}\hskip-.125em X}}
\font b=cmr9 \def\mc{\:b} % medium caps for names like PASCAL
\def\PASCAL{{\mc PASCAL}}
\def\ph{{\mc PASCAL-H}}
\font L=manfnt % font used for the METAFONT logo
\def\MF{{\:L META}\-{\:L FONT}}
\def\<#1>{$\langle#1\rangle$}
\def\kern{\penalty100000\hskip}

为了进行比较(省略版权注释),这里是规范中的相应代码tex.web

% Here is TeX material that gets inserted after \input webmac
\def\hang{\hangindent 3em\noindent\ignorespaces}
\def\hangg#1 {\hang\hbox{#1 }}
\def\textindent#1{\hangindent2.5em\noindent\hbox to2.5em{\hss#1 }\ignorespaces}
\font\ninerm=cmr9
\let\mc=\ninerm % medium caps for names like SAIL
\def\PASCAL{Pascal}
\def\ph{\hbox{Pascal-H}}
\def\pct!{{\char`\%}} % percent sign in ordinary text
\font\logo=logo10 % font used for the METAFONT logo
\def\MF{{\logo META}\-{\logo FONT}}
\def\<#1>{$\langle#1\rangle$}
\def\section{\mathhexbox278}

嗯,第一个区别是我们使用的是\inputting webhdr而不是 webmac。这很简单:在 TeX78 和 OldTeX 中, .tex包含格式定义的文件的文件名似乎以 结尾 hdr.tex而不是mac.tex。示例:manmac.texmanhdr.tex,而现代的对应文件taocpmac.tex(在 SAIL 中是非法文件名,因为它超过十个字符)是。acphdr.tex当然 ,虽然时间很短。 (我怀疑这个惯例与 SAIL 源代码的类似惯例有关(请参阅webmac.texwebhdr.tex
纹理贴图,并将其改为使 TeX 与 SAIL 保持距离并使其更加独立;但这仅仅是一个假设。)

\hang接下来是、\hangg(OldTeX 中没有)和 的定义\textindent。它们用于逐项列出;后来可能希望实现更精细的控制。在 OldTeX 和 TeX78 中,\!是一个原语,似乎与 TeX 的 同义\ignorespaces
我不确定 的确切用途\ \unskip\!\␣是一个“强制空间”,就像 TeX 中一样。\unskip列在TeX 和 METAFONT,排版的新方向作为“最近添加”,并删除最近的粘连。因此,该表达式似乎意味着<space><remove the space><supress following spaces>,这对我来说没有意义。

OldTeX 的下一个定义\at是生成一个 @ 符号,然后使 @ 符号成为一个活动字符(记住,文件@@中的任何字符WEB都会无条件转换为单个 @ 符号),并将其定义为生成一个不间断空格或领带---本质上,@做了什么~。注意在纯 TeX 中,~定义为惩罚 10000,而不是 的@999。@的类别代码的更改是通过 完成的;正如我上面所说,这本质上是和\chcode的组合。OldTeX 和 TeX78 之间的一个区别如下所示:在 TeX78 中,表达式将是(类别代码从零开始,而不是从一开始)。\catcode\mathcode\chcode@@=12

TeX 的原因\def\pct!...webmac.tex定义\%为字体中的百分号\ttwebhdr.tex却不是。\TeX的定义不是basicOldTeX 和 TeX78 的默认格式 的一部分,类似于 TeX 的 plain

\PASCAL请注意,即使名称最终被设置为小写,保留可能只是为了兼容性。

在 OldTeX 和 TeX78 中,字体处理(就定义字体和切换到字体而言)实际上非常复杂。TeX78 字体名称是单个字符,而不是控制序列;它们引用的实际内部字体编号由以下因素决定:取该字符对应的 ASCII 码的最后五位,将可寻址字体的数量限制为 32 个。后来扩展到 256 个。可以说,\font b=cmr9然后与然后\:b相同。我不熟悉任何其他细节。\font\b=cmr9\b

现在我们可以继续了。两个版本中剩余的 limbo 部分应该不会带来太多困难。

旧TeX

\def\(#1){} % this is used to make module names sort themselves better
\def\9#1{} % this is used for sort keys in the index via @:sort key}{entry@>

\outer\def\N#1. \[#2]#3.{\par\mark{#1}\vfill\eject % beginning of starred module
  \gdef\position{\:a#2\:ux\:a\topmark} % for part numbers
  \xdef\rhead{\uppercase{\!#3}}
  \sendcontents{\Z{\]#2]#3}{#1}{\count1}}
  \Q\noindent{\bf#1.\quad\!#3.\quad}\!}

\def\title{\TeX82}
\def\contentspagenumber{1}
\def\topofcontents{\hsize 5.5in
  \topspace 0pt plus 1fil minus 1fil
  \def\]##1]{\hbox to 1in{\hfil##1.\ }}
  }
\def\botofcontents{\vskip 0pt plus 1fil minus 1fil\setpage\let\]=\let}
\def\lheader{\hbox to1.5em{\:a\hss\count0}\:m\qquad\rhead\hfill\title\qquad
  \position} % top line on left-hand pages
\def\rheader{\position\:m\qquad\title\hfill\rhead\qquad
  \hbox to1.5em{\:a\hss\count0}} % top line on right-hand pages
\setcount0 \contentspagenumber
\topofcontents
\ctrline{(replace this page by the contents page printed later)}
\botofcontents
\mark{1}\eject

TeX

\def\(#1){} % this is used to make section names sort themselves better
\def\9#1{} % this is used for sort keys in the index via @@:sort key}{entry@@>

\outer\def\N#1. \[#2]#3.{\MN#1.\vfil\eject % begin starred section
  \def\rhead{PART #2:\uppercase{#3}} % define running headline
  \message{*\modno} % progress report
  \edef\next{\write\cont{\Z{\?#2]#3}{\modno}{\the\pageno}}}\next
  \ifon\startsection{\bf\ignorespaces#3.\quad}\ignorespaces}
\let\?=\relax % we want to be able to \write a \?

\def\title{\TeX82}
\def\topofcontents{\hsize 5.5in
  \vglue 0pt plus 1fil minus 1.5in
  \def\?##1]{\hbox to 1in{\hfil##1.\ }}
  }
\def\botofcontents{\vskip 0pt plus 1fil minus 1.5in}
\pageno=3

TeX 和 METAFONT 源补丁\N(所有WEB版本),用于修改运行标题和目录(出于美观原因)。 OldTeX 和 TeX 在这方面采取了不同的方向。

我必须承认,我对 WEAVE 的了解还不够,无法解释 \()和的用法\9\9是 WEAVE 的内部结构;它没有在 的任何地方使用tex.web\()另一方面,它经常用于与其他几个模块名称共享一个共同前缀的模块名称中(这不是对 WEAVE 的内部数据结构或它如何按前缀搜索的评论,而只是一种观察)。
例如,以下是四个模块名称,它们按顺序出现在单个部分(即 453)中:

@<Scan units and set |cur_val| to $x\cdot(|cur_val|+f/2^{16})$...@>=
@<Scan for \(u)units that are internal dimensions;
  |goto attach_sign| with |cur_val| set if found@>;
@<Scan for \(m)\.{mu} units and |goto attach_fraction|@>;
@<Scan for \(a)all other units and adjust |cur_val| and |f| accordingly;
  |goto done| in the case of scaled points@>

三个共享“扫描”的命令都有\(),但“扫描单位...”没有。同样,这个命令的确切用法和含义我还不清楚;也许有更了解的人可以插话。

哇哦,现在我们可以真正地进入一些代码了!对于 OldTeX 源代码和 TeX 源代码之间的每一个差异,我都做了一个 要旨它只包含运行 的输出diff oldtex.web tex.web。您会看到,当然有数千处更改;其中大多数是排版更正/更改,其中“排版”指代码文本和编织的 TeX 输出的排版。TeX 中通常还有更多索引。(我认为,不幸的是,索引命令通常会使代码有点难以阅读;对于刚接触 的人来说,这可能是一个问题WEB。)

但这里有一个非常重要的区别,它贯穿整个源代码:字符集。如今,与 ASCII 不兼容的东西非常少见,但在 SAIL 安装时,它却非常普遍。这就是为什么所有WEB进行任何输入和输出并期望可移植的程序都会将输入转换为内部的、类似 ASCII 的格式。SAIL 的字符集是 ASCII 的扩展,添加了许多数学上有用的字符。以下是 OldTeX 源代码的摘录,删除了WEB 命令并更改了缩进以增强清晰度:

@<Accumulate the constant...@>=
loop begin 
  if (cur_tok<zero_token+radix)∧(cur_tok≥zero_token)∧(cur_tok≤zero_token+9) then 
    d←cur_tok-zero_token
  else if (radix=16)∧(cur_tok≤A_token+5)∧(cur_tok≥A_token) then
    d←cur_tok-A_token+10
  else
    goto done;
  vacuous←false;
  if (cur_val≥m)∧((cur_val>m)∨(d>7)∨(radix≠10)) then
    begin if OK_so_far then
      begin print_nl("! Number too big");
        help2("I can only go up to 2147483647='17777777777=""7FFFFFFF,")
             ("so I'm using that number instead of yours.");
        error; cur_val←infinity; OK_so_far←false;
      end;
    end
  else cur_val←cur_val*radix+d;
  get_nc_token;
  end;
done:

字符都不是标准 ASCII。为了让 TeX 在尽可能多的系统上运行,并使移植过程尽可能轻松,显然不能使用这些字符;而现代的 tex.web确实是纯 ASCII。TeX78 语言也大量使用了 SAIL 的字符集;在对齐时将使用&。这也被废除了。阅读 TeX 的源代码和电子书,人们会觉得 Knuth 对于必须将一切都限制在“较低”的标准 ASCII 上感到有些不满。

为了将WEB代码从 SAIL 的字符集更改为标准 ASCII,David R. Fuchs 编写了一个 SAIL 程序,该程序可自动执行转换。该程序被称为 联合国教科文组织这个名字已经很有趣了,但当你想到该WEB系统的 TANGLE 组件的初始原型被命名为 联合国国际贸易法委员会

无论如何,@<Accumulate the constant...@>模块的最终形式如下:

@<Accumulate the constant...@>=
loop begin
  if (cur_tok<zero_token+radix)and(cur_tok>=zero_token)and
     (cur_tok<=zero_token+9) then
    d:=cur_tok-zero_token
  else
    if radix=16 then
      if (cur_tok<=A_token+5)and(cur_tok>=A_token) then
        d:=cur_tok-A_token+10
      else
        if (cur_tok<=other_A_token+5)and(cur_tok>=other_A_token) then
          d:=cur_tok-other_A_token+10
        else
          goto done
    else
      goto done;
  vacuous:=false;
  if (cur_val>=m)and((cur_val>m)or(d>7)or(radix<>10)) then
    begin if OK_so_far then
      begin print_err("Number too big");
      help2("I can only go up to 2147483647='17777777777=""7FFFFFFF,")
           ("so I'm using that number instead of yours.");
      error; cur_val:=infinity; OK_so_far:=false;
      end;
    end
  else cur_val:=cur_val*radix+d;
  get_x_token;
  end;
done:

(我非常同意 Kernighans 在其批评 Pascal 的文章第 5 部分中关于分号的观点;老实说,我不知道四重嵌套if 语句是否正确缩进。)

最大的问题(肯定比字符集更难处理)是缺少 OldTeX 编织源代码的印刷版本。在整个讲座中,经常提到确切的模块编号。这里有一个近似的解决方案:只需在行首搜索(删除换页符后)一个 at 符号,后跟一个空格或星号;这可以用模式匹配的常用符号表示为 ^@( |\*)。然后通过转到第 n 个结果,您实际上转到了第 n 个模块。这不是一个令人满意的解决方案,但没关系。

(好吧,这更像是一次信息转储,而不是一次演练,但现在它已经被记录下来了。)

相关内容