1// -*- C++ -*-
2/* Copyright (C) 1989-1992, 2000, 2001, 2002, 2003
3   Free Software Foundation, Inc.
4     Written by James Clark (jjc@jclark.com)
5
6This file is part of groff.
7
8groff is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
10Software Foundation; either version 2, or (at your option) any later
11version.
12
13groff is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16for more details.
17
18You should have received a copy of the GNU General Public License along
19with groff; see the file COPYING.  If not, write to the Free Software
20Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22#include "pic.h"
23
24extern int yyparse();
25extern "C" const char *Version_string;
26
27output *out;
28char *graphname;		// the picture box name in TeX mode
29
30int flyback_flag;
31int zero_length_line_flag = 0;
32// Non-zero means we're using a groff driver.
33int driver_extension_flag = 1;
34int compatible_flag = 0;
35int safer_flag = 1;
36int command_char = '.';		// the character that introduces lines
37				// that should be passed through tranparently
38static int lf_flag = 1;		// non-zero if we should attempt to understand
39				// lines beginning with `.lf'
40
41// Non-zero means a parse error was encountered.
42static int had_parse_error = 0;
43
44void do_file(const char *filename);
45
46class top_input : public input {
47  FILE *fp;
48  int bol;
49  int eof;
50  int push_back[3];
51  int start_lineno;
52public:
53  top_input(FILE *);
54  int get();
55  int peek();
56  int get_location(const char **, int *);
57};
58
59top_input::top_input(FILE *p) : fp(p), bol(1), eof(0)
60{
61  push_back[0] = push_back[1] = push_back[2] = EOF;
62  start_lineno = current_lineno;
63}
64
65int top_input::get()
66{
67  if (eof)
68    return EOF;
69  if (push_back[2] != EOF) {
70    int c = push_back[2];
71    push_back[2] = EOF;
72    return c;
73  }
74  else if (push_back[1] != EOF) {
75    int c = push_back[1];
76    push_back[1] = EOF;
77    return c;
78  }
79  else if (push_back[0] != EOF) {
80    int c = push_back[0];
81    push_back[0] = EOF;
82    return c;
83  }
84  int c = getc(fp);
85  while (invalid_input_char(c)) {
86    error("invalid input character code %1", int(c));
87    c = getc(fp);
88    bol = 0;
89  }
90  if (bol && c == '.') {
91    c = getc(fp);
92    if (c == 'P') {
93      c = getc(fp);
94      if (c == 'F' || c == 'E') {
95	int d = getc(fp);
96	if (d != EOF)
97	  ungetc(d, fp);
98	if (d == EOF || d == ' ' || d == '\n' || compatible_flag) {
99	  eof = 1;
100	  flyback_flag = c == 'F';
101	  return EOF;
102	}
103	push_back[0] = c;
104	push_back[1] = 'P';
105	return '.';
106      }
107      if (c == 'S') {
108	c = getc(fp);
109	if (c != EOF)
110	  ungetc(c, fp);
111	if (c == EOF || c == ' ' || c == '\n' || compatible_flag) {
112	  error("nested .PS");
113	  eof = 1;
114	  return EOF;
115	}
116	push_back[0] = 'S';
117	push_back[1] = 'P';
118	return '.';
119      }
120      if (c != EOF)
121	ungetc(c, fp);
122      push_back[0] = 'P';
123      return '.';
124    }
125    else {
126      if (c != EOF)
127	ungetc(c, fp);
128      return '.';
129    }
130  }
131  if (c == '\n') {
132    bol = 1;
133    current_lineno++;
134    return '\n';
135  }
136  bol = 0;
137  if (c == EOF) {
138    eof = 1;
139    error("end of file before .PE or .PF");
140    error_with_file_and_line(current_filename, start_lineno - 1,
141			     ".PS was here");
142  }
143  return c;
144}
145
146int top_input::peek()
147{
148  if (eof)
149    return EOF;
150  if (push_back[2] != EOF)
151    return push_back[2];
152  if (push_back[1] != EOF)
153    return push_back[1];
154  if (push_back[0] != EOF)
155    return push_back[0];
156  int c = getc(fp);
157  while (invalid_input_char(c)) {
158    error("invalid input character code %1", int(c));
159    c = getc(fp);
160    bol = 0;
161  }
162  if (bol && c == '.') {
163    c = getc(fp);
164    if (c == 'P') {
165      c = getc(fp);
166      if (c == 'F' || c == 'E') {
167	int d = getc(fp);
168	if (d != EOF)
169	  ungetc(d, fp);
170	if (d == EOF || d == ' ' || d == '\n' || compatible_flag) {
171	  eof = 1;
172	  flyback_flag = c == 'F';
173	  return EOF;
174	}
175	push_back[0] = c;
176	push_back[1] = 'P';
177	push_back[2] = '.';
178	return '.';
179      }
180      if (c == 'S') {
181	c = getc(fp);
182	if (c != EOF)
183	  ungetc(c, fp);
184	if (c == EOF || c == ' ' || c == '\n' || compatible_flag) {
185	  error("nested .PS");
186	  eof = 1;
187	  return EOF;
188	}
189	push_back[0] = 'S';
190	push_back[1] = 'P';
191	push_back[2] = '.';
192	return '.';
193      }
194      if (c != EOF)
195	ungetc(c, fp);
196      push_back[0] = 'P';
197      push_back[1] = '.';
198      return '.';
199    }
200    else {
201      if (c != EOF)
202	ungetc(c, fp);
203      push_back[0] = '.';
204      return '.';
205    }
206  }
207  if (c != EOF)
208    ungetc(c, fp);
209  if (c == '\n')
210    return '\n';
211  return c;
212}
213
214int top_input::get_location(const char **filenamep, int *linenop)
215{
216  *filenamep = current_filename;
217  *linenop = current_lineno;
218  return 1;
219}
220
221void do_picture(FILE *fp)
222{
223  flyback_flag = 0;
224  int c;
225  a_delete graphname;
226  graphname = strsave("graph");		// default picture name in TeX mode
227  while ((c = getc(fp)) == ' ')
228    ;
229  if (c == '<') {
230    string filename;
231    while ((c = getc(fp)) == ' ')
232      ;
233    while (c != EOF && c != ' ' && c != '\n') {
234      filename += char(c);
235      c = getc(fp);
236    }
237    if (c == ' ') {
238      do {
239	c = getc(fp);
240      } while (c != EOF && c != '\n');
241    }
242    if (c == '\n')
243      current_lineno++;
244    if (filename.length() == 0)
245      error("missing filename after `<'");
246    else {
247      filename += '\0';
248      const char *old_filename = current_filename;
249      int old_lineno = current_lineno;
250      // filenames must be permanent
251      do_file(strsave(filename.contents()));
252      current_filename = old_filename;
253      current_lineno = old_lineno;
254    }
255    out->set_location(current_filename, current_lineno);
256  }
257  else {
258    out->set_location(current_filename, current_lineno);
259    string start_line;
260    while (c != EOF) {
261      if (c == '\n') {
262	current_lineno++;
263	break;
264      }
265      start_line += c;
266      c = getc(fp);
267    }
268    if (c == EOF)
269      return;
270    start_line += '\0';
271    double wid, ht;
272    switch (sscanf(&start_line[0], "%lf %lf", &wid, &ht)) {
273    case 1:
274      ht = 0.0;
275      break;
276    case 2:
277      break;
278    default:
279      ht = wid = 0.0;
280      break;
281    }
282    out->set_desired_width_height(wid, ht);
283    out->set_args(start_line.contents());
284    lex_init(new top_input(fp));
285    if (yyparse()) {
286      had_parse_error = 1;
287      lex_error("giving up on this picture");
288    }
289    parse_cleanup();
290    lex_cleanup();
291
292    // skip the rest of the .PF/.PE line
293    while ((c = getc(fp)) != EOF && c != '\n')
294      ;
295    if (c == '\n')
296      current_lineno++;
297    out->set_location(current_filename, current_lineno);
298  }
299}
300
301void do_file(const char *filename)
302{
303  FILE *fp;
304  if (strcmp(filename, "-") == 0)
305    fp = stdin;
306  else {
307    errno = 0;
308    fp = fopen(filename, "r");
309    if (fp == 0) {
310      delete out;
311      fatal("can't open `%1': %2", filename, strerror(errno));
312    }
313  }
314  out->set_location(filename, 1);
315  current_filename = filename;
316  current_lineno = 1;
317  enum { START, MIDDLE, HAD_DOT, HAD_P, HAD_PS, HAD_l, HAD_lf } state = START;
318  for (;;) {
319    int c = getc(fp);
320    if (c == EOF)
321      break;
322    switch (state) {
323    case START:
324      if (c == '.')
325	state = HAD_DOT;
326      else {
327	putchar(c);
328	if (c == '\n') {
329	  current_lineno++;
330	  state = START;
331	}
332	else
333	  state = MIDDLE;
334      }
335      break;
336    case MIDDLE:
337      putchar(c);
338      if (c == '\n') {
339	current_lineno++;
340	state = START;
341      }
342      break;
343    case HAD_DOT:
344      if (c == 'P')
345	state = HAD_P;
346      else if (lf_flag && c == 'l')
347	state = HAD_l;
348      else {
349	putchar('.');
350	putchar(c);
351	if (c == '\n') {
352	  current_lineno++;
353	  state = START;
354	}
355	else
356	  state = MIDDLE;
357      }
358      break;
359    case HAD_P:
360      if (c == 'S')
361	state = HAD_PS;
362      else  {
363	putchar('.');
364	putchar('P');
365	putchar(c);
366	if (c == '\n') {
367	  current_lineno++;
368	  state = START;
369	}
370	else
371	  state = MIDDLE;
372      }
373      break;
374    case HAD_PS:
375      if (c == ' ' || c == '\n' || compatible_flag) {
376	ungetc(c, fp);
377	do_picture(fp);
378	state = START;
379      }
380      else {
381	fputs(".PS", stdout);
382	putchar(c);
383	state = MIDDLE;
384      }
385      break;
386    case HAD_l:
387      if (c == 'f')
388	state = HAD_lf;
389      else {
390	putchar('.');
391	putchar('l');
392	putchar(c);
393	if (c == '\n') {
394	  current_lineno++;
395	  state = START;
396	}
397	else
398	  state = MIDDLE;
399      }
400      break;
401    case HAD_lf:
402      if (c == ' ' || c == '\n' || compatible_flag) {
403	string line;
404	while (c != EOF) {
405	  line += c;
406	  if (c == '\n') {
407	    current_lineno++;
408	    break;
409	  }
410	  c = getc(fp);
411	}
412	line += '\0';
413	interpret_lf_args(line.contents());
414	printf(".lf%s", line.contents());
415	state = START;
416      }
417      else {
418	fputs(".lf", stdout);
419	putchar(c);
420	state = MIDDLE;
421      }
422      break;
423    default:
424      assert(0);
425    }
426  }
427  switch (state) {
428  case START:
429    break;
430  case MIDDLE:
431    putchar('\n');
432    break;
433  case HAD_DOT:
434    fputs(".\n", stdout);
435    break;
436  case HAD_P:
437    fputs(".P\n", stdout);
438    break;
439  case HAD_PS:
440    fputs(".PS\n", stdout);
441    break;
442  case HAD_l:
443    fputs(".l\n", stdout);
444    break;
445  case HAD_lf:
446    fputs(".lf\n", stdout);
447    break;
448  }
449  if (fp != stdin)
450    fclose(fp);
451}
452
453#ifdef FIG_SUPPORT
454void do_whole_file(const char *filename)
455{
456  // Do not set current_filename.
457  FILE *fp;
458  if (strcmp(filename, "-") == 0)
459    fp = stdin;
460  else {
461    errno = 0;
462    fp = fopen(filename, "r");
463    if (fp == 0)
464      fatal("can't open `%1': %2", filename, strerror(errno));
465  }
466  lex_init(new file_input(fp, filename));
467  if (yyparse())
468    had_parse_error = 1;
469  parse_cleanup();
470  lex_cleanup();
471}
472#endif
473
474void usage(FILE *stream)
475{
476  fprintf(stream, "usage: %s [ -nvC ] [ filename ... ]\n", program_name);
477#ifdef TEX_SUPPORT
478  fprintf(stream, "       %s -t [ -cvzC ] [ filename ... ]\n", program_name);
479#endif
480#ifdef FIG_SUPPORT
481  fprintf(stream, "       %s -f [ -v ] [ filename ]\n", program_name);
482#endif
483}
484
485#if defined(__MSDOS__) || defined(__EMX__)
486static char *fix_program_name(char *arg, char *dflt)
487{
488  if (!arg)
489    return dflt;
490  char *prog = strchr(arg, '\0');
491  for (;;) {
492    if (prog == arg)
493      break;
494    --prog;
495    if (strchr("\\/:", *prog)) {
496      prog++;
497      break;
498    }
499  }
500  char *ext = strchr(prog, '.');
501  if (ext)
502    *ext = '\0';
503  for (char *p = prog; *p; p++)
504    if ('A' <= *p && *p <= 'Z')
505      *p = 'a' + (*p - 'A');
506  return prog;
507}
508#endif /* __MSDOS__ || __EMX__ */
509
510int main(int argc, char **argv)
511{
512  setlocale(LC_NUMERIC, "C");
513#if defined(__MSDOS__) || defined(__EMX__)
514  argv[0] = fix_program_name(argv[0], "pic");
515#endif /* __MSDOS__ || __EMX__ */
516  program_name = argv[0];
517  static char stderr_buf[BUFSIZ];
518  setbuf(stderr, stderr_buf);
519  int opt;
520#ifdef TEX_SUPPORT
521  int tex_flag = 0;
522  int tpic_flag = 0;
523#endif
524#ifdef FIG_SUPPORT
525  int whole_file_flag = 0;
526  int fig_flag = 0;
527#endif
528  static const struct option long_options[] = {
529    { "help", no_argument, 0, CHAR_MAX + 1 },
530    { "version", no_argument, 0, 'v' },
531    { NULL, 0, 0, 0 }
532  };
533  while ((opt = getopt_long(argc, argv, "T:CDSUtcvnxzpf", long_options, NULL))
534	 != EOF)
535    switch (opt) {
536    case 'C':
537      compatible_flag = 1;
538      break;
539    case 'D':
540    case 'T':
541      break;
542    case 'S':
543      safer_flag = 1;
544      break;
545    case 'U':
546      safer_flag = 0;
547      break;
548    case 'f':
549#ifdef FIG_SUPPORT
550      whole_file_flag++;
551      fig_flag++;
552#else
553      fatal("fig support not included");
554#endif
555      break;
556    case 'n':
557      driver_extension_flag = 0;
558      break;
559    case 'p':
560    case 'x':
561      warning("-%1 option is obsolete", char(opt));
562      break;
563    case 't':
564#ifdef TEX_SUPPORT
565      tex_flag++;
566#else
567      fatal("TeX support not included");
568#endif
569      break;
570    case 'c':
571#ifdef TEX_SUPPORT
572      tpic_flag++;
573#else
574      fatal("TeX support not included");
575#endif
576      break;
577    case 'v':
578      {
579	printf("GNU pic (groff) version %s\n", Version_string);
580	exit(0);
581	break;
582      }
583    case 'z':
584      // zero length lines will be printed as dots
585      zero_length_line_flag++;
586      break;
587    case CHAR_MAX + 1: // --help
588      usage(stdout);
589      exit(0);
590      break;
591    case '?':
592      usage(stderr);
593      exit(1);
594      break;
595    default:
596      assert(0);
597    }
598  parse_init();
599#ifdef TEX_SUPPORT
600  if (tpic_flag) {
601    out = make_tpic_output();
602    lf_flag = 0;
603  }
604  else if (tex_flag) {
605    out = make_tex_output();
606    command_char = '\\';
607    lf_flag = 0;
608  }
609  else
610#endif
611#ifdef FIG_SUPPORT
612  if (fig_flag)
613    out = make_fig_output();
614  else
615#endif
616    out = make_troff_output();
617#ifdef FIG_SUPPORT
618  if (whole_file_flag) {
619    if (optind >= argc)
620      do_whole_file("-");
621    else if (argc - optind > 1) {
622      usage(stderr);
623      exit(1);
624    } else
625      do_whole_file(argv[optind]);
626  }
627  else {
628#endif
629    if (optind >= argc)
630      do_file("-");
631    else
632      for (int i = optind; i < argc; i++)
633	do_file(argv[i]);
634#ifdef FIG_SUPPORT
635  }
636#endif
637  delete out;
638  if (ferror(stdout) || fflush(stdout) < 0)
639    fatal("output error");
640  return had_parse_error;
641}
642
643