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