mandoc: update to 1.14.3 & drop -DOSNAME
[unleashed.git] / bin / mandoc / mdoc_validate.c
blob3a9b86f3fd2b555741c4576130e1c73300f92578
1 /* $Id: mdoc_validate.c,v 1.352 2017/08/02 13:29:04 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010-2017 Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
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.
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.
19 #include "config.h"
21 #include <sys/types.h>
22 #ifndef OSNAME
23 #include <sys/utsname.h>
24 #endif
26 #include <assert.h>
27 #include <ctype.h>
28 #include <limits.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
34 #include "mandoc_aux.h"
35 #include "mandoc.h"
36 #include "mandoc_xr.h"
37 #include "roff.h"
38 #include "mdoc.h"
39 #include "libmandoc.h"
40 #include "roff_int.h"
41 #include "libmdoc.h"
43 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
45 #define POST_ARGS struct roff_man *mdoc
47 enum check_ineq {
48 CHECK_LT,
49 CHECK_GT,
50 CHECK_EQ
53 typedef void (*v_post)(POST_ARGS);
55 static int build_list(struct roff_man *, int);
56 static void check_text(struct roff_man *, int, int, char *);
57 static void check_argv(struct roff_man *,
58 struct roff_node *, struct mdoc_argv *);
59 static void check_args(struct roff_man *, struct roff_node *);
60 static void check_toptext(struct roff_man *, int, int, const char *);
61 static int child_an(const struct roff_node *);
62 static size_t macro2len(enum roff_tok);
63 static void rewrite_macro2len(struct roff_man *, char **);
64 static int similar(const char *, const char *);
66 static void post_an(POST_ARGS);
67 static void post_an_norm(POST_ARGS);
68 static void post_at(POST_ARGS);
69 static void post_bd(POST_ARGS);
70 static void post_bf(POST_ARGS);
71 static void post_bk(POST_ARGS);
72 static void post_bl(POST_ARGS);
73 static void post_bl_block(POST_ARGS);
74 static void post_bl_head(POST_ARGS);
75 static void post_bl_norm(POST_ARGS);
76 static void post_bx(POST_ARGS);
77 static void post_defaults(POST_ARGS);
78 static void post_display(POST_ARGS);
79 static void post_dd(POST_ARGS);
80 static void post_delim(POST_ARGS);
81 static void post_delim_nb(POST_ARGS);
82 static void post_dt(POST_ARGS);
83 static void post_en(POST_ARGS);
84 static void post_es(POST_ARGS);
85 static void post_eoln(POST_ARGS);
86 static void post_ex(POST_ARGS);
87 static void post_fa(POST_ARGS);
88 static void post_fn(POST_ARGS);
89 static void post_fname(POST_ARGS);
90 static void post_fo(POST_ARGS);
91 static void post_hyph(POST_ARGS);
92 static void post_ignpar(POST_ARGS);
93 static void post_it(POST_ARGS);
94 static void post_lb(POST_ARGS);
95 static void post_nd(POST_ARGS);
96 static void post_nm(POST_ARGS);
97 static void post_ns(POST_ARGS);
98 static void post_obsolete(POST_ARGS);
99 static void post_os(POST_ARGS);
100 static void post_par(POST_ARGS);
101 static void post_prevpar(POST_ARGS);
102 static void post_root(POST_ARGS);
103 static void post_rs(POST_ARGS);
104 static void post_rv(POST_ARGS);
105 static void post_sh(POST_ARGS);
106 static void post_sh_head(POST_ARGS);
107 static void post_sh_name(POST_ARGS);
108 static void post_sh_see_also(POST_ARGS);
109 static void post_sh_authors(POST_ARGS);
110 static void post_sm(POST_ARGS);
111 static void post_st(POST_ARGS);
112 static void post_std(POST_ARGS);
113 static void post_sx(POST_ARGS);
114 static void post_useless(POST_ARGS);
115 static void post_xr(POST_ARGS);
116 static void post_xx(POST_ARGS);
118 static const v_post __mdoc_valids[MDOC_MAX - MDOC_Dd] = {
119 post_dd, /* Dd */
120 post_dt, /* Dt */
121 post_os, /* Os */
122 post_sh, /* Sh */
123 post_ignpar, /* Ss */
124 post_par, /* Pp */
125 post_display, /* D1 */
126 post_display, /* Dl */
127 post_display, /* Bd */
128 NULL, /* Ed */
129 post_bl, /* Bl */
130 NULL, /* El */
131 post_it, /* It */
132 post_delim_nb, /* Ad */
133 post_an, /* An */
134 NULL, /* Ap */
135 post_defaults, /* Ar */
136 NULL, /* Cd */
137 post_delim_nb, /* Cm */
138 post_delim_nb, /* Dv */
139 post_delim_nb, /* Er */
140 post_delim_nb, /* Ev */
141 post_ex, /* Ex */
142 post_fa, /* Fa */
143 NULL, /* Fd */
144 post_delim_nb, /* Fl */
145 post_fn, /* Fn */
146 post_delim_nb, /* Ft */
147 post_delim_nb, /* Ic */
148 post_delim_nb, /* In */
149 post_defaults, /* Li */
150 post_nd, /* Nd */
151 post_nm, /* Nm */
152 post_delim_nb, /* Op */
153 post_obsolete, /* Ot */
154 post_defaults, /* Pa */
155 post_rv, /* Rv */
156 post_st, /* St */
157 post_delim_nb, /* Va */
158 post_delim_nb, /* Vt */
159 post_xr, /* Xr */
160 NULL, /* %A */
161 post_hyph, /* %B */ /* FIXME: can be used outside Rs/Re. */
162 NULL, /* %D */
163 NULL, /* %I */
164 NULL, /* %J */
165 post_hyph, /* %N */
166 post_hyph, /* %O */
167 NULL, /* %P */
168 post_hyph, /* %R */
169 post_hyph, /* %T */ /* FIXME: can be used outside Rs/Re. */
170 NULL, /* %V */
171 NULL, /* Ac */
172 post_delim_nb, /* Ao */
173 post_delim_nb, /* Aq */
174 post_at, /* At */
175 NULL, /* Bc */
176 post_bf, /* Bf */
177 post_delim_nb, /* Bo */
178 NULL, /* Bq */
179 post_xx, /* Bsx */
180 post_bx, /* Bx */
181 post_obsolete, /* Db */
182 NULL, /* Dc */
183 NULL, /* Do */
184 NULL, /* Dq */
185 NULL, /* Ec */
186 NULL, /* Ef */
187 post_delim_nb, /* Em */
188 NULL, /* Eo */
189 post_xx, /* Fx */
190 post_delim_nb, /* Ms */
191 NULL, /* No */
192 post_ns, /* Ns */
193 post_xx, /* Nx */
194 post_xx, /* Ox */
195 NULL, /* Pc */
196 NULL, /* Pf */
197 post_delim_nb, /* Po */
198 post_delim_nb, /* Pq */
199 NULL, /* Qc */
200 post_delim_nb, /* Ql */
201 post_delim_nb, /* Qo */
202 post_delim_nb, /* Qq */
203 NULL, /* Re */
204 post_rs, /* Rs */
205 NULL, /* Sc */
206 post_delim_nb, /* So */
207 post_delim_nb, /* Sq */
208 post_sm, /* Sm */
209 post_sx, /* Sx */
210 post_delim_nb, /* Sy */
211 post_useless, /* Tn */
212 post_xx, /* Ux */
213 NULL, /* Xc */
214 NULL, /* Xo */
215 post_fo, /* Fo */
216 NULL, /* Fc */
217 post_delim_nb, /* Oo */
218 NULL, /* Oc */
219 post_bk, /* Bk */
220 NULL, /* Ek */
221 post_eoln, /* Bt */
222 post_obsolete, /* Hf */
223 post_obsolete, /* Fr */
224 post_eoln, /* Ud */
225 post_lb, /* Lb */
226 post_par, /* Lp */
227 post_delim_nb, /* Lk */
228 post_defaults, /* Mt */
229 post_delim_nb, /* Brq */
230 post_delim_nb, /* Bro */
231 NULL, /* Brc */
232 NULL, /* %C */
233 post_es, /* Es */
234 post_en, /* En */
235 post_xx, /* Dx */
236 NULL, /* %Q */
237 NULL, /* %U */
238 NULL, /* Ta */
240 static const v_post *const mdoc_valids = __mdoc_valids - MDOC_Dd;
242 #define RSORD_MAX 14 /* Number of `Rs' blocks. */
244 static const enum roff_tok rsord[RSORD_MAX] = {
245 MDOC__A,
246 MDOC__T,
247 MDOC__B,
248 MDOC__I,
249 MDOC__J,
250 MDOC__R,
251 MDOC__N,
252 MDOC__V,
253 MDOC__U,
254 MDOC__P,
255 MDOC__Q,
256 MDOC__C,
257 MDOC__D,
258 MDOC__O
261 static const char * const secnames[SEC__MAX] = {
262 NULL,
263 "NAME",
264 "LIBRARY",
265 "SYNOPSIS",
266 "DESCRIPTION",
267 "CONTEXT",
268 "IMPLEMENTATION NOTES",
269 "RETURN VALUES",
270 "ENVIRONMENT",
271 "FILES",
272 "EXIT STATUS",
273 "EXAMPLES",
274 "DIAGNOSTICS",
275 "COMPATIBILITY",
276 "ERRORS",
277 "SEE ALSO",
278 "STANDARDS",
279 "HISTORY",
280 "AUTHORS",
281 "CAVEATS",
282 "BUGS",
283 "SECURITY CONSIDERATIONS",
284 NULL
288 void
289 mdoc_node_validate(struct roff_man *mdoc)
291 struct roff_node *n;
292 const v_post *p;
294 n = mdoc->last;
295 mdoc->last = mdoc->last->child;
296 while (mdoc->last != NULL) {
297 mdoc_node_validate(mdoc);
298 if (mdoc->last == n)
299 mdoc->last = mdoc->last->child;
300 else
301 mdoc->last = mdoc->last->next;
304 mdoc->last = n;
305 mdoc->next = ROFF_NEXT_SIBLING;
306 switch (n->type) {
307 case ROFFT_TEXT:
308 if (n->sec != SEC_SYNOPSIS ||
309 (n->parent->tok != MDOC_Cd && n->parent->tok != MDOC_Fd))
310 check_text(mdoc, n->line, n->pos, n->string);
311 if (n->parent->tok == MDOC_It ||
312 (n->parent->type == ROFFT_BODY &&
313 (n->parent->tok == MDOC_Sh ||
314 n->parent->tok == MDOC_Ss)))
315 check_toptext(mdoc, n->line, n->pos, n->string);
316 break;
317 case ROFFT_EQN:
318 case ROFFT_TBL:
319 break;
320 case ROFFT_ROOT:
321 post_root(mdoc);
322 break;
323 default:
324 check_args(mdoc, mdoc->last);
327 * Closing delimiters are not special at the
328 * beginning of a block, opening delimiters
329 * are not special at the end.
332 if (n->child != NULL)
333 n->child->flags &= ~NODE_DELIMC;
334 if (n->last != NULL)
335 n->last->flags &= ~NODE_DELIMO;
337 /* Call the macro's postprocessor. */
339 if (n->tok < ROFF_MAX) {
340 switch(n->tok) {
341 case ROFF_br:
342 case ROFF_sp:
343 post_par(mdoc);
344 break;
345 default:
346 roff_validate(mdoc);
347 break;
349 break;
352 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
353 p = mdoc_valids + n->tok;
354 if (*p)
355 (*p)(mdoc);
356 if (mdoc->last == n)
357 mdoc_state(mdoc, n);
358 break;
362 static void
363 check_args(struct roff_man *mdoc, struct roff_node *n)
365 int i;
367 if (NULL == n->args)
368 return;
370 assert(n->args->argc);
371 for (i = 0; i < (int)n->args->argc; i++)
372 check_argv(mdoc, n, &n->args->argv[i]);
375 static void
376 check_argv(struct roff_man *mdoc, struct roff_node *n, struct mdoc_argv *v)
378 int i;
380 for (i = 0; i < (int)v->sz; i++)
381 check_text(mdoc, v->line, v->pos, v->value[i]);
384 static void
385 check_text(struct roff_man *mdoc, int ln, int pos, char *p)
387 char *cp;
389 if (MDOC_LITERAL & mdoc->flags)
390 return;
392 for (cp = p; NULL != (p = strchr(p, '\t')); p++)
393 mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
394 ln, pos + (int)(p - cp), NULL);
397 static void
398 check_toptext(struct roff_man *mdoc, int ln, int pos, const char *p)
400 const char *cp, *cpr;
402 if (*p == '\0')
403 return;
405 if ((cp = strstr(p, "OpenBSD")) != NULL)
406 mandoc_msg(MANDOCERR_BX, mdoc->parse,
407 ln, pos + (cp - p), "Ox");
408 if ((cp = strstr(p, "NetBSD")) != NULL)
409 mandoc_msg(MANDOCERR_BX, mdoc->parse,
410 ln, pos + (cp - p), "Nx");
411 if ((cp = strstr(p, "FreeBSD")) != NULL)
412 mandoc_msg(MANDOCERR_BX, mdoc->parse,
413 ln, pos + (cp - p), "Fx");
414 if ((cp = strstr(p, "DragonFly")) != NULL)
415 mandoc_msg(MANDOCERR_BX, mdoc->parse,
416 ln, pos + (cp - p), "Dx");
418 cp = p;
419 while ((cp = strstr(cp + 1, "()")) != NULL) {
420 for (cpr = cp - 1; cpr >= p; cpr--)
421 if (*cpr != '_' && !isalnum((unsigned char)*cpr))
422 break;
423 if ((cpr < p || *cpr == ' ') && cpr + 1 < cp) {
424 cpr++;
425 mandoc_vmsg(MANDOCERR_FUNC, mdoc->parse,
426 ln, pos + (cpr - p),
427 "%.*s()", (int)(cp - cpr), cpr);
432 static void
433 post_delim(POST_ARGS)
435 const struct roff_node *nch;
436 const char *lc;
437 enum mdelim delim;
438 enum roff_tok tok;
440 tok = mdoc->last->tok;
441 nch = mdoc->last->last;
442 if (nch == NULL || nch->type != ROFFT_TEXT)
443 return;
444 lc = strchr(nch->string, '\0') - 1;
445 if (lc < nch->string)
446 return;
447 delim = mdoc_isdelim(lc);
448 if (delim == DELIM_NONE || delim == DELIM_OPEN)
449 return;
450 if (*lc == ')' && (tok == MDOC_Nd || tok == MDOC_Sh ||
451 tok == MDOC_Ss || tok == MDOC_Fo))
452 return;
454 mandoc_vmsg(MANDOCERR_DELIM, mdoc->parse,
455 nch->line, nch->pos + (lc - nch->string),
456 "%s%s %s", roff_name[tok],
457 nch == mdoc->last->child ? "" : " ...", nch->string);
460 static void
461 post_delim_nb(POST_ARGS)
463 const struct roff_node *nch;
464 const char *lc, *cp;
465 int nw;
466 enum mdelim delim;
467 enum roff_tok tok;
470 * Find candidates: at least two bytes,
471 * the last one a closing or middle delimiter.
474 tok = mdoc->last->tok;
475 nch = mdoc->last->last;
476 if (nch == NULL || nch->type != ROFFT_TEXT)
477 return;
478 lc = strchr(nch->string, '\0') - 1;
479 if (lc <= nch->string)
480 return;
481 delim = mdoc_isdelim(lc);
482 if (delim == DELIM_NONE || delim == DELIM_OPEN)
483 return;
486 * Reduce false positives by allowing various cases.
489 /* Escaped delimiters. */
490 if (lc > nch->string + 1 && lc[-2] == '\\' &&
491 (lc[-1] == '&' || lc[-1] == 'e'))
492 return;
494 /* Specific byte sequences. */
495 switch (*lc) {
496 case ')':
497 for (cp = lc; cp >= nch->string; cp--)
498 if (*cp == '(')
499 return;
500 break;
501 case '.':
502 if (lc > nch->string + 1 && lc[-2] == '.' && lc[-1] == '.')
503 return;
504 if (lc[-1] == '.')
505 return;
506 break;
507 case ';':
508 if (tok == MDOC_Vt)
509 return;
510 break;
511 case '?':
512 if (lc[-1] == '?')
513 return;
514 break;
515 case ']':
516 for (cp = lc; cp >= nch->string; cp--)
517 if (*cp == '[')
518 return;
519 break;
520 case '|':
521 if (lc == nch->string + 1 && lc[-1] == '|')
522 return;
523 default:
524 break;
527 /* Exactly two non-alphanumeric bytes. */
528 if (lc == nch->string + 1 && !isalnum((unsigned char)lc[-1]))
529 return;
531 /* At least three alphabetic words with a sentence ending. */
532 if (strchr("!.:?", *lc) != NULL && (tok == MDOC_Em ||
533 tok == MDOC_Li || tok == MDOC_Po || tok == MDOC_Pq ||
534 tok == MDOC_Sy)) {
535 nw = 0;
536 for (cp = lc - 1; cp >= nch->string; cp--) {
537 if (*cp == ' ') {
538 nw++;
539 if (cp > nch->string && cp[-1] == ',')
540 cp--;
541 } else if (isalpha((unsigned int)*cp)) {
542 if (nw > 1)
543 return;
544 } else
545 break;
549 mandoc_vmsg(MANDOCERR_DELIM_NB, mdoc->parse,
550 nch->line, nch->pos + (lc - nch->string),
551 "%s%s %s", roff_name[tok],
552 nch == mdoc->last->child ? "" : " ...", nch->string);
555 static void
556 post_bl_norm(POST_ARGS)
558 struct roff_node *n;
559 struct mdoc_argv *argv, *wa;
560 int i;
561 enum mdocargt mdoclt;
562 enum mdoc_list lt;
564 n = mdoc->last->parent;
565 n->norm->Bl.type = LIST__NONE;
568 * First figure out which kind of list to use: bind ourselves to
569 * the first mentioned list type and warn about any remaining
570 * ones. If we find no list type, we default to LIST_item.
573 wa = (n->args == NULL) ? NULL : n->args->argv;
574 mdoclt = MDOC_ARG_MAX;
575 for (i = 0; n->args && i < (int)n->args->argc; i++) {
576 argv = n->args->argv + i;
577 lt = LIST__NONE;
578 switch (argv->arg) {
579 /* Set list types. */
580 case MDOC_Bullet:
581 lt = LIST_bullet;
582 break;
583 case MDOC_Dash:
584 lt = LIST_dash;
585 break;
586 case MDOC_Enum:
587 lt = LIST_enum;
588 break;
589 case MDOC_Hyphen:
590 lt = LIST_hyphen;
591 break;
592 case MDOC_Item:
593 lt = LIST_item;
594 break;
595 case MDOC_Tag:
596 lt = LIST_tag;
597 break;
598 case MDOC_Diag:
599 lt = LIST_diag;
600 break;
601 case MDOC_Hang:
602 lt = LIST_hang;
603 break;
604 case MDOC_Ohang:
605 lt = LIST_ohang;
606 break;
607 case MDOC_Inset:
608 lt = LIST_inset;
609 break;
610 case MDOC_Column:
611 lt = LIST_column;
612 break;
613 /* Set list arguments. */
614 case MDOC_Compact:
615 if (n->norm->Bl.comp)
616 mandoc_msg(MANDOCERR_ARG_REP,
617 mdoc->parse, argv->line,
618 argv->pos, "Bl -compact");
619 n->norm->Bl.comp = 1;
620 break;
621 case MDOC_Width:
622 wa = argv;
623 if (0 == argv->sz) {
624 mandoc_msg(MANDOCERR_ARG_EMPTY,
625 mdoc->parse, argv->line,
626 argv->pos, "Bl -width");
627 n->norm->Bl.width = "0n";
628 break;
630 if (NULL != n->norm->Bl.width)
631 mandoc_vmsg(MANDOCERR_ARG_REP,
632 mdoc->parse, argv->line,
633 argv->pos, "Bl -width %s",
634 argv->value[0]);
635 rewrite_macro2len(mdoc, argv->value);
636 n->norm->Bl.width = argv->value[0];
637 break;
638 case MDOC_Offset:
639 if (0 == argv->sz) {
640 mandoc_msg(MANDOCERR_ARG_EMPTY,
641 mdoc->parse, argv->line,
642 argv->pos, "Bl -offset");
643 break;
645 if (NULL != n->norm->Bl.offs)
646 mandoc_vmsg(MANDOCERR_ARG_REP,
647 mdoc->parse, argv->line,
648 argv->pos, "Bl -offset %s",
649 argv->value[0]);
650 rewrite_macro2len(mdoc, argv->value);
651 n->norm->Bl.offs = argv->value[0];
652 break;
653 default:
654 continue;
656 if (LIST__NONE == lt)
657 continue;
658 mdoclt = argv->arg;
660 /* Check: multiple list types. */
662 if (LIST__NONE != n->norm->Bl.type) {
663 mandoc_vmsg(MANDOCERR_BL_REP,
664 mdoc->parse, n->line, n->pos,
665 "Bl -%s", mdoc_argnames[argv->arg]);
666 continue;
669 /* The list type should come first. */
671 if (n->norm->Bl.width ||
672 n->norm->Bl.offs ||
673 n->norm->Bl.comp)
674 mandoc_vmsg(MANDOCERR_BL_LATETYPE,
675 mdoc->parse, n->line, n->pos, "Bl -%s",
676 mdoc_argnames[n->args->argv[0].arg]);
678 n->norm->Bl.type = lt;
679 if (LIST_column == lt) {
680 n->norm->Bl.ncols = argv->sz;
681 n->norm->Bl.cols = (void *)argv->value;
685 /* Allow lists to default to LIST_item. */
687 if (LIST__NONE == n->norm->Bl.type) {
688 mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
689 n->line, n->pos, "Bl");
690 n->norm->Bl.type = LIST_item;
691 mdoclt = MDOC_Item;
695 * Validate the width field. Some list types don't need width
696 * types and should be warned about them. Others should have it
697 * and must also be warned. Yet others have a default and need
698 * no warning.
701 switch (n->norm->Bl.type) {
702 case LIST_tag:
703 if (n->norm->Bl.width == NULL)
704 mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
705 n->line, n->pos, "Bl -tag");
706 break;
707 case LIST_column:
708 case LIST_diag:
709 case LIST_ohang:
710 case LIST_inset:
711 case LIST_item:
712 if (n->norm->Bl.width != NULL)
713 mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
714 wa->line, wa->pos, "Bl -%s",
715 mdoc_argnames[mdoclt]);
716 n->norm->Bl.width = NULL;
717 break;
718 case LIST_bullet:
719 case LIST_dash:
720 case LIST_hyphen:
721 if (n->norm->Bl.width == NULL)
722 n->norm->Bl.width = "2n";
723 break;
724 case LIST_enum:
725 if (n->norm->Bl.width == NULL)
726 n->norm->Bl.width = "3n";
727 break;
728 default:
729 break;
733 static void
734 post_bd(POST_ARGS)
736 struct roff_node *n;
737 struct mdoc_argv *argv;
738 int i;
739 enum mdoc_disp dt;
741 n = mdoc->last;
742 for (i = 0; n->args && i < (int)n->args->argc; i++) {
743 argv = n->args->argv + i;
744 dt = DISP__NONE;
746 switch (argv->arg) {
747 case MDOC_Centred:
748 dt = DISP_centered;
749 break;
750 case MDOC_Ragged:
751 dt = DISP_ragged;
752 break;
753 case MDOC_Unfilled:
754 dt = DISP_unfilled;
755 break;
756 case MDOC_Filled:
757 dt = DISP_filled;
758 break;
759 case MDOC_Literal:
760 dt = DISP_literal;
761 break;
762 case MDOC_File:
763 mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
764 n->line, n->pos, NULL);
765 break;
766 case MDOC_Offset:
767 if (0 == argv->sz) {
768 mandoc_msg(MANDOCERR_ARG_EMPTY,
769 mdoc->parse, argv->line,
770 argv->pos, "Bd -offset");
771 break;
773 if (NULL != n->norm->Bd.offs)
774 mandoc_vmsg(MANDOCERR_ARG_REP,
775 mdoc->parse, argv->line,
776 argv->pos, "Bd -offset %s",
777 argv->value[0]);
778 rewrite_macro2len(mdoc, argv->value);
779 n->norm->Bd.offs = argv->value[0];
780 break;
781 case MDOC_Compact:
782 if (n->norm->Bd.comp)
783 mandoc_msg(MANDOCERR_ARG_REP,
784 mdoc->parse, argv->line,
785 argv->pos, "Bd -compact");
786 n->norm->Bd.comp = 1;
787 break;
788 default:
789 abort();
791 if (DISP__NONE == dt)
792 continue;
794 if (DISP__NONE == n->norm->Bd.type)
795 n->norm->Bd.type = dt;
796 else
797 mandoc_vmsg(MANDOCERR_BD_REP,
798 mdoc->parse, n->line, n->pos,
799 "Bd -%s", mdoc_argnames[argv->arg]);
802 if (DISP__NONE == n->norm->Bd.type) {
803 mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
804 n->line, n->pos, "Bd");
805 n->norm->Bd.type = DISP_ragged;
810 * Stand-alone line macros.
813 static void
814 post_an_norm(POST_ARGS)
816 struct roff_node *n;
817 struct mdoc_argv *argv;
818 size_t i;
820 n = mdoc->last;
821 if (n->args == NULL)
822 return;
824 for (i = 1; i < n->args->argc; i++) {
825 argv = n->args->argv + i;
826 mandoc_vmsg(MANDOCERR_AN_REP,
827 mdoc->parse, argv->line, argv->pos,
828 "An -%s", mdoc_argnames[argv->arg]);
831 argv = n->args->argv;
832 if (argv->arg == MDOC_Split)
833 n->norm->An.auth = AUTH_split;
834 else if (argv->arg == MDOC_Nosplit)
835 n->norm->An.auth = AUTH_nosplit;
836 else
837 abort();
840 static void
841 post_eoln(POST_ARGS)
843 struct roff_node *n;
845 post_useless(mdoc);
846 n = mdoc->last;
847 if (n->child != NULL)
848 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, n->line,
849 n->pos, "%s %s", roff_name[n->tok], n->child->string);
851 while (n->child != NULL)
852 roff_node_delete(mdoc, n->child);
854 roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
855 "is currently in beta test." : "currently under development.");
856 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
857 mdoc->last = n;
860 static int
861 build_list(struct roff_man *mdoc, int tok)
863 struct roff_node *n;
864 int ic;
866 n = mdoc->last->next;
867 for (ic = 1;; ic++) {
868 roff_elem_alloc(mdoc, n->line, n->pos, tok);
869 mdoc->last->flags |= NODE_NOSRC;
870 mdoc_node_relink(mdoc, n);
871 n = mdoc->last = mdoc->last->parent;
872 mdoc->next = ROFF_NEXT_SIBLING;
873 if (n->next == NULL)
874 return ic;
875 if (ic > 1 || n->next->next != NULL) {
876 roff_word_alloc(mdoc, n->line, n->pos, ",");
877 mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
879 n = mdoc->last->next;
880 if (n->next == NULL) {
881 roff_word_alloc(mdoc, n->line, n->pos, "and");
882 mdoc->last->flags |= NODE_NOSRC;
887 static void
888 post_ex(POST_ARGS)
890 struct roff_node *n;
891 int ic;
893 post_std(mdoc);
895 n = mdoc->last;
896 mdoc->next = ROFF_NEXT_CHILD;
897 roff_word_alloc(mdoc, n->line, n->pos, "The");
898 mdoc->last->flags |= NODE_NOSRC;
900 if (mdoc->last->next != NULL)
901 ic = build_list(mdoc, MDOC_Nm);
902 else if (mdoc->meta.name != NULL) {
903 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
904 mdoc->last->flags |= NODE_NOSRC;
905 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
906 mdoc->last->flags |= NODE_NOSRC;
907 mdoc->last = mdoc->last->parent;
908 mdoc->next = ROFF_NEXT_SIBLING;
909 ic = 1;
910 } else {
911 mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
912 n->line, n->pos, "Ex");
913 ic = 0;
916 roff_word_alloc(mdoc, n->line, n->pos,
917 ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
918 mdoc->last->flags |= NODE_NOSRC;
919 roff_word_alloc(mdoc, n->line, n->pos,
920 "on success, and\\~>0 if an error occurs.");
921 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
922 mdoc->last = n;
925 static void
926 post_lb(POST_ARGS)
928 struct roff_node *n;
929 const char *p;
931 post_delim_nb(mdoc);
933 n = mdoc->last;
934 assert(n->child->type == ROFFT_TEXT);
935 mdoc->next = ROFF_NEXT_CHILD;
937 if ((p = mdoc_a2lib(n->child->string)) != NULL) {
938 n->child->flags |= NODE_NOPRT;
939 roff_word_alloc(mdoc, n->line, n->pos, p);
940 mdoc->last->flags = NODE_NOSRC;
941 mdoc->last = n;
942 return;
945 mandoc_vmsg(MANDOCERR_LB_BAD, mdoc->parse, n->child->line,
946 n->child->pos, "Lb %s", n->child->string);
948 roff_word_alloc(mdoc, n->line, n->pos, "library");
949 mdoc->last->flags = NODE_NOSRC;
950 roff_word_alloc(mdoc, n->line, n->pos, "\\(Lq");
951 mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
952 mdoc->last = mdoc->last->next;
953 roff_word_alloc(mdoc, n->line, n->pos, "\\(Rq");
954 mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
955 mdoc->last = n;
958 static void
959 post_rv(POST_ARGS)
961 struct roff_node *n;
962 int ic;
964 post_std(mdoc);
966 n = mdoc->last;
967 mdoc->next = ROFF_NEXT_CHILD;
968 if (n->child != NULL) {
969 roff_word_alloc(mdoc, n->line, n->pos, "The");
970 mdoc->last->flags |= NODE_NOSRC;
971 ic = build_list(mdoc, MDOC_Fn);
972 roff_word_alloc(mdoc, n->line, n->pos,
973 ic > 1 ? "functions return" : "function returns");
974 mdoc->last->flags |= NODE_NOSRC;
975 roff_word_alloc(mdoc, n->line, n->pos,
976 "the value\\~0 if successful;");
977 } else
978 roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
979 "completion, the value\\~0 is returned;");
980 mdoc->last->flags |= NODE_NOSRC;
982 roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
983 "the value\\~\\-1 is returned and the global variable");
984 mdoc->last->flags |= NODE_NOSRC;
985 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
986 mdoc->last->flags |= NODE_NOSRC;
987 roff_word_alloc(mdoc, n->line, n->pos, "errno");
988 mdoc->last->flags |= NODE_NOSRC;
989 mdoc->last = mdoc->last->parent;
990 mdoc->next = ROFF_NEXT_SIBLING;
991 roff_word_alloc(mdoc, n->line, n->pos,
992 "is set to indicate the error.");
993 mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
994 mdoc->last = n;
997 static void
998 post_std(POST_ARGS)
1000 struct roff_node *n;
1002 post_delim(mdoc);
1004 n = mdoc->last;
1005 if (n->args && n->args->argc == 1)
1006 if (n->args->argv[0].arg == MDOC_Std)
1007 return;
1009 mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
1010 n->line, n->pos, roff_name[n->tok]);
1013 static void
1014 post_st(POST_ARGS)
1016 struct roff_node *n, *nch;
1017 const char *p;
1019 n = mdoc->last;
1020 nch = n->child;
1021 assert(nch->type == ROFFT_TEXT);
1023 if ((p = mdoc_a2st(nch->string)) == NULL) {
1024 mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
1025 nch->line, nch->pos, "St %s", nch->string);
1026 roff_node_delete(mdoc, n);
1027 return;
1030 nch->flags |= NODE_NOPRT;
1031 mdoc->next = ROFF_NEXT_CHILD;
1032 roff_word_alloc(mdoc, nch->line, nch->pos, p);
1033 mdoc->last->flags |= NODE_NOSRC;
1034 mdoc->last= n;
1037 static void
1038 post_obsolete(POST_ARGS)
1040 struct roff_node *n;
1042 n = mdoc->last;
1043 if (n->type == ROFFT_ELEM || n->type == ROFFT_BLOCK)
1044 mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
1045 n->line, n->pos, roff_name[n->tok]);
1048 static void
1049 post_useless(POST_ARGS)
1051 struct roff_node *n;
1053 n = mdoc->last;
1054 mandoc_msg(MANDOCERR_MACRO_USELESS, mdoc->parse,
1055 n->line, n->pos, roff_name[n->tok]);
1059 * Block macros.
1062 static void
1063 post_bf(POST_ARGS)
1065 struct roff_node *np, *nch;
1068 * Unlike other data pointers, these are "housed" by the HEAD
1069 * element, which contains the goods.
1072 np = mdoc->last;
1073 if (np->type != ROFFT_HEAD)
1074 return;
1076 assert(np->parent->type == ROFFT_BLOCK);
1077 assert(np->parent->tok == MDOC_Bf);
1079 /* Check the number of arguments. */
1081 nch = np->child;
1082 if (np->parent->args == NULL) {
1083 if (nch == NULL) {
1084 mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
1085 np->line, np->pos, "Bf");
1086 return;
1088 nch = nch->next;
1090 if (nch != NULL)
1091 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1092 nch->line, nch->pos, "Bf ... %s", nch->string);
1094 /* Extract argument into data. */
1096 if (np->parent->args != NULL) {
1097 switch (np->parent->args->argv[0].arg) {
1098 case MDOC_Emphasis:
1099 np->norm->Bf.font = FONT_Em;
1100 break;
1101 case MDOC_Literal:
1102 np->norm->Bf.font = FONT_Li;
1103 break;
1104 case MDOC_Symbolic:
1105 np->norm->Bf.font = FONT_Sy;
1106 break;
1107 default:
1108 abort();
1110 return;
1113 /* Extract parameter into data. */
1115 if ( ! strcmp(np->child->string, "Em"))
1116 np->norm->Bf.font = FONT_Em;
1117 else if ( ! strcmp(np->child->string, "Li"))
1118 np->norm->Bf.font = FONT_Li;
1119 else if ( ! strcmp(np->child->string, "Sy"))
1120 np->norm->Bf.font = FONT_Sy;
1121 else
1122 mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
1123 np->child->line, np->child->pos,
1124 "Bf %s", np->child->string);
1127 static void
1128 post_fname(POST_ARGS)
1130 const struct roff_node *n;
1131 const char *cp;
1132 size_t pos;
1134 n = mdoc->last->child;
1135 pos = strcspn(n->string, "()");
1136 cp = n->string + pos;
1137 if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
1138 mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
1139 n->line, n->pos + pos, n->string);
1142 static void
1143 post_fn(POST_ARGS)
1146 post_fname(mdoc);
1147 post_fa(mdoc);
1150 static void
1151 post_fo(POST_ARGS)
1153 const struct roff_node *n;
1155 n = mdoc->last;
1157 if (n->type != ROFFT_HEAD)
1158 return;
1160 if (n->child == NULL) {
1161 mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
1162 n->line, n->pos, "Fo");
1163 return;
1165 if (n->child != n->last) {
1166 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1167 n->child->next->line, n->child->next->pos,
1168 "Fo ... %s", n->child->next->string);
1169 while (n->child != n->last)
1170 roff_node_delete(mdoc, n->last);
1171 } else
1172 post_delim(mdoc);
1174 post_fname(mdoc);
1177 static void
1178 post_fa(POST_ARGS)
1180 const struct roff_node *n;
1181 const char *cp;
1183 for (n = mdoc->last->child; n != NULL; n = n->next) {
1184 for (cp = n->string; *cp != '\0'; cp++) {
1185 /* Ignore callbacks and alterations. */
1186 if (*cp == '(' || *cp == '{')
1187 break;
1188 if (*cp != ',')
1189 continue;
1190 mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
1191 n->line, n->pos + (cp - n->string),
1192 n->string);
1193 break;
1196 post_delim_nb(mdoc);
1199 static void
1200 post_nm(POST_ARGS)
1202 struct roff_node *n;
1204 n = mdoc->last;
1206 if (n->sec == SEC_NAME && n->child != NULL &&
1207 n->child->type == ROFFT_TEXT && mdoc->meta.msec != NULL)
1208 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1210 if (n->last != NULL &&
1211 (n->last->tok == MDOC_Pp ||
1212 n->last->tok == MDOC_Lp))
1213 mdoc_node_relink(mdoc, n->last);
1215 if (mdoc->meta.name == NULL)
1216 deroff(&mdoc->meta.name, n);
1218 if (mdoc->meta.name == NULL ||
1219 (mdoc->lastsec == SEC_NAME && n->child == NULL))
1220 mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
1221 n->line, n->pos, "Nm");
1223 switch (n->type) {
1224 case ROFFT_ELEM:
1225 post_delim_nb(mdoc);
1226 break;
1227 case ROFFT_HEAD:
1228 post_delim(mdoc);
1229 break;
1230 default:
1231 return;
1234 if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1235 mdoc->meta.name == NULL)
1236 return;
1238 mdoc->next = ROFF_NEXT_CHILD;
1239 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1240 mdoc->last->flags |= NODE_NOSRC;
1241 mdoc->last = n;
1244 static void
1245 post_nd(POST_ARGS)
1247 struct roff_node *n;
1249 n = mdoc->last;
1251 if (n->type != ROFFT_BODY)
1252 return;
1254 if (n->sec != SEC_NAME)
1255 mandoc_msg(MANDOCERR_ND_LATE, mdoc->parse,
1256 n->line, n->pos, "Nd");
1258 if (n->child == NULL)
1259 mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
1260 n->line, n->pos, "Nd");
1261 else
1262 post_delim(mdoc);
1264 post_hyph(mdoc);
1267 static void
1268 post_display(POST_ARGS)
1270 struct roff_node *n, *np;
1272 n = mdoc->last;
1273 switch (n->type) {
1274 case ROFFT_BODY:
1275 if (n->end != ENDBODY_NOT) {
1276 if (n->tok == MDOC_Bd &&
1277 n->body->parent->args == NULL)
1278 roff_node_delete(mdoc, n);
1279 } else if (n->child == NULL)
1280 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1281 n->line, n->pos, roff_name[n->tok]);
1282 else if (n->tok == MDOC_D1)
1283 post_hyph(mdoc);
1284 break;
1285 case ROFFT_BLOCK:
1286 if (n->tok == MDOC_Bd) {
1287 if (n->args == NULL) {
1288 mandoc_msg(MANDOCERR_BD_NOARG,
1289 mdoc->parse, n->line, n->pos, "Bd");
1290 mdoc->next = ROFF_NEXT_SIBLING;
1291 while (n->body->child != NULL)
1292 mdoc_node_relink(mdoc,
1293 n->body->child);
1294 roff_node_delete(mdoc, n);
1295 break;
1297 post_bd(mdoc);
1298 post_prevpar(mdoc);
1300 for (np = n->parent; np != NULL; np = np->parent) {
1301 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1302 mandoc_vmsg(MANDOCERR_BD_NEST,
1303 mdoc->parse, n->line, n->pos,
1304 "%s in Bd", roff_name[n->tok]);
1305 break;
1308 break;
1309 default:
1310 break;
1314 static void
1315 post_defaults(POST_ARGS)
1317 struct roff_node *nn;
1319 if (mdoc->last->child != NULL) {
1320 post_delim_nb(mdoc);
1321 return;
1325 * The `Ar' defaults to "file ..." if no value is provided as an
1326 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1327 * gets an empty string.
1330 nn = mdoc->last;
1331 switch (nn->tok) {
1332 case MDOC_Ar:
1333 mdoc->next = ROFF_NEXT_CHILD;
1334 roff_word_alloc(mdoc, nn->line, nn->pos, "file");
1335 mdoc->last->flags |= NODE_NOSRC;
1336 roff_word_alloc(mdoc, nn->line, nn->pos, "...");
1337 mdoc->last->flags |= NODE_NOSRC;
1338 break;
1339 case MDOC_Pa:
1340 case MDOC_Mt:
1341 mdoc->next = ROFF_NEXT_CHILD;
1342 roff_word_alloc(mdoc, nn->line, nn->pos, "~");
1343 mdoc->last->flags |= NODE_NOSRC;
1344 break;
1345 default:
1346 abort();
1348 mdoc->last = nn;
1351 static void
1352 post_at(POST_ARGS)
1354 struct roff_node *n, *nch;
1355 const char *att;
1357 n = mdoc->last;
1358 nch = n->child;
1361 * If we have a child, look it up in the standard keys. If a
1362 * key exist, use that instead of the child; if it doesn't,
1363 * prefix "AT&T UNIX " to the existing data.
1366 att = NULL;
1367 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1368 mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
1369 nch->line, nch->pos, "At %s", nch->string);
1371 mdoc->next = ROFF_NEXT_CHILD;
1372 if (att != NULL) {
1373 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1374 nch->flags |= NODE_NOPRT;
1375 } else
1376 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1377 mdoc->last->flags |= NODE_NOSRC;
1378 mdoc->last = n;
1381 static void
1382 post_an(POST_ARGS)
1384 struct roff_node *np, *nch;
1386 post_an_norm(mdoc);
1388 np = mdoc->last;
1389 nch = np->child;
1390 if (np->norm->An.auth == AUTH__NONE) {
1391 if (nch == NULL)
1392 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1393 np->line, np->pos, "An");
1394 else
1395 post_delim_nb(mdoc);
1396 } else if (nch != NULL)
1397 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1398 nch->line, nch->pos, "An ... %s", nch->string);
1401 static void
1402 post_en(POST_ARGS)
1405 post_obsolete(mdoc);
1406 if (mdoc->last->type == ROFFT_BLOCK)
1407 mdoc->last->norm->Es = mdoc->last_es;
1410 static void
1411 post_es(POST_ARGS)
1414 post_obsolete(mdoc);
1415 mdoc->last_es = mdoc->last;
1418 static void
1419 post_xx(POST_ARGS)
1421 struct roff_node *n;
1422 const char *os;
1423 char *v;
1425 post_delim_nb(mdoc);
1427 n = mdoc->last;
1428 switch (n->tok) {
1429 case MDOC_Bsx:
1430 os = "BSD/OS";
1431 break;
1432 case MDOC_Dx:
1433 os = "DragonFly";
1434 break;
1435 case MDOC_Fx:
1436 os = "FreeBSD";
1437 break;
1438 case MDOC_Nx:
1439 os = "NetBSD";
1440 if (n->child == NULL)
1441 break;
1442 v = n->child->string;
1443 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1444 v[2] < '0' || v[2] > '9' ||
1445 v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1446 break;
1447 n->child->flags |= NODE_NOPRT;
1448 mdoc->next = ROFF_NEXT_CHILD;
1449 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1450 v = mdoc->last->string;
1451 v[3] = toupper((unsigned char)v[3]);
1452 mdoc->last->flags |= NODE_NOSRC;
1453 mdoc->last = n;
1454 break;
1455 case MDOC_Ox:
1456 os = "OpenBSD";
1457 break;
1458 case MDOC_Ux:
1459 os = "UNIX";
1460 break;
1461 default:
1462 abort();
1464 mdoc->next = ROFF_NEXT_CHILD;
1465 roff_word_alloc(mdoc, n->line, n->pos, os);
1466 mdoc->last->flags |= NODE_NOSRC;
1467 mdoc->last = n;
1470 static void
1471 post_it(POST_ARGS)
1473 struct roff_node *nbl, *nit, *nch;
1474 int i, cols;
1475 enum mdoc_list lt;
1477 post_prevpar(mdoc);
1479 nit = mdoc->last;
1480 if (nit->type != ROFFT_BLOCK)
1481 return;
1483 nbl = nit->parent->parent;
1484 lt = nbl->norm->Bl.type;
1486 switch (lt) {
1487 case LIST_tag:
1488 case LIST_hang:
1489 case LIST_ohang:
1490 case LIST_inset:
1491 case LIST_diag:
1492 if (nit->head->child == NULL)
1493 mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1494 mdoc->parse, nit->line, nit->pos,
1495 "Bl -%s It",
1496 mdoc_argnames[nbl->args->argv[0].arg]);
1497 break;
1498 case LIST_bullet:
1499 case LIST_dash:
1500 case LIST_enum:
1501 case LIST_hyphen:
1502 if (nit->body == NULL || nit->body->child == NULL)
1503 mandoc_vmsg(MANDOCERR_IT_NOBODY,
1504 mdoc->parse, nit->line, nit->pos,
1505 "Bl -%s It",
1506 mdoc_argnames[nbl->args->argv[0].arg]);
1507 /* FALLTHROUGH */
1508 case LIST_item:
1509 if ((nch = nit->head->child) != NULL)
1510 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse,
1511 nit->line, nit->pos, "It %s",
1512 nch->string == NULL ? roff_name[nch->tok] :
1513 nch->string);
1514 break;
1515 case LIST_column:
1516 cols = (int)nbl->norm->Bl.ncols;
1518 assert(nit->head->child == NULL);
1520 if (nit->head->next->child == NULL &&
1521 nit->head->next->next == NULL) {
1522 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1523 nit->line, nit->pos, "It");
1524 roff_node_delete(mdoc, nit);
1525 break;
1528 i = 0;
1529 for (nch = nit->child; nch != NULL; nch = nch->next) {
1530 if (nch->type != ROFFT_BODY)
1531 continue;
1532 if (i++ && nch->flags & NODE_LINE)
1533 mandoc_msg(MANDOCERR_TA_LINE, mdoc->parse,
1534 nch->line, nch->pos, "Ta");
1536 if (i < cols || i > cols + 1)
1537 mandoc_vmsg(MANDOCERR_BL_COL,
1538 mdoc->parse, nit->line, nit->pos,
1539 "%d columns, %d cells", cols, i);
1540 else if (nit->head->next->child != NULL &&
1541 nit->head->next->child->line > nit->line)
1542 mandoc_msg(MANDOCERR_IT_NOARG, mdoc->parse,
1543 nit->line, nit->pos, "Bl -column It");
1544 break;
1545 default:
1546 abort();
1550 static void
1551 post_bl_block(POST_ARGS)
1553 struct roff_node *n, *ni, *nc;
1555 post_prevpar(mdoc);
1557 n = mdoc->last;
1558 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1559 if (ni->body == NULL)
1560 continue;
1561 nc = ni->body->last;
1562 while (nc != NULL) {
1563 switch (nc->tok) {
1564 case MDOC_Pp:
1565 case MDOC_Lp:
1566 case ROFF_br:
1567 break;
1568 default:
1569 nc = NULL;
1570 continue;
1572 if (ni->next == NULL) {
1573 mandoc_msg(MANDOCERR_PAR_MOVE,
1574 mdoc->parse, nc->line, nc->pos,
1575 roff_name[nc->tok]);
1576 mdoc_node_relink(mdoc, nc);
1577 } else if (n->norm->Bl.comp == 0 &&
1578 n->norm->Bl.type != LIST_column) {
1579 mandoc_vmsg(MANDOCERR_PAR_SKIP,
1580 mdoc->parse, nc->line, nc->pos,
1581 "%s before It", roff_name[nc->tok]);
1582 roff_node_delete(mdoc, nc);
1583 } else
1584 break;
1585 nc = ni->body->last;
1591 * If the argument of -offset or -width is a macro,
1592 * replace it with the associated default width.
1594 static void
1595 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1597 size_t width;
1598 enum roff_tok tok;
1600 if (*arg == NULL)
1601 return;
1602 else if ( ! strcmp(*arg, "Ds"))
1603 width = 6;
1604 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1605 return;
1606 else
1607 width = macro2len(tok);
1609 free(*arg);
1610 mandoc_asprintf(arg, "%zun", width);
1613 static void
1614 post_bl_head(POST_ARGS)
1616 struct roff_node *nbl, *nh, *nch, *nnext;
1617 struct mdoc_argv *argv;
1618 int i, j;
1620 post_bl_norm(mdoc);
1622 nh = mdoc->last;
1623 if (nh->norm->Bl.type != LIST_column) {
1624 if ((nch = nh->child) == NULL)
1625 return;
1626 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1627 nch->line, nch->pos, "Bl ... %s", nch->string);
1628 while (nch != NULL) {
1629 roff_node_delete(mdoc, nch);
1630 nch = nh->child;
1632 return;
1636 * Append old-style lists, where the column width specifiers
1637 * trail as macro parameters, to the new-style ("normal-form")
1638 * lists where they're argument values following -column.
1641 if (nh->child == NULL)
1642 return;
1644 nbl = nh->parent;
1645 for (j = 0; j < (int)nbl->args->argc; j++)
1646 if (nbl->args->argv[j].arg == MDOC_Column)
1647 break;
1649 assert(j < (int)nbl->args->argc);
1652 * Accommodate for new-style groff column syntax. Shuffle the
1653 * child nodes, all of which must be TEXT, as arguments for the
1654 * column field. Then, delete the head children.
1657 argv = nbl->args->argv + j;
1658 i = argv->sz;
1659 for (nch = nh->child; nch != NULL; nch = nch->next)
1660 argv->sz++;
1661 argv->value = mandoc_reallocarray(argv->value,
1662 argv->sz, sizeof(char *));
1664 nh->norm->Bl.ncols = argv->sz;
1665 nh->norm->Bl.cols = (void *)argv->value;
1667 for (nch = nh->child; nch != NULL; nch = nnext) {
1668 argv->value[i++] = nch->string;
1669 nch->string = NULL;
1670 nnext = nch->next;
1671 roff_node_delete(NULL, nch);
1673 nh->child = NULL;
1676 static void
1677 post_bl(POST_ARGS)
1679 struct roff_node *nparent, *nprev; /* of the Bl block */
1680 struct roff_node *nblock, *nbody; /* of the Bl */
1681 struct roff_node *nchild, *nnext; /* of the Bl body */
1682 const char *prev_Er;
1683 int order;
1685 nbody = mdoc->last;
1686 switch (nbody->type) {
1687 case ROFFT_BLOCK:
1688 post_bl_block(mdoc);
1689 return;
1690 case ROFFT_HEAD:
1691 post_bl_head(mdoc);
1692 return;
1693 case ROFFT_BODY:
1694 break;
1695 default:
1696 return;
1698 if (nbody->end != ENDBODY_NOT)
1699 return;
1701 nchild = nbody->child;
1702 if (nchild == NULL) {
1703 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1704 nbody->line, nbody->pos, "Bl");
1705 return;
1707 while (nchild != NULL) {
1708 nnext = nchild->next;
1709 if (nchild->tok == MDOC_It ||
1710 (nchild->tok == MDOC_Sm &&
1711 nnext != NULL && nnext->tok == MDOC_It)) {
1712 nchild = nnext;
1713 continue;
1717 * In .Bl -column, the first rows may be implicit,
1718 * that is, they may not start with .It macros.
1719 * Such rows may be followed by nodes generated on the
1720 * roff level, for example .TS, which cannot be moved
1721 * out of the list. In that case, wrap such roff nodes
1722 * into an implicit row.
1725 if (nchild->prev != NULL) {
1726 mdoc->last = nchild;
1727 mdoc->next = ROFF_NEXT_SIBLING;
1728 roff_block_alloc(mdoc, nchild->line,
1729 nchild->pos, MDOC_It);
1730 roff_head_alloc(mdoc, nchild->line,
1731 nchild->pos, MDOC_It);
1732 mdoc->next = ROFF_NEXT_SIBLING;
1733 roff_body_alloc(mdoc, nchild->line,
1734 nchild->pos, MDOC_It);
1735 while (nchild->tok != MDOC_It) {
1736 mdoc_node_relink(mdoc, nchild);
1737 if ((nchild = nnext) == NULL)
1738 break;
1739 nnext = nchild->next;
1740 mdoc->next = ROFF_NEXT_SIBLING;
1742 mdoc->last = nbody;
1743 continue;
1746 mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1747 nchild->line, nchild->pos, roff_name[nchild->tok]);
1750 * Move the node out of the Bl block.
1751 * First, collect all required node pointers.
1754 nblock = nbody->parent;
1755 nprev = nblock->prev;
1756 nparent = nblock->parent;
1759 * Unlink this child.
1762 nbody->child = nnext;
1763 if (nnext == NULL)
1764 nbody->last = NULL;
1765 else
1766 nnext->prev = NULL;
1769 * Relink this child.
1772 nchild->parent = nparent;
1773 nchild->prev = nprev;
1774 nchild->next = nblock;
1776 nblock->prev = nchild;
1777 if (nprev == NULL)
1778 nparent->child = nchild;
1779 else
1780 nprev->next = nchild;
1782 nchild = nnext;
1785 if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1786 return;
1788 prev_Er = NULL;
1789 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1790 if (nchild->tok != MDOC_It)
1791 continue;
1792 if ((nnext = nchild->head->child) == NULL)
1793 continue;
1794 if (nnext->type == ROFFT_BLOCK)
1795 nnext = nnext->body->child;
1796 if (nnext == NULL || nnext->tok != MDOC_Er)
1797 continue;
1798 nnext = nnext->child;
1799 if (prev_Er != NULL) {
1800 order = strcmp(prev_Er, nnext->string);
1801 if (order > 0)
1802 mandoc_vmsg(MANDOCERR_ER_ORDER,
1803 mdoc->parse, nnext->line, nnext->pos,
1804 "Er %s %s (NetBSD)",
1805 prev_Er, nnext->string);
1806 else if (order == 0)
1807 mandoc_vmsg(MANDOCERR_ER_REP,
1808 mdoc->parse, nnext->line, nnext->pos,
1809 "Er %s (NetBSD)", prev_Er);
1811 prev_Er = nnext->string;
1815 static void
1816 post_bk(POST_ARGS)
1818 struct roff_node *n;
1820 n = mdoc->last;
1822 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1823 mandoc_msg(MANDOCERR_BLK_EMPTY,
1824 mdoc->parse, n->line, n->pos, "Bk");
1825 roff_node_delete(mdoc, n);
1829 static void
1830 post_sm(POST_ARGS)
1832 struct roff_node *nch;
1834 nch = mdoc->last->child;
1836 if (nch == NULL) {
1837 mdoc->flags ^= MDOC_SMOFF;
1838 return;
1841 assert(nch->type == ROFFT_TEXT);
1843 if ( ! strcmp(nch->string, "on")) {
1844 mdoc->flags &= ~MDOC_SMOFF;
1845 return;
1847 if ( ! strcmp(nch->string, "off")) {
1848 mdoc->flags |= MDOC_SMOFF;
1849 return;
1852 mandoc_vmsg(MANDOCERR_SM_BAD,
1853 mdoc->parse, nch->line, nch->pos,
1854 "%s %s", roff_name[mdoc->last->tok], nch->string);
1855 mdoc_node_relink(mdoc, nch);
1856 return;
1859 static void
1860 post_root(POST_ARGS)
1862 const char *openbsd_arch[] = {
1863 "alpha", "amd64", "arm64", "armv7", "hppa", "i386",
1864 "landisk", "loongson", "luna88k", "macppc", "mips64",
1865 "octeon", "sgi", "socppc", "sparc64", NULL
1867 const char *netbsd_arch[] = {
1868 "acorn26", "acorn32", "algor", "alpha", "amiga",
1869 "arc", "atari",
1870 "bebox", "cats", "cesfic", "cobalt", "dreamcast",
1871 "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5",
1872 "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa",
1873 "i386", "ibmnws", "luna68k",
1874 "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc",
1875 "netwinder", "news68k", "newsmips", "next68k",
1876 "pc532", "playstation2", "pmax", "pmppc", "prep",
1877 "sandpoint", "sbmips", "sgimips", "shark",
1878 "sparc", "sparc64", "sun2", "sun3",
1879 "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL
1881 const char **arches[] = { NULL, netbsd_arch, openbsd_arch };
1883 struct roff_node *n;
1884 const char **arch;
1886 /* Add missing prologue data. */
1888 if (mdoc->meta.date == NULL)
1889 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1890 mandoc_normdate(mdoc, NULL, 0, 0);
1892 if (mdoc->meta.title == NULL) {
1893 mandoc_msg(MANDOCERR_DT_NOTITLE,
1894 mdoc->parse, 0, 0, "EOF");
1895 mdoc->meta.title = mandoc_strdup("UNTITLED");
1898 if (mdoc->meta.vol == NULL)
1899 mdoc->meta.vol = mandoc_strdup("LOCAL");
1901 if (mdoc->meta.os == NULL) {
1902 mandoc_msg(MANDOCERR_OS_MISSING,
1903 mdoc->parse, 0, 0, NULL);
1904 mdoc->meta.os = mandoc_strdup("");
1905 } else if (mdoc->meta.os_e &&
1906 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1907 mandoc_msg(MANDOCERR_RCS_MISSING, mdoc->parse, 0, 0,
1908 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1909 "(OpenBSD)" : "(NetBSD)");
1911 if (mdoc->meta.arch != NULL &&
1912 (arch = arches[mdoc->meta.os_e]) != NULL) {
1913 while (*arch != NULL && strcmp(*arch, mdoc->meta.arch))
1914 arch++;
1915 if (*arch == NULL) {
1916 n = mdoc->first->child;
1917 while (n->tok != MDOC_Dt)
1918 n = n->next;
1919 n = n->child->next->next;
1920 mandoc_vmsg(MANDOCERR_ARCH_BAD,
1921 mdoc->parse, n->line, n->pos,
1922 "Dt ... %s %s", mdoc->meta.arch,
1923 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1924 "(OpenBSD)" : "(NetBSD)");
1928 /* Check that we begin with a proper `Sh'. */
1930 n = mdoc->first->child;
1931 while (n != NULL && n->tok >= MDOC_Dd &&
1932 mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1933 n = n->next;
1935 if (n == NULL)
1936 mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1937 else if (n->tok != MDOC_Sh)
1938 mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
1939 n->line, n->pos, roff_name[n->tok]);
1942 static void
1943 post_rs(POST_ARGS)
1945 struct roff_node *np, *nch, *next, *prev;
1946 int i, j;
1948 np = mdoc->last;
1950 if (np->type != ROFFT_BODY)
1951 return;
1953 if (np->child == NULL) {
1954 mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
1955 np->line, np->pos, "Rs");
1956 return;
1960 * The full `Rs' block needs special handling to order the
1961 * sub-elements according to `rsord'. Pick through each element
1962 * and correctly order it. This is an insertion sort.
1965 next = NULL;
1966 for (nch = np->child->next; nch != NULL; nch = next) {
1967 /* Determine order number of this child. */
1968 for (i = 0; i < RSORD_MAX; i++)
1969 if (rsord[i] == nch->tok)
1970 break;
1972 if (i == RSORD_MAX) {
1973 mandoc_msg(MANDOCERR_RS_BAD, mdoc->parse,
1974 nch->line, nch->pos, roff_name[nch->tok]);
1975 i = -1;
1976 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1977 np->norm->Rs.quote_T++;
1980 * Remove this child from the chain. This somewhat
1981 * repeats roff_node_unlink(), but since we're
1982 * just re-ordering, there's no need for the
1983 * full unlink process.
1986 if ((next = nch->next) != NULL)
1987 next->prev = nch->prev;
1989 if ((prev = nch->prev) != NULL)
1990 prev->next = nch->next;
1992 nch->prev = nch->next = NULL;
1995 * Scan back until we reach a node that's
1996 * to be ordered before this child.
1999 for ( ; prev ; prev = prev->prev) {
2000 /* Determine order of `prev'. */
2001 for (j = 0; j < RSORD_MAX; j++)
2002 if (rsord[j] == prev->tok)
2003 break;
2004 if (j == RSORD_MAX)
2005 j = -1;
2007 if (j <= i)
2008 break;
2012 * Set this child back into its correct place
2013 * in front of the `prev' node.
2016 nch->prev = prev;
2018 if (prev == NULL) {
2019 np->child->prev = nch;
2020 nch->next = np->child;
2021 np->child = nch;
2022 } else {
2023 if (prev->next)
2024 prev->next->prev = nch;
2025 nch->next = prev->next;
2026 prev->next = nch;
2032 * For some arguments of some macros,
2033 * convert all breakable hyphens into ASCII_HYPH.
2035 static void
2036 post_hyph(POST_ARGS)
2038 struct roff_node *nch;
2039 char *cp;
2041 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2042 if (nch->type != ROFFT_TEXT)
2043 continue;
2044 cp = nch->string;
2045 if (*cp == '\0')
2046 continue;
2047 while (*(++cp) != '\0')
2048 if (*cp == '-' &&
2049 isalpha((unsigned char)cp[-1]) &&
2050 isalpha((unsigned char)cp[1]))
2051 *cp = ASCII_HYPH;
2055 static void
2056 post_ns(POST_ARGS)
2058 struct roff_node *n;
2060 n = mdoc->last;
2061 if (n->flags & NODE_LINE ||
2062 (n->next != NULL && n->next->flags & NODE_DELIMC))
2063 mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
2064 n->line, n->pos, NULL);
2067 static void
2068 post_sx(POST_ARGS)
2070 post_delim(mdoc);
2071 post_hyph(mdoc);
2074 static void
2075 post_sh(POST_ARGS)
2078 post_ignpar(mdoc);
2080 switch (mdoc->last->type) {
2081 case ROFFT_HEAD:
2082 post_sh_head(mdoc);
2083 break;
2084 case ROFFT_BODY:
2085 switch (mdoc->lastsec) {
2086 case SEC_NAME:
2087 post_sh_name(mdoc);
2088 break;
2089 case SEC_SEE_ALSO:
2090 post_sh_see_also(mdoc);
2091 break;
2092 case SEC_AUTHORS:
2093 post_sh_authors(mdoc);
2094 break;
2095 default:
2096 break;
2098 break;
2099 default:
2100 break;
2104 static void
2105 post_sh_name(POST_ARGS)
2107 struct roff_node *n;
2108 int hasnm, hasnd;
2110 hasnm = hasnd = 0;
2112 for (n = mdoc->last->child; n != NULL; n = n->next) {
2113 switch (n->tok) {
2114 case MDOC_Nm:
2115 if (hasnm && n->child != NULL)
2116 mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT,
2117 mdoc->parse, n->line, n->pos,
2118 "Nm %s", n->child->string);
2119 hasnm = 1;
2120 continue;
2121 case MDOC_Nd:
2122 hasnd = 1;
2123 if (n->next != NULL)
2124 mandoc_msg(MANDOCERR_NAMESEC_ND,
2125 mdoc->parse, n->line, n->pos, NULL);
2126 break;
2127 case TOKEN_NONE:
2128 if (n->type == ROFFT_TEXT &&
2129 n->string[0] == ',' && n->string[1] == '\0' &&
2130 n->next != NULL && n->next->tok == MDOC_Nm) {
2131 n = n->next;
2132 continue;
2134 /* FALLTHROUGH */
2135 default:
2136 mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
2137 n->line, n->pos, roff_name[n->tok]);
2138 continue;
2140 break;
2143 if ( ! hasnm)
2144 mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
2145 mdoc->last->line, mdoc->last->pos, NULL);
2146 if ( ! hasnd)
2147 mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
2148 mdoc->last->line, mdoc->last->pos, NULL);
2151 static void
2152 post_sh_see_also(POST_ARGS)
2154 const struct roff_node *n;
2155 const char *name, *sec;
2156 const char *lastname, *lastsec, *lastpunct;
2157 int cmp;
2159 n = mdoc->last->child;
2160 lastname = lastsec = lastpunct = NULL;
2161 while (n != NULL) {
2162 if (n->tok != MDOC_Xr ||
2163 n->child == NULL ||
2164 n->child->next == NULL)
2165 break;
2167 /* Process one .Xr node. */
2169 name = n->child->string;
2170 sec = n->child->next->string;
2171 if (lastsec != NULL) {
2172 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2173 mandoc_vmsg(MANDOCERR_XR_PUNCT,
2174 mdoc->parse, n->line, n->pos,
2175 "%s before %s(%s)", lastpunct,
2176 name, sec);
2177 cmp = strcmp(lastsec, sec);
2178 if (cmp > 0)
2179 mandoc_vmsg(MANDOCERR_XR_ORDER,
2180 mdoc->parse, n->line, n->pos,
2181 "%s(%s) after %s(%s)", name,
2182 sec, lastname, lastsec);
2183 else if (cmp == 0 &&
2184 strcasecmp(lastname, name) > 0)
2185 mandoc_vmsg(MANDOCERR_XR_ORDER,
2186 mdoc->parse, n->line, n->pos,
2187 "%s after %s", name, lastname);
2189 lastname = name;
2190 lastsec = sec;
2192 /* Process the following node. */
2194 n = n->next;
2195 if (n == NULL)
2196 break;
2197 if (n->tok == MDOC_Xr) {
2198 lastpunct = "none";
2199 continue;
2201 if (n->type != ROFFT_TEXT)
2202 break;
2203 for (name = n->string; *name != '\0'; name++)
2204 if (isalpha((const unsigned char)*name))
2205 return;
2206 lastpunct = n->string;
2207 if (n->next == NULL || n->next->tok == MDOC_Rs)
2208 mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
2209 n->line, n->pos, "%s after %s(%s)",
2210 lastpunct, lastname, lastsec);
2211 n = n->next;
2215 static int
2216 child_an(const struct roff_node *n)
2219 for (n = n->child; n != NULL; n = n->next)
2220 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2221 return 1;
2222 return 0;
2225 static void
2226 post_sh_authors(POST_ARGS)
2229 if ( ! child_an(mdoc->last))
2230 mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
2231 mdoc->last->line, mdoc->last->pos, NULL);
2235 * Return an upper bound for the string distance (allowing
2236 * transpositions). Not a full Levenshtein implementation
2237 * because Levenshtein is quadratic in the string length
2238 * and this function is called for every standard name,
2239 * so the check for each custom name would be cubic.
2240 * The following crude heuristics is linear, resulting
2241 * in quadratic behaviour for checking one custom name,
2242 * which does not cause measurable slowdown.
2244 static int
2245 similar(const char *s1, const char *s2)
2247 const int maxdist = 3;
2248 int dist = 0;
2250 while (s1[0] != '\0' && s2[0] != '\0') {
2251 if (s1[0] == s2[0]) {
2252 s1++;
2253 s2++;
2254 continue;
2256 if (++dist > maxdist)
2257 return INT_MAX;
2258 if (s1[1] == s2[1]) { /* replacement */
2259 s1++;
2260 s2++;
2261 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2262 s1 += 2; /* transposition */
2263 s2 += 2;
2264 } else if (s1[0] == s2[1]) /* insertion */
2265 s2++;
2266 else if (s1[1] == s2[0]) /* deletion */
2267 s1++;
2268 else
2269 return INT_MAX;
2271 dist += strlen(s1) + strlen(s2);
2272 return dist > maxdist ? INT_MAX : dist;
2275 static void
2276 post_sh_head(POST_ARGS)
2278 struct roff_node *nch;
2279 const char *goodsec;
2280 const char *const *testsec;
2281 int dist, mindist;
2282 enum roff_sec sec;
2285 * Process a new section. Sections are either "named" or
2286 * "custom". Custom sections are user-defined, while named ones
2287 * follow a conventional order and may only appear in certain
2288 * manual sections.
2291 sec = mdoc->last->sec;
2293 /* The NAME should be first. */
2295 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2296 mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
2297 mdoc->last->line, mdoc->last->pos, "Sh %s",
2298 sec != SEC_CUSTOM ? secnames[sec] :
2299 (nch = mdoc->last->child) == NULL ? "" :
2300 nch->type == ROFFT_TEXT ? nch->string :
2301 roff_name[nch->tok]);
2303 /* The SYNOPSIS gets special attention in other areas. */
2305 if (sec == SEC_SYNOPSIS) {
2306 roff_setreg(mdoc->roff, "nS", 1, '=');
2307 mdoc->flags |= MDOC_SYNOPSIS;
2308 } else {
2309 roff_setreg(mdoc->roff, "nS", 0, '=');
2310 mdoc->flags &= ~MDOC_SYNOPSIS;
2313 /* Mark our last section. */
2315 mdoc->lastsec = sec;
2317 /* We don't care about custom sections after this. */
2319 if (sec == SEC_CUSTOM) {
2320 if ((nch = mdoc->last->child) == NULL ||
2321 nch->type != ROFFT_TEXT || nch->next != NULL)
2322 return;
2323 goodsec = NULL;
2324 mindist = INT_MAX;
2325 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2326 dist = similar(nch->string, *testsec);
2327 if (dist < mindist) {
2328 goodsec = *testsec;
2329 mindist = dist;
2332 if (goodsec != NULL)
2333 mandoc_vmsg(MANDOCERR_SEC_TYPO, mdoc->parse,
2334 nch->line, nch->pos, "Sh %s instead of %s",
2335 nch->string, goodsec);
2336 return;
2340 * Check whether our non-custom section is being repeated or is
2341 * out of order.
2344 if (sec == mdoc->lastnamed)
2345 mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
2346 mdoc->last->line, mdoc->last->pos,
2347 "Sh %s", secnames[sec]);
2349 if (sec < mdoc->lastnamed)
2350 mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
2351 mdoc->last->line, mdoc->last->pos,
2352 "Sh %s", secnames[sec]);
2354 /* Mark the last named section. */
2356 mdoc->lastnamed = sec;
2358 /* Check particular section/manual conventions. */
2360 if (mdoc->meta.msec == NULL)
2361 return;
2363 goodsec = NULL;
2364 switch (sec) {
2365 case SEC_ERRORS:
2366 if (*mdoc->meta.msec == '4')
2367 break;
2368 goodsec = "2, 3, 4, 9";
2369 /* FALLTHROUGH */
2370 case SEC_RETURN_VALUES:
2371 case SEC_LIBRARY:
2372 if (*mdoc->meta.msec == '2')
2373 break;
2374 if (*mdoc->meta.msec == '3')
2375 break;
2376 if (NULL == goodsec)
2377 goodsec = "2, 3, 9";
2378 /* FALLTHROUGH */
2379 case SEC_CONTEXT:
2380 if (*mdoc->meta.msec == '9')
2381 break;
2382 if (NULL == goodsec)
2383 goodsec = "9";
2384 mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
2385 mdoc->last->line, mdoc->last->pos,
2386 "Sh %s for %s only", secnames[sec], goodsec);
2387 break;
2388 default:
2389 break;
2393 static void
2394 post_xr(POST_ARGS)
2396 struct roff_node *n, *nch;
2398 n = mdoc->last;
2399 nch = n->child;
2400 if (nch->next == NULL) {
2401 mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse,
2402 n->line, n->pos, "Xr %s", nch->string);
2403 } else {
2404 assert(nch->next == n->last);
2405 if(mandoc_xr_add(nch->next->string, nch->string,
2406 nch->line, nch->pos))
2407 mandoc_vmsg(MANDOCERR_XR_SELF, mdoc->parse,
2408 nch->line, nch->pos, "Xr %s %s",
2409 nch->string, nch->next->string);
2411 post_delim_nb(mdoc);
2414 static void
2415 post_ignpar(POST_ARGS)
2417 struct roff_node *np;
2419 switch (mdoc->last->type) {
2420 case ROFFT_BLOCK:
2421 post_prevpar(mdoc);
2422 return;
2423 case ROFFT_HEAD:
2424 post_delim(mdoc);
2425 post_hyph(mdoc);
2426 return;
2427 case ROFFT_BODY:
2428 break;
2429 default:
2430 return;
2433 if ((np = mdoc->last->child) != NULL)
2434 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2435 mandoc_vmsg(MANDOCERR_PAR_SKIP,
2436 mdoc->parse, np->line, np->pos,
2437 "%s after %s", roff_name[np->tok],
2438 roff_name[mdoc->last->tok]);
2439 roff_node_delete(mdoc, np);
2442 if ((np = mdoc->last->last) != NULL)
2443 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2444 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2445 np->line, np->pos, "%s at the end of %s",
2446 roff_name[np->tok],
2447 roff_name[mdoc->last->tok]);
2448 roff_node_delete(mdoc, np);
2452 static void
2453 post_prevpar(POST_ARGS)
2455 struct roff_node *n;
2457 n = mdoc->last;
2458 if (NULL == n->prev)
2459 return;
2460 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2461 return;
2464 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
2465 * block: `Lp', `Pp', or non-compact `Bd' or `Bl'.
2468 if (n->prev->tok != MDOC_Pp &&
2469 n->prev->tok != MDOC_Lp &&
2470 n->prev->tok != ROFF_br)
2471 return;
2472 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2473 return;
2474 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2475 return;
2476 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2477 return;
2479 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2480 n->prev->line, n->prev->pos, "%s before %s",
2481 roff_name[n->prev->tok], roff_name[n->tok]);
2482 roff_node_delete(mdoc, n->prev);
2485 static void
2486 post_par(POST_ARGS)
2488 struct roff_node *np;
2490 np = mdoc->last;
2491 if (np->tok != ROFF_br && np->tok != ROFF_sp)
2492 post_prevpar(mdoc);
2494 if (np->tok == ROFF_sp) {
2495 if (np->child != NULL && np->child->next != NULL)
2496 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2497 np->child->next->line, np->child->next->pos,
2498 "sp ... %s", np->child->next->string);
2499 } else if (np->child != NULL)
2500 mandoc_vmsg(MANDOCERR_ARG_SKIP,
2501 mdoc->parse, np->line, np->pos, "%s %s",
2502 roff_name[np->tok], np->child->string);
2504 if ((np = mdoc->last->prev) == NULL) {
2505 np = mdoc->last->parent;
2506 if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
2507 return;
2508 } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
2509 (mdoc->last->tok != ROFF_br ||
2510 (np->tok != ROFF_sp && np->tok != ROFF_br)))
2511 return;
2513 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2514 mdoc->last->line, mdoc->last->pos, "%s after %s",
2515 roff_name[mdoc->last->tok], roff_name[np->tok]);
2516 roff_node_delete(mdoc, mdoc->last);
2519 static void
2520 post_dd(POST_ARGS)
2522 struct roff_node *n;
2523 char *datestr;
2525 n = mdoc->last;
2526 n->flags |= NODE_NOPRT;
2528 if (mdoc->meta.date != NULL) {
2529 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2530 n->line, n->pos, "Dd");
2531 free(mdoc->meta.date);
2532 } else if (mdoc->flags & MDOC_PBODY)
2533 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2534 n->line, n->pos, "Dd");
2535 else if (mdoc->meta.title != NULL)
2536 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2537 n->line, n->pos, "Dd after Dt");
2538 else if (mdoc->meta.os != NULL)
2539 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2540 n->line, n->pos, "Dd after Os");
2542 if (n->child == NULL || n->child->string[0] == '\0') {
2543 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
2544 mandoc_normdate(mdoc, NULL, n->line, n->pos);
2545 return;
2548 datestr = NULL;
2549 deroff(&datestr, n);
2550 if (mdoc->quick)
2551 mdoc->meta.date = datestr;
2552 else {
2553 mdoc->meta.date = mandoc_normdate(mdoc,
2554 datestr, n->line, n->pos);
2555 free(datestr);
2559 static void
2560 post_dt(POST_ARGS)
2562 struct roff_node *nn, *n;
2563 const char *cp;
2564 char *p;
2566 n = mdoc->last;
2567 n->flags |= NODE_NOPRT;
2569 if (mdoc->flags & MDOC_PBODY) {
2570 mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2571 n->line, n->pos, "Dt");
2572 return;
2575 if (mdoc->meta.title != NULL)
2576 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2577 n->line, n->pos, "Dt");
2578 else if (mdoc->meta.os != NULL)
2579 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2580 n->line, n->pos, "Dt after Os");
2582 free(mdoc->meta.title);
2583 free(mdoc->meta.msec);
2584 free(mdoc->meta.vol);
2585 free(mdoc->meta.arch);
2587 mdoc->meta.title = NULL;
2588 mdoc->meta.msec = NULL;
2589 mdoc->meta.vol = NULL;
2590 mdoc->meta.arch = NULL;
2592 /* Mandatory first argument: title. */
2594 nn = n->child;
2595 if (nn == NULL || *nn->string == '\0') {
2596 mandoc_msg(MANDOCERR_DT_NOTITLE,
2597 mdoc->parse, n->line, n->pos, "Dt");
2598 mdoc->meta.title = mandoc_strdup("UNTITLED");
2599 } else {
2600 mdoc->meta.title = mandoc_strdup(nn->string);
2602 /* Check that all characters are uppercase. */
2604 for (p = nn->string; *p != '\0'; p++)
2605 if (islower((unsigned char)*p)) {
2606 mandoc_vmsg(MANDOCERR_TITLE_CASE,
2607 mdoc->parse, nn->line,
2608 nn->pos + (p - nn->string),
2609 "Dt %s", nn->string);
2610 break;
2614 /* Mandatory second argument: section. */
2616 if (nn != NULL)
2617 nn = nn->next;
2619 if (nn == NULL) {
2620 mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2621 mdoc->parse, n->line, n->pos,
2622 "Dt %s", mdoc->meta.title);
2623 mdoc->meta.vol = mandoc_strdup("LOCAL");
2624 return; /* msec and arch remain NULL. */
2627 mdoc->meta.msec = mandoc_strdup(nn->string);
2629 /* Infer volume title from section number. */
2631 cp = mandoc_a2msec(nn->string);
2632 if (cp == NULL) {
2633 mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2634 nn->line, nn->pos, "Dt ... %s", nn->string);
2635 mdoc->meta.vol = mandoc_strdup(nn->string);
2636 } else
2637 mdoc->meta.vol = mandoc_strdup(cp);
2639 /* Optional third argument: architecture. */
2641 if ((nn = nn->next) == NULL)
2642 return;
2644 for (p = nn->string; *p != '\0'; p++)
2645 *p = tolower((unsigned char)*p);
2646 mdoc->meta.arch = mandoc_strdup(nn->string);
2648 /* Ignore fourth and later arguments. */
2650 if ((nn = nn->next) != NULL)
2651 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2652 nn->line, nn->pos, "Dt ... %s", nn->string);
2655 static void
2656 post_bx(POST_ARGS)
2658 struct roff_node *n, *nch;
2659 const char *macro;
2661 post_delim_nb(mdoc);
2663 n = mdoc->last;
2664 nch = n->child;
2666 if (nch != NULL) {
2667 macro = !strcmp(nch->string, "Open") ? "Ox" :
2668 !strcmp(nch->string, "Net") ? "Nx" :
2669 !strcmp(nch->string, "Free") ? "Fx" :
2670 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2671 if (macro != NULL)
2672 mandoc_msg(MANDOCERR_BX, mdoc->parse,
2673 n->line, n->pos, macro);
2674 mdoc->last = nch;
2675 nch = nch->next;
2676 mdoc->next = ROFF_NEXT_SIBLING;
2677 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2678 mdoc->last->flags |= NODE_NOSRC;
2679 mdoc->next = ROFF_NEXT_SIBLING;
2680 } else
2681 mdoc->next = ROFF_NEXT_CHILD;
2682 roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2683 mdoc->last->flags |= NODE_NOSRC;
2685 if (nch == NULL) {
2686 mdoc->last = n;
2687 return;
2690 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2691 mdoc->last->flags |= NODE_NOSRC;
2692 mdoc->next = ROFF_NEXT_SIBLING;
2693 roff_word_alloc(mdoc, n->line, n->pos, "-");
2694 mdoc->last->flags |= NODE_NOSRC;
2695 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2696 mdoc->last->flags |= NODE_NOSRC;
2697 mdoc->last = n;
2700 * Make `Bx's second argument always start with an uppercase
2701 * letter. Groff checks if it's an "accepted" term, but we just
2702 * uppercase blindly.
2705 *nch->string = (char)toupper((unsigned char)*nch->string);
2708 static void
2709 post_os(POST_ARGS)
2711 #ifndef OSNAME
2712 struct utsname utsname;
2713 static char *defbuf;
2714 #endif
2715 struct roff_node *n;
2717 n = mdoc->last;
2718 n->flags |= NODE_NOPRT;
2720 if (mdoc->meta.os != NULL)
2721 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2722 n->line, n->pos, "Os");
2723 else if (mdoc->flags & MDOC_PBODY)
2724 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2725 n->line, n->pos, "Os");
2727 post_delim(mdoc);
2730 * Set the operating system by way of the `Os' macro.
2731 * The order of precedence is:
2732 * 1. the argument of the `Os' macro, unless empty
2733 * 2. the -Ios=foo command line argument, if provided
2734 * 3. -DOSNAME="\"foo\"", if provided during compilation
2735 * 4. "sysname release" from uname(3)
2738 free(mdoc->meta.os);
2739 mdoc->meta.os = NULL;
2740 deroff(&mdoc->meta.os, n);
2741 if (mdoc->meta.os)
2742 goto out;
2744 if (mdoc->os_s != NULL) {
2745 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2746 goto out;
2749 #ifdef OSNAME
2750 mdoc->meta.os = mandoc_strdup(OSNAME);
2751 #else /*!OSNAME */
2752 if (defbuf == NULL) {
2753 if (uname(&utsname) == -1) {
2754 mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2755 n->line, n->pos, "Os");
2756 defbuf = mandoc_strdup("UNKNOWN");
2757 } else
2758 mandoc_asprintf(&defbuf, "%s %s",
2759 utsname.sysname, utsname.release);
2761 mdoc->meta.os = mandoc_strdup(defbuf);
2762 #endif /*!OSNAME*/
2764 out:
2765 if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2766 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2767 mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2768 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2769 mdoc->meta.os_e = MANDOC_OS_NETBSD;
2773 * This is the earliest point where we can check
2774 * Mdocdate conventions because we don't know
2775 * the operating system earlier.
2778 if (n->child != NULL)
2779 mandoc_vmsg(MANDOCERR_OS_ARG, mdoc->parse,
2780 n->child->line, n->child->pos,
2781 "Os %s (%s)", n->child->string,
2782 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2783 "OpenBSD" : "NetBSD");
2785 while (n->tok != MDOC_Dd)
2786 if ((n = n->prev) == NULL)
2787 return;
2788 if ((n = n->child) == NULL)
2789 return;
2790 if (strncmp(n->string, "$" "Mdocdate", 9)) {
2791 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2792 mandoc_vmsg(MANDOCERR_MDOCDATE_MISSING,
2793 mdoc->parse, n->line, n->pos,
2794 "Dd %s (OpenBSD)", n->string);
2795 } else {
2796 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2797 mandoc_vmsg(MANDOCERR_MDOCDATE,
2798 mdoc->parse, n->line, n->pos,
2799 "Dd %s (NetBSD)", n->string);
2803 enum roff_sec
2804 mdoc_a2sec(const char *p)
2806 int i;
2808 for (i = 0; i < (int)SEC__MAX; i++)
2809 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2810 return (enum roff_sec)i;
2812 return SEC_CUSTOM;
2815 static size_t
2816 macro2len(enum roff_tok macro)
2819 switch (macro) {
2820 case MDOC_Ad:
2821 return 12;
2822 case MDOC_Ao:
2823 return 12;
2824 case MDOC_An:
2825 return 12;
2826 case MDOC_Aq:
2827 return 12;
2828 case MDOC_Ar:
2829 return 12;
2830 case MDOC_Bo:
2831 return 12;
2832 case MDOC_Bq:
2833 return 12;
2834 case MDOC_Cd:
2835 return 12;
2836 case MDOC_Cm:
2837 return 10;
2838 case MDOC_Do:
2839 return 10;
2840 case MDOC_Dq:
2841 return 12;
2842 case MDOC_Dv:
2843 return 12;
2844 case MDOC_Eo:
2845 return 12;
2846 case MDOC_Em:
2847 return 10;
2848 case MDOC_Er:
2849 return 17;
2850 case MDOC_Ev:
2851 return 15;
2852 case MDOC_Fa:
2853 return 12;
2854 case MDOC_Fl:
2855 return 10;
2856 case MDOC_Fo:
2857 return 16;
2858 case MDOC_Fn:
2859 return 16;
2860 case MDOC_Ic:
2861 return 10;
2862 case MDOC_Li:
2863 return 16;
2864 case MDOC_Ms:
2865 return 6;
2866 case MDOC_Nm:
2867 return 10;
2868 case MDOC_No:
2869 return 12;
2870 case MDOC_Oo:
2871 return 10;
2872 case MDOC_Op:
2873 return 14;
2874 case MDOC_Pa:
2875 return 32;
2876 case MDOC_Pf:
2877 return 12;
2878 case MDOC_Po:
2879 return 12;
2880 case MDOC_Pq:
2881 return 12;
2882 case MDOC_Ql:
2883 return 16;
2884 case MDOC_Qo:
2885 return 12;
2886 case MDOC_So:
2887 return 12;
2888 case MDOC_Sq:
2889 return 12;
2890 case MDOC_Sy:
2891 return 6;
2892 case MDOC_Sx:
2893 return 16;
2894 case MDOC_Tn:
2895 return 10;
2896 case MDOC_Va:
2897 return 12;
2898 case MDOC_Vt:
2899 return 12;
2900 case MDOC_Xr:
2901 return 10;
2902 default:
2903 break;
2905 return 0;