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