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