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