]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_validate.c
In fragment identifiers, use ~%d for ordinal suffixes,
[mandoc.git] / mdoc_validate.c
1 /* $Id: mdoc_validate.c,v 1.385 2020/04/18 20:40:10 schwarze Exp $ */
2 /*
3 * Copyright (c) 2010-2020 Ingo Schwarze <schwarze@openbsd.org>
4 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6 *
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.
10 *
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.
18 *
19 * Validation module for mdoc(7) syntax trees used by mandoc(1).
20 */
21 #include "config.h"
22
23 #include <sys/types.h>
24 #ifndef OSNAME
25 #include <sys/utsname.h>
26 #endif
27
28 #include <assert.h>
29 #include <ctype.h>
30 #include <limits.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35
36 #include "mandoc_aux.h"
37 #include "mandoc.h"
38 #include "mandoc_xr.h"
39 #include "roff.h"
40 #include "mdoc.h"
41 #include "libmandoc.h"
42 #include "roff_int.h"
43 #include "libmdoc.h"
44 #include "tag.h"
45
46 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
47
48 #define POST_ARGS struct roff_man *mdoc
49
50 enum check_ineq {
51 CHECK_LT,
52 CHECK_GT,
53 CHECK_EQ
54 };
55
56 typedef void (*v_post)(POST_ARGS);
57
58 static int build_list(struct roff_man *, int);
59 static void check_argv(struct roff_man *,
60 struct roff_node *, struct mdoc_argv *);
61 static void check_args(struct roff_man *, struct roff_node *);
62 static void check_text(struct roff_man *, int, int, char *);
63 static void check_text_em(struct roff_man *, int, int, char *);
64 static void check_toptext(struct roff_man *, int, int, const char *);
65 static int child_an(const struct roff_node *);
66 static size_t macro2len(enum roff_tok);
67 static void rewrite_macro2len(struct roff_man *, char **);
68 static int similar(const char *, const char *);
69
70 static void post_abort(POST_ARGS) __attribute__((__noreturn__));
71 static void post_an(POST_ARGS);
72 static void post_an_norm(POST_ARGS);
73 static void post_at(POST_ARGS);
74 static void post_bd(POST_ARGS);
75 static void post_bf(POST_ARGS);
76 static void post_bk(POST_ARGS);
77 static void post_bl(POST_ARGS);
78 static void post_bl_block(POST_ARGS);
79 static void post_bl_head(POST_ARGS);
80 static void post_bl_norm(POST_ARGS);
81 static void post_bx(POST_ARGS);
82 static void post_defaults(POST_ARGS);
83 static void post_display(POST_ARGS);
84 static void post_dd(POST_ARGS);
85 static void post_delim(POST_ARGS);
86 static void post_delim_nb(POST_ARGS);
87 static void post_dt(POST_ARGS);
88 static void post_em(POST_ARGS);
89 static void post_en(POST_ARGS);
90 static void post_er(POST_ARGS);
91 static void post_es(POST_ARGS);
92 static void post_eoln(POST_ARGS);
93 static void post_ex(POST_ARGS);
94 static void post_fa(POST_ARGS);
95 static void post_fn(POST_ARGS);
96 static void post_fname(POST_ARGS);
97 static void post_fo(POST_ARGS);
98 static void post_hyph(POST_ARGS);
99 static void post_it(POST_ARGS);
100 static void post_lb(POST_ARGS);
101 static void post_nd(POST_ARGS);
102 static void post_nm(POST_ARGS);
103 static void post_ns(POST_ARGS);
104 static void post_obsolete(POST_ARGS);
105 static void post_os(POST_ARGS);
106 static void post_par(POST_ARGS);
107 static void post_prevpar(POST_ARGS);
108 static void post_root(POST_ARGS);
109 static void post_rs(POST_ARGS);
110 static void post_rv(POST_ARGS);
111 static void post_section(POST_ARGS);
112 static void post_sh(POST_ARGS);
113 static void post_sh_head(POST_ARGS);
114 static void post_sh_name(POST_ARGS);
115 static void post_sh_see_also(POST_ARGS);
116 static void post_sh_authors(POST_ARGS);
117 static void post_sm(POST_ARGS);
118 static void post_st(POST_ARGS);
119 static void post_std(POST_ARGS);
120 static void post_sx(POST_ARGS);
121 static void post_tag(POST_ARGS);
122 static void post_tg(POST_ARGS);
123 static void post_useless(POST_ARGS);
124 static void post_xr(POST_ARGS);
125 static void post_xx(POST_ARGS);
126
127 static const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
128 post_dd, /* Dd */
129 post_dt, /* Dt */
130 post_os, /* Os */
131 post_sh, /* Sh */
132 post_section, /* Ss */
133 post_par, /* Pp */
134 post_display, /* D1 */
135 post_display, /* Dl */
136 post_display, /* Bd */
137 NULL, /* Ed */
138 post_bl, /* Bl */
139 NULL, /* El */
140 post_it, /* It */
141 post_delim_nb, /* Ad */
142 post_an, /* An */
143 NULL, /* Ap */
144 post_defaults, /* Ar */
145 NULL, /* Cd */
146 post_tag, /* Cm */
147 post_tag, /* Dv */
148 post_er, /* Er */
149 post_tag, /* Ev */
150 post_ex, /* Ex */
151 post_fa, /* Fa */
152 NULL, /* Fd */
153 post_tag, /* Fl */
154 post_fn, /* Fn */
155 post_delim_nb, /* Ft */
156 post_tag, /* Ic */
157 post_delim_nb, /* In */
158 post_tag, /* Li */
159 post_nd, /* Nd */
160 post_nm, /* Nm */
161 post_delim_nb, /* Op */
162 post_abort, /* Ot */
163 post_defaults, /* Pa */
164 post_rv, /* Rv */
165 post_st, /* St */
166 post_delim_nb, /* Va */
167 post_delim_nb, /* Vt */
168 post_xr, /* Xr */
169 NULL, /* %A */
170 post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */
171 NULL, /* %D */
172 NULL, /* %I */
173 NULL, /* %J */
174 post_hyph, /* %N */
175 post_hyph, /* %O */
176 NULL, /* %P */
177 post_hyph, /* %R */
178 post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */
179 NULL, /* %V */
180 NULL, /* Ac */
181 NULL, /* Ao */
182 post_delim_nb, /* Aq */
183 post_at, /* At */
184 NULL, /* Bc */
185 post_bf, /* Bf */
186 NULL, /* Bo */
187 NULL, /* Bq */
188 post_xx, /* Bsx */
189 post_bx, /* Bx */
190 post_obsolete, /* Db */
191 NULL, /* Dc */
192 NULL, /* Do */
193 NULL, /* Dq */
194 NULL, /* Ec */
195 NULL, /* Ef */
196 post_em, /* Em */
197 NULL, /* Eo */
198 post_xx, /* Fx */
199 post_tag, /* Ms */
200 post_tag, /* No */
201 post_ns, /* Ns */
202 post_xx, /* Nx */
203 post_xx, /* Ox */
204 NULL, /* Pc */
205 NULL, /* Pf */
206 NULL, /* Po */
207 post_delim_nb, /* Pq */
208 NULL, /* Qc */
209 post_delim_nb, /* Ql */
210 NULL, /* Qo */
211 post_delim_nb, /* Qq */
212 NULL, /* Re */
213 post_rs, /* Rs */
214 NULL, /* Sc */
215 NULL, /* So */
216 post_delim_nb, /* Sq */
217 post_sm, /* Sm */
218 post_sx, /* Sx */
219 post_em, /* Sy */
220 post_useless, /* Tn */
221 post_xx, /* Ux */
222 NULL, /* Xc */
223 NULL, /* Xo */
224 post_fo, /* Fo */
225 NULL, /* Fc */
226 NULL, /* Oo */
227 NULL, /* Oc */
228 post_bk, /* Bk */
229 NULL, /* Ek */
230 post_eoln, /* Bt */
231 post_obsolete, /* Hf */
232 post_obsolete, /* Fr */
233 post_eoln, /* Ud */
234 post_lb, /* Lb */
235 post_abort, /* Lp */
236 post_delim_nb, /* Lk */
237 post_defaults, /* Mt */
238 post_delim_nb, /* Brq */
239 NULL, /* Bro */
240 NULL, /* Brc */
241 NULL, /* %C */
242 post_es, /* Es */
243 post_en, /* En */
244 post_xx, /* Dx */
245 NULL, /* %Q */
246 NULL, /* %U */
247 NULL, /* Ta */
248 post_tg, /* Tg */
249 };
250
251 #define RSORD_MAX 14 /* Number of `Rs' blocks. */
252
253 static const enum roff_tok rsord[RSORD_MAX] = {
254 MDOC__A,
255 MDOC__T,
256 MDOC__B,
257 MDOC__I,
258 MDOC__J,
259 MDOC__R,
260 MDOC__N,
261 MDOC__V,
262 MDOC__U,
263 MDOC__P,
264 MDOC__Q,
265 MDOC__C,
266 MDOC__D,
267 MDOC__O
268 };
269
270 static const char * const secnames[SEC__MAX] = {
271 NULL,
272 "NAME",
273 "LIBRARY",
274 "SYNOPSIS",
275 "DESCRIPTION",
276 "CONTEXT",
277 "IMPLEMENTATION NOTES",
278 "RETURN VALUES",
279 "ENVIRONMENT",
280 "FILES",
281 "EXIT STATUS",
282 "EXAMPLES",
283 "DIAGNOSTICS",
284 "COMPATIBILITY",
285 "ERRORS",
286 "SEE ALSO",
287 "STANDARDS",
288 "HISTORY",
289 "AUTHORS",
290 "CAVEATS",
291 "BUGS",
292 "SECURITY CONSIDERATIONS",
293 NULL
294 };
295
296 static int fn_prio = TAG_STRONG;
297
298
299 /* Validate the subtree rooted at mdoc->last. */
300 void
301 mdoc_validate(struct roff_man *mdoc)
302 {
303 struct roff_node *n, *np;
304 const v_post *p;
305
306 /*
307 * Translate obsolete macros to modern macros first
308 * such that later code does not need to look
309 * for the obsolete versions.
310 */
311
312 n = mdoc->last;
313 switch (n->tok) {
314 case MDOC_Lp:
315 n->tok = MDOC_Pp;
316 break;
317 case MDOC_Ot:
318 post_obsolete(mdoc);
319 n->tok = MDOC_Ft;
320 break;
321 default:
322 break;
323 }
324
325 /*
326 * Iterate over all children, recursing into each one
327 * in turn, depth-first.
328 */
329
330 mdoc->last = mdoc->last->child;
331 while (mdoc->last != NULL) {
332 mdoc_validate(mdoc);
333 if (mdoc->last == n)
334 mdoc->last = mdoc->last->child;
335 else
336 mdoc->last = mdoc->last->next;
337 }
338
339 /* Finally validate the macro itself. */
340
341 mdoc->last = n;
342 mdoc->next = ROFF_NEXT_SIBLING;
343 switch (n->type) {
344 case ROFFT_TEXT:
345 np = n->parent;
346 if (n->sec != SEC_SYNOPSIS ||
347 (np->tok != MDOC_Cd && np->tok != MDOC_Fd))
348 check_text(mdoc, n->line, n->pos, n->string);
349 if ((n->flags & NODE_NOFILL) == 0 &&
350 (np->tok != MDOC_It || np->type != ROFFT_HEAD ||
351 np->parent->parent->norm->Bl.type != LIST_diag))
352 check_text_em(mdoc, n->line, n->pos, n->string);
353 if (np->tok == MDOC_It || (np->type == ROFFT_BODY &&
354 (np->tok == MDOC_Sh || np->tok == MDOC_Ss)))
355 check_toptext(mdoc, n->line, n->pos, n->string);
356 break;
357 case ROFFT_COMMENT:
358 case ROFFT_EQN:
359 case ROFFT_TBL:
360 break;
361 case ROFFT_ROOT:
362 post_root(mdoc);
363 break;
364 default:
365 check_args(mdoc, mdoc->last);
366
367 /*
368 * Closing delimiters are not special at the
369 * beginning of a block, opening delimiters
370 * are not special at the end.
371 */
372
373 if (n->child != NULL)
374 n->child->flags &= ~NODE_DELIMC;
375 if (n->last != NULL)
376 n->last->flags &= ~NODE_DELIMO;
377
378 /* Call the macro's postprocessor. */
379
380 if (n->tok < ROFF_MAX) {
381 roff_validate(mdoc);
382 break;
383 }
384
385 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
386 p = mdoc_valids + (n->tok - MDOC_Dd);
387 if (*p)
388 (*p)(mdoc);
389 if (mdoc->last == n)
390 mdoc_state(mdoc, n);
391 break;
392 }
393 }
394
395 static void
396 check_args(struct roff_man *mdoc, struct roff_node *n)
397 {
398 int i;
399
400 if (NULL == n->args)
401 return;
402
403 assert(n->args->argc);
404 for (i = 0; i < (int)n->args->argc; i++)
405 check_argv(mdoc, n, &n->args->argv[i]);
406 }
407
408 static void
409 check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
410 {
411 int i;
412
413 for (i = 0; i < (int)v->sz; i++)
414 check_text(mdoc, v->line, v->pos, v->value[i]);
415 }
416
417 static void
418 check_text(struct roff_man *mdoc, int ln, int pos, char *p)
419 {
420 char *cp;
421
422 if (mdoc->last->flags & NODE_NOFILL)
423 return;
424
425 for (cp = p; NULL != (p = strchr(p, '\t')); p++)
426 mandoc_msg(MANDOCERR_FI_TAB, ln, pos + (int)(p - cp), NULL);
427 }
428
429 static void
430 check_text_em(struct roff_man *mdoc, int ln, int pos, char *p)
431 {
432 const struct roff_node *np, *nn;
433 char *cp;
434
435 np = mdoc->last->prev;
436 nn = mdoc->last->next;
437
438 /* Look for em-dashes wrongly encoded as "--". */
439
440 for (cp = p; *cp != '\0'; cp++) {
441 if (cp[0] != '-' || cp[1] != '-')
442 continue;
443 cp++;
444
445 /* Skip input sequences of more than two '-'. */
446
447 if (cp[1] == '-') {
448 while (cp[1] == '-')
449 cp++;
450 continue;
451 }
452
453 /* Skip "--" directly attached to something else. */
454
455 if ((cp - p > 1 && cp[-2] != ' ') ||
456 (cp[1] != '\0' && cp[1] != ' '))
457 continue;
458
459 /* Require a letter right before or right afterwards. */
460
461 if ((cp - p > 2 ?
462 isalpha((unsigned char)cp[-3]) :
463 np != NULL &&
464 np->type == ROFFT_TEXT &&
465 *np->string != '\0' &&
466 isalpha((unsigned char)np->string[
467 strlen(np->string) - 1])) ||
468 (cp[1] != '\0' && cp[2] != '\0' ?
469 isalpha((unsigned char)cp[2]) :
470 nn != NULL &&
471 nn->type == ROFFT_TEXT &&
472 isalpha((unsigned char)*nn->string))) {
473 mandoc_msg(MANDOCERR_DASHDASH,
474 ln, pos + (int)(cp - p) - 1, NULL);
475 break;
476 }
477 }
478 }
479
480 static void
481 check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
482 {
483 const char *cp, *cpr;
484
485 if (*p == '\0')
486 return;
487
488 if ((cp = strstr(p, "OpenBSD")) != NULL)
489 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Ox");
490 if ((cp = strstr(p, "NetBSD")) != NULL)
491 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Nx");
492 if ((cp = strstr(p, "FreeBSD")) != NULL)
493 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Fx");
494 if ((cp = strstr(p, "DragonFly")) != NULL)
495 mandoc_msg(MANDOCERR_BX, ln, pos + (int)(cp - p), "Dx");
496
497 cp = p;
498 while ((cp = strstr(cp + 1, "()")) != NULL) {
499 for (cpr = cp - 1; cpr >= p; cpr--)
500 if (*cpr != '_' && !isalnum((unsigned char)*cpr))
501 break;
502 if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
503 cpr++;
504 mandoc_msg(MANDOCERR_FUNC, ln, pos + (int)(cpr - p),
505 "%.*s()", (int)(cp - cpr), cpr);
506 }
507 }
508 }
509
510 static void
511 post_abort(POST_ARGS)
512 {
513 abort();
514 }
515
516 static void
517 post_delim(POST_ARGS)
518 {
519 const struct roff_node *nch;
520 const char *lc;
521 enum mdelim delim;
522 enum roff_tok tok;
523
524 tok = mdoc->last->tok;
525 nch = mdoc->last->last;
526 if (nch == NULL || nch->type != ROFFT_TEXT)
527 return;
528 lc = strchr(nch->string, '\0') - 1;
529 if (lc < nch->string)
530 return;
531 delim = mdoc_isdelim(lc);
532 if (delim == DELIM_NONE || delim == DELIM_OPEN)
533 return;
534 if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
535 tok == MDOC_Ss || tok == MDOC_Fo))
536 return;
537
538 mandoc_msg(MANDOCERR_DELIM, nch->line,
539 nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
540 nch == mdoc->last->child ? "" : " ...", nch->string);
541 }
542
543 static void
544 post_delim_nb(POST_ARGS)
545 {
546 const struct roff_node *nch;
547 const char *lc, *cp;
548 int nw;
549 enum mdelim delim;
550 enum roff_tok tok;
551
552 /*
553 * Find candidates: at least two bytes,
554 * the last one a closing or middle delimiter.
555 */
556
557 tok = mdoc->last->tok;
558 nch = mdoc->last->last;
559 if (nch == NULL || nch->type != ROFFT_TEXT)
560 return;
561 lc = strchr(nch->string, '\0') - 1;
562 if (lc <= nch->string)
563 return;
564 delim = mdoc_isdelim(lc);
565 if (delim == DELIM_NONE || delim == DELIM_OPEN)
566 return;
567
568 /*
569 * Reduce false positives by allowing various cases.
570 */
571
572 /* Escaped delimiters. */
573 if (lc > nch->string + 1 && lc[-2] == '\\' &&
574 (lc[-1] == '&' || lc[-1] == 'e'))
575 return;
576
577 /* Specific byte sequences. */
578 switch (*lc) {
579 case ')':
580 for (cp = lc; cp >= nch->string; cp--)
581 if (*cp == '(')
582 return;
583 break;
584 case '.':
585 if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
586 return;
587 if (lc[-1] == '.')
588 return;
589 break;
590 case ';':
591 if (tok == MDOC_Vt)
592 return;
593 break;
594 case '?':
595 if (lc[-1] == '?')
596 return;
597 break;
598 case ']':
599 for (cp = lc; cp >= nch->string; cp--)
600 if (*cp == '[')
601 return;
602 break;
603 case '|':
604 if (lc == nch->string + 1 && lc[-1] == '|')
605 return;
606 default:
607 break;
608 }
609
610 /* Exactly two non-alphanumeric bytes. */
611 if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
612 return;
613
614 /* At least three alphabetic words with a sentence ending. */
615 if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
616 tok == MDOC_Li || tok == MDOC_Pq || tok == MDOC_Sy)) {
617 nw = 0;
618 for (cp = lc - 1; cp >= nch->string; cp--) {
619 if (*cp == ' ') {
620 nw++;
621 if (cp > nch->string && cp[-1] == ',')
622 cp--;
623 } else if (isalpha((unsigned int)*cp)) {
624 if (nw > 1)
625 return;
626 } else
627 break;
628 }
629 }
630
631 mandoc_msg(MANDOCERR_DELIM_NB, nch->line,
632 nch->pos + (int)(lc - nch->string), "%s%s %s", roff_name[tok],
633 nch == mdoc->last->child ? "" : " ...", nch->string);
634 }
635
636 static void
637 post_bl_norm(POST_ARGS)
638 {
639 struct roff_node *n;
640 struct mdoc_argv *argv, *wa;
641 int i;
642 enum mdocargt mdoclt;
643 enum mdoc_list lt;
644
645 n = mdoc->last->parent;
646 n->norm->Bl.type = LIST__NONE;
647
648 /*
649 * First figure out which kind of list to use: bind ourselves to
650 * the first mentioned list type and warn about any remaining
651 * ones. If we find no list type, we default to LIST_item.
652 */
653
654 wa = (n->args == NULL) ? NULL : n->args->argv;
655 mdoclt = MDOC_ARG_MAX;
656 for (i = 0; n->args && i < (int)n->args->argc; i++) {
657 argv = n->args->argv + i;
658 lt = LIST__NONE;
659 switch (argv->arg) {
660 /* Set list types. */
661 case MDOC_Bullet:
662 lt = LIST_bullet;
663 break;
664 case MDOC_Dash:
665 lt = LIST_dash;
666 break;
667 case MDOC_Enum:
668 lt = LIST_enum;
669 break;
670 case MDOC_Hyphen:
671 lt = LIST_hyphen;
672 break;
673 case MDOC_Item:
674 lt = LIST_item;
675 break;
676 case MDOC_Tag:
677 lt = LIST_tag;
678 break;
679 case MDOC_Diag:
680 lt = LIST_diag;
681 break;
682 case MDOC_Hang:
683 lt = LIST_hang;
684 break;
685 case MDOC_Ohang:
686 lt = LIST_ohang;
687 break;
688 case MDOC_Inset:
689 lt = LIST_inset;
690 break;
691 case MDOC_Column:
692 lt = LIST_column;
693 break;
694 /* Set list arguments. */
695 case MDOC_Compact:
696 if (n->norm->Bl.comp)
697 mandoc_msg(MANDOCERR_ARG_REP,
698 argv->line, argv->pos, "Bl -compact");
699 n->norm->Bl.comp = 1;
700 break;
701 case MDOC_Width:
702 wa = argv;
703 if (0 == argv->sz) {
704 mandoc_msg(MANDOCERR_ARG_EMPTY,
705 argv->line, argv->pos, "Bl -width");
706 n->norm->Bl.width = "0n";
707 break;
708 }
709 if (NULL != n->norm->Bl.width)
710 mandoc_msg(MANDOCERR_ARG_REP,
711 argv->line, argv->pos,
712 "Bl -width %s", argv->value[0]);
713 rewrite_macro2len(mdoc, argv->value);
714 n->norm->Bl.width = argv->value[0];
715 break;
716 case MDOC_Offset:
717 if (0 == argv->sz) {
718 mandoc_msg(MANDOCERR_ARG_EMPTY,
719 argv->line, argv->pos, "Bl -offset");
720 break;
721 }
722 if (NULL != n->norm->Bl.offs)
723 mandoc_msg(MANDOCERR_ARG_REP,
724 argv->line, argv->pos,
725 "Bl -offset %s", argv->value[0]);
726 rewrite_macro2len(mdoc, argv->value);
727 n->norm->Bl.offs = argv->value[0];
728 break;
729 default:
730 continue;
731 }
732 if (LIST__NONE == lt)
733 continue;
734 mdoclt = argv->arg;
735
736 /* Check: multiple list types. */
737
738 if (LIST__NONE != n->norm->Bl.type) {
739 mandoc_msg(MANDOCERR_BL_REP, n->line, n->pos,
740 "Bl -%s", mdoc_argnames[argv->arg]);
741 continue;
742 }
743
744 /* The list type should come first. */
745
746 if (n->norm->Bl.width ||
747 n->norm->Bl.offs ||
748 n->norm->Bl.comp)
749 mandoc_msg(MANDOCERR_BL_LATETYPE,
750 n->line, n->pos, "Bl -%s",
751 mdoc_argnames[n->args->argv[0].arg]);
752
753 n->norm->Bl.type = lt;
754 if (LIST_column == lt) {
755 n->norm->Bl.ncols = argv->sz;
756 n->norm->Bl.cols = (void *)argv->value;
757 }
758 }
759
760 /* Allow lists to default to LIST_item. */
761
762 if (LIST__NONE == n->norm->Bl.type) {
763 mandoc_msg(MANDOCERR_BL_NOTYPE, n->line, n->pos, "Bl");
764 n->norm->Bl.type = LIST_item;
765 mdoclt = MDOC_Item;
766 }
767
768 /*
769 * Validate the width field. Some list types don't need width
770 * types and should be warned about them. Others should have it
771 * and must also be warned. Yet others have a default and need
772 * no warning.
773 */
774
775 switch (n->norm->Bl.type) {
776 case LIST_tag:
777 if (n->norm->Bl.width == NULL)
778 mandoc_msg(MANDOCERR_BL_NOWIDTH,
779 n->line, n->pos, "Bl -tag");
780 break;
781 case LIST_column:
782 case LIST_diag:
783 case LIST_ohang:
784 case LIST_inset:
785 case LIST_item:
786 if (n->norm->Bl.width != NULL)
787 mandoc_msg(MANDOCERR_BL_SKIPW, wa->line, wa->pos,
788 "Bl -%s", mdoc_argnames[mdoclt]);
789 n->norm->Bl.width = NULL;
790 break;
791 case LIST_bullet:
792 case LIST_dash:
793 case LIST_hyphen:
794 if (n->norm->Bl.width == NULL)
795 n->norm->Bl.width = "2n";
796 break;
797 case LIST_enum:
798 if (n->norm->Bl.width == NULL)
799 n->norm->Bl.width = "3n";
800 break;
801 default:
802 break;
803 }
804 }
805
806 static void
807 post_bd(POST_ARGS)
808 {
809 struct roff_node *n;
810 struct mdoc_argv *argv;
811 int i;
812 enum mdoc_disp dt;
813
814 n = mdoc->last;
815 for (i = 0; n->args && i < (int)n->args->argc; i++) {
816 argv = n->args->argv + i;
817 dt = DISP__NONE;
818
819 switch (argv->arg) {
820 case MDOC_Centred:
821 dt = DISP_centered;
822 break;
823 case MDOC_Ragged:
824 dt = DISP_ragged;
825 break;
826 case MDOC_Unfilled:
827 dt = DISP_unfilled;
828 break;
829 case MDOC_Filled:
830 dt = DISP_filled;
831 break;
832 case MDOC_Literal:
833 dt = DISP_literal;
834 break;
835 case MDOC_File:
836 mandoc_msg(MANDOCERR_BD_FILE, n->line, n->pos, NULL);
837 break;
838 case MDOC_Offset:
839 if (0 == argv->sz) {
840 mandoc_msg(MANDOCERR_ARG_EMPTY,
841 argv->line, argv->pos, "Bd -offset");
842 break;
843 }
844 if (NULL != n->norm->Bd.offs)
845 mandoc_msg(MANDOCERR_ARG_REP,
846 argv->line, argv->pos,
847 "Bd -offset %s", argv->value[0]);
848 rewrite_macro2len(mdoc, argv->value);
849 n->norm->Bd.offs = argv->value[0];
850 break;
851 case MDOC_Compact:
852 if (n->norm->Bd.comp)
853 mandoc_msg(MANDOCERR_ARG_REP,
854 argv->line, argv->pos, "Bd -compact");
855 n->norm->Bd.comp = 1;
856 break;
857 default:
858 abort();
859 }
860 if (DISP__NONE == dt)
861 continue;
862
863 if (DISP__NONE == n->norm->Bd.type)
864 n->norm->Bd.type = dt;
865 else
866 mandoc_msg(MANDOCERR_BD_REP, n->line, n->pos,
867 "Bd -%s", mdoc_argnames[argv->arg]);
868 }
869
870 if (DISP__NONE == n->norm->Bd.type) {
871 mandoc_msg(MANDOCERR_BD_NOTYPE, n->line, n->pos, "Bd");
872 n->norm->Bd.type = DISP_ragged;
873 }
874 }
875
876 /*
877 * Stand-alone line macros.
878 */
879
880 static void
881 post_an_norm(POST_ARGS)
882 {
883 struct roff_node *n;
884 struct mdoc_argv *argv;
885 size_t i;
886
887 n = mdoc->last;
888 if (n->args == NULL)
889 return;
890
891 for (i = 1; i < n->args->argc; i++) {
892 argv = n->args->argv + i;
893 mandoc_msg(MANDOCERR_AN_REP, argv->line, argv->pos,
894 "An -%s", mdoc_argnames[argv->arg]);
895 }
896
897 argv = n->args->argv;
898 if (argv->arg == MDOC_Split)
899 n->norm->An.auth = AUTH_split;
900 else if (argv->arg == MDOC_Nosplit)
901 n->norm->An.auth = AUTH_nosplit;
902 else
903 abort();
904 }
905
906 static void
907 post_eoln(POST_ARGS)
908 {
909 struct roff_node *n;
910
911 post_useless(mdoc);
912 n = mdoc->last;
913 if (n->child != NULL)
914 mandoc_msg(MANDOCERR_ARG_SKIP, n->line,
915 n->pos, "%s %s", roff_name[n->tok], n->child->string);
916
917 while (n->child != NULL)
918 roff_node_delete(mdoc, n->child);
919
920 roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
921 "is currently in beta test." : "currently under development.");
922 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
923 mdoc->last = n;
924 }
925
926 static int
927 build_list(struct roff_man *mdoc, int tok)
928 {
929 struct roff_node *n;
930 int ic;
931
932 n = mdoc->last->next;
933 for (ic = 1;; ic++) {
934 roff_elem_alloc(mdoc, n->line, n->pos, tok);
935 mdoc->last->flags |= NODE_NOSRC;
936 roff_node_relink(mdoc, n);
937 n = mdoc->last = mdoc->last->parent;
938 mdoc->next = ROFF_NEXT_SIBLING;
939 if (n->next == NULL)
940 return ic;
941 if (ic > 1 || n->next->next != NULL) {
942 roff_word_alloc(mdoc, n->line, n->pos, ",");
943 mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
944 }
945 n = mdoc->last->next;
946 if (n->next == NULL) {
947 roff_word_alloc(mdoc, n->line, n->pos, "and");
948 mdoc->last->flags |= NODE_NOSRC;
949 }
950 }
951 }
952
953 static void
954 post_ex(POST_ARGS)
955 {
956 struct roff_node *n;
957 int ic;
958
959 post_std(mdoc);
960
961 n = mdoc->last;
962 mdoc->next = ROFF_NEXT_CHILD;
963 roff_word_alloc(mdoc, n->line, n->pos, "The");
964 mdoc->last->flags |= NODE_NOSRC;
965
966 if (mdoc->last->next != NULL)
967 ic = build_list(mdoc, MDOC_Nm);
968 else if (mdoc->meta.name != NULL) {
969 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
970 mdoc->last->flags |= NODE_NOSRC;
971 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
972 mdoc->last->flags |= NODE_NOSRC;
973 mdoc->last = mdoc->last->parent;
974 mdoc->next = ROFF_NEXT_SIBLING;
975 ic = 1;
976 } else {
977 mandoc_msg(MANDOCERR_EX_NONAME, n->line, n->pos, "Ex");
978 ic = 0;
979 }
980
981 roff_word_alloc(mdoc, n->line, n->pos,
982 ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
983 mdoc->last->flags |= NODE_NOSRC;
984 roff_word_alloc(mdoc, n->line, n->pos,
985 "on success, and\\~>0 if an error occurs.");
986 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
987 mdoc->last = n;
988 }
989
990 static void
991 post_lb(POST_ARGS)
992 {
993 struct roff_node *n;
994 const char *p;
995
996 post_delim_nb(mdoc);
997
998 n = mdoc->last;
999 assert(n->child->type == ROFFT_TEXT);
1000 mdoc->next = ROFF_NEXT_CHILD;
1001
1002 if ((p = mdoc_a2lib(n->child->string)) != NULL) {
1003 n->child->flags |= NODE_NOPRT;
1004 roff_word_alloc(mdoc, n->line, n->pos, p);
1005 mdoc->last->flags = NODE_NOSRC;
1006 mdoc->last = n;
1007 return;
1008 }
1009
1010 mandoc_msg(MANDOCERR_LB_BAD, n->child->line,
1011 n->child->pos, "Lb %s", n->child->string);
1012
1013 roff_word_alloc(mdoc, n->line, n->pos, "library");
1014 mdoc->last->flags = NODE_NOSRC;
1015 roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
1016 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
1017 mdoc->last = mdoc->last->next;
1018 roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
1019 mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
1020 mdoc->last = n;
1021 }
1022
1023 static void
1024 post_rv(POST_ARGS)
1025 {
1026 struct roff_node *n;
1027 int ic;
1028
1029 post_std(mdoc);
1030
1031 n = mdoc->last;
1032 mdoc->next = ROFF_NEXT_CHILD;
1033 if (n->child != NULL) {
1034 roff_word_alloc(mdoc, n->line, n->pos, "The");
1035 mdoc->last->flags |= NODE_NOSRC;
1036 ic = build_list(mdoc, MDOC_Fn);
1037 roff_word_alloc(mdoc, n->line, n->pos,
1038 ic > 1 ? "functions return" : "function returns");
1039 mdoc->last->flags |= NODE_NOSRC;
1040 roff_word_alloc(mdoc, n->line, n->pos,
1041 "the value\\~0 if successful;");
1042 } else
1043 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
1044 "completion, the value\\~0 is returned;");
1045 mdoc->last->flags |= NODE_NOSRC;
1046
1047 roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
1048 "the value\\~\\-1 is returned and the global variable");
1049 mdoc->last->flags |= NODE_NOSRC;
1050 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
1051 mdoc->last->flags |= NODE_NOSRC;
1052 roff_word_alloc(mdoc, n->line, n->pos, "errno");
1053 mdoc->last->flags |= NODE_NOSRC;
1054 mdoc->last = mdoc->last->parent;
1055 mdoc->next = ROFF_NEXT_SIBLING;
1056 roff_word_alloc(mdoc, n->line, n->pos,
1057 "is set to indicate the error.");
1058 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
1059 mdoc->last = n;
1060 }
1061
1062 static void
1063 post_std(POST_ARGS)
1064 {
1065 struct roff_node *n;
1066
1067 post_delim(mdoc);
1068
1069 n = mdoc->last;
1070 if (n->args && n->args->argc == 1)
1071 if (n->args->argv[0].arg == MDOC_Std)
1072 return;
1073
1074 mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos,
1075 "%s", roff_name[n->tok]);
1076 }
1077
1078 static void
1079 post_st(POST_ARGS)
1080 {
1081 struct roff_node *n, *nch;
1082 const char *p;
1083
1084 n = mdoc->last;
1085 nch = n->child;
1086 assert(nch->type == ROFFT_TEXT);
1087
1088 if ((p = mdoc_a2st(nch->string)) == NULL) {
1089 mandoc_msg(MANDOCERR_ST_BAD,
1090 nch->line, nch->pos, "St %s", nch->string);
1091 roff_node_delete(mdoc, n);
1092 return;
1093 }
1094
1095 nch->flags |= NODE_NOPRT;
1096 mdoc->next = ROFF_NEXT_CHILD;
1097 roff_word_alloc(mdoc, nch->line, nch->pos, p);
1098 mdoc->last->flags |= NODE_NOSRC;
1099 mdoc->last= n;
1100 }
1101
1102 static void
1103 post_tg(POST_ARGS)
1104 {
1105 struct roff_node *n; /* The .Tg node. */
1106 struct roff_node *nch; /* The first child of the .Tg node. */
1107 struct roff_node *nn; /* The next node after the .Tg node. */
1108 struct roff_node *np; /* The parent of the next node. */
1109 struct roff_node *nt; /* The TEXT node containing the tag. */
1110 size_t len; /* The number of bytes in the tag. */
1111
1112 /* Find the next node. */
1113 n = mdoc->last;
1114 for (nn = n; nn != NULL; nn = nn->parent) {
1115 if (nn->next != NULL) {
1116 nn = nn->next;
1117 break;
1118 }
1119 }
1120
1121 /* Find the tag. */
1122 nt = nch = n->child;
1123 if (nch == NULL && nn != NULL && nn->child != NULL &&
1124 nn->child->type == ROFFT_TEXT)
1125 nt = nn->child;
1126
1127 /* Validate the tag. */
1128 if (nt == NULL || *nt->string == '\0')
1129 mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
1130 if (nt == NULL) {
1131 roff_node_delete(mdoc, n);
1132 return;
1133 }
1134 len = strcspn(nt->string, " \t\\");
1135 if (nt->string[len] != '\0')
1136 mandoc_msg(MANDOCERR_TG_SPC, nt->line,
1137 nt->pos + len, "Tg %s", nt->string);
1138
1139 /* Keep only the first argument. */
1140 if (nch != NULL && nch->next != NULL) {
1141 mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
1142 nch->next->pos, "Tg ... %s", nch->next->string);
1143 while (nch->next != NULL)
1144 roff_node_delete(mdoc, nch->next);
1145 }
1146
1147 /* Drop the macro if the first argument is invalid. */
1148 if (len == 0 || nt->string[len] != '\0') {
1149 roff_node_delete(mdoc, n);
1150 return;
1151 }
1152
1153 /* By default, tag the .Tg node itself. */
1154 if (nn == NULL || nn->flags & NODE_ID)
1155 nn = n;
1156
1157 /* Explicit tagging of specific macros. */
1158 switch (nn->tok) {
1159 case MDOC_Sh:
1160 case MDOC_Ss:
1161 case MDOC_Fo:
1162 nn = nn->head->child == NULL ? n : nn->head;
1163 break;
1164 case MDOC_It:
1165 np = nn->parent;
1166 while (np->tok != MDOC_Bl)
1167 np = np->parent;
1168 switch (np->norm->Bl.type) {
1169 case LIST_column:
1170 break;
1171 case LIST_diag:
1172 case LIST_hang:
1173 case LIST_inset:
1174 case LIST_ohang:
1175 case LIST_tag:
1176 nn = nn->head;
1177 break;
1178 case LIST_bullet:
1179 case LIST_dash:
1180 case LIST_enum:
1181 case LIST_hyphen:
1182 case LIST_item:
1183 nn = nn->body->child == NULL ? n : nn->body;
1184 break;
1185 default:
1186 abort();
1187 }
1188 break;
1189 case MDOC_Bd:
1190 case MDOC_Bl:
1191 case MDOC_D1:
1192 case MDOC_Dl:
1193 nn = nn->body->child == NULL ? n : nn->body;
1194 break;
1195 case MDOC_Pp:
1196 break;
1197 case MDOC_Cm:
1198 case MDOC_Dv:
1199 case MDOC_Em:
1200 case MDOC_Er:
1201 case MDOC_Ev:
1202 case MDOC_Fl:
1203 case MDOC_Fn:
1204 case MDOC_Ic:
1205 case MDOC_Li:
1206 case MDOC_Ms:
1207 case MDOC_No:
1208 case MDOC_Sy:
1209 if (nn->child == NULL)
1210 nn = n;
1211 break;
1212 default:
1213 nn = n;
1214 break;
1215 }
1216 tag_put(nt->string, TAG_MANUAL, nn);
1217 if (nn != n)
1218 n->flags |= NODE_NOPRT;
1219 }
1220
1221 static void
1222 post_obsolete(POST_ARGS)
1223 {
1224 struct roff_node *n;
1225
1226 n = mdoc->last;
1227 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1228 mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
1229 "%s", roff_name[n->tok]);
1230 }
1231
1232 static void
1233 post_useless(POST_ARGS)
1234 {
1235 struct roff_node *n;
1236
1237 n = mdoc->last;
1238 mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
1239 "%s", roff_name[n->tok]);
1240 }
1241
1242 /*
1243 * Block macros.
1244 */
1245
1246 static void
1247 post_bf(POST_ARGS)
1248 {
1249 struct roff_node *np, *nch;
1250
1251 /*
1252 * Unlike other data pointers, these are "housed" by the HEAD
1253 * element, which contains the goods.
1254 */
1255
1256 np = mdoc->last;
1257 if (np->type != ROFFT_HEAD)
1258 return;
1259
1260 assert(np->parent->type == ROFFT_BLOCK);
1261 assert(np->parent->tok == MDOC_Bf);
1262
1263 /* Check the number of arguments. */
1264
1265 nch = np->child;
1266 if (np->parent->args == NULL) {
1267 if (nch == NULL) {
1268 mandoc_msg(MANDOCERR_BF_NOFONT,
1269 np->line, np->pos, "Bf");
1270 return;
1271 }
1272 nch = nch->next;
1273 }
1274 if (nch != NULL)
1275 mandoc_msg(MANDOCERR_ARG_EXCESS,
1276 nch->line, nch->pos, "Bf ... %s", nch->string);
1277
1278 /* Extract argument into data. */
1279
1280 if (np->parent->args != NULL) {
1281 switch (np->parent->args->argv[0].arg) {
1282 case MDOC_Emphasis:
1283 np->norm->Bf.font = FONT_Em;
1284 break;
1285 case MDOC_Literal:
1286 np->norm->Bf.font = FONT_Li;
1287 break;
1288 case MDOC_Symbolic:
1289 np->norm->Bf.font = FONT_Sy;
1290 break;
1291 default:
1292 abort();
1293 }
1294 return;
1295 }
1296
1297 /* Extract parameter into data. */
1298
1299 if ( ! strcmp(np->child->string, "Em"))
1300 np->norm->Bf.font = FONT_Em;
1301 else if ( ! strcmp(np->child->string, "Li"))
1302 np->norm->Bf.font = FONT_Li;
1303 else if ( ! strcmp(np->child->string, "Sy"))
1304 np->norm->Bf.font = FONT_Sy;
1305 else
1306 mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
1307 np->child->pos, "Bf %s", np->child->string);
1308 }
1309
1310 static void
1311 post_fname(POST_ARGS)
1312 {
1313 struct roff_node *n, *nch;
1314 const char *cp;
1315 size_t pos;
1316
1317 n = mdoc->last;
1318 nch = n->child;
1319 cp = nch->string;
1320 if (*cp == '(') {
1321 if (cp[strlen(cp + 1)] == ')')
1322 return;
1323 pos = 0;
1324 } else {
1325 pos = strcspn(cp, "()");
1326 if (cp[pos] == '\0') {
1327 if (n->sec == SEC_DESCRIPTION ||
1328 n->sec == SEC_CUSTOM)
1329 tag_put(NULL, fn_prio++, n);
1330 return;
1331 }
1332 }
1333 mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
1334 }
1335
1336 static void
1337 post_fn(POST_ARGS)
1338 {
1339 post_fname(mdoc);
1340 post_fa(mdoc);
1341 }
1342
1343 static void
1344 post_fo(POST_ARGS)
1345 {
1346 const struct roff_node *n;
1347
1348 n = mdoc->last;
1349
1350 if (n->type != ROFFT_HEAD)
1351 return;
1352
1353 if (n->child == NULL) {
1354 mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
1355 return;
1356 }
1357 if (n->child != n->last) {
1358 mandoc_msg(MANDOCERR_ARG_EXCESS,
1359 n->child->next->line, n->child->next->pos,
1360 "Fo ... %s", n->child->next->string);
1361 while (n->child != n->last)
1362 roff_node_delete(mdoc, n->last);
1363 } else
1364 post_delim(mdoc);
1365
1366 post_fname(mdoc);
1367 }
1368
1369 static void
1370 post_fa(POST_ARGS)
1371 {
1372 const struct roff_node *n;
1373 const char *cp;
1374
1375 for (n = mdoc->last->child; n != NULL; n = n->next) {
1376 for (cp = n->string; *cp != '\0'; cp++) {
1377 /* Ignore callbacks and alterations. */
1378 if (*cp == '(' || *cp == '{')
1379 break;
1380 if (*cp != ',')
1381 continue;
1382 mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1383 n->pos + (int)(cp - n->string), "%s", n->string);
1384 break;
1385 }
1386 }
1387 post_delim_nb(mdoc);
1388 }
1389
1390 static void
1391 post_nm(POST_ARGS)
1392 {
1393 struct roff_node *n;
1394
1395 n = mdoc->last;
1396
1397 if (n->sec == SEC_NAME && n->child != NULL &&
1398 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1399 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1400
1401 if (n->last != NULL && n->last->tok == MDOC_Pp)
1402 roff_node_relink(mdoc, n->last);
1403
1404 if (mdoc->meta.name == NULL)
1405 deroff(&mdoc->meta.name, n);
1406
1407 if (mdoc->meta.name == NULL ||
1408 (mdoc->lastsec == SEC_NAME && n->child == NULL))
1409 mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
1410
1411 switch (n->type) {
1412 case ROFFT_ELEM:
1413 post_delim_nb(mdoc);
1414 break;
1415 case ROFFT_HEAD:
1416 post_delim(mdoc);
1417 break;
1418 default:
1419 return;
1420 }
1421
1422 if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1423 mdoc->meta.name == NULL)
1424 return;
1425
1426 mdoc->next = ROFF_NEXT_CHILD;
1427 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1428 mdoc->last->flags |= NODE_NOSRC;
1429 mdoc->last = n;
1430 }
1431
1432 static void
1433 post_nd(POST_ARGS)
1434 {
1435 struct roff_node *n;
1436
1437 n = mdoc->last;
1438
1439 if (n->type != ROFFT_BODY)
1440 return;
1441
1442 if (n->sec != SEC_NAME)
1443 mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
1444
1445 if (n->child == NULL)
1446 mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1447 else
1448 post_delim(mdoc);
1449
1450 post_hyph(mdoc);
1451 }
1452
1453 static void
1454 post_display(POST_ARGS)
1455 {
1456 struct roff_node *n, *np;
1457
1458 n = mdoc->last;
1459 switch (n->type) {
1460 case ROFFT_BODY:
1461 if (n->end != ENDBODY_NOT) {
1462 if (n->tok == MDOC_Bd &&
1463 n->body->parent->args == NULL)
1464 roff_node_delete(mdoc, n);
1465 } else if (n->child == NULL)
1466 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1467 "%s", roff_name[n->tok]);
1468 else if (n->tok == MDOC_D1)
1469 post_hyph(mdoc);
1470 break;
1471 case ROFFT_BLOCK:
1472 if (n->tok == MDOC_Bd) {
1473 if (n->args == NULL) {
1474 mandoc_msg(MANDOCERR_BD_NOARG,
1475 n->line, n->pos, "Bd");
1476 mdoc->next = ROFF_NEXT_SIBLING;
1477 while (n->body->child != NULL)
1478 roff_node_relink(mdoc,
1479 n->body->child);
1480 roff_node_delete(mdoc, n);
1481 break;
1482 }
1483 post_bd(mdoc);
1484 post_prevpar(mdoc);
1485 }
1486 for (np = n->parent; np != NULL; np = np->parent) {
1487 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1488 mandoc_msg(MANDOCERR_BD_NEST, n->line,
1489 n->pos, "%s in Bd", roff_name[n->tok]);
1490 break;
1491 }
1492 }
1493 break;
1494 default:
1495 break;
1496 }
1497 }
1498
1499 static void
1500 post_defaults(POST_ARGS)
1501 {
1502 struct roff_node *n;
1503
1504 n = mdoc->last;
1505 if (n->child != NULL) {
1506 post_delim_nb(mdoc);
1507 return;
1508 }
1509 mdoc->next = ROFF_NEXT_CHILD;
1510 switch (n->tok) {
1511 case MDOC_Ar:
1512 roff_word_alloc(mdoc, n->line, n->pos, "file");
1513 mdoc->last->flags |= NODE_NOSRC;
1514 roff_word_alloc(mdoc, n->line, n->pos, "...");
1515 break;
1516 case MDOC_Pa:
1517 case MDOC_Mt:
1518 roff_word_alloc(mdoc, n->line, n->pos, "~");
1519 break;
1520 default:
1521 abort();
1522 }
1523 mdoc->last->flags |= NODE_NOSRC;
1524 mdoc->last = n;
1525 }
1526
1527 static void
1528 post_at(POST_ARGS)
1529 {
1530 struct roff_node *n, *nch;
1531 const char *att;
1532
1533 n = mdoc->last;
1534 nch = n->child;
1535
1536 /*
1537 * If we have a child, look it up in the standard keys. If a
1538 * key exist, use that instead of the child; if it doesn't,
1539 * prefix "AT&T UNIX " to the existing data.
1540 */
1541
1542 att = NULL;
1543 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1544 mandoc_msg(MANDOCERR_AT_BAD,
1545 nch->line, nch->pos, "At %s", nch->string);
1546
1547 mdoc->next = ROFF_NEXT_CHILD;
1548 if (att != NULL) {
1549 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1550 nch->flags |= NODE_NOPRT;
1551 } else
1552 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1553 mdoc->last->flags |= NODE_NOSRC;
1554 mdoc->last = n;
1555 }
1556
1557 static void
1558 post_an(POST_ARGS)
1559 {
1560 struct roff_node *np, *nch;
1561
1562 post_an_norm(mdoc);
1563
1564 np = mdoc->last;
1565 nch = np->child;
1566 if (np->norm->An.auth == AUTH__NONE) {
1567 if (nch == NULL)
1568 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1569 np->line, np->pos, "An");
1570 else
1571 post_delim_nb(mdoc);
1572 } else if (nch != NULL)
1573 mandoc_msg(MANDOCERR_ARG_EXCESS,
1574 nch->line, nch->pos, "An ... %s", nch->string);
1575 }
1576
1577 static void
1578 post_em(POST_ARGS)
1579 {
1580 post_tag(mdoc);
1581 tag_put(NULL, TAG_FALLBACK, mdoc->last);
1582 }
1583
1584 static void
1585 post_en(POST_ARGS)
1586 {
1587 post_obsolete(mdoc);
1588 if (mdoc->last->type == ROFFT_BLOCK)
1589 mdoc->last->norm->Es = mdoc->last_es;
1590 }
1591
1592 static void
1593 post_er(POST_ARGS)
1594 {
1595 struct roff_node *n;
1596
1597 n = mdoc->last;
1598 if (n->sec == SEC_ERRORS &&
1599 (n->parent->tok == MDOC_It ||
1600 (n->parent->tok == MDOC_Bq &&
1601 n->parent->parent->parent->tok == MDOC_It)))
1602 tag_put(NULL, TAG_STRONG, n);
1603 post_delim_nb(mdoc);
1604 }
1605
1606 static void
1607 post_tag(POST_ARGS)
1608 {
1609 struct roff_node *n;
1610
1611 n = mdoc->last;
1612 if ((n->prev == NULL ||
1613 (n->prev->type == ROFFT_TEXT &&
1614 strcmp(n->prev->string, "|") == 0)) &&
1615 (n->parent->tok == MDOC_It ||
1616 (n->parent->tok == MDOC_Xo &&
1617 n->parent->parent->prev == NULL &&
1618 n->parent->parent->parent->tok == MDOC_It)))
1619 tag_put(NULL, TAG_STRONG, n);
1620 post_delim_nb(mdoc);
1621 }
1622
1623 static void
1624 post_es(POST_ARGS)
1625 {
1626 post_obsolete(mdoc);
1627 mdoc->last_es = mdoc->last;
1628 }
1629
1630 static void
1631 post_xx(POST_ARGS)
1632 {
1633 struct roff_node *n;
1634 const char *os;
1635 char *v;
1636
1637 post_delim_nb(mdoc);
1638
1639 n = mdoc->last;
1640 switch (n->tok) {
1641 case MDOC_Bsx:
1642 os = "BSD/OS";
1643 break;
1644 case MDOC_Dx:
1645 os = "DragonFly";
1646 break;
1647 case MDOC_Fx:
1648 os = "FreeBSD";
1649 break;
1650 case MDOC_Nx:
1651 os = "NetBSD";
1652 if (n->child == NULL)
1653 break;
1654 v = n->child->string;
1655 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1656 v[2] < '0' || v[2] > '9' ||
1657 v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1658 break;
1659 n->child->flags |= NODE_NOPRT;
1660 mdoc->next = ROFF_NEXT_CHILD;
1661 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1662 v = mdoc->last->string;
1663 v[3] = toupper((unsigned char)v[3]);
1664 mdoc->last->flags |= NODE_NOSRC;
1665 mdoc->last = n;
1666 break;
1667 case MDOC_Ox:
1668 os = "OpenBSD";
1669 break;
1670 case MDOC_Ux:
1671 os = "UNIX";
1672 break;
1673 default:
1674 abort();
1675 }
1676 mdoc->next = ROFF_NEXT_CHILD;
1677 roff_word_alloc(mdoc, n->line, n->pos, os);
1678 mdoc->last->flags |= NODE_NOSRC;
1679 mdoc->last = n;
1680 }
1681
1682 static void
1683 post_it(POST_ARGS)
1684 {
1685 struct roff_node *nbl, *nit, *nch;
1686 int i, cols;
1687 enum mdoc_list lt;
1688
1689 post_prevpar(mdoc);
1690
1691 nit = mdoc->last;
1692 if (nit->type != ROFFT_BLOCK)
1693 return;
1694
1695 nbl = nit->parent->parent;
1696 lt = nbl->norm->Bl.type;
1697
1698 switch (lt) {
1699 case LIST_tag:
1700 case LIST_hang:
1701 case LIST_ohang:
1702 case LIST_inset:
1703 case LIST_diag:
1704 if (nit->head->child == NULL)
1705 mandoc_msg(MANDOCERR_IT_NOHEAD,
1706 nit->line, nit->pos, "Bl -%s It",
1707 mdoc_argnames[nbl->args->argv[0].arg]);
1708 break;
1709 case LIST_bullet:
1710 case LIST_dash:
1711 case LIST_enum:
1712 case LIST_hyphen:
1713 if (nit->body == NULL || nit->body->child == NULL)
1714 mandoc_msg(MANDOCERR_IT_NOBODY,
1715 nit->line, nit->pos, "Bl -%s It",
1716 mdoc_argnames[nbl->args->argv[0].arg]);
1717 /* FALLTHROUGH */
1718 case LIST_item:
1719 if ((nch = nit->head->child) != NULL)
1720 mandoc_msg(MANDOCERR_ARG_SKIP,
1721 nit->line, nit->pos, "It %s",
1722 nch->type == ROFFT_TEXT ? nch->string :
1723 roff_name[nch->tok]);
1724 break;
1725 case LIST_column:
1726 cols = (int)nbl->norm->Bl.ncols;
1727
1728 assert(nit->head->child == NULL);
1729
1730 if (nit->head->next->child == NULL &&
1731 nit->head->next->next == NULL) {
1732 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1733 nit->line, nit->pos, "It");
1734 roff_node_delete(mdoc, nit);
1735 break;
1736 }
1737
1738 i = 0;
1739 for (nch = nit->child; nch != NULL; nch = nch->next) {
1740 if (nch->type != ROFFT_BODY)
1741 continue;
1742 if (i++ && nch->flags & NODE_LINE)
1743 mandoc_msg(MANDOCERR_TA_LINE,
1744 nch->line, nch->pos, "Ta");
1745 }
1746 if (i < cols || i > cols + 1)
1747 mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1748 "%d columns, %d cells", cols, i);
1749 else if (nit->head->next->child != NULL &&
1750 nit->head->next->child->flags & NODE_LINE)
1751 mandoc_msg(MANDOCERR_IT_NOARG,
1752 nit->line, nit->pos, "Bl -column It");
1753 break;
1754 default:
1755 abort();
1756 }
1757 }
1758
1759 static void
1760 post_bl_block(POST_ARGS)
1761 {
1762 struct roff_node *n, *ni, *nc;
1763
1764 post_prevpar(mdoc);
1765
1766 n = mdoc->last;
1767 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1768 if (ni->body == NULL)
1769 continue;
1770 nc = ni->body->last;
1771 while (nc != NULL) {
1772 switch (nc->tok) {
1773 case MDOC_Pp:
1774 case ROFF_br:
1775 break;
1776 default:
1777 nc = NULL;
1778 continue;
1779 }
1780 if (ni->next == NULL) {
1781 mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1782 nc->pos, "%s", roff_name[nc->tok]);
1783 roff_node_relink(mdoc, nc);
1784 } else if (n->norm->Bl.comp == 0 &&
1785 n->norm->Bl.type != LIST_column) {
1786 mandoc_msg(MANDOCERR_PAR_SKIP,
1787 nc->line, nc->pos,
1788 "%s before It", roff_name[nc->tok]);
1789 roff_node_delete(mdoc, nc);
1790 } else
1791 break;
1792 nc = ni->body->last;
1793 }
1794 }
1795 }
1796
1797 /*
1798 * If the argument of -offset or -width is a macro,
1799 * replace it with the associated default width.
1800 */
1801 static void
1802 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1803 {
1804 size_t width;
1805 enum roff_tok tok;
1806
1807 if (*arg == NULL)
1808 return;
1809 else if ( ! strcmp(*arg, "Ds"))
1810 width = 6;
1811 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1812 return;
1813 else
1814 width = macro2len(tok);
1815
1816 free(*arg);
1817 mandoc_asprintf(arg, "%zun", width);
1818 }
1819
1820 static void
1821 post_bl_head(POST_ARGS)
1822 {
1823 struct roff_node *nbl, *nh, *nch, *nnext;
1824 struct mdoc_argv *argv;
1825 int i, j;
1826
1827 post_bl_norm(mdoc);
1828
1829 nh = mdoc->last;
1830 if (nh->norm->Bl.type != LIST_column) {
1831 if ((nch = nh->child) == NULL)
1832 return;
1833 mandoc_msg(MANDOCERR_ARG_EXCESS,
1834 nch->line, nch->pos, "Bl ... %s", nch->string);
1835 while (nch != NULL) {
1836 roff_node_delete(mdoc, nch);
1837 nch = nh->child;
1838 }
1839 return;
1840 }
1841
1842 /*
1843 * Append old-style lists, where the column width specifiers
1844 * trail as macro parameters, to the new-style ("normal-form")
1845 * lists where they're argument values following -column.
1846 */
1847
1848 if (nh->child == NULL)
1849 return;
1850
1851 nbl = nh->parent;
1852 for (j = 0; j < (int)nbl->args->argc; j++)
1853 if (nbl->args->argv[j].arg == MDOC_Column)
1854 break;
1855
1856 assert(j < (int)nbl->args->argc);
1857
1858 /*
1859 * Accommodate for new-style groff column syntax. Shuffle the
1860 * child nodes, all of which must be TEXT, as arguments for the
1861 * column field. Then, delete the head children.
1862 */
1863
1864 argv = nbl->args->argv + j;
1865 i = argv->sz;
1866 for (nch = nh->child; nch != NULL; nch = nch->next)
1867 argv->sz++;
1868 argv->value = mandoc_reallocarray(argv->value,
1869 argv->sz, sizeof(char *));
1870
1871 nh->norm->Bl.ncols = argv->sz;
1872 nh->norm->Bl.cols = (void *)argv->value;
1873
1874 for (nch = nh->child; nch != NULL; nch = nnext) {
1875 argv->value[i++] = nch->string;
1876 nch->string = NULL;
1877 nnext = nch->next;
1878 roff_node_delete(NULL, nch);
1879 }
1880 nh->child = NULL;
1881 }
1882
1883 static void
1884 post_bl(POST_ARGS)
1885 {
1886 struct roff_node *nbody; /* of the Bl */
1887 struct roff_node *nchild, *nnext; /* of the Bl body */
1888 const char *prev_Er;
1889 int order;
1890
1891 nbody = mdoc->last;
1892 switch (nbody->type) {
1893 case ROFFT_BLOCK:
1894 post_bl_block(mdoc);
1895 return;
1896 case ROFFT_HEAD:
1897 post_bl_head(mdoc);
1898 return;
1899 case ROFFT_BODY:
1900 break;
1901 default:
1902 return;
1903 }
1904 if (nbody->end != ENDBODY_NOT)
1905 return;
1906
1907 /*
1908 * Up to the first item, move nodes before the list,
1909 * but leave transparent nodes where they are
1910 * if they precede an item.
1911 * The next non-transparent node is kept in nchild.
1912 * It only needs to be updated after a non-transparent
1913 * node was moved out, and at the very beginning
1914 * when no node at all was moved yet.
1915 */
1916
1917 nchild = mdoc->last;
1918 for (;;) {
1919 if (nchild == mdoc->last)
1920 nchild = roff_node_child(nbody);
1921 if (nchild == NULL) {
1922 mdoc->last = nbody;
1923 mandoc_msg(MANDOCERR_BLK_EMPTY,
1924 nbody->line, nbody->pos, "Bl");
1925 return;
1926 }
1927 if (nchild->tok == MDOC_It) {
1928 mdoc->last = nbody;
1929 break;
1930 }
1931 mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
1932 nbody->child->pos, "%s", roff_name[nbody->child->tok]);
1933 if (nbody->parent->prev == NULL) {
1934 mdoc->last = nbody->parent->parent;
1935 mdoc->next = ROFF_NEXT_CHILD;
1936 } else {
1937 mdoc->last = nbody->parent->prev;
1938 mdoc->next = ROFF_NEXT_SIBLING;
1939 }
1940 roff_node_relink(mdoc, nbody->child);
1941 }
1942
1943 /*
1944 * We have reached the first item,
1945 * so moving nodes out is no longer possible.
1946 * But in .Bl -column, the first rows may be implicit,
1947 * that is, they may not start with .It macros.
1948 * Such rows may be followed by nodes generated on the
1949 * roff level, for example .TS.
1950 * Wrap such roff nodes into an implicit row.
1951 */
1952
1953 while (nchild != NULL) {
1954 if (nchild->tok == MDOC_It) {
1955 nchild = roff_node_next(nchild);
1956 continue;
1957 }
1958 nnext = nchild->next;
1959 mdoc->last = nchild->prev;
1960 mdoc->next = ROFF_NEXT_SIBLING;
1961 roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1962 roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1963 mdoc->next = ROFF_NEXT_SIBLING;
1964 roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1965 while (nchild->tok != MDOC_It) {
1966 roff_node_relink(mdoc, nchild);
1967 if (nnext == NULL)
1968 break;
1969 nchild = nnext;
1970 nnext = nchild->next;
1971 mdoc->next = ROFF_NEXT_SIBLING;
1972 }
1973 mdoc->last = nbody;
1974 }
1975
1976 if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1977 return;
1978
1979 prev_Er = NULL;
1980 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1981 if (nchild->tok != MDOC_It)
1982 continue;
1983 if ((nnext = nchild->head->child) == NULL)
1984 continue;
1985 if (nnext->type == ROFFT_BLOCK)
1986 nnext = nnext->body->child;
1987 if (nnext == NULL || nnext->tok != MDOC_Er)
1988 continue;
1989 nnext = nnext->child;
1990 if (prev_Er != NULL) {
1991 order = strcmp(prev_Er, nnext->string);
1992 if (order > 0)
1993 mandoc_msg(MANDOCERR_ER_ORDER,
1994 nnext->line, nnext->pos,
1995 "Er %s %s (NetBSD)",
1996 prev_Er, nnext->string);
1997 else if (order == 0)
1998 mandoc_msg(MANDOCERR_ER_REP,
1999 nnext->line, nnext->pos,
2000 "Er %s (NetBSD)", prev_Er);
2001 }
2002 prev_Er = nnext->string;
2003 }
2004 }
2005
2006 static void
2007 post_bk(POST_ARGS)
2008 {
2009 struct roff_node *n;
2010
2011 n = mdoc->last;
2012
2013 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
2014 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
2015 roff_node_delete(mdoc, n);
2016 }
2017 }
2018
2019 static void
2020 post_sm(POST_ARGS)
2021 {
2022 struct roff_node *nch;
2023
2024 nch = mdoc->last->child;
2025
2026 if (nch == NULL) {
2027 mdoc->flags ^= MDOC_SMOFF;
2028 return;
2029 }
2030
2031 assert(nch->type == ROFFT_TEXT);
2032
2033 if ( ! strcmp(nch->string, "on")) {
2034 mdoc->flags &= ~MDOC_SMOFF;
2035 return;
2036 }
2037 if ( ! strcmp(nch->string, "off")) {
2038 mdoc->flags |= MDOC_SMOFF;
2039 return;
2040 }
2041
2042 mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
2043 "%s %s", roff_name[mdoc->last->tok], nch->string);
2044 roff_node_relink(mdoc, nch);
2045 return;
2046 }
2047
2048 static void
2049 post_root(POST_ARGS)
2050 {
2051 struct roff_node *n;
2052
2053 /* Add missing prologue data. */
2054
2055 if (mdoc->meta.date == NULL)
2056 mdoc->meta.date = mandoc_normdate(NULL, NULL);
2057
2058 if (mdoc->meta.title == NULL) {
2059 mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
2060 mdoc->meta.title = mandoc_strdup("UNTITLED");
2061 }
2062
2063 if (mdoc->meta.vol == NULL)
2064 mdoc->meta.vol = mandoc_strdup("LOCAL");
2065
2066 if (mdoc->meta.os == NULL) {
2067 mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
2068 mdoc->meta.os = mandoc_strdup("");
2069 } else if (mdoc->meta.os_e &&
2070 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
2071 mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
2072 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2073 "(OpenBSD)" : "(NetBSD)");
2074
2075 if (mdoc->meta.arch != NULL &&
2076 arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
2077 n = mdoc->meta.first->child;
2078 while (n->tok != MDOC_Dt ||
2079 n->child == NULL ||
2080 n->child->next == NULL ||
2081 n->child->next->next == NULL)
2082 n = n->next;
2083 n = n->child->next->next;
2084 mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
2085 "Dt ... %s %s", mdoc->meta.arch,
2086 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2087 "(OpenBSD)" : "(NetBSD)");
2088 }
2089
2090 /* Check that we begin with a proper `Sh'. */
2091
2092 n = mdoc->meta.first->child;
2093 while (n != NULL &&
2094 (n->type == ROFFT_COMMENT ||
2095 (n->tok >= MDOC_Dd &&
2096 mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
2097 n = n->next;
2098
2099 if (n == NULL)
2100 mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
2101 else if (n->tok != MDOC_Sh)
2102 mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
2103 "%s", roff_name[n->tok]);
2104 }
2105
2106 static void
2107 post_rs(POST_ARGS)
2108 {
2109 struct roff_node *np, *nch, *next, *prev;
2110 int i, j;
2111
2112 np = mdoc->last;
2113
2114 if (np->type != ROFFT_BODY)
2115 return;
2116
2117 if (np->child == NULL) {
2118 mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
2119 return;
2120 }
2121
2122 /*
2123 * The full `Rs' block needs special handling to order the
2124 * sub-elements according to `rsord'. Pick through each element
2125 * and correctly order it. This is an insertion sort.
2126 */
2127
2128 next = NULL;
2129 for (nch = np->child->next; nch != NULL; nch = next) {
2130 /* Determine order number of this child. */
2131 for (i = 0; i < RSORD_MAX; i++)
2132 if (rsord[i] == nch->tok)
2133 break;
2134
2135 if (i == RSORD_MAX) {
2136 mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
2137 "%s", roff_name[nch->tok]);
2138 i = -1;
2139 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
2140 np->norm->Rs.quote_T++;
2141
2142 /*
2143 * Remove this child from the chain. This somewhat
2144 * repeats roff_node_unlink(), but since we're
2145 * just re-ordering, there's no need for the
2146 * full unlink process.
2147 */
2148
2149 if ((next = nch->next) != NULL)
2150 next->prev = nch->prev;
2151
2152 if ((prev = nch->prev) != NULL)
2153 prev->next = nch->next;
2154
2155 nch->prev = nch->next = NULL;
2156
2157 /*
2158 * Scan back until we reach a node that's
2159 * to be ordered before this child.
2160 */
2161
2162 for ( ; prev ; prev = prev->prev) {
2163 /* Determine order of `prev'. */
2164 for (j = 0; j < RSORD_MAX; j++)
2165 if (rsord[j] == prev->tok)
2166 break;
2167 if (j == RSORD_MAX)
2168 j = -1;
2169
2170 if (j <= i)
2171 break;
2172 }
2173
2174 /*
2175 * Set this child back into its correct place
2176 * in front of the `prev' node.
2177 */
2178
2179 nch->prev = prev;
2180
2181 if (prev == NULL) {
2182 np->child->prev = nch;
2183 nch->next = np->child;
2184 np->child = nch;
2185 } else {
2186 if (prev->next)
2187 prev->next->prev = nch;
2188 nch->next = prev->next;
2189 prev->next = nch;
2190 }
2191 }
2192 }
2193
2194 /*
2195 * For some arguments of some macros,
2196 * convert all breakable hyphens into ASCII_HYPH.
2197 */
2198 static void
2199 post_hyph(POST_ARGS)
2200 {
2201 struct roff_node *n, *nch;
2202 char *cp;
2203
2204 n = mdoc->last;
2205 for (nch = n->child; nch != NULL; nch = nch->next) {
2206 if (nch->type != ROFFT_TEXT)
2207 continue;
2208 cp = nch->string;
2209 if (*cp == '\0')
2210 continue;
2211 while (*(++cp) != '\0')
2212 if (*cp == '-' &&
2213 isalpha((unsigned char)cp[-1]) &&
2214 isalpha((unsigned char)cp[1])) {
2215 if (n->tag == NULL && n->flags & NODE_ID)
2216 n->tag = mandoc_strdup(nch->string);
2217 *cp = ASCII_HYPH;
2218 }
2219 }
2220 }
2221
2222 static void
2223 post_ns(POST_ARGS)
2224 {
2225 struct roff_node *n;
2226
2227 n = mdoc->last;
2228 if (n->flags & NODE_LINE ||
2229 (n->next != NULL && n->next->flags & NODE_DELIMC))
2230 mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2231 }
2232
2233 static void
2234 post_sx(POST_ARGS)
2235 {
2236 post_delim(mdoc);
2237 post_hyph(mdoc);
2238 }
2239
2240 static void
2241 post_sh(POST_ARGS)
2242 {
2243 post_section(mdoc);
2244
2245 switch (mdoc->last->type) {
2246 case ROFFT_HEAD:
2247 post_sh_head(mdoc);
2248 break;
2249 case ROFFT_BODY:
2250 switch (mdoc->lastsec) {
2251 case SEC_NAME:
2252 post_sh_name(mdoc);
2253 break;
2254 case SEC_SEE_ALSO:
2255 post_sh_see_also(mdoc);
2256 break;
2257 case SEC_AUTHORS:
2258 post_sh_authors(mdoc);
2259 break;
2260 default:
2261 break;
2262 }
2263 break;
2264 default:
2265 break;
2266 }
2267 }
2268
2269 static void
2270 post_sh_name(POST_ARGS)
2271 {
2272 struct roff_node *n;
2273 int hasnm, hasnd;
2274
2275 hasnm = hasnd = 0;
2276
2277 for (n = mdoc->last->child; n != NULL; n = n->next) {
2278 switch (n->tok) {
2279 case MDOC_Nm:
2280 if (hasnm && n->child != NULL)
2281 mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2282 n->line, n->pos,
2283 "Nm %s", n->child->string);
2284 hasnm = 1;
2285 continue;
2286 case MDOC_Nd:
2287 hasnd = 1;
2288 if (n->next != NULL)
2289 mandoc_msg(MANDOCERR_NAMESEC_ND,
2290 n->line, n->pos, NULL);
2291 break;
2292 case TOKEN_NONE:
2293 if (n->type == ROFFT_TEXT &&
2294 n->string[0] == ',' && n->string[1] == '\0' &&
2295 n->next != NULL && n->next->tok == MDOC_Nm) {
2296 n = n->next;
2297 continue;
2298 }
2299 /* FALLTHROUGH */
2300 default:
2301 mandoc_msg(MANDOCERR_NAMESEC_BAD,
2302 n->line, n->pos, "%s", roff_name[n->tok]);
2303 continue;
2304 }
2305 break;
2306 }
2307
2308 if ( ! hasnm)
2309 mandoc_msg(MANDOCERR_NAMESEC_NONM,
2310 mdoc->last->line, mdoc->last->pos, NULL);
2311 if ( ! hasnd)
2312 mandoc_msg(MANDOCERR_NAMESEC_NOND,
2313 mdoc->last->line, mdoc->last->pos, NULL);
2314 }
2315
2316 static void
2317 post_sh_see_also(POST_ARGS)
2318 {
2319 const struct roff_node *n;
2320 const char *name, *sec;
2321 const char *lastname, *lastsec, *lastpunct;
2322 int cmp;
2323
2324 n = mdoc->last->child;
2325 lastname = lastsec = lastpunct = NULL;
2326 while (n != NULL) {
2327 if (n->tok != MDOC_Xr ||
2328 n->child == NULL ||
2329 n->child->next == NULL)
2330 break;
2331
2332 /* Process one .Xr node. */
2333
2334 name = n->child->string;
2335 sec = n->child->next->string;
2336 if (lastsec != NULL) {
2337 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2338 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2339 n->pos, "%s before %s(%s)",
2340 lastpunct, name, sec);
2341 cmp = strcmp(lastsec, sec);
2342 if (cmp > 0)
2343 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2344 n->pos, "%s(%s) after %s(%s)",
2345 name, sec, lastname, lastsec);
2346 else if (cmp == 0 &&
2347 strcasecmp(lastname, name) > 0)
2348 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2349 n->pos, "%s after %s", name, lastname);
2350 }
2351 lastname = name;
2352 lastsec = sec;
2353
2354 /* Process the following node. */
2355
2356 n = n->next;
2357 if (n == NULL)
2358 break;
2359 if (n->tok == MDOC_Xr) {
2360 lastpunct = "none";
2361 continue;
2362 }
2363 if (n->type != ROFFT_TEXT)
2364 break;
2365 for (name = n->string; *name != '\0'; name++)
2366 if (isalpha((const unsigned char)*name))
2367 return;
2368 lastpunct = n->string;
2369 if (n->next == NULL || n->next->tok == MDOC_Rs)
2370 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2371 n->pos, "%s after %s(%s)",
2372 lastpunct, lastname, lastsec);
2373 n = n->next;
2374 }
2375 }
2376
2377 static int
2378 child_an(const struct roff_node *n)
2379 {
2380
2381 for (n = n->child; n != NULL; n = n->next)
2382 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2383 return 1;
2384 return 0;
2385 }
2386
2387 static void
2388 post_sh_authors(POST_ARGS)
2389 {
2390
2391 if ( ! child_an(mdoc->last))
2392 mandoc_msg(MANDOCERR_AN_MISSING,
2393 mdoc->last->line, mdoc->last->pos, NULL);
2394 }
2395
2396 /*
2397 * Return an upper bound for the string distance (allowing
2398 * transpositions). Not a full Levenshtein implementation
2399 * because Levenshtein is quadratic in the string length
2400 * and this function is called for every standard name,
2401 * so the check for each custom name would be cubic.
2402 * The following crude heuristics is linear, resulting
2403 * in quadratic behaviour for checking one custom name,
2404 * which does not cause measurable slowdown.
2405 */
2406 static int
2407 similar(const char *s1, const char *s2)
2408 {
2409 const int maxdist = 3;
2410 int dist = 0;
2411
2412 while (s1[0] != '\0' && s2[0] != '\0') {
2413 if (s1[0] == s2[0]) {
2414 s1++;
2415 s2++;
2416 continue;
2417 }
2418 if (++dist > maxdist)
2419 return INT_MAX;
2420 if (s1[1] == s2[1]) { /* replacement */
2421 s1++;
2422 s2++;
2423 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2424 s1 += 2; /* transposition */
2425 s2 += 2;
2426 } else if (s1[0] == s2[1]) /* insertion */
2427 s2++;
2428 else if (s1[1] == s2[0]) /* deletion */
2429 s1++;
2430 else
2431 return INT_MAX;
2432 }
2433 dist += strlen(s1) + strlen(s2);
2434 return dist > maxdist ? INT_MAX : dist;
2435 }
2436
2437 static void
2438 post_sh_head(POST_ARGS)
2439 {
2440 struct roff_node *nch;
2441 const char *goodsec;
2442 const char *const *testsec;
2443 int dist, mindist;
2444 enum roff_sec sec;
2445
2446 /*
2447 * Process a new section. Sections are either "named" or
2448 * "custom". Custom sections are user-defined, while named ones
2449 * follow a conventional order and may only appear in certain
2450 * manual sections.
2451 */
2452
2453 sec = mdoc->last->sec;
2454
2455 /* The NAME should be first. */
2456
2457 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2458 mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2459 mdoc->last->line, mdoc->last->pos, "Sh %s",
2460 sec != SEC_CUSTOM ? secnames[sec] :
2461 (nch = mdoc->last->child) == NULL ? "" :
2462 nch->type == ROFFT_TEXT ? nch->string :
2463 roff_name[nch->tok]);
2464
2465 /* The SYNOPSIS gets special attention in other areas. */
2466
2467 if (sec == SEC_SYNOPSIS) {
2468 roff_setreg(mdoc->roff, "nS", 1, '=');
2469 mdoc->flags |= MDOC_SYNOPSIS;
2470 } else {
2471 roff_setreg(mdoc->roff, "nS", 0, '=');
2472 mdoc->flags &= ~MDOC_SYNOPSIS;
2473 }
2474 if (sec == SEC_DESCRIPTION)
2475 fn_prio = TAG_STRONG;
2476
2477 /* Mark our last section. */
2478
2479 mdoc->lastsec = sec;
2480
2481 /* We don't care about custom sections after this. */
2482
2483 if (sec == SEC_CUSTOM) {
2484 if ((nch = mdoc->last->child) == NULL ||
2485 nch->type != ROFFT_TEXT || nch->next != NULL)
2486 return;
2487 goodsec = NULL;
2488 mindist = INT_MAX;
2489 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2490 dist = similar(nch->string, *testsec);
2491 if (dist < mindist) {
2492 goodsec = *testsec;
2493 mindist = dist;
2494 }
2495 }
2496 if (goodsec != NULL)
2497 mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2498 "Sh %s instead of %s", nch->string, goodsec);
2499 return;
2500 }
2501
2502 /*
2503 * Check whether our non-custom section is being repeated or is
2504 * out of order.
2505 */
2506
2507 if (sec == mdoc->lastnamed)
2508 mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2509 mdoc->last->pos, "Sh %s", secnames[sec]);
2510
2511 if (sec < mdoc->lastnamed)
2512 mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2513 mdoc->last->pos, "Sh %s", secnames[sec]);
2514
2515 /* Mark the last named section. */
2516
2517 mdoc->lastnamed = sec;
2518
2519 /* Check particular section/manual conventions. */
2520
2521 if (mdoc->meta.msec == NULL)
2522 return;
2523
2524 goodsec = NULL;
2525 switch (sec) {
2526 case SEC_ERRORS:
2527 if (*mdoc->meta.msec == '4')
2528 break;
2529 goodsec = "2, 3, 4, 9";
2530 /* FALLTHROUGH */
2531 case SEC_RETURN_VALUES:
2532 case SEC_LIBRARY:
2533 if (*mdoc->meta.msec == '2')
2534 break;
2535 if (*mdoc->meta.msec == '3')
2536 break;
2537 if (NULL == goodsec)
2538 goodsec = "2, 3, 9";
2539 /* FALLTHROUGH */
2540 case SEC_CONTEXT:
2541 if (*mdoc->meta.msec == '9')
2542 break;
2543 if (NULL == goodsec)
2544 goodsec = "9";
2545 mandoc_msg(MANDOCERR_SEC_MSEC,
2546 mdoc->last->line, mdoc->last->pos,
2547 "Sh %s for %s only", secnames[sec], goodsec);
2548 break;
2549 default:
2550 break;
2551 }
2552 }
2553
2554 static void
2555 post_xr(POST_ARGS)
2556 {
2557 struct roff_node *n, *nch;
2558
2559 n = mdoc->last;
2560 nch = n->child;
2561 if (nch->next == NULL) {
2562 mandoc_msg(MANDOCERR_XR_NOSEC,
2563 n->line, n->pos, "Xr %s", nch->string);
2564 } else {
2565 assert(nch->next == n->last);
2566 if(mandoc_xr_add(nch->next->string, nch->string,
2567 nch->line, nch->pos))
2568 mandoc_msg(MANDOCERR_XR_SELF,
2569 nch->line, nch->pos, "Xr %s %s",
2570 nch->string, nch->next->string);
2571 }
2572 post_delim_nb(mdoc);
2573 }
2574
2575 static void
2576 post_section(POST_ARGS)
2577 {
2578 struct roff_node *n, *nch;
2579 char *cp, *tag;
2580
2581 n = mdoc->last;
2582 switch (n->type) {
2583 case ROFFT_BLOCK:
2584 post_prevpar(mdoc);
2585 return;
2586 case ROFFT_HEAD:
2587 tag = NULL;
2588 deroff(&tag, n);
2589 if (tag != NULL) {
2590 for (cp = tag; *cp != '\0'; cp++)
2591 if (*cp == ' ')
2592 *cp = '_';
2593 if ((nch = n->child) != NULL &&
2594 nch->type == ROFFT_TEXT &&
2595 strcmp(nch->string, tag) == 0)
2596 tag_put(NULL, TAG_WEAK, n);
2597 else
2598 tag_put(tag, TAG_FALLBACK, n);
2599 free(tag);
2600 }
2601 post_delim(mdoc);
2602 post_hyph(mdoc);
2603 return;
2604 case ROFFT_BODY:
2605 break;
2606 default:
2607 return;
2608 }
2609 if ((nch = n->child) != NULL &&
2610 (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
2611 nch->tok == ROFF_sp)) {
2612 mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2613 "%s after %s", roff_name[nch->tok],
2614 roff_name[n->tok]);
2615 roff_node_delete(mdoc, nch);
2616 }
2617 if ((nch = n->last) != NULL &&
2618 (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
2619 mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2620 "%s at the end of %s", roff_name[nch->tok],
2621 roff_name[n->tok]);
2622 roff_node_delete(mdoc, nch);
2623 }
2624 }
2625
2626 static void
2627 post_prevpar(POST_ARGS)
2628 {
2629 struct roff_node *n, *np;
2630
2631 n = mdoc->last;
2632 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2633 return;
2634 if ((np = roff_node_prev(n)) == NULL)
2635 return;
2636
2637 /*
2638 * Don't allow `Pp' prior to a paragraph-type
2639 * block: `Pp' or non-compact `Bd' or `Bl'.
2640 */
2641
2642 if (np->tok != MDOC_Pp && np->tok != ROFF_br)
2643 return;
2644 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2645 return;
2646 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2647 return;
2648 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2649 return;
2650
2651 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2652 "%s before %s", roff_name[np->tok], roff_name[n->tok]);
2653 roff_node_delete(mdoc, np);
2654 }
2655
2656 static void
2657 post_par(POST_ARGS)
2658 {
2659 struct roff_node *np;
2660
2661 fn_prio = TAG_STRONG;
2662 post_prevpar(mdoc);
2663
2664 np = mdoc->last;
2665 if (np->child != NULL)
2666 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2667 "%s %s", roff_name[np->tok], np->child->string);
2668 }
2669
2670 static void
2671 post_dd(POST_ARGS)
2672 {
2673 struct roff_node *n;
2674
2675 n = mdoc->last;
2676 n->flags |= NODE_NOPRT;
2677
2678 if (mdoc->meta.date != NULL) {
2679 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2680 free(mdoc->meta.date);
2681 } else if (mdoc->flags & MDOC_PBODY)
2682 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2683 else if (mdoc->meta.title != NULL)
2684 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2685 n->line, n->pos, "Dd after Dt");
2686 else if (mdoc->meta.os != NULL)
2687 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2688 n->line, n->pos, "Dd after Os");
2689
2690 if (mdoc->quick && n != NULL)
2691 mdoc->meta.date = mandoc_strdup("");
2692 else
2693 mdoc->meta.date = mandoc_normdate(n->child, n);
2694 }
2695
2696 static void
2697 post_dt(POST_ARGS)
2698 {
2699 struct roff_node *nn, *n;
2700 const char *cp;
2701 char *p;
2702
2703 n = mdoc->last;
2704 n->flags |= NODE_NOPRT;
2705
2706 if (mdoc->flags & MDOC_PBODY) {
2707 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2708 return;
2709 }
2710
2711 if (mdoc->meta.title != NULL)
2712 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2713 else if (mdoc->meta.os != NULL)
2714 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2715 n->line, n->pos, "Dt after Os");
2716
2717 free(mdoc->meta.title);
2718 free(mdoc->meta.msec);
2719 free(mdoc->meta.vol);
2720 free(mdoc->meta.arch);
2721
2722 mdoc->meta.title = NULL;
2723 mdoc->meta.msec = NULL;
2724 mdoc->meta.vol = NULL;
2725 mdoc->meta.arch = NULL;
2726
2727 /* Mandatory first argument: title. */
2728
2729 nn = n->child;
2730 if (nn == NULL || *nn->string == '\0') {
2731 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2732 mdoc->meta.title = mandoc_strdup("UNTITLED");
2733 } else {
2734 mdoc->meta.title = mandoc_strdup(nn->string);
2735
2736 /* Check that all characters are uppercase. */
2737
2738 for (p = nn->string; *p != '\0'; p++)
2739 if (islower((unsigned char)*p)) {
2740 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2741 nn->pos + (int)(p - nn->string),
2742 "Dt %s", nn->string);
2743 break;
2744 }
2745 }
2746
2747 /* Mandatory second argument: section. */
2748
2749 if (nn != NULL)
2750 nn = nn->next;
2751
2752 if (nn == NULL) {
2753 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2754 "Dt %s", mdoc->meta.title);
2755 mdoc->meta.vol = mandoc_strdup("LOCAL");
2756 return; /* msec and arch remain NULL. */
2757 }
2758
2759 mdoc->meta.msec = mandoc_strdup(nn->string);
2760
2761 /* Infer volume title from section number. */
2762
2763 cp = mandoc_a2msec(nn->string);
2764 if (cp == NULL) {
2765 mandoc_msg(MANDOCERR_MSEC_BAD,
2766 nn->line, nn->pos, "Dt ... %s", nn->string);
2767 mdoc->meta.vol = mandoc_strdup(nn->string);
2768 } else
2769 mdoc->meta.vol = mandoc_strdup(cp);
2770
2771 /* Optional third argument: architecture. */
2772
2773 if ((nn = nn->next) == NULL)
2774 return;
2775
2776 for (p = nn->string; *p != '\0'; p++)
2777 *p = tolower((unsigned char)*p);
2778 mdoc->meta.arch = mandoc_strdup(nn->string);
2779
2780 /* Ignore fourth and later arguments. */
2781
2782 if ((nn = nn->next) != NULL)
2783 mandoc_msg(MANDOCERR_ARG_EXCESS,
2784 nn->line, nn->pos, "Dt ... %s", nn->string);
2785 }
2786
2787 static void
2788 post_bx(POST_ARGS)
2789 {
2790 struct roff_node *n, *nch;
2791 const char *macro;
2792
2793 post_delim_nb(mdoc);
2794
2795 n = mdoc->last;
2796 nch = n->child;
2797
2798 if (nch != NULL) {
2799 macro = !strcmp(nch->string, "Open") ? "Ox" :
2800 !strcmp(nch->string, "Net") ? "Nx" :
2801 !strcmp(nch->string, "Free") ? "Fx" :
2802 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2803 if (macro != NULL)
2804 mandoc_msg(MANDOCERR_BX,
2805 n->line, n->pos, "%s", macro);
2806 mdoc->last = nch;
2807 nch = nch->next;
2808 mdoc->next = ROFF_NEXT_SIBLING;
2809 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2810 mdoc->last->flags |= NODE_NOSRC;
2811 mdoc->next = ROFF_NEXT_SIBLING;
2812 } else
2813 mdoc->next = ROFF_NEXT_CHILD;
2814 roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2815 mdoc->last->flags |= NODE_NOSRC;
2816
2817 if (nch == NULL) {
2818 mdoc->last = n;
2819 return;
2820 }
2821
2822 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2823 mdoc->last->flags |= NODE_NOSRC;
2824 mdoc->next = ROFF_NEXT_SIBLING;
2825 roff_word_alloc(mdoc, n->line, n->pos, "-");
2826 mdoc->last->flags |= NODE_NOSRC;
2827 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2828 mdoc->last->flags |= NODE_NOSRC;
2829 mdoc->last = n;
2830
2831 /*
2832 * Make `Bx's second argument always start with an uppercase
2833 * letter. Groff checks if it's an "accepted" term, but we just
2834 * uppercase blindly.
2835 */
2836
2837 *nch->string = (char)toupper((unsigned char)*nch->string);
2838 }
2839
2840 static void
2841 post_os(POST_ARGS)
2842 {
2843 #ifndef OSNAME
2844 struct utsname utsname;
2845 static char *defbuf;
2846 #endif
2847 struct roff_node *n;
2848
2849 n = mdoc->last;
2850 n->flags |= NODE_NOPRT;
2851
2852 if (mdoc->meta.os != NULL)
2853 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2854 else if (mdoc->flags & MDOC_PBODY)
2855 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2856
2857 post_delim(mdoc);
2858
2859 /*
2860 * Set the operating system by way of the `Os' macro.
2861 * The order of precedence is:
2862 * 1. the argument of the `Os' macro, unless empty
2863 * 2. the -Ios=foo command line argument, if provided
2864 * 3. -DOSNAME="\"foo\"", if provided during compilation
2865 * 4. "sysname release" from uname(3)
2866 */
2867
2868 free(mdoc->meta.os);
2869 mdoc->meta.os = NULL;
2870 deroff(&mdoc->meta.os, n);
2871 if (mdoc->meta.os)
2872 goto out;
2873
2874 if (mdoc->os_s != NULL) {
2875 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2876 goto out;
2877 }
2878
2879 #ifdef OSNAME
2880 mdoc->meta.os = mandoc_strdup(OSNAME);
2881 #else /*!OSNAME */
2882 if (defbuf == NULL) {
2883 if (uname(&utsname) == -1) {
2884 mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2885 defbuf = mandoc_strdup("UNKNOWN");
2886 } else
2887 mandoc_asprintf(&defbuf, "%s %s",
2888 utsname.sysname, utsname.release);
2889 }
2890 mdoc->meta.os = mandoc_strdup(defbuf);
2891 #endif /*!OSNAME*/
2892
2893 out:
2894 if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2895 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2896 mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2897 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2898 mdoc->meta.os_e = MANDOC_OS_NETBSD;
2899 }
2900
2901 /*
2902 * This is the earliest point where we can check
2903 * Mdocdate conventions because we don't know
2904 * the operating system earlier.
2905 */
2906
2907 if (n->child != NULL)
2908 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2909 "Os %s (%s)", n->child->string,
2910 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2911 "OpenBSD" : "NetBSD");
2912
2913 while (n->tok != MDOC_Dd)
2914 if ((n = n->prev) == NULL)
2915 return;
2916 if ((n = n->child) == NULL)
2917 return;
2918 if (strncmp(n->string, "$" "Mdocdate", 9)) {
2919 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2920 mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2921 n->pos, "Dd %s (OpenBSD)", n->string);
2922 } else {
2923 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2924 mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2925 n->pos, "Dd %s (NetBSD)", n->string);
2926 }
2927 }
2928
2929 enum roff_sec
2930 mdoc_a2sec(const char *p)
2931 {
2932 int i;
2933
2934 for (i = 0; i < (int)SEC__MAX; i++)
2935 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2936 return (enum roff_sec)i;
2937
2938 return SEC_CUSTOM;
2939 }
2940
2941 static size_t
2942 macro2len(enum roff_tok macro)
2943 {
2944
2945 switch (macro) {
2946 case MDOC_Ad:
2947 return 12;
2948 case MDOC_Ao:
2949 return 12;
2950 case MDOC_An:
2951 return 12;
2952 case MDOC_Aq:
2953 return 12;
2954 case MDOC_Ar:
2955 return 12;
2956 case MDOC_Bo:
2957 return 12;
2958 case MDOC_Bq:
2959 return 12;
2960 case MDOC_Cd:
2961 return 12;
2962 case MDOC_Cm:
2963 return 10;
2964 case MDOC_Do:
2965 return 10;
2966 case MDOC_Dq:
2967 return 12;
2968 case MDOC_Dv:
2969 return 12;
2970 case MDOC_Eo:
2971 return 12;
2972 case MDOC_Em:
2973 return 10;
2974 case MDOC_Er:
2975 return 17;
2976 case MDOC_Ev:
2977 return 15;
2978 case MDOC_Fa:
2979 return 12;
2980 case MDOC_Fl:
2981 return 10;
2982 case MDOC_Fo:
2983 return 16;
2984 case MDOC_Fn:
2985 return 16;
2986 case MDOC_Ic:
2987 return 10;
2988 case MDOC_Li:
2989 return 16;
2990 case MDOC_Ms:
2991 return 6;
2992 case MDOC_Nm:
2993 return 10;
2994 case MDOC_No:
2995 return 12;
2996 case MDOC_Oo:
2997 return 10;
2998 case MDOC_Op:
2999 return 14;
3000 case MDOC_Pa:
3001 return 32;
3002 case MDOC_Pf:
3003 return 12;
3004 case MDOC_Po:
3005 return 12;
3006 case MDOC_Pq:
3007 return 12;
3008 case MDOC_Ql:
3009 return 16;
3010 case MDOC_Qo:
3011 return 12;
3012 case MDOC_So:
3013 return 12;
3014 case MDOC_Sq:
3015 return 12;
3016 case MDOC_Sy:
3017 return 6;
3018 case MDOC_Sx:
3019 return 16;
3020 case MDOC_Tn:
3021 return 10;
3022 case MDOC_Va:
3023 return 12;
3024 case MDOC_Vt:
3025 return 12;
3026 case MDOC_Xr:
3027 return 10;
3028 default:
3029 break;
3030 };
3031 return 0;
3032 }