summaryrefslogtreecommitdiff
path: root/info/digests/tex-implementors/message.22
blob: 5840761828c828fa0b961b679c8ec94e8d6df873 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
Date:     8 Jan 90                              Message No:     022
Resent:   11 Jan 90
 
To:       TeX implementors and distributors
 
From:     Barbara Beeton
 
Subject:  Updates: files at labrea, TEX82.BUG, Knuth off-line
 
 
[ The initial mailing of this message and the TEX82.BUG file, in three
  parts, caused our mailer to suffer a serious breakdown; not only were
  my messages not delivered, but the entire mail server crashed, putting
  the whole Society out of the mail business for two days.  In order to
  recover in the most painless manner possible, I have made the recommended
  changes in the mailing list, and am re-sending the whole lot.  Please
  accept my apologies if you get two copies of anything.  The only change
  to any of them is the addition of the Resent: date at the top of this
  message, and this paragraph. ]
 
Preparing this installment has taken rather longer than I intended,
delayed by standards meetings, the holidays, and writing the user
documentation for the impending release of version 2.0 of both AMS-TeX
and the AMSFonts collection.  It is my understanding that the AMS
management wishes these two packages to have the widest distribution
possible, so an announcement should be forthcoming to make them both
available to TeX distributors and archives for inclusion in their
offerings.  (The AMSFonts package will not include the .mf sources;
those will be available only by individual request, and there wil be
some restrictions.  If you are interesed in the .mf sources, write to
Regina Girouard, rmg@math.ams.com; she is Manager of the AMS Composition
Services Department, in charge of the font distribution.)  Stay tuned.
 
There have been no updates to the TeX "core" at labrea.stanford.edu
since November.  On the assumption that TUGboat 10 #4 (the Proceedings
of this summer's tenth anniversary meeting) would be out and in your
hands by now, I had not sent out the updates to the TEX82.BUG file,
since it is very large, and all entries since the beginning of 1989
are printed in their entirety in a supplement accompanying that issue.
However, since there have been a number of delays in its production (it
was sent to the printer the first week of November), and the projected
mailing date now isn't until late January, I am sending under separate
cover, in two pieces, the additions that haven't been sent out before
in this form, items 355-370.  (They "duplicate" the code in the TEX.WEB
differences file that was sent early in November, as a supplement to
message 20.)  Since preparing the camera copy for the TUGboat supplement,
two additional bug listings, 371 and 372, have come to my attention.
371 is in response to a bug found by Wayne Sullivan, and Knuth distributed
372 in TeXhax 89 #110.  Both are reproduced below.  The TEX.WEB file
incorporating them has not yet appeared at labrea, as mentioned earlier.
This is the commentary that came with 371 (dated 24 Nov 89); it sounds
like caution is in order when applying it:
 
    Here's how I plan to fix the problem. The code isn't tested yet --- I'll
    wait a couple weeks to see if I can combine it with some other work ---
    but I think it catches all cases where scanning can interfere with
    the save_stack protocols. It preserves the locality of csnaming, and
    makes things like \discretionary consistent with \insert in this respect.
 
And here is the text that accompanied #372; again, caution:
 
    I just received a message from Rainer Sch\"opf and Frank Mittelbach
    in Gutenberg's city (Mainz) that they've done it again, found another
    subtle bug. This one is especially unique because it will not be
    detected in the TRIP test; it only occurs in the system-dependent code
    that figures out the "area" and "extension" part of a file name.
 
    As far as I know, the error occurs only when you try to combine
    \csname somehow with file name specification; for example,
        \input area:file.ext\csname xyz\endcsname
    will fail on many systems. (Reason: TeX may have to store the
    control sequence name xyz, while it's looking for the token following
    "ext". This moves things in the string pool; but the present TeX doesn't
    move the area and extension pointers, so they might point to garbage.)
 
    This bug will be corrected in version 2.993, but I decided to use
    TeXhax to let installers get a head start, since this bug affects
    nearly everybody's system-dependent code! The changes are simple---
    so simple I haven't even felt the need to test them yet---but they
    should be made, and you can get a head start by putting them into
    your change files before you get a new TEX.WEB file.
 
    Here are the changes I'll be making to TEX.WEB (unless there's
    an error here, which cannot be:)
 
Brian Hamilton Kelly pointed out that identifiers in TEX.WEB containing
a cap "E" (in his case, from local changes to support editors) get the
"E" lowered as it is in TeX, and suggested that perhaps this should be
fixed directly in the .WEB file.  We'd made a local change for that here
at AMS some years ago, so I forwarded the code to Brian.  I also sent
it to Don Knuth, who replied that he didn't think it appropriate in the
canonical TEX.WEB, but suggested that I forward the code to this list.
The solution isn't perfect -- it only suppresses the problem if the "E"
doesn't precede an "X" -- but it's a start.  If anyone has a better
solution, please send it to me and i'll distribute it to the list.
The code is given below.
 
Peter Breitenlohner has provided a list of modules in MF.WEB 1.8 that
have changed, as compared to 1.7; see below.  I have not yet retrieved
the source from labrea so that a difference list can be run, but it's
on my list.
 
Finally, it's now official -- Knuth has withdrawn from the electronic
networks, and will henceforth be accessible only via the postal service.
However, he has deputized me to keep an eye out for genuine bugs (as
reported through TeXhax, UKTeX, etc.) and has agreed to let his secretary
accept electronic messages from me.  Mail sent to Knuth's old address at
Sail will now yield the following result:
 
    Subject: Failed mail returned: user not accepting mail
 
    The message below was not deliverable to:
 
     DEK@SAIL.Stanford.EDU
 
    DEK (Don Knuth) has decided to discontinue reading email.
    Please write to him at the following address:
 
     Donald E. Knuth
     Professor of The Art of Computer Programming
     Computer Science Department
     Stanford University
     Stanford, CA 94305-2140 USA
 
If you have a bug to report and you would like me to forward it, I will.
Alternatively, you can send your report on paper to Knuth's new address.
 
 
########################################################################
 
Addenda to TEX82.BUG -- items 371 and 372
 
371. Prevent save_stack conflicts, e.g. in {\hbox\expandafter{\csname
     \endcsname}} (found by Sullivan).
@x module 645
@p procedure scan_spec; {scans a box specification and left brace}
label found;
begin if scan_keyword("to") then saved(0):=exactly
@.to@>
else if scan_keyword("spread") then saved(0):=additional
@.spread@>
else  begin saved(0):=additional; saved(1):=0;
  goto found;
  end;
scan_normal_dimen; saved(1):=cur_val;
found: save_ptr:=save_ptr+2; scan_left_brace;
@y
@p procedure scan_spec(@!c:group_code;@!three_codes:boolean);
  {scans a box specification and left brace}
label found;
var @!s:integer; {temporarily saved value}
@!spec_code:exactly..additional;
begin if three_codes then s:=saved(0);
if scan_keyword("to") then spec_code:=exactly
@.to@>
else if scan_keyword("spread") then spec_code:=additional
@.spread@>
else  begin spec_code:=additional; cur_val:=0;
  goto found;
  end;
scan_normal_dimen;
found: if three_codes then
  begin saved(0):=s; incr(save_ptr);
  end;
saved(0):=spec_code; saved(1):=cur_val; save_ptr:=save_ptr+2;
new_save_level(c); scan_left_brace;
@z
@x module 774
scan_spec; new_save_level(align_group);@/
@y
scan_spec(align_group,false);@/
@z
@x module 1073
  if t=0 then saved(0):=cur_val@+else saved(0):=-cur_val;
  scan_box;
  end;
any_mode(leader_ship): begin saved(0):=leader_flag-a_leaders+cur_chr; scan_box;
  end;
any_mode(make_box): begin saved(0):=0; begin_box;
  end;
@y
  if t=0 then scan_box(cur_val)@+else scan_box(-cur_val);
  end;
any_mode(leader_ship): scan_box(leader_flag-a_leaders+cur_chr);
any_mode(make_box): begin_box(0);
@z
@x module 1075
procedure box_end;
var p:pointer; {|ord_noad| for new box in math mode}
begin if saved(0)<box_flag then @<Append box |cur_box| to the current list,
    shifted by |saved(0)|@>
else if saved(0)<ship_out_flag then @<Store \(c)|cur_box| in a box register@>
else if cur_box<>null then
  if saved(0)>ship_out_flag then @<Append a new leader node that
      uses |cur_box|@>
@y
procedure box_end(@!box_context:integer);
var p:pointer; {|ord_noad| for new box in math mode}
begin if box_context<box_flag then @<Append box |cur_box| to the current list,
    shifted by |box_context|@>
else if box_context<ship_out_flag then @<Store \(c)|cur_box| in a box register@>
else if cur_box<>null then
  if box_context>ship_out_flag then @<Append a new leader node that
      uses |cur_box|@>
@z
@x module 1076
  begin shift_amount(cur_box):=saved(0);
@y
  begin shift_amount(cur_box):=box_context;
@z
@x module 1077
if saved(0)<box_flag+256 then
  eq_define(box_base-box_flag+saved(0),box_ref,cur_box)
else geq_define(box_base-box_flag-256+saved(0),box_ref,cur_box)
@y
if box_context<box_flag+256 then
  eq_define(box_base-box_flag+box_context,box_ref,cur_box)
else geq_define(box_base-box_flag-256+box_context,box_ref,cur_box)
@z
@x module 1078
  begin append_glue; subtype(tail):=saved(0)-(leader_flag-a_leaders);
@y
  begin append_glue; subtype(tail):=box_context-(leader_flag-a_leaders);
@z
@x module 1079
procedure begin_box;
@y
procedure begin_box(@!box_context:integer);
@z
@x ibid
box_end; {in simple cases, we use the box immediately}
@y
box_end(box_context); {in simple cases, we use the box immediately}
@z
@x module 1083
incr(save_ptr); scan_spec;
if k=hmode then
  if (saved(-3)<box_flag)and(abs(mode)=vmode) then
    new_save_level(adjusted_hbox_group)
  else new_save_level(hbox_group)
else  begin if k=vmode then new_save_level(vbox_group)
  else  begin new_save_level(vtop_group); k:=vmode;
@y
saved(0):=box_context;
if k=hmode then
  if (box_context<box_flag)and(abs(mode)=vmode) then
    scan_spec(adjusted_hbox_group,true)
  else scan_spec(hbox_group,true)
else  begin if k=vmode then scan_spec(vbox_group,true)
  else  begin scan_spec(vtop_group,true); k:=vmode;
@z
@x module 1084
procedure scan_box; {the next input should specify a box or perhaps a rule}
begin @<Get the next non-blank non-relax...@>;
if cur_cmd=make_box then begin_box
else if (saved(0)>=leader_flag)and((cur_cmd=hrule)or(cur_cmd=vrule)) then
  begin cur_box:=scan_rule_spec; box_end;
@y
procedure scan_box(@!box_context:integer);
  {the next input should specify a box or perhaps a rule}
begin @<Get the next non-blank non-relax...@>;
if cur_cmd=make_box then begin_box(box_context)
else if (box_context>=leader_flag)and((cur_cmd=hrule)or(cur_cmd=vrule)) then
  begin cur_box:=scan_rule_spec; box_end(box_context);
@z
@x module 1086
pop_nest; box_end;
@y
pop_nest; box_end(saved(0));
@z
@x module 1117
else  begin incr(save_ptr); saved(-1):=0; scan_left_brace;
  new_save_level(disc_group); push_nest; mode:=-hmode; space_factor:=1000;
@y
else  begin incr(save_ptr); saved(-1):=0; new_save_level(disc_group);
  scan_left_brace; push_nest; mode:=-hmode; space_factor:=1000;
@z
@x module 1119
incr(saved(-1)); scan_left_brace; new_save_level(disc_group);
@y
incr(saved(-1)); new_save_level(disc_group); scan_left_brace;
@z
@x module 1167
mmode+vcenter: begin scan_spec; new_save_level(vcenter_group); normal_paragraph;
@y
mmode+vcenter: begin scan_spec(vcenter_group,false); normal_paragraph;
@z
@x module 1172
scan_left_brace; push_math(math_choice_group);
@y
push_math(math_choice_group); scan_left_brace;
@z
@x module 1174
incr(saved(-1)); scan_left_brace; push_math(math_choice_group);
@y
incr(saved(-1)); push_math(math_choice_group); scan_left_brace;
@z
@x module 1241
  if global then saved(0):=box_flag+256+cur_val
  else saved(0):=box_flag+cur_val;
  scan_optional_equals; scan_box;
@y
  if global then n:=256+cur_val@+else n:=cur_val;
  scan_optional_equals; scan_box(box_flag+n);
@z
 
372. Bugfix 339 didn't go far enough (found by Sch\"opf and Mittelbach).
@x module 516 [NOTE: THIS AFFECTS ALMOST ALL CHANGE FILES!]
@ And here's the second.
@^system dependencies@>
 
@p function more_name(@!c:ASCII_code):boolean;
begin if c=" " then more_name:=false
else  begin if (c=">")or(c=":") then
    begin area_delimiter:=pool_ptr; ext_delimiter:=0;
    end
  else if (c=".")and(ext_delimiter=0) then ext_delimiter:=pool_ptr;
  str_room(1); append_char(c); {contribute |c| to the current string}
@y
@ And here's the second. The string pool might change as the file name is
being scanned, since a new \.{\\csname} might be entered; therefore we keep
|area_delimiter| and |ext_delimiter| relative to the beginning of the current
string, instead of assigning an absolute address like |pool_ptr| to them.
@^system dependencies@>
 
@p function more_name(@!c:ASCII_code):boolean;
begin if c=" " then more_name:=false
else  begin str_room(1); append_char(c); {contribute |c| to the current string}
  if (c=">")or(c=":") then
    begin area_delimiter:=cur_length; ext_delimiter:=0;
    end
  else if (c=".")and(ext_delimiter=0) then ext_delimiter:=cur_length;
@z
@x module 517 [NOTE: THIS DOES TOO, AND SO DOES THE NEXT!]
else  begin cur_area:=str_ptr; incr(str_ptr);
  str_start[str_ptr]:=area_delimiter+1;
@y
else  begin cur_area:=str_ptr;
  str_start[str_ptr+1]:=str_start[str_ptr]+area_delimiter; incr(str_ptr);
@z
@x ibid
else  begin cur_name:=str_ptr; incr(str_ptr);
  str_start[str_ptr]:=ext_delimiter; cur_ext:=make_string;
@y
else  begin cur_name:=str_ptr;
  str_start[str_ptr+1]:=str_start[str_ptr]+ext_delimiter-area_delimiter-1;
  incr(str_ptr); cur_ext:=make_string;
@z
 
 
########################################################################
 
Change to TEX.WEB (and others) to avoid dropped E in identifiers
 
@x [0] (AMS) Avoid dropped E in identifiers other than TeX
\def\drop{\kern-.1667em\lower.5ex\hbox{E}\kern-.125em} % middle of TeX
\catcode`E=13 \uppercase{\def E{e}}
\def\\#1{\hbox{\let E=\drop\it#1\/\kern.05em}} % italic type for identifiers
@y
\def\drop{\kern-.1667em\lower.5ex\hbox{E}\kern-.125em} % middle of TeX
\def\cape#1{\if X#1\drop\else E\fi#1} % other E's in identifiers
\catcode`E=13 \uppercase{\def E{e}}
\def\\#1{\hbox{\let E=\cape\it#1\/\kern.05em}} % italic type for identifiers
@z
 
 
########################################################################
 
% List of changed modules in MF.WEB 1.8 (as compared to 1.7)
% Peter Breitenlohner
 
% The [c]'s refer to the parts of the book `METAFONT: The Program'
 
