Line data Source code
1 : /* Copyright 1989-2025 Free Software Foundation, Inc.
2 : Written by James Clark (jjc@jclark.com)
3 :
4 : This file is part of groff, the GNU roff typesetting system.
5 :
6 : groff is free software; you can redistribute it and/or modify it under
7 : the terms of the GNU General Public License as published by the Free
8 : Software Foundation, either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : groff is distributed in the hope that it will be useful, but WITHOUT ANY
12 : WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 : for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 :
19 : #ifdef HAVE_CONFIG_H
20 : #include <config.h>
21 : #endif
22 :
23 : #include <getopt.h> // getopt_long()
24 :
25 : #include "eqn.h"
26 : #include "stringclass.h"
27 : #include "device.h"
28 : #include "searchpath.h"
29 : #include "macropath.h"
30 : #include "htmlhint.h"
31 : #include "pbox.h"
32 : #include "ctype.h"
33 : #include "lf.h"
34 :
35 : #define STARTUP_FILE "eqnrc"
36 :
37 : extern int yyparse();
38 : extern "C" const char *Version_string;
39 :
40 : static char *delim_search (char *, int);
41 : static int inline_equation (FILE *, string &, string &);
42 :
43 : char start_delim = '\0';
44 : char end_delim = '\0';
45 : int non_empty_flag;
46 : int inline_flag;
47 : int draw_flag = 0;
48 : int one_size_reduction_flag = 0;
49 : int compatible_flag = 0;
50 : int no_newline_in_delim_flag = 0;
51 : int html = 0;
52 : int xhtml = 0;
53 : eqnmode_t output_format;
54 :
55 4 : static const char *input_char_description(int c)
56 : {
57 4 : switch (c) {
58 0 : case '\001':
59 0 : return "a leader character";
60 0 : case '\n':
61 0 : return "a newline character";
62 0 : case '\b':
63 0 : return "a backspace character";
64 0 : case '\t':
65 0 : return "a tab character";
66 0 : case ' ':
67 0 : return "a space character";
68 0 : case '\177':
69 0 : return "a delete character";
70 : }
71 4 : const size_t bufsz = sizeof "character code " + INT_DIGITS + 1;
72 : static char buf[bufsz];
73 4 : (void) memset(buf, 0, bufsz);
74 4 : if (csprint(c)) {
75 4 : buf[0] = '\'';
76 4 : buf[1] = c;
77 4 : buf[2] = '\'';
78 4 : return buf;
79 : }
80 0 : (void) sprintf(buf, "character code %d", c);
81 0 : return buf;
82 : }
83 :
84 521161 : static bool read_line(FILE *fp, string *p)
85 : {
86 521161 : p->clear();
87 521161 : int c = -1;
88 10337315 : while ((c = getc(fp)) != EOF) {
89 10337208 : *p += char(c);
90 10337208 : if (c == '\n')
91 521054 : break;
92 : }
93 521161 : return (p->length() > 0);
94 : }
95 :
96 109 : void do_file(FILE *fp, const char *filename)
97 : {
98 214 : string linebuf;
99 214 : string str;
100 109 : string fn(filename);
101 109 : fn += '\0';
102 109 : normalize_file_name_for_lf_request(fn);
103 109 : current_lineno = 1;
104 109 : current_filename = fn.contents();
105 109 : if (output_format == troff)
106 109 : (void) printf(".lf %d %s%s\n", current_lineno,
107 109 : ('"' == current_filename[0]) ? "" : "\"", current_filename);
108 510374 : while (read_line(fp, &linebuf)) {
109 510269 : if (linebuf.length() >= 4
110 445076 : && linebuf[0] == '.' && linebuf[1] == 'l' && linebuf[2] == 'f'
111 955345 : && (linebuf[3] == ' ' || linebuf[3] == '\n' || compatible_flag))
112 : {
113 21659 : put_string(linebuf, stdout);
114 21659 : linebuf += '\0';
115 : // In GNU roff, `lf` assigns the number of the _next_ line.
116 21659 : if (interpret_lf_request_arguments(linebuf.contents() + 3))
117 21655 : current_lineno--;
118 : }
119 488610 : else if (linebuf.length() >= 4
120 423417 : && linebuf[0] == '.'
121 295130 : && linebuf[1] == 'E'
122 6631 : && linebuf[2] == 'Q'
123 912027 : && (linebuf[3] == ' ' || linebuf[3] == '\n'
124 0 : || compatible_flag)) {
125 3923 : put_string(linebuf, stdout);
126 3923 : int start_lineno = current_lineno + 1;
127 3923 : str.clear();
128 : for (;;) {
129 10784 : if (!read_line(fp, &linebuf)) {
130 1 : current_lineno = 0; // suppress report of line number
131 1 : fatal("end of file before .EN");
132 : }
133 10783 : if (linebuf.length() >= 3
134 10182 : && linebuf[0] == '.'
135 20965 : && linebuf[1] == 'E') {
136 3922 : if (linebuf[2] == 'N'
137 7842 : && (linebuf.length() == 3 || linebuf[3] == ' '
138 3920 : || linebuf[3] == '\n' || compatible_flag))
139 3920 : break;
140 4 : else if (linebuf[2] == 'Q' && linebuf.length() > 3
141 4 : && (linebuf[3] == ' ' || linebuf[3] == '\n'
142 0 : || compatible_flag)) {
143 2 : current_lineno++; // We just read another line.
144 2 : fatal("equations cannot be nested (.EQ within .EQ)");
145 : }
146 : }
147 6861 : str += linebuf;
148 : }
149 3920 : str += '\0';
150 3920 : start_string();
151 3920 : init_lex(str.contents(), current_filename, start_lineno);
152 3920 : non_empty_flag = 0;
153 3920 : inline_flag = 0;
154 3920 : yyparse();
155 3920 : restore_compatibility();
156 3920 : if (non_empty_flag) {
157 32 : if (output_format == mathml)
158 0 : putchar('\n');
159 : else {
160 32 : current_lineno++;
161 32 : printf(".lf %d\n", current_lineno);
162 32 : output_string();
163 : }
164 : }
165 3920 : if (output_format == troff) {
166 3920 : current_lineno++;
167 3920 : printf(".lf %d\n", current_lineno);
168 : }
169 3920 : put_string(linebuf, stdout);
170 : }
171 13125 : else if (start_delim != '\0' && linebuf.search(start_delim) >= 0
172 497812 : && inline_equation(fp, linebuf, str))
173 : ;
174 : else
175 484541 : put_string(linebuf, stdout);
176 510265 : current_lineno++;
177 : }
178 105 : current_filename = 0;
179 105 : current_lineno = 0;
180 105 : }
181 :
182 : // Handle an inline equation. Return 1 if it was an inline equation,
183 : // otherwise.
184 146 : static int inline_equation(FILE *fp, string &linebuf, string &str)
185 : {
186 146 : linebuf += '\0';
187 146 : char *ptr = &linebuf[0];
188 146 : char *start = delim_search(ptr, start_delim);
189 146 : if (!start) {
190 : // It wasn't a delimiter after all.
191 0 : linebuf.set_length(linebuf.length() - 1); // strip the '\0'
192 0 : return 0;
193 : }
194 146 : start_string();
195 146 : inline_flag = 1;
196 : for (;;) {
197 154 : if (no_newline_in_delim_flag && strchr(start + 1, end_delim) == 0) {
198 1 : error("unterminated inline equation; started with %1,"
199 : " expecting %2", input_char_description(start_delim),
200 1 : input_char_description(end_delim));
201 1 : char *nl = strchr(start + 1, '\n');
202 1 : if (nl != 0)
203 1 : *nl = '\0';
204 1 : do_text(ptr);
205 1 : break;
206 : }
207 153 : int start_lineno = current_lineno;
208 153 : *start = '\0';
209 153 : do_text(ptr);
210 153 : ptr = start + 1;
211 153 : str.clear();
212 : for (;;) {
213 155 : char *end = strchr(ptr, end_delim);
214 155 : if (end != 0) {
215 152 : *end = '\0';
216 152 : str += ptr;
217 152 : ptr = end + 1;
218 152 : break;
219 : }
220 3 : str += ptr;
221 3 : if (!read_line(fp, &linebuf))
222 1 : fatal("unterminated inline equation; started with %1,"
223 : " expecting %2", input_char_description(start_delim),
224 1 : input_char_description(end_delim));
225 2 : linebuf += '\0';
226 2 : ptr = &linebuf[0];
227 2 : }
228 152 : str += '\0';
229 152 : if (output_format == troff && html) {
230 2 : printf(".as1 %s ", LINE_STRING);
231 2 : html_begin_suppress();
232 2 : printf("\n");
233 : }
234 152 : init_lex(str.contents(), current_filename, start_lineno);
235 152 : yyparse();
236 152 : if (output_format == troff && html) {
237 2 : printf(".as1 %s ", LINE_STRING);
238 2 : html_end_suppress();
239 2 : printf("\n");
240 : }
241 152 : if (output_format == mathml)
242 0 : printf("\n");
243 152 : if (xhtml) {
244 : /* skip leading spaces */
245 0 : while ((*ptr != '\0') && (*ptr == ' '))
246 0 : ptr++;
247 : }
248 152 : start = delim_search(ptr, start_delim);
249 152 : if (start == 0) {
250 144 : char *nl = strchr(ptr, '\n');
251 144 : if (nl != 0)
252 144 : *nl = '\0';
253 144 : do_text(ptr);
254 144 : break;
255 : }
256 8 : }
257 145 : restore_compatibility();
258 145 : if (output_format == troff)
259 145 : printf(".lf %d\n", current_lineno);
260 145 : output_string();
261 145 : if (output_format == troff)
262 145 : printf(".lf %d\n", current_lineno + 1);
263 145 : return 1;
264 : }
265 :
266 : /* Search for delim. Skip over number register and string names etc. */
267 :
268 2170 : static char *delim_search(char *ptr, int delim)
269 : {
270 2170 : while (*ptr) {
271 2026 : if (*ptr == delim)
272 154 : return ptr;
273 1872 : if (*ptr++ == '\\') {
274 12 : switch (*ptr) {
275 0 : case 'n':
276 : case '*':
277 : case 'f':
278 : case 'g':
279 : case 'k':
280 0 : switch (*++ptr) {
281 0 : case '\0':
282 : case '\\':
283 0 : break;
284 0 : case '(':
285 0 : if (*++ptr != '\\' && *ptr != '\0'
286 0 : && *++ptr != '\\' && *ptr != '\0')
287 0 : ptr++;
288 0 : break;
289 0 : case '[':
290 0 : while (*++ptr != '\0')
291 0 : if (*ptr == ']') {
292 0 : ptr++;
293 0 : break;
294 : }
295 0 : break;
296 0 : default:
297 0 : ptr++;
298 0 : break;
299 : }
300 0 : break;
301 0 : case '\\':
302 : case '\0':
303 0 : break;
304 12 : default:
305 12 : ptr++;
306 12 : break;
307 : }
308 : }
309 : }
310 144 : return 0;
311 : }
312 :
313 0 : void usage(FILE *stream)
314 : {
315 0 : fprintf(stream,
316 : "usage: %s [-CNrR] [-d xy] [-f global-italic-font]"
317 : " [-m minimum-type-size] [-M eqnrc-directory]"
318 : " [-p super/subscript-size-reduction] [-s global-type-size]"
319 : " [-T device] [file ...]\n"
320 : "usage: %s {-v | --version}\n"
321 : "usage: %s --help\n",
322 : program_name, program_name, program_name);
323 0 : if (stdout == stream)
324 0 : fputs("\n"
325 : "GNU eqn is a filter that translates expressions in its own language,\n"
326 : "embedded in roff(7) input, into mathematical notation typeset by\n"
327 : "GNU troff(1). See the eqn(1) manual page.\n",
328 : stream);
329 0 : }
330 :
331 59 : int main(int argc, char **argv)
332 : {
333 59 : program_name = argv[0];
334 : static char stderr_buf[BUFSIZ];
335 59 : setbuf(stderr, stderr_buf);
336 : int opt;
337 59 : bool want_startup_file = true;
338 : static const struct option long_options[] = {
339 : { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 },
340 : { "version", no_argument, 0 /* nullptr */, 'v' },
341 : { 0 /* nullptr */, 0, 0 /* nullptr */, 0 }
342 : };
343 247 : while ((opt = getopt_long(argc, argv, ":CNrRd:f:m:M:p:s:T:v",
344 : long_options, 0 /* nullptr */))
345 247 : != EOF)
346 188 : switch (opt) {
347 0 : case 'C':
348 0 : compatible_flag = 1;
349 0 : break;
350 1 : case 'R': // don't load eqnrc
351 1 : want_startup_file = false;
352 1 : break;
353 129 : case 'M':
354 129 : config_macro_path.command_line_dir(optarg);
355 129 : break;
356 0 : case 'v':
357 0 : printf("GNU eqn (groff) version %s\n", Version_string);
358 0 : exit(EXIT_SUCCESS);
359 : break;
360 2 : case 'd':
361 2 : if (optarg[0] == '\0' || optarg[1] == '\0')
362 0 : error("'-d' option requires a two-character argument");
363 2 : else if (is_invalid_input_char(optarg[0]))
364 0 : error("invalid delimiter (%1) in '-d' option argument",
365 0 : input_char_description(optarg[0]));
366 2 : else if (is_invalid_input_char(optarg[1]))
367 0 : error("invalid delimiter (%1) in '-d' option argument",
368 0 : input_char_description(optarg[1]));
369 : else {
370 2 : start_delim = optarg[0];
371 2 : end_delim = optarg[1];
372 : }
373 2 : break;
374 0 : case 'f':
375 0 : set_gifont(optarg);
376 0 : break;
377 55 : case 'T':
378 55 : device = optarg;
379 55 : if (strcmp(device, "ps:html") == 0) {
380 3 : device = "ps";
381 3 : html = 1;
382 : }
383 52 : else if (strcmp(device, "MathML") == 0) {
384 0 : output_format = mathml;
385 0 : want_startup_file = false;
386 : }
387 52 : else if (strcmp(device, "mathml:xhtml") == 0) {
388 0 : device = "MathML";
389 0 : output_format = mathml;
390 0 : want_startup_file = false;
391 0 : xhtml = 1;
392 : }
393 55 : break;
394 0 : case 's':
395 0 : if (set_gsize(optarg))
396 0 : warning("option '-s' is deprecated");
397 : else
398 0 : error("invalid size '%1' in '-s' option argument ", optarg);
399 0 : break;
400 0 : case 'p':
401 : {
402 : int n;
403 0 : if (sscanf(optarg, "%d", &n) == 1) {
404 0 : warning("option '-p' is deprecated");
405 0 : set_script_reduction(n);
406 : }
407 : else
408 0 : error("invalid size '%1' in '-p' option argument ", optarg);
409 : }
410 0 : break;
411 0 : case 'm':
412 : {
413 : int n;
414 0 : if (sscanf(optarg, "%d", &n) == 1)
415 0 : set_minimum_size(n);
416 : else
417 0 : error("invalid size '%1' in '-n' option argument", optarg);
418 : }
419 0 : break;
420 0 : case 'r':
421 0 : one_size_reduction_flag = 1;
422 0 : break;
423 1 : case 'N':
424 1 : no_newline_in_delim_flag = 1;
425 1 : break;
426 0 : case CHAR_MAX + 1: // --help
427 0 : usage(stdout);
428 0 : exit(EXIT_SUCCESS);
429 : break;
430 0 : case '?':
431 0 : if (optopt != 0)
432 0 : error("unrecognized command-line option '%1'", char(optopt));
433 : else
434 0 : error("unrecognized command-line option '%1'",
435 0 : argv[(optind - 1)]);
436 0 : usage(stderr);
437 0 : exit(2);
438 : break;
439 0 : case ':':
440 0 : error("command-line option '%1' requires an argument",
441 0 : char(optopt));
442 0 : usage(stderr);
443 0 : exit(2);
444 : break;
445 0 : default:
446 0 : assert(0 == "unhandled getopt_long return value");
447 : }
448 59 : init_table(device);
449 59 : init_char_table();
450 59 : init_param_table();
451 59 : atexit(free_param_table);
452 59 : printf(".do if !dEQ .ds EQ\n"
453 : ".do if !dEN .ds EN\n");
454 59 : if (output_format == troff) {
455 59 : printf(".if !'\\*(.T'%s' "
456 : // the html device uses '-Tps' to render equations as images
457 : ".if !'\\*(.T'html' "
458 : ".tm \\n[.F]: warning: %s should have been given a"
459 : " '-T\\*(.T' option\n", device, program_name);
460 59 : printf(".if '\\*(.T'html' "
461 : ".if !'%s'ps' "
462 : ".tmc \\n[.F]: warning: %s should have been given a '-Tps'"
463 : " option\n", device, program_name);
464 59 : printf(".if '\\*(.T'html' "
465 : ".if !'%s'ps' .tm1 \" (consider invoking"
466 : " 'groff -Thtml -e')\n", device);
467 : }
468 59 : if (want_startup_file) {
469 : char *path;
470 58 : FILE *fp = config_macro_path.open_file(STARTUP_FILE, &path);
471 58 : if (fp != 0 /* nullptr */) {
472 50 : do_file(fp, path);
473 50 : if (fclose(fp) < 0)
474 0 : fatal("cannot close '%1': %2", STARTUP_FILE, strerror(errno));
475 50 : free(path);
476 : }
477 : else
478 8 : error("cannot open startup file '%1': %2", STARTUP_FILE,
479 16 : strerror(errno));
480 : }
481 59 : if (optind >= argc)
482 59 : do_file(stdin, "-");
483 : else
484 0 : for (int i = optind; i < argc; i++)
485 0 : if (strcmp(argv[i], "-") == 0)
486 0 : do_file(stdin, "-");
487 : else {
488 0 : errno = 0;
489 0 : FILE *fp = fopen(argv[i], "r");
490 0 : if (!fp)
491 0 : fatal("cannot open '%1': %2", argv[i], strerror(errno));
492 : else {
493 0 : do_file(fp, argv[i]);
494 0 : if (fclose(fp) < 0)
495 0 : fatal("cannot close '%1': %2", argv[i], strerror(errno));
496 : }
497 : }
498 55 : if (ferror(stdout))
499 0 : fatal("standard output stream is in an error state");
500 55 : if (fflush(stdout) < 0)
501 0 : fatal("cannot flush standard output stream: %1", strerror(errno));
502 55 : exit(EXIT_SUCCESS);
503 : }
504 :
505 : // Local Variables:
506 : // fill-column: 72
507 : // mode: C++
508 : // End:
509 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|