]> git.cameronkatri.com Git - mandoc.git/blob - main.c
e8031ae2e2b9ea7b3bd0409c95c2406bcca8fbd2
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.109 2010/11/29 15:45:15 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009, 2010 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 UNCONST(a) ((void *)(uintptr_t)(const void *)(a))
45
46 /* FIXME: Intel's compiler? LLVM? pcc? */
47
48 #if !defined(__GNUC__) || (__GNUC__ < 2)
49 # if !defined(lint)
50 # define __attribute__(x)
51 # endif
52 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
53
54 typedef void (*out_mdoc)(void *, const struct mdoc *);
55 typedef void (*out_man)(void *, const struct man *);
56 typedef void (*out_free)(void *);
57
58 struct buf {
59 char *buf;
60 size_t sz;
61 };
62
63 enum intt {
64 INTT_AUTO,
65 INTT_MDOC,
66 INTT_MAN
67 };
68
69 enum outt {
70 OUTT_ASCII = 0,
71 OUTT_TREE,
72 OUTT_HTML,
73 OUTT_XHTML,
74 OUTT_LINT,
75 OUTT_PS,
76 OUTT_PDF
77 };
78
79 struct curparse {
80 const char *file; /* Current parse. */
81 int fd; /* Current parse. */
82 enum mandoclevel wlevel; /* Ignore messages below this. */
83 int wstop; /* Stop after a file with a warning. */
84 enum intt inttype; /* which parser to use */
85 struct man *man; /* man parser */
86 struct mdoc *mdoc; /* mdoc parser */
87 struct roff *roff; /* roff parser (!NULL) */
88 struct regset regs; /* roff registers */
89 enum outt outtype; /* which output to use */
90 out_mdoc outmdoc; /* mdoc output ptr */
91 out_man outman; /* man output ptr */
92 out_free outfree; /* free output ptr */
93 void *outdata; /* data for output */
94 char outopts[BUFSIZ]; /* buf of output opts */
95 };
96
97 static const char * const mandoclevels[MANDOCLEVEL_MAX] = {
98 "SUCCESS",
99 "RESERVED",
100 "WARNING",
101 "ERROR",
102 "FATAL",
103 "BADARG",
104 "SYSERR"
105 };
106
107 static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = {
108 MANDOCERR_OK,
109 MANDOCERR_WARNING,
110 MANDOCERR_WARNING,
111 MANDOCERR_ERROR,
112 MANDOCERR_FATAL,
113 MANDOCERR_MAX,
114 MANDOCERR_MAX
115 };
116
117 static const char * const mandocerrs[MANDOCERR_MAX] = {
118 "ok",
119
120 "generic warning",
121
122 "text should be uppercase",
123 "sections out of conventional order",
124 "section name repeats",
125 "out of order prologue",
126 "repeated prologue entry",
127 "list type must come first",
128 "tab in non-literal context",
129 "bad escape sequence",
130 "unterminated quoted string",
131 "argument requires the width argument",
132 "superfluous width argument",
133 "bad date argument",
134 "bad width argument",
135 "unknown manual section",
136 "section not in conventional manual section",
137 "end of line whitespace",
138 "blocks badly nested",
139
140 "generic error",
141
142 "NAME section must come first",
143 "bad Boolean value",
144 "child violates parent syntax",
145 "bad AT&T symbol",
146 "bad standard",
147 "list type repeated",
148 "display type repeated",
149 "argument repeated",
150 "ignoring argument",
151 "manual name not yet set",
152 "obsolete macro ignored",
153 "empty macro ignored",
154 "macro not allowed in body",
155 "macro not allowed in prologue",
156 "bad character",
157 "bad NAME section contents",
158 "no blank lines",
159 "no text in this context",
160 "bad comment style",
161 "unknown macro will be lost",
162 "line scope broken",
163 "argument count wrong",
164 "request scope close w/none open",
165 "scope already open",
166 "scope open on exit",
167 "macro requires line argument(s)",
168 "macro requires body argument(s)",
169 "macro requires argument(s)",
170 "no title in document",
171 "missing list type",
172 "missing display type",
173 "missing font type",
174 "line argument(s) will be lost",
175 "body argument(s) will be lost",
176 "paragraph macro ignored",
177
178 "generic fatal error",
179
180 "column syntax is inconsistent",
181 "displays may not be nested",
182 "unsupported display type",
183 "blocks badly nested",
184 "no such block is open",
185 "line scope broken, syntax violated",
186 "argument count wrong, violates syntax",
187 "child violates parent syntax",
188 "argument count wrong, violates syntax",
189 "no document body",
190 "no document prologue",
191 "utsname system call failed",
192 "static buffer exhausted",
193 };
194
195 static void fdesc(struct curparse *);
196 static void ffile(const char *, struct curparse *);
197 static int moptions(enum intt *, char *);
198 static int mmsg(enum mandocerr, void *,
199 int, int, const char *);
200 static void pset(const char *, int, struct curparse *,
201 struct man **, struct mdoc **);
202 static int toptions(struct curparse *, char *);
203 static void usage(void) __attribute__((noreturn));
204 static void version(void) __attribute__((noreturn));
205 static int woptions(struct curparse *, char *);
206
207 static const char *progname;
208 static enum mandoclevel exit_status = MANDOCLEVEL_OK;
209
210 int
211 main(int argc, char *argv[])
212 {
213 int c;
214 struct curparse curp;
215
216 progname = strrchr(argv[0], '/');
217 if (progname == NULL)
218 progname = argv[0];
219 else
220 ++progname;
221
222 memset(&curp, 0, sizeof(struct curparse));
223
224 curp.inttype = INTT_AUTO;
225 curp.outtype = OUTT_ASCII;
226 curp.wlevel = MANDOCLEVEL_FATAL;
227
228 /* LINTED */
229 while (-1 != (c = getopt(argc, argv, "m:O:T:VW:")))
230 switch (c) {
231 case ('m'):
232 if ( ! moptions(&curp.inttype, optarg))
233 return((int)MANDOCLEVEL_BADARG);
234 break;
235 case ('O'):
236 (void)strlcat(curp.outopts, optarg, BUFSIZ);
237 (void)strlcat(curp.outopts, ",", BUFSIZ);
238 break;
239 case ('T'):
240 if ( ! toptions(&curp, optarg))
241 return((int)MANDOCLEVEL_BADARG);
242 break;
243 case ('W'):
244 if ( ! woptions(&curp, optarg))
245 return((int)MANDOCLEVEL_BADARG);
246 break;
247 case ('V'):
248 version();
249 /* NOTREACHED */
250 default:
251 usage();
252 /* NOTREACHED */
253 }
254
255 argc -= optind;
256 argv += optind;
257
258 if (NULL == *argv) {
259 curp.file = "<stdin>";
260 curp.fd = STDIN_FILENO;
261
262 fdesc(&curp);
263 }
264
265 while (*argv) {
266 ffile(*argv, &curp);
267 if (MANDOCLEVEL_OK != exit_status && curp.wstop)
268 break;
269 ++argv;
270 }
271
272 if (curp.outfree)
273 (*curp.outfree)(curp.outdata);
274 if (curp.mdoc)
275 mdoc_free(curp.mdoc);
276 if (curp.man)
277 man_free(curp.man);
278 if (curp.roff)
279 roff_free(curp.roff);
280
281 return((int)exit_status);
282 }
283
284
285 static void
286 version(void)
287 {
288
289 (void)printf("%s %s\n", progname, VERSION);
290 exit((int)MANDOCLEVEL_OK);
291 }
292
293
294 static void
295 usage(void)
296 {
297
298 (void)fprintf(stderr, "usage: %s [-V] [-foption] "
299 "[-mformat] [-Ooption] [-Toutput] "
300 "[-Werr] [file...]\n", progname);
301 exit((int)MANDOCLEVEL_BADARG);
302 }
303
304
305 static void
306 ffile(const char *file, struct curparse *curp)
307 {
308
309 curp->file = file;
310 if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) {
311 perror(curp->file);
312 exit_status = MANDOCLEVEL_SYSERR;
313 return;
314 }
315
316 fdesc(curp);
317
318 if (-1 == close(curp->fd))
319 perror(curp->file);
320 }
321
322
323 static void
324 resize_buf(struct buf *buf, size_t initial)
325 {
326
327 buf->sz = buf->sz ? 2 * buf->sz : initial;
328 buf->buf = realloc(buf->buf, buf->sz);
329 if (NULL == buf->buf) {
330 perror(NULL);
331 exit((int)MANDOCLEVEL_SYSERR);
332 }
333 }
334
335
336 static int
337 read_whole_file(struct curparse *curp, struct buf *fb, int *with_mmap)
338 {
339 struct stat st;
340 size_t off;
341 ssize_t ssz;
342
343 if (-1 == fstat(curp->fd, &st)) {
344 perror(curp->file);
345 return(0);
346 }
347
348 /*
349 * If we're a regular file, try just reading in the whole entry
350 * via mmap(). This is faster than reading it into blocks, and
351 * since each file is only a few bytes to begin with, I'm not
352 * concerned that this is going to tank any machines.
353 */
354
355 if (S_ISREG(st.st_mode)) {
356 if (st.st_size >= (1U << 31)) {
357 fprintf(stderr, "%s: input too large\n",
358 curp->file);
359 return(0);
360 }
361 *with_mmap = 1;
362 fb->sz = (size_t)st.st_size;
363 fb->buf = mmap(NULL, fb->sz, PROT_READ,
364 MAP_FILE|MAP_SHARED, curp->fd, 0);
365 if (fb->buf != MAP_FAILED)
366 return(1);
367 }
368
369 /*
370 * If this isn't a regular file (like, say, stdin), then we must
371 * go the old way and just read things in bit by bit.
372 */
373
374 *with_mmap = 0;
375 off = 0;
376 fb->sz = 0;
377 fb->buf = NULL;
378 for (;;) {
379 if (off == fb->sz) {
380 if (fb->sz == (1U << 31)) {
381 fprintf(stderr, "%s: input too large\n",
382 curp->file);
383 break;
384 }
385 resize_buf(fb, 65536);
386 }
387 ssz = read(curp->fd, fb->buf + (int)off, fb->sz - off);
388 if (ssz == 0) {
389 fb->sz = off;
390 return(1);
391 }
392 if (ssz == -1) {
393 perror(curp->file);
394 break;
395 }
396 off += (size_t)ssz;
397 }
398
399 free(fb->buf);
400 fb->buf = NULL;
401 return(0);
402 }
403
404
405 static void
406 fdesc(struct curparse *curp)
407 {
408 struct buf ln, blk;
409 int i, pos, lnn, lnn_start, with_mmap, of;
410 enum rofferr re;
411 unsigned char c;
412 struct man *man;
413 struct mdoc *mdoc;
414 struct roff *roff;
415
416 man = NULL;
417 mdoc = NULL;
418 roff = NULL;
419
420 memset(&ln, 0, sizeof(struct buf));
421
422 /*
423 * Two buffers: ln and buf. buf is the input file and may be
424 * memory mapped. ln is a line buffer and grows on-demand.
425 */
426
427 if ( ! read_whole_file(curp, &blk, &with_mmap)) {
428 exit_status = MANDOCLEVEL_SYSERR;
429 return;
430 }
431
432 if (NULL == curp->roff)
433 curp->roff = roff_alloc(&curp->regs, curp, mmsg);
434 assert(curp->roff);
435 roff = curp->roff;
436
437 for (i = 0, lnn = 1; i < (int)blk.sz;) {
438 pos = 0;
439 lnn_start = lnn;
440 while (i < (int)blk.sz) {
441 if ('\n' == blk.buf[i]) {
442 ++i;
443 ++lnn;
444 break;
445 }
446
447 /*
448 * Warn about bogus characters. If you're using
449 * non-ASCII encoding, you're screwing your
450 * readers. Since I'd rather this not happen,
451 * I'll be helpful and drop these characters so
452 * we don't display gibberish. Note to manual
453 * writers: use special characters.
454 */
455
456 c = (unsigned char) blk.buf[i];
457 if ( ! (isascii(c) && (isgraph(c) || isblank(c)))) {
458 mmsg(MANDOCERR_BADCHAR, curp,
459 lnn_start, pos, "ignoring byte");
460 i++;
461 continue;
462 }
463
464 /* Trailing backslash is like a plain character. */
465 if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) {
466 if (pos >= (int)ln.sz)
467 resize_buf(&ln, 256);
468 ln.buf[pos++] = blk.buf[i++];
469 continue;
470 }
471 /* Found an escape and at least one other character. */
472 if ('\n' == blk.buf[i + 1]) {
473 /* Escaped newlines are skipped over */
474 i += 2;
475 ++lnn;
476 continue;
477 }
478 if ('"' == blk.buf[i + 1]) {
479 i += 2;
480 /* Comment, skip to end of line */
481 for (; i < (int)blk.sz; ++i) {
482 if ('\n' == blk.buf[i]) {
483 ++i;
484 ++lnn;
485 break;
486 }
487 }
488 /* Backout trailing whitespaces */
489 for (; pos > 0; --pos) {
490 if (ln.buf[pos - 1] != ' ')
491 break;
492 if (pos > 2 && ln.buf[pos - 2] == '\\')
493 break;
494 }
495 break;
496 }
497 /* Some other escape sequence, copy and continue. */
498 if (pos + 1 >= (int)ln.sz)
499 resize_buf(&ln, 256);
500
501 ln.buf[pos++] = blk.buf[i++];
502 ln.buf[pos++] = blk.buf[i++];
503 }
504
505 if (pos >= (int)ln.sz)
506 resize_buf(&ln, 256);
507 ln.buf[pos] = '\0';
508
509 /*
510 * A significant amount of complexity is contained by
511 * the roff preprocessor. It's line-oriented but can be
512 * expressed on one line, so we need at times to
513 * readjust our starting point and re-run it. The roff
514 * preprocessor can also readjust the buffers with new
515 * data, so we pass them in wholesale.
516 */
517
518 of = 0;
519 do {
520 re = roff_parseln(roff, lnn_start,
521 &ln.buf, &ln.sz, of, &of);
522 } while (ROFF_RERUN == re);
523
524 if (ROFF_IGN == re) {
525 continue;
526 } else if (ROFF_ERR == re) {
527 assert(MANDOCLEVEL_FATAL <= exit_status);
528 goto cleanup;
529 }
530
531 /*
532 * If input parsers have not been allocated, do so now.
533 * We keep these instanced betwen parsers, but set them
534 * locally per parse routine since we can use different
535 * parsers with each one.
536 */
537
538 if ( ! (man || mdoc))
539 pset(ln.buf + of, pos - of, curp, &man, &mdoc);
540
541 /* Lastly, push down into the parsers themselves. */
542
543 if (man && ! man_parseln(man, lnn_start, ln.buf, of)) {
544 assert(MANDOCLEVEL_FATAL <= exit_status);
545 goto cleanup;
546 }
547 if (mdoc && ! mdoc_parseln(mdoc, lnn_start, ln.buf, of)) {
548 assert(MANDOCLEVEL_FATAL <= exit_status);
549 goto cleanup;
550 }
551 }
552
553 /* NOTE a parser may not have been assigned, yet. */
554
555 if ( ! (man || mdoc)) {
556 fprintf(stderr, "%s: Not a manual\n", curp->file);
557 exit_status = MANDOCLEVEL_FATAL;
558 goto cleanup;
559 }
560
561 /* Clean up the parse routine ASTs. */
562
563 if (mdoc && ! mdoc_endparse(mdoc)) {
564 assert(MANDOCLEVEL_FATAL <= exit_status);
565 goto cleanup;
566 }
567 if (man && ! man_endparse(man)) {
568 assert(MANDOCLEVEL_FATAL <= exit_status);
569 goto cleanup;
570 }
571 if (roff && ! roff_endparse(roff)) {
572 assert(MANDOCLEVEL_FATAL <= exit_status);
573 goto cleanup;
574 }
575
576 /*
577 * With -Wstop and warnings or errors of at least
578 * the requested level, do not produce output.
579 */
580
581 if (MANDOCLEVEL_OK != exit_status && curp->wstop)
582 goto cleanup;
583
584 /* If unset, allocate output dev now (if applicable). */
585
586 if ( ! (curp->outman && curp->outmdoc)) {
587 switch (curp->outtype) {
588 case (OUTT_XHTML):
589 curp->outdata = xhtml_alloc(curp->outopts);
590 break;
591 case (OUTT_HTML):
592 curp->outdata = html_alloc(curp->outopts);
593 break;
594 case (OUTT_ASCII):
595 curp->outdata = ascii_alloc(curp->outopts);
596 curp->outfree = ascii_free;
597 break;
598 case (OUTT_PDF):
599 curp->outdata = pdf_alloc(curp->outopts);
600 curp->outfree = pspdf_free;
601 break;
602 case (OUTT_PS):
603 curp->outdata = ps_alloc(curp->outopts);
604 curp->outfree = pspdf_free;
605 break;
606 default:
607 break;
608 }
609
610 switch (curp->outtype) {
611 case (OUTT_HTML):
612 /* FALLTHROUGH */
613 case (OUTT_XHTML):
614 curp->outman = html_man;
615 curp->outmdoc = html_mdoc;
616 curp->outfree = html_free;
617 break;
618 case (OUTT_TREE):
619 curp->outman = tree_man;
620 curp->outmdoc = tree_mdoc;
621 break;
622 case (OUTT_PDF):
623 /* FALLTHROUGH */
624 case (OUTT_ASCII):
625 /* FALLTHROUGH */
626 case (OUTT_PS):
627 curp->outman = terminal_man;
628 curp->outmdoc = terminal_mdoc;
629 break;
630 default:
631 break;
632 }
633 }
634
635 /* Execute the out device, if it exists. */
636
637 if (man && curp->outman)
638 (*curp->outman)(curp->outdata, man);
639 if (mdoc && curp->outmdoc)
640 (*curp->outmdoc)(curp->outdata, mdoc);
641
642 cleanup:
643 memset(&curp->regs, 0, sizeof(struct regset));
644 if (mdoc)
645 mdoc_reset(mdoc);
646 if (man)
647 man_reset(man);
648 if (roff)
649 roff_reset(roff);
650 if (ln.buf)
651 free(ln.buf);
652 if (with_mmap)
653 munmap(blk.buf, blk.sz);
654 else
655 free(blk.buf);
656
657 return;
658 }
659
660
661 static void
662 pset(const char *buf, int pos, struct curparse *curp,
663 struct man **man, struct mdoc **mdoc)
664 {
665 int i;
666
667 /*
668 * Try to intuit which kind of manual parser should be used. If
669 * passed in by command-line (-man, -mdoc), then use that
670 * explicitly. If passed as -mandoc, then try to guess from the
671 * line: either skip dot-lines, use -mdoc when finding `.Dt', or
672 * default to -man, which is more lenient.
673 */
674
675 if ('.' == buf[0] || '\'' == buf[0]) {
676 for (i = 1; buf[i]; i++)
677 if (' ' != buf[i] && '\t' != buf[i])
678 break;
679 if ('\0' == buf[i])
680 return;
681 }
682
683 switch (curp->inttype) {
684 case (INTT_MDOC):
685 if (NULL == curp->mdoc)
686 curp->mdoc = mdoc_alloc(&curp->regs, curp, mmsg);
687 assert(curp->mdoc);
688 *mdoc = curp->mdoc;
689 return;
690 case (INTT_MAN):
691 if (NULL == curp->man)
692 curp->man = man_alloc(&curp->regs, curp, mmsg);
693 assert(curp->man);
694 *man = curp->man;
695 return;
696 default:
697 break;
698 }
699
700 if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) {
701 if (NULL == curp->mdoc)
702 curp->mdoc = mdoc_alloc(&curp->regs, curp, mmsg);
703 assert(curp->mdoc);
704 *mdoc = curp->mdoc;
705 return;
706 }
707
708 if (NULL == curp->man)
709 curp->man = man_alloc(&curp->regs, curp, mmsg);
710 assert(curp->man);
711 *man = curp->man;
712 }
713
714
715 static int
716 moptions(enum intt *tflags, char *arg)
717 {
718
719 if (0 == strcmp(arg, "doc"))
720 *tflags = INTT_MDOC;
721 else if (0 == strcmp(arg, "andoc"))
722 *tflags = INTT_AUTO;
723 else if (0 == strcmp(arg, "an"))
724 *tflags = INTT_MAN;
725 else {
726 fprintf(stderr, "%s: Bad argument\n", arg);
727 return(0);
728 }
729
730 return(1);
731 }
732
733
734 static int
735 toptions(struct curparse *curp, char *arg)
736 {
737
738 if (0 == strcmp(arg, "ascii"))
739 curp->outtype = OUTT_ASCII;
740 else if (0 == strcmp(arg, "lint")) {
741 curp->outtype = OUTT_LINT;
742 curp->wlevel = MANDOCLEVEL_WARNING;
743 }
744 else if (0 == strcmp(arg, "tree"))
745 curp->outtype = OUTT_TREE;
746 else if (0 == strcmp(arg, "html"))
747 curp->outtype = OUTT_HTML;
748 else if (0 == strcmp(arg, "xhtml"))
749 curp->outtype = OUTT_XHTML;
750 else if (0 == strcmp(arg, "ps"))
751 curp->outtype = OUTT_PS;
752 else if (0 == strcmp(arg, "pdf"))
753 curp->outtype = OUTT_PDF;
754 else {
755 fprintf(stderr, "%s: Bad argument\n", arg);
756 return(0);
757 }
758
759 return(1);
760 }
761
762
763 static int
764 woptions(struct curparse *curp, char *arg)
765 {
766 char *v, *o;
767 const char *toks[6];
768
769 toks[0] = "stop";
770 toks[1] = "all";
771 toks[2] = "warning";
772 toks[3] = "error";
773 toks[4] = "fatal";
774 toks[5] = NULL;
775
776 while (*arg) {
777 o = arg;
778 switch (getsubopt(&arg, UNCONST(toks), &v)) {
779 case (0):
780 curp->wstop = 1;
781 break;
782 case (1):
783 /* FALLTHROUGH */
784 case (2):
785 curp->wlevel = MANDOCLEVEL_WARNING;
786 break;
787 case (3):
788 curp->wlevel = MANDOCLEVEL_ERROR;
789 break;
790 case (4):
791 curp->wlevel = MANDOCLEVEL_FATAL;
792 break;
793 default:
794 fprintf(stderr, "-W%s: Bad argument\n", o);
795 return(0);
796 }
797 }
798
799 return(1);
800 }
801
802
803 static int
804 mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg)
805 {
806 struct curparse *cp;
807 enum mandoclevel level;
808
809 level = MANDOCLEVEL_FATAL;
810 while (t < mandoclimits[level])
811 /* LINTED */
812 level--;
813
814 cp = (struct curparse *)arg;
815 if (level < cp->wlevel)
816 return(1);
817
818 fprintf(stderr, "%s:%d:%d: %s: %s",
819 cp->file, ln, col + 1, mandoclevels[level], mandocerrs[t]);
820 if (msg)
821 fprintf(stderr, ": %s", msg);
822 fputc('\n', stderr);
823
824 if (exit_status < level)
825 exit_status = level;
826
827 return(level < MANDOCLEVEL_FATAL);
828 }