limbo
[1] m.2, 11, 14
[2] m.17-19, 21-23
[3] m.30, 34
[4] m.37-38, 41, 45, 47-49
[5] m.59-60, 66
[6] m.85
[10] m.158-159
[12] m.186, 190, 192-193, 198-199
[13] m.200, 210-212
[14] m.223
[21] m.402, 410-412, 415-417, 422, 424-425, 436
[24] m.493
[35] m.717
[38] m.774-775, 784
[41] m.883
[42] m.912-913, 976-977
[45] m.1093, 1096-1097, 1103-1112, 1134-1141
[47] m.1160
[48] m.1192-1193, 1195, 1200
[49] m.1204-1205
 
 
########################################################################
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%  Character code reference
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%                       Upper case letters: ABCDEFGHIJKLMNOPQRSTUVWXYZ
%                       Lower case letters: abcdefghijklmnopqrstuvwxyz
%                                   Digits: 0123456789
% Square, curly, angle braces, parentheses: [] {} <> ()
%           Backslash, slash, vertical bar: \ / |
%                              Punctuation: . ? ! , : ;
%          Underscore, hyphen, equals sign: _ - =
%                Quotes--right left double: ' ` "
%"at", "number" "dollar", "percent", "and": @ # $ % &
%           "hat", "star", "plus", "tilde": ^ * + ~
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
[ end of message 022 ]

355. String startup problems (Wayne Sullivan, 17 Jul 89)
@x module 31 (Warning: This affects most change files!)
        overflow("buffer size",buf_size);
