Imported Upstream version 1.22.4
[platform/upstream/groff.git] / src / preproc / eqn / main.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989-2018 Free Software Foundation, Inc.
3      Written by James Clark (jjc@jclark.com)
4
5 This file is part of groff.
6
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
19
20 #include "eqn.h"
21 #include "stringclass.h"
22 #include "device.h"
23 #include "searchpath.h"
24 #include "macropath.h"
25 #include "htmlhint.h"
26 #include "pbox.h"
27 #include "ctype.h"
28 #include "lf.h"
29
30 #define STARTUP_FILE "eqnrc"
31
32 extern int yyparse();
33 extern "C" const char *Version_string;
34
35 static char *delim_search    (char *, int);
36 static int   inline_equation (FILE *, string &, string &);
37
38 char start_delim = '\0';
39 char end_delim = '\0';
40 int non_empty_flag;
41 int inline_flag;
42 int draw_flag = 0;
43 int one_size_reduction_flag = 0;
44 int compatible_flag = 0;
45 int no_newline_in_delim_flag = 0;
46 int html = 0;
47 int xhtml = 0;
48 eqnmode_t output_format;
49
50 int 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
66 void do_file(FILE *fp, const char *filename)
67 {
68   string linebuf;
69   string str;
70   string fn(filename);
71   fn += '\0';
72   normalize_for_lf(fn);
73   current_filename = fn.contents();
74   if (output_format == troff)
75     printf(".lf 1 %s\n", current_filename);
76   current_lineno = 0;
77   while (read_line(fp, &linebuf)) {
78     if (linebuf.length() >= 4
79         && linebuf[0] == '.' && linebuf[1] == 'l' && linebuf[2] == 'f'
80         && (linebuf[3] == ' ' || linebuf[3] == '\n' || compatible_flag)) {
81       put_string(linebuf, stdout);
82       linebuf += '\0';
83       if (interpret_lf_args(linebuf.contents() + 3))
84         current_lineno--;
85     }
86     else if (linebuf.length() >= 4
87              && linebuf[0] == '.'
88              && linebuf[1] == 'E'
89              && linebuf[2] == 'Q'
90              && (linebuf[3] == ' ' || linebuf[3] == '\n'
91                  || compatible_flag)) {
92       put_string(linebuf, stdout);
93       int start_lineno = current_lineno + 1;
94       str.clear();
95       for (;;) {
96         if (!read_line(fp, &linebuf))
97           fatal("end of file before .EN");
98         if (linebuf.length() >= 3 && linebuf[0] == '.' && linebuf[1] == 'E') {
99           if (linebuf[2] == 'N'
100               && (linebuf.length() == 3 || linebuf[3] == ' '
101                   || linebuf[3] == '\n' || compatible_flag))
102             break;
103           else if (linebuf[2] == 'Q' && linebuf.length() > 3
104                    && (linebuf[3] == ' ' || linebuf[3] == '\n'
105                        || compatible_flag))
106             fatal("nested .EQ");
107         }
108         str += linebuf;
109       }
110       str += '\0';
111       start_string();
112       init_lex(str.contents(), current_filename, start_lineno);
113       non_empty_flag = 0;
114       inline_flag = 0;
115       yyparse();
116       restore_compatibility();
117       if (non_empty_flag) {
118         if (output_format == mathml)
119           putchar('\n');
120         else {
121           printf(".lf %d\n", current_lineno - 1);
122           output_string();
123         }
124       }
125       if (output_format == troff)
126         printf(".lf %d\n", current_lineno);
127       put_string(linebuf, stdout);
128     }
129     else if (start_delim != '\0' && linebuf.search(start_delim) >= 0
130              && inline_equation(fp, linebuf, str))
131       ;
132     else
133       put_string(linebuf, stdout);
134   }
135   current_filename = 0;
136   current_lineno = 0;
137 }
138
139 // Handle an inline equation.  Return 1 if it was an inline equation,
140 // otherwise.
141 static int inline_equation(FILE *fp, string &linebuf, string &str)
142 {
143   linebuf += '\0';
144   char *ptr = &linebuf[0];
145   char *start = delim_search(ptr, start_delim);
146   if (!start) {
147     // It wasn't a delimiter after all.
148     linebuf.set_length(linebuf.length() - 1); // strip the '\0'
149     return 0;
150   }
151   start_string();
152   inline_flag = 1;
153   for (;;) {
154     if (no_newline_in_delim_flag && strchr(start + 1, end_delim) == 0) {
155       error("missing '%1'", end_delim);
156       char *nl = strchr(start + 1, '\n');
157       if (nl != 0)
158         *nl = '\0';
159       do_text(ptr);
160       break;
161     }
162     int start_lineno = current_lineno;
163     *start = '\0';
164     do_text(ptr);
165     ptr = start + 1;
166     str.clear();
167     for (;;) {
168       char *end = strchr(ptr, end_delim);
169       if (end != 0) {
170         *end = '\0';
171         str += ptr;
172         ptr = end + 1;
173         break;
174       }
175       str += ptr;
176       if (!read_line(fp, &linebuf))
177         fatal("unterminated '%1' at line %2, looking for '%3'",
178               start_delim, start_lineno, end_delim);
179       linebuf += '\0';
180       ptr = &linebuf[0];
181     }
182     str += '\0';
183     if (output_format == troff && html) {
184       printf(".as1 %s ", LINE_STRING);
185       html_begin_suppress();
186       printf("\n");
187     }
188     init_lex(str.contents(), current_filename, start_lineno);
189     yyparse();
190     if (output_format == troff && html) {
191       printf(".as1 %s ", LINE_STRING);
192       html_end_suppress();
193       printf("\n");
194     }
195     if (output_format == mathml)
196       printf("\n");
197     if (xhtml) {
198       /* skip leading spaces */
199       while ((*ptr != '\0') && (*ptr == ' '))
200         ptr++;
201     }
202     start = delim_search(ptr, start_delim);
203     if (start == 0) {
204       char *nl = strchr(ptr, '\n');
205       if (nl != 0)
206         *nl = '\0';
207       do_text(ptr);
208       break;
209     }
210   }
211   restore_compatibility();
212   if (output_format == troff)
213     printf(".lf %d\n", current_lineno);
214   output_string();
215   if (output_format == troff)
216     printf(".lf %d\n", current_lineno + 1);
217   return 1;
218 }
219
220 /* Search for delim.  Skip over number register and string names etc. */
221
222 static char *delim_search(char *ptr, int delim)
223 {
224   while (*ptr) {
225     if (*ptr == delim)
226       return ptr;
227     if (*ptr++ == '\\') {
228       switch (*ptr) {
229       case 'n':
230       case '*':
231       case 'f':
232       case 'g':
233       case 'k':
234         switch (*++ptr) {
235         case '\0':
236         case '\\':
237           break;
238         case '(':
239           if (*++ptr != '\\' && *ptr != '\0'
240               && *++ptr != '\\' && *ptr != '\0')
241               ptr++;
242           break;
243         case '[':
244           while (*++ptr != '\0')
245             if (*ptr == ']') {
246               ptr++;
247               break;
248             }
249           break;
250         default:
251           ptr++;
252           break;
253         }
254         break;
255       case '\\':
256       case '\0':
257         break;
258       default:
259         ptr++;
260         break;
261       }
262     }
263   }
264   return 0;
265 }
266
267 void usage(FILE *stream)
268 {
269   fprintf(stream,
270     "usage: %s [ -rvDCNR ] -dxx -fn -sn -pn -mn -Mdir -Ts [ files ... ]\n",
271     program_name);
272 }
273
274 int main(int argc, char **argv)
275 {
276   program_name = argv[0];
277   static char stderr_buf[BUFSIZ];
278   setbuf(stderr, stderr_buf);
279   int opt;
280   int load_startup_file = 1;
281   static const struct option long_options[] = {
282     { "help", no_argument, 0, CHAR_MAX + 1 },
283     { "version", no_argument, 0, 'v' },
284     { NULL, 0, 0, 0 }
285   };
286   while ((opt = getopt_long(argc, argv, "DCRvd:f:p:s:m:T:M:rN", long_options,
287                             NULL))
288          != EOF)
289     switch (opt) {
290     case 'C':
291       compatible_flag = 1;
292       break;
293     case 'R':                   // don't load eqnrc
294       load_startup_file = 0;
295       break;
296     case 'M':
297       config_macro_path.command_line_dir(optarg);
298       break;
299     case 'v':
300       printf("GNU eqn (groff) version %s\n", Version_string);
301       exit(0);
302       break;
303     case 'd':
304       if (optarg[0] == '\0' || optarg[1] == '\0')
305         error("-d requires two character argument");
306       else if (invalid_input_char(optarg[0]))
307         error("bad delimiter '%1'", optarg[0]);
308       else if (invalid_input_char(optarg[1]))
309         error("bad delimiter '%1'", optarg[1]);
310       else {
311         start_delim = optarg[0];
312         end_delim = optarg[1];
313       }
314       break;
315     case 'f':
316       set_gfont(optarg);
317       break;
318     case 'T':
319       device = optarg;
320       if (strcmp(device, "ps:html") == 0) {
321         device = "ps";
322         html = 1;
323       }
324       else if (strcmp(device, "MathML") == 0) {
325         output_format = mathml;
326         load_startup_file = 0;
327       }
328       else if (strcmp(device, "mathml:xhtml") == 0) {
329         device = "MathML";
330         output_format = mathml;
331         load_startup_file = 0;
332         xhtml = 1;
333       }
334       break;
335     case 's':
336       if (!set_gsize(optarg))
337         error("invalid size '%1'", optarg);
338       break;
339     case 'p':
340       {
341         int n;
342         if (sscanf(optarg, "%d", &n) == 1)
343           set_script_reduction(n);
344         else
345           error("bad size '%1'", optarg);
346       }
347       break;
348     case 'm':
349       {
350         int n;
351         if (sscanf(optarg, "%d", &n) == 1)
352           set_minimum_size(n);
353         else
354           error("bad size '%1'", optarg);
355       }
356       break;
357     case 'r':
358       one_size_reduction_flag = 1;
359       break;
360     case 'D':
361       warning("-D option is obsolete: use 'set draw_lines 1' instead");
362       draw_flag = 1;
363       break;
364     case 'N':
365       no_newline_in_delim_flag = 1;
366       break;
367     case CHAR_MAX + 1: // --help
368       usage(stdout);
369       exit(0);
370       break;
371     case '?':
372       usage(stderr);
373       exit(1);
374       break;
375     default:
376       assert(0);
377     }
378   init_table(device);
379   init_char_table();
380   printf(".if !dEQ .ds EQ\n"
381          ".if !dEN .ds EN\n");
382   if (output_format == troff) {
383     printf(".if !'\\*(.T'%s' "
384            ".if !'\\*(.T'html' "        // the html device uses '-Tps' to render
385                                   // equations as images
386            ".tm warning: %s should have been given a '-T\\*(.T' option\n",
387            device, program_name);
388     printf(".if '\\*(.T'html' "
389            ".if !'%s'ps' "
390            ".tm warning: %s should have been given a '-Tps' option\n",
391            device, program_name);
392     printf(".if '\\*(.T'html' "
393            ".if !'%s'ps' "
394            ".tm warning: (it is advisable to invoke groff via: groff -Thtml -e)\n",
395            device);
396   }
397   if (load_startup_file) {
398     char *path;
399     FILE *fp = config_macro_path.open_file(STARTUP_FILE, &path);
400     if (fp) {
401       do_file(fp, path);
402       fclose(fp);
403       free(path);
404     }
405   }
406   if (optind >= argc)
407     do_file(stdin, "-");
408   else
409     for (int i = optind; i < argc; i++)
410       if (strcmp(argv[i], "-") == 0)
411         do_file(stdin, "-");
412       else {
413         errno = 0;
414         FILE *fp = fopen(argv[i], "r");
415         if (!fp)
416           fatal("can't open '%1': %2", argv[i], strerror(errno));
417         else {
418           do_file(fp, argv[i]);
419           fclose(fp);
420         }
421       }
422   if (ferror(stdout) || fflush(stdout) < 0)
423     fatal("output error");
424   return 0;
425 }