mandoc: update to 1.14.2
[unleashed.git] / bin / mandoc / mdoc_validate.c
blobde36bb843eb2600732ff4cbd7415f43e0859c4c6
1 /* $Id: mdoc_validate.c,v 1.350 2017/07/20 12:54:02 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);
1140 if (n->sec == SEC_SYNOPSIS && mdoc->meta.msec != NULL)
1141 mandoc_xr_add(mdoc->meta.msec, n->string, -1, -1);
1144 static void
1145 post_fn(POST_ARGS)
1148 post_fname(mdoc);
1149 post_fa(mdoc);
1152 static void
1153 post_fo(POST_ARGS)
1155 const struct roff_node *n;
1157 n = mdoc->last;
1159 if (n->type != ROFFT_HEAD)
1160 return;
1162 if (n->child == NULL) {
1163 mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
1164 n->line, n->pos, "Fo");
1165 return;
1167 if (n->child != n->last) {
1168 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1169 n->child->next->line, n->child->next->pos,
1170 "Fo ... %s", n->child->next->string);
1171 while (n->child != n->last)
1172 roff_node_delete(mdoc, n->last);
1173 } else
1174 post_delim(mdoc);
1176 post_fname(mdoc);
1179 static void
1180 post_fa(POST_ARGS)
1182 const struct roff_node *n;
1183 const char *cp;
1185 for (n = mdoc->last->child; n != NULL; n = n->next) {
1186 for (cp = n->string; *cp != '\0'; cp++) {
1187 /* Ignore callbacks and alterations. */
1188 if (*cp == '(' || *cp == '{')
1189 break;
1190 if (*cp != ',')
1191 continue;
1192 mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
1193 n->line, n->pos + (cp - n->string),
1194 n->string);
1195 break;
1198 post_delim_nb(mdoc);
1201 static void
1202 post_nm(POST_ARGS)
1204 struct roff_node *n;
1206 n = mdoc->last;
1208 if ((n->sec == SEC_NAME || n->sec == SEC_SYNOPSIS) &&
1209 n->child != NULL && n->child->type == ROFFT_TEXT &&
1210 mdoc->meta.msec != NULL)
1211 mandoc_xr_add(mdoc->meta.msec, n->child->string, -1, -1);
1213 if (n->last != NULL &&
1214 (n->last->tok == MDOC_Pp ||
1215 n->last->tok == MDOC_Lp))
1216 mdoc_node_relink(mdoc, n->last);
1218 if (mdoc->meta.name == NULL)
1219 deroff(&mdoc->meta.name, n);
1221 if (mdoc->meta.name == NULL ||
1222 (mdoc->lastsec == SEC_NAME && n->child == NULL))
1223 mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
1224 n->line, n->pos, "Nm");
1226 switch (n->type) {
1227 case ROFFT_ELEM:
1228 post_delim_nb(mdoc);
1229 break;
1230 case ROFFT_HEAD:
1231 post_delim(mdoc);
1232 break;
1233 default:
1234 return;
1237 if ((n->child != NULL && n->child->type == ROFFT_TEXT) ||
1238 mdoc->meta.name == NULL)
1239 return;
1241 mdoc->next = ROFF_NEXT_CHILD;
1242 roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
1243 mdoc->last->flags |= NODE_NOSRC;
1244 mdoc->last = n;
1247 static void
1248 post_nd(POST_ARGS)
1250 struct roff_node *n;
1252 n = mdoc->last;
1254 if (n->type != ROFFT_BODY)
1255 return;
1257 if (n->sec != SEC_NAME)
1258 mandoc_msg(MANDOCERR_ND_LATE, mdoc->parse,
1259 n->line, n->pos, "Nd");
1261 if (n->child == NULL)
1262 mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
1263 n->line, n->pos, "Nd");
1264 else
1265 post_delim(mdoc);
1267 post_hyph(mdoc);
1270 static void
1271 post_display(POST_ARGS)
1273 struct roff_node *n, *np;
1275 n = mdoc->last;
1276 switch (n->type) {
1277 case ROFFT_BODY:
1278 if (n->end != ENDBODY_NOT) {
1279 if (n->tok == MDOC_Bd &&
1280 n->body->parent->args == NULL)
1281 roff_node_delete(mdoc, n);
1282 } else if (n->child == NULL)
1283 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1284 n->line, n->pos, roff_name[n->tok]);
1285 else if (n->tok == MDOC_D1)
1286 post_hyph(mdoc);
1287 break;
1288 case ROFFT_BLOCK:
1289 if (n->tok == MDOC_Bd) {
1290 if (n->args == NULL) {
1291 mandoc_msg(MANDOCERR_BD_NOARG,
1292 mdoc->parse, n->line, n->pos, "Bd");
1293 mdoc->next = ROFF_NEXT_SIBLING;
1294 while (n->body->child != NULL)
1295 mdoc_node_relink(mdoc,
1296 n->body->child);
1297 roff_node_delete(mdoc, n);
1298 break;
1300 post_bd(mdoc);
1301 post_prevpar(mdoc);
1303 for (np = n->parent; np != NULL; np = np->parent) {
1304 if (np->type == ROFFT_BLOCK && np->tok == MDOC_Bd) {
1305 mandoc_vmsg(MANDOCERR_BD_NEST,
1306 mdoc->parse, n->line, n->pos,
1307 "%s in Bd", roff_name[n->tok]);
1308 break;
1311 break;
1312 default:
1313 break;
1317 static void
1318 post_defaults(POST_ARGS)
1320 struct roff_node *nn;
1322 if (mdoc->last->child != NULL) {
1323 post_delim_nb(mdoc);
1324 return;
1328 * The `Ar' defaults to "file ..." if no value is provided as an
1329 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
1330 * gets an empty string.
1333 nn = mdoc->last;
1334 switch (nn->tok) {
1335 case MDOC_Ar:
1336 mdoc->next = ROFF_NEXT_CHILD;
1337 roff_word_alloc(mdoc, nn->line, nn->pos, "file");
1338 mdoc->last->flags |= NODE_NOSRC;
1339 roff_word_alloc(mdoc, nn->line, nn->pos, "...");
1340 mdoc->last->flags |= NODE_NOSRC;
1341 break;
1342 case MDOC_Pa:
1343 case MDOC_Mt:
1344 mdoc->next = ROFF_NEXT_CHILD;
1345 roff_word_alloc(mdoc, nn->line, nn->pos, "~");
1346 mdoc->last->flags |= NODE_NOSRC;
1347 break;
1348 default:
1349 abort();
1351 mdoc->last = nn;
1354 static void
1355 post_at(POST_ARGS)
1357 struct roff_node *n, *nch;
1358 const char *att;
1360 n = mdoc->last;
1361 nch = n->child;
1364 * If we have a child, look it up in the standard keys. If a
1365 * key exist, use that instead of the child; if it doesn't,
1366 * prefix "AT&T UNIX " to the existing data.
1369 att = NULL;
1370 if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
1371 mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
1372 nch->line, nch->pos, "At %s", nch->string);
1374 mdoc->next = ROFF_NEXT_CHILD;
1375 if (att != NULL) {
1376 roff_word_alloc(mdoc, nch->line, nch->pos, att);
1377 nch->flags |= NODE_NOPRT;
1378 } else
1379 roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
1380 mdoc->last->flags |= NODE_NOSRC;
1381 mdoc->last = n;
1384 static void
1385 post_an(POST_ARGS)
1387 struct roff_node *np, *nch;
1389 post_an_norm(mdoc);
1391 np = mdoc->last;
1392 nch = np->child;
1393 if (np->norm->An.auth == AUTH__NONE) {
1394 if (nch == NULL)
1395 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1396 np->line, np->pos, "An");
1397 else
1398 post_delim_nb(mdoc);
1399 } else if (nch != NULL)
1400 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1401 nch->line, nch->pos, "An ... %s", nch->string);
1404 static void
1405 post_en(POST_ARGS)
1408 post_obsolete(mdoc);
1409 if (mdoc->last->type == ROFFT_BLOCK)
1410 mdoc->last->norm->Es = mdoc->last_es;
1413 static void
1414 post_es(POST_ARGS)
1417 post_obsolete(mdoc);
1418 mdoc->last_es = mdoc->last;
1421 static void
1422 post_xx(POST_ARGS)
1424 struct roff_node *n;
1425 const char *os;
1426 char *v;
1428 post_delim_nb(mdoc);
1430 n = mdoc->last;
1431 switch (n->tok) {
1432 case MDOC_Bsx:
1433 os = "BSD/OS";
1434 break;
1435 case MDOC_Dx:
1436 os = "DragonFly";
1437 break;
1438 case MDOC_Fx:
1439 os = "FreeBSD";
1440 break;
1441 case MDOC_Nx:
1442 os = "NetBSD";
1443 if (n->child == NULL)
1444 break;
1445 v = n->child->string;
1446 if ((v[0] != '0' && v[0] != '1') || v[1] != '.' ||
1447 v[2] < '0' || v[2] > '9' ||
1448 v[3] < 'a' || v[3] > 'z' || v[4] != '\0')
1449 break;
1450 n->child->flags |= NODE_NOPRT;
1451 mdoc->next = ROFF_NEXT_CHILD;
1452 roff_word_alloc(mdoc, n->child->line, n->child->pos, v);
1453 v = mdoc->last->string;
1454 v[3] = toupper((unsigned char)v[3]);
1455 mdoc->last->flags |= NODE_NOSRC;
1456 mdoc->last = n;
1457 break;
1458 case MDOC_Ox:
1459 os = "OpenBSD";
1460 break;
1461 case MDOC_Ux:
1462 os = "UNIX";
1463 break;
1464 default:
1465 abort();
1467 mdoc->next = ROFF_NEXT_CHILD;
1468 roff_word_alloc(mdoc, n->line, n->pos, os);
1469 mdoc->last->flags |= NODE_NOSRC;
1470 mdoc->last = n;
1473 static void
1474 post_it(POST_ARGS)
1476 struct roff_node *nbl, *nit, *nch;
1477 int i, cols;
1478 enum mdoc_list lt;
1480 post_prevpar(mdoc);
1482 nit = mdoc->last;
1483 if (nit->type != ROFFT_BLOCK)
1484 return;
1486 nbl = nit->parent->parent;
1487 lt = nbl->norm->Bl.type;
1489 switch (lt) {
1490 case LIST_tag:
1491 case LIST_hang:
1492 case LIST_ohang:
1493 case LIST_inset:
1494 case LIST_diag:
1495 if (nit->head->child == NULL)
1496 mandoc_vmsg(MANDOCERR_IT_NOHEAD,
1497 mdoc->parse, nit->line, nit->pos,
1498 "Bl -%s It",
1499 mdoc_argnames[nbl->args->argv[0].arg]);
1500 break;
1501 case LIST_bullet:
1502 case LIST_dash:
1503 case LIST_enum:
1504 case LIST_hyphen:
1505 if (nit->body == NULL || nit->body->child == NULL)
1506 mandoc_vmsg(MANDOCERR_IT_NOBODY,
1507 mdoc->parse, nit->line, nit->pos,
1508 "Bl -%s It",
1509 mdoc_argnames[nbl->args->argv[0].arg]);
1510 /* FALLTHROUGH */
1511 case LIST_item:
1512 if ((nch = nit->head->child) != NULL)
1513 mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse,
1514 nit->line, nit->pos, "It %s",
1515 nch->string == NULL ? roff_name[nch->tok] :
1516 nch->string);
1517 break;
1518 case LIST_column:
1519 cols = (int)nbl->norm->Bl.ncols;
1521 assert(nit->head->child == NULL);
1523 if (nit->head->next->child == NULL &&
1524 nit->head->next->next == NULL) {
1525 mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
1526 nit->line, nit->pos, "It");
1527 roff_node_delete(mdoc, nit);
1528 break;
1531 i = 0;
1532 for (nch = nit->child; nch != NULL; nch = nch->next) {
1533 if (nch->type != ROFFT_BODY)
1534 continue;
1535 if (i++ && nch->flags & NODE_LINE)
1536 mandoc_msg(MANDOCERR_TA_LINE, mdoc->parse,
1537 nch->line, nch->pos, "Ta");
1539 if (i < cols || i > cols + 1)
1540 mandoc_vmsg(MANDOCERR_BL_COL,
1541 mdoc->parse, nit->line, nit->pos,
1542 "%d columns, %d cells", cols, i);
1543 else if (nit->head->next->child != NULL &&
1544 nit->head->next->child->line > nit->line)
1545 mandoc_msg(MANDOCERR_IT_NOARG, mdoc->parse,
1546 nit->line, nit->pos, "Bl -column It");
1547 break;
1548 default:
1549 abort();
1553 static void
1554 post_bl_block(POST_ARGS)
1556 struct roff_node *n, *ni, *nc;
1558 post_prevpar(mdoc);
1560 n = mdoc->last;
1561 for (ni = n->body->child; ni != NULL; ni = ni->next) {
1562 if (ni->body == NULL)
1563 continue;
1564 nc = ni->body->last;
1565 while (nc != NULL) {
1566 switch (nc->tok) {
1567 case MDOC_Pp:
1568 case MDOC_Lp:
1569 case ROFF_br:
1570 break;
1571 default:
1572 nc = NULL;
1573 continue;
1575 if (ni->next == NULL) {
1576 mandoc_msg(MANDOCERR_PAR_MOVE,
1577 mdoc->parse, nc->line, nc->pos,
1578 roff_name[nc->tok]);
1579 mdoc_node_relink(mdoc, nc);
1580 } else if (n->norm->Bl.comp == 0 &&
1581 n->norm->Bl.type != LIST_column) {
1582 mandoc_vmsg(MANDOCERR_PAR_SKIP,
1583 mdoc->parse, nc->line, nc->pos,
1584 "%s before It", roff_name[nc->tok]);
1585 roff_node_delete(mdoc, nc);
1586 } else
1587 break;
1588 nc = ni->body->last;
1594 * If the argument of -offset or -width is a macro,
1595 * replace it with the associated default width.
1597 static void
1598 rewrite_macro2len(struct roff_man *mdoc, char **arg)
1600 size_t width;
1601 enum roff_tok tok;
1603 if (*arg == NULL)
1604 return;
1605 else if ( ! strcmp(*arg, "Ds"))
1606 width = 6;
1607 else if ((tok = roffhash_find(mdoc->mdocmac, *arg, 0)) == TOKEN_NONE)
1608 return;
1609 else
1610 width = macro2len(tok);
1612 free(*arg);
1613 mandoc_asprintf(arg, "%zun", width);
1616 static void
1617 post_bl_head(POST_ARGS)
1619 struct roff_node *nbl, *nh, *nch, *nnext;
1620 struct mdoc_argv *argv;
1621 int i, j;
1623 post_bl_norm(mdoc);
1625 nh = mdoc->last;
1626 if (nh->norm->Bl.type != LIST_column) {
1627 if ((nch = nh->child) == NULL)
1628 return;
1629 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
1630 nch->line, nch->pos, "Bl ... %s", nch->string);
1631 while (nch != NULL) {
1632 roff_node_delete(mdoc, nch);
1633 nch = nh->child;
1635 return;
1639 * Append old-style lists, where the column width specifiers
1640 * trail as macro parameters, to the new-style ("normal-form")
1641 * lists where they're argument values following -column.
1644 if (nh->child == NULL)
1645 return;
1647 nbl = nh->parent;
1648 for (j = 0; j < (int)nbl->args->argc; j++)
1649 if (nbl->args->argv[j].arg == MDOC_Column)
1650 break;
1652 assert(j < (int)nbl->args->argc);
1655 * Accommodate for new-style groff column syntax. Shuffle the
1656 * child nodes, all of which must be TEXT, as arguments for the
1657 * column field. Then, delete the head children.
1660 argv = nbl->args->argv + j;
1661 i = argv->sz;
1662 for (nch = nh->child; nch != NULL; nch = nch->next)
1663 argv->sz++;
1664 argv->value = mandoc_reallocarray(argv->value,
1665 argv->sz, sizeof(char *));
1667 nh->norm->Bl.ncols = argv->sz;
1668 nh->norm->Bl.cols = (void *)argv->value;
1670 for (nch = nh->child; nch != NULL; nch = nnext) {
1671 argv->value[i++] = nch->string;
1672 nch->string = NULL;
1673 nnext = nch->next;
1674 roff_node_delete(NULL, nch);
1676 nh->child = NULL;
1679 static void
1680 post_bl(POST_ARGS)
1682 struct roff_node *nparent, *nprev; /* of the Bl block */
1683 struct roff_node *nblock, *nbody; /* of the Bl */
1684 struct roff_node *nchild, *nnext; /* of the Bl body */
1685 const char *prev_Er;
1686 int order;
1688 nbody = mdoc->last;
1689 switch (nbody->type) {
1690 case ROFFT_BLOCK:
1691 post_bl_block(mdoc);
1692 return;
1693 case ROFFT_HEAD:
1694 post_bl_head(mdoc);
1695 return;
1696 case ROFFT_BODY:
1697 break;
1698 default:
1699 return;
1701 if (nbody->end != ENDBODY_NOT)
1702 return;
1704 nchild = nbody->child;
1705 if (nchild == NULL) {
1706 mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
1707 nbody->line, nbody->pos, "Bl");
1708 return;
1710 while (nchild != NULL) {
1711 nnext = nchild->next;
1712 if (nchild->tok == MDOC_It ||
1713 (nchild->tok == MDOC_Sm &&
1714 nnext != NULL && nnext->tok == MDOC_It)) {
1715 nchild = nnext;
1716 continue;
1720 * In .Bl -column, the first rows may be implicit,
1721 * that is, they may not start with .It macros.
1722 * Such rows may be followed by nodes generated on the
1723 * roff level, for example .TS, which cannot be moved
1724 * out of the list. In that case, wrap such roff nodes
1725 * into an implicit row.
1728 if (nchild->prev != NULL) {
1729 mdoc->last = nchild;
1730 mdoc->next = ROFF_NEXT_SIBLING;
1731 roff_block_alloc(mdoc, nchild->line,
1732 nchild->pos, MDOC_It);
1733 roff_head_alloc(mdoc, nchild->line,
1734 nchild->pos, MDOC_It);
1735 mdoc->next = ROFF_NEXT_SIBLING;
1736 roff_body_alloc(mdoc, nchild->line,
1737 nchild->pos, MDOC_It);
1738 while (nchild->tok != MDOC_It) {
1739 mdoc_node_relink(mdoc, nchild);
1740 if ((nchild = nnext) == NULL)
1741 break;
1742 nnext = nchild->next;
1743 mdoc->next = ROFF_NEXT_SIBLING;
1745 mdoc->last = nbody;
1746 continue;
1749 mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
1750 nchild->line, nchild->pos, roff_name[nchild->tok]);
1753 * Move the node out of the Bl block.
1754 * First, collect all required node pointers.
1757 nblock = nbody->parent;
1758 nprev = nblock->prev;
1759 nparent = nblock->parent;
1762 * Unlink this child.
1765 nbody->child = nnext;
1766 if (nnext == NULL)
1767 nbody->last = NULL;
1768 else
1769 nnext->prev = NULL;
1772 * Relink this child.
1775 nchild->parent = nparent;
1776 nchild->prev = nprev;
1777 nchild->next = nblock;
1779 nblock->prev = nchild;
1780 if (nprev == NULL)
1781 nparent->child = nchild;
1782 else
1783 nprev->next = nchild;
1785 nchild = nnext;
1788 if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
1789 return;
1791 prev_Er = NULL;
1792 for (nchild = nbody->child; nchild != NULL; nchild = nchild->next) {
1793 if (nchild->tok != MDOC_It)
1794 continue;
1795 if ((nnext = nchild->head->child) == NULL)
1796 continue;
1797 if (nnext->type == ROFFT_BLOCK)
1798 nnext = nnext->body->child;
1799 if (nnext == NULL || nnext->tok != MDOC_Er)
1800 continue;
1801 nnext = nnext->child;
1802 if (prev_Er != NULL) {
1803 order = strcmp(prev_Er, nnext->string);
1804 if (order > 0)
1805 mandoc_vmsg(MANDOCERR_ER_ORDER,
1806 mdoc->parse, nnext->line, nnext->pos,
1807 "Er %s %s (NetBSD)",
1808 prev_Er, nnext->string);
1809 else if (order == 0)
1810 mandoc_vmsg(MANDOCERR_ER_REP,
1811 mdoc->parse, nnext->line, nnext->pos,
1812 "Er %s (NetBSD)", prev_Er);
1814 prev_Er = nnext->string;
1818 static void
1819 post_bk(POST_ARGS)
1821 struct roff_node *n;
1823 n = mdoc->last;
1825 if (n->type == ROFFT_BLOCK && n->body->child == NULL) {
1826 mandoc_msg(MANDOCERR_BLK_EMPTY,
1827 mdoc->parse, n->line, n->pos, "Bk");
1828 roff_node_delete(mdoc, n);
1832 static void
1833 post_sm(POST_ARGS)
1835 struct roff_node *nch;
1837 nch = mdoc->last->child;
1839 if (nch == NULL) {
1840 mdoc->flags ^= MDOC_SMOFF;
1841 return;
1844 assert(nch->type == ROFFT_TEXT);
1846 if ( ! strcmp(nch->string, "on")) {
1847 mdoc->flags &= ~MDOC_SMOFF;
1848 return;
1850 if ( ! strcmp(nch->string, "off")) {
1851 mdoc->flags |= MDOC_SMOFF;
1852 return;
1855 mandoc_vmsg(MANDOCERR_SM_BAD,
1856 mdoc->parse, nch->line, nch->pos,
1857 "%s %s", roff_name[mdoc->last->tok], nch->string);
1858 mdoc_node_relink(mdoc, nch);
1859 return;
1862 static void
1863 post_root(POST_ARGS)
1865 const char *openbsd_arch[] = {
1866 "alpha", "amd64", "arm64", "armv7", "hppa", "i386",
1867 "landisk", "loongson", "luna88k", "macppc", "mips64",
1868 "octeon", "sgi", "socppc", "sparc64", NULL
1870 const char *netbsd_arch[] = {
1871 "acorn26", "acorn32", "algor", "alpha", "amiga",
1872 "arc", "atari",
1873 "bebox", "cats", "cesfic", "cobalt", "dreamcast",
1874 "emips", "evbarm", "evbmips", "evbppc", "evbsh3", "evbsh5",
1875 "hp300", "hpcarm", "hpcmips", "hpcsh", "hppa",
1876 "i386", "ibmnws", "luna68k",
1877 "mac68k", "macppc", "mipsco", "mmeye", "mvme68k", "mvmeppc",
1878 "netwinder", "news68k", "newsmips", "next68k",
1879 "pc532", "playstation2", "pmax", "pmppc", "prep",
1880 "sandpoint", "sbmips", "sgimips", "shark",
1881 "sparc", "sparc64", "sun2", "sun3",
1882 "vax", "walnut", "x68k", "x86", "x86_64", "xen", NULL
1884 const char **arches[] = { NULL, netbsd_arch, openbsd_arch };
1886 struct roff_node *n;
1887 const char **arch;
1889 /* Add missing prologue data. */
1891 if (mdoc->meta.date == NULL)
1892 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
1893 mandoc_normdate(mdoc, NULL, 0, 0);
1895 if (mdoc->meta.title == NULL) {
1896 mandoc_msg(MANDOCERR_DT_NOTITLE,
1897 mdoc->parse, 0, 0, "EOF");
1898 mdoc->meta.title = mandoc_strdup("UNTITLED");
1901 if (mdoc->meta.vol == NULL)
1902 mdoc->meta.vol = mandoc_strdup("LOCAL");
1904 if (mdoc->meta.os == NULL) {
1905 mandoc_msg(MANDOCERR_OS_MISSING,
1906 mdoc->parse, 0, 0, NULL);
1907 mdoc->meta.os = mandoc_strdup("");
1908 } else if (mdoc->meta.os_e &&
1909 (mdoc->meta.rcsids & (1 << mdoc->meta.os_e)) == 0)
1910 mandoc_msg(MANDOCERR_RCS_MISSING, mdoc->parse, 0, 0,
1911 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1912 "(OpenBSD)" : "(NetBSD)");
1914 if (mdoc->meta.arch != NULL &&
1915 (arch = arches[mdoc->meta.os_e]) != NULL) {
1916 while (*arch != NULL && strcmp(*arch, mdoc->meta.arch))
1917 arch++;
1918 if (*arch == NULL) {
1919 n = mdoc->first->child;
1920 while (n->tok != MDOC_Dt)
1921 n = n->next;
1922 n = n->child->next->next;
1923 mandoc_vmsg(MANDOCERR_ARCH_BAD,
1924 mdoc->parse, n->line, n->pos,
1925 "Dt ... %s %s", mdoc->meta.arch,
1926 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
1927 "(OpenBSD)" : "(NetBSD)");
1931 /* Check that we begin with a proper `Sh'. */
1933 n = mdoc->first->child;
1934 while (n != NULL && n->tok != TOKEN_NONE &&
1935 mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
1936 n = n->next;
1938 if (n == NULL)
1939 mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
1940 else if (n->tok != MDOC_Sh)
1941 mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
1942 n->line, n->pos, roff_name[n->tok]);
1945 static void
1946 post_rs(POST_ARGS)
1948 struct roff_node *np, *nch, *next, *prev;
1949 int i, j;
1951 np = mdoc->last;
1953 if (np->type != ROFFT_BODY)
1954 return;
1956 if (np->child == NULL) {
1957 mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
1958 np->line, np->pos, "Rs");
1959 return;
1963 * The full `Rs' block needs special handling to order the
1964 * sub-elements according to `rsord'. Pick through each element
1965 * and correctly order it. This is an insertion sort.
1968 next = NULL;
1969 for (nch = np->child->next; nch != NULL; nch = next) {
1970 /* Determine order number of this child. */
1971 for (i = 0; i < RSORD_MAX; i++)
1972 if (rsord[i] == nch->tok)
1973 break;
1975 if (i == RSORD_MAX) {
1976 mandoc_msg(MANDOCERR_RS_BAD, mdoc->parse,
1977 nch->line, nch->pos, roff_name[nch->tok]);
1978 i = -1;
1979 } else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
1980 np->norm->Rs.quote_T++;
1983 * Remove this child from the chain. This somewhat
1984 * repeats roff_node_unlink(), but since we're
1985 * just re-ordering, there's no need for the
1986 * full unlink process.
1989 if ((next = nch->next) != NULL)
1990 next->prev = nch->prev;
1992 if ((prev = nch->prev) != NULL)
1993 prev->next = nch->next;
1995 nch->prev = nch->next = NULL;
1998 * Scan back until we reach a node that's
1999 * to be ordered before this child.
2002 for ( ; prev ; prev = prev->prev) {
2003 /* Determine order of `prev'. */
2004 for (j = 0; j < RSORD_MAX; j++)
2005 if (rsord[j] == prev->tok)
2006 break;
2007 if (j == RSORD_MAX)
2008 j = -1;
2010 if (j <= i)
2011 break;
2015 * Set this child back into its correct place
2016 * in front of the `prev' node.
2019 nch->prev = prev;
2021 if (prev == NULL) {
2022 np->child->prev = nch;
2023 nch->next = np->child;
2024 np->child = nch;
2025 } else {
2026 if (prev->next)
2027 prev->next->prev = nch;
2028 nch->next = prev->next;
2029 prev->next = nch;
2035 * For some arguments of some macros,
2036 * convert all breakable hyphens into ASCII_HYPH.
2038 static void
2039 post_hyph(POST_ARGS)
2041 struct roff_node *nch;
2042 char *cp;
2044 for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
2045 if (nch->type != ROFFT_TEXT)
2046 continue;
2047 cp = nch->string;
2048 if (*cp == '\0')
2049 continue;
2050 while (*(++cp) != '\0')
2051 if (*cp == '-' &&
2052 isalpha((unsigned char)cp[-1]) &&
2053 isalpha((unsigned char)cp[1]))
2054 *cp = ASCII_HYPH;
2058 static void
2059 post_ns(POST_ARGS)
2061 struct roff_node *n;
2063 n = mdoc->last;
2064 if (n->flags & NODE_LINE ||
2065 (n->next != NULL && n->next->flags & NODE_DELIMC))
2066 mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
2067 n->line, n->pos, NULL);
2070 static void
2071 post_sx(POST_ARGS)
2073 post_delim(mdoc);
2074 post_hyph(mdoc);
2077 static void
2078 post_sh(POST_ARGS)
2081 post_ignpar(mdoc);
2083 switch (mdoc->last->type) {
2084 case ROFFT_HEAD:
2085 post_sh_head(mdoc);
2086 break;
2087 case ROFFT_BODY:
2088 switch (mdoc->lastsec) {
2089 case SEC_NAME:
2090 post_sh_name(mdoc);
2091 break;
2092 case SEC_SEE_ALSO:
2093 post_sh_see_also(mdoc);
2094 break;
2095 case SEC_AUTHORS:
2096 post_sh_authors(mdoc);
2097 break;
2098 default:
2099 break;
2101 break;
2102 default:
2103 break;
2107 static void
2108 post_sh_name(POST_ARGS)
2110 struct roff_node *n;
2111 int hasnm, hasnd;
2113 hasnm = hasnd = 0;
2115 for (n = mdoc->last->child; n != NULL; n = n->next) {
2116 switch (n->tok) {
2117 case MDOC_Nm:
2118 if (hasnm && n->child != NULL)
2119 mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT,
2120 mdoc->parse, n->line, n->pos,
2121 "Nm %s", n->child->string);
2122 hasnm = 1;
2123 continue;
2124 case MDOC_Nd:
2125 hasnd = 1;
2126 if (n->next != NULL)
2127 mandoc_msg(MANDOCERR_NAMESEC_ND,
2128 mdoc->parse, n->line, n->pos, NULL);
2129 break;
2130 case TOKEN_NONE:
2131 if (n->type == ROFFT_TEXT &&
2132 n->string[0] == ',' && n->string[1] == '\0' &&
2133 n->next != NULL && n->next->tok == MDOC_Nm) {
2134 n = n->next;
2135 continue;
2137 /* FALLTHROUGH */
2138 default:
2139 mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
2140 n->line, n->pos, roff_name[n->tok]);
2141 continue;
2143 break;
2146 if ( ! hasnm)
2147 mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
2148 mdoc->last->line, mdoc->last->pos, NULL);
2149 if ( ! hasnd)
2150 mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
2151 mdoc->last->line, mdoc->last->pos, NULL);
2154 static void
2155 post_sh_see_also(POST_ARGS)
2157 const struct roff_node *n;
2158 const char *name, *sec;
2159 const char *lastname, *lastsec, *lastpunct;
2160 int cmp;
2162 n = mdoc->last->child;
2163 lastname = lastsec = lastpunct = NULL;
2164 while (n != NULL) {
2165 if (n->tok != MDOC_Xr ||
2166 n->child == NULL ||
2167 n->child->next == NULL)
2168 break;
2170 /* Process one .Xr node. */
2172 name = n->child->string;
2173 sec = n->child->next->string;
2174 if (lastsec != NULL) {
2175 if (lastpunct[0] != ',' || lastpunct[1] != '\0')
2176 mandoc_vmsg(MANDOCERR_XR_PUNCT,
2177 mdoc->parse, n->line, n->pos,
2178 "%s before %s(%s)", lastpunct,
2179 name, sec);
2180 cmp = strcmp(lastsec, sec);
2181 if (cmp > 0)
2182 mandoc_vmsg(MANDOCERR_XR_ORDER,
2183 mdoc->parse, n->line, n->pos,
2184 "%s(%s) after %s(%s)", name,
2185 sec, lastname, lastsec);
2186 else if (cmp == 0 &&
2187 strcasecmp(lastname, name) > 0)
2188 mandoc_vmsg(MANDOCERR_XR_ORDER,
2189 mdoc->parse, n->line, n->pos,
2190 "%s after %s", name, lastname);
2192 lastname = name;
2193 lastsec = sec;
2195 /* Process the following node. */
2197 n = n->next;
2198 if (n == NULL)
2199 break;
2200 if (n->tok == MDOC_Xr) {
2201 lastpunct = "none";
2202 continue;
2204 if (n->type != ROFFT_TEXT)
2205 break;
2206 for (name = n->string; *name != '\0'; name++)
2207 if (isalpha((const unsigned char)*name))
2208 return;
2209 lastpunct = n->string;
2210 if (n->next == NULL || n->next->tok == MDOC_Rs)
2211 mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
2212 n->line, n->pos, "%s after %s(%s)",
2213 lastpunct, lastname, lastsec);
2214 n = n->next;
2218 static int
2219 child_an(const struct roff_node *n)
2222 for (n = n->child; n != NULL; n = n->next)
2223 if ((n->tok == MDOC_An && n->child != NULL) || child_an(n))
2224 return 1;
2225 return 0;
2228 static void
2229 post_sh_authors(POST_ARGS)
2232 if ( ! child_an(mdoc->last))
2233 mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
2234 mdoc->last->line, mdoc->last->pos, NULL);
2238 * Return an upper bound for the string distance (allowing
2239 * transpositions). Not a full Levenshtein implementation
2240 * because Levenshtein is quadratic in the string length
2241 * and this function is called for every standard name,
2242 * so the check for each custom name would be cubic.
2243 * The following crude heuristics is linear, resulting
2244 * in quadratic behaviour for checking one custom name,
2245 * which does not cause measurable slowdown.
2247 static int
2248 similar(const char *s1, const char *s2)
2250 const int maxdist = 3;
2251 int dist = 0;
2253 while (s1[0] != '\0' && s2[0] != '\0') {
2254 if (s1[0] == s2[0]) {
2255 s1++;
2256 s2++;
2257 continue;
2259 if (++dist > maxdist)
2260 return INT_MAX;
2261 if (s1[1] == s2[1]) { /* replacement */
2262 s1++;
2263 s2++;
2264 } else if (s1[0] == s2[1] && s1[1] == s2[0]) {
2265 s1 += 2; /* transposition */
2266 s2 += 2;
2267 } else if (s1[0] == s2[1]) /* insertion */
2268 s2++;
2269 else if (s1[1] == s2[0]) /* deletion */
2270 s1++;
2271 else
2272 return INT_MAX;
2274 dist += strlen(s1) + strlen(s2);
2275 return dist > maxdist ? INT_MAX : dist;
2278 static void
2279 post_sh_head(POST_ARGS)
2281 struct roff_node *nch;
2282 const char *goodsec;
2283 const char *const *testsec;
2284 int dist, mindist;
2285 enum roff_sec sec;
2288 * Process a new section. Sections are either "named" or
2289 * "custom". Custom sections are user-defined, while named ones
2290 * follow a conventional order and may only appear in certain
2291 * manual sections.
2294 sec = mdoc->last->sec;
2296 /* The NAME should be first. */
2298 if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
2299 mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
2300 mdoc->last->line, mdoc->last->pos, "Sh %s",
2301 sec != SEC_CUSTOM ? secnames[sec] :
2302 (nch = mdoc->last->child) == NULL ? "" :
2303 nch->type == ROFFT_TEXT ? nch->string :
2304 roff_name[nch->tok]);
2306 /* The SYNOPSIS gets special attention in other areas. */
2308 if (sec == SEC_SYNOPSIS) {
2309 roff_setreg(mdoc->roff, "nS", 1, '=');
2310 mdoc->flags |= MDOC_SYNOPSIS;
2311 } else {
2312 roff_setreg(mdoc->roff, "nS", 0, '=');
2313 mdoc->flags &= ~MDOC_SYNOPSIS;
2316 /* Mark our last section. */
2318 mdoc->lastsec = sec;
2320 /* We don't care about custom sections after this. */
2322 if (sec == SEC_CUSTOM) {
2323 if ((nch = mdoc->last->child) == NULL ||
2324 nch->type != ROFFT_TEXT || nch->next != NULL)
2325 return;
2326 goodsec = NULL;
2327 mindist = INT_MAX;
2328 for (testsec = secnames + 1; *testsec != NULL; testsec++) {
2329 dist = similar(nch->string, *testsec);
2330 if (dist < mindist) {
2331 goodsec = *testsec;
2332 mindist = dist;
2335 if (goodsec != NULL)
2336 mandoc_vmsg(MANDOCERR_SEC_TYPO, mdoc->parse,
2337 nch->line, nch->pos, "Sh %s instead of %s",
2338 nch->string, goodsec);
2339 return;
2343 * Check whether our non-custom section is being repeated or is
2344 * out of order.
2347 if (sec == mdoc->lastnamed)
2348 mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
2349 mdoc->last->line, mdoc->last->pos,
2350 "Sh %s", secnames[sec]);
2352 if (sec < mdoc->lastnamed)
2353 mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
2354 mdoc->last->line, mdoc->last->pos,
2355 "Sh %s", secnames[sec]);
2357 /* Mark the last named section. */
2359 mdoc->lastnamed = sec;
2361 /* Check particular section/manual conventions. */
2363 if (mdoc->meta.msec == NULL)
2364 return;
2366 goodsec = NULL;
2367 switch (sec) {
2368 case SEC_ERRORS:
2369 if (*mdoc->meta.msec == '4')
2370 break;
2371 goodsec = "2, 3, 4, 9";
2372 /* FALLTHROUGH */
2373 case SEC_RETURN_VALUES:
2374 case SEC_LIBRARY:
2375 if (*mdoc->meta.msec == '2')
2376 break;
2377 if (*mdoc->meta.msec == '3')
2378 break;
2379 if (NULL == goodsec)
2380 goodsec = "2, 3, 9";
2381 /* FALLTHROUGH */
2382 case SEC_CONTEXT:
2383 if (*mdoc->meta.msec == '9')
2384 break;
2385 if (NULL == goodsec)
2386 goodsec = "9";
2387 mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
2388 mdoc->last->line, mdoc->last->pos,
2389 "Sh %s for %s only", secnames[sec], goodsec);
2390 break;
2391 default:
2392 break;
2396 static void
2397 post_xr(POST_ARGS)
2399 struct roff_node *n, *nch;
2401 n = mdoc->last;
2402 nch = n->child;
2403 if (nch->next == NULL) {
2404 mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse,
2405 n->line, n->pos, "Xr %s", nch->string);
2406 } else {
2407 assert(nch->next == n->last);
2408 if(mandoc_xr_add(nch->next->string, nch->string,
2409 nch->line, nch->pos))
2410 mandoc_vmsg(MANDOCERR_XR_SELF, mdoc->parse,
2411 nch->line, nch->pos, "Xr %s %s",
2412 nch->string, nch->next->string);
2414 post_delim_nb(mdoc);
2417 static void
2418 post_ignpar(POST_ARGS)
2420 struct roff_node *np;
2422 switch (mdoc->last->type) {
2423 case ROFFT_BLOCK:
2424 post_prevpar(mdoc);
2425 return;
2426 case ROFFT_HEAD:
2427 post_delim(mdoc);
2428 post_hyph(mdoc);
2429 return;
2430 case ROFFT_BODY:
2431 break;
2432 default:
2433 return;
2436 if ((np = mdoc->last->child) != NULL)
2437 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2438 mandoc_vmsg(MANDOCERR_PAR_SKIP,
2439 mdoc->parse, np->line, np->pos,
2440 "%s after %s", roff_name[np->tok],
2441 roff_name[mdoc->last->tok]);
2442 roff_node_delete(mdoc, np);
2445 if ((np = mdoc->last->last) != NULL)
2446 if (np->tok == MDOC_Pp || np->tok == MDOC_Lp) {
2447 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2448 np->line, np->pos, "%s at the end of %s",
2449 roff_name[np->tok],
2450 roff_name[mdoc->last->tok]);
2451 roff_node_delete(mdoc, np);
2455 static void
2456 post_prevpar(POST_ARGS)
2458 struct roff_node *n;
2460 n = mdoc->last;
2461 if (NULL == n->prev)
2462 return;
2463 if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
2464 return;
2467 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
2468 * block: `Lp', `Pp', or non-compact `Bd' or `Bl'.
2471 if (n->prev->tok != MDOC_Pp &&
2472 n->prev->tok != MDOC_Lp &&
2473 n->prev->tok != ROFF_br)
2474 return;
2475 if (n->tok == MDOC_Bl && n->norm->Bl.comp)
2476 return;
2477 if (n->tok == MDOC_Bd && n->norm->Bd.comp)
2478 return;
2479 if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
2480 return;
2482 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2483 n->prev->line, n->prev->pos, "%s before %s",
2484 roff_name[n->prev->tok], roff_name[n->tok]);
2485 roff_node_delete(mdoc, n->prev);
2488 static void
2489 post_par(POST_ARGS)
2491 struct roff_node *np;
2493 np = mdoc->last;
2494 if (np->tok != ROFF_br && np->tok != ROFF_sp)
2495 post_prevpar(mdoc);
2497 if (np->tok == ROFF_sp) {
2498 if (np->child != NULL && np->child->next != NULL)
2499 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2500 np->child->next->line, np->child->next->pos,
2501 "sp ... %s", np->child->next->string);
2502 } else if (np->child != NULL)
2503 mandoc_vmsg(MANDOCERR_ARG_SKIP,
2504 mdoc->parse, np->line, np->pos, "%s %s",
2505 roff_name[np->tok], np->child->string);
2507 if ((np = mdoc->last->prev) == NULL) {
2508 np = mdoc->last->parent;
2509 if (np->tok != MDOC_Sh && np->tok != MDOC_Ss)
2510 return;
2511 } else if (np->tok != MDOC_Pp && np->tok != MDOC_Lp &&
2512 (mdoc->last->tok != ROFF_br ||
2513 (np->tok != ROFF_sp && np->tok != ROFF_br)))
2514 return;
2516 mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
2517 mdoc->last->line, mdoc->last->pos, "%s after %s",
2518 roff_name[mdoc->last->tok], roff_name[np->tok]);
2519 roff_node_delete(mdoc, mdoc->last);
2522 static void
2523 post_dd(POST_ARGS)
2525 struct roff_node *n;
2526 char *datestr;
2528 n = mdoc->last;
2529 n->flags |= NODE_NOPRT;
2531 if (mdoc->meta.date != NULL) {
2532 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2533 n->line, n->pos, "Dd");
2534 free(mdoc->meta.date);
2535 } else if (mdoc->flags & MDOC_PBODY)
2536 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2537 n->line, n->pos, "Dd");
2538 else if (mdoc->meta.title != NULL)
2539 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2540 n->line, n->pos, "Dd after Dt");
2541 else if (mdoc->meta.os != NULL)
2542 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2543 n->line, n->pos, "Dd after Os");
2545 if (n->child == NULL || n->child->string[0] == '\0') {
2546 mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
2547 mandoc_normdate(mdoc, NULL, n->line, n->pos);
2548 return;
2551 datestr = NULL;
2552 deroff(&datestr, n);
2553 if (mdoc->quick)
2554 mdoc->meta.date = datestr;
2555 else {
2556 mdoc->meta.date = mandoc_normdate(mdoc,
2557 datestr, n->line, n->pos);
2558 free(datestr);
2562 static void
2563 post_dt(POST_ARGS)
2565 struct roff_node *nn, *n;
2566 const char *cp;
2567 char *p;
2569 n = mdoc->last;
2570 n->flags |= NODE_NOPRT;
2572 if (mdoc->flags & MDOC_PBODY) {
2573 mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
2574 n->line, n->pos, "Dt");
2575 return;
2578 if (mdoc->meta.title != NULL)
2579 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2580 n->line, n->pos, "Dt");
2581 else if (mdoc->meta.os != NULL)
2582 mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
2583 n->line, n->pos, "Dt after Os");
2585 free(mdoc->meta.title);
2586 free(mdoc->meta.msec);
2587 free(mdoc->meta.vol);
2588 free(mdoc->meta.arch);
2590 mdoc->meta.title = NULL;
2591 mdoc->meta.msec = NULL;
2592 mdoc->meta.vol = NULL;
2593 mdoc->meta.arch = NULL;
2595 /* Mandatory first argument: title. */
2597 nn = n->child;
2598 if (nn == NULL || *nn->string == '\0') {
2599 mandoc_msg(MANDOCERR_DT_NOTITLE,
2600 mdoc->parse, n->line, n->pos, "Dt");
2601 mdoc->meta.title = mandoc_strdup("UNTITLED");
2602 } else {
2603 mdoc->meta.title = mandoc_strdup(nn->string);
2605 /* Check that all characters are uppercase. */
2607 for (p = nn->string; *p != '\0'; p++)
2608 if (islower((unsigned char)*p)) {
2609 mandoc_vmsg(MANDOCERR_TITLE_CASE,
2610 mdoc->parse, nn->line,
2611 nn->pos + (p - nn->string),
2612 "Dt %s", nn->string);
2613 break;
2617 /* Mandatory second argument: section. */
2619 if (nn != NULL)
2620 nn = nn->next;
2622 if (nn == NULL) {
2623 mandoc_vmsg(MANDOCERR_MSEC_MISSING,
2624 mdoc->parse, n->line, n->pos,
2625 "Dt %s", mdoc->meta.title);
2626 mdoc->meta.vol = mandoc_strdup("LOCAL");
2627 return; /* msec and arch remain NULL. */
2630 mdoc->meta.msec = mandoc_strdup(nn->string);
2632 /* Infer volume title from section number. */
2634 cp = mandoc_a2msec(nn->string);
2635 if (cp == NULL) {
2636 mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
2637 nn->line, nn->pos, "Dt ... %s", nn->string);
2638 mdoc->meta.vol = mandoc_strdup(nn->string);
2639 } else
2640 mdoc->meta.vol = mandoc_strdup(cp);
2642 /* Optional third argument: architecture. */
2644 if ((nn = nn->next) == NULL)
2645 return;
2647 for (p = nn->string; *p != '\0'; p++)
2648 *p = tolower((unsigned char)*p);
2649 mdoc->meta.arch = mandoc_strdup(nn->string);
2651 /* Ignore fourth and later arguments. */
2653 if ((nn = nn->next) != NULL)
2654 mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
2655 nn->line, nn->pos, "Dt ... %s", nn->string);
2658 static void
2659 post_bx(POST_ARGS)
2661 struct roff_node *n, *nch;
2662 const char *macro;
2664 post_delim_nb(mdoc);
2666 n = mdoc->last;
2667 nch = n->child;
2669 if (nch != NULL) {
2670 macro = !strcmp(nch->string, "Open") ? "Ox" :
2671 !strcmp(nch->string, "Net") ? "Nx" :
2672 !strcmp(nch->string, "Free") ? "Fx" :
2673 !strcmp(nch->string, "DragonFly") ? "Dx" : NULL;
2674 if (macro != NULL)
2675 mandoc_msg(MANDOCERR_BX, mdoc->parse,
2676 n->line, n->pos, macro);
2677 mdoc->last = nch;
2678 nch = nch->next;
2679 mdoc->next = ROFF_NEXT_SIBLING;
2680 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2681 mdoc->last->flags |= NODE_NOSRC;
2682 mdoc->next = ROFF_NEXT_SIBLING;
2683 } else
2684 mdoc->next = ROFF_NEXT_CHILD;
2685 roff_word_alloc(mdoc, n->line, n->pos, "BSD");
2686 mdoc->last->flags |= NODE_NOSRC;
2688 if (nch == NULL) {
2689 mdoc->last = n;
2690 return;
2693 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2694 mdoc->last->flags |= NODE_NOSRC;
2695 mdoc->next = ROFF_NEXT_SIBLING;
2696 roff_word_alloc(mdoc, n->line, n->pos, "-");
2697 mdoc->last->flags |= NODE_NOSRC;
2698 roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
2699 mdoc->last->flags |= NODE_NOSRC;
2700 mdoc->last = n;
2703 * Make `Bx's second argument always start with an uppercase
2704 * letter. Groff checks if it's an "accepted" term, but we just
2705 * uppercase blindly.
2708 *nch->string = (char)toupper((unsigned char)*nch->string);
2711 static void
2712 post_os(POST_ARGS)
2714 #ifndef OSNAME
2715 struct utsname utsname;
2716 static char *defbuf;
2717 #endif
2718 struct roff_node *n;
2720 n = mdoc->last;
2721 n->flags |= NODE_NOPRT;
2723 if (mdoc->meta.os != NULL)
2724 mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
2725 n->line, n->pos, "Os");
2726 else if (mdoc->flags & MDOC_PBODY)
2727 mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
2728 n->line, n->pos, "Os");
2730 post_delim(mdoc);
2733 * Set the operating system by way of the `Os' macro.
2734 * The order of precedence is:
2735 * 1. the argument of the `Os' macro, unless empty
2736 * 2. the -Ios=foo command line argument, if provided
2737 * 3. -DOSNAME="\"foo\"", if provided during compilation
2738 * 4. "sysname release" from uname(3)
2741 free(mdoc->meta.os);
2742 mdoc->meta.os = NULL;
2743 deroff(&mdoc->meta.os, n);
2744 if (mdoc->meta.os)
2745 goto out;
2747 if (mdoc->os_s != NULL) {
2748 mdoc->meta.os = mandoc_strdup(mdoc->os_s);
2749 goto out;
2752 #ifdef OSNAME
2753 mdoc->meta.os = mandoc_strdup(OSNAME);
2754 #else /*!OSNAME */
2755 if (defbuf == NULL) {
2756 if (uname(&utsname) == -1) {
2757 mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
2758 n->line, n->pos, "Os");
2759 defbuf = mandoc_strdup("UNKNOWN");
2760 } else
2761 mandoc_asprintf(&defbuf, "%s %s",
2762 utsname.sysname, utsname.release);
2764 mdoc->meta.os = mandoc_strdup(defbuf);
2765 #endif /*!OSNAME*/
2767 out:
2768 if (mdoc->meta.os_e == MANDOC_OS_OTHER) {
2769 if (strstr(mdoc->meta.os, "OpenBSD") != NULL)
2770 mdoc->meta.os_e = MANDOC_OS_OPENBSD;
2771 else if (strstr(mdoc->meta.os, "NetBSD") != NULL)
2772 mdoc->meta.os_e = MANDOC_OS_NETBSD;
2776 * This is the earliest point where we can check
2777 * Mdocdate conventions because we don't know
2778 * the operating system earlier.
2781 if (n->child != NULL)
2782 mandoc_vmsg(MANDOCERR_OS_ARG, mdoc->parse,
2783 n->child->line, n->child->pos,
2784 "Os %s (%s)", n->child->string,
2785 mdoc->meta.os_e == MANDOC_OS_OPENBSD ?
2786 "OpenBSD" : "NetBSD");
2788 while (n->tok != MDOC_Dd)
2789 if ((n = n->prev) == NULL)
2790 return;
2791 if ((n = n->child) == NULL)
2792 return;
2793 if (strncmp(n->string, "$" "Mdocdate", 9)) {
2794 if (mdoc->meta.os_e == MANDOC_OS_OPENBSD)
2795 mandoc_vmsg(MANDOCERR_MDOCDATE_MISSING,
2796 mdoc->parse, n->line, n->pos,
2797 "Dd %s (OpenBSD)", n->string);
2798 } else {
2799 if (mdoc->meta.os_e == MANDOC_OS_NETBSD)
2800 mandoc_vmsg(MANDOCERR_MDOCDATE,
2801 mdoc->parse, n->line, n->pos,
2802 "Dd %s (NetBSD)", n->string);
2806 enum roff_sec
2807 mdoc_a2sec(const char *p)
2809 int i;
2811 for (i = 0; i < (int)SEC__MAX; i++)
2812 if (secnames[i] && 0 == strcmp(p, secnames[i]))
2813 return (enum roff_sec)i;
2815 return SEC_CUSTOM;
2818 static size_t
2819 macro2len(enum roff_tok macro)
2822 switch (macro) {
2823 case MDOC_Ad:
2824 return 12;
2825 case MDOC_Ao:
2826 return 12;
2827 case MDOC_An:
2828 return 12;
2829 case MDOC_Aq:
2830 return 12;
2831 case MDOC_Ar:
2832 return 12;
2833 case MDOC_Bo:
2834 return 12;
2835 case MDOC_Bq:
2836 return 12;
2837 case MDOC_Cd:
2838 return 12;
2839 case MDOC_Cm:
2840 return 10;
2841 case MDOC_Do:
2842 return 10;
2843 case MDOC_Dq:
2844 return 12;
2845 case MDOC_Dv:
2846 return 12;
2847 case MDOC_Eo:
2848 return 12;
2849 case MDOC_Em:
2850 return 10;
2851 case MDOC_Er:
2852 return 17;
2853 case MDOC_Ev:
2854 return 15;
2855 case MDOC_Fa:
2856 return 12;
2857 case MDOC_Fl:
2858 return 10;
2859 case MDOC_Fo:
2860 return 16;
2861 case MDOC_Fn:
2862 return 16;
2863 case MDOC_Ic:
2864 return 10;
2865 case MDOC_Li:
2866 return 16;
2867 case MDOC_Ms:
2868 return 6;
2869 case MDOC_Nm:
2870 return 10;
2871 case MDOC_No:
2872 return 12;
2873 case MDOC_Oo:
2874 return 10;
2875 case MDOC_Op:
2876 return 14;
2877 case MDOC_Pa:
2878 return 32;
2879 case MDOC_Pf:
2880 return 12;
2881 case MDOC_Po:
2882 return 12;
2883 case MDOC_Pq:
2884 return 12;
2885 case MDOC_Ql:
2886 return 16;
2887 case MDOC_Qo:
2888 return 12;
2889 case MDOC_So:
2890 return 12;
2891 case MDOC_Sq:
2892 return 12;
2893 case MDOC_Sy:
2894 return 6;
2895 case MDOC_Sx:
2896 return 16;
2897 case MDOC_Tn:
2898 return 10;
2899 case MDOC_Va:
2900 return 12;
2901 case MDOC_Vt:
2902 return 12;
2903 case MDOC_Xr:
2904 return 10;
2905 default:
2906 break;
2908 return 0;