1// -*- C++ -*-
2/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002
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 "eqn.h"
23#include "stringclass.h"
24#include "device.h"
25#include "searchpath.h"
26#include "macropath.h"
27#include "htmlhint.h"
28#include "pbox.h"
29#include "ctype.h"
30
31#define STARTUP_FILE "eqnrc"
32
33extern int yyparse();
34extern "C" const char *Version_string;
35
36static char *delim_search    (char *, int);
37static int   inline_equation (FILE *, string &, string &);
38
39char start_delim = '\0';
40char end_delim = '\0';
41int non_empty_flag;
42int inline_flag;
43int draw_flag = 0;
44int one_size_reduction_flag = 0;
45int compatible_flag = 0;
46int no_newline_in_delim_flag = 0;
47int html = 0;
48
49
50int read_line(FILE *fp, string *p)
51{
52  p->clear();
53  int c = -1;
54  while ((c = getc(fp)) != EOF) {
55    if (!invalid_input_char(c))
56      *p += char(c);
57    else
58      error("invalid input character code `%1'", c);
59    if (c == '\n')
60      break;
61  }
62  current_lineno++;
63  return p->length() > 0;
64}
65
66void do_file(FILE *fp, const char *filename)
67{
68  string linebuf;
69  string str;
70  printf(".lf 1 %s\n", filename);
71  current_filename = filename;
72  current_lineno = 0;
73  while (read_line(fp, &linebuf)) {
74    if (linebuf.length() >= 4
75	&& linebuf[0] == '.' && linebuf[1] == 'l' && linebuf[2] == 'f'
76	&& (linebuf[3] == ' ' || linebuf[3] == '\n' || compatible_flag)) {
77      put_string(linebuf, stdout);
78      linebuf += '\0';
79      if (interpret_lf_args(linebuf.contents() + 3))
80	current_lineno--;
81    }
82    else if (linebuf.length() >= 4
83	     && linebuf[0] == '.'
84	     && linebuf[1] == 'E'
85	     && linebuf[2] == 'Q'
86	     && (linebuf[3] == ' ' || linebuf[3] == '\n'
87		 || compatible_flag)) {
88      put_string(linebuf, stdout);
89      int start_lineno = current_lineno + 1;
90      str.clear();
91      for (;;) {
92	if (!read_line(fp, &linebuf))
93	  fatal("end of file before .EN");
94	if (linebuf.length() >= 3 && linebuf[0] == '.' && linebuf[1] == 'E') {
95	  if (linebuf[2] == 'N'
96	      && (linebuf.length() == 3 || linebuf[3] == ' '
97		  || linebuf[3] == '\n' || compatible_flag))
98	    break;
99	  else if (linebuf[2] == 'Q' && linebuf.length() > 3
100		   && (linebuf[3] == ' ' || linebuf[3] == '\n'
101		       || compatible_flag))
102	    fatal("nested .EQ");
103	}
104	str += linebuf;
105      }
106      str += '\0';
107      start_string();
108      init_lex(str.contents(), current_filename, start_lineno);
109      non_empty_flag = 0;
110      inline_flag = 0;
111      yyparse();
112      restore_compatibility();
113      if (non_empty_flag) {
114	printf(".lf %d\n", current_lineno - 1);
115	output_string();
116      }
117      printf(".lf %d\n", current_lineno);
118      put_string(linebuf, stdout);
119    }
120    else if (start_delim != '\0' && linebuf.search(start_delim) >= 0
121	     && inline_equation(fp, linebuf, str))
122      ;
123    else
124      put_string(linebuf, stdout);
125  }
126  current_filename = 0;
127  current_lineno = 0;
128}
129
130// Handle an inline equation.  Return 1 if it was an inline equation,
131// otherwise.
132static int inline_equation(FILE *fp, string &linebuf, string &str)
133{
134  linebuf += '\0';
135  char *ptr = &linebuf[0];
136  char *start = delim_search(ptr, start_delim);
137  if (!start) {
138    // It wasn't a delimiter after all.
139    linebuf.set_length(linebuf.length() - 1); // strip the '\0'
140    return 0;
141  }
142  start_string();
143  inline_flag = 1;
144  for (;;) {
145    if (no_newline_in_delim_flag && strchr(start + 1, end_delim) == 0) {
146      error("missing `%1'", end_delim);
147      char *nl = strchr(start + 1, '\n');
148      if (nl != 0)
149	*nl = '\0';
150      do_text(ptr);
151      break;
152    }
153    int start_lineno = current_lineno;
154    *start = '\0';
155    do_text(ptr);
156    ptr = start + 1;
157    str.clear();
158    for (;;) {
159      char *end = strchr(ptr, end_delim);
160      if (end != 0) {
161	*end = '\0';
162	str += ptr;
163	ptr = end + 1;
164	break;
165      }
166      str += ptr;
167      if (!read_line(fp, &linebuf))
168	fatal("unterminated `%1' at line %2, looking for `%3'",
169	      start_delim, start_lineno, end_delim);
170      linebuf += '\0';
171      ptr = &linebuf[0];
172    }
173    str += '\0';
174    if (html) {
175      printf(".as1 %s ", LINE_STRING);
176      html_begin_suppress();
177      printf("\n");
178    }
179    init_lex(str.contents(), current_filename, start_lineno);
180    yyparse();
181    if (html) {
182      printf(".as1 %s ", LINE_STRING);
183      html_end_suppress();
184      printf("\n");
185    }
186    start = delim_search(ptr, start_delim);
187    if (start == 0) {
188      char *nl = strchr(ptr, '\n');
189      if (nl != 0)
190	*nl = '\0';
191      do_text(ptr);
192      break;
193    }
194  }
195  restore_compatibility();
196  printf(".lf %d\n", current_lineno);
197  output_string();
198  printf(".lf %d\n", current_lineno + 1);
199  return 1;
200}
201
202/* Search for delim.  Skip over number register and string names etc. */
203
204static char *delim_search(char *ptr, int delim)
205{
206  while (*ptr) {
207    if (*ptr == delim)
208      return ptr;
209    if (*ptr++ == '\\') {
210      switch (*ptr) {
211      case 'n':
212      case '*':
213      case 'f':
214      case 'g':
215      case 'k':
216	switch (*++ptr) {
217	case '\0':
218	case '\\':
219	  break;
220	case '(':
221	  if (*++ptr != '\\' && *ptr != '\0'
222	      && *++ptr != '\\' && *ptr != '\0')
223	      ptr++;
224	  break;
225	case '[':
226	  while (*++ptr != '\0')
227	    if (*ptr == ']') {
228	      ptr++;
229	      break;
230	    }
231	  break;
232	default:
233	  ptr++;
234	  break;
235	}
236	break;
237      case '\\':
238      case '\0':
239	break;
240      default:
241	ptr++;
242	break;
243      }
244    }
245  }
246  return 0;
247}
248
249void usage(FILE *stream)
250{
251  fprintf(stream,
252    "usage: %s [ -rvDCNR ] -dxx -fn -sn -pn -mn -Mdir -Ts [ files ... ]\n",
253    program_name);
254}
255
256int main(int argc, char **argv)
257{
258  program_name = argv[0];
259  static char stderr_buf[BUFSIZ];
260  setbuf(stderr, stderr_buf);
261  int opt;
262  int load_startup_file = 1;
263  static const struct option long_options[] = {
264    { "help", no_argument, 0, CHAR_MAX + 1 },
265    { "version", no_argument, 0, 'v' },
266    { NULL, 0, 0, 0 }
267  };
268  while ((opt = getopt_long(argc, argv, "DCRvd:f:p:s:m:T:M:rN", long_options,
269			    NULL))
270	 != EOF)
271    switch (opt) {
272    case 'C':
273      compatible_flag = 1;
274      break;
275    case 'R':			// don't load eqnrc
276      load_startup_file = 0;
277      break;
278    case 'M':
279      config_macro_path.command_line_dir(optarg);
280      break;
281    case 'v':
282      {
283	printf("GNU eqn (groff) version %s\n", Version_string);
284	exit(0);
285	break;
286      }
287    case 'd':
288      if (optarg[0] == '\0' || optarg[1] == '\0')
289	error("-d requires two character argument");
290      else if (invalid_input_char(optarg[0]))
291	error("bad delimiter `%1'", optarg[0]);
292      else if (invalid_input_char(optarg[1]))
293	error("bad delimiter `%1'", optarg[1]);
294      else {
295	start_delim = optarg[0];
296	end_delim = optarg[1];
297      }
298      break;
299    case 'f':
300      set_gfont(optarg);
301      break;
302    case 'T':
303      device = optarg;
304      if (strcmp(device, "ps:html") == 0) {
305	device = "ps";
306	html = 1;
307      }
308      break;
309    case 's':
310      if (!set_gsize(optarg))
311	error("invalid size `%1'", optarg);
312      break;
313    case 'p':
314      {
315	int n;
316	if (sscanf(optarg, "%d", &n) == 1)
317	  set_script_reduction(n);
318	else
319	  error("bad size `%1'", optarg);
320      }
321      break;
322    case 'm':
323      {
324	int n;
325	if (sscanf(optarg, "%d", &n) == 1)
326	  set_minimum_size(n);
327	else
328	  error("bad size `%1'", optarg);
329      }
330      break;
331    case 'r':
332      one_size_reduction_flag = 1;
333      break;
334    case 'D':
335      warning("-D option is obsolete: use `set draw_lines 1' instead");
336      draw_flag = 1;
337      break;
338    case 'N':
339      no_newline_in_delim_flag = 1;
340      break;
341    case CHAR_MAX + 1: // --help
342      usage(stdout);
343      exit(0);
344      break;
345    case '?':
346      usage(stderr);
347      exit(1);
348      break;
349    default:
350      assert(0);
351    }
352  init_table(device);
353  init_char_table();
354  printf(".if !'\\*(.T'%s' "
355	 ".if !'\\*(.T'html' "	// the html device uses `-Tps' to render
356				// equations as images
357	 ".tm warning: %s should have been given a `-T\\*(.T' option\n",
358	 device, program_name);
359  printf(".if '\\*(.T'html' "
360	 ".if !'%s'ps' "
361	 ".tm warning: %s should have been given a `-Tps' option\n",
362	 device, program_name);
363  printf(".if '\\*(.T'html' "
364	 ".if !'%s'ps' "
365	 ".tm warning: (it is advisable to invoke groff via: groff -Thtml -e)\n",
366	 device);
367  if (load_startup_file) {
368    char *path;
369    FILE *fp = config_macro_path.open_file(STARTUP_FILE, &path);
370    if (fp) {
371      do_file(fp, path);
372      fclose(fp);
373      a_delete path;
374    }
375  }
376  if (optind >= argc)
377    do_file(stdin, "-");
378  else
379    for (int i = optind; i < argc; i++)
380      if (strcmp(argv[i], "-") == 0)
381	do_file(stdin, "-");
382      else {
383	errno = 0;
384	FILE *fp = fopen(argv[i], "r");
385	if (!fp)
386	  fatal("can't open `%1': %2", argv[i], strerror(errno));
387	else {
388	  do_file(fp, argv[i]);
389	  fclose(fp);
390	}
391      }
392  if (ferror(stdout) || fflush(stdout) < 0)
393    fatal("output error");
394  return 0;
395}
396