make the backend a callback
[mplib] / src / texk / web2c / mpdir / mpost.w
1 % $Id: mpost.w $
2 % MetaPost command-line program, by Taco Hoekwater.  Public domain.
3
4 \font\tenlogo=logo10 % font used for the METAFONT logo
5 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
6
7 \def\title{MetaPost executable}
8 \def\[#1]{#1.}
9 \pdfoutput=1
10
11 @* \[1] Metapost executable.
12
13 Now that all of \MP\ is a library, a separate program is needed to 
14 have our customary command-line interface. 
15
16 @ First, here are the C includes. |avl.h| is needed because of an 
17 |avl_allocator| that is defined in |mplib.h|
18
19 @d true 1
20 @d false 0
21  
22 @c
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <mplib.h>
28 #define HAVE_PROTOTYPES 1
29 #include <kpathsea/progname.h>
30 #include <kpathsea/tex-file.h>
31 #include <kpathsea/variable.h>
32 extern unsigned kpathsea_debug;
33 #include <kpathsea/concatn.h>
34 static string mpost_tex_program = "";
35
36 @ Allocating a bit of memory, with error detection:
37
38 @c
39 void  *xmalloc (size_t bytes) {
40   void *w = malloc (bytes);
41   if (w==NULL) {
42     fprintf(stderr,"Out of memory!\n");
43     exit(EXIT_FAILURE);
44   }
45   return w;
46 }
47 char *xstrdup(const char *s) {
48   char *w; 
49   if (s==NULL) return NULL;
50   w = strdup(s);
51   if (w==NULL) {
52     fprintf(stderr,"Out of memory!\n");
53     exit(EXIT_FAILURE);
54   }
55   return w;
56 }
57
58
59
60 @c
61 void mpost_run_editor (MP mp, char *fname, int fline) {
62   if (mp)
63     fprintf(stdout,"Ok, bye (%s,%d)!",fname, fline);
64   exit(EXIT_SUCCESS);
65 }
66
67
68 @<Register the callback routines@>=
69 options->run_editor = mpost_run_editor;
70
71 @
72 @c 
73 string normalize_quotes (const char *name, const char *mesg) {
74     int quoted = false;
75     int must_quote = (strchr(name, ' ') != NULL);
76     /* Leave room for quotes and NUL. */
77     string ret = (string)xmalloc(strlen(name)+3);
78     string p;
79     const_string q;
80     p = ret;
81     if (must_quote)
82         *p++ = '"';
83     for (q = name; *q; q++) {
84         if (*q == '"')
85             quoted = !quoted;
86         else
87             *p++ = *q;
88     }
89     if (must_quote)
90         *p++ = '"';
91     *p = '\0';
92     if (quoted) {
93         fprintf(stderr, "! Unbalanced quotes in %s %s\n", mesg, name);
94         exit(EXIT_FAILURE);
95     }
96     return ret;
97 }
98
99
100 @ Invoke makempx (or troffmpx) to make sure there is an up-to-date
101    .mpx file for a given .mp file.  (Original from John Hobby 3/14/90) 
102
103 @c
104
105 #ifndef MPXCOMMAND
106 #define MPXCOMMAND "makempx"
107 #endif
108 int mpost_run_make_mpx (MP mp, char *mpname, char *mpxname) {
109   int ret;
110   string cnf_cmd = kpse_var_value ("MPXCOMMAND");
111   
112   if (cnf_cmd && (strcmp (cnf_cmd, "0")==0)) {
113     /* If they turned off this feature, just return success.  */
114     ret = 0;
115
116   } else {
117     /* We will invoke something. Compile-time default if nothing else.  */
118     string cmd;
119     string qmpname = normalize_quotes(mpname, "mpname");
120     string qmpxname = normalize_quotes(mpxname, "mpxname");
121     if (!cnf_cmd)
122       cnf_cmd = xstrdup (MPXCOMMAND);
123
124     if (mp_troff_mode(mp))
125       cmd = concatn (cnf_cmd, " -troff ",
126                      qmpname, " ", qmpxname, NULL);
127     else if (mpost_tex_program && *mpost_tex_program)
128       cmd = concatn (cnf_cmd, " -tex=", mpost_tex_program, " ",
129                      qmpname, " ", qmpxname, NULL);
130     else
131       cmd = concatn (cnf_cmd, " -tex ", qmpname, " ", qmpxname, NULL);
132
133     /* Run it.  */
134     ret = system (cmd);
135     free (cmd);
136     free (qmpname);
137     free (qmpxname);
138   }
139
140   free (cnf_cmd);
141   return ret == 0;
142 }
143
144
145 @<Register the callback routines@>=
146 if (!nokpse)
147   options->run_make_mpx = mpost_run_make_mpx;
148
149
150 @ @c 
151 static int get_random_seed (void) {
152   int ret ;
153 #if defined (HAVE_GETTIMEOFDAY)
154   struct timeval tv;
155   gettimeofday(&tv, NULL);
156   ret = (tv.tv_usec + 1000000 * tv.tv_usec);
157 #elif defined (HAVE_FTIME)
158   struct timeb tb;
159   ftime(&tb);
160   ret = (tb.millitm + 1000 * tb.time);
161 #else
162   time_t clock = time ((time_t*)NULL);
163   struct tm *tmptr = localtime(&clock);
164   ret = (tmptr->tm_sec + 60*(tmptr->tm_min + 60*tmptr->tm_hour));
165 #endif
166   return ret;
167 }
168
169 @ @<Register the callback routines@>=
170 options->random_seed = get_random_seed();
171
172 @ @c 
173 char *mpost_find_file(char *fname, char *fmode, int ftype)  {
174   char *s;
175   int l ;
176   if (fmode[0]=='r') {
177         if (ftype>=mp_filetype_text) {
178       s = kpse_find_file (fname, kpse_mp_format, 0); 
179     } else {
180     switch(ftype) {
181     case mp_filetype_program: 
182       l = strlen(fname);
183           if (l>3 && strcmp(fname+l-3,".mf")==0) {
184             s = kpse_find_file (fname, kpse_mf_format, 0); 
185       } else {
186             s = kpse_find_file (fname, kpse_mp_format, 0); 
187       }
188       break;
189     case mp_filetype_memfile: 
190       s = kpse_find_file (fname, kpse_mem_format, 0); 
191       break;
192     case mp_filetype_metrics: 
193       s = kpse_find_file (fname, kpse_tfm_format, 0); 
194       break;
195     case mp_filetype_fontmap: 
196       s = kpse_find_file (fname, kpse_fontmap_format, 0); 
197       break;
198     case mp_filetype_font: 
199       s = kpse_find_file (fname, kpse_type1_format, 0); 
200       break;
201     case mp_filetype_encoding: 
202       s = kpse_find_file (fname, kpse_enc_format, 0); 
203       break;
204     }
205     }
206   } else {
207     s = xstrdup(fname); /* when writing */
208   }
209   return s;
210 }
211
212 @  @<Register the callback routines@>=
213 if (!nokpse)
214   options->find_file = mpost_find_file;
215
216 @ @c 
217 void *mpost_open_file(char *fname, char *fmode, int ftype)  {
218   char *s;
219   if (ftype==mp_filetype_terminal) {
220     return (fmode[0] == 'r' ? stdin : stdout);
221   } else if (ftype==mp_filetype_error) {
222     return stderr;
223   } else { 
224     s = mpost_find_file (fname, fmode, ftype);
225     if (s!=NULL) {
226       return fopen(s,fmode);
227     }
228   }
229   return NULL;
230 }
231
232 @  @<Register the callback routines@>=
233 if (!nokpse)
234   options->open_file = mpost_open_file;
235
236
237 @ At the moment, the command line is very simple.
238
239 @d option_is(A) ((strncmp(argv[a],"--" A, strlen(A)+2)==0) || 
240        (strncmp(argv[a],"-" A, strlen(A)+1)==0))
241 @d option_arg(B) (optarg && strncmp(optarg,B, strlen(B))==0)
242
243
244 @<Read and set commmand line options@>=
245 {
246   char *optarg;
247   while (++a<argc) {
248     optarg = strstr(argv[a],"=") ;
249     if (optarg!=NULL) {
250       optarg++;
251       if (!*optarg)  optarg=NULL;
252     }
253     if (option_is("ini")) {
254       options->ini_version = true;
255     } else if (option_is ("kpathsea-debug")) {
256       kpathsea_debug |= atoi (optarg);
257     } else if (option_is("mem")) {
258       options->mem_name = xstrdup(optarg);
259       if (!user_progname) 
260             user_progname = optarg;
261     } else if (option_is("jobname")) {
262       options->job_name = xstrdup(optarg);
263     } else if (option_is ("progname")) {
264       user_progname = optarg;
265     } else if (option_is("troff")) {
266       options->troff_mode = true;
267     } else if (option_is ("tex")) {
268       mpost_tex_program = optarg;
269     } else if (option_is("interaction")) {
270       if (option_arg("batchmode")) {
271         options->interaction = mp_batch_mode;
272       } else if (option_arg("nonstopmode")) {
273         options->interaction = mp_nonstop_mode;
274       } else if (option_arg("scrollmode")) {
275         options->interaction = mp_scroll_mode;
276       } else if (option_arg("errorstopmode")) {
277         options->interaction = mp_error_stop_mode;
278       } else {
279         fprintf(stdout,"unknown option argument %s\n", argv[a]);
280       }
281     } else if (option_is("no-kpathsea")) {
282       nokpse=1;
283     } else if (option_is("help")) {
284       @<Show help and exit@>;
285     } else if (option_is("version")) {
286       @<Show version and exit@>;
287     } else if (option_is("")) {
288       continue; /* ignore unknown options */
289     } else {
290       break;
291     }
292   }
293 }
294
295
296 @<Show help...@>=
297 {
298 fprintf(stdout,
299 "\n"
300 "Usage: mpost [OPTION] [MPNAME[.mp]] [COMMANDS]\n"
301 "\n"
302 "  Run MetaPost on MPNAME, usually creating MPNAME.NNN (and perhaps\n"
303 "  MPNAME.tfm), where NNN are the character numbers generated.\n"
304 "  Any remaining COMMANDS are processed as MetaPost input,\n"
305 "  after MPNAME is read.\n"
306 "\n"
307 "  If no arguments or options are specified, prompt for input.\n"
308 "\n"
309 "  -ini                    be inimpost, for dumping mems\n"
310 "  -interaction=STRING     set interaction mode (STRING=batchmode/nonstopmode/\n"
311 "                          scrollmode/errorstopmode)\n"
312 "  -jobname=STRING         set the job name to STRING\n"
313 "  -progname=STRING        set program (and mem) name to STRING\n"
314 "  -tex=TEXPROGRAM         use TEXPROGRAM for text labels\n"
315 "  -kpathsea-debug=NUMBER  set path searching debugging flags according to\n"
316 "                          the bits of NUMBER\n"
317 "  -mem=MEMNAME            use MEMNAME instead of program name or a %%& line\n"
318 "  -troff                  set the prologues variable, use `makempx -troff'\n"
319 "  -help                   display this help and exit\n"
320 "  -version                output version information and exit\n"
321 "\n"
322 "Email bug reports to mp-implementors@@tug.org.\n"
323 "\n");
324   exit(EXIT_SUCCESS);
325 }
326
327
328 @<Show version...@>=
329 {
330 fprintf(stdout,
331 "\n"
332 "MetaPost %s (CWeb version %s)\n"
333 "Copyright 2008 AT&T Bell Laboratories.\n"
334 "There is NO warranty.  Redistribution of this software is\n"
335 "covered by the terms of both the MetaPost copyright and\n"
336 "the Lesser GNU General Public License.\n"
337 "For more information about these matters, see the file\n"
338 "named COPYING and the MetaPost source.\n"
339 "Primary author of MetaPost: John Hobby.\n"
340 "Current maintainer of MetaPost: Taco Hoekwater.\n"
341 "\n", mp_metapost_version(mp), mp_mplib_version(mp));
342   exit(EXIT_SUCCESS);
343 }
344
345 @ The final part of the command line, after option processing, is
346 stored in the \MP\ instance, this will be taken as the first line of
347 input.
348
349 @d command_line_size 256
350
351 @<Copy the rest of the command line@>=
352 {
353   options->command_line = xmalloc(command_line_size);
354   strcpy(options->command_line,"");
355   if (a<argc) {
356     k=0;
357     for(;a<argc;a++) {
358       char *c = argv[a];
359       while (*c) {
360             if (k<(command_line_size-1)) {
361           options->command_line[k++] = *c;
362         }
363         c++;
364       }
365       options->command_line[k++] = ' ';
366     }
367         while (k>0) {
368       if (options->command_line[(k-1)] == ' ') 
369         k--; 
370       else 
371         break;
372     }
373     options->command_line[k] = 0;
374   }
375 }
376
377 @ A simple function to get numerical |texmf.cnf| values
378 @c
379 int setup_var (int def, char *var_name, int nokpse) {
380   if (!nokpse) {
381     char * expansion = kpse_var_value (var_name);
382     if (expansion) {
383       int conf_val = atoi (expansion);
384       free (expansion);
385       if (conf_val > 0) {
386         return conf_val;
387       }
388     }
389   }
390   return def;
391 }
392
393
394 @ Now this is really it: \MP\ starts and ends here.
395
396 @d xfree(A) if (A!=NULL) free(A)
397
398 @c 
399 int main (int argc, char **argv) { /* |start_here| */
400   int a=0; /* argc counter */
401   int k; /* index into buffer */
402   int history; /* the exit status */
403   MP mp; /* a metapost instance */
404   struct MP_options * options; /* instance options */
405   int nokpse = 0; /* switch to {\it not} enable kpse */
406   char *user_progname = NULL; /* If the user overrides argv[0] with -progname.  */
407   options = mp_options();
408   options->ini_version       = false;
409   options->print_found_names = true;
410   @<Read and set commmand line options@>;
411   if (!nokpse)
412     kpse_set_program_name("mpost",user_progname);  
413   if(putenv("engine=newmetapost"))
414     fprintf(stdout,"warning: could not set up $engine\n");
415   options->main_memory       = setup_var (50000,"main_memory",nokpse);
416   options->hash_size         = setup_var (9500,"hash_size",nokpse);
417   options->hash_prime        = 7919;
418   options->max_in_open       = setup_var (25,"max_in_open",nokpse);
419   options->param_size        = setup_var (1500,"param_size",nokpse);
420   options->error_line        = setup_var (79,"error_line",nokpse);
421   options->half_error_line   = setup_var (50,"half_error_line",nokpse);
422   options->max_print_line    = setup_var (100,"max_print_line",nokpse);
423   @<Copy the rest of the command line@>;
424   @<Register the callback routines@>;
425   mp = mp_new(options);
426   xfree(options->command_line);
427   xfree(options->mem_name);
428   xfree(options->job_name);
429   free(options);
430   if (mp==NULL)
431         exit(EXIT_FAILURE);
432   history = mp_initialize(mp);
433   if (history) 
434     exit(history);
435   history = mp_run(mp);
436   mp_free(mp);
437   exit(history);
438 }
439