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