@:TeX capacity exceeded buffer size}{\quad buffer size@>
@y
        @<Report overflow of the input buffer, and abort@>;
@z
@x module 35
consist of the remainder of the command line, after the part that invoked
\TeX.
@y
consist of the remainder of the command line, after the part that invoked
\TeX.
 
The first line is special also because it may be read before \TeX\ has
input a format file. In such cases, normal error messages cannot yet
be given. The following code uses concepts that will be explained later.
 
@<Report overflow of the input buffer, and abort@>=
if format_ident=0 then
  begin write_ln(term_out,'Buffer size exceeded!'); goto final_end;
@.Buffer size exceeded@>
  end
else begin cur_input.loc_field:=first; cur_input.limit_field:=last-1;
  overflow("buffer size",buf_size);
@:TeX capacity exceeded buffer size}{\quad buffer size@>
  end
@z
@x module 1310
k:=pool_ptr-4; undump_four_ASCII
@y
k:=pool_ptr-4; undump_four_ASCII;
init_str_ptr:=str_ptr; init_pool_ptr:=pool_ptr
@z
@x module 1332
tini@/
@y
init_str_ptr:=str_ptr; init_pool_ptr:=pool_ptr; fix_date_and_time;
tini@/
@z
@x module 1332
init_str_ptr:=str_ptr; init_pool_ptr:=pool_ptr;@/
@y
@z
 
356. Allow integer products to be 31 bits long (Mittelbach, 16 Aug 89)
@x module 105
@p function nx_plus_y(@!n:integer;@!x,@!y:scaled):scaled;
begin if n<0 then
  begin negate(x); negate(n);
  end;
if n=0 then nx_plus_y:=y
else if ((x<=(@'7777777777-y) div n)and(-x<=(@'7777777777+y) div n)) then
  nx_plus_y:=n*x+y
else  begin arith_error:=true; nx_plus_y:=0;
  end;
end;
@y
@d nx_plus_y(#)==mult_and_add(#,@'7777777777)
@d mult_integers(#)==mult_and_add(#,0,@'17777777777)
 
@p function mult_and_add(@!n:integer;@!x,@!y,@!max_answer:scaled):scaled;
begin if n<0 then
  begin negate(x); negate(n);
  end;
if n=0 then mult_and_add:=y
else if ((x<=(max_answer-y) div n)and(-x<=(max_answer+y) div n)) then
  mult_and_add:=n*x+y
else  begin arith_error:=true; mult_and_add:=0;
  end;
end;
@z
@x module 1240
  if q=multiply then cur_val:=nx_plus_y(eqtb[l].int,cur_val,0)
@y
  if q=multiply then
    if p=int_val then cur_val:=mult_integers(eqtb[l].int,cur_val)
    else cur_val:=nx_plus_y(eqtb[l].int,cur_val,0)
@z
 
357. Raise the maximum number of tokens normally shown (pointed out by
John Lavagnino in TeXhax on 19 May 89; corrected on 31 Aug 89).
@x module 295
begin if p<>null then show_token_list(link(p),null,1000);
@y
begin if p<>null then show_token_list(link(p),null,10000000);
@z
@x module 1370 (this part of the change is cosmetic only)
show_token_list(link(def_ref),null,10000000); print_ln;
@y
token_show(def_ref); print_ln;
@z
 
358. Like 353, but with $$\begingroup\eqno$$ (Mittelbach, 25 Jul 89)
@x module 1140
mmode+eq_no: if privileged then start_eq_no;
@y
mmode+eq_no: if privileged then
  if cur_group=math_shift_group then start_eq_no
  else off_save;
@z
 
359. Major extension to 8-bit character input. (These changes, and a few that
follow, were discussed during the 10th TUG meeting and installed during
the week of 10 Sep 1989. They bring the implementation of TeX82 up to date with
international developments and experience, and I believe they mark the
final development of this program into a completely stable system.)
Several changes to the documentation (not the program) are omitted here.
@x module 18
@!ASCII_code=0..127; {seven-bit numbers}
@y
@!ASCII_code=0..255; {eight-bit numbers}
@z
@x module 19
@d last_text_char=127 {ordinal number of the largest element of |text_char|}
 
@<Local variables for init...@>=
@!i:0..last_text_char;
@y
@d last_text_char=255 {ordinal number of the largest element of |text_char|}
 
@<Local variables for init...@>=
@!i:integer;
@z
@x module 21
xchr[0]:=' '; xchr[@'177]:=' ';
  {ASCII codes 0 and |@'177| do not appear in text}
@y
@z
@x module 23
for i:=1 to @'37 do xchr[i]:=' ';
@y changing ' ' to chr(i) here will allow all 8-bit characters to get in
for i:=0 to @'37 do xchr[i]:=' ';
for i:=@'177 to @'377 do xchr[i]:=' ';
@z
@x module 24
for i:=1 to @'176 do xord[xchr[i]]:=i;
@y
for i:=@'200 to @'377 do xord[xchr[i]]:=i;
for i:=0 to @'176 do xord[xchr[i]]:=i;
@z
@x module 38
@<Types...@>=
@!pool_pointer = 0..pool_size; {for variables that point into |str_pool|}
@!str_number = 0..max_strings; {for variables that point into |str_start|}
 
@ @<Glob...@>=
@!str_pool:packed array[pool_pointer] of ASCII_code; {the characters}
@y [OK to make si(#)==#-128 and so(#)==#+128 (without parens) in change files]
Some \PASCAL\ compilers won't pack integers into a single byte unless the
integers lie in the range |-128..127|. To accommodate such systems
we access the string pool only via macros that can easily be redefined.
 
@d si(#) == # {convert from |ASCII_code| to |packed_ASCII_code|}
@d so(#) == # {convert from |packed_ASCII_code| to |ASCII_code|}
 
@<Types...@>=
@!pool_pointer = 0..pool_size; {for variables that point into |str_pool|}
@!str_number = 0..max_strings; {for variables that point into |str_start|}
@!packed_ASCII_code = 0..255; {elements of |str_pool| array}
 
@ @<Glob...@>=
@!str_pool:packed array[pool_pointer] of packed_ASCII_code; {the characters}
@z
@x module 42
begin str_pool[pool_ptr]:=#; incr(pool_ptr);
@y
begin str_pool[pool_ptr]:=si(#); incr(pool_ptr);
@z
@x module 45
  begin if str_pool[j]<>buffer[k] then
@y
  begin if so(str_pool[j])<>buffer[k] then
@z
@x module 47
var k,@!l:0..127; {small indices or counters}
@y
var k,@!l:0..255; {small indices or counters}
@z
@x module 47
@<Make the first 128 strings@>;
@y
@<Make the first 256 strings@>;
@z
@x module 48
@ @<Make the first 128...@>=
for k:=0 to 127 do
  begin if (@<Character |k| cannot be printed@>) then
    begin append_char("^"); append_char("^");
    if k<@'100 then append_char(k+@'100)
    else append_char(k-@'100);
@y
@ @d app_lc_hex(#)==l:=#;
  if l<10 then append_char(l+"0)@+else append_char(l-10+"a")
 
@<Make the first 256...@>=
for k:=0 to 255 do
  begin if (@<Character |k| cannot be printed@>) then
    begin append_char("^"); append_char("^");
    if k<@'100 then append_char(k+@'100)
    else if k<@'200 then append_char(k-@'100)
    else begin app_lc_hex(k div 16); app_lc_hex(k mod 16);
      end;
@z
@x module 59
  begin print_char(str_pool[j]); incr(j);
@y
  begin print_char(so(str_pool[j])); incr(j);
@z
@x modules 59 and 60
else if s<128 then
@y
else if s<256 then
@z
@x module 60
  begin print(str_pool[j]); incr(j);
@y
  begin print(so(str_pool[j])); incr(j);
@z
@x module 63
if c>=0 then if c<128 then print(c);
@y
if c>=0 then if c<256 then print(c);
@z
@x module 68
@ In certain situations, \TeX\ prints either a standard visible ASCII
character or its hexadecimal ASCII code.
 
@p procedure print_ASCII(@!c:integer); {prints a character or its code}
begin if (c>=0) and (c<=127) then print(c)
else  begin print_char("[");
  if c<0 then print_int(c)@+else print_hex(c);
  print_char("]");
  end;
end;
@y
@ Old versions of \TeX\ needed a procedure called |print_ASCII| whose function
is now subsumed by |print|. We retain the old name here as a possible aid to
future software arch\ae ologists.
 
@d print_ASCII == print
@z
@x module 69
    begin print_char(str_pool[j]); n:=n-v;
    end;
  if n<=0 then return; {nonpositive input produces no output}
  k:=j+2; u:=v div (str_pool[k-1]-"0");
  if str_pool[k-1]="2" then
    begin k:=k+2; u:=u div (str_pool[k-1]-"0");
    end;
  if n+u>=v then
    begin print_char(str_pool[k]); n:=n+u;
    end
  else  begin j:=j+2; v:=v div (str_pool[j-1]-"0");
@y
    begin print_char(so(str_pool[j])); n:=n-v;
    end;
  if n<=0 then return; {nonpositive input produces no output}
  k:=j+2; u:=v div (so(str_pool[k-1])-"0");
  if str_pool[k-1]=si("2") then
    begin k:=k+2; u:=u div (so(str_pool[k-1])-"0");
    end;
  if n+u>=v then
    begin print_char(so(str_pool[k])); n:=n+u;
    end
  else  begin j:=j+2; v:=v div (so(str_pool[j-1])-"0");
@z
@x module 70
  begin print_char(str_pool[j]); incr(j);
@y
  begin print_char(so(str_pool[j])); incr(j);
@z
@x module 222
@d single_base=active_base+128 {equivalents of one-letter control sequences}
@d null_cs=single_base+128 {equivalent of \.{\\csname\\endcsname}}
@y
@d single_base=active_base+256 {equivalents of one-character control sequences}
@d null_cs=single_base+256 {equivalent of \.{\\csname\\endcsname}}
@z
@x module 230
  {table of 128 command codes (the ``catcodes'')}
@d lc_code_base=cat_code_base+128 {table of 128 lowercase mappings}
@d uc_code_base=lc_code_base+128 {table of 128 uppercase mappings}
@d sf_code_base=uc_code_base+128 {table of 128 spacefactor mappings}
@d math_code_base=sf_code_base+128 {table of 128 math mode mappings}
@d int_base=math_code_base+128 {beginning of region 5}
@y
  {table of 256 command codes (the ``catcodes'')}
@d lc_code_base=cat_code_base+256 {table of 256 lowercase mappings}
@d uc_code_base=lc_code_base+256 {table of 256 uppercase mappings}
@d sf_code_base=uc_code_base+256 {table of 256 spacefactor mappings}
@d math_code_base=sf_code_base+256 {table of 256 math mode mappings}
@d int_base=math_code_base+256 {beginning of region 5}
@z
@x module 232
for k:=0 to 127 do
@y
for k:=0 to 255 do
@z
@x module 236
@d del_code_base=count_base+256 {128 delimiter code mappings}
@d dimen_base=del_code_base+128 {beginning of region 6}
@y
@d del_code_base=count_base+256 {256 delimiter code mappings}
@d dimen_base=del_code_base+256 {beginning of region 6}
@z
@x module 240
for k:=0 to 127 do del_code(k):=-1;
@y
for k:=0 to 255 do del_code(k):=-1;
@z
@x module 264
begin if s<128 then cur_val:=s+single_base
@y
begin if s<256 then cur_val:=s+single_base
@z
@x ibid
  for j:=0 to l-1 do buffer[j]:=str_pool[k+j];
@y
  for j:=0 to l-1 do buffer[j]:=so(str_pool[k+j]);
@z
@x module 289
@d cs_token_flag==@'10000 {amount added to the |eqtb| location in a
  token that stands for a control sequence; is a multiple of~256}
@y
@d cs_token_flag==@'7777 {amount added to the |eqtb| location in a
  token that stands for a control sequence; is a multiple of~256, less~1}
@z
@x module 293
  if (info(p)<0)or(c>127) then print_esc("BAD.")
@y
  if info(p)<0 then print_esc("BAD.")
@z
@x module 341
@!cat:0..15; {|cat_code(cur_chr)|, usually}
@y
@!cat:0..15; {|cat_code(cur_chr)|, usually}
@!c,@!cc:ASCII_code; {constituents of a possible expanded code}
@!d:2..3; {number of excess characters in an expanded code}
@z
@x module 352
@<If this |sup_mark| starts a control character...@>=
begin if (cur_chr=buffer[loc])and(loc<limit) then
  begin if buffer[loc+1]<@'100 then cur_chr:=buffer[loc+1]+@'100
  else cur_chr:=buffer[loc+1]-@'100;
  loc:=loc+2; goto reswitch;
  end;
@y
@ Notice that a code like \.{\^\^8} becomes \.x if not followed by a hex digit.
 
@d is_hex(#)==(((#>="0")and(#<="9"))or((#>="a")and(#<="f")))
@d hex_to_cur_chr==
  if c<="9" then cur_chr:=c-"0" @+else cur_chr:=c-"a"+10;
  if cc<="9" then cur_chr:=16*cur_chr+cc-"0"
  else cur_chr:=16*cur_chr+cc-"a"+10
 
@<If this |sup_mark| starts an expanded character...@>=
begin if cur_chr=buffer[loc] then if loc<limit then
  begin c:=buffer[loc+1]; @+if c<@'200 then {yes we have an expanded char}
    begin loc:=loc+2;
    if is_hex(c) then if loc<=limit then
      begin cc:=buffer[loc]; @+if is_hex(cc) then
        begin incr(loc); hex_to_cur_chr; goto reswitch;
        end;
      end;
    if c<@'100 then cur_chr:=c+@'100 @+else cur_chr:=c-@'100;
    goto reswitch;
    end;
  end;
@z
@x module 355
  begin cur_chr:=buffer[k+1];
  if cur_chr<@'100 then buffer[k-1]:=cur_chr+@'100
  else buffer[k-1]:=cur_chr-@'100;
  limit:=limit-2; first:=first-2;
  while k<=limit do
    begin buffer[k]:=buffer[k+2]; incr(k);
    end;
  goto start_cs;
  end;
@y
begin c:=buffer[k+1]; @+if c<@'200 then {yes, one is indeed present}
    begin d:=2;
    if is_hex(c) then @+if k+2<=limit then
      begin cc:=buffer[k+2]; @+if is_hex(cc) then incr(d);
      end;
    if d>2 then
      begin hex_to_cur_chr; buffer[k-1]:=cur_chr;
      end
    else if c<@'100 then buffer[k-1]:=c+@'100
    else buffer[k-1]:=c-@'100;
    limit:=limit-d; first:=first-d;
    while k<=limit do
      begin buffer[k]:=buffer[k+d]; incr(k);
      end;
    goto start_cs;
    end;
  end;
@z
@x module 360
There is one more branch.
@y
There is one more branch.
 
@d end_line_char_inactive == (end_line_char<0)or(end_line_char>255)
@z
@x ibid
    if (end_line_char<0)or(end_line_char>127) then decr(limit)
@y
    if end_line_char_inactive then decr(limit)
@z
@x modules 362 and 483 and 538 and 1337
if (end_line_char<0)or(end_line_char>127) then decr(limit)
@y
if end_line_char_inactive then decr(limit)
@z
@x module 391
if (info(r)>match_token+127)or(info(r)<match_token) then s:=null
@y
if (info(r)>match_token+255)or(info(r)<match_token) then s:=null
@z
@x module 407
   ((cur_chr=str_pool[k])or(cur_chr=str_pool[k]-"a"+"A")) then
@y
   ((cur_chr=so(str_pool[k]))or(cur_chr=so(str_pool[k])-"a"+"A")) then
@z
@x module 414
begin scan_seven_bit_int;
@y
begin scan_char_num;
@z
@x module 432
|scan_something_internal|:
 
@<Declare procedures that scan restricted classes of integers@>=
procedure scan_seven_bit_int;
begin scan_int;
if (cur_val<0)or(cur_val>127) then
  begin print_err("Bad character code");
@.Bad character code@>
  help2("The numeric code for a character must be between 0 and 127.")@/
    ("I changed this one to zero."); int_error(cur_val); cur_val:=0;
  end;
end;
@y
|scan_something_internal|.
@z (and I'll interchange modules 434 and 435)
@x module 442
if cur_val>127 then
@y
if cur_val>255 then
@z
@x module 464
  begin t:=str_pool[k];
@y
  begin t:=so(str_pool[k]);
@z
@x module 506 (correction needed twice)
if (cur_cmd>active_char)or(cur_chr>127) then
@y
if (cur_cmd>active_char)or(cur_chr>255) then
@z
@x module 519
for j:=str_start[a] to str_start[a+1]-1 do append_to_name(str_pool[j]);
for j:=str_start[n] to str_start[n+1]-1 do append_to_name(str_pool[j]);
for j:=str_start[e] to str_start[e+1]-1 do append_to_name(str_pool[j]);
@y
for j:=str_start[a] to str_start[a+1]-1 do append_to_name(so(str_pool[j]));
for j:=str_start[n] to str_start[n+1]-1 do append_to_name(so(str_pool[j]));
for j:=str_start[e] to str_start[e+1]-1 do append_to_name(so(str_pool[j]));
@z
@x module 526
if (cur_cmd>active_char)or(cur_chr>127) then
@y
if (cur_cmd>active_char)or(cur_chr>255) then
@z
@x module 603 (change to be made twice)
  dvi_out(str_pool[k]);
@y
  dvi_out(so(str_pool[k]));
@z
@x module 617
  for s:=str_start[str_ptr] to pool_ptr-1 do dvi_out(str_pool[s]);
@y
  for s:=str_start[str_ptr] to pool_ptr-1 do dvi_out(so(str_pool[s]));
@z
@x module 766
  begin case str_pool[r_type*8+t+magic_offset] of
@y
  begin case so(str_pool[r_type*8+t+magic_offset]) of
@z
@x module 780
@d span_code=128 {distinct from any character}
@d cr_code=129 {distinct from |span_code| and from any character}
@y
@d span_code=256 {distinct from any character}
@d cr_code=257 {distinct from |span_code| and from any character}
@z
@x module 815
label done,done1,done2,done3,done4;
@y
label done,done1,done2,done3,done4,continue;
@z
@x module 896
  else if (type(s)=kern_node)and(subtype(s)=normal) then c:=128
  else if type(s)=whatsit_node then c:=128
  else goto done1;
  if c<128 then if lc_code(c)<>0 then
    if (lc_code(c)=c)or(uc_hyph>0) then goto done2
    else goto done1;
  s:=link(s);
@y
  else if (type(s)=kern_node)and(subtype(s)=normal) then goto continue
  else if type(s)=whatsit_node then goto continue
  else goto done1;
  if lc_code(c)<>0 then
    if (lc_code(c)=c)or(uc_hyph>0) then goto done2
    else goto done1;
continue: s:=link(s);
@z
@x module 897
    if c>=128 then goto done3;
    if (lc_code(c)=0)or(hn=63) then goto done3;
    hb:=s; incr(hn); hu[hn]:=c; hc[hn]:=lc_code(c)-1;
@y
    if lc_code(c)=0 then goto done3;
    if hn=63 then goto done3;
    hb:=s; incr(hn); hu[hn]:=c; hc[hn]:=lc_code(c);
@z
@x module 898
if c>=128 then goto done3;
if (lc_code(c)=0)or(j=63) then goto done3;
incr(j); hu[j]:=c; hc[j]:=lc_code(c)-1;@/
@y
if lc_code(c)=0 then goto done3;
if j=63 then goto done3;
incr(j); hu[j]:=c; hc[j]:=lc_code(c);@/
@z
@x module 923
hc[0]:=127; hc[hn+1]:=127; hc[hn+2]:=128; {insert delimiters}
@y
hc[0]:=0; hc[hn+1]:=0; hc[hn+2]:=256; {insert delimiters}
@z
@x ibid
  while hc[l]=trie_char(z) do
@y
  while hc[l]=qo(trie_char(z)) do
@z
@x module 931
  repeat if str_pool[u]<hc[j] then goto not_found;
  if str_pool[u]>hc[j] then goto done;
@y
  repeat if so(str_pool[u])<hc[j] then goto not_found;
  if so(str_pool[u])>hc[j] then goto done;
@z
@x module 937
else  begin if (cur_chr>127)or(lc_code(cur_chr)=0) then
@y
else  begin if lc_code(cur_chr)=0 then
@z
@x ibid
    begin incr(n); hc[n]:=lc_code(cur_chr)-1;
@y
    begin incr(n); hc[n]:=lc_code(cur_chr);
@z
@x module 945
@!init @!trie_c:packed array[trie_pointer] of ASCII_code; {characters to match}
@y
@!init @!trie_c:packed array[trie_pointer] of packed_ASCII_code;
  {characters to match}
@z
% In modules 951--954, change "127" to "255" and "128" to "256"
@x module 952
trie_link(0):=0; trie_char(0):=0; trie_op(0):=min_quarterword;
@y
trie_link(0):=0; trie_char(0):=min_quarterword; trie_op(0):=min_quarterword;
@z
@x module 953
begin c:=trie_c[p];
@y
begin c:=so(trie_c[p]);
@z
@x module 955
  begin if trie_link(h+trie_c[q])=0 then goto not_found;
@y
  begin if trie_link(h+so(trie_c[q]))=0 then goto not_found;
@z
@x module 956
repeat z:=h+trie_c[q]; trie_back(trie_link(z)):=trie_back(z);
@y
repeat z:=h+so(trie_c[q]); trie_back(trie_link(z)):=trie_back(z);
@z
@x module 959
  begin q:=trie_l[p]; c:=trie_c[p];
  trie_link(z+c):=trie_ref[q]; trie_char(z+c):=c; trie_op(z+c):=trie_o[p];
@y
  begin q:=trie_l[p]; c:=so(trie_c[p]);
  trie_link(z+c):=trie_ref[q]; trie_char(z+c):=qi(c); trie_op(z+c):=trie_o[p];
@z
@x module 962
  begin if cur_chr="." then cur_chr:=128 {edge-of-word delimiter}
  else  begin cur_chr:=lc_code(cur_chr);
    if cur_chr=0 then
      begin print_err("Nonletter");
@.Nonletter@>
      help1("(See Appendix H.)"); error; cur_chr:=128;
      end;
    end;
  if k<63 then
    begin incr(k); hc[k]:=cur_chr-1; hyf[k]:=0; digit_sensed:=false;
    end;
  end
else  begin hyf[k]:=cur_chr-"0";
  if k<63 then digit_sensed:=true;
  end
@y
  begin if cur_chr="." then cur_chr:=0 {edge-of-word delimiter}
  else  begin cur_chr:=lc_code(cur_chr);
    if cur_chr=0 then
      begin print_err("Nonletter");
@.Nonletter@>
      help1("(See Appendix H.)"); error;
      end;
    end;
  if k<63 then
    begin incr(k); hc[k]:=cur_chr; hyf[k]:=0; digit_sensed:=false;
    end;
  end
  else if k<63 then
    begin hyf[k]:=cur_chr-"0"; digit_sensed:=true;
    end;
  end
@z
@x module 963
  while (p>0)and(c>trie_c[p]) do
@y
  while (p>0)and(c>so(trie_c[p])) do
@z
@x ibid
  if (p=0)or(c<trie_c[p]) then
@y
  if (p=0)or(c<so(trie_c[p])) then
@z
@x module 964
trie_c[p]:=c; trie_o[p]:=min_quarterword;
@y
trie_c[p]:=si(c); trie_o[p]:=min_quarterword;
@z
@x module 965
if hc[1]=127 then hyf[0]:=0;
if hc[k]=127 then hyf[k]:=0;
@y
if hc[1]=0 then hyf[0]:=0;
if hc[k]=0 then hyf[k]:=0;
@z
@x module 966
h.rh:=0; h.b0:=min_quarterword; h.b1:=0; {|trie_link:=0|,
  |trie_op:=min_quarterword|, |trie_char:=0|}
@y
h.rh:=0; h.b0:=min_quarterword; h.b1:=min_quarterword; {|trie_link:=0|,
  |trie_op:=min_quarterword|, |trie_char:=qi(0)|}
@z
@x module 1034
if c<128 then
@y
@z
@x ibid
else space_factor:=1000
@y
@z
@x module 1151
letter,other_char,char_given: if cur_chr>=128 then c:=cur_chr
  else  begin c:=ho(math_code(cur_chr));
@y
letter,other_char,char_given: begin c:=ho(math_code(cur_chr));
@z
@x module 1154
mmode+letter,mmode+other_char,mmode+char_given: if cur_chr<128 then
    set_math_char(ho(math_code(cur_chr)))
  else set_math_char(cur_chr);
mmode+char_num: begin scan_char_num; cur_chr:=cur_val;
  if cur_chr<128 then set_math_char(ho(math_code(cur_chr)))
  else set_math_char(cur_chr);
@y
mmode+letter,mmode+other_char,mmode+char_given:
  set_math_char(ho(math_code(cur_chr)));
mmode+char_num: begin scan_char_num; cur_chr:=cur_val;
  set_math_char(ho(math_code(cur_chr)));
@z
@x module 1232
  p:=cur_chr; scan_seven_bit_int; p:=p+cur_val; scan_optional_equals;
@y
  p:=cur_chr; scan_char_num; p:=p+cur_val; scan_optional_equals;
@z
@x module 1233
else n:=127
@y
else n:=255
@z
@x module 1289
  begin if t>=cs_token_flag then t:=t-active_base;
  c:=t mod 256;
  if c<128 then if equiv(b+c)<>0 then t:=256*(t div 256)+equiv(b+c);
  if t>=cs_token_flag then info(p):=t+active_base
  else info(p):=t;
@y
  begin c:=t mod 256;
  if equiv(b+c)<>0 then info(p):=t-c+equiv(b+c);
@z
@x module 1309
  w.b0:=str_pool[k]; w.b1:=str_pool[k+1];
  w.b2:=str_pool[k+2]; w.b3:=str_pool[k+3];
@y [often qi(so(x))=x, but not e.g. when "quarterwords" are two bytes]
  w.b0:=qi(so(str_pool[k])); w.b1:=qi(so(str_pool[k+1]));
  w.b2:=qi(so(str_pool[k+2])); w.b3:=qi(so(str_pool[k+3]));
@z
@x module 1310
  str_pool[k]:=w.b0; str_pool[k+1]:=w.b1;
  str_pool[k+2]:=w.b2; str_pool[k+3]:=w.b3
@y
  str_pool[k]:=si(qo(w.b0)); str_pool[k+1]:=si(qo(w.b1));
  str_pool[k+2]:=si(qo(w.b2)); str_pool[k+3]:=si(qo(w.b3))
@z
@x module 1368
for k:=str_start[str_ptr] to pool_ptr-1 do dvi_out(str_pool[k]);
@y
for k:=str_start[str_ptr] to pool_ptr-1 do dvi_out(so(str_pool[k]));
@z
 
360. Major change to allow multiple hyphenation tables.
I've combined this with the code for a minor change:
361. New parameters \lefthyphenmin and \righthyphenmin.
@x module 11 (affects most change files!)
@!trie_size=8000; {space for hyphenation patterns; should be larger for
  \.{INITEX} than it is in production versions of \TeX}
@y
@!trie_size=8000; {space for hyphenation patterns; should be larger for
  \.{INITEX} than it is in production versions of \TeX}
@!trie_op_size=500; {space for ``opcodes'' in the hyphenation patterns}
@z
@x module 212
known as |space_factor|; it holds the current space factor used in spacing
calculations. In math mode, |aux| is also known as |incompleat_noad|; if
@y
known as |space_factor| and |clang|; it holds the current space factor used in
spacing calculations, and the current language used for hyphenation.
(The value of |clang| is undefined in restricted horizontal mode.)
In math mode, |aux| is also known as |incompleat_noad|; if
@z
@x ibid
  @!pg_field,@!aux_field,@!ml_field: integer;
@y
  @!pg_field,@!ml_field: integer;
  @!aux_field: memory_word;
@z
@x module 213
@d prev_depth==aux {the name of |aux| in vertical mode}
@d space_factor==aux {the name of |aux| in horizontal mode}
@d incompleat_noad==aux {the name of |aux| in math mode}
@y
@d prev_depth==aux.sc {the name of |aux| in vertical mode}
@d space_factor==aux.hh.lh {part of |aux| in horizontal mode}
@d clang==aux.hh.rh {the other part of |aux| in horizontal mode}
@d incompleat_noad==aux.int {the name of |aux| in math mode}
@z
@x module 218
@!a:integer; {auxiliary}
@y
@!a:memory_word; {auxiliary}
@z
@x module 219
  if a<=ignore_depth then print("ignored")
  else print_scaled(a);
  if nest[p].pg_field<>0 then
    begin print(", prevgraf ");
    print_int(nest[p].pg_field); print(" line");
    if nest[p].pg_field<>1 then print_char("s");
    end;
  end;
1: begin print_nl("spacefactor "); print_int(a);
  end;
2: if a<>null then
  begin print("this will be denominator of:"); show_box(a);
@y
  if a.sc<=ignore_depth then print("ignored")
  else print_scaled(a.sc);
  if nest[p].pg_field<>0 then
    begin print(", prevgraf ");
    print_int(nest[p].pg_field); print(" line");
    if nest[p].pg_field<>1 then print_char("s");
    end;
  end;
1: begin print_nl("spacefactor "); print_int(a.hh.lh);
  if m>0 then if a.hh.rh>0 then
    begin print(", current language "); print_int(a.hh.rh);
    end;
  end;
2: if a.int<>null then
  begin print("this will be denominator of:"); show_box(a.int);
@z
@x modules 236--238
@d int_pars=50 {total number of integer parameters}
@y
@d language_code=50 {current hyphenation table}
@d left_hyphen_min_code=51 {minimum left hyphenation fragment size}
@d right_hyphen_min_code=52 {minimum right hyphenation fragment size}
@d int_pars=53 {total number of integer parameters}
@z and define/print these new parameters appropriately (mimicking new_line_char)
@x module 418
else begin cur_val:=aux;
  if m=vmode then cur_val_level:=dimen_val@+else cur_val_level:=int_val;
  end
@y
else if m=vmode then
  begin cur_val:=prev_depth; cur_val_level:=dimen_val;
  end
else begin cur_val:=space_factor; cur_val_level:=int_val;
  end
@z
@x module 775
  begin mode:=-vmode; prev_depth:=nest[nest_ptr-2].aux_field;
@y
  begin mode:=-vmode; prev_depth:=nest[nest_ptr-2].aux_field.sc;
@z
@x module 786
begin push_nest; mode:=(-hmode-vmode)-mode; aux:=0;
@y
begin push_nest; mode:=(-hmode-vmode)-mode;
if mode=-hmode then space_factor:=0 @+else prev_depth:=0;
@z
@x module 863
loop@+  begin @<Create an active breakpoint representing the beginning of
    the paragraph@>;
@y
loop@+  begin if threshold>inf_bad then threshold:=inf_bad;
  if second_pass then @<Initialize for hyphenating a paragraph@>;
  @<Create an active breakpoint representing the beginning of the paragraph@>;
@z
@x module 891
passes on only about 5 per cent of the paragraphs.
@y
passes on only about 5 per cent of the paragraphs.
 
@<Initialize for hyphenating...@>=
begin @!init if trie_not_ready then init_trie; @+tini@;@/
l_hyf:=left_hyphen_min-1;@+if l_hyf<0 then l_hyf:=0;
r_hyf:=right_hyphen_min-1;@+if r_hyf<0 then r_hyf:=0;
min_hyf:=l_hyf+r_hyf+2; cur_lang:=0;
end
@z
@x module 892
@!hyf_char:integer; {hyphen character of the relevant font}
@y
@!hyf_char:integer; {hyphen character of the relevant font}
@!cur_lang:ASCII_code; {current hyphenation table of interest}
@!l_hyf,@!r_hyf,@!min_hyf:integer; {limits on fragment sizes}
@z
@x module 894
begin s:=link(cur_p);
@y
begin if min_hyf>63 then goto done1;
s:=link(cur_p);
@z
@x module 896
  else if type(s)=whatsit_node then goto continue
@y
  else if type(s)=whatsit_node then
    begin @<Advance \(p)past a whatsit node in the \(p)pre-hyphenation loop@>;
    goto continue;
    end
@z
@x module 899 (title of this module also changes appropriately)
if hn<5 then goto done1;
@y
if hn<min_hyf then goto done1;
@z
@x module 902
for j:=2 to hn-3 do if odd(hyf[j]) then goto found1;
@y
for j:=l_hyf+1 to hn-r_hyf-1 do if odd(hyf[j]) then goto found1;
@z
@x module 920
$p_1\ldots p_k$ by setting |@t$z_1$@>:=@t$p_1$@>| and then, for |1<i<=k|,
@y
$p_1\ldots p_k$ by letting $z_0$ be one greater than the relevant language index
and then, for |1<=i<=k|,
@z
@x ibid
the letters in |hc[(l-k+1)..l@,]|, we perform all of the required operations
for this pattern by carrying out the following little program: Set
|v:=trie_op(@t$z_k$@>)|. Then set |hyf[l-hyf_distance[v]]:=@tmax@>(
hyf[l-hyf_distance[v]], hyf_num[v])|, and |v:=hyf_next[v]|; repeat, if
necessary, until |v=min_quarterword|.
@y
the letters in |hc[(l-k+1)..l@,]| of language |t|,
we perform all of the required operations
for this pattern by carrying out the following little program: Set
|v:=trie_op(@t$z_k$@>)|. Then set |v:=v+op_start[t]|,
|hyf[l-hyf_distance[v]]:=@tmax@>(hyf[l-hyf_distance[v]], hyf_num[v])|,
and |v:=hyf_next[v]|; repeat, if necessary, until |v=min_quarterword|.
@z
@x module 921
@!hyf_distance:array[quarterword] of small_number; {position |k-j| of $n_j$}
@!hyf_num:array[quarterword] of small_number; {value of $n_j$}
@!hyf_next:array[quarterword] of quarterword; {continuation of this |trie_op|}
@y
@!hyf_distance:array[1..trie_op_size] of small_number; {position |k-j| of $n_j$}
@!hyf_num:array[1..trie_op_size] of small_number; {value of $n_j$}
@!hyf_next:array[1..trie_op_size] of quarterword; {continuation code}
@!op_start:array[ASCII_code] of 0..trie_op_size; {offset for current language}
@z
@x module 922
@!v:quarterword; {an index into |hyf_distance|, etc.}
@y
@!v:integer; {an index into |hyf_distance|, etc.}
@z
@x module 923
for j:=0 to hn-2 do
  begin z:=hc[j]; l:=j;
@y
if trie_char(cur_lang+1)<>qi(cur_lang) then return; {no patterns for |cur_lang|}
for j:=0 to hn-r_hyf do
  begin z:=trie_link(cur_lang+1)+hc[j]; l:=j;
@z
@x ibid
found: hyf[1]:=0; hyf[hn-2]:=0; hyf[hn-1]:=0; hyf[hn]:=0
@y
found: for j:=0 to l_hyf do hyf[j]:=0;
for j:=0 to r_hyf do hyf[hn-j]:=0
@z
@x module 924
repeat i:=l-hyf_distance[v];
@y
repeat v:=v+op_start[cur_lang]; i:=l-hyf_distance[v];
@z
@x module 930
h:=hc[1];
@y
h:=hc[1]; incr(hn); hc[hn]:=cur_lang;
@z
@x ibid
not_found:
@y
not_found: decr(hn)
@z
@x module 931
  goto found;
@y
  decr(hn); goto found;
@z
@x module 934
@p procedure new_hyph_exceptions; {enters new exceptions}
label reswitch, exit, found, not_found, done;
@y
@d set_cur_lang==if language<=0 then cur_lang:=0
  else if language>255 then cur_lang:=0
  else cur_lang:=language
 
@p procedure new_hyph_exceptions; {enters new exceptions}
label reswitch, exit, found, not_found;
@z
@x ibid
begin scan_left_brace; {a left brace must follow \.{\\hyphenation}}
@y
begin scan_left_brace; {a left brace must follow \.{\\hyphenation}}
set_cur_lang;
@z
@x module 935
  spacer,right_brace: begin if n>4 then @<Enter a hyphenation exception@>;
@y
  spacer,right_brace: begin if n>1 then @<Enter a hyphenation exception@>;
@z
@x module 938
begin if n>1 then
@y
begin if n<63 then
@z
@x module 939
begin str_room(n); h:=0;
@y
begin incr(n); hc[n]:=cur_lang; str_room(n); h:=0;
@z
@x ibid
loop@+  begin if p=null then goto done;
  if info(p)<n-2 then goto done;
  q:=link(p); free_avail(p); p:=q; {eliminate hyphens that \TeX\ doesn't like}
  end;
done: @<Insert the \(p)pair |(s,p)| into the exception table@>;
@y
@<Insert the \(p)pair |(s,p)| into the exception table@>;
@z
@x module 942
@p@!init @<Declare procedures for preprocessing hyphenation patterns@>@;
@y
@<Declare subprocedures for |line_break|@>=
@!init @<Declare procedures for preprocessing hyphenation patterns@>@;
@z
@x module 943
@d quarterword_diff=max_quarterword-min_quarterword
@d trie_op_hash_size=quarterword_diff+quarterword_diff {double}
 
@<Glob...@>=
@!init@! trie_op_hash:array[0..trie_op_hash_size] of quarterword;
  {trie op codes for triples}
tini@;@/
@t\hskip1em@>@!trie_op_ptr:quarterword; {highest |trie_op| assigned}
@y
@<Glob...@>=
@!init@! trie_op_hash:array[-trie_op_size..trie_op_size] of 0..trie_op_size;
  {trie op codes for quadruples}
@!trie_used:array[ASCII_code] of quarterword;
  {largest opcode used so far for this language}
@!trie_op_lang:array[1..trie_op_size] of ASCII_code;
  {language part of a hashed quadruple}
@!trie_op_val:array[1..trie_op_size] of quarterword;
  {opcode corresponding to a hashed quadruple}
@!trie_op_ptr:0..trie_op_size; {number of stored ops so far}
tini
@z
@x module 944 should be entirely replaced
@y by the following code:
@ It's tempting to remove the |overflow| stops in the following procedure;
|new_trie_op| could return |min_quarterword| (thereby simply ignoring
part of a hyphenation pattern) instead of aborting the job. However, that would
lead to different hyphenation results on different installations of \TeX\
using the same patterns. The |overflow| stops are necessary for portability
of patterns.
 
@<Declare procedures for preprocessing hyph...@>=
function new_trie_op(@!d,@!n:small_number;@!v:quarterword):quarterword;
label exit;
var h:-trie_op_size..trie_op_size; {trial hash location}
@!u:quarterword; {trial op code}
@!l:0..trie_op_size; {pointer to stored data}
begin h:=abs(n+313*d+361*v+1009*cur_lang) mod (trie_op_size+trie_op_size)
  - trie_op_size;
loop@+  begin l:=trie_op_hash[h];
  if l=0 then {empty position found for a new op}
    begin if trie_op_ptr=trie_op_size then
      overflow("pattern memory ops",trie_op_size);
    u:=trie_used[cur_lang];
    if u=max_quarterword then
      overflow("pattern memory ops per language",
        max_quarterword-min_quarterword);
    incr(trie_op_ptr); incr(u); trie_used[cur_lang]:=u;
    hyf_distance[trie_op_ptr]:=d;
    hyf_num[trie_op_ptr]:=n; hyf_next[trie_op_ptr]:=v;
    trie_op_lang[trie_op_ptr]:=cur_lang; trie_op_hash[h]:=trie_op_ptr;
    trie_op_val[trie_op_ptr]:=u; new_trie_op:=u; return;
    end;
  if (hyf_distance[l]=d)and(hyf_num[l]=n)and(hyf_next[l]=v)
   and(trie_op_lang[l]=cur_lang) then
    begin new_trie_op:=trie_op_val[l]; return;
    end;
  if h>-trie_op_size then decr(h)@+else h:=trie_op_size;
  end;
exit:end;
@z
@x module 945: move the code from this module into 946, and introduce new code:
@y
@ After |new_trie_op| has compressed the necessary opcode information,
plenty of information is available to unscramble the data into the
final form needed by our hyphenation algorithm.
 
@<Sort the hyphenation op tables into proper order@>=
op_start[0]:=-min_quarterword;
for j:=1 to 255 do op_start[j]:=op_start[j-1]+qo(trie_used[j-1]);
for j:=1 to trie_op_ptr do
  trie_op_hash[j]:=op_start[trie_op_lang[j]]+trie_op_val[j]; {destination}
for j:=1 to trie_op_ptr do while trie_op_hash[j]>j do
  begin k:=trie_op_hash[j];@/
  t:=hyf_distance[k]; hyf_distance[k]:=hyf_distance[j]; hyf_distance[j]:=t;@/
  t:=hyf_num[k]; hyf_num[k]:=hyf_num[j]; hyf_num[j]:=t;@/
  t:=hyf_next[k]; hyf_next[k]:=hyf_next[j]; hyf_next[j]:=t;@/
  trie_op_hash[j]:=trie_op_hash[k]; trie_op_hash[k]:=k;
  end
@z
@x module 949: Move this code to just after the new module 945!
mentioned so far, let's write a procedure that does the initialization.
 
@<Declare procedures for preprocessing hyph...@>=
procedure init_pattern_memory; {gets ready to build a linked trie}
var h:0..trie_op_hash_size; {an index into |trie_op_hash|}
@!p:trie_pointer; {an index into |trie_hash|}
begin for h:=0 to trie_op_hash_size do trie_op_hash[h]:=min_quarterword;
trie_op_ptr:=min_quarterword; trie_root:=0; trie_c[0]:=0; trie_ptr:=0;
for p:=0 to trie_size do trie_hash[p]:=0;
end;
@y
mentioned so far, let's write down the code that gets them started.
 
@<Initialize table entries...@>=
for k:=-trie_op_size to trie_op_size do trie_op_hash[k]:=0;
for k:=0 to 255 do trie_used[k]:=min_quarterword;
trie_op_ptr:=0;
@z
@x module 950
@t\hskip1em@>@!trie_min:trie_pointer;
  {all locations |<=trie_min| are vacant in |trie|}
tini@;@/
@t\hskip1em@>@!trie_max:trie_pointer; {largest location used in |trie|}
@y
@t\hskip10pt@>@!trie_min:array[ASCII_code] of trie_pointer;
  {the first possible slot for each character}
@t\hskip10pt@>@!trie_max:trie_pointer; {largest location used in |trie|}
@t\hskip10pt@>@!trie_not_ready:boolean; {is the trie still in linked form?}
tini
@z
@x modules 951 and 952
@ Here is how these data structures are initialized.
 
@<Declare procedures for preprocessing hyph...@>=
procedure init_trie_memory; {gets ready to pack into |trie|}
var p:trie_pointer; {index into |trie_ref|, |trie|, |trie_taken|}
begin for p:=0 to trie_ptr do trie_ref[p]:=0;
trie_max:=256; trie_min:=256; trie_link(0):=1; trie_taken[0]:=false;
trie_link(trie_size):=0; trie_back(0):=trie_size; {wrap around}
for p:=1 to 256 do
  begin trie_back(p):=p-1; trie_link(p):=p+1; trie_taken[p]:=false;
  end;
end;
 
@ Each time \.{\\patterns} appears, it overrides any patterns that were
entered earlier, so the arrays are not initialized until \TeX\ sees
\.{\\patterns}. However, some of the global variables must be
initialized when \.{INITEX} is loaded, in case the user never mentions
any \.{\\patterns}.
 
@<Initialize table entries...@>=
trie_op_ptr:=min_quarterword;@/
trie_link(0):=0; trie_char(0):=min_quarterword; trie_op(0):=min_quarterword;
for k:=1 to 255 do trie[k]:=trie[0];
trie_max:=255;
@y
@ Each time \.{\\patterns} appears, it contributes further patterns to
the future trie, which will be built only when hyphenation is attempted or
when a format file is dumped. The boolean variable |trie_not_ready|
will change to |false| when the trie is compressed; this will disable
further patterns.
 
@<Initialize table entries...@>=
trie_not_ready:=true; trie_root:=0; trie_c[0]:=si(0); trie_ptr:=0;
 
@ Here is how the trie-compression data structures are initialized.
If storage is tight, it would be possible to overlap |trie_op_hash|,
|trie_op_lang|, and |trie_op_val| with |trie|, |trie_hash|, and |trie_taken|,
because we finish with the former just before we need the latter.
 
@<Get ready to compress the trie@>=
@<Sort the hyphenation...@>;
for p:=0 to trie_size do trie_hash[p]:=0;
trie_root:=compress_trie(trie_root); {identify equivalent subtries}
for p:=0 to trie_ptr do trie_ref[p]:=0;
for p:=0 to 255 do trie_min[p]:=p+1;
trie_link(0):=1; trie_max:=0
@z
@x module 953
begin c:=so(trie_c[p]);
if c<trie_min then trie_min:=c;
if trie_min=0 then z:=trie_link(trie_size)
else z:=trie_link(trie_min-1); {get the first conceivably good hole}
loop@+  begin if z<c then goto not_found;
  h:=z-c;@/
@y
@!l,@!r:trie_pointer; {left and right neighbors}
@!ll:1..256; {upper limit of |trie_min| updating}
begin c:=so(trie_c[p]);
z:=trie_min[c]; {get the first conceivably good hole}
loop@+  begin h:=z-c;@/
@z
@x module 956
repeat z:=h+so(trie_c[q]); trie_back(trie_link(z)):=trie_back(z);
trie_link(trie_back(z)):=trie_link(z); trie_link(z):=0; q:=trie_r[q];
@y
repeat z:=h+so(trie_c[q]); l:=trie_back(z); r:=trie_link(z);
trie_back(r):=l; trie_link(l):=r; trie_link(z):=0;
if l<256 then
  begin if z<256 then ll:=z @+else ll:=256;
  repeat trie_min[l]:=r; incr(l);
  until l=ll;
  end;
q:=trie_r[q];
@z
@x module 958 is entirely replaced
@y by the following new code:
@ When the whole trie has been allocated into the sequential table, we
must go through it once again so that |trie| contains the correct
information. Null pointers in the linked trie will be represented by the
value~0, which properly implements an ``empty'' family.
 
@<Move the data into |trie|@>=
h.rh:=0; h.b0:=min_quarterword; h.b1:=min_quarterword; {|trie_link:=0|,
  |trie_op:=min_quarterword|, |trie_char:=qi(0)|}
if trie_root=0 then {no patterns were given}
  begin for r:=0 to 256 do trie[r]:=h;
  trie_max:=256;
  end
else begin trie_fix(trie_root); {this fixes the non-holes in |trie|}
  r:=0; {now we will zero out all the holes}
  repeat s:=trie_link(r); trie[r]:=h; r:=s;
  until r>trie_max;
  end;
trie_char(0):=qi("?"); {make |trie_char(c)<>c| for all |c|}
@z
@x module 960
@!r,@!s:trie_pointer; {used to clean up the packed |trie|}
@!h:two_halves; {template used to zero out |trie|'s holes}
begin scan_left_brace; {a left brace must follow \.{\\patterns}}
init_pattern_memory;@/
@<Enter all of the patterns into a linked trie, until coming to a right
  brace@>;
trie_root:=compress_trie(trie_root); {compress the trie}
@<Pack the trie@>;
end;
@y
begin if trie_not_ready then
  begin set_cur_lang; scan_left_brace; {a left brace must follow \.{\\patterns}}
  @<Enter all of the patterns into a linked trie, until coming to a right
  brace@>;
  end
else begin print_err("Too late for "); print_esc("patterns");
  help1("All patterns must be given before typesetting begins.");
  error; link(garbage):=scan_toks(false,false); flush_list(def_ref);
  end;
end;
@z
@x module 963
q:=0;
while l<k do
  begin incr(l); c:=hc[l]; p:=trie_l[q]; first_child:=true;
@y
q:=0; hc[0]:=cur_lang;
while l<=k do
  begin c:=hc[l]; incr(l); p:=trie_l[q]; first_child:=true;
@z
@x module 966 now says that the first call ``takes'' location 1,
@y and the Pascal code in this module is entirely replaced by the following:
@<Declare procedures for preprocessing hyphenation patterns@>=
procedure init_trie;
var @!p:trie_pointer; {pointer for initialization}
@!j,@!k,@!t:integer; {all-purpose registers for initialization}
@!r,@!s:trie_pointer; {used to clean up the packed |trie|}
@!h:two_halves; {template used to zero out |trie|'s holes}
begin @<Get ready to compress the trie@>;
if trie_root<>0 then
  begin first_fit(trie_root); trie_pack(trie_root);
  end;
@<Move the data into |trie|@>;
trie_not_ready:=false;
end;
@z
@x module 1033 gets new code before main_loop_1
@y
if mode>0 then if language<>clang then fix_language;
@z
@x modules 1091 and 1200
push_nest; mode:=hmode; space_factor:=1000;
@y
push_nest; mode:=hmode; space_factor:=1000; clang:=0;
@z
@x module 1324
for k:=0 to trie_max do dump_hh(trie[k]);
dump_int(trie_op_ptr);
for k:=min_quarterword+1 to trie_op_ptr do
  begin dump_int(hyf_distance[k]);
  dump_int(hyf_num[k]);
  dump_int(hyf_next[k]);
  end;
print_ln; print_int(hyph_count); print(" hyphenation exception");
if hyph_count<>1 then print_char("s");
print_nl("Hyphenation trie of length "); print_int(trie_max);
@.Hyphenation trie...@>
print(" has "); print_int(qo(trie_op_ptr)); print(" op");
if trie_op_ptr<>min_quarterword+1 then print_char("s")
@y
print_ln; print_int(hyph_count); print(" hyphenation exception");
if hyph_count<>1 then print_char("s");
if trie_not_ready then init_trie;
dump_int(trie_max);
for k:=0 to trie_max do dump_hh(trie[k]);
dump_int(trie_op_ptr);
for k:=1 to trie_op_ptr do
  begin dump_int(hyf_distance[k]);
  dump_int(hyf_num[k]);
  dump_int(hyf_next[k]);
  end;
print_nl("Hyphenation trie of length "); print_int(trie_max);
@.Hyphenation trie...@>
print(" has "); print_int(trie_op_ptr); print(" op");
if trie_op_ptr<>1 then print_char("s");
print(" out of "); print_int(trie_op_size);
for k:=255 downto 0 do if trie_used[k]>min_quarterword then
  begin print_nl("  "); print_int(qo(trie_used[k]));
  print(" for language "); print_int(k);
  dump_int(k); dump_int(qo(trie_used[k]));
  end
@z
@x module 1325
undump_size(0)(trie_size)('trie size')(trie_max);
for k:=0 to trie_max do undump_hh(trie[k]);
undump(min_quarterword)(max_quarterword)(trie_op_ptr);
for k:=min_quarterword+1 to trie_op_ptr do
  begin undump(0)(63)(hyf_distance[k]); {a |small_number|}
  undump(0)(63)(hyf_num[k]);
  undump(min_quarterword)(max_quarterword)(hyf_next[k]);
  end
@y
undump_size(0)(trie_size)('trie size')(j); {|trie_max|}
for k:=0 to j do undump_hh(trie[k]);
undump_size(0)(trie_op_size)('trie op size')(j); {|trie_op_ptr|}
for k:=1 to j do
  begin undump(0)(63)(hyf_distance[k]); {a |small_number|}
  undump(0)(63)(hyf_num[k]);
  undump(min_quarterword)(max_quarterword)(hyf_next[k]);
  end;
k:=256;
while j>0 do
  begin undump(0)(k-1)(k); undump(1)(j)(x); j:=j-x; op_start[k]:=qo(j);
  end;
@!init trie_not_ready:=false @+tini
@z
@x module 1341 gets two new definitions
@y
@d language_node=4 {|subtype| in whatsits that change the current language}
@d stored_language(#)==mem[#+1].int {language number, in the range |0..255|}
@z
@x module 1344 gets a new definition and a new Pascal statement
@y
@d set_language_code=5 {command modifier for \.{\\setlanguage}}
primitive("setlanguage",extension,set_language_code);@/
@!@:set_language_}{\.{\\setlanguage} primitive@>
@z
@x module 1346 gets a new case
@y
  set_language_code:print_esc("setlanguage");
@z
@x module 1348 gets a new case
@y
set_language_code:@<Implement \.{\\setlanguage}@>;
@z
@x module 1356 gets a new case
@y
language_node:begin print_esc("setlanguage");
  print_int(stored_language(p));
  end;
@z
% in modules 1357 and 1358, change "close_node" to "close_node,language_node".
@x module 1362 becomes two modules
@ @<Advance \(p)past a whatsit node in the |line_break| loop@>=do_nothing
@y
@ @<Advance \(p)past a whatsit node in the \(l)|line_break| loop@>=
if subtype(cur_p)=language_node then cur_lang:=stored_language(cur_p)
 
@ @<Advance \(p)past a whatsit node in the \(p)pre-hyphenation loop@>=
if subtype(s)=language_node then cur_lang:=stored_language(s)
@z and old module 1367 is moved to just before the old module 1378
@x module 1373 gets a new case
@y
language_node:do_nothing;
@z
@x new modules before the system-dependent changes (i.e. before the old 1376)
@y
@ @<Implement \.{\\setlanguage}@>=
if abs(mode)<>hmode then report_illegal_case
else begin new_whatsit(language_node,small_node_size);
  scan_int;
  if cur_val<=0 then clang:=0
  else if cur_val>255 then clang:=0
  else clang:=cur_val;
  stored_language(tail):=clang;
  end
 
@ Finally, we need a subroutine that comes into play when a character of
a non-|clang| language is being appended to the current paragraph.
 
@<Declare action...@>=
procedure fix_language;
var @!l:ASCII_code; {the new current language}
begin if language<=0 then l:=0
else if language>255 then l:=0
else l:=language;
if l<>clang then
  begin new_whatsit(language_node,small_node_size);
  stored_language(tail):=l; clang:=l;
  end;
end;
@z
 
362. Major extension to ligature capability.
@x module 143
a linked list of character nodes for those characters.
@y
a linked list of character nodes for all original characters that have been
deleted. (This list might be empty if the characters that generated the
ligature were retained in other nodes.)
 
The |subtype| field is 0, plus 2 and/or 1 if the original source of the
ligature included implicit left and/or right boundaries.
@z
@x in module 144 add the following procedure to the existing code
@y
function new_lig_item(@!c:quarterword):pointer;
var p:pointer; {the new node}
begin p:=get_node(small_node_size); character(p):=c; lig_ptr(p):=null;
new_lig_item:=p;
end;
@z
@x module 193
font_in_short_display:=font(lig_char(p));
short_display(lig_ptr(p)); print_char(")");
@y
if subtype(p)>1 then print_char("|");
font_in_short_display:=font(lig_char(p)); short_display(lig_ptr(p));
if odd(subtype(p)) then print_char("|");
print_char(")");
end
@z
@x modules 208 and 209
@d radical=65 {square root and similar signs ( \.{\\radical} )}
@y
@d no_boundary=65 {suppress boundary ligatures ( \.{\\noboundary} )}
@d radical=66 {square root and similar signs ( \.{\\radical} )}
@z and so on, adding 1 to each definition until getting to max_command=100
@x module 265 gets a new statement
@y
primitive("noboundary",no_boundary,0);@/
@!@:no_boundary_}{\.{\\noboundary} primitive@>
@z
@x module 266 gets a new case
@y
no_boundary:print_esc("noboundary");
@z
@x module 545 is entirely replaced
@y by the following new specifications:
@ The |lig_kern| array contains instructions in a simple programming language
that explains what to do for special letter pairs. Each word in this array is a
|@!lig_kern_command| of four bytes.
 
\yskip\hang first byte: |skip_byte|, indicates that this is the final program
  step if the byte is 128 or more, otherwise the next step is obtained by
  skipping this number of intervening steps.\par
\hang second byte: |next_char|, ``if |next_char| follows the current character,
  then perform the operation and stop, otherwise continue.''\par
\hang third byte: |op_byte|, indicates a ligature step if less than~128,
  a kern step otherwise.\par
\hang fourth byte: |remainder|.\par
\yskip\noindent
In a kern step, an
additional space equal to |kern[256*(op_byte-128)+remainder]| is inserted
between the current character and |next_char|. This amount is
often negative, so that the characters are brought closer together
by kerning; but it might be positive.
 
There are eight kinds of ligature steps, having |op_byte| codes $4a+2b+c$ where
$0\le a\le b+c$ and $0\le b,c\le1$. The character whose code is
|remainder| is inserted between the current character and |next_char|;
then the current character is deleted if $b=0$, and |next_char| is
deleted if $c=0$; then we pass over $a$~characters to reach the next
current character (which may have a ligature/kerning program of its own).
 
If the very first instruction of the |lig_kern| array has |skip_byte=255|,
the |next_char| byte is the so-called right boundary character of this font;
the value of |next_char| need not lie between |bc| and~|ec|.
If the very last instruction of the |lig_kern| array has |skip_byte=255|,
there is a special ligature/kerning program for a left boundary character,
beginning at location |256*op_byte+remainder|.
The interpretation is that \TeX\ puts implicit boundary characters
before and after each consecutive string of characters from the same font.
These implicit characters do not appear in the output, but they can affect
ligatures and kerning.
 
If the very first instruction of a character's |lig_kern| program has
|skip_byte>128|, the program actually begins in location
|256*op_byte+remainder|. This feature allows access to large |lig_kern|
arrays, because the first instruction must otherwise
appear in a location |<=255|.
 
Any instruction with |skip_byte>128| in the |lig_kern| array must have
|256*op_byte+remainder<nl|. If such an instruction is encountered during
normal program execution, it denotes an unconditional halt; no ligature
command is performed.
 
@d stop_flag=qi(128) {value indicating `\.{STOP}' in a lig/kern program}
@d kern_flag=qi(128) {op code for a kern step}
@d skip_byte(#)==#.b0
@d next_char(#)==#.b1
@d op_byte(#)==#.b2
@d rem_byte(#)==#.b3
@z
@x module 549
@<Glob...@>=
@y
@d non_char==qi(256) {a |halfword| code that can't match a real character}
@d non_address==font_mem_size {a spurious |font_index|}
 
@<Glob...@>=
@z
@x module 548 gets a new type definition
@y
@!font_index=0..font_mem_size;
@z
@x module 549
  {current \.{\\skewchar} values}
@y
  {current \.{\\skewchar} values}
@!bchar_label:array[internal_font_number] of font_index;
  {start of |lig_kern| program for left boundary character,
  |non_address| if there is none}
@!font_bchar:array[internal_font_number] of min_quarterword..non_char;
  {right boundary character, |non_char| if there is none}
@!font_false_bchar:array[internal_font_number] of min_quarterword..non_char;
  {|font_bchar| if it doesn't exist in the font, otherwise |non_char|}
@z
@x module 557
@d char_kern_end(#)==rem_byte(#)].sc
@y NOTE: Optimize kern_base_offset in your change file! It's a constant.
@d char_kern_end(#)==256*op_byte(#)+rem_byte(#)].sc
@d kern_base_offset==256*(kern_flag)
@d lig_kern_restart_end(#)==256*op_byte(#)+rem_byte(#)+32768-kern_base_offset
@d lig_kern_restart(#)==lig_kern_base[#]+lig_kern_restart_end
@z
@x module 560
@!z:scaled; {the design size or the ``at'' size}
@y
@!bch_label:integer; {left boundary start location, or infinity}
@!bchar:0..256; {right boundary character, or 256}
@!z:scaled; {the design size or the ``at'' size}
@z
@x module 566
kern_base[f]:=lig_kern_base[f]+nl;
exten_base[f]:=kern_base[f]+nk;
@y
kern_base[f]:=lig_kern_base[f]+nl-kern_base_offset;
exten_base[f]:=kern_base[f]+kern_base_offset+nk;
@z
@x module 573: The entire module is replaced
@y by the following code.
@ @d check_existence(#)==@t@>@;@/
  begin check_byte_range(#);
  qw:=char_info(f)(#); {N.B.: not |qi(#)|}
  if not char_exists(qw) then abort;
  end
 
@<Read ligature/kern program@>=
bch_label:=@'77777; bchar:=256;
if nl>0 then
  begin for k:=lig_kern_base[f] to kern_base[f]+kern_base_offset-1 do
    begin store_four_quarters(font_info[k].qqqq);
    if a>128 then
      begin if 256*c+d>=nl then abort;
      if a=255 then if k=lig_kern_base[f] then bchar:=b;
      end
    else begin if b<>bchar then check_existence(b);
      if c<128 then check_existence(d) {check ligature}
      else if 256*(c-128)+d>=nk then abort; {check kern}
      if a<128 then if k-lig_kern_base[f]+a+1>=nl then abort;
      end;
    end;
  if a=255 then bch_label:=256*c+d;
  end;
for k:=kern_base[f]+kern_base_offset to exten_base[f]-1 do
  store_scaled(font_info[k].sc);
@z
@x module 574 (actually a bugfix)
  if a<>0 then check_byte_range(a);
  if b<>0 then check_byte_range(b);
  if c<>0 then check_byte_range(c);
  check_byte_range(d);
@y
  if a<>0 then check_existence(a);
  if b<>0 then check_existence(b);
  if c<>0 then check_existence(c);
  check_existence(d);
@z
@x module 576
font_name[f]:=nom;
@y
if bch_label<nl then bchar_label[f]:=bch_label+lig_kern_base[f]
else bchar_label[f]:=non_address;
font_bchar[f]:=qi(bchar);
font_false_bchar[f]:=qi(bchar);
if bchar<=ec then if bchar>=bc then
  begin qw:=char_info(f)(bchar); {N.B.: not |qi(bchar)|}
  if char_exists(qw) then font_false_bchar[f]:=non_char;
  end;
font_name[f]:=nom;
@z
@x module 708 (slight but optional optimization)
continue: if (qo(y)>=font_bc[g])and(qo(y)<=font_ec[g]) then
  begin q:=char_info(g)(y);
@y
if (qo(y)>=font_bc[g])and(qo(y)<=font_ec[g]) then
  begin continue: q:=char_info(g)(y);
@z
@x module 740 (another bugfix)
  i:=char_info(f)(y);
@y
  i:=char_info(f)(y);
  if not char_exists(i) then goto done;
@z
@x module 741
    repeat cur_i:=font_info[a].qqqq;
    if qo(next_char(cur_i))=skew_char[cur_f] then
      begin if op_bit(cur_i)>=kern_flag then
        s:=char_kern(cur_f)(cur_i);
      goto done1;
      end;
    incr(a);
    until stop_bit(cur_i)>=stop_flag;
@y
    cur_i:=font_info[a].qqqq;
    if skip_byte(cur_i)>stop_flag then
      begin a:=256*(qo(op_byte(cur_i)))+rem_byte(cur_i);
      cur_i:=font_info[a].qqqq;
      end;
    loop begin if qo(next_char(cur_i))=skew_char[cur_f] then
        begin if op_byte(cur_i)>=kern_flag then
          if skip_byte(cur_i)<=stop_flag then s:=char_kern(cur_f)(cur_i);
        goto done1;
        end;
      if skip_byte(cur_i)>=stop_flag then goto done1
      else a:=a+qo(skip_byte(cur_i))+1;
      cur_i:=font_info[a].qqqq;
      end;
@z
@x module 749 (yet another bugfix of the same type!)
@!p,@!v,@!x,@!y,@!z:pointer; {temporary registers for box construction}
@y
@!p,@!v,@!x,@!y,@!z:pointer; {temporary registers for box construction}
@!c:quarterword;@+@!i:four_quarters; {registers for character examination}
@z
@x ibid
    begin cur_c:=rem_byte(cur_i); character(nucleus(q)):=cur_c;
    cur_i:=char_info(cur_f)(cur_c);
@y
    begin c:=rem_byte(cur_i); i:=char_info(cur_f)(c);
    if char_exists(i) then
      begin cur_c:=c; cur_i:=i; character(nucleus(q)):=c;
      end;
@z
@x module 752
@!p:pointer; {temporary register for list manipulation}
@y
@!p,@!r:pointer; {temporary registers for list manipulation}
@z
@x ibid
        repeat cur_i:=font_info[a].qqqq;@/
        @<If instruction |cur_i| is a kern with |cur_c|,
         attach the kern after |q| and |return|;
         or if it is a ligature with |cur_c|, combine
         noads |q| and |p| and |goto restart|@>;
        incr(a);
        until stop_bit(cur_i)>=stop_flag;
@y
        cur_i:=font_info[a].qqqq;
        if skip_byte(cur_i)>stop_flag then
          begin a:=lig_kern_restart(cur_f)(cur_i);
          cur_i:=font_info[a].qqqq;
          end;
        loop@+ begin @<If instruction |cur_i| is a kern with |cur_c|, attach
            the kern after~|q|; or if it is a ligature with |cur_c|, combine
            noads |q| and~|p| appropriately; then |return| if the cursor has
            moved past a noad, or |goto restart|@>;
          if skip_byte(cur_i)>=stop_flag then return;
          a:=a+qo(skip_byte(cur_i))+1;
          cur_i:=font_info[a].qqqq;
          end;
@z
@x The entire code of module 753 is revised
@y and should be replaced by the following:
@ Note that a ligature between an |ord_noad| and another kind of noad
is replaced by an |ord_noad|, when the two noads collapse into one.
But we could make a parenthesis (say) change shape when it follows
certain letters. Presumably a font designer will define such
ligatures only when this convention makes sense.
 
\chardef\?='174 % vertical line to indicate character retention
 
@<If instruction |cur_i| is a kern with |cur_c|, ...@>=
if next_char(cur_i)=cur_c then if skip_byte(cur_i)<=stop_flag then
  if op_byte(cur_i)>=kern_flag then
    begin p:=new_kern(char_kern(cur_f)(cur_i));
    link(p):=link(q); link(q):=p; return;
    end
  else  begin check_interrupt; {allow a way out of infinite ligature loop}
      case op_byte(cur_i) of
   qi(1),qi(5): character(nucleus(q)):=rem_byte(cur_i); {\.{=:\?}, \.{=:\?>}}
   qi(2),qi(6): character(nucleus(p)):=rem_byte(cur_i); {\.{\?=:}, \.{\?=:>}}
   qi(3),qi(7),qi(11):begin r:=new_noad; {\.{\?=:\?}, \.{\?=:\?>}, \.{\?=:\?>>}}
       character(nucleus(r)):=rem_byte(cur_i);
       fam(nucleus(r)):=fam(nucleus(q));@/
       link(q):=r; link(r):=p;
       if op_byte(cur_i)<qi(11) then math_type(nucleus(r)):=math_char
       else math_type(nucleus(r)):=math_text_char; {prevent combination}
       end;
     othercases begin link(q):=link(p);
       character(nucleus(q)):=rem_byte(cur_i); {\.{=:}}
       mem[subscr(q)]:=mem[subscr(p)]; mem[supscr(q)]:=mem[supscr(p)];@/
       free_node(p,noad_size);
       end
     endcases;
     if op_byte(cur_i)>qi(3) then return;
     math_type(nucleus(q)):=math_char; goto restart;
     end
@z
@x module 862
@!q,@!r,@!s:pointer; {miscellaneous nodes of temporary interest}
@y
@!q,@!r,@!s,@!prev_s:pointer; {miscellaneous nodes of temporary interest}
@z
@x module 892
nodes $p_a$ and~$p_b$ in the description above are placed into variables
@y
nodes $p_{a-1}$ and~$p_b$ in the description above are placed into variables
@z
@x ibid
@!hc:array[0..65] of halfword; {word to be hyphenated}
@y
@!hc:array[0..65] of 0..256; {word to be hyphenated}
@z
@x ibid
@!hu:array[1..63] of ASCII_code; {like |hc|, before conversion to lowercase}
@y
@!hu:array[0..63] of 0..256; {like |hc|, before conversion to lowercase}
@z
@x module 894
s:=link(cur_p);
@y
prev_s:=cur_p; s:=link(prev_s);
@z
@x module 895
label done,found,not_found,found1,exit;
@y
label common_ending,done,found,found1,not_found,not_found+1,exit;
@z
@x module 896
  else if type(s)=ligature_node then
    begin q:=lig_ptr(s); c:=qo(character(q)); hf:=font(q);
    end
@y
  else if type(s)=ligature_node then
    if lig_ptr(s)=null then goto continue
    else begin q:=lig_ptr(s); c:=qo(character(q)); hf:=font(q);
      end
@z
@x ibid
continue: s:=link(s);
@y
continue: prev_s:=s; s:=link(prev_s);
@z
@x ibid
ha:=s
@y
ha:=prev_s
@z
@x module 898
begin j:=hn; q:=lig_ptr(s);
if font(q)<>hf then goto done3;
repeat c:=qo(character(q));
if lc_code(c)=0 then goto done3;
if j=63 then goto done3;
incr(j); hu[j]:=c; hc[j]:=lc_code(c);@/
q:=link(q);
until q=null;
@y
begin if font(lig_char(s))<>hf then goto done3;
j:=hn; q:=lig_ptr(s);
while q>null do
  begin c:=qo(character(q));
  if lc_code(c)=0 then goto done3;
  if j=63 then goto done3;
  incr(j); hu[j]:=c; hc[j]:=lc_code(c);@/
  q:=link(q);
  end;
@z
@x module 900 gets three new global variables
@y
@!init_list:pointer; {list of punctuation characters preceding the word}
@!init_lig:boolean; {does |init_list| represent a ligature?}
@!init_lft:boolean; {if so, did the ligature involve a left boundary?}
@z
@x module 901
@!q,@!r,@!s:pointer; {temporary registers for list manipulation}
@y
@!p,@!q,@!r,@!s:pointer; {temporary registers for list manipulation}
@!bchar:halfword; {right boundary character of hyphenated word, or |non_char|}
@z
@x module 903 should be entirely replaced
@y by the following:
@ If hyphens are in fact going to be inserted, \TeX\ first deletes the
subsequence of nodes between |ha| and~|hb|. An attempt is made to
preserve the effect that implicit boundary characters and punctuation marks
had on ligatures inside the hyphenated word, by storing a left boundary or
preceding character in |hu[0]| and by storing a possible right boundary
in |bchar|. We set |j:=0| if |hu[0]| is to be part of the reconstruction;
otherwise |j:=1|.
The variable |s| will point to the tail of the current hlist, and
|q| will point to the node following |hb|, so that
things can be hooked up after we reconstitute the hyphenated word.
 
@<Replace nodes |ha..hb| by a sequence of nodes...@>=
q:=link(hb); link(hb):=null; r:=link(ha); link(ha):=null; bchar:=non_char;
if type(hb)=ligature_node then if odd(subtype(hb)) then
  bchar:=font_bchar[hf];
if is_char_node(ha) then
  begin init_list:=ha; init_lig:=false; hu[0]:=qo(character(ha));
  end
else if type(ha)=ligature_node then
  begin init_list:=lig_ptr(ha); init_lig:=true; init_lft:=(subtype(ha)>1);
  hu[0]:=qo(character(lig_char(ha)));
  if init_list=null then if init_lft then
    begin hu[0]:=256; init_lig:=false;
    end; {in this case a ligature will be reconstructed from scratch}
  free_node(ha,small_node_size);
  end
else goto not_found+1; {no punctuation found}
s:=cur_p; {we have |cur_p<>ha| because |type(cur_p)=glue_node|}
while link(s)<>ha do s:=link(s);
j:=0; goto common_ending;
not_found+1: j:=1; s:=ha; init_list:=null;
if not is_char_node(r) then if type(r)=ligature_node then
 if subtype(r)>1 then
  begin j:=0; hu[0]:=256; init_lig:=false;
  end;
common_ending: flush_node_list(r);
@<Reconstitute nodes for the hyphenated word, inserting discretionary hyphens@>;
flush_list(init_list)
@z
@x modules 905--911 are entirely replaced
@y by the following new code:
Still further complications arise in the presence of ligatures that do not
delete the original characters. When punctuation precedes the word being
hyphenated, \TeX's method is not perfect under all possible scenarios,
because punctuation marks and letters can propagate information back and forth.
For example, suppose the original pre-hyphenation pair
\.{*a} changes to \.{*y} via a \.{\?=:} ligature, which changes to \.{xy}
via a \.{=:\?} ligature; if $p_{a-1}=\.x$ and $p_a=\.y$, the reconstitution
procedure isn't smart enough to obtain \.{xy} again. In such cases the
font designer should include a ligature that goes from \.{xa} to \.{xy}.
 
@ The processing is facilitated by a subroutine called |reconstitute|. Given
a string of characters $x_j\ldots x_n$, there is a smallest index $m\ge j$
such that the ``translation'' of $x_j\ldots x_n$ by ligatures and kerning
has the form $y_1\ldots y_t$ followed by the translation of $x_{m+1}\ldots x_n$,
where $y_1\ldots y_t$ is some nonempty sequence of character, ligature, and
kern nodes. We call $x_j\ldots x_m$ a ``cut prefix'' of $x_j\ldots x_n$.
For example, if $x_1x_2x_3=\.{fly}$, and if the font contains `fl' as a
ligature and a kern between `fl' and `y', then $m=2$, $y=2$, and $y_1$ will
be a ligature node for `fl' followed by an appropriate kern node~$y_2$.
In the most common case, $x_j$~forms no ligature with $x_{j+1}$ and we
simply have $m=j$, $y_1=x_j$. If $m<n$ we can repeat the procedure on
$x_{m+1}\ldots x_n$ until the entire translation has been found.
 
The |reconstitute| function returns the integer $m$ and puts the nodes
$y_1\ldots y_t$ into a linked list starting at |link(hold_head)|,
getting the input $x_j\ldots x_n$ from the |hu| array. If $x_j=256$,
we consider $x_j$ to be an implicit left boundary character; in this
case |j| must be strictly less than~|n|. There is a
parameter |bchar|, which is either 256 or an implicit right boundary character
assumed to be present just following~$x_n$. (The value |hu[n+1]| is never
explicitly examined, but the algorithm imagines that |bchar| is there.)
 
If there exists an index |k| in the range $j\le k\le m$ such that |hyf[k]|
is odd and such that the result of reconstitute would have been different
if $x_{k+1}$ had been |hchar|, then |reconstitute| sets |hyphen_passed|
to the smallest such~|k|. Otherwise it sets |hyphen_passed| to zero.
 
A special convention is used in the case |j=0|: Then we assume that the
translation of |hu[0]| appears in a special list of charnodes starting at
|init_list|; moreover, if |init_lig| is |true|, then |hu[0]| will be
a ligature character, involving a left boundary if |init_lft| is |true|.
This facility is provided for cases when a hyphenated
word is preceded by punctuation (like single or double quotes) that might
affect the translation of the beginning of the word.
 
@<Glob...@>=
@!hyphen_passed:small_number; {first hyphen in a ligature, if any}
 
@ @<Declare the function called |reconstitute|@>=
function reconstitute(@!j,@!n:small_number;@!bchar,@!hchar:halfword):
  small_number;
label continue,done;
var @!p:pointer; {temporary register for list manipulation}
@!t:pointer; {a node being appended to}
@!q:four_quarters; {character information or a lig/kern instruction}
@!cur_rh:halfword; {hyphen character for ligature testing}
@!test_char:halfword; {hyphen or other character for ligature testing}
@!w:scaled; {amount of kerning}
@!k:font_index; {position of current lig/kern instruction}
begin hyphen_passed:=0; t:=hold_head; w:=0; link(hold_head):=null;
 {at this point |ligature_present=lft_hit=rt_hit=false|}
@<Set up data structures with the cursor following position |j|@>;
continue:@<If there's a ligature or kern at the cursor position, update the data
  structures, possibly advancing~|j|; continue until the cursor moves@>;
@<Append a ligature and/or kern to the translation;
  |goto continue| if the stack of inserted ligatures is nonempty@>;
reconstitute:=j;
end;
 
@ The reconstitution procedure shares many of the global data structures
by which \TeX\ has processed the words before they were hyphenated.
There is an implied ``cursor'' between characters |cur_l| and |cur_r|;
these characters will be tested for possible ligature activity. If
|ligature_present| then |cur_l| is a ligature character formed from the
original characters following |cur_q| in the current translation list.
There is a ``ligature stack'' between the cursor and character |j+1|,
consisting of pseudo-ligature nodes linked together by their |link| fields.
This stack is normally empty unless a ligature command has created a new
character that will need to be processed later. A pseudo-ligature is
a special node having a |character| field that represents a potential
ligature and a |lig_ptr| field that points to a |char_node| or is |null|.
We have
$$|cur_r|=\cases{|character(lig_stack)|,&if |lig_stack>null|;\cr
  |qi(hu[j+1])|,&if |lig_stack=null| and |j<n|;\cr
  bchar,&if |lig_stack=null| and |j=n|.\cr}$$
 
@<Glob...@>=
@!cur_l,@!cur_r:halfword; {characters before and after the cursor}
@!cur_q:pointer; {where a ligature should be detached}
@!lig_stack:pointer; {unfinished business to the right of the cursor}
@!ligature_present:boolean; {should a ligature node be made for |cur_l|?}
@!lft_hit,@!rt_hit:boolean; {did we hit a ligature with a boundary character?}
 
@ @d append_charnode_to_t(#)== begin link(t):=get_avail; t:=link(t);
    font(t):=hf; character(t):=#;
    end
@d set_cur_r==begin if j<n then cur_r:=qi(hu[j+1])@+else cur_r:=bchar;
    if odd(hyf[j]) then cur_rh:=hchar@+else cur_rh:=non_char;
    end
 
@<Set up data structures with the cursor following position |j|@>=
cur_l:=qi(hu[j]); cur_q:=t;
if j=0 then
  begin ligature_present:=init_lig; p:=init_list;
  if ligature_present then lft_hit:=init_lft;
  while p>null do
    begin append_charnode_to_t(character(p)); p:=link(p);
    end;
  end
else if cur_l<non_char then append_charnode_to_t(cur_l);
lig_stack:=null; set_cur_r
 
@ We may want to look at the lig/kern program twice, once for a hyphen
and once for a normal letter. (The hyphen might appear after the letter
in the program, so we'd better not try to look for both at once.)
 
@<If there's a ligature or kern at the cursor position, update...@>=
if cur_l=non_char then
  begin k:=bchar_label[hf];
  if k=non_address then goto done@+else q:=font_info[k].qqqq;
  end
else begin q:=char_info(hf)(cur_l);
  if char_tag(q)<>lig_tag then goto done;
  k:=lig_kern_start(hf)(q); q:=font_info[k].qqqq;
  if skip_byte(q)>stop_flag then
    begin k:=lig_kern_restart(hf)(q); q:=font_info[k].qqqq;
    end;
  end; {now |k| is the starting address of the lig/kern program}
if cur_rh<non_char then test_char:=cur_rh@+else test_char:=cur_r;
loop@+begin if next_char(q)=test_char then if skip_byte(q)<=stop_flag then
    if cur_rh<non_char then
      begin hyphen_passed:=j; hchar:=non_char; cur_rh:=non_char;
      goto continue;
      end
    else begin if hchar<non_char then if odd(hyf[j]) then
        begin hyphen_passed:=j; hchar:=non_char;
        end;
      if op_byte(q)<kern_flag then
      @<Carry out a ligature replacement, updating the cursor structure
        and possibly advancing~|j|; |goto continue| if the cursor doesn't
        advance, otherwise |goto done|@>;
      w:=char_kern(hf)(q); goto done; {this kern will be inserted below}
     end;
  if skip_byte(q)>=stop_flag then
    if cur_rh=non_char then goto done
    else begin cur_rh:=non_char; goto continue;
      end;
  k:=k+qo(skip_byte(q))+1; q:=font_info[k].qqqq;
  end;
done:
 
@ @d wrap_lig(#)==if ligature_present then
    begin p:=new_ligature(hf,cur_l,link(cur_q));
    if lft_hit then
      begin subtype(p):=2; lft_hit:=false;
      end;
    if # then if lig_stack=null then
      begin incr(subtype(p)); rt_hit:=false;
      end;
    link(cur_q):=p; t:=p; ligature_present:=false;
    end
@d pop_lig_stack==begin if lig_ptr(lig_stack)>null then
    begin link(t):=lig_ptr(lig_stack); {this is a charnode for |hu[j+1]|}
    t:=link(t); incr(j);
    end;
  p:=lig_stack; lig_stack:=link(p); free_node(p,small_node_size);
  if lig_stack=null then set_cur_r@+else cur_r:=character(lig_stack);
  end {if |lig_stack| isn't |null| we have |cur_rh=non_char|}
 
@<Append a ligature and/or kern to the translation...@>=
wrap_lig(rt_hit);
if w<>0 then
  begin link(t):=new_kern(w); t:=link(t); w:=0;
  end;
if lig_stack>null then
  begin cur_q:=t; cur_l:=character(lig_stack); ligature_present:=true;
  pop_lig_stack; goto continue;
  end
 
@ @<Carry out a ligature replacement, updating the cursor structure...@>=
begin if cur_l=non_char then lft_hit:=true;
if j=n then if lig_stack=null then rt_hit:=true;
check_interrupt; {allow a way out in case there's an infinite ligature loop}
case op_byte(q) of
qi(1),qi(5):begin cur_l:=rem_byte(q); {\.{=:\?}, \.{=:\?>}}
  ligature_present:=true;
  end;
qi(2),qi(6):begin cur_r:=rem_byte(q); {\.{\?=:}. \.{\?=:>}}
  if lig_stack>null then character(lig_stack):=cur_r
  else begin lig_stack:=new_lig_item(cur_r);
    if j=n then bchar:=non_char
    else begin p:=get_avail; lig_ptr(lig_stack):=p;
      character(p):=qi(hu[j+1]); font(p):=hf;
      end;
    end;
  end;
qi(3):begin cur_r:=rem_byte(q); {\.{\?=:\?}}
  p:=lig_stack; lig_stack:=new_lig_item(cur_r); link(lig_stack):=p;
  end;
qi(7),qi(11):begin wrap_lig(false); {\.{\?=:\?>}, \.{\?=:\?>>}}
  cur_q:=t; cur_l:=rem_byte(q); ligature_present:=true;
  end;
othercases begin cur_l:=rem_byte(q); ligature_present:=true; {\.{=:}}
  if lig_stack>null then pop_lig_stack
  else if j=n then goto done
  else begin append_charnode_to_t(cur_r); incr(j); set_cur_r;
    end;
  end
endcases;
if op_byte(q)>qi(4) then if op_byte(q)<>qi(7) then goto done;
goto continue;
end
@z
@x module 912
@!c:ASCII_code; {character temporarily replaced by a hyphen}
@y
@!c:ASCII_code; {character temporarily replaced by a hyphen}
@!c_loc:0..63; {where that character came from}
@!r_count:integer; {replacement count for discretionary}
@z
@x modules 913--918 are to be completely replaced
@y by the following code:
@ When the following code is performed, |hyf[0]| and |hyf[hn]| will be zero.
 
@<Reconstitute nodes for the hyphenated word...@>=
repeat l:=j; j:=reconstitute(j,hn,bchar,qi(hyf_char))+1;
if hyphen_passed=0 then
  begin link(s):=link(hold_head);
  while link(s)>null do s:=link(s);
  if odd(hyf[j-1]) then
    begin l:=j; hyphen_passed:=j-1; link(hold_head):=null;
    end;
  end;
if hyphen_passed>0 then
  @<Create and append a discretionary node as an alternative to the
    unhyphenated word, and continue to develop both branches until they
    become equivalent@>;
until j>hn;
link(s):=q
 
@ @d advance_major_tail==begin major_tail:=link(major_tail); incr(r_count);
    end
 
@<Create and append a discretionary node as an alternative...@>=
begin r:=get_node(small_node_size);
link(r):=link(hold_head); type(r):=disc_node;
major_tail:=r; r_count:=0;
while link(major_tail)>null do advance_major_tail;
i:=hyphen_passed;
@<Put the \(c)characters |hu[l..i]| and a hyphen into |pre_break(r)|@>;
@<Put the \(c)characters |hu[i+1..@,]| into |post_break(r)|, appending to this
  list and to |major_tail| until synchronization has been achieved@>;
@<Move pointer |s| to the end of the current list, and set |replace_count(r)|
  appropriately@>;
end
 
@ The new hyphen might combine with the previous character via ligature
or kern. At this point we have |l-1<=i<=j| and |i<hn|.
 
@<Put the \(c)characters |hu[l..i]| and a hyphen into |pre_break(r)|@>=
minor_tail:=null; pre_break(r):=null; hyf_node:=new_character(hf,hyf_char);
if hyf_node<>null then
  begin incr(i); c:=hu[i]; hu[i]:=hyf_char; free_avail(hyf_node);
  end;
while l<=i do
  begin l:=reconstitute(l,i,font_bchar[hf],non_char)+1;
  if link(hold_head)>null then
    begin if minor_tail=null then pre_break(r):=link(hold_head)
    else link(minor_tail):=link(hold_head);
    minor_tail:=link(hold_head);
    while link(minor_tail)>null do minor_tail:=link(minor_tail);
    end;
  end;
if hyf_node<>null then
  begin hu[i]:=c; {restore the character in the hyphen position}
  l:=i; decr(i);
  end
 
@ The synchronization algorithm begins with |l=i+1<=j|.
 
@<Put the \(c)characters |hu[i+1..@,]| into |post_break(r)|...@>=
minor_tail:=null; post_break(r):=null; c_loc:=0;
if bchar_label[hf]<non_address then {put left boundary at beginning of new line}
  begin decr(l); c:=hu[l]; c_loc:=l; hu[l]:=256;
  end;
while l<j do
  begin repeat l:=reconstitute(l,hn,bchar,non_char)+1;
  if c_loc>0 then
    begin hu[c_loc]:=c; c_loc:=0;
    end;
  if link(hold_head)>null then
    begin if minor_tail=null then post_break(r):=link(hold_head)
    else link(minor_tail):=link(hold_head);
    minor_tail:=link(hold_head);
    while link(minor_tail)>null do minor_tail:=link(minor_tail);
    end;
  until l>=j;
  while l>j do
    @<Append characters of |hu[j..@,]| to |major_tail|, advancing~|j|@>;
  end
 
@ @<Append characters of |hu[j..@,]|...@>=
begin j:=reconstitute(j,hn,bchar,non_char)+1;
link(major_tail):=link(hold_head);
while link(major_tail)>null do advance_major_tail;
end
 
@ Ligature insertion can cause a word to grow exponentially in size. Therefore
we must test the size of |r_count| here, even though the hyphenated text
was at most 63 characters long.
 
@<Move pointer |s| to the end of the current list...@>=
if r_count>127 then {we have to forget the discretionary hyphen}
  begin link(s):=link(r); link(r):=null; flush_node_list(r);
  end
else begin link(s):=r; replace_count(r):=r_count;
  end;
s:=major_tail
@z
@x module 1030
@d main_loop=70 {go here to typeset |cur_chr| in the current font}
@d main_loop_1=71 {like |main_loop|, but |(f,c)| = current font and char}
@d main_loop_2=72 {like |main_loop_1|, but |c| is known to be in range}
@d main_loop_3=73 {like |main_loop_2|, but several variables are set up}
@d append_normal_space=74 {go here to append a normal space between words}
@y
@d main_loop=70 {go here to typeset a string of consecutive characters}
@d main_loop_wrapup=80 {go here to finish a character or ligature}
@d main_loop_move=90 {go here to advance the ligature cursor}
@d main_loop_move_lig=95 {same, when advancing past a generated ligature}
@d main_loop_lookahead=100 {go here to bring in another character, if any}
@d main_lig_loop=110 {go here to check for ligatures or kerning}
@d append_normal_space=120 {go here to append a normal space between words}
@z
@x ibid
label big_switch,reswitch,main_loop,main_loop_1,main_loop_2,main_loop_3,
@y
label big_switch,reswitch,main_loop,main_loop_wrapup,
  main_loop_move,main_loop_move+1,main_loop_move+2,main_loop_move_lig,
  main_loop_lookahead,main_loop_lookahead+1,
  main_lig_loop,main_lig_loop+1,main_lig_loop+2,
@z
@x ibid
@<Local variables for the inner loop of |main_control|@>@;
@y
@z
@x ibid
hmode+char_num: begin scan_char_num; cur_chr:=cur_val; goto main_loop;
  end;
@y
hmode+char_num: begin scan_char_num; cur_chr:=cur_val; goto main_loop;@+end;
hmode+no_boundary: begin get_x_token;
  if (cur_cmd=letter)or(cur_cmd=other_char)or(cur_cmd=char_given)or
   (cur_cmd=char_num) then cancel_boundary:=true;
  goto reswitch;
  end;
@z
@x modules 1032--1040 are all to be replaced
@y by the following code:
@ The following part of the program was first written in a structured
manner, according to the philosophy that ``premature optimization is
the root of all evil.'' Then it was rearranged into pieces of
spaghetti so that the most common actions could proceed with little or
no redundancy.
 
The original unoptimized form of this algorithm resembles the
|reconstitute| procedure, which was described earlier in connection with
hyphenation. Again we have an implied ``cursor`` between characters
|cur_l| and |cur_r|. The main difference is that the |lig_stack| can now
contain a charnode as well as pseudo-ligatures; that stack is now
usually nonempty, because the next character of input (if any) has been
appended to it. In |main_control| we have
$$|cur_r|=\cases{|character(lig_stack)|,&if |lig_stack>null|;\cr
  |font_bchar[cur_font]|,&otherwise.\cr}$$
Several additional global variables are needed.
 
@<Glob...@>=
@!main_f:internal_font_number; {the current font}
@!main_i:four_quarters; {character information bytes for |cur_l|}
@!main_j:four_quarters; {ligature/kern command}
@!main_k:font_index; {index into |font_info|}
@!main_p:pointer; {temporary register for list manipulation}
@!main_s:integer; {space factor value}
@!bchar:halfword; {right boundary character of current font, or |non_char|}
@!false_bchar:halfword; {nonexistent character matching |bchar|, or |non_char|}
@!cancel_boundary:boolean; {should the left boundary be ignored?}
@!ins_disc:boolean; {should we insert a discretionary node?}
 
@ The boolean variables of the main loop are normally false, and always reset
to false before the loop is left. That saves us the extra work of initializing
each time.
 
@<Set init...@>=
ligature_present:=false; cancel_boundary:=false; lft_hit:=false; rt_hit:=false;
ins_disc:=false;
 
@ We leave |space_factor| unchanged if |sf_code(cur_chr)=0|; otherwise we
set it to |sf_code(cur_chr)|, except that the space factor never changes
from a value less than 1000 to a value exceeding 1000. The most common
case is |sf_code(cur_chr)=1000|, so we want that case to be fast.
 
The overall structure of the main loop is presented here. Some program labels
are inside the individual sections.
 
@d adjust_space_factor==@t@>@;@/
  main_s:=sf_code(cur_chr);
  if main_s=1000 then space_factor:=1000
  else if main_s<1000 then
    begin if main_s>0 then space_factor:=main_s;
    end
  else if space_factor<1000 then space_factor:=1000
  else space_factor:=main_s
 
@<Append character |cur_chr|...@>=
adjust_space_factor;@/
main_f:=cur_font;
bchar:=font_bchar[main_f]; false_bchar:=font_false_bchar[main_f];
if mode>0 then if language<>clang then fix_language;
fast_get_avail(lig_stack); font(lig_stack):=main_f; cur_l:=qi(cur_chr);
character(lig_stack):=cur_l;@/
cur_q:=tail;
if cancel_boundary then
  begin cancel_boundary:=false; main_k:=non_address;
  end
else main_k:=bchar_label[main_f];
if main_k=non_address then goto main_loop_move+2; {no left boundary processing}
cur_r:=cur_l; cur_l:=non_char;
goto main_lig_loop+1; {begin with cursor after left boundary}
@#
main_loop_wrapup:@<Make a ligature node, if |ligature_present|;
  insert a null discretionary, if appropriate@>;
main_loop_move:@<If the cursor is immediately followed by the right boundary,
  |goto reswitch|; if it's followed by an invalid character, |goto big_switch|;
  otherwise move the cursor one step to the right and |goto main_lig_loop|@>;
main_loop_lookahead:@<Look ahead for another character, or leave |lig_stack|
  empty if there's none there@>;
main_lig_loop:@<If there's a ligature/kern command relevant to |cur_l| and
  |cur_r|, adjust the text appropriately; exit to |main_loop_wrapup|@>;
main_loop_move_lig:@<Move the cursor past a pseudo-ligature, then
  |goto main_loop_lookahead| or |main_lig_loop|@>
 
@ If the current horizontal list is empty, the reference to |character(tail)|
here is not strictly legal, since |tail| will be a node freshly returned by
|get_avail|. But this should cause no problem on most implementations, and we
do want the inner loop to be fast.
@^dirty Pascal@>
 
A discretionary break is not inserted for an explicit hyphen when we are in
restricted horizontal mode. In particular, this avoids putting discretionary
nodes inside of other discretionaries.
 
@d pack_lig(#)== {the parameter is either |rt_hit| or |false|}
  begin main_p:=new_ligature(main_f,cur_l,link(cur_q));
  if lft_hit then
    begin subtype(main_p):=2; lft_hit:=false;
    end;
  if # then if lig_stack=null then
    begin incr(subtype(main_p)); rt_hit:=false;
    end;
  link(cur_q):=main_p; tail:=main_p; ligature_present:=false;
  end
 
@d wrapup(#)==if cur_l<non_char then
  begin if character(tail)=qi(hyphen_char[main_f]) then if link(cur_q)>null then
    ins_disc:=true;
  if ligature_present then pack_lig(#);
  if ins_disc then
    begin ins_disc:=false;
    if mode>0 then tail_append(new_disc);
    end;
  end
 
@<Make a ligature node, if |ligature_present|;...@>=
wrapup(rt_hit)
 
@ @<If the cursor is immediately followed by the right boundary...@>=
if lig_stack=null then goto reswitch;
cur_q:=tail; cur_l:=cur_r; {or |character(lig_stack)|}
main_loop_move+1:if not is_char_node(lig_stack) then goto main_loop_move_lig;
main_loop_move+2:if(cur_chr<font_bc[main_f])or(cur_chr>font_ec[main_f]) then
  begin char_warning(main_f,cur_chr); free_avail(lig_stack); goto big_switch;
  end;
main_i:=char_info(main_f)(cur_l);
if not char_exists(main_i) then
  begin char_warning(main_f,cur_chr); free_avail(lig_stack); goto big_switch;
  end;
tail_append(lig_stack) {|main_loop_lookahead| is next}
 
@ Here we are at |main_loop_move_lig|.
When we begin this code we have |cur_l=character(lig_stack)| and |cur_q=tail|.
 
@<Move the cursor past a pseudo-ligature...@>=
main_p:=lig_ptr(lig_stack);
if main_p>null then tail_append(main_p);
temp_ptr:=lig_stack; lig_stack:=link(temp_ptr);
free_node(temp_ptr,small_node_size);
main_i:=char_info(main_f)(cur_l); ligature_present:=true;
if lig_stack=null then
  if main_p>null then goto main_loop_lookahead
  else cur_r:=bchar
else cur_r:=character(lig_stack);
goto main_lig_loop
 
@ The result of \.{\\char} can participate in a ligature or kern, so we must
look ahead for it.
 
@<Look ahead for another character...@>=
get_next; {set only |cur_cmd| and |cur_chr|, for speed}
if cur_cmd=letter then goto main_loop_lookahead+1;
if cur_cmd=other_char then goto main_loop_lookahead+1;
if cur_cmd=char_given then goto main_loop_lookahead+1;
x_token; {now expand and set |cur_cmd|, |cur_chr|, |cur_tok|}
if cur_cmd=letter then goto main_loop_lookahead+1;
if cur_cmd=other_char then goto main_loop_lookahead+1;
if cur_cmd=char_given then goto main_loop_lookahead+1;
if cur_cmd=char_num then
  begin scan_char_num; cur_chr:=cur_val; goto main_loop_lookahead+1;
  end;
if cur_cmd=no_boundary then bchar:=non_char;
cur_r:=bchar; lig_stack:=null; goto main_lig_loop;
main_loop_lookahead+1: adjust_space_factor;
fast_get_avail(lig_stack); font(lig_stack):=main_f;
cur_r:=qi(cur_chr); character(lig_stack):=cur_r;
if cur_r=false_bchar then cur_r:=non_char {this prevents spurious ligatures}
 
@ Even though comparatively few characters have a lig/kern program, several
of the instructions here count as part of \TeX's inner loop, since a
potentially long sequential search must be performed. For example, tests with
Computer Modern Roman showed that about 40 per cent of all characters
actually encountered in practice had a lig/kern program, and that about four
lig/kern commands were investigated for every such character.
 
At the beginning of this code we have |main_i=char_info(main_f)(cur_l)|.
 
@<If there's a ligature/kern command...@>=
if char_tag(main_i)<>lig_tag then goto main_loop_wrapup;
main_k:=lig_kern_start(main_f)(main_i); main_j:=font_info[main_k].qqqq;
if skip_byte(main_j)<=stop_flag then goto main_lig_loop+2;
main_k:=lig_kern_restart(main_f)(main_j);
main_lig_loop+1:main_j:=font_info[main_k].qqqq;
main_lig_loop+2:if next_char(main_j)=cur_r then
 if skip_byte(main_j)<=stop_flag then
  @<Do ligature or kern command, returning to |main_lig_loop|
  or |main_loop_wrapup| or |main_loop_move|@>;
if skip_byte(main_j)=qi(0) then incr(main_k)
else begin if skip_byte(main_j)>=stop_flag then goto main_loop_wrapup;
  main_k:=main_k+qo(skip_byte(main_j))+1;
  end;
goto main_lig_loop+1
 
@ When a ligature or kern instruction matches a character, we know from
|read_font_info| that the character exists in the font, even though we
haven't verified its existence in the normal way.
 
This section could be made into a subroutine, if the code inside
|main_control| needs to be shortened.
 
\chardef\?='174 % vertical line to indicate character retention
 
@<Do ligature or kern command...@>=
begin if op_byte(main_j)>=kern_flag then
  begin wrapup(rt_hit);
  tail_append(new_kern(char_kern(main_f)(main_j))); goto main_loop_move;
  end;
if cur_l=non_char then lft_hit:=true
else if lig_stack=null then rt_hit:=true;
check_interrupt; {allow a way out in case there's an infinite ligature loop}
case op_byte(main_j) of
qi(1),qi(5):begin cur_l:=rem_byte(main_j); {\.{=:\?}, \.{=:\?>}}
  main_i:=char_info(main_f)(cur_l); ligature_present:=true;
  end;
qi(2),qi(6):begin cur_r:=rem_byte(main_j); {\.{\?=:}, \.{\?=:>}}
  if lig_stack=null then {right boundary character is being consumed}
    begin lig_stack:=new_lig_item(cur_r); bchar:=non_char;
    end
  else if is_char_node(lig_stack) then {|link(lig_stack)=null|}
    begin main_p:=lig_stack; lig_stack:=new_lig_item(cur_r);
    lig_ptr(lig_stack):=main_p;
    end
  else character(lig_stack):=cur_r;
  end;
qi(3):begin cur_r:=rem_byte(main_j); {\.{\?=:\?}}
  main_p:=lig_stack; lig_stack:=new_lig_item(cur_r);
  link(lig_stack):=main_p;
  end;
qi(7),qi(11):begin wrapup(false); {\.{\?=:\?>}, \.{\?=:\?>>}}
  cur_q:=tail; cur_l:=rem_byte(main_j);
  main_i:=char_info(main_f)(cur_l); ligature_present:=true;
  end;
othercases begin cur_l:=rem_byte(main_j); ligature_present:=true; {\.{=:}}
  if lig_stack=null then goto main_loop_wrapup
  else goto main_loop_move+1;
  end
endcases;
if op_byte(main_j)>qi(4) then
  if op_byte(main_j)<>qi(7) then goto main_loop_wrapup;
if cur_l<non_char then goto main_lig_loop;
main_k:=bchar_label[main_f]; goto main_lig_loop+1;
end
@z
% HINTS for making main_control shorter if you need to conserve space:
% (1) make pack_lig(#) a procedure call instead of a macro call
% (2) make the code of @<Do ligature...@> a function that returns one of
%     four values; you goto a label based on the value returned.
@x module 1042
begin p:=font_glue[cur_font];
if p=null then
  begin f:=cur_font; p:=new_spec(zero_glue); k:=param_base[f]+space_code;
  width(p):=font_info[k].sc; {that's |space(f)|}
  stretch(p):=font_info[k+1].sc; {and |space_stretch(f)|}
  shrink(p):=font_info[k+2].sc; {and |space_shrink(f)|}
  font_glue[f]:=p;
@y
begin main_p:=font_glue[cur_font];
if main_p=null then
  begin main_p:=new_spec(zero_glue); main_k:=param_base[cur_font]+space_code;
  width(main_p):=font_info[main_k].sc; {that's |space(cur_font)|}
  stretch(main_p):=font_info[main_k+1].sc; {and |space_stretch(cur_font)|}
  shrink(main_p):=font_info[main_k+2].sc; {and |space_shrink(cur_font)|}
  font_glue[cur_font]:=main_p;
@z
@x module 1045
any_mode(relax),vmode+spacer,mmode+spacer,mmode+no_boundary:do_nothing;
@y
any_mode(relax),vmode+spacer,mmode+spacer:do_nothing;
@z
@x module 1090
   vmode+ex_space:@t@>@;@/
@y
   vmode+ex_space,vmode+no_boundary:@t@>@;@/
@z
@x module 1322
dump_int(font_glue[k]);@/
@y
dump_int(font_glue[k]);@/
dump_int(bchar_label[k]);
dump_int(font_bchar[k]);
dump_int(font_false_bchar[k]);@/
@z
@x module 1323
undump(min_halfword)(lo_mem_max)(font_glue[k]);@/
@y
undump(min_halfword)(lo_mem_max)(font_glue[k]);@/
undump(0)(font_mem_size)(bchar_label[k]);
undump(min_quarterword)(non_char)(font_bchar[k]);
undump(min_quarterword)(non_char)(font_false_bchar[k]);
@z
 
363. New \inputlineno feature desired by Spivak
@x module 416 gets a new definition and a new statement
@y
@d input_line_no_code=glue_val+1 {code for \.{\\inputlineno}}
primitive("inputlineno",last_item,input_line_no_code);
@!@:input_line_no_}{\.{\\inputlineno} primitive@>
@z and module 417 changes in the obvious way
@x module 424 gets new code at the beginning
@y
if cur_chr>glue_val then
  begin cur_val:=line; cur_val_level:=int_val;
  end
else
@z
 
364. New feature \holdinginserts suggested by Mittelbach.
@x modules 236--238 get a new integer parameter
@d int_pars=53 {total number of integer parameters}
@y
@d holding_inserts_code=53 {do not remove insertion nodes from \.{\\box255}}
@d int_pars=54 {total number of integer parameters}
@z and appropriate further lines are added to match all the other parameters
@x module 1014
@<Prepare all the boxes involved in insertions to act as queues@>;
q:=hold_head; link(q):=null; prev_p:=page_head; p:=link(prev_p);
while p<>best_page_break do
  begin if type(p)=ins_node then @<Either insert the material
    specified by node |p| into the appropriate box, or
    hold it for the next page; also delete node |p| from
    the current page@>
@y
if holding_inserts<=0 then
  @<Prepare all the boxes involved in insertions to act as queues@>;
q:=hold_head; link(q):=null; prev_p:=page_head; p:=link(prev_p);
while p<>best_page_break do
  begin if type(p)=ins_node then
    begin if holding_inserts<=0 then
       @<Either insert the material specified by node |p| into the
         appropriate box, or hold it for the next page;
         also delete node |p| from the current page@>;
    end
@z
% also insert begin...end around the Pascal code of module 1018
 
365. New \badness feature (which I'd been resisting for years)
@x module 416 gets a new definition and a new statement
@y
@d badness_code=glue_val+2 {code for \.{\\badness}}
primitive("badness",last_item,badness_code);
@!@:badness_}{\.{\\badness} primitive@>
@z and module 417 changes in the obvious way
@x module 424 (the new code just added in #363)
  begin cur_val:=line; cur_val_level:=int_val;
@y
  begin if cur_chr=input_line_no_code then cur_val:=line
  else cur_val:=last_badness; {|cur_chr=badness_code|}
  cur_val_level:=int_val;
@z
@x module 646 gets a new global variable
@y
@!last_badness:integer; {badness of the most recently packaged box}
@z
@x module 648
@ @<Set init...@>=adjust_tail:=null;
@y
@ @<Set init...@>=adjust_tail:=null; last_badness:=0;
@z
@x modules 649 and 668
@!b:integer; {badness of the new box}
begin r:=get_node(box_node_size); type(r):=hlist_node;
@y (except it's vlist_node in 668)
begin last_badness:=0; r:=get_node(box_node_size); type(r):=hlist_node;
@z (except it's vlist_node in 668)
% change b to last_badness in modules 660&674 (4 times), 667&678 (3 times)
@x modules 658 and 673
if (hbadness<inf_bad)and(o=normal)and(list_ptr(r)<>null) then
@y (except it's vbadness in 673)
if o=normal then if list_ptr(r)<>null then
@z
@x modules 664 and 676
  begin set_glue_ratio_one(glue_set(r)); {this is the maximum shrinkage}
@y
  begin last_badness:=1000000;
  set_glue_ratio_one(glue_set(r)); {use the maximum shrinkage}
@z
@x ibid
else if (hbadness<100)and(o=normal)and(list_ptr(r)<>null) then
@y (except it's vbadness in 676)
else if o=normal then if list_ptr(r)<>null then
@z
 
366. New \emergencystretch feature
@x modules 247--248 get a new dimen parameter
@d dimen_pars=20 {total number of dimension parameters}
@y
@d emergency_stretch_code=20 {reduces badnesses on final pass of line-breaking}
@d dimen_pars=21 {total number of dimension parameters}
@z and appropriate further lines are added to match all the other parameters
@x module 828
@!second_pass:boolean; {is this our second attempt to break this paragraph?}
@y
@!second_pass:boolean; {is this our second attempt to break this paragraph?}
@!final_pass:boolean; {is this our final attempt to break this paragraph?}
@z
% change second_pass to final_pass in modules 854 and 873
@x module 863
  second_pass:=false;
  end
else  begin threshold:=tolerance; second_pass:=true;
@y
  second_pass:=false; final_pass:=false;
  end
else  begin threshold:=tolerance; second_pass:=true;
  final_pass:=(emergency_stretch<=0);
@z
@x ibid
  @!stat if tracing_paragraphs>0 then print_nl("@@secondpass");@;@+tats@/
  threshold:=tolerance; second_pass:=true; {if at first you don't
    succeed, \dots}
@y
  if not second_pass then
    begin@!stat if tracing_paragraphs>0 then print_nl("@@secondpass");@;@+tats@/
    threshold:=tolerance; second_pass:=true; final_pass:=(emergency_stretch<=0);
    end {if at first you don't succeed, \dots}
  else begin @!stat if tracing_paragraphs>0 then
      print_nl("@@finalpass");@;@+tats@/
    background[2]:=background[2]+emergency_stretch; final_pass:=true;
    end;
@z
 
367. New \errorcontextlines feature suggested by a TUG participant
@x modules 236--238 get a new integer parameter
@d int_pars=54 {total number of integer parameters}
@y
@d error_context_lines_code=54 {maximum intermediate line pairs shown}
@d int_pars=55 {total number of integer parameters}
@z and appropriate further lines are added to match all the other parameters
@x module 311
@<Local variables for formatting calculations@>@/
begin base_ptr:=input_ptr; input_stack[base_ptr]:=cur_input;
  {store current state}
loop@+begin cur_input:=input_stack[base_ptr]; {enter into the context}
  @<Display the current context@>;
  if (state<>token_list) then
    if (name>17) or (base_ptr=0) then goto done;
@y
@!nn:integer; {number of contexts shown so far, less one}
@!bottom_line:boolean; {have we reached the final context to be shown?}
@<Local variables for formatting calculations@>@/
begin base_ptr:=input_ptr; input_stack[base_ptr]:=cur_input;
  {store current state}
nn:=-1; bottom_line:=false;
loop@+begin cur_input:=input_stack[base_ptr]; {enter into the context}
  if (state<>token_list) then
    if (name>17) or (base_ptr=0) then bottom_line:=true;
  if (base_ptr=input_ptr)or bottom_line or(nn<error_context_lines) then
    @<Display the current context@>
  else if nn=error_context_lines then
    begin print_nl("..."); incr(nn); {omitted if |error_context_lines<0|}
    end;
  if bottom_line then goto done;
@z
% also insert begin...end around the Pascal code of module 312
% and add the statement "incr(nn)" to that module
 
368. char_warning inside hyphenation could clobber old_setting
@x module 863
done: @!stat if tracing_paragraphs>0 then end_diagnostic(true);@;@+tats@/
@y
done: @!stat if tracing_paragraphs>0 then
  begin end_diagnostic(true); normalize_selector;
  end;@+tats@/
@z
 
369. Make ".fmt" more easily switchable (Don Hosek).
@x module 520 gets a new definition
@y
@d format_extension=".fmt" {the extension, as a \.{WEB} constant}
@z now replace ".fmt" by format_extension in modules 529 and 1328.
 
370. Possible range check on weird nullfont (Breitenlohner, 16 Oct 89).
@x module 565
if (bc>ec+1)or(ec>255) then abort;
@y
if (bc>ec+1)or(ec>255) then abort;
if bc>255 then {|bc=256| and |ec=255|}
  begin bc:=1; ec:=0;
  end;
@z
 
371. (I sincerely hope that there won't be any more)
 
* Possibly nice ideas that will not be implemented
 
. Classes of marks analogous to classes of insertions
 
. \showcontext to show the current location without stopping for error
 
. \everyeof to insert tokens before an \input file ends
  (strange example: \everyeof{\noexpand} will allow things
    like \xdef\a{\input foo}!)
 
. generalize \leftskip and \rightskip to token lists (problems with
   displayed math then)
 
. generalize \widowline and \clubline to go further into a paragraph
 
* Bad ideas that will not be implemented
 
. several people want to be able to remove arbitrary elements of lists,
   but that must never be done because some of those elements (e.g. kerns
   for accents) depend on floating point arithmetic
 
. if anybody wants letter spacing desperately they should put it in their own
   private version (e.g. generalize the hpack routine) and NOT call it TeX.