some niceties proposed by splint
[mplib] / src / texk / web2c / mpdir / mpost.w
1 % $Id$
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 \font\tenlogo=logo10 % font used for the METAFONT logo
19 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
20
21 \def\title{MetaPost executable}
22 \def\[#1]{#1.}
23 \pdfoutput=1
24
25 @* \[1] Metapost executable.
26
27 Now that all of \MP\ is a library, a separate program is needed to 
28 have our customary command-line interface. 
29
30 @ First, here are the C includes. |avl.h| is needed because of an 
31 |avl_allocator| that is defined in |mplib.h|
32
33 @d true 1
34 @d false 0
35  
36 @c
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <time.h>
41 #include <mplib.h>
42 #include <mpxout.h>
43 #ifdef WIN32
44 #include <process.h>
45 #endif
46 #include <kpathsea/kpathsea.h>
47 extern char *kpathsea_version_string;
48 static const char *mpost_tex_program = "";
49 static int debug = 0; /* debugging for makempx */
50
51 @ Allocating a bit of memory, with error detection:
52
53 @c
54 static void  *mpost_xmalloc (size_t bytes) {
55   void *w = malloc (bytes); 
56   if (w==NULL) {
57     fprintf(stderr,"Out of memory!\n");
58     exit(EXIT_FAILURE);
59   }
60   return w;
61 }
62 static char *mpost_xstrdup(const char *s) {
63   char *w; 
64   if (s==NULL) return NULL;
65   w = strdup(s);
66   if (w==NULL) {
67     fprintf(stderr,"Out of memory!\n");
68     exit(EXIT_FAILURE);
69   }
70   return w;
71 }
72
73
74 @ @c
75 void mpost_run_editor (MP mp, char *fname, int fline) {
76   char *temp, *command, *edit_value;
77   char c;
78   int sdone, ddone;
79   sdone = ddone = 0;
80   edit_value = kpse_var_value ("MPEDIT");
81   if (edit_value == NULL)
82     edit_value = getenv("EDITOR");
83   if (edit_value == NULL) {
84     fprintf (stderr,"call_edit: can't find a suitable MPEDIT or EDITOR variable\n");
85     exit(mp_status(mp));    
86   }
87   command = (string) mpost_xmalloc (strlen (edit_value) + strlen(fname) + 11 + 3);
88   temp = command;
89   while ((c = *edit_value++) != (char)0) {
90       if (c == '%')   {
91         switch (c = *edit_value++) {
92             case 'd':
93               if (ddone) {
94             fprintf (stderr,"call_edit: `%%d' appears twice in editor command\n");
95             exit(EXIT_FAILURE);  
96           }
97           sprintf (temp, "%d", fline);
98           while (*temp != '\0')
99             temp++;
100           ddone = 1;
101           break;
102             case 's':
103           if (sdone) {
104             fprintf (stderr,"call_edit: `%%s' appears twice in editor command\n");
105             exit(EXIT_FAILURE);
106           }
107           while (*fname != '\0')
108                     *temp++ = *fname++;
109           *temp++ = '.';
110                   *temp++ = 'm';
111                   *temp++ = 'p';
112           sdone = 1;
113           break;
114             case '\0':
115           *temp++ = '%';
116           /* Back up to the null to force termination.  */
117               edit_value--;
118               break;
119             default:
120               *temp++ = '%';
121               *temp++ = c;
122               break;
123             }
124          } else {
125         *temp++ = c;
126      }
127    }
128   *temp = '\0';
129   if (system (command) != 0)
130     fprintf (stderr, "! Trouble executing `%s'.\n", command);
131   exit(EXIT_FAILURE);
132 }
133
134
135 @<Register the callback routines@>=
136 options->run_editor = mpost_run_editor;
137
138 @
139 @c 
140 string normalize_quotes (const char *name, const char *mesg) {
141     int quoted = false;
142     int must_quote = (strchr(name, ' ') != NULL);
143     /* Leave room for quotes and NUL. */
144     string ret = (string)mpost_xmalloc(strlen(name)+3);
145     string p;
146     const_string q;
147     p = ret;
148     if (must_quote)
149         *p++ = '"';
150     for (q = name; *q != '\0'; q++) {
151         if (*q == '"')
152             quoted = !quoted;
153         else
154             *p++ = *q;
155     }
156     if (must_quote)
157         *p++ = '"';
158     *p = '\0';
159     if (quoted) {
160         fprintf(stderr, "! Unbalanced quotes in %s %s\n", mesg, name);
161         exit(EXIT_FAILURE);
162     }
163     return ret;
164 }
165
166 @ @c 
167 static char *makempx_find_file (MPX mpx, const char *nam, const char *mode, int ftype) {
168   int fmt, req;
169   (void) mpx;
170   if (mode[0] != 'r') { 
171      return strdup(nam);
172   }
173   req = 1; fmt = -1;
174   switch(ftype) {
175   case mpx_tfm_format:       fmt = kpse_tfm_format; break;
176   case mpx_vf_format:        fmt = kpse_vf_format; req = 0; break;
177   case mpx_trfontmap_format: fmt = kpse_mpsupport_format; break;
178   case mpx_trcharadj_format: fmt = kpse_mpsupport_format; break;
179   case mpx_desc_format:      fmt = kpse_troff_font_format; break;
180   case mpx_fontdesc_format:  fmt = kpse_troff_font_format; break;
181   case mpx_specchar_format:  fmt = kpse_mpsupport_format; break;
182   }
183   if (fmt<0) return NULL;
184   return  kpse_find_file (nam, fmt, req);
185 }
186
187 @ Invoke makempx (or troffmpx) to make sure there is an up-to-date
188    .mpx file for a given .mp file.  (Original from John Hobby 3/14/90) 
189
190 @d default_args " --parse-first-line --interaction=nonstopmode"
191 @d TEX     "tex"
192 @d TROFF   "soelim | eqn -Tps -d$$ | troff -Tps"
193
194 @c
195 #ifndef MPXCOMMAND
196 #define MPXCOMMAND "makempx"
197 #endif
198 int mpost_run_make_mpx (MP mp, char *mpname, char *mpxname) {
199   int ret;
200   string cnf_cmd = kpse_var_value ("MPXCOMMAND");
201   
202   if (cnf_cmd != NULL && (strcmp (cnf_cmd, "0")==0)) {
203     /* If they turned off this feature, just return success.  */
204     ret = 0;
205
206   } else {
207     /* We will invoke something. Compile-time default if nothing else.  */
208     string cmd;
209     string qmpname = kpse_find_file (normalize_quotes(mpname, "mpname"),kpse_mp_format, 1);
210     string qmpxname = normalize_quotes(mpxname, "mpxname");
211     if (cnf_cmd) {
212       if (mp_troff_mode(mp))
213         cmd = concatn (cnf_cmd, " -troff ",
214                      qmpname, " ", qmpxname, NULL);
215       else if (mpost_tex_program!=NULL && *mpost_tex_program != '\0')
216         cmd = concatn (cnf_cmd, " -tex=", mpost_tex_program, " ",
217                      qmpname, " ", qmpxname, NULL);
218       else
219         cmd = concatn (cnf_cmd, " -tex ", qmpname, " ", qmpxname, NULL);
220   
221       /* Run it.  */
222       ret = system (cmd);
223       free (cmd);
224     } else {
225       makempx_options * mpxopt;
226       char *s = NULL;
227       char *maincmd = NULL;
228       int mpxmode = mp_troff_mode(mp);
229       const char *mpversion = mp_metapost_version () ;
230       mpxopt = mpost_xmalloc(sizeof(makempx_options));
231       if (mpost_tex_program && *mpost_tex_program) {
232         maincmd = mpost_xstrdup(mpost_tex_program);
233       } else {
234         if (mpxmode == mpx_tex_mode) {
235           s = kpse_var_value("TEX");
236           if (!s) s = kpse_var_value("MPXMAINCMD");
237           if (!s) s = mpost_xstrdup (TEX);
238           maincmd = (char *)mpost_xmalloc (strlen(s)+strlen(default_args)+1);
239           strcpy(maincmd,s);
240           strcat(maincmd,default_args);
241           free(s);
242         } else {
243           s = kpse_var_value("TROFF");
244           if (!s) s = kpse_var_value("MPXMAINCMD");
245           if (!s) s = mpost_xstrdup (TROFF);
246           maincmd = s;
247         }
248       }
249       mpxopt->mode = mpxmode;
250       mpxopt->cmd  = maincmd;
251       mpxopt->mptexpre = kpse_var_value("MPTEXPRE");
252       mpxopt->mpname = qmpname;
253       mpxopt->mpxname = qmpxname;
254       mpxopt->debug = debug;
255       mpxopt->find_file = makempx_find_file;
256       {
257         char *banner = "% Written by metapost version ";
258         mpxopt->banner = mpost_xmalloc(strlen(mpversion)+strlen(banner)+1);
259         strcpy (mpxopt->banner, banner);
260         strcat (mpxopt->banner, mpversion);
261       }
262       ret = mp_makempx(mpxopt);
263       free(mpxopt->cmd);
264       free(mpxopt->mptexpre);
265       free(mpxopt);
266     }
267     free (qmpname);
268     free (qmpxname);
269   }
270
271   free (cnf_cmd);
272   return ret == 0;
273 }
274
275
276 @<Register the callback routines@>=
277 if (!nokpse)
278   options->run_make_mpx = mpost_run_make_mpx;
279
280
281 @ @c 
282 static int get_random_seed (void) {
283   int ret ;
284 #if defined (HAVE_GETTIMEOFDAY)
285   struct timeval tv;
286   gettimeofday(&tv, NULL);
287   ret = (tv.tv_usec + 1000000 * tv.tv_usec);
288 #elif defined (HAVE_FTIME)
289   struct timeb tb;
290   ftime(&tb);
291   ret = (tb.millitm + 1000 * tb.time);
292 #else
293   time_t clock = time ((time_t*)NULL);
294   struct tm *tmptr = localtime(&clock);
295   ret = (tmptr->tm_sec + 60*(tmptr->tm_min + 60*tmptr->tm_hour));
296 #endif
297   return ret;
298 }
299
300 @ @<Register the callback routines@>=
301 options->random_seed = get_random_seed();
302
303 @ @c 
304 char *mpost_find_file(MP mp, const char *fname, const char *fmode, int ftype)  {
305   size_t l ;
306   char *s = NULL;
307   (void)mp;
308   if (fmode[0]=='r') {
309         if (ftype>=mp_filetype_text) {
310       s = kpse_find_file (fname, kpse_mp_format, 0); 
311     } else {
312     switch(ftype) {
313     case mp_filetype_program: 
314       l = strlen(fname);
315           if (l>3 && strcmp(fname+l-3,".mf")==0) {
316             s = kpse_find_file (fname, kpse_mf_format, 0); 
317       } else {
318             s = kpse_find_file (fname, kpse_mp_format, 0); 
319       }
320       break;
321     case mp_filetype_memfile: 
322       s = kpse_find_file (fname, kpse_mem_format, 0); 
323       break;
324     case mp_filetype_metrics: 
325       s = kpse_find_file (fname, kpse_tfm_format, 0); 
326       break;
327     case mp_filetype_fontmap: 
328       s = kpse_find_file (fname, kpse_fontmap_format, 0); 
329       break;
330     case mp_filetype_font: 
331       s = kpse_find_file (fname, kpse_type1_format, 0); 
332       break;
333     case mp_filetype_encoding: 
334       s = kpse_find_file (fname, kpse_enc_format, 0); 
335       break;
336     }
337     }
338   } else {
339     s = mpost_xstrdup(fname); /* when writing */
340   }
341   return s;
342 }
343
344 @  @<Register the callback routines@>=
345 if (!nokpse)
346   options->find_file = mpost_find_file;
347
348 @ @c 
349 void *mpost_open_file(MP mp, const char *fname, const char *fmode, int ftype)  {
350   char realmode[3];
351   char *s;
352   if (ftype==mp_filetype_terminal) {
353     return (fmode[0] == 'r' ? stdin : stdout);
354   } else if (ftype==mp_filetype_error) {
355     return stderr;
356   } else { 
357     s = mpost_find_file (mp, fname, fmode, ftype);
358     if (s!=NULL) {
359       void *ret = NULL;
360       realmode[0] = *fmode;
361           realmode[1] = 'b';
362           realmode[2] = '\0';
363       ret = fopen(s,realmode);
364       free(s);
365       return ret;
366     }
367   }
368   return NULL;
369 }
370
371 @  @<Register the callback routines@>=
372 if (!nokpse)
373   options->open_file = mpost_open_file;
374
375
376 @ At the moment, the command line is very simple.
377
378 @d option_is(A) ((strncmp(argv[a],"--" A, strlen(A)+2)==0) || 
379        (strncmp(argv[a],"-" A, strlen(A)+1)==0))
380 @d option_arg(B) (optarg && strncmp(optarg,B, strlen(B))==0)
381
382
383 @<Read and set command line options@>=
384 {
385   char *optarg;
386   boolean ini_version_test = false;
387   while (++a<argc) {
388     optarg = strstr(argv[a],"=") ;
389     if (optarg!=NULL) {
390       optarg++;
391       if (!*optarg)  optarg=NULL;
392     }
393     if (option_is("ini")) {
394       ini_version_test = true;
395     } else if (option_is("debug")) {
396       debug = 1;
397     } else if (option_is ("kpathsea-debug")) {
398       kpathsea_debug |= atoi (optarg);
399     } else if (option_is("mem")) {
400       options->mem_name = mpost_xstrdup(optarg);
401       if (!user_progname) 
402             user_progname = optarg;
403     } else if (option_is("jobname")) {
404       options->job_name = mpost_xstrdup(optarg);
405     } else if (option_is ("progname")) {
406       user_progname = optarg;
407     } else if (option_is("troff")) {
408       options->troff_mode = true;
409     } else if (option_is ("tex")) {
410       mpost_tex_program = optarg;
411     } else if (option_is("interaction")) {
412       if (option_arg("batchmode")) {
413         options->interaction = mp_batch_mode;
414       } else if (option_arg("nonstopmode")) {
415         options->interaction = mp_nonstop_mode;
416       } else if (option_arg("scrollmode")) {
417         options->interaction = mp_scroll_mode;
418       } else if (option_arg("errorstopmode")) {
419         options->interaction = mp_error_stop_mode;
420       } else {
421         fprintf(stdout,"unknown option argument %s\n", argv[a]);
422       }
423     } else if (option_is("no-kpathsea")) {
424       nokpse=1;
425     } else if (option_is("help")) {
426       @<Show help and exit@>;
427     } else if (option_is("version")) {
428       @<Show version and exit@>;
429     } else if (option_is("")) {
430       continue; /* ignore unknown options */
431     } else {
432       break;
433     }
434   }
435   options->ini_version = ini_version_test;
436 }
437
438
439 @<Show help...@>=
440 {
441 fprintf(stdout,
442 "\n"
443 "Usage: mpost [OPTION] [MPNAME[.mp]] [COMMANDS]\n"
444 "\n"
445 "  Run MetaPost on MPNAME, usually creating MPNAME.NNN (and perhaps\n"
446 "  MPNAME.tfm), where NNN are the character numbers generated.\n"
447 "  Any remaining COMMANDS are processed as MetaPost input,\n"
448 "  after MPNAME is read.\n\n");
449 fprintf(stdout,
450 "  If no arguments or options are specified, prompt for input.\n"
451 "\n"
452 "  -ini                    be inimpost, for dumping mems\n"
453 "  -interaction=STRING     set interaction mode (STRING=batchmode/nonstopmode/\n"
454 "                          scrollmode/errorstopmode)\n"
455 "  -jobname=STRING         set the job name to STRING\n"
456 "  -progname=STRING        set program (and mem) name to STRING\n");
457 fprintf(stdout,
458 "  -tex=TEXPROGRAM         use TEXPROGRAM for text labels\n"
459 "  -kpathsea-debug=NUMBER  set path searching debugging flags according to\n"
460 "                          the bits of NUMBER\n"
461 "  -mem=MEMNAME            use MEMNAME instead of program name or a %%& line\n"
462 "  -troff                  set the prologues variable, use `makempx -troff'\n"
463 "  -help                   display this help and exit\n"
464 "  -version                output version information and exit\n"
465 "\n"
466 "Email bug reports to mp-implementors@@tug.org.\n"
467 "\n");
468   exit(EXIT_SUCCESS);
469 }
470
471
472 @<Show version...@>=
473 {
474 fprintf(stdout, 
475 "\n"
476 "MetaPost %s\n"
477 "Copyright 2008 AT&T Bell Laboratories.\n"
478 "There is NO warranty.  Redistribution of this software is\n"
479 "covered by the terms of both the MetaPost copyright and\n"
480 "the Lesser GNU General Public License.\n"
481 "For more information about these matters, see the file\n"
482 "named COPYING and the MetaPost source.\n"
483 "Primary author of MetaPost: John Hobby.\n"
484 "Current maintainer of MetaPost: Taco Hoekwater.\n"
485 "\n", mp_metapost_version());
486   exit(EXIT_SUCCESS);
487 }
488
489 @ The final part of the command line, after option processing, is
490 stored in the \MP\ instance, this will be taken as the first line of
491 input.
492
493 @d command_line_size 256
494
495 @<Copy the rest of the command line@>=
496 {
497   options->command_line = mpost_xmalloc(command_line_size);
498   strcpy(options->command_line,"");
499   if (a<argc) {
500     k=0;
501     for(;a<argc;a++) {
502       char *c = argv[a];
503       while (*c != '\0') {
504             if (k<(command_line_size-1)) {
505           options->command_line[k++] = *c;
506         }
507         c++;
508       }
509       options->command_line[k++] = ' ';
510     }
511         while (k>0) {
512       if (options->command_line[(k-1)] == ' ') 
513         k--; 
514       else 
515         break;
516     }
517     options->command_line[k] = '\0';
518   }
519 }
520
521 @ A simple function to get numerical |texmf.cnf| values
522 @c
523 int setup_var (int def, const char *var_name, int nokpse) {
524   if (!nokpse) {
525     char * expansion = kpse_var_value (var_name);
526     if (expansion) {
527       int conf_val = atoi (expansion);
528       free (expansion);
529       if (conf_val > 0) {
530         return conf_val;
531       }
532     }
533   }
534   return def;
535 }
536
537 @ @<Set up the banner line@>=
538 {
539   const char *mpversion = mp_metapost_version () ;
540   const char * banner = "This is MetaPost, version ";
541   const char * kpsebanner_start = " (";
542   const char * kpsebanner_stop = ")";
543   options->banner = mpost_xmalloc(strlen(banner)+
544                             strlen(mpversion)+
545                             strlen(kpsebanner_start)+
546                             strlen(kpathsea_version_string)+
547                             strlen(kpsebanner_stop)+1);
548   strcpy (options->banner, banner);
549   strcat (options->banner, mpversion);
550   strcat (options->banner, kpsebanner_start);
551   strcat (options->banner, kpathsea_version_string);
552   strcat (options->banner, kpsebanner_stop);
553 }
554
555
556 @ Now this is really it: \MP\ starts and ends here.
557
558 @d xfree(A) if (A!=NULL) free(A)
559
560 @c 
561 int main (int argc, char **argv) { /* |start_here| */
562   int k; /* index into buffer */
563   int history; /* the exit status */
564   MP mp; /* a metapost instance */
565   struct MP_options * options; /* instance options */
566   int a=0; /* argc counter */
567   int nokpse = 0; /* switch to {\it not} enable kpse */
568   char *user_progname = NULL; /* If the user overrides argv[0] with -progname.  */
569   options = mp_options();
570   options->ini_version       = false;
571   options->print_found_names = true;
572   @<Read and set command line options@>;
573   if (!nokpse)
574     kpse_set_program_name("mpost",user_progname);  
575   if(putenv((char *)"engine=metapost"))
576     fprintf(stdout,"warning: could not set up $engine\n");
577   options->main_memory       = setup_var (50000,"main_memory",nokpse);
578   options->hash_size         = setup_var (16384,"hash_size",nokpse);
579   options->max_in_open       = setup_var (25,"max_in_open",nokpse);
580   options->param_size        = setup_var (1500,"param_size",nokpse);
581   options->error_line        = setup_var (79,"error_line",nokpse);
582   options->half_error_line   = setup_var (50,"half_error_line",nokpse);
583   options->max_print_line    = setup_var (100,"max_print_line",nokpse);
584   @<Set up the banner line@>;
585   @<Copy the rest of the command line@>;
586   @<Register the callback routines@>;
587   mp = mp_initialize(options);
588   xfree(options->command_line);
589   xfree(options->mem_name);
590   xfree(options->job_name);
591   xfree(options->banner);
592   free(options);
593   if (mp==NULL)
594         exit(EXIT_FAILURE);
595   history = mp_status(mp);
596   if (history)
597         exit(history);
598   history = mp_run(mp);
599   (void)mp_finish(mp);
600   exit(history);
601 }
602