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 <assert.h>
24 : #include <errno.h>
25 : #include <locale.h> // setlocale()
26 : #include <stdio.h> // EOF, FILE, fclose(), ferror(), fflush(), fopen(),
27 : // fprintf(), fputs(), getc(), printf(), setbuf(),
28 : // stderr, stdin, stdout, ungetc()
29 : #include <stdlib.h> // exit(), EXIT_FAILURE, EXIT_SUCCESS, free()
30 : #include <string.h> // strerror()
31 :
32 : #include <getopt.h> // getopt_long()
33 :
34 : #include "pic.h"
35 :
36 : extern int yyparse();
37 : extern "C" const char *Version_string;
38 :
39 : output *out;
40 : char *graphname; // the picture box name in TeX mode
41 :
42 : bool want_flyback = false;
43 : // groff pic supports '.PY' to work around mm package stepping on 'PF'.
44 : bool want_alternate_flyback = false;
45 : int zero_length_line_flag = 0;
46 : // Non-zero means we're using a groff driver.
47 : int driver_extension_flag = 1;
48 : int compatible_flag = 0;
49 : bool want_unsafe_mode = false;
50 : int command_char = '.'; // the character that introduces lines
51 : // that should be passed through transparently
52 : static int lf_flag = 1; // non-zero if we should attempt to understand
53 : // lines beginning with '.lf'
54 :
55 : // Non-zero means a parse error was encountered.
56 : static int had_parse_error = 0;
57 :
58 : void do_file(const char *filename);
59 :
60 : class top_input : public input {
61 : FILE *fp;
62 : int bol;
63 : int eof;
64 : int push_back[3];
65 : int start_lineno;
66 : public:
67 : top_input(FILE *);
68 : int get();
69 : int peek();
70 : int get_location(const char **, int *);
71 : };
72 :
73 114 : top_input::top_input(FILE *p) : fp(p), bol(1), eof(0)
74 : {
75 114 : push_back[0] = push_back[1] = push_back[2] = EOF;
76 114 : start_lineno = current_lineno;
77 114 : }
78 :
79 30270 : int top_input::get()
80 : {
81 30270 : if (eof)
82 114 : return EOF;
83 30156 : if (push_back[2] != EOF) {
84 0 : int c = push_back[2];
85 0 : push_back[2] = EOF;
86 0 : return c;
87 : }
88 30156 : else if (push_back[1] != EOF) {
89 0 : int c = push_back[1];
90 0 : push_back[1] = EOF;
91 0 : return c;
92 : }
93 30156 : else if (push_back[0] != EOF) {
94 0 : int c = push_back[0];
95 0 : push_back[0] = EOF;
96 0 : return c;
97 : }
98 30156 : int c = getc(fp);
99 30156 : if (bol && c == '.') {
100 138 : c = getc(fp);
101 138 : if (c == 'P') {
102 114 : c = getc(fp);
103 114 : if (c == 'E' || c == 'F' || c == 'Y') {
104 114 : int d = getc(fp);
105 114 : if (d != EOF)
106 114 : ungetc(d, fp);
107 114 : if (d == EOF || d == ' ' || d == '\n' || compatible_flag) {
108 114 : eof = 1;
109 114 : want_flyback = (c == 'F');
110 114 : want_alternate_flyback = (c == 'Y');
111 114 : return EOF;
112 : }
113 0 : push_back[0] = c;
114 0 : push_back[1] = 'P';
115 0 : return '.';
116 : }
117 0 : if (c == 'S') {
118 0 : c = getc(fp);
119 0 : if (c != EOF)
120 0 : ungetc(c, fp);
121 0 : if (c == EOF || c == ' ' || c == '\n' || compatible_flag) {
122 0 : error("nested .PS");
123 0 : eof = 1;
124 0 : return EOF;
125 : }
126 0 : push_back[0] = 'S';
127 0 : push_back[1] = 'P';
128 0 : return '.';
129 : }
130 0 : if (c != EOF)
131 0 : ungetc(c, fp);
132 0 : push_back[0] = 'P';
133 0 : return '.';
134 : }
135 : else {
136 24 : if (c != EOF)
137 24 : ungetc(c, fp);
138 24 : return '.';
139 : }
140 : }
141 30018 : if (c == '\n') {
142 1151 : bol = 1;
143 1151 : current_lineno++;
144 1151 : return '\n';
145 : }
146 28867 : bol = 0;
147 28867 : if (c == EOF) {
148 0 : eof = 1;
149 0 : error("end of file before .PE, .PF, or .PY");
150 0 : error_with_file_and_line(current_filename, start_lineno - 1,
151 : ".PS was here");
152 : }
153 28867 : return c;
154 : }
155 :
156 11238 : int top_input::peek()
157 : {
158 11238 : if (eof)
159 0 : return EOF;
160 11238 : if (push_back[2] != EOF)
161 0 : return push_back[2];
162 11238 : if (push_back[1] != EOF)
163 0 : return push_back[1];
164 11238 : if (push_back[0] != EOF)
165 0 : return push_back[0];
166 11238 : int c = getc(fp);
167 11238 : if (bol && c == '.') {
168 0 : c = getc(fp);
169 0 : if (c == 'P') {
170 0 : c = getc(fp);
171 0 : if (c == 'E' || c == 'F' || c == 'Y') {
172 0 : int d = getc(fp);
173 0 : if (d != EOF)
174 0 : ungetc(d, fp);
175 0 : if (d == EOF || d == ' ' || d == '\n' || compatible_flag) {
176 0 : eof = 1;
177 0 : want_flyback = (c == 'F');
178 0 : want_alternate_flyback = (c == 'Y');
179 0 : return EOF;
180 : }
181 0 : push_back[0] = c;
182 0 : push_back[1] = 'P';
183 0 : push_back[2] = '.';
184 0 : return '.';
185 : }
186 0 : if (c == 'S') {
187 0 : c = getc(fp);
188 0 : if (c != EOF)
189 0 : ungetc(c, fp);
190 0 : if (c == EOF || c == ' ' || c == '\n' || compatible_flag) {
191 0 : error("nested .PS");
192 0 : eof = 1;
193 0 : return EOF;
194 : }
195 0 : push_back[0] = 'S';
196 0 : push_back[1] = 'P';
197 0 : push_back[2] = '.';
198 0 : return '.';
199 : }
200 0 : if (c != EOF)
201 0 : ungetc(c, fp);
202 0 : push_back[0] = 'P';
203 0 : push_back[1] = '.';
204 0 : return '.';
205 : }
206 : else {
207 0 : if (c != EOF)
208 0 : ungetc(c, fp);
209 0 : push_back[0] = '.';
210 0 : return '.';
211 : }
212 : }
213 11238 : if (c != EOF)
214 11238 : ungetc(c, fp);
215 11238 : if (c == '\n')
216 100 : return '\n';
217 11138 : return c;
218 : }
219 :
220 937 : int top_input::get_location(const char **filenamep, int *linenop)
221 : {
222 937 : *filenamep = current_filename;
223 937 : *linenop = current_lineno;
224 937 : return 1;
225 : }
226 :
227 114 : void do_picture(FILE *fp)
228 : {
229 114 : want_flyback = false;
230 : int c;
231 114 : if (!graphname)
232 13 : free(graphname);
233 114 : graphname = strsave("graph"); // default picture name in TeX mode
234 117 : while ((c = getc(fp)) == ' ')
235 : ;
236 114 : if (c == '<') {
237 0 : string filename;
238 0 : while ((c = getc(fp)) == ' ')
239 : ;
240 0 : while (c != EOF && c != ' ' && c != '\n') {
241 0 : filename += char(c);
242 0 : c = getc(fp);
243 : }
244 0 : if (c == ' ') {
245 0 : do {
246 0 : c = getc(fp);
247 0 : } while (c != EOF && c != '\n');
248 : }
249 0 : if (c == '\n')
250 0 : current_lineno++;
251 0 : if (filename.length() == 0)
252 0 : error("missing filename after '<'");
253 : else {
254 0 : filename += '\0';
255 0 : const char *old_filename = current_filename;
256 0 : int old_lineno = current_lineno;
257 : // filenames must be permanent
258 0 : do_file(strsave(filename.contents()));
259 0 : current_filename = old_filename;
260 0 : current_lineno = old_lineno;
261 : }
262 0 : out->set_location(current_filename, current_lineno);
263 : }
264 : else {
265 114 : out->set_location(current_filename, current_lineno);
266 114 : string start_line;
267 132 : while (c != EOF) {
268 132 : if (c == '\n') {
269 114 : current_lineno++;
270 114 : break;
271 : }
272 18 : start_line += c;
273 18 : c = getc(fp);
274 : }
275 114 : if (c == EOF)
276 0 : return;
277 114 : start_line += '\0';
278 : double wid, ht;
279 114 : switch (sscanf(&start_line[0], "%lf %lf", &wid, &ht)) {
280 3 : case 1:
281 3 : ht = 0.0;
282 3 : break;
283 0 : case 2:
284 0 : break;
285 111 : default:
286 111 : ht = wid = 0.0;
287 111 : break;
288 : }
289 114 : out->set_desired_width_height(wid, ht);
290 114 : out->set_args(start_line.contents());
291 114 : lex_init(new top_input(fp));
292 114 : if (yyparse()) {
293 0 : had_parse_error = 1;
294 0 : lex_error("giving up on this picture");
295 : }
296 114 : parse_cleanup();
297 114 : lex_cleanup();
298 :
299 : // skip the rest of the .PE/.PF/.PY line
300 114 : while ((c = getc(fp)) != EOF && c != '\n')
301 : ;
302 114 : if (c == '\n')
303 114 : current_lineno++;
304 114 : out->set_location(current_filename, current_lineno);
305 : }
306 : }
307 :
308 48 : void do_file(const char *filename)
309 : {
310 : FILE *fp;
311 48 : if (strcmp(filename, "-") == 0)
312 37 : fp = stdin;
313 : else {
314 11 : errno = 0;
315 11 : fp = fopen(filename, "r");
316 11 : if (fp == 0) {
317 0 : delete out;
318 0 : fatal("cannot open '%1': %2", filename, strerror(errno));
319 : }
320 : }
321 96 : string fn(filename);
322 48 : fn += '\0';
323 48 : normalize_file_name_for_lf_request(fn);
324 48 : current_filename = fn.contents();
325 48 : out->set_location(current_filename, 1);
326 48 : current_lineno = 1;
327 48 : enum { START, MIDDLE, HAD_DOT, HAD_P, HAD_PS, HAD_l, HAD_lf } state
328 : = START;
329 : for (;;) {
330 5627081 : int c = getc(fp);
331 5627081 : if (c == EOF)
332 48 : break;
333 5627033 : switch (state) {
334 282808 : case START:
335 282808 : if (c == '.')
336 167807 : state = HAD_DOT;
337 : else {
338 115001 : putchar(c);
339 115001 : if (c == '\n') {
340 140 : current_lineno++;
341 140 : state = START;
342 : }
343 : else
344 114861 : state = MIDDLE;
345 : }
346 282808 : break;
347 5169287 : case MIDDLE:
348 5169287 : putchar(c);
349 5169287 : if (c == '\n') {
350 226862 : current_lineno++;
351 226862 : state = START;
352 : }
353 5169287 : break;
354 167807 : case HAD_DOT:
355 167807 : if (c == 'P')
356 6549 : state = HAD_P;
357 161258 : else if (lf_flag && c == 'l')
358 234 : state = HAD_l;
359 : else {
360 161024 : putchar('.');
361 161024 : putchar(c);
362 161024 : if (c == '\n') {
363 51879 : current_lineno++;
364 51879 : state = START;
365 : }
366 : else
367 109145 : state = MIDDLE;
368 : }
369 167807 : break;
370 6549 : case HAD_P:
371 6549 : if (c == 'S')
372 114 : state = HAD_PS;
373 : else {
374 6435 : putchar('.');
375 6435 : putchar('P');
376 6435 : putchar(c);
377 6435 : if (c == '\n') {
378 3579 : current_lineno++;
379 3579 : state = START;
380 : }
381 : else
382 2856 : state = MIDDLE;
383 : }
384 6549 : break;
385 114 : case HAD_PS:
386 114 : if (c == ' ' || c == '\n' || compatible_flag) {
387 114 : ungetc(c, fp);
388 114 : do_picture(fp);
389 114 : state = START;
390 : }
391 : else {
392 0 : fputs(".PS", stdout);
393 0 : putchar(c);
394 0 : state = MIDDLE;
395 : }
396 114 : break;
397 234 : case HAD_l:
398 234 : if (c == 'f')
399 234 : state = HAD_lf;
400 : else {
401 0 : putchar('.');
402 0 : putchar('l');
403 0 : putchar(c);
404 0 : if (c == '\n') {
405 0 : current_lineno++;
406 0 : state = START;
407 : }
408 : else
409 0 : state = MIDDLE;
410 : }
411 234 : break;
412 234 : case HAD_lf:
413 234 : if (c == ' ' || c == '\n' || compatible_flag) {
414 234 : string line;
415 7127 : while (c != EOF) {
416 7127 : line += c;
417 7127 : if (c == '\n') {
418 234 : current_lineno++;
419 234 : break;
420 : }
421 6893 : c = getc(fp);
422 : }
423 234 : line += '\0';
424 234 : interpret_lf_request_arguments(line.contents());
425 234 : printf(".lf%s", line.contents());
426 234 : state = START;
427 : }
428 : else {
429 0 : fputs(".lf", stdout);
430 0 : putchar(c);
431 0 : state = MIDDLE;
432 : }
433 234 : break;
434 0 : default:
435 0 : assert(0 == "unhandled parser state");
436 : }
437 5627033 : }
438 48 : switch (state) {
439 48 : case START:
440 48 : break;
441 0 : case MIDDLE:
442 0 : putchar('\n');
443 0 : break;
444 0 : case HAD_DOT:
445 0 : fputs(".\n", stdout);
446 0 : break;
447 0 : case HAD_P:
448 0 : fputs(".P\n", stdout);
449 0 : break;
450 0 : case HAD_PS:
451 0 : fputs(".PS\n", stdout);
452 0 : break;
453 0 : case HAD_l:
454 0 : fputs(".l\n", stdout);
455 0 : break;
456 0 : case HAD_lf:
457 0 : fputs(".lf\n", stdout);
458 0 : break;
459 : }
460 48 : if (fp != stdin)
461 11 : fclose(fp);
462 48 : }
463 :
464 : #ifdef FIG_SUPPORT
465 : void do_whole_file(const char *filename)
466 : {
467 : // Do not set current_filename.
468 : FILE *fp;
469 : if (strcmp(filename, "-") == 0)
470 : fp = stdin;
471 : else {
472 : errno = 0;
473 : fp = fopen(filename, "r");
474 : if (fp == 0)
475 : fatal("cannot open '%1': %2", filename, strerror(errno));
476 : }
477 : lex_init(new file_input(fp, filename));
478 : if (yyparse())
479 : had_parse_error = 1;
480 : parse_cleanup();
481 : lex_cleanup();
482 : }
483 : #endif
484 :
485 0 : void usage(FILE *stream)
486 : {
487 0 : fprintf(stream, "usage: %s [-CnSU] [file ...]\n", program_name);
488 : #ifdef TEX_SUPPORT
489 0 : fprintf(stream, "usage: %s -t [-cCSUz] [file ...]\n", program_name);
490 : #endif
491 : #ifdef FIG_SUPPORT
492 : fprintf(stream, "usage: %s -f [-v] [file]\n", program_name);
493 : #endif
494 0 : fprintf(stream, "usage: %s {-v | --version}\n", program_name);
495 0 : fprintf(stream, "usage: %s --help\n", program_name);
496 0 : if (stdout == stream) {
497 0 : fputs(
498 : "\n"
499 : "GNU pic is a troff(1) preprocessor that translates descriptions of\n"
500 : "diagrammatic pictures embedded in roff(7)"
501 : #ifdef TEX_SUPPORT
502 : " or TeX"
503 : #endif
504 : " input into the language\n"
505 : "understood by"
506 : #ifdef TEX_SUPPORT
507 : " TeX or"
508 : #endif
509 : " troff. See the pic(1) manual page.\n",
510 : stream);
511 : }
512 0 : }
513 :
514 : #if defined(__MSDOS__) || defined(__EMX__)
515 : static char *fix_program_name(char *arg, char *dflt)
516 : {
517 : if (!arg)
518 : return dflt;
519 : char *prog = strchr(arg, '\0');
520 : for (;;) {
521 : if (prog == arg)
522 : break;
523 : --prog;
524 : if (strchr("\\/:", *prog)) {
525 : prog++;
526 : break;
527 : }
528 : }
529 : char *ext = strchr(prog, '.');
530 : if (ext)
531 : *ext = '\0';
532 : for (char *p = prog; *p; p++)
533 : if ('A' <= *p && *p <= 'Z')
534 : *p = 'a' + (*p - 'A');
535 : return prog;
536 : }
537 : #endif /* __MSDOS__ || __EMX__ */
538 :
539 48 : int main(int argc, char **argv)
540 : {
541 48 : setlocale(LC_NUMERIC, "C");
542 : #if defined(__MSDOS__) || defined(__EMX__)
543 : argv[0] = fix_program_name(argv[0], "pic");
544 : #endif /* __MSDOS__ || __EMX__ */
545 48 : program_name = argv[0];
546 : static char stderr_buf[BUFSIZ];
547 48 : setbuf(stderr, stderr_buf);
548 : int opt;
549 48 : bool is_safer_mode_locked = false;
550 : #ifdef TEX_SUPPORT
551 48 : int tex_flag = 0;
552 48 : int tpic_flag = 0;
553 : #endif
554 : #ifdef FIG_SUPPORT
555 : int whole_file_flag = 0;
556 : int fig_flag = 0;
557 : #endif
558 : static const struct option long_options[] = {
559 : { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 },
560 : { "version", no_argument, 0 /* nullptr */, 'v' },
561 : { 0 /* nullptr */, 0, 0 /* nullptr */, 0 }
562 : };
563 59 : while ((opt = getopt_long(argc, argv, ":cCDfnpStT:Uvxz", long_options,
564 : 0 /* nullptr */))
565 59 : != EOF)
566 11 : switch (opt) {
567 0 : case 'C':
568 0 : compatible_flag = 1;
569 0 : break;
570 0 : case 'D':
571 : case 'T':
572 0 : break;
573 0 : case 'S':
574 0 : want_unsafe_mode = false;
575 0 : is_safer_mode_locked = true;
576 0 : break;
577 11 : case 'U':
578 11 : if (is_safer_mode_locked)
579 0 : error("ignoring '-U' option; '-S' already specified");
580 : else
581 11 : want_unsafe_mode = true;
582 11 : break;
583 0 : case 'f':
584 : #ifdef FIG_SUPPORT
585 : whole_file_flag++;
586 : fig_flag++;
587 : #else
588 0 : fatal("fig support is not built into this configuration");
589 : #endif
590 0 : break;
591 0 : case 'n':
592 0 : driver_extension_flag = 0;
593 0 : break;
594 0 : case 'p':
595 : case 'x':
596 0 : warning("command-line option '-%1' is obsolete; ignoring",
597 0 : char(opt));
598 0 : break;
599 0 : case 't':
600 : #ifdef TEX_SUPPORT
601 0 : tex_flag++;
602 : #else
603 : fatal("TeX support is not built into this configuration");
604 : #endif
605 0 : break;
606 0 : case 'c':
607 : #ifdef TEX_SUPPORT
608 0 : tpic_flag++;
609 : #else
610 : fatal("TeX support is not built into this configuration");
611 : #endif
612 0 : break;
613 0 : case 'v':
614 : {
615 0 : printf("GNU pic (groff) version %s\n", Version_string);
616 0 : exit(EXIT_SUCCESS);
617 : break;
618 : }
619 0 : case 'z':
620 : // zero length lines will be printed as dots
621 0 : zero_length_line_flag++;
622 0 : break;
623 0 : case CHAR_MAX + 1: // --help
624 0 : usage(stdout);
625 0 : exit(EXIT_SUCCESS);
626 : break;
627 0 : case '?':
628 0 : if (optopt != 0)
629 0 : error("unrecognized command-line option '%1'", char(optopt));
630 : else
631 0 : error("unrecognized command-line option '%1'",
632 0 : argv[(optind - 1)]);
633 0 : usage(stderr);
634 0 : exit(2);
635 : break;
636 0 : case ':':
637 0 : error("command-line option '%1' requires an argument",
638 0 : char(optopt));
639 0 : usage(stderr);
640 0 : exit(2);
641 : break;
642 0 : default:
643 0 : assert(0 == "unhandled getopt_long return value");
644 : }
645 48 : parse_init();
646 : #ifdef TEX_SUPPORT
647 48 : if (tpic_flag) {
648 0 : out = make_tpic_output();
649 0 : lf_flag = 0;
650 : }
651 48 : else if (tex_flag) {
652 0 : out = make_tex_output();
653 0 : command_char = '\\';
654 0 : lf_flag = 0;
655 : }
656 : else
657 : #endif
658 : #ifdef FIG_SUPPORT
659 : if (fig_flag)
660 : out = make_fig_output();
661 : else
662 : #endif
663 : {
664 48 : out = make_troff_output();
665 48 : printf(".do if !dPS .ds PS\n"
666 : ".do if !dPE .ds PE\n"
667 : ".do if !dPF .ds PF\n"
668 : ".do if !dPY .ds PY\n");
669 : }
670 : #ifdef FIG_SUPPORT
671 : if (whole_file_flag) {
672 : if (optind >= argc)
673 : do_whole_file("-");
674 : else if (argc - optind > 1) {
675 : usage(stderr);
676 : exit(EXIT_FAILURE);
677 : }
678 : else
679 : do_whole_file(argv[optind]);
680 : }
681 : else {
682 : #endif
683 48 : if (optind >= argc)
684 37 : do_file("-");
685 : else
686 22 : for (int i = optind; i < argc; i++)
687 11 : do_file(argv[i]);
688 : #ifdef FIG_SUPPORT
689 : }
690 : #endif
691 48 : delete out;
692 48 : if (ferror(stdout))
693 0 : fatal("error status on standard output stream");
694 48 : if (fflush(stdout) < 0)
695 0 : fatal("cannot flush standard output stream: %1", strerror(errno));
696 48 : return had_parse_error;
697 : }
698 :
699 : // Local Variables:
700 : // fill-column: 72
701 : // mode: C++
702 : // End:
703 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|