Unit tests for Literate Haskell
[ohcount] / test / src_dir / knuth_web.web
1 % Sample WEB file, obtained with excerpts from mpost.web\r
2 \r
3 @* \[1] Introduction.\r
4 This is \MP, a graphics-language processor based on D. E. Knuth's \MF.\r
5 The \PASCAL\ program that follows defines a standard version\r
6 @:PASCAL}{\PASCAL@>\r
7 of \MP\ that is designed to be highly portable so that identical output\r
8 will be obtainable on a great variety of computers.\r
9 \r
10 @ The present implementation is a preliminary version, but the possibilities\r
11 for new features are limited by the desire to remain as nearly compatible\r
12 with \MF\ as possible.\r
13 \r
14 @d banner=='This is MetaPost, Version 0.641' {printed when \MP\ starts}\r
15 \r
16 @ Actually the heading shown here is not quite normal: The |program| line\r
17 does not mention any |output| file, because \ph\ would ask the \MP\ user\r
18 to specify a file name if |output| were specified here.\r
19 @^system dependencies@>\r
20 \r
21 @d mtype==t@&y@&p@&e {this is a \.{WEB} coding trick:}\r
22 @f mtype==type {`\&{mtype}' will be equivalent to `\&{type}'}\r
23 @f type==true {but `|type|' will not be treated as a reserved word}\r
24 \r
25 @p @t\4@>@<Compiler directives@>@/\r
26 program MP; {all file names are defined dynamically}\r
27 label @<Labels in the outer block@>@/\r
28 const @<Constants in the outer block@>@/\r
29 mtype @<Types in the outer block@>@/\r
30 var @<Global variables@>@/\r
31 @#\r
32 procedure initialize; {this procedure gets things started properly}\r
33   var @<Local variables for initialization@>@/\r
34   begin @<Set initial values of key variables@>@/\r
35   end;@#\r
36 @t\4@>@<Basic printing procedures@>@/\r
37 @t\4@>@<Error handling procedures@>@/\r
38 \r
39 @<Labels in the out...@>=\r
40 start_of_MP@t\hskip-2pt@>, end_of_MP@t\hskip-2pt@>,@,final_end;\r
41   {key control points}\r
42 \r
43 @ Some of the code below is intended to be used only when diagnosing the\r
44 strange behavior that sometimes occurs when \MP\ is being installed or\r
45 when system wizards are fooling around with \MP\ without quite knowing\r
46 what they are doing. Such code will not normally be compiled; it is\r
47 delimited by the codewords `$|debug|\ldots|gubed|$', with apologies\r
48 to people who wish to preserve the purity of English.\r
49 \r
50 Similarly, there is some conditional code delimited by\r
51 `$|stat|\ldots|tats|$' that is intended for use when statistics are to be\r
52 kept about \MP's memory usage.\r
53 @^debugging@>\r
54 \r
55 @d debug==@{ {change this to `$\\{debug}\equiv\null$' when debugging}\r
56 @d gubed==@t@>@} {change this to `$\\{gubed}\equiv\null$' when debugging}\r
57 @f debug==begin\r
58 @f gubed==end\r
59 @#\r
60 @d stat==@{ {change this to `$\\{stat}\equiv\null$' when gathering\r
61   usage statistics}\r
62 @d tats==@t@>@} {change this to `$\\{tats}\equiv\null$' when gathering\r
63   usage statistics}\r
64 @f stat==begin\r
65 @f tats==end\r
66 \r
67 @ If the first character of a \PASCAL\ comment is a dollar sign,\r
68 \ph\ treats the comment as a list of ``compiler directives'' that will\r
69 affect the translation of this program into machine language.  The\r
70 directives shown below specify full checking and inclusion of the \PASCAL\\r
71 debugger when \MP\ is being debugged, but they cause range checking and other\r
72 redundant code to be eliminated when the production system is being generated.\r
73 Arithmetic overflow will be detected in all cases.\r
74 @^system dependencies@>\r
75 @^Overflow in arithmetic@>\r
76 \r
77 @<Compiler directives@>=\r
78 @{@&$C-,A+,D-@} {no range check, catch arithmetic overflow, no debug overhead}\r
79 @!debug @{@&$C+,D+@}@+ gubed {but turn everything on when debugging}\r
80 \r
81 @ The following parameters can be changed at compile time to extend or\r
82 reduce \MP's capacity. They may have different values in \.{INIMP} and\r
83 in production versions of \MP.\r
84 @.INIMP@>\r
85 @^system dependencies@>\r
86 \r
87 @<Constants...@>=\r
88 @!mem_max=30000; {greatest index in \MP's internal |mem| array;\r
89   must be strictly less than |max_halfword|;\r
90   must be equal to |mem_top| in \.{INIMP}, otherwise |>=mem_top|}\r
91 @!max_internal=100; {maximum number of internal quantities}\r
92 @!buf_size=500; {maximum number of characters simultaneously present in\r
93   current lines of open files; must not exceed |max_halfword|}\r
94 \r
95 @ Here are some macros for common programming idioms.\r
96 \r
97 @d incr(#) == #:=#+1 {increase a variable by unity}\r
98 @d decr(#) == #:=#-1 {decrease a variable by unity}\r
99 @d negate(#) == #:=-# {change the sign of a variable}\r
100 @d double(#) == #:=#+# {multiply a variable by two}\r
101 @d loop == @+ while true do@+ {repeat over and over until a |goto| happens}\r
102 @f loop == xclause\r
103   {\.{WEB}'s |xclause| acts like `\ignorespaces|while true do|\unskip'}\r
104 @d do_nothing == {empty statement}\r
105 @d return == goto exit {terminate a procedure call}\r
106 @f return == nil {\.{WEB} will henceforth say |return| instead of \\{return}}\r
107 \r
108 @* \[2] The character set.\r
109 In order to make \MP\ readily portable to a wide variety of\r
110 computers, all of its input text is converted to an internal eight-bit\r
111 code that includes standard ASCII, the ``American Standard Code for\r
112 Information Interchange.''  This conversion is done immediately when each\r
113 character is read in. Conversely, characters are converted from ASCII to\r
114 the user's external representation just before they are output to a\r
115 text file.\r
116 @^ASCII code@>\r
117 \r
118 @ Since we are assuming that our \PASCAL\ system is able to read and\r
119 write the visible characters of standard ASCII (although not\r
120 necessarily using the ASCII codes to represent them), the following\r
121 assignment statements initialize the standard part of the |xchr| array\r
122 properly, without needing any system-dependent changes. On the other\r
123 hand, it is possible to implement \MP\ with less complete character\r
124 sets, and in such cases it will be necessary to change something here.\r
125 @^system dependencies@>\r
126 \r
127 @<Set init...@>=\r
128 xchr[@'40]:=' ';\r
129 xchr[@'47]:='''';@/\r
130 xchr[@'100]:='@@';\r
131 xchr[@'134]:='\';\r
132 \r
133 @ We need a special routine to read the first line of \MP\ input from\r
134 the user's terminal. This line is different because it is read before we\r
135 have opened the transcript file; there is sort of a ``chicken and\r
136 egg'' problem here. If the user types `\.{input cmr10}' on the first\r
137 line, or if some macro invoked by that line does such an \.{input},\r
138 the transcript file will be named `\.{cmr10.log}'; but if no \.{input}\r
139 commands are performed during the first line of terminal input, the transcript\r
140 file will acquire its default name `\.{mpout.log}'. (The transcript file\r
141 will not contain error messages generated by the first line before the\r
142 first \.{input} command.)\r
143 \r
144 The first line is even more special if we are lucky enough to have an operating\r
145 system that treats \MP\ differently from a run-of-the-mill \PASCAL\ object\r
146 program. It's nice to let the user start running a \MP\ job by typing\r
147 a command line like `\.{MP cmr10}'; in such a case, \MP\ will operate\r
148 as if the first line of input were `\.{cmr10}', i.e., the first line will\r
149 consist of the remainder of the command line, after the part that invoked \MP.\r
150 \r
151 The first line is special also because it may be read before \MP\ has\r
152 input a mem file. In such cases, normal error messages cannot yet\r
153 be given. The following code uses concepts that will be explained later.\r
154 \r
155 @<Report overflow of the input buffer, and abort@>=\r
156 if mem_ident=0 then\r
157   begin write_ln(term_out,'Buffer size exceeded!'); goto final_end;\r
158 @.Buffer size exceeded@>\r
159   end\r
160 else begin cur_input.loc_field:=first; cur_input.limit_field:=last-1;\r
161   overflow("buffer size",buf_size);\r
162 @:MetaPost capacity exceeded buffer size}{\quad buffer size@>\r
163   end\r
164 \r
165 @ Different systems have different ways to get started. But regardless of\r
166 what conventions are adopted, the routine that initializes the terminal\r
167 should satisfy the following specifications:\r
168 \r
169 \yskip\textindent{1)}It should open file |term_in| for input from the\r
170   terminal. (The file |term_out| will already be open for output to the\r
171   terminal.)\r
172 \r
173 \textindent{2)}If the user has given a command line, this line should be\r
174   considered the first line of terminal input. Otherwise the\r
175   user should be prompted with `\.{**}', and the first line of input\r
176   should be whatever is typed in response.\r
177 \r
178 \textindent{3)}The first line of input, which might or might not be a\r
179   command line, should appear in locations |first| to |last-1| of the\r
180   |buffer| array.\r
181 \r
182 \textindent{4)}The global variable |loc| should be set so that the\r
183   character to be read next by \MP\ is in |buffer[loc]|. This\r
184   character should not be blank, and we should have |loc<last|.\r
185 \r
186 \yskip\noindent(It may be necessary to prompt the user several times\r
187 before a non-blank line comes in. The prompt is `\.{**}' instead of the\r
188 later `\.*' because the meaning is slightly different: `\.{input}' need\r
189 not be typed immediately after~`\.{**}'.)\r
190 \r
191 @d loc==cur_input.loc_field {location of first unread character in |buffer|}\r
192 \r
193 @ Strings are created by appending character codes to |str_pool|.\r
194 The |append_char| macro, defined here, does not check to see if the\r
195 value of |pool_ptr| has gotten too high; this test is supposed to be\r
196 made before |append_char| is used.\r
197 \r
198 @ @<Declare the procedure called |do_compaction|@>=\r
199 procedure do_compaction(@!needed:pool_pointer);\r
200 label done;\r
201 var @!str_use:str_number; {a count of strings in use}\r
202 @!r,@!s,@!t:str_number; {strings being manipulated}\r
203 @!p,@!q:pool_pointer; {destination and source for copying string characters}\r
204 begin @<Advance |last_fixed_str| as far as possible and set |str_use|@>;\r
205 r:=last_fixed_str;\r
206 s:=next_str[r];\r
207 p:=str_start[s];\r
208 while s<>str_ptr do\r
209   begin while str_ref[s]=0 do\r
210     @<Advance |s| and add the old |s| to the list of free string numbers;\r
211       then |goto done| if |s=str_ptr|@>;\r
212   r:=s; s:=next_str[s];\r
213   incr(str_use);\r
214   @<Move string |r| back so that |str_start[r]=p|; make |p| the location\r
215     after the end of the string@>;\r
216   end;\r
217 done: @<Move the current string back so that it starts at |p|@>;\r
218 if needed<pool_size then\r
219   @<Make sure that there is room for another string with |needed| characters@>;\r
220 stat @<Account for the compaction and make sure the statistics agree with the\r
221     global versions@>;\r
222 tats@;\r
223 strs_used_up:=str_use;\r
224 end;\r
225 \r
226 @ @<Handle the test for eastward directions when $y_1y_3=y_2^2$;\r
227     either |goto found| or |goto done|@>=\r
228 begin if ab_vs_cd(y1,y2,0,0)<0 then\r
229   begin t:=make_fraction(y1,y1-y2);\r
230   x1:=t_of_the_way(x1)(x2);\r
231   x2:=t_of_the_way(x2)(x3);\r
232   if t_of_the_way(x1)(x2)>=0 then we_found_it;\r
233   end\r
234 else if y3=0 then\r
235   if y1=0 then\r
236     @<Exit to |found| if the derivative $B(x_1,x_2,x_3;t)$ becomes |>=0|@>\r
237   else if x3>=0 then\r
238     begin tt:=unity; goto found;\r
239     end;\r
240 goto done;\r
241 end\r
242 \r
243 @ At this point we know that the derivative of |y(t)| is identically zero,\r
244 and that |x1<0|; but either |x2>=0| or |x3>=0|, so there's some hope of\r
245 traveling east.\r
246 \r
247 @ Now this is really it: \MP\ starts and ends here.\r
248 \r
249 The initial test involving |ready_already| should be deleted if the\r
250 \PASCAL\ runtime system is smart enough to detect such a ``mistake.''\r
251 @^system dependencies@>\r
252 \r
253 @p begin @!{|start_here|}\r
254 history:=fatal_error_stop; {in case we quit during initialization}\r
255 t_open_out; {open the terminal for output}\r
256 if ready_already=314159 then goto start_of_MP;\r
257 @<Check the ``constant'' values...@>@;\r
258 if bad>0 then\r
259   begin wterm_ln('Ouch---my internal constants have been clobbered!',\r
260     '---case ',bad:1);\r
261 @.Ouch...clobbered@>\r
262   goto final_end;\r
263   end;\r
264 initialize; {set global variables to their starting values}\r
265 @!init if not get_strings_started then goto final_end;\r
266 init_tab; {initialize the tables}\r
267 init_prim; {call |primitive| for each primitive}\r
268 init_str_use:=str_ptr; init_pool_ptr:=pool_ptr;@/\r
269 max_str_ptr:=str_ptr; max_pool_ptr:=pool_ptr;\r
270 fix_date_and_time;\r
271 tini@/\r
272 ready_already:=314159;\r
273 start_of_MP: @<Initialize the output routines@>;\r
274 @<Get the first line of input and prepare to start@>;\r
275 history:=spotless; {ready to go!}\r
276 if start_sym>0 then {insert the `\&{everyjob}' symbol}\r
277   begin cur_sym:=start_sym; back_input;\r
278   end;\r
279 main_control; {come to life}\r
280 final_cleanup; {prepare for death}\r
281 end_of_MP: close_files_and_terminate;\r
282 final_end: ready_already:=0;\r
283 end.\r
284 \r
285 @ Here we do whatever is needed to complete \MP's job gracefully on the\r
286 local operating system. The code here might come into play after a fatal\r
287 error; it must therefore consist entirely of ``safe'' operations that\r
288 cannot produce error messages. For example, it would be a mistake to call\r
289 |str_room| or |make_string| at this time, because a call on |overflow|\r
290 might lead to an infinite loop.\r
291 @^system dependencies@>\r
292 \r