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