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