1114402Sru// -*- C++ -*-
2114402Sru/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002
3114402Sru   Free Software Foundation, Inc.
4114402Sru     Written by James Clark (jjc@jclark.com)
5114402Sru
6114402SruThis file is part of groff.
7114402Sru
8114402Srugroff is free software; you can redistribute it and/or modify it under
9114402Sruthe terms of the GNU General Public License as published by the Free
10114402SruSoftware Foundation; either version 2, or (at your option) any later
11114402Sruversion.
12114402Sru
13114402Srugroff is distributed in the hope that it will be useful, but WITHOUT ANY
14114402SruWARRANTY; without even the implied warranty of MERCHANTABILITY or
15114402SruFITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16114402Srufor more details.
17114402Sru
18114402SruYou should have received a copy of the GNU General Public License along
19114402Sruwith groff; see the file COPYING.  If not, write to the Free Software
20151497SruFoundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21114402Sru
22114402Sru#include "eqn.h"
23114402Sru#include "stringclass.h"
24114402Sru#include "device.h"
25114402Sru#include "searchpath.h"
26114402Sru#include "macropath.h"
27114402Sru#include "htmlhint.h"
28114402Sru#include "pbox.h"
29114402Sru#include "ctype.h"
30114402Sru
31114402Sru#define STARTUP_FILE "eqnrc"
32114402Sru
33114402Sruextern int yyparse();
34114402Sruextern "C" const char *Version_string;
35114402Sru
36114402Srustatic char *delim_search    (char *, int);
37114402Srustatic int   inline_equation (FILE *, string &, string &);
38114402Sru
39114402Sruchar start_delim = '\0';
40114402Sruchar end_delim = '\0';
41114402Sruint non_empty_flag;
42114402Sruint inline_flag;
43114402Sruint draw_flag = 0;
44114402Sruint one_size_reduction_flag = 0;
45114402Sruint compatible_flag = 0;
46114402Sruint no_newline_in_delim_flag = 0;
47114402Sruint html = 0;
48114402Sru
49114402Sru
50114402Sruint read_line(FILE *fp, string *p)
51114402Sru{
52114402Sru  p->clear();
53114402Sru  int c = -1;
54114402Sru  while ((c = getc(fp)) != EOF) {
55114402Sru    if (!invalid_input_char(c))
56114402Sru      *p += char(c);
57114402Sru    else
58114402Sru      error("invalid input character code `%1'", c);
59114402Sru    if (c == '\n')
60114402Sru      break;
61114402Sru  }
62114402Sru  current_lineno++;
63114402Sru  return p->length() > 0;
64114402Sru}
65114402Sru
66114402Sruvoid do_file(FILE *fp, const char *filename)
67114402Sru{
68114402Sru  string linebuf;
69114402Sru  string str;
70114402Sru  printf(".lf 1 %s\n", filename);
71114402Sru  current_filename = filename;
72114402Sru  current_lineno = 0;
73114402Sru  while (read_line(fp, &linebuf)) {
74114402Sru    if (linebuf.length() >= 4
75114402Sru	&& linebuf[0] == '.' && linebuf[1] == 'l' && linebuf[2] == 'f'
76114402Sru	&& (linebuf[3] == ' ' || linebuf[3] == '\n' || compatible_flag)) {
77114402Sru      put_string(linebuf, stdout);
78114402Sru      linebuf += '\0';
79114402Sru      if (interpret_lf_args(linebuf.contents() + 3))
80114402Sru	current_lineno--;
81114402Sru    }
82114402Sru    else if (linebuf.length() >= 4
83114402Sru	     && linebuf[0] == '.'
84114402Sru	     && linebuf[1] == 'E'
85114402Sru	     && linebuf[2] == 'Q'
86114402Sru	     && (linebuf[3] == ' ' || linebuf[3] == '\n'
87114402Sru		 || compatible_flag)) {
88114402Sru      put_string(linebuf, stdout);
89114402Sru      int start_lineno = current_lineno + 1;
90114402Sru      str.clear();
91114402Sru      for (;;) {
92114402Sru	if (!read_line(fp, &linebuf))
93114402Sru	  fatal("end of file before .EN");
94114402Sru	if (linebuf.length() >= 3 && linebuf[0] == '.' && linebuf[1] == 'E') {
95114402Sru	  if (linebuf[2] == 'N'
96114402Sru	      && (linebuf.length() == 3 || linebuf[3] == ' '
97114402Sru		  || linebuf[3] == '\n' || compatible_flag))
98114402Sru	    break;
99114402Sru	  else if (linebuf[2] == 'Q' && linebuf.length() > 3
100114402Sru		   && (linebuf[3] == ' ' || linebuf[3] == '\n'
101114402Sru		       || compatible_flag))
102114402Sru	    fatal("nested .EQ");
103114402Sru	}
104114402Sru	str += linebuf;
105114402Sru      }
106114402Sru      str += '\0';
107114402Sru      start_string();
108114402Sru      init_lex(str.contents(), current_filename, start_lineno);
109114402Sru      non_empty_flag = 0;
110114402Sru      inline_flag = 0;
111114402Sru      yyparse();
112114402Sru      restore_compatibility();
113114402Sru      if (non_empty_flag) {
114114402Sru	printf(".lf %d\n", current_lineno - 1);
115114402Sru	output_string();
116114402Sru      }
117114402Sru      printf(".lf %d\n", current_lineno);
118114402Sru      put_string(linebuf, stdout);
119114402Sru    }
120114402Sru    else if (start_delim != '\0' && linebuf.search(start_delim) >= 0
121114402Sru	     && inline_equation(fp, linebuf, str))
122114402Sru      ;
123114402Sru    else
124114402Sru      put_string(linebuf, stdout);
125114402Sru  }
126114402Sru  current_filename = 0;
127114402Sru  current_lineno = 0;
128114402Sru}
129114402Sru
130114402Sru// Handle an inline equation.  Return 1 if it was an inline equation,
131114402Sru// otherwise.
132114402Srustatic int inline_equation(FILE *fp, string &linebuf, string &str)
133114402Sru{
134114402Sru  linebuf += '\0';
135114402Sru  char *ptr = &linebuf[0];
136114402Sru  char *start = delim_search(ptr, start_delim);
137114402Sru  if (!start) {
138114402Sru    // It wasn't a delimiter after all.
139114402Sru    linebuf.set_length(linebuf.length() - 1); // strip the '\0'
140114402Sru    return 0;
141114402Sru  }
142114402Sru  start_string();
143114402Sru  inline_flag = 1;
144114402Sru  for (;;) {
145114402Sru    if (no_newline_in_delim_flag && strchr(start + 1, end_delim) == 0) {
146114402Sru      error("missing `%1'", end_delim);
147114402Sru      char *nl = strchr(start + 1, '\n');
148114402Sru      if (nl != 0)
149114402Sru	*nl = '\0';
150114402Sru      do_text(ptr);
151114402Sru      break;
152114402Sru    }
153114402Sru    int start_lineno = current_lineno;
154114402Sru    *start = '\0';
155114402Sru    do_text(ptr);
156114402Sru    ptr = start + 1;
157114402Sru    str.clear();
158114402Sru    for (;;) {
159114402Sru      char *end = strchr(ptr, end_delim);
160114402Sru      if (end != 0) {
161114402Sru	*end = '\0';
162114402Sru	str += ptr;
163114402Sru	ptr = end + 1;
164114402Sru	break;
165114402Sru      }
166114402Sru      str += ptr;
167114402Sru      if (!read_line(fp, &linebuf))
168114402Sru	fatal("unterminated `%1' at line %2, looking for `%3'",
169114402Sru	      start_delim, start_lineno, end_delim);
170114402Sru      linebuf += '\0';
171114402Sru      ptr = &linebuf[0];
172114402Sru    }
173114402Sru    str += '\0';
174114402Sru    if (html) {
175114402Sru      printf(".as1 %s ", LINE_STRING);
176114402Sru      html_begin_suppress();
177114402Sru      printf("\n");
178114402Sru    }
179114402Sru    init_lex(str.contents(), current_filename, start_lineno);
180114402Sru    yyparse();
181114402Sru    if (html) {
182114402Sru      printf(".as1 %s ", LINE_STRING);
183114402Sru      html_end_suppress();
184114402Sru      printf("\n");
185114402Sru    }
186114402Sru    start = delim_search(ptr, start_delim);
187114402Sru    if (start == 0) {
188114402Sru      char *nl = strchr(ptr, '\n');
189114402Sru      if (nl != 0)
190114402Sru	*nl = '\0';
191114402Sru      do_text(ptr);
192114402Sru      break;
193114402Sru    }
194114402Sru  }
195114402Sru  restore_compatibility();
196114402Sru  printf(".lf %d\n", current_lineno);
197114402Sru  output_string();
198114402Sru  printf(".lf %d\n", current_lineno + 1);
199114402Sru  return 1;
200114402Sru}
201114402Sru
202114402Sru/* Search for delim.  Skip over number register and string names etc. */
203114402Sru
204114402Srustatic char *delim_search(char *ptr, int delim)
205114402Sru{
206114402Sru  while (*ptr) {
207114402Sru    if (*ptr == delim)
208114402Sru      return ptr;
209114402Sru    if (*ptr++ == '\\') {
210114402Sru      switch (*ptr) {
211114402Sru      case 'n':
212114402Sru      case '*':
213114402Sru      case 'f':
214114402Sru      case 'g':
215114402Sru      case 'k':
216114402Sru	switch (*++ptr) {
217114402Sru	case '\0':
218114402Sru	case '\\':
219114402Sru	  break;
220114402Sru	case '(':
221114402Sru	  if (*++ptr != '\\' && *ptr != '\0'
222114402Sru	      && *++ptr != '\\' && *ptr != '\0')
223114402Sru	      ptr++;
224114402Sru	  break;
225114402Sru	case '[':
226114402Sru	  while (*++ptr != '\0')
227114402Sru	    if (*ptr == ']') {
228114402Sru	      ptr++;
229114402Sru	      break;
230114402Sru	    }
231114402Sru	  break;
232114402Sru	default:
233114402Sru	  ptr++;
234114402Sru	  break;
235114402Sru	}
236114402Sru	break;
237114402Sru      case '\\':
238114402Sru      case '\0':
239114402Sru	break;
240114402Sru      default:
241114402Sru	ptr++;
242114402Sru	break;
243114402Sru      }
244114402Sru    }
245114402Sru  }
246114402Sru  return 0;
247114402Sru}
248114402Sru
249114402Sruvoid usage(FILE *stream)
250114402Sru{
251114402Sru  fprintf(stream,
252114402Sru    "usage: %s [ -rvDCNR ] -dxx -fn -sn -pn -mn -Mdir -Ts [ files ... ]\n",
253114402Sru    program_name);
254114402Sru}
255114402Sru
256114402Sruint main(int argc, char **argv)
257114402Sru{
258114402Sru  program_name = argv[0];
259114402Sru  static char stderr_buf[BUFSIZ];
260114402Sru  setbuf(stderr, stderr_buf);
261114402Sru  int opt;
262114402Sru  int load_startup_file = 1;
263114402Sru  static const struct option long_options[] = {
264114402Sru    { "help", no_argument, 0, CHAR_MAX + 1 },
265114402Sru    { "version", no_argument, 0, 'v' },
266114402Sru    { NULL, 0, 0, 0 }
267114402Sru  };
268114402Sru  while ((opt = getopt_long(argc, argv, "DCRvd:f:p:s:m:T:M:rN", long_options,
269114402Sru			    NULL))
270114402Sru	 != EOF)
271114402Sru    switch (opt) {
272114402Sru    case 'C':
273114402Sru      compatible_flag = 1;
274114402Sru      break;
275114402Sru    case 'R':			// don't load eqnrc
276114402Sru      load_startup_file = 0;
277114402Sru      break;
278114402Sru    case 'M':
279114402Sru      config_macro_path.command_line_dir(optarg);
280114402Sru      break;
281114402Sru    case 'v':
282114402Sru      {
283114402Sru	printf("GNU eqn (groff) version %s\n", Version_string);
284114402Sru	exit(0);
285114402Sru	break;
286114402Sru      }
287114402Sru    case 'd':
288114402Sru      if (optarg[0] == '\0' || optarg[1] == '\0')
289114402Sru	error("-d requires two character argument");
290114402Sru      else if (invalid_input_char(optarg[0]))
291114402Sru	error("bad delimiter `%1'", optarg[0]);
292114402Sru      else if (invalid_input_char(optarg[1]))
293114402Sru	error("bad delimiter `%1'", optarg[1]);
294114402Sru      else {
295114402Sru	start_delim = optarg[0];
296114402Sru	end_delim = optarg[1];
297114402Sru      }
298114402Sru      break;
299114402Sru    case 'f':
300114402Sru      set_gfont(optarg);
301114402Sru      break;
302114402Sru    case 'T':
303114402Sru      device = optarg;
304114402Sru      if (strcmp(device, "ps:html") == 0) {
305114402Sru	device = "ps";
306114402Sru	html = 1;
307114402Sru      }
308114402Sru      break;
309114402Sru    case 's':
310114402Sru      if (!set_gsize(optarg))
311114402Sru	error("invalid size `%1'", optarg);
312114402Sru      break;
313114402Sru    case 'p':
314114402Sru      {
315114402Sru	int n;
316114402Sru	if (sscanf(optarg, "%d", &n) == 1)
317114402Sru	  set_script_reduction(n);
318114402Sru	else
319114402Sru	  error("bad size `%1'", optarg);
320114402Sru      }
321114402Sru      break;
322114402Sru    case 'm':
323114402Sru      {
324114402Sru	int n;
325114402Sru	if (sscanf(optarg, "%d", &n) == 1)
326114402Sru	  set_minimum_size(n);
327114402Sru	else
328114402Sru	  error("bad size `%1'", optarg);
329114402Sru      }
330114402Sru      break;
331114402Sru    case 'r':
332114402Sru      one_size_reduction_flag = 1;
333114402Sru      break;
334114402Sru    case 'D':
335114402Sru      warning("-D option is obsolete: use `set draw_lines 1' instead");
336114402Sru      draw_flag = 1;
337114402Sru      break;
338114402Sru    case 'N':
339114402Sru      no_newline_in_delim_flag = 1;
340114402Sru      break;
341114402Sru    case CHAR_MAX + 1: // --help
342114402Sru      usage(stdout);
343114402Sru      exit(0);
344114402Sru      break;
345114402Sru    case '?':
346114402Sru      usage(stderr);
347114402Sru      exit(1);
348114402Sru      break;
349114402Sru    default:
350114402Sru      assert(0);
351114402Sru    }
352114402Sru  init_table(device);
353114402Sru  init_char_table();
354114402Sru  printf(".if !'\\*(.T'%s' "
355114402Sru	 ".if !'\\*(.T'html' "	// the html device uses `-Tps' to render
356114402Sru				// equations as images
357114402Sru	 ".tm warning: %s should have been given a `-T\\*(.T' option\n",
358114402Sru	 device, program_name);
359114402Sru  printf(".if '\\*(.T'html' "
360114402Sru	 ".if !'%s'ps' "
361114402Sru	 ".tm warning: %s should have been given a `-Tps' option\n",
362114402Sru	 device, program_name);
363114402Sru  printf(".if '\\*(.T'html' "
364114402Sru	 ".if !'%s'ps' "
365114402Sru	 ".tm warning: (it is advisable to invoke groff via: groff -Thtml -e)\n",
366114402Sru	 device);
367114402Sru  if (load_startup_file) {
368114402Sru    char *path;
369114402Sru    FILE *fp = config_macro_path.open_file(STARTUP_FILE, &path);
370114402Sru    if (fp) {
371114402Sru      do_file(fp, path);
372114402Sru      fclose(fp);
373114402Sru      a_delete path;
374114402Sru    }
375114402Sru  }
376114402Sru  if (optind >= argc)
377114402Sru    do_file(stdin, "-");
378114402Sru  else
379114402Sru    for (int i = optind; i < argc; i++)
380114402Sru      if (strcmp(argv[i], "-") == 0)
381114402Sru	do_file(stdin, "-");
382114402Sru      else {
383114402Sru	errno = 0;
384114402Sru	FILE *fp = fopen(argv[i], "r");
385114402Sru	if (!fp)
386114402Sru	  fatal("can't open `%1': %2", argv[i], strerror(errno));
387114402Sru	else {
388114402Sru	  do_file(fp, argv[i]);
389114402Sru	  fclose(fp);
390114402Sru	}
391114402Sru      }
392114402Sru  if (ferror(stdout) || fflush(stdout) < 0)
393114402Sru    fatal("output error");
394114402Sru  return 0;
395114402Sru}
396