Home | History | Annotate | Line # | Download | only in dist
man_validate.c revision 1.2
      1  1.2  christos /*	Id: man_validate.c,v 1.146 2018/12/31 10:04:39 schwarze Exp  */
      2  1.1     joerg /*
      3  1.2  christos  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps (at) bsd.lv>
      4  1.2  christos  * Copyright (c) 2010, 2012-2018 Ingo Schwarze <schwarze (at) openbsd.org>
      5  1.1     joerg  *
      6  1.1     joerg  * Permission to use, copy, modify, and distribute this software for any
      7  1.1     joerg  * purpose with or without fee is hereby granted, provided that the above
      8  1.1     joerg  * copyright notice and this permission notice appear in all copies.
      9  1.1     joerg  *
     10  1.2  christos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
     11  1.1     joerg  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  1.2  christos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
     13  1.1     joerg  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  1.1     joerg  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  1.1     joerg  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  1.1     joerg  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  1.1     joerg  */
     18  1.2  christos #include "config.h"
     19  1.2  christos 
     20  1.1     joerg #include <sys/types.h>
     21  1.1     joerg 
     22  1.1     joerg #include <assert.h>
     23  1.1     joerg #include <ctype.h>
     24  1.1     joerg #include <errno.h>
     25  1.1     joerg #include <limits.h>
     26  1.1     joerg #include <stdarg.h>
     27  1.2  christos #include <stdio.h>
     28  1.1     joerg #include <stdlib.h>
     29  1.2  christos #include <string.h>
     30  1.2  christos #include <time.h>
     31  1.1     joerg 
     32  1.2  christos #include "mandoc_aux.h"
     33  1.2  christos #include "mandoc.h"
     34  1.2  christos #include "roff.h"
     35  1.2  christos #include "man.h"
     36  1.2  christos #include "libmandoc.h"
     37  1.2  christos #include "roff_int.h"
     38  1.1     joerg #include "libman.h"
     39  1.1     joerg 
     40  1.2  christos #define	CHKARGS	  struct roff_man *man, struct roff_node *n
     41  1.1     joerg 
     42  1.2  christos typedef	void	(*v_check)(CHKARGS);
     43  1.1     joerg 
     44  1.2  christos static	void	  check_abort(CHKARGS) __dead;
     45  1.2  christos static	void	  check_par(CHKARGS);
     46  1.2  christos static	void	  check_part(CHKARGS);
     47  1.2  christos static	void	  check_root(CHKARGS);
     48  1.2  christos static	void	  check_text(CHKARGS);
     49  1.2  christos 
     50  1.2  christos static	void	  post_AT(CHKARGS);
     51  1.2  christos static	void	  post_EE(CHKARGS);
     52  1.2  christos static	void	  post_EX(CHKARGS);
     53  1.2  christos static	void	  post_IP(CHKARGS);
     54  1.2  christos static	void	  post_OP(CHKARGS);
     55  1.2  christos static	void	  post_SH(CHKARGS);
     56  1.2  christos static	void	  post_TH(CHKARGS);
     57  1.2  christos static	void	  post_UC(CHKARGS);
     58  1.2  christos static	void	  post_UR(CHKARGS);
     59  1.2  christos static	void	  post_in(CHKARGS);
     60  1.2  christos 
     61  1.2  christos static	const v_check man_valids[MAN_MAX - MAN_TH] = {
     62  1.2  christos 	post_TH,    /* TH */
     63  1.2  christos 	post_SH,    /* SH */
     64  1.2  christos 	post_SH,    /* SS */
     65  1.2  christos 	NULL,       /* TP */
     66  1.2  christos 	NULL,       /* TQ */
     67  1.2  christos 	check_abort,/* LP */
     68  1.2  christos 	check_par,  /* PP */
     69  1.2  christos 	check_abort,/* P */
     70  1.2  christos 	post_IP,    /* IP */
     71  1.2  christos 	NULL,       /* HP */
     72  1.2  christos 	NULL,       /* SM */
     73  1.2  christos 	NULL,       /* SB */
     74  1.2  christos 	NULL,       /* BI */
     75  1.2  christos 	NULL,       /* IB */
     76  1.2  christos 	NULL,       /* BR */
     77  1.2  christos 	NULL,       /* RB */
     78  1.2  christos 	NULL,       /* R */
     79  1.2  christos 	NULL,       /* B */
     80  1.2  christos 	NULL,       /* I */
     81  1.2  christos 	NULL,       /* IR */
     82  1.2  christos 	NULL,       /* RI */
     83  1.2  christos 	NULL,       /* RE */
     84  1.2  christos 	check_part, /* RS */
     85  1.2  christos 	NULL,       /* DT */
     86  1.2  christos 	post_UC,    /* UC */
     87  1.2  christos 	NULL,       /* PD */
     88  1.2  christos 	post_AT,    /* AT */
     89  1.2  christos 	post_in,    /* in */
     90  1.2  christos 	NULL,       /* SY */
     91  1.2  christos 	NULL,       /* YS */
     92  1.2  christos 	post_OP,    /* OP */
     93  1.2  christos 	post_EX,    /* EX */
     94  1.2  christos 	post_EE,    /* EE */
     95  1.2  christos 	post_UR,    /* UR */
     96  1.2  christos 	NULL,       /* UE */
     97  1.2  christos 	post_UR,    /* MT */
     98  1.2  christos 	NULL,       /* ME */
     99  1.1     joerg };
    100  1.1     joerg 
    101  1.1     joerg 
    102  1.2  christos /* Validate the subtree rooted at man->last. */
    103  1.2  christos void
    104  1.2  christos man_validate(struct roff_man *man)
    105  1.1     joerg {
    106  1.2  christos 	struct roff_node *n;
    107  1.2  christos 	const v_check	 *cp;
    108  1.1     joerg 
    109  1.2  christos 	/*
    110  1.2  christos 	 * Translate obsolete macros such that later code
    111  1.2  christos 	 * does not need to look for them.
    112  1.2  christos 	 */
    113  1.2  christos 
    114  1.2  christos 	n = man->last;
    115  1.2  christos 	switch (n->tok) {
    116  1.2  christos 	case MAN_LP:
    117  1.2  christos 	case MAN_P:
    118  1.2  christos 		n->tok = MAN_PP;
    119  1.2  christos 		break;
    120  1.2  christos 	default:
    121  1.2  christos 		break;
    122  1.2  christos 	}
    123  1.1     joerg 
    124  1.2  christos 	/*
    125  1.2  christos 	 * Iterate over all children, recursing into each one
    126  1.2  christos 	 * in turn, depth-first.
    127  1.2  christos 	 */
    128  1.2  christos 
    129  1.2  christos 	man->last = man->last->child;
    130  1.2  christos 	while (man->last != NULL) {
    131  1.2  christos 		man_validate(man);
    132  1.2  christos 		if (man->last == n)
    133  1.2  christos 			man->last = man->last->child;
    134  1.2  christos 		else
    135  1.2  christos 			man->last = man->last->next;
    136  1.2  christos 	}
    137  1.1     joerg 
    138  1.2  christos 	/* Finally validate the macro itself. */
    139  1.1     joerg 
    140  1.2  christos 	man->last = n;
    141  1.2  christos 	man->next = ROFF_NEXT_SIBLING;
    142  1.2  christos 	switch (n->type) {
    143  1.2  christos 	case ROFFT_TEXT:
    144  1.2  christos 		check_text(man, n);
    145  1.2  christos 		break;
    146  1.2  christos 	case ROFFT_ROOT:
    147  1.2  christos 		check_root(man, n);
    148  1.2  christos 		break;
    149  1.2  christos 	case ROFFT_COMMENT:
    150  1.2  christos 	case ROFFT_EQN:
    151  1.2  christos 	case ROFFT_TBL:
    152  1.2  christos 		break;
    153  1.1     joerg 	default:
    154  1.2  christos 		if (n->tok < ROFF_MAX) {
    155  1.2  christos 			roff_validate(man);
    156  1.2  christos 			break;
    157  1.2  christos 		}
    158  1.2  christos 		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
    159  1.2  christos 		cp = man_valids + (n->tok - MAN_TH);
    160  1.2  christos 		if (*cp)
    161  1.2  christos 			(*cp)(man, n);
    162  1.2  christos 		if (man->last == n)
    163  1.2  christos 			n->flags |= NODE_VALID;
    164  1.1     joerg 		break;
    165  1.1     joerg 	}
    166  1.2  christos }
    167  1.1     joerg 
    168  1.2  christos static void
    169  1.2  christos check_root(CHKARGS)
    170  1.2  christos {
    171  1.2  christos 	assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
    172  1.1     joerg 
    173  1.2  christos 	if (n->last == NULL || n->last->type == ROFFT_COMMENT)
    174  1.2  christos 		mandoc_msg(MANDOCERR_DOC_EMPTY, n->line, n->pos, NULL);
    175  1.2  christos 	else
    176  1.2  christos 		man->meta.hasbody = 1;
    177  1.2  christos 
    178  1.2  christos 	if (NULL == man->meta.title) {
    179  1.2  christos 		mandoc_msg(MANDOCERR_TH_NOTITLE, n->line, n->pos, NULL);
    180  1.2  christos 
    181  1.2  christos 		/*
    182  1.2  christos 		 * If a title hasn't been set, do so now (by
    183  1.2  christos 		 * implication, date and section also aren't set).
    184  1.2  christos 		 */
    185  1.2  christos 
    186  1.2  christos 		man->meta.title = mandoc_strdup("");
    187  1.2  christos 		man->meta.msec = mandoc_strdup("");
    188  1.2  christos 		man->meta.date = man->quick ? mandoc_strdup("") :
    189  1.2  christos 		    mandoc_normdate(man, NULL, n->line, n->pos);
    190  1.2  christos 	}
    191  1.2  christos 
    192  1.2  christos 	if (man->meta.os_e &&
    193  1.2  christos 	    (man->meta.rcsids & (1 << man->meta.os_e)) == 0)
    194  1.2  christos 		mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
    195  1.2  christos 		    man->meta.os_e == MANDOC_OS_OPENBSD ?
    196  1.2  christos 		    "(OpenBSD)" : "(NetBSD)");
    197  1.1     joerg }
    198  1.1     joerg 
    199  1.2  christos static void
    200  1.2  christos check_abort(CHKARGS)
    201  1.2  christos {
    202  1.2  christos 	abort();
    203  1.2  christos }
    204  1.1     joerg 
    205  1.2  christos static void
    206  1.2  christos check_text(CHKARGS)
    207  1.1     joerg {
    208  1.2  christos 	char		*cp, *p;
    209  1.1     joerg 
    210  1.2  christos 	if (n->flags & NODE_NOFILL)
    211  1.2  christos 		return;
    212  1.1     joerg 
    213  1.2  christos 	cp = n->string;
    214  1.2  christos 	for (p = cp; NULL != (p = strchr(p, '\t')); p++)
    215  1.2  christos 		mandoc_msg(MANDOCERR_FI_TAB,
    216  1.2  christos 		    n->line, n->pos + (int)(p - cp), NULL);
    217  1.2  christos }
    218  1.1     joerg 
    219  1.2  christos static void
    220  1.2  christos post_EE(CHKARGS)
    221  1.2  christos {
    222  1.2  christos 	if ((n->flags & NODE_NOFILL) == 0)
    223  1.2  christos 		mandoc_msg(MANDOCERR_FI_SKIP, n->line, n->pos, "EE");
    224  1.2  christos }
    225  1.1     joerg 
    226  1.2  christos static void
    227  1.2  christos post_EX(CHKARGS)
    228  1.2  christos {
    229  1.2  christos 	if (n->flags & NODE_NOFILL)
    230  1.2  christos 		mandoc_msg(MANDOCERR_NF_SKIP, n->line, n->pos, "EX");
    231  1.1     joerg }
    232  1.1     joerg 
    233  1.2  christos static void
    234  1.2  christos post_OP(CHKARGS)
    235  1.2  christos {
    236  1.2  christos 
    237  1.2  christos 	if (n->child == NULL)
    238  1.2  christos 		mandoc_msg(MANDOCERR_OP_EMPTY, n->line, n->pos, "OP");
    239  1.2  christos 	else if (n->child->next != NULL && n->child->next->next != NULL) {
    240  1.2  christos 		n = n->child->next->next;
    241  1.2  christos 		mandoc_msg(MANDOCERR_ARG_EXCESS,
    242  1.2  christos 		    n->line, n->pos, "OP ... %s", n->string);
    243  1.2  christos 	}
    244  1.2  christos }
    245  1.1     joerg 
    246  1.2  christos static void
    247  1.2  christos post_SH(CHKARGS)
    248  1.1     joerg {
    249  1.2  christos 	struct roff_node	*nc;
    250  1.1     joerg 
    251  1.2  christos 	if (n->type != ROFFT_BODY || (nc = n->child) == NULL)
    252  1.2  christos 		return;
    253  1.1     joerg 
    254  1.2  christos 	if (nc->tok == MAN_PP && nc->body->child != NULL) {
    255  1.2  christos 		while (nc->body->last != NULL) {
    256  1.2  christos 			man->next = ROFF_NEXT_CHILD;
    257  1.2  christos 			roff_node_relink(man, nc->body->last);
    258  1.2  christos 			man->last = n;
    259  1.1     joerg 		}
    260  1.2  christos 	}
    261  1.1     joerg 
    262  1.2  christos 	if (nc->tok == MAN_PP || nc->tok == ROFF_sp || nc->tok == ROFF_br) {
    263  1.2  christos 		mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos,
    264  1.2  christos 		    "%s after %s", roff_name[nc->tok], roff_name[n->tok]);
    265  1.2  christos 		roff_node_delete(man, nc);
    266  1.2  christos 	}
    267  1.1     joerg 
    268  1.2  christos 	/*
    269  1.2  christos 	 * Trailing PP is empty, so it is deleted by check_par().
    270  1.2  christos 	 * Trailing sp is significant.
    271  1.2  christos 	 */
    272  1.2  christos 
    273  1.2  christos 	if ((nc = n->last) != NULL && nc->tok == ROFF_br) {
    274  1.2  christos 		mandoc_msg(MANDOCERR_PAR_SKIP,
    275  1.2  christos 		    nc->line, nc->pos, "%s at the end of %s",
    276  1.2  christos 		    roff_name[nc->tok], roff_name[n->tok]);
    277  1.2  christos 		roff_node_delete(man, nc);
    278  1.1     joerg 	}
    279  1.2  christos }
    280  1.1     joerg 
    281  1.2  christos static void
    282  1.2  christos post_UR(CHKARGS)
    283  1.2  christos {
    284  1.2  christos 	if (n->type == ROFFT_HEAD && n->child == NULL)
    285  1.2  christos 		mandoc_msg(MANDOCERR_UR_NOHEAD, n->line, n->pos,
    286  1.2  christos 		    "%s", roff_name[n->tok]);
    287  1.2  christos 	check_part(man, n);
    288  1.1     joerg }
    289  1.1     joerg 
    290  1.2  christos static void
    291  1.2  christos check_part(CHKARGS)
    292  1.2  christos {
    293  1.1     joerg 
    294  1.2  christos 	if (n->type == ROFFT_BODY && n->child == NULL)
    295  1.2  christos 		mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
    296  1.2  christos 		    "%s", roff_name[n->tok]);
    297  1.1     joerg }
    298  1.1     joerg 
    299  1.2  christos static void
    300  1.2  christos check_par(CHKARGS)
    301  1.1     joerg {
    302  1.1     joerg 
    303  1.2  christos 	switch (n->type) {
    304  1.2  christos 	case ROFFT_BLOCK:
    305  1.2  christos 		if (n->body->child == NULL)
    306  1.2  christos 			roff_node_delete(man, n);
    307  1.2  christos 		break;
    308  1.2  christos 	case ROFFT_BODY:
    309  1.2  christos 		if (n->child != NULL &&
    310  1.2  christos 		    (n->child->tok == ROFF_sp || n->child->tok == ROFF_br)) {
    311  1.2  christos 			mandoc_msg(MANDOCERR_PAR_SKIP,
    312  1.2  christos 			    n->child->line, n->child->pos,
    313  1.2  christos 			    "%s after %s", roff_name[n->child->tok],
    314  1.2  christos 			    roff_name[n->tok]);
    315  1.2  christos 			roff_node_delete(man, n->child);
    316  1.2  christos 		}
    317  1.2  christos 		if (n->child == NULL)
    318  1.2  christos 			mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
    319  1.2  christos 			    "%s empty", roff_name[n->tok]);
    320  1.2  christos 		break;
    321  1.2  christos 	case ROFFT_HEAD:
    322  1.2  christos 		if (n->child != NULL)
    323  1.2  christos 			mandoc_msg(MANDOCERR_ARG_SKIP,
    324  1.2  christos 			    n->line, n->pos, "%s %s%s",
    325  1.2  christos 			    roff_name[n->tok], n->child->string,
    326  1.2  christos 			    n->child->next != NULL ? " ..." : "");
    327  1.2  christos 		break;
    328  1.2  christos 	default:
    329  1.2  christos 		break;
    330  1.2  christos 	}
    331  1.1     joerg }
    332  1.1     joerg 
    333  1.2  christos static void
    334  1.2  christos post_IP(CHKARGS)
    335  1.1     joerg {
    336  1.1     joerg 
    337  1.2  christos 	switch (n->type) {
    338  1.2  christos 	case ROFFT_BLOCK:
    339  1.2  christos 		if (n->head->child == NULL && n->body->child == NULL)
    340  1.2  christos 			roff_node_delete(man, n);
    341  1.2  christos 		break;
    342  1.2  christos 	case ROFFT_BODY:
    343  1.2  christos 		if (n->parent->head->child == NULL && n->child == NULL)
    344  1.2  christos 			mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
    345  1.2  christos 			    "%s empty", roff_name[n->tok]);
    346  1.2  christos 		break;
    347  1.2  christos 	default:
    348  1.2  christos 		break;
    349  1.2  christos 	}
    350  1.1     joerg }
    351  1.1     joerg 
    352  1.2  christos static void
    353  1.2  christos post_TH(CHKARGS)
    354  1.1     joerg {
    355  1.2  christos 	struct roff_node *nb;
    356  1.2  christos 	const char	*p;
    357  1.1     joerg 
    358  1.2  christos 	free(man->meta.title);
    359  1.2  christos 	free(man->meta.vol);
    360  1.2  christos 	free(man->meta.os);
    361  1.2  christos 	free(man->meta.msec);
    362  1.2  christos 	free(man->meta.date);
    363  1.2  christos 
    364  1.2  christos 	man->meta.title = man->meta.vol = man->meta.date =
    365  1.2  christos 	    man->meta.msec = man->meta.os = NULL;
    366  1.2  christos 
    367  1.2  christos 	nb = n;
    368  1.2  christos 
    369  1.2  christos 	/* ->TITLE<- MSEC DATE OS VOL */
    370  1.2  christos 
    371  1.2  christos 	n = n->child;
    372  1.2  christos 	if (n && n->string) {
    373  1.2  christos 		for (p = n->string; '\0' != *p; p++) {
    374  1.2  christos 			/* Only warn about this once... */
    375  1.2  christos 			if (isalpha((unsigned char)*p) &&
    376  1.2  christos 			    ! isupper((unsigned char)*p)) {
    377  1.2  christos 				mandoc_msg(MANDOCERR_TITLE_CASE, n->line,
    378  1.2  christos 				    n->pos + (int)(p - n->string),
    379  1.2  christos 				    "TH %s", n->string);
    380  1.1     joerg 				break;
    381  1.2  christos 			}
    382  1.1     joerg 		}
    383  1.2  christos 		man->meta.title = mandoc_strdup(n->string);
    384  1.2  christos 	} else {
    385  1.2  christos 		man->meta.title = mandoc_strdup("");
    386  1.2  christos 		mandoc_msg(MANDOCERR_TH_NOTITLE, nb->line, nb->pos, "TH");
    387  1.2  christos 	}
    388  1.2  christos 
    389  1.2  christos 	/* TITLE ->MSEC<- DATE OS VOL */
    390  1.2  christos 
    391  1.2  christos 	if (n)
    392  1.2  christos 		n = n->next;
    393  1.2  christos 	if (n && n->string)
    394  1.2  christos 		man->meta.msec = mandoc_strdup(n->string);
    395  1.2  christos 	else {
    396  1.2  christos 		man->meta.msec = mandoc_strdup("");
    397  1.2  christos 		mandoc_msg(MANDOCERR_MSEC_MISSING,
    398  1.2  christos 		    nb->line, nb->pos, "TH %s", man->meta.title);
    399  1.2  christos 	}
    400  1.2  christos 
    401  1.2  christos 	/* TITLE MSEC ->DATE<- OS VOL */
    402  1.2  christos 
    403  1.2  christos 	if (n)
    404  1.2  christos 		n = n->next;
    405  1.2  christos 	if (n && n->string && '\0' != n->string[0]) {
    406  1.2  christos 		man->meta.date = man->quick ?
    407  1.2  christos 		    mandoc_strdup(n->string) :
    408  1.2  christos 		    mandoc_normdate(man, n->string, n->line, n->pos);
    409  1.2  christos 	} else {
    410  1.2  christos 		man->meta.date = mandoc_strdup("");
    411  1.2  christos 		mandoc_msg(MANDOCERR_DATE_MISSING,
    412  1.2  christos 		    n ? n->line : nb->line,
    413  1.2  christos 		    n ? n->pos : nb->pos, "TH");
    414  1.2  christos 	}
    415  1.2  christos 
    416  1.2  christos 	/* TITLE MSEC DATE ->OS<- VOL */
    417  1.2  christos 
    418  1.2  christos 	if (n && (n = n->next))
    419  1.2  christos 		man->meta.os = mandoc_strdup(n->string);
    420  1.2  christos 	else if (man->os_s != NULL)
    421  1.2  christos 		man->meta.os = mandoc_strdup(man->os_s);
    422  1.2  christos 	if (man->meta.os_e == MANDOC_OS_OTHER && man->meta.os != NULL) {
    423  1.2  christos 		if (strstr(man->meta.os, "OpenBSD") != NULL)
    424  1.2  christos 			man->meta.os_e = MANDOC_OS_OPENBSD;
    425  1.2  christos 		else if (strstr(man->meta.os, "NetBSD") != NULL)
    426  1.2  christos 			man->meta.os_e = MANDOC_OS_NETBSD;
    427  1.2  christos 	}
    428  1.2  christos 
    429  1.2  christos 	/* TITLE MSEC DATE OS ->VOL<- */
    430  1.2  christos 	/* If missing, use the default VOL name for MSEC. */
    431  1.1     joerg 
    432  1.2  christos 	if (n && (n = n->next))
    433  1.2  christos 		man->meta.vol = mandoc_strdup(n->string);
    434  1.2  christos 	else if ('\0' != man->meta.msec[0] &&
    435  1.2  christos 	    (NULL != (p = mandoc_a2msec(man->meta.msec))))
    436  1.2  christos 		man->meta.vol = mandoc_strdup(p);
    437  1.2  christos 
    438  1.2  christos 	if (n != NULL && (n = n->next) != NULL)
    439  1.2  christos 		mandoc_msg(MANDOCERR_ARG_EXCESS,
    440  1.2  christos 		    n->line, n->pos, "TH ... %s", n->string);
    441  1.2  christos 
    442  1.2  christos 	/*
    443  1.2  christos 	 * Remove the `TH' node after we've processed it for our
    444  1.2  christos 	 * meta-data.
    445  1.2  christos 	 */
    446  1.2  christos 	roff_node_delete(man, man->last);
    447  1.1     joerg }
    448  1.1     joerg 
    449  1.2  christos static void
    450  1.2  christos post_UC(CHKARGS)
    451  1.2  christos {
    452  1.2  christos 	static const char * const bsd_versions[] = {
    453  1.2  christos 	    "3rd Berkeley Distribution",
    454  1.2  christos 	    "4th Berkeley Distribution",
    455  1.2  christos 	    "4.2 Berkeley Distribution",
    456  1.2  christos 	    "4.3 Berkeley Distribution",
    457  1.2  christos 	    "4.4 Berkeley Distribution",
    458  1.2  christos 	};
    459  1.2  christos 
    460  1.2  christos 	const char	*p, *s;
    461  1.2  christos 
    462  1.2  christos 	n = n->child;
    463  1.2  christos 
    464  1.2  christos 	if (n == NULL || n->type != ROFFT_TEXT)
    465  1.2  christos 		p = bsd_versions[0];
    466  1.2  christos 	else {
    467  1.2  christos 		s = n->string;
    468  1.2  christos 		if (0 == strcmp(s, "3"))
    469  1.2  christos 			p = bsd_versions[0];
    470  1.2  christos 		else if (0 == strcmp(s, "4"))
    471  1.2  christos 			p = bsd_versions[1];
    472  1.2  christos 		else if (0 == strcmp(s, "5"))
    473  1.2  christos 			p = bsd_versions[2];
    474  1.2  christos 		else if (0 == strcmp(s, "6"))
    475  1.2  christos 			p = bsd_versions[3];
    476  1.2  christos 		else if (0 == strcmp(s, "7"))
    477  1.2  christos 			p = bsd_versions[4];
    478  1.2  christos 		else
    479  1.2  christos 			p = bsd_versions[0];
    480  1.2  christos 	}
    481  1.2  christos 
    482  1.2  christos 	free(man->meta.os);
    483  1.2  christos 	man->meta.os = mandoc_strdup(p);
    484  1.2  christos }
    485  1.1     joerg 
    486  1.2  christos static void
    487  1.2  christos post_AT(CHKARGS)
    488  1.1     joerg {
    489  1.2  christos 	static const char * const unix_versions[] = {
    490  1.2  christos 	    "7th Edition",
    491  1.2  christos 	    "System III",
    492  1.2  christos 	    "System V",
    493  1.2  christos 	    "System V Release 2",
    494  1.2  christos 	};
    495  1.2  christos 
    496  1.2  christos 	struct roff_node *nn;
    497  1.2  christos 	const char	*p, *s;
    498  1.2  christos 
    499  1.2  christos 	n = n->child;
    500  1.2  christos 
    501  1.2  christos 	if (n == NULL || n->type != ROFFT_TEXT)
    502  1.2  christos 		p = unix_versions[0];
    503  1.2  christos 	else {
    504  1.2  christos 		s = n->string;
    505  1.2  christos 		if (0 == strcmp(s, "3"))
    506  1.2  christos 			p = unix_versions[0];
    507  1.2  christos 		else if (0 == strcmp(s, "4"))
    508  1.2  christos 			p = unix_versions[1];
    509  1.2  christos 		else if (0 == strcmp(s, "5")) {
    510  1.2  christos 			nn = n->next;
    511  1.2  christos 			if (nn != NULL &&
    512  1.2  christos 			    nn->type == ROFFT_TEXT &&
    513  1.2  christos 			    nn->string[0] != '\0')
    514  1.2  christos 				p = unix_versions[3];
    515  1.2  christos 			else
    516  1.2  christos 				p = unix_versions[2];
    517  1.2  christos 		} else
    518  1.2  christos 			p = unix_versions[0];
    519  1.2  christos 	}
    520  1.1     joerg 
    521  1.2  christos 	free(man->meta.os);
    522  1.2  christos 	man->meta.os = mandoc_strdup(p);
    523  1.1     joerg }
    524  1.1     joerg 
    525  1.2  christos static void
    526  1.2  christos post_in(CHKARGS)
    527  1.2  christos {
    528  1.2  christos 	char	*s;
    529  1.2  christos 
    530  1.2  christos 	if (n->parent->tok != MAN_TP ||
    531  1.2  christos 	    n->parent->type != ROFFT_HEAD ||
    532  1.2  christos 	    n->child == NULL ||
    533  1.2  christos 	    *n->child->string == '+' ||
    534  1.2  christos 	    *n->child->string == '-')
    535  1.2  christos 		return;
    536  1.2  christos 	mandoc_asprintf(&s, "+%s", n->child->string);
    537  1.2  christos 	free(n->child->string);
    538  1.2  christos 	n->child->string = s;
    539  1.2  christos }
    540