move uname syscall manpage to section 2
[unleashed.git] / bin / mandoc / mdoc_validate.c
blob99eae19e5ec205da6e4dceac4a8ada6e92209a53
1 /* $Id: mdoc_validate.c,v 1.360 2018/08/01 16:00:58 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2018 Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include "config.h"
21 #include <sys/types.h>
22 #ifndef OSNAME
23 #include <sys/utsname.h>
24 #endif
26 #include <assert.h>
27 #include <ctype.h>
28 #include <limits.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
34 #include "mandoc_aux.h"
35 #include "mandoc.h"
36 #include "mandoc_xr.h"
37 #include "roff.h"
38 #include "mdoc.h"
39 #include "libmandoc.h"
40 #include "roff_int.h"
41 #include "libmdoc.h"
43 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
45 #define POST_ARGS struct roff_man *mdoc
47 enum check_ineq {
48 CHECK_LT,
49 CHECK_GT,
50 CHECK_EQ
53 typedef void (*v_post)(POST_ARGS);
55 static int build_list(struct roff_man *, int);
56 static void check_argv(struct roff_man *,
57 struct roff_node *, struct mdoc_argv *);
58 static void check_args(struct roff_man *, struct roff_node *);
59 static void check_text(struct roff_man *, int, int, char *);
60 static void check_text_em(struct roff_man *, int, int, char *);
61 static void check_toptext(struct roff_man *, int, int, const char *);
62 static int child_an(const struct roff_node *);
63 static size_t macro2len(enum roff_tok);
64 static void rewrite_macro2len(struct roff_man *, char **);
65 static int similar(const char *, const char *);
67 static void post_an(POST_ARGS);
68 static void post_an_norm(POST_ARGS);
69 static void post_at(POST_ARGS);
70 static void post_bd(POST_ARGS);
71 static void post_bf(POST_ARGS);
72 static void post_bk(POST_ARGS);
73 static void post_bl(POST_ARGS);
74 static void post_bl_block(POST_ARGS);
75 static void post_bl_head(POST_ARGS);
76 static void post_bl_norm(POST_ARGS);
77 static void post_bx(POST_ARGS);
78 static void post_defaults(POST_ARGS);
79 static void post_display(POST_ARGS);
80 static void post_dd(POST_ARGS);
81 static void post_delim(POST_ARGS);
82 static void post_delim_nb(POST_ARGS);
83 static void post_dt(POST_ARGS);
84 static void post_en(POST_ARGS);
85 static void post_es(POST_ARGS);
86 static void post_eoln(POST_ARGS);
87 static void post_ex(POST_ARGS);
88 static void post_fa(POST_ARGS);
89 static void post_fn(POST_ARGS);
90 static void post_fname(POST_ARGS);
91 static void post_fo(POST_ARGS);
92 static void post_hyph(POST_ARGS);
93 static void post_ignpar(POST_ARGS);
94 static void post_it(POST_ARGS);
95 static void post_lb(POST_ARGS);
96 static void post_nd(POST_ARGS);
97 static void post_nm(POST_ARGS);
98 static void post_ns(POST_ARGS);
99 static void post_obsolete(POST_ARGS);
100 static void post_os(POST_ARGS);
101 static void post_par(POST_ARGS);
102 static void post_prevpar(POST_ARGS);
103 static void post_root(POST_ARGS);
104 static void post_rs(POST_ARGS);
105 static void post_rv(POST_ARGS);
106 static void post_sh(POST_ARGS);
107 static void post_sh_head(POST_ARGS);
108 static void post_sh_name(POST_ARGS);
109 static void post_sh_see_also(POST_ARGS);
110 static void post_sh_authors(POST_ARGS);
111 static void post_sm(POST_ARGS);
112 static void post_st(POST_ARGS);
113 static void post_std(POST_ARGS);
114 static void post_sx(POST_ARGS);
115 static void post_useless(POST_ARGS);
116 static void post_xr(POST_ARGS);
117 static void post_xx(POST_ARGS);
119 static const v_post __mdoc_valids[MDOC_MAX - MDOC_Dd] = {
120 post_dd, /* Dd */
121 post_dt, /* Dt */
122 post_os, /* Os */
123 post_sh, /* Sh */
124 post_ignpar, /* Ss */
125 post_par, /* Pp */
126 post_display, /* D1 */
127 post_display, /* Dl */
128 post_display, /* Bd */
129 NULL, /* Ed */
130 post_bl, /* Bl */
131 NULL, /* El */
132 post_it, /* It */
133 post_delim_nb, /* Ad */
134 post_an, /* An */
135 NULL, /* Ap */
136 post_defaults, /* Ar */
137 NULL, /* Cd */
138 post_delim_nb, /* Cm */
139 post_delim_nb, /* Dv */
140 post_delim_nb, /* Er */
141 post_delim_nb, /* Ev */
142 post_ex, /* Ex */
143 post_fa, /* Fa */
144 NULL, /* Fd */
145 post_delim_nb, /* Fl */
146 post_fn, /* Fn */
147 post_delim_nb, /* Ft */
148 post_delim_nb, /* Ic */
149 post_delim_nb, /* In */
150 post_defaults, /* Li */
151 post_nd, /* Nd */
152 post_nm, /* Nm */
153 post_delim_nb, /* Op */
154 post_obsolete, /* Ot */
155 post_defaults, /* Pa */
156 post_rv, /* Rv */
157 post_st, /* St */
158 post_delim_nb, /* Va */
159 post_delim_nb, /* Vt */
160 post_xr, /* Xr */
161 NULL, /* %A */
162 post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */
163 NULL, /* %D */
164 NULL, /* %I */
165 NULL, /* %J */
166 post_hyph, /* %N */
167 post_hyph, /* %O */
168 NULL, /* %P */
169 post_hyph, /* %R */
170 post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */
171 NULL, /* %V */
172 NULL, /* Ac */
173 NULL, /* Ao */
174 post_delim_nb, /* Aq */
175 post_at, /* At */
176 NULL, /* Bc */
177 post_bf, /* Bf */
178 NULL, /* Bo */
179 NULL, /* Bq */
180 post_xx, /* Bsx */
181 post_bx, /* Bx */
182 post_obsolete, /* Db */
183 NULL, /* Dc */
184 NULL, /* Do */
185 NULL, /* Dq */
186 NULL, /* Ec */
187 NULL, /* Ef */
188 post_delim_nb, /* Em */
189 NULL, /* Eo */
190 post_xx, /* Fx */
191 post_delim_nb, /* Ms */
192 NULL, /* No */
193 post_ns, /* Ns */
194 post_xx, /* Nx */
195 post_xx, /* Ox */
196 NULL, /* Pc */
197 NULL, /* Pf */
198 NULL, /* Po */
199 post_delim_nb, /* Pq */
200 NULL, /* Qc */
201 post_delim_nb, /* Ql */
202 NULL, /* Qo */
203 post_delim_nb, /* Qq */
204 NULL, /* Re */
205 post_rs, /* Rs */
206 NULL, /* Sc */
207 NULL, /* So */
208 post_delim_nb, /* Sq */
209 post_sm, /* Sm */
210 post_sx, /* Sx */
211 post_delim_nb, /* Sy */
212 post_useless, /* Tn */
213 post_xx, /* Ux */
214 NULL, /* Xc */
215 NULL, /* Xo */
216 post_fo, /* Fo */
217 NULL, /* Fc */
218 NULL, /* Oo */
219 NULL, /* Oc */
220 post_bk, /* Bk */
221 NULL, /* Ek */
222 post_eoln, /* Bt */
223 post_obsolete, /* Hf */
224 post_obsolete, /* Fr */
225 post_eoln, /* Ud */
226 post_lb, /* Lb */
227 post_par, /* Lp */
228 post_delim_nb, /* Lk */
229 post_defaults, /* Mt */
230 post_delim_nb, /* Brq */
231 NULL, /* Bro */
232 NULL, /* Brc */
233 NULL, /* %C */
234 post_es, /* Es */
235 post_en, /* En */
236 post_xx, /* Dx */
237 NULL, /* %Q */
238 NULL, /* %U */
239 NULL, /* Ta */
241 static const v_post *const mdoc_valids = __mdoc_valids - MDOC_Dd;
243 #define RSORD_MAX 14 /* Number of `Rs' blocks. */
245 static const enum roff_tok rsord[RSORD_MAX] = {
246 MDOC__A,
247 MDOC__T,
248 MDOC__B,
249 MDOC__I,
250 MDOC__J,
251 MDOC__R,
252 MDOC__N,
253 MDOC__V,
254 MDOC__U,
255 MDOC__P,
256 MDOC__Q,
257 MDOC__C,
258 MDOC__D,
259 MDOC__O
262 static const char * const secnames[SEC__MAX] = {
263 NULL,
264 "NAME",
265 "LIBRARY",
266 "SYNOPSIS",
267 "DESCRIPTION",
268 "CONTEXT",
269 "IMPLEMENTATION NOTES",
270 "RETURN VALUES",
271 "ENVIRONMENT",
272 "FILES",
273 "EXIT STATUS",
274 "EXAMPLES",
275 "DIAGNOSTICS",
276 "COMPATIBILITY",
277 "ERRORS",
278 "SEE ALSO",
279 "STANDARDS",
280 "HISTORY",
281 "AUTHORS",
282 "CAVEATS",
283 "BUGS",
284 "SECURITY CONSIDERATIONS",
285 NULL
289 void
290 mdoc_node_validate(struct roff_man *mdoc)
292 struct roff_node *n, *np;
293 const v_post *p;
295 n = mdoc->last;
296 mdoc->last = mdoc->last->child;
297 while (mdoc->last != NULL) {
298 mdoc_node_validate(mdoc);
299 if (mdoc->last == n)
300 mdoc->last = mdoc->last->child;
301 else
302 mdoc->last = mdoc->last->next;
305 mdoc->last = n;
306 mdoc->next = ROFF_NEXT_SIBLING;
307 switch (n->type) {
308 case ROFFT_TEXT:
309 np = n->parent;
310 if (n->sec != SEC_SYNOPSIS ||
311 (np->tok != MDOC_Cd && np->tok != MDOC_Fd))
312 check_text(mdoc, n->line, n->pos, n->string);
313 if (np->tok != MDOC_Ql && np->tok != MDOC_Dl &&
314 (np->tok != MDOC_Bd ||
315 (mdoc->flags & MDOC_LITERAL) == 0) &&
316 (np->tok != MDOC_It || np->type != ROFFT_HEAD ||
317 np->parent->parent->norm->Bl.type != LIST_diag))
318 check_text_em(mdoc, n->line, n->pos, n->string);
319 if (np->tok == MDOC_It || (np->type == ROFFT_BODY &&
320 (np->tok == MDOC_Sh || np->tok == MDOC_Ss)))
321 check_toptext(mdoc, n->line, n->pos, n->string);
322 break;
323 case ROFFT_COMMENT:
324 case ROFFT_EQN:
325 case ROFFT_TBL:
326 break;
327 case ROFFT_ROOT:
328 post_root(mdoc);
329 break;
330 default:
331 check_args(mdoc, mdoc->last);
334 * Closing delimiters are not special at the
335 * beginning of a block, opening delimiters
336 * are not special at the end.
339 if (n->child != NULL)
340 n->child->flags &= ~NODE_DELIMC;
341 if (n->last != NULL)
342 n->last->flags &= ~NODE_DELIMO;
344 /* Call the macro's postprocessor. */
346 if (n->tok < ROFF_MAX) {
347 switch(n->tok) {
348 case ROFF_br:
349 case ROFF_sp:
350 post_par(mdoc);
351 break;
352 default:
353 roff_validate(mdoc);
354 break;
356 break;
359 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
360 p = mdoc_valids + n->tok;
361 if (*p)
362 (*p)(mdoc);
363 if (mdoc->last == n)
364 mdoc_state(mdoc, n);
365 break;
369 static void
370 check_args(struct roff_man *mdoc, struct roff_node *n)
372 int i;
374 if (NULL == n->args)
375 return;
377 assert(n->args->argc);
378 for (i = 0; i < (int)n->args->argc; i++)
379 check_argv(mdoc, n, &n->args->argv[i]);
382 static void
383 check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
385 int i;
387 for (i = 0; i < (int)v->sz; i++)
388 check_text(mdoc, v->line, v->pos, v->value[i]);
391 static void
392 check_text(struct roff_man *mdoc, int ln, int pos, char *p)
394 char *cp;
396 if (MDOC_LITERAL & mdoc->flags)
397 return;
399 for (cp = p; NULL != (p = strchr(p, '\t')); p++)
400 mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
401 ln, pos + (int)(p - cp), NULL);
404 static void
405 check_text_em(struct roff_man *mdoc, int ln, int pos, char *p)
407 const struct roff_node *np, *nn;
408 char *cp;
410 np = mdoc->last->prev;
411 nn = mdoc->last->next;
413 /* Look for em-dashes wrongly encoded as "--". */
415 for (cp = p; *cp != '\0'; cp++) {
416 if (cp[0] != '-' || cp[1] != '-')
417 continue;
418 cp++;
420 /* Skip input sequences of more than two '-'. */
422 if (cp[1] == '-') {
423 while (cp[1] == '-')
424 cp++;
425 continue;
428 /* Skip "--" directly attached to something else. */
430 if ((cp - p > 1 && cp[-2] != ' ') ||
431 (cp[1] != '\0' && cp[1] != ' '))
432 continue;
434 /* Require a letter right before or right afterwards. */
436 if ((cp - p > 2 ?
437 isalpha((unsigned char)cp[-3]) :
438 np != NULL &&
439 np->type == ROFFT_TEXT &&
440 *np->string != '\0' &&
441 isalpha((unsigned char)np->string[
442 strlen(np->string) - 1])) ||
443 (cp[1] != '\0' && cp[2] != '\0' ?
444 isalpha((unsigned char)cp[2]) :
445 nn != NULL &&
446 nn->type == ROFFT_TEXT &&
447 isalpha((unsigned char)*nn->string))) {
448 mandoc_msg(MANDOCERR_DASHDASH, mdoc->parse,
449 ln, pos + (int)(cp - p) - 1, NULL);
450 break;
455 static void
456 check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
458 const char *cp, *cpr;
460 if (*p == '\0')
461 return;
463 if ((cp = strstr(p, "OpenBSD")) != NULL)
464 mandoc_msg(MANDOCERR_BX, mdoc->parse,
465 ln, pos + (cp - p), "Ox");
466 if ((cp = strstr(p, "NetBSD")) != NULL)
467 mandoc_msg(MANDOCERR_BX, mdoc->parse,
468 ln, pos + (cp - p), "Nx");
469 if ((cp = strstr(p, "FreeBSD")) != NULL)
470 mandoc_msg(MANDOCERR_BX, mdoc->parse,
471 ln, pos + (cp - p), "Fx");
472 if ((cp = strstr(p, "DragonFly")) != NULL)
473 mandoc_msg(MANDOCERR_BX, mdoc->parse,
474 ln, pos + (cp - p), "Dx");
476 cp = p;
477 while ((cp = strstr(cp + 1, "()")) != NULL) {
478 for (cpr = cp - 1; cpr >= p; cpr--)
479 if (*cpr != '_' && !isalnum((unsigned char)*cpr))
480 break;
481 if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
482 cpr++;
483 mandoc_vmsg(MANDOCERR_FUNC, mdoc->parse,
484 ln, pos + (cpr - p),
485 "%.*s()", (int)(cp - cpr), cpr);
490 static void
491 post_delim(POST_ARGS)
493 const struct roff_node *nch;
494 const char *lc;
495 enum mdelim delim;
496 enum roff_tok tok;
498 tok = mdoc->last->tok;
499 nch = mdoc->last->last;
500 if (nch == NULL || nch->type != ROFFT_TEXT)
501 return;
502 lc = strchr(nch->string, '\0') - 1;
503 if (lc < nch->string)
504 return;
505 delim = mdoc_isdelim(lc);
506 if (delim == DELIM_NONE || delim == DELIM_OPEN)
507 return;
508 if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
509 tok == MDOC_Ss || tok == MDOC_Fo))
510 return;
512 mandoc_vmsg(MANDOCERR_DELIM, mdoc->parse,
513 nch->line, nch->pos + (lc - nch->string),
514 "%s%s %s", roff_name[tok],
515 nch == mdoc->last->child ? "" : " ...", nch->string);
518 static void
519 post_delim_nb(POST_ARGS)
521 const struct roff_node *nch;
522 const char *lc, *cp;
523 int nw;
524 enum mdelim delim;
525 enum roff_tok tok;
528 * Find candidates: at least two bytes,
529 * the last one a closing or middle delimiter.
532 tok = mdoc->last->tok;
533 nch = mdoc->last->last;
534 if (nch == NULL || nch->type != ROFFT_TEXT)
535 return;
536 lc = strchr(nch->string, '\0') - 1;
537 if (lc <= nch->string)
538 return;
539 delim = mdoc_isdelim(lc);
540 if (delim == DELIM_NONE || delim == DELIM_OPEN)
541 return;
544 * Reduce false positives by allowing various cases.
547 /* Escaped delimiters. */
548 if (lc > nch->string + 1 && lc[-2] == '\\' &&
549 (lc[-1] == '&' || lc[-1] == 'e'))
550 return;
552 /* Specific byte sequences. */
553 switch (*lc) {
554 case ')':
555 for (cp = lc; cp >= nch->string; cp--)
556 if (*cp == '(')
557 return;
558 break;
559 case '.':
560 if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
561 return;
562 if (lc[-1] == '.')
563 return;
564 break;
565 case ';':
566 if (tok == MDOC_Vt)
567 return;
568 break;
569 case '?':
570 if (lc[-1] == '?')
571 return;
572 break;
573 case ']':
574 for (cp = lc; cp >= nch->string; cp--)
575 if (*cp == '[')
576 return;
577 break;
578 case '|':
579 if (lc == nch->string + 1 && lc[-1] == '|')
580 return;
581 default:
582 break;
585 /* Exactly two non-alphanumeric bytes. */
586 if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
587 return;
589 /* At least three alphabetic words with a sentence ending. */
590 if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
591 tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) {
592 nw = 0;
593 for (cp = lc - 1; cp >= nch->string; cp--) {
594 if (*cp == ' ') {
595 nw++;
596 if (cp > nch->string && cp[-1] == ',')
597 cp--;
598 } else if (isalpha((unsigned int)*cp)) {
599 if (nw > 1)
600 return;
601 } else
602 break;
606 mandoc_vmsg(MANDOCERR_DELIM_NB, mdoc->parse,
607 nch->line, nch->pos + (lc - nch->string),
608 "%s%s %s", roff_name[tok],
609 nch == mdoc->last->child ? "" : " ...", nch->string);
612 static void
613 post_bl_norm(POST_ARGS)
615 struct roff_node *n;
616 struct mdoc_argv *argv, *wa;
617 int i;
618 enum mdocargt mdoclt;
619 enum mdoc_list lt;
621 n = mdoc->last->parent;
622 n->norm->Bl.type = LIST__NONE;
625 * First figure out which kind of list to use: bind ourselves to
626 * the first mentioned list type and warn about any remaining
627 * ones. If we find no list type, we default to LIST_item.
630 wa = (n->args == NULL) ? NULL : n->args->argv;
631 mdoclt = MDOC_ARG_MAX;
632 for (i = 0; n->args && i < (int)n->args->argc; i++) {
633 argv = n->args->argv + i;
634 lt = LIST__NONE;
635 switch (argv->arg) {
636 /* Set list types. */
637 case MDOC_Bullet:
638 lt = LIST_bullet;
639 break;
640 case MDOC_Dash:
641 lt = LIST_dash;
642 break;
643 case MDOC_Enum:
644 lt = LIST_enum;
645 break;
646 case MDOC_Hyphen:
647 lt = LIST_hyphen;
648 break;
649 case MDOC_Item:
650 lt = LIST_item;
651 break;
652 case MDOC_Tag:
653 lt = LIST_tag;
654 break;
655 case MDOC_Diag:
656 lt = LIST_diag;
657 break;
658 case MDOC_Hang:
659 lt = LIST_hang;
660 break;
661 case MDOC_Ohang:
662 lt = LIST_ohang;
663 break;
664 case MDOC_Inset:
665 lt = LIST_inset;
666 break;
667 case MDOC_Column:
668 lt = LIST_column;
669 break;
670 /* Set list arguments. */
671 case MDOC_Compact:
672 if (n->norm->Bl.comp)
673 mandoc_msg(MANDOCERR_ARG_REP,
674 mdoc->parse, argv->line,
675 argv->pos, "Bl -compact");
676 n->norm->Bl.comp = 1;
677 break;
678 case MDOC_Width:
679 wa = argv;
680 if (0 == argv->sz) {
681 mandoc_msg(MANDOCERR_ARG_EMPTY,
682 mdoc->parse, argv->line,
683 argv->pos, "Bl -width");
684 n->norm->Bl.width = "0n";
685 break;
687 if (NULL != n->norm->Bl.width)
688 mandoc_vmsg(MANDOCERR_ARG_REP,
689 mdoc->parse, argv->line,
690 argv->pos, "Bl -width %s",
691 argv->value[0]);
692 rewrite_macro2len(mdoc, argv->value);
693 n->norm->Bl.width = argv->value[0];
694 break;
695 case MDOC_Offset:
696 if (0 == argv->sz) {
697 mandoc_msg(MANDOCERR_ARG_EMPTY,
698 mdoc->parse, argv->line,
699 argv->pos, "Bl -offset");
700 break;
702 if (NULL != n->norm->Bl.offs)
703 mandoc_vmsg(MANDOCERR_ARG_REP,
704 mdoc->parse, argv->line,
705 argv->pos, "Bl -offset %s",
706 argv->value[0]);
707 rewrite_macro2len(mdoc, argv->value);
708 n->norm->Bl.offs = argv->value[0];
709 break;
710 default:
711 continue;
713 if (LIST__NONE == lt)
714 continue;
715 mdoclt = argv->arg;
717 /* Check: multiple list types. */
719 if (LIST__NONE != n->norm->Bl.type) {
720 mandoc_vmsg(MANDOCERR_BL_REP,
721 mdoc->parse, n->line, n->pos,
722 "Bl -%s", mdoc_argnames[argv->arg]);
723 continue;
726 /* The list type should come first. */
728 if (n->norm->Bl.width ||
729 n->norm->Bl.offs ||
730 n->norm->Bl.comp)
731 mandoc_vmsg(MANDOCERR_BL_LATETYPE,
732 mdoc->parse, n->line, n->pos, "Bl -%s",
733 mdoc_argnames[n->args->argv[0].arg]);
735 n->norm->Bl.type = lt;
736 if (LIST_column == lt) {
737 n->norm->Bl.ncols = argv->sz;
738 n->norm->Bl.cols = (void *)argv->value;
742 /* Allow lists to default to LIST_item. */
744 if (LIST__NONE == n->norm->Bl.type) {
745 mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
746 n->line, n->pos, "Bl");
747 n->norm->Bl.type = LIST_item;
748 mdoclt = MDOC_Item;
752 * Validate the width field. Some list types don't need width
753 * types and should be warned about them. Others should have it
754 * and must also be warned. Yet others have a default and need
755 * no warning.
758 switch (n->norm->Bl.type) {
759 case LIST_tag:
760 if (n->norm->Bl.width == NULL)
761 mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
762 n->line, n->pos, "Bl -tag");
763 break;
764 case LIST_column:
765 case LIST_diag:
766 case LIST_ohang:
767 case LIST_inset:
768 case LIST_item:
769 if (n->norm->Bl.width != NULL)
770 mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
771 wa->line, wa->pos, "Bl -%s",
772 mdoc_argnames[mdoclt]);
773 n->norm->Bl.width = NULL;
774 break;
775 case LIST_bullet:
776 case LIST_dash:
777 case LIST_hyphen:
778 if (n->norm->Bl.width == NULL)
779 n->norm->Bl.width = "2n";
780 break;
781 case LIST_enum:
782 if (n->norm->Bl.width == NULL)
783 n->norm->Bl.width = "3n";
784 break;
785 default:
786 break;
790 static void
791 post_bd(POST_ARGS)
793 struct roff_node *n;
794 struct mdoc_argv *argv;
795 int i;
796 enum mdoc_disp dt;
798 n = mdoc->last;
799 for (i = 0; n->args && i < (int)n->args->argc; i++) {
800 argv = n->args->argv + i;
801 dt = DISP__NONE;
803 switch (argv->arg) {
804 case MDOC_Centred:
805 dt = DISP_centered;
806 break;
807 case MDOC_Ragged:
808 dt = DISP_ragged;
809 break;
810 case MDOC_Unfilled:
811 dt = DISP_unfilled;
812 break;
813 case MDOC_Filled:
814 dt = DISP_filled;
815 break;
816 case MDOC_Literal:
817 dt = DISP_literal;
818 break;
819 case MDOC_File:
820 mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
821 n->line, n->pos, NULL);
822 break;
823 case MDOC_Offset:
824 if (0 == argv->sz) {
825 mandoc_msg(MANDOCERR_ARG_EMPTY,
826 mdoc->parse, argv->line,
827 argv->pos, "Bd -offset");
828 break;
830 if (NULL != n->norm->Bd.offs)
831 mandoc_vmsg(MANDOCERR_ARG_REP,
832 mdoc->parse, argv->line,
833 argv->pos, "Bd -offset %s",
834 argv->value[0]);
835 rewrite_macro2len(mdoc, argv->value);
836 n->norm->Bd.offs = argv->value[0];
837 break;
838 case MDOC_Compact:
839 if (n->norm->Bd.comp)
840 mandoc_msg(MANDOCERR_ARG_REP,
841 mdoc->parse, argv->line,
842 argv->pos, "Bd -compact");
843 n->norm->Bd.comp = 1;
844 break;
845 default:
846 abort();
848 if (DISP__NONE == dt)
849 continue;
851 if (DISP__NONE == n->norm->Bd.type)
852 n->norm->Bd.type = dt;
853 else
854 mandoc_vmsg(MANDOCERR_BD_REP,
855 mdoc->parse, n->line, n->pos,
856 "Bd -%s", mdoc_argnames[argv->arg]);
859 if (DISP__NONE == n->norm->Bd.type) {
860 mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
861 n->line, n->pos, "Bd");
862 n->norm->Bd.type = DISP_ragged;
867 * Stand-alone line macros.
870 static void
871 post_an_norm(POST_ARGS)
873 struct roff_node *n;
874 struct mdoc_argv *argv;
875 size_t i;
877 n = mdoc->last;
878 if (n->args == NULL)
879 return;
881 for (i = 1; i < n->args->argc; i++) {
882 argv = n->args->argv + i;
883 mandoc_vmsg(MANDOCERR_AN_REP,
884 mdoc->parse, argv->line, argv->pos,
885 "An -%s", mdoc_argnames[argv->arg]);
888 argv = n->args->argv;
889 if (argv->arg == MDOC_Split)
890 n->norm->An.auth = AUTH_split;
891 else if (argv->arg == MDOC_Nosplit)
892 n->norm->An.auth = AUTH_nosplit;
893 else
894 abort();
897 static void
898 post_eoln(POST_ARGS)
900 struct roff_node *n;
902 post_useless(mdoc);
903 n = mdoc->last;
904 if (n->child != NULL)
905 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, n->line,
906 n->pos, "%s %s", roff_name[n->tok], n->child->string);
908 while (n->child != NULL)
909 roff_node_delete(mdoc, n->child);
911 roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
912 "is currently in beta test." : "currently under development.");
913 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
914 mdoc->last = n;
917 static int
918 build_list(struct roff_man *mdoc, int tok)
920 struct roff_node *n;
921 int ic;
923 n = mdoc->last->next;
924 for (ic = 1;; ic++) {
925 roff_elem_alloc(mdoc, n->line, n->pos, tok);
926 mdoc->last->flags |= NODE_NOSRC;
927 mdoc_node_relink(mdoc, n);
928 n = mdoc->last = mdoc->last->parent;
929 mdoc->next = ROFF_NEXT_SIBLING;
930 if (n->next == NULL)
931 return ic;
932 if (ic > 1 || n->next->next != NULL) {
933 roff_word_alloc(mdoc, n->line, n->pos, ",");
934 mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
936 n = mdoc->last->next;
937 if (n->next == NULL) {
938 roff_word_alloc(mdoc, n->line, n->pos, "and");
939 mdoc->last->flags |= NODE_NOSRC;
944 static void
945 post_ex(POST_ARGS)
947 struct roff_node *n;
948 int ic;
950 post_std(mdoc);
952 n = mdoc->last;
953 mdoc->next = ROFF_NEXT_CHILD;
954 roff_word_alloc(mdoc, n->line, n->pos, "The");
955 mdoc->last->flags |= NODE_NOSRC;
957 if (mdoc->last->next != NULL)
958 ic = build_list(mdoc, MDOC_Nm);
959 else if (mdoc->meta.name != NULL) {
960 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
961 mdoc->last->flags |= NODE_NOSRC;
962 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
963 mdoc->last->flags |= NODE_NOSRC;
964 mdoc->last = mdoc->last->parent;
965 mdoc->next = ROFF_NEXT_SIBLING;
966 ic = 1;
967 } else {
968 mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
969 n->line, n->pos, "Ex");
970 ic = 0;
973 roff_word_alloc(mdoc, n->line, n->pos,
974 ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
975 mdoc->last->flags |= NODE_NOSRC;
976 roff_word_alloc(mdoc, n->line, n->pos,
977 "on success, and\\~>0 if an error occurs.");
978 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
979 mdoc->last = n;
982 static void
983 post_lb(POST_ARGS)
985 struct roff_node *n;
986 const char *p;
988 post_delim_nb(mdoc);
990 n = mdoc->last;
991 assert(n->child->type == ROFFT_TEXT);
992 mdoc->next = ROFF_NEXT_CHILD;
994 if ((p = mdoc_a2lib(n->child->string)) != NULL) {
995 n->child->flags |= NODE_NOPRT;
996 roff_word_alloc(mdoc, n->line, n->pos, p);
997 mdoc->last->flags = NODE_NOSRC;
998 mdoc->last = n;
999 return;
1002 mandoc_vmsg(MANDOCERR_LB_BAD, mdoc->parse, n->child->line,
1003 n->child->pos, "Lb %s", n->child->string);
1005 roff_word_alloc(mdoc, n->line, n->pos, "library");
1006 mdoc->last->flags = NODE_NOSRC;
1007 roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
1008 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
1009 mdoc->last = mdoc->last->next;
1010 roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
1011 mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
1012 mdoc->last = n;
1015 static void
1016 post_rv(POST_ARGS)
1018 struct roff_node *n;
1019 int ic;
1021 post_std(mdoc);
1023 n = mdoc->last;
1024 mdoc->next = ROFF_NEXT_CHILD;
1025 if (n->child != NULL) {
1026 roff_word_alloc(mdoc, n->line, n->pos, "The");
1027 mdoc->last->flags |= NODE_NOSRC;
1028 ic = build_list(mdoc, MDOC_Fn);
1029 roff_word_alloc(mdoc, n->line, n->pos,
1030 ic > 1 ? "functions return" : "function returns");
1031 mdoc->last->flags |= NODE_NOSRC;
1032 roff_word_alloc(mdoc, n->line, n->pos,
1033 "the value\\~0 if successful;");
1034 } else
1035 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
1036 "completion, the value\\~0 is returned;");
1037 mdoc->last->flags |= NODE_NOSRC;
1039 roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
1040 "the value\\~\\-1 is returned and the global variable");
1041 mdoc->last->flags |= NODE_NOSRC;
1042 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
1043 mdoc->last->flags |= NODE_NOSRC;
1044 roff_word_alloc(mdoc, n->line, n->pos, "errno");
1045 mdoc->last->flags |= NODE_NOSRC;
1046 mdoc->last = mdoc->last->parent;
1047 mdoc->next = ROFF_NEXT_SIBLING;
1048 roff_word_alloc(mdoc, n->line, n->pos,
1049 "is set to indicate the error.");
1050 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
1051 mdoc->last = n;
1054 static void
1055 post_std(POST_ARGS)
1057 struct roff_node *n;
1059 post_delim(mdoc);
1061 n = mdoc->last;
1062 if (n->args && n->args->argc == 1)
1063 if (n->args->argv[0].arg == MDOC_Std)
1064 return;
1066 mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
1067 n->line, n->pos, roff_name[n->tok]);
1070 static void
1071 post_st(POST_ARGS)
1073 struct roff_node *n, *nch;
1074 const char *p;
1076 n = mdoc->last;
1077 nch = n->child;
1078 assert(nch->type == ROFFT_TEXT);
1080 if ((p = mdoc_a2st(nch->string)) == NULL) {
1081 mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
1082 nch->line, nch->pos, "St %s", nch->string);
1083 roff_node_delete(mdoc, n);
1084 return;
1087 nch->flags |= NODE_NOPRT;
1088 mdoc->next = ROFF_NEXT_CHILD;
1089 roff_word_alloc(mdoc, nch->line, nch->pos, p);
1090 mdoc->last->flags |= NODE_NOSRC;
1091 mdoc->last= n;
1094 static void
1095 post_obsolete(POST_ARGS)
1097 struct roff_node *n;
1099 n = mdoc->last;
1100 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1101 mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
1102 n->line, n->pos, roff_name[n->tok]);
1105 static void
1106 post_useless(POST_ARGS)
1108 struct roff_node *n;
1110 n = mdoc->last;
1111 mandoc_msg(MANDOCERR_MACRO_USELESS, mdoc->parse,
1112 n->line, n->pos, roff_name[n->tok]);
1116 * Block macros.
1119 static void
1120 post_bf(POST_ARGS)
1122 struct roff_node *np, *nch;
1125 * Unlike other data pointers, these are "housed" by the HEAD
1126 * element, which contains the goods.
1129 np = mdoc->last;
1130 if (np->type != ROFFT_HEAD)
1131 return;
1133 assert(np->parent->type == ROFFT_BLOCK);
1134 assert(np->parent->tok == MDOC_Bf);
1136 /* Check the number of arguments. */
1138 nch = np->child;
1139 if (np->parent->args == NULL) {
1140 if (nch == NULL) {
1141 mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
1142 np->line, np->pos, "Bf");
1143 return;
1145 nch = nch->next;
1147 if (nch != NULL)
1148 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1149 nch->line, nch->pos, "Bf ... %s", nch->string);
1151 /* Extract argument into data. */
1153 if (np->parent->args != NULL) {
1154 switch (np->parent->args->argv[0].arg) {
1155 case MDOC_Emphasis:
1156 np->norm->Bf.font = FONT_Em;
1157 break;
1158 case MDOC_Literal:
1159 np->norm->Bf.font = FONT_Li;
1160 break;
1161 case MDOC_Symbolic:
1162 np->norm->Bf.font = FONT_Sy;
1163 break;
1164 default:
1165 abort();
1167 return;
1170 /* Extract parameter into data. */
1172 if ( ! strcmp(np->child->string, "Em"))
1173 np->norm->Bf.font = FONT_Em;
1174 else if ( ! strcmp(np->child->string, "Li"))
1175 np->norm->Bf.font = FONT_Li;
1176 else if ( ! strcmp(np->child->string, "Sy"))
1177 np->norm->Bf.font = FONT_Sy;
1178 else
1179 mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
1180 np->child->line, np->child->pos,
1181 "Bf %s", np->child->string);
1184 static void
1185 post_fname(POST_ARGS)
1187 const struct roff_node *n;
1188 const char *cp;
1189 size_t pos;
1191 n = mdoc->last->child;
1192 pos = strcspn(n->string, "()");
1193 cp = n->string + pos;
1194 if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
1195 mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
1196 n->line, n->pos + pos, n->string);
1199 static void
1200 post_fn(POST_ARGS)
1203 post_fname(mdoc);
1204 post_fa(mdoc);
1207 static void
1208 post_fo(POST_ARGS)
1210 const struct roff_node *n;
1212 n = mdoc->last;
1214 if (n->type != ROFFT_HEAD)
1215 return;
1217 if (n->child == NULL) {
1218 mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
1219 n->line, n->pos, "Fo");
1220 return;
1222 if (n->child != n->last) {
1223 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1224 n->child->next->line, n->child->next->pos,
1225 "Fo ... %s", n->child->next->string);
1226 while (n->child != n->last)
1227 roff_node_delete(mdoc, n->last);
1228 } else
1229 post_delim(mdoc);
1231 post_fname(mdoc);
1234 static void
1235 post_fa(POST_ARGS)
1237 const struct roff_node *n;
1238 const char *cp;
1240 for (n = mdoc->last->child; n != NULL; n = n->next) {
1241 for (cp = n->string; *cp != '\0'; cp++) {
1242 /* Ignore callbacks and alterations. */
1243 if (*cp == '(' || *cp == '{')
1244 break;
1245 if (*cp != ',')
1246 continue;
1247 mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
1248 n->line, n->pos + (cp - n->string),
1249 n->string);
1250 break;
1253 post_delim_nb(mdoc);
1256 static void
1257 post_nm(POST_ARGS)
1259 struct roff_node *n;
1261 n = mdoc->last;
1263 if (n->sec == SEC_NAME && n->child != NULL &&
1264 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1265 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1267 if (n->last != NULL &&
1268 (n->last->tok == MDOC_Pp ||
1269 n->last->tok == MDOC_Lp))
1270 mdoc_node_relink(mdoc, n->last);
1272 if (mdoc->meta.name == NULL)
1273 deroff(&mdoc->meta.name, n);
1275 if (mdoc->meta.name == NULL ||
1276 (mdoc->lastsec == SEC_NAME && n->child == NULL))
1277 mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
1278 n->line, n->pos, "Nm");
1280 switch (n->type) {
1281 case ROFFT_ELEM:
1282 post_delim_nb(mdoc);
1283 break;
1284 case ROFFT_HEAD:
1285 post_delim(mdoc);
1286 break;
1287 default:
1288 return;
1291 if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1292 mdoc->meta.name == NULL)
1293 return;
1295 mdoc->next = ROFF_NEXT_CHILD;
1296 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1297 mdoc->last->flags |= NODE_NOSRC;
1298 mdoc->last = n;
1301 static void
1302 post_nd(POST_ARGS)
1304 struct roff_node *n;
1306 n = mdoc->last;
1308 if (n->type != ROFFT_BODY)
1309 return;
1311 if (n->sec != SEC_NAME)
1312 mandoc_msg(MANDOCERR_ND_LATE, mdoc->parse,
1313 n->line, n->pos, "Nd");
1315 if (n->child == NULL)
1316 mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
1317 n->line, n->pos, "Nd");
1318 else
1319 post_delim(mdoc);
1321 post_hyph(mdoc);
1324 static void
1325 post_display(POST_ARGS)
1327 struct roff_node *n, *np;
1329 n = mdoc->last;
1330 switch (n->type) {
1331 case ROFFT_BODY:
1332 if (n->end != ENDBODY_NOT) {
1333 if (n->tok == MDOC_Bd &&
1334 n->body->parent->args == NULL)
1335 roff_node_delete(mdoc, n);
1336 } else if (n->child == NULL)
1337 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1338 n->line, n->pos, roff_name[n->tok]);
1339 else if (n->tok == MDOC_D1)
1340 post_hyph(mdoc);
1341 break;
1342 case ROFFT_BLOCK:
1343 if (n->tok == MDOC_Bd) {
1344 if (n->args == NULL) {
1345 mandoc_msg(MANDOCERR_BD_NOARG,
1346 mdoc->parse, n->line, n->pos, "Bd");
1347 mdoc->next = ROFF_NEXT_SIBLING;
1348 while (n->body->child != NULL)
1349 mdoc_node_relink(mdoc,
1350 n->body->child);
1351 roff_node_delete(mdoc, n);
1352 break;
1354 post_bd(mdoc);
1355 post_prevpar(mdoc);
1357 for (np = n->parent; np != NULL; np = np->parent) {
1358 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1359 mandoc_vmsg(MANDOCERR_BD_NEST,
1360 mdoc->parse, n->line, n->pos,
1361 "%s in Bd", roff_name[n->tok]);
1362 break;
1365 break;
1366 default:
1367 break;
1371 static void
1372 post_defaults(POST_ARGS)
1374 struct roff_node *nn;
1376 if (mdoc->last->child != NULL) {
1377 post_delim_nb(mdoc);
1378 return;
1382 * The `Ar' defaults to "file ..." if no value is provided as an
1383 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1384 * gets an empty string.
1387 nn = mdoc->last;
1388 switch (nn->tok) {
1389 case MDOC_Ar:
1390 mdoc->next = ROFF_NEXT_CHILD;
1391 roff_word_alloc(mdoc, nn->line, nn->pos, "file");
1392 mdoc->last->flags |= NODE_NOSRC;
1393 roff_word_alloc(mdoc, nn->line, nn->pos, "...");
1394 mdoc->last->flags |= NODE_NOSRC;
1395 break;
1396 case MDOC_Pa:
1397 case MDOC_Mt:
1398 mdoc->next = ROFF_NEXT_CHILD;
1399 roff_word_alloc(mdoc, nn->line, nn->pos, "~");
1400 mdoc->last->flags |= NODE_NOSRC;
1401 break;
1402 default:
1403 abort();
1405 mdoc->last = nn;
1408 static void
1409 post_at(POST_ARGS)
1411 struct roff_node *n, *nch;
1412 const char *att;
1414 n = mdoc->last;
1415 nch = n->child;
1418 * If we have a child, look it up in the standard keys. If a
1419 * key exist, use that instead of the child; if it doesn't,
1420 * prefix "AT&T UNIX " to the existing data.
1423 att = NULL;
1424 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1425 mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
1426 nch->line, nch->pos, "At %s", nch->string);
1428 mdoc->next = ROFF_NEXT_CHILD;
1429 if (att != NULL) {
1430 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1431 nch->flags |= NODE_NOPRT;
1432 } else
1433 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1434 mdoc->last->flags |= NODE_NOSRC;
1435 mdoc->last = n;
1438 static void
1439 post_an(POST_ARGS)
1441 struct roff_node *np, *nch;
1443 post_an_norm(mdoc);
1445 np = mdoc->last;
1446 nch = np->child;
1447 if (np->norm->An.auth == AUTH__NONE) {
1448 if (nch == NULL)
1449 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1450 np->line, np->pos, "An");
1451 else
1452 post_delim_nb(mdoc);
1453 } else if (nch != NULL)
1454 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1455 nch->line, nch->pos, "An ... %s", nch->string);
1458 static void
1459 post_en(POST_ARGS)
1462 post_obsolete(mdoc);
1463 if (mdoc->last->type == ROFFT_BLOCK)
1464 mdoc->last->norm->Es = mdoc->last_es;
1467 static void
1468 post_es(POST_ARGS)
1471 post_obsolete(mdoc);
1472 mdoc->last_es = mdoc->last;
1475 static void
1476 post_xx(POST_ARGS)
1478 struct roff_node *n;
1479 const char *os;
1480 char *v;
1482 post_delim_nb(mdoc);
1484 n = mdoc->last;
1485 switch (n->tok) {
1486 case MDOC_Bsx:
1487 os = "BSD/OS";
1488 break;
1489 case MDOC_Dx:
1490 os = "DragonFly";
1491 break;
1492 case MDOC_Fx:
1493 os = "FreeBSD";
1494 break;
1495 case MDOC_Nx:
1496 os = "NetBSD";
1497 if (n->child == NULL)
1498 break;
1499 v = n->child->string;
1500 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1501 v[2] < '0' || v[2] > '9' ||
1502 v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1503 break;
1504 n->child->flags |= NODE_NOPRT;
1505 mdoc->next = ROFF_NEXT_CHILD;
1506 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1507 v = mdoc->last->string;
1508 v[3] = toupper((unsigned char)v[3]);
1509 mdoc->last->flags |= NODE_NOSRC;
1510 mdoc->last = n;
1511 break;
1512 case MDOC_Ox:
1513 os = "OpenBSD";
1514 break;
1515 case MDOC_Ux:
1516 os = "UNIX";
1517 break;
1518 default:
1519 abort();
1521 mdoc->next = ROFF_NEXT_CHILD;
1522 roff_word_alloc(mdoc, n->line, n->pos, os);
1523 mdoc->last->flags |= NODE_NOSRC;
1524 mdoc->last = n;
1527 static void
1528 post_it(POST_ARGS)
1530 struct roff_node *nbl, *nit, *nch;
1531 int i, cols;
1532 enum mdoc_list lt;
1534 post_prevpar(mdoc);
1536 nit = mdoc->last;
1537 if (nit->type != ROFFT_BLOCK)
1538 return;
1540 nbl = nit->parent->parent;
1541 lt = nbl->norm->Bl.type;
1543 switch (lt) {
1544 case LIST_tag:
1545 case LIST_hang:
1546 case LIST_ohang:
1547 case LIST_inset:
1548 case LIST_diag:
1549 if (nit->head->child == NULL)
1550 mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1551 mdoc->parse, nit->line, nit->pos,
1552 "Bl -%s It",
1553 mdoc_argnames[nbl->args->argv[0].arg]);
1554 break;
1555 case LIST_bullet:
1556 case LIST_dash:
1557 case LIST_enum:
1558 case LIST_hyphen:
1559 if (nit->body == NULL || nit->body->child == NULL)
1560 mandoc_vmsg(MANDOCERR_IT_NOBODY,
1561 mdoc->parse, nit->line, nit->pos,
1562 "Bl -%s It",
1563 mdoc_argnames[nbl->args->argv[0].arg]);
1564 /* FALLTHROUGH */
1565 case LIST_item:
1566 if ((nch = nit->head->child) != NULL)
1567 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse,
1568 nit->line, nit->pos, "It %s",
1569 nch->string == NULL ? roff_name[nch->tok] :
1570 nch->string);
1571 break;
1572 case LIST_column:
1573 cols = (int)nbl->norm->Bl.ncols;
1575 assert(nit->head->child == NULL);
1577 if (nit->head->next->child == NULL &&
1578 nit->head->next->next == NULL) {
1579 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1580 nit->line, nit->pos, "It");
1581 roff_node_delete(mdoc, nit);
1582 break;
1585 i = 0;
1586 for (nch = nit->child; nch != NULL; nch = nch->next) {
1587 if (nch->type != ROFFT_BODY)
1588 continue;
1589 if (i++ && nch->flags & NODE_LINE)
1590 mandoc_msg(MANDOCERR_TA_LINE, mdoc->parse,
1591 nch->line, nch->pos, "Ta");
1593 if (i < cols || i > cols + 1)
1594 mandoc_vmsg(MANDOCERR_BL_COL,
1595 mdoc->parse, nit->line, nit->pos,
1596 "%d columns, %d cells", cols, i);
1597 else if (nit->head->next->child != NULL &&
1598 nit->head->next->child->line > nit->line)
1599 mandoc_msg(MANDOCERR_IT_NOARG, mdoc->parse,
1600 nit->line, nit->pos, "Bl -column It");
1601 break;
1602 default:
1603 abort();
1607 static void
1608 post_bl_block(POST_ARGS)
1610 struct roff_node *n, *ni, *nc;
1612 post_prevpar(mdoc);
1614 n = mdoc->last;
1615 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1616 if (ni->body == NULL)
1617 continue;
1618 nc = ni->body->last;
1619 while (nc != NULL) {
1620 switch (nc->tok) {
1621 case MDOC_Pp:
1622 case MDOC_Lp:
1623 case ROFF_br:
1624 break;
1625 default:
1626 nc = NULL;
1627 continue;
1629 if (ni->next == NULL) {
1630 mandoc_msg(MANDOCERR_PAR_MOVE,
1631 mdoc->parse, nc->line, nc->pos,
1632 roff_name[nc->tok]);
1633 mdoc_node_relink(mdoc, nc);
1634 } else if (n->norm->Bl.comp == 0 &&
1635 n->norm->Bl.type != LIST_column) {
1636 mandoc_vmsg(MANDOCERR_PAR_SKIP,
1637 mdoc->parse, nc->line, nc->pos,
1638 "%s before It", roff_name[nc->tok]);
1639 roff_node_delete(mdoc, nc);
1640 } else
1641 break;
1642 nc = ni->body->last;
1648 * If the argument of -offset or -width is a macro,
1649 * replace it with the associated default width.
1651 static void
1652 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1654 size_t width;
1655 enum roff_tok tok;
1657 if (*arg == NULL)
1658 return;
1659 else if ( ! strcmp(*arg, "Ds"))
1660 width = 6;
1661 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1662 return;
1663 else
1664 width = macro2len(tok);
1666 free(*arg);
1667 mandoc_asprintf(arg, "%zun", width);
1670 static void
1671 post_bl_head(POST_ARGS)
1673 struct roff_node *nbl, *nh, *nch, *nnext;
1674 struct mdoc_argv *argv;
1675 int i, j;
1677 post_bl_norm(mdoc);
1679 nh = mdoc->last;
1680 if (nh->norm->Bl.type != LIST_column) {
1681 if ((nch = nh->child) == NULL)
1682 return;
1683 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1684 nch->line, nch->pos, "Bl ... %s", nch->string);
1685 while (nch != NULL) {
1686 roff_node_delete(mdoc, nch);
1687 nch = nh->child;
1689 return;
1693 * Append old-style lists, where the column width specifiers
1694 * trail as macro parameters, to the new-style ("normal-form")
1695 * lists where they're argument values following -column.
1698 if (nh->child == NULL)
1699 return;
1701 nbl = nh->parent;
1702 for (j = 0; j < (int)nbl->args->argc; j++)
1703 if (nbl->args->argv[j].arg == MDOC_Column)
1704 break;
1706 assert(j < (int)nbl->args->argc);
1709 * Accommodate for new-style groff column syntax. Shuffle the
1710 * child nodes, all of which must be TEXT, as arguments for the
1711 * column field. Then, delete the head children.
1714 argv = nbl->args->argv + j;
1715 i = argv->sz;
1716 for (nch = nh->child; nch != NULL; nch = nch->next)
1717 argv->sz++;
1718 argv->value = mandoc_reallocarray(argv->value,
1719 argv->sz, sizeof(char *));
1721 nh->norm->Bl.ncols = argv->sz;
1722 nh->norm->Bl.cols = (void *)argv->value;
1724 for (nch = nh->child; nch != NULL; nch = nnext) {
1725 argv->value[i++] = nch->string;
1726 nch->string = NULL;
1727 nnext = nch->next;
1728 roff_node_delete(NULL, nch);
1730 nh->child = NULL;
1733 static void
1734 post_bl(POST_ARGS)
1736 struct roff_node *nparent, *nprev; /* of the Bl block */
1737 struct roff_node *nblock, *nbody; /* of the Bl */
1738 struct roff_node *nchild, *nnext; /* of the Bl body */
1739 const char *prev_Er;
1740 int order;
1742 nbody = mdoc->last;
1743 switch (nbody->type) {
1744 case ROFFT_BLOCK:
1745 post_bl_block(mdoc);
1746 return;
1747 case ROFFT_HEAD:
1748 post_bl_head(mdoc);
1749 return;
1750 case ROFFT_BODY:
1751 break;
1752 default:
1753 return;
1755 if (nbody->end != ENDBODY_NOT)
1756 return;
1758 nchild = nbody->child;
1759 if (nchild == NULL) {
1760 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1761 nbody->line, nbody->pos, "Bl");
1762 return;
1764 while (nchild != NULL) {
1765 nnext = nchild->next;
1766 if (nchild->tok == MDOC_It ||
1767 (nchild->tok == MDOC_Sm &&
1768 nnext != NULL && nnext->tok == MDOC_It)) {
1769 nchild = nnext;
1770 continue;
1774 * In .Bl -column, the first rows may be implicit,
1775 * that is, they may not start with .It macros.
1776 * Such rows may be followed by nodes generated on the
1777 * roff level, for example .TS, which cannot be moved
1778 * out of the list. In that case, wrap such roff nodes
1779 * into an implicit row.
1782 if (nchild->prev != NULL) {
1783 mdoc->last = nchild;
1784 mdoc->next = ROFF_NEXT_SIBLING;
1785 roff_block_alloc(mdoc, nchild->line,
1786 nchild->pos, MDOC_It);
1787 roff_head_alloc(mdoc, nchild->line,
1788 nchild->pos, MDOC_It);
1789 mdoc->next = ROFF_NEXT_SIBLING;
1790 roff_body_alloc(mdoc, nchild->line,
1791 nchild->pos, MDOC_It);
1792 while (nchild->tok != MDOC_It) {
1793 mdoc_node_relink(mdoc, nchild);
1794 if ((nchild = nnext) == NULL)
1795 break;
1796 nnext = nchild->next;
1797 mdoc->next = ROFF_NEXT_SIBLING;
1799 mdoc->last = nbody;
1800 continue;
1803 mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1804 nchild->line, nchild->pos, roff_name[nchild->tok]);
1807 * Move the node out of the Bl block.
1808 * First, collect all required node pointers.
1811 nblock = nbody->parent;
1812 nprev = nblock->prev;
1813 nparent = nblock->parent;
1816 * Unlink this child.
1819 nbody->child = nnext;
1820 if (nnext == NULL)
1821 nbody->last = NULL;
1822 else
1823 nnext->prev = NULL;
1826 * Relink this child.
1829 nchild->parent = nparent;
1830 nchild->prev = nprev;
1831 nchild->next = nblock;
1833 nblock->prev = nchild;
1834 if (nprev == NULL)
1835 nparent->child = nchild;
1836 else
1837 nprev->next = nchild;
1839 nchild = nnext;
1842 if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1843 return;
1845 prev_Er = NULL;
1846 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1847 if (nchild->tok != MDOC_It)
1848 continue;
1849 if ((nnext = nchild->head->child) == NULL)
1850 continue;
1851 if (nnext->type == ROFFT_BLOCK)
1852 nnext = nnext->body->child;
1853 if (nnext == NULL || nnext->tok != MDOC_Er)
1854 continue;
1855 nnext = nnext->child;
1856 if (prev_Er != NULL) {
1857 order = strcmp(prev_Er, nnext->string);
1858 if (order > 0)
1859 mandoc_vmsg(MANDOCERR_ER_ORDER,
1860 mdoc->parse, nnext->line, nnext->pos,
1861 "Er %s %s (NetBSD)",
1862 prev_Er, nnext->string);
1863 else if (order == 0)
1864 mandoc_vmsg(MANDOCERR_ER_REP,
1865 mdoc->parse, nnext->line, nnext->pos,
1866 "Er %s (NetBSD)", prev_Er);
1868 prev_Er = nnext->string;
1872 static void
1873 post_bk(POST_ARGS)
1875 struct roff_node *n;
1877 n = mdoc->last;
1879 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1880 mandoc_msg(MANDOCERR_BLK_EMPTY,
1881 mdoc->parse, n->line, n->pos, "Bk");
1882 roff_node_delete(mdoc, n);
1886 static void
1887 post_sm(POST_ARGS)
1889 struct roff_node *nch;
1891 nch = mdoc->last->child;
1893 if (nch == NULL) {
1894 mdoc->flags ^= MDOC_SMOFF;
1895 return;
1898 assert(nch->type == ROFFT_TEXT);
1900 if ( ! strcmp(nch->string, "on")) {
1901 mdoc->flags &= ~MDOC_SMOFF;
1902 return;
1904 if ( ! strcmp(nch->string, "off")) {
1905 mdoc->flags |= MDOC_SMOFF;
1906 return;
1909 mandoc_vmsg(MANDOCERR_SM_BAD,
1910 mdoc->parse, nch->line, nch->pos,
1911 "%s %s", roff_name[mdoc->last->tok], nch->string);
1912 mdoc_node_relink(mdoc, nch);
1913 return;
1916 static void
1917 post_root(POST_ARGS)
1919 const char *openbsd_arch[] = {
1920 "alpha", "amd64", "arm64", "armv7", "hppa", "i386",
1921 "landisk", "loongson", "luna88k", "macppc", "mips64",
1922 "octeon", "sgi", "socppc", "sparc64", NULL
1924 const char *netbsd_arch[] = {
1925 "acorn26", "acorn32", "algor", "alpha", "amiga",
1926 "arc", "atari",
1927 "bebox", "cats", "cesfic", "cobalt", "dreamcast",
1928 "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5",
1929 "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa",
1930 "i386", "ibmnws", "luna68k",
1931 "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc",
1932 "netwinder", "news68k", "newsmips", "next68k",
1933 "pc532", "playstation2", "pmax", "pmppc", "prep",
1934 "sandpoint", "sbmips", "sgimips", "shark",
1935 "sparc", "sparc64", "sun2", "sun3",
1936 "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL
1938 const char **arches[] = { NULL, netbsd_arch, openbsd_arch };
1940 struct roff_node *n;
1941 const char **arch;
1943 /* Add missing prologue data. */
1945 if (mdoc->meta.date == NULL)
1946 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1947 mandoc_normdate(mdoc, NULL, 0, 0);
1949 if (mdoc->meta.title == NULL) {
1950 mandoc_msg(MANDOCERR_DT_NOTITLE,
1951 mdoc->parse, 0, 0, "EOF");
1952 mdoc->meta.title = mandoc_strdup("UNTITLED");
1955 if (mdoc->meta.vol == NULL)
1956 mdoc->meta.vol = mandoc_strdup("LOCAL");
1958 if (mdoc->meta.os == NULL) {
1959 mandoc_msg(MANDOCERR_OS_MISSING,
1960 mdoc->parse, 0, 0, NULL);
1961 mdoc->meta.os = mandoc_strdup("");
1962 } else if (mdoc->meta.os_e &&
1963 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1964 mandoc_msg(MANDOCERR_RCS_MISSING, mdoc->parse, 0, 0,
1965 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1966 "(OpenBSD)" : "(NetBSD)");
1968 if (mdoc->meta.arch != NULL &&
1969 (arch = arches[mdoc->meta.os_e]) != NULL) {
1970 while (*arch != NULL && strcmp(*arch, mdoc->meta.arch))
1971 arch++;
1972 if (*arch == NULL) {
1973 n = mdoc->first->child;
1974 while (n->tok != MDOC_Dt ||
1975 n->child == NULL ||
1976 n->child->next == NULL ||
1977 n->child->next->next == NULL)
1978 n = n->next;
1979 n = n->child->next->next;
1980 mandoc_vmsg(MANDOCERR_ARCH_BAD,
1981 mdoc->parse, n->line, n->pos,
1982 "Dt ... %s %s", mdoc->meta.arch,
1983 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1984 "(OpenBSD)" : "(NetBSD)");
1988 /* Check that we begin with a proper `Sh'. */
1990 n = mdoc->first->child;
1991 while (n != NULL &&
1992 (n->type == ROFFT_COMMENT ||
1993 (n->tok >= MDOC_Dd &&
1994 mdoc_macros[n->tok].flags & MDOC_PROLOGUE)))
1995 n = n->next;
1997 if (n == NULL)
1998 mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1999 else if (n->tok != MDOC_Sh)
2000 mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
2001 n->line, n->pos, roff_name[n->tok]);
2004 static void
2005 post_rs(POST_ARGS)
2007 struct roff_node *np, *nch, *next, *prev;
2008 int i, j;
2010 np = mdoc->last;
2012 if (np->type != ROFFT_BODY)
2013 return;
2015 if (np->child == NULL) {
2016 mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
2017 np->line, np->pos, "Rs");
2018 return;
2022 * The full `Rs' block needs special handling to order the
2023 * sub-elements according to `rsord'. Pick through each element
2024 * and correctly order it. This is an insertion sort.
2027 next = NULL;
2028 for (nch = np->child->next; nch != NULL; nch = next) {
2029 /* Determine order number of this child. */
2030 for (i = 0; i < RSORD_MAX; i++)
2031 if (rsord[i] == nch->tok)
2032 break;
2034 if (i == RSORD_MAX) {
2035 mandoc_msg(MANDOCERR_RS_BAD, mdoc->parse,
2036 nch->line, nch->pos, roff_name[nch->tok]);
2037 i = -1;
2038 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
2039 np->norm->Rs.quote_T++;
2042 * Remove this child from the chain. This somewhat
2043 * repeats roff_node_unlink(), but since we're
2044 * just re-ordering, there's no need for the
2045 * full unlink process.
2048 if ((next = nch->next) != NULL)
2049 next->prev = nch->prev;
2051 if ((prev = nch->prev) != NULL)
2052 prev->next = nch->next;
2054 nch->prev = nch->next = NULL;
2057 * Scan back until we reach a node that's
2058 * to be ordered before this child.
2061 for ( ; prev ; prev = prev->prev) {
2062 /* Determine order of `prev'. */
2063 for (j = 0; j < RSORD_MAX; j++)
2064 if (rsord[j] == prev->tok)
2065 break;
2066 if (j == RSORD_MAX)
2067 j = -1;
2069 if (j <= i)
2070 break;
2074 * Set this child back into its correct place
2075 * in front of the `prev' node.
2078 nch->prev = prev;
2080 if (prev == NULL) {
2081 np->child->prev = nch;
2082 nch->next = np->child;
2083 np->child = nch;
2084 } else {
2085 if (prev->next)
2086 prev->next->prev = nch;
2087 nch->next = prev->next;
2088 prev->next = nch;
2094 * For some arguments of some macros,
2095 * convert all breakable hyphens into ASCII_HYPH.
2097 static void
2098 post_hyph(POST_ARGS)
2100 struct roff_node *nch;
2101 char *cp;
2103 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2104 if (nch->type != ROFFT_TEXT)
2105 continue;
2106 cp = nch->string;
2107 if (*cp == '\0')
2108 continue;
2109 while (*(++cp) != '\0')
2110 if (*cp == '-' &&
2111 isalpha((unsigned char)cp[-1]) &&
2112 isalpha((unsigned char)cp[1]))
2113 *cp = ASCII_HYPH;
2117 static void
2118 post_ns(POST_ARGS)
2120 struct roff_node *n;
2122 n = mdoc->last;
2123 if (n->flags & NODE_LINE ||
2124 (n->next != NULL && n->next->flags & NODE_DELIMC))
2125 mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
2126 n->line, n->pos, NULL);
2129 static void
2130 post_sx(POST_ARGS)
2132 post_delim(mdoc);
2133 post_hyph(mdoc);
2136 static void
2137 post_sh(POST_ARGS)
2140 post_ignpar(mdoc);
2142 switch (mdoc->last->type) {
2143 case ROFFT_HEAD:
2144 post_sh_head(mdoc);
2145 break;
2146 case ROFFT_BODY:
2147 switch (mdoc->lastsec) {
2148 case SEC_NAME:
2149 post_sh_name(mdoc);
2150 break;
2151 case SEC_SEE_ALSO:
2152 post_sh_see_also(mdoc);
2153 break;
2154 case SEC_AUTHORS:
2155 post_sh_authors(mdoc);
2156 break;
2157 default:
2158 break;
2160 break;
2161 default:
2162 break;
2166 static void
2167 post_sh_name(POST_ARGS)
2169 struct roff_node *n;
2170 int hasnm, hasnd;
2172 hasnm = hasnd = 0;
2174 for (n = mdoc->last->child; n != NULL; n = n->next) {
2175 switch (n->tok) {
2176 case MDOC_Nm:
2177 if (hasnm && n->child != NULL)
2178 mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT,
2179 mdoc->parse, n->line, n->pos,
2180 "Nm %s", n->child->string);
2181 hasnm = 1;
2182 continue;
2183 case MDOC_Nd:
2184 hasnd = 1;
2185 if (n->next != NULL)
2186 mandoc_msg(MANDOCERR_NAMESEC_ND,
2187 mdoc->parse, n->line, n->pos, NULL);
2188 break;
2189 case TOKEN_NONE:
2190 if (n->type == ROFFT_TEXT &&
2191 n->string[0] == ',' && n->string[1] == '\0' &&
2192 n->next != NULL && n->next->tok == MDOC_Nm) {
2193 n = n->next;
2194 continue;
2196 /* FALLTHROUGH */
2197 default:
2198 mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
2199 n->line, n->pos, roff_name[n->tok]);
2200 continue;
2202 break;
2205 if ( ! hasnm)
2206 mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
2207 mdoc->last->line, mdoc->last->pos, NULL);
2208 if ( ! hasnd)
2209 mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
2210 mdoc->last->line, mdoc->last->pos, NULL);
2213 static void
2214 post_sh_see_also(POST_ARGS)
2216 const struct roff_node *n;
2217 const char *name, *sec;
2218 const char *lastname, *lastsec, *lastpunct;
2219 int cmp;
2221 n = mdoc->last->child;
2222 lastname = lastsec = lastpunct = NULL;
2223 while (n != NULL) {
2224 if (n->tok != MDOC_Xr ||
2225 n->child == NULL ||
2226 n->child->next == NULL)
2227 break;
2229 /* Process one .Xr node. */
2231 name = n->child->string;
2232 sec = n->child->next->string;
2233 if (lastsec != NULL) {
2234 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2235 mandoc_vmsg(MANDOCERR_XR_PUNCT,
2236 mdoc->parse, n->line, n->pos,
2237 "%s before %s(%s)", lastpunct,
2238 name, sec);
2239 cmp = strcmp(lastsec, sec);
2240 if (cmp > 0)
2241 mandoc_vmsg(MANDOCERR_XR_ORDER,
2242 mdoc->parse, n->line, n->pos,
2243 "%s(%s) after %s(%s)", name,
2244 sec, lastname, lastsec);
2245 else if (cmp == 0 &&
2246 strcasecmp(lastname, name) > 0)
2247 mandoc_vmsg(MANDOCERR_XR_ORDER,
2248 mdoc->parse, n->line, n->pos,
2249 "%s after %s", name, lastname);
2251 lastname = name;
2252 lastsec = sec;
2254 /* Process the following node. */
2256 n = n->next;
2257 if (n == NULL)
2258 break;
2259 if (n->tok == MDOC_Xr) {
2260 lastpunct = "none";
2261 continue;
2263 if (n->type != ROFFT_TEXT)
2264 break;
2265 for (name = n->string; *name != '\0'; name++)
2266 if (isalpha((const unsigned char)*name))
2267 return;
2268 lastpunct = n->string;
2269 if (n->next == NULL || n->next->tok == MDOC_Rs)
2270 mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
2271 n->line, n->pos, "%s after %s(%s)",
2272 lastpunct, lastname, lastsec);
2273 n = n->next;
2277 static int
2278 child_an(const struct roff_node *n)
2281 for (n = n->child; n != NULL; n = n->next)
2282 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2283 return 1;
2284 return 0;
2287 static void
2288 post_sh_authors(POST_ARGS)
2291 if ( ! child_an(mdoc->last))
2292 mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
2293 mdoc->last->line, mdoc->last->pos, NULL);
2297 * Return an upper bound for the string distance (allowing
2298 * transpositions). Not a full Levenshtein implementation
2299 * because Levenshtein is quadratic in the string length
2300 * and this function is called for every standard name,
2301 * so the check for each custom name would be cubic.
2302 * The following crude heuristics is linear, resulting
2303 * in quadratic behaviour for checking one custom name,
2304 * which does not cause measurable slowdown.
2306 static int
2307 similar(const char *s1, const char *s2)
2309 const int maxdist = 3;
2310 int dist = 0;
2312 while (s1[0] != '\0' && s2[0] != '\0') {
2313 if (s1[0] == s2[0]) {
2314 s1++;
2315 s2++;
2316 continue;
2318 if (++dist > maxdist)
2319 return INT_MAX;
2320 if (s1[1] == s2[1]) { /* replacement */
2321 s1++;
2322 s2++;
2323 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2324 s1 += 2; /* transposition */
2325 s2 += 2;
2326 } else if (s1[0] == s2[1]) /* insertion */
2327 s2++;
2328 else if (s1[1] == s2[0]) /* deletion */
2329 s1++;
2330 else
2331 return INT_MAX;
2333 dist += strlen(s1) + strlen(s2);
2334 return dist > maxdist ? INT_MAX : dist;
2337 static void
2338 post_sh_head(POST_ARGS)
2340 struct roff_node *nch;
2341 const char *goodsec;
2342 const char *const *testsec;
2343 int dist, mindist;
2344 enum roff_sec sec;
2347 * Process a new section. Sections are either "named" or
2348 * "custom". Custom sections are user-defined, while named ones
2349 * follow a conventional order and may only appear in certain
2350 * manual sections.
2353 sec = mdoc->last->sec;
2355 /* The NAME should be first. */
2357 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2358 mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
2359 mdoc->last->line, mdoc->last->pos, "Sh %s",
2360 sec != SEC_CUSTOM ? secnames[sec] :
2361 (nch = mdoc->last->child) == NULL ? "" :
2362 nch->type == ROFFT_TEXT ? nch->string :
2363 roff_name[nch->tok]);
2365 /* The SYNOPSIS gets special attention in other areas. */
2367 if (sec == SEC_SYNOPSIS) {
2368 roff_setreg(mdoc->roff, "nS", 1, '=');
2369 mdoc->flags |= MDOC_SYNOPSIS;
2370 } else {
2371 roff_setreg(mdoc->roff, "nS", 0, '=');
2372 mdoc->flags &= ~MDOC_SYNOPSIS;
2375 /* Mark our last section. */
2377 mdoc->lastsec = sec;
2379 /* We don't care about custom sections after this. */
2381 if (sec == SEC_CUSTOM) {
2382 if ((nch = mdoc->last->child) == NULL ||
2383 nch->type != ROFFT_TEXT || nch->next != NULL)
2384 return;
2385 goodsec = NULL;
2386 mindist = INT_MAX;
2387 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2388 dist = similar(nch->string, *testsec);
2389 if (dist < mindist) {
2390 goodsec = *testsec;
2391 mindist = dist;
2394 if (goodsec != NULL)
2395 mandoc_vmsg(MANDOCERR_SEC_TYPO, mdoc->parse,
2396 nch->line, nch->pos, "Sh %s instead of %s",
2397 nch->string, goodsec);
2398 return;
2402 * Check whether our non-custom section is being repeated or is
2403 * out of order.
2406 if (sec == mdoc->lastnamed)
2407 mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
2408 mdoc->last->line, mdoc->last->pos,
2409 "Sh %s", secnames[sec]);
2411 if (sec < mdoc->lastnamed)
2412 mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
2413 mdoc->last->line, mdoc->last->pos,
2414 "Sh %s", secnames[sec]);
2416 /* Mark the last named section. */
2418 mdoc->lastnamed = sec;
2420 /* Check particular section/manual conventions. */
2422 if (mdoc->meta.msec == NULL)
2423 return;
2425 goodsec = NULL;
2426 switch (sec) {
2427 case SEC_ERRORS:
2428 if (*mdoc->meta.msec == '4')
2429 break;
2430 goodsec = "2, 3, 4, 9";
2431 /* FALLTHROUGH */
2432 case SEC_RETURN_VALUES:
2433 case SEC_LIBRARY:
2434 if (*mdoc->meta.msec == '2')
2435 break;
2436 if (*mdoc->meta.msec == '3')
2437 break;
2438 if (NULL == goodsec)
2439 goodsec = "2, 3, 9";
2440 /* FALLTHROUGH */
2441 case SEC_CONTEXT:
2442 if (*mdoc->meta.msec == '9')
2443 break;
2444 if (NULL == goodsec)
2445 goodsec = "9";
2446 mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
2447 mdoc->last->line, mdoc->last->pos,
2448 "Sh %s for %s only", secnames[sec], goodsec);
2449 break;
2450 default:
2451 break;
2455 static void
2456 post_xr(POST_ARGS)
2458 struct roff_node *n, *nch;
2460 n = mdoc->last;
2461 nch = n->child;
2462 if (nch->next == NULL) {
2463 mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse,
2464 n->line, n->pos, "Xr %s", nch->string);
2465 } else {
2466 assert(nch->next == n->last);
2467 if(mandoc_xr_add(nch->next->string, nch->string,
2468 nch->line, nch->pos))
2469 mandoc_vmsg(MANDOCERR_XR_SELF, mdoc->parse,
2470 nch->line, nch->pos, "Xr %s %s",
2471 nch->string, nch->next->string);
2473 post_delim_nb(mdoc);
2476 static void
2477 post_ignpar(POST_ARGS)
2479 struct roff_node *np;
2481 switch (mdoc->last->type) {
2482 case ROFFT_BLOCK:
2483 post_prevpar(mdoc);
2484 return;
2485 case ROFFT_HEAD:
2486 post_delim(mdoc);
2487 post_hyph(mdoc);
2488 return;
2489 case ROFFT_BODY:
2490 break;
2491 default:
2492 return;
2495 if ((np = mdoc->last->child) != NULL)
2496 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2497 mandoc_vmsg(MANDOCERR_PAR_SKIP,
2498 mdoc->parse, np->line, np->pos,
2499 "%s after %s", roff_name[np->tok],
2500 roff_name[mdoc->last->tok]);
2501 roff_node_delete(mdoc, np);
2504 if ((np = mdoc->last->last) != NULL)
2505 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2506 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2507 np->line, np->pos, "%s at the end of %s",
2508 roff_name[np->tok],
2509 roff_name[mdoc->last->tok]);
2510 roff_node_delete(mdoc, np);
2514 static void
2515 post_prevpar(POST_ARGS)
2517 struct roff_node *n;
2519 n = mdoc->last;
2520 if (NULL == n->prev)
2521 return;
2522 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2523 return;
2526 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
2527 * block: `Lp', `Pp', or non-compact `Bd' or `Bl'.
2530 if (n->prev->tok != MDOC_Pp &&
2531 n->prev->tok != MDOC_Lp &&
2532 n->prev->tok != ROFF_br)
2533 return;
2534 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2535 return;
2536 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2537 return;
2538 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2539 return;
2541 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2542 n->prev->line, n->prev->pos, "%s before %s",
2543 roff_name[n->prev->tok], roff_name[n->tok]);
2544 roff_node_delete(mdoc, n->prev);
2547 static void
2548 post_par(POST_ARGS)
2550 struct roff_node *np;
2552 np = mdoc->last;
2553 if (np->tok != ROFF_br && np->tok != ROFF_sp)
2554 post_prevpar(mdoc);
2556 if (np->tok == ROFF_sp) {
2557 if (np->child != NULL && np->child->next != NULL)
2558 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2559 np->child->next->line, np->child->next->pos,
2560 "sp ... %s", np->child->next->string);
2561 } else if (np->child != NULL)
2562 mandoc_vmsg(MANDOCERR_ARG_SKIP,
2563 mdoc->parse, np->line, np->pos, "%s %s",
2564 roff_name[np->tok], np->child->string);
2566 if ((np = mdoc->last->prev) == NULL) {
2567 np = mdoc->last->parent;
2568 if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
2569 return;
2570 } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
2571 (mdoc->last->tok != ROFF_br ||
2572 (np->tok != ROFF_sp && np->tok != ROFF_br)))
2573 return;
2575 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2576 mdoc->last->line, mdoc->last->pos, "%s after %s",
2577 roff_name[mdoc->last->tok], roff_name[np->tok]);
2578 roff_node_delete(mdoc, mdoc->last);
2581 static void
2582 post_dd(POST_ARGS)
2584 struct roff_node *n;
2585 char *datestr;
2587 n = mdoc->last;
2588 n->flags |= NODE_NOPRT;
2590 if (mdoc->meta.date != NULL) {
2591 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2592 n->line, n->pos, "Dd");
2593 free(mdoc->meta.date);
2594 } else if (mdoc->flags & MDOC_PBODY)
2595 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2596 n->line, n->pos, "Dd");
2597 else if (mdoc->meta.title != NULL)
2598 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2599 n->line, n->pos, "Dd after Dt");
2600 else if (mdoc->meta.os != NULL)
2601 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2602 n->line, n->pos, "Dd after Os");
2604 if (n->child == NULL || n->child->string[0] == '\0') {
2605 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
2606 mandoc_normdate(mdoc, NULL, n->line, n->pos);
2607 return;
2610 datestr = NULL;
2611 deroff(&datestr, n);
2612 if (mdoc->quick)
2613 mdoc->meta.date = datestr;
2614 else {
2615 mdoc->meta.date = mandoc_normdate(mdoc,
2616 datestr, n->line, n->pos);
2617 free(datestr);
2621 static void
2622 post_dt(POST_ARGS)
2624 struct roff_node *nn, *n;
2625 const char *cp;
2626 char *p;
2628 n = mdoc->last;
2629 n->flags |= NODE_NOPRT;
2631 if (mdoc->flags & MDOC_PBODY) {
2632 mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2633 n->line, n->pos, "Dt");
2634 return;
2637 if (mdoc->meta.title != NULL)
2638 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2639 n->line, n->pos, "Dt");
2640 else if (mdoc->meta.os != NULL)
2641 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2642 n->line, n->pos, "Dt after Os");
2644 free(mdoc->meta.title);
2645 free(mdoc->meta.msec);
2646 free(mdoc->meta.vol);
2647 free(mdoc->meta.arch);
2649 mdoc->meta.title = NULL;
2650 mdoc->meta.msec = NULL;
2651 mdoc->meta.vol = NULL;
2652 mdoc->meta.arch = NULL;
2654 /* Mandatory first argument: title. */
2656 nn = n->child;
2657 if (nn == NULL || *nn->string == '\0') {
2658 mandoc_msg(MANDOCERR_DT_NOTITLE,
2659 mdoc->parse, n->line, n->pos, "Dt");
2660 mdoc->meta.title = mandoc_strdup("UNTITLED");
2661 } else {
2662 mdoc->meta.title = mandoc_strdup(nn->string);
2664 /* Check that all characters are uppercase. */
2666 for (p = nn->string; *p != '\0'; p++)
2667 if (islower((unsigned char)*p)) {
2668 mandoc_vmsg(MANDOCERR_TITLE_CASE,
2669 mdoc->parse, nn->line,
2670 nn->pos + (p - nn->string),
2671 "Dt %s", nn->string);
2672 break;
2676 /* Mandatory second argument: section. */
2678 if (nn != NULL)
2679 nn = nn->next;
2681 if (nn == NULL) {
2682 mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2683 mdoc->parse, n->line, n->pos,
2684 "Dt %s", mdoc->meta.title);
2685 mdoc->meta.vol = mandoc_strdup("LOCAL");
2686 return; /* msec and arch remain NULL. */
2689 mdoc->meta.msec = mandoc_strdup(nn->string);
2691 /* Infer volume title from section number. */
2693 cp = mandoc_a2msec(nn->string);
2694 if (cp == NULL) {
2695 mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2696 nn->line, nn->pos, "Dt ... %s", nn->string);
2697 mdoc->meta.vol = mandoc_strdup(nn->string);
2698 } else
2699 mdoc->meta.vol = mandoc_strdup(cp);
2701 /* Optional third argument: architecture. */
2703 if ((nn = nn->next) == NULL)
2704 return;
2706 for (p = nn->string; *p != '\0'; p++)
2707 *p = tolower((unsigned char)*p);
2708 mdoc->meta.arch = mandoc_strdup(nn->string);
2710 /* Ignore fourth and later arguments. */
2712 if ((nn = nn->next) != NULL)
2713 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2714 nn->line, nn->pos, "Dt ... %s", nn->string);
2717 static void
2718 post_bx(POST_ARGS)
2720 struct roff_node *n, *nch;
2721 const char *macro;
2723 post_delim_nb(mdoc);
2725 n = mdoc->last;
2726 nch = n->child;
2728 if (nch != NULL) {
2729 macro = !strcmp(nch->string, "Open") ? "Ox" :
2730 !strcmp(nch->string, "Net") ? "Nx" :
2731 !strcmp(nch->string, "Free") ? "Fx" :
2732 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2733 if (macro != NULL)
2734 mandoc_msg(MANDOCERR_BX, mdoc->parse,
2735 n->line, n->pos, macro);
2736 mdoc->last = nch;
2737 nch = nch->next;
2738 mdoc->next = ROFF_NEXT_SIBLING;
2739 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2740 mdoc->last->flags |= NODE_NOSRC;
2741 mdoc->next = ROFF_NEXT_SIBLING;
2742 } else
2743 mdoc->next = ROFF_NEXT_CHILD;
2744 roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2745 mdoc->last->flags |= NODE_NOSRC;
2747 if (nch == NULL) {
2748 mdoc->last = n;
2749 return;
2752 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2753 mdoc->last->flags |= NODE_NOSRC;
2754 mdoc->next = ROFF_NEXT_SIBLING;
2755 roff_word_alloc(mdoc, n->line, n->pos, "-");
2756 mdoc->last->flags |= NODE_NOSRC;
2757 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2758 mdoc->last->flags |= NODE_NOSRC;
2759 mdoc->last = n;
2762 * Make `Bx's second argument always start with an uppercase
2763 * letter. Groff checks if it's an "accepted" term, but we just
2764 * uppercase blindly.
2767 *nch->string = (char)toupper((unsigned char)*nch->string);
2770 static void
2771 post_os(POST_ARGS)
2773 #ifndef OSNAME
2774 struct utsname utsname;
2775 static char *defbuf;
2776 #endif
2777 struct roff_node *n;
2779 n = mdoc->last;
2780 n->flags |= NODE_NOPRT;
2782 if (mdoc->meta.os != NULL)
2783 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2784 n->line, n->pos, "Os");
2785 else if (mdoc->flags & MDOC_PBODY)
2786 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2787 n->line, n->pos, "Os");
2789 post_delim(mdoc);
2792 * Set the operating system by way of the `Os' macro.
2793 * The order of precedence is:
2794 * 1. the argument of the `Os' macro, unless empty
2795 * 2. the -Ios=foo command line argument, if provided
2796 * 3. -DOSNAME="\"foo\"", if provided during compilation
2797 * 4. "sysname release" from uname(2)
2800 free(mdoc->meta.os);
2801 mdoc->meta.os = NULL;
2802 deroff(&mdoc->meta.os, n);
2803 if (mdoc->meta.os)
2804 goto out;
2806 if (mdoc->os_s != NULL) {
2807 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2808 goto out;
2811 #ifdef OSNAME
2812 mdoc->meta.os = mandoc_strdup(OSNAME);
2813 #else /*!OSNAME */
2814 if (defbuf == NULL) {
2815 if (uname(&utsname) == -1) {
2816 mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2817 n->line, n->pos, "Os");
2818 defbuf = mandoc_strdup("UNKNOWN");
2819 } else
2820 mandoc_asprintf(&defbuf, "%s %s",
2821 utsname.sysname, utsname.release);
2823 mdoc->meta.os = mandoc_strdup(defbuf);
2824 #endif /*!OSNAME*/
2826 out:
2827 if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2828 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2829 mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2830 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2831 mdoc->meta.os_e = MANDOC_OS_NETBSD;
2835 * This is the earliest point where we can check
2836 * Mdocdate conventions because we don't know
2837 * the operating system earlier.
2840 if (n->child != NULL)
2841 mandoc_vmsg(MANDOCERR_OS_ARG, mdoc->parse,
2842 n->child->line, n->child->pos,
2843 "Os %s (%s)", n->child->string,
2844 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2845 "OpenBSD" : "NetBSD");
2847 while (n->tok != MDOC_Dd)
2848 if ((n = n->prev) == NULL)
2849 return;
2850 if ((n = n->child) == NULL)
2851 return;
2852 if (strncmp(n->string, "$" "Mdocdate", 9)) {
2853 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2854 mandoc_vmsg(MANDOCERR_MDOCDATE_MISSING,
2855 mdoc->parse, n->line, n->pos,
2856 "Dd %s (OpenBSD)", n->string);
2857 } else {
2858 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2859 mandoc_vmsg(MANDOCERR_MDOCDATE,
2860 mdoc->parse, n->line, n->pos,
2861 "Dd %s (NetBSD)", n->string);
2865 enum roff_sec
2866 mdoc_a2sec(const char *p)
2868 int i;
2870 for (i = 0; i < (int)SEC__MAX; i++)
2871 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2872 return (enum roff_sec)i;
2874 return SEC_CUSTOM;
2877 static size_t
2878 macro2len(enum roff_tok macro)
2881 switch (macro) {
2882 case MDOC_Ad:
2883 return 12;
2884 case MDOC_Ao:
2885 return 12;
2886 case MDOC_An:
2887 return 12;
2888 case MDOC_Aq:
2889 return 12;
2890 case MDOC_Ar:
2891 return 12;
2892 case MDOC_Bo:
2893 return 12;
2894 case MDOC_Bq:
2895 return 12;
2896 case MDOC_Cd:
2897 return 12;
2898 case MDOC_Cm:
2899 return 10;
2900 case MDOC_Do:
2901 return 10;
2902 case MDOC_Dq:
2903 return 12;
2904 case MDOC_Dv:
2905 return 12;
2906 case MDOC_Eo:
2907 return 12;
2908 case MDOC_Em:
2909 return 10;
2910 case MDOC_Er:
2911 return 17;
2912 case MDOC_Ev:
2913 return 15;
2914 case MDOC_Fa:
2915 return 12;
2916 case MDOC_Fl:
2917 return 10;
2918 case MDOC_Fo:
2919 return 16;
2920 case MDOC_Fn:
2921 return 16;
2922 case MDOC_Ic:
2923 return 10;
2924 case MDOC_Li:
2925 return 16;
2926 case MDOC_Ms:
2927 return 6;
2928 case MDOC_Nm:
2929 return 10;
2930 case MDOC_No:
2931 return 12;
2932 case MDOC_Oo:
2933 return 10;
2934 case MDOC_Op:
2935 return 14;
2936 case MDOC_Pa:
2937 return 32;
2938 case MDOC_Pf:
2939 return 12;
2940 case MDOC_Po:
2941 return 12;
2942 case MDOC_Pq:
2943 return 12;
2944 case MDOC_Ql:
2945 return 16;
2946 case MDOC_Qo:
2947 return 12;
2948 case MDOC_So:
2949 return 12;
2950 case MDOC_Sq:
2951 return 12;
2952 case MDOC_Sy:
2953 return 6;
2954 case MDOC_Sx:
2955 return 16;
2956 case MDOC_Tn:
2957 return 10;
2958 case MDOC_Va:
2959 return 12;
2960 case MDOC_Vt:
2961 return 12;
2962 case MDOC_Xr:
2963 return 10;
2964 default:
2965 break;
2967 return 0;