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