Better error reporting for invalid mem files
[mplib] / src / texk / web2c / mpdir / memio.w
1 % $Id: psout.w 616 2008-07-09 14:05:55Z taco $
2 %
3 % Copyright 2008 Taco Hoekwater.
4 %
5 % This program is free software: you can redistribute it and/or modify
6 % it under the terms of the GNU General Public License as published by
7 % the Free Software Foundation, either version 2 of the License, or
8 % (at your option) any later version.
9 %
10 % This program is distributed in the hope that it will be useful,
11 % but WITHOUT ANY WARRANTY; without even the implied warranty of
12 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 % GNU General Public License for more details.
14 %
15 % You should have received a copy of the GNU General Public License
16 % along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 %
18 % TeX is a trademark of the American Mathematical Society.
19 % METAFONT is a trademark of Addison-Wesley Publishing Company.
20 % PostScript is a trademark of Adobe Systems Incorporated.
21
22 % Here is TeX material that gets inserted after \input webmac
23
24 \font\tenlogo=logo10 % font used for the METAFONT logo
25 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
26 \def\title{MetaPost MEM reading and writing}
27 \def\topofcontents{\hsize 5.5in
28   \vglue -30pt plus 1fil minus 1.5in
29   \def\?##1]{\hbox to 1in{\hfil##1.\ }}
30   }
31 \def\botofcontents{\vskip 0pt plus 1fil minus 1.5in}
32 \pdfoutput=1
33 \pageno=3
34
35 @ As usual, need true and false.
36
37 @d true 1
38 @d false 0
39 @d null 0 /* the null pointer */
40 @d incr(A)   (A)=(A)+1 /* increase a variable by unity */
41 @d decr(A)   (A)=(A)-1 /* decrease a variable by unity */
42 @d qo(A) (A) /* to read eight bits from a quarterword */
43 @d qi(A) (quarterword)(A) /* to store eight bits in a quarterword */
44 @d max_quarterword 0x3FFF /* largest allowable value in a |quarterword| */
45 @d max_halfword 0xFFFFFFF /* largest allowable value in a |halfword| */
46 @d max_str_ref 127 /* ``infinite'' number of references */
47
48
49 @c
50 #include "config.h"
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include "mplib.h"
55 #include "mpmp.h"
56
57 @ @c void mp_store_mem_file (MP mp) {
58   integer k;  /* all-purpose index */
59   pointer p,q; /* all-purpose pointers */
60   integer x; /* something to dump */
61   four_quarters w; /* four ASCII codes */
62   memory_word WW;
63   @<Create the |mem_ident|, open the mem file,
64     and inform the user that dumping has begun@>;
65   @<Dump constants for consistency check@>;
66   @<Dump the string pool@>;
67   @<Dump the dynamic memory@>;
68   @<Dump the table of equivalents and the hash table@>;
69   @<Dump a few more things and the closing check word@>;
70   @<Close the mem file@>;
71 }
72
73 @ Corresponding to the procedure that dumps a mem file, we also have a function
74 that reads~one~in. The function returns |false| if the dumped mem is
75 incompatible with the present \MP\ table sizes, etc.
76
77 @d too_small(A) { wake_up_terminal;
78   wterm_ln("---! Must increase the "); wterm((A));
79 @.Must increase the x@>
80   goto OFF_BASE;
81   }
82
83 @c 
84 boolean mp_load_mem_file (MP mp) {
85   integer k; /* all-purpose index */
86   pointer p,q; /* all-purpose pointers */
87   integer x; /* something undumped */
88   str_number s; /* some temporary string */
89   four_quarters w; /* four ASCII codes */
90   memory_word WW;
91   @<Undump the string pool@>;
92   @<Undump the dynamic memory@>;
93   @<Undump the table of equivalents and the hash table@>;
94   @<Undump a few more things and the closing check word@>;
95   return true; /* it worked! */
96 OFF_BASE: 
97   wake_up_terminal;
98   wterm_ln("(Fatal mem file error; I'm stymied)\n");
99 @.Fatal mem file error@>
100    return false;
101 }
102
103 @ Mem files consist of |memory_word| items, and we use the following
104 macros to dump words of different types:
105
106 @d dump_wd(A)   { WW=(A);       (mp->write_binary_file)(mp,mp->mem_file,&WW,sizeof(WW)); }
107 @d dump_int(A)  { int cint=(A); (mp->write_binary_file)(mp,mp->mem_file,&cint,sizeof(cint)); }
108 @d dump_hh(A)   { WW.hh=(A);    (mp->write_binary_file)(mp,mp->mem_file,&WW,sizeof(WW)); }
109 @d dump_qqqq(A) { WW.qqqq=(A);  (mp->write_binary_file)(mp,mp->mem_file,&WW,sizeof(WW)); }
110 @d dump_string(A) { dump_int((int)(strlen(A)+1));
111                     (mp->write_binary_file)(mp,mp->mem_file,A,strlen(A)+1); }
112
113 @ The inverse macros are slightly more complicated, since we need to check
114 the range of the values we are reading in. We say `|undump(a)(b)(x)|' to
115 read an integer value |x| that is supposed to be in the range |a<=x<=b|.
116
117 @d mgeti(A) do {
118   size_t wanted = sizeof(A);
119   void *A_ptr = &A;
120   (mp->read_binary_file)(mp, mp->mem_file,&A_ptr,&wanted);
121   if (wanted!=sizeof(A)) goto OFF_BASE;
122 } while (0)
123
124 @d mgetw(A) do {
125   size_t wanted = sizeof(A);
126   void *A_ptr = &A;
127   (mp->read_binary_file)(mp, mp->mem_file,&A_ptr,&wanted);
128   if (wanted!=sizeof(A)) goto OFF_BASE;
129 } while (0)
130
131 @d undump_wd(A)   { mgetw(WW); A=WW; }
132 @d undump_int(A)  { int cint; mgeti(cint); A=cint; }
133 @d undump_hh(A)   { mgetw(WW); A=WW.hh; }
134 @d undump_qqqq(A) { mgetw(WW); A=WW.qqqq; }
135 @d undump_strings(A,B,C) { 
136    undump_int(x); if ( (x<(A)) || (x>(B)) ) goto OFF_BASE; else C=str(x); }
137 @d undump(A,B,C) { undump_int(x); 
138                    if ( (x<(A)) || (x>(int)(B)) ) goto OFF_BASE; else C=x; }
139 @d undump_size(A,B,C,D) { undump_int(x);
140                           if (x<(A)) goto OFF_BASE; 
141                           if (x>(B)) too_small((C)); else D=x; }
142 @d undump_string(A) { 
143   size_t the_wanted; 
144   void *the_string;
145   integer XX=0; 
146   undump_int(XX);
147   the_wanted = (size_t)XX;
148   the_string = mp_xmalloc(mp,(size_t)XX,1);
149   (mp->read_binary_file)(mp,mp->mem_file,&the_string,&the_wanted);
150   A = (char *)the_string;
151   if (the_wanted!=(size_t)XX) goto OFF_BASE;
152 }
153
154 @ The next few sections of the program should make it clear how we use the
155 dump/undump macros.
156
157 @<Dump constants for consistency check@>=
158 x = metapost_magic; dump_int(x);
159 dump_int(mp->mem_top);
160 dump_int((integer)mp->hash_size);
161 dump_int(mp->hash_prime)
162 dump_int(mp->param_size);
163 dump_int(mp->max_in_open);
164
165 @ Sections of a \.{WEB} program that are ``commented out'' still contribute
166 strings to the string pool; therefore \.{INIMP} and \MP\ will have
167 the same strings. (And it is, of course, a good thing that they do.)
168 @.WEB@>
169 @^string pool@>
170
171 @ @c
172 int mp_undump_constants (MP mp) {
173   integer x;
174   undump_int(x); 
175   if (x!=metapost_magic) return x;
176   undump_int(x); mp->mem_top = x;
177   undump_int(x); mp->hash_size = (unsigned)x;
178   undump_int(x); mp->hash_prime = x;
179   undump_int(x); mp->param_size = x;
180   undump_int(x); mp->max_in_open = x;
181   return metapost_magic;
182   OFF_BASE:
183     return -1;
184 }
185
186 @ We do string pool compaction to avoid dumping unused strings.
187
188 @d dump_four_ASCII 
189   w.b0=qi(mp->str_pool[k]); w.b1=qi(mp->str_pool[k+1]);
190   w.b2=qi(mp->str_pool[k+2]); w.b3=qi(mp->str_pool[k+3]);
191   dump_qqqq(w)
192
193 @<Dump the string pool@>=
194 mp_do_compaction(mp, mp->pool_size);
195 dump_int(mp->pool_ptr);
196 dump_int(mp->max_str_ptr);
197 dump_int(mp->str_ptr);
198 k=0;
199 while ( (mp->next_str[k]==k+1) && (k<=mp->max_str_ptr) ) 
200   k++;
201 dump_int(k);
202 while ( k<=mp->max_str_ptr ) { 
203   dump_int(mp->next_str[k]); incr(k);
204 }
205 k=0;
206 while (1)  { 
207   dump_int(mp->str_start[k]); /* TODO: valgrind warning here */
208   if ( k==mp->str_ptr ) {
209     break;
210   } else { 
211     k=mp->next_str[k]; 
212   }
213 }
214 k=0;
215 while (k+4<mp->pool_ptr ) {
216   dump_four_ASCII; k=k+4; 
217 }
218 k=mp->pool_ptr-4; dump_four_ASCII;
219 mp_print_ln(mp); mp_print(mp, "at most "); mp_print_int(mp, mp->max_str_ptr);
220 mp_print(mp, " strings of total length ");
221 mp_print_int(mp, mp->pool_ptr)
222
223 @ @d undump_four_ASCII 
224   undump_qqqq(w);
225   mp->str_pool[k]=(ASCII_code)qo(w.b0); mp->str_pool[k+1]=(ASCII_code)qo(w.b1);
226   mp->str_pool[k+2]=(ASCII_code)qo(w.b2); mp->str_pool[k+3]=(ASCII_code)qo(w.b3)
227
228 @<Undump the string pool@>=
229 undump_int(mp->pool_ptr);
230 mp_reallocate_pool(mp, mp->pool_ptr) ;
231 undump_int(mp->max_str_ptr);
232 mp_reallocate_strings (mp,mp->max_str_ptr) ;
233 undump(0,mp->max_str_ptr,mp->str_ptr);
234 undump(0,mp->max_str_ptr+1,s);
235 for (k=0;k<=s-1;k++) 
236   mp->next_str[k]=k+1;
237 for (k=s;k<=mp->max_str_ptr;k++) 
238   undump(s+1,mp->max_str_ptr+1,mp->next_str[k]);
239 mp->fixed_str_use=0;
240 k=0;
241 while (1) { 
242   undump(0,mp->pool_ptr,mp->str_start[k]);
243   if ( k==mp->str_ptr ) break;
244   mp->str_ref[k]=max_str_ref;
245   incr(mp->fixed_str_use);
246   mp->last_fixed_str=k; k=mp->next_str[k];
247 }
248 k=0;
249 while ( k+4<mp->pool_ptr ) { 
250   undump_four_ASCII; k=k+4;
251 }
252 k=mp->pool_ptr-4; undump_four_ASCII;
253 mp->init_str_use=mp->fixed_str_use; mp->init_pool_ptr=mp->pool_ptr;
254 mp->max_pool_ptr=mp->pool_ptr;
255 mp->strs_used_up=mp->fixed_str_use;
256 mp->pool_in_use=mp->str_start[mp->str_ptr]; mp->strs_in_use=mp->fixed_str_use;
257 mp->max_pl_used=mp->pool_in_use; mp->max_strs_used=mp->strs_in_use;
258 mp->pact_count=0; mp->pact_chars=0; mp->pact_strs=0;
259
260 @ By sorting the list of available spaces in the variable-size portion of
261 |mem|, we are usually able to get by without having to dump very much
262 of the dynamic memory.
263
264 We recompute |var_used| and |dyn_used|, so that \.{INIMP} dumps valid
265 information even when it has not been gathering statistics.
266
267 @<Dump the dynamic memory@>=
268 mp_sort_avail(mp); mp->var_used=0;
269 dump_int(mp->lo_mem_max); dump_int(mp->rover);
270 p=0; q=mp->rover; x=0;
271 do {  
272   for (k=p;k<= q+1;k++) 
273     dump_wd(mp->mem[k]);
274   x=x+q+2-p; mp->var_used=mp->var_used+q-p;
275   p=q+node_size(q); q=rmp_link(q);
276 } while (q!=mp->rover);
277 mp->var_used=mp->var_used+mp->lo_mem_max-p; 
278 mp->dyn_used=mp->mem_end+1-mp->hi_mem_min;
279 for (k=p;k<= mp->lo_mem_max;k++ ) 
280   dump_wd(mp->mem[k]);
281 x=x+mp->lo_mem_max+1-p;
282 dump_int(mp->hi_mem_min); dump_int(mp->avail);
283 for (k=mp->hi_mem_min;k<=mp->mem_end;k++ ) 
284   dump_wd(mp->mem[k]);
285 x=x+mp->mem_end+1-mp->hi_mem_min;
286 p=mp->avail;
287 while ( p!=null ) { 
288   decr(mp->dyn_used); p=mp_link(p);
289 }
290 dump_int(mp->var_used); dump_int(mp->dyn_used);
291 mp_print_ln(mp); mp_print_int(mp, x);
292 mp_print(mp, " memory locations dumped; current usage is ");
293 mp_print_int(mp, mp->var_used); mp_print_char(mp, xord('&')); mp_print_int(mp, mp->dyn_used)
294
295 @ @<Undump the dynamic memory@>=
296 undump(lo_mem_stat_max+1000,hi_mem_stat_min-1,mp->lo_mem_max);
297 undump(lo_mem_stat_max+1,mp->lo_mem_max,mp->rover);
298 p=0; q=mp->rover;
299 do {  
300   for (k=p;k<= q+1; k++) 
301     undump_wd(mp->mem[k]);
302   p=q+node_size(q);
303   if ( (p>mp->lo_mem_max)||((q>=rmp_link(q))&&(rmp_link(q)!=mp->rover)) ) 
304     goto OFF_BASE;
305   q=rmp_link(q);
306 } while (q!=mp->rover);
307 for (k=p;k<=mp->lo_mem_max;k++ ) 
308   undump_wd(mp->mem[k]);
309 undump(mp->lo_mem_max+1,hi_mem_stat_min,mp->hi_mem_min);
310 undump(null,mp->mem_top,mp->avail); mp->mem_end=mp->mem_top;
311 mp->last_pending=spec_head;
312 for (k=mp->hi_mem_min;k<= mp->mem_end;k++) 
313   undump_wd(mp->mem[k]);
314 undump_int(mp->var_used); undump_int(mp->dyn_used)
315
316 @ A different scheme is used to compress the hash table, since its lower region
317 is usually sparse. When |text(p)<>0| for |p<=hash_used|, we output three
318 words: |p|, |hash[p]|, and |eqtb[p]|. The hash table is, of course, densely
319 packed for |p>=hash_used|, so the remaining entries are output in~a~block.
320
321 @<Dump the table of equivalents and the hash table@>=
322 dump_int(mp->hash_used); 
323 mp->st_count=frozen_inaccessible-1-mp->hash_used;
324 for (p=1;p<=mp->hash_used;p++) {
325   if ( text(p)!=0 ) {
326      dump_int(p); dump_hh(mp->hash[p]); dump_hh(mp->eqtb[p]); incr(mp->st_count);
327   }
328 }
329 for (p=mp->hash_used+1;p<=(int)hash_end;p++) {
330   dump_hh(mp->hash[p]); dump_hh(mp->eqtb[p]);
331 }
332 dump_int(mp->st_count);
333 mp_print_ln(mp); mp_print_int(mp, mp->st_count); mp_print(mp, " symbolic tokens")
334
335 @ @<Undump the table of equivalents and the hash table@>=
336 undump(1,frozen_inaccessible,mp->hash_used); 
337 p=0;
338 do {  
339   undump(p+1,mp->hash_used,p); 
340   undump_hh(mp->hash[p]); undump_hh(mp->eqtb[p]);
341 } while (p!=mp->hash_used);
342 for (p=mp->hash_used+1;p<=(int)hash_end;p++ )  { 
343   undump_hh(mp->hash[p]); undump_hh(mp->eqtb[p]);
344 }
345 undump_int(mp->st_count)
346
347 @ We have already printed a lot of statistics, so we set |mp_tracing_stats:=0|
348 to prevent them appearing again.
349
350 @<Dump a few more things and the closing check word@>=
351 dump_int(mp->max_internal);
352 dump_int(mp->int_ptr);
353 for (k=1;k<= mp->int_ptr;k++ ) { 
354   dump_int(mp->internal[k]); 
355   dump_string(mp->int_name[k]);
356 }
357 dump_int(mp->start_sym); 
358 dump_int(mp->interaction); 
359 dump_string(mp->mem_ident);
360 dump_int(mp->bg_loc); dump_int(mp->eg_loc); dump_int(mp->serial_no); dump_int(69073);
361 mp->internal[mp_tracing_stats]=0
362
363 @ @<Undump a few more things and the closing check word@>=
364 undump_int(x);
365 if (x>mp->max_internal) mp_grow_internals(mp,x);
366 undump_int(mp->int_ptr);
367 for (k=1;k<= mp->int_ptr;k++) { 
368   undump_int(mp->internal[k]);
369   undump_string(mp->int_name[k]);
370 }
371 undump(0,frozen_inaccessible,mp->start_sym);
372 if (mp->interaction==mp_unspecified_mode) {
373   undump(mp_unspecified_mode,mp_error_stop_mode,mp->interaction);
374 } else {
375   undump(mp_unspecified_mode,mp_error_stop_mode,x);
376 }
377 undump_string(mp->mem_ident);
378 undump(1,hash_end,mp->bg_loc);
379 undump(1,hash_end,mp->eg_loc);
380 undump_int(mp->serial_no);
381 undump_int(x); 
382 if (x!=69073) goto OFF_BASE
383
384 @ @<Create the |mem_ident|...@>=
385
386   char *tmp = mp_xmalloc(mp,11,1);
387   mp_xfree(mp->mem_ident);
388   mp->mem_ident = mp_xmalloc(mp,256,1);
389   mp_snprintf(tmp,11,"%04d.%02d.%02d",
390           (int)mp_round_unscaled(mp, mp->internal[mp_year]),
391           (int)mp_round_unscaled(mp, mp->internal[mp_month]),
392           (int)mp_round_unscaled(mp, mp->internal[mp_day]));
393   mp_snprintf(mp->mem_ident,256," (mem=%s %s)",mp->job_name, tmp);
394   mp_xfree(tmp);
395   mp_pack_job_name(mp, ".mem");
396   while (! mp_w_open_out(mp, &mp->mem_file) )
397     mp_prompt_file_name(mp, "mem file name", ".mem");
398   mp_print_nl(mp, "Beginning to dump on file ");
399 @.Beginning to dump...@>
400   mp_print(mp, mp->name_of_file); 
401   mp_print_nl(mp, mp->mem_ident);
402 }
403
404 @ @<Close the mem file@>=
405 (mp->close_file)(mp,mp->mem_file)