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