Import mdocml-1.10.9
[dragonfly.git] / contrib / mdocml / main.c
CommitLineData
80387638
SW
1/* $Id: main.c,v 1.135 2011/01/04 15:02:00 kristaps Exp $ */
2/*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#ifdef HAVE_CONFIG_H
19#include "config.h"
20#endif
21
22#include <sys/mman.h>
23#include <sys/stat.h>
24
25#include <assert.h>
26#include <ctype.h>
27#include <fcntl.h>
28#include <stdio.h>
29#include <stdint.h>
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33
34#include "mandoc.h"
35#include "main.h"
36#include "mdoc.h"
37#include "man.h"
38#include "roff.h"
39
40#ifndef MAP_FILE
41#define MAP_FILE 0
42#endif
43
44#define REPARSE_LIMIT 1000
45#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a))
46
47/* FIXME: Intel's compiler? LLVM? pcc? */
48
49#if !defined(__GNUC__) || (__GNUC__ < 2)
50# if !defined(lint)
51# define __attribute__(x)
52# endif
53#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
54
55typedef void (*out_mdoc)(void *, const struct mdoc *);
56typedef void (*out_man)(void *, const struct man *);
57typedef void (*out_free)(void *);
58
59struct buf {
60 char *buf;
61 size_t sz;
62};
63
64enum intt {
65 INTT_AUTO,
66 INTT_MDOC,
67 INTT_MAN
68};
69
70enum outt {
71 OUTT_ASCII = 0,
72 OUTT_TREE,
73 OUTT_HTML,
74 OUTT_XHTML,
75 OUTT_LINT,
76 OUTT_PS,
77 OUTT_PDF
78};
79
80struct curparse {
81 const char *file; /* Current parse. */
82 int fd; /* Current parse. */
83 int line; /* Line number in the file. */
84 enum mandoclevel wlevel; /* Ignore messages below this. */
85 int wstop; /* Stop after a file with a warning. */
86 enum intt inttype; /* which parser to use */
87 struct man *pman; /* persistent man parser */
88 struct mdoc *pmdoc; /* persistent mdoc parser */
89 struct man *man; /* man parser */
90 struct mdoc *mdoc; /* mdoc parser */
91 struct roff *roff; /* roff parser (!NULL) */
92 struct regset regs; /* roff registers */
93 int reparse_count; /* finite interpolation stack */
94 enum outt outtype; /* which output to use */
95 out_mdoc outmdoc; /* mdoc output ptr */
96 out_man outman; /* man output ptr */
97 out_free outfree; /* free output ptr */
98 void *outdata; /* data for output */
99 char outopts[BUFSIZ]; /* buf of output opts */
100};
101
102static const char * const mandoclevels[MANDOCLEVEL_MAX] = {
103 "SUCCESS",
104 "RESERVED",
105 "WARNING",
106 "ERROR",
107 "FATAL",
108 "BADARG",
109 "SYSERR"
110};
111
112static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = {
113 MANDOCERR_OK,
114 MANDOCERR_WARNING,
115 MANDOCERR_WARNING,
116 MANDOCERR_ERROR,
117 MANDOCERR_FATAL,
118 MANDOCERR_MAX,
119 MANDOCERR_MAX
120};
121
122static const char * const mandocerrs[MANDOCERR_MAX] = {
123 "ok",
124
125 "generic warning",
126
127 /* related to the prologue */
128 "no title in document",
129 "document title should be all caps",
130 "unknown manual section",
131 "cannot parse date argument",
132 "prologue macros out of order",
133 "duplicate prologue macro",
134 "macro not allowed in prologue",
135 "macro not allowed in body",
136
137 /* related to document structure */
138 ".so is fragile, better use ln(1)",
139 "NAME section must come first",
140 "bad NAME section contents",
141 "manual name not yet set",
142 "sections out of conventional order",
143 "duplicate section name",
144 "section not in conventional manual section",
145
146 /* related to macros and nesting */
147 "skipping obsolete macro",
148 "skipping paragraph macro",
149 "blocks badly nested",
150 "child violates parent syntax",
151 "nested displays are not portable",
152 "already in literal mode",
153
154 /* related to missing macro arguments */
155 "skipping empty macro",
156 "argument count wrong",
157 "missing display type",
158 "list type must come first",
159 "tag lists require a width argument",
160 "missing font type",
161
162 /* related to bad macro arguments */
163 "skipping argument",
164 "duplicate argument",
165 "duplicate display type",
166 "duplicate list type",
167 "unknown AT&T UNIX version",
168 "bad Boolean value",
169 "unknown font",
170 "unknown standard specifier",
171 "bad width argument",
172
173 /* related to plain text */
174 "blank line in non-literal context",
175 "tab in non-literal context",
176 "end of line whitespace",
177 "bad comment style",
178 "unknown escape sequence",
179 "unterminated quoted string",
180
181 /* related to tables */
182 "extra data cells",
183
184 "generic error",
185
186 /* related to tables */
187 "bad table syntax",
188 "bad table option",
189 "bad table layout",
190 "no table layout cells specified",
191 "no table data cells specified",
192 "ignore data in cell",
193 "data block still open",
194
195 "input stack limit exceeded, infinite loop?",
196 "skipping bad character",
197 "skipping text before the first section header",
198 "skipping unknown macro",
199 "NOT IMPLEMENTED: skipping request",
200 "line scope broken",
201 "argument count wrong",
202 "skipping end of block that is not open",
203 "missing end of block",
204 "scope open on exit",
205 "uname(3) system call failed",
206 "macro requires line argument(s)",
207 "macro requires body argument(s)",
208 "macro requires argument(s)",
209 "missing list type",
210 "line argument(s) will be lost",
211 "body argument(s) will be lost",
212
213 "generic fatal error",
214
215 "column syntax is inconsistent",
216 "NOT IMPLEMENTED: .Bd -file",
217 "line scope broken, syntax violated",
218 "argument count wrong, violates syntax",
219 "child violates parent syntax",
220 "argument count wrong, violates syntax",
221 "NOT IMPLEMENTED: .so with absolute path or \"..\"",
222 "no document body",
223 "no document prologue",
224 "static buffer exhausted",
225};
226
227static void parsebuf(struct curparse *, struct buf, int);
228static void pdesc(struct curparse *);
229static void fdesc(struct curparse *);
230static void ffile(const char *, struct curparse *);
231static int pfile(const char *, struct curparse *);
232static int moptions(enum intt *, char *);
233static int mmsg(enum mandocerr, void *,
234 int, int, const char *);
235static void pset(const char *, int, struct curparse *);
236static int toptions(struct curparse *, char *);
237static void usage(void) __attribute__((noreturn));
238static void version(void) __attribute__((noreturn));
239static int woptions(struct curparse *, char *);
240
241static const char *progname;
242static enum mandoclevel file_status = MANDOCLEVEL_OK;
243static enum mandoclevel exit_status = MANDOCLEVEL_OK;
244
245int
246main(int argc, char *argv[])
247{
248 int c;
249 struct curparse curp;
250
251 progname = strrchr(argv[0], '/');
252 if (progname == NULL)
253 progname = argv[0];
254 else
255 ++progname;
256
257 memset(&curp, 0, sizeof(struct curparse));
258
259 curp.inttype = INTT_AUTO;
260 curp.outtype = OUTT_ASCII;
261 curp.wlevel = MANDOCLEVEL_FATAL;
262
263 /* LINTED */
264 while (-1 != (c = getopt(argc, argv, "m:O:T:VW:")))
265 switch (c) {
266 case ('m'):
267 if ( ! moptions(&curp.inttype, optarg))
268 return((int)MANDOCLEVEL_BADARG);
269 break;
270 case ('O'):
271 (void)strlcat(curp.outopts, optarg, BUFSIZ);
272 (void)strlcat(curp.outopts, ",", BUFSIZ);
273 break;
274 case ('T'):
275 if ( ! toptions(&curp, optarg))
276 return((int)MANDOCLEVEL_BADARG);
277 break;
278 case ('W'):
279 if ( ! woptions(&curp, optarg))
280 return((int)MANDOCLEVEL_BADARG);
281 break;
282 case ('V'):
283 version();
284 /* NOTREACHED */
285 default:
286 usage();
287 /* NOTREACHED */
288 }
289
290 argc -= optind;
291 argv += optind;
292
293 if (NULL == *argv) {
294 curp.file = "<stdin>";
295 curp.fd = STDIN_FILENO;
296
297 fdesc(&curp);
298 }
299
300 while (*argv) {
301 ffile(*argv, &curp);
302 if (MANDOCLEVEL_OK != exit_status && curp.wstop)
303 break;
304 ++argv;
305 }
306
307 if (curp.outfree)
308 (*curp.outfree)(curp.outdata);
309 if (curp.pmdoc)
310 mdoc_free(curp.pmdoc);
311 if (curp.pman)
312 man_free(curp.pman);
313 if (curp.roff)
314 roff_free(curp.roff);
315
316 return((int)exit_status);
317}
318
319
320static void
321version(void)
322{
323
324 (void)printf("%s %s\n", progname, VERSION);
325 exit((int)MANDOCLEVEL_OK);
326}
327
328
329static void
330usage(void)
331{
332
333 (void)fprintf(stderr, "usage: %s "
334 "[-V] "
335 "[-foption] "
336 "[-mformat] "
337 "[-Ooption] "
338 "[-Toutput] "
339 "[-Werr] "
340 "[file...]\n",
341 progname);
342
343 exit((int)MANDOCLEVEL_BADARG);
344}
345
346static void
347ffile(const char *file, struct curparse *curp)
348{
349
350 /*
351 * Called once per input file. Get the file ready for reading,
352 * pass it through to the parser-driver, then close it out.
353 * XXX: don't do anything special as this is only called for
354 * files; stdin goes directly to fdesc().
355 */
356
357 curp->file = file;
358
359 if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) {
360 perror(curp->file);
361 exit_status = MANDOCLEVEL_SYSERR;
362 return;
363 }
364
365 fdesc(curp);
366
367 if (-1 == close(curp->fd))
368 perror(curp->file);
369}
370
371static int
372pfile(const char *file, struct curparse *curp)
373{
374 const char *savefile;
375 int fd, savefd;
376
377 if (-1 == (fd = open(file, O_RDONLY, 0))) {
378 perror(file);
379 file_status = MANDOCLEVEL_SYSERR;
380 return(0);
381 }
382
383 savefile = curp->file;
384 savefd = curp->fd;
385
386 curp->file = file;
387 curp->fd = fd;
388
389 pdesc(curp);
390
391 curp->file = savefile;
392 curp->fd = savefd;
393
394 if (-1 == close(fd))
395 perror(file);
396
397 return(MANDOCLEVEL_FATAL > file_status ? 1 : 0);
398}
399
400
401static void
402resize_buf(struct buf *buf, size_t initial)
403{
404
405 buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial;
406 buf->buf = realloc(buf->buf, buf->sz);
407 if (NULL == buf->buf) {
408 perror(NULL);
409 exit((int)MANDOCLEVEL_SYSERR);
410 }
411}
412
413
414static int
415read_whole_file(struct curparse *curp, struct buf *fb, int *with_mmap)
416{
417 struct stat st;
418 size_t off;
419 ssize_t ssz;
420
421 if (-1 == fstat(curp->fd, &st)) {
422 perror(curp->file);
423 return(0);
424 }
425
426 /*
427 * If we're a regular file, try just reading in the whole entry
428 * via mmap(). This is faster than reading it into blocks, and
429 * since each file is only a few bytes to begin with, I'm not
430 * concerned that this is going to tank any machines.
431 */
432
433 if (S_ISREG(st.st_mode)) {
434 if (st.st_size >= (1U << 31)) {
435 fprintf(stderr, "%s: input too large\n",
436 curp->file);
437 return(0);
438 }
439 *with_mmap = 1;
440 fb->sz = (size_t)st.st_size;
441 fb->buf = mmap(NULL, fb->sz, PROT_READ,
442 MAP_FILE|MAP_SHARED, curp->fd, 0);
443 if (fb->buf != MAP_FAILED)
444 return(1);
445 }
446
447 /*
448 * If this isn't a regular file (like, say, stdin), then we must
449 * go the old way and just read things in bit by bit.
450 */
451
452 *with_mmap = 0;
453 off = 0;
454 fb->sz = 0;
455 fb->buf = NULL;
456 for (;;) {
457 if (off == fb->sz) {
458 if (fb->sz == (1U << 31)) {
459 fprintf(stderr, "%s: input too large\n",
460 curp->file);
461 break;
462 }
463 resize_buf(fb, 65536);
464 }
465 ssz = read(curp->fd, fb->buf + (int)off, fb->sz - off);
466 if (ssz == 0) {
467 fb->sz = off;
468 return(1);
469 }
470 if (ssz == -1) {
471 perror(curp->file);
472 break;
473 }
474 off += (size_t)ssz;
475 }
476
477 free(fb->buf);
478 fb->buf = NULL;
479 return(0);
480}
481
482
483static void
484fdesc(struct curparse *curp)
485{
486
487 /*
488 * Called once per file with an opened file descriptor. All
489 * pre-file-parse operations (whether stdin or a file) should go
490 * here.
491 *
492 * This calls down into the nested parser, which drills down and
493 * fully parses a file and all its dependences (i.e., `so'). It
494 * then runs the cleanup validators and pushes to output.
495 */
496
497 /* Zero the parse type. */
498
499 curp->mdoc = NULL;
500 curp->man = NULL;
501 file_status = MANDOCLEVEL_OK;
502
503 /* Make sure the mandotory roff parser is initialised. */
504
505 if (NULL == curp->roff) {
506 curp->roff = roff_alloc(&curp->regs, curp, mmsg);
507 assert(curp->roff);
508 }
509
510 /* Fully parse the file. */
511
512 pdesc(curp);
513
514 if (MANDOCLEVEL_FATAL <= file_status)
515 goto cleanup;
516
517 /* NOTE a parser may not have been assigned, yet. */
518
519 if ( ! (curp->man || curp->mdoc)) {
520 fprintf(stderr, "%s: Not a manual\n", curp->file);
521 file_status = MANDOCLEVEL_FATAL;
522 goto cleanup;
523 }
524
525 /* Clean up the parse routine ASTs. */
526
527 if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
528 assert(MANDOCLEVEL_FATAL <= file_status);
529 goto cleanup;
530 }
531
532 if (curp->man && ! man_endparse(curp->man)) {
533 assert(MANDOCLEVEL_FATAL <= file_status);
534 goto cleanup;
535 }
536
537 assert(curp->roff);
538 roff_endparse(curp->roff);
539
540 /*
541 * With -Wstop and warnings or errors of at least
542 * the requested level, do not produce output.
543 */
544
545 if (MANDOCLEVEL_OK != file_status && curp->wstop)
546 goto cleanup;
547
548 /* If unset, allocate output dev now (if applicable). */
549
550 if ( ! (curp->outman && curp->outmdoc)) {
551 switch (curp->outtype) {
552 case (OUTT_XHTML):
553 curp->outdata = xhtml_alloc(curp->outopts);
554 break;
555 case (OUTT_HTML):
556 curp->outdata = html_alloc(curp->outopts);
557 break;
558 case (OUTT_ASCII):
559 curp->outdata = ascii_alloc(curp->outopts);
560 curp->outfree = ascii_free;
561 break;
562 case (OUTT_PDF):
563 curp->outdata = pdf_alloc(curp->outopts);
564 curp->outfree = pspdf_free;
565 break;
566 case (OUTT_PS):
567 curp->outdata = ps_alloc(curp->outopts);
568 curp->outfree = pspdf_free;
569 break;
570 default:
571 break;
572 }
573
574 switch (curp->outtype) {
575 case (OUTT_HTML):
576 /* FALLTHROUGH */
577 case (OUTT_XHTML):
578 curp->outman = html_man;
579 curp->outmdoc = html_mdoc;
580 curp->outfree = html_free;
581 break;
582 case (OUTT_TREE):
583 curp->outman = tree_man;
584 curp->outmdoc = tree_mdoc;
585 break;
586 case (OUTT_PDF):
587 /* FALLTHROUGH */
588 case (OUTT_ASCII):
589 /* FALLTHROUGH */
590 case (OUTT_PS):
591 curp->outman = terminal_man;
592 curp->outmdoc = terminal_mdoc;
593 break;
594 default:
595 break;
596 }
597 }
598
599 /* Execute the out device, if it exists. */
600
601 if (curp->man && curp->outman)
602 (*curp->outman)(curp->outdata, curp->man);
603 if (curp->mdoc && curp->outmdoc)
604 (*curp->outmdoc)(curp->outdata, curp->mdoc);
605
606 cleanup:
607
608 memset(&curp->regs, 0, sizeof(struct regset));
609
610 /* Reset the current-parse compilers. */
611
612 if (curp->mdoc)
613 mdoc_reset(curp->mdoc);
614 if (curp->man)
615 man_reset(curp->man);
616
617 assert(curp->roff);
618 roff_reset(curp->roff);
619
620 if (exit_status < file_status)
621 exit_status = file_status;
622
623 return;
624}
625
626static void
627pdesc(struct curparse *curp)
628{
629 struct buf blk;
630 int with_mmap;
631
632 /*
633 * Run for each opened file; may be called more than once for
634 * each full parse sequence if the opened file is nested (i.e.,
635 * from `so'). Simply sucks in the whole file and moves into
636 * the parse phase for the file.
637 */
638
639 if ( ! read_whole_file(curp, &blk, &with_mmap)) {
640 file_status = MANDOCLEVEL_SYSERR;
641 return;
642 }
643
644 /* Line number is per-file. */
645
646 curp->line = 1;
647
648 parsebuf(curp, blk, 1);
649
650 if (with_mmap)
651 munmap(blk.buf, blk.sz);
652 else
653 free(blk.buf);
654}
655
656static void
657parsebuf(struct curparse *curp, struct buf blk, int start)
658{
659 struct buf ln;
660 enum rofferr rr;
661 int i, of, rc;
662 int pos; /* byte number in the ln buffer */
663 int lnn; /* line number in the real file */
664 unsigned char c;
665
666 /*
667 * Main parse routine for an opened file. This is called for
668 * each opened file and simply loops around the full input file,
669 * possibly nesting (i.e., with `so').
670 */
671
672 memset(&ln, 0, sizeof(struct buf));
673
674 lnn = curp->line;
675 pos = 0;
676
677 for (i = 0; i < (int)blk.sz; ) {
678 if (0 == pos && '\0' == blk.buf[i])
679 break;
680
681 if (start) {
682 curp->line = lnn;
683 curp->reparse_count = 0;
684 }
685
686 while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) {
687 if ('\n' == blk.buf[i]) {
688 ++i;
689 ++lnn;
690 break;
691 }
692
693 /*
694 * Warn about bogus characters. If you're using
695 * non-ASCII encoding, you're screwing your
696 * readers. Since I'd rather this not happen,
697 * I'll be helpful and drop these characters so
698 * we don't display gibberish. Note to manual
699 * writers: use special characters.
700 */
701
702 c = (unsigned char) blk.buf[i];
703
704 if ( ! (isascii(c) &&
705 (isgraph(c) || isblank(c)))) {
706 mmsg(MANDOCERR_BADCHAR, curp,
707 curp->line, pos, "ignoring byte");
708 i++;
709 continue;
710 }
711
712 /* Trailing backslash = a plain char. */
713
714 if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) {
715 if (pos >= (int)ln.sz)
716 resize_buf(&ln, 256);
717 ln.buf[pos++] = blk.buf[i++];
718 continue;
719 }
720
721 /* Found escape & at least one other char. */
722
723 if ('\n' == blk.buf[i + 1]) {
724 i += 2;
725 /* Escaped newlines are skipped over */
726 ++lnn;
727 continue;
728 }
729
730 if ('"' == blk.buf[i + 1]) {
731 i += 2;
732 /* Comment, skip to end of line */
733 for (; i < (int)blk.sz; ++i) {
734 if ('\n' == blk.buf[i]) {
735 ++i;
736 ++lnn;
737 break;
738 }
739 }
740
741 /* Backout trailing whitespaces */
742 for (; pos > 0; --pos) {
743 if (ln.buf[pos - 1] != ' ')
744 break;
745 if (pos > 2 && ln.buf[pos - 2] == '\\')
746 break;
747 }
748 break;
749 }
750
751 /* Some other escape sequence, copy & cont. */
752
753 if (pos + 1 >= (int)ln.sz)
754 resize_buf(&ln, 256);
755
756 ln.buf[pos++] = blk.buf[i++];
757 ln.buf[pos++] = blk.buf[i++];
758 }
759
760 if (pos >= (int)ln.sz)
761 resize_buf(&ln, 256);
762
763 ln.buf[pos] = '\0';
764
765 /*
766 * A significant amount of complexity is contained by
767 * the roff preprocessor. It's line-oriented but can be
768 * expressed on one line, so we need at times to
769 * readjust our starting point and re-run it. The roff
770 * preprocessor can also readjust the buffers with new
771 * data, so we pass them in wholesale.
772 */
773
774 of = 0;
775
776rerun:
777 rr = roff_parseln
778 (curp->roff, curp->line,
779 &ln.buf, &ln.sz, of, &of);
780
781 switch (rr) {
782 case (ROFF_REPARSE):
783 if (REPARSE_LIMIT >= ++curp->reparse_count)
784 parsebuf(curp, ln, 0);
785 else
786 mmsg(MANDOCERR_ROFFLOOP, curp,
787 curp->line, pos, NULL);
788 pos = 0;
789 continue;
790 case (ROFF_APPEND):
791 pos = strlen(ln.buf);
792 continue;
793 case (ROFF_RERUN):
794 goto rerun;
795 case (ROFF_IGN):
796 pos = 0;
797 continue;
798 case (ROFF_ERR):
799 assert(MANDOCLEVEL_FATAL <= file_status);
800 break;
801 case (ROFF_SO):
802 if (pfile(ln.buf + of, curp)) {
803 pos = 0;
804 continue;
805 } else
806 break;
807 default:
808 break;
809 }
810
811 /*
812 * If we encounter errors in the recursive parsebuf()
813 * call, make sure we don't continue parsing.
814 */
815
816 if (MANDOCLEVEL_FATAL <= file_status)
817 break;
818
819 /*
820 * If input parsers have not been allocated, do so now.
821 * We keep these instanced betwen parsers, but set them
822 * locally per parse routine since we can use different
823 * parsers with each one.
824 */
825
826 if ( ! (curp->man || curp->mdoc))
827 pset(ln.buf + of, pos - of, curp);
828
829 /*
830 * Lastly, push down into the parsers themselves. One
831 * of these will have already been set in the pset()
832 * routine.
833 * If libroff returns ROFF_TBL, then add it to the
834 * currently open parse. Since we only get here if
835 * there does exist data (see tbl_data.c), we're
836 * guaranteed that something's been allocated.
837 */
838
839 if (ROFF_TBL == rr) {
840 assert(curp->man || curp->mdoc);
841 if (curp->man)
842 man_addspan(curp->man, roff_span(curp->roff));
843 else
844 mdoc_addspan(curp->mdoc, roff_span(curp->roff));
845
846 } else if (curp->man || curp->mdoc) {
847 rc = curp->man ?
848 man_parseln(curp->man,
849 curp->line, ln.buf, of) :
850 mdoc_parseln(curp->mdoc,
851 curp->line, ln.buf, of);
852
853 if ( ! rc) {
854 assert(MANDOCLEVEL_FATAL <= file_status);
855 break;
856 }
857 }
858
859 /* Temporary buffers typically are not full. */
860
861 if (0 == start && '\0' == blk.buf[i])
862 break;
863
864 /* Start the next input line. */
865
866 pos = 0;
867 }
868
869 free(ln.buf);
870}
871
872static void
873pset(const char *buf, int pos, struct curparse *curp)
874{
875 int i;
876
877 /*
878 * Try to intuit which kind of manual parser should be used. If
879 * passed in by command-line (-man, -mdoc), then use that
880 * explicitly. If passed as -mandoc, then try to guess from the
881 * line: either skip dot-lines, use -mdoc when finding `.Dt', or
882 * default to -man, which is more lenient.
883 *
884 * Separate out pmdoc/pman from mdoc/man: the first persists
885 * through all parsers, while the latter is used per-parse.
886 */
887
888 if ('.' == buf[0] || '\'' == buf[0]) {
889 for (i = 1; buf[i]; i++)
890 if (' ' != buf[i] && '\t' != buf[i])
891 break;
892 if ('\0' == buf[i])
893 return;
894 }
895
896 switch (curp->inttype) {
897 case (INTT_MDOC):
898 if (NULL == curp->pmdoc)
899 curp->pmdoc = mdoc_alloc
900 (&curp->regs, curp, mmsg);
901 assert(curp->pmdoc);
902 curp->mdoc = curp->pmdoc;
903 return;
904 case (INTT_MAN):
905 if (NULL == curp->pman)
906 curp->pman = man_alloc
907 (&curp->regs, curp, mmsg);
908 assert(curp->pman);
909 curp->man = curp->pman;
910 return;
911 default:
912 break;
913 }
914
915 if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) {
916 if (NULL == curp->pmdoc)
917 curp->pmdoc = mdoc_alloc
918 (&curp->regs, curp, mmsg);
919 assert(curp->pmdoc);
920 curp->mdoc = curp->pmdoc;
921 return;
922 }
923
924 if (NULL == curp->pman)
925 curp->pman = man_alloc(&curp->regs, curp, mmsg);
926 assert(curp->pman);
927 curp->man = curp->pman;
928}
929
930static int
931moptions(enum intt *tflags, char *arg)
932{
933
934 if (0 == strcmp(arg, "doc"))
935 *tflags = INTT_MDOC;
936 else if (0 == strcmp(arg, "andoc"))
937 *tflags = INTT_AUTO;
938 else if (0 == strcmp(arg, "an"))
939 *tflags = INTT_MAN;
940 else {
941 fprintf(stderr, "%s: Bad argument\n", arg);
942 return(0);
943 }
944
945 return(1);
946}
947
948static int
949toptions(struct curparse *curp, char *arg)
950{
951
952 if (0 == strcmp(arg, "ascii"))
953 curp->outtype = OUTT_ASCII;
954 else if (0 == strcmp(arg, "lint")) {
955 curp->outtype = OUTT_LINT;
956 curp->wlevel = MANDOCLEVEL_WARNING;
957 }
958 else if (0 == strcmp(arg, "tree"))
959 curp->outtype = OUTT_TREE;
960 else if (0 == strcmp(arg, "html"))
961 curp->outtype = OUTT_HTML;
962 else if (0 == strcmp(arg, "xhtml"))
963 curp->outtype = OUTT_XHTML;
964 else if (0 == strcmp(arg, "ps"))
965 curp->outtype = OUTT_PS;
966 else if (0 == strcmp(arg, "pdf"))
967 curp->outtype = OUTT_PDF;
968 else {
969 fprintf(stderr, "%s: Bad argument\n", arg);
970 return(0);
971 }
972
973 return(1);
974}
975
976static int
977woptions(struct curparse *curp, char *arg)
978{
979 char *v, *o;
980 const char *toks[6];
981
982 toks[0] = "stop";
983 toks[1] = "all";
984 toks[2] = "warning";
985 toks[3] = "error";
986 toks[4] = "fatal";
987 toks[5] = NULL;
988
989 while (*arg) {
990 o = arg;
991 switch (getsubopt(&arg, UNCONST(toks), &v)) {
992 case (0):
993 curp->wstop = 1;
994 break;
995 case (1):
996 /* FALLTHROUGH */
997 case (2):
998 curp->wlevel = MANDOCLEVEL_WARNING;
999 break;
1000 case (3):
1001 curp->wlevel = MANDOCLEVEL_ERROR;
1002 break;
1003 case (4):
1004 curp->wlevel = MANDOCLEVEL_FATAL;
1005 break;
1006 default:
1007 fprintf(stderr, "-W%s: Bad argument\n", o);
1008 return(0);
1009 }
1010 }
1011
1012 return(1);
1013}
1014
1015static int
1016mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg)
1017{
1018 struct curparse *cp;
1019 enum mandoclevel level;
1020
1021 level = MANDOCLEVEL_FATAL;
1022 while (t < mandoclimits[level])
1023 /* LINTED */
1024 level--;
1025
1026 cp = (struct curparse *)arg;
1027 if (level < cp->wlevel)
1028 return(1);
1029
1030 fprintf(stderr, "%s:%d:%d: %s: %s",
1031 cp->file, ln, col + 1, mandoclevels[level], mandocerrs[t]);
1032 if (msg)
1033 fprintf(stderr, ": %s", msg);
1034 fputc('\n', stderr);
1035
1036 if (file_status < level)
1037 file_status = level;
1038
1039 return(level < MANDOCLEVEL_FATAL);
1040}