1 /* $Id: mdoc_validate.c,v 1.393 2025/06/05 12:38:26 schwarze Exp $ */
2 /*
3 * Copyright (c) 2010-2022, 2025 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, *nch;
995 const char *ccp;
996 char *cp;
997
998 post_delim_nb(mdoc);
999
1000 n = mdoc->last;
1001 nch = n->child;
1002 assert(nch->type == ROFFT_TEXT);
1003 mdoc->next = ROFF_NEXT_CHILD;
1004
1005 if (n->sec == SEC_SYNOPSIS) {
1006 roff_word_alloc(mdoc, n->line, n->pos, "/*");
1007 mdoc->last->flags = NODE_NOSRC;
1008 while (nch != NULL) {
1009 roff_word_alloc(mdoc, n->line, n->pos, "-l");
1010 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
1011 mdoc->last = nch;
1012 assert(nch->type == ROFFT_TEXT);
1013 cp = nch->string;
1014 if (strncmp(cp, "lib", 3) == 0)
1015 memmove(cp, cp + 3, strlen(cp) - 3 + 1);
1016 nch = nch->next;
1017 }
1018 roff_word_alloc(mdoc, n->line, n->pos, "*/");
1019 mdoc->last->flags = NODE_NOSRC;
1020 mdoc->last = n;
1021 return;
1022 }
1023
1024 if ((ccp = mdoc_a2lib(n->child->string)) != NULL) {
1025 n->child->flags |= NODE_NOPRT;
1026 roff_word_alloc(mdoc, n->line, n->pos, ccp);
1027 mdoc->last->flags = NODE_NOSRC;
1028 mdoc->last = n;
1029 return;
1030 }
1031
1032 mandoc_msg(MANDOCERR_LB_BAD, n->child->line,
1033 n->child->pos, "Lb %s", n->child->string);
1034
1035 roff_word_alloc(mdoc, n->line, n->pos, "library");
1036 mdoc->last->flags = NODE_NOSRC;
1037 roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
1038 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
1039 mdoc->last = mdoc->last->next;
1040 roff_word_alloc(mdoc, n->line, n->pos, "\\(rq");
1041 mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
1042 mdoc->last = n;
1043 }
1044
1045 static void
post_rv(POST_ARGS)1046 post_rv(POST_ARGS)
1047 {
1048 struct roff_node *n;
1049 int ic;
1050
1051 post_std(mdoc);
1052
1053 n = mdoc->last;
1054 mdoc->next = ROFF_NEXT_CHILD;
1055 if (n->child != NULL) {
1056 roff_word_alloc(mdoc, n->line, n->pos, "The");
1057 mdoc->last->flags |= NODE_NOSRC;
1058 ic = build_list(mdoc, MDOC_Fn);
1059 roff_word_alloc(mdoc, n->line, n->pos,
1060 ic > 1 ? "functions return" : "function returns");
1061 mdoc->last->flags |= NODE_NOSRC;
1062 roff_word_alloc(mdoc, n->line, n->pos,
1063 "the value\\~0 if successful;");
1064 } else
1065 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
1066 "completion, the value\\~0 is returned;");
1067 mdoc->last->flags |= NODE_NOSRC;
1068
1069 roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
1070 "the value\\~\\-1 is returned and the global variable");
1071 mdoc->last->flags |= NODE_NOSRC;
1072 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
1073 mdoc->last->flags |= NODE_NOSRC;
1074 roff_word_alloc(mdoc, n->line, n->pos, "errno");
1075 mdoc->last->flags |= NODE_NOSRC;
1076 mdoc->last = mdoc->last->parent;
1077 mdoc->next = ROFF_NEXT_SIBLING;
1078 roff_word_alloc(mdoc, n->line, n->pos,
1079 "is set to indicate the error.");
1080 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
1081 mdoc->last = n;
1082 }
1083
1084 static void
post_std(POST_ARGS)1085 post_std(POST_ARGS)
1086 {
1087 struct roff_node *n;
1088
1089 post_delim(mdoc);
1090
1091 n = mdoc->last;
1092 if (n->args && n->args->argc == 1)
1093 if (n->args->argv[0].arg == MDOC_Std)
1094 return;
1095
1096 mandoc_msg(MANDOCERR_ARG_STD, n->line, n->pos,
1097 "%s", roff_name[n->tok]);
1098 }
1099
1100 static void
post_st(POST_ARGS)1101 post_st(POST_ARGS)
1102 {
1103 struct roff_node *n, *nch;
1104 const char *p;
1105
1106 n = mdoc->last;
1107 nch = n->child;
1108 assert(nch->type == ROFFT_TEXT);
1109
1110 if ((p = mdoc_a2st(nch->string)) == NULL) {
1111 mandoc_msg(MANDOCERR_ST_BAD,
1112 nch->line, nch->pos, "St %s", nch->string);
1113 roff_node_delete(mdoc, n);
1114 return;
1115 }
1116
1117 nch->flags |= NODE_NOPRT;
1118 mdoc->next = ROFF_NEXT_CHILD;
1119 roff_word_alloc(mdoc, nch->line, nch->pos, p);
1120 mdoc->last->flags |= NODE_NOSRC;
1121 mdoc->last= n;
1122 }
1123
1124 static void
post_tg(POST_ARGS)1125 post_tg(POST_ARGS)
1126 {
1127 struct roff_node *n; /* The .Tg node. */
1128 struct roff_node *nch; /* The first child of the .Tg node. */
1129 struct roff_node *nn; /* The next node after the .Tg node. */
1130 struct roff_node *np; /* The parent of the next node. */
1131 struct roff_node *nt; /* The TEXT node containing the tag. */
1132 size_t len; /* The number of bytes in the tag. */
1133
1134 /* Find the next node. */
1135 n = mdoc->last;
1136 for (nn = n; nn != NULL; nn = nn->parent) {
1137 if (nn->type != ROFFT_HEAD && nn->type != ROFFT_BODY &&
1138 nn->type != ROFFT_TAIL && nn->next != NULL) {
1139 nn = nn->next;
1140 break;
1141 }
1142 }
1143
1144 /* Find the tag. */
1145 nt = nch = n->child;
1146 if (nch == NULL && nn != NULL && nn->child != NULL &&
1147 nn->child->type == ROFFT_TEXT)
1148 nt = nn->child;
1149
1150 /* Validate the tag. */
1151 if (nt == NULL || *nt->string == '\0')
1152 mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
1153 if (nt == NULL) {
1154 roff_node_delete(mdoc, n);
1155 return;
1156 }
1157 len = strcspn(nt->string, " \t\\");
1158 if (nt->string[len] != '\0')
1159 mandoc_msg(MANDOCERR_TG_SPC, nt->line,
1160 nt->pos + len, "Tg %s", nt->string);
1161
1162 /* Keep only the first argument. */
1163 if (nch != NULL && nch->next != NULL) {
1164 mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
1165 nch->next->pos, "Tg ... %s", nch->next->string);
1166 while (nch->next != NULL)
1167 roff_node_delete(mdoc, nch->next);
1168 }
1169
1170 /* Drop the macro if the first argument is invalid. */
1171 if (len == 0 || nt->string[len] != '\0') {
1172 roff_node_delete(mdoc, n);
1173 return;
1174 }
1175
1176 /* By default, tag the .Tg node itself. */
1177 if (nn == NULL || nn->flags & NODE_ID)
1178 nn = n;
1179
1180 /* Explicit tagging of specific macros. */
1181 switch (nn->tok) {
1182 case MDOC_Sh:
1183 case MDOC_Ss:
1184 case MDOC_Fo:
1185 nn = nn->head->child == NULL ? n : nn->head;
1186 break;
1187 case MDOC_It:
1188 np = nn->parent;
1189 while (np->tok != MDOC_Bl)
1190 np = np->parent;
1191 switch (np->norm->Bl.type) {
1192 case LIST_column:
1193 break;
1194 case LIST_diag:
1195 case LIST_hang:
1196 case LIST_inset:
1197 case LIST_ohang:
1198 case LIST_tag:
1199 nn = nn->head;
1200 break;
1201 case LIST_bullet:
1202 case LIST_dash:
1203 case LIST_enum:
1204 case LIST_hyphen:
1205 case LIST_item:
1206 nn = nn->body->child == NULL ? n : nn->body;
1207 break;
1208 default:
1209 abort();
1210 }
1211 break;
1212 case MDOC_Bd:
1213 case MDOC_Bl:
1214 case MDOC_D1:
1215 case MDOC_Dl:
1216 nn = nn->body->child == NULL ? n : nn->body;
1217 break;
1218 case MDOC_Pp:
1219 break;
1220 case MDOC_Cm:
1221 case MDOC_Dv:
1222 case MDOC_Em:
1223 case MDOC_Er:
1224 case MDOC_Ev:
1225 case MDOC_Fl:
1226 case MDOC_Fn:
1227 case MDOC_Ic:
1228 case MDOC_Li:
1229 case MDOC_Ms:
1230 case MDOC_No:
1231 case MDOC_Sy:
1232 if (nn->child == NULL)
1233 nn = n;
1234 break;
1235 default:
1236 nn = n;
1237 break;
1238 }
1239 tag_put(nt->string, TAG_MANUAL, nn);
1240 if (nn != n)
1241 n->flags |= NODE_NOPRT;
1242 }
1243
1244 static void
post_obsolete(POST_ARGS)1245 post_obsolete(POST_ARGS)
1246 {
1247 struct roff_node *n;
1248
1249 n = mdoc->last;
1250 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1251 mandoc_msg(MANDOCERR_MACRO_OBS, n->line, n->pos,
1252 "%s", roff_name[n->tok]);
1253 }
1254
1255 static void
post_useless(POST_ARGS)1256 post_useless(POST_ARGS)
1257 {
1258 struct roff_node *n;
1259
1260 n = mdoc->last;
1261 mandoc_msg(MANDOCERR_MACRO_USELESS, n->line, n->pos,
1262 "%s", roff_name[n->tok]);
1263 }
1264
1265 /*
1266 * Block macros.
1267 */
1268
1269 static void
post_bf(POST_ARGS)1270 post_bf(POST_ARGS)
1271 {
1272 struct roff_node *np, *nch;
1273
1274 /*
1275 * Unlike other data pointers, these are "housed" by the HEAD
1276 * element, which contains the goods.
1277 */
1278
1279 np = mdoc->last;
1280 if (np->type != ROFFT_HEAD)
1281 return;
1282
1283 assert(np->parent->type == ROFFT_BLOCK);
1284 assert(np->parent->tok == MDOC_Bf);
1285
1286 /* Check the number of arguments. */
1287
1288 nch = np->child;
1289 if (np->parent->args == NULL) {
1290 if (nch == NULL) {
1291 mandoc_msg(MANDOCERR_BF_NOFONT,
1292 np->line, np->pos, "Bf");
1293 return;
1294 }
1295 nch = nch->next;
1296 }
1297 if (nch != NULL)
1298 mandoc_msg(MANDOCERR_ARG_EXCESS,
1299 nch->line, nch->pos, "Bf ... %s", nch->string);
1300
1301 /* Extract argument into data. */
1302
1303 if (np->parent->args != NULL) {
1304 switch (np->parent->args->argv[0].arg) {
1305 case MDOC_Emphasis:
1306 np->norm->Bf.font = FONT_Em;
1307 break;
1308 case MDOC_Literal:
1309 np->norm->Bf.font = FONT_Li;
1310 break;
1311 case MDOC_Symbolic:
1312 np->norm->Bf.font = FONT_Sy;
1313 break;
1314 default:
1315 abort();
1316 }
1317 return;
1318 }
1319
1320 /* Extract parameter into data. */
1321
1322 if ( ! strcmp(np->child->string, "Em"))
1323 np->norm->Bf.font = FONT_Em;
1324 else if ( ! strcmp(np->child->string, "Li"))
1325 np->norm->Bf.font = FONT_Li;
1326 else if ( ! strcmp(np->child->string, "Sy"))
1327 np->norm->Bf.font = FONT_Sy;
1328 else
1329 mandoc_msg(MANDOCERR_BF_BADFONT, np->child->line,
1330 np->child->pos, "Bf %s", np->child->string);
1331 }
1332
1333 static void
post_fname(POST_ARGS)1334 post_fname(POST_ARGS)
1335 {
1336 struct roff_node *n, *nch;
1337 const char *cp;
1338 size_t pos;
1339
1340 n = mdoc->last;
1341 nch = n->child;
1342 cp = nch->string;
1343 if (*cp == '(') {
1344 if (cp[strlen(cp + 1)] == ')')
1345 return;
1346 pos = 0;
1347 } else {
1348 pos = strcspn(cp, "()");
1349 if (cp[pos] == '\0') {
1350 if (n->sec == SEC_DESCRIPTION ||
1351 n->sec == SEC_CUSTOM)
1352 tag_put(NULL, fn_prio++, n);
1353 return;
1354 }
1355 }
1356 mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
1357 }
1358
1359 static void
post_fn(POST_ARGS)1360 post_fn(POST_ARGS)
1361 {
1362 post_fname(mdoc);
1363 post_fa(mdoc);
1364 }
1365
1366 static void
post_fo(POST_ARGS)1367 post_fo(POST_ARGS)
1368 {
1369 const struct roff_node *n;
1370
1371 n = mdoc->last;
1372
1373 if (n->type != ROFFT_HEAD)
1374 return;
1375
1376 if (n->child == NULL) {
1377 mandoc_msg(MANDOCERR_FO_NOHEAD, n->line, n->pos, "Fo");
1378 return;
1379 }
1380 if (n->child != n->last) {
1381 mandoc_msg(MANDOCERR_ARG_EXCESS,
1382 n->child->next->line, n->child->next->pos,
1383 "Fo ... %s", n->child->next->string);
1384 while (n->child != n->last)
1385 roff_node_delete(mdoc, n->last);
1386 } else
1387 post_delim(mdoc);
1388
1389 post_fname(mdoc);
1390 }
1391
1392 static void
post_fa(POST_ARGS)1393 post_fa(POST_ARGS)
1394 {
1395 const struct roff_node *n;
1396 const char *cp;
1397
1398 for (n = mdoc->last->child; n != NULL; n = n->next) {
1399 for (cp = n->string; *cp != '\0'; cp++) {
1400 /* Ignore callbacks and alterations. */
1401 if (*cp == '(' || *cp == '{')
1402 break;
1403 if (*cp != ',')
1404 continue;
1405 mandoc_msg(MANDOCERR_FA_COMMA, n->line,
1406 n->pos + (int)(cp - n->string), "%s", n->string);
1407 break;
1408 }
1409 }
1410 post_delim_nb(mdoc);
1411 }
1412
1413 static void
post_nm(POST_ARGS)1414 post_nm(POST_ARGS)
1415 {
1416 struct roff_node *n;
1417
1418 n = mdoc->last;
1419
1420 if (n->sec == SEC_NAME && n->child != NULL &&
1421 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1422 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1423
1424 if (n->last != NULL && n->last->tok == MDOC_Pp)
1425 roff_node_relink(mdoc, n->last);
1426
1427 if (mdoc->meta.name == NULL)
1428 deroff(&mdoc->meta.name, n);
1429
1430 if (mdoc->meta.name == NULL ||
1431 (mdoc->lastsec == SEC_NAME && n->child == NULL))
1432 mandoc_msg(MANDOCERR_NM_NONAME, n->line, n->pos, "Nm");
1433
1434 switch (n->type) {
1435 case ROFFT_ELEM:
1436 post_delim_nb(mdoc);
1437 break;
1438 case ROFFT_HEAD:
1439 post_delim(mdoc);
1440 break;
1441 default:
1442 return;
1443 }
1444
1445 if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1446 mdoc->meta.name == NULL)
1447 return;
1448
1449 mdoc->next = ROFF_NEXT_CHILD;
1450 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1451 mdoc->last->flags |= NODE_NOSRC;
1452 mdoc->last = n;
1453 }
1454
1455 static void
post_nd(POST_ARGS)1456 post_nd(POST_ARGS)
1457 {
1458 struct roff_node *n;
1459
1460 n = mdoc->last;
1461
1462 if (n->type != ROFFT_BODY)
1463 return;
1464
1465 if (n->sec != SEC_NAME)
1466 mandoc_msg(MANDOCERR_ND_LATE, n->line, n->pos, "Nd");
1467
1468 if (n->child == NULL)
1469 mandoc_msg(MANDOCERR_ND_EMPTY, n->line, n->pos, "Nd");
1470 else
1471 post_delim(mdoc);
1472
1473 post_hyph(mdoc);
1474 }
1475
1476 static void
post_display(POST_ARGS)1477 post_display(POST_ARGS)
1478 {
1479 struct roff_node *n, *np;
1480
1481 n = mdoc->last;
1482 switch (n->type) {
1483 case ROFFT_BODY:
1484 if (n->end != ENDBODY_NOT) {
1485 if (n->tok == MDOC_Bd &&
1486 n->body->parent->args == NULL)
1487 roff_node_delete(mdoc, n);
1488 } else if (n->child == NULL)
1489 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
1490 "%s", roff_name[n->tok]);
1491 else if (n->tok == MDOC_D1)
1492 post_hyph(mdoc);
1493 break;
1494 case ROFFT_BLOCK:
1495 if (n->tok == MDOC_Bd) {
1496 if (n->args == NULL) {
1497 mandoc_msg(MANDOCERR_BD_NOARG,
1498 n->line, n->pos, "Bd");
1499 mdoc->next = ROFF_NEXT_SIBLING;
1500 while (n->body->child != NULL)
1501 roff_node_relink(mdoc,
1502 n->body->child);
1503 roff_node_delete(mdoc, n);
1504 break;
1505 }
1506 post_bd(mdoc);
1507 post_prevpar(mdoc);
1508 }
1509 for (np = n->parent; np != NULL; np = np->parent) {
1510 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1511 mandoc_msg(MANDOCERR_BD_NEST, n->line,
1512 n->pos, "%s in Bd", roff_name[n->tok]);
1513 break;
1514 }
1515 }
1516 break;
1517 default:
1518 break;
1519 }
1520 }
1521
1522 static void
post_defaults(POST_ARGS)1523 post_defaults(POST_ARGS)
1524 {
1525 struct roff_node *n;
1526
1527 n = mdoc->last;
1528 if (n->child != NULL) {
1529 post_delim_nb(mdoc);
1530 return;
1531 }
1532 mdoc->next = ROFF_NEXT_CHILD;
1533 switch (n->tok) {
1534 case MDOC_Ar:
1535 roff_word_alloc(mdoc, n->line, n->pos, "file");
1536 mdoc->last->flags |= NODE_NOSRC;
1537 roff_word_alloc(mdoc, n->line, n->pos, "...");
1538 break;
1539 case MDOC_Pa:
1540 case MDOC_Mt:
1541 roff_word_alloc(mdoc, n->line, n->pos, "~");
1542 break;
1543 default:
1544 abort();
1545 }
1546 mdoc->last->flags |= NODE_NOSRC;
1547 mdoc->last = n;
1548 }
1549
1550 static void
post_at(POST_ARGS)1551 post_at(POST_ARGS)
1552 {
1553 struct roff_node *n, *nch;
1554 const char *att;
1555
1556 n = mdoc->last;
1557 nch = n->child;
1558
1559 /*
1560 * If we have a child, look it up in the standard keys. If a
1561 * key exist, use that instead of the child; if it doesn't,
1562 * prefix "AT&T UNIX " to the existing data.
1563 */
1564
1565 att = NULL;
1566 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1567 mandoc_msg(MANDOCERR_AT_BAD,
1568 nch->line, nch->pos, "At %s", nch->string);
1569
1570 mdoc->next = ROFF_NEXT_CHILD;
1571 if (att != NULL) {
1572 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1573 nch->flags |= NODE_NOPRT;
1574 } else
1575 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1576 mdoc->last->flags |= NODE_NOSRC;
1577 mdoc->last = n;
1578 }
1579
1580 static void
post_an(POST_ARGS)1581 post_an(POST_ARGS)
1582 {
1583 struct roff_node *np, *nch;
1584
1585 post_an_norm(mdoc);
1586
1587 np = mdoc->last;
1588 nch = np->child;
1589 if (np->norm->An.auth == AUTH__NONE) {
1590 if (nch == NULL)
1591 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1592 np->line, np->pos, "An");
1593 else
1594 post_delim_nb(mdoc);
1595 } else if (nch != NULL)
1596 mandoc_msg(MANDOCERR_ARG_EXCESS,
1597 nch->line, nch->pos, "An ... %s", nch->string);
1598 }
1599
1600 static void
post_em(POST_ARGS)1601 post_em(POST_ARGS)
1602 {
1603 post_tag(mdoc);
1604 tag_put(NULL, TAG_FALLBACK, mdoc->last);
1605 }
1606
1607 static void
post_en(POST_ARGS)1608 post_en(POST_ARGS)
1609 {
1610 post_obsolete(mdoc);
1611 if (mdoc->last->type == ROFFT_BLOCK)
1612 mdoc->last->norm->Es = mdoc->last_es;
1613 }
1614
1615 static void
post_er(POST_ARGS)1616 post_er(POST_ARGS)
1617 {
1618 struct roff_node *n;
1619
1620 n = mdoc->last;
1621 if (n->sec == SEC_ERRORS &&
1622 (n->parent->tok == MDOC_It ||
1623 (n->parent->tok == MDOC_Bq &&
1624 n->parent->parent->parent->tok == MDOC_It)))
1625 tag_put(NULL, TAG_STRONG, n);
1626 post_delim_nb(mdoc);
1627 }
1628
1629 static void
post_tag(POST_ARGS)1630 post_tag(POST_ARGS)
1631 {
1632 struct roff_node *n;
1633
1634 n = mdoc->last;
1635 if ((n->prev == NULL ||
1636 (n->prev->type == ROFFT_TEXT &&
1637 strcmp(n->prev->string, "|") == 0)) &&
1638 (n->parent->tok == MDOC_It ||
1639 (n->parent->tok == MDOC_Xo &&
1640 n->parent->parent->prev == NULL &&
1641 n->parent->parent->parent->tok == MDOC_It)))
1642 tag_put(NULL, TAG_STRONG, n);
1643 post_delim_nb(mdoc);
1644 }
1645
1646 static void
post_es(POST_ARGS)1647 post_es(POST_ARGS)
1648 {
1649 post_obsolete(mdoc);
1650 mdoc->last_es = mdoc->last;
1651 }
1652
1653 static void
post_fl(POST_ARGS)1654 post_fl(POST_ARGS)
1655 {
1656 struct roff_node *n;
1657 char *cp;
1658
1659 /*
1660 * Transform ".Fl Fl long" to ".Fl \-long",
1661 * resulting for example in better HTML output.
1662 */
1663
1664 n = mdoc->last;
1665 if (n->prev != NULL && n->prev->tok == MDOC_Fl &&
1666 n->prev->child == NULL && n->child != NULL &&
1667 (n->flags & NODE_LINE) == 0) {
1668 mandoc_asprintf(&cp, "\\-%s", n->child->string);
1669 free(n->child->string);
1670 n->child->string = cp;
1671 roff_node_delete(mdoc, n->prev);
1672 }
1673 post_tag(mdoc);
1674 }
1675
1676 static void
post_xx(POST_ARGS)1677 post_xx(POST_ARGS)
1678 {
1679 struct roff_node *n;
1680 const char *os;
1681 char *v;
1682
1683 post_delim_nb(mdoc);
1684
1685 n = mdoc->last;
1686 switch (n->tok) {
1687 case MDOC_Bsx:
1688 os = "BSD/OS";
1689 break;
1690 case MDOC_Dx:
1691 os = "DragonFly";
1692 break;
1693 case MDOC_Fx:
1694 os = "FreeBSD";
1695 break;
1696 case MDOC_Nx:
1697 os = "NetBSD";
1698 if (n->child == NULL)
1699 break;
1700 v = n->child->string;
1701 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1702 v[2] < '0' || v[2] > '9' ||
1703 v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1704 break;
1705 n->child->flags |= NODE_NOPRT;
1706 mdoc->next = ROFF_NEXT_CHILD;
1707 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1708 v = mdoc->last->string;
1709 v[3] = toupper((unsigned char)v[3]);
1710 mdoc->last->flags |= NODE_NOSRC;
1711 mdoc->last = n;
1712 break;
1713 case MDOC_Ox:
1714 os = "OpenBSD";
1715 break;
1716 case MDOC_Ux:
1717 os = "UNIX";
1718 break;
1719 default:
1720 abort();
1721 }
1722 mdoc->next = ROFF_NEXT_CHILD;
1723 roff_word_alloc(mdoc, n->line, n->pos, os);
1724 mdoc->last->flags |= NODE_NOSRC;
1725 mdoc->last = n;
1726 }
1727
1728 static void
post_it(POST_ARGS)1729 post_it(POST_ARGS)
1730 {
1731 struct roff_node *nbl, *nit, *nch;
1732 int i, cols;
1733 enum mdoc_list lt;
1734
1735 post_prevpar(mdoc);
1736
1737 nit = mdoc->last;
1738 if (nit->type != ROFFT_BLOCK)
1739 return;
1740
1741 nbl = nit->parent->parent;
1742 lt = nbl->norm->Bl.type;
1743
1744 switch (lt) {
1745 case LIST_tag:
1746 case LIST_hang:
1747 case LIST_ohang:
1748 case LIST_inset:
1749 case LIST_diag:
1750 if (nit->head->child == NULL)
1751 mandoc_msg(MANDOCERR_IT_NOHEAD,
1752 nit->line, nit->pos, "Bl -%s It",
1753 mdoc_argnames[nbl->args->argv[0].arg]);
1754 break;
1755 case LIST_bullet:
1756 case LIST_dash:
1757 case LIST_enum:
1758 case LIST_hyphen:
1759 if (nit->body == NULL || nit->body->child == NULL)
1760 mandoc_msg(MANDOCERR_IT_NOBODY,
1761 nit->line, nit->pos, "Bl -%s It",
1762 mdoc_argnames[nbl->args->argv[0].arg]);
1763 /* FALLTHROUGH */
1764 case LIST_item:
1765 if ((nch = nit->head->child) != NULL)
1766 mandoc_msg(MANDOCERR_ARG_SKIP,
1767 nit->line, nit->pos, "It %s",
1768 nch->type == ROFFT_TEXT ? nch->string :
1769 roff_name[nch->tok]);
1770 break;
1771 case LIST_column:
1772 cols = (int)nbl->norm->Bl.ncols;
1773
1774 assert(nit->head->child == NULL);
1775
1776 if (nit->head->next->child == NULL &&
1777 nit->head->next->next == NULL) {
1778 mandoc_msg(MANDOCERR_MACRO_EMPTY,
1779 nit->line, nit->pos, "It");
1780 roff_node_delete(mdoc, nit);
1781 break;
1782 }
1783
1784 i = 0;
1785 for (nch = nit->child; nch != NULL; nch = nch->next) {
1786 if (nch->type != ROFFT_BODY)
1787 continue;
1788 if (i++ && nch->flags & NODE_LINE)
1789 mandoc_msg(MANDOCERR_TA_LINE,
1790 nch->line, nch->pos, "Ta");
1791 }
1792 if (i < cols || i > cols + 1)
1793 mandoc_msg(MANDOCERR_BL_COL, nit->line, nit->pos,
1794 "%d columns, %d cells", cols, i);
1795 else if (nit->head->next->child != NULL &&
1796 nit->head->next->child->flags & NODE_LINE)
1797 mandoc_msg(MANDOCERR_IT_NOARG,
1798 nit->line, nit->pos, "Bl -column It");
1799 break;
1800 default:
1801 abort();
1802 }
1803 }
1804
1805 static void
post_bl_block(POST_ARGS)1806 post_bl_block(POST_ARGS)
1807 {
1808 struct roff_node *n, *ni, *nc;
1809
1810 post_prevpar(mdoc);
1811
1812 n = mdoc->last;
1813 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1814 if (ni->body == NULL)
1815 continue;
1816 nc = ni->body->last;
1817 while (nc != NULL) {
1818 switch (nc->tok) {
1819 case MDOC_Pp:
1820 case ROFF_br:
1821 break;
1822 default:
1823 nc = NULL;
1824 continue;
1825 }
1826 if (ni->next == NULL) {
1827 mandoc_msg(MANDOCERR_PAR_MOVE, nc->line,
1828 nc->pos, "%s", roff_name[nc->tok]);
1829 roff_node_relink(mdoc, nc);
1830 } else if (n->norm->Bl.comp == 0 &&
1831 n->norm->Bl.type != LIST_column) {
1832 mandoc_msg(MANDOCERR_PAR_SKIP,
1833 nc->line, nc->pos,
1834 "%s before It", roff_name[nc->tok]);
1835 roff_node_delete(mdoc, nc);
1836 } else
1837 break;
1838 nc = ni->body->last;
1839 }
1840 }
1841 }
1842
1843 /*
1844 * If "in" begins with a dot, a word, and whitespace, return a dynamically
1845 * allocated copy of "in" that skips all of those. Otherwise, return NULL.
1846 *
1847 * This is a partial workaround for the TODO list item beginning with:
1848 * - When the -width string contains macros, the macros must be rendered
1849 */
1850 static char *
skip_leading_dot_word(const char * in)1851 skip_leading_dot_word(const char *in)
1852 {
1853 const char *iter = in;
1854 const char *space;
1855
1856 if (*iter != '.')
1857 return NULL;
1858 iter++;
1859
1860 while (*iter != '\0' && !isspace(*iter))
1861 iter++;
1862 /*
1863 * If the dot was followed by space or NUL,
1864 * do not skip anything.
1865 */
1866 if (iter == in + 1)
1867 return NULL;
1868
1869 space = iter;
1870 while (isspace(*iter))
1871 iter++;
1872 /*
1873 * If the word was not followed by space,
1874 * do not skip anything.
1875 */
1876 if (iter == space)
1877 return NULL;
1878
1879 return strdup(iter);
1880 }
1881
1882 /*
1883 * If the argument of -offset or -width is a macro,
1884 * replace it with the associated default width.
1885 */
1886 static void
rewrite_macro2len(struct roff_man * mdoc,char ** arg)1887 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1888 {
1889 size_t width;
1890 enum roff_tok tok;
1891 char *newarg;
1892
1893 newarg = NULL;
1894 if (*arg == NULL)
1895 return;
1896 else if ( ! strcmp(*arg, "Ds"))
1897 width = 6;
1898 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) != TOKEN_NONE)
1899 width = macro2len(tok);
1900 else if ((newarg = skip_leading_dot_word(*arg)) == NULL)
1901 return;
1902
1903 free(*arg);
1904 if (newarg != NULL)
1905 *arg = newarg;
1906 else
1907 mandoc_asprintf(arg, "%zun", width);
1908 }
1909
1910 static void
post_bl_head(POST_ARGS)1911 post_bl_head(POST_ARGS)
1912 {
1913 struct roff_node *nbl, *nh, *nch, *nnext;
1914 struct mdoc_argv *argv;
1915 int i, j;
1916
1917 post_bl_norm(mdoc);
1918
1919 nh = mdoc->last;
1920 if (nh->norm->Bl.type != LIST_column) {
1921 if ((nch = nh->child) == NULL)
1922 return;
1923 mandoc_msg(MANDOCERR_ARG_EXCESS,
1924 nch->line, nch->pos, "Bl ... %s", nch->string);
1925 while (nch != NULL) {
1926 roff_node_delete(mdoc, nch);
1927 nch = nh->child;
1928 }
1929 return;
1930 }
1931
1932 /*
1933 * Append old-style lists, where the column width specifiers
1934 * trail as macro parameters, to the new-style ("normal-form")
1935 * lists where they're argument values following -column.
1936 */
1937
1938 if (nh->child == NULL)
1939 return;
1940
1941 nbl = nh->parent;
1942 for (j = 0; j < (int)nbl->args->argc; j++)
1943 if (nbl->args->argv[j].arg == MDOC_Column)
1944 break;
1945
1946 assert(j < (int)nbl->args->argc);
1947
1948 /*
1949 * Accommodate for new-style groff column syntax. Shuffle the
1950 * child nodes, all of which must be TEXT, as arguments for the
1951 * column field. Then, delete the head children.
1952 */
1953
1954 argv = nbl->args->argv + j;
1955 i = argv->sz;
1956 for (nch = nh->child; nch != NULL; nch = nch->next)
1957 argv->sz++;
1958 argv->value = mandoc_reallocarray(argv->value,
1959 argv->sz, sizeof(char *));
1960
1961 nh->norm->Bl.ncols = argv->sz;
1962 nh->norm->Bl.cols = (void *)argv->value;
1963
1964 for (nch = nh->child; nch != NULL; nch = nnext) {
1965 argv->value[i++] = nch->string;
1966 nch->string = NULL;
1967 nnext = nch->next;
1968 roff_node_delete(NULL, nch);
1969 }
1970 nh->child = NULL;
1971 }
1972
1973 static void
post_bl(POST_ARGS)1974 post_bl(POST_ARGS)
1975 {
1976 struct roff_node *nbody; /* of the Bl */
1977 struct roff_node *nchild, *nnext; /* of the Bl body */
1978 const char *prev_Er;
1979 int order;
1980
1981 nbody = mdoc->last;
1982 switch (nbody->type) {
1983 case ROFFT_BLOCK:
1984 post_bl_block(mdoc);
1985 return;
1986 case ROFFT_HEAD:
1987 post_bl_head(mdoc);
1988 return;
1989 case ROFFT_BODY:
1990 break;
1991 default:
1992 return;
1993 }
1994 if (nbody->end != ENDBODY_NOT)
1995 return;
1996
1997 /*
1998 * Up to the first item, move nodes before the list,
1999 * but leave transparent nodes where they are
2000 * if they precede an item.
2001 * The next non-transparent node is kept in nchild.
2002 * It only needs to be updated after a non-transparent
2003 * node was moved out, and at the very beginning
2004 * when no node at all was moved yet.
2005 */
2006
2007 nchild = mdoc->last;
2008 for (;;) {
2009 if (nchild == mdoc->last)
2010 nchild = roff_node_child(nbody);
2011 if (nchild == NULL) {
2012 mdoc->last = nbody;
2013 mandoc_msg(MANDOCERR_BLK_EMPTY,
2014 nbody->line, nbody->pos, "Bl");
2015 return;
2016 }
2017 if (nchild->tok == MDOC_It) {
2018 mdoc->last = nbody;
2019 break;
2020 }
2021 mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
2022 nbody->child->pos, "%s", roff_name[nbody->child->tok]);
2023 if (nbody->parent->prev == NULL) {
2024 mdoc->last = nbody->parent->parent;
2025 mdoc->next = ROFF_NEXT_CHILD;
2026 } else {
2027 mdoc->last = nbody->parent->prev;
2028 mdoc->next = ROFF_NEXT_SIBLING;
2029 }
2030 roff_node_relink(mdoc, nbody->child);
2031 }
2032
2033 /*
2034 * We have reached the first item,
2035 * so moving nodes out is no longer possible.
2036 * But in .Bl -column, the first rows may be implicit,
2037 * that is, they may not start with .It macros.
2038 * Such rows may be followed by nodes generated on the
2039 * roff level, for example .TS.
2040 * Wrap such roff nodes into an implicit row.
2041 */
2042
2043 while (nchild != NULL) {
2044 if (nchild->tok == MDOC_It) {
2045 nchild = roff_node_next(nchild);
2046 continue;
2047 }
2048 nnext = nchild->next;
2049 mdoc->last = nchild->prev;
2050 mdoc->next = ROFF_NEXT_SIBLING;
2051 roff_block_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
2052 roff_head_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
2053 mdoc->next = ROFF_NEXT_SIBLING;
2054 roff_body_alloc(mdoc, nchild->line, nchild->pos, MDOC_It);
2055 while (nchild->tok != MDOC_It) {
2056 roff_node_relink(mdoc, nchild);
2057 if (nnext == NULL)
2058 break;
2059 nchild = nnext;
2060 nnext = nchild->next;
2061 mdoc->next = ROFF_NEXT_SIBLING;
2062 }
2063 mdoc->last = nbody;
2064 }
2065
2066 if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
2067 return;
2068
2069 prev_Er = NULL;
2070 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
2071 if (nchild->tok != MDOC_It)
2072 continue;
2073 if ((nnext = nchild->head->child) == NULL)
2074 continue;
2075 if (nnext->type == ROFFT_BLOCK)
2076 nnext = nnext->body->child;
2077 if (nnext == NULL || nnext->tok != MDOC_Er)
2078 continue;
2079 nnext = nnext->child;
2080 if (prev_Er != NULL) {
2081 order = strcmp(prev_Er, nnext->string);
2082 if (order > 0)
2083 mandoc_msg(MANDOCERR_ER_ORDER,
2084 nnext->line, nnext->pos,
2085 "Er %s %s (NetBSD)",
2086 prev_Er, nnext->string);
2087 else if (order == 0)
2088 mandoc_msg(MANDOCERR_ER_REP,
2089 nnext->line, nnext->pos,
2090 "Er %s (NetBSD)", prev_Er);
2091 }
2092 prev_Er = nnext->string;
2093 }
2094 }
2095
2096 static void
post_bk(POST_ARGS)2097 post_bk(POST_ARGS)
2098 {
2099 struct roff_node *n;
2100
2101 n = mdoc->last;
2102
2103 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
2104 mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos, "Bk");
2105 roff_node_delete(mdoc, n);
2106 }
2107 }
2108
2109 static void
post_sm(POST_ARGS)2110 post_sm(POST_ARGS)
2111 {
2112 struct roff_node *nch;
2113
2114 nch = mdoc->last->child;
2115
2116 if (nch == NULL) {
2117 mdoc->flags ^= MDOC_SMOFF;
2118 return;
2119 }
2120
2121 assert(nch->type == ROFFT_TEXT);
2122
2123 if ( ! strcmp(nch->string, "on")) {
2124 mdoc->flags &= ~MDOC_SMOFF;
2125 return;
2126 }
2127 if ( ! strcmp(nch->string, "off")) {
2128 mdoc->flags |= MDOC_SMOFF;
2129 return;
2130 }
2131
2132 mandoc_msg(MANDOCERR_SM_BAD, nch->line, nch->pos,
2133 "%s %s", roff_name[mdoc->last->tok], nch->string);
2134 roff_node_relink(mdoc, nch);
2135 return;
2136 }
2137
2138 static void
post_root(POST_ARGS)2139 post_root(POST_ARGS)
2140 {
2141 struct roff_node *n;
2142
2143 /* Add missing prologue data. */
2144
2145 if (mdoc->meta.date == NULL)
2146 mdoc->meta.date = mandoc_normdate(NULL, NULL);
2147
2148 if (mdoc->meta.title == NULL) {
2149 mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
2150 mdoc->meta.title = mandoc_strdup("UNTITLED");
2151 }
2152
2153 if (mdoc->meta.vol == NULL)
2154 mdoc->meta.vol = mandoc_strdup("LOCAL");
2155
2156 if (mdoc->meta.os == NULL) {
2157 mandoc_msg(MANDOCERR_OS_MISSING, 0, 0, NULL);
2158 mdoc->meta.os = mandoc_strdup("");
2159 } else if (mdoc->meta.os_e &&
2160 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
2161 mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
2162 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2163 "(OpenBSD)" : "(NetBSD)");
2164
2165 if (mdoc->meta.arch != NULL &&
2166 arch_valid(mdoc->meta.arch, mdoc->meta.os_e) == 0) {
2167 n = mdoc->meta.first->child;
2168 while (n->tok != MDOC_Dt ||
2169 n->child == NULL ||
2170 n->child->next == NULL ||
2171 n->child->next->next == NULL)
2172 n = n->next;
2173 n = n->child->next->next;
2174 mandoc_msg(MANDOCERR_ARCH_BAD, n->line, n->pos,
2175 "Dt ... %s %s", mdoc->meta.arch,
2176 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2177 "(OpenBSD)" : "(NetBSD)");
2178 }
2179
2180 /* Check that we begin with a proper `Sh'. */
2181
2182 n = mdoc->meta.first->child;
2183 while (n != NULL &&
2184 (n->type == ROFFT_COMMENT ||
2185 (n->tok >= MDOC_Dd &&
2186 mdoc_macro(n->tok)->flags & MDOC_PROLOGUE)))
2187 n = n->next;
2188
2189 if (n == NULL)
2190 mandoc_msg(MANDOCERR_DOC_EMPTY, 0, 0, NULL);
2191 else if (n->tok != MDOC_Sh)
2192 mandoc_msg(MANDOCERR_SEC_BEFORE, n->line, n->pos,
2193 "%s", roff_name[n->tok]);
2194 }
2195
2196 static void
post_rs(POST_ARGS)2197 post_rs(POST_ARGS)
2198 {
2199 struct roff_node *np, *nch, *next, *prev;
2200 int i, j;
2201
2202 np = mdoc->last;
2203
2204 if (np->type != ROFFT_BODY)
2205 return;
2206
2207 if (np->child == NULL) {
2208 mandoc_msg(MANDOCERR_RS_EMPTY, np->line, np->pos, "Rs");
2209 return;
2210 }
2211
2212 /*
2213 * The full `Rs' block needs special handling to order the
2214 * sub-elements according to `rsord'. Pick through each element
2215 * and correctly order it. This is an insertion sort.
2216 */
2217
2218 next = NULL;
2219 for (nch = np->child->next; nch != NULL; nch = next) {
2220 /* Determine order number of this child. */
2221 for (i = 0; i < RSORD_MAX; i++)
2222 if (rsord[i] == nch->tok)
2223 break;
2224
2225 if (i == RSORD_MAX) {
2226 mandoc_msg(MANDOCERR_RS_BAD, nch->line, nch->pos,
2227 "%s", roff_name[nch->tok]);
2228 i = -1;
2229 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
2230 np->norm->Rs.quote_T++;
2231
2232 /*
2233 * Remove this child from the chain. This somewhat
2234 * repeats roff_node_unlink(), but since we're
2235 * just re-ordering, there's no need for the
2236 * full unlink process.
2237 */
2238
2239 if ((next = nch->next) != NULL)
2240 next->prev = nch->prev;
2241
2242 if ((prev = nch->prev) != NULL)
2243 prev->next = nch->next;
2244
2245 nch->prev = nch->next = NULL;
2246
2247 /*
2248 * Scan back until we reach a node that's
2249 * to be ordered before this child.
2250 */
2251
2252 for ( ; prev ; prev = prev->prev) {
2253 /* Determine order of `prev'. */
2254 for (j = 0; j < RSORD_MAX; j++)
2255 if (rsord[j] == prev->tok)
2256 break;
2257 if (j == RSORD_MAX)
2258 j = -1;
2259
2260 if (j <= i)
2261 break;
2262 }
2263
2264 /*
2265 * Set this child back into its correct place
2266 * in front of the `prev' node.
2267 */
2268
2269 nch->prev = prev;
2270
2271 if (prev == NULL) {
2272 np->child->prev = nch;
2273 nch->next = np->child;
2274 np->child = nch;
2275 } else {
2276 if (prev->next)
2277 prev->next->prev = nch;
2278 nch->next = prev->next;
2279 prev->next = nch;
2280 }
2281 }
2282 }
2283
2284 /*
2285 * For some arguments of some macros,
2286 * convert all breakable hyphens into ASCII_HYPH.
2287 */
2288 static void
post_hyph(POST_ARGS)2289 post_hyph(POST_ARGS)
2290 {
2291 struct roff_node *n, *nch;
2292 char *cp;
2293
2294 n = mdoc->last;
2295 for (nch = n->child; nch != NULL; nch = nch->next) {
2296 if (nch->type != ROFFT_TEXT)
2297 continue;
2298 cp = nch->string;
2299 if (*cp == '\0')
2300 continue;
2301 while (*(++cp) != '\0')
2302 if (*cp == '-' &&
2303 isalpha((unsigned char)cp[-1]) &&
2304 isalpha((unsigned char)cp[1])) {
2305 if (n->tag == NULL && n->flags & NODE_ID)
2306 n->tag = mandoc_strdup(nch->string);
2307 *cp = ASCII_HYPH;
2308 }
2309 }
2310 }
2311
2312 static void
post_ns(POST_ARGS)2313 post_ns(POST_ARGS)
2314 {
2315 struct roff_node *n;
2316
2317 n = mdoc->last;
2318 if (n->flags & NODE_LINE ||
2319 (n->next != NULL && n->next->flags & NODE_DELIMC))
2320 mandoc_msg(MANDOCERR_NS_SKIP, n->line, n->pos, NULL);
2321 }
2322
2323 static void
post_sx(POST_ARGS)2324 post_sx(POST_ARGS)
2325 {
2326 post_delim(mdoc);
2327 post_hyph(mdoc);
2328 }
2329
2330 static void
post_sh(POST_ARGS)2331 post_sh(POST_ARGS)
2332 {
2333 post_section(mdoc);
2334
2335 switch (mdoc->last->type) {
2336 case ROFFT_HEAD:
2337 post_sh_head(mdoc);
2338 break;
2339 case ROFFT_BODY:
2340 switch (mdoc->lastsec) {
2341 case SEC_NAME:
2342 post_sh_name(mdoc);
2343 break;
2344 case SEC_SEE_ALSO:
2345 post_sh_see_also(mdoc);
2346 break;
2347 case SEC_AUTHORS:
2348 post_sh_authors(mdoc);
2349 break;
2350 default:
2351 break;
2352 }
2353 break;
2354 default:
2355 break;
2356 }
2357 }
2358
2359 static void
post_sh_name(POST_ARGS)2360 post_sh_name(POST_ARGS)
2361 {
2362 struct roff_node *n;
2363 int hasnm, hasnd;
2364
2365 hasnm = hasnd = 0;
2366
2367 for (n = mdoc->last->child; n != NULL; n = n->next) {
2368 switch (n->tok) {
2369 case MDOC_Nm:
2370 if (hasnm && n->child != NULL)
2371 mandoc_msg(MANDOCERR_NAMESEC_PUNCT,
2372 n->line, n->pos,
2373 "Nm %s", n->child->string);
2374 hasnm = 1;
2375 continue;
2376 case MDOC_Nd:
2377 hasnd = 1;
2378 if (n->next != NULL)
2379 mandoc_msg(MANDOCERR_NAMESEC_ND,
2380 n->line, n->pos, NULL);
2381 break;
2382 case TOKEN_NONE:
2383 if (n->type == ROFFT_TEXT &&
2384 n->string[0] == ',' && n->string[1] == '\0' &&
2385 n->next != NULL && n->next->tok == MDOC_Nm) {
2386 n = n->next;
2387 continue;
2388 }
2389 /* FALLTHROUGH */
2390 default:
2391 mandoc_msg(MANDOCERR_NAMESEC_BAD,
2392 n->line, n->pos, "%s", roff_name[n->tok]);
2393 continue;
2394 }
2395 break;
2396 }
2397
2398 if ( ! hasnm)
2399 mandoc_msg(MANDOCERR_NAMESEC_NONM,
2400 mdoc->last->line, mdoc->last->pos, NULL);
2401 if ( ! hasnd)
2402 mandoc_msg(MANDOCERR_NAMESEC_NOND,
2403 mdoc->last->line, mdoc->last->pos, NULL);
2404 }
2405
2406 static void
post_sh_see_also(POST_ARGS)2407 post_sh_see_also(POST_ARGS)
2408 {
2409 const struct roff_node *n;
2410 const char *name, *sec;
2411 const char *lastname, *lastsec, *lastpunct;
2412 int cmp;
2413
2414 n = mdoc->last->child;
2415 lastname = lastsec = lastpunct = NULL;
2416 while (n != NULL) {
2417 if (n->tok != MDOC_Xr ||
2418 n->child == NULL ||
2419 n->child->next == NULL)
2420 break;
2421
2422 /* Process one .Xr node. */
2423
2424 name = n->child->string;
2425 sec = n->child->next->string;
2426 if (lastsec != NULL) {
2427 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2428 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2429 n->pos, "%s before %s(%s)",
2430 lastpunct, name, sec);
2431 cmp = strcmp(lastsec, sec);
2432 if (cmp > 0)
2433 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2434 n->pos, "%s(%s) after %s(%s)",
2435 name, sec, lastname, lastsec);
2436 else if (cmp == 0 &&
2437 strcasecmp(lastname, name) > 0)
2438 mandoc_msg(MANDOCERR_XR_ORDER, n->line,
2439 n->pos, "%s after %s", name, lastname);
2440 }
2441 lastname = name;
2442 lastsec = sec;
2443
2444 /* Process the following node. */
2445
2446 n = n->next;
2447 if (n == NULL)
2448 break;
2449 if (n->tok == MDOC_Xr) {
2450 lastpunct = "none";
2451 continue;
2452 }
2453 if (n->type != ROFFT_TEXT)
2454 break;
2455 for (name = n->string; *name != '\0'; name++)
2456 if (isalpha((const unsigned char)*name))
2457 return;
2458 lastpunct = n->string;
2459 if (n->next == NULL || n->next->tok == MDOC_Rs)
2460 mandoc_msg(MANDOCERR_XR_PUNCT, n->line,
2461 n->pos, "%s after %s(%s)",
2462 lastpunct, lastname, lastsec);
2463 n = n->next;
2464 }
2465 }
2466
2467 static int
child_an(const struct roff_node * n)2468 child_an(const struct roff_node *n)
2469 {
2470
2471 for (n = n->child; n != NULL; n = n->next)
2472 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2473 return 1;
2474 return 0;
2475 }
2476
2477 static void
post_sh_authors(POST_ARGS)2478 post_sh_authors(POST_ARGS)
2479 {
2480
2481 if ( ! child_an(mdoc->last))
2482 mandoc_msg(MANDOCERR_AN_MISSING,
2483 mdoc->last->line, mdoc->last->pos, NULL);
2484 }
2485
2486 /*
2487 * Return an upper bound for the string distance (allowing
2488 * transpositions). Not a full Levenshtein implementation
2489 * because Levenshtein is quadratic in the string length
2490 * and this function is called for every standard name,
2491 * so the check for each custom name would be cubic.
2492 * The following crude heuristics is linear, resulting
2493 * in quadratic behaviour for checking one custom name,
2494 * which does not cause measurable slowdown.
2495 */
2496 static int
similar(const char * s1,const char * s2)2497 similar(const char *s1, const char *s2)
2498 {
2499 const int maxdist = 3;
2500 int dist = 0;
2501
2502 while (s1[0] != '\0' && s2[0] != '\0') {
2503 if (s1[0] == s2[0]) {
2504 s1++;
2505 s2++;
2506 continue;
2507 }
2508 if (++dist > maxdist)
2509 return INT_MAX;
2510 if (s1[1] == s2[1]) { /* replacement */
2511 s1++;
2512 s2++;
2513 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2514 s1 += 2; /* transposition */
2515 s2 += 2;
2516 } else if (s1[0] == s2[1]) /* insertion */
2517 s2++;
2518 else if (s1[1] == s2[0]) /* deletion */
2519 s1++;
2520 else
2521 return INT_MAX;
2522 }
2523 dist += strlen(s1) + strlen(s2);
2524 return dist > maxdist ? INT_MAX : dist;
2525 }
2526
2527 static void
post_sh_head(POST_ARGS)2528 post_sh_head(POST_ARGS)
2529 {
2530 struct roff_node *nch;
2531 const char *goodsec;
2532 const char *const *testsec;
2533 int dist, mindist;
2534 enum roff_sec sec;
2535
2536 /*
2537 * Process a new section. Sections are either "named" or
2538 * "custom". Custom sections are user-defined, while named ones
2539 * follow a conventional order and may only appear in certain
2540 * manual sections.
2541 */
2542
2543 sec = mdoc->last->sec;
2544
2545 /* The NAME should be first. */
2546
2547 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2548 mandoc_msg(MANDOCERR_NAMESEC_FIRST,
2549 mdoc->last->line, mdoc->last->pos, "Sh %s",
2550 sec != SEC_CUSTOM ? secnames[sec] :
2551 (nch = mdoc->last->child) == NULL ? "" :
2552 nch->type == ROFFT_TEXT ? nch->string :
2553 roff_name[nch->tok]);
2554
2555 /* The SYNOPSIS gets special attention in other areas. */
2556
2557 if (sec == SEC_SYNOPSIS) {
2558 roff_setreg(mdoc->roff, "nS", 1, '=');
2559 mdoc->flags |= MDOC_SYNOPSIS;
2560 } else {
2561 roff_setreg(mdoc->roff, "nS", 0, '=');
2562 mdoc->flags &= ~MDOC_SYNOPSIS;
2563 }
2564 if (sec == SEC_DESCRIPTION)
2565 fn_prio = TAG_STRONG;
2566
2567 /* Mark our last section. */
2568
2569 mdoc->lastsec = sec;
2570
2571 /* We don't care about custom sections after this. */
2572
2573 if (sec == SEC_CUSTOM) {
2574 if ((nch = mdoc->last->child) == NULL ||
2575 nch->type != ROFFT_TEXT || nch->next != NULL)
2576 return;
2577 goodsec = NULL;
2578 mindist = INT_MAX;
2579 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2580 dist = similar(nch->string, *testsec);
2581 if (dist < mindist) {
2582 goodsec = *testsec;
2583 mindist = dist;
2584 }
2585 }
2586 if (goodsec != NULL)
2587 mandoc_msg(MANDOCERR_SEC_TYPO, nch->line, nch->pos,
2588 "Sh %s instead of %s", nch->string, goodsec);
2589 return;
2590 }
2591
2592 /*
2593 * Check whether our non-custom section is being repeated or is
2594 * out of order.
2595 */
2596
2597 if (sec == mdoc->lastnamed)
2598 mandoc_msg(MANDOCERR_SEC_REP, mdoc->last->line,
2599 mdoc->last->pos, "Sh %s", secnames[sec]);
2600
2601 if (sec < mdoc->lastnamed)
2602 mandoc_msg(MANDOCERR_SEC_ORDER, mdoc->last->line,
2603 mdoc->last->pos, "Sh %s", secnames[sec]);
2604
2605 /* Mark the last named section. */
2606
2607 mdoc->lastnamed = sec;
2608
2609 /* Check particular section/manual conventions. */
2610
2611 if (mdoc->meta.msec == NULL)
2612 return;
2613
2614 goodsec = NULL;
2615 switch (sec) {
2616 case SEC_ERRORS:
2617 if (*mdoc->meta.msec == '4')
2618 break;
2619 goodsec = "2, 3, 4, 9";
2620 /* FALLTHROUGH */
2621 case SEC_RETURN_VALUES:
2622 case SEC_LIBRARY:
2623 if (*mdoc->meta.msec == '2')
2624 break;
2625 if (*mdoc->meta.msec == '3')
2626 break;
2627 if (NULL == goodsec)
2628 goodsec = "2, 3, 9";
2629 /* FALLTHROUGH */
2630 case SEC_CONTEXT:
2631 if (*mdoc->meta.msec == '9')
2632 break;
2633 if (NULL == goodsec)
2634 goodsec = "9";
2635 mandoc_msg(MANDOCERR_SEC_MSEC,
2636 mdoc->last->line, mdoc->last->pos,
2637 "Sh %s for %s only", secnames[sec], goodsec);
2638 break;
2639 default:
2640 break;
2641 }
2642 }
2643
2644 static void
post_xr(POST_ARGS)2645 post_xr(POST_ARGS)
2646 {
2647 struct roff_node *n, *nch;
2648
2649 n = mdoc->last;
2650 nch = n->child;
2651 if (nch->next == NULL) {
2652 mandoc_msg(MANDOCERR_XR_NOSEC,
2653 n->line, n->pos, "Xr %s", nch->string);
2654 } else {
2655 assert(nch->next == n->last);
2656 if(mandoc_xr_add(nch->next->string, nch->string,
2657 nch->line, nch->pos))
2658 mandoc_msg(MANDOCERR_XR_SELF,
2659 nch->line, nch->pos, "Xr %s %s",
2660 nch->string, nch->next->string);
2661 }
2662 post_delim_nb(mdoc);
2663 }
2664
2665 static void
post_section(POST_ARGS)2666 post_section(POST_ARGS)
2667 {
2668 struct roff_node *n, *nch;
2669 char *cp, *tag;
2670
2671 n = mdoc->last;
2672 switch (n->type) {
2673 case ROFFT_BLOCK:
2674 post_prevpar(mdoc);
2675 return;
2676 case ROFFT_HEAD:
2677 tag = NULL;
2678 deroff(&tag, n);
2679 if (tag != NULL) {
2680 for (cp = tag; *cp != '\0'; cp++)
2681 if (*cp == ' ')
2682 *cp = '_';
2683 if ((nch = n->child) != NULL &&
2684 nch->type == ROFFT_TEXT &&
2685 strcmp(nch->string, tag) == 0)
2686 tag_put(NULL, TAG_STRONG, n);
2687 else
2688 tag_put(tag, TAG_FALLBACK, n);
2689 free(tag);
2690 }
2691 post_delim(mdoc);
2692 post_hyph(mdoc);
2693 return;
2694 case ROFFT_BODY:
2695 break;
2696 default:
2697 return;
2698 }
2699 if ((nch = n->child) != NULL &&
2700 (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
2701 nch->tok == ROFF_sp)) {
2702 mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2703 "%s after %s", roff_name[nch->tok],
2704 roff_name[n->tok]);
2705 roff_node_delete(mdoc, nch);
2706 }
2707 if ((nch = n->last) != NULL &&
2708 (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
2709 mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
2710 "%s at the end of %s", roff_name[nch->tok],
2711 roff_name[n->tok]);
2712 roff_node_delete(mdoc, nch);
2713 }
2714 }
2715
2716 static void
post_prevpar(POST_ARGS)2717 post_prevpar(POST_ARGS)
2718 {
2719 struct roff_node *n, *np;
2720
2721 n = mdoc->last;
2722 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2723 return;
2724 if ((np = roff_node_prev(n)) == NULL)
2725 return;
2726
2727 /*
2728 * Don't allow `Pp' prior to a paragraph-type
2729 * block: `Pp' or non-compact `Bd' or `Bl'.
2730 */
2731
2732 if (np->tok != MDOC_Pp && np->tok != ROFF_br)
2733 return;
2734 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2735 return;
2736 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2737 return;
2738 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2739 return;
2740
2741 mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
2742 "%s before %s", roff_name[np->tok], roff_name[n->tok]);
2743 roff_node_delete(mdoc, np);
2744 }
2745
2746 static void
post_par(POST_ARGS)2747 post_par(POST_ARGS)
2748 {
2749 struct roff_node *np;
2750
2751 fn_prio = TAG_STRONG;
2752 post_prevpar(mdoc);
2753
2754 np = mdoc->last;
2755 if (np->child != NULL)
2756 mandoc_msg(MANDOCERR_ARG_SKIP, np->line, np->pos,
2757 "%s %s", roff_name[np->tok], np->child->string);
2758 }
2759
2760 static void
post_dd(POST_ARGS)2761 post_dd(POST_ARGS)
2762 {
2763 struct roff_node *n;
2764
2765 n = mdoc->last;
2766 n->flags |= NODE_NOPRT;
2767
2768 if (mdoc->meta.date != NULL) {
2769 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dd");
2770 free(mdoc->meta.date);
2771 } else if (mdoc->flags & MDOC_PBODY)
2772 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Dd");
2773 else if (mdoc->meta.title != NULL)
2774 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2775 n->line, n->pos, "Dd after Dt");
2776 else if (mdoc->meta.os != NULL)
2777 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2778 n->line, n->pos, "Dd after Os");
2779
2780 if (mdoc->quick && n != NULL)
2781 mdoc->meta.date = mandoc_strdup("");
2782 else
2783 mdoc->meta.date = mandoc_normdate(n->child, n);
2784 }
2785
2786 static void
post_dt(POST_ARGS)2787 post_dt(POST_ARGS)
2788 {
2789 struct roff_node *nn, *n;
2790 const char *cp;
2791 char *p;
2792
2793 n = mdoc->last;
2794 n->flags |= NODE_NOPRT;
2795
2796 if (mdoc->flags & MDOC_PBODY) {
2797 mandoc_msg(MANDOCERR_DT_LATE, n->line, n->pos, "Dt");
2798 return;
2799 }
2800
2801 if (mdoc->meta.title != NULL)
2802 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Dt");
2803 else if (mdoc->meta.os != NULL)
2804 mandoc_msg(MANDOCERR_PROLOG_ORDER,
2805 n->line, n->pos, "Dt after Os");
2806
2807 free(mdoc->meta.title);
2808 free(mdoc->meta.msec);
2809 free(mdoc->meta.vol);
2810 free(mdoc->meta.arch);
2811
2812 mdoc->meta.title = NULL;
2813 mdoc->meta.msec = NULL;
2814 mdoc->meta.vol = NULL;
2815 mdoc->meta.arch = NULL;
2816
2817 /* Mandatory first argument: title. */
2818
2819 nn = n->child;
2820 if (nn == NULL || *nn->string == '\0') {
2821 mandoc_msg(MANDOCERR_DT_NOTITLE, n->line, n->pos, "Dt");
2822 mdoc->meta.title = mandoc_strdup("UNTITLED");
2823 } else {
2824 mdoc->meta.title = mandoc_strdup(nn->string);
2825
2826 /* Check that all characters are uppercase. */
2827
2828 for (p = nn->string; *p != '\0'; p++)
2829 if (islower((unsigned char)*p)) {
2830 mandoc_msg(MANDOCERR_TITLE_CASE, nn->line,
2831 nn->pos + (int)(p - nn->string),
2832 "Dt %s", nn->string);
2833 break;
2834 }
2835 }
2836
2837 /* Mandatory second argument: section. */
2838
2839 if (nn != NULL)
2840 nn = nn->next;
2841
2842 if (nn == NULL) {
2843 mandoc_msg(MANDOCERR_MSEC_MISSING, n->line, n->pos,
2844 "Dt %s", mdoc->meta.title);
2845 mdoc->meta.vol = mandoc_strdup("LOCAL");
2846 return; /* msec and arch remain NULL. */
2847 }
2848
2849 mdoc->meta.msec = mandoc_strdup(nn->string);
2850
2851 /* Infer volume title from section number. */
2852
2853 cp = mandoc_a2msec(nn->string);
2854 if (cp == NULL) {
2855 mandoc_msg(MANDOCERR_MSEC_BAD,
2856 nn->line, nn->pos, "Dt ... %s", nn->string);
2857 mdoc->meta.vol = mandoc_strdup(nn->string);
2858 } else {
2859 mdoc->meta.vol = mandoc_strdup(cp);
2860 if (mdoc->filesec != '\0' &&
2861 mdoc->filesec != *nn->string &&
2862 *nn->string >= '1' && *nn->string <= '9')
2863 mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos,
2864 "*.%c vs Dt ... %c", mdoc->filesec, *nn->string);
2865 }
2866
2867 /* Optional third argument: architecture. */
2868
2869 if ((nn = nn->next) == NULL)
2870 return;
2871
2872 for (p = nn->string; *p != '\0'; p++)
2873 *p = tolower((unsigned char)*p);
2874 mdoc->meta.arch = mandoc_strdup(nn->string);
2875
2876 /* Ignore fourth and later arguments. */
2877
2878 if ((nn = nn->next) != NULL)
2879 mandoc_msg(MANDOCERR_ARG_EXCESS,
2880 nn->line, nn->pos, "Dt ... %s", nn->string);
2881 }
2882
2883 static void
post_bx(POST_ARGS)2884 post_bx(POST_ARGS)
2885 {
2886 struct roff_node *n, *nch;
2887 const char *macro;
2888
2889 post_delim_nb(mdoc);
2890
2891 n = mdoc->last;
2892 nch = n->child;
2893
2894 if (nch != NULL) {
2895 macro = !strcmp(nch->string, "Open") ? "Ox" :
2896 !strcmp(nch->string, "Net") ? "Nx" :
2897 !strcmp(nch->string, "Free") ? "Fx" :
2898 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2899 if (macro != NULL)
2900 mandoc_msg(MANDOCERR_BX,
2901 n->line, n->pos, "%s", macro);
2902 mdoc->last = nch;
2903 nch = nch->next;
2904 mdoc->next = ROFF_NEXT_SIBLING;
2905 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2906 mdoc->last->flags |= NODE_NOSRC;
2907 mdoc->next = ROFF_NEXT_SIBLING;
2908 } else
2909 mdoc->next = ROFF_NEXT_CHILD;
2910 roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2911 mdoc->last->flags |= NODE_NOSRC;
2912
2913 if (nch == NULL) {
2914 mdoc->last = n;
2915 return;
2916 }
2917
2918 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2919 mdoc->last->flags |= NODE_NOSRC;
2920 mdoc->next = ROFF_NEXT_SIBLING;
2921 roff_word_alloc(mdoc, n->line, n->pos, "-");
2922 mdoc->last->flags |= NODE_NOSRC;
2923 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2924 mdoc->last->flags |= NODE_NOSRC;
2925 mdoc->last = n;
2926
2927 /*
2928 * Make `Bx's second argument always start with an uppercase
2929 * letter. Groff checks if it's an "accepted" term, but we just
2930 * uppercase blindly.
2931 */
2932
2933 *nch->string = (char)toupper((unsigned char)*nch->string);
2934 }
2935
2936 static void
post_os(POST_ARGS)2937 post_os(POST_ARGS)
2938 {
2939 #ifndef OSNAME
2940 struct utsname utsname;
2941 #endif
2942 struct roff_node *n;
2943
2944 n = mdoc->last;
2945 n->flags |= NODE_NOPRT;
2946
2947 if (mdoc->meta.os != NULL)
2948 mandoc_msg(MANDOCERR_PROLOG_REP, n->line, n->pos, "Os");
2949 else if (mdoc->flags & MDOC_PBODY)
2950 mandoc_msg(MANDOCERR_PROLOG_LATE, n->line, n->pos, "Os");
2951
2952 post_delim(mdoc);
2953
2954 /*
2955 * Set the operating system by way of the `Os' macro.
2956 * The order of precedence is:
2957 * 1. the argument of the `Os' macro, unless empty
2958 * 2. the -Ios=foo command line argument, if provided
2959 * 3. -DOSNAME="\"foo\"", if provided during compilation
2960 * 4. "sysname release" from uname(3)
2961 */
2962
2963 free(mdoc->meta.os);
2964 mdoc->meta.os = NULL;
2965 deroff(&mdoc->meta.os, n);
2966 if (mdoc->meta.os)
2967 goto out;
2968
2969 if (mdoc->os_s != NULL) {
2970 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2971 goto out;
2972 }
2973
2974 #ifdef OSNAME
2975 mdoc->meta.os = mandoc_strdup(OSNAME);
2976 #else /*!OSNAME */
2977 if (mdoc->os_r == NULL) {
2978 if (uname(&utsname) == -1) {
2979 mandoc_msg(MANDOCERR_OS_UNAME, n->line, n->pos, "Os");
2980 mdoc->os_r = mandoc_strdup("UNKNOWN");
2981 } else
2982 mandoc_asprintf(&mdoc->os_r, "%s %s",
2983 utsname.sysname, utsname.release);
2984 }
2985 mdoc->meta.os = mandoc_strdup(mdoc->os_r);
2986 #endif /*!OSNAME*/
2987
2988 out:
2989 if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2990 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2991 mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2992 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2993 mdoc->meta.os_e = MANDOC_OS_NETBSD;
2994 }
2995
2996 /*
2997 * This is the earliest point where we can check
2998 * Mdocdate conventions because we don't know
2999 * the operating system earlier.
3000 */
3001
3002 if (n->child != NULL)
3003 mandoc_msg(MANDOCERR_OS_ARG, n->child->line, n->child->pos,
3004 "Os %s (%s)", n->child->string,
3005 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
3006 "OpenBSD" : "NetBSD");
3007
3008 while (n->tok != MDOC_Dd)
3009 if ((n = n->prev) == NULL)
3010 return;
3011 if ((n = n->child) == NULL)
3012 return;
3013 if (strncmp(n->string, "$" "Mdocdate", 9)) {
3014 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
3015 mandoc_msg(MANDOCERR_MDOCDATE_MISSING, n->line,
3016 n->pos, "Dd %s (OpenBSD)", n->string);
3017 } else {
3018 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
3019 mandoc_msg(MANDOCERR_MDOCDATE, n->line,
3020 n->pos, "Dd %s (NetBSD)", n->string);
3021 }
3022 }
3023
3024 enum roff_sec
mdoc_a2sec(const char * p)3025 mdoc_a2sec(const char *p)
3026 {
3027 int i;
3028
3029 for (i = 0; i < (int)SEC__MAX; i++)
3030 if (secnames[i] && 0 == strcmp(p, secnames[i]))
3031 return (enum roff_sec)i;
3032
3033 return SEC_CUSTOM;
3034 }
3035
3036 static size_t
macro2len(enum roff_tok macro)3037 macro2len(enum roff_tok macro)
3038 {
3039
3040 switch (macro) {
3041 case MDOC_Ad:
3042 return 12;
3043 case MDOC_Ao:
3044 return 12;
3045 case MDOC_An:
3046 return 12;
3047 case MDOC_Aq:
3048 return 12;
3049 case MDOC_Ar:
3050 return 12;
3051 case MDOC_Bo:
3052 return 12;
3053 case MDOC_Bq:
3054 return 12;
3055 case MDOC_Cd:
3056 return 12;
3057 case MDOC_Cm:
3058 return 10;
3059 case MDOC_Do:
3060 return 10;
3061 case MDOC_Dq:
3062 return 12;
3063 case MDOC_Dv:
3064 return 12;
3065 case MDOC_Eo:
3066 return 12;
3067 case MDOC_Em:
3068 return 10;
3069 case MDOC_Er:
3070 return 17;
3071 case MDOC_Ev:
3072 return 15;
3073 case MDOC_Fa:
3074 return 12;
3075 case MDOC_Fl:
3076 return 10;
3077 case MDOC_Fo:
3078 return 16;
3079 case MDOC_Fn:
3080 return 16;
3081 case MDOC_Ic:
3082 return 10;
3083 case MDOC_Li:
3084 return 16;
3085 case MDOC_Ms:
3086 return 6;
3087 case MDOC_Nm:
3088 return 10;
3089 case MDOC_No:
3090 return 12;
3091 case MDOC_Oo:
3092 return 10;
3093 case MDOC_Op:
3094 return 14;
3095 case MDOC_Pa:
3096 return 32;
3097 case MDOC_Pf:
3098 return 12;
3099 case MDOC_Po:
3100 return 12;
3101 case MDOC_Pq:
3102 return 12;
3103 case MDOC_Ql:
3104 return 16;
3105 case MDOC_Qo:
3106 return 12;
3107 case MDOC_So:
3108 return 12;
3109 case MDOC_Sq:
3110 return 12;
3111 case MDOC_Sy:
3112 return 6;
3113 case MDOC_Sx:
3114 return 16;
3115 case MDOC_Tn:
3116 return 10;
3117 case MDOC_Va:
3118 return 12;
3119 case MDOC_Vt:
3120 return 12;
3121 case MDOC_Xr:
3122 return 10;
3123 default:
3124 break;
3125 }
3126 return 0;
3127 }
3128