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