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