]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_validate.c
Just like we are already doing it in HTML output, automatically tag
[mandoc.git] / mdoc_validate.c
1 /* $Id: mdoc_validate.c,v 1.381 2020/04/01 20:21:08 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 "tag.h"
41 #include "mdoc.h"
42 #include "libmandoc.h"
43 #include "roff_int.h"
44 #include "libmdoc.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 *nt; /* The TEXT node containing the tag. */
1109 size_t len; /* The number of bytes in the tag. */
1110
1111 /* Find the next node. */
1112 n = mdoc->last;
1113 for (nn = n; nn != NULL; nn = nn->parent) {
1114 if (nn->next != NULL) {
1115 nn = nn->next;
1116 break;
1117 }
1118 }
1119
1120 /* Find the tag. */
1121 nt = nch = n->child;
1122 if (nch == NULL && nn != NULL && nn->child != NULL &&
1123 nn->child->type == ROFFT_TEXT)
1124 nt = nn->child;
1125
1126 /* Validate the tag. */
1127 if (nt == NULL || *nt->string == '\0')
1128 mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
1129 if (nt == NULL) {
1130 roff_node_delete(mdoc, n);
1131 return;
1132 }
1133 len = strcspn(nt->string, " \t\\");
1134 if (nt->string[len] != '\0')
1135 mandoc_msg(MANDOCERR_TG_SPC, nt->line,
1136 nt->pos + len, "Tg %s", nt->string);
1137
1138 /* Keep only the first argument. */
1139 if (nch != NULL && nch->next != NULL) {
1140 mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
1141 nch->next->pos, "Tg ... %s", nch->next->string);
1142 while (nch->next != NULL)
1143 roff_node_delete(mdoc, nch->next);
1144 }
1145
1146 /* Drop the macro if the first argument is invalid. */
1147 if (len == 0 || nt->string[len] != '\0') {
1148 roff_node_delete(mdoc, n);
1149 return;
1150 }
1151
1152 /* By default, tag the .Tg node itself. */
1153 if (nn == NULL)
1154 nn = n;
1155
1156 /* Explicit tagging of specific macros. */
1157 switch (nn->tok) {
1158 case MDOC_Sh:
1159 case MDOC_Ss:
1160 case MDOC_Fo:
1161 nn = nn->head;
1162 /* FALLTHROUGH */
1163 case MDOC_Cm:
1164 case MDOC_Dv:
1165 case MDOC_Em:
1166 case MDOC_Er:
1167 case MDOC_Ev:
1168 case MDOC_Fl:
1169 case MDOC_Fn:
1170 case MDOC_Ic:
1171 case MDOC_Li:
1172 case MDOC_Ms:
1173 case MDOC_No:
1174 case MDOC_Sy:
1175 if (nn->child != NULL && (nn->flags & NODE_ID) == 0)
1176 break;
1177 /* FALLTHROUGH */
1178 default:
1179 nn = n;
1180 break;
1181 }
1182 tag_put(nt->string, TAG_MANUAL, nn);
1183 if (nn != n)
1184 n->flags |= NODE_NOPRT;
1185 }
1186
1187 static void
1188 post_obsolete(POST_ARGS)
1189 {
1190 struct roff_node *n;
1191
1192 n = mdoc->last;
1193 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1194 mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
1195 "%s", roff_name[n->tok]);
1196 }
1197
1198 static void
1199 post_useless(POST_ARGS)
1200 {
1201 struct roff_node *n;
1202
1203 n = mdoc->last;
1204 mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
1205 "%s", roff_name[n->tok]);
1206 }
1207
1208 /*
1209 * Block macros.
1210 */
1211
1212 static void
1213 post_bf(POST_ARGS)
1214 {
1215 struct roff_node *np, *nch;
1216
1217 /*
1218 * Unlike other data pointers, these are "housed" by the HEAD
1219 * element, which contains the goods.
1220 */
1221
1222 np = mdoc->last;
1223 if (np->type != ROFFT_HEAD)
1224 return;
1225
1226 assert(np->parent->type == ROFFT_BLOCK);
1227 assert(np->parent->tok == MDOC_Bf);
1228
1229 /* Check the number of arguments. */
1230
1231 nch = np->child;
1232 if (np->parent->args == NULL) {
1233 if (nch == NULL) {
1234 mandoc_msg(MANDOCERR_BF_NOFONT,
1235 np->line, np->pos, "Bf");
1236 return;
1237 }
1238 nch = nch->next;
1239 }
1240 if (nch != NULL)
1241 mandoc_msg(MANDOCERR_ARG_EXCESS,
1242 nch->line, nch->pos, "Bf ... %s", nch->string);
1243
1244 /* Extract argument into data. */
1245
1246 if (np->parent->args != NULL) {
1247 switch (np->parent->args->argv[0].arg) {
1248 case MDOC_Emphasis:
1249 np->norm->Bf.font = FONT_Em;
1250 break;
1251 case MDOC_Literal:
1252 np->norm->Bf.font = FONT_Li;
1253 break;
1254 case MDOC_Symbolic:
1255 np->norm->Bf.font = FONT_Sy;
1256 break;
1257 default:
1258 abort();
1259 }
1260 return;
1261 }
1262
1263 /* Extract parameter into data. */
1264
1265 if ( ! strcmp(np->child->string, "Em"))
1266 np->norm->Bf.font = FONT_Em;
1267 else if ( ! strcmp(np->child->string, "Li"))
1268 np->norm->Bf.font = FONT_Li;
1269 else if ( ! strcmp(np->child->string, "Sy"))
1270 np->norm->Bf.font = FONT_Sy;
1271 else
1272 mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
1273 np->child->pos, "Bf %s", np->child->string);
1274 }
1275
1276 static void
1277 post_fname(POST_ARGS)
1278 {
1279 struct roff_node *n, *nch;
1280 const char *cp;
1281 size_t pos;
1282
1283 n = mdoc->last;
1284 nch = n->child;
1285 cp = nch->string;
1286 if (*cp == '(') {
1287 if (cp[strlen(cp + 1)] == ')')
1288 return;
1289 pos = 0;
1290 } else {
1291 pos = strcspn(cp, "()");
1292 if (cp[pos] == '\0') {
1293 if (n->sec == SEC_DESCRIPTION ||
1294 n->sec == SEC_CUSTOM)
1295 tag_put(NULL, fn_prio++, n);
1296 return;
1297 }
1298 }
1299 mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
1300 }
1301
1302 static void
1303 post_fn(POST_ARGS)
1304 {
1305 post_fname(mdoc);
1306 post_fa(mdoc);
1307 }
1308
1309 static void
1310 post_fo(POST_ARGS)
1311 {
1312 const struct roff_node *n;
1313
1314 n = mdoc->last;
1315
1316 if (n->type != ROFFT_HEAD)
1317 return;
1318
1319 if (n->child == NULL) {
1320 mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
1321 return;
1322 }
1323 if (n->child != n->last) {
1324 mandoc_msg(MANDOCERR_ARG_EXCESS,
1325 n->child->next->line, n->child->next->pos,
1326 "Fo ... %s", n->child->next->string);
1327 while (n->child != n->last)
1328 roff_node_delete(mdoc, n->last);
1329 } else
1330 post_delim(mdoc);
1331
1332 post_fname(mdoc);
1333 }
1334
1335 static void
1336 post_fa(POST_ARGS)
1337 {
1338 const struct roff_node *n;
1339 const char *cp;
1340
1341 for (n = mdoc->last->child; n != NULL; n = n->next) {
1342 for (cp = n->string; *cp != '\0'; cp++) {
1343 /* Ignore callbacks and alterations. */
1344 if (*cp == '(' || *cp == '{')
1345 break;
1346 if (*cp != ',')
1347 continue;
1348 mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1349 n->pos + (int)(cp - n->string), "%s", n->string);
1350 break;
1351 }
1352 }
1353 post_delim_nb(mdoc);
1354 }
1355
1356 static void
1357 post_nm(POST_ARGS)
1358 {
1359 struct roff_node *n;
1360
1361 n = mdoc->last;
1362
1363 if (n->sec == SEC_NAME && n->child != NULL &&
1364 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1365 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1366
1367 if (n->last != NULL && n->last->tok == MDOC_Pp)
1368 roff_node_relink(mdoc, n->last);
1369
1370 if (mdoc->meta.name == NULL)
1371 deroff(&mdoc->meta.name, n);
1372
1373 if (mdoc->meta.name == NULL ||
1374 (mdoc->lastsec == SEC_NAME && n->child == NULL))
1375 mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
1376
1377 switch (n->type) {
1378 case ROFFT_ELEM:
1379 post_delim_nb(mdoc);
1380 break;
1381 case ROFFT_HEAD:
1382 post_delim(mdoc);
1383 break;
1384 default:
1385 return;
1386 }
1387
1388 if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1389 mdoc->meta.name == NULL)
1390 return;
1391
1392 mdoc->next = ROFF_NEXT_CHILD;
1393 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1394 mdoc->last->flags |= NODE_NOSRC;
1395 mdoc->last = n;
1396 }
1397
1398 static void
1399 post_nd(POST_ARGS)
1400 {
1401 struct roff_node *n;
1402
1403 n = mdoc->last;
1404
1405 if (n->type != ROFFT_BODY)
1406 return;
1407
1408 if (n->sec != SEC_NAME)
1409 mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
1410
1411 if (n->child == NULL)
1412 mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1413 else
1414 post_delim(mdoc);
1415
1416 post_hyph(mdoc);
1417 }
1418
1419 static void
1420 post_display(POST_ARGS)
1421 {
1422 struct roff_node *n, *np;
1423
1424 n = mdoc->last;
1425 switch (n->type) {
1426 case ROFFT_BODY:
1427 if (n->end != ENDBODY_NOT) {
1428 if (n->tok == MDOC_Bd &&
1429 n->body->parent->args == NULL)
1430 roff_node_delete(mdoc, n);
1431 } else if (n->child == NULL)
1432 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1433 "%s", roff_name[n->tok]);
1434 else if (n->tok == MDOC_D1)
1435 post_hyph(mdoc);
1436 break;
1437 case ROFFT_BLOCK:
1438 if (n->tok == MDOC_Bd) {
1439 if (n->args == NULL) {
1440 mandoc_msg(MANDOCERR_BD_NOARG,
1441 n->line, n->pos, "Bd");
1442 mdoc->next = ROFF_NEXT_SIBLING;
1443 while (n->body->child != NULL)
1444 roff_node_relink(mdoc,
1445 n->body->child);
1446 roff_node_delete(mdoc, n);
1447 break;
1448 }
1449 post_bd(mdoc);
1450 post_prevpar(mdoc);
1451 }
1452 for (np = n->parent; np != NULL; np = np->parent) {
1453 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1454 mandoc_msg(MANDOCERR_BD_NEST, n->line,
1455 n->pos, "%s in Bd", roff_name[n->tok]);
1456 break;
1457 }
1458 }
1459 break;
1460 default:
1461 break;
1462 }
1463 }
1464
1465 static void
1466 post_defaults(POST_ARGS)
1467 {
1468 struct roff_node *n;
1469
1470 n = mdoc->last;
1471 if (n->child != NULL) {
1472 post_delim_nb(mdoc);
1473 return;
1474 }
1475 mdoc->next = ROFF_NEXT_CHILD;
1476 switch (n->tok) {
1477 case MDOC_Ar:
1478 roff_word_alloc(mdoc, n->line, n->pos, "file");
1479 mdoc->last->flags |= NODE_NOSRC;
1480 roff_word_alloc(mdoc, n->line, n->pos, "...");
1481 break;
1482 case MDOC_Pa:
1483 case MDOC_Mt:
1484 roff_word_alloc(mdoc, n->line, n->pos, "~");
1485 break;
1486 default:
1487 abort();
1488 }
1489 mdoc->last->flags |= NODE_NOSRC;
1490 mdoc->last = n;
1491 }
1492
1493 static void
1494 post_at(POST_ARGS)
1495 {
1496 struct roff_node *n, *nch;
1497 const char *att;
1498
1499 n = mdoc->last;
1500 nch = n->child;
1501
1502 /*
1503 * If we have a child, look it up in the standard keys. If a
1504 * key exist, use that instead of the child; if it doesn't,
1505 * prefix "AT&T UNIX " to the existing data.
1506 */
1507
1508 att = NULL;
1509 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1510 mandoc_msg(MANDOCERR_AT_BAD,
1511 nch->line, nch->pos, "At %s", nch->string);
1512
1513 mdoc->next = ROFF_NEXT_CHILD;
1514 if (att != NULL) {
1515 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1516 nch->flags |= NODE_NOPRT;
1517 } else
1518 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1519 mdoc->last->flags |= NODE_NOSRC;
1520 mdoc->last = n;
1521 }
1522
1523 static void
1524 post_an(POST_ARGS)
1525 {
1526 struct roff_node *np, *nch;
1527
1528 post_an_norm(mdoc);
1529
1530 np = mdoc->last;
1531 nch = np->child;
1532 if (np->norm->An.auth == AUTH__NONE) {
1533 if (nch == NULL)
1534 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1535 np->line, np->pos, "An");
1536 else
1537 post_delim_nb(mdoc);
1538 } else if (nch != NULL)
1539 mandoc_msg(MANDOCERR_ARG_EXCESS,
1540 nch->line, nch->pos, "An ... %s", nch->string);
1541 }
1542
1543 static void
1544 post_em(POST_ARGS)
1545 {
1546 post_tag(mdoc);
1547 tag_put(NULL, TAG_FALLBACK, mdoc->last);
1548 }
1549
1550 static void
1551 post_en(POST_ARGS)
1552 {
1553 post_obsolete(mdoc);
1554 if (mdoc->last->type == ROFFT_BLOCK)
1555 mdoc->last->norm->Es = mdoc->last_es;
1556 }
1557
1558 static void
1559 post_er(POST_ARGS)
1560 {
1561 struct roff_node *n;
1562
1563 n = mdoc->last;
1564 if (n->sec == SEC_ERRORS &&
1565 (n->parent->tok == MDOC_It ||
1566 (n->parent->tok == MDOC_Bq &&
1567 n->parent->parent->parent->tok == MDOC_It)))
1568 tag_put(NULL, TAG_STRONG, n);
1569 post_delim_nb(mdoc);
1570 }
1571
1572 static void
1573 post_tag(POST_ARGS)
1574 {
1575 struct roff_node *n;
1576
1577 n = mdoc->last;
1578 if ((n->prev == NULL ||
1579 (n->prev->type == ROFFT_TEXT &&
1580 strcmp(n->prev->string, "|") == 0)) &&
1581 (n->parent->tok == MDOC_It ||
1582 (n->parent->tok == MDOC_Xo &&
1583 n->parent->parent->prev == NULL &&
1584 n->parent->parent->parent->tok == MDOC_It)))
1585 tag_put(NULL, TAG_STRONG, n);
1586 post_delim_nb(mdoc);
1587 }
1588
1589 static void
1590 post_es(POST_ARGS)
1591 {
1592 post_obsolete(mdoc);
1593 mdoc->last_es = mdoc->last;
1594 }
1595
1596 static void
1597 post_xx(POST_ARGS)
1598 {
1599 struct roff_node *n;
1600 const char *os;
1601 char *v;
1602
1603 post_delim_nb(mdoc);
1604
1605 n = mdoc->last;
1606 switch (n->tok) {
1607 case MDOC_Bsx:
1608 os = "BSD/OS";
1609 break;
1610 case MDOC_Dx:
1611 os = "DragonFly";
1612 break;
1613 case MDOC_Fx:
1614 os = "FreeBSD";
1615 break;
1616 case MDOC_Nx:
1617 os = "NetBSD";
1618 if (n->child == NULL)
1619 break;
1620 v = n->child->string;
1621 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1622 v[2] < '0' || v[2] > '9' ||
1623 v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1624 break;
1625 n->child->flags |= NODE_NOPRT;
1626 mdoc->next = ROFF_NEXT_CHILD;
1627 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1628 v = mdoc->last->string;
1629 v[3] = toupper((unsigned char)v[3]);
1630 mdoc->last->flags |= NODE_NOSRC;
1631 mdoc->last = n;
1632 break;
1633 case MDOC_Ox:
1634 os = "OpenBSD";
1635 break;
1636 case MDOC_Ux:
1637 os = "UNIX";
1638 break;
1639 default:
1640 abort();
1641 }
1642 mdoc->next = ROFF_NEXT_CHILD;
1643 roff_word_alloc(mdoc, n->line, n->pos, os);
1644 mdoc->last->flags |= NODE_NOSRC;
1645 mdoc->last = n;
1646 }
1647
1648 static void
1649 post_it(POST_ARGS)
1650 {
1651 struct roff_node *nbl, *nit, *nch;
1652 int i, cols;
1653 enum mdoc_list lt;
1654
1655 post_prevpar(mdoc);
1656
1657 nit = mdoc->last;
1658 if (nit->type != ROFFT_BLOCK)
1659 return;
1660
1661 nbl = nit->parent->parent;
1662 lt = nbl->norm->Bl.type;
1663
1664 switch (lt) {
1665 case LIST_tag:
1666 case LIST_hang:
1667 case LIST_ohang:
1668 case LIST_inset:
1669 case LIST_diag:
1670 if (nit->head->child == NULL)
1671 mandoc_msg(MANDOCERR_IT_NOHEAD,
1672 nit->line, nit->pos, "Bl -%s It",
1673 mdoc_argnames[nbl->args->argv[0].arg]);
1674 break;
1675 case LIST_bullet:
1676 case LIST_dash:
1677 case LIST_enum:
1678 case LIST_hyphen:
1679 if (nit->body == NULL || nit->body->child == NULL)
1680 mandoc_msg(MANDOCERR_IT_NOBODY,
1681 nit->line, nit->pos, "Bl -%s It",
1682 mdoc_argnames[nbl->args->argv[0].arg]);
1683 /* FALLTHROUGH */
1684 case LIST_item:
1685 if ((nch = nit->head->child) != NULL)
1686 mandoc_msg(MANDOCERR_ARG_SKIP,
1687 nit->line, nit->pos, "It %s",
1688 nch->type == ROFFT_TEXT ? nch->string :
1689 roff_name[nch->tok]);
1690 break;
1691 case LIST_column:
1692 cols = (int)nbl->norm->Bl.ncols;
1693
1694 assert(nit->head->child == NULL);
1695
1696 if (nit->head->next->child == NULL &&
1697 nit->head->next->next == NULL) {
1698 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1699 nit->line, nit->pos, "It");
1700 roff_node_delete(mdoc, nit);
1701 break;
1702 }
1703
1704 i = 0;
1705 for (nch = nit->child; nch != NULL; nch = nch->next) {
1706 if (nch->type != ROFFT_BODY)
1707 continue;
1708 if (i++ && nch->flags & NODE_LINE)
1709 mandoc_msg(MANDOCERR_TA_LINE,
1710 nch->line, nch->pos, "Ta");
1711 }
1712 if (i < cols || i > cols + 1)
1713 mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1714 "%d columns, %d cells", cols, i);
1715 else if (nit->head->next->child != NULL &&
1716 nit->head->next->child->flags & NODE_LINE)
1717 mandoc_msg(MANDOCERR_IT_NOARG,
1718 nit->line, nit->pos, "Bl -column It");
1719 break;
1720 default:
1721 abort();
1722 }
1723 }
1724
1725 static void
1726 post_bl_block(POST_ARGS)
1727 {
1728 struct roff_node *n, *ni, *nc;
1729
1730 post_prevpar(mdoc);
1731
1732 n = mdoc->last;
1733 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1734 if (ni->body == NULL)
1735 continue;
1736 nc = ni->body->last;
1737 while (nc != NULL) {
1738 switch (nc->tok) {
1739 case MDOC_Pp:
1740 case ROFF_br:
1741 break;
1742 default:
1743 nc = NULL;
1744 continue;
1745 }
1746 if (ni->next == NULL) {
1747 mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1748 nc->pos, "%s", roff_name[nc->tok]);
1749 roff_node_relink(mdoc, nc);
1750 } else if (n->norm->Bl.comp == 0 &&
1751 n->norm->Bl.type != LIST_column) {
1752 mandoc_msg(MANDOCERR_PAR_SKIP,
1753 nc->line, nc->pos,
1754 "%s before It", roff_name[nc->tok]);
1755 roff_node_delete(mdoc, nc);
1756 } else
1757 break;
1758 nc = ni->body->last;
1759 }
1760 }
1761 }
1762
1763 /*
1764 * If the argument of -offset or -width is a macro,
1765 * replace it with the associated default width.
1766 */
1767 static void
1768 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1769 {
1770 size_t width;
1771 enum roff_tok tok;
1772
1773 if (*arg == NULL)
1774 return;
1775 else if ( ! strcmp(*arg, "Ds"))
1776 width = 6;
1777 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1778 return;
1779 else
1780 width = macro2len(tok);
1781
1782 free(*arg);
1783 mandoc_asprintf(arg, "%zun", width);
1784 }
1785
1786 static void
1787 post_bl_head(POST_ARGS)
1788 {
1789 struct roff_node *nbl, *nh, *nch, *nnext;
1790 struct mdoc_argv *argv;
1791 int i, j;
1792
1793 post_bl_norm(mdoc);
1794
1795 nh = mdoc->last;
1796 if (nh->norm->Bl.type != LIST_column) {
1797 if ((nch = nh->child) == NULL)
1798 return;
1799 mandoc_msg(MANDOCERR_ARG_EXCESS,
1800 nch->line, nch->pos, "Bl ... %s", nch->string);
1801 while (nch != NULL) {
1802 roff_node_delete(mdoc, nch);
1803 nch = nh->child;
1804 }
1805 return;
1806 }
1807
1808 /*
1809 * Append old-style lists, where the column width specifiers
1810 * trail as macro parameters, to the new-style ("normal-form")
1811 * lists where they're argument values following -column.
1812 */
1813
1814 if (nh->child == NULL)
1815 return;
1816
1817 nbl = nh->parent;
1818 for (j = 0; j < (int)nbl->args->argc; j++)
1819 if (nbl->args->argv[j].arg == MDOC_Column)
1820 break;
1821
1822 assert(j < (int)nbl->args->argc);
1823
1824 /*
1825 * Accommodate for new-style groff column syntax. Shuffle the
1826 * child nodes, all of which must be TEXT, as arguments for the
1827 * column field. Then, delete the head children.
1828 */
1829
1830 argv = nbl->args->argv + j;
1831 i = argv->sz;
1832 for (nch = nh->child; nch != NULL; nch = nch->next)
1833 argv->sz++;
1834 argv->value = mandoc_reallocarray(argv->value,
1835 argv->sz, sizeof(char *));
1836
1837 nh->norm->Bl.ncols = argv->sz;
1838 nh->norm->Bl.cols = (void *)argv->value;
1839
1840 for (nch = nh->child; nch != NULL; nch = nnext) {
1841 argv->value[i++] = nch->string;
1842 nch->string = NULL;
1843 nnext = nch->next;
1844 roff_node_delete(NULL, nch);
1845 }
1846 nh->child = NULL;
1847 }
1848
1849 static void
1850 post_bl(POST_ARGS)
1851 {
1852 struct roff_node *nbody; /* of the Bl */
1853 struct roff_node *nchild, *nnext; /* of the Bl body */
1854 const char *prev_Er;
1855 int order;
1856
1857 nbody = mdoc->last;
1858 switch (nbody->type) {
1859 case ROFFT_BLOCK:
1860 post_bl_block(mdoc);
1861 return;
1862 case ROFFT_HEAD:
1863 post_bl_head(mdoc);
1864 return;
1865 case ROFFT_BODY:
1866 break;
1867 default:
1868 return;
1869 }
1870 if (nbody->end != ENDBODY_NOT)
1871 return;
1872
1873 /*
1874 * Up to the first item, move nodes before the list,
1875 * but leave transparent nodes where they are
1876 * if they precede an item.
1877 * The next non-transparent node is kept in nchild.
1878 * It only needs to be updated after a non-transparent
1879 * node was moved out, and at the very beginning
1880 * when no node at all was moved yet.
1881 */
1882
1883 nchild = mdoc->last;
1884 for (;;) {
1885 if (nchild == mdoc->last)
1886 nchild = roff_node_child(nbody);
1887 if (nchild == NULL) {
1888 mdoc->last = nbody;
1889 mandoc_msg(MANDOCERR_BLK_EMPTY,
1890 nbody->line, nbody->pos, "Bl");
1891 return;
1892 }
1893 if (nchild->tok == MDOC_It) {
1894 mdoc->last = nbody;
1895 break;
1896 }
1897 mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
1898 nbody->child->pos, "%s", roff_name[nbody->child->tok]);
1899 if (nbody->parent->prev == NULL) {
1900 mdoc->last = nbody->parent->parent;
1901 mdoc->next = ROFF_NEXT_CHILD;
1902 } else {
1903 mdoc->last = nbody->parent->prev;
1904 mdoc->next = ROFF_NEXT_SIBLING;
1905 }
1906 roff_node_relink(mdoc, nbody->child);
1907 }
1908
1909 /*
1910 * We have reached the first item,
1911 * so moving nodes out is no longer possible.
1912 * But in .Bl -column, the first rows may be implicit,
1913 * that is, they may not start with .It macros.
1914 * Such rows may be followed by nodes generated on the
1915 * roff level, for example .TS.
1916 * Wrap such roff nodes into an implicit row.
1917 */
1918
1919 while (nchild != NULL) {
1920 if (nchild->tok == MDOC_It) {
1921 nchild = roff_node_next(nchild);
1922 continue;
1923 }
1924 nnext = nchild->next;
1925 mdoc->last = nchild->prev;
1926 mdoc->next = ROFF_NEXT_SIBLING;
1927 roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1928 roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1929 mdoc->next = ROFF_NEXT_SIBLING;
1930 roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
1931 while (nchild->tok != MDOC_It) {
1932 roff_node_relink(mdoc, nchild);
1933 if (nnext == NULL)
1934 break;
1935 nchild = nnext;
1936 nnext = nchild->next;
1937 mdoc->next = ROFF_NEXT_SIBLING;
1938 }
1939 mdoc->last = nbody;
1940 }
1941
1942 if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1943 return;
1944
1945 prev_Er = NULL;
1946 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1947 if (nchild->tok != MDOC_It)
1948 continue;
1949 if ((nnext = nchild->head->child) == NULL)
1950 continue;
1951 if (nnext->type == ROFFT_BLOCK)
1952 nnext = nnext->body->child;
1953 if (nnext == NULL || nnext->tok != MDOC_Er)
1954 continue;
1955 nnext = nnext->child;
1956 if (prev_Er != NULL) {
1957 order = strcmp(prev_Er, nnext->string);
1958 if (order > 0)
1959 mandoc_msg(MANDOCERR_ER_ORDER,
1960 nnext->line, nnext->pos,
1961 "Er %s %s (NetBSD)",
1962 prev_Er, nnext->string);
1963 else if (order == 0)
1964 mandoc_msg(MANDOCERR_ER_REP,
1965 nnext->line, nnext->pos,
1966 "Er %s (NetBSD)", prev_Er);
1967 }
1968 prev_Er = nnext->string;
1969 }
1970 }
1971
1972 static void
1973 post_bk(POST_ARGS)
1974 {
1975 struct roff_node *n;
1976
1977 n = mdoc->last;
1978
1979 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1980 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
1981 roff_node_delete(mdoc, n);
1982 }
1983 }
1984
1985 static void
1986 post_sm(POST_ARGS)
1987 {
1988 struct roff_node *nch;
1989
1990 nch = mdoc->last->child;
1991
1992 if (nch == NULL) {
1993 mdoc->flags ^= MDOC_SMOFF;
1994 return;
1995 }
1996
1997 assert(nch->type == ROFFT_TEXT);
1998
1999 if ( ! strcmp(nch->string, "on")) {
2000 mdoc->flags &= ~MDOC_SMOFF;
2001 return;
2002 }
2003 if ( ! strcmp(nch->string, "off")) {
2004 mdoc->flags |= MDOC_SMOFF;
2005 return;
2006 }
2007
2008 mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
2009 "%s %s", roff_name[mdoc->last->tok], nch->string);
2010 roff_node_relink(mdoc, nch);
2011 return;
2012 }
2013
2014 static void
2015 post_root(POST_ARGS)
2016 {
2017 struct roff_node *n;
2018
2019 /* Add missing prologue data. */
2020
2021 if (mdoc->meta.date == NULL)
2022 mdoc->meta.date = mandoc_normdate(NULL, NULL);
2023
2024 if (mdoc->meta.title == NULL) {
2025 mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
2026 mdoc->meta.title = mandoc_strdup("UNTITLED");
2027 }
2028
2029 if (mdoc->meta.vol == NULL)
2030 mdoc->meta.vol = mandoc_strdup("LOCAL");
2031
2032 if (mdoc->meta.os == NULL) {
2033 mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
2034 mdoc->meta.os = mandoc_strdup("");
2035 } else if (mdoc->meta.os_e &&
2036 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
2037 mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
2038 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2039 "(OpenBSD)" : "(NetBSD)");
2040
2041 if (mdoc->meta.arch != NULL &&
2042 arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
2043 n = mdoc->meta.first->child;
2044 while (n->tok != MDOC_Dt ||
2045 n->child == NULL ||
2046 n->child->next == NULL ||
2047 n->child->next->next == NULL)
2048 n = n->next;
2049 n = n->child->next->next;
2050 mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
2051 "Dt ... %s %s", mdoc->meta.arch,
2052 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2053 "(OpenBSD)" : "(NetBSD)");
2054 }
2055
2056 /* Check that we begin with a proper `Sh'. */
2057
2058 n = mdoc->meta.first->child;
2059 while (n != NULL &&
2060 (n->type == ROFFT_COMMENT ||
2061 (n->tok >= MDOC_Dd &&
2062 mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
2063 n = n->next;
2064
2065 if (n == NULL)
2066 mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
2067 else if (n->tok != MDOC_Sh)
2068 mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
2069 "%s", roff_name[n->tok]);
2070 }
2071
2072 static void
2073 post_rs(POST_ARGS)
2074 {
2075 struct roff_node *np, *nch, *next, *prev;
2076 int i, j;
2077
2078 np = mdoc->last;
2079
2080 if (np->type != ROFFT_BODY)
2081 return;
2082
2083 if (np->child == NULL) {
2084 mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
2085 return;
2086 }
2087
2088 /*
2089 * The full `Rs' block needs special handling to order the
2090 * sub-elements according to `rsord'. Pick through each element
2091 * and correctly order it. This is an insertion sort.
2092 */
2093
2094 next = NULL;
2095 for (nch = np->child->next; nch != NULL; nch = next) {
2096 /* Determine order number of this child. */
2097 for (i = 0; i < RSORD_MAX; i++)
2098 if (rsord[i] == nch->tok)
2099 break;
2100
2101 if (i == RSORD_MAX) {
2102 mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
2103 "%s", roff_name[nch->tok]);
2104 i = -1;
2105 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
2106 np->norm->Rs.quote_T++;
2107
2108 /*
2109 * Remove this child from the chain. This somewhat
2110 * repeats roff_node_unlink(), but since we're
2111 * just re-ordering, there's no need for the
2112 * full unlink process.
2113 */
2114
2115 if ((next = nch->next) != NULL)
2116 next->prev = nch->prev;
2117
2118 if ((prev = nch->prev) != NULL)
2119 prev->next = nch->next;
2120
2121 nch->prev = nch->next = NULL;
2122
2123 /*
2124 * Scan back until we reach a node that's
2125 * to be ordered before this child.
2126 */
2127
2128 for ( ; prev ; prev = prev->prev) {
2129 /* Determine order of `prev'. */
2130 for (j = 0; j < RSORD_MAX; j++)
2131 if (rsord[j] == prev->tok)
2132 break;
2133 if (j == RSORD_MAX)
2134 j = -1;
2135
2136 if (j <= i)
2137 break;
2138 }
2139
2140 /*
2141 * Set this child back into its correct place
2142 * in front of the `prev' node.
2143 */
2144
2145 nch->prev = prev;
2146
2147 if (prev == NULL) {
2148 np->child->prev = nch;
2149 nch->next = np->child;
2150 np->child = nch;
2151 } else {
2152 if (prev->next)
2153 prev->next->prev = nch;
2154 nch->next = prev->next;
2155 prev->next = nch;
2156 }
2157 }
2158 }
2159
2160 /*
2161 * For some arguments of some macros,
2162 * convert all breakable hyphens into ASCII_HYPH.
2163 */
2164 static void
2165 post_hyph(POST_ARGS)
2166 {
2167 struct roff_node *nch;
2168 char *cp;
2169
2170 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2171 if (nch->type != ROFFT_TEXT)
2172 continue;
2173 cp = nch->string;
2174 if (*cp == '\0')
2175 continue;
2176 while (*(++cp) != '\0')
2177 if (*cp == '-' &&
2178 isalpha((unsigned char)cp[-1]) &&
2179 isalpha((unsigned char)cp[1]))
2180 *cp = ASCII_HYPH;
2181 }
2182 }
2183
2184 static void
2185 post_ns(POST_ARGS)
2186 {
2187 struct roff_node *n;
2188
2189 n = mdoc->last;
2190 if (n->flags & NODE_LINE ||
2191 (n->next != NULL && n->next->flags & NODE_DELIMC))
2192 mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2193 }
2194
2195 static void
2196 post_sx(POST_ARGS)
2197 {
2198 post_delim(mdoc);
2199 post_hyph(mdoc);
2200 }
2201
2202 static void
2203 post_sh(POST_ARGS)
2204 {
2205 post_section(mdoc);
2206
2207 switch (mdoc->last->type) {
2208 case ROFFT_HEAD:
2209 post_sh_head(mdoc);
2210 break;
2211 case ROFFT_BODY:
2212 switch (mdoc->lastsec) {
2213 case SEC_NAME:
2214 post_sh_name(mdoc);
2215 break;
2216 case SEC_SEE_ALSO:
2217 post_sh_see_also(mdoc);
2218 break;
2219 case SEC_AUTHORS:
2220 post_sh_authors(mdoc);
2221 break;
2222 default:
2223 break;
2224 }
2225 break;
2226 default:
2227 break;
2228 }
2229 }
2230
2231 static void
2232 post_sh_name(POST_ARGS)
2233 {
2234 struct roff_node *n;
2235 int hasnm, hasnd;
2236
2237 hasnm = hasnd = 0;
2238
2239 for (n = mdoc->last->child; n != NULL; n = n->next) {
2240 switch (n->tok) {
2241 case MDOC_Nm:
2242 if (hasnm && n->child != NULL)
2243 mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2244 n->line, n->pos,
2245 "Nm %s", n->child->string);
2246 hasnm = 1;
2247 continue;
2248 case MDOC_Nd:
2249 hasnd = 1;
2250 if (n->next != NULL)
2251 mandoc_msg(MANDOCERR_NAMESEC_ND,
2252 n->line, n->pos, NULL);
2253 break;
2254 case TOKEN_NONE:
2255 if (n->type == ROFFT_TEXT &&
2256 n->string[0] == ',' && n->string[1] == '\0' &&
2257 n->next != NULL && n->next->tok == MDOC_Nm) {
2258 n = n->next;
2259 continue;
2260 }
2261 /* FALLTHROUGH */
2262 default:
2263 mandoc_msg(MANDOCERR_NAMESEC_BAD,
2264 n->line, n->pos, "%s", roff_name[n->tok]);
2265 continue;
2266 }
2267 break;
2268 }
2269
2270 if ( ! hasnm)
2271 mandoc_msg(MANDOCERR_NAMESEC_NONM,
2272 mdoc->last->line, mdoc->last->pos, NULL);
2273 if ( ! hasnd)
2274 mandoc_msg(MANDOCERR_NAMESEC_NOND,
2275 mdoc->last->line, mdoc->last->pos, NULL);
2276 }
2277
2278 static void
2279 post_sh_see_also(POST_ARGS)
2280 {
2281 const struct roff_node *n;
2282 const char *name, *sec;
2283 const char *lastname, *lastsec, *lastpunct;
2284 int cmp;
2285
2286 n = mdoc->last->child;
2287 lastname = lastsec = lastpunct = NULL;
2288 while (n != NULL) {
2289 if (n->tok != MDOC_Xr ||
2290 n->child == NULL ||
2291 n->child->next == NULL)
2292 break;
2293
2294 /* Process one .Xr node. */
2295
2296 name = n->child->string;
2297 sec = n->child->next->string;
2298 if (lastsec != NULL) {
2299 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2300 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2301 n->pos, "%s before %s(%s)",
2302 lastpunct, name, sec);
2303 cmp = strcmp(lastsec, sec);
2304 if (cmp > 0)
2305 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2306 n->pos, "%s(%s) after %s(%s)",
2307 name, sec, lastname, lastsec);
2308 else if (cmp == 0 &&
2309 strcasecmp(lastname, name) > 0)
2310 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2311 n->pos, "%s after %s", name, lastname);
2312 }
2313 lastname = name;
2314 lastsec = sec;
2315
2316 /* Process the following node. */
2317
2318 n = n->next;
2319 if (n == NULL)
2320 break;
2321 if (n->tok == MDOC_Xr) {
2322 lastpunct = "none";
2323 continue;
2324 }
2325 if (n->type != ROFFT_TEXT)
2326 break;
2327 for (name = n->string; *name != '\0'; name++)
2328 if (isalpha((const unsigned char)*name))
2329 return;
2330 lastpunct = n->string;
2331 if (n->next == NULL || n->next->tok == MDOC_Rs)
2332 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2333 n->pos, "%s after %s(%s)",
2334 lastpunct, lastname, lastsec);
2335 n = n->next;
2336 }
2337 }
2338
2339 static int
2340 child_an(const struct roff_node *n)
2341 {
2342
2343 for (n = n->child; n != NULL; n = n->next)
2344 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2345 return 1;
2346 return 0;
2347 }
2348
2349 static void
2350 post_sh_authors(POST_ARGS)
2351 {
2352
2353 if ( ! child_an(mdoc->last))
2354 mandoc_msg(MANDOCERR_AN_MISSING,
2355 mdoc->last->line, mdoc->last->pos, NULL);
2356 }
2357
2358 /*
2359 * Return an upper bound for the string distance (allowing
2360 * transpositions). Not a full Levenshtein implementation
2361 * because Levenshtein is quadratic in the string length
2362 * and this function is called for every standard name,
2363 * so the check for each custom name would be cubic.
2364 * The following crude heuristics is linear, resulting
2365 * in quadratic behaviour for checking one custom name,
2366 * which does not cause measurable slowdown.
2367 */
2368 static int
2369 similar(const char *s1, const char *s2)
2370 {
2371 const int maxdist = 3;
2372 int dist = 0;
2373
2374 while (s1[0] != '\0' && s2[0] != '\0') {
2375 if (s1[0] == s2[0]) {
2376 s1++;
2377 s2++;
2378 continue;
2379 }
2380 if (++dist > maxdist)
2381 return INT_MAX;
2382 if (s1[1] == s2[1]) { /* replacement */
2383 s1++;
2384 s2++;
2385 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2386 s1 += 2; /* transposition */
2387 s2 += 2;
2388 } else if (s1[0] == s2[1]) /* insertion */
2389 s2++;
2390 else if (s1[1] == s2[0]) /* deletion */
2391 s1++;
2392 else
2393 return INT_MAX;
2394 }
2395 dist += strlen(s1) + strlen(s2);
2396 return dist > maxdist ? INT_MAX : dist;
2397 }
2398
2399 static void
2400 post_sh_head(POST_ARGS)
2401 {
2402 struct roff_node *nch;
2403 const char *goodsec;
2404 const char *const *testsec;
2405 int dist, mindist;
2406 enum roff_sec sec;
2407
2408 /*
2409 * Process a new section. Sections are either "named" or
2410 * "custom". Custom sections are user-defined, while named ones
2411 * follow a conventional order and may only appear in certain
2412 * manual sections.
2413 */
2414
2415 sec = mdoc->last->sec;
2416
2417 /* The NAME should be first. */
2418
2419 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2420 mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2421 mdoc->last->line, mdoc->last->pos, "Sh %s",
2422 sec != SEC_CUSTOM ? secnames[sec] :
2423 (nch = mdoc->last->child) == NULL ? "" :
2424 nch->type == ROFFT_TEXT ? nch->string :
2425 roff_name[nch->tok]);
2426
2427 /* The SYNOPSIS gets special attention in other areas. */
2428
2429 if (sec == SEC_SYNOPSIS) {
2430 roff_setreg(mdoc->roff, "nS", 1, '=');
2431 mdoc->flags |= MDOC_SYNOPSIS;
2432 } else {
2433 roff_setreg(mdoc->roff, "nS", 0, '=');
2434 mdoc->flags &= ~MDOC_SYNOPSIS;
2435 }
2436 if (sec == SEC_DESCRIPTION)
2437 fn_prio = TAG_STRONG;
2438
2439 /* Mark our last section. */
2440
2441 mdoc->lastsec = sec;
2442
2443 /* We don't care about custom sections after this. */
2444
2445 if (sec == SEC_CUSTOM) {
2446 if ((nch = mdoc->last->child) == NULL ||
2447 nch->type != ROFFT_TEXT || nch->next != NULL)
2448 return;
2449 goodsec = NULL;
2450 mindist = INT_MAX;
2451 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2452 dist = similar(nch->string, *testsec);
2453 if (dist < mindist) {
2454 goodsec = *testsec;
2455 mindist = dist;
2456 }
2457 }
2458 if (goodsec != NULL)
2459 mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2460 "Sh %s instead of %s", nch->string, goodsec);
2461 return;
2462 }
2463
2464 /*
2465 * Check whether our non-custom section is being repeated or is
2466 * out of order.
2467 */
2468
2469 if (sec == mdoc->lastnamed)
2470 mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2471 mdoc->last->pos, "Sh %s", secnames[sec]);
2472
2473 if (sec < mdoc->lastnamed)
2474 mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2475 mdoc->last->pos, "Sh %s", secnames[sec]);
2476
2477 /* Mark the last named section. */
2478
2479 mdoc->lastnamed = sec;
2480
2481 /* Check particular section/manual conventions. */
2482
2483 if (mdoc->meta.msec == NULL)
2484 return;
2485
2486 goodsec = NULL;
2487 switch (sec) {
2488 case SEC_ERRORS:
2489 if (*mdoc->meta.msec == '4')
2490 break;
2491 goodsec = "2, 3, 4, 9";
2492 /* FALLTHROUGH */
2493 case SEC_RETURN_VALUES:
2494 case SEC_LIBRARY:
2495 if (*mdoc->meta.msec == '2')
2496 break;
2497 if (*mdoc->meta.msec == '3')
2498 break;
2499 if (NULL == goodsec)
2500 goodsec = "2, 3, 9";
2501 /* FALLTHROUGH */
2502 case SEC_CONTEXT:
2503 if (*mdoc->meta.msec == '9')
2504 break;
2505 if (NULL == goodsec)
2506 goodsec = "9";
2507 mandoc_msg(MANDOCERR_SEC_MSEC,
2508 mdoc->last->line, mdoc->last->pos,
2509 "Sh %s for %s only", secnames[sec], goodsec);
2510 break;
2511 default:
2512 break;
2513 }
2514 }
2515
2516 static void
2517 post_xr(POST_ARGS)
2518 {
2519 struct roff_node *n, *nch;
2520
2521 n = mdoc->last;
2522 nch = n->child;
2523 if (nch->next == NULL) {
2524 mandoc_msg(MANDOCERR_XR_NOSEC,
2525 n->line, n->pos, "Xr %s", nch->string);
2526 } else {
2527 assert(nch->next == n->last);
2528 if(mandoc_xr_add(nch->next->string, nch->string,
2529 nch->line, nch->pos))
2530 mandoc_msg(MANDOCERR_XR_SELF,
2531 nch->line, nch->pos, "Xr %s %s",
2532 nch->string, nch->next->string);
2533 }
2534 post_delim_nb(mdoc);
2535 }
2536
2537 static void
2538 post_section(POST_ARGS)
2539 {
2540 struct roff_node *n, *nch;
2541 char *cp, *tag;
2542
2543 n = mdoc->last;
2544 switch (n->type) {
2545 case ROFFT_BLOCK:
2546 post_prevpar(mdoc);
2547 return;
2548 case ROFFT_HEAD:
2549 tag = NULL;
2550 deroff(&tag, n);
2551 if (tag != NULL) {
2552 for (cp = tag; *cp != '\0'; cp++)
2553 if (*cp == ' ')
2554 *cp = '_';
2555 if ((nch = n->child) != NULL &&
2556 nch->type == ROFFT_TEXT &&
2557 strcmp(nch->string, tag) == 0)
2558 tag_put(NULL, TAG_WEAK, n);
2559 else
2560 tag_put(tag, TAG_FALLBACK, n);
2561 free(tag);
2562 }
2563 post_delim(mdoc);
2564 post_hyph(mdoc);
2565 return;
2566 case ROFFT_BODY:
2567 break;
2568 default:
2569 return;
2570 }
2571 if ((nch = n->child) != NULL &&
2572 (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
2573 nch->tok == ROFF_sp)) {
2574 mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2575 "%s after %s", roff_name[nch->tok],
2576 roff_name[n->tok]);
2577 roff_node_delete(mdoc, nch);
2578 }
2579 if ((nch = n->last) != NULL &&
2580 (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
2581 mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2582 "%s at the end of %s", roff_name[nch->tok],
2583 roff_name[n->tok]);
2584 roff_node_delete(mdoc, nch);
2585 }
2586 }
2587
2588 static void
2589 post_prevpar(POST_ARGS)
2590 {
2591 struct roff_node *n, *np;
2592
2593 n = mdoc->last;
2594 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2595 return;
2596 if ((np = roff_node_prev(n)) == NULL)
2597 return;
2598
2599 /*
2600 * Don't allow `Pp' prior to a paragraph-type
2601 * block: `Pp' or non-compact `Bd' or `Bl'.
2602 */
2603
2604 if (np->tok != MDOC_Pp && np->tok != ROFF_br)
2605 return;
2606 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2607 return;
2608 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2609 return;
2610 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2611 return;
2612
2613 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2614 "%s before %s", roff_name[np->tok], roff_name[n->tok]);
2615 roff_node_delete(mdoc, np);
2616 }
2617
2618 static void
2619 post_par(POST_ARGS)
2620 {
2621 struct roff_node *np;
2622
2623 fn_prio = TAG_STRONG;
2624 post_prevpar(mdoc);
2625
2626 np = mdoc->last;
2627 if (np->child != NULL)
2628 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2629 "%s %s", roff_name[np->tok], np->child->string);
2630 }
2631
2632 static void
2633 post_dd(POST_ARGS)
2634 {
2635 struct roff_node *n;
2636
2637 n = mdoc->last;
2638 n->flags |= NODE_NOPRT;
2639
2640 if (mdoc->meta.date != NULL) {
2641 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2642 free(mdoc->meta.date);
2643 } else if (mdoc->flags & MDOC_PBODY)
2644 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2645 else if (mdoc->meta.title != NULL)
2646 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2647 n->line, n->pos, "Dd after Dt");
2648 else if (mdoc->meta.os != NULL)
2649 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2650 n->line, n->pos, "Dd after Os");
2651
2652 if (mdoc->quick && n != NULL)
2653 mdoc->meta.date = mandoc_strdup("");
2654 else
2655 mdoc->meta.date = mandoc_normdate(n->child, n);
2656 }
2657
2658 static void
2659 post_dt(POST_ARGS)
2660 {
2661 struct roff_node *nn, *n;
2662 const char *cp;
2663 char *p;
2664
2665 n = mdoc->last;
2666 n->flags |= NODE_NOPRT;
2667
2668 if (mdoc->flags & MDOC_PBODY) {
2669 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2670 return;
2671 }
2672
2673 if (mdoc->meta.title != NULL)
2674 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2675 else if (mdoc->meta.os != NULL)
2676 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2677 n->line, n->pos, "Dt after Os");
2678
2679 free(mdoc->meta.title);
2680 free(mdoc->meta.msec);
2681 free(mdoc->meta.vol);
2682 free(mdoc->meta.arch);
2683
2684 mdoc->meta.title = NULL;
2685 mdoc->meta.msec = NULL;
2686 mdoc->meta.vol = NULL;
2687 mdoc->meta.arch = NULL;
2688
2689 /* Mandatory first argument: title. */
2690
2691 nn = n->child;
2692 if (nn == NULL || *nn->string == '\0') {
2693 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2694 mdoc->meta.title = mandoc_strdup("UNTITLED");
2695 } else {
2696 mdoc->meta.title = mandoc_strdup(nn->string);
2697
2698 /* Check that all characters are uppercase. */
2699
2700 for (p = nn->string; *p != '\0'; p++)
2701 if (islower((unsigned char)*p)) {
2702 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2703 nn->pos + (int)(p - nn->string),
2704 "Dt %s", nn->string);
2705 break;
2706 }
2707 }
2708
2709 /* Mandatory second argument: section. */
2710
2711 if (nn != NULL)
2712 nn = nn->next;
2713
2714 if (nn == NULL) {
2715 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2716 "Dt %s", mdoc->meta.title);
2717 mdoc->meta.vol = mandoc_strdup("LOCAL");
2718 return; /* msec and arch remain NULL. */
2719 }
2720
2721 mdoc->meta.msec = mandoc_strdup(nn->string);
2722
2723 /* Infer volume title from section number. */
2724
2725 cp = mandoc_a2msec(nn->string);
2726 if (cp == NULL) {
2727 mandoc_msg(MANDOCERR_MSEC_BAD,
2728 nn->line, nn->pos, "Dt ... %s", nn->string);
2729 mdoc->meta.vol = mandoc_strdup(nn->string);
2730 } else
2731 mdoc->meta.vol = mandoc_strdup(cp);
2732
2733 /* Optional third argument: architecture. */
2734
2735 if ((nn = nn->next) == NULL)
2736 return;
2737
2738 for (p = nn->string; *p != '\0'; p++)
2739 *p = tolower((unsigned char)*p);
2740 mdoc->meta.arch = mandoc_strdup(nn->string);
2741
2742 /* Ignore fourth and later arguments. */
2743
2744 if ((nn = nn->next) != NULL)
2745 mandoc_msg(MANDOCERR_ARG_EXCESS,
2746 nn->line, nn->pos, "Dt ... %s", nn->string);
2747 }
2748
2749 static void
2750 post_bx(POST_ARGS)
2751 {
2752 struct roff_node *n, *nch;
2753 const char *macro;
2754
2755 post_delim_nb(mdoc);
2756
2757 n = mdoc->last;
2758 nch = n->child;
2759
2760 if (nch != NULL) {
2761 macro = !strcmp(nch->string, "Open") ? "Ox" :
2762 !strcmp(nch->string, "Net") ? "Nx" :
2763 !strcmp(nch->string, "Free") ? "Fx" :
2764 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2765 if (macro != NULL)
2766 mandoc_msg(MANDOCERR_BX,
2767 n->line, n->pos, "%s", macro);
2768 mdoc->last = nch;
2769 nch = nch->next;
2770 mdoc->next = ROFF_NEXT_SIBLING;
2771 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2772 mdoc->last->flags |= NODE_NOSRC;
2773 mdoc->next = ROFF_NEXT_SIBLING;
2774 } else
2775 mdoc->next = ROFF_NEXT_CHILD;
2776 roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2777 mdoc->last->flags |= NODE_NOSRC;
2778
2779 if (nch == NULL) {
2780 mdoc->last = n;
2781 return;
2782 }
2783
2784 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2785 mdoc->last->flags |= NODE_NOSRC;
2786 mdoc->next = ROFF_NEXT_SIBLING;
2787 roff_word_alloc(mdoc, n->line, n->pos, "-");
2788 mdoc->last->flags |= NODE_NOSRC;
2789 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2790 mdoc->last->flags |= NODE_NOSRC;
2791 mdoc->last = n;
2792
2793 /*
2794 * Make `Bx's second argument always start with an uppercase
2795 * letter. Groff checks if it's an "accepted" term, but we just
2796 * uppercase blindly.
2797 */
2798
2799 *nch->string = (char)toupper((unsigned char)*nch->string);
2800 }
2801
2802 static void
2803 post_os(POST_ARGS)
2804 {
2805 #ifndef OSNAME
2806 struct utsname utsname;
2807 static char *defbuf;
2808 #endif
2809 struct roff_node *n;
2810
2811 n = mdoc->last;
2812 n->flags |= NODE_NOPRT;
2813
2814 if (mdoc->meta.os != NULL)
2815 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2816 else if (mdoc->flags & MDOC_PBODY)
2817 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2818
2819 post_delim(mdoc);
2820
2821 /*
2822 * Set the operating system by way of the `Os' macro.
2823 * The order of precedence is:
2824 * 1. the argument of the `Os' macro, unless empty
2825 * 2. the -Ios=foo command line argument, if provided
2826 * 3. -DOSNAME="\"foo\"", if provided during compilation
2827 * 4. "sysname release" from uname(3)
2828 */
2829
2830 free(mdoc->meta.os);
2831 mdoc->meta.os = NULL;
2832 deroff(&mdoc->meta.os, n);
2833 if (mdoc->meta.os)
2834 goto out;
2835
2836 if (mdoc->os_s != NULL) {
2837 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2838 goto out;
2839 }
2840
2841 #ifdef OSNAME
2842 mdoc->meta.os = mandoc_strdup(OSNAME);
2843 #else /*!OSNAME */
2844 if (defbuf == NULL) {
2845 if (uname(&utsname) == -1) {
2846 mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2847 defbuf = mandoc_strdup("UNKNOWN");
2848 } else
2849 mandoc_asprintf(&defbuf, "%s %s",
2850 utsname.sysname, utsname.release);
2851 }
2852 mdoc->meta.os = mandoc_strdup(defbuf);
2853 #endif /*!OSNAME*/
2854
2855 out:
2856 if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2857 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2858 mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2859 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2860 mdoc->meta.os_e = MANDOC_OS_NETBSD;
2861 }
2862
2863 /*
2864 * This is the earliest point where we can check
2865 * Mdocdate conventions because we don't know
2866 * the operating system earlier.
2867 */
2868
2869 if (n->child != NULL)
2870 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
2871 "Os %s (%s)", n->child->string,
2872 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2873 "OpenBSD" : "NetBSD");
2874
2875 while (n->tok != MDOC_Dd)
2876 if ((n = n->prev) == NULL)
2877 return;
2878 if ((n = n->child) == NULL)
2879 return;
2880 if (strncmp(n->string, "$" "Mdocdate", 9)) {
2881 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2882 mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
2883 n->pos, "Dd %s (OpenBSD)", n->string);
2884 } else {
2885 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2886 mandoc_msg(MANDOCERR_MDOCDATE, n->line,
2887 n->pos, "Dd %s (NetBSD)", n->string);
2888 }
2889 }
2890
2891 enum roff_sec
2892 mdoc_a2sec(const char *p)
2893 {
2894 int i;
2895
2896 for (i = 0; i < (int)SEC__MAX; i++)
2897 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2898 return (enum roff_sec)i;
2899
2900 return SEC_CUSTOM;
2901 }
2902
2903 static size_t
2904 macro2len(enum roff_tok macro)
2905 {
2906
2907 switch (macro) {
2908 case MDOC_Ad:
2909 return 12;
2910 case MDOC_Ao:
2911 return 12;
2912 case MDOC_An:
2913 return 12;
2914 case MDOC_Aq:
2915 return 12;
2916 case MDOC_Ar:
2917 return 12;
2918 case MDOC_Bo:
2919 return 12;
2920 case MDOC_Bq:
2921 return 12;
2922 case MDOC_Cd:
2923 return 12;
2924 case MDOC_Cm:
2925 return 10;
2926 case MDOC_Do:
2927 return 10;
2928 case MDOC_Dq:
2929 return 12;
2930 case MDOC_Dv:
2931 return 12;
2932 case MDOC_Eo:
2933 return 12;
2934 case MDOC_Em:
2935 return 10;
2936 case MDOC_Er:
2937 return 17;
2938 case MDOC_Ev:
2939 return 15;
2940 case MDOC_Fa:
2941 return 12;
2942 case MDOC_Fl:
2943 return 10;
2944 case MDOC_Fo:
2945 return 16;
2946 case MDOC_Fn:
2947 return 16;
2948 case MDOC_Ic:
2949 return 10;
2950 case MDOC_Li:
2951 return 16;
2952 case MDOC_Ms:
2953 return 6;
2954 case MDOC_Nm:
2955 return 10;
2956 case MDOC_No:
2957 return 12;
2958 case MDOC_Oo:
2959 return 10;
2960 case MDOC_Op:
2961 return 14;
2962 case MDOC_Pa:
2963 return 32;
2964 case MDOC_Pf:
2965 return 12;
2966 case MDOC_Po:
2967 return 12;
2968 case MDOC_Pq:
2969 return 12;
2970 case MDOC_Ql:
2971 return 16;
2972 case MDOC_Qo:
2973 return 12;
2974 case MDOC_So:
2975 return 12;
2976 case MDOC_Sq:
2977 return 12;
2978 case MDOC_Sy:
2979 return 6;
2980 case MDOC_Sx:
2981 return 16;
2982 case MDOC_Tn:
2983 return 10;
2984 case MDOC_Va:
2985 return 12;
2986 case MDOC_Vt:
2987 return 12;
2988 case MDOC_Xr:
2989 return 10;
2990 default:
2991 break;
2992 };
2993 return 0;
2994 }