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