From a9685eaab1313767d1d4dac672c3a468519aa899 Mon Sep 17 00:00:00 2001 From: Marcel Telka Date: Thu, 15 Oct 2015 09:00:58 +0200 Subject: 6321 mountd: The IP to name translation is usually not needed in nfsauth_access() Reviewed by: Gordon Ross Approved by: Robert Mustacchi --- usr/src/cmd/fs.d/nfs/mountd/mountd.c | 580 ++++++++++++++++------------------ usr/src/cmd/fs.d/nfs/mountd/mountd.h | 31 +- usr/src/cmd/fs.d/nfs/mountd/nfs_cmd.c | 33 +- usr/src/cmd/fs.d/nfs/mountd/nfsauth.c | 67 +--- 4 files changed, 322 insertions(+), 389 deletions(-) (limited to 'usr/src') diff --git a/usr/src/cmd/fs.d/nfs/mountd/mountd.c b/usr/src/cmd/fs.d/nfs/mountd/mountd.c index 50180d3815..150455f3d9 100644 --- a/usr/src/cmd/fs.d/nfs/mountd/mountd.c +++ b/usr/src/cmd/fs.d/nfs/mountd/mountd.c @@ -102,20 +102,12 @@ static mutex_t logging_queue_lock; static cond_t logging_queue_cv; static share_t *find_lofsentry(char *, int *); -static int getclientsnames_lazy(char *, struct netbuf **, - struct nd_hostservlist **); -static int getclientsnames(SVCXPRT *, struct netbuf **, - struct nd_hostservlist **); -static int getclientsflavors_old(share_t *, SVCXPRT *, struct netbuf **, - struct nd_hostservlist **, int *); -static int getclientsflavors_new(share_t *, SVCXPRT *, struct netbuf **, - struct nd_hostservlist **, int *); -static int check_client_old(share_t *, SVCXPRT *, struct netbuf **, - struct nd_hostservlist **, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, - gid_t *, uint_t *, gid_t **); -static int check_client_new(share_t *, SVCXPRT *, struct netbuf **, - struct nd_hostservlist **, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, - gid_t *i, uint_t *, gid_t **); +static int getclientsflavors_old(share_t *, struct cln *, int *); +static int getclientsflavors_new(share_t *, struct cln *, int *); +static int check_client_old(share_t *, struct cln *, int, uid_t, gid_t, uint_t, + gid_t *, uid_t *, gid_t *, uint_t *, gid_t **); +static int check_client_new(share_t *, struct cln *, int, uid_t, gid_t, uint_t, + gid_t *, uid_t *, gid_t *i, uint_t *, gid_t **); static void mnt(struct svc_req *, SVCXPRT *); static void mnt_pathconf(struct svc_req *); static int mount(struct svc_req *r); @@ -301,16 +293,13 @@ do_logging_queue(logging_data *lq) int cleared = 0; char *host; - struct nd_hostservlist *clnames; - while (lq) { + struct cln cln; + if (lq->ld_host == NULL) { DTRACE_PROBE(mountd, name_by_lazy); - if (getclientsnames_lazy(lq->ld_netid, - &lq->ld_nb, &clnames) != 0) - host = NULL; - else - host = clnames->h_hostservs[0].h_host; + cln_init_lazy(&cln, lq->ld_netid, lq->ld_nb); + host = cln_gethost(&cln); } else host = lq->ld_host; @@ -320,8 +309,8 @@ do_logging_queue(logging_data *lq) if (lq->ld_rpath) mntlist_new(host, lq->ld_rpath); - if (lq->ld_host != host) - netdir_free(clnames, ND_HOSTSERVLIST); + if (lq->ld_host == NULL) + cln_fini(&cln); free_logging_data(lq); cleared++; @@ -765,164 +754,38 @@ mnt(struct svc_req *rqstp, SVCXPRT *transp) } } -/* Set up anonymous client */ - -struct nd_hostservlist * -anon_client(char *host) +void +log_cant_reply_cln(struct cln *cln) { - struct nd_hostservlist *anon_hsl; - struct nd_hostserv *anon_hs; - - anon_hsl = malloc(sizeof (*anon_hsl)); - if (anon_hsl == NULL) - return (NULL); + int saverrno; + char *host; - anon_hs = malloc(sizeof (*anon_hs)); - if (anon_hs == NULL) { - free(anon_hsl); - return (NULL); - } + saverrno = errno; /* save error code */ + host = cln_gethost(cln); if (host == NULL) - anon_hs->h_host = strdup("(anon)"); - else - anon_hs->h_host = strdup(host); - - if (anon_hs->h_host == NULL) { - free(anon_hs); - free(anon_hsl); - return (NULL); - } - anon_hs->h_serv = '\0'; - - anon_hsl->h_cnt = 1; - anon_hsl->h_hostservs = anon_hs; - - return (anon_hsl); -} - -static int -getclientsnames_common(struct netconfig *nconf, struct netbuf **nbuf, - struct nd_hostservlist **serv) -{ - char host[MAXIPADDRLEN]; - - assert(*nbuf != NULL); - - /* - * Use the this API instead of the netdir_getbyaddr() - * to avoid service lookup. - */ - if (__netdir_getbyaddr_nosrv(nconf, serv, *nbuf) != 0) { - if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { - struct sockaddr_in *sa; - - /* LINTED pointer alignment */ - sa = (struct sockaddr_in *)((*nbuf)->buf); - (void) inet_ntoa_r(sa->sin_addr, host); - } else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { - struct sockaddr_in6 *sa; - - /* LINTED pointer alignment */ - sa = (struct sockaddr_in6 *)((*nbuf)->buf); - (void) inet_ntop(AF_INET6, sa->sin6_addr.s6_addr, - host, INET6_ADDRSTRLEN); - } else { - syslog(LOG_ERR, gettext( - "Client's address is neither IPv4 nor IPv6")); - return (EINVAL); - } - - *serv = anon_client(host); - if (*serv == NULL) - return (ENOMEM); - } - - assert(*serv != NULL); - return (0); -} - -/* - * Get the client's hostname from the copy of the - * relevant transport handle parts. - * If the name is not available then return "(anon)". - */ -static int -getclientsnames_lazy(char *netid, struct netbuf **nbuf, - struct nd_hostservlist **serv) -{ - struct netconfig *nconf; - int rc; - - nconf = getnetconfigent(netid); - if (nconf == NULL) { - syslog(LOG_ERR, "%s: getnetconfigent failed", netid); - *serv = anon_client(NULL); - if (*serv == NULL) - return (ENOMEM); - return (0); - } - - rc = getclientsnames_common(nconf, nbuf, serv); - freenetconfigent(nconf); - return (rc); -} - -/* - * Get the client's hostname from the transport handle. - * If the name is not available then return "(anon)". - */ -int -getclientsnames(SVCXPRT *transp, struct netbuf **nbuf, - struct nd_hostservlist **serv) -{ - struct netconfig *nconf; - int rc; - - nconf = getnetconfigent(transp->xp_netid); - if (nconf == NULL) { - syslog(LOG_ERR, "%s: getnetconfigent failed", - transp->xp_netid); - *serv = anon_client(NULL); - if (*serv == NULL) - return (ENOMEM); - return (0); - } - - *nbuf = svc_getrpccaller(transp); - if (*nbuf == NULL) { - freenetconfigent(nconf); - *serv = anon_client(NULL); - if (*serv == NULL) - return (ENOMEM); - return (0); - } + return; - rc = getclientsnames_common(nconf, nbuf, serv); - freenetconfigent(nconf); - return (rc); + errno = saverrno; + if (errno == 0) + syslog(LOG_ERR, "couldn't send reply to %s", host); + else + syslog(LOG_ERR, "couldn't send reply to %s: %m", host); } void log_cant_reply(SVCXPRT *transp) { int saverrno; - struct nd_hostservlist *clnames = NULL; - char *host; - struct netbuf *nb; + struct cln cln; saverrno = errno; /* save error code */ - if (getclientsnames(transp, &nb, &clnames) != 0) - return; - host = clnames->h_hostservs->h_host; - + cln_init(&cln, transp); errno = saverrno; - if (errno == 0) - syslog(LOG_ERR, "couldn't send reply to %s", host); - else - syslog(LOG_ERR, "couldn't send reply to %s: %m", host); - netdir_free(clnames, ND_HOSTSERVLIST); + log_cant_reply_cln(&cln); + + cln_fini(&cln); } /* @@ -1030,8 +893,7 @@ checkrootmount(share_t *sh, char *rpath) * otherwise EACCES. */ static int -mount_enoent_error(SVCXPRT *transp, char *path, char *rpath, - struct nd_hostservlist **clnames, struct netbuf **nb, int *flavor_list) +mount_enoent_error(struct cln *cln, char *path, char *rpath, int *flavor_list) { char *checkpath, *dp; share_t *sh = NULL; @@ -1074,11 +936,11 @@ mount_enoent_error(SVCXPRT *transp, char *path, char *rpath, * Check permissions in mount table. */ if (newopts(sh->sh_opts)) - flavor_count = getclientsflavors_new(sh, - transp, nb, clnames, flavor_list); + flavor_count = getclientsflavors_new(sh, cln, + flavor_list); else - flavor_count = getclientsflavors_old(sh, - transp, nb, clnames, flavor_list); + flavor_count = getclientsflavors_old(sh, cln, + flavor_list); if (flavor_count != 0) { /* * Found entry in table and @@ -1202,6 +1064,140 @@ cleanup: return (FALSE); } + +#define CLN_CLNAMES (1 << 0) +#define CLN_HOST (1 << 1) + +static void +cln_init_common(struct cln *cln, SVCXPRT *transp, char *netid, + struct netbuf *nbuf) +{ + if ((cln->transp = transp) != NULL) { + assert(netid == NULL && nbuf == NULL); + cln->netid = transp->xp_netid; + cln->nbuf = svc_getrpccaller(transp); + } else { + cln->netid = netid; + cln->nbuf = nbuf; + } + + cln->nconf = NULL; + cln->clnames = NULL; + cln->host = NULL; + + cln->flags = 0; +} + +void +cln_init(struct cln *cln, SVCXPRT *transp) +{ + cln_init_common(cln, transp, NULL, NULL); +} + +void +cln_init_lazy(struct cln *cln, char *netid, struct netbuf *nbuf) +{ + cln_init_common(cln, NULL, netid, nbuf); +} + +void +cln_fini(struct cln *cln) +{ + if (cln->nconf != NULL) + freenetconfigent(cln->nconf); + + if (cln->clnames != NULL) + netdir_free(cln->clnames, ND_HOSTSERVLIST); + + free(cln->host); +} + +struct netbuf * +cln_getnbuf(struct cln *cln) +{ + return (cln->nbuf); +} + +struct nd_hostservlist * +cln_getclientsnames(struct cln *cln) +{ + if ((cln->flags & CLN_CLNAMES) == 0) { + /* + * nconf is not needed if we do not have nbuf (see + * cln_gethost() too), so we check for nbuf and in a case it is + * NULL we do not try to get nconf. + */ + if (cln->netid != NULL && cln->nbuf != NULL) { + cln->nconf = getnetconfigent(cln->netid); + if (cln->nconf == NULL) + syslog(LOG_ERR, "%s: getnetconfigent failed", + cln->netid); + } + + if (cln->nconf != NULL && cln->nbuf != NULL) + (void) __netdir_getbyaddr_nosrv(cln->nconf, + &cln->clnames, cln->nbuf); + + cln->flags |= CLN_CLNAMES; + } + + return (cln->clnames); +} + +/* + * Return B_TRUE if the host is already available at no cost + */ +boolean_t +cln_havehost(struct cln *cln) +{ + return ((cln->flags & (CLN_CLNAMES | CLN_HOST)) != 0); +} + +char * +cln_gethost(struct cln *cln) +{ + if (cln_getclientsnames(cln) != NULL) + return (cln->clnames->h_hostservs[0].h_host); + + if ((cln->flags & CLN_HOST) == 0) { + if (cln->nconf == NULL || cln->nbuf == NULL) { + cln->host = strdup("(anon)"); + } else { + char host[MAXIPADDRLEN]; + + if (strcmp(cln->nconf->nc_protofmly, NC_INET) == 0) { + struct sockaddr_in *sa; + + /* LINTED pointer alignment */ + sa = (struct sockaddr_in *)(cln->nbuf->buf); + (void) inet_ntoa_r(sa->sin_addr, host); + + cln->host = strdup(host); + } else if (strcmp(cln->nconf->nc_protofmly, + NC_INET6) == 0) { + struct sockaddr_in6 *sa; + + /* LINTED pointer alignment */ + sa = (struct sockaddr_in6 *)(cln->nbuf->buf); + (void) inet_ntop(AF_INET6, + sa->sin6_addr.s6_addr, + host, INET6_ADDRSTRLEN); + + cln->host = strdup(host); + } else { + syslog(LOG_ERR, gettext("Client's address is " + "neither IPv4 nor IPv6")); + + cln->host = strdup("(anon)"); + } + } + + cln->flags |= CLN_HOST; + } + + return (cln->host); +} + /* * Check mount requests, add to mounted list if ok */ @@ -1216,12 +1212,11 @@ mount(struct svc_req *rqstp) int len = FHSIZE3; char *path, rpath[MAXPATHLEN]; share_t *sh = NULL; - struct nd_hostservlist *clnames = NULL; + struct cln cln; char *host = NULL; int error = 0, lofs_tried = 0, enqueued; int flavor_list[MAX_FLAVORS]; int flavor_count; - struct netbuf *nb = NULL; ucred_t *uc = NULL; int audit_status; @@ -1235,6 +1230,8 @@ mount(struct svc_req *rqstp) return (EACCES); } + cln_init(&cln, transp); + /* * Put off getting the name for the client until we * need it. This is a performance gain. If we are logging, @@ -1244,7 +1241,7 @@ mount(struct svc_req *rqstp) */ if (verbose) { DTRACE_PROBE(mountd, name_by_verbose); - if (getclientsnames(transp, &nb, &clnames) != 0) { + if ((host = cln_gethost(&cln)) == NULL) { /* * We failed to get a name for the client, even * 'anon', probably because we ran out of memory. @@ -1254,7 +1251,6 @@ mount(struct svc_req *rqstp) error = EACCES; goto reply; } - host = clnames->h_hostservs[0].h_host; } /* @@ -1295,8 +1291,8 @@ mount(struct svc_req *rqstp) syslog(LOG_ERR, "mount request: realpath: %s: %m", path); if (error == ENOENT) - error = mount_enoent_error(transp, path, rpath, - &clnames, &nb, flavor_list); + error = mount_enoent_error(&cln, path, rpath, + flavor_list); goto reply; } @@ -1316,14 +1312,9 @@ mount(struct svc_req *rqstp) } if (newopts(sh->sh_opts)) - flavor_count = getclientsflavors_new(sh, transp, &nb, &clnames, - flavor_list); + flavor_count = getclientsflavors_new(sh, &cln, flavor_list); else - flavor_count = getclientsflavors_old(sh, transp, &nb, &clnames, - flavor_list); - - if (clnames) - host = clnames->h_hostservs[0].h_host; + flavor_count = getclientsflavors_old(sh, &cln, flavor_list); if (flavor_count == 0) { error = EACCES; @@ -1442,7 +1433,7 @@ reply: fhs.fhs_status = error; if (!svc_sendreply(transp, xdr_fhstatus, (char *)&fhs)) - log_cant_reply(transp); + log_cant_reply_cln(&cln); audit_status = fhs.fhs_status; break; @@ -1459,12 +1450,15 @@ reply: mountres3.fhs_status = error; if (!svc_sendreply(transp, xdr_mountres3, (char *)&mountres3)) - log_cant_reply(transp); + log_cant_reply_cln(&cln); audit_status = mountres3.fhs_status; break; } + if (cln_havehost(&cln)) + host = cln_gethost(&cln); + if (verbose) syslog(LOG_NOTICE, "MOUNT: %s %s %s", (host == NULL) ? "unknown host" : host, @@ -1479,8 +1473,7 @@ reply: if (enqueued == FALSE) { if (host == NULL) { DTRACE_PROBE(mountd, name_by_in_thread); - if (getclientsnames(transp, &nb, &clnames) == 0) - host = clnames->h_hostservs[0].h_host; + host = cln_gethost(&cln); } DTRACE_PROBE(mountd, logged_in_thread); @@ -1494,7 +1487,8 @@ reply: if (sh) sharefree(sh); - netdir_free(clnames, ND_HOSTSERVLIST); + + cln_fini(&cln); return (error); } @@ -1791,13 +1785,13 @@ done: * not distinguished syntactically. We check for hosts first because * it's cheaper, then try netgroups. * - * If pnb and pclnames are NULL, it means that we have to use transp - * to resolve client IP address to hostname. If they aren't NULL - * then transp argument won't be used and can be NULL. + * Return values: + * 1 - access is granted + * 0 - access is denied + * -1 - an error occured */ int -in_access_list(SVCXPRT *transp, struct netbuf **pnb, - struct nd_hostservlist **pclnames, +in_access_list(struct cln *cln, char *access_list) /* N.B. we clobber this "input" parameter */ { char addr[INET_ADDRSTRLEN]; @@ -1805,45 +1799,29 @@ in_access_list(SVCXPRT *transp, struct netbuf **pnb, int nentries = 0; char *cstr = access_list; char *gr = access_list; - char *host; - int off; int i; int response; - int sbr = 0; - struct nd_hostservlist *clnames; - struct netent n, *np; + struct netbuf *pnb; + struct nd_hostservlist *clnames = NULL; /* If no access list - then it's unrestricted */ if (access_list == NULL || *access_list == '\0') return (1); - assert(transp != NULL || (*pnb != NULL && *pclnames != NULL)); - - /* Get client address if it wasn't provided */ - if (*pnb == NULL) - /* Don't grant access if client address isn't known */ - if ((*pnb = svc_getrpccaller(transp)) == NULL) - return (0); - - /* Try to lookup client hostname if it wasn't provided */ - if (*pclnames == NULL) - getclientsnames(transp, pnb, pclnames); - clnames = *pclnames; + if ((pnb = cln_getnbuf(cln)) == NULL) + return (-1); for (;;) { - if ((cstr = strpbrk(cstr, "[]:")) != NULL) { - switch (*cstr) { - case '[': - case ']': - sbr = !sbr; + if ((cstr = strpbrk(cstr, "[:")) != NULL) { + if (*cstr == ':') { + *cstr = '\0'; + } else { + assert(*cstr == '['); + cstr = strchr(cstr + 1, ']'); + if (cstr == NULL) + return (-1); cstr++; continue; - case ':': - if (sbr) { - cstr++; - continue; - } - *cstr = '\0'; } } @@ -1867,6 +1845,8 @@ in_access_list(SVCXPRT *transp, struct netbuf **pnb, /* Netname support */ if (!isdigit(*gr) && *gr != '[') { + struct netent n, *np; + if ((np = getnetbyname_r(gr, &n, buff, sizeof (buff))) != NULL && np->n_net != 0) { @@ -1876,38 +1856,27 @@ in_access_list(SVCXPRT *transp, struct netbuf **pnb, if (inet_ntop(AF_INET, &np->n_net, addr, INET_ADDRSTRLEN) == NULL) break; - if (inet_matchaddr((*pnb)->buf, addr)) + if (inet_matchaddr(pnb->buf, addr)) return (response); } } else { - if (inet_matchaddr((*pnb)->buf, gr)) + if (inet_matchaddr(pnb->buf, gr)) return (response); } - if (cstr == NULL) - break; - - gr = ++cstr; - - continue; + goto next; } /* * No other checks can be performed if client address * can't be resolved. */ - if (clnames == NULL) { - if (cstr == NULL) - break; - - gr = ++cstr; - - continue; - } + if ((clnames = cln_getclientsnames(cln)) == NULL) + goto next; /* Otherwise loop through all client hostname aliases */ for (i = 0; i < clnames->h_cnt; i++) { - host = clnames->h_hostservs[i].h_host; + char *host = clnames->h_hostservs[i].h_host; /* * If the list name begins with a dot then @@ -1920,7 +1889,7 @@ in_access_list(SVCXPRT *transp, struct netbuf **pnb, if (strchr(host, '.') == NULL) return (response); } else { - off = strlen(host) - strlen(gr); + int off = strlen(host) - strlen(gr); if (off > 0 && strcasecmp(host + off, gr) == 0) { return (response); @@ -1935,6 +1904,7 @@ in_access_list(SVCXPRT *transp, struct netbuf **pnb, nentries++; +next: if (cstr == NULL) break; @@ -2032,8 +2002,7 @@ newopts(char *opts) * default is that access is granted. */ static int -getclientsflavors_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, - struct nd_hostservlist **clnames, int *flavors) +getclientsflavors_old(share_t *sh, struct cln *cln, int *flavors) { char *opts, *p, *val; boolean_t ok = B_FALSE; @@ -2059,13 +2028,13 @@ getclientsflavors_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, case OPT_RO: case OPT_RW: defaultaccess = 0; - if (in_access_list(transp, nb, clnames, val)) - ok++; + if (in_access_list(cln, val) > 0) + ok = B_TRUE; break; case OPT_NONE: defaultaccess = 0; - if (in_access_list(transp, nb, clnames, val)) + if (in_access_list(cln, val) > 0) reject = B_TRUE; } } @@ -2074,7 +2043,7 @@ getclientsflavors_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, /* none takes precedence over everything else */ if (reject) - ok = B_TRUE; + ok = B_FALSE; return (defaultaccess || ok); } @@ -2097,13 +2066,13 @@ getclientsflavors_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, * version 3 of the mount protocol. */ static int -getclientsflavors_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, - struct nd_hostservlist **clnames, int *flavors) +getclientsflavors_new(share_t *sh, struct cln *cln, int *flavors) { char *opts, *p, *val; char *lasts; char *f; - boolean_t access_ok; + boolean_t defaultaccess = B_TRUE; /* default access is rw */ + boolean_t access_ok = B_FALSE; int count, c; boolean_t reject = B_FALSE; @@ -2115,43 +2084,43 @@ getclientsflavors_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, p = opts; count = c = 0; - /* default access is rw */ - access_ok = B_TRUE; while (*p) { switch (getsubopt(&p, optlist, &val)) { case OPT_SEC: + if (reject) + access_ok = B_FALSE; + /* * Before a new sec=xxx option, check if we need * to move the c index back to the previous count. */ - if (!access_ok) { + if (!defaultaccess && !access_ok) { c = count; } /* get all the sec=f1[:f2] flavors */ - while ((f = strtok_r(val, ":", &lasts)) - != NULL) { + while ((f = strtok_r(val, ":", &lasts)) != NULL) { flavors[c++] = map_flavor(f); val = NULL; } /* for a new sec=xxx option, default is rw access */ - access_ok = B_TRUE; + defaultaccess = B_TRUE; + access_ok = B_FALSE; + reject = B_FALSE; break; case OPT_RO: case OPT_RW: - if (in_access_list(transp, nb, clnames, val)) { - count = c; + defaultaccess = B_FALSE; + if (in_access_list(cln, val) > 0) access_ok = B_TRUE; - } else { - access_ok = B_FALSE; - } break; case OPT_NONE: - if (in_access_list(transp, nb, clnames, val)) + defaultaccess = B_FALSE; + if (in_access_list(cln, val) > 0) reject = B_TRUE; /* none overides rw/ro */ break; } @@ -2160,7 +2129,7 @@ getclientsflavors_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, if (reject) access_ok = B_FALSE; - if (!access_ok) + if (!defaultaccess && !access_ok) c = count; free(opts); @@ -2178,19 +2147,18 @@ getclientsflavors_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, * flavor. Other flavors are values of the new "sec=" option. */ int -check_client(share_t *sh, struct netbuf *nb, - struct nd_hostservlist *clnames, int flavor, uid_t clnt_uid, gid_t clnt_gid, - uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, - uint_t *srv_ngids, gid_t **srv_gids) +check_client(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, + gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, + gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { if (newopts(sh->sh_opts)) - return (check_client_new(sh, NULL, &nb, &clnames, flavor, - clnt_uid, clnt_gid, clnt_ngids, clnt_gids, srv_uid, srv_gid, - srv_ngids, srv_gids)); + return (check_client_new(sh, cln, flavor, clnt_uid, clnt_gid, + clnt_ngids, clnt_gids, srv_uid, srv_gid, srv_ngids, + srv_gids)); else - return (check_client_old(sh, NULL, &nb, &clnames, flavor, - clnt_uid, clnt_gid, clnt_ngids, clnt_gids, srv_uid, srv_gid, - srv_ngids, srv_gids)); + return (check_client_old(sh, cln, flavor, clnt_uid, clnt_gid, + clnt_ngids, clnt_gids, srv_uid, srv_gid, srv_ngids, + srv_gids)); } extern int _getgroupsbymember(const char *, gid_t[], int, int); @@ -2313,8 +2281,7 @@ get_gid(char *value, gid_t *gid) } static int -check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, - struct nd_hostservlist **clnames, int flavor, uid_t clnt_uid, +check_client_old(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { @@ -2363,7 +2330,7 @@ check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, list++; if (val != NULL) ro_val++; - if (in_access_list(transp, nb, clnames, val)) + if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RO; break; @@ -2371,7 +2338,7 @@ check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, list++; if (val != NULL) rw_val++; - if (in_access_list(transp, nb, clnames, val)) + if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RW; break; @@ -2390,7 +2357,7 @@ check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, if (clnt_uid != 0) break; - if (in_access_list(transp, nb, clnames, val)) { + if (in_access_list(cln, val) > 0) { perm |= NFSAUTH_ROOT; perm |= NFSAUTH_UIDMAP | NFSAUTH_GIDMAP; map_deny = B_FALSE; @@ -2410,7 +2377,7 @@ check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, * to this share at all. This option behaves * more like "root" than either "rw" or "ro". */ - if (in_access_list(transp, nb, clnames, val)) + if (in_access_list(cln, val) > 0) perm |= NFSAUTH_DENIED; break; @@ -2469,7 +2436,7 @@ check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, break; } - if (in_access_list(transp, nb, clnames, al)) { + if (in_access_list(cln, al) > 0) { *srv_uid = srv; perm |= NFSAUTH_UIDMAP; @@ -2542,7 +2509,7 @@ check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, break; } - if (in_access_list(transp, nb, clnames, al)) { + if (in_access_list(cln, al) > 0) { *srv_gid = srv; perm |= NFSAUTH_GIDMAP; @@ -2627,15 +2594,13 @@ check_client_old(share_t *sh, SVCXPRT *transp, struct netbuf **nb, * return TRUE to indicate that this "flavor" is a wrong sec. */ static bool_t -is_wrongsec(share_t *sh, SVCXPRT *transp, struct netbuf **nb, - struct nd_hostservlist **clnames, int flavor) +is_wrongsec(share_t *sh, struct cln *cln, int flavor) { int flavor_list[MAX_FLAVORS]; int flavor_count, i; /* get the flavor list that the client has access with */ - flavor_count = getclientsflavors_new(sh, transp, nb, - clnames, flavor_list); + flavor_count = getclientsflavors_new(sh, cln, flavor_list); if (flavor_count == 0) return (FALSE); @@ -2673,8 +2638,7 @@ is_wrongsec(share_t *sh, SVCXPRT *transp, struct netbuf **nb, */ static int -check_client_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, - struct nd_hostservlist **clnames, int flavor, uid_t clnt_uid, +check_client_new(share_t *sh, struct cln *cln, int flavor, uid_t clnt_uid, gid_t clnt_gid, uint_t clnt_ngids, gid_t *clnt_gids, uid_t *srv_uid, gid_t *srv_gid, uint_t *srv_ngids, gid_t **srv_gids) { @@ -2729,7 +2693,7 @@ check_client_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, list++; if (val != NULL) ro_val++; - if (in_access_list(transp, nb, clnames, val)) + if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RO; break; @@ -2740,7 +2704,7 @@ check_client_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, list++; if (val != NULL) rw_val++; - if (in_access_list(transp, nb, clnames, val)) + if (in_access_list(cln, val) > 0) perm |= NFSAUTH_RW; break; @@ -2762,7 +2726,7 @@ check_client_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, if (clnt_uid != 0) break; - if (in_access_list(transp, nb, clnames, val)) { + if (in_access_list(cln, val) > 0) { perm |= NFSAUTH_ROOT; perm |= NFSAUTH_UIDMAP | NFSAUTH_GIDMAP; map_deny = B_FALSE; @@ -2782,7 +2746,7 @@ check_client_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, * to this share at all. This option behaves * more like "root" than either "rw" or "ro". */ - if (in_access_list(transp, nb, clnames, val)) + if (in_access_list(cln, val) > 0) perm |= NFSAUTH_DENIED; break; @@ -2841,7 +2805,7 @@ check_client_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, break; } - if (in_access_list(transp, nb, clnames, al)) { + if (in_access_list(cln, al) > 0) { *srv_uid = srv; perm |= NFSAUTH_UIDMAP; @@ -2914,7 +2878,7 @@ check_client_new(share_t *sh, SVCXPRT *transp, struct netbuf **nb, break; } - if (in_access_list(transp, nb, clnames, al)) { + if (in_access_list(cln, al) > 0) { *srv_gid = srv; perm |= NFSAUTH_GIDMAP; @@ -2967,7 +2931,7 @@ done: * If not, return NFSAUTH_DENIED. */ if ((perm & (NFSAUTH_RO | NFSAUTH_RW)) == 0) { - if (is_wrongsec(sh, transp, nb, clnames, flavor)) + if (is_wrongsec(sh, cln, flavor)) perm |= NFSAUTH_WRONGSEC; else perm |= NFSAUTH_DENIED; @@ -3137,9 +3101,8 @@ umount(struct svc_req *rqstp) { char *host, *path, *remove_path; char rpath[MAXPATHLEN]; - struct nd_hostservlist *clnames = NULL; SVCXPRT *transp; - struct netbuf *nb; + struct cln cln; transp = rqstp->rq_xprt; path = NULL; @@ -3147,11 +3110,15 @@ umount(struct svc_req *rqstp) svcerr_decode(transp); return; } + + cln_init(&cln, transp); + errno = 0; if (!svc_sendreply(transp, xdr_void, (char *)NULL)) - log_cant_reply(transp); + log_cant_reply_cln(&cln); - if (getclientsnames(transp, &nb, &clnames) != 0) { + host = cln_gethost(&cln); + if (host == NULL) { /* * Without the hostname we can't do audit or delete * this host from the mount entries. @@ -3159,7 +3126,6 @@ umount(struct svc_req *rqstp) svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); return; } - host = clnames->h_hostservs[0].h_host; if (verbose) syslog(LOG_NOTICE, "UNMOUNT: %s unmounted %s", host, path); @@ -3175,8 +3141,9 @@ umount(struct svc_req *rqstp) mntlist_delete(host, remove_path); /* remove from mount list */ + cln_fini(&cln); + svc_freeargs(transp, xdr_dirpath, (caddr_t)&path); - netdir_free(clnames, ND_HOSTSERVLIST); } /* @@ -3185,10 +3152,9 @@ umount(struct svc_req *rqstp) static void umountall(struct svc_req *rqstp) { - struct nd_hostservlist *clnames = NULL; SVCXPRT *transp; char *host; - struct netbuf *nb; + struct cln cln; transp = rqstp->rq_xprt; if (!svc_getargs(transp, xdr_void, NULL)) { @@ -3202,13 +3168,15 @@ umountall(struct svc_req *rqstp) * on the net blasting the requester with a response. */ svcerr_systemerr(transp); - if (getclientsnames(transp, &nb, &clnames) != 0) { + + cln_init(&cln, transp); + + host = cln_gethost(&cln); + if (host == NULL) { /* Can't do anything without the name of the client */ return; } - host = clnames->h_hostservs[0].h_host; - /* * Remove all hosts entries from mount list */ @@ -3217,7 +3185,7 @@ umountall(struct svc_req *rqstp) if (verbose) syslog(LOG_NOTICE, "UNMOUNTALL: from %s", host); - netdir_free(clnames, ND_HOSTSERVLIST); + cln_fini(&cln); } void * diff --git a/usr/src/cmd/fs.d/nfs/mountd/mountd.h b/usr/src/cmd/fs.d/nfs/mountd/mountd.h index 1bc7b088a7..85a506e1b9 100644 --- a/usr/src/cmd/fs.d/nfs/mountd/mountd.h +++ b/usr/src/cmd/fs.d/nfs/mountd/mountd.h @@ -60,18 +60,35 @@ extern struct sh_list *share_list; extern rwlock_t sharetab_lock; extern void check_sharetab(void); +struct cln; extern void log_cant_reply(SVCXPRT *); +extern void log_cant_reply_cln(struct cln *); extern void *exmalloc(size_t); extern struct share *findentry(char *); -extern int check_client(struct share *, struct netbuf *, - struct nd_hostservlist *, int, uid_t, gid_t, uint_t, gid_t *, uid_t *, - gid_t *, uint_t *, gid_t **); -extern struct nd_hostservlist *anon_client(char *host); - -extern int in_access_list(SVCXPRT *, struct netbuf **, - struct nd_hostservlist **, char *); +extern int check_client(struct share *, struct cln *, int, uid_t, gid_t, uint_t, + gid_t *, uid_t *, gid_t *, uint_t *, gid_t **); + +extern int in_access_list(struct cln *, char *); + +struct cln { + SVCXPRT *transp; + char *netid; + struct netconfig *nconf; + struct netbuf *nbuf; + struct nd_hostservlist *clnames; + char *host; + int flags; +}; + +extern void cln_init(struct cln *, SVCXPRT *); +extern void cln_init_lazy(struct cln *, char *, struct netbuf *); +extern void cln_fini(struct cln *); +extern struct netbuf *cln_getnbuf(struct cln *); +extern struct nd_hostservlist *cln_getclientsnames(struct cln *); +extern boolean_t cln_havehost(struct cln *); +extern char *cln_gethost(struct cln *); /* * These functions are defined here due to the fact diff --git a/usr/src/cmd/fs.d/nfs/mountd/nfs_cmd.c b/usr/src/cmd/fs.d/nfs/mountd/nfs_cmd.c index fba58fa821..b92a862d91 100644 --- a/usr/src/cmd/fs.d/nfs/mountd/nfs_cmd.c +++ b/usr/src/cmd/fs.d/nfs/mountd/nfs_cmd.c @@ -101,42 +101,29 @@ charmap_search(struct netbuf *nbuf, char *opts) char *next; char *name; char *result = NULL; - struct netconfig *nconf; - struct nd_hostservlist *hl = NULL; + char *netid; struct sockaddr *sa; + struct cln cln; + sa = (struct sockaddr *)nbuf->buf; switch (sa->sa_family) { case AF_INET: - nconf = getnetconfigent("tcp"); + netid = "tcp"; break; case AF_INET6: - nconf = getnetconfigent("tcp6"); + netid = "tcp6"; break; default: return (NULL); } - if (nconf == NULL) { - return (NULL); - } - - /* - * Use the this API instead of the netdir_getbyaddr() - * to avoid service lookup. - */ - if (__netdir_getbyaddr_nosrv(nconf, &hl, nbuf)) { - syslog(LOG_ERR, "netdir: %s\n", netdir_sperror()); - freenetconfigent(nconf); - return (NULL); - } - copts = strdup(opts); - if (copts == NULL) { - freenetconfigent(nconf); + if (copts == NULL) return (NULL); - } + + cln_init_lazy(&cln, netid, nbuf); next = copts; while (*next != '\0') { @@ -152,7 +139,7 @@ charmap_search(struct netbuf *nbuf, char *opts) cp = strchr(name, '='); if (cp != NULL) *cp = '\0'; - if (in_access_list(NULL, &nbuf, &hl, val)) { + if (in_access_list(&cln, val) > 0) { result = name; break; } @@ -162,8 +149,8 @@ charmap_search(struct netbuf *nbuf, char *opts) if (result != NULL) result = strdup(result); + cln_fini(&cln); free(copts); - freenetconfigent(nconf); return (result); } diff --git a/usr/src/cmd/fs.d/nfs/mountd/nfsauth.c b/usr/src/cmd/fs.d/nfs/mountd/nfsauth.c index 34a971759b..fe49f911b2 100644 --- a/usr/src/cmd/fs.d/nfs/mountd/nfsauth.c +++ b/usr/src/cmd/fs.d/nfs/mountd/nfsauth.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2014 Nexenta Systems, Inc. All rights reserved. + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* @@ -57,69 +57,31 @@ static void nfsauth_access(auth_req *argp, auth_res *result) { - struct netconfig *nconf; - struct nd_hostservlist *clnames = NULL; struct netbuf nbuf; struct share *sh; - char tmp[MAXIPADDRLEN]; - char *host = NULL; - result->auth_perm = NFSAUTH_DENIED; + struct cln cln; - /* - * Convert the client's address to a hostname - */ - nconf = getnetconfigent(argp->req_netid); - if (nconf == NULL) { - syslog(LOG_ERR, "No netconfig entry for %s", argp->req_netid); - return; - } + result->auth_perm = NFSAUTH_DENIED; nbuf.len = argp->req_client.n_len; nbuf.buf = argp->req_client.n_bytes; if (nbuf.len == 0 || nbuf.buf == NULL) - goto done; - - if (netdir_getbyaddr(nconf, &clnames, &nbuf)) { - host = &tmp[0]; - if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { - struct sockaddr_in *sa; - - /* LINTED pointer alignment */ - sa = (struct sockaddr_in *)nbuf.buf; - (void) inet_ntoa_r(sa->sin_addr, tmp); - } else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { - struct sockaddr_in6 *sa; - /* LINTED pointer */ - sa = (struct sockaddr_in6 *)nbuf.buf; - (void) inet_ntop(AF_INET6, sa->sin6_addr.s6_addr, - tmp, INET6_ADDRSTRLEN); - } - clnames = anon_client(host); - } - /* - * Both netdir_getbyaddr() and anon_client() can return a NULL - * clnames. This has been seen when the DNS entry for the client - * name does not have the correct format or a reverse lookup DNS - * entry cannot be found for the client's IP address. - */ - if (clnames == NULL) { - syslog(LOG_ERR, "Could not find DNS entry for %s", - argp->req_netid); - goto done; - } + return; /* - * Now find the export + * Find the export */ sh = findentry(argp->req_path); if (sh == NULL) { syslog(LOG_ERR, "%s not exported", argp->req_path); - goto done; + return; } - result->auth_perm = check_client(sh, &nbuf, clnames, argp->req_flavor, + cln_init_lazy(&cln, argp->req_netid, &nbuf); + + result->auth_perm = check_client(sh, &cln, argp->req_flavor, argp->req_clnt_uid, argp->req_clnt_gid, argp->req_clnt_gids.len, argp->req_clnt_gids.val, &result->auth_srv_uid, &result->auth_srv_gid, &result->auth_srv_gids.len, @@ -128,14 +90,13 @@ nfsauth_access(auth_req *argp, auth_res *result) sharefree(sh); if (result->auth_perm == NFSAUTH_DENIED) { - syslog(LOG_ERR, "%s denied access to %s", - clnames->h_hostservs[0].h_host, argp->req_path); + char *host = cln_gethost(&cln); + if (host != NULL) + syslog(LOG_ERR, "%s denied access to %s", host, + argp->req_path); } -done: - freenetconfigent(nconf); - if (clnames) - netdir_free(clnames, ND_HOSTSERVLIST); + cln_fini(&cln); } void -- cgit v1.2.3 From fd76205d7372b305e64b7cfcd27939a39c743cb4 Mon Sep 17 00:00:00 2001 From: Saso Kiselkov Date: Tue, 25 Feb 2014 19:18:32 +0100 Subject: 6397 libiscsit: several leaks due to left "errlist" Reviewed by: Joshua M. Clulow Approved by: Robert Mustacchi --- usr/src/lib/libiscsit/common/libiscsit.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'usr/src') diff --git a/usr/src/lib/libiscsit/common/libiscsit.c b/usr/src/lib/libiscsit/common/libiscsit.c index c45b9b1c1d..e584999ac5 100644 --- a/usr/src/lib/libiscsit/common/libiscsit.c +++ b/usr/src/lib/libiscsit/common/libiscsit.c @@ -22,7 +22,7 @@ * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. */ /* - * Copyright 2011 Nexenta Systems, Inc. All rights reserved. + * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ #include @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,9 @@ iqnstr(char *s); static void euistr(char *s); +static void +free_empty_errlist(nvlist_t **errlist); + /* * Function: it_config_load() * @@ -484,6 +488,9 @@ it_config_setprop(it_config_t *cfg, nvlist_t *proplist, nvlist_t **errlist) } } + if (ret == 0) + free_empty_errlist(errlist); + return (ret); } @@ -704,6 +711,8 @@ it_tgt_setprop(it_config_t *cfg, it_tgt_t *tgt, nvlist_t *proplist, } tgt->tgt_properties = tprops; + free_empty_errlist(errlist); + return (0); } @@ -1451,6 +1460,8 @@ it_ini_setprop(it_ini_t *ini, nvlist_t *proplist, nvlist_t **errlist) } ini->ini_properties = iprops; + free_empty_errlist(errlist); + return (0); } @@ -2042,3 +2053,13 @@ euistr(char *s) } } } + +static void +free_empty_errlist(nvlist_t **errlist) +{ + if (errlist != NULL && *errlist != NULL) { + assert(fnvlist_num_pairs(*errlist) == 0); + nvlist_free(*errlist); + *errlist = NULL; + } +} -- cgit v1.2.3 From 3a005aada8ac0e291c13cbc488ba9ae1473f0a96 Mon Sep 17 00:00:00 2001 From: Yuri Pankov Date: Fri, 23 Oct 2015 03:35:20 +0300 Subject: 6377 Fix man page issues found by mandoc 1.13.3 Reviewed by: Garrett D'Amore Reviewed by: Igor Kozhukhov Reviewed by: Robert Mustacchi Approved by: Gordon Ross --- usr/src/man/man1m/ipadm.1m | 2 +- usr/src/man/man1m/mailwrapper.1m | 4 ++-- usr/src/man/man1m/share_nfs.1m | 2 +- usr/src/man/man2/vfork.2 | 2 +- usr/src/man/man3avl/avl_add.3avl | 4 ++-- usr/src/man/man3avl/avl_destroy.3avl | 4 ++-- usr/src/man/man3avl/avl_destroy_nodes.3avl | 4 ++-- usr/src/man/man3avl/avl_find.3avl | 4 ++-- usr/src/man/man3avl/avl_first.3avl | 4 ++-- usr/src/man/man3avl/avl_insert.3avl | 6 +++--- usr/src/man/man3c/clearenv.3c | 2 +- usr/src/man/man3c/eventfd.3c | 2 +- usr/src/man/man3c/getentropy.3c | 2 +- usr/src/man/man3c/pthread_mutex_consistent.3c | 4 ++-- usr/src/man/man3c/timerfd_create.3c | 4 ++-- usr/src/man/man3socket/sockaddr.3socket | 2 +- usr/src/man/man4/ctf.4 | 2 +- usr/src/man/man4/mailer.conf.4 | 2 +- usr/src/man/man5/ieee802.3.5 | 2 +- usr/src/man/man5/man.5 | 2 +- usr/src/man/man5/pam_timestamp.5 | 4 ++-- usr/src/man/man7p/ndp.7p | 2 +- usr/src/man/man9f/avl.9f | 6 +++--- 23 files changed, 36 insertions(+), 36 deletions(-) (limited to 'usr/src') diff --git a/usr/src/man/man1m/ipadm.1m b/usr/src/man/man1m/ipadm.1m index 1c5e8ad498..1ef5f44174 100644 --- a/usr/src/man/man1m/ipadm.1m +++ b/usr/src/man/man1m/ipadm.1m @@ -831,4 +831,4 @@ subcommand for the list of property names. .Xr ndd 1M , .Xr zonecfg 1M , .Xr nsswitch.conf 4 , -.Xr dhcp 5 . +.Xr dhcp 5 diff --git a/usr/src/man/man1m/mailwrapper.1m b/usr/src/man/man1m/mailwrapper.1m index 90cc4ea26a..e1b176f5d6 100644 --- a/usr/src/man/man1m/mailwrapper.1m +++ b/usr/src/man/man1m/mailwrapper.1m @@ -126,5 +126,5 @@ or does not contain a mapping for the name under which it was invoked. .Xr mail 1 , .Xr mailq 1 , .Xr newaliases 1M , -.Xr mailer.conf 4 , -.Xr sendmail 1M +.Xr sendmail 1M , +.Xr mailer.conf 4 diff --git a/usr/src/man/man1m/share_nfs.1m b/usr/src/man/man1m/share_nfs.1m index f95320cc7b..f19aec79b6 100644 --- a/usr/src/man/man1m/share_nfs.1m +++ b/usr/src/man/man1m/share_nfs.1m @@ -517,8 +517,8 @@ share -o uidmap=100:joe:@10.0.0.1 /export .Xr share 1M , .Xr unshare 1M , .Xr getnetbyname 3SOCKET , -.Xr nfslog.conf 4 , .Xr netgroup 4 , +.Xr nfslog.conf 4 , .Xr attributes 5 , .Xr nfssec 5 .Sh NOTES diff --git a/usr/src/man/man2/vfork.2 b/usr/src/man/man2/vfork.2 index 6285d7b22d..e6b39b04c0 100644 --- a/usr/src/man/man2/vfork.2 +++ b/usr/src/man/man2/vfork.2 @@ -204,8 +204,8 @@ function is .Xr exit 3C , .Xr posix_spawn 3C , .Xr posix_spawnp 3C , -.Xr signal.h 3HEAD , .Xr wait 3C , +.Xr signal.h 3HEAD , .Xr standards 5 .Sh NOTES To avoid a possible deadlock situation, processes that are children in the diff --git a/usr/src/man/man3avl/avl_add.3avl b/usr/src/man/man3avl/avl_add.3avl index 0ac7912264..3d1629408b 100644 --- a/usr/src/man/man3avl/avl_add.3avl +++ b/usr/src/man/man3avl/avl_add.3avl @@ -88,8 +88,8 @@ See in .Xr libavl 3LIB . .Sh SEE ALSO -.Xr libavl 3LIB , .Xr avl_create 3AVL , +.Xr avl_destroy 3AVL , .Xr avl_insert 3AVL , .Xr avl_insert_here 3AVL , -.Xr avl_destroy 3AVL +.Xr libavl 3LIB diff --git a/usr/src/man/man3avl/avl_destroy.3avl b/usr/src/man/man3avl/avl_destroy.3avl index 6ae78c9c60..b892fbb7f5 100644 --- a/usr/src/man/man3avl/avl_destroy.3avl +++ b/usr/src/man/man3avl/avl_destroy.3avl @@ -57,6 +57,6 @@ See in .Xr libavl 3LIB . .Sh SEE ALSO -.Xr libavl 3LIB , .Xr avl_create 3AVL , -.Xr avl_destroy_nodes 3AVL +.Xr avl_destroy_nodes 3AVL , +.Xr libavl 3LIB diff --git a/usr/src/man/man3avl/avl_destroy_nodes.3avl b/usr/src/man/man3avl/avl_destroy_nodes.3avl index c5488f43fb..94b433f466 100644 --- a/usr/src/man/man3avl/avl_destroy_nodes.3avl +++ b/usr/src/man/man3avl/avl_destroy_nodes.3avl @@ -92,5 +92,5 @@ See in .Xr libavl 3LIB . .Sh SEE ALSO -.Xr libavl 3LIB , -.Xr avl_destroy 3AVL +.Xr avl_destroy 3AVL , +.Xr libavl 3LIB diff --git a/usr/src/man/man3avl/avl_find.3avl b/usr/src/man/man3avl/avl_find.3avl index a98778598f..a43ceac4be 100644 --- a/usr/src/man/man3avl/avl_find.3avl +++ b/usr/src/man/man3avl/avl_find.3avl @@ -97,7 +97,7 @@ See in .Xr libavl 3LIB . .Sh SEE ALSO -.Xr libavl 3LIB , .Xr avl_create 3AVL , .Xr avl_insert 3AVL , -.Xr avl_nearest 3AVL +.Xr avl_nearest 3AVL , +.Xr libavl 3LIB diff --git a/usr/src/man/man3avl/avl_first.3avl b/usr/src/man/man3avl/avl_first.3avl index a886308472..a4417c60cf 100644 --- a/usr/src/man/man3avl/avl_first.3avl +++ b/usr/src/man/man3avl/avl_first.3avl @@ -128,5 +128,5 @@ See in .Xr libavl 3LIB . .Sh SEE ALSO -.Xr libavl 3LIB , -.Xr avl_create 3AVL +.Xr avl_create 3AVL , +.Xr libavl 3LIB diff --git a/usr/src/man/man3avl/avl_insert.3avl b/usr/src/man/man3avl/avl_insert.3avl index 89d6c8a7af..f00cf533ad 100644 --- a/usr/src/man/man3avl/avl_insert.3avl +++ b/usr/src/man/man3avl/avl_insert.3avl @@ -80,7 +80,7 @@ then the argument should have the value .Dv AVL_BEFORE . Otherwise, to indicate that the new entry should be inserted after -.Fa here, +.Fa here , then .Fa direction should be set to @@ -105,6 +105,6 @@ See in .Xr libavl 3LIB . .Sh SEE ALSO -.Xr libavl 3LIB , .Xr avl_add 3AVL , -.Xr avl_find 3AVL +.Xr avl_find 3AVL , +.Xr libavl 3LIB diff --git a/usr/src/man/man3c/clearenv.3c b/usr/src/man/man3c/clearenv.3c index 1bf5cbde17..aa32c3f7a9 100644 --- a/usr/src/man/man3c/clearenv.3c +++ b/usr/src/man/man3c/clearenv.3c @@ -57,8 +57,8 @@ No errors are defined. .Sy Mt-Safe . .Sh SEE ALSO .Xr getenv 3C , -.Xr unsetenv 3C , .Xr setenv 3C , +.Xr unsetenv 3C , .Xr attributes 5 , .Xr environ 5 , .Xr standards 5 diff --git a/usr/src/man/man3c/eventfd.3c b/usr/src/man/man3c/eventfd.3c index 3a9f8be284..74e164404e 100644 --- a/usr/src/man/man3c/eventfd.3c +++ b/usr/src/man/man3c/eventfd.3c @@ -179,6 +179,6 @@ file descriptors open in the calling process. .El .Sh SEE ALSO .Xr poll 2 , -.Xr port_get 3C , .Xr epoll_wait 3C , +.Xr port_get 3C , .Xr eventfd 5 diff --git a/usr/src/man/man3c/getentropy.3c b/usr/src/man/man3c/getentropy.3c index c149521c73..c15f6b5fff 100644 --- a/usr/src/man/man3c/getentropy.3c +++ b/usr/src/man/man3c/getentropy.3c @@ -61,5 +61,5 @@ Too many bytes requested, or some other fatal error occurred. .Sh MT-LEVEL .Sy Async-Signal-Safe .Sh SEE ALSO -.Xr arc4random 3C +.Xr arc4random 3C , .Xr attributes 5 diff --git a/usr/src/man/man3c/pthread_mutex_consistent.3c b/usr/src/man/man3c/pthread_mutex_consistent.3c index 2b6fd0206d..7adfec6527 100644 --- a/usr/src/man/man3c/pthread_mutex_consistent.3c +++ b/usr/src/man/man3c/pthread_mutex_consistent.3c @@ -77,10 +77,10 @@ state. .Sh MT-LEVEL .Sy MT-Safe .Sh SEE ALSO -.Xr pthread_mutexattr_getrobust 3C , .Xr pthread_mutex_destroy 3C , -.Xr pthread_mutex_lock 3C , .Xr pthread_mutex_init 3C , +.Xr pthread_mutex_lock 3C , +.Xr pthread_mutexattr_getrobust 3C , .Xr pthread 3HEAD , .Xr libpthread 3LIB , .Xr attributes 5 , diff --git a/usr/src/man/man3c/timerfd_create.3c b/usr/src/man/man3c/timerfd_create.3c index 84df47e245..f597bfbdc3 100644 --- a/usr/src/man/man3c/timerfd_create.3c +++ b/usr/src/man/man3c/timerfd_create.3c @@ -170,7 +170,7 @@ is returned. Otherwise, is returned and errno is set to indicate the error. .Sh ERRORS The -.Fn timerfd_create() +.Fn timerfd_create function will fail if: .Bl -tag -width Er .It Er EINAL @@ -192,8 +192,8 @@ is not asserted in the effective set of the calling process. .El .Sh SEE ALSO .Xr poll 2 , -.Xr port_get 3C , .Xr epoll_wait 3C , +.Xr port_get 3C , .Xr timer_create 3C , .Xr timer_gettime 3C , .Xr timer_settime 3C , diff --git a/usr/src/man/man3socket/sockaddr.3socket b/usr/src/man/man3socket/sockaddr.3socket index c5c796dcb0..5f9dcc7f67 100644 --- a/usr/src/man/man3socket/sockaddr.3socket +++ b/usr/src/man/man3socket/sockaddr.3socket @@ -559,4 +559,4 @@ main(void) .Xr inet 7P , .Xr inet6 7P , .Xr ip 7P , -.Xr ip6 7P , +.Xr ip6 7P diff --git a/usr/src/man/man4/ctf.4 b/usr/src/man/man4/ctf.4 index 9e807abc4c..6c057beb5f 100644 --- a/usr/src/man/man4/ctf.4 +++ b/usr/src/man/man4/ctf.4 @@ -1135,6 +1135,6 @@ alignment must be 4. .Sh SEE ALSO .Xr mdb 1 , .Xr dtrace 1M , -.Xr libelf 3LIB , .Xr gelf 3ELF , +.Xr libelf 3LIB , .Xr a.out 4 diff --git a/usr/src/man/man4/mailer.conf.4 b/usr/src/man/man4/mailer.conf.4 index dcf52807bf..b4d52c608f 100644 --- a/usr/src/man/man4/mailer.conf.4 +++ b/usr/src/man/man4/mailer.conf.4 @@ -100,8 +100,8 @@ newaliases /usr/lib/smtp/sendmail/sendmail .Sh SEE ALSO .Xr mail 1 , .Xr mailq 1 , -.Xr newaliases 1M , .Xr mailwrapper 1M , +.Xr newaliases 1M , .Xr sendmail 1M .Sh AUTHORS .An Perry E. Metzger Aq perry@piermont.com diff --git a/usr/src/man/man5/ieee802.3.5 b/usr/src/man/man5/ieee802.3.5 index 596f3590eb..79be2b2124 100644 --- a/usr/src/man/man5/ieee802.3.5 +++ b/usr/src/man/man5/ieee802.3.5 @@ -480,8 +480,8 @@ The DLPI and IEEE 802.3 itself are .Xr dladm 1M , .Xr ifconfig 1M , .Xr kstat 1M , -.Xr netstat 1M , .Xr ndd 1M , +.Xr netstat 1M , .Xr libdlpi 3LIB , .Xr driver.conf 4 , .Xr dlpi 7P diff --git a/usr/src/man/man5/man.5 b/usr/src/man/man5/man.5 index f44edba55f..0ed6910e33 100644 --- a/usr/src/man/man5/man.5 +++ b/usr/src/man/man5/man.5 @@ -245,8 +245,8 @@ package should be used instead. .Xr man 1 , .Xr mandoc 1 , .Xr nroff 1 , -.Xr troff 1 , .Xr tbl 1 , +.Xr troff 1 , .Xr whatis 1 , .Xr mdoc 5 .Rs diff --git a/usr/src/man/man5/pam_timestamp.5 b/usr/src/man/man5/pam_timestamp.5 index 8ed105f825..7e31657ab1 100644 --- a/usr/src/man/man5/pam_timestamp.5 +++ b/usr/src/man/man5/pam_timestamp.5 @@ -107,8 +107,8 @@ su auth required pam_unix_auth.so.1 .Sy MT-Safe . .Sh SEE ALSO .Xr su 1M , +.Xr syslog 3C , .Xr pam 3PAM , .Xr pam_sm_authenticate 3PAM , .Xr pam_sm_setcred 3PAM , -.Xr pam.conf 4 , -.Xr syslog 3C +.Xr pam.conf 4 diff --git a/usr/src/man/man7p/ndp.7p b/usr/src/man/man7p/ndp.7p index ef57b1b3d6..589a6dc5ad 100644 --- a/usr/src/man/man7p/ndp.7p +++ b/usr/src/man/man7p/ndp.7p @@ -329,9 +329,9 @@ int main(int argc, char *argv[]) { } .Ed .Sh SEE ALSO +.Xr ifconfig 1M , .Xr in.ndpd 1M , .Xr ndp 1M , -.Xr ifconfig 1M , .Xr sockaddr_in6 3SOCKET , .Xr privileges 5 .Rs diff --git a/usr/src/man/man9f/avl.9f b/usr/src/man/man9f/avl.9f index e494abad65..f1166ccdf4 100644 --- a/usr/src/man/man9f/avl.9f +++ b/usr/src/man/man9f/avl.9f @@ -67,7 +67,6 @@ and .Xr rwlock 9F for more information on synchronization primitives. .Sh SEE ALSO -.Xr libavl 3LIB , .Xr avl_add 3AVL , .Xr avl_create 3AVL , .Xr avl_destroy 3AVL , @@ -79,10 +78,11 @@ for more information on synchronization primitives. .Xr avl_is_empty 3AVL , .Xr avl_last 3AVL , .Xr avl_nearest 3AVL , -.Xr avl_numnodes 3AVL , -.Xr avl_swap 3AVL , .Xr AVL_NEXT 3AVL , +.Xr avl_numnodes 3AVL , .Xr AVL_PREV 3AVL , +.Xr avl_swap 3AVL , +.Xr libavl 3LIB .Rs .%T Writing Device Drivers .Re -- cgit v1.2.3 From 1058dba45ec5d6876a5bbb0b9174347e13e5748d Mon Sep 17 00:00:00 2001 From: Yuri Pankov Date: Fri, 23 Oct 2015 03:42:12 +0300 Subject: 6346 zfs(1M) has spurious comma Reviewed by: Garrett D'Amore Reviewed by: Igor Kozhukhov Reviewed by: Robert Mustacchi Approved by: Gordon Ross --- usr/src/man/man1m/zfs.1m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'usr/src') diff --git a/usr/src/man/man1m/zfs.1m b/usr/src/man/man1m/zfs.1m index 328fdcca3a..1fdadc346a 100644 --- a/usr/src/man/man1m/zfs.1m +++ b/usr/src/man/man1m/zfs.1m @@ -773,7 +773,7 @@ in which case it will be interpreted as a snapshot in the same filesystem as this dataset. The .Em snapshot may be a full snapshot name -.No Po Em filesystem Ns @ Ns Em snapshot Pc , +.Po Em filesystem Ns @ Ns Em snapshot Pc , which for clones may be a snapshot in the origin's filesystem .Pq or the origin of the origin's filesystem, etc. .El @@ -1620,7 +1620,7 @@ administrators can use them to annotate datasets .Pq file systems, volumes, and snapshots . .Pp User property names must contain a colon -.No Po Ns Sy \&: Ns Pc +.Pq Qq Sy \&: character to distinguish them from native properties. They may contain lowercase letters, numbers, and the following punctuation characters: colon .Pq Qq Sy \&: , @@ -3527,7 +3527,7 @@ M F /tank/test/modified .Sh INTERFACE STABILITY .Sy Commited . .Sh SEE ALSO -.Xr gzip 1, +.Xr gzip 1 , .Xr ssh 1 , .Xr mount 1M , .Xr share 1M , -- cgit v1.2.3 From 260e9a87725c090ba5835b1f9f0b62fa2f96036f Mon Sep 17 00:00:00 2001 From: Yuri Pankov Date: Sun, 18 Oct 2015 14:22:06 +0300 Subject: 6356 Update mdocml to 1.13.3 Reviewed by: Toomas Soome Reviewed by: Igor Kozhukhov Approved by: Gordon Ross --- exception_lists/cstyle | 6 +- exception_lists/hdrchk | 3 + usr/src/cmd/man/man.c | 23 +- usr/src/cmd/man/man.h | 3 +- usr/src/cmd/mandoc/Makefile | 26 +- usr/src/cmd/mandoc/Makefile.common | 61 +- usr/src/cmd/mandoc/THIRDPARTYLICENSE | 41 +- usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip | 2 +- usr/src/cmd/mandoc/arch.c | 39 - usr/src/cmd/mandoc/arch.in | 112 -- usr/src/cmd/mandoc/att.c | 26 +- usr/src/cmd/mandoc/att.in | 40 - usr/src/cmd/mandoc/chars.c | 58 +- usr/src/cmd/mandoc/chars.in | 294 +-- usr/src/cmd/mandoc/compat_fgetln.c | 94 + usr/src/cmd/mandoc/compat_reallocarray.c | 49 + usr/src/cmd/mandoc/compat_strtonum.c | 76 + usr/src/cmd/mandoc/config.h | 66 +- usr/src/cmd/mandoc/eqn.c | 1462 ++++++++------ usr/src/cmd/mandoc/eqn_html.c | 189 +- usr/src/cmd/mandoc/eqn_term.c | 91 +- usr/src/cmd/mandoc/html.c | 373 ++-- usr/src/cmd/mandoc/html.h | 68 +- usr/src/cmd/mandoc/lib.c | 9 +- usr/src/cmd/mandoc/libman.h | 39 +- usr/src/cmd/mandoc/libmandoc.h | 75 +- usr/src/cmd/mandoc/libmdoc.h | 57 +- usr/src/cmd/mandoc/libroff.h | 52 +- usr/src/cmd/mandoc/main.c | 853 ++++++-- usr/src/cmd/mandoc/main.h | 29 +- usr/src/cmd/mandoc/man.c | 510 +++-- usr/src/cmd/mandoc/man.h | 13 +- usr/src/cmd/mandoc/man_hash.c | 11 +- usr/src/cmd/mandoc/man_html.c | 193 +- usr/src/cmd/mandoc/man_macro.c | 370 ++-- usr/src/cmd/mandoc/man_term.c | 574 +++--- usr/src/cmd/mandoc/man_validate.c | 474 ++--- usr/src/cmd/mandoc/mandoc.c | 277 ++- usr/src/cmd/mandoc/mandoc.h | 360 ++-- usr/src/cmd/mandoc/mandoc_aux.c | 119 ++ usr/src/cmd/mandoc/mandoc_aux.h | 29 + usr/src/cmd/mandoc/manpath.c | 237 +++ usr/src/cmd/mandoc/manpath.h | 34 + usr/src/cmd/mandoc/mansearch.h | 111 ++ usr/src/cmd/mandoc/mdoc.c | 539 ++--- usr/src/cmd/mandoc/mdoc.h | 40 +- usr/src/cmd/mandoc/mdoc_argv.c | 286 ++- usr/src/cmd/mandoc/mdoc_hash.c | 8 +- usr/src/cmd/mandoc/mdoc_html.c | 895 +++++---- usr/src/cmd/mandoc/mdoc_macro.c | 1858 +++++++----------- usr/src/cmd/mandoc/mdoc_man.c | 536 +++-- usr/src/cmd/mandoc/mdoc_term.c | 1148 ++++++----- usr/src/cmd/mandoc/mdoc_validate.c | 2696 ++++++++++++-------------- usr/src/cmd/mandoc/msec.c | 7 +- usr/src/cmd/mandoc/out.c | 237 ++- usr/src/cmd/mandoc/out.h | 20 +- usr/src/cmd/mandoc/preconv.c | 523 +---- usr/src/cmd/mandoc/read.c | 784 +++++--- usr/src/cmd/mandoc/roff.c | 2322 +++++++++++++++------- usr/src/cmd/mandoc/st.c | 9 +- usr/src/cmd/mandoc/st.in | 97 +- usr/src/cmd/mandoc/tbl.c | 130 +- usr/src/cmd/mandoc/tbl_data.c | 172 +- usr/src/cmd/mandoc/tbl_html.c | 41 +- usr/src/cmd/mandoc/tbl_layout.c | 472 +++-- usr/src/cmd/mandoc/tbl_opts.c | 308 +-- usr/src/cmd/mandoc/tbl_term.c | 354 ++-- usr/src/cmd/mandoc/term.c | 484 ++--- usr/src/cmd/mandoc/term.h | 53 +- usr/src/cmd/mandoc/term_ascii.c | 260 ++- usr/src/cmd/mandoc/term_ps.c | 506 +++-- usr/src/cmd/mandoc/tree.c | 206 +- usr/src/cmd/mandoc/vol.c | 39 - usr/src/cmd/mandoc/vol.in | 35 - usr/src/man/man1/mandoc.1 | 1434 ++++++++++++-- usr/src/man/man5/eqn.5 | 339 +++- usr/src/man/man5/mandoc_char.5 | 113 +- usr/src/man/man5/mandoc_roff.5 | 1469 ++++++++++++-- usr/src/man/man5/mdoc.5 | 870 +++++---- usr/src/man/man5/tbl.5 | 206 +- usr/src/pkg/manifests/system-man.mf | 6 +- usr/src/tools/mandoc/Makefile | 36 +- 82 files changed, 15762 insertions(+), 11404 deletions(-) delete mode 100644 usr/src/cmd/mandoc/arch.c delete mode 100644 usr/src/cmd/mandoc/arch.in delete mode 100644 usr/src/cmd/mandoc/att.in create mode 100644 usr/src/cmd/mandoc/compat_fgetln.c create mode 100644 usr/src/cmd/mandoc/compat_reallocarray.c create mode 100644 usr/src/cmd/mandoc/compat_strtonum.c create mode 100644 usr/src/cmd/mandoc/mandoc_aux.c create mode 100644 usr/src/cmd/mandoc/mandoc_aux.h create mode 100644 usr/src/cmd/mandoc/manpath.c create mode 100644 usr/src/cmd/mandoc/manpath.h create mode 100644 usr/src/cmd/mandoc/mansearch.h delete mode 100644 usr/src/cmd/mandoc/vol.c delete mode 100644 usr/src/cmd/mandoc/vol.in (limited to 'usr/src') diff --git a/exception_lists/cstyle b/exception_lists/cstyle index 2845dce902..01c01b6c53 100644 --- a/exception_lists/cstyle +++ b/exception_lists/cstyle @@ -61,9 +61,10 @@ usr/src/cmd/krb5/ldap_util/kdb5_ldap_util.h usr/src/cmd/krb5/slave/kprop.c usr/src/cmd/krb5/slave/kprop.h usr/src/cmd/krb5/slave/kpropd.c -usr/src/cmd/mandoc/arch.c usr/src/cmd/mandoc/att.c usr/src/cmd/mandoc/chars.c +usr/src/cmd/mandoc/compat_fgetln.c +usr/src/cmd/mandoc/compat_reallocarray.c usr/src/cmd/mandoc/config.h usr/src/cmd/mandoc/eqn.c usr/src/cmd/mandoc/eqn_html.c @@ -86,6 +87,8 @@ usr/src/cmd/mandoc/man_term.c usr/src/cmd/mandoc/man_validate.c usr/src/cmd/mandoc/mandoc.c usr/src/cmd/mandoc/mandoc.h +usr/src/cmd/mandoc/mandoc_aux.c +usr/src/cmd/mandoc/manpath.c usr/src/cmd/mandoc/mdoc.c usr/src/cmd/mandoc/mdoc.h usr/src/cmd/mandoc/mdoc_argv.c @@ -113,7 +116,6 @@ usr/src/cmd/mandoc/term.h usr/src/cmd/mandoc/term_ascii.c usr/src/cmd/mandoc/term_ps.c usr/src/cmd/mandoc/tree.c -usr/src/cmd/mandoc/vol.c usr/src/common/bzip2/bzlib.h usr/src/common/bzip2/crctable.c usr/src/common/bzip2/randtable.c diff --git a/exception_lists/hdrchk b/exception_lists/hdrchk index 2d4c49f38c..38679e1432 100644 --- a/exception_lists/hdrchk +++ b/exception_lists/hdrchk @@ -26,6 +26,9 @@ usr/src/cmd/mandoc/libroff.h usr/src/cmd/mandoc/main.h usr/src/cmd/mandoc/man.h usr/src/cmd/mandoc/mandoc.h +usr/src/cmd/mandoc/mandoc_aux.h +usr/src/cmd/mandoc/manpath.h +usr/src/cmd/mandoc/mansearch.h usr/src/cmd/mandoc/mdoc.h usr/src/cmd/mandoc/out.h usr/src/cmd/mandoc/term.h diff --git a/usr/src/cmd/man/man.c b/usr/src/cmd/man/man.c index 8038cabbac..d181c6d374 100644 --- a/usr/src/cmd/man/man.c +++ b/usr/src/cmd/man/man.c @@ -22,8 +22,8 @@ /* * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012, Josef 'Jeff' Sipek . All rights reserved. - * Copyright 2013 Nexenta Systems, Inc. All rights reserved. * Copyright 2014 Garrett D'Amore + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T. */ @@ -1193,7 +1193,6 @@ format(char *path, char *dir, char *name, char *pg) char manpname[MAXPATHLEN], catpname[MAXPATHLEN]; char cmdbuf[BUFSIZ], tmpbuf[BUFSIZ]; char *cattool; - int utf8 = 0; struct stat sbman, sbcat; found++; @@ -1227,22 +1226,16 @@ format(char *path, char *dir, char *name, char *pg) else cattool = "cat"; - /* Preprocess UTF-8 input with preconv (could be smarter) */ - if (strstr(path, "UTF-8") != NULL) - utf8 = 1; - if (psoutput) { (void) snprintf(cmdbuf, BUFSIZ, - "cd %s; %s %s%s | mandoc -Tps | lp -Tpostscript", - path, cattool, manpname, - utf8 ? " | " PRECONV " -e UTF-8" : ""); + "cd %s; %s %s | mandoc -Tps | lp -Tpostscript", + path, cattool, manpname); DPRINTF("-- Using manpage: %s\n", manpname); goto cmd; } else if (lintout) { (void) snprintf(cmdbuf, BUFSIZ, - "cd %s; %s %s%s | mandoc -Tlint", - path, cattool, manpname, - utf8 ? " | " PRECONV " -e UTF-8" : ""); + "cd %s; %s %s | mandoc -Tlint", + path, cattool, manpname); DPRINTF("-- Linting manpage: %s\n", manpname); goto cmd; } @@ -1262,10 +1255,8 @@ format(char *path, char *dir, char *name, char *pg) DPRINTF("-- Using manpage: %s\n", manpname); if (manwidth > 0) (void) snprintf(tmpbuf, BUFSIZ, "-Owidth=%d ", manwidth); - (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s%s | mandoc -T%s %s| %s", - path, cattool, manpname, - utf8 ? " | " PRECONV " -e UTF-8 " : "", - utf8 ? "utf8" : "ascii", (manwidth > 0) ? tmpbuf : "", pager); + (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s | mandoc %s| %s", + path, cattool, manpname, (manwidth > 0) ? tmpbuf : "", pager); cmd: DPRINTF("-- Command: %s\n", cmdbuf); diff --git a/usr/src/cmd/man/man.h b/usr/src/cmd/man/man.h index e6803a7969..b4ec3f380a 100644 --- a/usr/src/cmd/man/man.h +++ b/usr/src/cmd/man/man.h @@ -10,8 +10,8 @@ */ /* - * Copyright 2012 Nexenta Systems, Inc. All rights reserved. * Copyright 2014 Garrett D'Amore + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* @@ -26,7 +26,6 @@ #define INDENT 24 #define PAGER "less -ins" #define WHATIS "whatis" -#define PRECONV "/usr/lib/mandoc_preconv" #define LINE_ALLOC 4096 #define MAXDIRS 128 diff --git a/usr/src/cmd/mandoc/Makefile b/usr/src/cmd/mandoc/Makefile index 08cea9764a..4353cb74d7 100644 --- a/usr/src/cmd/mandoc/Makefile +++ b/usr/src/cmd/mandoc/Makefile @@ -10,40 +10,24 @@ # # -# Copyright 2014 Nexenta Systems, Inc. All rights reserved. # Copyright 2014 Garrett D'Amore +# Copyright 2015 Nexenta Systems, Inc. All rights reserved. # -PROGS= mandoc mandoc_preconv - -# We place preconv in /usr/lib. This is done to avoid conflicting with -# GNU groff, which puts it into /usr/bin. We also rename it so that it -# will only be seen by mandoc -- it isn't intended for general end-user use. - -ROOTPROGS = $(ROOTBIN)/mandoc $(ROOTLIB)/mandoc_preconv - -OBJS= $(preconv_OBJS) $(mandoc_OBJS) - -CLOBBERFILES += $(PROGS) - include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/mandoc/Makefile.common .KEEP_STATE: -all: $(PROGS) - -mandoc_preconv: $(preconv_OBJS) - $(LINK.c) $(preconv_OBJS) -o $@ $(LDLIBS) - $(POST_PROCESS) +all: $(PROG) -mandoc: $(mandoc_OBJS) - $(LINK.c) $(mandoc_OBJS) -o $@ $(LDLIBS) +$(PROG): $(OBJS) + $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) -install: all $(ROOTPROGS) +install: all $(ROOTPROG) include $(SRC)/cmd/Makefile.targ diff --git a/usr/src/cmd/mandoc/Makefile.common b/usr/src/cmd/mandoc/Makefile.common index b1a67b3805..d4e95ffbfd 100644 --- a/usr/src/cmd/mandoc/Makefile.common +++ b/usr/src/cmd/mandoc/Makefile.common @@ -10,23 +10,58 @@ # # -# Copyright 2012 Nexenta Systems, Inc. All rights reserved. # Copyright 2014 Garrett D'Amore +# Copyright 2015 Nexenta Systems, Inc. All rights reserved. # -PROGS= mandoc mandoc_preconv -mandoc_OBJS = arch.o att.o chars.o eqn.o eqn_html.o eqn_term.o \ - html.o lib.o main.o man.o man_hash.o man_html.o \ - man_macro.o man_term.o man_validate.o mandoc.o mdoc.o \ - mdoc_argv.o mdoc_hash.o mdoc_html.o mdoc_macro.o \ - mdoc_man.o mdoc_term.o mdoc_validate.o msec.o out.o \ - read.o roff.o st.o tbl.o tbl_data.o tbl_html.o \ - tbl_layout.o tbl_opts.o tbl_term.o term.o term_ascii.o \ - term_ps.o tree.o vol.o +PROG= mandoc -preconv_OBJS = preconv.o +OBJS= att.o \ + chars.o \ + eqn.o \ + eqn_html.o \ + eqn_term.o \ + html.o \ + lib.o \ + main.o \ + man.o \ + manpath.o \ + man_hash.o \ + man_html.o \ + man_macro.o \ + man_term.o \ + man_validate.o \ + mandoc.o \ + mandoc_aux.o \ + mdoc.o \ + mdoc_argv.o \ + mdoc_hash.o \ + mdoc_html.o \ + mdoc_macro.o \ + mdoc_man.o \ + mdoc_term.o \ + mdoc_validate.o \ + msec.o \ + out.o \ + read.o \ + roff.o \ + preconv.o \ + st.o \ + tbl.o \ + tbl_data.o \ + tbl_html.o \ + tbl_layout.o \ + tbl_opts.o \ + tbl_term.o \ + term.o \ + term_ascii.o \ + term_ps.o \ + tree.o + +OBJS += compat_fgetln.o \ + compat_reallocarray.o \ + compat_strtonum.o CFLAGS += $(CC_VERBOSE) -CPPFLAGS += -DHAVE_CONFIG_H -DUSE_WCHAR \ - -DOSNAME="\"illumos\"" +CPPFLAGS += -DOSNAME="\"illumos\"" diff --git a/usr/src/cmd/mandoc/THIRDPARTYLICENSE b/usr/src/cmd/mandoc/THIRDPARTYLICENSE index 0fae1fd4b2..080c04fa26 100644 --- a/usr/src/cmd/mandoc/THIRDPARTYLICENSE +++ b/usr/src/cmd/mandoc/THIRDPARTYLICENSE @@ -1,14 +1,47 @@ -Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons -Copyright (c) 2011 Ingo Schwarze +$Id: LICENSE,v 1.7 2015/02/16 14:56:22 schwarze Exp $ + +With the exceptions noted below, all code and documentation +contained in the mdocml toolkit is protected by the Copyright +of the following developers: + +Copyright (c) 2008-2012, 2014 Kristaps Dzonsons +Copyright (c) 2010-2015 Ingo Schwarze +Copyright (c) 2009, 2010, 2011, 2012 Joerg Sonnenberger +Copyright (c) 2013 Franco Fichtner +Copyright (c) 1999, 2004 Marc Espie +Copyright (c) 1998, 2004, 2010 Todd C. Miller +Copyright (c) 2008 Otto Moerbeek +Copyright (c) 2004 Ted Unangst +Copyright (c) 2003, 2007, 2008, 2014 Jason McIntyre + +See the individual source files for information about who contributed +to which file during which years. + + +The mdocml distribution as a whole is distributed by its developers +under the following license: Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +The following files included from outside sources are protected by +other people's Copyright and are distributed under a 3-clause BSD +license; see these individual files for details. + +compat_fts.c, compat_fts.h, +compat_getsubopt.c, compat_strcasestr.c, compat_strsep.c, +man.1: +Copyright (c) 1989,1990,1993,1994 The Regents of the University of California + +compat_fgetln.c: +Copyright (c) 1998 The NetBSD Foundation, Inc. diff --git a/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip b/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip index ecb8c678a0..b89cd5be30 100644 --- a/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip +++ b/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip @@ -1 +1 @@ -MANDOC - FORMAT AND DISPLAY UNIX MANUALS +MDOCML - The mandoc UNIX manpage compiler toolset diff --git a/usr/src/cmd/mandoc/arch.c b/usr/src/cmd/mandoc/arch.c deleted file mode 100644 index e764bfe993..0000000000 --- a/usr/src/cmd/mandoc/arch.c +++ /dev/null @@ -1,39 +0,0 @@ -/* $Id: arch.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ -/* - * Copyright (c) 2009 Kristaps Dzonsons - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include - -#include "mdoc.h" -#include "mandoc.h" -#include "libmdoc.h" - -#define LINE(x, y) \ - if (0 == strcmp(p, x)) return(y); - -const char * -mdoc_a2arch(const char *p) -{ - -#include "arch.in" - - return(NULL); -} diff --git a/usr/src/cmd/mandoc/arch.in b/usr/src/cmd/mandoc/arch.in deleted file mode 100644 index d0c445f308..0000000000 --- a/usr/src/cmd/mandoc/arch.in +++ /dev/null @@ -1,112 +0,0 @@ -/* $Id: arch.in,v 1.14 2013/09/16 22:12:57 schwarze Exp $ */ -/* - * Copyright (c) 2009 Kristaps Dzonsons - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* - * This file defines the architecture token of the .Dt prologue macro. - * All architectures that your system supports (or the manuals of your - * system) should be included here. The right-hand-side is the - * formatted output. - * - * Be sure to escape strings. - * - * REMEMBER TO ADD NEW ARCHITECTURES TO MDOC.7! - */ - -LINE("acorn26", "Acorn26") -LINE("acorn32", "Acorn32") -LINE("algor", "Algor") -LINE("alpha", "Alpha") -LINE("amd64", "AMD64") -LINE("amiga", "Amiga") -LINE("amigappc", "AmigaPPC") -LINE("arc", "ARC") -LINE("arm", "ARM") -LINE("arm26", "ARM26") -LINE("arm32", "ARM32") -LINE("armish", "ARMISH") -LINE("armv7", "ARMv7") -LINE("aviion", "AViiON") -LINE("atari", "ATARI") -LINE("bebox", "BeBox") -LINE("cats", "cats") -LINE("cesfic", "CESFIC") -LINE("cobalt", "Cobalt") -LINE("dreamcast", "Dreamcast") -LINE("emips", "EMIPS") -LINE("evbarm", "evbARM") -LINE("evbmips", "evbMIPS") -LINE("evbppc", "evbPPC") -LINE("evbsh3", "evbSH3") -LINE("ews4800mips", "EWS4800MIPS") -LINE("hp300", "HP300") -LINE("hp700", "HP700") -LINE("hpcarm", "HPCARM") -LINE("hpcmips", "HPCMIPS") -LINE("hpcsh", "HPCSH") -LINE("hppa", "HPPA") -LINE("hppa64", "HPPA64") -LINE("ia64", "ia64") -LINE("i386", "i386") -LINE("ibmnws", "IBMNWS") -LINE("iyonix", "Iyonix") -LINE("landisk", "LANDISK") -LINE("loongson", "Loongson") -LINE("luna68k", "Luna68k") -LINE("luna88k", "Luna88k") -LINE("m68k", "m68k") -LINE("mac68k", "Mac68k") -LINE("macppc", "MacPPC") -LINE("mips", "MIPS") -LINE("mips64", "MIPS64") -LINE("mipsco", "MIPSCo") -LINE("mmeye", "mmEye") -LINE("mvme68k", "MVME68k") -LINE("mvme88k", "MVME88k") -LINE("mvmeppc", "MVMEPPC") -LINE("netwinder", "NetWinder") -LINE("news68k", "NeWS68k") -LINE("newsmips", "NeWSMIPS") -LINE("next68k", "NeXT68k") -LINE("octeon", "OCTEON") -LINE("ofppc", "OFPPC") -LINE("palm", "Palm") -LINE("pc532", "PC532") -LINE("playstation2", "PlayStation2") -LINE("pmax", "PMAX") -LINE("pmppc", "pmPPC") -LINE("powerpc", "PowerPC") -LINE("prep", "PReP") -LINE("rs6000", "RS6000") -LINE("sandpoint", "Sandpoint") -LINE("sbmips", "SBMIPS") -LINE("sgi", "SGI") -LINE("sgimips", "SGIMIPS") -LINE("sh3", "SH3") -LINE("shark", "Shark") -LINE("socppc", "SOCPPC") -LINE("solbourne", "Solbourne") -LINE("sparc", "SPARC") -LINE("sparc64", "SPARC64") -LINE("sun2", "Sun2") -LINE("sun3", "Sun3") -LINE("tahoe", "Tahoe") -LINE("vax", "VAX") -LINE("x68k", "X68k") -LINE("x86", "x86") -LINE("x86_64", "x86_64") -LINE("xen", "Xen") -LINE("zaurus", "Zaurus") diff --git a/usr/src/cmd/mandoc/att.c b/usr/src/cmd/mandoc/att.c index 24d757ddf7..a1703ebcc6 100644 --- a/usr/src/cmd/mandoc/att.c +++ b/usr/src/cmd/mandoc/att.c @@ -1,4 +1,4 @@ -/* $Id: att.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ +/* $Id: att.c,v 1.13 2014/11/28 18:57:31 schwarze Exp $ */ /* * Copyright (c) 2009 Kristaps Dzonsons * @@ -14,26 +14,36 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef HAVE_CONFIG_H #include "config.h" -#endif -#include +#include #include -#include #include "mdoc.h" -#include "mandoc.h" #include "libmdoc.h" #define LINE(x, y) \ - if (0 == strcmp(p, x)) return(y); + if (0 == strcmp(p, x)) return(y) + const char * mdoc_a2att(const char *p) { -#include "att.in" + LINE("v1", "Version\\~1 AT&T UNIX"); + LINE("v2", "Version\\~2 AT&T UNIX"); + LINE("v3", "Version\\~3 AT&T UNIX"); + LINE("v4", "Version\\~4 AT&T UNIX"); + LINE("v5", "Version\\~5 AT&T UNIX"); + LINE("v6", "Version\\~6 AT&T UNIX"); + LINE("v7", "Version\\~7 AT&T UNIX"); + LINE("32v", "Version\\~32V AT&T UNIX"); + LINE("III", "AT&T System\\~III UNIX"); + LINE("V", "AT&T System\\~V UNIX"); + LINE("V.1", "AT&T System\\~V Release\\~1 UNIX"); + LINE("V.2", "AT&T System\\~V Release\\~2 UNIX"); + LINE("V.3", "AT&T System\\~V Release\\~3 UNIX"); + LINE("V.4", "AT&T System\\~V Release\\~4 UNIX"); return(NULL); } diff --git a/usr/src/cmd/mandoc/att.in b/usr/src/cmd/mandoc/att.in deleted file mode 100644 index b4ef822158..0000000000 --- a/usr/src/cmd/mandoc/att.in +++ /dev/null @@ -1,40 +0,0 @@ -/* $Id: att.in,v 1.8 2011/07/31 17:30:33 schwarze Exp $ */ -/* - * Copyright (c) 2009 Kristaps Dzonsons - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* - * This file defines the AT&T versions of the .At macro. This probably - * isn't going to change. The right-hand side is the formatted string. - * - * Be sure to escape strings. - * The non-breaking blanks prevent ending an output line right before - * a number. Groff prevent line breaks at the same places. - */ - -LINE("v1", "Version\\~1 AT&T UNIX") -LINE("v2", "Version\\~2 AT&T UNIX") -LINE("v3", "Version\\~3 AT&T UNIX") -LINE("v4", "Version\\~4 AT&T UNIX") -LINE("v5", "Version\\~5 AT&T UNIX") -LINE("v6", "Version\\~6 AT&T UNIX") -LINE("v7", "Version\\~7 AT&T UNIX") -LINE("32v", "Version\\~32V AT&T UNIX") -LINE("III", "AT&T System\\~III UNIX") -LINE("V", "AT&T System\\~V UNIX") -LINE("V.1", "AT&T System\\~V Release\\~1 UNIX") -LINE("V.2", "AT&T System\\~V Release\\~2 UNIX") -LINE("V.3", "AT&T System\\~V Release\\~3 UNIX") -LINE("V.4", "AT&T System\\~V Release\\~4 UNIX") diff --git a/usr/src/cmd/mandoc/chars.c b/usr/src/cmd/mandoc/chars.c index 3ad1f57471..6b5eba9537 100644 --- a/usr/src/cmd/mandoc/chars.c +++ b/usr/src/cmd/mandoc/chars.c @@ -1,7 +1,7 @@ -/* $Id: chars.c,v 1.54 2013/06/20 22:39:30 schwarze Exp $ */ +/* $Id: chars.c,v 1.66 2015/02/17 20:37:16 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2011 Ingo Schwarze + * Copyright (c) 2011, 2014 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,9 +15,9 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef HAVE_CONFIG_H #include "config.h" -#endif + +#include #include #include @@ -25,6 +25,7 @@ #include #include "mandoc.h" +#include "mandoc_aux.h" #include "libmandoc.h" #define PRINT_HI 126 @@ -37,7 +38,7 @@ struct ln { int unicode; }; -#define LINES_MAX 329 +#define LINES_MAX 332 #define CHAR(in, ch, code) \ { NULL, (in), (ch), (code) }, @@ -51,9 +52,10 @@ struct mchars { struct ln **htab; }; -static const struct ln *find(const struct mchars *, +static const struct ln *find(const struct mchars *, const char *, size_t); + void mchars_free(struct mchars *arg) { @@ -102,49 +104,55 @@ mchars_spec2cp(const struct mchars *arg, const char *p, size_t sz) const struct ln *ln; ln = find(arg, p, sz); - if (NULL == ln) - return(-1); - return(ln->unicode); + return(ln != NULL ? ln->unicode : sz == 1 ? (unsigned char)*p : -1); } -char +int mchars_num2char(const char *p, size_t sz) { - int i; + int i; - if ((i = mandoc_strntoi(p, sz, 10)) < 0) - return('\0'); - return(i > 0 && i < 256 && isprint(i) ? - /* LINTED */ i : '\0'); + i = mandoc_strntoi(p, sz, 10); + return(i >= 0 && i < 256 ? i : -1); } int mchars_num2uc(const char *p, size_t sz) { - int i; + int i; - if ((i = mandoc_strntoi(p, sz, 16)) < 0) - return('\0'); - /* FIXME: make sure we're not in a bogus range. */ - return(i > 0x80 && i <= 0x10FFFF ? i : '\0'); + i = mandoc_strntoi(p, sz, 16); + assert(i >= 0 && i <= 0x10FFFF); + return(i); } const char * -mchars_spec2str(const struct mchars *arg, +mchars_spec2str(const struct mchars *arg, const char *p, size_t sz, size_t *rsz) { const struct ln *ln; ln = find(arg, p, sz); - if (NULL == ln) { + if (ln == NULL) { *rsz = 1; - return(NULL); + return(sz == 1 ? p : NULL); } *rsz = strlen(ln->ascii); return(ln->ascii); } +const char * +mchars_uc2str(int uc) +{ + int i; + + for (i = 0; i < LINES_MAX; i++) + if (uc == lines[i].unicode) + return(lines[i].ascii); + return(""); +} + static const struct ln * find(const struct mchars *tab, const char *p, size_t sz) { @@ -159,8 +167,8 @@ find(const struct mchars *tab, const char *p, size_t sz) hash = (int)p[0] - PRINT_LO; for (pp = tab->htab[hash]; pp; pp = pp->next) - if (0 == strncmp(pp->code, p, sz) && - '\0' == pp->code[(int)sz]) + if (0 == strncmp(pp->code, p, sz) && + '\0' == pp->code[(int)sz]) return(pp); return(NULL); diff --git a/usr/src/cmd/mandoc/chars.in b/usr/src/cmd/mandoc/chars.in index cc6549e7e5..ac72aba858 100644 --- a/usr/src/cmd/mandoc/chars.in +++ b/usr/src/cmd/mandoc/chars.in @@ -1,6 +1,7 @@ -/* $Id: chars.in,v 1.43 2013/06/20 22:39:30 schwarze Exp $ */ +/* $Id: chars.in,v 1.52 2015/02/17 20:37:16 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons + * Copyright (c) 2014 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,7 +17,7 @@ */ /* - * The ASCII translation tables. + * The ASCII translation tables. * * The left-hand side corresponds to the input sequence (\x, \(xx, \*(xx * and so on) whose length is listed second element. The right-hand @@ -27,47 +28,52 @@ * XXX - update LINES_MAX if adding more! */ -/* Non-breaking, non-collapsing space uses unit separator. */ +/* Special break control characters. */ static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' }; +static const char ascii_break[2] = { ASCII_BREAK, '\0' }; CHAR_TBL_START /* Spacing. */ -CHAR("c", "", 0) -CHAR("0", " ", 8194) CHAR(" ", ascii_nbrsp, 160) CHAR("~", ascii_nbrsp, 160) -CHAR("%", "", 0) -CHAR("&", "", 0) -CHAR("^", "", 0) +CHAR("0", " ", 8194) CHAR("|", "", 0) -CHAR("}", "", 0) +CHAR("^", "", 0) +CHAR("&", "", 0) +CHAR("%", "", 0) +CHAR(":", ascii_break, 0) +/* XXX The following three do not really belong into this file. */ CHAR("t", "", 0) +CHAR("c", "", 0) +CHAR("}", "", 0) /* Accents. */ -CHAR("a\"", "\"", 779) +CHAR("a\"", "\"", 733) CHAR("a-", "-", 175) CHAR("a.", ".", 729) -CHAR("a^", "^", 770) -CHAR("\'", "\'", 769) -CHAR("aa", "\'", 769) -CHAR("ga", "`", 768) -CHAR("`", "`", 768) -CHAR("ab", "`", 774) -CHAR("ac", ",", 807) -CHAR("ad", "\"", 776) +CHAR("a^", "^", 94) +CHAR("\'", "\'", 180) +CHAR("aa", "\'", 180) +CHAR("ga", "`", 96) +CHAR("`", "`", 96) +CHAR("ab", "'\b`", 728) +CHAR("ac", ",", 184) +CHAR("ad", "\"", 168) CHAR("ah", "v", 711) CHAR("ao", "o", 730) -CHAR("a~", "~", 771) -CHAR("ho", ",", 808) +CHAR("a~", "~", 126) +CHAR("ho", ",", 731) CHAR("ha", "^", 94) CHAR("ti", "~", 126) /* Quotes. */ CHAR("Bq", ",,", 8222) CHAR("bq", ",", 8218) -CHAR("lq", "``", 8220) -CHAR("rq", "\'\'", 8221) +CHAR("lq", "\"", 8220) +CHAR("rq", "\"", 8221) +CHAR("Lq", "``", 8220) +CHAR("Rq", "''", 8221) CHAR("oq", "`", 8216) CHAR("cq", "\'", 8217) CHAR("aq", "\'", 39) @@ -82,161 +88,161 @@ CHAR("lB", "[", 91) CHAR("rB", "]", 93) CHAR("lC", "{", 123) CHAR("rC", "}", 125) -CHAR("la", "<", 60) -CHAR("ra", ">", 62) +CHAR("la", "<", 10216) +CHAR("ra", ">", 10217) CHAR("bv", "|", 9130) CHAR("braceex", "|", 9130) CHAR("bracketlefttp", "|", 9121) -CHAR("bracketleftbp", "|", 9123) +CHAR("bracketleftbt", "|", 9123) CHAR("bracketleftex", "|", 9122) CHAR("bracketrighttp", "|", 9124) -CHAR("bracketrightbp", "|", 9126) +CHAR("bracketrightbt", "|", 9126) CHAR("bracketrightex", "|", 9125) CHAR("lt", ",-", 9127) CHAR("bracelefttp", ",-", 9127) CHAR("lk", "{", 9128) CHAR("braceleftmid", "{", 9128) -CHAR("lb", ",-", 9129) -CHAR("braceleftbp", "`-", 9129) +CHAR("lb", "`-", 9129) +CHAR("braceleftbt", "`-", 9129) CHAR("braceleftex", "|", 9130) CHAR("rt", "-.", 9131) CHAR("bracerighttp", "-.", 9131) CHAR("rk", "}", 9132) CHAR("bracerightmid", "}", 9132) CHAR("rb", "-\'", 9133) -CHAR("bracerightbp", "-\'", 9133) +CHAR("bracerightbt", "-\'", 9133) CHAR("bracerightex", "|", 9130) CHAR("parenlefttp", "/", 9115) -CHAR("parenleftbp", "\\", 9117) +CHAR("parenleftbt", "\\", 9117) CHAR("parenleftex", "|", 9116) CHAR("parenrighttp", "\\", 9118) -CHAR("parenrightbp", "/", 9120) +CHAR("parenrightbt", "/", 9120) CHAR("parenrightex", "|", 9119) /* Greek characters. */ CHAR("*A", "A", 913) CHAR("*B", "B", 914) -CHAR("*G", "|", 915) -CHAR("*D", "/\\", 916) +CHAR("*G", "G", 915) +CHAR("*D", "_\b/_\b\\", 916) CHAR("*E", "E", 917) CHAR("*Z", "Z", 918) CHAR("*Y", "H", 919) -CHAR("*H", "O", 920) +CHAR("*H", "-\bO", 920) CHAR("*I", "I", 921) CHAR("*K", "K", 922) CHAR("*L", "/\\", 923) CHAR("*M", "M", 924) CHAR("*N", "N", 925) -CHAR("*C", "H", 926) +CHAR("*C", "_\bH", 926) CHAR("*O", "O", 927) CHAR("*P", "TT", 928) CHAR("*R", "P", 929) -CHAR("*S", ">", 931) +CHAR("*S", "S", 931) CHAR("*T", "T", 932) CHAR("*U", "Y", 933) -CHAR("*F", "O_", 934) +CHAR("*F", "I\bO", 934) CHAR("*X", "X", 935) -CHAR("*Q", "Y", 936) -CHAR("*W", "O", 937) +CHAR("*Q", "I\bY", 936) +CHAR("*W", "_\bO", 937) CHAR("*a", "a", 945) CHAR("*b", "B", 946) CHAR("*g", "y", 947) CHAR("*d", "d", 948) CHAR("*e", "e", 949) -CHAR("*z", "C", 950) +CHAR("*z", ",\bC", 950) CHAR("*y", "n", 951) -CHAR("*h", "0", 952) +CHAR("*h", "-\b0", 952) CHAR("*i", "i", 953) CHAR("*k", "k", 954) -CHAR("*l", "\\", 955) -CHAR("*m", "u", 956) +CHAR("*l", ">\b\\", 955) +CHAR("*m", ",\bu", 956) CHAR("*n", "v", 957) -CHAR("*c", "E", 958) +CHAR("*c", ",\bE", 958) CHAR("*o", "o", 959) -CHAR("*p", "n", 960) +CHAR("*p", "-\bn", 960) CHAR("*r", "p", 961) -CHAR("*s", "o", 963) -CHAR("*t", "t", 964) +CHAR("*s", "-\bo", 963) +CHAR("*t", "~\bt", 964) CHAR("*u", "u", 965) -CHAR("*f", "o", 981) +CHAR("*f", "|\bo", 981) CHAR("*x", "x", 967) -CHAR("*q", "u", 968) +CHAR("*q", "|\bu", 968) CHAR("*w", "w", 969) -CHAR("+h", "0", 977) -CHAR("+f", "o", 966) -CHAR("+p", "w", 982) +CHAR("+h", "-\b0", 977) +CHAR("+f", "|\bo", 966) +CHAR("+p", "-\bw", 982) CHAR("+e", "e", 1013) CHAR("ts", "s", 962) /* Accented letters. */ -CHAR(",C", "C", 199) -CHAR(",c", "c", 231) -CHAR("/L", "L", 321) -CHAR("/O", "O", 216) -CHAR("/l", "l", 322) -CHAR("/o", "o", 248) -CHAR("oA", "A", 197) -CHAR("oa", "a", 229) -CHAR(":A", "A", 196) -CHAR(":E", "E", 203) -CHAR(":I", "I", 207) -CHAR(":O", "O", 214) -CHAR(":U", "U", 220) -CHAR(":a", "a", 228) -CHAR(":e", "e", 235) -CHAR(":i", "i", 239) -CHAR(":o", "o", 246) -CHAR(":u", "u", 252) -CHAR(":y", "y", 255) -CHAR("\'A", "A", 193) -CHAR("\'E", "E", 201) -CHAR("\'I", "I", 205) -CHAR("\'O", "O", 211) -CHAR("\'U", "U", 218) -CHAR("\'a", "a", 225) -CHAR("\'e", "e", 233) -CHAR("\'i", "i", 237) -CHAR("\'o", "o", 243) -CHAR("\'u", "u", 250) -CHAR("^A", "A", 194) -CHAR("^E", "E", 202) -CHAR("^I", "I", 206) -CHAR("^O", "O", 212) -CHAR("^U", "U", 219) -CHAR("^a", "a", 226) -CHAR("^e", "e", 234) -CHAR("^i", "i", 238) -CHAR("^o", "o", 244) -CHAR("^u", "u", 251) -CHAR("`A", "A", 192) -CHAR("`E", "E", 200) -CHAR("`I", "I", 204) -CHAR("`O", "O", 210) -CHAR("`U", "U", 217) -CHAR("`a", "a", 224) -CHAR("`e", "e", 232) -CHAR("`i", "i", 236) -CHAR("`o", "o", 242) -CHAR("`u", "u", 249) -CHAR("~A", "A", 195) -CHAR("~N", "N", 209) -CHAR("~O", "O", 213) -CHAR("~a", "a", 227) -CHAR("~n", "n", 241) -CHAR("~o", "o", 245) +CHAR(",C", ",\bC", 199) +CHAR(",c", ",\bc", 231) +CHAR("/L", "/\bL", 321) +CHAR("/O", "/\bO", 216) +CHAR("/l", "/\bl", 322) +CHAR("/o", "/\bo", 248) +CHAR("oA", "o\bA", 197) +CHAR("oa", "o\ba", 229) +CHAR(":A", "\"\bA", 196) +CHAR(":E", "\"\bE", 203) +CHAR(":I", "\"\bI", 207) +CHAR(":O", "\"\bO", 214) +CHAR(":U", "\"\bU", 220) +CHAR(":a", "\"\ba", 228) +CHAR(":e", "\"\be", 235) +CHAR(":i", "\"\bi", 239) +CHAR(":o", "\"\bo", 246) +CHAR(":u", "\"\bu", 252) +CHAR(":y", "\"\by", 255) +CHAR("'A", "'\bA", 193) +CHAR("'E", "'\bE", 201) +CHAR("'I", "'\bI", 205) +CHAR("'O", "'\bO", 211) +CHAR("'U", "'\bU", 218) +CHAR("'a", "'\ba", 225) +CHAR("'e", "'\be", 233) +CHAR("'i", "'\bi", 237) +CHAR("'o", "'\bo", 243) +CHAR("'u", "'\bu", 250) +CHAR("^A", "^\bA", 194) +CHAR("^E", "^\bE", 202) +CHAR("^I", "^\bI", 206) +CHAR("^O", "^\bO", 212) +CHAR("^U", "^\bU", 219) +CHAR("^a", "^\ba", 226) +CHAR("^e", "^\be", 234) +CHAR("^i", "^\bi", 238) +CHAR("^o", "^\bo", 244) +CHAR("^u", "^\bu", 251) +CHAR("`A", "`\bA", 192) +CHAR("`E", "`\bE", 200) +CHAR("`I", "`\bI", 204) +CHAR("`O", "`\bO", 210) +CHAR("`U", "`\bU", 217) +CHAR("`a", "`\ba", 224) +CHAR("`e", "`\be", 232) +CHAR("`i", "`\bi", 236) +CHAR("`o", "`\bo", 242) +CHAR("`u", "`\bu", 249) +CHAR("~A", "~\bA", 195) +CHAR("~N", "~\bN", 209) +CHAR("~O", "~\bO", 213) +CHAR("~a", "~\ba", 227) +CHAR("~n", "~\bn", 241) +CHAR("~o", "~\bo", 245) /* Arrows and lines. */ CHAR("<-", "<-", 8592) CHAR("->", "->", 8594) -CHAR("<>", "<>", 8596) -CHAR("da", "v", 8595) -CHAR("ua", "^", 8593) +CHAR("<>", "<->", 8596) +CHAR("da", "|\bv", 8595) +CHAR("ua", "|\b^", 8593) CHAR("va", "^v", 8597) CHAR("lA", "<=", 8656) CHAR("rA", "=>", 8658) CHAR("hA", "<=>", 8660) -CHAR("dA", "v", 8659) -CHAR("uA", "^", 8657) +CHAR("dA", "=\bv", 8659) +CHAR("uA", "=\b^", 8657) CHAR("vA", "^=v", 8661) /* Logic. */ @@ -245,7 +251,7 @@ CHAR("OR", "v", 8744) CHAR("no", "~", 172) CHAR("tno", "~", 172) CHAR("te", "3", 8707) -CHAR("fa", "V", 8704) +CHAR("fa", "-\bV", 8704) CHAR("st", "-)", 8715) CHAR("tf", ".:.", 8756) CHAR("3d", ".:.", 8756) @@ -262,8 +268,8 @@ CHAR("pc", ".", 183) CHAR("md", ".", 8901) CHAR("mu", "x", 215) CHAR("tmu", "x", 215) -CHAR("c*", "x", 8855) -CHAR("c+", "+", 8853) +CHAR("c*", "O\bx", 8855) +CHAR("c+", "O\b+", 8853) CHAR("di", "-:-", 247) CHAR("tdi", "-:-", 247) CHAR("f/", "/", 8260) @@ -277,10 +283,10 @@ CHAR("!=", "!=", 8800) CHAR("==", "==", 8801) CHAR("ne", "!==", 8802) CHAR("=~", "=~", 8773) -CHAR("-~", "-~", 8771) +CHAR("|=", "-~", 8771) CHAR("ap", "~", 8764) CHAR("~~", "~~", 8776) -CHAR("~=", "~=", 8780) +CHAR("~=", "~=", 8776) CHAR("pt", "oc", 8733) CHAR("es", "{}", 8709) CHAR("mo", "E", 8712) @@ -289,14 +295,14 @@ CHAR("sb", "(=", 8834) CHAR("nb", "(!=", 8836) CHAR("sp", "=)", 8835) CHAR("nc", "!=)", 8837) -CHAR("ib", "(=", 8838) -CHAR("ip", "=)", 8839) +CHAR("ib", "(=\b_", 8838) +CHAR("ip", "=\b_)", 8839) CHAR("ca", "(^)", 8745) CHAR("cu", "U", 8746) -CHAR("/_", "/_", 8736) -CHAR("pp", "_|_", 8869) -CHAR("is", "I", 8747) -CHAR("integral", "I", 8747) +CHAR("/_", "_\b/", 8736) +CHAR("pp", "_\b|", 8869) +CHAR("is", "'\b,\bI", 8747) +CHAR("integral", "'\b,\bI", 8747) CHAR("sum", "E", 8721) CHAR("product", "TT", 8719) CHAR("coproduct", "U", 8720) @@ -332,41 +338,41 @@ CHAR("IJ", "IJ", 306) CHAR("ij", "ij", 307) /* Special letters. */ -CHAR("-D", "D", 208) -CHAR("Sd", "o", 240) -CHAR("TP", "b", 222) -CHAR("Tp", "b", 254) +CHAR("-D", "-\bD", 208) +CHAR("Sd", "d", 240) +CHAR("TP", "Th", 222) +CHAR("Tp", "th", 254) CHAR(".i", "i", 305) CHAR(".j", "j", 567) /* Currency. */ CHAR("Do", "$", 36) -CHAR("ct", "c", 162) +CHAR("ct", "/\bc", 162) CHAR("Eu", "EUR", 8364) CHAR("eu", "EUR", 8364) -CHAR("Ye", "Y", 165) -CHAR("Po", "L", 163) -CHAR("Cs", "x", 164) -CHAR("Fn", "f", 402) +CHAR("Ye", "=\bY", 165) +CHAR("Po", "GBP", 163) +CHAR("Cs", "o\bx", 164) +CHAR("Fn", ",\bf", 402) /* Lines. */ CHAR("ba", "|", 124) CHAR("br", "|", 9474) CHAR("ul", "_", 95) -CHAR("rl", "-", 8254) +CHAR("rn", "-", 8254) CHAR("bb", "|", 166) CHAR("sl", "/", 47) CHAR("rs", "\\", 92) /* Text markers. */ -CHAR("ci", "o", 9675) -CHAR("bu", "o", 8226) -CHAR("dd", "=", 8225) -CHAR("dg", "-", 8224) +CHAR("ci", "O", 9675) +CHAR("bu", "+\bo", 8226) +CHAR("dd", "|\b=", 8225) +CHAR("dg", "|\b-", 8224) CHAR("lz", "<>", 9674) CHAR("sq", "[]", 9633) -CHAR("ps", "9|", 182) -CHAR("sc", "S", 167) +CHAR("ps", "", 182) +CHAR("sc", "", 167) CHAR("lh", "<=", 9756) CHAR("rh", "=>", 9758) CHAR("at", "@", 64) @@ -381,18 +387,18 @@ CHAR("tm", "tm", 8482) /* Punctuation. */ CHAR(".", ".", 46) -CHAR("r!", "i", 161) -CHAR("r?", "c", 191) +CHAR("r!", "!", 161) +CHAR("r?", "?", 191) CHAR("em", "--", 8212) CHAR("en", "-", 8211) CHAR("hy", "-", 8208) CHAR("e", "\\", 92) /* Units. */ -CHAR("de", "o", 176) +CHAR("de", "", 176) CHAR("%0", "%o", 8240) CHAR("fm", "\'", 8242) -CHAR("sd", "\"", 8243) -CHAR("mc", "mu", 181) +CHAR("sd", "''", 8243) +CHAR("mc", ",\bu", 181) CHAR_TBL_END diff --git a/usr/src/cmd/mandoc/compat_fgetln.c b/usr/src/cmd/mandoc/compat_fgetln.c new file mode 100644 index 0000000000..3760ab994d --- /dev/null +++ b/usr/src/cmd/mandoc/compat_fgetln.c @@ -0,0 +1,94 @@ +#include "config.h" + +#if HAVE_FGETLN + +int dummy; + +#else + +/* $NetBSD: fgetln.c,v 1.3 2006/09/25 07:18:17 lukem Exp $ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include + +char * +fgetln(fp, len) + FILE *fp; + size_t *len; +{ + static char *buf = NULL; + static size_t bufsiz = 0; + char *ptr; + + + if (buf == NULL) { + bufsiz = BUFSIZ; + if ((buf = malloc(bufsiz)) == NULL) + return NULL; + } + + if (fgets(buf, bufsiz, fp) == NULL) + return NULL; + + *len = 0; + while ((ptr = strchr(&buf[*len], '\n')) == NULL) { + size_t nbufsiz = bufsiz + BUFSIZ; + char *nbuf = realloc(buf, nbufsiz); + + if (nbuf == NULL) { + int oerrno = errno; + free(buf); + errno = oerrno; + buf = NULL; + return NULL; + } else + buf = nbuf; + + *len = bufsiz; + if (fgets(&buf[bufsiz], BUFSIZ, fp) == NULL) + return buf; + + bufsiz = nbufsiz; + } + + *len = (ptr - buf) + 1; + return buf; +} + +#endif diff --git a/usr/src/cmd/mandoc/compat_reallocarray.c b/usr/src/cmd/mandoc/compat_reallocarray.c new file mode 100644 index 0000000000..6615190425 --- /dev/null +++ b/usr/src/cmd/mandoc/compat_reallocarray.c @@ -0,0 +1,49 @@ +#include "config.h" + +#if HAVE_REALLOCARRAY + +int dummy; + +#else + +/* $Id: compat_reallocarray.c,v 1.4 2014/12/11 09:05:01 schwarze Exp $ */ +/* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */ +/* + * Copyright (c) 2008 Otto Moerbeek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void * +reallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); +} + +#endif /*!HAVE_REALLOCARRAY*/ diff --git a/usr/src/cmd/mandoc/compat_strtonum.c b/usr/src/cmd/mandoc/compat_strtonum.c new file mode 100644 index 0000000000..628e5d51b8 --- /dev/null +++ b/usr/src/cmd/mandoc/compat_strtonum.c @@ -0,0 +1,76 @@ +#include "config.h" + +#if HAVE_STRTONUM + +int dummy; + +#else + +/* $Id: compat_strtonum.c,v 1.1 2015/02/16 14:56:22 schwarze Exp $ */ +/* $OpenBSD: strtonum.c,v 1.7 2013/04/17 18:40:58 tedu Exp $ */ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#define INVALID 1 +#define TOOSMALL 2 +#define TOOLARGE 3 + +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + int error = 0; + char *ep; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) { + error = INVALID; + } else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = TOOSMALL; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +} + +#endif /* !HAVE_STRTONUM */ diff --git a/usr/src/cmd/mandoc/config.h b/usr/src/cmd/mandoc/config.h index 7f4f1edf09..040fc56e39 100644 --- a/usr/src/cmd/mandoc/config.h +++ b/usr/src/cmd/mandoc/config.h @@ -1,20 +1,35 @@ -#ifndef MANDOC_CONFIG_H -#define MANDOC_CONFIG_H +#ifndef MANDOC_CONFIG_H +#define MANDOC_CONFIG_H #if defined(__linux__) || defined(__MINT__) -# define _GNU_SOURCE /* strptime(), getsubopt() */ +#define _GNU_SOURCE /* See test-*.c what needs this. */ #endif +#include #include -#define VERSION "1.12.3" -#define HAVE_STRPTIME -#define HAVE_GETSUBOPT -#define HAVE_STRLCAT -#define HAVE_STRLCPY -#define HAVE_MMAP - -#include +#define HAVE_DIRENT_NAMLEN 0 +#define HAVE_FGETLN 0 +#define HAVE_FTS 0 +#define HAVE_GETSUBOPT 1 +#define HAVE_MMAP 1 +#define HAVE_REALLOCARRAY 0 +#define HAVE_STRCASESTR 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRPTIME 1 +#define HAVE_STRSEP 1 +#define HAVE_STRTONUM 0 +#define HAVE_WCHAR 1 +#define HAVE_SQLITE3 0 +#define HAVE_SQLITE3_ERRSTR 1 +#define HAVE_OHASH 1 +#define HAVE_MANPATH 0 + +#define BINM_APROPOS "apropos" +#define BINM_MAN "man" +#define BINM_WHATIS "whatis" +#define BINM_MAKEWHATIS "makewhatis" #if !defined(__BEGIN_DECLS) # ifdef __cplusplus @@ -31,30 +46,13 @@ # endif #endif -#ifndef HAVE_BETOH64 -# if defined(__APPLE__) -# define betoh64(x) OSSwapBigToHostInt64(x) -# define htobe64(x) OSSwapHostToBigInt64(x) -# elif defined(__sun) -# define betoh64(x) BE_64(x) -# define htobe64(x) BE_64(x) -# else -# define betoh64(x) be64toh(x) -# endif -#endif - -#ifndef HAVE_STRLCAT +extern char *fgetln(FILE *, size_t *); +extern int getsubopt(char **, char * const *, char **); +extern void *reallocarray(void *, size_t, size_t); +extern char *strcasestr(const char *, const char *); extern size_t strlcat(char *, const char *, size_t); -#endif -#ifndef HAVE_STRLCPY extern size_t strlcpy(char *, const char *, size_t); -#endif -#ifndef HAVE_GETSUBOPT -extern int getsubopt(char **, char * const *, char **); -extern char *suboptarg; -#endif -#ifndef HAVE_FGETLN -extern char *fgetln(FILE *, size_t *); -#endif +extern char *strsep(char **, const char *); +extern long long strtonum(const char *, long long, long long, const char **); #endif /* MANDOC_CONFIG_H */ diff --git a/usr/src/cmd/mandoc/eqn.c b/usr/src/cmd/mandoc/eqn.c index 37f01bcb5b..9da57f06e2 100644 --- a/usr/src/cmd/mandoc/eqn.c +++ b/usr/src/cmd/mandoc/eqn.c @@ -1,6 +1,7 @@ -/* $Id: eqn.c,v 1.38 2011/07/25 15:37:00 kristaps Exp $ */ +/* $Id: eqn.c,v 1.58 2015/03/04 12:19:49 schwarze Exp $ */ /* - * Copyright (c) 2011 Kristaps Dzonsons + * Copyright (c) 2011, 2014 Kristaps Dzonsons + * Copyright (c) 2014, 2015 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,9 +15,9 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef HAVE_CONFIG_H #include "config.h" -#endif + +#include #include #include @@ -26,17 +27,111 @@ #include #include "mandoc.h" +#include "mandoc_aux.h" #include "libmandoc.h" #include "libroff.h" #define EQN_NEST_MAX 128 /* maximum nesting of defines */ -#define EQN_MSG(t, x) mandoc_msg((t), (x)->parse, (x)->eqn.ln, (x)->eqn.pos, NULL) +#define STRNEQ(p1, sz1, p2, sz2) \ + ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1))) -enum eqn_rest { - EQN_DESCOPE, - EQN_ERR, - EQN_OK, - EQN_EOF +enum eqn_tok { + EQN_TOK_DYAD = 0, + EQN_TOK_VEC, + EQN_TOK_UNDER, + EQN_TOK_BAR, + EQN_TOK_TILDE, + EQN_TOK_HAT, + EQN_TOK_DOT, + EQN_TOK_DOTDOT, + EQN_TOK_FWD, + EQN_TOK_BACK, + EQN_TOK_DOWN, + EQN_TOK_UP, + EQN_TOK_FAT, + EQN_TOK_ROMAN, + EQN_TOK_ITALIC, + EQN_TOK_BOLD, + EQN_TOK_SIZE, + EQN_TOK_SUB, + EQN_TOK_SUP, + EQN_TOK_SQRT, + EQN_TOK_OVER, + EQN_TOK_FROM, + EQN_TOK_TO, + EQN_TOK_BRACE_OPEN, + EQN_TOK_BRACE_CLOSE, + EQN_TOK_GSIZE, + EQN_TOK_GFONT, + EQN_TOK_MARK, + EQN_TOK_LINEUP, + EQN_TOK_LEFT, + EQN_TOK_RIGHT, + EQN_TOK_PILE, + EQN_TOK_LPILE, + EQN_TOK_RPILE, + EQN_TOK_CPILE, + EQN_TOK_MATRIX, + EQN_TOK_CCOL, + EQN_TOK_LCOL, + EQN_TOK_RCOL, + EQN_TOK_DELIM, + EQN_TOK_DEFINE, + EQN_TOK_TDEFINE, + EQN_TOK_NDEFINE, + EQN_TOK_UNDEF, + EQN_TOK_EOF, + EQN_TOK_ABOVE, + EQN_TOK__MAX +}; + +static const char *eqn_toks[EQN_TOK__MAX] = { + "dyad", /* EQN_TOK_DYAD */ + "vec", /* EQN_TOK_VEC */ + "under", /* EQN_TOK_UNDER */ + "bar", /* EQN_TOK_BAR */ + "tilde", /* EQN_TOK_TILDE */ + "hat", /* EQN_TOK_HAT */ + "dot", /* EQN_TOK_DOT */ + "dotdot", /* EQN_TOK_DOTDOT */ + "fwd", /* EQN_TOK_FWD * */ + "back", /* EQN_TOK_BACK */ + "down", /* EQN_TOK_DOWN */ + "up", /* EQN_TOK_UP */ + "fat", /* EQN_TOK_FAT */ + "roman", /* EQN_TOK_ROMAN */ + "italic", /* EQN_TOK_ITALIC */ + "bold", /* EQN_TOK_BOLD */ + "size", /* EQN_TOK_SIZE */ + "sub", /* EQN_TOK_SUB */ + "sup", /* EQN_TOK_SUP */ + "sqrt", /* EQN_TOK_SQRT */ + "over", /* EQN_TOK_OVER */ + "from", /* EQN_TOK_FROM */ + "to", /* EQN_TOK_TO */ + "{", /* EQN_TOK_BRACE_OPEN */ + "}", /* EQN_TOK_BRACE_CLOSE */ + "gsize", /* EQN_TOK_GSIZE */ + "gfont", /* EQN_TOK_GFONT */ + "mark", /* EQN_TOK_MARK */ + "lineup", /* EQN_TOK_LINEUP */ + "left", /* EQN_TOK_LEFT */ + "right", /* EQN_TOK_RIGHT */ + "pile", /* EQN_TOK_PILE */ + "lpile", /* EQN_TOK_LPILE */ + "rpile", /* EQN_TOK_RPILE */ + "cpile", /* EQN_TOK_CPILE */ + "matrix", /* EQN_TOK_MATRIX */ + "ccol", /* EQN_TOK_CCOL */ + "lcol", /* EQN_TOK_LCOL */ + "rcol", /* EQN_TOK_RCOL */ + "delim", /* EQN_TOK_DELIM */ + "define", /* EQN_TOK_DEFINE */ + "tdefine", /* EQN_TOK_TDEFINE */ + "ndefine", /* EQN_TOK_NDEFINE */ + "undef", /* EQN_TOK_UNDEF */ + NULL, /* EQN_TOK_EOF */ + "above", /* EQN_TOK_ABOVE */ }; enum eqn_symt { @@ -99,187 +194,95 @@ enum eqn_symt { EQNSYM_equiv, EQNSYM_lessequal, EQNSYM_moreequal, + EQNSYM_minus, EQNSYM__MAX }; -enum eqnpartt { - EQN_DEFINE = 0, - EQN_NDEFINE, - EQN_TDEFINE, - EQN_SET, - EQN_UNDEF, - EQN_GFONT, - EQN_GSIZE, - EQN_BACK, - EQN_FWD, - EQN_UP, - EQN_DOWN, - EQN__MAX -}; - -struct eqnstr { - const char *name; - size_t sz; -}; - -#define STRNEQ(p1, sz1, p2, sz2) \ - ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1))) -#define EQNSTREQ(x, p, sz) \ - STRNEQ((x)->name, (x)->sz, (p), (sz)) - -struct eqnpart { - struct eqnstr str; - int (*fp)(struct eqn_node *); -}; - struct eqnsym { - struct eqnstr str; + const char *str; const char *sym; }; +static const struct eqnsym eqnsyms[EQNSYM__MAX] = { + { "alpha", "*a" }, /* EQNSYM_alpha */ + { "beta", "*b" }, /* EQNSYM_beta */ + { "chi", "*x" }, /* EQNSYM_chi */ + { "delta", "*d" }, /* EQNSYM_delta */ + { "epsilon", "*e" }, /* EQNSYM_epsilon */ + { "eta", "*y" }, /* EQNSYM_eta */ + { "gamma", "*g" }, /* EQNSYM_gamma */ + { "iota", "*i" }, /* EQNSYM_iota */ + { "kappa", "*k" }, /* EQNSYM_kappa */ + { "lambda", "*l" }, /* EQNSYM_lambda */ + { "mu", "*m" }, /* EQNSYM_mu */ + { "nu", "*n" }, /* EQNSYM_nu */ + { "omega", "*w" }, /* EQNSYM_omega */ + { "omicron", "*o" }, /* EQNSYM_omicron */ + { "phi", "*f" }, /* EQNSYM_phi */ + { "pi", "*p" }, /* EQNSYM_pi */ + { "psi", "*q" }, /* EQNSYM_psi */ + { "rho", "*r" }, /* EQNSYM_rho */ + { "sigma", "*s" }, /* EQNSYM_sigma */ + { "tau", "*t" }, /* EQNSYM_tau */ + { "theta", "*h" }, /* EQNSYM_theta */ + { "upsilon", "*u" }, /* EQNSYM_upsilon */ + { "xi", "*c" }, /* EQNSYM_xi */ + { "zeta", "*z" }, /* EQNSYM_zeta */ + { "DELTA", "*D" }, /* EQNSYM_DELTA */ + { "GAMMA", "*G" }, /* EQNSYM_GAMMA */ + { "LAMBDA", "*L" }, /* EQNSYM_LAMBDA */ + { "OMEGA", "*W" }, /* EQNSYM_OMEGA */ + { "PHI", "*F" }, /* EQNSYM_PHI */ + { "PI", "*P" }, /* EQNSYM_PI */ + { "PSI", "*Q" }, /* EQNSYM_PSI */ + { "SIGMA", "*S" }, /* EQNSYM_SIGMA */ + { "THETA", "*H" }, /* EQNSYM_THETA */ + { "UPSILON", "*U" }, /* EQNSYM_UPSILON */ + { "XI", "*C" }, /* EQNSYM_XI */ + { "inter", "ca" }, /* EQNSYM_inter */ + { "union", "cu" }, /* EQNSYM_union */ + { "prod", "product" }, /* EQNSYM_prod */ + { "int", "integral" }, /* EQNSYM_int */ + { "sum", "sum" }, /* EQNSYM_sum */ + { "grad", "gr" }, /* EQNSYM_grad */ + { "del", "gr" }, /* EQNSYM_del */ + { "times", "mu" }, /* EQNSYM_times */ + { "cdot", "pc" }, /* EQNSYM_cdot */ + { "nothing", "&" }, /* EQNSYM_nothing */ + { "approx", "~~" }, /* EQNSYM_approx */ + { "prime", "fm" }, /* EQNSYM_prime */ + { "half", "12" }, /* EQNSYM_half */ + { "partial", "pd" }, /* EQNSYM_partial */ + { "inf", "if" }, /* EQNSYM_inf */ + { ">>", ">>" }, /* EQNSYM_muchgreat */ + { "<<", "<<" }, /* EQNSYM_muchless */ + { "<-", "<-" }, /* EQNSYM_larrow */ + { "->", "->" }, /* EQNSYM_rarrow */ + { "+-", "+-" }, /* EQNSYM_pm */ + { "!=", "!=" }, /* EQNSYM_nequal */ + { "==", "==" }, /* EQNSYM_equiv */ + { "<=", "<=" }, /* EQNSYM_lessequal */ + { ">=", ">=" }, /* EQNSYM_moreequal */ + { "-", "mi" }, /* EQNSYM_minus */ +}; -static enum eqn_rest eqn_box(struct eqn_node *, struct eqn_box *); -static struct eqn_box *eqn_box_alloc(struct eqn_node *, - struct eqn_box *); +static struct eqn_box *eqn_box_alloc(struct eqn_node *, struct eqn_box *); static void eqn_box_free(struct eqn_box *); -static struct eqn_def *eqn_def_find(struct eqn_node *, - const char *, size_t); -static int eqn_do_gfont(struct eqn_node *); -static int eqn_do_gsize(struct eqn_node *); -static int eqn_do_define(struct eqn_node *); -static int eqn_do_ign1(struct eqn_node *); -static int eqn_do_ign2(struct eqn_node *); -static int eqn_do_tdefine(struct eqn_node *); -static int eqn_do_undef(struct eqn_node *); -static enum eqn_rest eqn_eqn(struct eqn_node *, struct eqn_box *); -static enum eqn_rest eqn_list(struct eqn_node *, struct eqn_box *); -static enum eqn_rest eqn_matrix(struct eqn_node *, struct eqn_box *); -static const char *eqn_nexttok(struct eqn_node *, size_t *); +static struct eqn_box *eqn_box_makebinary(struct eqn_node *, + enum eqn_post, struct eqn_box *); +static void eqn_def(struct eqn_node *); +static struct eqn_def *eqn_def_find(struct eqn_node *, const char *, size_t); +static void eqn_delim(struct eqn_node *); +static const char *eqn_next(struct eqn_node *, char, size_t *, int); static const char *eqn_nextrawtok(struct eqn_node *, size_t *); -static const char *eqn_next(struct eqn_node *, - char, size_t *, int); -static void eqn_rewind(struct eqn_node *); - -static const struct eqnpart eqnparts[EQN__MAX] = { - { { "define", 6 }, eqn_do_define }, /* EQN_DEFINE */ - { { "ndefine", 7 }, eqn_do_define }, /* EQN_NDEFINE */ - { { "tdefine", 7 }, eqn_do_tdefine }, /* EQN_TDEFINE */ - { { "set", 3 }, eqn_do_ign2 }, /* EQN_SET */ - { { "undef", 5 }, eqn_do_undef }, /* EQN_UNDEF */ - { { "gfont", 5 }, eqn_do_gfont }, /* EQN_GFONT */ - { { "gsize", 5 }, eqn_do_gsize }, /* EQN_GSIZE */ - { { "back", 4 }, eqn_do_ign1 }, /* EQN_BACK */ - { { "fwd", 3 }, eqn_do_ign1 }, /* EQN_FWD */ - { { "up", 2 }, eqn_do_ign1 }, /* EQN_UP */ - { { "down", 4 }, eqn_do_ign1 }, /* EQN_DOWN */ -}; - -static const struct eqnstr eqnmarks[EQNMARK__MAX] = { - { "", 0 }, /* EQNMARK_NONE */ - { "dot", 3 }, /* EQNMARK_DOT */ - { "dotdot", 6 }, /* EQNMARK_DOTDOT */ - { "hat", 3 }, /* EQNMARK_HAT */ - { "tilde", 5 }, /* EQNMARK_TILDE */ - { "vec", 3 }, /* EQNMARK_VEC */ - { "dyad", 4 }, /* EQNMARK_DYAD */ - { "bar", 3 }, /* EQNMARK_BAR */ - { "under", 5 }, /* EQNMARK_UNDER */ -}; - -static const struct eqnstr eqnfonts[EQNFONT__MAX] = { - { "", 0 }, /* EQNFONT_NONE */ - { "roman", 5 }, /* EQNFONT_ROMAN */ - { "bold", 4 }, /* EQNFONT_BOLD */ - { "fat", 3 }, /* EQNFONT_FAT */ - { "italic", 6 }, /* EQNFONT_ITALIC */ -}; - -static const struct eqnstr eqnposs[EQNPOS__MAX] = { - { "", 0 }, /* EQNPOS_NONE */ - { "over", 4 }, /* EQNPOS_OVER */ - { "sup", 3 }, /* EQNPOS_SUP */ - { "sub", 3 }, /* EQNPOS_SUB */ - { "to", 2 }, /* EQNPOS_TO */ - { "from", 4 }, /* EQNPOS_FROM */ -}; - -static const struct eqnstr eqnpiles[EQNPILE__MAX] = { - { "", 0 }, /* EQNPILE_NONE */ - { "pile", 4 }, /* EQNPILE_PILE */ - { "cpile", 5 }, /* EQNPILE_CPILE */ - { "rpile", 5 }, /* EQNPILE_RPILE */ - { "lpile", 5 }, /* EQNPILE_LPILE */ - { "col", 3 }, /* EQNPILE_COL */ - { "ccol", 4 }, /* EQNPILE_CCOL */ - { "rcol", 4 }, /* EQNPILE_RCOL */ - { "lcol", 4 }, /* EQNPILE_LCOL */ -}; +static const char *eqn_nexttok(struct eqn_node *, size_t *); +static enum rofferr eqn_parse(struct eqn_node *, struct eqn_box *); +static enum eqn_tok eqn_tok_parse(struct eqn_node *, char **); +static void eqn_undef(struct eqn_node *); -static const struct eqnsym eqnsyms[EQNSYM__MAX] = { - { { "alpha", 5 }, "*a" }, /* EQNSYM_alpha */ - { { "beta", 4 }, "*b" }, /* EQNSYM_beta */ - { { "chi", 3 }, "*x" }, /* EQNSYM_chi */ - { { "delta", 5 }, "*d" }, /* EQNSYM_delta */ - { { "epsilon", 7 }, "*e" }, /* EQNSYM_epsilon */ - { { "eta", 3 }, "*y" }, /* EQNSYM_eta */ - { { "gamma", 5 }, "*g" }, /* EQNSYM_gamma */ - { { "iota", 4 }, "*i" }, /* EQNSYM_iota */ - { { "kappa", 5 }, "*k" }, /* EQNSYM_kappa */ - { { "lambda", 6 }, "*l" }, /* EQNSYM_lambda */ - { { "mu", 2 }, "*m" }, /* EQNSYM_mu */ - { { "nu", 2 }, "*n" }, /* EQNSYM_nu */ - { { "omega", 5 }, "*w" }, /* EQNSYM_omega */ - { { "omicron", 7 }, "*o" }, /* EQNSYM_omicron */ - { { "phi", 3 }, "*f" }, /* EQNSYM_phi */ - { { "pi", 2 }, "*p" }, /* EQNSYM_pi */ - { { "psi", 2 }, "*q" }, /* EQNSYM_psi */ - { { "rho", 3 }, "*r" }, /* EQNSYM_rho */ - { { "sigma", 5 }, "*s" }, /* EQNSYM_sigma */ - { { "tau", 3 }, "*t" }, /* EQNSYM_tau */ - { { "theta", 5 }, "*h" }, /* EQNSYM_theta */ - { { "upsilon", 7 }, "*u" }, /* EQNSYM_upsilon */ - { { "xi", 2 }, "*c" }, /* EQNSYM_xi */ - { { "zeta", 4 }, "*z" }, /* EQNSYM_zeta */ - { { "DELTA", 5 }, "*D" }, /* EQNSYM_DELTA */ - { { "GAMMA", 5 }, "*G" }, /* EQNSYM_GAMMA */ - { { "LAMBDA", 6 }, "*L" }, /* EQNSYM_LAMBDA */ - { { "OMEGA", 5 }, "*W" }, /* EQNSYM_OMEGA */ - { { "PHI", 3 }, "*F" }, /* EQNSYM_PHI */ - { { "PI", 2 }, "*P" }, /* EQNSYM_PI */ - { { "PSI", 3 }, "*Q" }, /* EQNSYM_PSI */ - { { "SIGMA", 5 }, "*S" }, /* EQNSYM_SIGMA */ - { { "THETA", 5 }, "*H" }, /* EQNSYM_THETA */ - { { "UPSILON", 7 }, "*U" }, /* EQNSYM_UPSILON */ - { { "XI", 2 }, "*C" }, /* EQNSYM_XI */ - { { "inter", 5 }, "ca" }, /* EQNSYM_inter */ - { { "union", 5 }, "cu" }, /* EQNSYM_union */ - { { "prod", 4 }, "product" }, /* EQNSYM_prod */ - { { "int", 3 }, "integral" }, /* EQNSYM_int */ - { { "sum", 3 }, "sum" }, /* EQNSYM_sum */ - { { "grad", 4 }, "gr" }, /* EQNSYM_grad */ - { { "del", 3 }, "gr" }, /* EQNSYM_del */ - { { "times", 5 }, "mu" }, /* EQNSYM_times */ - { { "cdot", 4 }, "pc" }, /* EQNSYM_cdot */ - { { "nothing", 7 }, "&" }, /* EQNSYM_nothing */ - { { "approx", 6 }, "~~" }, /* EQNSYM_approx */ - { { "prime", 5 }, "aq" }, /* EQNSYM_prime */ - { { "half", 4 }, "12" }, /* EQNSYM_half */ - { { "partial", 7 }, "pd" }, /* EQNSYM_partial */ - { { "inf", 3 }, "if" }, /* EQNSYM_inf */ - { { ">>", 2 }, ">>" }, /* EQNSYM_muchgreat */ - { { "<<", 2 }, "<<" }, /* EQNSYM_muchless */ - { { "<-", 2 }, "<-" }, /* EQNSYM_larrow */ - { { "->", 2 }, "->" }, /* EQNSYM_rarrow */ - { { "+-", 2 }, "+-" }, /* EQNSYM_pm */ - { { "!=", 2 }, "!=" }, /* EQNSYM_nequal */ - { { "==", 2 }, "==" }, /* EQNSYM_equiv */ - { { "<=", 2 }, "<=" }, /* EQNSYM_lessequal */ - { { ">=", 2 }, ">=" }, /* EQNSYM_moreequal */ -}; -/* ARGSUSED */ enum rofferr -eqn_read(struct eqn_node **epp, int ln, +eqn_read(struct eqn_node **epp, int ln, const char *p, int pos, int *offs) { size_t sz; @@ -298,9 +301,10 @@ eqn_read(struct eqn_node **epp, int ln, p += 3; while (' ' == *p || '\t' == *p) p++; - if ('\0' == *p) + if ('\0' == *p) return(er); - mandoc_msg(MANDOCERR_ARGSLOST, ep->parse, ln, pos, NULL); + mandoc_vmsg(MANDOCERR_ARG_SKIP, ep->parse, + ln, pos, "EN %s", p); return(er); } @@ -324,24 +328,12 @@ eqn_read(struct eqn_node **epp, int ln, } struct eqn_node * -eqn_alloc(const char *name, int pos, int line, struct mparse *parse) +eqn_alloc(int pos, int line, struct mparse *parse) { struct eqn_node *p; - size_t sz; - const char *end; p = mandoc_calloc(1, sizeof(struct eqn_node)); - if (name && '\0' != *name) { - sz = strlen(name); - assert(sz); - do { - sz--; - end = name + (int)sz; - } while (' ' == *end || '\t' == *end); - p->eqn.name = mandoc_strndup(name, sz + 1); - } - p->parse = parse; p->eqn.ln = line; p->eqn.pos = pos; @@ -350,366 +342,27 @@ eqn_alloc(const char *name, int pos, int line, struct mparse *parse) return(p); } -enum rofferr -eqn_end(struct eqn_node **epp) -{ - struct eqn_node *ep; - struct eqn_box *root; - enum eqn_rest c; - - ep = *epp; - *epp = NULL; - - ep->eqn.root = mandoc_calloc(1, sizeof(struct eqn_box)); - - root = ep->eqn.root; - root->type = EQN_ROOT; - - if (0 == ep->sz) - return(ROFF_IGN); - - if (EQN_DESCOPE == (c = eqn_eqn(ep, root))) { - EQN_MSG(MANDOCERR_EQNNSCOPE, ep); - c = EQN_ERR; - } - - return(EQN_EOF == c ? ROFF_EQN : ROFF_IGN); -} - -static enum eqn_rest -eqn_eqn(struct eqn_node *ep, struct eqn_box *last) -{ - struct eqn_box *bp; - enum eqn_rest c; - - bp = eqn_box_alloc(ep, last); - bp->type = EQN_SUBEXPR; - - while (EQN_OK == (c = eqn_box(ep, bp))) - /* Spin! */ ; - - return(c); -} - -static enum eqn_rest -eqn_matrix(struct eqn_node *ep, struct eqn_box *last) -{ - struct eqn_box *bp; - const char *start; - size_t sz; - enum eqn_rest c; - - bp = eqn_box_alloc(ep, last); - bp->type = EQN_MATRIX; - - if (NULL == (start = eqn_nexttok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - if ( ! STRNEQ(start, sz, "{", 1)) { - EQN_MSG(MANDOCERR_EQNSYNT, ep); - return(EQN_ERR); - } - - while (EQN_OK == (c = eqn_box(ep, bp))) - switch (bp->last->pile) { - case (EQNPILE_LCOL): - /* FALLTHROUGH */ - case (EQNPILE_CCOL): - /* FALLTHROUGH */ - case (EQNPILE_RCOL): - continue; - default: - EQN_MSG(MANDOCERR_EQNSYNT, ep); - return(EQN_ERR); - }; - - if (EQN_DESCOPE != c) { - if (EQN_EOF == c) - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - - eqn_rewind(ep); - start = eqn_nexttok(ep, &sz); - assert(start); - if (STRNEQ(start, sz, "}", 1)) - return(EQN_OK); - - EQN_MSG(MANDOCERR_EQNBADSCOPE, ep); - return(EQN_ERR); -} - -static enum eqn_rest -eqn_list(struct eqn_node *ep, struct eqn_box *last) -{ - struct eqn_box *bp; - const char *start; - size_t sz; - enum eqn_rest c; - - bp = eqn_box_alloc(ep, last); - bp->type = EQN_LIST; - - if (NULL == (start = eqn_nexttok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - if ( ! STRNEQ(start, sz, "{", 1)) { - EQN_MSG(MANDOCERR_EQNSYNT, ep); - return(EQN_ERR); - } - - while (EQN_DESCOPE == (c = eqn_eqn(ep, bp))) { - eqn_rewind(ep); - start = eqn_nexttok(ep, &sz); - assert(start); - if ( ! STRNEQ(start, sz, "above", 5)) - break; - } - - if (EQN_DESCOPE != c) { - if (EQN_ERR != c) - EQN_MSG(MANDOCERR_EQNSCOPE, ep); - return(EQN_ERR); - } - - eqn_rewind(ep); - start = eqn_nexttok(ep, &sz); - assert(start); - if (STRNEQ(start, sz, "}", 1)) - return(EQN_OK); - - EQN_MSG(MANDOCERR_EQNBADSCOPE, ep); - return(EQN_ERR); -} - -static enum eqn_rest -eqn_box(struct eqn_node *ep, struct eqn_box *last) -{ - size_t sz; - const char *start; - char *left; - char sym[64]; - enum eqn_rest c; - int i, size; - struct eqn_box *bp; - - if (NULL == (start = eqn_nexttok(ep, &sz))) - return(EQN_EOF); - - if (STRNEQ(start, sz, "}", 1)) - return(EQN_DESCOPE); - else if (STRNEQ(start, sz, "right", 5)) - return(EQN_DESCOPE); - else if (STRNEQ(start, sz, "above", 5)) - return(EQN_DESCOPE); - else if (STRNEQ(start, sz, "mark", 4)) - return(EQN_OK); - else if (STRNEQ(start, sz, "lineup", 6)) - return(EQN_OK); - - for (i = 0; i < (int)EQN__MAX; i++) { - if ( ! EQNSTREQ(&eqnparts[i].str, start, sz)) - continue; - return((*eqnparts[i].fp)(ep) ? - EQN_OK : EQN_ERR); - } - - if (STRNEQ(start, sz, "{", 1)) { - if (EQN_DESCOPE != (c = eqn_eqn(ep, last))) { - if (EQN_ERR != c) - EQN_MSG(MANDOCERR_EQNSCOPE, ep); - return(EQN_ERR); - } - eqn_rewind(ep); - start = eqn_nexttok(ep, &sz); - assert(start); - if (STRNEQ(start, sz, "}", 1)) - return(EQN_OK); - EQN_MSG(MANDOCERR_EQNBADSCOPE, ep); - return(EQN_ERR); - } - - for (i = 0; i < (int)EQNPILE__MAX; i++) { - if ( ! EQNSTREQ(&eqnpiles[i], start, sz)) - continue; - if (EQN_OK == (c = eqn_list(ep, last))) - last->last->pile = (enum eqn_pilet)i; - return(c); - } - - if (STRNEQ(start, sz, "matrix", 6)) - return(eqn_matrix(ep, last)); - - if (STRNEQ(start, sz, "left", 4)) { - if (NULL == (start = eqn_nexttok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - left = mandoc_strndup(start, sz); - c = eqn_eqn(ep, last); - if (last->last) - last->last->left = left; - else - free(left); - if (EQN_DESCOPE != c) - return(c); - assert(last->last); - eqn_rewind(ep); - start = eqn_nexttok(ep, &sz); - assert(start); - if ( ! STRNEQ(start, sz, "right", 5)) - return(EQN_DESCOPE); - if (NULL == (start = eqn_nexttok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - last->last->right = mandoc_strndup(start, sz); - return(EQN_OK); - } - - for (i = 0; i < (int)EQNPOS__MAX; i++) { - if ( ! EQNSTREQ(&eqnposs[i], start, sz)) - continue; - if (NULL == last->last) { - EQN_MSG(MANDOCERR_EQNSYNT, ep); - return(EQN_ERR); - } - last->last->pos = (enum eqn_post)i; - if (EQN_EOF == (c = eqn_box(ep, last))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - return(c); - } - - for (i = 0; i < (int)EQNMARK__MAX; i++) { - if ( ! EQNSTREQ(&eqnmarks[i], start, sz)) - continue; - if (NULL == last->last) { - EQN_MSG(MANDOCERR_EQNSYNT, ep); - return(EQN_ERR); - } - last->last->mark = (enum eqn_markt)i; - if (EQN_EOF == (c = eqn_box(ep, last))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - return(c); - } - - for (i = 0; i < (int)EQNFONT__MAX; i++) { - if ( ! EQNSTREQ(&eqnfonts[i], start, sz)) - continue; - if (EQN_EOF == (c = eqn_box(ep, last))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } else if (EQN_OK == c) - last->last->font = (enum eqn_fontt)i; - return(c); - } - - if (STRNEQ(start, sz, "size", 4)) { - if (NULL == (start = eqn_nexttok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } - size = mandoc_strntoi(start, sz, 10); - if (EQN_EOF == (c = eqn_box(ep, last))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(EQN_ERR); - } else if (EQN_OK != c) - return(c); - last->last->size = size; - } - - bp = eqn_box_alloc(ep, last); - bp->type = EQN_TEXT; - for (i = 0; i < (int)EQNSYM__MAX; i++) - if (EQNSTREQ(&eqnsyms[i].str, start, sz)) { - sym[63] = '\0'; - snprintf(sym, 62, "\\[%s]", eqnsyms[i].sym); - bp->text = mandoc_strdup(sym); - return(EQN_OK); - } - - bp->text = mandoc_strndup(start, sz); - return(EQN_OK); -} - -void -eqn_free(struct eqn_node *p) +/* + * Find the key "key" of the give size within our eqn-defined values. + */ +static struct eqn_def * +eqn_def_find(struct eqn_node *ep, const char *key, size_t sz) { int i; - eqn_box_free(p->eqn.root); - - for (i = 0; i < (int)p->defsz; i++) { - free(p->defs[i].key); - free(p->defs[i].val); - } - - free(p->eqn.name); - free(p->data); - free(p->defs); - free(p); -} - -static struct eqn_box * -eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent) -{ - struct eqn_box *bp; - - bp = mandoc_calloc(1, sizeof(struct eqn_box)); - bp->parent = parent; - bp->size = ep->gsize; - - if (NULL == parent->first) - parent->first = bp; - else - parent->last->next = bp; - - parent->last = bp; - return(bp); -} - -static void -eqn_box_free(struct eqn_box *bp) -{ - - if (bp->first) - eqn_box_free(bp->first); - if (bp->next) - eqn_box_free(bp->next); - - free(bp->text); - free(bp->left); - free(bp->right); - free(bp); -} - -static const char * -eqn_nextrawtok(struct eqn_node *ep, size_t *sz) -{ - - return(eqn_next(ep, '"', sz, 0)); -} - -static const char * -eqn_nexttok(struct eqn_node *ep, size_t *sz) -{ - - return(eqn_next(ep, '"', sz, 1)); -} - -static void -eqn_rewind(struct eqn_node *ep) -{ + for (i = 0; i < (int)ep->defsz; i++) + if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key, + ep->defs[i].keysz, key, sz)) + return(&ep->defs[i]); - ep->cur = ep->rew; + return(NULL); } +/* + * Get the next token from the input stream using the given quote + * character. + * Optionally make any replacements. + */ static const char * eqn_next(struct eqn_node *ep, char quote, size_t *sz, int repl) { @@ -727,7 +380,8 @@ again: /* Prevent self-definitions. */ if (lim >= EQN_NEST_MAX) { - EQN_MSG(MANDOCERR_ROFFLOOP, ep); + mandoc_msg(MANDOCERR_ROFFLOOP, ep->parse, + ep->eqn.ln, ep->eqn.pos, NULL); return(NULL); } @@ -762,13 +416,14 @@ again: if (q) ep->cur++; while (' ' == ep->data[(int)ep->cur] || - '\t' == ep->data[(int)ep->cur] || - '^' == ep->data[(int)ep->cur] || - '~' == ep->data[(int)ep->cur]) + '\t' == ep->data[(int)ep->cur] || + '^' == ep->data[(int)ep->cur] || + '~' == ep->data[(int)ep->cur]) ep->cur++; } else { if (q) - EQN_MSG(MANDOCERR_BADQUOTE, ep); + mandoc_msg(MANDOCERR_ARG_QUOTE, ep->parse, + ep->eqn.ln, ep->eqn.pos, NULL); next = strchr(start, '\0'); *sz = (size_t)(next - start); ep->cur += *sz; @@ -790,8 +445,8 @@ again: } diff = def->valsz - *sz; - memmove(start + *sz + diff, start + *sz, - (strlen(start) - *sz) + 1); + memmove(start + *sz + diff, start + *sz, + (strlen(start) - *sz) + 1); memcpy(start, def->val, def->valsz); goto again; } @@ -799,64 +454,207 @@ again: return(start); } -static int -eqn_do_ign1(struct eqn_node *ep) +/* + * Get the next delimited token using the default current quote + * character. + */ +static const char * +eqn_nexttok(struct eqn_node *ep, size_t *sz) { - if (NULL == eqn_nextrawtok(ep, NULL)) - EQN_MSG(MANDOCERR_EQNEOF, ep); - else - return(1); + return(eqn_next(ep, '"', sz, 1)); +} + +/* + * Get next token without replacement. + */ +static const char * +eqn_nextrawtok(struct eqn_node *ep, size_t *sz) +{ - return(0); + return(eqn_next(ep, '"', sz, 0)); } -static int -eqn_do_ign2(struct eqn_node *ep) +/* + * Parse a token from the stream of text. + * A token consists of one of the recognised eqn(7) strings. + * Strings are separated by delimiting marks. + * This returns EQN_TOK_EOF when there are no more tokens. + * If the token is an unrecognised string literal, then it returns + * EQN_TOK__MAX and sets the "p" pointer to an allocated, nil-terminated + * string. + * This must be later freed with free(3). + */ +static enum eqn_tok +eqn_tok_parse(struct eqn_node *ep, char **p) { + const char *start; + size_t i, sz; + int quoted; + + if (NULL != p) + *p = NULL; + + quoted = ep->data[ep->cur] == '"'; + + if (NULL == (start = eqn_nexttok(ep, &sz))) + return(EQN_TOK_EOF); + + if (quoted) { + if (p != NULL) + *p = mandoc_strndup(start, sz); + return(EQN_TOK__MAX); + } + + for (i = 0; i < EQN_TOK__MAX; i++) { + if (NULL == eqn_toks[i]) + continue; + if (STRNEQ(start, sz, eqn_toks[i], strlen(eqn_toks[i]))) + break; + } - if (NULL == eqn_nextrawtok(ep, NULL)) - EQN_MSG(MANDOCERR_EQNEOF, ep); - else if (NULL == eqn_nextrawtok(ep, NULL)) - EQN_MSG(MANDOCERR_EQNEOF, ep); - else - return(1); + if (i == EQN_TOK__MAX && NULL != p) + *p = mandoc_strndup(start, sz); - return(0); + return(i); } -static int -eqn_do_tdefine(struct eqn_node *ep) +static void +eqn_box_free(struct eqn_box *bp) { - if (NULL == eqn_nextrawtok(ep, NULL)) - EQN_MSG(MANDOCERR_EQNEOF, ep); - else if (NULL == eqn_next(ep, ep->data[(int)ep->cur], NULL, 0)) - EQN_MSG(MANDOCERR_EQNEOF, ep); - else - return(1); + if (bp->first) + eqn_box_free(bp->first); + if (bp->next) + eqn_box_free(bp->next); - return(0); + free(bp->text); + free(bp->left); + free(bp->right); + free(bp->top); + free(bp->bottom); + free(bp); } -static int -eqn_do_define(struct eqn_node *ep) +/* + * Allocate a box as the last child of the parent node. + */ +static struct eqn_box * +eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent) +{ + struct eqn_box *bp; + + bp = mandoc_calloc(1, sizeof(struct eqn_box)); + bp->parent = parent; + bp->parent->args++; + bp->expectargs = UINT_MAX; + bp->size = ep->gsize; + + if (NULL != parent->first) { + parent->last->next = bp; + bp->prev = parent->last; + } else + parent->first = bp; + + parent->last = bp; + return(bp); +} + +/* + * Reparent the current last node (of the current parent) under a new + * EQN_SUBEXPR as the first element. + * Then return the new parent. + * The new EQN_SUBEXPR will have a two-child limit. + */ +static struct eqn_box * +eqn_box_makebinary(struct eqn_node *ep, + enum eqn_post pos, struct eqn_box *parent) +{ + struct eqn_box *b, *newb; + + assert(NULL != parent->last); + b = parent->last; + if (parent->last == parent->first) + parent->first = NULL; + parent->args--; + parent->last = b->prev; + b->prev = NULL; + newb = eqn_box_alloc(ep, parent); + newb->pos = pos; + newb->type = EQN_SUBEXPR; + newb->expectargs = 2; + newb->args = 1; + newb->first = newb->last = b; + newb->first->next = NULL; + b->parent = newb; + return(newb); +} + +/* + * Parse the "delim" control statement. + */ +static void +eqn_delim(struct eqn_node *ep) +{ + const char *start; + size_t sz; + + if ((start = eqn_nextrawtok(ep, &sz)) == NULL) + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, "delim"); + else if (strncmp(start, "off", 3) == 0) + ep->delim = 0; + else if (strncmp(start, "on", 2) == 0) { + if (ep->odelim && ep->cdelim) + ep->delim = 1; + } else if (start[1] != '\0') { + ep->odelim = start[0]; + ep->cdelim = start[1]; + ep->delim = 1; + } +} + +/* + * Undefine a previously-defined string. + */ +static void +eqn_undef(struct eqn_node *ep) +{ + const char *start; + struct eqn_def *def; + size_t sz; + + if ((start = eqn_nextrawtok(ep, &sz)) == NULL) { + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, "undef"); + return; + } + if ((def = eqn_def_find(ep, start, sz)) == NULL) + return; + free(def->key); + free(def->val); + def->key = def->val = NULL; + def->keysz = def->valsz = 0; +} + +static void +eqn_def(struct eqn_node *ep) { const char *start; size_t sz; struct eqn_def *def; int i; - if (NULL == (start = eqn_nextrawtok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(0); + if ((start = eqn_nextrawtok(ep, &sz)) == NULL) { + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, "define"); + return; } - /* - * Search for a key that already exists. + /* + * Search for a key that already exists. * Create a new key if none is found. */ - if (NULL == (def = eqn_def_find(ep, start, sz))) { /* Find holes in string array. */ for (i = 0; i < (int)ep->defsz; i++) @@ -865,85 +663,463 @@ eqn_do_define(struct eqn_node *ep) if (i == (int)ep->defsz) { ep->defsz++; - ep->defs = mandoc_realloc - (ep->defs, ep->defsz * - sizeof(struct eqn_def)); + ep->defs = mandoc_reallocarray(ep->defs, + ep->defsz, sizeof(struct eqn_def)); ep->defs[i].key = ep->defs[i].val = NULL; } - ep->defs[i].keysz = sz; - ep->defs[i].key = mandoc_realloc - (ep->defs[i].key, sz + 1); - - memcpy(ep->defs[i].key, start, sz); - ep->defs[i].key[(int)sz] = '\0'; - def = &ep->defs[i]; + def = ep->defs + i; + free(def->key); + def->key = mandoc_strndup(start, sz); + def->keysz = sz; } start = eqn_next(ep, ep->data[(int)ep->cur], &sz, 0); - - if (NULL == start) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(0); + if (start == NULL) { + mandoc_vmsg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, "define %s", def->key); + free(def->key); + free(def->val); + def->key = def->val = NULL; + def->keysz = def->valsz = 0; + return; } - + free(def->val); + def->val = mandoc_strndup(start, sz); def->valsz = sz; - def->val = mandoc_realloc(def->val, sz + 1); - memcpy(def->val, start, sz); - def->val[(int)sz] = '\0'; - return(1); } -static int -eqn_do_gfont(struct eqn_node *ep) +/* + * Recursively parse an eqn(7) expression. + */ +static enum rofferr +eqn_parse(struct eqn_node *ep, struct eqn_box *parent) { + char sym[64]; + struct eqn_box *cur; + const char *start; + char *p; + size_t i, sz; + enum eqn_tok tok, subtok; + enum eqn_post pos; + int size; - if (NULL == eqn_nextrawtok(ep, NULL)) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(0); - } - return(1); -} + assert(parent != NULL); -static int -eqn_do_gsize(struct eqn_node *ep) -{ - const char *start; - size_t sz; + /* + * Empty equation. + * Do not add it to the high-level syntax tree. + */ + + if (ep->data == NULL) + return(ROFF_IGN); + +next_tok: + tok = eqn_tok_parse(ep, &p); + +this_tok: + switch (tok) { + case (EQN_TOK_UNDEF): + eqn_undef(ep); + break; + case (EQN_TOK_NDEFINE): + case (EQN_TOK_DEFINE): + eqn_def(ep); + break; + case (EQN_TOK_TDEFINE): + if (eqn_nextrawtok(ep, NULL) == NULL || + eqn_next(ep, ep->data[(int)ep->cur], NULL, 0) == NULL) + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, "tdefine"); + break; + case (EQN_TOK_DELIM): + eqn_delim(ep); + break; + case (EQN_TOK_GFONT): + if (eqn_nextrawtok(ep, NULL) == NULL) + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + break; + case (EQN_TOK_MARK): + case (EQN_TOK_LINEUP): + /* Ignore these. */ + break; + case (EQN_TOK_DYAD): + case (EQN_TOK_VEC): + case (EQN_TOK_UNDER): + case (EQN_TOK_BAR): + case (EQN_TOK_TILDE): + case (EQN_TOK_HAT): + case (EQN_TOK_DOT): + case (EQN_TOK_DOTDOT): + if (parent->last == NULL) { + mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + cur = eqn_box_alloc(ep, parent); + cur->type = EQN_TEXT; + cur->text = mandoc_strdup(""); + } + parent = eqn_box_makebinary(ep, EQNPOS_NONE, parent); + parent->type = EQN_LISTONE; + parent->expectargs = 1; + switch (tok) { + case (EQN_TOK_DOTDOT): + strlcpy(sym, "\\[ad]", sizeof(sym)); + break; + case (EQN_TOK_VEC): + strlcpy(sym, "\\[->]", sizeof(sym)); + break; + case (EQN_TOK_DYAD): + strlcpy(sym, "\\[<>]", sizeof(sym)); + break; + case (EQN_TOK_TILDE): + strlcpy(sym, "\\[a~]", sizeof(sym)); + break; + case (EQN_TOK_UNDER): + strlcpy(sym, "\\[ul]", sizeof(sym)); + break; + case (EQN_TOK_BAR): + strlcpy(sym, "\\[rl]", sizeof(sym)); + break; + case (EQN_TOK_DOT): + strlcpy(sym, "\\[a.]", sizeof(sym)); + break; + case (EQN_TOK_HAT): + strlcpy(sym, "\\[ha]", sizeof(sym)); + break; + default: + abort(); + } - if (NULL == (start = eqn_nextrawtok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(0); - } - ep->gsize = mandoc_strntoi(start, sz, 10); - return(1); + switch (tok) { + case (EQN_TOK_DOTDOT): + case (EQN_TOK_VEC): + case (EQN_TOK_DYAD): + case (EQN_TOK_TILDE): + case (EQN_TOK_BAR): + case (EQN_TOK_DOT): + case (EQN_TOK_HAT): + parent->top = mandoc_strdup(sym); + break; + case (EQN_TOK_UNDER): + parent->bottom = mandoc_strdup(sym); + break; + default: + abort(); + } + parent = parent->parent; + break; + case (EQN_TOK_FWD): + case (EQN_TOK_BACK): + case (EQN_TOK_DOWN): + case (EQN_TOK_UP): + subtok = eqn_tok_parse(ep, NULL); + if (subtok != EQN_TOK__MAX) { + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + tok = subtok; + goto this_tok; + } + break; + case (EQN_TOK_FAT): + case (EQN_TOK_ROMAN): + case (EQN_TOK_ITALIC): + case (EQN_TOK_BOLD): + while (parent->args == parent->expectargs) + parent = parent->parent; + /* + * These values apply to the next word or sequence of + * words; thus, we mark that we'll have a child with + * exactly one of those. + */ + parent = eqn_box_alloc(ep, parent); + parent->type = EQN_LISTONE; + parent->expectargs = 1; + switch (tok) { + case (EQN_TOK_FAT): + parent->font = EQNFONT_FAT; + break; + case (EQN_TOK_ROMAN): + parent->font = EQNFONT_ROMAN; + break; + case (EQN_TOK_ITALIC): + parent->font = EQNFONT_ITALIC; + break; + case (EQN_TOK_BOLD): + parent->font = EQNFONT_BOLD; + break; + default: + abort(); + } + break; + case (EQN_TOK_SIZE): + case (EQN_TOK_GSIZE): + /* Accept two values: integral size and a single. */ + if (NULL == (start = eqn_nexttok(ep, &sz))) { + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + break; + } + size = mandoc_strntoi(start, sz, 10); + if (-1 == size) { + mandoc_msg(MANDOCERR_IT_NONUM, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + break; + } + if (EQN_TOK_GSIZE == tok) { + ep->gsize = size; + break; + } + parent = eqn_box_alloc(ep, parent); + parent->type = EQN_LISTONE; + parent->expectargs = 1; + parent->size = size; + break; + case (EQN_TOK_FROM): + case (EQN_TOK_TO): + case (EQN_TOK_SUB): + case (EQN_TOK_SUP): + /* + * We have a left-right-associative expression. + * Repivot under a positional node, open a child scope + * and keep on reading. + */ + if (parent->last == NULL) { + mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + cur = eqn_box_alloc(ep, parent); + cur->type = EQN_TEXT; + cur->text = mandoc_strdup(""); + } + /* Handle the "subsup" and "fromto" positions. */ + if (EQN_TOK_SUP == tok && parent->pos == EQNPOS_SUB) { + parent->expectargs = 3; + parent->pos = EQNPOS_SUBSUP; + break; + } + if (EQN_TOK_TO == tok && parent->pos == EQNPOS_FROM) { + parent->expectargs = 3; + parent->pos = EQNPOS_FROMTO; + break; + } + switch (tok) { + case (EQN_TOK_FROM): + pos = EQNPOS_FROM; + break; + case (EQN_TOK_TO): + pos = EQNPOS_TO; + break; + case (EQN_TOK_SUP): + pos = EQNPOS_SUP; + break; + case (EQN_TOK_SUB): + pos = EQNPOS_SUB; + break; + default: + abort(); + } + parent = eqn_box_makebinary(ep, pos, parent); + break; + case (EQN_TOK_SQRT): + while (parent->args == parent->expectargs) + parent = parent->parent; + /* + * Accept a left-right-associative set of arguments just + * like sub and sup and friends but without rebalancing + * under a pivot. + */ + parent = eqn_box_alloc(ep, parent); + parent->type = EQN_SUBEXPR; + parent->pos = EQNPOS_SQRT; + parent->expectargs = 1; + break; + case (EQN_TOK_OVER): + /* + * We have a right-left-associative fraction. + * Close out anything that's currently open, then + * rebalance and continue reading. + */ + if (parent->last == NULL) { + mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + cur = eqn_box_alloc(ep, parent); + cur->type = EQN_TEXT; + cur->text = mandoc_strdup(""); + } + while (EQN_SUBEXPR == parent->type) + parent = parent->parent; + parent = eqn_box_makebinary(ep, EQNPOS_OVER, parent); + break; + case (EQN_TOK_RIGHT): + case (EQN_TOK_BRACE_CLOSE): + /* + * Close out the existing brace. + * FIXME: this is a shitty sentinel: we should really + * have a native EQN_BRACE type or whatnot. + */ + for (cur = parent; cur != NULL; cur = cur->parent) + if (cur->type == EQN_LIST && + (tok == EQN_TOK_BRACE_CLOSE || + cur->left != NULL)) + break; + if (cur == NULL) { + mandoc_msg(MANDOCERR_BLK_NOTOPEN, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + break; + } + parent = cur; + if (EQN_TOK_RIGHT == tok) { + if (NULL == (start = eqn_nexttok(ep, &sz))) { + mandoc_msg(MANDOCERR_REQ_EMPTY, + ep->parse, ep->eqn.ln, + ep->eqn.pos, eqn_toks[tok]); + break; + } + /* Handling depends on right/left. */ + if (STRNEQ(start, sz, "ceiling", 7)) { + strlcpy(sym, "\\[rc]", sizeof(sym)); + parent->right = mandoc_strdup(sym); + } else if (STRNEQ(start, sz, "floor", 5)) { + strlcpy(sym, "\\[rf]", sizeof(sym)); + parent->right = mandoc_strdup(sym); + } else + parent->right = mandoc_strndup(start, sz); + } + parent = parent->parent; + if (EQN_TOK_BRACE_CLOSE == tok && parent && + (parent->type == EQN_PILE || + parent->type == EQN_MATRIX)) + parent = parent->parent; + /* Close out any "singleton" lists. */ + while (parent->type == EQN_LISTONE && + parent->args == parent->expectargs) + parent = parent->parent; + break; + case (EQN_TOK_BRACE_OPEN): + case (EQN_TOK_LEFT): + /* + * If we already have something in the stack and we're + * in an expression, then rewind til we're not any more + * (just like with the text node). + */ + while (parent->args == parent->expectargs) + parent = parent->parent; + if (EQN_TOK_LEFT == tok && + (start = eqn_nexttok(ep, &sz)) == NULL) { + mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + break; + } + parent = eqn_box_alloc(ep, parent); + parent->type = EQN_LIST; + if (EQN_TOK_LEFT == tok) { + if (STRNEQ(start, sz, "ceiling", 7)) { + strlcpy(sym, "\\[lc]", sizeof(sym)); + parent->left = mandoc_strdup(sym); + } else if (STRNEQ(start, sz, "floor", 5)) { + strlcpy(sym, "\\[lf]", sizeof(sym)); + parent->left = mandoc_strdup(sym); + } else + parent->left = mandoc_strndup(start, sz); + } + break; + case (EQN_TOK_PILE): + case (EQN_TOK_LPILE): + case (EQN_TOK_RPILE): + case (EQN_TOK_CPILE): + case (EQN_TOK_CCOL): + case (EQN_TOK_LCOL): + case (EQN_TOK_RCOL): + while (parent->args == parent->expectargs) + parent = parent->parent; + parent = eqn_box_alloc(ep, parent); + parent->type = EQN_PILE; + parent->expectargs = 1; + break; + case (EQN_TOK_ABOVE): + for (cur = parent; cur != NULL; cur = cur->parent) + if (cur->type == EQN_PILE) + break; + if (cur == NULL) { + mandoc_msg(MANDOCERR_IT_STRAY, ep->parse, + ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); + break; + } + parent = eqn_box_alloc(ep, cur); + parent->type = EQN_LIST; + break; + case (EQN_TOK_MATRIX): + while (parent->args == parent->expectargs) + parent = parent->parent; + parent = eqn_box_alloc(ep, parent); + parent->type = EQN_MATRIX; + parent->expectargs = 1; + break; + case (EQN_TOK_EOF): + /* + * End of file! + * TODO: make sure we're not in an open subexpression. + */ + return(ROFF_EQN); + default: + assert(tok == EQN_TOK__MAX); + assert(NULL != p); + /* + * If we already have something in the stack and we're + * in an expression, then rewind til we're not any more. + */ + while (parent->args == parent->expectargs) + parent = parent->parent; + cur = eqn_box_alloc(ep, parent); + cur->type = EQN_TEXT; + for (i = 0; i < EQNSYM__MAX; i++) + if (0 == strcmp(eqnsyms[i].str, p)) { + (void)snprintf(sym, sizeof(sym), + "\\[%s]", eqnsyms[i].sym); + cur->text = mandoc_strdup(sym); + free(p); + break; + } + + if (i == EQNSYM__MAX) + cur->text = p; + /* + * Post-process list status. + */ + while (parent->type == EQN_LISTONE && + parent->args == parent->expectargs) + parent = parent->parent; + break; + } + goto next_tok; } -static int -eqn_do_undef(struct eqn_node *ep) +enum rofferr +eqn_end(struct eqn_node **epp) { - const char *start; - struct eqn_def *def; - size_t sz; + struct eqn_node *ep; - if (NULL == (start = eqn_nextrawtok(ep, &sz))) { - EQN_MSG(MANDOCERR_EQNEOF, ep); - return(0); - } else if (NULL != (def = eqn_def_find(ep, start, sz))) - def->keysz = 0; + ep = *epp; + *epp = NULL; - return(1); + ep->eqn.root = mandoc_calloc(1, sizeof(struct eqn_box)); + ep->eqn.root->expectargs = UINT_MAX; + return(eqn_parse(ep, ep->eqn.root)); } -static struct eqn_def * -eqn_def_find(struct eqn_node *ep, const char *key, size_t sz) +void +eqn_free(struct eqn_node *p) { int i; - for (i = 0; i < (int)ep->defsz; i++) - if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key, - ep->defs[i].keysz, key, sz)) - return(&ep->defs[i]); + eqn_box_free(p->eqn.root); - return(NULL); + for (i = 0; i < (int)p->defsz; i++) { + free(p->defs[i].key); + free(p->defs[i].val); + } + + free(p->data); + free(p->defs); + free(p); } diff --git a/usr/src/cmd/mandoc/eqn_html.c b/usr/src/cmd/mandoc/eqn_html.c index 80c82f1de5..f29733613b 100644 --- a/usr/src/cmd/mandoc/eqn_html.c +++ b/usr/src/cmd/mandoc/eqn_html.c @@ -1,6 +1,6 @@ -/* $Id: eqn_html.c,v 1.2 2011/07/24 10:09:03 kristaps Exp $ */ +/* $Id: eqn_html.c,v 1.10 2014/10/12 19:31:41 schwarze Exp $ */ /* - * Copyright (c) 2011 Kristaps Dzonsons + * Copyright (c) 2011, 2014 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,9 +14,9 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef HAVE_CONFIG_H #include "config.h" -#endif + +#include #include #include @@ -27,16 +27,153 @@ #include "out.h" #include "html.h" -static const enum htmltag fontmap[EQNFONT__MAX] = { - TAG_SPAN, /* EQNFONT_NONE */ - TAG_SPAN, /* EQNFONT_ROMAN */ - TAG_B, /* EQNFONT_BOLD */ - TAG_B, /* EQNFONT_FAT */ - TAG_I /* EQNFONT_ITALIC */ -}; +static void +eqn_box(struct html *p, const struct eqn_box *bp) +{ + struct tag *post, *row, *cell, *t; + struct htmlpair tag[2]; + const struct eqn_box *child, *parent; + size_t i, j, rows; + + if (NULL == bp) + return; + + post = NULL; + + /* + * Special handling for a matrix, which is presented to us in + * column order, but must be printed in row-order. + */ + if (EQN_MATRIX == bp->type) { + if (NULL == bp->first) + goto out; + if (EQN_LIST != bp->first->type) { + eqn_box(p, bp->first); + goto out; + } + if (NULL == (parent = bp->first->first)) + goto out; + /* Estimate the number of rows, first. */ + if (NULL == (child = parent->first)) + goto out; + for (rows = 0; NULL != child; rows++) + child = child->next; + /* Print row-by-row. */ + post = print_otag(p, TAG_MTABLE, 0, NULL); + for (i = 0; i < rows; i++) { + parent = bp->first->first; + row = print_otag(p, TAG_MTR, 0, NULL); + while (NULL != parent) { + child = parent->first; + for (j = 0; j < i; j++) { + if (NULL == child) + break; + child = child->next; + } + cell = print_otag + (p, TAG_MTD, 0, NULL); + /* + * If we have no data for this + * particular cell, then print a + * placeholder and continue--don't puke. + */ + if (NULL != child) + eqn_box(p, child->first); + print_tagq(p, cell); + parent = parent->next; + } + print_tagq(p, row); + } + goto out; + } + + switch (bp->pos) { + case (EQNPOS_TO): + post = print_otag(p, TAG_MOVER, 0, NULL); + break; + case (EQNPOS_SUP): + post = print_otag(p, TAG_MSUP, 0, NULL); + break; + case (EQNPOS_FROM): + post = print_otag(p, TAG_MUNDER, 0, NULL); + break; + case (EQNPOS_SUB): + post = print_otag(p, TAG_MSUB, 0, NULL); + break; + case (EQNPOS_OVER): + post = print_otag(p, TAG_MFRAC, 0, NULL); + break; + case (EQNPOS_FROMTO): + post = print_otag(p, TAG_MUNDEROVER, 0, NULL); + break; + case (EQNPOS_SUBSUP): + post = print_otag(p, TAG_MSUBSUP, 0, NULL); + break; + case (EQNPOS_SQRT): + post = print_otag(p, TAG_MSQRT, 0, NULL); + break; + default: + break; + } + + if (bp->top || bp->bottom) { + assert(NULL == post); + if (bp->top && NULL == bp->bottom) + post = print_otag(p, TAG_MOVER, 0, NULL); + else if (bp->top && bp->bottom) + post = print_otag(p, TAG_MUNDEROVER, 0, NULL); + else if (bp->bottom) + post = print_otag(p, TAG_MUNDER, 0, NULL); + } + + if (EQN_PILE == bp->type) { + assert(NULL == post); + if (bp->first != NULL && bp->first->type == EQN_LIST) + post = print_otag(p, TAG_MTABLE, 0, NULL); + } else if (bp->type == EQN_LIST && + bp->parent && bp->parent->type == EQN_PILE) { + assert(NULL == post); + post = print_otag(p, TAG_MTR, 0, NULL); + print_otag(p, TAG_MTD, 0, NULL); + } + + if (NULL != bp->text) { + assert(NULL == post); + post = print_otag(p, TAG_MI, 0, NULL); + print_text(p, bp->text); + } else if (NULL == post) { + if (NULL != bp->left || NULL != bp->right) { + PAIR_INIT(&tag[0], ATTR_OPEN, + NULL == bp->left ? "" : bp->left); + PAIR_INIT(&tag[1], ATTR_CLOSE, + NULL == bp->right ? "" : bp->right); + post = print_otag(p, TAG_MFENCED, 2, tag); + } + if (NULL == post) + post = print_otag(p, TAG_MROW, 0, NULL); + else + print_otag(p, TAG_MROW, 0, NULL); + } + + eqn_box(p, bp->first); + +out: + if (NULL != bp->bottom) { + t = print_otag(p, TAG_MO, 0, NULL); + print_text(p, bp->bottom); + print_tagq(p, t); + } + if (NULL != bp->top) { + t = print_otag(p, TAG_MO, 0, NULL); + print_text(p, bp->top); + print_tagq(p, t); + } + if (NULL != post) + print_tagq(p, post); -static void eqn_box(struct html *, const struct eqn_box *); + eqn_box(p, bp->next); +} void print_eqn(struct html *p, const struct eqn *ep) @@ -45,7 +182,7 @@ print_eqn(struct html *p, const struct eqn *ep) struct tag *t; PAIR_CLASS_INIT(&tag, "eqn"); - t = print_otag(p, TAG_SPAN, 1, &tag); + t = print_otag(p, TAG_MATH, 1, &tag); p->flags |= HTML_NONOSPACE; eqn_box(p, ep->root); @@ -53,29 +190,3 @@ print_eqn(struct html *p, const struct eqn *ep) print_tagq(p, t); } - -static void -eqn_box(struct html *p, const struct eqn_box *bp) -{ - struct tag *t; - - t = EQNFONT_NONE == bp->font ? NULL : - print_otag(p, fontmap[(int)bp->font], 0, NULL); - - if (bp->left) - print_text(p, bp->left); - - if (bp->text) - print_text(p, bp->text); - - if (bp->first) - eqn_box(p, bp->first); - - if (NULL != t) - print_tagq(p, t); - if (bp->right) - print_text(p, bp->right); - - if (bp->next) - eqn_box(p, bp->next); -} diff --git a/usr/src/cmd/mandoc/eqn_term.c b/usr/src/cmd/mandoc/eqn_term.c index cfbd8d48f8..5f2818b405 100644 --- a/usr/src/cmd/mandoc/eqn_term.c +++ b/usr/src/cmd/mandoc/eqn_term.c @@ -1,6 +1,7 @@ -/* $Id: eqn_term.c,v 1.4 2011/07/24 10:09:03 kristaps Exp $ */ +/* $Id: eqn_term.c,v 1.8 2015/01/01 15:36:08 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons + * Copyright (c) 2014, 2015 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,9 +15,9 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef HAVE_CONFIG_H #include "config.h" -#endif + +#include #include #include @@ -37,40 +38,90 @@ static const enum termfont fontmap[EQNFONT__MAX] = { static void eqn_box(struct termp *, const struct eqn_box *); + void term_eqn(struct termp *p, const struct eqn *ep) { - p->flags |= TERMP_NONOSPACE; eqn_box(p, ep->root); - term_word(p, " "); - p->flags &= ~TERMP_NONOSPACE; + p->flags &= ~TERMP_NOSPACE; } static void eqn_box(struct termp *p, const struct eqn_box *bp) { + const struct eqn_box *child; - if (EQNFONT_NONE != bp->font) + if (bp->type == EQN_LIST || + (bp->type == EQN_PILE && (bp->prev || bp->next)) || + (bp->parent != NULL && bp->parent->pos == EQNPOS_SQRT)) { + if (bp->parent->type == EQN_SUBEXPR && bp->prev != NULL) + p->flags |= TERMP_NOSPACE; + term_word(p, bp->left != NULL ? bp->left : "("); + p->flags |= TERMP_NOSPACE; + } + if (bp->font != EQNFONT_NONE) term_fontpush(p, fontmap[(int)bp->font]); - if (bp->left) - term_word(p, bp->left); - if (EQN_SUBEXPR == bp->type) - term_word(p, "("); - if (bp->text) + if (bp->text != NULL) term_word(p, bp->text); - if (bp->first) + if (bp->pos == EQNPOS_SQRT) { + term_word(p, "sqrt"); + p->flags |= TERMP_NOSPACE; eqn_box(p, bp->first); + } else if (bp->type == EQN_SUBEXPR) { + child = bp->first; + eqn_box(p, child); + p->flags |= TERMP_NOSPACE; + term_word(p, bp->pos == EQNPOS_OVER ? "/" : + (bp->pos == EQNPOS_SUP || + bp->pos == EQNPOS_TO) ? "^" : "_"); + p->flags |= TERMP_NOSPACE; + child = child->next; + if (child != NULL) { + eqn_box(p, child); + if (bp->pos == EQNPOS_FROMTO || + bp->pos == EQNPOS_SUBSUP) { + p->flags |= TERMP_NOSPACE; + term_word(p, "^"); + p->flags |= TERMP_NOSPACE; + child = child->next; + if (child != NULL) + eqn_box(p, child); + } + } + } else { + child = bp->first; + if (bp->type == EQN_MATRIX && child->type == EQN_LIST) + child = child->first; + while (child != NULL) { + eqn_box(p, + bp->type == EQN_PILE && + child->type == EQN_LIST && + child->args == 1 ? + child->first : child); + child = child->next; + } + } - if (EQN_SUBEXPR == bp->type) - term_word(p, ")"); - if (bp->right) - term_word(p, bp->right); - if (EQNFONT_NONE != bp->font) + if (bp->font != EQNFONT_NONE) term_fontpop(p); + if (bp->type == EQN_LIST || + (bp->type == EQN_PILE && (bp->prev || bp->next)) || + (bp->parent != NULL && bp->parent->pos == EQNPOS_SQRT)) { + p->flags |= TERMP_NOSPACE; + term_word(p, bp->right != NULL ? bp->right : ")"); + if (bp->parent->type == EQN_SUBEXPR && bp->next != NULL) + p->flags |= TERMP_NOSPACE; + } - if (bp->next) - eqn_box(p, bp->next); + if (bp->top != NULL) { + p->flags |= TERMP_NOSPACE; + term_word(p, bp->top); + } + if (bp->bottom != NULL) { + p->flags |= TERMP_NOSPACE; + term_word(p, "_"); + } } diff --git a/usr/src/cmd/mandoc/html.c b/usr/src/cmd/mandoc/html.c index 9d28b4270e..487dacda94 100644 --- a/usr/src/cmd/mandoc/html.c +++ b/usr/src/cmd/mandoc/html.c @@ -1,7 +1,7 @@ -/* $Id: html.c,v 1.152 2013/08/08 20:07:47 schwarze Exp $ */ +/* $Id: html.c,v 1.185 2015/01/21 20:33:25 schwarze Exp $ */ /* - * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2011, 2012, 2013 Ingo Schwarze + * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons + * Copyright (c) 2011-2015 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,9 +15,7 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef HAVE_CONFIG_H #include "config.h" -#endif #include @@ -31,7 +29,7 @@ #include #include "mandoc.h" -#include "libmandoc.h" +#include "mandoc_aux.h" #include "out.h" #include "html.h" #include "main.h" @@ -69,17 +67,31 @@ static const struct htmldata htmltags[TAG_MAX] = { {"dt", HTML_CLRLINE}, /* TAG_DT */ {"dd", HTML_CLRLINE}, /* TAG_DD */ {"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */ - {"p", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */ {"pre", HTML_CLRLINE }, /* TAG_PRE */ {"b", 0 }, /* TAG_B */ {"i", 0 }, /* TAG_I */ {"code", 0 }, /* TAG_CODE */ {"small", 0 }, /* TAG_SMALL */ + {"style", HTML_CLRLINE}, /* TAG_STYLE */ + {"math", HTML_CLRLINE}, /* TAG_MATH */ + {"mrow", 0}, /* TAG_MROW */ + {"mi", 0}, /* TAG_MI */ + {"mo", 0}, /* TAG_MO */ + {"msup", 0}, /* TAG_MSUP */ + {"msub", 0}, /* TAG_MSUB */ + {"msubsup", 0}, /* TAG_MSUBSUP */ + {"mfrac", 0}, /* TAG_MFRAC */ + {"msqrt", 0}, /* TAG_MSQRT */ + {"mfenced", 0}, /* TAG_MFENCED */ + {"mtable", 0}, /* TAG_MTABLE */ + {"mtr", 0}, /* TAG_MTR */ + {"mtd", 0}, /* TAG_MTD */ + {"munderover", 0}, /* TAG_MUNDEROVER */ + {"munder", 0}, /* TAG_MUNDER*/ + {"mover", 0}, /* TAG_MOVER*/ }; static const char *const htmlattrs[ATTR_MAX] = { - "http-equiv", /* ATTR_HTTPEQUIV */ - "content", /* ATTR_CONTENT */ "name", /* ATTR_NAME */ "rel", /* ATTR_REL */ "href", /* ATTR_HREF */ @@ -87,11 +99,12 @@ static const char *const htmlattrs[ATTR_MAX] = { "media", /* ATTR_MEDIA */ "class", /* ATTR_CLASS */ "style", /* ATTR_STYLE */ - "width", /* ATTR_WIDTH */ "id", /* ATTR_ID */ - "summary", /* ATTR_SUMMARY */ - "align", /* ATTR_ALIGN */ "colspan", /* ATTR_COLSPAN */ + "charset", /* ATTR_CHARSET */ + "open", /* ATTR_OPEN */ + "close", /* ATTR_CLOSE */ + "mathvariant", /* ATTR_MATHVARIANT */ }; static const char *const roffscales[SCALE_MAX] = { @@ -108,14 +121,15 @@ static const char *const roffscales[SCALE_MAX] = { }; static void bufncat(struct html *, const char *, size_t); -static void print_ctag(struct html *, enum htmltag); +static void print_ctag(struct html *, struct tag *); +static int print_escape(char); static int print_encode(struct html *, const char *, int); static void print_metaf(struct html *, enum mandoc_esc); static void print_attr(struct html *, const char *, const char *); -static void *ml_alloc(char *, enum htmltype); -static void * -ml_alloc(char *outopts, enum htmltype type) + +void * +html_alloc(const struct mchars *mchars, char *outopts) { struct html *h; const char *toks[5]; @@ -129,22 +143,21 @@ ml_alloc(char *outopts, enum htmltype type) h = mandoc_calloc(1, sizeof(struct html)); - h->type = type; h->tags.head = NULL; - h->symtab = mchars_alloc(); + h->symtab = mchars; while (outopts && *outopts) switch (getsubopt(&outopts, UNCONST(toks), &v)) { - case (0): + case 0: h->style = v; break; - case (1): + case 1: h->base_man = v; break; - case (2): + case 2: h->base_includes = v; break; - case (3): + case 3: h->oflags |= HTML_FRAGMENT; break; default: @@ -154,22 +167,6 @@ ml_alloc(char *outopts, enum htmltype type) return(h); } -void * -html_alloc(char *outopts) -{ - - return(ml_alloc(outopts, HTML_HTML_4_01_STRICT)); -} - - -void * -xhtml_alloc(char *outopts) -{ - - return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT)); -} - - void html_free(void *p) { @@ -179,33 +176,34 @@ html_free(void *p) h = (struct html *)p; while ((tag = h->tags.head) != NULL) { - h->tags.head = tag->next; + h->tags.head = tag->next; free(tag); } - - if (h->symtab) - mchars_free(h->symtab); free(h); } - void print_gen_head(struct html *h) { struct htmlpair tag[4]; + struct tag *t; - tag[0].key = ATTR_HTTPEQUIV; - tag[0].val = "Content-Type"; - tag[1].key = ATTR_CONTENT; - tag[1].val = "text/html; charset=utf-8"; - print_otag(h, TAG_META, 2, tag); + tag[0].key = ATTR_CHARSET; + tag[0].val = "utf-8"; + print_otag(h, TAG_META, 1, tag); - tag[0].key = ATTR_NAME; - tag[0].val = "resource-type"; - tag[1].key = ATTR_CONTENT; - tag[1].val = "document"; - print_otag(h, TAG_META, 2, tag); + /* + * Print a default style-sheet. + */ + t = print_otag(h, TAG_STYLE, 0, NULL); + print_text(h, "table.head, table.foot { width: 100%; }\n" + "td.head-rtitle, td.foot-os { text-align: right; }\n" + "td.head-vol { text-align: center; }\n" + "table.foot td { width: 50%; }\n" + "table.head td { width: 33%; }\n" + "div.spacer { margin: 1em 0; }\n"); + print_tagq(h, t); if (h->style) { tag[0].key = ATTR_REL; @@ -226,21 +224,21 @@ print_metaf(struct html *h, enum mandoc_esc deco) enum htmlfont font; switch (deco) { - case (ESCAPE_FONTPREV): + case ESCAPE_FONTPREV: font = h->metal; break; - case (ESCAPE_FONTITALIC): + case ESCAPE_FONTITALIC: font = HTMLFONT_ITALIC; break; - case (ESCAPE_FONTBOLD): + case ESCAPE_FONTBOLD: font = HTMLFONT_BOLD; break; - case (ESCAPE_FONTBI): + case ESCAPE_FONTBI: font = HTMLFONT_BI; break; - case (ESCAPE_FONT): + case ESCAPE_FONT: /* FALLTHROUGH */ - case (ESCAPE_FONTROMAN): + case ESCAPE_FONTROMAN: font = HTMLFONT_NONE; break; default: @@ -257,13 +255,13 @@ print_metaf(struct html *h, enum mandoc_esc deco) h->metac = font; switch (font) { - case (HTMLFONT_ITALIC): + case HTMLFONT_ITALIC: h->metaf = print_otag(h, TAG_I, 0, NULL); break; - case (HTMLFONT_BOLD): + case HTMLFONT_BOLD: h->metaf = print_otag(h, TAG_B, 0, NULL); break; - case (HTMLFONT_BI): + case HTMLFONT_BI: h->metaf = print_otag(h, TAG_B, 0, NULL); print_otag(h, TAG_I, 0, NULL); break; @@ -302,19 +300,21 @@ html_strlen(const char *cp) break; cp++; switch (mandoc_escape(&cp, NULL, NULL)) { - case (ESCAPE_ERROR): + case ESCAPE_ERROR: return(sz); - case (ESCAPE_UNICODE): + case ESCAPE_UNICODE: /* FALLTHROUGH */ - case (ESCAPE_NUMBERED): + case ESCAPE_NUMBERED: /* FALLTHROUGH */ - case (ESCAPE_SPECIAL): + case ESCAPE_SPECIAL: + /* FALLTHROUGH */ + case ESCAPE_OVERSTRIKE: if (skip) skip = 0; else sz++; break; - case (ESCAPE_SKIPCHAR): + case ESCAPE_SKIPCHAR: skip = 1; break; default: @@ -324,6 +324,37 @@ html_strlen(const char *cp) return(sz); } +static int +print_escape(char c) +{ + + switch (c) { + case '<': + printf("<"); + break; + case '>': + printf(">"); + break; + case '&': + printf("&"); + break; + case '"': + printf("""); + break; + case ASCII_NBRSP: + putchar('-'); + break; + case ASCII_HYPH: + putchar('-'); + /* FALLTHROUGH */ + case ASCII_BREAK: + break; + default: + return(0); + } + return(1); +} + static int print_encode(struct html *h, const char *p, int norecurse) { @@ -331,7 +362,8 @@ print_encode(struct html *h, const char *p, int norecurse) int c, len, nospace; const char *seq; enum mandoc_esc esc; - static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' }; + static const char rejs[9] = { '\\', '<', '>', '&', '"', + ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; nospace = 0; @@ -350,43 +382,29 @@ print_encode(struct html *h, const char *p, int norecurse) if ('\0' == *p) break; - switch (*p++) { - case ('<'): - printf("<"); - continue; - case ('>'): - printf(">"); - continue; - case ('&'): - printf("&"); - continue; - case (ASCII_HYPH): - putchar('-'); + if (print_escape(*p++)) continue; - default: - break; - } esc = mandoc_escape(&p, &seq, &len); if (ESCAPE_ERROR == esc) break; switch (esc) { - case (ESCAPE_FONT): + case ESCAPE_FONT: /* FALLTHROUGH */ - case (ESCAPE_FONTPREV): + case ESCAPE_FONTPREV: /* FALLTHROUGH */ - case (ESCAPE_FONTBOLD): + case ESCAPE_FONTBOLD: /* FALLTHROUGH */ - case (ESCAPE_FONTITALIC): + case ESCAPE_FONTITALIC: /* FALLTHROUGH */ - case (ESCAPE_FONTBI): + case ESCAPE_FONTBI: /* FALLTHROUGH */ - case (ESCAPE_FONTROMAN): + case ESCAPE_FONTROMAN: if (0 == norecurse) print_metaf(h, esc); continue; - case (ESCAPE_SKIPCHAR): + case ESCAPE_SKIPCHAR: h->flags |= HTML_SKIPCHAR; continue; default: @@ -399,37 +417,44 @@ print_encode(struct html *h, const char *p, int norecurse) } switch (esc) { - case (ESCAPE_UNICODE): - /* Skip passed "u" header. */ + case ESCAPE_UNICODE: + /* Skip past "u" header. */ c = mchars_num2uc(seq + 1, len - 1); - if ('\0' != c) - printf("&#x%x;", c); break; - case (ESCAPE_NUMBERED): + case ESCAPE_NUMBERED: c = mchars_num2char(seq, len); - if ('\0' != c) - putchar(c); + if (c < 0) + continue; break; - case (ESCAPE_SPECIAL): + case ESCAPE_SPECIAL: c = mchars_spec2cp(h->symtab, seq, len); - if (c > 0) - printf("&#%d;", c); - else if (-1 == c && 1 == len) - putchar((int)*seq); + if (c <= 0) + continue; break; - case (ESCAPE_NOSPACE): + case ESCAPE_NOSPACE: if ('\0' == *p) nospace = 1; + continue; + case ESCAPE_OVERSTRIKE: + if (len == 0) + continue; + c = seq[len - 1]; break; default: - break; + continue; } + if ((c < 0x20 && c != 0x09) || + (c > 0x7E && c < 0xA0)) + c = 0xFFFD; + if (c > 0x7E) + printf("&#%d;", c); + else if ( ! print_escape(c)) + putchar(c); } return(nospace); } - static void print_attr(struct html *h, const char *key, const char *val) { @@ -438,9 +463,8 @@ print_attr(struct html *h, const char *key, const char *val) putchar('\"'); } - struct tag * -print_otag(struct html *h, enum htmltag tag, +print_otag(struct html *h, enum htmltag tag, int sz, const struct htmlpair *p) { int i; @@ -478,24 +502,10 @@ print_otag(struct html *h, enum htmltag tag, for (i = 0; i < sz; i++) print_attr(h, htmlattrs[p[i].key], p[i].val); - /* Add non-overridable attributes. */ - - if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) { - print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml"); - print_attr(h, "xml:lang", "en"); - print_attr(h, "lang", "en"); - } - - /* Accommodate for XML "well-formed" singleton escaping. */ + /* Accommodate for "well-formed" singleton escaping. */ if (HTML_AUTOCLOSE & htmltags[tag].flags) - switch (h->type) { - case (HTML_XHTML_1_0_STRICT): - putchar('/'); - break; - default: - break; - } + putchar('/'); putchar('>'); @@ -507,41 +517,34 @@ print_otag(struct html *h, enum htmltag tag, return(t); } - static void -print_ctag(struct html *h, enum htmltag tag) +print_ctag(struct html *h, struct tag *tag) { - - printf("", htmltags[tag].name); - if (HTML_CLRLINE & htmltags[tag].flags) { + + /* + * Remember to close out and nullify the current + * meta-font and table, if applicable. + */ + if (tag == h->metaf) + h->metaf = NULL; + if (tag == h->tblt) + h->tblt = NULL; + + printf("", htmltags[tag->tag].name); + if (HTML_CLRLINE & htmltags[tag->tag].flags) { h->flags |= HTML_NOSPACE; putchar('\n'); - } + } + + h->tags.head = tag->next; + free(tag); } void print_gen_decls(struct html *h) { - const char *doctype; - const char *dtd; - const char *name; - - switch (h->type) { - case (HTML_HTML_4_01_STRICT): - name = "HTML"; - doctype = "-//W3C//DTD HTML 4.01//EN"; - dtd = "http://www.w3.org/TR/html4/strict.dtd"; - break; - default: - puts(""); - name = "html"; - doctype = "-//W3C//DTD XHTML 1.0 Strict//EN"; - dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"; - break; - } - printf("\n", - name, doctype, dtd); + puts(""); } void @@ -560,13 +563,13 @@ print_text(struct html *h, const char *word) assert(NULL == h->metaf); switch (h->metac) { - case (HTMLFONT_ITALIC): + case HTMLFONT_ITALIC: h->metaf = print_otag(h, TAG_I, 0, NULL); break; - case (HTMLFONT_BOLD): + case HTMLFONT_BOLD: h->metaf = print_otag(h, TAG_B, 0, NULL); break; - case (HTMLFONT_BI): + case HTMLFONT_BI: h->metaf = print_otag(h, TAG_B, 0, NULL); print_otag(h, TAG_I, 0, NULL); break; @@ -578,8 +581,9 @@ print_text(struct html *h, const char *word) if ( ! print_encode(h, word, 0)) { if ( ! (h->flags & HTML_NONOSPACE)) h->flags &= ~HTML_NOSPACE; + h->flags &= ~HTML_NONEWLINE; } else - h->flags |= HTML_NOSPACE; + h->flags |= HTML_NOSPACE | HTML_NONEWLINE; if (h->metaf) { print_tagq(h, h->metaf); @@ -589,30 +593,18 @@ print_text(struct html *h, const char *word) h->flags &= ~HTML_IGNDELIM; } - void print_tagq(struct html *h, const struct tag *until) { struct tag *tag; while ((tag = h->tags.head) != NULL) { - /* - * Remember to close out and nullify the current - * meta-font and table, if applicable. - */ - if (tag == h->metaf) - h->metaf = NULL; - if (tag == h->tblt) - h->tblt = NULL; - print_ctag(h, tag->tag); - h->tags.head = tag->next; - free(tag); + print_ctag(h, tag); if (until && tag == until) return; } } - void print_stagq(struct html *h, const struct tag *suntil) { @@ -621,20 +613,22 @@ print_stagq(struct html *h, const struct tag *suntil) while ((tag = h->tags.head) != NULL) { if (suntil && tag == suntil) return; - /* - * Remember to close out and nullify the current - * meta-font and table, if applicable. - */ - if (tag == h->metaf) - h->metaf = NULL; - if (tag == h->tblt) - h->tblt = NULL; - print_ctag(h, tag->tag); - h->tags.head = tag->next; - free(tag); + print_ctag(h, tag); } } +void +print_paragraph(struct html *h) +{ + struct tag *t; + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "spacer"); + t = print_otag(h, TAG_DIV, 1, &tag); + print_tagq(h, t); +} + + void bufinit(struct html *h) { @@ -657,6 +651,12 @@ void bufcat(struct html *h, const char *p) { + /* + * XXX This is broken and not easy to fix. + * When using the -Oincludes option, buffmt_includes() + * may pass in strings overrunning BUFSIZ, causing a crash. + */ + h->buflen = strlcat(h->buf, p, BUFSIZ); assert(h->buflen < BUFSIZ); } @@ -667,8 +667,8 @@ bufcat_fmt(struct html *h, const char *fmt, ...) va_list ap; va_start(ap, fmt); - (void)vsnprintf(h->buf + (int)h->buflen, - BUFSIZ - h->buflen - 1, fmt, ap); + (void)vsnprintf(h->buf + (int)h->buflen, + BUFSIZ - h->buflen - 1, fmt, ap); va_end(ap); h->buflen = strlen(h->buf); } @@ -688,12 +688,12 @@ buffmt_includes(struct html *h, const char *name) const char *p, *pp; pp = h->base_includes; - + bufinit(h); while (NULL != (p = strchr(pp, '%'))) { bufncat(h, pp, (size_t)(p - pp)); switch (*(p + 1)) { - case('I'): + case'I': bufcat(h, name); break; default: @@ -707,22 +707,21 @@ buffmt_includes(struct html *h, const char *name) } void -buffmt_man(struct html *h, - const char *name, const char *sec) +buffmt_man(struct html *h, const char *name, const char *sec) { const char *p, *pp; pp = h->base_man; - + bufinit(h); while (NULL != (p = strchr(pp, '%'))) { bufncat(h, pp, (size_t)(p - pp)); switch (*(p + 1)) { - case('S'): + case 'S': bufcat(h, sec ? sec : "1"); break; - case('N'): - bufcat_fmt(h, name); + case 'N': + bufcat_fmt(h, "%s", name); break; default: bufncat(h, p, 2); @@ -742,6 +741,8 @@ bufcat_su(struct html *h, const char *p, const struct roffsu *su) v = su->scale; if (SCALE_MM == su->unit && 0.0 == (v /= 100.0)) v = 1.0; + else if (SCALE_BU == su->unit) + v /= 24.0; bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]); } diff --git a/usr/src/cmd/mandoc/html.h b/usr/src/cmd/mandoc/html.h index 894cfc4cff..bbf6183cc5 100644 --- a/usr/src/cmd/mandoc/html.h +++ b/usr/src/cmd/mandoc/html.h @@ -1,6 +1,6 @@ -/* $Id: html.h,v 1.49 2013/08/08 20:07:47 schwarze Exp $ */ +/* $Id: html.h,v 1.70 2014/12/02 10:08:06 schwarze Exp $ */ /* - * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons + * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,10 +14,6 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef HTML_H -#define HTML_H - -__BEGIN_DECLS enum htmltag { TAG_HTML, @@ -44,18 +40,32 @@ enum htmltag { TAG_DT, TAG_DD, TAG_BLOCKQUOTE, - TAG_P, TAG_PRE, TAG_B, TAG_I, TAG_CODE, TAG_SMALL, + TAG_STYLE, + TAG_MATH, + TAG_MROW, + TAG_MI, + TAG_MO, + TAG_MSUP, + TAG_MSUB, + TAG_MSUBSUP, + TAG_MFRAC, + TAG_MSQRT, + TAG_MFENCED, + TAG_MTABLE, + TAG_MTR, + TAG_MTD, + TAG_MUNDEROVER, + TAG_MUNDER, + TAG_MOVER, TAG_MAX }; enum htmlattr { - ATTR_HTTPEQUIV, - ATTR_CONTENT, ATTR_NAME, ATTR_REL, ATTR_HREF, @@ -63,11 +73,12 @@ enum htmlattr { ATTR_MEDIA, ATTR_CLASS, ATTR_STYLE, - ATTR_WIDTH, ATTR_ID, - ATTR_SUMMARY, - ATTR_ALIGN, ATTR_COLSPAN, + ATTR_CHARSET, + ATTR_OPEN, + ATTR_CLOSE, + ATTR_MATHVARIANT, ATTR_MAX }; @@ -103,12 +114,6 @@ struct htmlpair { #define PAIR_CLASS_INIT(p, v) PAIR_INIT(p, ATTR_CLASS, v) #define PAIR_HREF_INIT(p, v) PAIR_INIT(p, ATTR_HREF, v) #define PAIR_STYLE_INIT(p, h) PAIR_INIT(p, ATTR_STYLE, (h)->buf) -#define PAIR_SUMMARY_INIT(p, v) PAIR_INIT(p, ATTR_SUMMARY, v) - -enum htmltype { - HTML_HTML_4_01_STRICT, - HTML_XHTML_1_0_STRICT -}; struct html { int flags; @@ -119,26 +124,33 @@ struct html { #define HTML_NONOSPACE (1 << 4) /* never add spaces */ #define HTML_LITERAL (1 << 5) /* literal (e.g.,
) context */
 #define	HTML_SKIPCHAR	 (1 << 6) /* skip the next character */
+#define	HTML_NOSPLIT	 (1 << 7) /* do not break line before .An */
+#define	HTML_SPLIT	 (1 << 8) /* break line before .An */
+#define	HTML_NONEWLINE	 (1 << 9) /* No line break in nofill mode. */
 	struct tagq	  tags; /* stack of open tags */
 	struct rofftbl	  tbl; /* current table */
 	struct tag	 *tblt; /* current open table scope */
-	struct mchars	 *symtab; /* character-escapes */
+	const struct mchars *symtab; /* character table */
 	char		 *base_man; /* base for manpage href */
 	char		 *base_includes; /* base for include href */
 	char		 *style; /* style-sheet URI */
 	char		  buf[BUFSIZ]; /* see bufcat and friends */
-	size_t		  buflen; 
+	size_t		  buflen;
 	struct tag	 *metaf; /* current open font scope */
 	enum htmlfont	  metal; /* last used font */
 	enum htmlfont	  metac; /* current font mode */
-	enum htmltype	  type; /* output media type */
 	int		  oflags; /* output options */
 #define	HTML_FRAGMENT	 (1 << 0) /* don't emit HTML/HEAD/BODY */
 };
 
+__BEGIN_DECLS
+
+struct	tbl_span;
+struct	eqn;
+
 void		  print_gen_decls(struct html *);
 void		  print_gen_head(struct html *);
-struct tag	 *print_otag(struct html *, enum htmltag, 
+struct tag	 *print_otag(struct html *, enum htmltag,
 				int, const struct htmlpair *);
 void		  print_tagq(struct html *, const struct tag *);
 void		  print_stagq(struct html *, const struct tag *);
@@ -146,21 +158,23 @@ void		  print_text(struct html *, const char *);
 void		  print_tblclose(struct html *);
 void		  print_tbl(struct html *, const struct tbl_span *);
 void		  print_eqn(struct html *, const struct eqn *);
+void		  print_paragraph(struct html *);
 
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 2, 3)))
+#endif
 void		  bufcat_fmt(struct html *, const char *, ...);
 void		  bufcat(struct html *, const char *);
 void		  bufcat_id(struct html *, const char *);
-void		  bufcat_style(struct html *, 
+void		  bufcat_style(struct html *,
 			const char *, const char *);
-void		  bufcat_su(struct html *, const char *, 
+void		  bufcat_su(struct html *, const char *,
 			const struct roffsu *);
 void		  bufinit(struct html *);
-void		  buffmt_man(struct html *, 
+void		  buffmt_man(struct html *,
 			const char *, const char *);
 void		  buffmt_includes(struct html *, const char *);
 
 int		  html_strlen(const char *);
 
 __END_DECLS
-
-#endif /*!HTML_H*/
diff --git a/usr/src/cmd/mandoc/lib.c b/usr/src/cmd/mandoc/lib.c
index 7a18a5dd4f..17ce5296de 100644
--- a/usr/src/cmd/mandoc/lib.c
+++ b/usr/src/cmd/mandoc/lib.c
@@ -1,4 +1,4 @@
-/*	$Id: lib.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*	$Id: lib.c,v 1.11 2014/08/10 23:54:41 schwarze Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  *
@@ -14,16 +14,13 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
-#include 
+#include 
+
 #include 
-#include 
 
 #include "mdoc.h"
-#include "mandoc.h"
 #include "libmdoc.h"
 
 #define LINE(x, y) \
diff --git a/usr/src/cmd/mandoc/libman.h b/usr/src/cmd/mandoc/libman.h
index f2ba6a1256..8d115b3abe 100644
--- a/usr/src/cmd/mandoc/libman.h
+++ b/usr/src/cmd/mandoc/libman.h
@@ -1,6 +1,7 @@
-/*	$Id: libman.h,v 1.56 2012/11/17 00:26:33 schwarze Exp $ */
+/*	$Id: libman.h,v 1.67 2014/12/28 14:42:27 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,8 +15,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef LIBMAN_H
-#define LIBMAN_H
 
 enum	man_next {
 	MAN_NEXT_SIBLING = 0,
@@ -24,13 +23,12 @@ enum	man_next {
 
 struct	man {
 	struct mparse	*parse; /* parse pointer */
+	const char	*defos; /* default OS argument for .TH */
+	int		 quick; /* abort parse early */
 	int		 flags; /* parse flags */
-#define	MAN_HALT	(1 << 0) /* badness happened: die */
 #define	MAN_ELINE	(1 << 1) /* Next-line element scope. */
 #define	MAN_BLINE	(1 << 2) /* Next-line block scope. */
-#define	MAN_ILINE	(1 << 3) /* Ignored in next-line scope. */
 #define	MAN_LITERAL	(1 << 4) /* Literal input. */
-#define	MAN_BPLINE	(1 << 5)
 #define	MAN_NEWLINE	(1 << 6) /* first macro/text in a line */
 	enum man_next	 next; /* where to put the next node */
 	struct man_node	*last; /* the last parsed node */
@@ -47,7 +45,7 @@ struct	man {
 			  char *buf
 
 struct	man_macro {
-	int		(*fp)(MACRO_PROT_ARGS);
+	void		(*fp)(MACRO_PROT_ARGS);
 	int		  flags;
 #define	MAN_SCOPED	 (1 << 0)
 #define	MAN_EXPLICIT	 (1 << 1)	/* See blk_imp(). */
@@ -55,31 +53,24 @@ struct	man_macro {
 #define	MAN_NSCOPED	 (1 << 3)	/* See in_line_eoln(). */
 #define	MAN_NOCLOSE	 (1 << 4)	/* See blk_exp(). */
 #define	MAN_BSCOPE	 (1 << 5)	/* Break BLINE scope. */
+#define	MAN_JOIN	 (1 << 6)	/* Join arguments together. */
 };
 
 extern	const struct man_macro *const man_macros;
 
 __BEGIN_DECLS
 
-#define		  man_pmsg(man, l, p, t) \
-		  mandoc_msg((t), (man)->parse, (l), (p), NULL)
-#define		  man_nmsg(man, n, t) \
-		  mandoc_msg((t), (man)->parse, (n)->line, (n)->pos, NULL)
-int		  man_word_alloc(struct man *, int, int, const char *);
-int		  man_block_alloc(struct man *, int, int, enum mant);
-int		  man_head_alloc(struct man *, int, int, enum mant);
-int		  man_tail_alloc(struct man *, int, int, enum mant);
-int		  man_body_alloc(struct man *, int, int, enum mant);
-int		  man_elem_alloc(struct man *, int, int, enum mant);
+void		  man_word_alloc(struct man *, int, int, const char *);
+void		  man_word_append(struct man *, const char *);
+void		  man_block_alloc(struct man *, int, int, enum mant);
+void		  man_head_alloc(struct man *, int, int, enum mant);
+void		  man_body_alloc(struct man *, int, int, enum mant);
+void		  man_elem_alloc(struct man *, int, int, enum mant);
 void		  man_node_delete(struct man *, struct man_node *);
 void		  man_hash_init(void);
 enum mant	  man_hash_find(const char *);
-int		  man_macroend(struct man *);
-int		  man_valid_post(struct man *);
-int		  man_valid_pre(struct man *, struct man_node *);
-int		  man_unscope(struct man *, 
-			const struct man_node *, enum mandocerr);
+void		  man_macroend(struct man *);
+void		  man_valid_post(struct man *);
+void		  man_unscope(struct man *, const struct man_node *);
 
 __END_DECLS
-
-#endif /*!LIBMAN_H*/
diff --git a/usr/src/cmd/mandoc/libmandoc.h b/usr/src/cmd/mandoc/libmandoc.h
index 3c005e106d..11feebdc10 100644
--- a/usr/src/cmd/mandoc/libmandoc.h
+++ b/usr/src/cmd/mandoc/libmandoc.h
@@ -1,7 +1,7 @@
-/*	$Id: libmandoc.h,v 1.35 2013/12/15 21:23:52 schwarze Exp $ */
+/*	$Id: libmandoc.h,v 1.55 2015/01/15 04:26:39 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons 
- * Copyright (c) 2013 Ingo Schwarze 
+ * Copyright (c) 2013, 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,8 +15,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef LIBMANDOC_H
-#define LIBMANDOC_H
 
 enum	rofferr {
 	ROFF_CONT, /* continue processing line */
@@ -26,63 +24,72 @@ enum	rofferr {
 	ROFF_SO, /* include another file */
 	ROFF_IGN, /* ignore current line */
 	ROFF_TBL, /* a table row was successfully parsed */
-	ROFF_EQN, /* an equation was successfully parsed */
-	ROFF_ERR /* badness: puke and stop */
+	ROFF_EQN /* an equation was successfully parsed */
+};
+
+struct	buf {
+	char	*buf;
+	size_t	 sz;
 };
 
 __BEGIN_DECLS
 
+struct	mparse;
+struct	mchars;
+struct	tbl_span;
+struct	eqn;
 struct	roff;
 struct	mdoc;
 struct	man;
 
-void		 mandoc_msg(enum mandocerr, struct mparse *, 
+void		 mandoc_msg(enum mandocerr, struct mparse *,
 			int, int, const char *);
-void		 mandoc_vmsg(enum mandocerr, struct mparse *, 
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 5, 6)))
+#endif
+void		 mandoc_vmsg(enum mandocerr, struct mparse *,
 			int, int, const char *, ...);
 char		*mandoc_getarg(struct mparse *, char **, int, int *);
 char		*mandoc_normdate(struct mparse *, char *, int, int);
-int		 mandoc_eos(const char *, size_t, int);
+int		 mandoc_eos(const char *, size_t);
 int		 mandoc_strntoi(const char *, size_t, int);
 const char	*mandoc_a2msec(const char*);
 
-void	 	 mdoc_free(struct mdoc *);
-struct	mdoc	*mdoc_alloc(struct roff *, struct mparse *, char *);
+void		 mdoc_free(struct mdoc *);
+struct	mdoc	*mdoc_alloc(struct roff *, struct mparse *,
+			const char *, int);
 void		 mdoc_reset(struct mdoc *);
-int	 	 mdoc_parseln(struct mdoc *, int, char *, int);
-int		 mdoc_endparse(struct mdoc *);
-int		 mdoc_addspan(struct mdoc *, const struct tbl_span *);
-int		 mdoc_addeqn(struct mdoc *, const struct eqn *);
+int		 mdoc_parseln(struct mdoc *, int, char *, int);
+void		 mdoc_endparse(struct mdoc *);
+void		 mdoc_addspan(struct mdoc *, const struct tbl_span *);
+void		 mdoc_addeqn(struct mdoc *, const struct eqn *);
 
-void	 	 man_free(struct man *);
-struct	man	*man_alloc(struct roff *, struct mparse *);
+void		 man_free(struct man *);
+struct	man	*man_alloc(struct roff *, struct mparse *,
+			const char *, int);
 void		 man_reset(struct man *);
-int	 	 man_parseln(struct man *, int, char *, int);
-int		 man_endparse(struct man *);
-int		 man_addspan(struct man *, const struct tbl_span *);
-int		 man_addeqn(struct man *, const struct eqn *);
+int		 man_parseln(struct man *, int, char *, int);
+void		 man_endparse(struct man *);
+void		 man_addspan(struct man *, const struct tbl_span *);
+void		 man_addeqn(struct man *, const struct eqn *);
+
+int		 preconv_cue(const struct buf *, size_t);
+int		 preconv_encode(struct buf *, size_t *,
+			struct buf *, size_t *, int *);
 
-void	 	 roff_free(struct roff *);
-struct roff	*roff_alloc(enum mparset, struct mparse *);
+void		 roff_free(struct roff *);
+struct roff	*roff_alloc(struct mparse *, const struct mchars *, int);
 void		 roff_reset(struct roff *);
-enum rofferr	 roff_parseln(struct roff *, int, 
-			char **, size_t *, int, int *);
+enum rofferr	 roff_parseln(struct roff *, int, struct buf *, int *);
 void		 roff_endparse(struct roff *);
 void		 roff_setreg(struct roff *, const char *, int, char sign);
 int		 roff_getreg(const struct roff *, const char *);
 char		*roff_strdup(const struct roff *, const char *);
-int		 roff_getcontrol(const struct roff *, 
+int		 roff_getcontrol(const struct roff *,
 			const char *, int *);
-#if 0
-char		 roff_eqndelim(const struct roff *);
-void		 roff_openeqn(struct roff *, const char *, 
-			int, int, const char *);
-int		 roff_closeeqn(struct roff *);
-#endif
+int		 roff_getformat(const struct roff *);
 
 const struct tbl_span	*roff_span(const struct roff *);
 const struct eqn	*roff_eqn(const struct roff *);
 
 __END_DECLS
-
-#endif /*!LIBMANDOC_H*/
diff --git a/usr/src/cmd/mandoc/libmdoc.h b/usr/src/cmd/mandoc/libmdoc.h
index 3f14519d3b..527fe02703 100644
--- a/usr/src/cmd/mandoc/libmdoc.h
+++ b/usr/src/cmd/mandoc/libmdoc.h
@@ -1,7 +1,7 @@
-/*	$Id: libmdoc.h,v 1.82 2013/10/21 23:47:58 schwarze Exp $ */
+/*	$Id: libmdoc.h,v 1.97 2015/02/02 04:26:44 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2013 Ingo Schwarze 
+ * Copyright (c) 2013, 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,8 +15,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef LIBMDOC_H
-#define LIBMDOC_H
 
 enum	mdoc_next {
 	MDOC_NEXT_SIBLING = 0,
@@ -25,9 +23,9 @@ enum	mdoc_next {
 
 struct	mdoc {
 	struct mparse	 *parse; /* parse pointer */
-	char		 *defos; /* default argument for .Os */
+	const char	 *defos; /* default argument for .Os */
+	int		  quick; /* abort parse early */
 	int		  flags; /* parse flags */
-#define	MDOC_HALT	 (1 << 0) /* error in parse: halt */
 #define	MDOC_LITERAL	 (1 << 1) /* in a literal scope */
 #define	MDOC_PBODY	 (1 << 2) /* in the document body */
 #define	MDOC_NEWLINE	 (1 << 3) /* first macro/text in a line */
@@ -37,9 +35,11 @@ struct	mdoc {
 #define	MDOC_SYNOPSIS	 (1 << 7) /* SYNOPSIS-style formatting */
 #define	MDOC_KEEP	 (1 << 8) /* in a word keep */
 #define	MDOC_SMOFF	 (1 << 9) /* spacing is off */
+#define	MDOC_NODELIMC	 (1 << 10) /* disable closing delimiter handling */
 	enum mdoc_next	  next; /* where to put the next node */
 	struct mdoc_node *last; /* the last node parsed */
 	struct mdoc_node *first; /* the first node parsed */
+	struct mdoc_node *last_es; /* the most recent Es node */
 	struct mdoc_meta  meta; /* document meta-data */
 	enum mdoc_sec	  lastnamed;
 	enum mdoc_sec	  lastsec;
@@ -54,7 +54,7 @@ struct	mdoc {
 			char *buf
 
 struct	mdoc_macro {
-	int		(*fp)(MACRO_PROT_ARGS);
+	void		(*fp)(MACRO_PROT_ARGS);
 	int		  flags;
 #define	MDOC_CALLABLE	 (1 << 0)
 #define	MDOC_PARSED	 (1 << 1)
@@ -75,13 +75,6 @@ enum	margserr {
 	ARGS_PEND /* last phrase (-column) */
 };
 
-enum	margverr {
-	ARGV_ERROR,
-	ARGV_EOLN, /* end of line */
-	ARGV_ARG, /* valid argument */
-	ARGV_WORD /* normal word (or bad argument---same thing) */
-};
-
 /*
  * A punctuation delimiter is opening, closing, or "middle mark"
  * punctuation.  These govern spacing.
@@ -103,44 +96,34 @@ extern	const struct mdoc_macro *const mdoc_macros;
 
 __BEGIN_DECLS
 
-#define		  mdoc_pmsg(mdoc, l, p, t) \
-		  mandoc_msg((t), (mdoc)->parse, (l), (p), NULL)
-#define		  mdoc_nmsg(mdoc, n, t) \
-		  mandoc_msg((t), (mdoc)->parse, (n)->line, (n)->pos, NULL)
-int		  mdoc_macro(MACRO_PROT_ARGS);
-int		  mdoc_word_alloc(struct mdoc *, 
-			int, int, const char *);
+void		  mdoc_macro(MACRO_PROT_ARGS);
+void		  mdoc_word_alloc(struct mdoc *, int, int, const char *);
 void		  mdoc_word_append(struct mdoc *, const char *);
-int		  mdoc_elem_alloc(struct mdoc *, int, int, 
+void		  mdoc_elem_alloc(struct mdoc *, int, int,
 			enum mdoct, struct mdoc_arg *);
-int		  mdoc_block_alloc(struct mdoc *, int, int, 
+struct mdoc_node *mdoc_block_alloc(struct mdoc *, int, int,
 			enum mdoct, struct mdoc_arg *);
-int		  mdoc_head_alloc(struct mdoc *, int, int, enum mdoct);
-int		  mdoc_tail_alloc(struct mdoc *, int, int, enum mdoct);
-int		  mdoc_body_alloc(struct mdoc *, int, int, enum mdoct);
-int		  mdoc_endbody_alloc(struct mdoc *, int, int, enum mdoct,
+struct mdoc_node *mdoc_head_alloc(struct mdoc *, int, int, enum mdoct);
+void		  mdoc_tail_alloc(struct mdoc *, int, int, enum mdoct);
+struct mdoc_node *mdoc_body_alloc(struct mdoc *, int, int, enum mdoct);
+struct mdoc_node *mdoc_endbody_alloc(struct mdoc *, int, int, enum mdoct,
 			struct mdoc_node *, enum mdoc_endbody);
 void		  mdoc_node_delete(struct mdoc *, struct mdoc_node *);
-int		  mdoc_node_relink(struct mdoc *, struct mdoc_node *);
+void		  mdoc_node_relink(struct mdoc *, struct mdoc_node *);
 void		  mdoc_hash_init(void);
 enum mdoct	  mdoc_hash_find(const char *);
 const char	 *mdoc_a2att(const char *);
 const char	 *mdoc_a2lib(const char *);
 const char	 *mdoc_a2st(const char *);
 const char	 *mdoc_a2arch(const char *);
-const char	 *mdoc_a2vol(const char *);
-int		  mdoc_valid_pre(struct mdoc *, struct mdoc_node *);
-int		  mdoc_valid_post(struct mdoc *);
-enum margverr	  mdoc_argv(struct mdoc *, int, enum mdoct,
+void		  mdoc_valid_pre(struct mdoc *, struct mdoc_node *);
+void		  mdoc_valid_post(struct mdoc *);
+void		  mdoc_argv(struct mdoc *, int, enum mdoct,
 			struct mdoc_arg **, int *, char *);
 void		  mdoc_argv_free(struct mdoc_arg *);
 enum margserr	  mdoc_args(struct mdoc *, int,
 			int *, char *, enum mdoct, char **);
-enum margserr	  mdoc_zargs(struct mdoc *, int, 
-			int *, char *, char **);
-int		  mdoc_macroend(struct mdoc *);
+void		  mdoc_macroend(struct mdoc *);
 enum mdelim	  mdoc_isdelim(const char *);
 
 __END_DECLS
-
-#endif /*!LIBMDOC_H*/
diff --git a/usr/src/cmd/mandoc/libroff.h b/usr/src/cmd/mandoc/libroff.h
index 5b84c5fc45..08ed1f7925 100644
--- a/usr/src/cmd/mandoc/libroff.h
+++ b/usr/src/cmd/mandoc/libroff.h
@@ -1,6 +1,7 @@
-/*	$Id: libroff.h,v 1.28 2013/05/31 21:37:17 schwarze Exp $ */
+/*	$Id: libroff.h,v 1.38 2015/01/30 04:11:50 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,10 +15,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef LIBROFF_H
-#define LIBROFF_H
-
-__BEGIN_DECLS
 
 enum	tbl_part {
 	TBL_PART_OPTS, /* in options (first line) */
@@ -37,22 +34,23 @@ struct	tbl_node {
 	struct tbl_span	 *first_span;
 	struct tbl_span	 *current_span;
 	struct tbl_span	 *last_span;
-	struct tbl_head	 *first_head;
-	struct tbl_head	 *last_head;
 	struct tbl_node	 *next;
 };
 
 struct	eqn_node {
-	struct eqn_def	 *defs;
-	size_t		  defsz;
-	char		 *data;
-	size_t		  rew;
-	size_t		  cur;
-	size_t		  sz;
-	int		  gsize;
-	struct eqn	  eqn;
-	struct mparse	 *parse;
-	struct eqn_node  *next;
+	struct eqn	  eqn;    /* syntax tree of this equation */
+	struct mparse	 *parse;  /* main parser, for error reporting */
+	struct eqn_node  *next;   /* singly linked list of equations */
+	struct eqn_def	 *defs;   /* array of definitions */
+	char		 *data;   /* source code of this equation */
+	size_t		  defsz;  /* number of definitions */
+	size_t		  sz;     /* length of the source code */
+	size_t		  cur;    /* parse point in the source code */
+	size_t		  rew;    /* beginning of the current token */
+	int		  gsize;  /* default point size */
+	int		  delim;  /* in-line delimiters enabled */
+	char		  odelim; /* in-line opening delimiter */
+	char		  cdelim; /* in-line closing delimiter */
 };
 
 struct	eqn_def {
@@ -62,23 +60,23 @@ struct	eqn_def {
 	size_t		  valsz;
 };
 
+__BEGIN_DECLS
+
 struct tbl_node	*tbl_alloc(int, int, struct mparse *);
 void		 tbl_restart(int, int, struct tbl_node *);
 void		 tbl_free(struct tbl_node *);
 void		 tbl_reset(struct tbl_node *);
-enum rofferr 	 tbl_read(struct tbl_node *, int, const char *, int);
-int		 tbl_option(struct tbl_node *, int, const char *);
-int		 tbl_layout(struct tbl_node *, int, const char *);
-int		 tbl_data(struct tbl_node *, int, const char *);
-int		 tbl_cdata(struct tbl_node *, int, const char *);
+enum rofferr	 tbl_read(struct tbl_node *, int, const char *, int);
+void		 tbl_option(struct tbl_node *, int, const char *, int *);
+void		 tbl_layout(struct tbl_node *, int, const char *, int);
+void		 tbl_data(struct tbl_node *, int, const char *, int);
+int		 tbl_cdata(struct tbl_node *, int, const char *, int);
 const struct tbl_span	*tbl_span(struct tbl_node *);
-void		 tbl_end(struct tbl_node **);
-struct eqn_node	*eqn_alloc(const char *, int, int, struct mparse *);
+int		 tbl_end(struct tbl_node **);
+struct eqn_node	*eqn_alloc(int, int, struct mparse *);
 enum rofferr	 eqn_end(struct eqn_node **);
 void		 eqn_free(struct eqn_node *);
-enum rofferr 	 eqn_read(struct eqn_node **, int, 
+enum rofferr	 eqn_read(struct eqn_node **, int,
 			const char *, int, int *);
 
 __END_DECLS
-
-#endif /*LIBROFF_H*/
diff --git a/usr/src/cmd/mandoc/main.c b/usr/src/cmd/mandoc/main.c
index 7e5c7a98ae..f0cd8ca0cc 100644
--- a/usr/src/cmd/mandoc/main.c
+++ b/usr/src/cmd/mandoc/main.c
@@ -1,7 +1,8 @@
-/*	$Id: main.c,v 1.167 2012/11/19 17:22:26 schwarze Exp $ */
+/*	$Id: main.c,v 1.225 2015/03/10 13:50:03 schwarze Exp $ */
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2011, 2012 Ingo Schwarze 
+ * Copyright (c) 2008-2012 Kristaps Dzonsons 
+ * Copyright (c) 2010-2012, 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2010 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,11 +16,17 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
+#include 	/* MACHINE */
+#include 
 
 #include 
+#include 
+#include 
+#include 
+#include 
 #include 
 #include 
 #include 
@@ -27,9 +34,12 @@
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "main.h"
 #include "mdoc.h"
 #include "man.h"
+#include "manpath.h"
+#include "mansearch.h"
 
 #if !defined(__GNUC__) || (__GNUC__ < 2)
 # if !defined(lint)
@@ -37,6 +47,15 @@
 # endif
 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
 
+enum	outmode {
+	OUTMODE_DEF = 0,
+	OUTMODE_FLN,
+	OUTMODE_LST,
+	OUTMODE_ALL,
+	OUTMODE_INT,
+	OUTMODE_ONE
+};
+
 typedef	void		(*out_mdoc)(void *, const struct mdoc *);
 typedef	void		(*out_man)(void *, const struct man *);
 typedef	void		(*out_free)(void *);
@@ -48,7 +67,6 @@ enum	outt {
 	OUTT_TREE,	/* -Ttree */
 	OUTT_MAN,	/* -Tman */
 	OUTT_HTML,	/* -Thtml */
-	OUTT_XHTML,	/* -Txhtml */
 	OUTT_LINT,	/* -Tlint */
 	OUTT_PS,	/* -Tps */
 	OUTT_PDF	/* -Tpdf */
@@ -56,91 +74,342 @@ enum	outt {
 
 struct	curparse {
 	struct mparse	 *mp;
+	struct mchars	 *mchars;	/* character table */
 	enum mandoclevel  wlevel;	/* ignore messages below this */
 	int		  wstop;	/* stop after a file with a warning */
-	enum outt	  outtype; 	/* which output to use */
+	enum outt	  outtype;	/* which output to use */
 	out_mdoc	  outmdoc;	/* mdoc output ptr */
-	out_man	  	  outman;	/* man output ptr */
+	out_man		  outman;	/* man output ptr */
 	out_free	  outfree;	/* free output ptr */
 	void		 *outdata;	/* data for output */
 	char		  outopts[BUFSIZ]; /* buf of output opts */
 };
 
-static	int		  moptions(enum mparset *, char *);
+static	int		  fs_lookup(const struct manpaths *,
+				size_t ipath, const char *,
+				const char *, const char *,
+				struct manpage **, size_t *);
+static	void		  fs_search(const struct mansearch *,
+				const struct manpaths *, int, char**,
+				struct manpage **, size_t *);
+static	int		  koptions(int *, char *);
+#if HAVE_SQLITE3
+int			  mandocdb(int, char**);
+#endif
+static	int		  moptions(int *, char *);
 static	void		  mmsg(enum mandocerr, enum mandoclevel,
 				const char *, int, int, const char *);
-static	void		  parse(struct curparse *, int, 
+static	void		  parse(struct curparse *, int,
 				const char *, enum mandoclevel *);
+static	enum mandoclevel  passthrough(const char *, int, int);
+static	pid_t		  spawn_pager(void);
 static	int		  toptions(struct curparse *, char *);
-static	void		  usage(void) __attribute__((noreturn));
-static	void		  version(void) __attribute__((noreturn));
+static	void		  usage(enum argmode) __attribute__((noreturn));
 static	int		  woptions(struct curparse *, char *);
 
+static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
+static	char		  help_arg[] = "help";
+static	char		 *help_argv[] = {help_arg, NULL};
 static	const char	 *progname;
 
+
 int
 main(int argc, char *argv[])
 {
-	int		 c;
 	struct curparse	 curp;
-	enum mparset	 type;
-	enum mandoclevel rc;
+	struct mansearch search;
+	struct manpaths	 paths;
+	char		*auxpaths;
 	char		*defos;
+	unsigned char	*uc;
+	struct manpage	*res, *resp;
+	char		*conf_file, *defpaths;
+	size_t		 isec, i, sz;
+	int		 prio, best_prio, synopsis_only;
+	char		 sec;
+	enum mandoclevel rc, rctmp;
+	enum outmode	 outmode;
+	int		 fd;
+	int		 show_usage;
+	int		 options;
+	int		 c;
+	pid_t		 pager_pid;  /* 0: don't use; 1: not yet spawned. */
 
-	progname = strrchr(argv[0], '/');
-	if (progname == NULL)
+	if (argc < 1)
+		progname = "mandoc";
+	else if ((progname = strrchr(argv[0], '/')) == NULL)
 		progname = argv[0];
 	else
 		++progname;
 
-	memset(&curp, 0, sizeof(struct curparse));
+#if HAVE_SQLITE3
+	if (strcmp(progname, BINM_MAKEWHATIS) == 0)
+		return(mandocdb(argc, argv));
+#endif
+
+	/* Search options. */
 
-	type = MPARSE_AUTO;
-	curp.outtype = OUTT_ASCII;
-	curp.wlevel  = MANDOCLEVEL_FATAL;
+	memset(&paths, 0, sizeof(struct manpaths));
+	conf_file = defpaths = NULL;
+	auxpaths = NULL;
+
+	memset(&search, 0, sizeof(struct mansearch));
+	search.outkey = "Nd";
+
+	if (strcmp(progname, BINM_MAN) == 0)
+		search.argmode = ARG_NAME;
+	else if (strcmp(progname, BINM_APROPOS) == 0)
+		search.argmode = ARG_EXPR;
+	else if (strcmp(progname, BINM_WHATIS) == 0)
+		search.argmode = ARG_WORD;
+	else if (strncmp(progname, "help", 4) == 0)
+		search.argmode = ARG_NAME;
+	else
+		search.argmode = ARG_FILE;
+
+	/* Parser and formatter options. */
+
+	memset(&curp, 0, sizeof(struct curparse));
+	curp.outtype = OUTT_LOCALE;
+	curp.wlevel  = MANDOCLEVEL_BADARG;
+	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
 	defos = NULL;
 
-	/* LINTED */
-	while (-1 != (c = getopt(argc, argv, "I:m:O:T:VW:")))
+	pager_pid = 1;
+	show_usage = 0;
+	synopsis_only = 0;
+	outmode = OUTMODE_DEF;
+
+	while (-1 != (c = getopt(argc, argv,
+			"aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
 		switch (c) {
-		case ('I'):
+		case 'a':
+			outmode = OUTMODE_ALL;
+			break;
+		case 'C':
+			conf_file = optarg;
+			break;
+		case 'c':
+			pager_pid = 0;
+			break;
+		case 'f':
+			search.argmode = ARG_WORD;
+			break;
+		case 'h':
+			(void)strlcat(curp.outopts, "synopsis,", BUFSIZ);
+			synopsis_only = 1;
+			pager_pid = 0;
+			outmode = OUTMODE_ALL;
+			break;
+		case 'I':
 			if (strncmp(optarg, "os=", 3)) {
-				fprintf(stderr, "-I%s: Bad argument\n",
-						optarg);
+				fprintf(stderr,
+				    "%s: -I %s: Bad argument\n",
+				    progname, optarg);
 				return((int)MANDOCLEVEL_BADARG);
 			}
 			if (defos) {
-				fprintf(stderr, "-I%s: Duplicate argument\n",
-						optarg);
+				fprintf(stderr,
+				    "%s: -I %s: Duplicate argument\n",
+				    progname, optarg);
 				return((int)MANDOCLEVEL_BADARG);
 			}
 			defos = mandoc_strdup(optarg + 3);
 			break;
-		case ('m'):
-			if ( ! moptions(&type, optarg))
+		case 'i':
+			outmode = OUTMODE_INT;
+			break;
+		case 'K':
+			if ( ! koptions(&options, optarg))
 				return((int)MANDOCLEVEL_BADARG);
 			break;
-		case ('O'):
+		case 'k':
+			search.argmode = ARG_EXPR;
+			break;
+		case 'l':
+			search.argmode = ARG_FILE;
+			outmode = OUTMODE_ALL;
+			break;
+		case 'M':
+			defpaths = optarg;
+			break;
+		case 'm':
+			auxpaths = optarg;
+			break;
+		case 'O':
+			search.outkey = optarg;
 			(void)strlcat(curp.outopts, optarg, BUFSIZ);
 			(void)strlcat(curp.outopts, ",", BUFSIZ);
 			break;
-		case ('T'):
+		case 'S':
+			search.arch = optarg;
+			break;
+		case 's':
+			search.sec = optarg;
+			break;
+		case 'T':
 			if ( ! toptions(&curp, optarg))
 				return((int)MANDOCLEVEL_BADARG);
 			break;
-		case ('W'):
+		case 'W':
 			if ( ! woptions(&curp, optarg))
 				return((int)MANDOCLEVEL_BADARG);
 			break;
-		case ('V'):
-			version();
-			/* NOTREACHED */
+		case 'w':
+			outmode = OUTMODE_FLN;
+			break;
 		default:
-			usage();
-			/* NOTREACHED */
+			show_usage = 1;
+			break;
 		}
+	}
+
+	if (show_usage)
+		usage(search.argmode);
 
-	curp.mp = mparse_alloc(type, curp.wlevel, mmsg, &curp, defos);
+	/* Postprocess options. */
+
+	if (outmode == OUTMODE_DEF) {
+		switch (search.argmode) {
+		case ARG_FILE:
+			outmode = OUTMODE_ALL;
+			pager_pid = 0;
+			break;
+		case ARG_NAME:
+			outmode = OUTMODE_ONE;
+			break;
+		default:
+			outmode = OUTMODE_LST;
+			break;
+		}
+	}
+
+	/* Parse arguments. */
+
+	if (argc > 0) {
+		argc -= optind;
+		argv += optind;
+	}
+	resp = NULL;
+
+	/*
+	 * Quirks for help(1)
+	 * and for a man(1) section argument without -s.
+	 */
+
+	if (search.argmode == ARG_NAME) {
+		if (*progname == 'h') {
+			if (argc == 0) {
+				argv = help_argv;
+				argc = 1;
+			}
+		} else if (argc > 1 &&
+		    ((uc = (unsigned char *)argv[0]) != NULL) &&
+		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
+		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
+		     (uc[0] == 'n' && uc[1] == '\0'))) {
+			search.sec = (char *)uc;
+			argv++;
+			argc--;
+		}
+		if (search.arch == NULL)
+			search.arch = getenv("MACHINE");
+#ifdef MACHINE
+		if (search.arch == NULL)
+			search.arch = MACHINE;
+#endif
+	}
+
+	rc = MANDOCLEVEL_OK;
+
+	/* man(1), whatis(1), apropos(1) */
+
+	if (search.argmode != ARG_FILE) {
+		if (argc == 0)
+			usage(search.argmode);
+
+		if (search.argmode == ARG_NAME &&
+		    outmode == OUTMODE_ONE)
+			search.firstmatch = 1;
+
+		/* Access the mandoc database. */
+
+		manpath_parse(&paths, conf_file, defpaths, auxpaths);
+#if HAVE_SQLITE3
+		mansearch_setup(1);
+		if( ! mansearch(&search, &paths, argc, argv, &res, &sz))
+			usage(search.argmode);
+#else
+		if (search.argmode != ARG_NAME) {
+			fputs("mandoc: database support not compiled in\n",
+			    stderr);
+			return((int)MANDOCLEVEL_BADARG);
+		}
+		sz = 0;
+#endif
+
+		if (sz == 0 && search.argmode == ARG_NAME)
+			fs_search(&search, &paths, argc, argv, &res, &sz);
+
+		if (sz == 0) {
+			rc = MANDOCLEVEL_BADARG;
+			goto out;
+		}
+
+		/*
+		 * For standard man(1) and -a output mode,
+		 * prepare for copying filename pointers
+		 * into the program parameter array.
+		 */
+
+		if (outmode == OUTMODE_ONE) {
+			argc = 1;
+			best_prio = 10;
+		} else if (outmode == OUTMODE_ALL)
+			argc = (int)sz;
+
+		/* Iterate all matching manuals. */
+
+		resp = res;
+		for (i = 0; i < sz; i++) {
+			if (outmode == OUTMODE_FLN)
+				puts(res[i].file);
+			else if (outmode == OUTMODE_LST)
+				printf("%s - %s\n", res[i].names,
+				    res[i].output == NULL ? "" :
+				    res[i].output);
+			else if (outmode == OUTMODE_ONE) {
+				/* Search for the best section. */
+				isec = strcspn(res[i].file, "123456789");
+				sec = res[i].file[isec];
+				if ('\0' == sec)
+					continue;
+				prio = sec_prios[sec - '1'];
+				if (prio >= best_prio)
+					continue;
+				best_prio = prio;
+				resp = res + i;
+			}
+		}
+
+		/*
+		 * For man(1), -a and -i output mode, fall through
+		 * to the main mandoc(1) code iterating files
+		 * and running the parsers on each of them.
+		 */
+
+		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
+			goto out;
+	}
+
+	/* mandoc(1) */
+
+	if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths))
+		return((int)MANDOCLEVEL_BADARG);
+
+	curp.mchars = mchars_alloc();
+	curp.mp = mparse_alloc(options, curp.wlevel, mmsg,
+	    curp.mchars, defos);
 
 	/*
 	 * Conditionally start up the lookaside buffer before parsing.
@@ -148,58 +417,220 @@ main(int argc, char *argv[])
 	if (OUTT_MAN == curp.outtype)
 		mparse_keep(curp.mp);
 
-	argc -= optind;
-	argv += optind;
+	if (argc < 1) {
+		if (pager_pid == 1 && isatty(STDOUT_FILENO))
+			pager_pid = spawn_pager();
+		parse(&curp, STDIN_FILENO, "", &rc);
+	}
 
-	rc = MANDOCLEVEL_OK;
+	while (argc > 0) {
+		rctmp = mparse_open(curp.mp, &fd,
+		    resp != NULL ? resp->file : *argv);
+		if (rc < rctmp)
+			rc = rctmp;
+
+		if (fd != -1) {
+			if (pager_pid == 1 && isatty(STDOUT_FILENO))
+				pager_pid = spawn_pager();
+
+			if (resp == NULL)
+				parse(&curp, fd, *argv, &rc);
+			else if (resp->form & FORM_SRC) {
+				/* For .so only; ignore failure. */
+				chdir(paths.paths[resp->ipath]);
+				parse(&curp, fd, resp->file, &rc);
+			} else {
+				rctmp = passthrough(resp->file, fd,
+				    synopsis_only);
+				if (rc < rctmp)
+					rc = rctmp;
+			}
 
-	if (NULL == *argv)
-		parse(&curp, STDIN_FILENO, "", &rc);
+			rctmp = mparse_wait(curp.mp);
+			if (rc < rctmp)
+				rc = rctmp;
+
+			if (argc > 1 && curp.outtype <= OUTT_UTF8)
+				ascii_sepline(curp.outdata);
+		}
 
-	while (*argv) {
-		parse(&curp, -1, *argv, &rc);
 		if (MANDOCLEVEL_OK != rc && curp.wstop)
 			break;
-		++argv;
+
+		if (resp != NULL)
+			resp++;
+		else
+			argv++;
+		if (--argc)
+			mparse_reset(curp.mp);
 	}
 
 	if (curp.outfree)
 		(*curp.outfree)(curp.outdata);
-	if (curp.mp)
-		mparse_free(curp.mp);
+	mparse_free(curp.mp);
+	mchars_free(curp.mchars);
+
+out:
+	if (search.argmode != ARG_FILE) {
+		manpath_free(&paths);
+#if HAVE_SQLITE3
+		mansearch_free(res, sz);
+		mansearch_setup(0);
+#endif
+	}
+
 	free(defos);
 
+	/*
+	 * If a pager is attached, flush the pipe leading to it
+	 * and signal end of file such that the user can browse
+	 * to the end.  Then wait for the user to close the pager.
+	 */
+
+	if (pager_pid != 0 && pager_pid != 1) {
+		fclose(stdout);
+		waitpid(pager_pid, NULL, 0);
+	}
+
 	return((int)rc);
 }
 
 static void
-version(void)
+usage(enum argmode argmode)
 {
 
-	printf("%s %s\n", progname, VERSION);
-	exit((int)MANDOCLEVEL_OK);
+	switch (argmode) {
+	case ARG_FILE:
+		fputs("usage: mandoc [-acfhkl] [-Ios=name] "
+		    "[-Kencoding] [-mformat] [-Ooption]\n"
+		    "\t      [-Toutput] [-Wlevel] [file ...]\n", stderr);
+		break;
+	case ARG_NAME:
+		fputs("usage: man [-acfhklw] [-C file] [-I os=name] "
+		    "[-K encoding] [-M path] [-m path]\n"
+		    "\t   [-O option=value] [-S subsection] [-s section] "
+		    "[-T output] [-W level]\n"
+		    "\t   [section] name ...\n", stderr);
+		break;
+	case ARG_WORD:
+		fputs("usage: whatis [-acfhklw] [-C file] "
+		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
+		    "\t      [-s section] name ...\n", stderr);
+		break;
+	case ARG_EXPR:
+		fputs("usage: apropos [-acfhklw] [-C file] "
+		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
+		    "\t       [-s section] expression ...\n", stderr);
+		break;
+	}
+	exit((int)MANDOCLEVEL_BADARG);
 }
 
-static void
-usage(void)
+static int
+fs_lookup(const struct manpaths *paths, size_t ipath,
+	const char *sec, const char *arch, const char *name,
+	struct manpage **res, size_t *ressz)
 {
+	glob_t		 globinfo;
+	struct manpage	*page;
+	char		*file;
+	int		 form, globres;
+
+	form = FORM_SRC;
+	mandoc_asprintf(&file, "%s/man%s/%s.%s",
+	    paths->paths[ipath], sec, name, sec);
+	if (access(file, R_OK) != -1)
+		goto found;
+	free(file);
+
+	mandoc_asprintf(&file, "%s/cat%s/%s.0",
+	    paths->paths[ipath], sec, name);
+	if (access(file, R_OK) != -1) {
+		form = FORM_CAT;
+		goto found;
+	}
+	free(file);
+
+	if (arch != NULL) {
+		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
+		    paths->paths[ipath], sec, arch, name, sec);
+		if (access(file, R_OK) != -1)
+			goto found;
+		free(file);
+	}
+
+	mandoc_asprintf(&file, "%s/man%s/%s.*",
+	    paths->paths[ipath], sec, name);
+	globres = glob(file, 0, NULL, &globinfo);
+	if (globres != 0 && globres != GLOB_NOMATCH)
+		fprintf(stderr, "%s: %s: glob: %s\n",
+		    progname, file, strerror(errno));
+	free(file);
+	if (globres == 0)
+		file = mandoc_strdup(*globinfo.gl_pathv);
+	globfree(&globinfo);
+	if (globres != 0)
+		return(0);
 
-	fprintf(stderr, "usage: %s "
-			"[-V] "
-			"[-Ios=name] "
-			"[-mformat] "
-			"[-Ooption] "
-			"[-Toutput] "
-			"[-Wlevel]\n"
-			"\t      [file ...]\n", 
-			progname);
+found:
+#if HAVE_SQLITE3
+	fprintf(stderr, "%s: outdated mandoc.db lacks %s(%s) entry,\n"
+	    "     consider running  # makewhatis %s\n",
+	    progname, name, sec, paths->paths[ipath]);
+#endif
 
-	exit((int)MANDOCLEVEL_BADARG);
+	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
+	page = *res + (*ressz - 1);
+	page->file = file;
+	page->names = NULL;
+	page->output = NULL;
+	page->ipath = ipath;
+	page->bits = NAME_FILE & NAME_MASK;
+	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
+	page->form = form;
+	return(1);
+}
+
+static void
+fs_search(const struct mansearch *cfg, const struct manpaths *paths,
+	int argc, char **argv, struct manpage **res, size_t *ressz)
+{
+	const char *const sections[] =
+	    {"1", "8", "6", "2", "3", "3p", "5", "7", "4", "9"};
+	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
+
+	size_t		 ipath, isec, lastsz;
+
+	assert(cfg->argmode == ARG_NAME);
+
+	*res = NULL;
+	*ressz = lastsz = 0;
+	while (argc) {
+		for (ipath = 0; ipath < paths->sz; ipath++) {
+			if (cfg->sec != NULL) {
+				if (fs_lookup(paths, ipath, cfg->sec,
+				    cfg->arch, *argv, res, ressz) &&
+				    cfg->firstmatch)
+					return;
+			} else for (isec = 0; isec < nsec; isec++)
+				if (fs_lookup(paths, ipath, sections[isec],
+				    cfg->arch, *argv, res, ressz) &&
+				    cfg->firstmatch)
+					return;
+		}
+		if (*ressz == lastsz)
+			fprintf(stderr,
+			    "%s: No entry for %s in the manual.\n",
+			    progname, *argv);
+		lastsz = *ressz;
+		argv++;
+		argc--;
+	}
 }
 
 static void
-parse(struct curparse *curp, int fd, 
-		const char *file, enum mandoclevel *level)
+parse(struct curparse *curp, int fd, const char *file,
+	enum mandoclevel *level)
 {
 	enum mandoclevel  rc;
 	struct mdoc	 *mdoc;
@@ -212,11 +643,6 @@ parse(struct curparse *curp, int fd,
 
 	rc = mparse_readfd(curp->mp, fd, file);
 
-	/* Stop immediately if the parse has failed. */
-
-	if (MANDOCLEVEL_FATAL <= rc)
-		goto cleanup;
-
 	/*
 	 * With -Wstop and warnings or errors of at least the requested
 	 * level, do not produce output.
@@ -229,32 +655,34 @@ parse(struct curparse *curp, int fd,
 
 	if ( ! (curp->outman && curp->outmdoc)) {
 		switch (curp->outtype) {
-		case (OUTT_XHTML):
-			curp->outdata = xhtml_alloc(curp->outopts);
-			curp->outfree = html_free;
-			break;
-		case (OUTT_HTML):
-			curp->outdata = html_alloc(curp->outopts);
+		case OUTT_HTML:
+			curp->outdata = html_alloc(curp->mchars,
+			    curp->outopts);
 			curp->outfree = html_free;
 			break;
-		case (OUTT_UTF8):
-			curp->outdata = utf8_alloc(curp->outopts);
+		case OUTT_UTF8:
+			curp->outdata = utf8_alloc(curp->mchars,
+			    curp->outopts);
 			curp->outfree = ascii_free;
 			break;
-		case (OUTT_LOCALE):
-			curp->outdata = locale_alloc(curp->outopts);
+		case OUTT_LOCALE:
+			curp->outdata = locale_alloc(curp->mchars,
+			    curp->outopts);
 			curp->outfree = ascii_free;
 			break;
-		case (OUTT_ASCII):
-			curp->outdata = ascii_alloc(curp->outopts);
+		case OUTT_ASCII:
+			curp->outdata = ascii_alloc(curp->mchars,
+			    curp->outopts);
 			curp->outfree = ascii_free;
 			break;
-		case (OUTT_PDF):
-			curp->outdata = pdf_alloc(curp->outopts);
+		case OUTT_PDF:
+			curp->outdata = pdf_alloc(curp->mchars,
+			    curp->outopts);
 			curp->outfree = pspdf_free;
 			break;
-		case (OUTT_PS):
-			curp->outdata = ps_alloc(curp->outopts);
+		case OUTT_PS:
+			curp->outdata = ps_alloc(curp->mchars,
+			    curp->outopts);
 			curp->outfree = pspdf_free;
 			break;
 		default:
@@ -262,29 +690,27 @@ parse(struct curparse *curp, int fd,
 		}
 
 		switch (curp->outtype) {
-		case (OUTT_HTML):
-			/* FALLTHROUGH */
-		case (OUTT_XHTML):
+		case OUTT_HTML:
 			curp->outman = html_man;
 			curp->outmdoc = html_mdoc;
 			break;
-		case (OUTT_TREE):
+		case OUTT_TREE:
 			curp->outman = tree_man;
 			curp->outmdoc = tree_mdoc;
 			break;
-		case (OUTT_MAN):
+		case OUTT_MAN:
 			curp->outmdoc = man_mdoc;
 			curp->outman = man_man;
 			break;
-		case (OUTT_PDF):
+		case OUTT_PDF:
 			/* FALLTHROUGH */
-		case (OUTT_ASCII):
+		case OUTT_ASCII:
 			/* FALLTHROUGH */
-		case (OUTT_UTF8):
+		case OUTT_UTF8:
 			/* FALLTHROUGH */
-		case (OUTT_LOCALE):
+		case OUTT_LOCALE:
 			/* FALLTHROUGH */
-		case (OUTT_PS):
+		case OUTT_PS:
 			curp->outman = terminal_man;
 			curp->outmdoc = terminal_mdoc;
 			break;
@@ -293,7 +719,7 @@ parse(struct curparse *curp, int fd,
 		}
 	}
 
-	mparse_result(curp->mp, &mdoc, &man);
+	mparse_result(curp->mp, &mdoc, &man, NULL);
 
 	/* Execute the out device, if it exists. */
 
@@ -302,26 +728,112 @@ parse(struct curparse *curp, int fd,
 	if (mdoc && curp->outmdoc)
 		(*curp->outmdoc)(curp->outdata, mdoc);
 
- cleanup:
-
-	mparse_reset(curp->mp);
-
+cleanup:
 	if (*level < rc)
 		*level = rc;
 }
 
+static enum mandoclevel
+passthrough(const char *file, int fd, int synopsis_only)
+{
+	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
+	const char	 synr[] = "SYNOPSIS";
+
+	FILE		*stream;
+	const char	*syscall;
+	char		*line;
+	size_t		 len, off;
+	ssize_t		 nw;
+	int		 print;
+
+	fflush(stdout);
+
+	if ((stream = fdopen(fd, "r")) == NULL) {
+		close(fd);
+		syscall = "fdopen";
+		goto fail;
+	}
+
+	print = 0;
+	while ((line = fgetln(stream, &len)) != NULL) {
+		if (synopsis_only) {
+			if (print) {
+				if ( ! isspace((unsigned char)*line))
+					goto done;
+				while (len &&
+				    isspace((unsigned char)*line)) {
+					line++;
+					len--;
+				}
+			} else {
+				if ((len == sizeof(synb) &&
+				     ! strncmp(line, synb, len - 1)) ||
+				    (len == sizeof(synr) &&
+				     ! strncmp(line, synr, len - 1)))
+					print = 1;
+				continue;
+			}
+		}
+		for (off = 0; off < len; off += nw)
+			if ((nw = write(STDOUT_FILENO, line + off,
+			    len - off)) == -1 || nw == 0) {
+				fclose(stream);
+				syscall = "write";
+				goto fail;
+			}
+	}
+
+	if (ferror(stream)) {
+		fclose(stream);
+		syscall = "fgetln";
+		goto fail;
+	}
+
+done:
+	fclose(stream);
+	return(MANDOCLEVEL_OK);
+
+fail:
+	fprintf(stderr, "%s: %s: SYSERR: %s: %s",
+	    progname, file, syscall, strerror(errno));
+	return(MANDOCLEVEL_SYSERR);
+}
+
+static int
+koptions(int *options, char *arg)
+{
+
+	if ( ! strcmp(arg, "utf-8")) {
+		*options |=  MPARSE_UTF8;
+		*options &= ~MPARSE_LATIN1;
+	} else if ( ! strcmp(arg, "iso-8859-1")) {
+		*options |=  MPARSE_LATIN1;
+		*options &= ~MPARSE_UTF8;
+	} else if ( ! strcmp(arg, "us-ascii")) {
+		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
+	} else {
+		fprintf(stderr, "%s: -K %s: Bad argument\n",
+		    progname, arg);
+		return(0);
+	}
+	return(1);
+}
+
 static int
-moptions(enum mparset *tflags, char *arg)
+moptions(int *options, char *arg)
 {
 
-	if (0 == strcmp(arg, "doc"))
-		*tflags = MPARSE_MDOC;
+	if (arg == NULL)
+		/* nothing to do */;
+	else if (0 == strcmp(arg, "doc"))
+		*options |= MPARSE_MDOC;
 	else if (0 == strcmp(arg, "andoc"))
-		*tflags = MPARSE_AUTO;
+		/* nothing to do */;
 	else if (0 == strcmp(arg, "an"))
-		*tflags = MPARSE_MAN;
+		*options |= MPARSE_MAN;
 	else {
-		fprintf(stderr, "%s: Bad argument\n", arg);
+		fprintf(stderr, "%s: -m %s: Bad argument\n",
+		    progname, arg);
 		return(0);
 	}
 
@@ -348,13 +860,14 @@ toptions(struct curparse *curp, char *arg)
 	else if (0 == strcmp(arg, "locale"))
 		curp->outtype = OUTT_LOCALE;
 	else if (0 == strcmp(arg, "xhtml"))
-		curp->outtype = OUTT_XHTML;
+		curp->outtype = OUTT_HTML;
 	else if (0 == strcmp(arg, "ps"))
 		curp->outtype = OUTT_PS;
 	else if (0 == strcmp(arg, "pdf"))
 		curp->outtype = OUTT_PDF;
 	else {
-		fprintf(stderr, "%s: Bad argument\n", arg);
+		fprintf(stderr, "%s: -T %s: Bad argument\n",
+		    progname, arg);
 		return(0);
 	}
 
@@ -365,34 +878,39 @@ static int
 woptions(struct curparse *curp, char *arg)
 {
 	char		*v, *o;
-	const char	*toks[6]; 
+	const char	*toks[7];
 
 	toks[0] = "stop";
 	toks[1] = "all";
 	toks[2] = "warning";
 	toks[3] = "error";
-	toks[4] = "fatal";
-	toks[5] = NULL;
+	toks[4] = "unsupp";
+	toks[5] = "fatal";
+	toks[6] = NULL;
 
 	while (*arg) {
 		o = arg;
 		switch (getsubopt(&arg, UNCONST(toks), &v)) {
-		case (0):
+		case 0:
 			curp->wstop = 1;
 			break;
-		case (1):
+		case 1:
 			/* FALLTHROUGH */
-		case (2):
+		case 2:
 			curp->wlevel = MANDOCLEVEL_WARNING;
 			break;
-		case (3):
+		case 3:
 			curp->wlevel = MANDOCLEVEL_ERROR;
 			break;
-		case (4):
-			curp->wlevel = MANDOCLEVEL_FATAL;
+		case 4:
+			curp->wlevel = MANDOCLEVEL_UNSUPP;
+			break;
+		case 5:
+			curp->wlevel = MANDOCLEVEL_BADARG;
 			break;
 		default:
-			fprintf(stderr, "-W%s: Bad argument\n", o);
+			fprintf(stderr, "%s: -W %s: Bad argument\n",
+			    progname, o);
 			return(0);
 		}
 	}
@@ -401,17 +919,102 @@ woptions(struct curparse *curp, char *arg)
 }
 
 static void
-mmsg(enum mandocerr t, enum mandoclevel lvl, 
+mmsg(enum mandocerr t, enum mandoclevel lvl,
 		const char *file, int line, int col, const char *msg)
 {
+	const char	*mparse_msg;
+
+	fprintf(stderr, "%s: %s:", progname, file);
+
+	if (line)
+		fprintf(stderr, "%d:%d:", line, col + 1);
 
-	fprintf(stderr, "%s:%d:%d: %s: %s", 
-			file, line, col + 1, 
-			mparse_strlevel(lvl),
-			mparse_strerror(t));
+	fprintf(stderr, " %s", mparse_strlevel(lvl));
+
+	if (NULL != (mparse_msg = mparse_strerror(t)))
+		fprintf(stderr, ": %s", mparse_msg);
 
 	if (msg)
 		fprintf(stderr, ": %s", msg);
 
 	fputc('\n', stderr);
 }
+
+static pid_t
+spawn_pager(void)
+{
+#define MAX_PAGER_ARGS 16
+	char		*argv[MAX_PAGER_ARGS];
+	const char	*pager;
+	char		*cp;
+	int		 fildes[2];
+	int		 argc;
+	pid_t		 pager_pid;
+
+	if (pipe(fildes) == -1) {
+		fprintf(stderr, "%s: pipe: %s\n",
+		    progname, strerror(errno));
+		return(0);
+	}
+
+	switch (pager_pid = fork()) {
+	case -1:
+		fprintf(stderr, "%s: fork: %s\n",
+		    progname, strerror(errno));
+		exit((int)MANDOCLEVEL_SYSERR);
+	case 0:
+		break;
+	default:
+		close(fildes[0]);
+		if (dup2(fildes[1], STDOUT_FILENO) == -1) {
+			fprintf(stderr, "%s: dup output: %s\n",
+			    progname, strerror(errno));
+			exit((int)MANDOCLEVEL_SYSERR);
+		}
+		close(fildes[1]);
+		return(pager_pid);
+	}
+
+	/* The child process becomes the pager. */
+
+	close(fildes[1]);
+	if (dup2(fildes[0], STDIN_FILENO) == -1) {
+		fprintf(stderr, "%s: dup input: %s\n",
+		    progname, strerror(errno));
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	close(fildes[0]);
+
+	pager = getenv("MANPAGER");
+	if (pager == NULL || *pager == '\0')
+		pager = getenv("PAGER");
+	if (pager == NULL || *pager == '\0')
+		pager = "/usr/bin/more -s";
+	cp = mandoc_strdup(pager);
+
+	/*
+	 * Parse the pager command into words.
+	 * Intentionally do not do anything fancy here.
+	 */
+
+	argc = 0;
+	while (argc + 1 < MAX_PAGER_ARGS) {
+		argv[argc++] = cp;
+		cp = strchr(cp, ' ');
+		if (cp == NULL)
+			break;
+		*cp++ = '\0';
+		while (*cp == ' ')
+			cp++;
+		if (*cp == '\0')
+			break;
+	}
+	argv[argc] = NULL;
+
+	/* Hand over to the pager. */
+
+	execvp(argv[0], argv);
+	fprintf(stderr, "%s: exec: %s\n",
+	    progname, strerror(errno));
+	exit((int)MANDOCLEVEL_SYSERR);
+}
diff --git a/usr/src/cmd/mandoc/main.h b/usr/src/cmd/mandoc/main.h
index 79dcf489ae..9b04a7816a 100644
--- a/usr/src/cmd/mandoc/main.h
+++ b/usr/src/cmd/mandoc/main.h
@@ -1,6 +1,7 @@
-/*	$Id: main.h,v 1.15 2011/10/06 22:29:12 kristaps Exp $ */
+/*	$Id: main.h,v 1.20 2014/12/31 16:52:40 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,26 +15,23 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef	MAIN_H
-#define	MAIN_H
+
+#define	UNCONST(a)	((void *)(uintptr_t)(const void *)(a))
 
 __BEGIN_DECLS
 
+struct	mchars;
 struct	mdoc;
 struct	man;
 
-#define	UNCONST(a)	((void *)(uintptr_t)(const void *)(a))
-
-
-/* 
+/*
  * Definitions for main.c-visible output device functions, e.g., -Thtml
  * and -Tascii.  Note that ascii_alloc() is named as such in
  * anticipation of latin1_alloc() and so on, all of which map into the
  * terminal output routines with different character settings.
  */
 
-void		 *html_alloc(char *);
-void		 *xhtml_alloc(char *);
+void		 *html_alloc(const struct mchars *, char *);
 void		  html_mdoc(void *, const struct mdoc *);
 void		  html_man(void *, const struct man *);
 void		  html_free(void *);
@@ -44,18 +42,17 @@ void		  tree_man(void *, const struct man *);
 void		  man_mdoc(void *, const struct mdoc *);
 void		  man_man(void *, const struct man *);
 
-void		 *locale_alloc(char *);
-void		 *utf8_alloc(char *);
-void		 *ascii_alloc(char *);
+void		 *locale_alloc(const struct mchars *, char *);
+void		 *utf8_alloc(const struct mchars *, char *);
+void		 *ascii_alloc(const struct mchars *, char *);
 void		  ascii_free(void *);
+void		  ascii_sepline(void *);
 
-void		 *pdf_alloc(char *);
-void		 *ps_alloc(char *);
+void		 *pdf_alloc(const struct mchars *, char *);
+void		 *ps_alloc(const struct mchars *, char *);
 void		  pspdf_free(void *);
 
 void		  terminal_mdoc(void *, const struct mdoc *);
 void		  terminal_man(void *, const struct man *);
 
 __END_DECLS
-
-#endif /*!MAIN_H*/
diff --git a/usr/src/cmd/mandoc/man.c b/usr/src/cmd/mandoc/man.c
index e6e1c28992..4e7a398d9b 100644
--- a/usr/src/cmd/mandoc/man.c
+++ b/usr/src/cmd/mandoc/man.c
@@ -1,6 +1,8 @@
-/*	$Id: man.c,v 1.121 2013/11/10 22:54:40 schwarze Exp $ */
+/*	$Id: man.c,v 1.149 2015/01/30 21:28:46 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2011 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,13 +16,12 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -28,56 +29,53 @@
 
 #include "man.h"
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libman.h"
 #include "libmandoc.h"
 
-const	char *const __man_macronames[MAN_MAX] = {		 
+const	char *const __man_macronames[MAN_MAX] = {
 	"br",		"TH",		"SH",		"SS",
-	"TP", 		"LP",		"PP",		"P",
+	"TP",		"LP",		"PP",		"P",
 	"IP",		"HP",		"SM",		"SB",
 	"BI",		"IB",		"BR",		"RB",
 	"R",		"B",		"I",		"IR",
-	"RI",		"na",		"sp",		"nf",
+	"RI",		"sp",		"nf",
 	"fi",		"RE",		"RS",		"DT",
 	"UC",		"PD",		"AT",		"in",
 	"ft",		"OP",		"EX",		"EE",
-	"UR",		"UE"
+	"UR",		"UE",		"ll"
 	};
 
 const	char * const *man_macronames = __man_macronames;
 
-static	struct man_node	*man_node_alloc(struct man *, int, int, 
+static	void		 man_alloc1(struct man *);
+static	void		 man_breakscope(struct man *, enum mant);
+static	void		 man_descope(struct man *, int, int);
+static	void		 man_free1(struct man *);
+static	struct man_node	*man_node_alloc(struct man *, int, int,
 				enum man_type, enum mant);
-static	int		 man_node_append(struct man *, 
-				struct man_node *);
+static	void		 man_node_append(struct man *, struct man_node *);
 static	void		 man_node_free(struct man_node *);
-static	void		 man_node_unlink(struct man *, 
+static	void		 man_node_unlink(struct man *,
 				struct man_node *);
 static	int		 man_ptext(struct man *, int, char *, int);
 static	int		 man_pmacro(struct man *, int, char *, int);
-static	void		 man_free1(struct man *);
-static	void		 man_alloc1(struct man *);
-static	int		 man_descope(struct man *, int, int);
 
 
 const struct man_node *
 man_node(const struct man *man)
 {
 
-	assert( ! (MAN_HALT & man->flags));
 	return(man->first);
 }
 
-
 const struct man_meta *
 man_meta(const struct man *man)
 {
 
-	assert( ! (MAN_HALT & man->flags));
 	return(&man->meta);
 }
 
-
 void
 man_reset(struct man *man)
 {
@@ -86,7 +84,6 @@ man_reset(struct man *man)
 	man_alloc1(man);
 }
 
-
 void
 man_free(struct man *man)
 {
@@ -95,9 +92,9 @@ man_free(struct man *man)
 	free(man);
 }
 
-
 struct man *
-man_alloc(struct roff *roff, struct mparse *parse)
+man_alloc(struct roff *roff, struct mparse *parse,
+	const char *defos, int quick)
 {
 	struct man	*p;
 
@@ -105,58 +102,46 @@ man_alloc(struct roff *roff, struct mparse *parse)
 
 	man_hash_init();
 	p->parse = parse;
+	p->defos = defos;
+	p->quick = quick;
 	p->roff = roff;
 
 	man_alloc1(p);
 	return(p);
 }
 
-
-int
+void
 man_endparse(struct man *man)
 {
 
-	assert( ! (MAN_HALT & man->flags));
-	if (man_macroend(man))
-		return(1);
-	man->flags |= MAN_HALT;
-	return(0);
+	man_macroend(man);
 }
 
-
 int
 man_parseln(struct man *man, int ln, char *buf, int offs)
 {
 
-	man->flags |= MAN_NEWLINE;
-
-	assert( ! (MAN_HALT & man->flags));
+	if (man->last->type != MAN_EQN || ln > man->last->line)
+		man->flags |= MAN_NEWLINE;
 
 	return (roff_getcontrol(man->roff, buf, &offs) ?
-			man_pmacro(man, ln, buf, offs) : 
-			man_ptext(man, ln, buf, offs));
+	    man_pmacro(man, ln, buf, offs) :
+	    man_ptext(man, ln, buf, offs));
 }
 
-
 static void
 man_free1(struct man *man)
 {
 
 	if (man->first)
 		man_node_delete(man, man->first);
-	if (man->meta.title)
-		free(man->meta.title);
-	if (man->meta.source)
-		free(man->meta.source);
-	if (man->meta.date)
-		free(man->meta.date);
-	if (man->meta.vol)
-		free(man->meta.vol);
-	if (man->meta.msec)
-		free(man->meta.msec);
+	free(man->meta.title);
+	free(man->meta.source);
+	free(man->meta.date);
+	free(man->meta.vol);
+	free(man->meta.msec);
 }
 
-
 static void
 man_alloc1(struct man *man)
 {
@@ -171,21 +156,21 @@ man_alloc1(struct man *man)
 }
 
 
-static int
+static void
 man_node_append(struct man *man, struct man_node *p)
 {
 
 	assert(man->last);
 	assert(man->first);
-	assert(MAN_ROOT != p->type);
+	assert(p->type != MAN_ROOT);
 
 	switch (man->next) {
-	case (MAN_NEXT_SIBLING):
+	case MAN_NEXT_SIBLING:
 		man->last->next = p;
 		p->prev = man->last;
 		p->parent = man->last->parent;
 		break;
-	case (MAN_NEXT_CHILD):
+	case MAN_NEXT_CHILD:
 		man->last->child = p;
 		p->parent = man->last;
 		break;
@@ -193,24 +178,21 @@ man_node_append(struct man *man, struct man_node *p)
 		abort();
 		/* NOTREACHED */
 	}
-	
+
 	assert(p->parent);
 	p->parent->nchild++;
 
-	if ( ! man_valid_pre(man, p))
-		return(0);
-
 	switch (p->type) {
-	case (MAN_HEAD):
-		assert(MAN_BLOCK == p->parent->type);
-		p->parent->head = p;
+	case MAN_BLOCK:
+		if (p->tok == MAN_SH || p->tok == MAN_SS)
+			man->flags &= ~MAN_LITERAL;
 		break;
-	case (MAN_TAIL):
-		assert(MAN_BLOCK == p->parent->type);
-		p->parent->tail = p;
+	case MAN_HEAD:
+		assert(p->parent->type == MAN_BLOCK);
+		p->parent->head = p;
 		break;
-	case (MAN_BODY):
-		assert(MAN_BLOCK == p->parent->type);
+	case MAN_BODY:
+		assert(p->parent->type == MAN_BLOCK);
 		p->parent->body = p;
 		break;
 	default:
@@ -220,22 +202,18 @@ man_node_append(struct man *man, struct man_node *p)
 	man->last = p;
 
 	switch (p->type) {
-	case (MAN_TBL):
+	case MAN_TBL:
 		/* FALLTHROUGH */
-	case (MAN_TEXT):
-		if ( ! man_valid_post(man))
-			return(0);
+	case MAN_TEXT:
+		man_valid_post(man);
 		break;
 	default:
 		break;
 	}
-
-	return(1);
 }
 
-
 static struct man_node *
-man_node_alloc(struct man *man, int line, int pos, 
+man_node_alloc(struct man *man, int line, int pos,
 		enum man_type type, enum mant tok)
 {
 	struct man_node *p;
@@ -246,92 +224,77 @@ man_node_alloc(struct man *man, int line, int pos,
 	p->type = type;
 	p->tok = tok;
 
-	if (MAN_NEWLINE & man->flags)
+	if (man->flags & MAN_NEWLINE)
 		p->flags |= MAN_LINE;
 	man->flags &= ~MAN_NEWLINE;
 	return(p);
 }
 
-
-int
+void
 man_elem_alloc(struct man *man, int line, int pos, enum mant tok)
 {
 	struct man_node *p;
 
 	p = man_node_alloc(man, line, pos, MAN_ELEM, tok);
-	if ( ! man_node_append(man, p))
-		return(0);
+	man_node_append(man, p);
 	man->next = MAN_NEXT_CHILD;
-	return(1);
 }
 
-
-int
-man_tail_alloc(struct man *man, int line, int pos, enum mant tok)
-{
-	struct man_node *p;
-
-	p = man_node_alloc(man, line, pos, MAN_TAIL, tok);
-	if ( ! man_node_append(man, p))
-		return(0);
-	man->next = MAN_NEXT_CHILD;
-	return(1);
-}
-
-
-int
+void
 man_head_alloc(struct man *man, int line, int pos, enum mant tok)
 {
 	struct man_node *p;
 
 	p = man_node_alloc(man, line, pos, MAN_HEAD, tok);
-	if ( ! man_node_append(man, p))
-		return(0);
+	man_node_append(man, p);
 	man->next = MAN_NEXT_CHILD;
-	return(1);
 }
 
-
-int
+void
 man_body_alloc(struct man *man, int line, int pos, enum mant tok)
 {
 	struct man_node *p;
 
 	p = man_node_alloc(man, line, pos, MAN_BODY, tok);
-	if ( ! man_node_append(man, p))
-		return(0);
+	man_node_append(man, p);
 	man->next = MAN_NEXT_CHILD;
-	return(1);
 }
 
-
-int
+void
 man_block_alloc(struct man *man, int line, int pos, enum mant tok)
 {
 	struct man_node *p;
 
 	p = man_node_alloc(man, line, pos, MAN_BLOCK, tok);
-	if ( ! man_node_append(man, p))
-		return(0);
+	man_node_append(man, p);
 	man->next = MAN_NEXT_CHILD;
-	return(1);
 }
 
-int
+void
 man_word_alloc(struct man *man, int line, int pos, const char *word)
 {
 	struct man_node	*n;
 
 	n = man_node_alloc(man, line, pos, MAN_TEXT, MAN_MAX);
 	n->string = roff_strdup(man->roff, word);
-
-	if ( ! man_node_append(man, n))
-		return(0);
-
+	man_node_append(man, n);
 	man->next = MAN_NEXT_SIBLING;
-	return(1);
 }
 
+void
+man_word_append(struct man *man, const char *word)
+{
+	struct man_node	*n;
+	char		*addstr, *newstr;
+
+	n = man->last;
+	addstr = roff_strdup(man->roff, word);
+	mandoc_asprintf(&newstr, "%s %s", n->string, addstr);
+	free(addstr);
+	free(n->string);
+	n->string = newstr;
+	man->next = MAN_NEXT_SIBLING;
+}
 
 /*
  * Free all of the resources held by a node.  This does NOT unlink a
@@ -341,12 +304,10 @@ static void
 man_node_free(struct man_node *p)
 {
 
-	if (p->string)
-		free(p->string);
+	free(p->string);
 	free(p);
 }
 
-
 void
 man_node_delete(struct man *man, struct man_node *p)
 {
@@ -358,41 +319,34 @@ man_node_delete(struct man *man, struct man_node *p)
 	man_node_free(p);
 }
 
-int
+void
 man_addeqn(struct man *man, const struct eqn *ep)
 {
 	struct man_node	*n;
 
-	assert( ! (MAN_HALT & man->flags));
-
 	n = man_node_alloc(man, ep->ln, ep->pos, MAN_EQN, MAN_MAX);
 	n->eqn = ep;
-
-	if ( ! man_node_append(man, n))
-		return(0);
-
+	if (ep->ln > man->last->line)
+		n->flags |= MAN_LINE;
+	man_node_append(man, n);
 	man->next = MAN_NEXT_SIBLING;
-	return(man_descope(man, ep->ln, ep->pos));
+	man_descope(man, ep->ln, ep->pos);
 }
 
-int
+void
 man_addspan(struct man *man, const struct tbl_span *sp)
 {
 	struct man_node	*n;
 
-	assert( ! (MAN_HALT & man->flags));
-
+	man_breakscope(man, MAN_MAX);
 	n = man_node_alloc(man, sp->line, 0, MAN_TBL, MAN_MAX);
 	n->span = sp;
-
-	if ( ! man_node_append(man, n))
-		return(0);
-
+	man_node_append(man, n);
 	man->next = MAN_NEXT_SIBLING;
-	return(man_descope(man, sp->line, 0));
+	man_descope(man, sp->line, 0);
 }
 
-static int
+static void
 man_descope(struct man *man, int line, int offs)
 {
 	/*
@@ -401,19 +355,15 @@ man_descope(struct man *man, int line, int offs)
 	 * out the block scope (also if applicable).
 	 */
 
-	if (MAN_ELINE & man->flags) {
+	if (man->flags & MAN_ELINE) {
 		man->flags &= ~MAN_ELINE;
-		if ( ! man_unscope(man, man->last->parent, MANDOCERR_MAX))
-			return(0);
+		man_unscope(man, man->last->parent);
 	}
-
-	if ( ! (MAN_BLINE & man->flags))
-		return(1);
+	if ( ! (man->flags & MAN_BLINE))
+		return;
 	man->flags &= ~MAN_BLINE;
-
-	if ( ! man_unscope(man, man->last->parent, MANDOCERR_MAX))
-		return(0);
-	return(man_body_alloc(man, line, offs, man->last->tok));
+	man_unscope(man, man->last->parent);
+	man_body_alloc(man, line, offs, man->last->tok);
 }
 
 static int
@@ -423,13 +373,13 @@ man_ptext(struct man *man, int line, char *buf, int offs)
 
 	/* Literal free-form text whitespace is preserved. */
 
-	if (MAN_LITERAL & man->flags) {
-		if ( ! man_word_alloc(man, line, offs, buf + offs))
-			return(0);
-		return(man_descope(man, line, offs));
+	if (man->flags & MAN_LITERAL) {
+		man_word_alloc(man, line, offs, buf + offs);
+		man_descope(man, line, offs);
+		return(1);
 	}
 
-	for (i = offs; ' ' == buf[i]; i++)
+	for (i = offs; buf[i] == ' '; i++)
 		/* Skip leading whitespace. */ ;
 
 	/*
@@ -437,20 +387,19 @@ man_ptext(struct man *man, int line, char *buf, int offs)
 	 * but add a single vertical space elsewhere.
 	 */
 
-	if ('\0' == buf[i]) {
+	if (buf[i] == '\0') {
 		/* Allocate a blank entry. */
-		if (MAN_SH != man->last->tok &&
-		    MAN_SS != man->last->tok) {
-			if ( ! man_elem_alloc(man, line, offs, MAN_sp))
-				return(0);
+		if (man->last->tok != MAN_SH &&
+		    man->last->tok != MAN_SS) {
+			man_elem_alloc(man, line, offs, MAN_sp);
 			man->next = MAN_NEXT_SIBLING;
 		}
 		return(1);
 	}
 
-	/* 
+	/*
 	 * Warn if the last un-escaped character is whitespace. Then
-	 * strip away the remaining spaces (tabs stay!).   
+	 * strip away the remaining spaces (tabs stay!).
 	 */
 
 	i = (int)strlen(buf);
@@ -458,7 +407,8 @@ man_ptext(struct man *man, int line, char *buf, int offs)
 
 	if (' ' == buf[i - 1] || '\t' == buf[i - 1]) {
 		if (i > 1 && '\\' != buf[i - 2])
-			man_pmsg(man, line, i - 1, MANDOCERR_EOLNSPACE);
+			mandoc_msg(MANDOCERR_SPACE_EOL, man->parse,
+			    line, i - 1, NULL);
 
 		for (--i; i && ' ' == buf[i]; i--)
 			/* Spin back to non-space. */ ;
@@ -468,9 +418,7 @@ man_ptext(struct man *man, int line, char *buf, int offs)
 
 		buf[i] = '\0';
 	}
-
-	if ( ! man_word_alloc(man, line, offs, buf + offs))
-		return(0);
+	man_word_alloc(man, line, offs, buf + offs);
 
 	/*
 	 * End-of-sentence check.  If the last character is an unescaped
@@ -479,173 +427,167 @@ man_ptext(struct man *man, int line, char *buf, int offs)
 	 */
 
 	assert(i);
-	if (mandoc_eos(buf, (size_t)i, 0))
+	if (mandoc_eos(buf, (size_t)i))
 		man->last->flags |= MAN_EOS;
 
-	return(man_descope(man, line, offs));
+	man_descope(man, line, offs);
+	return(1);
 }
 
 static int
 man_pmacro(struct man *man, int ln, char *buf, int offs)
 {
-	int		 i, ppos;
+	struct man_node	*n;
+	const char	*cp;
 	enum mant	 tok;
+	int		 i, ppos;
+	int		 bline;
 	char		 mac[5];
-	struct man_node	*n;
-
-	if ('"' == buf[offs]) {
-		man_pmsg(man, ln, offs, MANDOCERR_BADCOMMENT);
-		return(1);
-	} else if ('\0' == buf[offs])
-		return(1);
 
 	ppos = offs;
 
 	/*
 	 * Copy the first word into a nil-terminated buffer.
-	 * Stop copying when a tab, space, or eoln is encountered.
+	 * Stop when a space, tab, escape, or eoln is encountered.
 	 */
 
 	i = 0;
-	while (i < 4 && '\0' != buf[offs] && 
-			' ' != buf[offs] && '\t' != buf[offs])
+	while (i < 4 && strchr(" \t\\", buf[offs]) == NULL)
 		mac[i++] = buf[offs++];
 
 	mac[i] = '\0';
 
 	tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX;
 
-	if (MAN_MAX == tok) {
-		mandoc_vmsg(MANDOCERR_MACRO, man->parse, ln, 
-				ppos, "%s", buf + ppos - 1);
+	if (tok == MAN_MAX) {
+		mandoc_msg(MANDOCERR_MACRO, man->parse,
+		    ln, ppos, buf + ppos - 1);
 		return(1);
 	}
 
-	/* The macro is sane.  Jump to the next word. */
+	/* Skip a leading escape sequence or tab. */
 
-	while (buf[offs] && ' ' == buf[offs])
+	switch (buf[offs]) {
+	case '\\':
+		cp = buf + offs + 1;
+		mandoc_escape(&cp, NULL, NULL);
+		offs = cp - buf;
+		break;
+	case '\t':
 		offs++;
+		break;
+	default:
+		break;
+	}
+
+	/* Jump to the next non-whitespace word. */
 
-	/* 
+	while (buf[offs] && buf[offs] == ' ')
+		offs++;
+
+	/*
 	 * Trailing whitespace.  Note that tabs are allowed to be passed
 	 * into the parser as "text", so we only warn about spaces here.
 	 */
 
-	if ('\0' == buf[offs] && ' ' == buf[offs - 1])
-		man_pmsg(man, ln, offs - 1, MANDOCERR_EOLNSPACE);
+	if (buf[offs] == '\0' && buf[offs - 1] == ' ')
+		mandoc_msg(MANDOCERR_SPACE_EOL, man->parse,
+		    ln, offs - 1, NULL);
 
-	/* 
-	 * Remove prior ELINE macro, as it's being clobbered by a new
-	 * macro.  Note that NSCOPED macros do not close out ELINE
-	 * macros---they don't print text---so we let those slip by.
+	/*
+	 * Some macros break next-line scopes; otherwise, remember
+	 * whether we are in next-line scope for a block head.
 	 */
 
-	if ( ! (MAN_NSCOPED & man_macros[tok].flags) &&
-			man->flags & MAN_ELINE) {
-		n = man->last;
-		assert(MAN_TEXT != n->type);
+	man_breakscope(man, tok);
+	bline = man->flags & MAN_BLINE;
 
-		/* Remove repeated NSCOPED macros causing ELINE. */
+	/* Call to handler... */
 
-		if (MAN_NSCOPED & man_macros[n->tok].flags)
-			n = n->parent;
+	assert(man_macros[tok].fp);
+	(*man_macros[tok].fp)(man, tok, ln, ppos, &offs, buf);
 
-		mandoc_vmsg(MANDOCERR_LINESCOPE, man->parse, n->line, 
-		    n->pos, "%s breaks %s", man_macronames[tok],
-		    man_macronames[n->tok]);
+	/* In quick mode (for mandocdb), abort after the NAME section. */
 
-		man_node_delete(man, n);
-		man->flags &= ~MAN_ELINE;
+	if (man->quick && tok == MAN_SH) {
+		n = man->last;
+		if (n->type == MAN_BODY &&
+		    strcmp(n->prev->child->string, "NAME"))
+			return(2);
 	}
 
 	/*
-	 * Remove prior BLINE macro that is being clobbered.
+	 * If we are in a next-line scope for a block head,
+	 * close it out now and switch to the body,
+	 * unless the next-line scope is allowed to continue.
 	 */
-	if ((man->flags & MAN_BLINE) &&
-	    (MAN_BSCOPE & man_macros[tok].flags)) {
-		n = man->last;
-
-		/* Might be a text node like 8 in
-		 * .TP 8
-		 * .SH foo
-		 */
-		if (MAN_TEXT == n->type)
-			n = n->parent;
 
-		/* Remove element that didn't end BLINE, if any. */
-		if ( ! (MAN_BSCOPE & man_macros[n->tok].flags))
-			n = n->parent;
+	if ( ! bline || man->flags & MAN_ELINE ||
+	    man_macros[tok].flags & MAN_NSCOPED)
+		return(1);
 
-		assert(MAN_HEAD == n->type);
-		n = n->parent;
-		assert(MAN_BLOCK == n->type);
-		assert(MAN_SCOPED & man_macros[n->tok].flags);
+	assert(man->flags & MAN_BLINE);
+	man->flags &= ~MAN_BLINE;
 
-		mandoc_vmsg(MANDOCERR_LINESCOPE, man->parse, n->line, 
-		    n->pos, "%s breaks %s", man_macronames[tok],
-		    man_macronames[n->tok]);
+	man_unscope(man, man->last->parent);
+	man_body_alloc(man, ln, ppos, man->last->tok);
+	return(1);
+}
 
-		man_node_delete(man, n);
-		man->flags &= ~MAN_BLINE;
-	}
+void
+man_breakscope(struct man *man, enum mant tok)
+{
+	struct man_node	*n;
 
 	/*
-	 * Save the fact that we're in the next-line for a block.  In
-	 * this way, embedded roff instructions can "remember" state
-	 * when they exit.
+	 * An element next line scope is open,
+	 * and the new macro is not allowed inside elements.
+	 * Delete the element that is being broken.
 	 */
 
-	if (MAN_BLINE & man->flags)
-		man->flags |= MAN_BPLINE;
-
-	/* Call to handler... */
-
-	assert(man_macros[tok].fp);
-	if ( ! (*man_macros[tok].fp)(man, tok, ln, ppos, &offs, buf))
-		goto err;
+	if (man->flags & MAN_ELINE && (tok == MAN_MAX ||
+	    ! (man_macros[tok].flags & MAN_NSCOPED))) {
+		n = man->last;
+		assert(n->type != MAN_TEXT);
+		if (man_macros[n->tok].flags & MAN_NSCOPED)
+			n = n->parent;
 
-	/* 
-	 * We weren't in a block-line scope when entering the
-	 * above-parsed macro, so return.
-	 */
+		mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse,
+		    n->line, n->pos, "%s breaks %s",
+		    tok == MAN_MAX ? "TS" : man_macronames[tok],
+		    man_macronames[n->tok]);
 
-	if ( ! (MAN_BPLINE & man->flags)) {
-		man->flags &= ~MAN_ILINE; 
-		return(1);
+		man_node_delete(man, n);
+		man->flags &= ~MAN_ELINE;
 	}
-	man->flags &= ~MAN_BPLINE;
 
 	/*
-	 * If we're in a block scope, then allow this macro to slip by
-	 * without closing scope around it.
+	 * A block header next line scope is open,
+	 * and the new macro is not allowed inside block headers.
+	 * Delete the block that is being broken.
 	 */
 
-	if (MAN_ILINE & man->flags) {
-		man->flags &= ~MAN_ILINE;
-		return(1);
-	}
-
-	/* 
-	 * If we've opened a new next-line element scope, then return
-	 * now, as the next line will close out the block scope.
-	 */
-
-	if (MAN_ELINE & man->flags)
-		return(1);
-
-	/* Close out the block scope opened in the prior line.  */
-
-	assert(MAN_BLINE & man->flags);
-	man->flags &= ~MAN_BLINE;
+	if (man->flags & MAN_BLINE && (tok == MAN_MAX ||
+	    man_macros[tok].flags & MAN_BSCOPE)) {
+		n = man->last;
+		if (n->type == MAN_TEXT)
+			n = n->parent;
+		if ( ! (man_macros[n->tok].flags & MAN_BSCOPE))
+			n = n->parent;
 
-	if ( ! man_unscope(man, man->last->parent, MANDOCERR_MAX))
-		return(0);
-	return(man_body_alloc(man, ln, ppos, man->last->tok));
+		assert(n->type == MAN_HEAD);
+		n = n->parent;
+		assert(n->type == MAN_BLOCK);
+		assert(man_macros[n->tok].flags & MAN_SCOPED);
 
-err:	/* Error out. */
+		mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse,
+		    n->line, n->pos, "%s breaks %s",
+		    tok == MAN_MAX ? "TS" : man_macronames[tok],
+		    man_macronames[n->tok]);
 
-	man->flags |= MAN_HALT;
-	return(0);
+		man_node_delete(man, n);
+		man->flags &= ~MAN_BLINE;
+	}
 }
 
 /*
@@ -696,3 +638,49 @@ man_mparse(const struct man *man)
 	assert(man && man->parse);
 	return(man->parse);
 }
+
+void
+man_deroff(char **dest, const struct man_node *n)
+{
+	char	*cp;
+	size_t	 sz;
+
+	if (n->type != MAN_TEXT) {
+		for (n = n->child; n; n = n->next)
+			man_deroff(dest, n);
+		return;
+	}
+
+	/* Skip leading whitespace and escape sequences. */
+
+	cp = n->string;
+	while ('\0' != *cp) {
+		if ('\\' == *cp) {
+			cp++;
+			mandoc_escape((const char **)&cp, NULL, NULL);
+		} else if (isspace((unsigned char)*cp))
+			cp++;
+		else
+			break;
+	}
+
+	/* Skip trailing whitespace. */
+
+	for (sz = strlen(cp); sz; sz--)
+		if (0 == isspace((unsigned char)cp[sz-1]))
+			break;
+
+	/* Skip empty strings. */
+
+	if (0 == sz)
+		return;
+
+	if (NULL == *dest) {
+		*dest = mandoc_strndup(cp, sz);
+		return;
+	}
+
+	mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
+	free(*dest);
+	*dest = cp;
+}
diff --git a/usr/src/cmd/mandoc/man.h b/usr/src/cmd/mandoc/man.h
index ef9480f276..9e8eb03e57 100644
--- a/usr/src/cmd/mandoc/man.h
+++ b/usr/src/cmd/mandoc/man.h
@@ -1,6 +1,7 @@
-/*	$Id: man.h,v 1.62 2013/10/17 20:54:58 schwarze Exp $ */
+/*	$Id: man.h,v 1.69 2015/01/24 02:41:49 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,8 +15,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef MAN_H
-#define MAN_H
 
 enum	mant {
 	MAN_br = 0,
@@ -39,7 +38,6 @@ enum	mant {
 	MAN_I,
 	MAN_IR,
 	MAN_RI,
-	MAN_na,
 	MAN_sp,
 	MAN_nf,
 	MAN_fi,
@@ -56,6 +54,7 @@ enum	mant {
 	MAN_EE,
 	MAN_UR,
 	MAN_UE,
+	MAN_ll,
 	MAN_MAX
 };
 
@@ -66,7 +65,6 @@ enum	man_type {
 	MAN_BLOCK,
 	MAN_HEAD,
 	MAN_BODY,
-	MAN_TAIL,
 	MAN_TBL,
 	MAN_EQN
 };
@@ -77,6 +75,7 @@ struct	man_meta {
 	char		*vol; /* `TH' volume */
 	char		*title; /* `TH' title (e.g., FOO) */
 	char		*source; /* `TH' source (e.g., GNU) */
+	int		 hasbody; /* document is not empty */
 };
 
 struct	man_node {
@@ -99,6 +98,7 @@ struct	man_node {
 	struct man_node	*body; /* BLOCK node BODY ptr */
 	const struct tbl_span *span; /* TBL */
 	const struct eqn *eqn; /* EQN */
+	int		 aux; /* decoded node data, type-dependent */
 };
 
 /* Names of macros.  Index is enum mant. */
@@ -111,7 +111,6 @@ struct	man;
 const struct man_node *man_node(const struct man *);
 const struct man_meta *man_meta(const struct man *);
 const struct mparse   *man_mparse(const struct man *);
+void man_deroff(char **, const struct man_node *);
 
 __END_DECLS
-
-#endif /*!MAN_H*/
diff --git a/usr/src/cmd/mandoc/man_hash.c b/usr/src/cmd/mandoc/man_hash.c
index 86c5c40a19..1cbfb1b7f8 100644
--- a/usr/src/cmd/mandoc/man_hash.c
+++ b/usr/src/cmd/mandoc/man_hash.c
@@ -1,4 +1,4 @@
-/*	$Id: man_hash.c,v 1.25 2011/07/24 18:15:14 kristaps Exp $ */
+/*	$Id: man_hash.c,v 1.29 2014/12/01 08:05:52 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons 
  *
@@ -14,20 +14,16 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
 #include 
 #include 
-#include 
 #include 
 
 #include "man.h"
-#include "mandoc.h"
 #include "libman.h"
 
 #define	HASH_DEPTH	 6
@@ -49,6 +45,7 @@
  */
 static	unsigned char	 table[26 * HASH_DEPTH];
 
+
 /*
  * XXX - this hash has global scope, so if intended for use as a library
  * with multiple callers, it will need re-invocation protection.
@@ -60,8 +57,7 @@ man_hash_init(void)
 
 	memset(table, UCHAR_MAX, sizeof(table));
 
-	assert(/* LINTED */ 
-			MAN_MAX < UCHAR_MAX);
+	assert(MAN_MAX < UCHAR_MAX);
 
 	for (i = 0; i < (int)MAN_MAX; i++) {
 		x = man_macronames[i][0];
@@ -80,7 +76,6 @@ man_hash_init(void)
 	}
 }
 
-
 enum mant
 man_hash_find(const char *tmp)
 {
diff --git a/usr/src/cmd/mandoc/man_html.c b/usr/src/cmd/mandoc/man_html.c
index 2c4e220a11..1109415025 100644
--- a/usr/src/cmd/mandoc/man_html.c
+++ b/usr/src/cmd/mandoc/man_html.c
@@ -1,7 +1,7 @@
-/*	$Id: man_html.c,v 1.90 2013/10/17 20:54:58 schwarze Exp $ */
+/*	$Id: man_html.c,v 1.112 2015/03/03 21:11:34 schwarze Exp $ */
 /*
- * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2013 Ingo Schwarze 
+ * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons 
+ * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,7 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
@@ -27,10 +25,10 @@
 #include 
 #include 
 
-#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "man.h"
 #include "out.h"
 #include "html.h"
-#include "man.h"
 #include "main.h"
 
 /* TODO: preserve ident widths. */
@@ -53,7 +51,7 @@ struct	htmlman {
 	int		(*post)(MAN_ARGS);
 };
 
-static	void		  print_bvspace(struct html *, 
+static	void		  print_bvspace(struct html *,
 				const struct man_node *);
 static	void		  print_man(MAN_ARGS);
 static	void		  print_man_head(MAN_ARGS);
@@ -90,7 +88,7 @@ static	const struct htmlman mans[MAN_MAX] = {
 	{ man_PP_pre, NULL }, /* PP */
 	{ man_PP_pre, NULL }, /* P */
 	{ man_IP_pre, NULL }, /* IP */
-	{ man_HP_pre, NULL }, /* HP */ 
+	{ man_HP_pre, NULL }, /* HP */
 	{ man_SM_pre, NULL }, /* SM */
 	{ man_SM_pre, NULL }, /* SB */
 	{ man_alt_pre, NULL }, /* BI */
@@ -102,7 +100,6 @@ static	const struct htmlman mans[MAN_MAX] = {
 	{ man_I_pre, NULL }, /* I */
 	{ man_alt_pre, NULL }, /* IR */
 	{ man_alt_pre, NULL }, /* RI */
-	{ man_ign_pre, NULL }, /* na */
 	{ man_br_pre, NULL }, /* sp */
 	{ man_literal_pre, NULL }, /* nf */
 	{ man_literal_pre, NULL }, /* fi */
@@ -119,8 +116,10 @@ static	const struct htmlman mans[MAN_MAX] = {
 	{ man_literal_pre, NULL }, /* EE */
 	{ man_UR_pre, NULL }, /* UR */
 	{ NULL, NULL }, /* UE */
+	{ man_ign_pre, NULL }, /* ll */
 };
 
+
 /*
  * Printing leading vertical space before a block.
  * This is used for the paragraph macros.
@@ -141,7 +140,7 @@ print_bvspace(struct html *h, const struct man_node *n)
 		if (NULL == n->prev)
 			return;
 
-	print_otag(h, TAG_P, 0, NULL);
+	print_paragraph(h);
 }
 
 void
@@ -155,7 +154,7 @@ html_man(void *arg, const struct man *man)
 }
 
 static void
-print_man(MAN_ARGS) 
+print_man(MAN_ARGS)
 {
 	struct tag	*t, *tt;
 	struct htmlpair	 tag;
@@ -170,15 +169,13 @@ print_man(MAN_ARGS)
 		print_tagq(h, tt);
 		print_otag(h, TAG_BODY, 0, NULL);
 		print_otag(h, TAG_DIV, 1, &tag);
-	} else 
+	} else
 		t = print_otag(h, TAG_DIV, 1, &tag);
 
 	print_man_nodelist(man, n, mh, h);
 	print_tagq(h, t);
 }
 
-
-/* ARGSUSED */
 static void
 print_man_head(MAN_ARGS)
 {
@@ -191,17 +188,16 @@ print_man_head(MAN_ARGS)
 	print_text(h, h->buf);
 }
 
-
 static void
 print_man_nodelist(MAN_ARGS)
 {
 
-	print_man_node(man, n, mh, h);
-	if (n->next)
-		print_man_nodelist(man, n->next, mh, h);
+	while (n != NULL) {
+		print_man_node(man, n, mh, h);
+		n = n->next;
+	}
 }
 
-
 static void
 print_man_node(MAN_ARGS)
 {
@@ -212,31 +208,26 @@ print_man_node(MAN_ARGS)
 	t = h->tags.head;
 
 	switch (n->type) {
-	case (MAN_ROOT):
+	case MAN_ROOT:
 		man_root_pre(man, n, mh, h);
 		break;
-	case (MAN_TEXT):
-		/*
-		 * If we have a blank line, output a vertical space.
-		 * If we have a space as the first character, break
-		 * before printing the line's data.
-		 */
+	case MAN_TEXT:
 		if ('\0' == *n->string) {
-			print_otag(h, TAG_P, 0, NULL);
+			print_paragraph(h);
 			return;
 		}
-
-		if (' ' == *n->string && MAN_LINE & n->flags)
-			print_otag(h, TAG_BR, 0, NULL);
-		else if (MANH_LITERAL & mh->fl && n->prev)
+		if (n->flags & MAN_LINE && (*n->string == ' ' ||
+		    (n->prev != NULL && mh->fl & MANH_LITERAL &&
+		     ! (h->flags & HTML_NONEWLINE))))
 			print_otag(h, TAG_BR, 0, NULL);
-
 		print_text(h, n->string);
 		return;
-	case (MAN_EQN):
+	case MAN_EQN:
+		if (n->flags & MAN_LINE)
+			putchar('\n');
 		print_eqn(h, n->eqn);
 		break;
-	case (MAN_TBL):
+	case MAN_TBL:
 		/*
 		 * This will take care of initialising all of the table
 		 * state data for the first table, then tearing it down
@@ -245,7 +236,7 @@ print_man_node(MAN_ARGS)
 		print_tbl(h, n->span);
 		return;
 	default:
-		/* 
+		/*
 		 * Close out scope of font prior to opening a macro
 		 * scope.
 		 */
@@ -275,10 +266,10 @@ print_man_node(MAN_ARGS)
 	print_stagq(h, t);
 
 	switch (n->type) {
-	case (MAN_ROOT):
+	case MAN_ROOT:
 		man_root_post(man, n, mh, h);
 		break;
-	case (MAN_EQN):
+	case MAN_EQN:
 		break;
 	default:
 		if (mans[n->tok].post)
@@ -287,95 +278,74 @@ print_man_node(MAN_ARGS)
 	}
 }
 
-
 static int
 a2width(const struct man_node *n, struct roffsu *su)
 {
 
 	if (MAN_TEXT != n->type)
 		return(0);
-	if (a2roffsu(n->string, su, SCALE_BU))
+	if (a2roffsu(n->string, su, SCALE_EN))
 		return(1);
 
 	return(0);
 }
 
-
-/* ARGSUSED */
 static void
 man_root_pre(MAN_ARGS)
 {
-	struct htmlpair	 tag[3];
+	struct htmlpair	 tag;
 	struct tag	*t, *tt;
-	char		 b[BUFSIZ], title[BUFSIZ];
-
-	b[0] = 0;
-	if (man->vol)
-		(void)strlcat(b, man->vol, BUFSIZ);
+	char		*title;
 
 	assert(man->title);
 	assert(man->msec);
-	snprintf(title, BUFSIZ - 1, "%s(%s)", man->title, man->msec);
+	mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
 
-	PAIR_SUMMARY_INIT(&tag[0], "Document Header");
-	PAIR_CLASS_INIT(&tag[1], "head");
-	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
-	t = print_otag(h, TAG_TABLE, 3, tag);
-	PAIR_INIT(&tag[0], ATTR_WIDTH, "30%");
-	print_otag(h, TAG_COL, 1, tag);
-	print_otag(h, TAG_COL, 1, tag);
-	print_otag(h, TAG_COL, 1, tag);
+	PAIR_CLASS_INIT(&tag, "head");
+	t = print_otag(h, TAG_TABLE, 1, &tag);
 
 	print_otag(h, TAG_TBODY, 0, NULL);
 
 	tt = print_otag(h, TAG_TR, 0, NULL);
 
-	PAIR_CLASS_INIT(&tag[0], "head-ltitle");
-	print_otag(h, TAG_TD, 1, tag);
+	PAIR_CLASS_INIT(&tag, "head-ltitle");
+	print_otag(h, TAG_TD, 1, &tag);
 	print_text(h, title);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag[0], "head-vol");
-	PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
-	print_otag(h, TAG_TD, 2, tag);
-	print_text(h, b);
+	PAIR_CLASS_INIT(&tag, "head-vol");
+	print_otag(h, TAG_TD, 1, &tag);
+	if (NULL != man->vol)
+		print_text(h, man->vol);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag[0], "head-rtitle");
-	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
-	print_otag(h, TAG_TD, 2, tag);
+	PAIR_CLASS_INIT(&tag, "head-rtitle");
+	print_otag(h, TAG_TD, 1, &tag);
 	print_text(h, title);
 	print_tagq(h, t);
+	free(title);
 }
 
-
-/* ARGSUSED */
 static void
 man_root_post(MAN_ARGS)
 {
-	struct htmlpair	 tag[3];
+	struct htmlpair	 tag;
 	struct tag	*t, *tt;
 
-	PAIR_SUMMARY_INIT(&tag[0], "Document Footer");
-	PAIR_CLASS_INIT(&tag[1], "foot");
-	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
-	t = print_otag(h, TAG_TABLE, 3, tag);
-	PAIR_INIT(&tag[0], ATTR_WIDTH, "50%");
-	print_otag(h, TAG_COL, 1, tag);
-	print_otag(h, TAG_COL, 1, tag);
+	PAIR_CLASS_INIT(&tag, "foot");
+	t = print_otag(h, TAG_TABLE, 1, &tag);
 
 	tt = print_otag(h, TAG_TR, 0, NULL);
 
-	PAIR_CLASS_INIT(&tag[0], "foot-date");
-	print_otag(h, TAG_TD, 1, tag);
+	PAIR_CLASS_INIT(&tag, "foot-date");
+	print_otag(h, TAG_TD, 1, &tag);
 
 	assert(man->date);
 	print_text(h, man->date);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag[0], "foot-os");
-	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
-	print_otag(h, TAG_TD, 2, tag);
+	PAIR_CLASS_INIT(&tag, "foot-os");
+	print_otag(h, TAG_TD, 1, &tag);
 
 	if (man->source)
 		print_text(h, man->source);
@@ -383,7 +353,6 @@ man_root_post(MAN_ARGS)
 }
 
 
-/* ARGSUSED */
 static int
 man_br_pre(MAN_ARGS)
 {
@@ -395,9 +364,9 @@ man_br_pre(MAN_ARGS)
 	if (MAN_sp == n->tok) {
 		if (NULL != (n = n->child))
 			if ( ! a2roffsu(n->string, &su, SCALE_VS))
-				SCALE_VS_INIT(&su, atoi(n->string));
+				su.scale = 1.0;
 	} else
-		su.scale = 0;
+		su.scale = 0.0;
 
 	bufinit(h);
 	bufcat_su(h, "height", &su);
@@ -410,7 +379,6 @@ man_br_pre(MAN_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 man_SH_pre(MAN_ARGS)
 {
@@ -428,7 +396,6 @@ man_SH_pre(MAN_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_alt_pre(MAN_ARGS)
 {
@@ -437,7 +404,7 @@ man_alt_pre(MAN_ARGS)
 	enum htmltag	 fp;
 	struct tag	*t;
 
-	if ((savelit = mh->fl & MANH_LITERAL)) 
+	if ((savelit = mh->fl & MANH_LITERAL))
 		print_otag(h, TAG_BR, 0, NULL);
 
 	mh->fl &= ~MANH_LITERAL;
@@ -445,22 +412,22 @@ man_alt_pre(MAN_ARGS)
 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
 		t = NULL;
 		switch (n->tok) {
-		case (MAN_BI):
+		case MAN_BI:
 			fp = i % 2 ? TAG_I : TAG_B;
 			break;
-		case (MAN_IB):
+		case MAN_IB:
 			fp = i % 2 ? TAG_B : TAG_I;
 			break;
-		case (MAN_RI):
+		case MAN_RI:
 			fp = i % 2 ? TAG_I : TAG_MAX;
 			break;
-		case (MAN_IR):
+		case MAN_IR:
 			fp = i % 2 ? TAG_MAX : TAG_I;
 			break;
-		case (MAN_BR):
+		case MAN_BR:
 			fp = i % 2 ? TAG_MAX : TAG_B;
 			break;
-		case (MAN_RB):
+		case MAN_RB:
 			fp = i % 2 ? TAG_B : TAG_MAX;
 			break;
 		default:
@@ -486,18 +453,16 @@ man_alt_pre(MAN_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 man_SM_pre(MAN_ARGS)
 {
-	
+
 	print_otag(h, TAG_SMALL, 0, NULL);
 	if (MAN_SB == n->tok)
 		print_otag(h, TAG_B, 0, NULL);
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_SS_pre(MAN_ARGS)
 {
@@ -515,7 +480,6 @@ man_SS_pre(MAN_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_PP_pre(MAN_ARGS)
 {
@@ -528,13 +492,12 @@ man_PP_pre(MAN_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_IP_pre(MAN_ARGS)
 {
 	const struct man_node	*nn;
 
-	if (MAN_BODY == n->type) { 
+	if (MAN_BODY == n->type) {
 		print_otag(h, TAG_DD, 0, NULL);
 		return(1);
 	} else if (MAN_HEAD != n->type) {
@@ -553,19 +516,23 @@ man_IP_pre(MAN_ARGS)
 
 	/* For TP, only print next-line header elements. */
 
-	if (MAN_TP == n->tok)
-		for (nn = n->child; nn; nn = nn->next)
-			if (nn->line > n->line)
-				print_man_node(man, nn, mh, h);
+	if (MAN_TP == n->tok) {
+		nn = n->child;
+		while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+			nn = nn->next;
+		while (NULL != nn) {
+			print_man_node(man, nn, mh, h);
+			nn = nn->next;
+		}
+	}
 
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 man_HP_pre(MAN_ARGS)
 {
-	struct htmlpair	 tag;
+	struct htmlpair	 tag[2];
 	struct roffsu	 su;
 	const struct man_node *np;
 
@@ -585,12 +552,12 @@ man_HP_pre(MAN_ARGS)
 	bufcat_su(h, "margin-left", &su);
 	su.scale = -su.scale;
 	bufcat_su(h, "text-indent", &su);
-	PAIR_STYLE_INIT(&tag, h);
-	print_otag(h, TAG_P, 1, &tag);
+	PAIR_STYLE_INIT(&tag[0], h);
+	PAIR_CLASS_INIT(&tag[1], "spacer");
+	print_otag(h, TAG_DIV, 2, tag);
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_OP_pre(MAN_ARGS)
 {
@@ -620,8 +587,6 @@ man_OP_pre(MAN_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 man_B_pre(MAN_ARGS)
 {
@@ -630,16 +595,14 @@ man_B_pre(MAN_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_I_pre(MAN_ARGS)
 {
-	
+
 	print_otag(h, TAG_I, 0, NULL);
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_literal_pre(MAN_ARGS)
 {
@@ -653,7 +616,6 @@ man_literal_pre(MAN_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 man_in_pre(MAN_ARGS)
 {
@@ -662,7 +624,6 @@ man_in_pre(MAN_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 man_ign_pre(MAN_ARGS)
 {
@@ -670,7 +631,6 @@ man_ign_pre(MAN_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 man_RS_pre(MAN_ARGS)
 {
@@ -693,7 +653,6 @@ man_RS_pre(MAN_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 man_UR_pre(MAN_ARGS)
 {
diff --git a/usr/src/cmd/mandoc/man_macro.c b/usr/src/cmd/mandoc/man_macro.c
index 479d0484c4..c86ab6f13f 100644
--- a/usr/src/cmd/mandoc/man_macro.c
+++ b/usr/src/cmd/mandoc/man_macro.c
@@ -1,7 +1,7 @@
-/*	$Id: man_macro.c,v 1.79 2013/12/25 00:50:05 schwarze Exp $ */
+/*	$Id: man_macro.c,v 1.98 2015/02/06 11:54:36 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2012, 2013, 2014, 2015 Ingo Schwarze 
  * Copyright (c) 2013 Franco Fichtner 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -16,9 +16,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -36,21 +36,19 @@ enum	rew {
 	REW_HALT
 };
 
-static	int		 blk_close(MACRO_PROT_ARGS);
-static	int		 blk_exp(MACRO_PROT_ARGS);
-static	int		 blk_imp(MACRO_PROT_ARGS);
-static	int		 in_line_eoln(MACRO_PROT_ARGS);
-static	int		 man_args(struct man *, int, 
+static	void		 blk_close(MACRO_PROT_ARGS);
+static	void		 blk_exp(MACRO_PROT_ARGS);
+static	void		 blk_imp(MACRO_PROT_ARGS);
+static	void		 in_line_eoln(MACRO_PROT_ARGS);
+static	int		 man_args(struct man *, int,
 				int *, char *, char **);
 
-static	int		 rew_scope(enum man_type, 
+static	void		 rew_scope(enum man_type,
 				struct man *, enum mant);
-static	enum rew	 rew_dohalt(enum mant, enum man_type, 
+static	enum rew	 rew_dohalt(enum mant, enum man_type,
 				const struct man_node *);
-static	enum rew	 rew_block(enum mant, enum man_type, 
+static	enum rew	 rew_block(enum mant, enum man_type,
 				const struct man_node *);
-static	void		 rew_warn(struct man *, 
-				struct man_node *, enum mandocerr);
 
 const	struct man_macro __man_macros[MAN_MAX] = {
 	{ in_line_eoln, MAN_NSCOPED }, /* br */
@@ -63,22 +61,21 @@ const	struct man_macro __man_macros[MAN_MAX] = {
 	{ blk_imp, MAN_BSCOPE }, /* P */
 	{ blk_imp, MAN_BSCOPE }, /* IP */
 	{ blk_imp, MAN_BSCOPE }, /* HP */
-	{ in_line_eoln, MAN_SCOPED }, /* SM */
-	{ in_line_eoln, MAN_SCOPED }, /* SB */
+	{ in_line_eoln, MAN_SCOPED | MAN_JOIN }, /* SM */
+	{ in_line_eoln, MAN_SCOPED | MAN_JOIN }, /* SB */
 	{ in_line_eoln, 0 }, /* BI */
 	{ in_line_eoln, 0 }, /* IB */
 	{ in_line_eoln, 0 }, /* BR */
 	{ in_line_eoln, 0 }, /* RB */
-	{ in_line_eoln, MAN_SCOPED }, /* R */
-	{ in_line_eoln, MAN_SCOPED }, /* B */
-	{ in_line_eoln, MAN_SCOPED }, /* I */
+	{ in_line_eoln, MAN_SCOPED | MAN_JOIN }, /* R */
+	{ in_line_eoln, MAN_SCOPED | MAN_JOIN }, /* B */
+	{ in_line_eoln, MAN_SCOPED | MAN_JOIN }, /* I */
 	{ in_line_eoln, 0 }, /* IR */
 	{ in_line_eoln, 0 }, /* RI */
-	{ in_line_eoln, MAN_NSCOPED }, /* na */
 	{ in_line_eoln, MAN_NSCOPED }, /* sp */
 	{ in_line_eoln, MAN_BSCOPE }, /* nf */
 	{ in_line_eoln, MAN_BSCOPE }, /* fi */
-	{ blk_close, 0 }, /* RE */
+	{ blk_close, MAN_BSCOPE }, /* RE */
 	{ blk_exp, MAN_BSCOPE | MAN_EXPLICIT }, /* RS */
 	{ in_line_eoln, 0 }, /* DT */
 	{ in_line_eoln, 0 }, /* UC */
@@ -90,86 +87,89 @@ const	struct man_macro __man_macros[MAN_MAX] = {
 	{ in_line_eoln, MAN_BSCOPE }, /* EX */
 	{ in_line_eoln, MAN_BSCOPE }, /* EE */
 	{ blk_exp, MAN_BSCOPE | MAN_EXPLICIT }, /* UR */
-	{ blk_close, 0 }, /* UE */
+	{ blk_close, MAN_BSCOPE }, /* UE */
+	{ in_line_eoln, 0 }, /* ll */
 };
 
 const	struct man_macro * const man_macros = __man_macros;
 
 
-/*
- * Warn when "n" is an explicit non-roff macro.
- */
-static void
-rew_warn(struct man *man, struct man_node *n, enum mandocerr er)
-{
-
-	if (er == MANDOCERR_MAX || MAN_BLOCK != n->type)
-		return;
-	if (MAN_VALID & n->flags)
-		return;
-	if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags))
-		return;
-
-	assert(er < MANDOCERR_FATAL);
-	man_nmsg(man, n, er);
-}
-
-
-/*
- * Rewind scope.  If a code "er" != MANDOCERR_MAX has been provided, it
- * will be used if an explicit block scope is being closed out.
- */
-int
-man_unscope(struct man *man, const struct man_node *to, 
-		enum mandocerr er)
+void
+man_unscope(struct man *man, const struct man_node *to)
 {
 	struct man_node	*n;
 
-	assert(to);
-
-	man->next = MAN_NEXT_SIBLING;
+	to = to->parent;
+	n = man->last;
+	while (n != to) {
+
+		/* Reached the end of the document? */
+
+		if (to == NULL && ! (n->flags & MAN_VALID)) {
+			if (man->flags & (MAN_BLINE | MAN_ELINE) &&
+			    man_macros[n->tok].flags & MAN_SCOPED) {
+				mandoc_vmsg(MANDOCERR_BLK_LINE,
+				    man->parse, n->line, n->pos,
+				    "EOF breaks %s",
+				    man_macronames[n->tok]);
+				if (man->flags & MAN_ELINE)
+					man->flags &= ~MAN_ELINE;
+				else {
+					assert(n->type == MAN_HEAD);
+					n = n->parent;
+					man->flags &= ~MAN_BLINE;
+				}
+				man->last = n;
+				n = n->parent;
+				man_node_delete(man, man->last);
+				continue;
+			}
+			if (n->type == MAN_BLOCK &&
+			    man_macros[n->tok].flags & MAN_EXPLICIT)
+				mandoc_msg(MANDOCERR_BLK_NOEND,
+				    man->parse, n->line, n->pos,
+				    man_macronames[n->tok]);
+		}
 
-	/* LINTED */
-	while (man->last != to) {
 		/*
-		 * Save the parent here, because we may delete the
-		 * man->last node in the post-validation phase and reset
-		 * it to man->last->parent, causing a step in the closing
-		 * out to be lost.
+		 * We might delete the man->last node
+		 * in the post-validation phase.
+		 * Save a pointer to the parent such that
+		 * we know where to continue the iteration.
 		 */
-		n = man->last->parent;
-		rew_warn(man, man->last, er);
-		if ( ! man_valid_post(man))
-			return(0);
+
 		man->last = n;
-		assert(man->last);
+		n = n->parent;
+		man_valid_post(man);
 	}
 
-	rew_warn(man, man->last, er);
-	if ( ! man_valid_post(man))
-		return(0);
+	/*
+	 * If we ended up at the parent of the node we were
+	 * supposed to rewind to, that means the target node
+	 * got deleted, so add the next node we parse as a child
+	 * of the parent instead of as a sibling of the target.
+	 */
 
-	return(1);
+	man->next = (man->last == to) ?
+	    MAN_NEXT_CHILD : MAN_NEXT_SIBLING;
 }
 
-
 static enum rew
 rew_block(enum mant ntok, enum man_type type, const struct man_node *n)
 {
 
-	if (MAN_BLOCK == type && ntok == n->parent->tok && 
-			MAN_BODY == n->parent->type)
+	if (type == MAN_BLOCK && ntok == n->parent->tok &&
+	    n->parent->type == MAN_BODY)
 		return(REW_REWIND);
 	return(ntok == n->tok ? REW_HALT : REW_NOHALT);
 }
 
-
 /*
  * There are three scope levels: scoped to the root (all), scoped to the
  * section (all less sections), and scoped to subsections (all less
  * sections and subsections).
  */
-static enum rew 
+static enum rew
 rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
 {
 	enum rew	 c;
@@ -196,20 +196,20 @@ rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
 			return(REW_REWIND);
 	}
 
-	/* 
+	/*
 	 * Next follow the implicit scope-smashings as defined by man.7:
 	 * section, sub-section, etc.
 	 */
 
 	switch (tok) {
-	case (MAN_SH):
+	case MAN_SH:
 		break;
-	case (MAN_SS):
+	case MAN_SS:
 		/* Rewind to a section, if a block. */
 		if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
 			return(c);
 		break;
-	case (MAN_RS):
+	case MAN_RS:
 		/* Preserve empty paragraphs before RS. */
 		if (0 == n->nchild && (MAN_P == n->tok ||
 		    MAN_PP == n->tok || MAN_LP == n->tok))
@@ -237,57 +237,73 @@ rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
 	return(REW_NOHALT);
 }
 
-
 /*
  * Rewinding entails ascending the parse tree until a coherent point,
  * for example, the `SH' macro will close out any intervening `SS'
  * scopes.  When a scope is closed, it must be validated and actioned.
  */
-static int
+static void
 rew_scope(enum man_type type, struct man *man, enum mant tok)
 {
 	struct man_node	*n;
 	enum rew	 c;
 
-	/* LINTED */
 	for (n = man->last; n; n = n->parent) {
-		/* 
+		/*
 		 * Whether we should stop immediately (REW_HALT), stop
 		 * and rewind until this point (REW_REWIND), or keep
 		 * rewinding (REW_NOHALT).
 		 */
 		c = rew_dohalt(tok, type, n);
 		if (REW_HALT == c)
-			return(1);
+			return;
 		if (REW_REWIND == c)
 			break;
 	}
 
-	/* 
+	/*
 	 * Rewind until the current point.  Warn if we're a roff
 	 * instruction that's mowing over explicit scopes.
 	 */
-	assert(n);
 
-	return(man_unscope(man, n, MANDOCERR_MAX));
+	man_unscope(man, n);
 }
 
 
 /*
  * Close out a generic explicit macro.
  */
-/* ARGSUSED */
-int
+void
 blk_close(MACRO_PROT_ARGS)
 {
-	enum mant	 	 ntok;
+	enum mant		 ntok;
 	const struct man_node	*nn;
+	char			*p;
+	int			 nrew, target;
 
+	nrew = 1;
 	switch (tok) {
-	case (MAN_RE):
+	case MAN_RE:
 		ntok = MAN_RS;
+		if ( ! man_args(man, line, pos, buf, &p))
+			break;
+		for (nn = man->last->parent; nn; nn = nn->parent)
+			if (nn->tok == ntok && nn->type == MAN_BLOCK)
+				nrew++;
+		target = strtol(p, &p, 10);
+		if (*p != '\0')
+			mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
+			    line, p - buf, "RE ... %s", p);
+		if (target == 0)
+			target = 1;
+		nrew -= target;
+		if (nrew < 1) {
+			mandoc_vmsg(MANDOCERR_RE_NOTOPEN, man->parse,
+			    line, ppos, "RE %d", target);
+			return;
+		}
 		break;
-	case (MAN_UE):
+	case MAN_UE:
 		ntok = MAN_UR;
 		break;
 	default:
@@ -296,90 +312,70 @@ blk_close(MACRO_PROT_ARGS)
 	}
 
 	for (nn = man->last->parent; nn; nn = nn->parent)
-		if (ntok == nn->tok && MAN_BLOCK == nn->type)
+		if (nn->tok == ntok && nn->type == MAN_BLOCK && ! --nrew)
 			break;
 
-	if (NULL == nn) {
-		man_pmsg(man, line, ppos, MANDOCERR_NOSCOPE);
-		if ( ! rew_scope(MAN_BLOCK, man, MAN_PP))
-			return(0);
-	} else 
-		man_unscope(man, nn, MANDOCERR_MAX);
-
-	return(1);
+	if (nn == NULL) {
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, man->parse,
+		    line, ppos, man_macronames[tok]);
+		rew_scope(MAN_BLOCK, man, MAN_PP);
+	} else {
+		line = man->last->line;
+		ppos = man->last->pos;
+		ntok = man->last->tok;
+		man_unscope(man, nn);
+
+		/* Move a trailing paragraph behind the block. */
+
+		if (ntok == MAN_LP || ntok == MAN_PP || ntok == MAN_P) {
+			*pos = strlen(buf);
+			blk_imp(man, ntok, line, ppos, pos, buf);
+		}
+	}
 }
 
-
-/* ARGSUSED */
-int
+void
 blk_exp(MACRO_PROT_ARGS)
 {
-	struct man_node	*n;
-	int		 la;
+	struct man_node	*head;
 	char		*p;
+	int		 la;
 
-	/* Close out prior implicit scopes. */
-
-	if ( ! rew_scope(MAN_BLOCK, man, tok))
-		return(0);
-
-	if ( ! man_block_alloc(man, line, ppos, tok))
-		return(0);
-	if ( ! man_head_alloc(man, line, ppos, tok))
-		return(0);
-
-	for (;;) {
-		la = *pos;
-		if ( ! man_args(man, line, pos, buf, &p))
-			break;
-		if ( ! man_word_alloc(man, line, la, p))
-			return(0);
-	}
+	rew_scope(MAN_BLOCK, man, tok);
+	man_block_alloc(man, line, ppos, tok);
+	man_head_alloc(man, line, ppos, tok);
+	head = man->last;
 
-	assert(man);
-	assert(tok != MAN_MAX);
+	la = *pos;
+	if (man_args(man, line, pos, buf, &p))
+		man_word_alloc(man, line, la, p);
 
-	for (n = man->last; n; n = n->parent) {
-		if (n->tok != tok)
-			continue;
-		assert(MAN_HEAD == n->type);
-		man_unscope(man, n, MANDOCERR_MAX);
-		break;
-	}
+	if (buf[*pos] != '\0')
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS,
+		    man->parse, line, *pos, "%s ... %s",
+		    man_macronames[tok], buf + *pos);
 
-	return(man_body_alloc(man, line, ppos, tok));
+	man_unscope(man, head);
+	man_body_alloc(man, line, ppos, tok);
 }
 
-
-
 /*
  * Parse an implicit-block macro.  These contain a MAN_HEAD and a
  * MAN_BODY contained within a MAN_BLOCK.  Rules for closing out other
  * scopes, such as `SH' closing out an `SS', are defined in the rew
  * routines.
  */
-/* ARGSUSED */
-int
+void
 blk_imp(MACRO_PROT_ARGS)
 {
 	int		 la;
 	char		*p;
 	struct man_node	*n;
 
-	/* Close out prior scopes. */
-
-	if ( ! rew_scope(MAN_BODY, man, tok))
-		return(0);
-	if ( ! rew_scope(MAN_BLOCK, man, tok))
-		return(0);
-
-	/* Allocate new block & head scope. */
-
-	if ( ! man_block_alloc(man, line, ppos, tok))
-		return(0);
-	if ( ! man_head_alloc(man, line, ppos, tok))
-		return(0);
-
+	rew_scope(MAN_BODY, man, tok);
+	rew_scope(MAN_BLOCK, man, tok);
+	man_block_alloc(man, line, ppos, tok);
+	man_head_alloc(man, line, ppos, tok);
 	n = man->last;
 
 	/* Add line arguments. */
@@ -388,48 +384,58 @@ blk_imp(MACRO_PROT_ARGS)
 		la = *pos;
 		if ( ! man_args(man, line, pos, buf, &p))
 			break;
-		if ( ! man_word_alloc(man, line, la, p))
-			return(0);
+		man_word_alloc(man, line, la, p);
 	}
 
 	/* Close out head and open body (unless MAN_SCOPE). */
 
-	if (MAN_SCOPED & man_macros[tok].flags) {
+	if (man_macros[tok].flags & MAN_SCOPED) {
 		/* If we're forcing scope (`TP'), keep it open. */
-		if (MAN_FSCOPED & man_macros[tok].flags) {
+		if (man_macros[tok].flags & MAN_FSCOPED) {
 			man->flags |= MAN_BLINE;
-			return(1);
+			return;
 		} else if (n == man->last) {
 			man->flags |= MAN_BLINE;
-			return(1);
+			return;
 		}
 	}
-
-	if ( ! rew_scope(MAN_HEAD, man, tok))
-		return(0);
-	return(man_body_alloc(man, line, ppos, tok));
+	rew_scope(MAN_HEAD, man, tok);
+	man_body_alloc(man, line, ppos, tok);
 }
 
-
-/* ARGSUSED */
-int
+void
 in_line_eoln(MACRO_PROT_ARGS)
 {
 	int		 la;
 	char		*p;
 	struct man_node	*n;
 
-	if ( ! man_elem_alloc(man, line, ppos, tok))
-		return(0);
-
+	man_elem_alloc(man, line, ppos, tok);
 	n = man->last;
 
 	for (;;) {
+		if (buf[*pos] != '\0' && (tok == MAN_br ||
+		    tok == MAN_fi || tok == MAN_nf)) {
+			mandoc_vmsg(MANDOCERR_ARG_SKIP,
+			    man->parse, line, *pos, "%s %s",
+			    man_macronames[tok], buf + *pos);
+			break;
+		}
+		if (buf[*pos] != '\0' && man->last != n &&
+		    (tok == MAN_PD || tok == MAN_ft || tok == MAN_sp)) {
+			mandoc_vmsg(MANDOCERR_ARG_EXCESS,
+			    man->parse, line, *pos, "%s ... %s",
+			    man_macronames[tok], buf + *pos);
+			break;
+		}
 		la = *pos;
 		if ( ! man_args(man, line, pos, buf, &p))
 			break;
-		if ( ! man_word_alloc(man, line, la, p))
-			return(0);
+		if (man_macros[tok].flags & MAN_JOIN &&
+		    man->last->type == MAN_TEXT)
+			man_word_append(man, p);
+		else
+			man_word_alloc(man, line, la, p);
 	}
 
 	/*
@@ -438,7 +444,7 @@ in_line_eoln(MACRO_PROT_ARGS)
 	 */
 
 	if (n != man->last &&
-	    mandoc_eos(man->last->string, strlen(man->last->string), 0))
+	    mandoc_eos(man->last->string, strlen(man->last->string)))
 		man->last->flags |= MAN_EOS;
 
 	/*
@@ -447,22 +453,15 @@ in_line_eoln(MACRO_PROT_ARGS)
 	 * waiting for terms to load into our context.
 	 */
 
-	if (n == man->last && MAN_SCOPED & man_macros[tok].flags) {
-		assert( ! (MAN_NSCOPED & man_macros[tok].flags));
+	if (n == man->last && man_macros[tok].flags & MAN_SCOPED) {
+		assert( ! (man_macros[tok].flags & MAN_NSCOPED));
 		man->flags |= MAN_ELINE;
-		return(1);
-	} 
-
-	/* Set ignorable context, if applicable. */
-
-	if (MAN_NSCOPED & man_macros[tok].flags) {
-		assert( ! (MAN_SCOPED & man_macros[tok].flags));
-		man->flags |= MAN_ILINE;
+		return;
 	}
 
-	assert(MAN_ROOT != man->last->type);
+	assert(man->last->type != MAN_ROOT);
 	man->next = MAN_NEXT_SIBLING;
-	
+
 	/*
 	 * Rewind our element scope.  Note that when TH is pruned, we'll
 	 * be back at the root, so make sure that we don't clobber as
@@ -474,28 +473,25 @@ in_line_eoln(MACRO_PROT_ARGS)
 			break;
 		if (man->last->type == MAN_ROOT)
 			break;
-		if ( ! man_valid_post(man))
-			return(0);
+		man_valid_post(man);
 	}
 
 	assert(man->last);
 
 	/*
-	 * Same here regarding whether we're back at the root. 
+	 * Same here regarding whether we're back at the root.
 	 */
 
-	if (man->last->type != MAN_ROOT && ! man_valid_post(man))
-		return(0);
-
-	return(1);
+	if (man->last->type != MAN_ROOT)
+		man_valid_post(man);
 }
 
 
-int
+void
 man_macroend(struct man *man)
 {
 
-	return(man_unscope(man, man->first, MANDOCERR_SCOPEEXIT));
+	man_unscope(man, man->first);
 }
 
 static int
diff --git a/usr/src/cmd/mandoc/man_term.c b/usr/src/cmd/mandoc/man_term.c
index 4bd62443b4..8be7927a65 100644
--- a/usr/src/cmd/mandoc/man_term.c
+++ b/usr/src/cmd/mandoc/man_term.c
@@ -1,7 +1,7 @@
-/*	$Id: man_term.c,v 1.139 2013/12/22 23:34:13 schwarze Exp $ */
+/*	$Id: man_term.c,v 1.169 2015/03/06 15:48:52 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,19 +15,19 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "out.h"
 #include "man.h"
 #include "term.h"
@@ -38,16 +38,16 @@
 struct	mtermp {
 	int		  fl;
 #define	MANT_LITERAL	 (1 << 0)
-	size_t		  lmargin[MAXMARGINS]; /* margins (incl. visible page) */
+	int		  lmargin[MAXMARGINS]; /* margins (incl. vis. page) */
 	int		  lmargincur; /* index of current margin */
 	int		  lmarginsz; /* actual number of nested margins */
 	size_t		  offset; /* default offset to visible page */
 	int		  pardist; /* vert. space before par., unit: [v] */
 };
 
-#define	DECL_ARGS 	  struct termp *p, \
+#define	DECL_ARGS	  struct termp *p, \
 			  struct mtermp *mt, \
-			  const struct man_node *n, \
+			  struct man_node *n, \
 			  const struct man_meta *meta
 
 struct	termact {
@@ -57,14 +57,11 @@ struct	termact {
 #define	MAN_NOTEXT	 (1 << 0) /* Never has text children. */
 };
 
-static	int		  a2width(const struct termp *, const char *);
-static	size_t		  a2height(const struct termp *, const char *);
-
 static	void		  print_man_nodelist(DECL_ARGS);
 static	void		  print_man_node(DECL_ARGS);
 static	void		  print_man_head(struct termp *, const void *);
 static	void		  print_man_foot(struct termp *, const void *);
-static	void		  print_bvspace(struct termp *, 
+static	void		  print_bvspace(struct termp *,
 				const struct man_node *, int);
 
 static	int		  pre_B(DECL_ARGS);
@@ -84,6 +81,7 @@ static	int		  pre_ft(DECL_ARGS);
 static	int		  pre_ign(DECL_ARGS);
 static	int		  pre_in(DECL_ARGS);
 static	int		  pre_literal(DECL_ARGS);
+static	int		  pre_ll(DECL_ARGS);
 static	int		  pre_sp(DECL_ARGS);
 
 static	void		  post_IP(DECL_ARGS);
@@ -104,7 +102,7 @@ static	const struct termact termacts[MAN_MAX] = {
 	{ pre_PP, NULL, 0 }, /* PP */
 	{ pre_PP, NULL, 0 }, /* P */
 	{ pre_IP, post_IP, 0 }, /* IP */
-	{ pre_HP, post_HP, 0 }, /* HP */ 
+	{ pre_HP, post_HP, 0 }, /* HP */
 	{ NULL, NULL, 0 }, /* SM */
 	{ pre_B, NULL, 0 }, /* SB */
 	{ pre_alternate, NULL, 0 }, /* BI */
@@ -116,14 +114,13 @@ static	const struct termact termacts[MAN_MAX] = {
 	{ pre_I, NULL, 0 }, /* I */
 	{ pre_alternate, NULL, 0 }, /* IR */
 	{ pre_alternate, NULL, 0 }, /* RI */
-	{ pre_ign, NULL, MAN_NOTEXT }, /* na */
 	{ pre_sp, NULL, MAN_NOTEXT }, /* sp */
 	{ pre_literal, NULL, 0 }, /* nf */
 	{ pre_literal, NULL, 0 }, /* fi */
 	{ NULL, NULL, 0 }, /* RE */
 	{ pre_RS, post_RS, 0 }, /* RS */
 	{ pre_ign, NULL, 0 }, /* DT */
-	{ pre_ign, NULL, 0 }, /* UC */
+	{ pre_ign, NULL, MAN_NOTEXT }, /* UC */
 	{ pre_PD, NULL, MAN_NOTEXT }, /* PD */
 	{ pre_ign, NULL, 0 }, /* AT */
 	{ pre_in, NULL, MAN_NOTEXT }, /* in */
@@ -133,70 +130,55 @@ static	const struct termact termacts[MAN_MAX] = {
 	{ pre_literal, NULL, 0 }, /* EE */
 	{ pre_UR, post_UR, 0 }, /* UR */
 	{ NULL, NULL, 0 }, /* UE */
+	{ pre_ll, NULL, MAN_NOTEXT }, /* ll */
 };
 
 
-
 void
 terminal_man(void *arg, const struct man *man)
 {
 	struct termp		*p;
-	const struct man_node	*n;
 	const struct man_meta	*meta;
+	struct man_node		*n;
 	struct mtermp		 mt;
 
 	p = (struct termp *)arg;
 
-	if (0 == p->defindent)
-		p->defindent = 7;
-
 	p->overstep = 0;
-	p->maxrmargin = p->defrmargin;
+	p->rmargin = p->maxrmargin = p->defrmargin;
 	p->tabwidth = term_len(p, 5);
 
-	if (NULL == p->symtab)
-		p->symtab = mchars_alloc();
-
-	n = man_node(man);
+	n = man_node(man)->child;
 	meta = man_meta(man);
 
-	term_begin(p, print_man_head, print_man_foot, meta);
-	p->flags |= TERMP_NOSPACE;
-
 	memset(&mt, 0, sizeof(struct mtermp));
 
 	mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
 	mt.offset = term_len(p, p->defindent);
 	mt.pardist = 1;
 
-	if (n->child)
-		print_man_nodelist(p, &mt, n->child, meta);
-
-	term_end(p);
-}
-
-
-static size_t
-a2height(const struct termp *p, const char *cp)
-{
-	struct roffsu	 su;
-
-	if ( ! a2roffsu(cp, &su, SCALE_VS))
-		SCALE_VS_INIT(&su, atoi(cp));
-
-	return(term_vspan(p, &su));
-}
-
-
-static int
-a2width(const struct termp *p, const char *cp)
-{
-	struct roffsu	 su;
-
-	if ( ! a2roffsu(cp, &su, SCALE_BU))
-		return(-1);
-
-	return((int)term_hspan(p, &su));
+	if (p->synopsisonly) {
+		while (n != NULL) {
+			if (n->tok == MAN_SH &&
+			    n->child->child->type == MAN_TEXT &&
+			    !strcmp(n->child->child->string, "SYNOPSIS")) {
+				if (n->child->next->child != NULL)
+					print_man_nodelist(p, &mt,
+					    n->child->next->child, meta);
+				term_newln(p);
+				break;
+			}
+			n = n->next;
+		}
+	} else {
+		if (p->defindent == 0)
+			p->defindent = 7;
+		term_begin(p, print_man_head, print_man_foot, meta);
+		p->flags |= TERMP_NOSPACE;
+		if (n != NULL)
+			print_man_nodelist(p, &mt, n, meta);
+		term_end(p);
+	}
 }
 
 /*
@@ -226,7 +208,7 @@ print_bvspace(struct termp *p, const struct man_node *n, int pardist)
 		term_vspace(p);
 }
 
-/* ARGSUSED */
+
 static int
 pre_ign(DECL_ARGS)
 {
@@ -234,8 +216,14 @@ pre_ign(DECL_ARGS)
 	return(0);
 }
 
+static int
+pre_ll(DECL_ARGS)
+{
+
+	term_setwidth(p, n->nchild ? n->child->string : NULL);
+	return(0);
+}
 
-/* ARGSUSED */
 static int
 pre_I(DECL_ARGS)
 {
@@ -244,8 +232,6 @@ pre_I(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 pre_literal(DECL_ARGS)
 {
@@ -266,58 +252,58 @@ pre_literal(DECL_ARGS)
 		p->offset = p->rmargin;
 		p->rmargin = p->maxrmargin;
 		p->trailspace = 0;
-		p->flags &= ~TERMP_NOBREAK;
+		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
 		p->flags |= TERMP_NOSPACE;
 	}
 
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 pre_PD(DECL_ARGS)
 {
+	struct roffsu	 su;
 
 	n = n->child;
-	if (0 == n) {
+	if (n == NULL) {
 		mt->pardist = 1;
 		return(0);
 	}
 	assert(MAN_TEXT == n->type);
-	mt->pardist = atoi(n->string);
+	if (a2roffsu(n->string, &su, SCALE_VS))
+		mt->pardist = term_vspan(p, &su);
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 pre_alternate(DECL_ARGS)
 {
 	enum termfont		 font[2];
-	const struct man_node	*nn;
+	struct man_node		*nn;
 	int			 savelit, i;
 
 	switch (n->tok) {
-	case (MAN_RB):
+	case MAN_RB:
 		font[0] = TERMFONT_NONE;
 		font[1] = TERMFONT_BOLD;
 		break;
-	case (MAN_RI):
+	case MAN_RI:
 		font[0] = TERMFONT_NONE;
 		font[1] = TERMFONT_UNDER;
 		break;
-	case (MAN_BR):
+	case MAN_BR:
 		font[0] = TERMFONT_BOLD;
 		font[1] = TERMFONT_NONE;
 		break;
-	case (MAN_BI):
+	case MAN_BI:
 		font[0] = TERMFONT_BOLD;
 		font[1] = TERMFONT_UNDER;
 		break;
-	case (MAN_IR):
+	case MAN_IR:
 		font[0] = TERMFONT_UNDER;
 		font[1] = TERMFONT_NONE;
 		break;
-	case (MAN_IB):
+	case MAN_IB:
 		font[0] = TERMFONT_UNDER;
 		font[1] = TERMFONT_BOLD;
 		break;
@@ -340,7 +326,6 @@ pre_alternate(DECL_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 pre_B(DECL_ARGS)
 {
@@ -349,7 +334,6 @@ pre_B(DECL_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 pre_OP(DECL_ARGS)
 {
@@ -372,7 +356,6 @@ pre_OP(DECL_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 pre_ft(DECL_ARGS)
 {
@@ -385,26 +368,26 @@ pre_ft(DECL_ARGS)
 
 	cp = n->child->string;
 	switch (*cp) {
-	case ('4'):
+	case '4':
 		/* FALLTHROUGH */
-	case ('3'):
+	case '3':
 		/* FALLTHROUGH */
-	case ('B'):
+	case 'B':
 		term_fontrepl(p, TERMFONT_BOLD);
 		break;
-	case ('2'):
+	case '2':
 		/* FALLTHROUGH */
-	case ('I'):
+	case 'I':
 		term_fontrepl(p, TERMFONT_UNDER);
 		break;
-	case ('P'):
+	case 'P':
 		term_fontlast(p);
 		break;
-	case ('1'):
+	case '1':
 		/* FALLTHROUGH */
-	case ('C'):
+	case 'C':
 		/* FALLTHROUGH */
-	case ('R'):
+	case 'R':
 		term_fontrepl(p, TERMFONT_NONE);
 		break;
 	default:
@@ -413,13 +396,13 @@ pre_ft(DECL_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 pre_in(DECL_ARGS)
 {
-	int		 len, less;
-	size_t		 v;
+	struct roffsu	 su;
 	const char	*cp;
+	size_t		 v;
+	int		 less;
 
 	term_newln(p);
 
@@ -438,46 +421,40 @@ pre_in(DECL_ARGS)
 	else
 		cp--;
 
-	if ((len = a2width(p, ++cp)) < 0)
+	if ( ! a2roffsu(++cp, &su, SCALE_EN))
 		return(0);
 
-	v = (size_t)len;
+	v = term_hspan(p, &su);
 
 	if (less < 0)
 		p->offset -= p->offset > v ? v : p->offset;
 	else if (less > 0)
 		p->offset += v;
-	else 
+	else
 		p->offset = v;
-
-	/* Don't let this creep beyond the right margin. */
-
-	if (p->offset > p->rmargin)
-		p->offset = p->rmargin;
+	if (p->offset > SHRT_MAX)
+		p->offset = term_len(p, p->defindent);
 
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 pre_sp(DECL_ARGS)
 {
-	char		*s;
-	size_t		 i, len;
-	int		 neg;
+	struct roffsu	 su;
+	int		 i, len;
 
 	if ((NULL == n->prev && n->parent)) {
 		switch (n->parent->tok) {
-		case (MAN_SH):
+		case MAN_SH:
 			/* FALLTHROUGH */
-		case (MAN_SS):
+		case MAN_SS:
 			/* FALLTHROUGH */
-		case (MAN_PP):
+		case MAN_PP:
 			/* FALLTHROUGH */
-		case (MAN_LP):
+		case MAN_LP:
 			/* FALLTHROUGH */
-		case (MAN_P):
+		case MAN_P:
 			/* FALLTHROUGH */
 			return(0);
 		default:
@@ -485,29 +462,20 @@ pre_sp(DECL_ARGS)
 		}
 	}
 
-	neg = 0;
-	switch (n->tok) {
-	case (MAN_br):
+	if (n->tok == MAN_br)
 		len = 0;
-		break;
-	default:
-		if (NULL == n->child) {
-			len = 1;
-			break;
-		}
-		s = n->child->string;
-		if ('-' == *s) {
-			neg = 1;
-			s++;
-		}
-		len = a2height(p, s);
-		break;
+	else if (n->child == NULL)
+		len = 1;
+	else {
+		if ( ! a2roffsu(n->child->string, &su, SCALE_VS))
+			su.scale = 1.0;
+		len = term_vspan(p, &su);
 	}
 
-	if (0 == len)
+	if (len == 0)
 		term_newln(p);
-	else if (neg)
-		p->skipvsp += len;
+	else if (len < 0)
+		p->skipvsp -= len;
 	else
 		for (i = 0; i < len; i++)
 			term_vspace(p);
@@ -515,62 +483,54 @@ pre_sp(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 pre_HP(DECL_ARGS)
 {
-	size_t			 len, one;
-	int			 ival;
+	struct roffsu		 su;
 	const struct man_node	*nn;
+	int			 len;
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		print_bvspace(p, n, mt->pardist);
 		return(1);
-	case (MAN_BODY):
+	case MAN_BODY:
 		break;
 	default:
 		return(0);
 	}
 
 	if ( ! (MANT_LITERAL & mt->fl)) {
-		p->flags |= TERMP_NOBREAK;
+		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
 		p->trailspace = 2;
 	}
 
-	len = mt->lmargin[mt->lmargincur];
-	ival = -1;
-
 	/* Calculate offset. */
 
-	if (NULL != (nn = n->parent->head->child))
-		if ((ival = a2width(p, nn->string)) >= 0)
-			len = (size_t)ival;
-
-	one = term_len(p, 1);
-	if (len < one)
-		len = one;
+	if ((nn = n->parent->head->child) != NULL &&
+	    a2roffsu(nn->string, &su, SCALE_EN)) {
+		len = term_hspan(p, &su);
+		if (len < 0 && (size_t)(-len) > mt->offset)
+			len = -mt->offset;
+		else if (len > SHRT_MAX)
+			len = term_len(p, p->defindent);
+		mt->lmargin[mt->lmargincur] = len;
+	} else
+		len = mt->lmargin[mt->lmargincur];
 
 	p->offset = mt->offset;
 	p->rmargin = mt->offset + len;
-
-	if (ival >= 0)
-		mt->lmargin[mt->lmargincur] = (size_t)ival;
-
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 post_HP(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MAN_BODY):
+	case MAN_BODY:
 		term_newln(p);
-		p->flags &= ~TERMP_NOBREAK;
+		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
 		p->trailspace = 0;
 		p->offset = mt->offset;
 		p->rmargin = p->maxrmargin;
@@ -580,14 +540,12 @@ post_HP(DECL_ARGS)
 	}
 }
 
-
-/* ARGSUSED */
 static int
 pre_PP(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 		print_bvspace(p, n, mt->pardist);
 		break;
@@ -599,52 +557,45 @@ pre_PP(DECL_ARGS)
 	return(MAN_HEAD != n->type);
 }
 
-
-/* ARGSUSED */
 static int
 pre_IP(DECL_ARGS)
 {
+	struct roffsu		 su;
 	const struct man_node	*nn;
-	size_t			 len;
-	int			 savelit, ival;
+	int			 len, savelit;
 
 	switch (n->type) {
-	case (MAN_BODY):
+	case MAN_BODY:
 		p->flags |= TERMP_NOSPACE;
 		break;
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		p->flags |= TERMP_NOBREAK;
 		p->trailspace = 1;
 		break;
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		print_bvspace(p, n, mt->pardist);
 		/* FALLTHROUGH */
 	default:
 		return(1);
 	}
 
-	len = mt->lmargin[mt->lmargincur];
-	ival = -1;
-
 	/* Calculate the offset from the optional second argument. */
-	if (NULL != (nn = n->parent->head->child))
-		if (NULL != (nn = nn->next))
-			if ((ival = a2width(p, nn->string)) >= 0)
-				len = (size_t)ival;
+	if ((nn = n->parent->head->child) != NULL &&
+	    (nn = nn->next) != NULL &&
+	    a2roffsu(nn->string, &su, SCALE_EN)) {
+		len = term_hspan(p, &su);
+		if (len < 0 && (size_t)(-len) > mt->offset)
+			len = -mt->offset;
+		else if (len > SHRT_MAX)
+			len = term_len(p, p->defindent);
+		mt->lmargin[mt->lmargincur] = len;
+	} else
+		len = mt->lmargin[mt->lmargincur];
 
 	switch (n->type) {
-	case (MAN_HEAD):
-		/* Handle zero-width lengths. */
-		if (0 == len)
-			len = term_len(p, 1);
-
+	case MAN_HEAD:
 		p->offset = mt->offset;
 		p->rmargin = mt->offset + len;
-		if (ival < 0)
-			break;
-
-		/* Set the saved left-margin. */
-		mt->lmargin[mt->lmargincur] = (size_t)ival;
 
 		savelit = MANT_LITERAL & mt->fl;
 		mt->fl &= ~MANT_LITERAL;
@@ -656,7 +607,7 @@ pre_IP(DECL_ARGS)
 			mt->fl |= MANT_LITERAL;
 
 		return(0);
-	case (MAN_BODY):
+	case MAN_BODY:
 		p->offset = mt->offset + len;
 		p->rmargin = p->maxrmargin;
 		break;
@@ -667,20 +618,18 @@ pre_IP(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 post_IP(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		term_flushln(p);
 		p->flags &= ~TERMP_NOBREAK;
 		p->trailspace = 0;
 		p->rmargin = p->maxrmargin;
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		term_newln(p);
 		p->offset = mt->offset;
 		break;
@@ -689,46 +638,44 @@ post_IP(DECL_ARGS)
 	}
 }
 
-
-/* ARGSUSED */
 static int
 pre_TP(DECL_ARGS)
 {
-	const struct man_node	*nn;
-	size_t			 len;
-	int			 savelit, ival;
+	struct roffsu		 su;
+	struct man_node		*nn;
+	int			 len, savelit;
 
 	switch (n->type) {
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		p->flags |= TERMP_NOBREAK;
 		p->trailspace = 1;
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		p->flags |= TERMP_NOSPACE;
 		break;
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		print_bvspace(p, n, mt->pardist);
 		/* FALLTHROUGH */
 	default:
 		return(1);
 	}
 
-	len = (size_t)mt->lmargin[mt->lmargincur];
-	ival = -1;
-
 	/* Calculate offset. */
 
-	if (NULL != (nn = n->parent->head->child))
-		if (nn->string && nn->parent->line == nn->line)
-			if ((ival = a2width(p, nn->string)) >= 0)
-				len = (size_t)ival;
+	if ((nn = n->parent->head->child) != NULL &&
+	    nn->string != NULL && ! (MAN_LINE & nn->flags) &&
+	    a2roffsu(nn->string, &su, SCALE_EN)) {
+		len = term_hspan(p, &su);
+		if (len < 0 && (size_t)(-len) > mt->offset)
+			len = -mt->offset;
+		else if (len > SHRT_MAX)
+			len = term_len(p, p->defindent);
+		mt->lmargin[mt->lmargincur] = len;
+	} else
+		len = mt->lmargin[mt->lmargincur];
 
 	switch (n->type) {
-	case (MAN_HEAD):
-		/* Handle zero-length properly. */
-		if (0 == len)
-			len = term_len(p, 1);
-
+	case MAN_HEAD:
 		p->offset = mt->offset;
 		p->rmargin = mt->offset + len;
 
@@ -736,17 +683,19 @@ pre_TP(DECL_ARGS)
 		mt->fl &= ~MANT_LITERAL;
 
 		/* Don't print same-line elements. */
-		for (nn = n->child; nn; nn = nn->next)
-			if (nn->line > n->line)
-				print_man_node(p, mt, nn, meta);
+		nn = n->child;
+		while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+			nn = nn->next;
+
+		while (NULL != nn) {
+			print_man_node(p, mt, nn, meta);
+			nn = nn->next;
+		}
 
 		if (savelit)
 			mt->fl |= MANT_LITERAL;
-		if (ival >= 0)
-			mt->lmargin[mt->lmargincur] = (size_t)ival;
-
 		return(0);
-	case (MAN_BODY):
+	case MAN_BODY:
 		p->offset = mt->offset + len;
 		p->rmargin = p->maxrmargin;
 		p->trailspace = 0;
@@ -759,17 +708,15 @@ pre_TP(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 post_TP(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		term_flushln(p);
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		term_newln(p);
 		p->offset = mt->offset;
 		break;
@@ -778,32 +725,36 @@ post_TP(DECL_ARGS)
 	}
 }
 
-
-/* ARGSUSED */
 static int
 pre_SS(DECL_ARGS)
 {
 	int	 i;
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		mt->fl &= ~MANT_LITERAL;
 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 		mt->offset = term_len(p, p->defindent);
-		/* If following a prior empty `SS', no vspace. */
-		if (n->prev && MAN_SS == n->prev->tok)
-			if (NULL == n->prev->body->child)
-				break;
-		if (NULL == n->prev)
+
+		/*
+		 * No vertical space before the first subsection
+		 * and after an empty subsection.
+		 */
+
+		do {
+			n = n->prev;
+		} while (n != NULL && termacts[n->tok].flags & MAN_NOTEXT);
+		if (n == NULL || (n->tok == MAN_SS && n->body->child == NULL))
 			break;
+
 		for (i = 0; i < mt->pardist; i++)
 			term_vspace(p);
 		break;
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		term_fontrepl(p, TERMFONT_BOLD);
 		p->offset = term_len(p, 3);
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		p->offset = mt->offset;
 		break;
 	default:
@@ -813,17 +764,15 @@ pre_SS(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 post_SS(DECL_ARGS)
 {
-	
+
 	switch (n->type) {
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		term_newln(p);
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		term_newln(p);
 		break;
 	default:
@@ -831,33 +780,36 @@ post_SS(DECL_ARGS)
 	}
 }
 
-
-/* ARGSUSED */
 static int
 pre_SH(DECL_ARGS)
 {
 	int	 i;
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		mt->fl &= ~MANT_LITERAL;
 		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
 		mt->offset = term_len(p, p->defindent);
-		/* If following a prior empty `SH', no vspace. */
-		if (n->prev && MAN_SH == n->prev->tok)
-			if (NULL == n->prev->body->child)
-				break;
-		/* If the first macro, no vspae. */
-		if (NULL == n->prev)
+
+		/*
+		 * No vertical space before the first section
+		 * and after an empty section.
+		 */
+
+		do {
+			n = n->prev;
+		} while (n != NULL && termacts[n->tok].flags & MAN_NOTEXT);
+		if (n == NULL || (n->tok == MAN_SH && n->body->child == NULL))
 			break;
+
 		for (i = 0; i < mt->pardist; i++)
 			term_vspace(p);
 		break;
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		term_fontrepl(p, TERMFONT_BOLD);
 		p->offset = 0;
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		p->offset = mt->offset;
 		break;
 	default:
@@ -867,17 +819,15 @@ pre_SH(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 post_SH(DECL_ARGS)
 {
-	
+
 	switch (n->type) {
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		term_newln(p);
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		term_newln(p);
 		break;
 	default:
@@ -885,32 +835,33 @@ post_SH(DECL_ARGS)
 	}
 }
 
-/* ARGSUSED */
 static int
 pre_RS(DECL_ARGS)
 {
-	int		 ival;
-	size_t		 sz;
+	struct roffsu	 su;
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		term_newln(p);
 		return(1);
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		return(0);
 	default:
 		break;
 	}
 
-	sz = term_len(p, p->defindent);
-
-	if (NULL != (n = n->parent->head->child))
-		if ((ival = a2width(p, n->string)) >= 0) 
-			sz = (size_t)ival;
+	n = n->parent->head;
+	n->aux = SHRT_MAX + 1;
+	if (n->child != NULL && a2roffsu(n->child->string, &su, SCALE_EN))
+		n->aux = term_hspan(p, &su);
+	if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
+		n->aux = -mt->offset;
+	else if (n->aux > SHRT_MAX)
+		n->aux = term_len(p, p->defindent);
 
-	mt->offset += sz;
+	mt->offset += n->aux;
+	p->offset = mt->offset;
 	p->rmargin = p->maxrmargin;
-	p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
 
 	if (++mt->lmarginsz < MAXMARGINS)
 		mt->lmargincur = mt->lmarginsz;
@@ -919,37 +870,27 @@ pre_RS(DECL_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static void
 post_RS(DECL_ARGS)
 {
-	int		 ival;
-	size_t		 sz;
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		return;
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		return;
 	default:
 		term_newln(p);
 		break;
 	}
 
-	sz = term_len(p, p->defindent);
-
-	if (NULL != (n = n->parent->head->child)) 
-		if ((ival = a2width(p, n->string)) >= 0) 
-			sz = (size_t)ival;
-
-	mt->offset = mt->offset < sz ?  0 : mt->offset - sz;
+	mt->offset -= n->parent->head->aux;
 	p->offset = mt->offset;
 
 	if (--mt->lmarginsz < MAXMARGINS)
 		mt->lmargincur = mt->lmarginsz;
 }
 
-/* ARGSUSED */
 static int
 pre_UR(DECL_ARGS)
 {
@@ -957,7 +898,6 @@ pre_UR(DECL_ARGS)
 	return (MAN_HEAD != n->type);
 }
 
-/* ARGSUSED */
 static void
 post_UR(DECL_ARGS)
 {
@@ -982,7 +922,7 @@ print_man_node(DECL_ARGS)
 	int		 c;
 
 	switch (n->type) {
-	case(MAN_TEXT):
+	case MAN_TEXT:
 		/*
 		 * If we have a blank line, output a vertical space.
 		 * If we have a space as the first character, break
@@ -997,16 +937,16 @@ print_man_node(DECL_ARGS)
 		term_word(p, n->string);
 		goto out;
 
-	case (MAN_EQN):
+	case MAN_EQN:
+		if ( ! (n->flags & MAN_LINE))
+			p->flags |= TERMP_NOSPACE;
 		term_eqn(p, n->eqn);
+		if (n->next != NULL && ! (n->next->flags & MAN_LINE))
+			p->flags |= TERMP_NOSPACE;
 		return;
-	case (MAN_TBL):
-		/*
-		 * Tables are preceded by a newline.  Then process a
-		 * table line, which will cause line termination,
-		 */
-		if (TBL_SPAN_FIRST & n->span->flags) 
-			term_newln(p);
+	case MAN_TBL:
+		if (p->tbl.cols == NULL)
+			term_vspace(p);
 		term_tbl(p, n->span);
 		return;
 	default:
@@ -1036,13 +976,14 @@ out:
 	 * -man doesn't have nested macros, we don't need to be
 	 * more specific than this.
 	 */
-	if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
-	    (NULL == n->next || n->next->line > n->line)) {
+	if (mt->fl & MANT_LITERAL &&
+	    ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
+	    (n->next == NULL || n->next->flags & MAN_LINE)) {
 		rm = p->rmargin;
 		rmax = p->maxrmargin;
 		p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
 		p->flags |= TERMP_NOSPACE;
-		if (NULL != n->string && '\0' != *n->string)
+		if (n->string != NULL && *n->string != '\0')
 			term_flushln(p);
 		else
 			term_newln(p);
@@ -1062,19 +1003,18 @@ static void
 print_man_nodelist(DECL_ARGS)
 {
 
-	print_man_node(p, mt, n, meta);
-	if ( ! n->next)
-		return;
-	print_man_nodelist(p, mt, n->next, meta);
+	while (n != NULL) {
+		print_man_node(p, mt, n, meta);
+		n = n->next;
+	}
 }
 
-
 static void
 print_man_foot(struct termp *p, const void *arg)
 {
-	char		title[BUFSIZ];
-	size_t		datelen;
-	const struct man_meta *meta;
+	const struct man_meta	*meta;
+	char			*title;
+	size_t			 datelen, titlen;
 
 	meta = (const struct man_meta *)arg;
 	assert(meta->title);
@@ -1083,7 +1023,8 @@ print_man_foot(struct termp *p, const void *arg)
 
 	term_fontrepl(p, TERMFONT_NONE);
 
-	term_vspace(p);
+	if (meta->hasbody)
+		term_vspace(p);
 
 	/*
 	 * Temporary, undocumented option to imitate mdoc(7) output.
@@ -1092,13 +1033,16 @@ print_man_foot(struct termp *p, const void *arg)
 	 */
 
 	if ( ! p->mdocstyle) {
-		term_vspace(p);
-		term_vspace(p);
-		snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
+		if (meta->hasbody) {
+			term_vspace(p);
+			term_vspace(p);
+		}
+		mandoc_asprintf(&title, "%s(%s)",
+		    meta->title, meta->msec);
 	} else if (meta->source) {
-		strlcpy(title, meta->source, BUFSIZ);
+		title = mandoc_strdup(meta->source);
 	} else {
-		title[0] = '\0';
+		title = mandoc_strdup("");
 	}
 	datelen = term_strlen(p, meta->date);
 
@@ -1107,7 +1051,8 @@ print_man_foot(struct termp *p, const void *arg)
 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
 	p->trailspace = 1;
 	p->offset = 0;
-	p->rmargin = (p->maxrmargin - datelen + term_len(p, 1)) / 2;
+	p->rmargin = p->maxrmargin > datelen ?
+	    (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
 
 	if (meta->source)
 		term_word(p, meta->source);
@@ -1115,11 +1060,10 @@ print_man_foot(struct termp *p, const void *arg)
 
 	/* At the bottom in the middle: manual date. */
 
-	p->flags |= TERMP_NOSPACE;
 	p->offset = p->rmargin;
-	p->rmargin = p->maxrmargin - term_strlen(p, title);
-	if (p->offset + datelen >= p->rmargin)
-		p->rmargin = p->offset + datelen;
+	titlen = term_strlen(p, title);
+	p->rmargin = p->maxrmargin > titlen ? p->maxrmargin - titlen : 0;
+	p->flags |= TERMP_NOSPACE;
 
 	term_word(p, meta->date);
 	term_flushln(p);
@@ -1134,38 +1078,35 @@ print_man_foot(struct termp *p, const void *arg)
 
 	term_word(p, title);
 	term_flushln(p);
+	free(title);
 }
 
-
 static void
 print_man_head(struct termp *p, const void *arg)
 {
-	char		buf[BUFSIZ], title[BUFSIZ];
-	size_t		buflen, titlen;
-	const struct man_meta *meta;
+	const struct man_meta	*meta;
+	const char		*volume;
+	char			*title;
+	size_t			 vollen, titlen;
 
 	meta = (const struct man_meta *)arg;
 	assert(meta->title);
 	assert(meta->msec);
 
-	if (meta->vol)
-		strlcpy(buf, meta->vol, BUFSIZ);
-	else
-		buf[0] = '\0';
-	buflen = term_strlen(p, buf);
+	volume = NULL == meta->vol ? "" : meta->vol;
+	vollen = term_strlen(p, volume);
 
 	/* Top left corner: manual title and section. */
 
-	snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
+	mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
 	titlen = term_strlen(p, title);
 
 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
 	p->trailspace = 1;
 	p->offset = 0;
-	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
-	    (p->maxrmargin - 
-	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
-	    p->maxrmargin - buflen;
+	p->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
+	    (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
+	    vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
 
 	term_word(p, title);
 	term_flushln(p);
@@ -1174,10 +1115,10 @@ print_man_head(struct termp *p, const void *arg)
 
 	p->flags |= TERMP_NOSPACE;
 	p->offset = p->rmargin;
-	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
+	p->rmargin = p->offset + vollen + titlen < p->maxrmargin ?
 	    p->maxrmargin - titlen : p->maxrmargin;
 
-	term_word(p, buf);
+	term_word(p, volume);
 	term_flushln(p);
 
 	/* Top right corner: title and section, again. */
@@ -1196,7 +1137,7 @@ print_man_head(struct termp *p, const void *arg)
 	p->offset = 0;
 	p->rmargin = p->maxrmargin;
 
-	/* 
+	/*
 	 * Groff prints three blank lines before the content.
 	 * Do the same, except in the temporary, undocumented
 	 * mode imitating mdoc(7) output.
@@ -1207,4 +1148,5 @@ print_man_head(struct termp *p, const void *arg)
 		term_vspace(p);
 		term_vspace(p);
 	}
+	free(title);
 }
diff --git a/usr/src/cmd/mandoc/man_validate.c b/usr/src/cmd/mandoc/man_validate.c
index da2e557ebb..93ee9b3f20 100644
--- a/usr/src/cmd/mandoc/man_validate.c
+++ b/usr/src/cmd/mandoc/man_validate.c
@@ -1,7 +1,7 @@
-/*	$Id: man_validate.c,v 1.86 2013/10/17 20:54:58 schwarze Exp $ */
+/*	$OpenBSD$ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,7 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
@@ -32,190 +30,128 @@
 
 #include "man.h"
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libman.h"
 #include "libmandoc.h"
 
 #define	CHKARGS	  struct man *man, struct man_node *n
 
-typedef	int	(*v_check)(CHKARGS);
+typedef	void	(*v_check)(CHKARGS);
 
-struct	man_valid {
-	v_check	 *pres;
-	v_check	 *posts;
-};
-
-static	int	  check_eq0(CHKARGS);
-static	int	  check_eq2(CHKARGS);
-static	int	  check_le1(CHKARGS);
-static	int	  check_ge2(CHKARGS);
-static	int	  check_le5(CHKARGS);
-static	int	  check_head1(CHKARGS);
-static	int	  check_par(CHKARGS);
-static	int	  check_part(CHKARGS);
-static	int	  check_root(CHKARGS);
+static	void	  check_par(CHKARGS);
+static	void	  check_part(CHKARGS);
+static	void	  check_root(CHKARGS);
 static	void	  check_text(CHKARGS);
 
-static	int	  post_AT(CHKARGS);
-static	int	  post_IP(CHKARGS);
-static	int	  post_vs(CHKARGS);
-static	int	  post_fi(CHKARGS);
-static	int	  post_ft(CHKARGS);
-static	int	  post_nf(CHKARGS);
-static	int	  post_sec(CHKARGS);
-static	int	  post_TH(CHKARGS);
-static	int	  post_UC(CHKARGS);
-static	int	  pre_sec(CHKARGS);
-
-static	v_check	  posts_at[] = { post_AT, NULL };
-static	v_check	  posts_br[] = { post_vs, check_eq0, NULL };
-static	v_check	  posts_eq0[] = { check_eq0, NULL };
-static	v_check	  posts_eq2[] = { check_eq2, NULL };
-static	v_check	  posts_fi[] = { check_eq0, post_fi, NULL };
-static	v_check	  posts_ft[] = { post_ft, NULL };
-static	v_check	  posts_ip[] = { post_IP, NULL };
-static	v_check	  posts_le1[] = { check_le1, NULL };
-static	v_check	  posts_nf[] = { check_eq0, post_nf, NULL };
-static	v_check	  posts_par[] = { check_par, NULL };
-static	v_check	  posts_part[] = { check_part, NULL };
-static	v_check	  posts_sec[] = { post_sec, NULL };
-static	v_check	  posts_sp[] = { post_vs, check_le1, NULL };
-static	v_check	  posts_th[] = { check_ge2, check_le5, post_TH, NULL };
-static	v_check	  posts_uc[] = { post_UC, NULL };
-static	v_check	  posts_ur[] = { check_head1, check_part, NULL };
-static	v_check	  pres_sec[] = { pre_sec, NULL };
-
-static	const struct man_valid man_valids[MAN_MAX] = {
-	{ NULL, posts_br }, /* br */
-	{ NULL, posts_th }, /* TH */
-	{ pres_sec, posts_sec }, /* SH */
-	{ pres_sec, posts_sec }, /* SS */
-	{ NULL, NULL }, /* TP */
-	{ NULL, posts_par }, /* LP */
-	{ NULL, posts_par }, /* PP */
-	{ NULL, posts_par }, /* P */
-	{ NULL, posts_ip }, /* IP */
-	{ NULL, NULL }, /* HP */
-	{ NULL, NULL }, /* SM */
-	{ NULL, NULL }, /* SB */
-	{ NULL, NULL }, /* BI */
-	{ NULL, NULL }, /* IB */
-	{ NULL, NULL }, /* BR */
-	{ NULL, NULL }, /* RB */
-	{ NULL, NULL }, /* R */
-	{ NULL, NULL }, /* B */
-	{ NULL, NULL }, /* I */
-	{ NULL, NULL }, /* IR */
-	{ NULL, NULL }, /* RI */
-	{ NULL, posts_eq0 }, /* na */
-	{ NULL, posts_sp }, /* sp */
-	{ NULL, posts_nf }, /* nf */
-	{ NULL, posts_fi }, /* fi */
-	{ NULL, NULL }, /* RE */
-	{ NULL, posts_part }, /* RS */
-	{ NULL, NULL }, /* DT */
-	{ NULL, posts_uc }, /* UC */
-	{ NULL, posts_le1 }, /* PD */
-	{ NULL, posts_at }, /* AT */
-	{ NULL, NULL }, /* in */
-	{ NULL, posts_ft }, /* ft */
-	{ NULL, posts_eq2 }, /* OP */
-	{ NULL, posts_nf }, /* EX */
-	{ NULL, posts_fi }, /* EE */
-	{ NULL, posts_ur }, /* UR */
-	{ NULL, NULL }, /* UE */
+static	void	  post_AT(CHKARGS);
+static	void	  post_IP(CHKARGS);
+static	void	  post_vs(CHKARGS);
+static	void	  post_fi(CHKARGS);
+static	void	  post_ft(CHKARGS);
+static	void	  post_nf(CHKARGS);
+static	void	  post_OP(CHKARGS);
+static	void	  post_TH(CHKARGS);
+static	void	  post_UC(CHKARGS);
+static	void	  post_UR(CHKARGS);
+
+static	v_check man_valids[MAN_MAX] = {
+	post_vs,    /* br */
+	post_TH,    /* TH */
+	NULL,       /* SH */
+	NULL,       /* SS */
+	NULL,       /* TP */
+	check_par,  /* LP */
+	check_par,  /* PP */
+	check_par,  /* P */
+	post_IP,    /* IP */
+	NULL,       /* HP */
+	NULL,       /* SM */
+	NULL,       /* SB */
+	NULL,       /* BI */
+	NULL,       /* IB */
+	NULL,       /* BR */
+	NULL,       /* RB */
+	NULL,       /* R */
+	NULL,       /* B */
+	NULL,       /* I */
+	NULL,       /* IR */
+	NULL,       /* RI */
+	post_vs,    /* sp */
+	post_nf,    /* nf */
+	post_fi,    /* fi */
+	NULL,       /* RE */
+	check_part, /* RS */
+	NULL,       /* DT */
+	post_UC,    /* UC */
+	NULL,       /* PD */
+	post_AT,    /* AT */
+	NULL,       /* in */
+	post_ft,    /* ft */
+	post_OP,    /* OP */
+	post_nf,    /* EX */
+	post_fi,    /* EE */
+	post_UR,    /* UR */
+	NULL,       /* UE */
+	NULL,       /* ll */
 };
 
 
-int
-man_valid_pre(struct man *man, struct man_node *n)
+void
+man_valid_post(struct man *man)
 {
+	struct man_node	*n;
 	v_check		*cp;
 
+	n = man->last;
+	if (n->flags & MAN_VALID)
+		return;
+	n->flags |= MAN_VALID;
+
 	switch (n->type) {
-	case (MAN_TEXT):
-		/* FALLTHROUGH */
-	case (MAN_ROOT):
-		/* FALLTHROUGH */
-	case (MAN_EQN):
-		/* FALLTHROUGH */
-	case (MAN_TBL):
-		return(1);
-	default:
+	case MAN_TEXT:
+		check_text(man, n);
 		break;
-	}
-
-	if (NULL == (cp = man_valids[n->tok].pres))
-		return(1);
-	for ( ; *cp; cp++)
-		if ( ! (*cp)(man, n)) 
-			return(0);
-	return(1);
-}
-
-
-int
-man_valid_post(struct man *man)
-{
-	v_check		*cp;
-
-	if (MAN_VALID & man->last->flags)
-		return(1);
-	man->last->flags |= MAN_VALID;
-
-	switch (man->last->type) {
-	case (MAN_TEXT): 
-		check_text(man, man->last);
-		return(1);
-	case (MAN_ROOT):
-		return(check_root(man, man->last));
-	case (MAN_EQN):
+	case MAN_ROOT:
+		check_root(man, n);
+		break;
+	case MAN_EQN:
 		/* FALLTHROUGH */
-	case (MAN_TBL):
-		return(1);
+	case MAN_TBL:
+		break;
 	default:
+		cp = man_valids + n->tok;
+		if (*cp)
+			(*cp)(man, n);
 		break;
 	}
-
-	if (NULL == (cp = man_valids[man->last->tok].posts))
-		return(1);
-	for ( ; *cp; cp++)
-		if ( ! (*cp)(man, man->last))
-			return(0);
-
-	return(1);
 }
 
-
-static int
-check_root(CHKARGS) 
+static void
+check_root(CHKARGS)
 {
 
-	if (MAN_BLINE & man->flags)
-		man_nmsg(man, n, MANDOCERR_SCOPEEXIT);
-	else if (MAN_ELINE & man->flags)
-		man_nmsg(man, n, MANDOCERR_SCOPEEXIT);
+	assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
 
-	man->flags &= ~MAN_BLINE;
-	man->flags &= ~MAN_ELINE;
+	if (NULL == man->first->child)
+		mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
+		    n->line, n->pos, NULL);
+	else
+		man->meta.hasbody = 1;
 
-	if (NULL == man->first->child) {
-		man_nmsg(man, n, MANDOCERR_NODOCBODY);
-		return(0);
-	} else if (NULL == man->meta.title) {
-		man_nmsg(man, n, MANDOCERR_NOTITLE);
+	if (NULL == man->meta.title) {
+		mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
+		    n->line, n->pos, NULL);
 
 		/*
 		 * If a title hasn't been set, do so now (by
 		 * implication, date and section also aren't set).
 		 */
 
-	        man->meta.title = mandoc_strdup("unknown");
-		man->meta.msec = mandoc_strdup("1");
-		man->meta.date = mandoc_normdate
-			(man->parse, NULL, n->line, n->pos);
+		man->meta.title = mandoc_strdup("");
+		man->meta.msec = mandoc_strdup("");
+		man->meta.date = man->quick ? mandoc_strdup("") :
+		    mandoc_normdate(man->parse, NULL, n->line, n->pos);
 	}
-
-	return(1);
 }
 
 static void
@@ -228,71 +164,67 @@ check_text(CHKARGS)
 
 	cp = n->string;
 	for (p = cp; NULL != (p = strchr(p, '\t')); p++)
-		man_pmsg(man, n->line, (int)(p - cp), MANDOCERR_BADTAB);
+		mandoc_msg(MANDOCERR_FI_TAB, man->parse,
+		    n->line, n->pos + (p - cp), NULL);
 }
 
-#define	INEQ_DEFINE(x, ineq, name) \
-static int \
-check_##name(CHKARGS) \
-{ \
-	if (n->nchild ineq (x)) \
-		return(1); \
-	mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line, n->pos, \
-			"line arguments %s %d (have %d)", \
-			#ineq, (x), n->nchild); \
-	return(1); \
-}
+static void
+post_OP(CHKARGS)
+{
 
-INEQ_DEFINE(0, ==, eq0)
-INEQ_DEFINE(2, ==, eq2)
-INEQ_DEFINE(1, <=, le1)
-INEQ_DEFINE(2, >=, ge2)
-INEQ_DEFINE(5, <=, le5)
+	if (n->nchild == 0)
+		mandoc_msg(MANDOCERR_OP_EMPTY, man->parse,
+		    n->line, n->pos, "OP");
+	else if (n->nchild > 2) {
+		n = n->child->next->next;
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
+		    n->line, n->pos, "OP ... %s", n->string);
+	}
+}
 
-static int
-check_head1(CHKARGS)
+static void
+post_UR(CHKARGS)
 {
 
-	if (MAN_HEAD == n->type && 1 != n->nchild)
-		mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
-		    n->pos, "line arguments eq 1 (have %d)", n->nchild);
-
-	return(1);
+	if (n->type == MAN_HEAD && n->child == NULL)
+		mandoc_vmsg(MANDOCERR_UR_NOHEAD, man->parse,
+		    n->line, n->pos, "UR");
+	check_part(man, n);
 }
 
-static int
+static void
 post_ft(CHKARGS)
 {
 	char	*cp;
 	int	 ok;
 
 	if (0 == n->nchild)
-		return(1);
+		return;
 
 	ok = 0;
 	cp = n->child->string;
 	switch (*cp) {
-	case ('1'):
+	case '1':
 		/* FALLTHROUGH */
-	case ('2'):
+	case '2':
 		/* FALLTHROUGH */
-	case ('3'):
+	case '3':
 		/* FALLTHROUGH */
-	case ('4'):
+	case '4':
 		/* FALLTHROUGH */
-	case ('I'):
+	case 'I':
 		/* FALLTHROUGH */
-	case ('P'):
+	case 'P':
 		/* FALLTHROUGH */
-	case ('R'):
+	case 'R':
 		if ('\0' == cp[1])
 			ok = 1;
 		break;
-	case ('B'):
+	case 'B':
 		if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
 			ok = 1;
 		break;
-	case ('C'):
+	case 'C':
 		if ('W' == cp[1] && '\0' == cp[2])
 			ok = 1;
 		break;
@@ -301,101 +233,74 @@ post_ft(CHKARGS)
 	}
 
 	if (0 == ok) {
-		mandoc_vmsg
-			(MANDOCERR_BADFONT, man->parse,
-			 n->line, n->pos, "%s", cp);
+		mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
+		    n->line, n->pos, "ft %s", cp);
 		*cp = '\0';
 	}
-
-	if (1 < n->nchild)
-		mandoc_vmsg
-			(MANDOCERR_ARGCOUNT, man->parse, n->line, 
-			 n->pos, "want one child (have %d)", 
-			 n->nchild);
-
-	return(1);
-}
-
-static int
-pre_sec(CHKARGS)
-{
-
-	if (MAN_BLOCK == n->type)
-		man->flags &= ~MAN_LITERAL;
-	return(1);
-}
-
-static int
-post_sec(CHKARGS)
-{
-
-	if ( ! (MAN_HEAD == n->type && 0 == n->nchild)) 
-		return(1);
-
-	man_nmsg(man, n, MANDOCERR_SYNTARGCOUNT);
-	return(0);
 }
 
-static int
+static void
 check_part(CHKARGS)
 {
 
-	if (MAN_BODY == n->type && 0 == n->nchild)
-		mandoc_msg(MANDOCERR_ARGCWARN, man->parse, n->line, 
-				n->pos, "want children (have none)");
-
-	return(1);
+	if (n->type == MAN_BODY && n->child == NULL)
+		mandoc_msg(MANDOCERR_BLK_EMPTY, man->parse,
+		    n->line, n->pos, man_macronames[n->tok]);
 }
 
-
-static int
+static void
 check_par(CHKARGS)
 {
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		if (0 == n->body->nchild)
 			man_node_delete(man, n);
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		if (0 == n->nchild)
-			man_nmsg(man, n, MANDOCERR_IGNPAR);
+			mandoc_vmsg(MANDOCERR_PAR_SKIP,
+			    man->parse, n->line, n->pos,
+			    "%s empty", man_macronames[n->tok]);
 		break;
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		if (n->nchild)
-			man_nmsg(man, n, MANDOCERR_ARGSLOST);
+			mandoc_vmsg(MANDOCERR_ARG_SKIP,
+			    man->parse, n->line, n->pos,
+			    "%s %s%s", man_macronames[n->tok],
+			    n->child->string,
+			    n->nchild > 1 ? " ..." : "");
 		break;
 	default:
 		break;
 	}
-
-	return(1);
 }
 
-static int
+static void
 post_IP(CHKARGS)
 {
 
 	switch (n->type) {
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		if (0 == n->head->nchild && 0 == n->body->nchild)
 			man_node_delete(man, n);
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		if (0 == n->parent->head->nchild && 0 == n->nchild)
-			man_nmsg(man, n, MANDOCERR_IGNPAR);
+			mandoc_vmsg(MANDOCERR_PAR_SKIP,
+			    man->parse, n->line, n->pos,
+			    "%s empty", man_macronames[n->tok]);
 		break;
 	default:
 		break;
 	}
-	return(1);
 }
 
-static int
+static void
 post_TH(CHKARGS)
 {
+	struct man_node	*nb;
 	const char	*p;
-	int		 line, pos;
 
 	free(man->meta.title);
 	free(man->meta.vol);
@@ -403,10 +308,10 @@ post_TH(CHKARGS)
 	free(man->meta.msec);
 	free(man->meta.date);
 
-	line = n->line;
-	pos = n->pos;
 	man->meta.title = man->meta.vol = man->meta.date =
-		man->meta.msec = man->meta.source = NULL;
+	    man->meta.msec = man->meta.source = NULL;
+
+	nb = n;
 
 	/* ->TITLE<- MSEC DATE SOURCE VOL */
 
@@ -414,15 +319,21 @@ post_TH(CHKARGS)
 	if (n && n->string) {
 		for (p = n->string; '\0' != *p; p++) {
 			/* Only warn about this once... */
-			if (isalpha((unsigned char)*p) && 
-					! isupper((unsigned char)*p)) {
-				man_nmsg(man, n, MANDOCERR_UPPERCASE);
+			if (isalpha((unsigned char)*p) &&
+			    ! isupper((unsigned char)*p)) {
+				mandoc_vmsg(MANDOCERR_TITLE_CASE,
+				    man->parse, n->line,
+				    n->pos + (p - n->string),
+				    "TH %s", n->string);
 				break;
 			}
 		}
 		man->meta.title = mandoc_strdup(n->string);
-	} else
+	} else {
 		man->meta.title = mandoc_strdup("");
+		mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
+		    nb->line, nb->pos, "TH");
+	}
 
 	/* TITLE ->MSEC<- DATE SOURCE VOL */
 
@@ -430,24 +341,34 @@ post_TH(CHKARGS)
 		n = n->next;
 	if (n && n->string)
 		man->meta.msec = mandoc_strdup(n->string);
-	else
+	else {
 		man->meta.msec = mandoc_strdup("");
+		mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
+		    nb->line, nb->pos, "TH %s", man->meta.title);
+	}
 
 	/* TITLE MSEC ->DATE<- SOURCE VOL */
 
 	if (n)
 		n = n->next;
 	if (n && n->string && '\0' != n->string[0]) {
-		pos = n->pos;
-		man->meta.date = mandoc_normdate
-		    (man->parse, n->string, line, pos);
-	} else
+		man->meta.date = man->quick ?
+		    mandoc_strdup(n->string) :
+		    mandoc_normdate(man->parse, n->string,
+			n->line, n->pos);
+	} else {
 		man->meta.date = mandoc_strdup("");
+		mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
+		    n ? n->line : nb->line,
+		    n ? n->pos : nb->pos, "TH");
+	}
 
 	/* TITLE MSEC DATE ->SOURCE<- VOL */
 
 	if (n && (n = n->next))
 		man->meta.source = mandoc_strdup(n->string);
+	else if (man->defos != NULL)
+		man->meta.source = mandoc_strdup(man->defos);
 
 	/* TITLE MSEC DATE SOURCE ->VOL<- */
 	/* If missing, use the default VOL name for MSEC. */
@@ -458,37 +379,40 @@ post_TH(CHKARGS)
 	    (NULL != (p = mandoc_a2msec(man->meta.msec))))
 		man->meta.vol = mandoc_strdup(p);
 
+	if (n != NULL && (n = n->next) != NULL)
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
+		    n->line, n->pos, "TH ... %s", n->string);
+
 	/*
 	 * Remove the `TH' node after we've processed it for our
 	 * meta-data.
 	 */
 	man_node_delete(man, man->last);
-	return(1);
 }
 
-static int
+static void
 post_nf(CHKARGS)
 {
 
-	if (MAN_LITERAL & man->flags)
-		man_nmsg(man, n, MANDOCERR_SCOPEREP);
+	if (man->flags & MAN_LITERAL)
+		mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
+		    n->line, n->pos, "nf");
 
 	man->flags |= MAN_LITERAL;
-	return(1);
 }
 
-static int
+static void
 post_fi(CHKARGS)
 {
 
 	if ( ! (MAN_LITERAL & man->flags))
-		man_nmsg(man, n, MANDOCERR_WNOSCOPE);
+		mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
+		    n->line, n->pos, "fi");
 
 	man->flags &= ~MAN_LITERAL;
-	return(1);
 }
 
-static int
+static void
 post_UC(CHKARGS)
 {
 	static const char * const bsd_versions[] = {
@@ -523,10 +447,9 @@ post_UC(CHKARGS)
 
 	free(man->meta.source);
 	man->meta.source = mandoc_strdup(p);
-	return(1);
 }
 
-static int
+static void
 post_AT(CHKARGS)
 {
 	static const char * const unix_versions[] = {
@@ -561,24 +484,25 @@ post_AT(CHKARGS)
 
 	free(man->meta.source);
 	man->meta.source = mandoc_strdup(p);
-	return(1);
 }
 
-static int
+static void
 post_vs(CHKARGS)
 {
 
 	if (NULL != n->prev)
-		return(1);
+		return;
 
 	switch (n->parent->tok) {
-	case (MAN_SH):
+	case MAN_SH:
 		/* FALLTHROUGH */
-	case (MAN_SS):
-		man_nmsg(man, n, MANDOCERR_IGNPAR);
+	case MAN_SS:
+		mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
+		    "%s after %s", man_macronames[n->tok],
+		    man_macronames[n->parent->tok]);
 		/* FALLTHROUGH */
-	case (MAN_MAX):
-		/* 
+	case MAN_MAX:
+		/*
 		 * Don't warn about this because it occurs in pod2man
 		 * and would cause considerable (unfixable) warnage.
 		 */
@@ -587,6 +511,4 @@ post_vs(CHKARGS)
 	default:
 		break;
 	}
-
-	return(1);
 }
diff --git a/usr/src/cmd/mandoc/mandoc.c b/usr/src/cmd/mandoc/mandoc.c
index da738f20fa..0619420cb1 100644
--- a/usr/src/cmd/mandoc/mandoc.c
+++ b/usr/src/cmd/mandoc/mandoc.c
@@ -1,7 +1,7 @@
-/*	$Id: mandoc.c,v 1.74 2013/12/30 18:30:32 schwarze Exp $ */
+/*	$Id: mandoc.c,v 1.92 2015/02/20 23:55:10 schwarze Exp $ */
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons 
+ * Copyright (c) 2011-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,7 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
@@ -31,6 +29,7 @@
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmandoc.h"
 
 #define DATESIZE 32
@@ -45,7 +44,7 @@ mandoc_escape(const char **end, const char **start, int *sz)
 	const char	*local_start;
 	int		 local_sz;
 	char		 term;
-	enum mandoc_esc	 gly; 
+	enum mandoc_esc	 gly;
 
 	/*
 	 * When the caller doesn't provide return storage,
@@ -74,80 +73,69 @@ mandoc_escape(const char **end, const char **start, int *sz)
 	 * these, but each eventually returns a substring of the glyph
 	 * name.
 	 */
-	case ('('):
+	case '(':
 		gly = ESCAPE_SPECIAL;
 		*sz = 2;
 		break;
-	case ('['):
+	case '[':
 		gly = ESCAPE_SPECIAL;
-		/*
-		 * Unicode escapes are defined in groff as \[uXXXX] to
-		 * \[u10FFFF], where the contained value must be a valid
-		 * Unicode codepoint.  Here, however, only check whether
-		 * it's not a zero-width escape.
-		 */
-		if ('u' == (*start)[0] && ']' != (*start)[1])
-			gly = ESCAPE_UNICODE;
 		term = ']';
 		break;
-	case ('C'):
+	case 'C':
 		if ('\'' != **start)
 			return(ESCAPE_ERROR);
 		*start = ++*end;
-		if ('u' == (*start)[0] && '\'' != (*start)[1])
-			gly = ESCAPE_UNICODE;
-		else
-			gly = ESCAPE_SPECIAL;
+		gly = ESCAPE_SPECIAL;
 		term = '\'';
 		break;
 
 	/*
 	 * Escapes taking no arguments at all.
 	 */
-	case ('d'):
+	case 'd':
 		/* FALLTHROUGH */
-	case ('u'):
+	case 'u':
 		return(ESCAPE_IGNORE);
 
 	/*
 	 * The \z escape is supposed to output the following
-	 * character without advancing the cursor position.  
+	 * character without advancing the cursor position.
 	 * Since we are mostly dealing with terminal mode,
 	 * let us just skip the next character.
 	 */
-	case ('z'):
+	case 'z':
 		return(ESCAPE_SKIPCHAR);
 
 	/*
 	 * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
 	 * 'X' is the trigger.  These have opaque sub-strings.
 	 */
-	case ('F'):
+	case 'F':
 		/* FALLTHROUGH */
-	case ('g'):
+	case 'g':
 		/* FALLTHROUGH */
-	case ('k'):
+	case 'k':
 		/* FALLTHROUGH */
-	case ('M'):
+	case 'M':
 		/* FALLTHROUGH */
-	case ('m'):
+	case 'm':
 		/* FALLTHROUGH */
-	case ('n'):
+	case 'n':
 		/* FALLTHROUGH */
-	case ('V'):
+	case 'V':
 		/* FALLTHROUGH */
-	case ('Y'):
+	case 'Y':
 		gly = ESCAPE_IGNORE;
 		/* FALLTHROUGH */
-	case ('f'):
+	case 'f':
 		if (ESCAPE_ERROR == gly)
 			gly = ESCAPE_FONT;
 		switch (**start) {
-		case ('('):
+		case '(':
 			*start = ++*end;
 			*sz = 2;
 			break;
-		case ('['):
+		case '[':
 			*start = ++*end;
 			term = ']';
 			break;
@@ -160,60 +148,62 @@ mandoc_escape(const char **end, const char **start, int *sz)
 	/*
 	 * These escapes are of the form \X'Y', where 'X' is the trigger
 	 * and 'Y' is any string.  These have opaque sub-strings.
+	 * The \B and \w escapes are handled in roff.c, roff_res().
 	 */
-	case ('A'):
-		/* FALLTHROUGH */
-	case ('b'):
-		/* FALLTHROUGH */
-	case ('B'):
+	case 'A':
 		/* FALLTHROUGH */
-	case ('D'):
+	case 'b':
 		/* FALLTHROUGH */
-	case ('o'):
+	case 'D':
 		/* FALLTHROUGH */
-	case ('R'):
+	case 'R':
 		/* FALLTHROUGH */
-	case ('w'):
+	case 'X':
 		/* FALLTHROUGH */
-	case ('X'):
+	case 'Z':
+		gly = ESCAPE_IGNORE;
 		/* FALLTHROUGH */
-	case ('Z'):
-		if ('\'' != **start)
+	case 'o':
+		if (**start == '\0')
 			return(ESCAPE_ERROR);
-		gly = ESCAPE_IGNORE;
+		if (gly == ESCAPE_ERROR)
+			gly = ESCAPE_OVERSTRIKE;
+		term = **start;
 		*start = ++*end;
-		term = '\'';
 		break;
 
 	/*
 	 * These escapes are of the form \X'N', where 'X' is the trigger
 	 * and 'N' resolves to a numerical expression.
 	 */
-	case ('h'):
+	case 'h':
 		/* FALLTHROUGH */
-	case ('H'):
+	case 'H':
 		/* FALLTHROUGH */
-	case ('L'):
+	case 'L':
 		/* FALLTHROUGH */
-	case ('l'):
+	case 'l':
 		/* FALLTHROUGH */
-	case ('S'):
+	case 'S':
 		/* FALLTHROUGH */
-	case ('v'):
+	case 'v':
 		/* FALLTHROUGH */
-	case ('x'):
-		if ('\'' != **start)
+	case 'x':
+		if (strchr(" %&()*+-./0123456789:<=>", **start)) {
+			if ('\0' != **start)
+				++*end;
 			return(ESCAPE_ERROR);
+		}
 		gly = ESCAPE_IGNORE;
+		term = **start;
 		*start = ++*end;
-		term = '\'';
 		break;
 
 	/*
 	 * Special handling for the numbered character escape.
 	 * XXX Do any other escapes need similar handling?
 	 */
-	case ('N'):
+	case 'N':
 		if ('\0' == **start)
 			return(ESCAPE_ERROR);
 		(*end)++;
@@ -229,29 +219,37 @@ mandoc_escape(const char **end, const char **start, int *sz)
 			(*end)++;
 		return(ESCAPE_NUMBERED);
 
-	/* 
+	/*
 	 * Sizes get a special category of their own.
 	 */
-	case ('s'):
+	case 's':
 		gly = ESCAPE_IGNORE;
 
 		/* See +/- counts as a sign. */
 		if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
-			(*end)++;
+			*start = ++*end;
 
 		switch (**end) {
-		case ('('):
+		case '(':
 			*start = ++*end;
 			*sz = 2;
 			break;
-		case ('['):
+		case '[':
 			*start = ++*end;
 			term = ']';
 			break;
-		case ('\''):
+		case '\'':
 			*start = ++*end;
 			term = '\'';
 			break;
+		case '3':
+			/* FALLTHROUGH */
+		case '2':
+			/* FALLTHROUGH */
+		case '1':
+			*sz = (*end)[-1] == 's' &&
+			    isdigit((unsigned char)(*end)[1]) ? 2 : 1;
+			break;
 		default:
 			*sz = 1;
 			break;
@@ -280,9 +278,9 @@ mandoc_escape(const char **end, const char **start, int *sz)
 	if ('\0' != term) {
 		while (**end != term) {
 			switch (**end) {
-			case ('\0'):
+			case '\0':
 				return(ESCAPE_ERROR);
-			case ('\\'):
+			case '\\':
 				(*end)++;
 				if (ESCAPE_ERROR ==
 				    mandoc_escape(end, NULL, NULL))
@@ -304,7 +302,7 @@ mandoc_escape(const char **end, const char **start, int *sz)
 	/* Run post-processors. */
 
 	switch (gly) {
-	case (ESCAPE_FONT):
+	case ESCAPE_FONT:
 		if (2 == *sz) {
 			if ('C' == **start) {
 				/*
@@ -322,29 +320,44 @@ mandoc_escape(const char **end, const char **start, int *sz)
 			break;
 
 		switch (**start) {
-		case ('3'):
+		case '3':
 			/* FALLTHROUGH */
-		case ('B'):
+		case 'B':
 			gly = ESCAPE_FONTBOLD;
 			break;
-		case ('2'):
+		case '2':
 			/* FALLTHROUGH */
-		case ('I'):
+		case 'I':
 			gly = ESCAPE_FONTITALIC;
 			break;
-		case ('P'):
+		case 'P':
 			gly = ESCAPE_FONTPREV;
 			break;
-		case ('1'):
+		case '1':
 			/* FALLTHROUGH */
-		case ('R'):
+		case 'R':
 			gly = ESCAPE_FONTROMAN;
 			break;
 		}
 		break;
-	case (ESCAPE_SPECIAL):
+	case ESCAPE_SPECIAL:
 		if (1 == *sz && 'c' == **start)
 			gly = ESCAPE_NOSPACE;
+		/*
+		 * Unicode escapes are defined in groff as \[u0000]
+		 * to \[u10FFFF], where the contained value must be
+		 * a valid Unicode codepoint.  Here, however, only
+		 * check the length and range.
+		 */
+		if (**start != 'u' || *sz < 5 || *sz > 7)
+			break;
+		if (*sz == 7 && ((*start)[1] != '1' || (*start)[2] != '0'))
+			break;
+		if (*sz == 6 && (*start)[1] == '0')
+			break;
+		if ((int)strspn(*start + 1, "0123456789ABCDEFabcdef")
+		    + 1 == *sz)
+			gly = ESCAPE_UNICODE;
 		break;
 	default:
 		break;
@@ -353,74 +366,6 @@ mandoc_escape(const char **end, const char **start, int *sz)
 	return(gly);
 }
 
-void *
-mandoc_calloc(size_t num, size_t size)
-{
-	void		*ptr;
-
-	ptr = calloc(num, size);
-	if (NULL == ptr) {
-		perror(NULL);
-		exit((int)MANDOCLEVEL_SYSERR);
-	}
-
-	return(ptr);
-}
-
-
-void *
-mandoc_malloc(size_t size)
-{
-	void		*ptr;
-
-	ptr = malloc(size);
-	if (NULL == ptr) {
-		perror(NULL);
-		exit((int)MANDOCLEVEL_SYSERR);
-	}
-
-	return(ptr);
-}
-
-
-void *
-mandoc_realloc(void *ptr, size_t size)
-{
-
-	ptr = realloc(ptr, size);
-	if (NULL == ptr) {
-		perror(NULL);
-		exit((int)MANDOCLEVEL_SYSERR);
-	}
-
-	return(ptr);
-}
-
-char *
-mandoc_strndup(const char *ptr, size_t sz)
-{
-	char		*p;
-
-	p = mandoc_malloc(sz + 1);
-	memcpy(p, ptr, sz);
-	p[(int)sz] = '\0';
-	return(p);
-}
-
-char *
-mandoc_strdup(const char *ptr)
-{
-	char		*p;
-
-	p = strdup(ptr);
-	if (NULL == p) {
-		perror(NULL);
-		exit((int)MANDOCLEVEL_SYSERR);
-	}
-
-	return(p);
-}
-
 /*
  * Parse a quoted or unquoted roff-style request or macro argument.
  * Return a pointer to the parsed argument, which is either the original
@@ -442,7 +387,7 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
 	if ('"' == *start) {
 		quoted = 1;
 		start++;
-	} 
+	}
 
 	pairs = 0;
 	white = 0;
@@ -461,14 +406,14 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
 			 * backslashes and backslash-t to literal tabs.
 			 */
 			switch (cp[1]) {
-			case ('t'):
+			case 't':
 				cp[0] = '\t';
 				/* FALLTHROUGH */
-			case ('\\'):
+			case '\\':
 				pairs++;
 				cp++;
 				break;
-			case (' '):
+			case ' ':
 				/* Skip escaped blanks. */
 				if (0 == quoted)
 					cp++;
@@ -497,7 +442,7 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
 
 	/* Quoted argument without a closing quote. */
 	if (1 == quoted)
-		mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL);
+		mandoc_msg(MANDOCERR_ARG_QUOTE, parse, ln, *pos, NULL);
 
 	/* NUL-terminate this argument and move to the next one. */
 	if (pairs)
@@ -511,7 +456,7 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
 	*cpp = cp;
 
 	if ('\0' == *cp && (white || ' ' == cp[-1]))
-		mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL);
+		mandoc_msg(MANDOCERR_SPACE_EOL, parse, ln, *pos, NULL);
 
 	return(start);
 }
@@ -525,7 +470,7 @@ a2time(time_t *t, const char *fmt, const char *p)
 	memset(&tm, 0, sizeof(struct tm));
 
 	pp = NULL;
-#ifdef	HAVE_STRPTIME
+#if HAVE_STRPTIME
 	pp = strptime(p, fmt, &tm);
 #endif
 	if (NULL != pp && '\0' == *pp) {
@@ -545,6 +490,8 @@ time2a(time_t t)
 	int		 isz;
 
 	tm = localtime(&t);
+	if (tm == NULL)
+		return(NULL);
 
 	/*
 	 * Reserve space:
@@ -579,14 +526,14 @@ mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
 
 	if (NULL == in || '\0' == *in ||
 	    0 == strcmp(in, "$" "Mdocdate$")) {
-		mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL);
+		mandoc_msg(MANDOCERR_DATE_MISSING, parse, ln, pos, NULL);
 		time(&t);
 	}
 	else if (a2time(&t, "%Y-%m-%d", in))
 		t = 0;
 	else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) &&
 	    !a2time(&t, "%b %d, %Y", in)) {
-		mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL);
+		mandoc_msg(MANDOCERR_DATE_BAD, parse, ln, pos, in);
 		t = 0;
 	}
 	out = t ? time2a(t) : NULL;
@@ -594,10 +541,10 @@ mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
 }
 
 int
-mandoc_eos(const char *p, size_t sz, int enclosed)
+mandoc_eos(const char *p, size_t sz)
 {
-	const char *q;
-	int found;
+	const char	*q;
+	int		 enclosed, found;
 
 	if (0 == sz)
 		return(0);
@@ -608,24 +555,24 @@ mandoc_eos(const char *p, size_t sz, int enclosed)
 	 * propagate outward.
 	 */
 
-	found = 0;
+	enclosed = found = 0;
 	for (q = p + (int)sz - 1; q >= p; q--) {
 		switch (*q) {
-		case ('\"'):
+		case '\"':
 			/* FALLTHROUGH */
-		case ('\''):
+		case '\'':
 			/* FALLTHROUGH */
-		case (']'):
+		case ']':
 			/* FALLTHROUGH */
-		case (')'):
+		case ')':
 			if (0 == found)
 				enclosed = 1;
 			break;
-		case ('.'):
+		case '.':
 			/* FALLTHROUGH */
-		case ('!'):
+		case '!':
 			/* FALLTHROUGH */
-		case ('?'):
+		case '?':
 			found = 1;
 			break;
 		default:
diff --git a/usr/src/cmd/mandoc/mandoc.h b/usr/src/cmd/mandoc/mandoc.h
index 4c6a32f7a6..eb8a1aa6f2 100644
--- a/usr/src/cmd/mandoc/mandoc.h
+++ b/usr/src/cmd/mandoc/mandoc.h
@@ -1,7 +1,7 @@
-/*	$Id: mandoc.h,v 1.112 2013/12/30 18:30:32 schwarze Exp $ */
+/*	$Id: mandoc.h,v 1.201 2015/02/23 13:31:04 schwarze Exp $ */
 /*
- * Copyright (c) 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010, 2011, 2014 Kristaps Dzonsons 
+ * Copyright (c) 2010-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,11 +15,10 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef MANDOC_H
-#define MANDOC_H
 
 #define ASCII_NBRSP	 31  /* non-breaking space */
 #define	ASCII_HYPH	 30  /* breakable hyphen */
+#define	ASCII_BREAK	 29  /* breakable zero-width space */
 
 /*
  * Status level.  This refers to both internal status (i.e., whilst
@@ -32,7 +31,7 @@ enum	mandoclevel {
 	MANDOCLEVEL_RESERVED,
 	MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */
 	MANDOCLEVEL_ERROR, /* input has been thrown away */
-	MANDOCLEVEL_FATAL, /* input is borked */
+	MANDOCLEVEL_UNSUPP, /* input needs unimplemented features */
 	MANDOCLEVEL_BADARG, /* bad argument in invocation */
 	MANDOCLEVEL_SYSERR, /* system error */
 	MANDOCLEVEL_MAX
@@ -48,127 +47,158 @@ enum	mandocerr {
 	MANDOCERR_WARNING, /* ===== start of warnings ===== */
 
 	/* related to the prologue */
-	MANDOCERR_NOTITLE, /* no title in document */
-	MANDOCERR_UPPERCASE, /* document title should be all caps */
-	MANDOCERR_BADMSEC, /* unknown manual section */
-	MANDOCERR_BADVOLARCH, /* unknown manual volume or arch */
-	MANDOCERR_NODATE, /* date missing, using today's date */
-	MANDOCERR_BADDATE, /* cannot parse date, using it verbatim */
-	MANDOCERR_PROLOGOOO, /* prologue macros out of order */
-	MANDOCERR_PROLOGREP, /* duplicate prologue macro */
-	MANDOCERR_BADPROLOG, /* macro not allowed in prologue */
-	MANDOCERR_BADBODY, /* macro not allowed in body */
+	MANDOCERR_DT_NOTITLE, /* missing manual title, using UNTITLED: line */
+	MANDOCERR_TH_NOTITLE, /* missing manual title, using "": [macro] */
+	MANDOCERR_TITLE_CASE, /* lower case character in document title */
+	MANDOCERR_MSEC_MISSING, /* missing manual section, using "": macro */
+	MANDOCERR_MSEC_BAD, /* unknown manual section: Dt ... section */
+	MANDOCERR_DATE_MISSING, /* missing date, using today's date */
+	MANDOCERR_DATE_BAD, /* cannot parse date, using it verbatim: date */
+	MANDOCERR_OS_MISSING, /* missing Os macro, using "" */
+	MANDOCERR_PROLOG_REP, /* duplicate prologue macro: macro */
+	MANDOCERR_PROLOG_LATE, /* late prologue macro: macro */
+	MANDOCERR_DT_LATE, /* skipping late title macro: Dt args */
+	MANDOCERR_PROLOG_ORDER, /* prologue macros out of order: macros */
 
 	/* related to document structure */
-	MANDOCERR_SO, /* .so is fragile, better use ln(1) */
-	MANDOCERR_NAMESECFIRST, /* NAME section must come first */
-	MANDOCERR_BADNAMESEC, /* bad NAME section contents */
-	MANDOCERR_SECOOO, /* sections out of conventional order */
-	MANDOCERR_SECREP, /* duplicate section name */
-	MANDOCERR_SECMSEC, /* section header suited to sections ... */
+	MANDOCERR_SO, /* .so is fragile, better use ln(1): so path */
+	MANDOCERR_DOC_EMPTY, /* no document body */
+	MANDOCERR_SEC_BEFORE, /* content before first section header: macro */
+	MANDOCERR_NAMESEC_FIRST, /* first section is not NAME: Sh title */
+	MANDOCERR_NAMESEC_NONM, /* NAME section without name */
+	MANDOCERR_NAMESEC_NOND, /* NAME section without description */
+	MANDOCERR_NAMESEC_ND, /* description not at the end of NAME */
+	MANDOCERR_NAMESEC_BAD, /* bad NAME section content: macro */
+	MANDOCERR_ND_EMPTY, /* missing description line, using "" */
+	MANDOCERR_SEC_ORDER, /* sections out of conventional order: Sh title */
+	MANDOCERR_SEC_REP, /* duplicate section title: Sh title */
+	MANDOCERR_SEC_MSEC, /* unexpected section: Sh title for ... only */
+	MANDOCERR_XR_ORDER, /* unusual Xr order: ... after ... */
+	MANDOCERR_XR_PUNCT, /* unusual Xr punctuation: ... after ... */
+	MANDOCERR_AN_MISSING, /* AUTHORS section without An macro */
 
 	/* related to macros and nesting */
-	MANDOCERR_MACROOBS, /* skipping obsolete macro */
-	MANDOCERR_IGNPAR, /* skipping paragraph macro */
-	MANDOCERR_MOVEPAR, /* moving paragraph macro out of list */
-	MANDOCERR_IGNNS, /* skipping no-space macro */
-	MANDOCERR_SCOPENEST, /* blocks badly nested */
-	MANDOCERR_CHILD, /* child violates parent syntax */
-	MANDOCERR_NESTEDDISP, /* nested displays are not portable */
-	MANDOCERR_SCOPEREP, /* already in literal mode */
-	MANDOCERR_LINESCOPE, /* line scope broken */
-
-	/* related to missing macro arguments */
-	MANDOCERR_MACROEMPTY, /* skipping empty macro */
-	MANDOCERR_ARGCWARN, /* argument count wrong */
-	MANDOCERR_DISPTYPE, /* missing display type */
-	MANDOCERR_LISTFIRST, /* list type must come first */
-	MANDOCERR_NOWIDTHARG, /* tag lists require a width argument */
-	MANDOCERR_FONTTYPE, /* missing font type */
-	MANDOCERR_WNOSCOPE, /* skipping end of block that is not open */
-
-	/* related to bad macro arguments */
-	MANDOCERR_IGNARGV, /* skipping argument */
-	MANDOCERR_ARGVREP, /* duplicate argument */
-	MANDOCERR_DISPREP, /* duplicate display type */
-	MANDOCERR_LISTREP, /* duplicate list type */
-	MANDOCERR_BADATT, /* unknown AT&T UNIX version */
-	MANDOCERR_BADBOOL, /* bad Boolean value */
-	MANDOCERR_BADFONT, /* unknown font */
-	MANDOCERR_BADSTANDARD, /* unknown standard specifier */
-	MANDOCERR_BADWIDTH, /* bad width argument */
+	MANDOCERR_MACRO_OBS, /* obsolete macro: macro */
+	MANDOCERR_MACRO_CALL, /* macro neither callable nor escaped: macro */
+	MANDOCERR_PAR_SKIP, /* skipping paragraph macro: macro ... */
+	MANDOCERR_PAR_MOVE, /* moving paragraph macro out of list: macro */
+	MANDOCERR_NS_SKIP, /* skipping no-space macro */
+	MANDOCERR_BLK_NEST, /* blocks badly nested: macro ... */
+	MANDOCERR_BD_NEST, /* nested displays are not portable: macro ... */
+	MANDOCERR_BL_MOVE, /* moving content out of list: macro */
+	MANDOCERR_VT_CHILD, /* .Vt block has child macro: macro */
+	MANDOCERR_FI_SKIP, /* fill mode already enabled, skipping: fi */
+	MANDOCERR_NF_SKIP, /* fill mode already disabled, skipping: nf */
+	MANDOCERR_BLK_LINE, /* line scope broken: macro breaks macro */
+
+	/* related to missing arguments */
+	MANDOCERR_REQ_EMPTY, /* skipping empty request: request */
+	MANDOCERR_COND_EMPTY, /* conditional request controls empty scope */
+	MANDOCERR_MACRO_EMPTY, /* skipping empty macro: macro */
+	MANDOCERR_BLK_EMPTY, /* empty block: macro */
+	MANDOCERR_ARG_EMPTY, /* empty argument, using 0n: macro arg */
+	MANDOCERR_BD_NOTYPE, /* missing display type, using -ragged: Bd */
+	MANDOCERR_BL_LATETYPE, /* list type is not the first argument: Bl arg */
+	MANDOCERR_BL_NOWIDTH, /* missing -width in -tag list, using 8n */
+	MANDOCERR_EX_NONAME, /* missing utility name, using "": Ex */
+	MANDOCERR_FO_NOHEAD, /* missing function name, using "": Fo */
+	MANDOCERR_IT_NOHEAD, /* empty head in list item: Bl -type It */
+	MANDOCERR_IT_NOBODY, /* empty list item: Bl -type It */
+	MANDOCERR_BF_NOFONT, /* missing font type, using \fR: Bf */
+	MANDOCERR_BF_BADFONT, /* unknown font type, using \fR: Bf font */
+	MANDOCERR_PF_SKIP, /* nothing follows prefix: Pf arg */
+	MANDOCERR_RS_EMPTY, /* empty reference block: Rs */
+	MANDOCERR_ARG_STD, /* missing -std argument, adding it: macro */
+	MANDOCERR_OP_EMPTY, /* missing option string, using "": OP */
+	MANDOCERR_UR_NOHEAD, /* missing resource identifier, using "": UR */
+	MANDOCERR_EQN_NOBOX, /* missing eqn box, using "": op */
+
+	/* related to bad arguments */
+	MANDOCERR_ARG_QUOTE, /* unterminated quoted argument */
+	MANDOCERR_ARG_REP, /* duplicate argument: macro arg */
+	MANDOCERR_AN_REP, /* skipping duplicate argument: An -arg */
+	MANDOCERR_BD_REP, /* skipping duplicate display type: Bd -type */
+	MANDOCERR_BL_REP, /* skipping duplicate list type: Bl -type */
+	MANDOCERR_BL_SKIPW, /* skipping -width argument: Bl -type */
+	MANDOCERR_BL_COL, /* wrong number of cells */
+	MANDOCERR_AT_BAD, /* unknown AT&T UNIX version: At version */
+	MANDOCERR_FA_COMMA, /* comma in function argument: arg */
+	MANDOCERR_FN_PAREN, /* parenthesis in function name: arg */
+	MANDOCERR_RS_BAD, /* invalid content in Rs block: macro */
+	MANDOCERR_SM_BAD, /* invalid Boolean argument: macro arg */
+	MANDOCERR_FT_BAD, /* unknown font, skipping request: ft font */
+	MANDOCERR_TR_ODD, /* odd number of characters in request: tr char */
 
 	/* related to plain text */
-	MANDOCERR_NOBLANKLN, /* blank line in non-literal context */
-	MANDOCERR_BADTAB, /* tab in non-literal context */
-	MANDOCERR_EOLNSPACE, /* end of line whitespace */
-	MANDOCERR_BADCOMMENT, /* bad comment style */
-	MANDOCERR_BADESCAPE, /* unknown escape sequence */
-	MANDOCERR_BADQUOTE, /* unterminated quoted string */
+	MANDOCERR_FI_BLANK, /* blank line in fill mode, using .sp */
+	MANDOCERR_FI_TAB, /* tab in filled text */
+	MANDOCERR_SPACE_EOL, /* whitespace at end of input line */
+	MANDOCERR_COMMENT_BAD, /* bad comment style */
+	MANDOCERR_ESC_BAD, /* invalid escape sequence: esc */
+	MANDOCERR_STR_UNDEF, /* undefined string, using "": name */
 
-	/* related to equations */
-	MANDOCERR_EQNQUOTE, /* unexpected literal in equation */
+	/* related to tables */
+	MANDOCERR_TBLLAYOUT_SPAN, /* tbl line starts with span */
+	MANDOCERR_TBLLAYOUT_DOWN, /* tbl column starts with span */
+	MANDOCERR_TBLLAYOUT_VERT, /* skipping vertical bar in tbl layout */
 
 	MANDOCERR_ERROR, /* ===== start of errors ===== */
 
-	/* related to equations */
-	MANDOCERR_EQNNSCOPE, /* unexpected equation scope closure*/
-	MANDOCERR_EQNSCOPE, /* equation scope open on exit */
-	MANDOCERR_EQNBADSCOPE, /* overlapping equation scopes */
-	MANDOCERR_EQNEOF, /* unexpected end of equation */
-	MANDOCERR_EQNSYNT, /* equation syntax error */
-
 	/* related to tables */
-	MANDOCERR_TBL, /* bad table syntax */
-	MANDOCERR_TBLOPT, /* bad table option */
-	MANDOCERR_TBLLAYOUT, /* bad table layout */
-	MANDOCERR_TBLNOLAYOUT, /* no table layout cells specified */
-	MANDOCERR_TBLNODATA, /* no table data cells specified */
-	MANDOCERR_TBLIGNDATA, /* ignore data in cell */
-	MANDOCERR_TBLBLOCK, /* data block still open */
-	MANDOCERR_TBLEXTRADAT, /* ignoring extra data cells */
-
+	MANDOCERR_TBLOPT_ALPHA, /* non-alphabetic character in tbl options */
+	MANDOCERR_TBLOPT_BAD, /* skipping unknown tbl option: option */
+	MANDOCERR_TBLOPT_NOARG, /* missing tbl option argument: option */
+	MANDOCERR_TBLOPT_ARGSZ, /* wrong tbl option argument size: option */
+	MANDOCERR_TBLLAYOUT_NONE, /* empty tbl layout */
+	MANDOCERR_TBLLAYOUT_CHAR, /* invalid character in tbl layout: char */
+	MANDOCERR_TBLLAYOUT_PAR, /* unmatched parenthesis in tbl layout */
+	MANDOCERR_TBLDATA_NONE, /* tbl without any data cells */
+	MANDOCERR_TBLDATA_SPAN, /* ignoring data in spanned tbl cell: data */
+	MANDOCERR_TBLDATA_EXTRA, /* ignoring extra tbl data cells: data */
+	MANDOCERR_TBLDATA_BLK, /* data block open at end of tbl: macro */
+
+	/* related to document structure and macros */
+	MANDOCERR_FILE, /* cannot open file */
 	MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
-	MANDOCERR_BADCHAR, /* skipping bad character */
-	MANDOCERR_NAMESC, /* escaped character not allowed in a name */
-	MANDOCERR_NONAME, /* manual name not yet set */
-	MANDOCERR_NOTEXT, /* skipping text before the first section header */
-	MANDOCERR_MACRO, /* skipping unknown macro */
-	MANDOCERR_REQUEST, /* NOT IMPLEMENTED: skipping request */
-	MANDOCERR_ARGCOUNT, /* argument count wrong */
-	MANDOCERR_STRAYTA, /* skipping column outside column list */
-	MANDOCERR_NOSCOPE, /* skipping end of block that is not open */
-	MANDOCERR_SCOPEBROKEN, /* missing end of block */
-	MANDOCERR_SCOPEEXIT, /* scope open on exit */
-	MANDOCERR_UNAME, /* uname(3) system call failed */
-	/* FIXME: merge following with MANDOCERR_ARGCOUNT */
-	MANDOCERR_NOARGS, /* macro requires line argument(s) */
-	MANDOCERR_NOBODY, /* macro requires body argument(s) */
-	MANDOCERR_NOARGV, /* macro requires argument(s) */
-	MANDOCERR_NUMERIC, /* request requires a numeric argument */
-	MANDOCERR_LISTTYPE, /* missing list type */
-	MANDOCERR_ARGSLOST, /* line argument(s) will be lost */
-	MANDOCERR_BODYLOST, /* body argument(s) will be lost */
-
-	MANDOCERR_FATAL, /* ===== start of fatal errors ===== */
-
-	MANDOCERR_NOTMANUAL, /* manual isn't really a manual */
-	MANDOCERR_COLUMNS, /* column syntax is inconsistent */
-	MANDOCERR_BADDISP, /* NOT IMPLEMENTED: .Bd -file */
-	MANDOCERR_SYNTARGVCOUNT, /* argument count wrong, violates syntax */
-	MANDOCERR_SYNTCHILD, /* child violates parent syntax */
-	MANDOCERR_SYNTARGCOUNT, /* argument count wrong, violates syntax */
-	MANDOCERR_SOPATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
-	MANDOCERR_NODOCBODY, /* no document body */
-	MANDOCERR_NODOCPROLOG, /* no document prologue */
-	MANDOCERR_MEM, /* static buffer exhausted */
+	MANDOCERR_CHAR_BAD, /* skipping bad character: number */
+	MANDOCERR_MACRO, /* skipping unknown macro: macro */
+	MANDOCERR_REQ_INSEC, /* skipping insecure request: request */
+	MANDOCERR_IT_STRAY, /* skipping item outside list: It ... */
+	MANDOCERR_TA_STRAY, /* skipping column outside column list: Ta */
+	MANDOCERR_BLK_NOTOPEN, /* skipping end of block that is not open */
+	MANDOCERR_RE_NOTOPEN, /* fewer RS blocks open, skipping: RE arg */
+	MANDOCERR_BLK_BROKEN, /* inserting missing end of block: macro ... */
+	MANDOCERR_BLK_NOEND, /* appending missing end of block: macro */
+
+	/* related to request and macro arguments */
+	MANDOCERR_NAMESC, /* escaped character not allowed in a name: name */
+	MANDOCERR_BD_FILE, /* NOT IMPLEMENTED: Bd -file */
+	MANDOCERR_BL_NOTYPE, /* missing list type, using -item: Bl */
+	MANDOCERR_NM_NONAME, /* missing manual name, using "": Nm */
+	MANDOCERR_OS_UNAME, /* uname(3) system call failed, using UNKNOWN */
+	MANDOCERR_ST_BAD, /* unknown standard specifier: St standard */
+	MANDOCERR_IT_NONUM, /* skipping request without numeric argument */
+	MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
+	MANDOCERR_SO_FAIL, /* .so request failed */
+	MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */
+	MANDOCERR_ARG_EXCESS, /* skipping excess arguments: macro ... args */
+	MANDOCERR_DIVZERO, /* divide by zero */
+
+	MANDOCERR_UNSUPP, /* ===== start of unsupported features ===== */
+
+	MANDOCERR_TOOLARGE, /* input too large */
+	MANDOCERR_CHAR_UNSUPP, /* unsupported control character: number */
+	MANDOCERR_REQ_UNSUPP, /* unsupported roff request: request */
+	MANDOCERR_TBLOPT_EQN, /* eqn delim option in tbl: arg */
+	MANDOCERR_TBLLAYOUT_MOD, /* unsupported tbl layout modifier: m */
+	MANDOCERR_TBLMACRO, /* ignoring macro in table: macro */
+
 	MANDOCERR_MAX
 };
 
 struct	tbl_opts {
 	char		  tab; /* cell-separator */
 	char		  decimal; /* decimal point */
-	int		  linesize;
 	int		  opts;
 #define	TBL_OPT_CENTRE	 (1 << 0)
 #define	TBL_OPT_EXPAND	 (1 << 1)
@@ -177,19 +207,10 @@ struct	tbl_opts {
 #define	TBL_OPT_ALLBOX	 (1 << 4)
 #define	TBL_OPT_NOKEEP	 (1 << 5)
 #define	TBL_OPT_NOSPACE	 (1 << 6)
+#define	TBL_OPT_NOWARN	 (1 << 7)
 	int		  cols; /* number of columns */
-};
-
-/*
- * The head of a table specifies all of its columns.  When formatting a
- * tbl_span, iterate over these and plug in data from the tbl_span when
- * appropriate, using tbl_cell as a guide to placement.
- */
-struct	tbl_head {
-	int		  ident; /* 0 <= unique id < cols */
-	int		  vert; /* width of preceding vertical line */
-	struct tbl_head	 *next;
-	struct tbl_head	 *prev;
+	int		  lvert; /* width of left vertical line */
+	int		  rvert; /* width of right vertical line */
 };
 
 enum	tbl_cellt {
@@ -210,9 +231,10 @@ enum	tbl_cellt {
  */
 struct	tbl_cell {
 	struct tbl_cell	 *next;
-	int		  vert; /* width of preceding vertical line */
+	int		  vert; /* width of subsequent vertical line */
 	enum tbl_cellt	  pos;
 	size_t		  spacing;
+	int		  col; /* column number, starting from 0 */
 	int		  flags;
 #define	TBL_CELL_TALIGN	 (1 << 0) /* t, T */
 #define	TBL_CELL_BALIGN	 (1 << 1) /* d, D */
@@ -221,7 +243,7 @@ struct	tbl_cell {
 #define	TBL_CELL_EQUAL	 (1 << 4) /* e, E */
 #define	TBL_CELL_UP	 (1 << 5) /* u, U */
 #define	TBL_CELL_WIGN	 (1 << 6) /* z, Z */
-	struct tbl_head	 *head;
+#define	TBL_CELL_WMAX	 (1 << 7) /* x, X */
 };
 
 /*
@@ -231,6 +253,7 @@ struct	tbl_row {
 	struct tbl_row	 *next;
 	struct tbl_cell	 *first;
 	struct tbl_cell	 *last;
+	int		  vert; /* width of left vertical line */
 };
 
 enum	tbl_datt {
@@ -265,37 +288,23 @@ enum	tbl_spant {
  */
 struct	tbl_span {
 	struct tbl_opts	 *opts;
-	struct tbl_head	 *head;
 	struct tbl_row	 *layout; /* layout row */
 	struct tbl_dat	 *first;
 	struct tbl_dat	 *last;
+	struct tbl_span	 *prev;
+	struct tbl_span	 *next;
 	int		  line; /* parse line */
-	int		  flags;
-#define	TBL_SPAN_FIRST	 (1 << 0)
-#define	TBL_SPAN_LAST	 (1 << 1)
 	enum tbl_spant	  pos;
-	struct tbl_span	 *next;
 };
 
 enum	eqn_boxt {
 	EQN_ROOT, /* root of parse tree */
 	EQN_TEXT, /* text (number, variable, whatever) */
 	EQN_SUBEXPR, /* nested `eqn' subexpression */
-	EQN_LIST, /* subexpressions list */
-	EQN_MATRIX /* matrix subexpression */
-};
-
-enum	eqn_markt {
-	EQNMARK_NONE = 0,
-	EQNMARK_DOT,
-	EQNMARK_DOTDOT,
-	EQNMARK_HAT,
-	EQNMARK_TILDE,
-	EQNMARK_VEC,
-	EQNMARK_DYAD,
-	EQNMARK_BAR,
-	EQNMARK_UNDER,
-	EQNMARK__MAX
+	EQN_LIST, /* list (braces, etc.) */
+	EQN_LISTONE, /* singleton list */
+	EQN_PILE, /* vertical pile */
+	EQN_MATRIX /* pile of piles */
 };
 
 enum	eqn_fontt {
@@ -309,11 +318,14 @@ enum	eqn_fontt {
 
 enum	eqn_post {
 	EQNPOS_NONE = 0,
-	EQNPOS_OVER,
 	EQNPOS_SUP,
+	EQNPOS_SUBSUP,
 	EQNPOS_SUB,
 	EQNPOS_TO,
 	EQNPOS_FROM,
+	EQNPOS_FROMTO,
+	EQNPOS_OVER,
+	EQNPOS_SQRT,
 	EQNPOS__MAX
 };
 
@@ -341,19 +353,23 @@ struct	eqn_box {
 	struct eqn_box	 *first; /* first child node */
 	struct eqn_box	 *last; /* last child node */
 	struct eqn_box	 *next; /* node sibling */
+	struct eqn_box	 *prev; /* node sibling */
 	struct eqn_box	 *parent; /* node sibling */
 	char		 *text; /* text (or NULL) */
-	char		 *left;
-	char		 *right;
+	char		 *left; /* fence left-hand */
+	char		 *right; /* fence right-hand */
+	char		 *top; /* expression over-symbol */
+	char		 *bottom; /* expression under-symbol */
+	size_t		  args; /* arguments in parent */
+	size_t		  expectargs; /* max arguments in parent */
 	enum eqn_post	  pos; /* position of next box */
-	enum eqn_markt	  mark; /* a mark about the box */
 	enum eqn_fontt	  font; /* font of box */
 	enum eqn_pilet	  pile; /* equation piling */
 };
 
 /*
  * An equation consists of a tree of expressions starting at a given
- * line and position. 
+ * line and position.
  */
 struct	eqn {
 	char		 *name; /* identifier (or NULL) */
@@ -363,15 +379,14 @@ struct	eqn {
 };
 
 /*
- * The type of parse sequence.  This value is usually passed via the
- * mandoc(1) command line of -man and -mdoc.  It's almost exclusively
- * -mandoc but the others have been retained for compatibility.
+ * Parse options.
  */
-enum	mparset {
-	MPARSE_AUTO, /* magically determine the document type */
-	MPARSE_MDOC, /* assume -mdoc */
-	MPARSE_MAN /* assume -man */
-};
+#define	MPARSE_MDOC	1  /* assume -mdoc */
+#define	MPARSE_MAN	2  /* assume -man */
+#define	MPARSE_SO	4  /* honour .so requests */
+#define	MPARSE_QUICK	8  /* abort the parse early */
+#define	MPARSE_UTF8	16 /* accept UTF-8 input */
+#define	MPARSE_LATIN1	32 /* accept ISO-LATIN-1 input */
 
 enum	mandoc_esc {
 	ESCAPE_ERROR = 0, /* bail! unparsable escape */
@@ -386,47 +401,44 @@ enum	mandoc_esc {
 	ESCAPE_NUMBERED, /* a numbered glyph */
 	ESCAPE_UNICODE, /* a unicode codepoint */
 	ESCAPE_NOSPACE, /* suppress space if the last on a line */
-	ESCAPE_SKIPCHAR /* skip the next character */
+	ESCAPE_SKIPCHAR, /* skip the next character */
+	ESCAPE_OVERSTRIKE /* overstrike all chars in the argument */
 };
 
 typedef	void	(*mandocmsg)(enum mandocerr, enum mandoclevel,
 			const char *, int, int, const char *);
 
+__BEGIN_DECLS
+
 struct	mparse;
 struct	mchars;
 struct	mdoc;
 struct	man;
 
-__BEGIN_DECLS
-
-void		 *mandoc_calloc(size_t, size_t);
 enum mandoc_esc	  mandoc_escape(const char **, const char **, int *);
-void		 *mandoc_malloc(size_t);
-void		 *mandoc_realloc(void *, size_t);
-char		 *mandoc_strdup(const char *);
-char		 *mandoc_strndup(const char *, size_t);
 struct mchars	 *mchars_alloc(void);
 void		  mchars_free(struct mchars *);
-char	 	  mchars_num2char(const char *, size_t);
+int		  mchars_num2char(const char *, size_t);
+const char	 *mchars_uc2str(int);
 int		  mchars_num2uc(const char *, size_t);
-int		  mchars_spec2cp(const struct mchars *, 
+int		  mchars_spec2cp(const struct mchars *,
 			const char *, size_t);
-const char	 *mchars_spec2str(const struct mchars *, 
+const char	 *mchars_spec2str(const struct mchars *,
 			const char *, size_t, size_t *);
-struct mparse	 *mparse_alloc(enum mparset, enum mandoclevel,
-			mandocmsg, void *, char *);
+struct mparse	 *mparse_alloc(int, enum mandoclevel, mandocmsg,
+			const struct mchars *, const char *);
 void		  mparse_free(struct mparse *);
 void		  mparse_keep(struct mparse *);
+enum mandoclevel  mparse_open(struct mparse *, int *, const char *);
 enum mandoclevel  mparse_readfd(struct mparse *, int, const char *);
-enum mandoclevel  mparse_readmem(struct mparse *, const void *, size_t,
+enum mandoclevel  mparse_readmem(struct mparse *, void *, size_t,
 			const char *);
 void		  mparse_reset(struct mparse *);
-void		  mparse_result(struct mparse *, 
-			struct mdoc **, struct man **);
+void		  mparse_result(struct mparse *,
+			struct mdoc **, struct man **, char **);
 const char	 *mparse_getkeep(const struct mparse *);
 const char	 *mparse_strerror(enum mandocerr);
 const char	 *mparse_strlevel(enum mandoclevel);
+enum mandoclevel  mparse_wait(struct mparse *);
 
 __END_DECLS
-
-#endif /*!MANDOC_H*/
diff --git a/usr/src/cmd/mandoc/mandoc_aux.c b/usr/src/cmd/mandoc/mandoc_aux.c
new file mode 100644
index 0000000000..2275bbcf36
--- /dev/null
+++ b/usr/src/cmd/mandoc/mandoc_aux.c
@@ -0,0 +1,119 @@
+/*	$Id: mandoc_aux.c,v 1.4 2014/08/10 23:54:41 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014 Ingo Schwarze 
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "config.h"
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+
+int
+mandoc_asprintf(char **dest, const char *fmt, ...)
+{
+	va_list	 ap;
+	int	 ret;
+
+	va_start(ap, fmt);
+	ret = vasprintf(dest, fmt, ap);
+	va_end(ap);
+
+	if (-1 == ret) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	return(ret);
+}
+
+void *
+mandoc_calloc(size_t num, size_t size)
+{
+	void	*ptr;
+
+	ptr = calloc(num, size);
+	if (NULL == ptr) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	return(ptr);
+}
+
+void *
+mandoc_malloc(size_t size)
+{
+	void	*ptr;
+
+	ptr = malloc(size);
+	if (NULL == ptr) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	return(ptr);
+}
+
+void *
+mandoc_realloc(void *ptr, size_t size)
+{
+
+	ptr = realloc(ptr, size);
+	if (NULL == ptr) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	return(ptr);
+}
+
+void *
+mandoc_reallocarray(void *ptr, size_t num, size_t size)
+{
+
+	ptr = reallocarray(ptr, num, size);
+	if (NULL == ptr) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	return(ptr);
+}
+
+char *
+mandoc_strdup(const char *ptr)
+{
+	char	*p;
+
+	p = strdup(ptr);
+	if (NULL == p) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	return(p);
+}
+
+char *
+mandoc_strndup(const char *ptr, size_t sz)
+{
+	char	*p;
+
+	p = mandoc_malloc(sz + 1);
+	memcpy(p, ptr, sz);
+	p[(int)sz] = '\0';
+	return(p);
+}
diff --git a/usr/src/cmd/mandoc/mandoc_aux.h b/usr/src/cmd/mandoc/mandoc_aux.h
new file mode 100644
index 0000000000..e72fe4e40e
--- /dev/null
+++ b/usr/src/cmd/mandoc/mandoc_aux.h
@@ -0,0 +1,29 @@
+/*	$Id: mandoc_aux.h,v 1.3 2014/12/01 04:05:32 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014 Ingo Schwarze 
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+__BEGIN_DECLS
+
+int		  mandoc_asprintf(char **, const char *, ...);
+void		 *mandoc_calloc(size_t, size_t);
+void		 *mandoc_malloc(size_t);
+void		 *mandoc_realloc(void *, size_t);
+void		 *mandoc_reallocarray(void *, size_t, size_t);
+char		 *mandoc_strdup(const char *);
+char		 *mandoc_strndup(const char *, size_t);
+
+__END_DECLS
diff --git a/usr/src/cmd/mandoc/manpath.c b/usr/src/cmd/mandoc/manpath.c
new file mode 100644
index 0000000000..e85175e945
--- /dev/null
+++ b/usr/src/cmd/mandoc/manpath.c
@@ -0,0 +1,237 @@
+/*	$Id: manpath.c,v 1.19 2014/11/27 00:30:40 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2014 Ingo Schwarze 
+ * Copyright (c) 2011 Kristaps Dzonsons 
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "config.h"
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "mandoc_aux.h"
+#include "manpath.h"
+
+#define MAN_CONF_FILE	"/etc/man.conf"
+#define MAN_CONF_KEY	"_whatdb"
+
+static	void	 manpath_add(struct manpaths *, const char *, int);
+static	void	 manpath_parseline(struct manpaths *, char *, int);
+
+void
+manpath_parse(struct manpaths *dirs, const char *file,
+		char *defp, char *auxp)
+{
+#if HAVE_MANPATH
+	char		 cmd[(PATH_MAX * 3) + 20];
+	FILE		*stream;
+	char		*buf;
+	size_t		 sz, bsz;
+
+	strlcpy(cmd, "manpath", sizeof(cmd));
+	if (file) {
+		strlcat(cmd, " -C ", sizeof(cmd));
+		strlcat(cmd, file, sizeof(cmd));
+	}
+	if (auxp) {
+		strlcat(cmd, " -m ", sizeof(cmd));
+		strlcat(cmd, auxp, sizeof(cmd));
+	}
+	if (defp) {
+		strlcat(cmd, " -M ", sizeof(cmd));
+		strlcat(cmd, defp, sizeof(cmd));
+	}
+
+	/* Open manpath(1).  Ignore errors. */
+
+	stream = popen(cmd, "r");
+	if (NULL == stream)
+		return;
+
+	buf = NULL;
+	bsz = 0;
+
+	/* Read in as much output as we can. */
+
+	do {
+		buf = mandoc_realloc(buf, bsz + 1024);
+		sz = fread(buf + bsz, 1, 1024, stream);
+		bsz += sz;
+	} while (sz > 0);
+
+	if ( ! ferror(stream) && feof(stream) &&
+			bsz && '\n' == buf[bsz - 1]) {
+		buf[bsz - 1] = '\0';
+		manpath_parseline(dirs, buf, 1);
+	}
+
+	free(buf);
+	pclose(stream);
+#else
+	char		*insert;
+
+	/* Always prepend -m. */
+	manpath_parseline(dirs, auxp, 1);
+
+	/* If -M is given, it overrides everything else. */
+	if (NULL != defp) {
+		manpath_parseline(dirs, defp, 1);
+		return;
+	}
+
+	/* MANPATH and man.conf(5) cooperate. */
+	defp = getenv("MANPATH");
+	if (NULL == file)
+		file = MAN_CONF_FILE;
+
+	/* No MANPATH; use man.conf(5) only. */
+	if (NULL == defp || '\0' == defp[0]) {
+		manpath_manconf(dirs, file);
+		return;
+	}
+
+	/* Prepend man.conf(5) to MANPATH. */
+	if (':' == defp[0]) {
+		manpath_manconf(dirs, file);
+		manpath_parseline(dirs, defp, 0);
+		return;
+	}
+
+	/* Append man.conf(5) to MANPATH. */
+	if (':' == defp[strlen(defp) - 1]) {
+		manpath_parseline(dirs, defp, 0);
+		manpath_manconf(dirs, file);
+		return;
+	}
+
+	/* Insert man.conf(5) into MANPATH. */
+	insert = strstr(defp, "::");
+	if (NULL != insert) {
+		*insert++ = '\0';
+		manpath_parseline(dirs, defp, 0);
+		manpath_manconf(dirs, file);
+		manpath_parseline(dirs, insert + 1, 0);
+		return;
+	}
+
+	/* MANPATH overrides man.conf(5) completely. */
+	manpath_parseline(dirs, defp, 0);
+#endif
+}
+
+/*
+ * Parse a FULL pathname from a colon-separated list of arrays.
+ */
+static void
+manpath_parseline(struct manpaths *dirs, char *path, int complain)
+{
+	char	*dir;
+
+	if (NULL == path)
+		return;
+
+	for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
+		manpath_add(dirs, dir, complain);
+}
+
+/*
+ * Add a directory to the array, ignoring bad directories.
+ * Grow the array one-by-one for simplicity's sake.
+ */
+static void
+manpath_add(struct manpaths *dirs, const char *dir, int complain)
+{
+	char		 buf[PATH_MAX];
+	struct stat	 sb;
+	char		*cp;
+	size_t		 i;
+
+	if (NULL == (cp = realpath(dir, buf))) {
+		if (complain) {
+			fputs("manpath: ", stderr);
+			perror(dir);
+		}
+		return;
+	}
+
+	for (i = 0; i < dirs->sz; i++)
+		if (0 == strcmp(dirs->paths[i], dir))
+			return;
+
+	if (stat(cp, &sb) == -1) {
+		if (complain) {
+			fputs("manpath: ", stderr);
+			perror(dir);
+		}
+		return;
+	}
+
+	dirs->paths = mandoc_reallocarray(dirs->paths,
+	    dirs->sz + 1, sizeof(char *));
+
+	dirs->paths[dirs->sz++] = mandoc_strdup(cp);
+}
+
+void
+manpath_free(struct manpaths *p)
+{
+	size_t		 i;
+
+	for (i = 0; i < p->sz; i++)
+		free(p->paths[i]);
+
+	free(p->paths);
+}
+
+void
+manpath_manconf(struct manpaths *dirs, const char *file)
+{
+	FILE		*stream;
+	char		*p, *q;
+	size_t		 len, keysz;
+
+	keysz = strlen(MAN_CONF_KEY);
+	assert(keysz > 0);
+
+	if (NULL == (stream = fopen(file, "r")))
+		return;
+
+	while (NULL != (p = fgetln(stream, &len))) {
+		if (0 == len || '\n' != p[--len])
+			break;
+		p[len] = '\0';
+		while (isspace((unsigned char)*p))
+			p++;
+		if (strncmp(MAN_CONF_KEY, p, keysz))
+			continue;
+		p += keysz;
+		while (isspace((unsigned char)*p))
+			p++;
+		if ('\0' == *p)
+			continue;
+		if (NULL == (q = strrchr(p, '/')))
+			continue;
+		*q = '\0';
+		manpath_add(dirs, p, 0);
+	}
+
+	fclose(stream);
+}
diff --git a/usr/src/cmd/mandoc/manpath.h b/usr/src/cmd/mandoc/manpath.h
new file mode 100644
index 0000000000..728373b2cc
--- /dev/null
+++ b/usr/src/cmd/mandoc/manpath.h
@@ -0,0 +1,34 @@
+/*	$Id: manpath.h,v 1.7 2014/12/01 04:05:32 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Ingo Schwarze 
+ * Copyright (c) 2011 Kristaps Dzonsons 
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Unsorted list of unique, absolute paths to be searched for manual
+ * databases.
+ */
+struct	manpaths {
+	size_t	  sz;
+	char	**paths;
+};
+
+__BEGIN_DECLS
+
+void	 manpath_manconf(struct manpaths *, const char *);
+void	 manpath_parse(struct manpaths *, const char *, char *, char *);
+void	 manpath_free(struct manpaths *);
+
+__END_DECLS
diff --git a/usr/src/cmd/mandoc/mansearch.h b/usr/src/cmd/mandoc/mansearch.h
new file mode 100644
index 0000000000..14ec8ceacd
--- /dev/null
+++ b/usr/src/cmd/mandoc/mansearch.h
@@ -0,0 +1,111 @@
+/*	$Id: mansearch.h,v 1.23 2014/12/01 08:05:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons 
+ * Copyright (c) 2013, 2014 Ingo Schwarze 
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define	MANDOC_DB	 "mandoc.db"
+
+#define	TYPE_arch	 0x0000000000000001ULL
+#define	TYPE_sec	 0x0000000000000002ULL
+#define	TYPE_Xr		 0x0000000000000004ULL
+#define	TYPE_Ar		 0x0000000000000008ULL
+#define	TYPE_Fa		 0x0000000000000010ULL
+#define	TYPE_Fl		 0x0000000000000020ULL
+#define	TYPE_Dv		 0x0000000000000040ULL
+#define	TYPE_Fn		 0x0000000000000080ULL
+#define	TYPE_Ic		 0x0000000000000100ULL
+#define	TYPE_Pa		 0x0000000000000200ULL
+#define	TYPE_Cm		 0x0000000000000400ULL
+#define	TYPE_Li		 0x0000000000000800ULL
+#define	TYPE_Em		 0x0000000000001000ULL
+#define	TYPE_Cd		 0x0000000000002000ULL
+#define	TYPE_Va		 0x0000000000004000ULL
+#define	TYPE_Ft		 0x0000000000008000ULL
+#define	TYPE_Tn		 0x0000000000010000ULL
+#define	TYPE_Er		 0x0000000000020000ULL
+#define	TYPE_Ev		 0x0000000000040000ULL
+#define	TYPE_Sy		 0x0000000000080000ULL
+#define	TYPE_Sh		 0x0000000000100000ULL
+#define	TYPE_In		 0x0000000000200000ULL
+#define	TYPE_Ss		 0x0000000000400000ULL
+#define	TYPE_Ox		 0x0000000000800000ULL
+#define	TYPE_An		 0x0000000001000000ULL
+#define	TYPE_Mt		 0x0000000002000000ULL
+#define	TYPE_St		 0x0000000004000000ULL
+#define	TYPE_Bx		 0x0000000008000000ULL
+#define	TYPE_At		 0x0000000010000000ULL
+#define	TYPE_Nx		 0x0000000020000000ULL
+#define	TYPE_Fx		 0x0000000040000000ULL
+#define	TYPE_Lk		 0x0000000080000000ULL
+#define	TYPE_Ms		 0x0000000100000000ULL
+#define	TYPE_Bsx	 0x0000000200000000ULL
+#define	TYPE_Dx		 0x0000000400000000ULL
+#define	TYPE_Rs		 0x0000000800000000ULL
+#define	TYPE_Vt		 0x0000001000000000ULL
+#define	TYPE_Lb		 0x0000002000000000ULL
+#define	TYPE_Nm		 0x0000004000000000ULL
+#define	TYPE_Nd		 0x0000008000000000ULL
+
+#define	NAME_SYN	 0x0000004000000001ULL
+#define	NAME_FIRST	 0x0000004000000004ULL
+#define	NAME_TITLE	 0x0000004000000006ULL
+#define	NAME_HEAD	 0x0000004000000008ULL
+#define	NAME_FILE	 0x0000004000000010ULL
+#define	NAME_MASK	 0x000000000000001fULL
+
+#define	FORM_CAT	 0  /* manual page is preformatted */
+#define	FORM_SRC	 1  /* format is mdoc(7) or man(7) */
+#define	FORM_NONE	 4  /* format is unknown */
+
+enum	argmode {
+	ARG_FILE = 0,
+	ARG_NAME,
+	ARG_WORD,
+	ARG_EXPR
+};
+
+struct	manpage {
+	char		*file; /* to be prefixed by manpath */
+	char		*names; /* a list of names with sections */
+	char		*output; /* user-defined additional output */
+	size_t		 ipath; /* number of the manpath */
+	uint64_t	 bits; /* name type mask */
+	int		 sec; /* section number, 10 means invalid */
+	int		 form; /* 0 == catpage */
+};
+
+struct	mansearch {
+	const char	*arch; /* architecture/NULL */
+	const char	*sec; /* mansection/NULL */
+	const char	*outkey; /* show content of this macro */
+	enum argmode	 argmode; /* interpretation of arguments */
+	int		 firstmatch; /* first matching database only */
+};
+
+__BEGIN_DECLS
+
+struct	manpaths;
+
+int	mansearch_setup(int);
+int	mansearch(const struct mansearch *cfg, /* options */
+		const struct manpaths *paths, /* manpaths */
+		int argc, /* size of argv */
+		char *argv[],  /* search terms */
+		struct manpage **res, /* results */
+		size_t *ressz); /* results returned */
+void	mansearch_free(struct manpage *, size_t);
+
+__END_DECLS
diff --git a/usr/src/cmd/mandoc/mdoc.c b/usr/src/cmd/mandoc/mdoc.c
index 87b358797b..027ecbeb31 100644
--- a/usr/src/cmd/mandoc/mdoc.c
+++ b/usr/src/cmd/mandoc/mdoc.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc.c,v 1.206 2013/12/24 19:11:46 schwarze Exp $ */
+/*	$Id: mdoc.c,v 1.238 2015/02/12 13:00:52 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,13 +15,12 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -30,10 +29,11 @@
 
 #include "mdoc.h"
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmdoc.h"
 #include "libmandoc.h"
 
-const	char *const __mdoc_macronames[MDOC_MAX] = {		 
+const	char *const __mdoc_macronames[MDOC_MAX + 1] = {
 	"Ap",		"Dd",		"Dt",		"Os",
 	"Sh",		"Ss",		"Pp",		"D1",
 	"Dl",		"Bd",		"Ed",		"Bl",
@@ -44,11 +44,8 @@ const	char *const __mdoc_macronames[MDOC_MAX] = {
 	"Ic",		"In",		"Li",		"Nd",
 	"Nm",		"Op",		"Ot",		"Pa",
 	"Rv",		"St",		"Va",		"Vt",
-	/* LINTED */
 	"Xr",		"%A",		"%B",		"%D",
-	/* LINTED */
 	"%I",		"%J",		"%N",		"%O",
-	/* LINTED */
 	"%P",		"%R",		"%T",		"%V",
 	"Ac",		"Ao",		"Aq",		"At",
 	"Bc",		"Bf",		"Bo",		"Bq",
@@ -65,22 +62,19 @@ const	char *const __mdoc_macronames[MDOC_MAX] = {
 	"Bk",		"Ek",		"Bt",		"Hf",
 	"Fr",		"Ud",		"Lb",		"Lp",
 	"Lk",		"Mt",		"Brq",		"Bro",
-	/* LINTED */
 	"Brc",		"%C",		"Es",		"En",
-	/* LINTED */
 	"Dx",		"%Q",		"br",		"sp",
-	/* LINTED */
-	"%U",		"Ta"
+	"%U",		"Ta",		"ll",		"text",
 	};
 
-const	char *const __mdoc_argnames[MDOC_ARG_MAX] = {		 
+const	char *const __mdoc_argnames[MDOC_ARG_MAX] = {
 	"split",		"nosplit",		"ragged",
-	"unfilled",		"literal",		"file",		 
-	"offset",		"bullet",		"dash",		 
-	"hyphen",		"item",			"enum",		 
-	"tag",			"diag",			"hang",		 
-	"ohang",		"inset",		"column",	 
-	"width",		"compact",		"std",	 
+	"unfilled",		"literal",		"file",
+	"offset",		"bullet",		"dash",
+	"hyphen",		"item",			"enum",
+	"tag",			"diag",			"hang",
+	"ohang",		"inset",		"column",
+	"width",		"compact",		"std",
 	"filled",		"words",		"emphasis",
 	"symbolic",		"nested",		"centered"
 	};
@@ -89,38 +83,31 @@ const	char * const *mdoc_macronames = __mdoc_macronames;
 const	char * const *mdoc_argnames = __mdoc_argnames;
 
 static	void		  mdoc_node_free(struct mdoc_node *);
-static	void		  mdoc_node_unlink(struct mdoc *, 
+static	void		  mdoc_node_unlink(struct mdoc *,
 				struct mdoc_node *);
 static	void		  mdoc_free1(struct mdoc *);
 static	void		  mdoc_alloc1(struct mdoc *);
-static	struct mdoc_node *node_alloc(struct mdoc *, int, int, 
+static	struct mdoc_node *node_alloc(struct mdoc *, int, int,
 				enum mdoct, enum mdoc_type);
-static	int		  node_append(struct mdoc *, 
-				struct mdoc_node *);
-#if 0
-static	int		  mdoc_preptext(struct mdoc *, int, char *, int);
-#endif
+static	void		  node_append(struct mdoc *, struct mdoc_node *);
 static	int		  mdoc_ptext(struct mdoc *, int, char *, int);
 static	int		  mdoc_pmacro(struct mdoc *, int, char *, int);
 
+
 const struct mdoc_node *
 mdoc_node(const struct mdoc *mdoc)
 {
 
-	assert( ! (MDOC_HALT & mdoc->flags));
 	return(mdoc->first);
 }
 
-
 const struct mdoc_meta *
 mdoc_meta(const struct mdoc *mdoc)
 {
 
-	assert( ! (MDOC_HALT & mdoc->flags));
 	return(&mdoc->meta);
 }
 
-
 /*
  * Frees volatile resources (parse tree, meta-data, fields).
  */
@@ -130,23 +117,15 @@ mdoc_free1(struct mdoc *mdoc)
 
 	if (mdoc->first)
 		mdoc_node_delete(mdoc, mdoc->first);
-	if (mdoc->meta.title)
-		free(mdoc->meta.title);
-	if (mdoc->meta.os)
-		free(mdoc->meta.os);
-	if (mdoc->meta.name)
-		free(mdoc->meta.name);
-	if (mdoc->meta.arch)
-		free(mdoc->meta.arch);
-	if (mdoc->meta.vol)
-		free(mdoc->meta.vol);
-	if (mdoc->meta.msec)
-		free(mdoc->meta.msec);
-	if (mdoc->meta.date)
-		free(mdoc->meta.date);
+	free(mdoc->meta.msec);
+	free(mdoc->meta.vol);
+	free(mdoc->meta.arch);
+	free(mdoc->meta.date);
+	free(mdoc->meta.title);
+	free(mdoc->meta.os);
+	free(mdoc->meta.name);
 }
 
-
 /*
  * Allocate all volatile resources (parse tree, meta-data, fields).
  */
@@ -164,7 +143,6 @@ mdoc_alloc1(struct mdoc *mdoc)
 	mdoc->next = MDOC_NEXT_CHILD;
 }
 
-
 /*
  * Free up volatile resources (see mdoc_free1()) then re-initialises the
  * data with mdoc_alloc1().  After invocation, parse data has been reset
@@ -179,7 +157,6 @@ mdoc_reset(struct mdoc *mdoc)
 	mdoc_alloc1(mdoc);
 }
 
-
 /*
  * Completely free up all volatile and non-volatile parse resources.
  * After invocation, the pointer is no longer usable.
@@ -192,12 +169,12 @@ mdoc_free(struct mdoc *mdoc)
 	free(mdoc);
 }
 
-
 /*
- * Allocate volatile and non-volatile parse resources.  
+ * Allocate volatile and non-volatile parse resources.
  */
 struct mdoc *
-mdoc_alloc(struct roff *roff, struct mparse *parse, char *defos)
+mdoc_alloc(struct roff *roff, struct mparse *parse,
+	const char *defos, int quick)
 {
 	struct mdoc	*p;
 
@@ -205,6 +182,7 @@ mdoc_alloc(struct roff *roff, struct mparse *parse, char *defos)
 
 	p->parse = parse;
 	p->defos = defos;
+	p->quick = quick;
 	p->roff = roff;
 
 	mdoc_hash_init();
@@ -212,71 +190,37 @@ mdoc_alloc(struct roff *roff, struct mparse *parse, char *defos)
 	return(p);
 }
 
-
-/*
- * Climb back up the parse tree, validating open scopes.  Mostly calls
- * through to macro_end() in macro.c.
- */
-int
+void
 mdoc_endparse(struct mdoc *mdoc)
 {
 
-	assert( ! (MDOC_HALT & mdoc->flags));
-	if (mdoc_macroend(mdoc))
-		return(1);
-	mdoc->flags |= MDOC_HALT;
-	return(0);
+	mdoc_macroend(mdoc);
 }
 
-int
+void
 mdoc_addeqn(struct mdoc *mdoc, const struct eqn *ep)
 {
 	struct mdoc_node *n;
 
-	assert( ! (MDOC_HALT & mdoc->flags));
-
-	/* No text before an initial macro. */
-
-	if (SEC_NONE == mdoc->lastnamed) {
-		mdoc_pmsg(mdoc, ep->ln, ep->pos, MANDOCERR_NOTEXT);
-		return(1);
-	}
-
 	n = node_alloc(mdoc, ep->ln, ep->pos, MDOC_MAX, MDOC_EQN);
 	n->eqn = ep;
-
-	if ( ! node_append(mdoc, n))
-		return(0);
-
+	if (ep->ln > mdoc->last->line)
+		n->flags |= MDOC_LINE;
+	node_append(mdoc, n);
 	mdoc->next = MDOC_NEXT_SIBLING;
-	return(1);
 }
 
-int
+void
 mdoc_addspan(struct mdoc *mdoc, const struct tbl_span *sp)
 {
 	struct mdoc_node *n;
 
-	assert( ! (MDOC_HALT & mdoc->flags));
-
-	/* No text before an initial macro. */
-
-	if (SEC_NONE == mdoc->lastnamed) {
-		mdoc_pmsg(mdoc, sp->line, 0, MANDOCERR_NOTEXT);
-		return(1);
-	}
-
 	n = node_alloc(mdoc, sp->line, 0, MDOC_MAX, MDOC_TBL);
 	n->span = sp;
-
-	if ( ! node_append(mdoc, n))
-		return(0);
-
+	node_append(mdoc, n);
 	mdoc->next = MDOC_NEXT_SIBLING;
-	return(1);
 }
 
-
 /*
  * Main parse routine.  Parses a single line -- really just hands off to
  * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()).
@@ -285,9 +229,8 @@ int
 mdoc_parseln(struct mdoc *mdoc, int ln, char *buf, int offs)
 {
 
-	assert( ! (MDOC_HALT & mdoc->flags));
-
-	mdoc->flags |= MDOC_NEWLINE;
+	if (mdoc->last->type != MDOC_EQN || ln > mdoc->last->line)
+		mdoc->flags |= MDOC_NEWLINE;
 
 	/*
 	 * Let the roff nS register switch SYNOPSIS mode early,
@@ -301,47 +244,38 @@ mdoc_parseln(struct mdoc *mdoc, int ln, char *buf, int offs)
 		mdoc->flags &= ~MDOC_SYNOPSIS;
 
 	return(roff_getcontrol(mdoc->roff, buf, &offs) ?
-			mdoc_pmacro(mdoc, ln, buf, offs) :
-			mdoc_ptext(mdoc, ln, buf, offs));
+	    mdoc_pmacro(mdoc, ln, buf, offs) :
+	    mdoc_ptext(mdoc, ln, buf, offs));
 }
 
-int
+void
 mdoc_macro(MACRO_PROT_ARGS)
 {
 	assert(tok < MDOC_MAX);
 
-	/* If we're in the body, deny prologue calls. */
-
-	if (MDOC_PROLOGUE & mdoc_macros[tok].flags && 
-			MDOC_PBODY & mdoc->flags) {
-		mdoc_pmsg(mdoc, line, ppos, MANDOCERR_BADBODY);
-		return(1);
-	}
-
-	/* If we're in the prologue, deny "body" macros.  */
-
-	if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && 
-			! (MDOC_PBODY & mdoc->flags)) {
-		mdoc_pmsg(mdoc, line, ppos, MANDOCERR_BADPROLOG);
-		if (NULL == mdoc->meta.msec)
-			mdoc->meta.msec = mandoc_strdup("1");
-		if (NULL == mdoc->meta.title)
-			mdoc->meta.title = mandoc_strdup("UNKNOWN");
+	if (mdoc->flags & MDOC_PBODY) {
+		if (tok == MDOC_Dt) {
+			mandoc_vmsg(MANDOCERR_DT_LATE,
+			    mdoc->parse, line, ppos,
+			    "Dt %s", buf + *pos);
+			return;
+		}
+	} else if ( ! (mdoc_macros[tok].flags & MDOC_PROLOGUE)) {
+		if (mdoc->meta.title == NULL) {
+			mandoc_vmsg(MANDOCERR_DT_NOTITLE,
+			    mdoc->parse, line, ppos, "%s %s",
+			    mdoc_macronames[tok], buf + *pos);
+			mdoc->meta.title = mandoc_strdup("UNTITLED");
+		}
 		if (NULL == mdoc->meta.vol)
 			mdoc->meta.vol = mandoc_strdup("LOCAL");
-		if (NULL == mdoc->meta.os)
-			mdoc->meta.os = mandoc_strdup("LOCAL");
-		if (NULL == mdoc->meta.date)
-			mdoc->meta.date = mandoc_normdate
-				(mdoc->parse, NULL, line, ppos);
 		mdoc->flags |= MDOC_PBODY;
 	}
-
-	return((*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf));
+	(*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf);
 }
 
 
-static int
+static void
 node_append(struct mdoc *mdoc, struct mdoc_node *p)
 {
 
@@ -350,12 +284,12 @@ node_append(struct mdoc *mdoc, struct mdoc_node *p)
 	assert(MDOC_ROOT != p->type);
 
 	switch (mdoc->next) {
-	case (MDOC_NEXT_SIBLING):
+	case MDOC_NEXT_SIBLING:
 		mdoc->last->next = p;
 		p->prev = mdoc->last;
 		p->parent = mdoc->last->parent;
 		break;
-	case (MDOC_NEXT_CHILD):
+	case MDOC_NEXT_CHILD:
 		mdoc->last->child = p;
 		p->parent = mdoc->last;
 		break;
@@ -372,32 +306,31 @@ node_append(struct mdoc *mdoc, struct mdoc_node *p)
 	 */
 
 	switch (p->type) {
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		if (ENDBODY_NOT != p->end)
 			break;
 		/* FALLTHROUGH */
-	case (MDOC_TAIL):
+	case MDOC_TAIL:
 		/* FALLTHROUGH */
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		p->norm = p->parent->norm;
 		break;
 	default:
 		break;
 	}
 
-	if ( ! mdoc_valid_pre(mdoc, p))
-		return(0);
+	mdoc_valid_pre(mdoc, p);
 
 	switch (p->type) {
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		assert(MDOC_BLOCK == p->parent->type);
 		p->parent->head = p;
 		break;
-	case (MDOC_TAIL):
+	case MDOC_TAIL:
 		assert(MDOC_BLOCK == p->parent->type);
 		p->parent->tail = p;
 		break;
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		if (p->end)
 			break;
 		assert(MDOC_BLOCK == p->parent->type);
@@ -410,22 +343,18 @@ node_append(struct mdoc *mdoc, struct mdoc_node *p)
 	mdoc->last = p;
 
 	switch (p->type) {
-	case (MDOC_TBL):
+	case MDOC_TBL:
 		/* FALLTHROUGH */
-	case (MDOC_TEXT):
-		if ( ! mdoc_valid_post(mdoc))
-			return(0);
+	case MDOC_TEXT:
+		mdoc_valid_post(mdoc);
 		break;
 	default:
 		break;
 	}
-
-	return(1);
 }
 
-
 static struct mdoc_node *
-node_alloc(struct mdoc *mdoc, int line, int pos, 
+node_alloc(struct mdoc *mdoc, int line, int pos,
 		enum mdoct tok, enum mdoc_type type)
 {
 	struct mdoc_node *p;
@@ -434,7 +363,6 @@ node_alloc(struct mdoc *mdoc, int line, int pos,
 	p->sec = mdoc->lastsec;
 	p->line = line;
 	p->pos = pos;
-	p->lastline = line;
 	p->tok = tok;
 	p->type = type;
 
@@ -451,68 +379,59 @@ node_alloc(struct mdoc *mdoc, int line, int pos,
 	return(p);
 }
 
-
-int
+void
 mdoc_tail_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok)
 {
 	struct mdoc_node *p;
 
 	p = node_alloc(mdoc, line, pos, tok, MDOC_TAIL);
-	if ( ! node_append(mdoc, p))
-		return(0);
+	node_append(mdoc, p);
 	mdoc->next = MDOC_NEXT_CHILD;
-	return(1);
 }
 
-
-int
+struct mdoc_node *
 mdoc_head_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok)
 {
 	struct mdoc_node *p;
 
 	assert(mdoc->first);
 	assert(mdoc->last);
-
 	p = node_alloc(mdoc, line, pos, tok, MDOC_HEAD);
-	if ( ! node_append(mdoc, p))
-		return(0);
+	node_append(mdoc, p);
 	mdoc->next = MDOC_NEXT_CHILD;
-	return(1);
+	return(p);
 }
 
-
-int
+struct mdoc_node *
 mdoc_body_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok)
 {
 	struct mdoc_node *p;
 
 	p = node_alloc(mdoc, line, pos, tok, MDOC_BODY);
-	if ( ! node_append(mdoc, p))
-		return(0);
+	node_append(mdoc, p);
 	mdoc->next = MDOC_NEXT_CHILD;
-	return(1);
+	return(p);
 }
 
-
-int
+struct mdoc_node *
 mdoc_endbody_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok,
 		struct mdoc_node *body, enum mdoc_endbody end)
 {
 	struct mdoc_node *p;
 
+	body->flags |= MDOC_ENDED;
+	body->parent->flags |= MDOC_ENDED;
 	p = node_alloc(mdoc, line, pos, tok, MDOC_BODY);
-	p->pending = body;
+	p->body = body;
 	p->norm = body->norm;
 	p->end = end;
-	if ( ! node_append(mdoc, p))
-		return(0);
+	node_append(mdoc, p);
 	mdoc->next = MDOC_NEXT_SIBLING;
-	return(1);
+	return(p);
 }
 
-
-int
-mdoc_block_alloc(struct mdoc *mdoc, int line, int pos, 
+struct mdoc_node *
+mdoc_block_alloc(struct mdoc *mdoc, int line, int pos,
 		enum mdoct tok, struct mdoc_arg *args)
 {
 	struct mdoc_node *p;
@@ -523,28 +442,27 @@ mdoc_block_alloc(struct mdoc *mdoc, int line, int pos,
 		(args->refcnt)++;
 
 	switch (tok) {
-	case (MDOC_Bd):
+	case MDOC_Bd:
+		/* FALLTHROUGH */
+	case MDOC_Bf:
 		/* FALLTHROUGH */
-	case (MDOC_Bf):
+	case MDOC_Bl:
 		/* FALLTHROUGH */
-	case (MDOC_Bl):
+	case MDOC_En:
 		/* FALLTHROUGH */
-	case (MDOC_Rs):
+	case MDOC_Rs:
 		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
 		break;
 	default:
 		break;
 	}
-
-	if ( ! node_append(mdoc, p))
-		return(0);
+	node_append(mdoc, p);
 	mdoc->next = MDOC_NEXT_CHILD;
-	return(1);
+	return(p);
 }
 
-
-int
-mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos, 
+void
+mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos,
 		enum mdoct tok, struct mdoc_arg *args)
 {
 	struct mdoc_node *p;
@@ -555,32 +473,25 @@ mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos,
 		(args->refcnt)++;
 
 	switch (tok) {
-	case (MDOC_An):
+	case MDOC_An:
 		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
 		break;
 	default:
 		break;
 	}
-
-	if ( ! node_append(mdoc, p))
-		return(0);
+	node_append(mdoc, p);
 	mdoc->next = MDOC_NEXT_CHILD;
-	return(1);
 }
 
-int
+void
 mdoc_word_alloc(struct mdoc *mdoc, int line, int pos, const char *p)
 {
 	struct mdoc_node *n;
 
 	n = node_alloc(mdoc, line, pos, MDOC_MAX, MDOC_TEXT);
 	n->string = roff_strdup(mdoc->roff, p);
-
-	if ( ! node_append(mdoc, n))
-		return(0);
-
+	node_append(mdoc, n);
 	mdoc->next = MDOC_NEXT_SIBLING;
-	return(1);
 }
 
 void
@@ -591,10 +502,7 @@ mdoc_word_append(struct mdoc *mdoc, const char *p)
 
 	n = mdoc->last;
 	addstr = roff_strdup(mdoc->roff, p);
-	if (-1 == asprintf(&newstr, "%s %s", n->string, addstr)) {
-		perror(NULL);
-		exit((int)MANDOCLEVEL_SYSERR);
-	}
+	mandoc_asprintf(&newstr, "%s %s", n->string, addstr);
 	free(addstr);
 	free(n->string);
 	n->string = newstr;
@@ -614,7 +522,6 @@ mdoc_node_free(struct mdoc_node *p)
 	free(p);
 }
 
-
 static void
 mdoc_node_unlink(struct mdoc *mdoc, struct mdoc_node *n)
 {
@@ -652,7 +559,6 @@ mdoc_node_unlink(struct mdoc *mdoc, struct mdoc_node *n)
 		mdoc->first = NULL;
 }
 
-
 void
 mdoc_node_delete(struct mdoc *mdoc, struct mdoc_node *p)
 {
@@ -667,67 +573,13 @@ mdoc_node_delete(struct mdoc *mdoc, struct mdoc_node *p)
 	mdoc_node_free(p);
 }
 
-int
+void
 mdoc_node_relink(struct mdoc *mdoc, struct mdoc_node *p)
 {
 
 	mdoc_node_unlink(mdoc, p);
-	return(node_append(mdoc, p));
-}
-
-#if 0
-/*
- * Pre-treat a text line.
- * Text lines can consist of equations, which must be handled apart from
- * the regular text.
- * Thus, use this function to step through a line checking if it has any
- * equations embedded in it.
- * This must handle multiple equations AND equations that do not end at
- * the end-of-line, i.e., will re-enter in the next roff parse.
- */
-static int
-mdoc_preptext(struct mdoc *mdoc, int line, char *buf, int offs)
-{
-	char		*start, *end;
-	char		 delim;
-
-	while ('\0' != buf[offs]) {
-		/* Mark starting position if eqn is set. */
-		start = NULL;
-		if ('\0' != (delim = roff_eqndelim(mdoc->roff)))
-			if (NULL != (start = strchr(buf + offs, delim)))
-				*start++ = '\0';
-
-		/* Parse text as normal. */
-		if ( ! mdoc_ptext(mdoc, line, buf, offs))
-			return(0);
-
-		/* Continue only if an equation exists. */
-		if (NULL == start)
-			break;
-
-		/* Read past the end of the equation. */
-		offs += start - (buf + offs);
-		assert(start == &buf[offs]);
-		if (NULL != (end = strchr(buf + offs, delim))) {
-			*end++ = '\0';
-			while (' ' == *end)
-				end++;
-		}
-
-		/* Parse the equation itself. */
-		roff_openeqn(mdoc->roff, NULL, line, offs, buf);
-
-		/* Process a finished equation? */
-		if (roff_closeeqn(mdoc->roff))
-			if ( ! mdoc_addeqn(mdoc, roff_eqn(mdoc->roff)))
-				return(0);
-		offs += (end - (buf + offs));
-	} 
-
-	return(1);
+	node_append(mdoc, p);
 }
-#endif
 
 /*
  * Parse free-form text, that is, a line that does not begin with the
@@ -739,13 +591,6 @@ mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs)
 	char		 *c, *ws, *end;
 	struct mdoc_node *n;
 
-	/* No text before an initial macro. */
-
-	if (SEC_NONE == mdoc->lastnamed) {
-		mdoc_pmsg(mdoc, line, offs, MANDOCERR_NOTEXT);
-		return(1);
-	}
-
 	assert(mdoc->last);
 	n = mdoc->last;
 
@@ -756,20 +601,22 @@ mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs)
 	 * process within its context in the normal way).
 	 */
 
-	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
-			LIST_column == n->norm->Bl.type) {
+	if (n->tok == MDOC_Bl && n->type == MDOC_BODY &&
+	    n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) {
 		/* `Bl' is open without any children. */
 		mdoc->flags |= MDOC_FREECOL;
-		return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf));
+		mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf);
+		return(1);
 	}
 
 	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
-			NULL != n->parent &&
-			MDOC_Bl == n->parent->tok &&
-			LIST_column == n->parent->norm->Bl.type) {
+	    NULL != n->parent &&
+	    MDOC_Bl == n->parent->tok &&
+	    LIST_column == n->parent->norm->Bl.type) {
 		/* `Bl' has block-level `It' children. */
 		mdoc->flags |= MDOC_FREECOL;
-		return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf));
+		mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf);
+		return(1);
 	}
 
 	/*
@@ -814,28 +661,27 @@ mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs)
 	*end = '\0';
 
 	if (ws)
-		mdoc_pmsg(mdoc, line, (int)(ws-buf), MANDOCERR_EOLNSPACE);
+		mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+		    line, (int)(ws-buf), NULL);
 
-	if ('\0' == buf[offs] && ! (MDOC_LITERAL & mdoc->flags)) {
-		mdoc_pmsg(mdoc, line, (int)(c-buf), MANDOCERR_NOBLANKLN);
+	if (buf[offs] == '\0' && ! (mdoc->flags & MDOC_LITERAL)) {
+		mandoc_msg(MANDOCERR_FI_BLANK, mdoc->parse,
+		    line, (int)(c - buf), NULL);
 
 		/*
 		 * Insert a `sp' in the case of a blank line.  Technically,
 		 * blank lines aren't allowed, but enough manuals assume this
 		 * behaviour that we want to work around it.
 		 */
-		if ( ! mdoc_elem_alloc(mdoc, line, offs, MDOC_sp, NULL))
-			return(0);
-
+		mdoc_elem_alloc(mdoc, line, offs, MDOC_sp, NULL);
 		mdoc->next = MDOC_NEXT_SIBLING;
-
-		return(mdoc_valid_post(mdoc));
+		mdoc_valid_post(mdoc);
+		return(1);
 	}
 
-	if ( ! mdoc_word_alloc(mdoc, line, offs, buf+offs))
-		return(0);
+	mdoc_word_alloc(mdoc, line, offs, buf+offs);
 
-	if (MDOC_LITERAL & mdoc->flags)
+	if (mdoc->flags & MDOC_LITERAL)
 		return(1);
 
 	/*
@@ -846,13 +692,11 @@ mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs)
 
 	assert(buf < end);
 
-	if (mandoc_eos(buf+offs, (size_t)(end-buf-offs), 0))
+	if (mandoc_eos(buf+offs, (size_t)(end-buf-offs)))
 		mdoc->last->flags |= MDOC_EOS;
-
 	return(1);
 }
 
-
 /*
  * Parse a macro line, that is, a line beginning with the control
  * character.
@@ -860,58 +704,61 @@ mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs)
 static int
 mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs)
 {
+	struct mdoc_node *n;
+	const char	 *cp;
 	enum mdoct	  tok;
 	int		  i, sv;
 	char		  mac[5];
-	struct mdoc_node *n;
-
-	/* Empty post-control lines are ignored. */
-
-	if ('"' == buf[offs]) {
-		mdoc_pmsg(mdoc, ln, offs, MANDOCERR_BADCOMMENT);
-		return(1);
-	} else if ('\0' == buf[offs])
-		return(1);
 
 	sv = offs;
 
-	/* 
+	/*
 	 * Copy the first word into a nil-terminated buffer.
-	 * Stop copying when a tab, space, or eoln is encountered.
+	 * Stop when a space, tab, escape, or eoln is encountered.
 	 */
 
 	i = 0;
-	while (i < 4 && '\0' != buf[offs] && 
-			' ' != buf[offs] && '\t' != buf[offs])
+	while (i < 4 && strchr(" \t\\", buf[offs]) == NULL)
 		mac[i++] = buf[offs++];
 
 	mac[i] = '\0';
 
-	tok = (i > 1 || i < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
+	tok = (i > 1 && i < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
 
-	if (MDOC_MAX == tok) {
-		mandoc_vmsg(MANDOCERR_MACRO, mdoc->parse, 
-				ln, sv, "%s", buf + sv - 1);
+	if (tok == MDOC_MAX) {
+		mandoc_msg(MANDOCERR_MACRO, mdoc->parse,
+		    ln, sv, buf + sv - 1);
 		return(1);
 	}
 
-	/* Disregard the first trailing tab, if applicable. */
+	/* Skip a leading escape sequence or tab. */
 
-	if ('\t' == buf[offs])
+	switch (buf[offs]) {
+	case '\\':
+		cp = buf + offs + 1;
+		mandoc_escape(&cp, NULL, NULL);
+		offs = cp - buf;
+		break;
+	case '\t':
 		offs++;
+		break;
+	default:
+		break;
+	}
 
 	/* Jump to the next non-whitespace word. */
 
 	while (buf[offs] && ' ' == buf[offs])
 		offs++;
 
-	/* 
+	/*
 	 * Trailing whitespace.  Note that tabs are allowed to be passed
 	 * into the parser as "text", so we only warn about spaces here.
 	 */
 
 	if ('\0' == buf[offs] && ' ' == buf[offs - 1])
-		mdoc_pmsg(mdoc, ln, offs - 1, MANDOCERR_EOLNSPACE);
+		mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+		    ln, offs - 1, NULL);
 
 	/*
 	 * If an initial macro or a list invocation, divert directly
@@ -919,8 +766,7 @@ mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs)
 	 */
 
 	if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok) {
-		if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf)) 
-			goto err;
+		mdoc_macro(mdoc, tok, ln, sv, &offs, buf);
 		return(1);
 	}
 
@@ -932,11 +778,10 @@ mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs)
 	 * context around the parsed macro.
 	 */
 
-	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
-			LIST_column == n->norm->Bl.type) {
+	if (n->tok == MDOC_Bl && n->type == MDOC_BODY &&
+	    n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) {
 		mdoc->flags |= MDOC_FREECOL;
-		if ( ! mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf))
-			goto err;
+		mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf);
 		return(1);
 	}
 
@@ -947,26 +792,25 @@ mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs)
 	 */
 
 	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
-			NULL != n->parent &&
-			MDOC_Bl == n->parent->tok &&
-			LIST_column == n->parent->norm->Bl.type) {
+	    NULL != n->parent &&
+	    MDOC_Bl == n->parent->tok &&
+	    LIST_column == n->parent->norm->Bl.type) {
 		mdoc->flags |= MDOC_FREECOL;
-		if ( ! mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf)) 
-			goto err;
+		mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf);
 		return(1);
 	}
 
 	/* Normal processing of a macro. */
 
-	if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf)) 
-		goto err;
+	mdoc_macro(mdoc, tok, ln, sv, &offs, buf);
 
-	return(1);
+	/* In quick mode (for mandocdb), abort after the NAME section. */
 
-err:	/* Error out. */
+	if (mdoc->quick && MDOC_Sh == tok &&
+	    SEC_NAME != mdoc->last->sec)
+		return(2);
 
-	mdoc->flags |= MDOC_HALT;
-	return(0);
+	return(1);
 }
 
 enum mdelim
@@ -978,27 +822,27 @@ mdoc_isdelim(const char *p)
 
 	if ('\0' == p[1])
 		switch (p[0]) {
-		case('('):
+		case '(':
 			/* FALLTHROUGH */
-		case('['):
+		case '[':
 			return(DELIM_OPEN);
-		case('|'):
+		case '|':
 			return(DELIM_MIDDLE);
-		case('.'):
+		case '.':
 			/* FALLTHROUGH */
-		case(','):
+		case ',':
 			/* FALLTHROUGH */
-		case(';'):
+		case ';':
 			/* FALLTHROUGH */
-		case(':'):
+		case ':':
 			/* FALLTHROUGH */
-		case('?'):
+		case '?':
 			/* FALLTHROUGH */
-		case('!'):
+		case '!':
 			/* FALLTHROUGH */
-		case(')'):
+		case ')':
 			/* FALLTHROUGH */
-		case(']'):
+		case ']':
 			return(DELIM_CLOSE);
 		default:
 			return(DELIM_NONE);
@@ -1014,3 +858,42 @@ mdoc_isdelim(const char *p)
 
 	return(DELIM_NONE);
 }
+
+void
+mdoc_deroff(char **dest, const struct mdoc_node *n)
+{
+	char	*cp;
+	size_t	 sz;
+
+	if (MDOC_TEXT != n->type) {
+		for (n = n->child; n; n = n->next)
+			mdoc_deroff(dest, n);
+		return;
+	}
+
+	/* Skip leading whitespace. */
+
+	for (cp = n->string; '\0' != *cp; cp++)
+		if (0 == isspace((unsigned char)*cp))
+			break;
+
+	/* Skip trailing whitespace. */
+
+	for (sz = strlen(cp); sz; sz--)
+		if (0 == isspace((unsigned char)cp[sz-1]))
+			break;
+
+	/* Skip empty strings. */
+
+	if (0 == sz)
+		return;
+
+	if (NULL == *dest) {
+		*dest = mandoc_strndup(cp, sz);
+		return;
+	}
+
+	mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
+	free(*dest);
+	*dest = cp;
+}
diff --git a/usr/src/cmd/mandoc/mdoc.h b/usr/src/cmd/mandoc/mdoc.h
index d0153b4480..e45786d4aa 100644
--- a/usr/src/cmd/mandoc/mdoc.h
+++ b/usr/src/cmd/mandoc/mdoc.h
@@ -1,6 +1,7 @@
-/*	$Id: mdoc.h,v 1.125 2013/12/24 19:11:45 schwarze Exp $ */
+/*	$Id: mdoc.h,v 1.136 2015/02/12 12:24:33 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,8 +15,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef MDOC_H
-#define MDOC_H
 
 enum	mdoct {
 	MDOC_Ap = 0,
@@ -140,6 +139,7 @@ enum	mdoct {
 	MDOC_sp,
 	MDOC__U,
 	MDOC_Ta,
+	MDOC_ll,
 	MDOC_MAX
 };
 
@@ -186,7 +186,7 @@ enum	mdoc_type {
 	MDOC_ROOT
 };
 
-/* 
+/*
  * Section (named/unnamed) of `Sh'.   Note that these appear in the
  * conventional order imposed by mdoc.7.  In the case of SEC_NONE, no
  * section has been invoked (this shouldn't happen).  SEC_CUSTOM refers
@@ -198,6 +198,7 @@ enum	mdoc_sec {
 	SEC_LIBRARY, /* LIBRARY */
 	SEC_SYNOPSIS, /* SYNOPSIS */
 	SEC_DESCRIPTION, /* DESCRIPTION */
+	SEC_CONTEXT, /* CONTEXT */
 	SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */
 	SEC_RETURN_VALUES, /* RETURN VALUES */
 	SEC_ENVIRONMENT,  /* ENVIRONMENT */
@@ -214,7 +215,7 @@ enum	mdoc_sec {
 	SEC_CAVEATS, /* CAVEATS */
 	SEC_BUGS, /* BUGS */
 	SEC_SECURITY, /* SECURITY */
-	SEC_CUSTOM, 
+	SEC_CUSTOM,
 	SEC__MAX
 };
 
@@ -228,11 +229,11 @@ struct	mdoc_meta {
 	char		 *name; /* leading `Nm' name */
 };
 
-/* 
- * An argument to a macro (multiple values = `-column xxx yyy'). 
+/*
+ * An argument to a macro (multiple values = `-column xxx yyy').
  */
 struct	mdoc_argv {
-	enum mdocargt  	  arg; /* type of argument */
+	enum mdocargt	  arg; /* type of argument */
 	int		  line;
 	int		  pos;
 	size_t		  sz; /* elements in "value" */
@@ -244,7 +245,7 @@ struct	mdoc_argv {
  * blocks have multiple instances of the same arguments spread across
  * the HEAD, BODY, TAIL, and BLOCK node types.
  */
-struct 	mdoc_arg {
+struct	mdoc_arg {
 	size_t		  argc;
 	struct mdoc_argv *argv;
 	unsigned int	  refcnt;
@@ -278,7 +279,7 @@ enum	mdoc_list {
 
 enum	mdoc_disp {
 	DISP__NONE = 0,
-	DISP_centred, /* -centered */
+	DISP_centered, /* -centered */
 	DISP_ragged, /* -ragged */
 	DISP_unfilled, /* -unfilled */
 	DISP_filled, /* -filled */
@@ -332,15 +333,16 @@ struct	mdoc_rs {
  * provided, etc.
  */
 union	mdoc_data {
-	struct mdoc_an 	  An;
+	struct mdoc_an	  An;
 	struct mdoc_bd	  Bd;
 	struct mdoc_bf	  Bf;
 	struct mdoc_bl	  Bl;
+	struct mdoc_node *Es;
 	struct mdoc_rs	  Rs;
 };
 
-/* 
- * Single node in tree-linked AST. 
+/*
+ * Single node in tree-linked AST.
  */
 struct	mdoc_node {
 	struct mdoc_node *parent; /* parent AST node */
@@ -351,25 +353,24 @@ struct	mdoc_node {
 	int		  nchild; /* number children */
 	int		  line; /* parse line */
 	int		  pos; /* parse column */
-	int		  lastline; /* the node ends on this line */
 	enum mdoct	  tok; /* tok or MDOC__MAX if none */
 	int		  flags;
 #define	MDOC_VALID	 (1 << 0) /* has been validated */
+#define	MDOC_ENDED	 (1 << 1) /* gone past body end mark */
 #define	MDOC_EOS	 (1 << 2) /* at sentence boundary */
 #define	MDOC_LINE	 (1 << 3) /* first macro/text on line */
 #define	MDOC_SYNPRETTY	 (1 << 4) /* SYNOPSIS-style formatting */
-#define	MDOC_ENDED	 (1 << 5) /* rendering has been ended */
+#define	MDOC_BROKEN	 (1 << 5) /* must validate parent when ending */
 #define	MDOC_DELIMO	 (1 << 6)
 #define	MDOC_DELIMC	 (1 << 7)
 	enum mdoc_type	  type; /* AST node type */
 	enum mdoc_sec	  sec; /* current named section */
 	union mdoc_data	 *norm; /* normalised args */
-	const void	 *prev_font; /* before entering this node */
+	int		  prev_font; /* before entering this node */
 	/* FIXME: these can be union'd to shave a few bytes. */
 	struct mdoc_arg	 *args; /* BLOCK/ELEM */
-	struct mdoc_node *pending; /* BLOCK */
 	struct mdoc_node *head; /* BLOCK */
-	struct mdoc_node *body; /* BLOCK */
+	struct mdoc_node *body; /* BLOCK/ENDBODY */
 	struct mdoc_node *tail; /* BLOCK */
 	char		 *string; /* TEXT */
 	const struct tbl_span *span; /* TBL */
@@ -389,7 +390,6 @@ struct	mdoc;
 
 const struct mdoc_node *mdoc_node(const struct mdoc *);
 const struct mdoc_meta *mdoc_meta(const struct mdoc *);
+void mdoc_deroff(char **, const struct mdoc_node *);
 
 __END_DECLS
-
-#endif /*!MDOC_H*/
diff --git a/usr/src/cmd/mandoc/mdoc_argv.c b/usr/src/cmd/mandoc/mdoc_argv.c
index bb9bc6c339..a53389bf99 100644
--- a/usr/src/cmd/mandoc/mdoc_argv.c
+++ b/usr/src/cmd/mandoc/mdoc_argv.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_argv.c,v 1.89 2013/12/25 00:50:05 schwarze Exp $ */
+/*	$Id: mdoc_argv.c,v 1.100 2015/02/04 18:59:45 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2012 Ingo Schwarze 
+ * Copyright (c) 2012, 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,7 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
@@ -28,11 +26,12 @@
 
 #include "mdoc.h"
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmdoc.h"
 #include "libmandoc.h"
 
 #define	MULTI_STEP	 5 /* pre-allocate argument values */
-#define	DELIMSZ	  	 6 /* max possible size of a delimiter */
+#define	DELIMSZ		 6 /* max possible size of a delimiter */
 
 enum	argsflag {
 	ARGSFL_NONE = 0,
@@ -52,12 +51,12 @@ struct	mdocarg {
 };
 
 static	void		 argn_free(struct mdoc_arg *, int);
-static	enum margserr	 args(struct mdoc *, int, int *, 
+static	enum margserr	 args(struct mdoc *, int, int *,
 				char *, enum argsflag, char **);
 static	int		 args_checkpunct(const char *, int);
-static	int		 argv_multi(struct mdoc *, int, 
+static	void		 argv_multi(struct mdoc *, int,
 				struct mdoc_argv *, int *, char *);
-static	int		 argv_single(struct mdoc *, int, 
+static	void		 argv_single(struct mdoc *, int,
 				struct mdoc_argv *, int *, char *);
 
 static	const enum argvflag argvflags[MDOC_ARG_MAX] = {
@@ -149,8 +148,8 @@ static	const struct mdocarg mdocargs[MDOC_MAX] = {
 	{ ARGSFL_NONE, NULL }, /* Dt */
 	{ ARGSFL_NONE, NULL }, /* Os */
 	{ ARGSFL_NONE, NULL }, /* Sh */
-	{ ARGSFL_NONE, NULL }, /* Ss */ 
-	{ ARGSFL_NONE, NULL }, /* Pp */ 
+	{ ARGSFL_NONE, NULL }, /* Ss */
+	{ ARGSFL_NONE, NULL }, /* Pp */
 	{ ARGSFL_DELIM, NULL }, /* D1 */
 	{ ARGSFL_DELIM, NULL }, /* Dl */
 	{ ARGSFL_NONE, args_Bd }, /* Bd */
@@ -158,32 +157,32 @@ static	const struct mdocarg mdocargs[MDOC_MAX] = {
 	{ ARGSFL_NONE, args_Bl }, /* Bl */
 	{ ARGSFL_NONE, NULL }, /* El */
 	{ ARGSFL_NONE, NULL }, /* It */
-	{ ARGSFL_DELIM, NULL }, /* Ad */ 
+	{ ARGSFL_DELIM, NULL }, /* Ad */
 	{ ARGSFL_DELIM, args_An }, /* An */
 	{ ARGSFL_DELIM, NULL }, /* Ar */
 	{ ARGSFL_DELIM, NULL }, /* Cd */
 	{ ARGSFL_DELIM, NULL }, /* Cm */
-	{ ARGSFL_DELIM, NULL }, /* Dv */ 
-	{ ARGSFL_DELIM, NULL }, /* Er */ 
-	{ ARGSFL_DELIM, NULL }, /* Ev */ 
+	{ ARGSFL_DELIM, NULL }, /* Dv */
+	{ ARGSFL_DELIM, NULL }, /* Er */
+	{ ARGSFL_DELIM, NULL }, /* Ev */
 	{ ARGSFL_NONE, args_Ex }, /* Ex */
-	{ ARGSFL_DELIM, NULL }, /* Fa */ 
-	{ ARGSFL_NONE, NULL }, /* Fd */ 
+	{ ARGSFL_DELIM, NULL }, /* Fa */
+	{ ARGSFL_NONE, NULL }, /* Fd */
 	{ ARGSFL_DELIM, NULL }, /* Fl */
-	{ ARGSFL_DELIM, NULL }, /* Fn */ 
-	{ ARGSFL_DELIM, NULL }, /* Ft */ 
-	{ ARGSFL_DELIM, NULL }, /* Ic */ 
-	{ ARGSFL_DELIM, NULL }, /* In */ 
+	{ ARGSFL_DELIM, NULL }, /* Fn */
+	{ ARGSFL_DELIM, NULL }, /* Ft */
+	{ ARGSFL_DELIM, NULL }, /* Ic */
+	{ ARGSFL_DELIM, NULL }, /* In */
 	{ ARGSFL_DELIM, NULL }, /* Li */
-	{ ARGSFL_NONE, NULL }, /* Nd */ 
-	{ ARGSFL_DELIM, NULL }, /* Nm */ 
+	{ ARGSFL_NONE, NULL }, /* Nd */
+	{ ARGSFL_DELIM, NULL }, /* Nm */
 	{ ARGSFL_DELIM, NULL }, /* Op */
-	{ ARGSFL_NONE, NULL }, /* Ot */
+	{ ARGSFL_DELIM, NULL }, /* Ot */
 	{ ARGSFL_DELIM, NULL }, /* Pa */
 	{ ARGSFL_NONE, args_Ex }, /* Rv */
-	{ ARGSFL_DELIM, NULL }, /* St */ 
+	{ ARGSFL_DELIM, NULL }, /* St */
 	{ ARGSFL_DELIM, NULL }, /* Va */
-	{ ARGSFL_DELIM, NULL }, /* Vt */ 
+	{ ARGSFL_DELIM, NULL }, /* Vt */
 	{ ARGSFL_DELIM, NULL }, /* Xr */
 	{ ARGSFL_NONE, NULL }, /* %A */
 	{ ARGSFL_NONE, NULL }, /* %B */
@@ -201,7 +200,7 @@ static	const struct mdocarg mdocargs[MDOC_MAX] = {
 	{ ARGSFL_DELIM, NULL }, /* Aq */
 	{ ARGSFL_DELIM, NULL }, /* At */
 	{ ARGSFL_DELIM, NULL }, /* Bc */
-	{ ARGSFL_NONE, args_Bf }, /* Bf */ 
+	{ ARGSFL_NONE, args_Bf }, /* Bf */
 	{ ARGSFL_NONE, NULL }, /* Bo */
 	{ ARGSFL_DELIM, NULL }, /* Bq */
 	{ ARGSFL_DELIM, NULL }, /* Bsx */
@@ -212,7 +211,7 @@ static	const struct mdocarg mdocargs[MDOC_MAX] = {
 	{ ARGSFL_DELIM, NULL }, /* Dq */
 	{ ARGSFL_DELIM, NULL }, /* Ec */
 	{ ARGSFL_NONE, NULL }, /* Ef */
-	{ ARGSFL_DELIM, NULL }, /* Em */ 
+	{ ARGSFL_DELIM, NULL }, /* Em */
 	{ ARGSFL_NONE, NULL }, /* Eo */
 	{ ARGSFL_DELIM, NULL }, /* Fx */
 	{ ARGSFL_DELIM, NULL }, /* Ms */
@@ -240,15 +239,15 @@ static	const struct mdocarg mdocargs[MDOC_MAX] = {
 	{ ARGSFL_DELIM, NULL }, /* Ux */
 	{ ARGSFL_DELIM, NULL }, /* Xc */
 	{ ARGSFL_NONE, NULL }, /* Xo */
-	{ ARGSFL_NONE, NULL }, /* Fo */ 
-	{ ARGSFL_DELIM, NULL }, /* Fc */ 
+	{ ARGSFL_NONE, NULL }, /* Fo */
+	{ ARGSFL_DELIM, NULL }, /* Fc */
 	{ ARGSFL_NONE, NULL }, /* Oo */
 	{ ARGSFL_DELIM, NULL }, /* Oc */
 	{ ARGSFL_NONE, args_Bk }, /* Bk */
 	{ ARGSFL_NONE, NULL }, /* Ek */
 	{ ARGSFL_NONE, NULL }, /* Bt */
 	{ ARGSFL_NONE, NULL }, /* Hf */
-	{ ARGSFL_NONE, NULL }, /* Fr */
+	{ ARGSFL_DELIM, NULL }, /* Fr */
 	{ ARGSFL_NONE, NULL }, /* Ud */
 	{ ARGSFL_DELIM, NULL }, /* Lb */
 	{ ARGSFL_NONE, NULL }, /* Lp */
@@ -259,111 +258,117 @@ static	const struct mdocarg mdocargs[MDOC_MAX] = {
 	{ ARGSFL_DELIM, NULL }, /* Brc */
 	{ ARGSFL_NONE, NULL }, /* %C */
 	{ ARGSFL_NONE, NULL }, /* Es */
-	{ ARGSFL_NONE, NULL }, /* En */
+	{ ARGSFL_DELIM, NULL }, /* En */
 	{ ARGSFL_DELIM, NULL }, /* Dx */
 	{ ARGSFL_NONE, NULL }, /* %Q */
 	{ ARGSFL_NONE, NULL }, /* br */
 	{ ARGSFL_NONE, NULL }, /* sp */
 	{ ARGSFL_NONE, NULL }, /* %U */
 	{ ARGSFL_NONE, NULL }, /* Ta */
+	{ ARGSFL_NONE, NULL }, /* ll */
 };
 
 
 /*
- * Parse an argument from line text.  This comes in the form of -key
- * [value0...], which may either have a single mandatory value, at least
- * one mandatory value, an optional single value, or no value.
+ * Parse flags and their arguments from the input line.
+ * These come in the form -flag [argument ...].
+ * Some flags take no argument, some one, some multiple.
  */
-enum margverr
+void
 mdoc_argv(struct mdoc *mdoc, int line, enum mdoct tok,
-		struct mdoc_arg **v, int *pos, char *buf)
+	struct mdoc_arg **reta, int *pos, char *buf)
 {
-	char		 *p, sv;
-	struct mdoc_argv tmp;
-	struct mdoc_arg	 *arg;
-	const enum mdocargt *ap;
+	struct mdoc_argv	  tmpv;
+	struct mdoc_argv	**retv;
+	const enum mdocargt	 *argtable;
+	char			 *argname;
+	int			  ipos, retc;
+	char			  savechar;
 
-	if ('\0' == buf[*pos])
-		return(ARGV_EOLN);
-	else if (NULL == (ap = mdocargs[tok].argvs))
-		return(ARGV_WORD);
-	else if ('-' != buf[*pos])
-		return(ARGV_WORD);
+	*reta = NULL;
 
-	/* Seek to the first unescaped space. */
+	/* Which flags does this macro support? */
 
-	p = &buf[++(*pos)];
+	argtable = mdocargs[tok].argvs;
+	if (argtable == NULL)
+		return;
 
-	assert(*pos > 0);
+	/* Loop over the flags on the input line. */
 
-	for ( ; buf[*pos] ; (*pos)++)
-		if (' ' == buf[*pos] && '\\' != buf[*pos - 1])
-			break;
+	ipos = *pos;
+	while (buf[ipos] == '-') {
 
-	/* 
-	 * We want to nil-terminate the word to look it up (it's easier
-	 * that way).  But we may not have a flag, in which case we need
-	 * to restore the line as-is.  So keep around the stray byte,
-	 * which we'll reset upon exiting (if necessary).
-	 */
+		/* Seek to the first unescaped space. */
 
-	if ('\0' != (sv = buf[*pos])) 
-		buf[(*pos)++] = '\0';
+		for (argname = buf + ++ipos; buf[ipos] != '\0'; ipos++)
+			if (buf[ipos] == ' ' && buf[ipos - 1] != '\\')
+				break;
 
-	/*
-	 * Now look up the word as a flag.  Use temporary storage that
-	 * we'll copy into the node's flags, if necessary.
-	 */
+		/*
+		 * We want to nil-terminate the word to look it up.
+		 * But we may not have a flag, in which case we need
+		 * to restore the line as-is.  So keep around the
+		 * stray byte, which we'll reset upon exiting.
+		 */
 
-	memset(&tmp, 0, sizeof(struct mdoc_argv));
+		if ((savechar = buf[ipos]) != '\0')
+			buf[ipos++] = '\0';
 
-	tmp.line = line;
-	tmp.pos = *pos;
-	tmp.arg = MDOC_ARG_MAX;
+		/*
+		 * Now look up the word as a flag.  Use temporary
+		 * storage that we'll copy into the node's flags.
+		 */
 
-	while (MDOC_ARG_MAX != (tmp.arg = *ap++))
-		if (0 == strcmp(p, mdoc_argnames[tmp.arg]))
+		while ((tmpv.arg = *argtable++) != MDOC_ARG_MAX)
+			if ( ! strcmp(argname, mdoc_argnames[tmpv.arg]))
+				break;
+
+		/* If it isn't a flag, restore the saved byte. */
+
+		if (tmpv.arg == MDOC_ARG_MAX) {
+			if (savechar != '\0')
+				buf[ipos - 1] = savechar;
 			break;
+		}
 
-	if (MDOC_ARG_MAX == tmp.arg) {
-		/* 
-		 * The flag was not found.
-		 * Restore saved zeroed byte and return as a word.
-		 */
-		if (sv)
-			buf[*pos - 1] = sv;
-		return(ARGV_WORD);
-	}
+		/* Read to the next word (the first argument). */
 
-	/* Read to the next word (the argument). */
-
-	while (buf[*pos] && ' ' == buf[*pos])
-		(*pos)++;
-
-	switch (argvflags[tmp.arg]) {
-	case (ARGV_SINGLE):
-		if ( ! argv_single(mdoc, line, &tmp, pos, buf))
-			return(ARGV_ERROR);
-		break;
-	case (ARGV_MULTI):
-		if ( ! argv_multi(mdoc, line, &tmp, pos, buf))
-			return(ARGV_ERROR);
-		break;
-	case (ARGV_NONE):
-		break;
-	}
+		while (buf[ipos] == ' ')
+			ipos++;
 
-	if (NULL == (arg = *v))
-		arg = *v = mandoc_calloc(1, sizeof(struct mdoc_arg));
+		/* Parse the arguments of the flag. */
+
+		tmpv.line  = line;
+		tmpv.pos   = *pos;
+		tmpv.sz    = 0;
+		tmpv.value = NULL;
+
+		switch (argvflags[tmpv.arg]) {
+		case ARGV_SINGLE:
+			argv_single(mdoc, line, &tmpv, &ipos, buf);
+			break;
+		case ARGV_MULTI:
+			argv_multi(mdoc, line, &tmpv, &ipos, buf);
+			break;
+		case ARGV_NONE:
+			break;
+		}
 
-	arg->argc++;
-	arg->argv = mandoc_realloc
-		(arg->argv, arg->argc * sizeof(struct mdoc_argv));
+		/* Append to the return values. */
 
-	memcpy(&arg->argv[(int)arg->argc - 1], 
-			&tmp, sizeof(struct mdoc_argv));
+		if (*reta == NULL)
+			*reta = mandoc_calloc(1, sizeof(**reta));
 
-	return(ARGV_ARG);
+		retc = ++(*reta)->argc;
+		retv = &(*reta)->argv;
+		*retv = mandoc_reallocarray(*retv, retc, sizeof(**retv));
+		memcpy(*retv + retc - 1, &tmpv, sizeof(**retv));
+
+		/* Prepare for parsing the next flag. */
+
+		*pos = ipos;
+		argtable = mdocargs[tok].argvs;
+	}
 }
 
 void
@@ -397,7 +402,7 @@ argn_free(struct mdoc_arg *p, int iarg)
 	arg = &p->argv[iarg];
 
 	if (arg->sz && arg->value) {
-		for (j = (int)arg->sz - 1; j >= 0; j--) 
+		for (j = (int)arg->sz - 1; j >= 0; j--)
 			free(arg->value[j]);
 		free(arg->value);
 	}
@@ -407,22 +412,17 @@ argn_free(struct mdoc_arg *p, int iarg)
 }
 
 enum margserr
-mdoc_zargs(struct mdoc *mdoc, int line, int *pos, char *buf, char **v)
-{
-
-	return(args(mdoc, line, pos, buf, ARGSFL_NONE, v));
-}
-
-enum margserr
-mdoc_args(struct mdoc *mdoc, int line, int *pos, 
+mdoc_args(struct mdoc *mdoc, int line, int *pos,
 		char *buf, enum mdoct tok, char **v)
 {
-	enum argsflag	  fl;
 	struct mdoc_node *n;
+	char		 *v_local;
+	enum argsflag	  fl;
 
-	fl = mdocargs[tok].flags;
-
-	if (MDOC_It != tok)
+	if (v == NULL)
+		v = &v_local;
+	fl = tok == MDOC_MAX ? ARGSFL_NONE : mdocargs[tok].flags;
+	if (tok != MDOC_It)
 		return(args(mdoc, line, pos, buf, fl, v));
 
 	/*
@@ -443,7 +443,7 @@ mdoc_args(struct mdoc *mdoc, int line, int *pos,
 }
 
 static enum margserr
-args(struct mdoc *mdoc, int line, int *pos, 
+args(struct mdoc *mdoc, int line, int *pos,
 		char *buf, enum argsflag fl, char **v)
 {
 	char		*p, *pp;
@@ -459,7 +459,8 @@ args(struct mdoc *mdoc, int line, int *pos,
 		 * is unterminated.
 		 */
 		if (MDOC_PHRASELIT & mdoc->flags)
-			mdoc_pmsg(mdoc, line, *pos, MANDOCERR_BADQUOTE);
+			mandoc_msg(MANDOCERR_ARG_QUOTE,
+			    mdoc->parse, line, *pos, NULL);
 
 		mdoc->flags &= ~MDOC_PHRASELIT;
 		return(ARGS_EOLN);
@@ -484,7 +485,7 @@ args(struct mdoc *mdoc, int line, int *pos,
 		pp = NULL;
 
 		/* Scan ahead to unescaped `Ta'. */
-		if ( ! (MDOC_PHRASELIT & mdoc->flags)) 
+		if ( ! (MDOC_PHRASELIT & mdoc->flags))
 			for (pp = *v; ; pp++) {
 				if (NULL == (pp = strstr(pp, "Ta")))
 					break;
@@ -497,7 +498,7 @@ args(struct mdoc *mdoc, int line, int *pos,
 		/* By default, assume a phrase. */
 		rc = ARGS_PHRASE;
 
-		/* 
+		/*
 		 * Adjust new-buffer position to be beyond delimiter
 		 * mark (e.g., Ta -> end + 2).
 		 */
@@ -518,7 +519,8 @@ args(struct mdoc *mdoc, int line, int *pos,
 
 		/* Whitespace check for eoln case... */
 		if ('\0' == *p && ' ' == *(p - 1))
-			mdoc_pmsg(mdoc, line, *pos, MANDOCERR_EOLNSPACE);
+			mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+			    line, *pos, NULL);
 
 		*pos += (int)(p - *v);
 
@@ -573,7 +575,8 @@ args(struct mdoc *mdoc, int line, int *pos,
 		if ('\0' == buf[*pos]) {
 			if (MDOC_PPHRASE & mdoc->flags)
 				return(ARGS_QWORD);
-			mdoc_pmsg(mdoc, line, *pos, MANDOCERR_BADQUOTE);
+			mandoc_msg(MANDOCERR_ARG_QUOTE,
+			    mdoc->parse, line, *pos, NULL);
 			return(ARGS_QWORD);
 		}
 
@@ -587,7 +590,8 @@ args(struct mdoc *mdoc, int line, int *pos,
 			(*pos)++;
 
 		if ('\0' == buf[*pos])
-			mdoc_pmsg(mdoc, line, *pos, MANDOCERR_EOLNSPACE);
+			mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse,
+			    line, *pos, NULL);
 
 		return(ARGS_QWORD);
 	}
@@ -598,7 +602,7 @@ args(struct mdoc *mdoc, int line, int *pos,
 	return(ARGS_WORD);
 }
 
-/* 
+/*
  * Check if the string consists only of space-separated closing
  * delimiters.  This is a bit of a dance: the first must be a close
  * delimiter, but it may be followed by middle delimiters.  Arbitrary
@@ -627,7 +631,7 @@ args_checkpunct(const char *buf, int i)
 		i++;
 
 	/* Remaining must NOT be open/none. */
-	
+
 	while (buf[i]) {
 		j = 0;
 		while (buf[i] && ' ' != buf[i] && j < DELIMSZ)
@@ -648,48 +652,40 @@ args_checkpunct(const char *buf, int i)
 	return('\0' == buf[i]);
 }
 
-static int
-argv_multi(struct mdoc *mdoc, int line, 
+static void
+argv_multi(struct mdoc *mdoc, int line,
 		struct mdoc_argv *v, int *pos, char *buf)
 {
 	enum margserr	 ac;
 	char		*p;
 
 	for (v->sz = 0; ; v->sz++) {
-		if ('-' == buf[*pos])
+		if (buf[*pos] == '-')
 			break;
 		ac = args(mdoc, line, pos, buf, ARGSFL_NONE, &p);
-		if (ARGS_ERROR == ac)
-			return(0);
-		else if (ARGS_EOLN == ac)
+		if (ac == ARGS_EOLN)
 			break;
 
-		if (0 == v->sz % MULTI_STEP)
-			v->value = mandoc_realloc(v->value, 
-				(v->sz + MULTI_STEP) * sizeof(char *));
+		if (v->sz % MULTI_STEP == 0)
+			v->value = mandoc_reallocarray(v->value,
+			    v->sz + MULTI_STEP, sizeof(char *));
 
 		v->value[(int)v->sz] = mandoc_strdup(p);
 	}
-
-	return(1);
 }
 
-static int
-argv_single(struct mdoc *mdoc, int line, 
+static void
+argv_single(struct mdoc *mdoc, int line,
 		struct mdoc_argv *v, int *pos, char *buf)
 {
 	enum margserr	 ac;
 	char		*p;
 
 	ac = args(mdoc, line, pos, buf, ARGSFL_NONE, &p);
-	if (ARGS_ERROR == ac)
-		return(0);
-	if (ARGS_EOLN == ac)
-		return(1);
+	if (ac == ARGS_EOLN)
+		return;
 
 	v->sz = 1;
 	v->value = mandoc_malloc(sizeof(char *));
 	v->value[0] = mandoc_strdup(p);
-
-	return(1);
 }
diff --git a/usr/src/cmd/mandoc/mdoc_hash.c b/usr/src/cmd/mandoc/mdoc_hash.c
index 59a8d26a88..5e34fe8f58 100644
--- a/usr/src/cmd/mandoc/mdoc_hash.c
+++ b/usr/src/cmd/mandoc/mdoc_hash.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_hash.c,v 1.18 2011/07/24 18:15:14 kristaps Exp $ */
+/*	$Id: mdoc_hash.c,v 1.21 2014/08/10 23:54:41 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009 Kristaps Dzonsons 
  *
@@ -14,9 +14,7 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
@@ -28,11 +26,11 @@
 #include 
 
 #include "mdoc.h"
-#include "mandoc.h"
 #include "libmdoc.h"
 
 static	unsigned char	 table[27 * 12];
 
+
 /*
  * XXX - this hash has global scope, so if intended for use as a library
  * with multiple callers, it will need re-invocation protection.
@@ -77,7 +75,7 @@ mdoc_hash_find(const char *p)
 		major = 12 * (tolower((unsigned char)p[1]) - 97);
 	else if ('1' == p[1])
 		major = 12 * 26;
-	else 
+	else
 		return(MDOC_MAX);
 
 	if (p[2] && p[3])
diff --git a/usr/src/cmd/mandoc/mdoc_html.c b/usr/src/cmd/mandoc/mdoc_html.c
index a7aa722d94..fba7fb6fa4 100644
--- a/usr/src/cmd/mandoc/mdoc_html.c
+++ b/usr/src/cmd/mandoc/mdoc_html.c
@@ -1,6 +1,7 @@
-/*	$Id: mdoc_html.c,v 1.186 2013/12/24 20:45:27 schwarze Exp $ */
+/*	$Id: mdoc_html.c,v 1.226 2015/03/03 21:11:34 schwarze Exp $ */
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons 
+ * Copyright (c) 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,9 +15,7 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
@@ -27,16 +26,16 @@
 #include 
 #include 
 
-#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "mdoc.h"
 #include "out.h"
 #include "html.h"
-#include "mdoc.h"
 #include "main.h"
 
 #define	INDENT		 5
 
 #define	MDOC_ARGS	  const struct mdoc_meta *meta, \
-			  const struct mdoc_node *n, \
+			  struct mdoc_node *n, \
 			  struct html *h
 
 #ifndef MIN
@@ -52,11 +51,10 @@ static	void		  print_mdoc(MDOC_ARGS);
 static	void		  print_mdoc_head(MDOC_ARGS);
 static	void		  print_mdoc_node(MDOC_ARGS);
 static	void		  print_mdoc_nodelist(MDOC_ARGS);
-static	void	  	  synopsis_pre(struct html *, 
+static	void		  synopsis_pre(struct html *,
 				const struct mdoc_node *);
 
 static	void		  a2width(const char *, struct roffsu *);
-static	void		  a2offs(const char *, struct roffsu *);
 
 static	void		  mdoc_root_post(MDOC_ARGS);
 static	int		  mdoc_root_pre(MDOC_ARGS);
@@ -83,6 +81,8 @@ static	int		  mdoc_fl_pre(MDOC_ARGS);
 static	int		  mdoc_fn_pre(MDOC_ARGS);
 static	int		  mdoc_ft_pre(MDOC_ARGS);
 static	int		  mdoc_em_pre(MDOC_ARGS);
+static	void		  mdoc_eo_post(MDOC_ARGS);
+static	int		  mdoc_eo_pre(MDOC_ARGS);
 static	int		  mdoc_er_pre(MDOC_ARGS);
 static	int		  mdoc_ev_pre(MDOC_ARGS);
 static	int		  mdoc_ex_pre(MDOC_ARGS);
@@ -99,6 +99,7 @@ static	int		  mdoc_mt_pre(MDOC_ARGS);
 static	int		  mdoc_ms_pre(MDOC_ARGS);
 static	int		  mdoc_nd_pre(MDOC_ARGS);
 static	int		  mdoc_nm_pre(MDOC_ARGS);
+static	int		  mdoc_no_pre(MDOC_ARGS);
 static	int		  mdoc_ns_pre(MDOC_ARGS);
 static	int		  mdoc_pa_pre(MDOC_ARGS);
 static	void		  mdoc_pf_post(MDOC_ARGS);
@@ -108,6 +109,7 @@ static	int		  mdoc_quote_pre(MDOC_ARGS);
 static	int		  mdoc_rs_pre(MDOC_ARGS);
 static	int		  mdoc_rv_pre(MDOC_ARGS);
 static	int		  mdoc_sh_pre(MDOC_ARGS);
+static	int		  mdoc_skip_pre(MDOC_ARGS);
 static	int		  mdoc_sm_pre(MDOC_ARGS);
 static	int		  mdoc_sp_pre(MDOC_ARGS);
 static	int		  mdoc_ss_pre(MDOC_ARGS);
@@ -125,8 +127,8 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{NULL, NULL}, /* Dt */
 	{NULL, NULL}, /* Os */
 	{mdoc_sh_pre, NULL }, /* Sh */
-	{mdoc_ss_pre, NULL }, /* Ss */ 
-	{mdoc_pp_pre, NULL}, /* Pp */ 
+	{mdoc_ss_pre, NULL }, /* Ss */
+	{mdoc_pp_pre, NULL}, /* Pp */
 	{mdoc_d1_pre, NULL}, /* D1 */
 	{mdoc_d1_pre, NULL}, /* Dl */
 	{mdoc_bd_pre, NULL}, /* Bd */
@@ -134,32 +136,32 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{mdoc_bl_pre, NULL}, /* Bl */
 	{NULL, NULL}, /* El */
 	{mdoc_it_pre, NULL}, /* It */
-	{mdoc_ad_pre, NULL}, /* Ad */ 
+	{mdoc_ad_pre, NULL}, /* Ad */
 	{mdoc_an_pre, NULL}, /* An */
 	{mdoc_ar_pre, NULL}, /* Ar */
 	{mdoc_cd_pre, NULL}, /* Cd */
 	{mdoc_fl_pre, NULL}, /* Cm */
-	{mdoc_dv_pre, NULL}, /* Dv */ 
-	{mdoc_er_pre, NULL}, /* Er */ 
-	{mdoc_ev_pre, NULL}, /* Ev */ 
+	{mdoc_dv_pre, NULL}, /* Dv */
+	{mdoc_er_pre, NULL}, /* Er */
+	{mdoc_ev_pre, NULL}, /* Ev */
 	{mdoc_ex_pre, NULL}, /* Ex */
-	{mdoc_fa_pre, NULL}, /* Fa */ 
-	{mdoc_fd_pre, NULL}, /* Fd */ 
+	{mdoc_fa_pre, NULL}, /* Fa */
+	{mdoc_fd_pre, NULL}, /* Fd */
 	{mdoc_fl_pre, NULL}, /* Fl */
-	{mdoc_fn_pre, NULL}, /* Fn */ 
-	{mdoc_ft_pre, NULL}, /* Ft */ 
-	{mdoc_ic_pre, NULL}, /* Ic */ 
-	{mdoc_in_pre, NULL}, /* In */ 
+	{mdoc_fn_pre, NULL}, /* Fn */
+	{mdoc_ft_pre, NULL}, /* Ft */
+	{mdoc_ic_pre, NULL}, /* Ic */
+	{mdoc_in_pre, NULL}, /* In */
 	{mdoc_li_pre, NULL}, /* Li */
-	{mdoc_nd_pre, NULL}, /* Nd */ 
-	{mdoc_nm_pre, NULL}, /* Nm */ 
+	{mdoc_nd_pre, NULL}, /* Nd */
+	{mdoc_nm_pre, NULL}, /* Nm */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
-	{NULL, NULL}, /* Ot */
+	{mdoc_ft_pre, NULL}, /* Ot */
 	{mdoc_pa_pre, NULL}, /* Pa */
 	{mdoc_rv_pre, NULL}, /* Rv */
-	{NULL, NULL}, /* St */ 
+	{NULL, NULL}, /* St */
 	{mdoc_va_pre, NULL}, /* Va */
-	{mdoc_vt_pre, NULL}, /* Vt */ 
+	{mdoc_vt_pre, NULL}, /* Vt */
 	{mdoc_xr_pre, NULL}, /* Xr */
 	{mdoc__x_pre, mdoc__x_post}, /* %A */
 	{mdoc__x_pre, mdoc__x_post}, /* %B */
@@ -177,22 +179,22 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{mdoc_quote_pre, mdoc_quote_post}, /* Aq */
 	{NULL, NULL}, /* At */
 	{NULL, NULL}, /* Bc */
-	{mdoc_bf_pre, NULL}, /* Bf */ 
+	{mdoc_bf_pre, NULL}, /* Bf */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Bo */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Bq */
 	{mdoc_xx_pre, NULL}, /* Bsx */
 	{mdoc_bx_pre, NULL}, /* Bx */
-	{NULL, NULL}, /* Db */
+	{mdoc_skip_pre, NULL}, /* Db */
 	{NULL, NULL}, /* Dc */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Do */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Dq */
 	{NULL, NULL}, /* Ec */ /* FIXME: no space */
 	{NULL, NULL}, /* Ef */
-	{mdoc_em_pre, NULL}, /* Em */ 
-	{mdoc_quote_pre, mdoc_quote_post}, /* Eo */
+	{mdoc_em_pre, NULL}, /* Em */
+	{mdoc_eo_pre, mdoc_eo_post}, /* Eo */
 	{mdoc_xx_pre, NULL}, /* Fx */
 	{mdoc_ms_pre, NULL}, /* Ms */
-	{mdoc_igndelim_pre, NULL}, /* No */
+	{mdoc_no_pre, NULL}, /* No */
 	{mdoc_ns_pre, NULL}, /* Ns */
 	{mdoc_xx_pre, NULL}, /* Nx */
 	{mdoc_xx_pre, NULL}, /* Ox */
@@ -209,39 +211,40 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{NULL, NULL}, /* Sc */
 	{mdoc_quote_pre, mdoc_quote_post}, /* So */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Sq */
-	{mdoc_sm_pre, NULL}, /* Sm */ 
+	{mdoc_sm_pre, NULL}, /* Sm */
 	{mdoc_sx_pre, NULL}, /* Sx */
 	{mdoc_sy_pre, NULL}, /* Sy */
 	{NULL, NULL}, /* Tn */
 	{mdoc_xx_pre, NULL}, /* Ux */
 	{NULL, NULL}, /* Xc */
 	{NULL, NULL}, /* Xo */
-	{mdoc_fo_pre, mdoc_fo_post}, /* Fo */ 
-	{NULL, NULL}, /* Fc */ 
+	{mdoc_fo_pre, mdoc_fo_post}, /* Fo */
+	{NULL, NULL}, /* Fc */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Oo */
 	{NULL, NULL}, /* Oc */
 	{mdoc_bk_pre, mdoc_bk_post}, /* Bk */
 	{NULL, NULL}, /* Ek */
 	{mdoc_bt_pre, NULL}, /* Bt */
 	{NULL, NULL}, /* Hf */
-	{NULL, NULL}, /* Fr */
+	{mdoc_em_pre, NULL}, /* Fr */
 	{mdoc_ud_pre, NULL}, /* Ud */
 	{mdoc_lb_pre, NULL}, /* Lb */
-	{mdoc_pp_pre, NULL}, /* Lp */ 
-	{mdoc_lk_pre, NULL}, /* Lk */ 
-	{mdoc_mt_pre, NULL}, /* Mt */ 
-	{mdoc_quote_pre, mdoc_quote_post}, /* Brq */ 
-	{mdoc_quote_pre, mdoc_quote_post}, /* Bro */ 
-	{NULL, NULL}, /* Brc */ 
-	{mdoc__x_pre, mdoc__x_post}, /* %C */ 
-	{NULL, NULL}, /* Es */  /* TODO */
-	{NULL, NULL}, /* En */  /* TODO */
-	{mdoc_xx_pre, NULL}, /* Dx */ 
-	{mdoc__x_pre, mdoc__x_post}, /* %Q */ 
+	{mdoc_pp_pre, NULL}, /* Lp */
+	{mdoc_lk_pre, NULL}, /* Lk */
+	{mdoc_mt_pre, NULL}, /* Mt */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Brq */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Bro */
+	{NULL, NULL}, /* Brc */
+	{mdoc__x_pre, mdoc__x_post}, /* %C */
+	{mdoc_skip_pre, NULL}, /* Es */
+	{mdoc_quote_pre, mdoc_quote_post}, /* En */
+	{mdoc_xx_pre, NULL}, /* Dx */
+	{mdoc__x_pre, mdoc__x_post}, /* %Q */
 	{mdoc_sp_pre, NULL}, /* br */
-	{mdoc_sp_pre, NULL}, /* sp */ 
-	{mdoc__x_pre, mdoc__x_post}, /* %U */ 
-	{NULL, NULL}, /* Ta */ 
+	{mdoc_sp_pre, NULL}, /* sp */
+	{mdoc__x_pre, mdoc__x_post}, /* %U */
+	{NULL, NULL}, /* Ta */
+	{mdoc_skip_pre, NULL}, /* ll */
 };
 
 static	const char * const lists[LIST_MAX] = {
@@ -259,16 +262,16 @@ static	const char * const lists[LIST_MAX] = {
 	"list-tag"
 };
 
+
 void
 html_mdoc(void *arg, const struct mdoc *mdoc)
 {
 
-	print_mdoc(mdoc_meta(mdoc), mdoc_node(mdoc),
-			(struct html *)arg);
+	print_mdoc(mdoc_meta(mdoc), mdoc_node(mdoc)->child,
+	    (struct html *)arg);
 	putchar('\n');
 }
 
-
 /*
  * Calculate the scaling unit passed in a `-width' argument.  This uses
  * either a native scaling unit (e.g., 1i, 2m) or the string length of
@@ -278,13 +281,13 @@ static void
 a2width(const char *p, struct roffsu *su)
 {
 
-	if ( ! a2roffsu(p, su, SCALE_MAX)) {
-		su->unit = SCALE_BU;
+	if (a2roffsu(p, su, SCALE_MAX) < 2) {
+		su->unit = SCALE_EN;
 		su->scale = html_strlen(p);
-	}
+	} else if (su->scale < 0.0)
+		su->scale = 0.0;
 }
 
-
 /*
  * See the same function in mdoc_term.c for documentation.
  */
@@ -295,29 +298,29 @@ synopsis_pre(struct html *h, const struct mdoc_node *n)
 	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
 		return;
 
-	if (n->prev->tok == n->tok && 
-			MDOC_Fo != n->tok && 
-			MDOC_Ft != n->tok && 
-			MDOC_Fn != n->tok) {
+	if (n->prev->tok == n->tok &&
+	    MDOC_Fo != n->tok &&
+	    MDOC_Ft != n->tok &&
+	    MDOC_Fn != n->tok) {
 		print_otag(h, TAG_BR, 0, NULL);
 		return;
 	}
 
 	switch (n->prev->tok) {
-	case (MDOC_Fd):
+	case MDOC_Fd:
 		/* FALLTHROUGH */
-	case (MDOC_Fn):
+	case MDOC_Fn:
 		/* FALLTHROUGH */
-	case (MDOC_Fo):
+	case MDOC_Fo:
 		/* FALLTHROUGH */
-	case (MDOC_In):
+	case MDOC_In:
 		/* FALLTHROUGH */
-	case (MDOC_Vt):
-		print_otag(h, TAG_P, 0, NULL);
+	case MDOC_Vt:
+		print_paragraph(h);
 		break;
-	case (MDOC_Ft):
+	case MDOC_Ft:
 		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
-			print_otag(h, TAG_P, 0, NULL);
+			print_paragraph(h);
 			break;
 		}
 		/* FALLTHROUGH */
@@ -327,29 +330,6 @@ synopsis_pre(struct html *h, const struct mdoc_node *n)
 	}
 }
 
-
-/*
- * Calculate the scaling unit passed in an `-offset' argument.  This
- * uses either a native scaling unit (e.g., 1i, 2m), one of a set of
- * predefined strings (indent, etc.), or the string length of the value.
- */
-static void
-a2offs(const char *p, struct roffsu *su)
-{
-
-	/* FIXME: "right"? */
-
-	if (0 == strcmp(p, "left"))
-		SCALE_HS_INIT(su, 0);
-	else if (0 == strcmp(p, "indent"))
-		SCALE_HS_INIT(su, INDENT);
-	else if (0 == strcmp(p, "indent-two"))
-		SCALE_HS_INIT(su, INDENT * 2);
-	else if ( ! a2roffsu(p, su, SCALE_MAX))
-		SCALE_HS_INIT(su, html_strlen(p));
-}
-
-
 static void
 print_mdoc(MDOC_ARGS)
 {
@@ -366,23 +346,22 @@ print_mdoc(MDOC_ARGS)
 		print_tagq(h, tt);
 		print_otag(h, TAG_BODY, 0, NULL);
 		print_otag(h, TAG_DIV, 1, &tag);
-	} else 
+	} else
 		t = print_otag(h, TAG_DIV, 1, &tag);
 
 	print_mdoc_nodelist(meta, n, h);
 	print_tagq(h, t);
 }
 
-
-/* ARGSUSED */
 static void
 print_mdoc_head(MDOC_ARGS)
 {
 
 	print_gen_head(h);
 	bufinit(h);
-	bufcat_fmt(h, "%s(%s)", meta->title, meta->msec);
-
+	bufcat(h, meta->title);
+	if (meta->msec)
+		bufcat_fmt(h, "(%s)", meta->msec);
 	if (meta->arch)
 		bufcat_fmt(h, " (%s)", meta->arch);
 
@@ -390,17 +369,16 @@ print_mdoc_head(MDOC_ARGS)
 	print_text(h, h->buf);
 }
 
-
 static void
 print_mdoc_nodelist(MDOC_ARGS)
 {
 
-	print_mdoc_node(meta, n, h);
-	if (n->next)
-		print_mdoc_nodelist(meta, n->next, h);
+	while (n != NULL) {
+		print_mdoc_node(meta, n, h);
+		n = n->next;
+	}
 }
 
-
 static void
 print_mdoc_node(MDOC_ARGS)
 {
@@ -409,12 +387,13 @@ print_mdoc_node(MDOC_ARGS)
 
 	child = 1;
 	t = h->tags.head;
+	n->flags &= ~MDOC_ENDED;
 
 	switch (n->type) {
-	case (MDOC_ROOT):
+	case MDOC_ROOT:
 		child = mdoc_root_pre(meta, n, h);
 		break;
-	case (MDOC_TEXT):
+	case MDOC_TEXT:
 		/* No tables in this mode... */
 		assert(NULL == h->tblt);
 
@@ -431,10 +410,12 @@ print_mdoc_node(MDOC_ARGS)
 		if (MDOC_DELIMO & n->flags)
 			h->flags |= HTML_NOSPACE;
 		return;
-	case (MDOC_EQN):
+	case MDOC_EQN:
+		if (n->flags & MDOC_LINE)
+			putchar('\n');
 		print_eqn(h, n->eqn);
 		break;
-	case (MDOC_TBL):
+	case MDOC_TBL:
 		/*
 		 * This will take care of initialising all of the table
 		 * state data for the first table, then tearing it down
@@ -448,23 +429,19 @@ print_mdoc_node(MDOC_ARGS)
 		 * the "meta" table state.  This will be reopened on the
 		 * next table element.
 		 */
-		if (h->tblt) {
+		if (h->tblt != NULL) {
 			print_tblclose(h);
 			t = h->tags.head;
 		}
-
-		assert(NULL == h->tblt);
-		if (mdocs[n->tok].pre && ENDBODY_NOT == n->end)
+		assert(h->tblt == NULL);
+		if (mdocs[n->tok].pre && (n->end == ENDBODY_NOT || n->child))
 			child = (*mdocs[n->tok].pre)(meta, n, h);
 		break;
 	}
 
-	if (HTML_KEEP & h->flags) {
-		if (n->prev ? (n->prev->lastline != n->line) :
-		    (n->parent && n->parent->line != n->line)) {
-			h->flags &= ~HTML_KEEP;
-			h->flags |= HTML_PREKEEP;
-		}
+	if (h->flags & HTML_KEEP && n->flags & MDOC_LINE) {
+		h->flags &= ~HTML_KEEP;
+		h->flags |= HTML_PREKEEP;
 	}
 
 	if (child && n->child)
@@ -473,113 +450,110 @@ print_mdoc_node(MDOC_ARGS)
 	print_stagq(h, t);
 
 	switch (n->type) {
-	case (MDOC_ROOT):
+	case MDOC_ROOT:
 		mdoc_root_post(meta, n, h);
 		break;
-	case (MDOC_EQN):
+	case MDOC_EQN:
 		break;
 	default:
-		if (mdocs[n->tok].post && ENDBODY_NOT == n->end)
-			(*mdocs[n->tok].post)(meta, n, h);
+		if ( ! mdocs[n->tok].post || n->flags & MDOC_ENDED)
+			break;
+		(*mdocs[n->tok].post)(meta, n, h);
+		if (n->end != ENDBODY_NOT)
+			n->body->flags |= MDOC_ENDED;
+		if (n->end == ENDBODY_NOSPACE)
+			h->flags |= HTML_NOSPACE;
 		break;
 	}
 }
 
-/* ARGSUSED */
 static void
 mdoc_root_post(MDOC_ARGS)
 {
-	struct htmlpair	 tag[3];
+	struct htmlpair	 tag;
 	struct tag	*t, *tt;
 
-	PAIR_SUMMARY_INIT(&tag[0], "Document Footer");
-	PAIR_CLASS_INIT(&tag[1], "foot");
-	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
-	t = print_otag(h, TAG_TABLE, 3, tag);
-	PAIR_INIT(&tag[0], ATTR_WIDTH, "50%");
-	print_otag(h, TAG_COL, 1, tag);
-	print_otag(h, TAG_COL, 1, tag);
+	PAIR_CLASS_INIT(&tag, "foot");
+	t = print_otag(h, TAG_TABLE, 1, &tag);
 
 	print_otag(h, TAG_TBODY, 0, NULL);
 
 	tt = print_otag(h, TAG_TR, 0, NULL);
 
-	PAIR_CLASS_INIT(&tag[0], "foot-date");
-	print_otag(h, TAG_TD, 1, tag);
+	PAIR_CLASS_INIT(&tag, "foot-date");
+	print_otag(h, TAG_TD, 1, &tag);
 	print_text(h, meta->date);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag[0], "foot-os");
-	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
-	print_otag(h, TAG_TD, 2, tag);
+	PAIR_CLASS_INIT(&tag, "foot-os");
+	print_otag(h, TAG_TD, 1, &tag);
 	print_text(h, meta->os);
 	print_tagq(h, t);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_root_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[3];
+	struct htmlpair	 tag;
 	struct tag	*t, *tt;
-	char		 b[BUFSIZ], title[BUFSIZ];
-
-	strlcpy(b, meta->vol, BUFSIZ);
+	char		*volume, *title;
 
-	if (meta->arch) {
-		strlcat(b, " (", BUFSIZ);
-		strlcat(b, meta->arch, BUFSIZ);
-		strlcat(b, ")", BUFSIZ);
-	}
+	if (NULL == meta->arch)
+		volume = mandoc_strdup(meta->vol);
+	else
+		mandoc_asprintf(&volume, "%s (%s)",
+		    meta->vol, meta->arch);
 
-	snprintf(title, BUFSIZ - 1, "%s(%s)", meta->title, meta->msec);
+	if (NULL == meta->msec)
+		title = mandoc_strdup(meta->title);
+	else
+		mandoc_asprintf(&title, "%s(%s)",
+		    meta->title, meta->msec);
 
-	PAIR_SUMMARY_INIT(&tag[0], "Document Header");
-	PAIR_CLASS_INIT(&tag[1], "head");
-	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
-	t = print_otag(h, TAG_TABLE, 3, tag);
-	PAIR_INIT(&tag[0], ATTR_WIDTH, "30%");
-	print_otag(h, TAG_COL, 1, tag);
-	print_otag(h, TAG_COL, 1, tag);
-	print_otag(h, TAG_COL, 1, tag);
+	PAIR_CLASS_INIT(&tag, "head");
+	t = print_otag(h, TAG_TABLE, 1, &tag);
 
 	print_otag(h, TAG_TBODY, 0, NULL);
 
 	tt = print_otag(h, TAG_TR, 0, NULL);
 
-	PAIR_CLASS_INIT(&tag[0], "head-ltitle");
-	print_otag(h, TAG_TD, 1, tag);
+	PAIR_CLASS_INIT(&tag, "head-ltitle");
+	print_otag(h, TAG_TD, 1, &tag);
 	print_text(h, title);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag[0], "head-vol");
-	PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
-	print_otag(h, TAG_TD, 2, tag);
-	print_text(h, b);
+	PAIR_CLASS_INIT(&tag, "head-vol");
+	print_otag(h, TAG_TD, 1, &tag);
+	print_text(h, volume);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag[0], "head-rtitle");
-	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
-	print_otag(h, TAG_TD, 2, tag);
+	PAIR_CLASS_INIT(&tag, "head-rtitle");
+	print_otag(h, TAG_TD, 1, &tag);
 	print_text(h, title);
 	print_tagq(h, t);
+
+	free(title);
+	free(volume);
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_sh_pre(MDOC_ARGS)
 {
 	struct htmlpair	 tag;
 
-	if (MDOC_BLOCK == n->type) {
+	switch (n->type) {
+	case MDOC_BLOCK:
 		PAIR_CLASS_INIT(&tag, "section");
 		print_otag(h, TAG_DIV, 1, &tag);
 		return(1);
-	} else if (MDOC_BODY == n->type)
+	case MDOC_BODY:
+		if (n->sec == SEC_AUTHORS)
+			h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
 		return(1);
+	default:
+		break;
+	}
 
 	bufinit(h);
 	bufcat(h, "x");
@@ -599,7 +573,6 @@ mdoc_sh_pre(MDOC_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 mdoc_ss_pre(MDOC_ARGS)
 {
@@ -630,8 +603,6 @@ mdoc_ss_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_fl_pre(MDOC_ARGS)
 {
@@ -647,16 +618,15 @@ mdoc_fl_pre(MDOC_ARGS)
 
 	print_text(h, "\\-");
 
-	if (n->child)
-		h->flags |= HTML_NOSPACE;
-	else if (n->next && n->next->line == n->line)
+	if ( ! (n->nchild == 0 &&
+	    (n->next == NULL ||
+	     n->next->type == MDOC_TEXT ||
+	     n->next->flags & MDOC_LINE)))
 		h->flags |= HTML_NOSPACE;
 
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_nd_pre(MDOC_ARGS)
 {
@@ -673,7 +643,6 @@ mdoc_nd_pre(MDOC_ARGS)
 	return(1);
 }
 
-
 static int
 mdoc_nm_pre(MDOC_ARGS)
 {
@@ -682,19 +651,19 @@ mdoc_nm_pre(MDOC_ARGS)
 	int		 len;
 
 	switch (n->type) {
-	case (MDOC_ELEM):
+	case MDOC_ELEM:
 		synopsis_pre(h, n);
 		PAIR_CLASS_INIT(&tag, "name");
 		print_otag(h, TAG_B, 1, &tag);
 		if (NULL == n->child && meta->name)
 			print_text(h, meta->name);
 		return(1);
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		print_otag(h, TAG_TD, 0, NULL);
 		if (NULL == n->child && meta->name)
 			print_text(h, meta->name);
 		return(1);
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		print_otag(h, TAG_TD, 0, NULL);
 		return(1);
 	default:
@@ -712,7 +681,7 @@ mdoc_nm_pre(MDOC_ARGS)
 	if (0 == len && meta->name)
 		len = html_strlen(meta->name);
 
-	SCALE_HS_INIT(&su, (double)len);
+	SCALE_HS_INIT(&su, len);
 	bufinit(h);
 	bufcat_su(h, "width", &su);
 	PAIR_STYLE_INIT(&tag, h);
@@ -723,8 +692,6 @@ mdoc_nm_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_xr_pre(MDOC_ARGS)
 {
@@ -736,9 +703,9 @@ mdoc_xr_pre(MDOC_ARGS)
 	PAIR_CLASS_INIT(&tag[0], "link-man");
 
 	if (h->base_man) {
-		buffmt_man(h, n->child->string, 
-				n->child->next ? 
-				n->child->next->string : NULL);
+		buffmt_man(h, n->child->string,
+		    n->child->next ?
+		    n->child->next->string : NULL);
 		PAIR_HREF_INIT(&tag[1], h->buf);
 		print_otag(h, TAG_A, 2, tag);
 	} else
@@ -759,8 +726,6 @@ mdoc_xr_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ns_pre(MDOC_ARGS)
 {
@@ -770,8 +735,6 @@ mdoc_ns_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ar_pre(MDOC_ARGS)
 {
@@ -782,8 +745,6 @@ mdoc_ar_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_xx_pre(MDOC_ARGS)
 {
@@ -792,22 +753,22 @@ mdoc_xx_pre(MDOC_ARGS)
 	int		 flags;
 
 	switch (n->tok) {
-	case (MDOC_Bsx):
+	case MDOC_Bsx:
 		pp = "BSD/OS";
 		break;
-	case (MDOC_Dx):
+	case MDOC_Dx:
 		pp = "DragonFly";
 		break;
-	case (MDOC_Fx):
+	case MDOC_Fx:
 		pp = "FreeBSD";
 		break;
-	case (MDOC_Nx):
+	case MDOC_Nx:
 		pp = "NetBSD";
 		break;
-	case (MDOC_Ox):
+	case MDOC_Ox:
 		pp = "OpenBSD";
 		break;
-	case (MDOC_Ux):
+	case MDOC_Ux:
 		pp = "UNIX";
 		break;
 	default:
@@ -827,8 +788,6 @@ mdoc_xx_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_bx_pre(MDOC_ARGS)
 {
@@ -856,7 +815,6 @@ mdoc_bx_pre(MDOC_ARGS)
 	return(0);
 }
 
-/* ARGSUSED */
 static int
 mdoc_it_pre(MDOC_ARGS)
 {
@@ -880,25 +838,25 @@ mdoc_it_pre(MDOC_ARGS)
 
 	if (MDOC_HEAD == n->type) {
 		switch (type) {
-		case(LIST_bullet):
+		case LIST_bullet:
 			/* FALLTHROUGH */
-		case(LIST_dash):
+		case LIST_dash:
 			/* FALLTHROUGH */
-		case(LIST_item):
+		case LIST_item:
 			/* FALLTHROUGH */
-		case(LIST_hyphen):
+		case LIST_hyphen:
 			/* FALLTHROUGH */
-		case(LIST_enum):
+		case LIST_enum:
 			return(0);
-		case(LIST_diag):
+		case LIST_diag:
 			/* FALLTHROUGH */
-		case(LIST_hang):
+		case LIST_hang:
 			/* FALLTHROUGH */
-		case(LIST_inset):
+		case LIST_inset:
 			/* FALLTHROUGH */
-		case(LIST_ohang):
+		case LIST_ohang:
 			/* FALLTHROUGH */
-		case(LIST_tag):
+		case LIST_tag:
 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
 			bufcat_su(h, "margin-top", &su);
 			PAIR_STYLE_INIT(&tag[1], h);
@@ -908,36 +866,36 @@ mdoc_it_pre(MDOC_ARGS)
 			PAIR_CLASS_INIT(&tag[0], "diag");
 			print_otag(h, TAG_B, 1, tag);
 			break;
-		case(LIST_column):
+		case LIST_column:
 			break;
 		default:
 			break;
 		}
 	} else if (MDOC_BODY == n->type) {
 		switch (type) {
-		case(LIST_bullet):
+		case LIST_bullet:
 			/* FALLTHROUGH */
-		case(LIST_hyphen):
+		case LIST_hyphen:
 			/* FALLTHROUGH */
-		case(LIST_dash):
+		case LIST_dash:
 			/* FALLTHROUGH */
-		case(LIST_enum):
+		case LIST_enum:
 			/* FALLTHROUGH */
-		case(LIST_item):
+		case LIST_item:
 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
 			bufcat_su(h, "margin-top", &su);
 			PAIR_STYLE_INIT(&tag[1], h);
 			print_otag(h, TAG_LI, 2, tag);
 			break;
-		case(LIST_diag):
+		case LIST_diag:
 			/* FALLTHROUGH */
-		case(LIST_hang):
+		case LIST_hang:
 			/* FALLTHROUGH */
-		case(LIST_inset):
+		case LIST_inset:
 			/* FALLTHROUGH */
-		case(LIST_ohang):
+		case LIST_ohang:
 			/* FALLTHROUGH */
-		case(LIST_tag):
+		case LIST_tag:
 			if (NULL == bl->norm->Bl.width) {
 				print_otag(h, TAG_DD, 1, tag);
 				break;
@@ -947,7 +905,7 @@ mdoc_it_pre(MDOC_ARGS)
 			PAIR_STYLE_INIT(&tag[1], h);
 			print_otag(h, TAG_DD, 2, tag);
 			break;
-		case(LIST_column):
+		case LIST_column:
 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
 			bufcat_su(h, "margin-top", &su);
 			PAIR_STYLE_INIT(&tag[1], h);
@@ -958,7 +916,7 @@ mdoc_it_pre(MDOC_ARGS)
 		}
 	} else {
 		switch (type) {
-		case (LIST_column):
+		case LIST_column:
 			print_otag(h, TAG_TR, 1, tag);
 			break;
 		default:
@@ -969,7 +927,6 @@ mdoc_it_pre(MDOC_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 mdoc_bl_pre(MDOC_ARGS)
 {
@@ -1016,42 +973,42 @@ mdoc_bl_pre(MDOC_ARGS)
 	PAIR_STYLE_INIT(&tag[0], h);
 
 	assert(lists[n->norm->Bl.type]);
-	strlcpy(buf, "list ", BUFSIZ);
-	strlcat(buf, lists[n->norm->Bl.type], BUFSIZ);
+	(void)strlcpy(buf, "list ", BUFSIZ);
+	(void)strlcat(buf, lists[n->norm->Bl.type], BUFSIZ);
 	PAIR_INIT(&tag[1], ATTR_CLASS, buf);
 
 	/* Set the block's left-hand margin. */
 
 	if (n->norm->Bl.offs) {
-		a2offs(n->norm->Bl.offs, &su);
+		a2width(n->norm->Bl.offs, &su);
 		bufcat_su(h, "margin-left", &su);
 	}
 
 	switch (n->norm->Bl.type) {
-	case(LIST_bullet):
+	case LIST_bullet:
 		/* FALLTHROUGH */
-	case(LIST_dash):
+	case LIST_dash:
 		/* FALLTHROUGH */
-	case(LIST_hyphen):
+	case LIST_hyphen:
 		/* FALLTHROUGH */
-	case(LIST_item):
+	case LIST_item:
 		print_otag(h, TAG_UL, 2, tag);
 		break;
-	case(LIST_enum):
+	case LIST_enum:
 		print_otag(h, TAG_OL, 2, tag);
 		break;
-	case(LIST_diag):
+	case LIST_diag:
 		/* FALLTHROUGH */
-	case(LIST_hang):
+	case LIST_hang:
 		/* FALLTHROUGH */
-	case(LIST_inset):
+	case LIST_inset:
 		/* FALLTHROUGH */
-	case(LIST_ohang):
+	case LIST_ohang:
 		/* FALLTHROUGH */
-	case(LIST_tag):
+	case LIST_tag:
 		print_otag(h, TAG_DL, 2, tag);
 		break;
-	case(LIST_column):
+	case LIST_column:
 		print_otag(h, TAG_TABLE, 2, tag);
 		break;
 	default:
@@ -1062,7 +1019,6 @@ mdoc_bl_pre(MDOC_ARGS)
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 mdoc_ex_pre(MDOC_ARGS)
 {
@@ -1095,16 +1051,14 @@ mdoc_ex_pre(MDOC_ARGS)
 	}
 
 	if (nchild > 1)
-		print_text(h, "utilities exit");
+		print_text(h, "utilities exit\\~0");
 	else
-		print_text(h, "utility exits");
+		print_text(h, "utility exits\\~0");
 
-       	print_text(h, "0 on success, and >0 if an error occurs.");
+	print_text(h, "on success, and\\~>0 if an error occurs.");
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_em_pre(MDOC_ARGS)
 {
@@ -1115,8 +1069,6 @@ mdoc_em_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_d1_pre(MDOC_ARGS)
 {
@@ -1141,13 +1093,11 @@ mdoc_d1_pre(MDOC_ARGS)
 	if (MDOC_Dl == n->tok) {
 		PAIR_CLASS_INIT(&tag[0], "lit");
 		print_otag(h, TAG_CODE, 1, tag);
-	} 
+	}
 
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_sx_pre(MDOC_ARGS)
 {
@@ -1170,14 +1120,12 @@ mdoc_sx_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_bd_pre(MDOC_ARGS)
 {
-	struct htmlpair	 	 tag[2];
-	int		 	 comp, sv;
-	const struct mdoc_node	*nn;
+	struct htmlpair		 tag[2];
+	int			 comp, sv;
+	struct mdoc_node	*nn;
 	struct roffsu		 su;
 
 	if (MDOC_HEAD == n->type)
@@ -1194,20 +1142,28 @@ mdoc_bd_pre(MDOC_ARGS)
 				break;
 		}
 		if ( ! comp)
-			print_otag(h, TAG_P, 0, NULL);
+			print_paragraph(h);
 		return(1);
 	}
 
-	SCALE_HS_INIT(&su, 0);
-	if (n->norm->Bd.offs)
-		a2offs(n->norm->Bd.offs, &su);
-	
+	/* Handle the -offset argument. */
+
+	if (n->norm->Bd.offs == NULL ||
+	    ! strcmp(n->norm->Bd.offs, "left"))
+		SCALE_HS_INIT(&su, 0);
+	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
+		SCALE_HS_INIT(&su, INDENT);
+	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
+		SCALE_HS_INIT(&su, INDENT * 2);
+	else
+		a2width(n->norm->Bd.offs, &su);
+
 	bufinit(h);
 	bufcat_su(h, "margin-left", &su);
 	PAIR_STYLE_INIT(&tag[0], h);
 
-	if (DISP_unfilled != n->norm->Bd.type && 
-			DISP_literal != n->norm->Bd.type) {
+	if (DISP_unfilled != n->norm->Bd.type &&
+	    DISP_literal != n->norm->Bd.type) {
 		PAIR_CLASS_INIT(&tag[1], "display");
 		print_otag(h, TAG_DIV, 2, tag);
 		return(1);
@@ -1230,26 +1186,27 @@ mdoc_bd_pre(MDOC_ARGS)
 		 * anyway, so don't sweat it.
 		 */
 		switch (nn->tok) {
-		case (MDOC_Sm):
+		case MDOC_Sm:
 			/* FALLTHROUGH */
-		case (MDOC_br):
+		case MDOC_br:
 			/* FALLTHROUGH */
-		case (MDOC_sp):
+		case MDOC_sp:
 			/* FALLTHROUGH */
-		case (MDOC_Bl):
+		case MDOC_Bl:
 			/* FALLTHROUGH */
-		case (MDOC_D1):
+		case MDOC_D1:
 			/* FALLTHROUGH */
-		case (MDOC_Dl):
+		case MDOC_Dl:
 			/* FALLTHROUGH */
-		case (MDOC_Lp):
+		case MDOC_Lp:
 			/* FALLTHROUGH */
-		case (MDOC_Pp):
+		case MDOC_Pp:
 			continue;
 		default:
 			break;
 		}
-		if (nn->next && nn->next->line == nn->line)
+		if (h->flags & HTML_NONEWLINE ||
+		    (nn->next && ! (nn->next->flags & MDOC_LINE)))
 			continue;
 		else if (nn->next)
 			print_text(h, "\n");
@@ -1263,8 +1220,6 @@ mdoc_bd_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_pa_pre(MDOC_ARGS)
 {
@@ -1275,8 +1230,6 @@ mdoc_pa_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ad_pre(MDOC_ARGS)
 {
@@ -1287,22 +1240,33 @@ mdoc_ad_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_an_pre(MDOC_ARGS)
 {
 	struct htmlpair	tag;
 
-	/* TODO: -split and -nosplit (see termp_an_pre()). */
+	if (n->norm->An.auth == AUTH_split) {
+		h->flags &= ~HTML_NOSPLIT;
+		h->flags |= HTML_SPLIT;
+		return(0);
+	}
+	if (n->norm->An.auth == AUTH_nosplit) {
+		h->flags &= ~HTML_SPLIT;
+		h->flags |= HTML_NOSPLIT;
+		return(0);
+	}
+
+	if (h->flags & HTML_SPLIT)
+		print_otag(h, TAG_BR, 0, NULL);
+
+	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
+		h->flags |= HTML_SPLIT;
 
 	PAIR_CLASS_INIT(&tag, "author");
 	print_otag(h, TAG_SPAN, 1, &tag);
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_cd_pre(MDOC_ARGS)
 {
@@ -1314,8 +1278,6 @@ mdoc_cd_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_dv_pre(MDOC_ARGS)
 {
@@ -1326,8 +1288,6 @@ mdoc_dv_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ev_pre(MDOC_ARGS)
 {
@@ -1338,8 +1298,6 @@ mdoc_ev_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_er_pre(MDOC_ARGS)
 {
@@ -1350,8 +1308,6 @@ mdoc_er_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_fa_pre(MDOC_ARGS)
 {
@@ -1383,8 +1339,6 @@ mdoc_fa_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_fd_pre(MDOC_ARGS)
 {
@@ -1413,21 +1367,30 @@ mdoc_fd_pre(MDOC_ARGS)
 
 	if (NULL != (n = n->next)) {
 		assert(MDOC_TEXT == n->type);
-		strlcpy(buf, '<' == *n->string || '"' == *n->string ? 
-				n->string + 1 : n->string, BUFSIZ);
+
+		/*
+		 * XXX This is broken and not easy to fix.
+		 * When using -Oincludes, truncation may occur.
+		 * Dynamic allocation wouldn't help because
+		 * passing long strings to buffmt_includes()
+		 * does not work either.
+		 */
+
+		strlcpy(buf, '<' == *n->string || '"' == *n->string ?
+		    n->string + 1 : n->string, BUFSIZ);
 
 		sz = strlen(buf);
 		if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1]))
 			buf[sz - 1] = '\0';
 
 		PAIR_CLASS_INIT(&tag[0], "link-includes");
-		
+
 		i = 1;
 		if (h->base_includes) {
 			buffmt_includes(h, buf);
 			PAIR_HREF_INIT(&tag[i], h->buf);
 			i++;
-		} 
+		}
 
 		t = print_otag(h, TAG_A, i, tag);
 		print_text(h, n->string);
@@ -1444,8 +1407,6 @@ mdoc_fd_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_vt_pre(MDOC_ARGS)
 {
@@ -1464,8 +1425,6 @@ mdoc_vt_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ft_pre(MDOC_ARGS)
 {
@@ -1477,8 +1436,6 @@ mdoc_ft_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_fn_pre(MDOC_ARGS)
 {
@@ -1499,7 +1456,7 @@ mdoc_fn_pre(MDOC_ARGS)
 	if (NULL != ep) {
 		PAIR_CLASS_INIT(&tag[0], "ftype");
 		t = print_otag(h, TAG_I, 1, tag);
-	
+
 		while (ep) {
 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
 			(void)memcpy(nbuf, sp, (size_t)sz);
@@ -1531,10 +1488,8 @@ mdoc_fn_pre(MDOC_ARGS)
 
 	t = print_otag(h, TAG_B, 1, tag);
 
-	if (sp) {
-		strlcpy(nbuf, sp, BUFSIZ);
-		print_text(h, nbuf);
-	}
+	if (sp)
+		print_text(h, sp);
 
 	print_tagq(h, t);
 
@@ -1571,43 +1526,38 @@ mdoc_fn_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_sm_pre(MDOC_ARGS)
 {
 
-	assert(n->child && MDOC_TEXT == n->child->type);
-	if (0 == strcmp("on", n->child->string)) {
-		/* 
-		 * FIXME: no p->col to check.  Thus, if we have
-		 *  .Bd -literal
-		 *  .Sm off
-		 *  1 2
-		 *  .Sm on
-		 *  3
-		 *  .Ed
-		 * the "3" is preceded by a space.
-		 */
-		h->flags &= ~HTML_NOSPACE;
+	if (NULL == n->child)
+		h->flags ^= HTML_NONOSPACE;
+	else if (0 == strcmp("on", n->child->string))
 		h->flags &= ~HTML_NONOSPACE;
-	} else
+	else
 		h->flags |= HTML_NONOSPACE;
 
+	if ( ! (HTML_NONOSPACE & h->flags))
+		h->flags &= ~HTML_NOSPACE;
+
 	return(0);
 }
 
-/* ARGSUSED */
 static int
-mdoc_pp_pre(MDOC_ARGS)
+mdoc_skip_pre(MDOC_ARGS)
 {
 
-	print_otag(h, TAG_P, 0, NULL);
 	return(0);
+}
+
+static int
+mdoc_pp_pre(MDOC_ARGS)
+{
 
+	print_paragraph(h);
+	return(0);
 }
 
-/* ARGSUSED */
 static int
 mdoc_sp_pre(MDOC_ARGS)
 {
@@ -1617,11 +1567,14 @@ mdoc_sp_pre(MDOC_ARGS)
 	SCALE_VS_INIT(&su, 1);
 
 	if (MDOC_sp == n->tok) {
-		if (NULL != (n = n->child))
+		if (NULL != (n = n->child)) {
 			if ( ! a2roffsu(n->string, &su, SCALE_VS))
-				SCALE_VS_INIT(&su, atoi(n->string));
+				su.scale = 1.0;
+			else if (su.scale < 0.0)
+				su.scale = 0.0;
+		}
 	} else
-		su.scale = 0;
+		su.scale = 0.0;
 
 	bufinit(h);
 	bufcat_su(h, "height", &su);
@@ -1635,7 +1588,6 @@ mdoc_sp_pre(MDOC_ARGS)
 
 }
 
-/* ARGSUSED */
 static int
 mdoc_lk_pre(MDOC_ARGS)
 {
@@ -1660,8 +1612,6 @@ mdoc_lk_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_mt_pre(MDOC_ARGS)
 {
@@ -1682,12 +1632,10 @@ mdoc_mt_pre(MDOC_ARGS)
 		print_text(h, n->string);
 		print_tagq(h, t);
 	}
-	
+
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_fo_pre(MDOC_ARGS)
 {
@@ -1716,8 +1664,6 @@ mdoc_fo_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static void
 mdoc_fo_post(MDOC_ARGS)
 {
@@ -1730,8 +1676,6 @@ mdoc_fo_post(MDOC_ARGS)
 	print_text(h, ";");
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_in_pre(MDOC_ARGS)
 {
@@ -1767,7 +1711,7 @@ mdoc_in_pre(MDOC_ARGS)
 			buffmt_includes(h, n->string);
 			PAIR_HREF_INIT(&tag[i], h->buf);
 			i++;
-		} 
+		}
 
 		t = print_otag(h, TAG_A, i, tag);
 		print_text(h, n->string);
@@ -1787,8 +1731,6 @@ mdoc_in_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ic_pre(MDOC_ARGS)
 {
@@ -1799,8 +1741,6 @@ mdoc_ic_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_rv_pre(MDOC_ARGS)
 {
@@ -1813,46 +1753,50 @@ mdoc_rv_pre(MDOC_ARGS)
 
 	PAIR_CLASS_INIT(&tag, "fname");
 
-	print_text(h, "The");
-
 	nchild = n->nchild;
-	for (n = n->child; n; n = n->next) {
-		assert(MDOC_TEXT == n->type);
-
-		t = print_otag(h, TAG_B, 1, &tag);
-		print_text(h, n->string);
-		print_tagq(h, t);
+	if (nchild > 0) {
+		print_text(h, "The");
 
-		h->flags |= HTML_NOSPACE;
-		print_text(h, "()");
+		for (n = n->child; n; n = n->next) {
+			t = print_otag(h, TAG_B, 1, &tag);
+			print_text(h, n->string);
+			print_tagq(h, t);
 
-		if (nchild > 2 && n->next) {
 			h->flags |= HTML_NOSPACE;
-			print_text(h, ",");
+			print_text(h, "()");
+
+			if (n->next == NULL)
+				continue;
+
+			if (nchild > 2) {
+				h->flags |= HTML_NOSPACE;
+				print_text(h, ",");
+			}
+			if (n->next->next == NULL)
+				print_text(h, "and");
 		}
 
-		if (n->next && NULL == n->next->next)
-			print_text(h, "and");
-	}
+		if (nchild > 1)
+			print_text(h, "functions return");
+		else
+			print_text(h, "function returns");
 
-	if (nchild > 1)
-		print_text(h, "functions return");
-	else
-		print_text(h, "function returns");
+		print_text(h, "the value\\~0 if successful;");
+	} else
+		print_text(h, "Upon successful completion,"
+                    " the value\\~0 is returned;");
 
-       	print_text(h, "the value 0 if successful; otherwise the value "
-			"-1 is returned and the global variable");
+	print_text(h, "otherwise the value\\~\\-1 is returned"
+	   " and the global variable");
 
 	PAIR_CLASS_INIT(&tag, "var");
 	t = print_otag(h, TAG_B, 1, &tag);
 	print_text(h, "errno");
 	print_tagq(h, t);
-       	print_text(h, "is set to indicate the error.");
+	print_text(h, "is set to indicate the error.");
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_va_pre(MDOC_ARGS)
 {
@@ -1863,20 +1807,16 @@ mdoc_va_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ap_pre(MDOC_ARGS)
 {
-	
+
 	h->flags |= HTML_NOSPACE;
 	print_text(h, "\\(aq");
 	h->flags |= HTML_NOSPACE;
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_bf_pre(MDOC_ARGS)
 {
@@ -1888,18 +1828,18 @@ mdoc_bf_pre(MDOC_ARGS)
 	else if (MDOC_BODY != n->type)
 		return(1);
 
-	if (FONT_Em == n->norm->Bf.font) 
+	if (FONT_Em == n->norm->Bf.font)
 		PAIR_CLASS_INIT(&tag[0], "emph");
-	else if (FONT_Sy == n->norm->Bf.font) 
+	else if (FONT_Sy == n->norm->Bf.font)
 		PAIR_CLASS_INIT(&tag[0], "symb");
-	else if (FONT_Li == n->norm->Bf.font) 
+	else if (FONT_Li == n->norm->Bf.font)
 		PAIR_CLASS_INIT(&tag[0], "lit");
 	else
 		PAIR_CLASS_INIT(&tag[0], "none");
 
-	/* 
+	/*
 	 * We want this to be inline-formatted, but needs to be div to
-	 * accept block children. 
+	 * accept block children.
 	 */
 	bufinit(h);
 	bufcat_style(h, "display", "inline");
@@ -1911,8 +1851,6 @@ mdoc_bf_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ms_pre(MDOC_ARGS)
 {
@@ -1923,8 +1861,6 @@ mdoc_ms_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_igndelim_pre(MDOC_ARGS)
 {
@@ -1933,17 +1869,14 @@ mdoc_igndelim_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 mdoc_pf_post(MDOC_ARGS)
 {
 
-	h->flags |= HTML_NOSPACE;
+	if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
+		h->flags |= HTML_NOSPACE;
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_rs_pre(MDOC_ARGS)
 {
@@ -1953,16 +1886,23 @@ mdoc_rs_pre(MDOC_ARGS)
 		return(1);
 
 	if (n->prev && SEC_SEE_ALSO == n->sec)
-		print_otag(h, TAG_P, 0, NULL);
+		print_paragraph(h);
 
 	PAIR_CLASS_INIT(&tag, "ref");
 	print_otag(h, TAG_SPAN, 1, &tag);
 	return(1);
 }
 
+static int
+mdoc_no_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
 
+	PAIR_CLASS_INIT(&tag, "none");
+	print_otag(h, TAG_CODE, 1, &tag);
+	return(1);
+}
 
-/* ARGSUSED */
 static int
 mdoc_li_pre(MDOC_ARGS)
 {
@@ -1973,8 +1913,6 @@ mdoc_li_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_sy_pre(MDOC_ARGS)
 {
@@ -1985,8 +1923,6 @@ mdoc_sy_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_bt_pre(MDOC_ARGS)
 {
@@ -1995,8 +1931,6 @@ mdoc_bt_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_ud_pre(MDOC_ARGS)
 {
@@ -2005,8 +1939,6 @@ mdoc_ud_pre(MDOC_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_lb_pre(MDOC_ARGS)
 {
@@ -2020,8 +1952,6 @@ mdoc_lb_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc__x_pre(MDOC_ARGS)
 {
@@ -2031,52 +1961,52 @@ mdoc__x_pre(MDOC_ARGS)
 	t = TAG_SPAN;
 
 	switch (n->tok) {
-	case(MDOC__A):
+	case MDOC__A:
 		PAIR_CLASS_INIT(&tag[0], "ref-auth");
 		if (n->prev && MDOC__A == n->prev->tok)
 			if (NULL == n->next || MDOC__A != n->next->tok)
 				print_text(h, "and");
 		break;
-	case(MDOC__B):
+	case MDOC__B:
 		PAIR_CLASS_INIT(&tag[0], "ref-book");
 		t = TAG_I;
 		break;
-	case(MDOC__C):
+	case MDOC__C:
 		PAIR_CLASS_INIT(&tag[0], "ref-city");
 		break;
-	case(MDOC__D):
+	case MDOC__D:
 		PAIR_CLASS_INIT(&tag[0], "ref-date");
 		break;
-	case(MDOC__I):
+	case MDOC__I:
 		PAIR_CLASS_INIT(&tag[0], "ref-issue");
 		t = TAG_I;
 		break;
-	case(MDOC__J):
+	case MDOC__J:
 		PAIR_CLASS_INIT(&tag[0], "ref-jrnl");
 		t = TAG_I;
 		break;
-	case(MDOC__N):
+	case MDOC__N:
 		PAIR_CLASS_INIT(&tag[0], "ref-num");
 		break;
-	case(MDOC__O):
+	case MDOC__O:
 		PAIR_CLASS_INIT(&tag[0], "ref-opt");
 		break;
-	case(MDOC__P):
+	case MDOC__P:
 		PAIR_CLASS_INIT(&tag[0], "ref-page");
 		break;
-	case(MDOC__Q):
+	case MDOC__Q:
 		PAIR_CLASS_INIT(&tag[0], "ref-corp");
 		break;
-	case(MDOC__R):
+	case MDOC__R:
 		PAIR_CLASS_INIT(&tag[0], "ref-rep");
 		break;
-	case(MDOC__T):
+	case MDOC__T:
 		PAIR_CLASS_INIT(&tag[0], "ref-title");
 		break;
-	case(MDOC__U):
+	case MDOC__U:
 		PAIR_CLASS_INIT(&tag[0], "link-ref");
 		break;
-	case(MDOC__V):
+	case MDOC__V:
 		PAIR_CLASS_INIT(&tag[0], "ref-vol");
 		break;
 	default:
@@ -2095,8 +2025,6 @@ mdoc__x_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 mdoc__x_post(MDOC_ARGS)
 {
@@ -2115,18 +2043,16 @@ mdoc__x_post(MDOC_ARGS)
 	print_text(h, n->next ? "," : ".");
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_bk_pre(MDOC_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		return(0);
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		if (n->parent->args || 0 == n->prev->nchild)
 			h->flags |= HTML_PREKEEP;
 		break;
@@ -2138,8 +2064,6 @@ mdoc_bk_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 mdoc_bk_post(MDOC_ARGS)
 {
@@ -2148,8 +2072,6 @@ mdoc_bk_post(MDOC_ARGS)
 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
 }
 
-
-/* ARGSUSED */
 static int
 mdoc_quote_pre(MDOC_ARGS)
 {
@@ -2159,54 +2081,59 @@ mdoc_quote_pre(MDOC_ARGS)
 		return(1);
 
 	switch (n->tok) {
-	case (MDOC_Ao):
+	case MDOC_Ao:
 		/* FALLTHROUGH */
-	case (MDOC_Aq):
-		print_text(h, "\\(la");
+	case MDOC_Aq:
+		print_text(h, n->nchild == 1 &&
+		    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
 		break;
-	case (MDOC_Bro):
+	case MDOC_Bro:
 		/* FALLTHROUGH */
-	case (MDOC_Brq):
+	case MDOC_Brq:
 		print_text(h, "\\(lC");
 		break;
-	case (MDOC_Bo):
+	case MDOC_Bo:
 		/* FALLTHROUGH */
-	case (MDOC_Bq):
+	case MDOC_Bq:
 		print_text(h, "\\(lB");
 		break;
-	case (MDOC_Oo):
+	case MDOC_Oo:
 		/* FALLTHROUGH */
-	case (MDOC_Op):
+	case MDOC_Op:
 		print_text(h, "\\(lB");
 		h->flags |= HTML_NOSPACE;
 		PAIR_CLASS_INIT(&tag, "opt");
 		print_otag(h, TAG_SPAN, 1, &tag);
 		break;
-	case (MDOC_Eo):
+	case MDOC_En:
+		if (NULL == n->norm->Es ||
+		    NULL == n->norm->Es->child)
+			return(1);
+		print_text(h, n->norm->Es->child->string);
 		break;
-	case (MDOC_Do):
+	case MDOC_Do:
 		/* FALLTHROUGH */
-	case (MDOC_Dq):
+	case MDOC_Dq:
 		/* FALLTHROUGH */
-	case (MDOC_Qo):
+	case MDOC_Qo:
 		/* FALLTHROUGH */
-	case (MDOC_Qq):
+	case MDOC_Qq:
 		print_text(h, "\\(lq");
 		break;
-	case (MDOC_Po):
+	case MDOC_Po:
 		/* FALLTHROUGH */
-	case (MDOC_Pq):
+	case MDOC_Pq:
 		print_text(h, "(");
 		break;
-	case (MDOC_Ql):
+	case MDOC_Ql:
 		print_text(h, "\\(oq");
 		h->flags |= HTML_NOSPACE;
 		PAIR_CLASS_INIT(&tag, "lit");
 		print_otag(h, TAG_CODE, 1, &tag);
 		break;
-	case (MDOC_So):
+	case MDOC_So:
 		/* FALLTHROUGH */
-	case (MDOC_Sq):
+	case MDOC_Sq:
 		print_text(h, "\\(oq");
 		break;
 	default:
@@ -2218,58 +2145,63 @@ mdoc_quote_pre(MDOC_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 mdoc_quote_post(MDOC_ARGS)
 {
 
-	if (MDOC_BODY != n->type)
+	if (n->type != MDOC_BODY && n->type != MDOC_ELEM)
 		return;
 
 	h->flags |= HTML_NOSPACE;
 
 	switch (n->tok) {
-	case (MDOC_Ao):
+	case MDOC_Ao:
 		/* FALLTHROUGH */
-	case (MDOC_Aq):
-		print_text(h, "\\(ra");
+	case MDOC_Aq:
+		print_text(h, n->nchild == 1 &&
+		    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
 		break;
-	case (MDOC_Bro):
+	case MDOC_Bro:
 		/* FALLTHROUGH */
-	case (MDOC_Brq):
+	case MDOC_Brq:
 		print_text(h, "\\(rC");
 		break;
-	case (MDOC_Oo):
+	case MDOC_Oo:
 		/* FALLTHROUGH */
-	case (MDOC_Op):
+	case MDOC_Op:
 		/* FALLTHROUGH */
-	case (MDOC_Bo):
+	case MDOC_Bo:
 		/* FALLTHROUGH */
-	case (MDOC_Bq):
+	case MDOC_Bq:
 		print_text(h, "\\(rB");
 		break;
-	case (MDOC_Eo):
+	case MDOC_En:
+		if (n->norm->Es == NULL ||
+		    n->norm->Es->child == NULL ||
+		    n->norm->Es->child->next == NULL)
+			h->flags &= ~HTML_NOSPACE;
+		else
+			print_text(h, n->norm->Es->child->next->string);
 		break;
-	case (MDOC_Qo):
+	case MDOC_Qo:
 		/* FALLTHROUGH */
-	case (MDOC_Qq):
+	case MDOC_Qq:
 		/* FALLTHROUGH */
-	case (MDOC_Do):
+	case MDOC_Do:
 		/* FALLTHROUGH */
-	case (MDOC_Dq):
+	case MDOC_Dq:
 		print_text(h, "\\(rq");
 		break;
-	case (MDOC_Po):
+	case MDOC_Po:
 		/* FALLTHROUGH */
-	case (MDOC_Pq):
+	case MDOC_Pq:
 		print_text(h, ")");
 		break;
-	case (MDOC_Ql):
+	case MDOC_Ql:
 		/* FALLTHROUGH */
-	case (MDOC_So):
+	case MDOC_So:
 		/* FALLTHROUGH */
-	case (MDOC_Sq):
+	case MDOC_Sq:
 		print_text(h, "\\(cq");
 		break;
 	default:
@@ -2278,4 +2210,43 @@ mdoc_quote_post(MDOC_ARGS)
 	}
 }
 
+static int
+mdoc_eo_pre(MDOC_ARGS)
+{
+
+	if (n->type != MDOC_BODY)
+		return(1);
+
+	if (n->end == ENDBODY_NOT &&
+	    n->parent->head->child == NULL &&
+	    n->child != NULL &&
+	    n->child->end != ENDBODY_NOT)
+		print_text(h, "\\&");
+	else if (n->end != ENDBODY_NOT ? n->child != NULL :
+	    n->parent->head->child != NULL && (n->child != NULL ||
+	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
+		h->flags |= HTML_NOSPACE;
+	return(1);
+}
 
+static void
+mdoc_eo_post(MDOC_ARGS)
+{
+	int	 body, tail;
+
+	if (n->type != MDOC_BODY)
+		return;
+
+	if (n->end != ENDBODY_NOT) {
+		h->flags &= ~HTML_NOSPACE;
+		return;
+	}
+
+	body = n->child != NULL || n->parent->head->child != NULL;
+	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
+
+	if (body && tail)
+		h->flags |= HTML_NOSPACE;
+	else if ( ! tail)
+		h->flags &= ~HTML_NOSPACE;
+}
diff --git a/usr/src/cmd/mandoc/mdoc_macro.c b/usr/src/cmd/mandoc/mdoc_macro.c
index 2a63ca92e3..efb48d023f 100644
--- a/usr/src/cmd/mandoc/mdoc_macro.c
+++ b/usr/src/cmd/mandoc/mdoc_macro.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_macro.c,v 1.125 2013/12/24 20:45:27 schwarze Exp $ */
+/*	$Id: mdoc_macro.c,v 1.183 2015/02/12 12:24:33 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -31,43 +31,28 @@
 #include "libmdoc.h"
 #include "libmandoc.h"
 
-enum	rew {	/* see rew_dohalt() */
-	REWIND_NONE,
-	REWIND_THIS,
-	REWIND_MORE,
-	REWIND_FORCE,
-	REWIND_LATER,
-	REWIND_ERROR
-};
-
-static	int	  	blk_full(MACRO_PROT_ARGS);
-static	int	  	blk_exp_close(MACRO_PROT_ARGS);
-static	int	  	blk_part_exp(MACRO_PROT_ARGS);
-static	int	  	blk_part_imp(MACRO_PROT_ARGS);
-static	int	  	ctx_synopsis(MACRO_PROT_ARGS);
-static	int	  	in_line_eoln(MACRO_PROT_ARGS);
-static	int	  	in_line_argn(MACRO_PROT_ARGS);
-static	int	  	in_line(MACRO_PROT_ARGS);
-static	int	  	obsolete(MACRO_PROT_ARGS);
-static	int	  	phrase_ta(MACRO_PROT_ARGS);
-
-static	int		dword(struct mdoc *, int, int, const char *,
+static	void		blk_full(MACRO_PROT_ARGS);
+static	void		blk_exp_close(MACRO_PROT_ARGS);
+static	void		blk_part_exp(MACRO_PROT_ARGS);
+static	void		blk_part_imp(MACRO_PROT_ARGS);
+static	void		ctx_synopsis(MACRO_PROT_ARGS);
+static	void		in_line_eoln(MACRO_PROT_ARGS);
+static	void		in_line_argn(MACRO_PROT_ARGS);
+static	void		in_line(MACRO_PROT_ARGS);
+static	void		phrase_ta(MACRO_PROT_ARGS);
+
+static	void		dword(struct mdoc *, int, int, const char *,
 				 enum mdelim, int);
-static	int	  	append_delims(struct mdoc *, 
+static	void		append_delims(struct mdoc *, int, int *, char *);
+static	enum mdoct	lookup(struct mdoc *, enum mdoct,
+				int, int, const char *);
+static	int		macro_or_word(MACRO_PROT_ARGS, int);
+static	int		parse_rest(struct mdoc *, enum mdoct,
 				int, int *, char *);
-static	enum mdoct	lookup(enum mdoct, const char *);
-static	enum mdoct	lookup_raw(const char *);
-static	int		make_pending(struct mdoc_node *, enum mdoct,
-				struct mdoc *, int, int);
-static	int	  	phrase(struct mdoc *, int, int, char *);
-static	enum mdoct 	rew_alt(enum mdoct);
-static	enum rew  	rew_dohalt(enum mdoct, enum mdoc_type, 
-				const struct mdoc_node *);
-static	int	  	rew_elem(struct mdoc *, enum mdoct);
-static	int	  	rew_last(struct mdoc *, 
-				const struct mdoc_node *);
-static	int	  	rew_sub(enum mdoc_type, struct mdoc *, 
-				enum mdoct, int, int);
+static	enum mdoct	rew_alt(enum mdoct);
+static	void		rew_elem(struct mdoc *, enum mdoct);
+static	void		rew_last(struct mdoc *, const struct mdoc_node *);
+static	void		rew_pending(struct mdoc *, const struct mdoc_node *);
 
 const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ap */
@@ -87,7 +72,7 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* An */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */
-	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cd */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Cd */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Er */
@@ -98,13 +83,13 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fl */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fn */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ft */
-	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ic */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ic */
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Li */
 	{ blk_full, MDOC_JOIN }, /* Nd */
 	{ ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */
 	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Op */
-	{ obsolete, 0 }, /* Ot */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ot */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Pa */
 	{ in_line_eoln, 0 }, /* Rv */
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */
@@ -148,8 +133,7 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Fx */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ms */
-	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED |
-			MDOC_IGNDELIM | MDOC_JOIN }, /* No */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* No */
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED |
 			MDOC_IGNDELIM | MDOC_JOIN }, /* Ns */
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */
@@ -173,7 +157,7 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED |
 			MDOC_EXPLICIT | MDOC_JOIN }, /* So */
 	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sq */
-	{ in_line_eoln, 0 }, /* Sm */
+	{ in_line_argn, 0 }, /* Sm */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sx */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Sy */
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */
@@ -191,7 +175,7 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ blk_exp_close, MDOC_EXPLICIT | MDOC_JOIN }, /* Ek */
 	{ in_line_eoln, 0 }, /* Bt */
 	{ in_line_eoln, 0 }, /* Hf */
-	{ obsolete, 0 }, /* Fr */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fr */
 	{ in_line_eoln, 0 }, /* Ud */
 	{ in_line, 0 }, /* Lb */
 	{ in_line_eoln, 0 }, /* Lp */
@@ -203,14 +187,15 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ blk_exp_close, MDOC_CALLABLE | MDOC_PARSED |
 			 MDOC_EXPLICIT | MDOC_JOIN }, /* Brc */
 	{ in_line_eoln, MDOC_JOIN }, /* %C */
-	{ obsolete, 0 }, /* Es */
-	{ obsolete, 0 }, /* En */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Es */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* En */
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */
 	{ in_line_eoln, MDOC_JOIN }, /* %Q */
 	{ in_line_eoln, 0 }, /* br */
 	{ in_line_eoln, 0 }, /* sp */
 	{ in_line_eoln, 0 }, /* %U */
 	{ phrase_ta, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ta */
+	{ in_line_eoln, MDOC_PROLOGUE }, /* ll */
 };
 
 const	struct mdoc_macro * const mdoc_macros = __mdoc_macros;
@@ -221,65 +206,59 @@ const	struct mdoc_macro * const mdoc_macros = __mdoc_macros;
  * closing out open [implicit] scopes.  Obviously, open explicit scopes
  * are errors.
  */
-int
+void
 mdoc_macroend(struct mdoc *mdoc)
 {
 	struct mdoc_node *n;
 
 	/* Scan for open explicit scopes. */
 
-	n = MDOC_VALID & mdoc->last->flags ?
-			mdoc->last->parent : mdoc->last;
+	n = mdoc->last->flags & MDOC_VALID ?
+	    mdoc->last->parent : mdoc->last;
 
 	for ( ; n; n = n->parent)
-		if (MDOC_BLOCK == n->type &&
-		    MDOC_EXPLICIT & mdoc_macros[n->tok].flags)
-			mdoc_nmsg(mdoc, n, MANDOCERR_SCOPEEXIT);
+		if (n->type == MDOC_BLOCK &&
+		    mdoc_macros[n->tok].flags & MDOC_EXPLICIT)
+			mandoc_msg(MANDOCERR_BLK_NOEND, mdoc->parse,
+			    n->line, n->pos, mdoc_macronames[n->tok]);
 
 	/* Rewind to the first. */
 
-	return(rew_last(mdoc, mdoc->first));
-}
-
-
-/*
- * Look up a macro from within a subsequent context.
- */
-static enum mdoct
-lookup(enum mdoct from, const char *p)
-{
-
-	if ( ! (MDOC_PARSED & mdoc_macros[from].flags))
-		return(MDOC_MAX);
-	return(lookup_raw(p));
+	rew_last(mdoc, mdoc->first);
 }
 
-
 /*
- * Lookup a macro following the initial line macro.
+ * Look up the macro at *p called by "from",
+ * or as a line macro if from == MDOC_MAX.
  */
 static enum mdoct
-lookup_raw(const char *p)
+lookup(struct mdoc *mdoc, enum mdoct from, int line, int ppos, const char *p)
 {
 	enum mdoct	 res;
 
-	if (MDOC_MAX == (res = mdoc_hash_find(p)))
-		return(MDOC_MAX);
-	if (MDOC_CALLABLE & mdoc_macros[res].flags)
-		return(res);
+	if (from == MDOC_MAX || mdoc_macros[from].flags & MDOC_PARSED) {
+		res = mdoc_hash_find(p);
+		if (res != MDOC_MAX) {
+			if (mdoc_macros[res].flags & MDOC_CALLABLE)
+				return(res);
+			if (res != MDOC_br && res != MDOC_sp && res != MDOC_ll)
+				mandoc_msg(MANDOCERR_MACRO_CALL,
+				    mdoc->parse, line, ppos, p);
+		}
+	}
 	return(MDOC_MAX);
 }
 
-
-static int
+/*
+ * Rewind up to and including a specific node.
+ */
+static void
 rew_last(struct mdoc *mdoc, const struct mdoc_node *to)
 {
 	struct mdoc_node *n, *np;
 
 	assert(to);
 	mdoc->next = MDOC_NEXT_SIBLING;
-
-	/* LINTED */
 	while (mdoc->last != to) {
 		/*
 		 * Save the parent here, because we may delete the
@@ -288,17 +267,52 @@ rew_last(struct mdoc *mdoc, const struct mdoc_node *to)
 		 * out to be lost.
 		 */
 		np = mdoc->last->parent;
-		if ( ! mdoc_valid_post(mdoc))
-			return(0);
+		mdoc_valid_post(mdoc);
 		n = mdoc->last;
 		mdoc->last = np;
 		assert(mdoc->last);
 		mdoc->last->last = n;
 	}
-
-	return(mdoc_valid_post(mdoc));
+	mdoc_valid_post(mdoc);
 }
 
+/*
+ * Rewind up to a specific block, including all blocks that broke it.
+ */
+static void
+rew_pending(struct mdoc *mdoc, const struct mdoc_node *n)
+{
+
+	for (;;) {
+		rew_last(mdoc, n);
+
+		switch (n->type) {
+		case MDOC_HEAD:
+			mdoc_body_alloc(mdoc, n->line, n->pos, n->tok);
+			return;
+		case MDOC_BLOCK:
+			break;
+		default:
+			return;
+		}
+
+		if ( ! (n->flags & MDOC_BROKEN))
+			return;
+
+		for (;;) {
+			if ((n = n->parent) == NULL)
+				return;
+
+			if (n->type == MDOC_BLOCK ||
+			    n->type == MDOC_HEAD) {
+				if (n->flags & MDOC_ENDED)
+					break;
+				else
+					return;
+			}
+		}
+	}
+}
 
 /*
  * For a block closing macro, return the corresponding opening one.
@@ -308,37 +322,37 @@ static enum mdoct
 rew_alt(enum mdoct tok)
 {
 	switch (tok) {
-	case (MDOC_Ac):
+	case MDOC_Ac:
 		return(MDOC_Ao);
-	case (MDOC_Bc):
+	case MDOC_Bc:
 		return(MDOC_Bo);
-	case (MDOC_Brc):
+	case MDOC_Brc:
 		return(MDOC_Bro);
-	case (MDOC_Dc):
+	case MDOC_Dc:
 		return(MDOC_Do);
-	case (MDOC_Ec):
+	case MDOC_Ec:
 		return(MDOC_Eo);
-	case (MDOC_Ed):
+	case MDOC_Ed:
 		return(MDOC_Bd);
-	case (MDOC_Ef):
+	case MDOC_Ef:
 		return(MDOC_Bf);
-	case (MDOC_Ek):
+	case MDOC_Ek:
 		return(MDOC_Bk);
-	case (MDOC_El):
+	case MDOC_El:
 		return(MDOC_Bl);
-	case (MDOC_Fc):
+	case MDOC_Fc:
 		return(MDOC_Fo);
-	case (MDOC_Oc):
+	case MDOC_Oc:
 		return(MDOC_Oo);
-	case (MDOC_Pc):
+	case MDOC_Pc:
 		return(MDOC_Po);
-	case (MDOC_Qc):
+	case MDOC_Qc:
 		return(MDOC_Qo);
-	case (MDOC_Re):
+	case MDOC_Re:
 		return(MDOC_Rs);
-	case (MDOC_Sc):
+	case MDOC_Sc:
 		return(MDOC_So);
-	case (MDOC_Xc):
+	case MDOC_Xc:
 		return(MDOC_Xo);
 	default:
 		return(tok);
@@ -346,118 +360,7 @@ rew_alt(enum mdoct tok)
 	/* NOTREACHED */
 }
 
-
-/*
- * Rewinding to tok, how do we have to handle *p?
- * REWIND_NONE: *p would delimit tok, but no tok scope is open
- *   inside *p, so there is no need to rewind anything at all.
- * REWIND_THIS: *p matches tok, so rewind *p and nothing else.
- * REWIND_MORE: *p is implicit, rewind it and keep searching for tok.
- * REWIND_FORCE: *p is explicit, but tok is full, force rewinding *p.
- * REWIND_LATER: *p is explicit and still open, postpone rewinding.
- * REWIND_ERROR: No tok block is open at all.
- */
-static enum rew
-rew_dohalt(enum mdoct tok, enum mdoc_type type, 
-		const struct mdoc_node *p)
-{
-
-	/*
-	 * No matching token, no delimiting block, no broken block.
-	 * This can happen when full implicit macros are called for
-	 * the first time but try to rewind their previous
-	 * instance anyway.
-	 */
-	if (MDOC_ROOT == p->type)
-		return(MDOC_BLOCK == type &&
-		    MDOC_EXPLICIT & mdoc_macros[tok].flags ?
-		    REWIND_ERROR : REWIND_NONE);
-
-	/*
-	 * When starting to rewind, skip plain text 
-	 * and nodes that have already been rewound.
-	 */
-	if (MDOC_TEXT == p->type || MDOC_VALID & p->flags)
-		return(REWIND_MORE);
-
-	/*
-	 * The easiest case:  Found a matching token.
-	 * This applies to both blocks and elements.
-	 */
-	tok = rew_alt(tok);
-	if (tok == p->tok)
-		return(p->end ? REWIND_NONE :
-		    type == p->type ? REWIND_THIS : REWIND_MORE);
-
-	/*
-	 * While elements do require rewinding for themselves,
-	 * they never affect rewinding of other nodes.
-	 */
-	if (MDOC_ELEM == p->type)
-		return(REWIND_MORE);
-
-	/*
-	 * Blocks delimited by our target token get REWIND_MORE.
-	 * Blocks delimiting our target token get REWIND_NONE. 
-	 */
-	switch (tok) {
-	case (MDOC_Bl):
-		if (MDOC_It == p->tok)
-			return(REWIND_MORE);
-		break;
-	case (MDOC_It):
-		if (MDOC_BODY == p->type && MDOC_Bl == p->tok)
-			return(REWIND_NONE);
-		break;
-	/*
-	 * XXX Badly nested block handling still fails badly
-	 * when one block is breaking two blocks of the same type.
-	 * This is an incomplete and extremely ugly workaround,
-	 * required to let the OpenBSD tree build.
-	 */
-	case (MDOC_Oo):
-		if (MDOC_Op == p->tok)
-			return(REWIND_MORE);
-		break;
-	case (MDOC_Nm):
-		return(REWIND_NONE);
-	case (MDOC_Nd):
-		/* FALLTHROUGH */
-	case (MDOC_Ss):
-		if (MDOC_BODY == p->type && MDOC_Sh == p->tok)
-			return(REWIND_NONE);
-		/* FALLTHROUGH */
-	case (MDOC_Sh):
-		if (MDOC_Nd == p->tok || MDOC_Ss == p->tok ||
-		    MDOC_Sh == p->tok)
-			return(REWIND_MORE);
-		break;
-	default:
-		break;
-	}
-
-	/*
-	 * Default block rewinding rules.
-	 * In particular, always skip block end markers,
-	 * and let all blocks rewind Nm children.
-	 */
-	if (ENDBODY_NOT != p->end || MDOC_Nm == p->tok ||
-	    (MDOC_BLOCK == p->type &&
-	    ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)))
-		return(REWIND_MORE);
-
-	/*
-	 * By default, closing out full blocks
-	 * forces closing of broken explicit blocks,
-	 * while closing out partial blocks
-	 * allows delayed rewinding by default.
-	 */
-	return (&blk_full == mdoc_macros[tok].fp ?
-	    REWIND_FORCE : REWIND_LATER);
-}
-
-
-static int
+static void
 rew_elem(struct mdoc *mdoc, enum mdoct tok)
 {
 	struct mdoc_node *n;
@@ -467,209 +370,60 @@ rew_elem(struct mdoc *mdoc, enum mdoct tok)
 		n = n->parent;
 	assert(MDOC_ELEM == n->type);
 	assert(tok == n->tok);
-
-	return(rew_last(mdoc, n));
-}
-
-
-/*
- * We are trying to close a block identified by tok,
- * but the child block *broken is still open.
- * Thus, postpone closing the tok block
- * until the rew_sub call closing *broken.
- */
-static int
-make_pending(struct mdoc_node *broken, enum mdoct tok,
-		struct mdoc *mdoc, int line, int ppos)
-{
-	struct mdoc_node *breaker;
-
-	/*
-	 * Iterate backwards, searching for the block matching tok,
-	 * that is, the block breaking the *broken block.
-	 */
-	for (breaker = broken->parent; breaker; breaker = breaker->parent) {
-
-		/*
-		 * If the *broken block had already been broken before
-		 * and we encounter its breaker, make the tok block
-		 * pending on the inner breaker.
-		 * Graphically, "[A breaker=[B broken=[C->B B] tok=A] C]"
-		 * becomes "[A broken=[B [C->B B] tok=A] C]"
-		 * and finally "[A [B->A [C->B B] A] C]".
-		 */
-		if (breaker == broken->pending) {
-			broken = breaker;
-			continue;
-		}
-
-		if (REWIND_THIS != rew_dohalt(tok, MDOC_BLOCK, breaker))
-			continue;
-		if (MDOC_BODY == broken->type)
-			broken = broken->parent;
-
-		/*
-		 * Found the breaker.
-		 * If another, outer breaker is already pending on
-		 * the *broken block, we must not clobber the link
-		 * to the outer breaker, but make it pending on the
-		 * new, now inner breaker.
-		 * Graphically, "[A breaker=[B broken=[C->A A] tok=B] C]"
-		 * becomes "[A breaker=[B->A broken=[C A] tok=B] C]"
-		 * and finally "[A [B->A [C->B A] B] C]".
-		 */
-		if (broken->pending) {
-			struct mdoc_node *taker;
-
-			/*
-			 * If the breaker had also been broken before,
-			 * it cannot take on the outer breaker itself,
-			 * but must hand it on to its own breakers.
-			 * Graphically, this is the following situation:
-			 * "[A [B breaker=[C->B B] broken=[D->A A] tok=C] D]"
-			 * "[A taker=[B->A breaker=[C->B B] [D->C A] C] D]"
-			 */
-			taker = breaker;
-			while (taker->pending)
-				taker = taker->pending;
-			taker->pending = broken->pending;
-		}
-		broken->pending = breaker;
-		mandoc_vmsg(MANDOCERR_SCOPENEST, mdoc->parse, line, ppos,
-				"%s breaks %s", mdoc_macronames[tok],
-				mdoc_macronames[broken->tok]);
-		return(1);
-	}
-
-	/*
-	 * Found no matching block for tok.
-	 * Are you trying to close a block that is not open?
-	 */
-	return(0);
-}
-
-
-static int
-rew_sub(enum mdoc_type t, struct mdoc *mdoc, 
-		enum mdoct tok, int line, int ppos)
-{
-	struct mdoc_node *n;
-
-	n = mdoc->last;
-	while (n) {
-		switch (rew_dohalt(tok, t, n)) {
-		case (REWIND_NONE):
-			return(1);
-		case (REWIND_THIS):
-			n->lastline = line -
-			    (MDOC_NEWLINE & mdoc->flags &&
-			     ! (MDOC_EXPLICIT & mdoc_macros[tok].flags));
-			break;
-		case (REWIND_FORCE):
-			mandoc_vmsg(MANDOCERR_SCOPEBROKEN, mdoc->parse, 
-					line, ppos, "%s breaks %s", 
-					mdoc_macronames[tok],
-					mdoc_macronames[n->tok]);
-			/* FALLTHROUGH */
-		case (REWIND_MORE):
-			n->lastline = line -
-			    (MDOC_NEWLINE & mdoc->flags ? 1 : 0);
-			n = n->parent;
-			continue;
-		case (REWIND_LATER):
-			if (make_pending(n, tok, mdoc, line, ppos) ||
-			    MDOC_BLOCK != t)
-				return(1);
-			/* FALLTHROUGH */
-		case (REWIND_ERROR):
-			mdoc_pmsg(mdoc, line, ppos, MANDOCERR_NOSCOPE);
-			return(1);
-		}
-		break;
-	}
-
-	assert(n);
-	if ( ! rew_last(mdoc, n))
-		return(0);
-
-	/*
-	 * The current block extends an enclosing block.
-	 * Now that the current block ends, close the enclosing block, too.
-	 */
-	while (NULL != (n = n->pending)) {
-		if ( ! rew_last(mdoc, n))
-			return(0);
-		if (MDOC_HEAD == n->type &&
-		    ! mdoc_body_alloc(mdoc, n->line, n->pos, n->tok))
-			return(0);
-	}
-
-	return(1);
+	rew_last(mdoc, n);
 }
 
 /*
  * Allocate a word and check whether it's punctuation or not.
  * Punctuation consists of those tokens found in mdoc_isdelim().
  */
-static int
+static void
 dword(struct mdoc *mdoc, int line, int col, const char *p,
 		enum mdelim d, int may_append)
 {
-	
-	if (DELIM_MAX == d)
+
+	if (d == DELIM_MAX)
 		d = mdoc_isdelim(p);
 
 	if (may_append &&
-	    ! ((MDOC_SYNOPSIS | MDOC_KEEP | MDOC_SMOFF) & mdoc->flags) &&
-	    DELIM_NONE == d && MDOC_TEXT == mdoc->last->type &&
-	    DELIM_NONE == mdoc_isdelim(mdoc->last->string)) {
+	    ! (mdoc->flags & (MDOC_SYNOPSIS | MDOC_KEEP | MDOC_SMOFF)) &&
+	    d == DELIM_NONE && mdoc->last->type == MDOC_TEXT &&
+	    mdoc_isdelim(mdoc->last->string) == DELIM_NONE) {
 		mdoc_word_append(mdoc, p);
-		return(1);
+		return;
 	}
 
-	if ( ! mdoc_word_alloc(mdoc, line, col, p))
-		return(0);
-
-	if (DELIM_OPEN == d)
-		mdoc->last->flags |= MDOC_DELIMO;
+	mdoc_word_alloc(mdoc, line, col, p);
 
 	/*
-	 * Closing delimiters only suppress the preceding space
-	 * when they follow something, not when they start a new
-	 * block or element, and not when they follow `No'.
-	 *
-	 * XXX	Explicitly special-casing MDOC_No here feels
-	 *	like a layering violation.  Find a better way
-	 *	and solve this in the code related to `No'!
+	 * If the word consists of a bare delimiter,
+	 * flag the new node accordingly,
+	 * unless doing so was vetoed by the invoking macro.
+	 * Always clear the veto, it is only valid for one word.
 	 */
 
-	else if (DELIM_CLOSE == d && mdoc->last->prev &&
-			mdoc->last->prev->tok != MDOC_No &&
-			mdoc->last->parent->tok != MDOC_Fd)
+	if (d == DELIM_OPEN)
+		mdoc->last->flags |= MDOC_DELIMO;
+	else if (d == DELIM_CLOSE &&
+	    ! (mdoc->flags & MDOC_NODELIMC) &&
+	    mdoc->last->parent->tok != MDOC_Fd)
 		mdoc->last->flags |= MDOC_DELIMC;
-
-	return(1);
+	mdoc->flags &= ~MDOC_NODELIMC;
 }
 
-static int
+static void
 append_delims(struct mdoc *mdoc, int line, int *pos, char *buf)
 {
-	int		 la;
-	enum margserr	 ac;
 	char		*p;
+	int		 la;
 
-	if ('\0' == buf[*pos])
-		return(1);
+	if (buf[*pos] == '\0')
+		return;
 
 	for (;;) {
 		la = *pos;
-		ac = mdoc_zargs(mdoc, line, pos, buf, &p);
-
-		if (ARGS_ERROR == ac)
-			return(0);
-		else if (ARGS_EOLN == ac)
+		if (mdoc_args(mdoc, line, pos, buf, MDOC_MAX, &p) == ARGS_EOLN)
 			break;
-
 		dword(mdoc, line, la, p, DELIM_MAX, 1);
 
 		/*
@@ -683,25 +437,57 @@ append_delims(struct mdoc *mdoc, int line, int *pos, char *buf)
 		 * knowing which symbols break this behaviour, for
 		 * example, `.  ;' shouldn't propagate the double-space.
 		 */
-		if (mandoc_eos(p, strlen(p), 0))
+
+		if (mandoc_eos(p, strlen(p)))
 			mdoc->last->flags |= MDOC_EOS;
 	}
-
-	return(1);
 }
 
-
 /*
- * Close out block partial/full explicit.  
+ * Parse one word.
+ * If it is a macro, call it and return 1.
+ * Otherwise, allocate it and return 0.
  */
 static int
+macro_or_word(MACRO_PROT_ARGS, int parsed)
+{
+	char		*p;
+	enum mdoct	 ntok;
+
+	p = buf + ppos;
+	ntok = MDOC_MAX;
+	if (*p == '"')
+		p++;
+	else if (parsed && ! (mdoc->flags & MDOC_PHRASELIT))
+		ntok = lookup(mdoc, tok, line, ppos, p);
+
+	if (ntok == MDOC_MAX) {
+		dword(mdoc, line, ppos, p, DELIM_MAX, tok == MDOC_MAX ||
+		    mdoc_macros[tok].flags & MDOC_JOIN);
+		return(0);
+	} else {
+		if (mdoc_macros[tok].fp == in_line_eoln)
+			rew_elem(mdoc, tok);
+		mdoc_macro(mdoc, ntok, line, ppos, pos, buf);
+		if (tok == MDOC_MAX)
+			append_delims(mdoc, line, pos, buf);
+		return(1);
+	}
+}
+
+/*
+ * Close out block partial/full explicit.
+ */
+static void
 blk_exp_close(MACRO_PROT_ARGS)
 {
 	struct mdoc_node *body;		/* Our own body. */
+	struct mdoc_node *endbody;	/* Our own end marker. */
+	struct mdoc_node *itblk;	/* An It block starting later. */
 	struct mdoc_node *later;	/* A sub-block starting later. */
-	struct mdoc_node *n;		/* For searching backwards. */
+	struct mdoc_node *n;		/* Search back to our block. */
 
-	int	 	 j, lastarg, maxargs, flushed, nl;
+	int		 j, lastarg, maxargs, nl;
 	enum margserr	 ac;
 	enum mdoct	 atok, ntok;
 	char		*p;
@@ -709,11 +495,12 @@ blk_exp_close(MACRO_PROT_ARGS)
 	nl = MDOC_NEWLINE & mdoc->flags;
 
 	switch (tok) {
-	case (MDOC_Ec):
+	case MDOC_Ec:
 		maxargs = 1;
 		break;
-	case (MDOC_Ek):
+	case MDOC_Ek:
 		mdoc->flags &= ~MDOC_KEEP;
+		/* FALLTHROUGH */
 	default:
 		maxargs = 0;
 		break;
@@ -723,21 +510,32 @@ blk_exp_close(MACRO_PROT_ARGS)
 	 * Search backwards for beginnings of blocks,
 	 * both of our own and of pending sub-blocks.
 	 */
+
 	atok = rew_alt(tok);
-	body = later = NULL;
+	body = endbody = itblk = later = NULL;
 	for (n = mdoc->last; n; n = n->parent) {
-		if (MDOC_VALID & n->flags)
+		if (n->flags & MDOC_ENDED) {
+			if ( ! (n->flags & MDOC_VALID))
+				n->flags |= MDOC_BROKEN;
 			continue;
+		}
 
 		/* Remember the start of our own body. */
-		if (MDOC_BODY == n->type && atok == n->tok) {
-			if (ENDBODY_NOT == n->end)
+
+		if (n->type == MDOC_BODY && atok == n->tok) {
+			if (n->end == ENDBODY_NOT)
 				body = n;
 			continue;
 		}
 
-		if (MDOC_BLOCK != n->type || MDOC_Nm == n->tok)
+		if (n->type != MDOC_BLOCK || n->tok == MDOC_Nm)
 			continue;
+
+		if (n->tok == MDOC_It) {
+			itblk = n;
+			continue;
+		}
+
 		if (atok == n->tok) {
 			assert(body);
 
@@ -746,109 +544,125 @@ blk_exp_close(MACRO_PROT_ARGS)
 			 * When there is no pending sub block,
 			 * just proceed to closing out.
 			 */
-			if (NULL == later)
-				break;
 
-			/* 
-			 * When there is a pending sub block,
-			 * postpone closing out the current block
-			 * until the rew_sub() closing out the sub-block.
-			 */
-			make_pending(later, tok, mdoc, line, ppos);
+			if (later == NULL ||
+			    (tok == MDOC_El && itblk == NULL))
+				break;
 
 			/*
+			 * When there is a pending sub block, postpone
+			 * closing out the current block until the
+			 * rew_pending() closing out the sub-block.
 			 * Mark the place where the formatting - but not
 			 * the scope - of the current block ends.
 			 */
-			if ( ! mdoc_endbody_alloc(mdoc, line, ppos,
-			    atok, body, ENDBODY_SPACE))
-				return(0);
+
+			mandoc_vmsg(MANDOCERR_BLK_NEST, mdoc->parse,
+			    line, ppos, "%s breaks %s",
+			    mdoc_macronames[atok],
+			    mdoc_macronames[later->tok]);
+
+			endbody = mdoc_endbody_alloc(mdoc, line, ppos,
+			    atok, body, ENDBODY_SPACE);
+
+			if (tok == MDOC_El)
+				itblk->flags |= MDOC_ENDED | MDOC_BROKEN;
+
+			/*
+			 * If a block closing macro taking arguments
+			 * breaks another block, put the arguments
+			 * into the end marker.
+			 */
+
+			if (maxargs)
+				mdoc->next = MDOC_NEXT_CHILD;
 			break;
 		}
 
-		/*
-		 * When finding an open sub block, remember the last
-		 * open explicit block, or, in case there are only
-		 * implicit ones, the first open implicit block.
-		 */
-		if (later &&
-		    MDOC_EXPLICIT & mdoc_macros[later->tok].flags)
+		/* Explicit blocks close out description lines. */
+
+		if (n->tok == MDOC_Nd) {
+			rew_last(mdoc, n);
 			continue;
-		if (MDOC_It != n->tok)
-			later = n;
-	}
+		}
 
-	if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) {
-		/* FIXME: do this in validate */
-		if (buf[*pos]) 
-			mdoc_pmsg(mdoc, line, ppos, MANDOCERR_ARGSLOST);
+		/* Breaking an open sub block. */
 
-		if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
-			return(0);
-		return(rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos));
+		n->flags |= MDOC_BROKEN;
+		if (later == NULL)
+			later = n;
 	}
 
-	if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
-		return(0);
+	if (body == NULL) {
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, mdoc->parse,
+		    line, ppos, mdoc_macronames[tok]);
+		if (maxargs && endbody == NULL) {
+			/*
+			 * Stray .Ec without previous .Eo:
+			 * Break the output line, keep the arguments.
+			 */
+			mdoc_elem_alloc(mdoc, line, ppos, MDOC_br, NULL);
+			rew_elem(mdoc, MDOC_br);
+		}
+	} else if (endbody == NULL) {
+		rew_last(mdoc, body);
+		if (maxargs)
+			mdoc_tail_alloc(mdoc, line, ppos, atok);
+	}
 
-	if (NULL == later && maxargs > 0) 
-		if ( ! mdoc_tail_alloc(mdoc, line, ppos, rew_alt(tok)))
-			return(0);
+	if ( ! (mdoc_macros[tok].flags & MDOC_PARSED)) {
+		if (buf[*pos] != '\0')
+			mandoc_vmsg(MANDOCERR_ARG_SKIP,
+			    mdoc->parse, line, ppos,
+			    "%s %s", mdoc_macronames[tok],
+			    buf + *pos);
+		if (endbody == NULL && n != NULL)
+			rew_pending(mdoc, n);
+		return;
+	}
 
-	for (flushed = j = 0; ; j++) {
+	if (endbody != NULL)
+		n = endbody;
+	for (j = 0; ; j++) {
 		lastarg = *pos;
 
-		if (j == maxargs && ! flushed) {
-			if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
-				return(0);
-			flushed = 1;
+		if (j == maxargs && n != NULL) {
+			rew_pending(mdoc, n);
+			n = NULL;
 		}
 
 		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
-
-		if (ARGS_ERROR == ac)
-			return(0);
-		if (ARGS_PUNCT == ac)
-			break;
-		if (ARGS_EOLN == ac)
+		if (ac == ARGS_PUNCT || ac == ARGS_EOLN)
 			break;
 
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+		ntok = ac == ARGS_QWORD ? MDOC_MAX :
+		    lookup(mdoc, tok, line, lastarg, p);
 
-		if (MDOC_MAX == ntok) {
-			if ( ! dword(mdoc, line, lastarg, p, DELIM_MAX,
-			    MDOC_JOIN & mdoc_macros[tok].flags))
-				return(0);
+		if (ntok == MDOC_MAX) {
+			dword(mdoc, line, lastarg, p, DELIM_MAX,
+			    MDOC_JOIN & mdoc_macros[tok].flags);
 			continue;
 		}
 
-		if ( ! flushed) {
-			if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
-				return(0);
-			flushed = 1;
+		if (n != NULL) {
+			rew_pending(mdoc, n);
+			n = NULL;
 		}
-
 		mdoc->flags &= ~MDOC_NEWLINE;
-
-		if ( ! mdoc_macro(mdoc, ntok, line, lastarg, pos, buf))
-			return(0);
+		mdoc_macro(mdoc, ntok, line, lastarg, pos, buf);
 		break;
 	}
 
-	if ( ! flushed && ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
-		return(0);
-
-	if ( ! nl)
-		return(1);
-	return(append_delims(mdoc, line, pos, buf));
+	if (n != NULL)
+		rew_pending(mdoc, n);
+	if (nl)
+		append_delims(mdoc, line, pos, buf);
 }
 
-
-static int
+static void
 in_line(MACRO_PROT_ARGS)
 {
-	int		 la, scope, cnt, nc, nl;
-	enum margverr	 av;
+	int		 la, scope, cnt, firstarg, mayopen, nc, nl;
 	enum mdoct	 ntok;
 	enum margserr	 ac;
 	enum mdelim	 d;
@@ -863,17 +677,17 @@ in_line(MACRO_PROT_ARGS)
 	 */
 
 	switch (tok) {
-	case (MDOC_An):
+	case MDOC_An:
 		/* FALLTHROUGH */
-	case (MDOC_Ar):
+	case MDOC_Ar:
 		/* FALLTHROUGH */
-	case (MDOC_Fl):
+	case MDOC_Fl:
 		/* FALLTHROUGH */
-	case (MDOC_Mt):
+	case MDOC_Mt:
 		/* FALLTHROUGH */
-	case (MDOC_Nm):
+	case MDOC_Nm:
 		/* FALLTHROUGH */
-	case (MDOC_Pa):
+	case MDOC_Pa:
 		nc = 1;
 		break;
 	default:
@@ -881,126 +695,132 @@ in_line(MACRO_PROT_ARGS)
 		break;
 	}
 
-	for (arg = NULL;; ) {
-		la = *pos;
-		av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
-
-		if (ARGV_WORD == av) {
-			*pos = la;
-			break;
-		} 
-		if (ARGV_EOLN == av)
-			break;
-		if (ARGV_ARG == av)
-			continue;
-
-		mdoc_argv_free(arg);
-		return(0);
-	}
+	mdoc_argv(mdoc, line, tok, &arg, pos, buf);
 
+	d = DELIM_NONE;
+	firstarg = 1;
+	mayopen = 1;
 	for (cnt = scope = 0;; ) {
 		la = *pos;
 		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
 
-		if (ARGS_ERROR == ac)
-			return(0);
-		if (ARGS_EOLN == ac)
+		/*
+		 * At the end of a macro line,
+		 * opening delimiters do not suppress spacing.
+		 */
+
+		if (ac == ARGS_EOLN) {
+			if (d == DELIM_OPEN)
+				mdoc->last->flags &= ~MDOC_DELIMO;
 			break;
-		if (ARGS_PUNCT == ac)
+		}
+
+		/*
+		 * The rest of the macro line is only punctuation,
+		 * to be handled by append_delims().
+		 * If there were no other arguments,
+		 * do not allow the first one to suppress spacing,
+		 * even if it turns out to be a closing one.
+		 */
+
+		if (ac == ARGS_PUNCT) {
+			if (cnt == 0 && (nc == 0 || tok == MDOC_An))
+				mdoc->flags |= MDOC_NODELIMC;
 			break;
+		}
 
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+		ntok = (ac == ARGS_QWORD || (tok == MDOC_Fn && !cnt)) ?
+		    MDOC_MAX : lookup(mdoc, tok, line, la, p);
 
-		/* 
+		/*
 		 * In this case, we've located a submacro and must
 		 * execute it.  Close out scope, if open.  If no
 		 * elements have been generated, either create one (nc)
 		 * or raise a warning.
 		 */
 
-		if (MDOC_MAX != ntok) {
-			if (scope && ! rew_elem(mdoc, tok))
-				return(0);
-			if (nc && 0 == cnt) {
-				if ( ! mdoc_elem_alloc(mdoc, line,
-						ppos, tok, arg))
-					return(0);
-				if ( ! rew_last(mdoc, mdoc->last))
-					return(0);
-			} else if ( ! nc && 0 == cnt) {
+		if (ntok != MDOC_MAX) {
+			if (scope)
+				rew_elem(mdoc, tok);
+			if (nc && ! cnt) {
+				mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+				rew_last(mdoc, mdoc->last);
+			} else if ( ! nc && ! cnt) {
 				mdoc_argv_free(arg);
-				mdoc_pmsg(mdoc, line, ppos,
-					MANDOCERR_MACROEMPTY);
+				mandoc_msg(MANDOCERR_MACRO_EMPTY,
+				    mdoc->parse, line, ppos,
+				    mdoc_macronames[tok]);
 			}
+			mdoc_macro(mdoc, ntok, line, la, pos, buf);
+			if (nl)
+				append_delims(mdoc, line, pos, buf);
+			return;
+		}
 
-			if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
-				return(0);
-			if ( ! nl)
-				return(1);
-			return(append_delims(mdoc, line, pos, buf));
-		} 
-
-		/* 
+		/*
 		 * Non-quote-enclosed punctuation.  Set up our scope, if
 		 * a word; rewind the scope, if a delimiter; then append
-		 * the word. 
+		 * the word.
 		 */
 
-		d = ARGS_QWORD == ac ? DELIM_NONE : mdoc_isdelim(p);
+		d = ac == ARGS_QWORD ? DELIM_NONE : mdoc_isdelim(p);
 
 		if (DELIM_NONE != d) {
 			/*
 			 * If we encounter closing punctuation, no word
-			 * has been omitted, no scope is open, and we're
+			 * has been emitted, no scope is open, and we're
 			 * allowed to have an empty element, then start
-			 * a new scope.  `Ar', `Fl', and `Li', only do
-			 * this once per invocation.  There may be more
-			 * of these (all of them?).
+			 * a new scope.
 			 */
-			if (0 == cnt && (nc || MDOC_Li == tok) && 
-					DELIM_CLOSE == d && ! scope) {
-				if ( ! mdoc_elem_alloc(mdoc, line,
-						ppos, tok, arg))
-					return(0);
-				if (MDOC_Ar == tok || MDOC_Li == tok || 
-						MDOC_Fl == tok)
-					cnt++;
+			if ((d == DELIM_CLOSE ||
+			     (d == DELIM_MIDDLE && tok == MDOC_Fl)) &&
+			    !cnt && !scope && nc && mayopen) {
+				mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
 				scope = 1;
+				cnt++;
+				if (tok == MDOC_Nm)
+					mayopen = 0;
 			}
 			/*
 			 * Close out our scope, if one is open, before
 			 * any punctuation.
 			 */
-			if (scope && ! rew_elem(mdoc, tok))
-				return(0);
+			if (scope)
+				rew_elem(mdoc, tok);
 			scope = 0;
-		} else if ( ! scope) {
-			if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
-				return(0);
+			if (tok == MDOC_Fn)
+				mayopen = 0;
+		} else if (mayopen && !scope) {
+			mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
 			scope = 1;
+			cnt++;
 		}
 
-		if (DELIM_NONE == d)
-			cnt++;
+		dword(mdoc, line, la, p, d,
+		    MDOC_JOIN & mdoc_macros[tok].flags);
 
-		if ( ! dword(mdoc, line, la, p, d,
-		    MDOC_JOIN & mdoc_macros[tok].flags))
-			return(0);
+		/*
+		 * If the first argument is a closing delimiter,
+		 * do not suppress spacing before it.
+		 */
+
+		if (firstarg && d == DELIM_CLOSE && !nc)
+			mdoc->last->flags &= ~MDOC_DELIMC;
+		firstarg = 0;
 
 		/*
 		 * `Fl' macros have their scope re-opened with each new
 		 * word so that the `-' can be added to each one without
 		 * having to parse out spaces.
 		 */
-		if (scope && MDOC_Fl == tok) {
-			if ( ! rew_elem(mdoc, tok))
-				return(0);
+		if (scope && tok == MDOC_Fl) {
+			rew_elem(mdoc, tok);
 			scope = 0;
 		}
 	}
 
-	if (scope && ! rew_elem(mdoc, tok))
-		return(0);
+	if (scope)
+		rew_elem(mdoc, tok);
 
 	/*
 	 * If no elements have been collected and we're allowed to have
@@ -1008,122 +828,165 @@ in_line(MACRO_PROT_ARGS)
 	 * raise a warning.
 	 */
 
-	if (nc && 0 == cnt) {
-		if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
-			return(0);
-		if ( ! rew_last(mdoc, mdoc->last))
-			return(0);
-	} else if ( ! nc && 0 == cnt) {
-		mdoc_argv_free(arg);
-		mdoc_pmsg(mdoc, line, ppos, MANDOCERR_MACROEMPTY);
+	if ( ! cnt) {
+		if (nc) {
+			mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+			rew_last(mdoc, mdoc->last);
+		} else {
+			mdoc_argv_free(arg);
+			mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+			    line, ppos, mdoc_macronames[tok]);
+		}
 	}
-
-	if ( ! nl)
-		return(1);
-	return(append_delims(mdoc, line, pos, buf));
+	if (nl)
+		append_delims(mdoc, line, pos, buf);
 }
 
-
-static int
+static void
 blk_full(MACRO_PROT_ARGS)
 {
-	int		  la, nl, nparsed;
+	int		  la, nl, parsed;
 	struct mdoc_arg	 *arg;
-	struct mdoc_node *head; /* save of head macro */
-	struct mdoc_node *body; /* save of body macro */
+	struct mdoc_node *blk; /* Our own or a broken block. */
+	struct mdoc_node *head; /* Our own head. */
+	struct mdoc_node *body; /* Our own body. */
 	struct mdoc_node *n;
-	enum mdoc_type	  mtt;
-	enum mdoct	  ntok;
 	enum margserr	  ac, lac;
-	enum margverr	  av;
 	char		 *p;
 
 	nl = MDOC_NEWLINE & mdoc->flags;
 
-	/* Close out prior implicit scope. */
-
-	if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) {
-		if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
-			return(0);
-		if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
-			return(0);
+	if (buf[*pos] == '\0' && (tok == MDOC_Sh || tok == MDOC_Ss)) {
+		mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+		    line, ppos, mdoc_macronames[tok]);
+		return;
 	}
 
-	/*
-	 * This routine accommodates implicitly- and explicitly-scoped
-	 * macro openings.  Implicit ones first close out prior scope
-	 * (seen above).  Delay opening the head until necessary to
-	 * allow leading punctuation to print.  Special consideration
-	 * for `It -column', which has phrase-part syntax instead of
-	 * regular child nodes.
-	 */
+	if ( ! (mdoc_macros[tok].flags & MDOC_EXPLICIT)) {
 
-	for (arg = NULL;; ) {
-		la = *pos;
-		av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+		/* Here, tok is one of Sh Ss Nm Nd It. */
 
-		if (ARGV_WORD == av) {
-			*pos = la;
-			break;
-		} 
+		blk = NULL;
+		for (n = mdoc->last; n != NULL; n = n->parent) {
+			if (n->flags & MDOC_ENDED) {
+				if ( ! (n->flags & MDOC_VALID))
+					n->flags |= MDOC_BROKEN;
+				continue;
+			}
+			if (n->type != MDOC_BLOCK)
+				continue;
 
-		if (ARGV_EOLN == av)
-			break;
-		if (ARGV_ARG == av)
-			continue;
+			if (tok == MDOC_It && n->tok == MDOC_Bl) {
+				if (blk != NULL) {
+					mandoc_vmsg(MANDOCERR_BLK_BROKEN,
+					    mdoc->parse, line, ppos,
+					    "It breaks %s",
+					    mdoc_macronames[blk->tok]);
+					rew_pending(mdoc, blk);
+				}
+				break;
+			}
 
-		mdoc_argv_free(arg);
-		return(0);
+			if (mdoc_macros[n->tok].flags & MDOC_EXPLICIT) {
+				switch (tok) {
+				case MDOC_Sh:
+					/* FALLTHROUGH */
+				case MDOC_Ss:
+					mandoc_vmsg(MANDOCERR_BLK_BROKEN,
+					    mdoc->parse, line, ppos,
+					    "%s breaks %s",
+					    mdoc_macronames[tok],
+					    mdoc_macronames[n->tok]);
+					rew_pending(mdoc, n);
+					n = mdoc->last;
+					continue;
+				case MDOC_It:
+					/* Delay in case it's astray. */
+					blk = n;
+					continue;
+				default:
+					break;
+				}
+				break;
+			}
+
+			/* Here, n is one of Sh Ss Nm Nd It. */
+
+			if (tok != MDOC_Sh && (n->tok == MDOC_Sh ||
+			    (tok != MDOC_Ss && (n->tok == MDOC_Ss ||
+			     (tok != MDOC_It && n->tok == MDOC_It)))))
+				break;
+
+			/* Item breaking an explicit block. */
+
+			if (blk != NULL) {
+				mandoc_vmsg(MANDOCERR_BLK_BROKEN,
+				    mdoc->parse, line, ppos,
+				    "It breaks %s",
+				    mdoc_macronames[blk->tok]);
+				rew_pending(mdoc, blk);
+				blk = NULL;
+			}
+
+			/* Close out prior implicit scopes. */
+
+			rew_last(mdoc, n);
+		}
+
+		/* Skip items outside lists. */
+
+		if (tok == MDOC_It && (n == NULL || n->tok != MDOC_Bl)) {
+			mandoc_vmsg(MANDOCERR_IT_STRAY, mdoc->parse,
+			    line, ppos, "It %s", buf + *pos);
+			mdoc_elem_alloc(mdoc, line, ppos, MDOC_br, NULL);
+			rew_elem(mdoc, MDOC_br);
+			return;
+		}
 	}
 
-	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, arg))
-		return(0);
+	/*
+	 * This routine accommodates implicitly- and explicitly-scoped
+	 * macro openings.  Implicit ones first close out prior scope
+	 * (seen above).  Delay opening the head until necessary to
+	 * allow leading punctuation to print.  Special consideration
+	 * for `It -column', which has phrase-part syntax instead of
+	 * regular child nodes.
+	 */
 
+	mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+	blk = mdoc_block_alloc(mdoc, line, ppos, tok, arg);
 	head = body = NULL;
 
 	/*
 	 * Exception: Heads of `It' macros in `-diag' lists are not
 	 * parsed, even though `It' macros in general are parsed.
 	 */
-	nparsed = MDOC_It == tok &&
-		MDOC_Bl == mdoc->last->parent->tok &&
-		LIST_diag == mdoc->last->parent->norm->Bl.type;
+
+	parsed = tok != MDOC_It ||
+	    mdoc->last->parent->tok != MDOC_Bl ||
+	    mdoc->last->parent->norm->Bl.type != LIST_diag;
 
 	/*
 	 * The `Nd' macro has all arguments in its body: it's a hybrid
 	 * of block partial-explicit and full-implicit.  Stupid.
 	 */
 
-	if (MDOC_Nd == tok) {
-		if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
-			return(0);
-		head = mdoc->last;
-		if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
-			return(0);
-		if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-			return(0);
-		body = mdoc->last;
+	if (tok == MDOC_Nd) {
+		head = mdoc_head_alloc(mdoc, line, ppos, tok);
+		rew_last(mdoc, head);
+		body = mdoc_body_alloc(mdoc, line, ppos, tok);
 	}
 
-	if (MDOC_Bk == tok)
+	if (tok == MDOC_Bk)
 		mdoc->flags |= MDOC_KEEP;
 
-	ac = ARGS_ERROR;
-
-	for ( ; ; ) {
+	ac = ARGS_PEND;
+	for (;;) {
 		la = *pos;
-		/* Initialise last-phrase-type with ARGS_PEND. */
-		lac = ARGS_ERROR == ac ? ARGS_PEND : ac;
+		lac = ac;
 		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
-
-		if (ARGS_PUNCT == ac)
-			break;
-
-		if (ARGS_ERROR == ac)
-			return(0);
-
-		if (ARGS_EOLN == ac) {
-			if (ARGS_PPHRASE != lac && ARGS_PHRASE != lac)
+		if (ac == ARGS_EOLN) {
+			if (lac != ARGS_PPHRASE && lac != ARGS_PHRASE)
 				break;
 			/*
 			 * This is necessary: if the last token on a
@@ -1132,55 +995,56 @@ blk_full(MACRO_PROT_ARGS)
 			 * reopen our scope if the last parse was a
 			 * phrase or partial phrase.
 			 */
-			if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
-				return(0);
-			if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-				return(0);
-			body = mdoc->last;
+			if (body != NULL)
+				rew_last(mdoc, body);
+			body = mdoc_body_alloc(mdoc, line, ppos, tok);
+			break;
+		}
+		if (tok == MDOC_Bd || tok == MDOC_Bk) {
+			mandoc_vmsg(MANDOCERR_ARG_EXCESS,
+			    mdoc->parse, line, la, "%s ... %s",
+			    mdoc_macronames[tok], buf + la);
 			break;
 		}
+		if (tok == MDOC_Rs) {
+			mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse,
+			    line, la, "Rs %s", buf + la);
+			break;
+		}
+		if (ac == ARGS_PUNCT)
+			break;
 
-		/* 
+		/*
 		 * Emit leading punctuation (i.e., punctuation before
 		 * the MDOC_HEAD) for non-phrase types.
 		 */
 
-		if (NULL == head && 
-				ARGS_PEND != ac &&
-				ARGS_PHRASE != ac &&
-				ARGS_PPHRASE != ac &&
-				ARGS_QWORD != ac &&
-				DELIM_OPEN == mdoc_isdelim(p)) {
-			if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
-				return(0);
+		if (head == NULL &&
+		    ac != ARGS_PEND &&
+		    ac != ARGS_PHRASE &&
+		    ac != ARGS_PPHRASE &&
+		    ac != ARGS_QWORD &&
+		    mdoc_isdelim(p) == DELIM_OPEN) {
+			dword(mdoc, line, la, p, DELIM_OPEN, 0);
 			continue;
 		}
 
 		/* Open a head if one hasn't been opened. */
 
-		if (NULL == head) {
-			if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
-				return(0);
-			head = mdoc->last;
-		}
+		if (head == NULL)
+			head = mdoc_head_alloc(mdoc, line, ppos, tok);
+
+		if (ac == ARGS_PHRASE ||
+		    ac == ARGS_PEND ||
+		    ac == ARGS_PPHRASE) {
 
-		if (ARGS_PHRASE == ac || 
-				ARGS_PEND == ac ||
-				ARGS_PPHRASE == ac) {
 			/*
 			 * If we haven't opened a body yet, rewind the
 			 * head; if we have, rewind that instead.
 			 */
 
-			mtt = body ? MDOC_BODY : MDOC_HEAD;
-			if ( ! rew_sub(mtt, mdoc, tok, line, ppos))
-				return(0);
-			
-			/* Then allocate our body context. */
-
-			if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-				return(0);
-			body = mdoc->last;
+			rew_last(mdoc, body == NULL ? head : body);
+			body = mdoc_body_alloc(mdoc, line, ppos, tok);
 
 			/*
 			 * Process phrases: set whether we're in a
@@ -1188,88 +1052,65 @@ blk_full(MACRO_PROT_ARGS)
 			 * then call down into the phrase parser.
 			 */
 
-			if (ARGS_PPHRASE == ac)
+			if (ac == ARGS_PPHRASE)
 				mdoc->flags |= MDOC_PPHRASE;
-			if (ARGS_PEND == ac && ARGS_PPHRASE == lac)
+			if (ac == ARGS_PEND && lac == ARGS_PPHRASE)
 				mdoc->flags |= MDOC_PPHRASE;
-
-			if ( ! phrase(mdoc, line, la, buf))
-				return(0);
-
+			parse_rest(mdoc, MDOC_MAX, line, &la, buf);
 			mdoc->flags &= ~MDOC_PPHRASE;
 			continue;
 		}
 
-		ntok = nparsed || ARGS_QWORD == ac ? 
-			MDOC_MAX : lookup(tok, p);
-
-		if (MDOC_MAX == ntok) {
-			if ( ! dword(mdoc, line, la, p, DELIM_MAX,
-			    MDOC_JOIN & mdoc_macros[tok].flags))
-				return(0);
-			continue;
-		}
-
-		if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
-			return(0);
-		break;
-	}
-
-	if (NULL == head) {
-		if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
-			return(0);
-		head = mdoc->last;
+		if (macro_or_word(mdoc, tok, line, la, pos, buf, parsed))
+			break;
 	}
-	
-	if (nl && ! append_delims(mdoc, line, pos, buf))
-		return(0);
 
-	/* If we've already opened our body, exit now. */
-
-	if (NULL != body)
+	if (blk->flags & MDOC_VALID)
+		return;
+	if (head == NULL)
+		head = mdoc_head_alloc(mdoc, line, ppos, tok);
+	if (nl && tok != MDOC_Bd && tok != MDOC_Bl && tok != MDOC_Rs)
+		append_delims(mdoc, line, pos, buf);
+	if (body != NULL)
 		goto out;
 
 	/*
 	 * If there is an open (i.e., unvalidated) sub-block requiring
 	 * explicit close-out, postpone switching the current block from
-	 * head to body until the rew_sub() call closing out that
+	 * head to body until the rew_pending() call closing out that
 	 * sub-block.
 	 */
 	for (n = mdoc->last; n && n != head; n = n->parent) {
-		if (MDOC_BLOCK == n->type && 
-				MDOC_EXPLICIT & mdoc_macros[n->tok].flags &&
-				! (MDOC_VALID & n->flags)) {
-			n->pending = head;
-			return(1);
+		if (n->flags & MDOC_ENDED) {
+			if ( ! (n->flags & MDOC_VALID))
+				n->flags |= MDOC_BROKEN;
+			continue;
+		}
+		if (n->type == MDOC_BLOCK &&
+		    mdoc_macros[n->tok].flags & MDOC_EXPLICIT) {
+			n->flags = MDOC_BROKEN;
+			head->flags = MDOC_ENDED;
 		}
 	}
+	if (head->flags & MDOC_ENDED)
+		return;
 
 	/* Close out scopes to remain in a consistent state. */
 
-	if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
-		return(0);
-	if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-		return(0);
-
+	rew_last(mdoc, head);
+	body = mdoc_body_alloc(mdoc, line, ppos, tok);
 out:
-	if ( ! (MDOC_FREECOL & mdoc->flags))
-		return(1);
-
-	if ( ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
-		return(0);
-	if ( ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
-		return(0);
-
-	mdoc->flags &= ~MDOC_FREECOL;
-	return(1);
+	if (mdoc->flags & MDOC_FREECOL) {
+		rew_last(mdoc, body);
+		rew_last(mdoc, blk);
+		mdoc->flags &= ~MDOC_FREECOL;
+	}
 }
 
-
-static int
+static void
 blk_part_imp(MACRO_PROT_ARGS)
 {
 	int		  la, nl;
-	enum mdoct	  ntok;
 	enum margserr	  ac;
 	char		 *p;
 	struct mdoc_node *blk; /* saved block context */
@@ -1287,15 +1128,8 @@ blk_part_imp(MACRO_PROT_ARGS)
 	 * or more closing punctuation nodes.
 	 */
 
-	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL))
-		return(0);
-
-	blk = mdoc->last;
-
-	if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
-		return(0);
-	if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
-		return(0);
+	blk = mdoc_block_alloc(mdoc, line, ppos, tok, NULL);
+	rew_last(mdoc, mdoc_head_alloc(mdoc, line, ppos, tok));
 
 	/*
 	 * Open the body scope "on-demand", that is, after we've
@@ -1306,129 +1140,74 @@ blk_part_imp(MACRO_PROT_ARGS)
 	for (body = NULL; ; ) {
 		la = *pos;
 		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
-
-		if (ARGS_ERROR == ac)
-			return(0);
-		if (ARGS_EOLN == ac)
-			break;
-		if (ARGS_PUNCT == ac)
+		if (ac == ARGS_EOLN || ac == ARGS_PUNCT)
 			break;
 
-		if (NULL == body && ARGS_QWORD != ac &&
-				DELIM_OPEN == mdoc_isdelim(p)) {
-			if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
-				return(0);
+		if (body == NULL && ac != ARGS_QWORD &&
+		    mdoc_isdelim(p) == DELIM_OPEN) {
+			dword(mdoc, line, la, p, DELIM_OPEN, 0);
 			continue;
 		}
 
-		if (NULL == body) {
-		       if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-			       return(0);
-			body = mdoc->last;
-		}
-
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
-
-		if (MDOC_MAX == ntok) {
-			if ( ! dword(mdoc, line, la, p, DELIM_MAX,
-			    MDOC_JOIN & mdoc_macros[tok].flags))
-				return(0);
-			continue;
-		}
-
-		if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
-			return(0);
-		break;
-	}
-
-	/* Clean-ups to leave in a consistent state. */
-
-	if (NULL == body) {
-		if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-			return(0);
-		body = mdoc->last;
-	}
-
-	for (n = body->child; n && n->next; n = n->next)
-		/* Do nothing. */ ;
-	
-	/* 
-	 * End of sentence spacing: if the last node is a text node and
-	 * has a trailing period, then mark it as being end-of-sentence.
-	 */
-
-	if (n && MDOC_TEXT == n->type && n->string)
-		if (mandoc_eos(n->string, strlen(n->string), 1))
-			n->flags |= MDOC_EOS;
+		if (body == NULL)
+			body = mdoc_body_alloc(mdoc, line, ppos, tok);
 
-	/* Up-propagate the end-of-space flag. */
-
-	if (n && (MDOC_EOS & n->flags)) {
-		body->flags |= MDOC_EOS;
-		body->parent->flags |= MDOC_EOS;
+		if (macro_or_word(mdoc, tok, line, la, pos, buf, 1))
+			break;
 	}
+	if (body == NULL)
+		body = mdoc_body_alloc(mdoc, line, ppos, tok);
 
 	/*
 	 * If there is an open sub-block requiring explicit close-out,
-	 * postpone closing out the current block
-	 * until the rew_sub() call closing out the sub-block.
+	 * postpone closing out the current block until the
+	 * rew_pending() call closing out the sub-block.
 	 */
+
 	for (n = mdoc->last; n && n != body && n != blk->parent;
-			n = n->parent) {
-		if (MDOC_BLOCK == n->type &&
-		    MDOC_EXPLICIT & mdoc_macros[n->tok].flags &&
-		    ! (MDOC_VALID & n->flags)) {
-			make_pending(n, tok, mdoc, line, ppos);
-			if ( ! mdoc_endbody_alloc(mdoc, line, ppos,
-			    tok, body, ENDBODY_NOSPACE))
-				return(0);
-			return(1);
+	     n = n->parent) {
+		if (n->flags & MDOC_ENDED) {
+			if ( ! (n->flags & MDOC_VALID))
+				n->flags |= MDOC_BROKEN;
+			continue;
+		}
+		if (n->type == MDOC_BLOCK &&
+		    mdoc_macros[n->tok].flags & MDOC_EXPLICIT) {
+			n->flags |= MDOC_BROKEN;
+			if ( ! (body->flags & MDOC_ENDED)) {
+				mandoc_vmsg(MANDOCERR_BLK_NEST,
+				    mdoc->parse, line, ppos,
+				    "%s breaks %s", mdoc_macronames[tok],
+				    mdoc_macronames[n->tok]);
+				mdoc_endbody_alloc(mdoc, line, ppos,
+				    tok, body, ENDBODY_NOSPACE);
+			}
 		}
 	}
+	assert(n == body);
+	if (body->flags & MDOC_ENDED)
+		return;
 
-	/* 
-	 * If we can't rewind to our body, then our scope has already
-	 * been closed by another macro (like `Oc' closing `Op').  This
-	 * is ugly behaviour nodding its head to OpenBSD's overwhelming
-	 * crufty use of `Op' breakage.
-	 */
-	if (n != body)
-		mandoc_vmsg(MANDOCERR_SCOPENEST, mdoc->parse, line, ppos, 
-				"%s broken", mdoc_macronames[tok]);
-
-	if (n && ! rew_sub(MDOC_BODY, mdoc, tok, line, ppos))
-		return(0);
-
-	/* Standard appending of delimiters. */
-
-	if (nl && ! append_delims(mdoc, line, pos, buf))
-		return(0);
-
-	/* Rewind scope, if applicable. */
-
-	if (n && ! rew_sub(MDOC_BLOCK, mdoc, tok, line, ppos))
-		return(0);
+	rew_last(mdoc, body);
+	if (nl)
+		append_delims(mdoc, line, pos, buf);
+	rew_pending(mdoc, blk);
 
 	/* Move trailing .Ns out of scope. */
 
 	for (n = body->child; n && n->next; n = n->next)
 		/* Do nothing. */ ;
-	if (n && MDOC_Ns == n->tok)
+	if (n && n->tok == MDOC_Ns)
 		mdoc_node_relink(mdoc, n);
-
-	return(1);
 }
 
-
-static int
+static void
 blk_part_exp(MACRO_PROT_ARGS)
 {
 	int		  la, nl;
 	enum margserr	  ac;
 	struct mdoc_node *head; /* keep track of head */
-	struct mdoc_node *body; /* keep track of body */
 	char		 *p;
-	enum mdoct	  ntok;
 
 	nl = MDOC_NEWLINE & mdoc->flags;
 
@@ -1438,108 +1217,57 @@ blk_part_exp(MACRO_PROT_ARGS)
 	 * case of `Eo'); and a body that may be empty.
 	 */
 
-	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL))
-		return(0); 
-
-	for (head = body = NULL; ; ) {
+	mdoc_block_alloc(mdoc, line, ppos, tok, NULL);
+	head = NULL;
+	for (;;) {
 		la = *pos;
 		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
-
-		if (ARGS_ERROR == ac)
-			return(0);
-		if (ARGS_PUNCT == ac)
-			break;
-		if (ARGS_EOLN == ac)
+		if (ac == ARGS_PUNCT || ac == ARGS_EOLN)
 			break;
 
 		/* Flush out leading punctuation. */
 
-		if (NULL == head && ARGS_QWORD != ac &&
-				DELIM_OPEN == mdoc_isdelim(p)) {
-			assert(NULL == body);
-			if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
-				return(0);
+		if (head == NULL && ac != ARGS_QWORD &&
+		    mdoc_isdelim(p) == DELIM_OPEN) {
+			dword(mdoc, line, la, p, DELIM_OPEN, 0);
 			continue;
 		}
 
-		if (NULL == head) {
-			assert(NULL == body);
-			if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
-				return(0);
-			head = mdoc->last;
-		}
-
-		/*
-		 * `Eo' gobbles any data into the head, but most other
-		 * macros just immediately close out and begin the body.
-		 */
-
-		if (NULL == body) {
-			assert(head);
-			/* No check whether it's a macro! */
-			if (MDOC_Eo == tok)
-				if ( ! dword(mdoc, line, la, p, DELIM_MAX, 0))
-					return(0);
-
-			if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
-				return(0);
-			if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-				return(0);
-			body = mdoc->last;
-
-			if (MDOC_Eo == tok)
+		if (head == NULL) {
+			head = mdoc_head_alloc(mdoc, line, ppos, tok);
+			if (tok == MDOC_Eo)  /* Not parsed. */
+				dword(mdoc, line, la, p, DELIM_MAX, 0);
+			rew_last(mdoc, head);
+			mdoc_body_alloc(mdoc, line, ppos, tok);
+			if (tok == MDOC_Eo)
 				continue;
 		}
 
-		assert(NULL != head && NULL != body);
-
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
-
-		if (MDOC_MAX == ntok) {
-			if ( ! dword(mdoc, line, la, p, DELIM_MAX,
-			    MDOC_JOIN & mdoc_macros[tok].flags))
-				return(0);
-			continue;
-		}
-
-		if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
-			return(0);
-		break;
+		if (macro_or_word(mdoc, tok, line, la, pos, buf, 1))
+			break;
 	}
 
 	/* Clean-up to leave in a consistent state. */
 
-	if (NULL == head)
-		if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
-			return(0);
-
-	if (NULL == body) {
-		if ( ! rew_sub(MDOC_HEAD, mdoc, tok, line, ppos))
-			return(0);
-		if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
-			return(0);
+	if (head == NULL) {
+		rew_last(mdoc, mdoc_head_alloc(mdoc, line, ppos, tok));
+		mdoc_body_alloc(mdoc, line, ppos, tok);
 	}
-
-	/* Standard appending of delimiters. */
-
-	if ( ! nl)
-		return(1);
-	return(append_delims(mdoc, line, pos, buf));
+	if (nl)
+		append_delims(mdoc, line, pos, buf);
 }
 
-
-/* ARGSUSED */
-static int
+static void
 in_line_argn(MACRO_PROT_ARGS)
 {
-	int		 la, flushed, j, maxargs, nl;
-	enum margserr	 ac;
-	enum margverr	 av;
 	struct mdoc_arg	*arg;
 	char		*p;
+	enum margserr	 ac;
 	enum mdoct	 ntok;
+	int		 state; /* arg#; -1: not yet open; -2: closed */
+	int		 la, maxargs, nl;
 
-	nl = MDOC_NEWLINE & mdoc->flags;
+	nl = mdoc->flags & MDOC_NEWLINE;
 
 	/*
 	 * A line macro that has a fixed number of arguments (maxargs).
@@ -1550,18 +1278,18 @@ in_line_argn(MACRO_PROT_ARGS)
 	 */
 
 	switch (tok) {
-	case (MDOC_Ap):
+	case MDOC_Ap:
 		/* FALLTHROUGH */
-	case (MDOC_No):
+	case MDOC_Ns:
 		/* FALLTHROUGH */
-	case (MDOC_Ns):
-		/* FALLTHROUGH */
-	case (MDOC_Ux):
+	case MDOC_Ux:
 		maxargs = 0;
 		break;
-	case (MDOC_Bx):
+	case MDOC_Bx:
+		/* FALLTHROUGH */
+	case MDOC_Es:
 		/* FALLTHROUGH */
-	case (MDOC_Xr):
+	case MDOC_Xr:
 		maxargs = 2;
 		break;
 	default:
@@ -1569,286 +1297,176 @@ in_line_argn(MACRO_PROT_ARGS)
 		break;
 	}
 
-	for (arg = NULL; ; ) {
-		la = *pos;
-		av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+	mdoc_argv(mdoc, line, tok, &arg, pos, buf);
 
-		if (ARGV_WORD == av) {
-			*pos = la;
-			break;
-		} 
+	state = -1;
+	p = NULL;
+	for (;;) {
+		la = *pos;
+		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
 
-		if (ARGV_EOLN == av)
-			break;
-		if (ARGV_ARG == av)
+		if (ac == ARGS_WORD && state == -1 &&
+		    ! (mdoc_macros[tok].flags & MDOC_IGNDELIM) &&
+		    mdoc_isdelim(p) == DELIM_OPEN) {
+			dword(mdoc, line, la, p, DELIM_OPEN, 0);
 			continue;
+		}
 
-		mdoc_argv_free(arg);
-		return(0);
-	}
-
-	for (flushed = j = 0; ; ) {
-		la = *pos;
-		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
+		if (state == -1 && tok != MDOC_In &&
+		    tok != MDOC_St && tok != MDOC_Xr) {
+			mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+			state = 0;
+		}
 
-		if (ARGS_ERROR == ac)
-			return(0);
-		if (ARGS_PUNCT == ac)
-			break;
-		if (ARGS_EOLN == ac)
+		if (ac == ARGS_PUNCT || ac == ARGS_EOLN) {
+			if (abs(state) < 2 && tok == MDOC_Pf)
+				mandoc_vmsg(MANDOCERR_PF_SKIP,
+				    mdoc->parse, line, ppos, "Pf %s",
+				    p == NULL ? "at eol" : p);
 			break;
+		}
 
-		if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && 
-				ARGS_QWORD != ac && 0 == j && 
-				DELIM_OPEN == mdoc_isdelim(p)) {
-			if ( ! dword(mdoc, line, la, p, DELIM_OPEN, 0))
-				return(0);
-			continue;
-		} else if (0 == j)
-		       if ( ! mdoc_elem_alloc(mdoc, line, la, tok, arg))
-			       return(0);
-
-		if (j == maxargs && ! flushed) {
-			if ( ! rew_elem(mdoc, tok))
-				return(0);
-			flushed = 1;
+		if (state == maxargs) {
+			rew_elem(mdoc, tok);
+			state = -2;
 		}
 
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+		ntok = (ac == ARGS_QWORD || (tok == MDOC_Pf && state == 0)) ?
+		    MDOC_MAX : lookup(mdoc, tok, line, la, p);
 
-		if (MDOC_MAX != ntok) {
-			if ( ! flushed && ! rew_elem(mdoc, tok))
-				return(0);
-			flushed = 1;
-			if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
-				return(0);
-			j++;
+		if (ntok != MDOC_MAX) {
+			if (state >= 0) {
+				rew_elem(mdoc, tok);
+				state = -2;
+			}
+			mdoc_macro(mdoc, ntok, line, la, pos, buf);
 			break;
 		}
 
-		if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) &&
-				ARGS_QWORD != ac &&
-				! flushed &&
-				DELIM_NONE != mdoc_isdelim(p)) {
-			if ( ! rew_elem(mdoc, tok))
-				return(0);
-			flushed = 1;
+		if (ac == ARGS_QWORD ||
+		    mdoc_macros[tok].flags & MDOC_IGNDELIM ||
+		    mdoc_isdelim(p) == DELIM_NONE) {
+			if (state == -1) {
+				mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+				state = 1;
+			} else if (state >= 0)
+				state++;
+		} else if (state >= 0) {
+			rew_elem(mdoc, tok);
+			state = -2;
 		}
 
-		if ( ! dword(mdoc, line, la, p, DELIM_MAX,
-		    MDOC_JOIN & mdoc_macros[tok].flags))
-			return(0);
-		j++;
+		dword(mdoc, line, la, p, DELIM_MAX,
+		    MDOC_JOIN & mdoc_macros[tok].flags);
 	}
 
-	if (0 == j && ! mdoc_elem_alloc(mdoc, line, la, tok, arg))
-	       return(0);
-
-	/* Close out in a consistent state. */
+	if (state == -1) {
+		mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+		    line, ppos, mdoc_macronames[tok]);
+		return;
+	}
 
-	if ( ! flushed && ! rew_elem(mdoc, tok))
-		return(0);
-	if ( ! nl)
-		return(1);
-	return(append_delims(mdoc, line, pos, buf));
+	if (state == 0 && tok == MDOC_Pf)
+		append_delims(mdoc, line, pos, buf);
+	if (state >= 0)
+		rew_elem(mdoc, tok);
+	if (nl)
+		append_delims(mdoc, line, pos, buf);
 }
 
-
-static int
+static void
 in_line_eoln(MACRO_PROT_ARGS)
 {
-	int		 la;
-	enum margserr	 ac;
-	enum margverr	 av;
-	struct mdoc_arg	*arg;
-	char		*p;
-	enum mdoct	 ntok;
-
-	assert( ! (MDOC_PARSED & mdoc_macros[tok].flags));
-
-	if (tok == MDOC_Pp)
-		rew_sub(MDOC_BLOCK, mdoc, MDOC_Nm, line, ppos);
+	struct mdoc_node	*n;
+	struct mdoc_arg		*arg;
 
-	/* Parse macro arguments. */
-
-	for (arg = NULL; ; ) {
-		la = *pos;
-		av = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
-
-		if (ARGV_WORD == av) {
-			*pos = la;
-			break;
-		}
-		if (ARGV_EOLN == av) 
-			break;
-		if (ARGV_ARG == av)
-			continue;
-
-		mdoc_argv_free(arg);
-		return(0);
+	if ((tok == MDOC_Pp || tok == MDOC_Lp) &&
+	    ! (mdoc->flags & MDOC_SYNOPSIS)) {
+		n = mdoc->last;
+		if (mdoc->next == MDOC_NEXT_SIBLING)
+			n = n->parent;
+		if (n->tok == MDOC_Nm)
+			rew_last(mdoc, mdoc->last->parent);
 	}
 
-	/* Open element scope. */
+	if (buf[*pos] == '\0' &&
+	    (tok == MDOC_Fd || mdoc_macronames[tok][0] == '%')) {
+		mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+		    line, ppos, mdoc_macronames[tok]);
+		return;
+	}
 
-	if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
-		return(0);
+	mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+	mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+	if (parse_rest(mdoc, tok, line, pos, buf))
+		return;
+	rew_elem(mdoc, tok);
+}
 
-	/* Parse argument terms. */
+/*
+ * The simplest argument parser available: Parse the remaining
+ * words until the end of the phrase or line and return 0
+ * or until the next macro, call that macro, and return 1.
+ */
+static int
+parse_rest(struct mdoc *mdoc, enum mdoct tok, int line, int *pos, char *buf)
+{
+	int		 la;
 
 	for (;;) {
 		la = *pos;
-		ac = mdoc_args(mdoc, line, pos, buf, tok, &p);
-
-		if (ARGS_ERROR == ac)
+		if (mdoc_args(mdoc, line, pos, buf, tok, NULL) == ARGS_EOLN)
 			return(0);
-		if (ARGS_EOLN == ac)
-			break;
-
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
-
-		if (MDOC_MAX == ntok) {
-			if ( ! dword(mdoc, line, la, p, DELIM_MAX,
-			    MDOC_JOIN & mdoc_macros[tok].flags))
-				return(0);
-			continue;
-		}
-
-		if ( ! rew_elem(mdoc, tok))
-			return(0);
-		return(mdoc_macro(mdoc, ntok, line, la, pos, buf));
+		if (macro_or_word(mdoc, tok, line, la, pos, buf, 1))
+			return(1);
 	}
-
-	/* Close out (no delimiters). */
-
-	return(rew_elem(mdoc, tok));
 }
 
-
-/* ARGSUSED */
-static int
+static void
 ctx_synopsis(MACRO_PROT_ARGS)
 {
-	int		 nl;
-
-	nl = MDOC_NEWLINE & mdoc->flags;
 
-	/* If we're not in the SYNOPSIS, go straight to in-line. */
-	if ( ! (MDOC_SYNOPSIS & mdoc->flags))
-		return(in_line(mdoc, tok, line, ppos, pos, buf));
-
-	/* If we're a nested call, same place. */
-	if ( ! nl)
-		return(in_line(mdoc, tok, line, ppos, pos, buf));
-
-	/*
-	 * XXX: this will open a block scope; however, if later we end
-	 * up formatting the block scope, then child nodes will inherit
-	 * the formatting.  Be careful.
-	 */
-	if (MDOC_Nm == tok)
-		return(blk_full(mdoc, tok, line, ppos, pos, buf));
-	assert(MDOC_Vt == tok);
-	return(blk_part_imp(mdoc, tok, line, ppos, pos, buf));
-}
-
-
-/* ARGSUSED */
-static int
-obsolete(MACRO_PROT_ARGS)
-{
-
-	mdoc_pmsg(mdoc, line, ppos, MANDOCERR_MACROOBS);
-	return(1);
+	if (~mdoc->flags & (MDOC_SYNOPSIS | MDOC_NEWLINE))
+		in_line(mdoc, tok, line, ppos, pos, buf);
+	else if (tok == MDOC_Nm)
+		blk_full(mdoc, tok, line, ppos, pos, buf);
+	else {
+		assert(tok == MDOC_Vt);
+		blk_part_imp(mdoc, tok, line, ppos, pos, buf);
+	}
 }
 
-
 /*
  * Phrases occur within `Bl -column' entries, separated by `Ta' or tabs.
  * They're unusual because they're basically free-form text until a
  * macro is encountered.
  */
-static int
-phrase(struct mdoc *mdoc, int line, int ppos, char *buf)
+static void
+phrase_ta(MACRO_PROT_ARGS)
 {
-	int		 la, pos;
-	enum margserr	 ac;
-	enum mdoct	 ntok;
-	char		*p;
-
-	for (pos = ppos; ; ) {
-		la = pos;
-
-		ac = mdoc_zargs(mdoc, line, &pos, buf, &p);
-
-		if (ARGS_ERROR == ac)
-			return(0);
-		if (ARGS_EOLN == ac)
-			break;
+	struct mdoc_node *body, *n;
 
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p);
+	/* Make sure we are in a column list or ignore this macro. */
 
-		if (MDOC_MAX == ntok) {
-			if ( ! dword(mdoc, line, la, p, DELIM_MAX, 1))
-				return(0);
+	body = NULL;
+	for (n = mdoc->last; n != NULL; n = n->parent) {
+		if (n->flags & MDOC_ENDED)
 			continue;
-		}
-
-		if ( ! mdoc_macro(mdoc, ntok, line, la, &pos, buf))
-			return(0);
-		return(append_delims(mdoc, line, &pos, buf));
+		if (n->tok == MDOC_It && n->type == MDOC_BODY)
+			body = n;
+		if (n->tok == MDOC_Bl)
+			break;
 	}
 
-	return(1);
-}
-
-
-/* ARGSUSED */
-static int
-phrase_ta(MACRO_PROT_ARGS)
-{
-	struct mdoc_node *n;
-	int		  la;
-	enum mdoct	  ntok;
-	enum margserr	  ac;
-	char		 *p;
-
-	/* Make sure we are in a column list or ignore this macro. */
-	n = mdoc->last;
-	while (NULL != n && MDOC_Bl != n->tok)
-		n = n->parent;
-	if (NULL == n || LIST_column != n->norm->Bl.type) {
-		mdoc_pmsg(mdoc, line, ppos, MANDOCERR_STRAYTA);
-		return(1);
+	if (n == NULL || n->norm->Bl.type != LIST_column) {
+		mandoc_msg(MANDOCERR_TA_STRAY, mdoc->parse,
+		    line, ppos, "Ta");
+		return;
 	}
 
 	/* Advance to the next column. */
-	if ( ! rew_sub(MDOC_BODY, mdoc, MDOC_It, line, ppos))
-		return(0);
-	if ( ! mdoc_body_alloc(mdoc, line, ppos, MDOC_It))
-		return(0);
-
-	for (;;) {
-		la = *pos;
-		ac = mdoc_zargs(mdoc, line, pos, buf, &p);
-
-		if (ARGS_ERROR == ac)
-			return(0);
-		if (ARGS_EOLN == ac)
-			break;
-
-		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p);
-
-		if (MDOC_MAX == ntok) {
-			if ( ! dword(mdoc, line, la, p, DELIM_MAX,
-			    MDOC_JOIN & mdoc_macros[tok].flags))
-				return(0);
-			continue;
-		}
-
-		if ( ! mdoc_macro(mdoc, ntok, line, la, pos, buf))
-			return(0);
-		return(append_delims(mdoc, line, pos, buf));
-	}
 
-	return(1);
+	rew_last(mdoc, body);
+	mdoc_body_alloc(mdoc, line, ppos, MDOC_It);
+	parse_rest(mdoc, MDOC_MAX, line, pos, buf);
 }
diff --git a/usr/src/cmd/mandoc/mdoc_man.c b/usr/src/cmd/mandoc/mdoc_man.c
index 6ee8b3abf4..9c086a576c 100644
--- a/usr/src/cmd/mandoc/mdoc_man.c
+++ b/usr/src/cmd/mandoc/mdoc_man.c
@@ -1,6 +1,6 @@
-/*	$Id: mdoc_man.c,v 1.57 2013/12/25 22:00:45 schwarze Exp $ */
+/*	$Id: mdoc_man.c,v 1.88 2015/02/17 20:37:17 schwarze Exp $ */
 /*
- * Copyright (c) 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2011-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,22 +14,22 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "out.h"
 #include "man.h"
 #include "mdoc.h"
 #include "main.h"
 
-#define	DECL_ARGS const struct mdoc_meta *meta, \
-		  const struct mdoc_node *n
+#define	DECL_ARGS const struct mdoc_meta *meta, struct mdoc_node *n
 
 struct	manact {
 	int		(*cond)(DECL_ARGS); /* DON'T run actions */
@@ -45,11 +45,13 @@ static  void	  font_push(char);
 static	void	  font_pop(void);
 static	void	  mid_it(void);
 static	void	  post__t(DECL_ARGS);
+static	void	  post_aq(DECL_ARGS);
 static	void	  post_bd(DECL_ARGS);
 static	void	  post_bf(DECL_ARGS);
 static	void	  post_bk(DECL_ARGS);
 static	void	  post_bl(DECL_ARGS);
 static	void	  post_dl(DECL_ARGS);
+static	void	  post_en(DECL_ARGS);
 static	void	  post_enc(DECL_ARGS);
 static	void	  post_eo(DECL_ARGS);
 static	void	  post_fa(DECL_ARGS);
@@ -70,6 +72,7 @@ static	void	  post_vt(DECL_ARGS);
 static	int	  pre__t(DECL_ARGS);
 static	int	  pre_an(DECL_ARGS);
 static	int	  pre_ap(DECL_ARGS);
+static	int	  pre_aq(DECL_ARGS);
 static	int	  pre_bd(DECL_ARGS);
 static	int	  pre_bf(DECL_ARGS);
 static	int	  pre_bk(DECL_ARGS);
@@ -77,8 +80,12 @@ static	int	  pre_bl(DECL_ARGS);
 static	int	  pre_br(DECL_ARGS);
 static	int	  pre_bx(DECL_ARGS);
 static	int	  pre_dl(DECL_ARGS);
+static	int	  pre_en(DECL_ARGS);
 static	int	  pre_enc(DECL_ARGS);
 static	int	  pre_em(DECL_ARGS);
+static	int	  pre_skip(DECL_ARGS);
+static	int	  pre_eo(DECL_ARGS);
+static	int	  pre_ex(DECL_ARGS);
 static	int	  pre_fa(DECL_ARGS);
 static	int	  pre_fd(DECL_ARGS);
 static	int	  pre_fl(DECL_ARGS);
@@ -89,11 +96,13 @@ static	int	  pre_in(DECL_ARGS);
 static	int	  pre_it(DECL_ARGS);
 static	int	  pre_lk(DECL_ARGS);
 static	int	  pre_li(DECL_ARGS);
+static	int	  pre_ll(DECL_ARGS);
 static	int	  pre_nm(DECL_ARGS);
 static	int	  pre_no(DECL_ARGS);
 static	int	  pre_ns(DECL_ARGS);
 static	int	  pre_pp(DECL_ARGS);
 static	int	  pre_rs(DECL_ARGS);
+static	int	  pre_rv(DECL_ARGS);
 static	int	  pre_sm(DECL_ARGS);
 static	int	  pre_sp(DECL_ARGS);
 static	int	  pre_sect(DECL_ARGS);
@@ -105,9 +114,9 @@ static	int	  pre_xr(DECL_ARGS);
 static	void	  print_word(const char *);
 static	void	  print_line(const char *, int);
 static	void	  print_block(const char *, int);
-static	void	  print_offs(const char *);
-static	void	  print_width(const char *,
-				const struct mdoc_node *, size_t);
+static	void	  print_offs(const char *, int);
+static	void	  print_width(const struct mdoc_bl *,
+			const struct mdoc_node *);
 static	void	  print_count(int *);
 static	void	  print_node(DECL_ARGS);
 
@@ -134,9 +143,7 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, pre_li, post_font, NULL, NULL }, /* Dv */
 	{ NULL, pre_li, post_font, NULL, NULL }, /* Er */
 	{ NULL, pre_li, post_font, NULL, NULL }, /* Ev */
-	{ NULL, pre_enc, post_enc, "The \\fB",
-	    "\\fP\nutility exits 0 on success, and >0 if an error occurs."
-	    }, /* Ex */
+	{ NULL, pre_ex, NULL, NULL, NULL }, /* Ex */
 	{ NULL, pre_fa, post_fa, NULL, NULL }, /* Fa */
 	{ NULL, pre_fd, post_fd, NULL, NULL }, /* Fd */
 	{ NULL, pre_fl, post_fl, NULL, NULL }, /* Fl */
@@ -148,13 +155,9 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ cond_head, pre_enc, NULL, "\\- ", NULL }, /* Nd */
 	{ NULL, pre_nm, post_nm, NULL, NULL }, /* Nm */
 	{ cond_body, pre_enc, post_enc, "[", "]" }, /* Op */
-	{ NULL, NULL, NULL, NULL, NULL }, /* Ot */
+	{ NULL, pre_ft, post_font, NULL, NULL }, /* Ot */
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Pa */
-	{ NULL, pre_enc, post_enc, "The \\fB",
-		"\\fP\nfunction returns the value 0 if successful;\n"
-		"otherwise the value -1 is returned and the global\n"
-		"variable \\fIerrno\\fP is set to indicate the error."
-		}, /* Rv */
+	{ NULL, pre_rv, NULL, NULL, NULL }, /* Rv */
 	{ NULL, NULL, NULL, NULL, NULL }, /* St */
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Va */
 	{ NULL, pre_vt, post_vt, NULL, NULL }, /* Vt */
@@ -171,8 +174,8 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, pre__t, post__t, NULL, NULL }, /* %T */
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %V */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ac */
-	{ cond_body, pre_enc, post_enc, "<", ">" }, /* Ao */
-	{ cond_body, pre_enc, post_enc, "<", ">" }, /* Aq */
+	{ cond_body, pre_aq, post_aq, NULL, NULL }, /* Ao */
+	{ cond_body, pre_aq, post_aq, NULL, NULL }, /* Aq */
 	{ NULL, NULL, NULL, NULL, NULL }, /* At */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Bc */
 	{ NULL, pre_bf, post_bf, NULL, NULL }, /* Bf */
@@ -180,14 +183,14 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ cond_body, pre_enc, post_enc, "[", "]" }, /* Bq */
 	{ NULL, pre_ux, NULL, "BSD/OS", NULL }, /* Bsx */
 	{ NULL, pre_bx, NULL, NULL, NULL }, /* Bx */
-	{ NULL, NULL, NULL, NULL, NULL }, /* Db */
+	{ NULL, pre_skip, NULL, NULL, NULL }, /* Db */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Dc */
-	{ cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Do */
-	{ cond_body, pre_enc, post_enc, "\\(lq", "\\(rq" }, /* Dq */
+	{ cond_body, pre_enc, post_enc, "\\(Lq", "\\(Rq" }, /* Do */
+	{ cond_body, pre_enc, post_enc, "\\(Lq", "\\(Rq" }, /* Dq */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ec */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ef */
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Em */
-	{ NULL, NULL, post_eo, NULL, NULL }, /* Eo */
+	{ cond_body, pre_eo, post_eo, NULL, NULL }, /* Eo */
 	{ NULL, pre_ux, NULL, "FreeBSD", NULL }, /* Fx */
 	{ NULL, pre_sy, post_font, NULL, NULL }, /* Ms */
 	{ NULL, pre_no, NULL, NULL, NULL }, /* No */
@@ -222,7 +225,7 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ek */
 	{ NULL, pre_ux, NULL, "is currently in beta test.", NULL }, /* Bt */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Hf */
-	{ NULL, NULL, NULL, NULL, NULL }, /* Fr */
+	{ NULL, pre_em, post_font, NULL, NULL }, /* Fr */
 	{ NULL, pre_ux, NULL, "currently under development.", NULL }, /* Ud */
 	{ NULL, NULL, post_lb, NULL, NULL }, /* Lb */
 	{ NULL, pre_pp, NULL, NULL, NULL }, /* Lp */
@@ -232,14 +235,15 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ cond_body, pre_enc, post_enc, "{", "}" }, /* Bro */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Brc */
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %C */
-	{ NULL, NULL, NULL, NULL, NULL }, /* Es */
-	{ NULL, NULL, NULL, NULL, NULL }, /* En */
+	{ NULL, pre_skip, NULL, NULL, NULL }, /* Es */
+	{ cond_body, pre_en, post_en, NULL, NULL }, /* En */
 	{ NULL, pre_ux, NULL, "DragonFly", NULL }, /* Dx */
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %Q */
 	{ NULL, pre_br, NULL, NULL, NULL }, /* br */
 	{ NULL, pre_sp, post_sp, NULL, NULL }, /* sp */
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %U */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ta */
+	{ NULL, pre_ll, post_sp, NULL, NULL }, /* ll */
 	{ NULL, NULL, NULL, NULL, NULL }, /* ROOT */
 };
 
@@ -260,7 +264,7 @@ static	int		outflags;
 
 #define	BL_STACK_MAX	32
 
-static	size_t		Bl_stack[BL_STACK_MAX];  /* offsets [chars] */
+static	int		Bl_stack[BL_STACK_MAX];  /* offsets [chars] */
 static	int		Bl_stack_post[BL_STACK_MAX];  /* add final .RE */
 static	int		Bl_stack_len;  /* number of nested Bl blocks */
 static	int		TPremain;  /* characters before tag is full */
@@ -271,6 +275,7 @@ static	struct {
 	size_t	 size;
 }	fontqueue;
 
+
 static void
 font_push(char newfont)
 {
@@ -278,7 +283,7 @@ font_push(char newfont)
 	if (fontqueue.head + fontqueue.size <= ++fontqueue.tail) {
 		fontqueue.size += 8;
 		fontqueue.head = mandoc_realloc(fontqueue.head,
-				fontqueue.size);
+		    fontqueue.size);
 	}
 	*fontqueue.tail = newfont;
 	print_word("");
@@ -304,7 +309,7 @@ print_word(const char *s)
 {
 
 	if ((MMAN_PP | MMAN_sp | MMAN_br | MMAN_nl) & outflags) {
-		/* 
+		/*
 		 * If we need a newline, print it now and start afresh.
 		 */
 		if (MMAN_PP & outflags) {
@@ -359,13 +364,16 @@ print_word(const char *s)
 
 	for ( ; *s; s++) {
 		switch (*s) {
-		case (ASCII_NBRSP):
+		case ASCII_NBRSP:
 			printf("\\ ");
 			break;
-		case (ASCII_HYPH):
+		case ASCII_HYPH:
 			putchar('-');
 			break;
-		case (' '):
+		case ASCII_BREAK:
+			printf("\\:");
+			break;
+		case ' ':
 			if (MMAN_nbrword & outflags) {
 				printf("\\ ");
 				break;
@@ -410,22 +418,22 @@ print_block(const char *s, int newflags)
 }
 
 static void
-print_offs(const char *v)
+print_offs(const char *v, int keywords)
 {
 	char		  buf[24];
 	struct roffsu	  su;
-	size_t		  sz;
+	int		  sz;
 
 	print_line(".RS", MMAN_Bk_susp);
 
 	/* Convert v into a number (of characters). */
-	if (NULL == v || '\0' == *v || 0 == strcmp(v, "left"))
+	if (NULL == v || '\0' == *v || (keywords && !strcmp(v, "left")))
 		sz = 0;
-	else if (0 == strcmp(v, "indent"))
+	else if (keywords && !strcmp(v, "indent"))
 		sz = 6;
-	else if (0 == strcmp(v, "indent-two"))
+	else if (keywords && !strcmp(v, "indent-two"))
 		sz = 12;
-	else if (a2roffsu(v, &su, SCALE_MAX)) {
+	else if (a2roffsu(v, &su, SCALE_EN) > 1) {
 		if (SCALE_EN == su.unit)
 			sz = su.scale;
 		else {
@@ -450,7 +458,7 @@ print_offs(const char *v)
 	if (Bl_stack_len)
 		sz += Bl_stack[Bl_stack_len - 1];
 
-	snprintf(buf, sizeof(buf), "%zun", sz);
+	(void)snprintf(buf, sizeof(buf), "%dn", sz);
 	print_word(buf);
 	outflags |= MMAN_nl;
 }
@@ -458,21 +466,20 @@ print_offs(const char *v)
 /*
  * Set up the indentation for a list item; used from pre_it().
  */
-void
-print_width(const char *v, const struct mdoc_node *child, size_t defsz)
+static void
+print_width(const struct mdoc_bl *bl, const struct mdoc_node *child)
 {
 	char		  buf[24];
 	struct roffsu	  su;
-	size_t		  sz, chsz;
-	int		  numeric, remain;
+	int		  numeric, remain, sz, chsz;
 
 	numeric = 1;
 	remain = 0;
 
-	/* Convert v into a number (of characters). */
-	if (NULL == v)
-		sz = defsz;
-	else if (a2roffsu(v, &su, SCALE_MAX)) {
+	/* Convert the width into a number (of characters). */
+	if (bl->width == NULL)
+		sz = (bl->type == LIST_hang) ? 6 : 0;
+	else if (a2roffsu(bl->width, &su, SCALE_MAX) > 1) {
 		if (SCALE_EN == su.unit)
 			sz = su.scale;
 		else {
@@ -480,11 +487,15 @@ print_width(const char *v, const struct mdoc_node *child, size_t defsz)
 			numeric = 0;
 		}
 	} else
-		sz = strlen(v);
+		sz = strlen(bl->width);
 
 	/* XXX Rough estimation, might have multiple parts. */
-	chsz = (NULL != child && MDOC_TEXT == child->type) ?
-			strlen(child->string) : 0;
+	if (bl->type == LIST_enum)
+		chsz = (bl->count > 8) + 1;
+	else if (child != NULL && child->type == MDOC_TEXT)
+		chsz = strlen(child->string);
+	else
+		chsz = 0;
 
 	/* Maybe we are inside an enclosing list? */
 	mid_it();
@@ -496,26 +507,26 @@ print_width(const char *v, const struct mdoc_node *child, size_t defsz)
 	Bl_stack[Bl_stack_len++] = sz + 2;
 
 	/* Set up the current list. */
-	if (defsz && chsz > sz)
+	if (chsz > sz && bl->type != LIST_tag)
 		print_block(".HP", 0);
 	else {
 		print_block(".TP", 0);
 		remain = sz + 2;
 	}
 	if (numeric) {
-		snprintf(buf, sizeof(buf), "%zun", sz + 2);
+		(void)snprintf(buf, sizeof(buf), "%dn", sz + 2);
 		print_word(buf);
 	} else
-		print_word(v);
+		print_word(bl->width);
 	TPremain = remain;
 }
 
-void
+static void
 print_count(int *count)
 {
-	char		  buf[12];
+	char		  buf[24];
 
-	snprintf(buf, sizeof(buf), "%d.", ++*count);
+	(void)snprintf(buf, sizeof(buf), "%d.\\&", ++*count);
 	print_word(buf);
 }
 
@@ -536,14 +547,15 @@ void
 man_mdoc(void *arg, const struct mdoc *mdoc)
 {
 	const struct mdoc_meta *meta;
-	const struct mdoc_node *n;
+	struct mdoc_node *n;
 
 	meta = mdoc_meta(mdoc);
-	n = mdoc_node(mdoc);
+	n = mdoc_node(mdoc)->child;
 
 	printf(".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n",
-			meta->title, meta->msec, meta->date,
-			meta->os, meta->vol);
+	    meta->title,
+	    (meta->msec == NULL ? "" : meta->msec),
+	    meta->date, meta->os, meta->vol);
 
 	/* Disable hyphenation and if nroff, disable justification. */
 	printf(".nh\n.if n .ad l");
@@ -554,15 +566,18 @@ man_mdoc(void *arg, const struct mdoc *mdoc)
 		fontqueue.head = fontqueue.tail = mandoc_malloc(8);
 		*fontqueue.tail = 'R';
 	}
-	print_node(meta, n);
+	while (n != NULL) {
+		print_node(meta, n);
+		n = n->next;
+	}
 	putchar('\n');
 }
 
 static void
 print_node(DECL_ARGS)
 {
-	const struct mdoc_node	*sub;
 	const struct manact	*act;
+	struct mdoc_node	*sub;
 	int			 cond, do_sub;
 
 	/*
@@ -575,31 +590,36 @@ print_node(DECL_ARGS)
 	act = NULL;
 	cond = 0;
 	do_sub = 1;
+	n->flags &= ~MDOC_ENDED;
 
 	if (MDOC_TEXT == n->type) {
 		/*
 		 * Make sure that we don't happen to start with a
 		 * control character at the start of a line.
 		 */
-		if (MMAN_nl & outflags && ('.' == *n->string || 
-					'\'' == *n->string)) {
+		if (MMAN_nl & outflags &&
+		    ('.' == *n->string || '\'' == *n->string)) {
 			print_word("");
 			printf("\\&");
 			outflags &= ~MMAN_spc;
 		}
+		if (outflags & MMAN_Sm && ! (n->flags & MDOC_DELIMC))
+			outflags |= MMAN_spc_force;
 		print_word(n->string);
+		if (outflags & MMAN_Sm && ! (n->flags & MDOC_DELIMO))
+			outflags |= MMAN_spc;
 	} else {
 		/*
 		 * Conditionally run the pre-node action handler for a
 		 * node.
 		 */
 		act = manacts + n->tok;
-		cond = NULL == act->cond || (*act->cond)(meta, n);
-		if (cond && act->pre)
+		cond = act->cond == NULL || (*act->cond)(meta, n);
+		if (cond && act->pre && (n->end == ENDBODY_NOT || n->nchild))
 			do_sub = (*act->pre)(meta, n);
 	}
 
-	/* 
+	/*
 	 * Conditionally run all child nodes.
 	 * Note that this iterates over children instead of using
 	 * recursion.  This prevents unnecessary depth in the stack.
@@ -611,8 +631,17 @@ print_node(DECL_ARGS)
 	/*
 	 * Lastly, conditionally run the post-node handler.
 	 */
+	if (MDOC_ENDED & n->flags)
+		return;
+
 	if (cond && act->post)
 		(*act->post)(meta, n);
+
+	if (ENDBODY_NOT != n->end)
+		n->body->flags |= MDOC_ENDED;
+
+	if (ENDBODY_NOSPACE == n->end)
+		outflags &= ~(MMAN_spc | MMAN_nl);
 }
 
 static int
@@ -650,10 +679,46 @@ post_enc(DECL_ARGS)
 	suffix = manacts[n->tok].suffix;
 	if (NULL == suffix)
 		return;
-	outflags &= ~MMAN_spc;
+	outflags &= ~(MMAN_spc | MMAN_nl);
 	print_word(suffix);
 }
 
+static int
+pre_ex(DECL_ARGS)
+{
+	int	 nchild;
+
+	outflags |= MMAN_br | MMAN_nl;
+
+	print_word("The");
+
+	nchild = n->nchild;
+	for (n = n->child; n; n = n->next) {
+		font_push('B');
+		print_word(n->string);
+		font_pop();
+
+		if (n->next == NULL)
+			continue;
+
+		if (nchild > 2) {
+			outflags &= ~MMAN_spc;
+			print_word(",");
+		}
+		if (n->next->next == NULL)
+			print_word("and");
+	}
+
+	if (nchild > 1)
+		print_word("utilities exit\\~0");
+	else
+		print_word("utility exits\\~0");
+
+	print_word("on success, and\\~>0 if an error occurs.");
+	outflags |= MMAN_nl;
+	return(0);
+}
+
 static void
 post_font(DECL_ARGS)
 {
@@ -682,8 +747,8 @@ static int
 pre__t(DECL_ARGS)
 {
 
-        if (n->parent && MDOC_Rs == n->parent->tok &&
-                        n->parent->norm->Rs.quote_T) {
+	if (n->parent && MDOC_Rs == n->parent->tok &&
+	    n->parent->norm->Rs.quote_T) {
 		print_word("");
 		putchar('\"');
 		outflags &= ~MMAN_spc;
@@ -696,8 +761,8 @@ static void
 post__t(DECL_ARGS)
 {
 
-        if (n->parent && MDOC_Rs == n->parent->tok &&
-                        n->parent->norm->Rs.quote_T) {
+	if (n->parent && MDOC_Rs == n->parent->tok &&
+	    n->parent->norm->Rs.quote_T) {
 		outflags &= ~MMAN_spc;
 		print_word("");
 		putchar('\"');
@@ -749,26 +814,26 @@ pre_syn(const struct mdoc_node *n)
 		return;
 
 	if (n->prev->tok == n->tok &&
-			MDOC_Ft != n->tok &&
-			MDOC_Fo != n->tok &&
-			MDOC_Fn != n->tok) {
+	    MDOC_Ft != n->tok &&
+	    MDOC_Fo != n->tok &&
+	    MDOC_Fn != n->tok) {
 		outflags |= MMAN_br;
 		return;
 	}
 
 	switch (n->prev->tok) {
-	case (MDOC_Fd):
+	case MDOC_Fd:
 		/* FALLTHROUGH */
-	case (MDOC_Fn):
+	case MDOC_Fn:
 		/* FALLTHROUGH */
-	case (MDOC_Fo):
+	case MDOC_Fo:
 		/* FALLTHROUGH */
-	case (MDOC_In):
+	case MDOC_In:
 		/* FALLTHROUGH */
-	case (MDOC_Vt):
+	case MDOC_Vt:
 		outflags |= MMAN_sp;
 		break;
-	case (MDOC_Ft):
+	case MDOC_Ft:
 		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
 			outflags |= MMAN_sp;
 			break;
@@ -785,11 +850,11 @@ pre_an(DECL_ARGS)
 {
 
 	switch (n->norm->An.auth) {
-	case (AUTH_split):
+	case AUTH_split:
 		outflags &= ~MMAN_An_nosplit;
 		outflags |= MMAN_An_split;
 		return(0);
-	case (AUTH_nosplit):
+	case AUTH_nosplit:
 		outflags &= ~MMAN_An_split;
 		outflags |= MMAN_An_nosplit;
 		return(0);
@@ -813,6 +878,25 @@ pre_ap(DECL_ARGS)
 	return(0);
 }
 
+static int
+pre_aq(DECL_ARGS)
+{
+
+	print_word(n->nchild == 1 &&
+	    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
+	outflags &= ~MMAN_spc;
+	return(1);
+}
+
+static void
+post_aq(DECL_ARGS)
+{
+
+	outflags &= ~(MMAN_spc | MMAN_nl);
+	print_word(n->nchild == 1 &&
+	    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
+}
+
 static int
 pre_bd(DECL_ARGS)
 {
@@ -824,7 +908,7 @@ pre_bd(DECL_ARGS)
 		print_line(".nf", 0);
 	if (0 == n->norm->Bd.comp && NULL != n->parent->prev)
 		outflags |= MMAN_sp;
-	print_offs(n->norm->Bd.offs);
+	print_offs(n->norm->Bd.offs, 1);
 	return(1);
 }
 
@@ -848,18 +932,18 @@ pre_bf(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		return(1);
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		break;
 	default:
 		return(0);
 	}
 	switch (n->norm->Bf.font) {
-	case (FONT_Em):
+	case FONT_Em:
 		font_push('I');
 		break;
-	case (FONT_Sy):
+	case FONT_Sy:
 		font_push('B');
 		break;
 	default:
@@ -882,9 +966,9 @@ pre_bk(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		return(1);
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		outflags |= MMAN_Bk;
 		return(1);
 	default:
@@ -911,24 +995,26 @@ pre_bl(DECL_ARGS)
 	 * just nest and do not add up their indentation.
 	 */
 	if (n->norm->Bl.offs) {
-		print_offs(n->norm->Bl.offs);
+		print_offs(n->norm->Bl.offs, 0);
 		Bl_stack[Bl_stack_len++] = 0;
 	}
 
 	switch (n->norm->Bl.type) {
-	case (LIST_enum):
+	case LIST_enum:
 		n->norm->Bl.count = 0;
 		return(1);
-	case (LIST_column):
+	case LIST_column:
 		break;
 	default:
 		return(1);
 	}
 
-	print_line(".TS", MMAN_nl);
-	for (icol = 0; icol < n->norm->Bl.ncols; icol++)
-		print_word("l");
-	print_word(".");
+	if (n->nchild) {
+		print_line(".TS", MMAN_nl);
+		for (icol = 0; icol < n->norm->Bl.ncols; icol++)
+			print_word("l");
+		print_word(".");
+	}
 	outflags |= MMAN_nl;
 	return(1);
 }
@@ -938,10 +1024,11 @@ post_bl(DECL_ARGS)
 {
 
 	switch (n->norm->Bl.type) {
-	case (LIST_column):
-		print_line(".TE", 0);
+	case LIST_column:
+		if (n->nchild)
+			print_line(".TE", 0);
 		break;
-	case (LIST_enum):
+	case LIST_enum:
 		n->norm->Bl.count = 0;
 		break;
 	default:
@@ -996,7 +1083,7 @@ static int
 pre_dl(DECL_ARGS)
 {
 
-	print_offs("6n");
+	print_offs("6n", 0);
 	return(1);
 }
 
@@ -1019,12 +1106,68 @@ pre_em(DECL_ARGS)
 	return(1);
 }
 
+static int
+pre_en(DECL_ARGS)
+{
+
+	if (NULL == n->norm->Es ||
+	    NULL == n->norm->Es->child)
+		return(1);
+
+	print_word(n->norm->Es->child->string);
+	outflags &= ~MMAN_spc;
+	return(1);
+}
+
+static void
+post_en(DECL_ARGS)
+{
+
+	if (NULL == n->norm->Es ||
+	    NULL == n->norm->Es->child ||
+	    NULL == n->norm->Es->child->next)
+		return;
+
+	outflags &= ~MMAN_spc;
+	print_word(n->norm->Es->child->next->string);
+	return;
+}
+
+static int
+pre_eo(DECL_ARGS)
+{
+
+	if (n->end == ENDBODY_NOT &&
+	    n->parent->head->child == NULL &&
+	    n->child != NULL &&
+	    n->child->end != ENDBODY_NOT)
+		print_word("\\&");
+	else if (n->end != ENDBODY_NOT ? n->child != NULL :
+	    n->parent->head->child != NULL && (n->child != NULL ||
+	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
+		outflags &= ~(MMAN_spc | MMAN_nl);
+	return(1);
+}
+
 static void
 post_eo(DECL_ARGS)
 {
+	int	 body, tail;
+
+	if (n->end != ENDBODY_NOT) {
+		outflags |= MMAN_spc;
+		return;
+	}
+
+	body = n->child != NULL || n->parent->head->child != NULL;
+	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
 
-	if (MDOC_HEAD == n->type || MDOC_BODY == n->type)
+	if (body && tail)
 		outflags &= ~MMAN_spc;
+	else if ( ! (body || tail))
+		print_word("\\&");
+	else if ( ! tail)
+		outflags |= MMAN_spc;
 }
 
 static int
@@ -1080,7 +1223,8 @@ pre_fl(DECL_ARGS)
 
 	font_push('B');
 	print_word("\\-");
-	outflags &= ~MMAN_spc;
+	if (n->nchild)
+		outflags &= ~MMAN_spc;
 	return(1);
 }
 
@@ -1089,8 +1233,10 @@ post_fl(DECL_ARGS)
 {
 
 	font_pop();
-	if (0 == n->nchild && NULL != n->next &&
-			n->next->line == n->line)
+	if ( ! (n->nchild ||
+	    n->next == NULL ||
+	    n->next->type == MDOC_TEXT ||
+	    n->next->flags & MDOC_LINE))
 		outflags &= ~MMAN_spc;
 }
 
@@ -1136,16 +1282,18 @@ pre_fo(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		pre_syn(n);
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
+		if (n->child == NULL)
+			return(0);
 		if (MDOC_SYNPRETTY & n->flags)
 			print_block(".HP 4n", MMAN_nl);
 		font_push('B');
 		break;
-	case (MDOC_BODY):
-		outflags &= ~MMAN_spc;
+	case MDOC_BODY:
+		outflags &= ~(MMAN_spc | MMAN_nl);
 		print_word("(");
 		outflags &= ~MMAN_spc;
 		break;
@@ -1160,10 +1308,11 @@ post_fo(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_HEAD):
-		font_pop();
+	case MDOC_HEAD:
+		if (n->child != NULL)
+			font_pop();
 		break;
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		post_fn(meta, n);
 		break;
 	default:
@@ -1219,7 +1368,7 @@ pre_it(DECL_ARGS)
 	const struct mdoc_node *bln;
 
 	switch (n->type) {
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		outflags |= MMAN_PP | MMAN_nl;
 		bln = n->parent->parent;
 		if (0 == bln->norm->Bl.comp ||
@@ -1228,53 +1377,55 @@ pre_it(DECL_ARGS)
 			outflags |= MMAN_sp;
 		outflags &= ~MMAN_br;
 		switch (bln->norm->Bl.type) {
-		case (LIST_item):
+		case LIST_item:
 			return(0);
-		case (LIST_inset):
+		case LIST_inset:
 			/* FALLTHROUGH */
-		case (LIST_diag):
+		case LIST_diag:
 			/* FALLTHROUGH */
-		case (LIST_ohang):
+		case LIST_ohang:
 			if (bln->norm->Bl.type == LIST_diag)
 				print_line(".B \"", 0);
 			else
 				print_line(".R \"", 0);
 			outflags &= ~MMAN_spc;
 			return(1);
-		case (LIST_bullet):
+		case LIST_bullet:
 			/* FALLTHROUGH */
-		case (LIST_dash):
+		case LIST_dash:
 			/* FALLTHROUGH */
-		case (LIST_hyphen):
-			print_width(bln->norm->Bl.width, NULL, 0);
+		case LIST_hyphen:
+			print_width(&bln->norm->Bl, NULL);
 			TPremain = 0;
 			outflags |= MMAN_nl;
 			font_push('B');
 			if (LIST_bullet == bln->norm->Bl.type)
-				print_word("o");
+				print_word("\\(bu");
 			else
 				print_word("-");
 			font_pop();
-			break;
-		case (LIST_enum):
-			print_width(bln->norm->Bl.width, NULL, 0);
+			outflags |= MMAN_nl;
+			return(0);
+		case LIST_enum:
+			print_width(&bln->norm->Bl, NULL);
 			TPremain = 0;
 			outflags |= MMAN_nl;
 			print_count(&bln->norm->Bl.count);
-			break;
-		case (LIST_hang):
-			print_width(bln->norm->Bl.width, n->child, 6);
+			outflags |= MMAN_nl;
+			return(0);
+		case LIST_hang:
+			print_width(&bln->norm->Bl, n->child);
 			TPremain = 0;
-			break;
-		case (LIST_tag):
-			print_width(bln->norm->Bl.width, n->child, 0);
+			outflags |= MMAN_nl;
+			return(1);
+		case LIST_tag:
+			print_width(&bln->norm->Bl, n->child);
 			putchar('\n');
 			outflags &= ~MMAN_spc;
 			return(1);
 		default:
 			return(1);
 		}
-		outflags |= MMAN_nl;
 	default:
 		break;
 	}
@@ -1300,7 +1451,8 @@ mid_it(void)
 
 	/* Restore the indentation of the enclosing list. */
 	print_line(".RS", MMAN_Bk_susp);
-	snprintf(buf, sizeof(buf), "%zun", Bl_stack[Bl_stack_len - 1]);
+	(void)snprintf(buf, sizeof(buf), "%dn",
+	    Bl_stack[Bl_stack_len - 1]);
 	print_word(buf);
 
 	/* Remeber to close out this .RS block later. */
@@ -1315,32 +1467,32 @@ post_it(DECL_ARGS)
 	bln = n->parent->parent;
 
 	switch (n->type) {
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		switch (bln->norm->Bl.type) {
-		case (LIST_diag):
+		case LIST_diag:
 			outflags &= ~MMAN_spc;
 			print_word("\\ ");
 			break;
-		case (LIST_ohang):
+		case LIST_ohang:
 			outflags |= MMAN_br;
 			break;
 		default:
 			break;
 		}
 		break;
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		switch (bln->norm->Bl.type) {
-		case (LIST_bullet):
+		case LIST_bullet:
 			/* FALLTHROUGH */
-		case (LIST_dash):
+		case LIST_dash:
 			/* FALLTHROUGH */
-		case (LIST_hyphen):
+		case LIST_hyphen:
 			/* FALLTHROUGH */
-		case (LIST_enum):
+		case LIST_enum:
 			/* FALLTHROUGH */
-		case (LIST_hang):
+		case LIST_hang:
 			/* FALLTHROUGH */
-		case (LIST_tag):
+		case LIST_tag:
 			assert(Bl_stack_len);
 			Bl_stack[--Bl_stack_len] = 0;
 
@@ -1354,7 +1506,7 @@ post_it(DECL_ARGS)
 				Bl_stack_post[Bl_stack_len] = 0;
 			}
 			break;
-		case (LIST_column):
+		case LIST_column:
 			if (NULL != n->next) {
 				putchar('\t');
 				outflags &= ~MMAN_spc;
@@ -1401,6 +1553,14 @@ pre_lk(DECL_ARGS)
 	return(0);
 }
 
+static int
+pre_ll(DECL_ARGS)
+{
+
+	print_line(".ll", 0);
+	return(1);
+}
+
 static int
 pre_li(DECL_ARGS)
 {
@@ -1441,13 +1601,14 @@ post_nm(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		outflags &= ~MMAN_Bk;
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		/* FALLTHROUGH */
-	case (MDOC_ELEM):
-		font_pop();
+	case MDOC_ELEM:
+		if (n->child != NULL || meta->name != NULL)
+			font_pop();
 		break;
 	default:
 		break;
@@ -1474,7 +1635,8 @@ static void
 post_pf(DECL_ARGS)
 {
 
-	outflags &= ~MMAN_spc;
+	if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
+		outflags &= ~MMAN_spc;
 }
 
 static int
@@ -1499,15 +1661,79 @@ pre_rs(DECL_ARGS)
 	return(1);
 }
 
+static int
+pre_rv(DECL_ARGS)
+{
+	int	 nchild;
+
+	outflags |= MMAN_br | MMAN_nl;
+
+	nchild = n->nchild;
+	if (nchild > 0) {
+		print_word("The");
+
+		for (n = n->child; n; n = n->next) {
+			font_push('B');
+			print_word(n->string);
+			font_pop();
+
+			outflags &= ~MMAN_spc;
+			print_word("()");
+
+			if (n->next == NULL)
+				continue;
+
+			if (nchild > 2) {
+				outflags &= ~MMAN_spc;
+				print_word(",");
+			}
+			if (n->next->next == NULL)
+				print_word("and");
+		}
+
+		if (nchild > 1)
+			print_word("functions return");
+		else
+			print_word("function returns");
+
+		print_word("the value\\~0 if successful;");
+	} else
+		print_word("Upon successful completion, "
+		    "the value\\~0 is returned;");
+
+	print_word("otherwise the value\\~\\-1 is returned"
+	    " and the global variable");
+
+	font_push('I');
+	print_word("errno");
+	font_pop();
+
+	print_word("is set to indicate the error.");
+	outflags |= MMAN_nl;
+	return(0);
+}
+
+static int
+pre_skip(DECL_ARGS)
+{
+
+	return(0);
+}
+
 static int
 pre_sm(DECL_ARGS)
 {
 
-	assert(n->child && MDOC_TEXT == n->child->type);
-	if (0 == strcmp("on", n->child->string))
-		outflags |= MMAN_Sm | MMAN_spc;
+	if (NULL == n->child)
+		outflags ^= MMAN_Sm;
+	else if (0 == strcmp("on", n->child->string))
+		outflags |= MMAN_Sm;
 	else
 		outflags &= ~MMAN_Sm;
+
+	if (MMAN_Sm & outflags)
+		outflags |= MMAN_spc;
+
 	return(0);
 }
 
@@ -1544,10 +1770,10 @@ pre_vt(DECL_ARGS)
 
 	if (MDOC_SYNPRETTY & n->flags) {
 		switch (n->type) {
-		case (MDOC_BLOCK):
+		case MDOC_BLOCK:
 			pre_syn(n);
 			return(1);
-		case (MDOC_BODY):
+		case MDOC_BODY:
 			break;
 		default:
 			return(0);
diff --git a/usr/src/cmd/mandoc/mdoc_term.c b/usr/src/cmd/mandoc/mdoc_term.c
index 268fcae006..20c47d6668 100644
--- a/usr/src/cmd/mandoc/mdoc_term.c
+++ b/usr/src/cmd/mandoc/mdoc_term.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_term.c,v 1.258 2013/12/25 21:24:12 schwarze Exp $ */
+/*	$Id: mdoc_term.c,v 1.313 2015/03/06 15:48:52 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2015 Ingo Schwarze 
  * Copyright (c) 2013 Franco Fichtner 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -16,20 +16,20 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "out.h"
 #include "term.h"
 #include "mdoc.h"
@@ -42,7 +42,7 @@ struct	termpair {
 
 #define	DECL_ARGS struct termp *p, \
 		  struct termpair *pair, \
-	  	  const struct mdoc_meta *meta, \
+		  const struct mdoc_meta *meta, \
 		  struct mdoc_node *n
 
 struct	termact {
@@ -50,26 +50,24 @@ struct	termact {
 	void	(*post)(DECL_ARGS);
 };
 
-static	size_t	  a2width(const struct termp *, const char *);
-static	size_t	  a2height(const struct termp *, const char *);
-static	size_t	  a2offs(const struct termp *, const char *);
+static	int	  a2width(const struct termp *, const char *);
 
 static	void	  print_bvspace(struct termp *,
 			const struct mdoc_node *,
 			const struct mdoc_node *);
-static	void  	  print_mdoc_node(DECL_ARGS);
+static	void	  print_mdoc_node(DECL_ARGS);
 static	void	  print_mdoc_nodelist(DECL_ARGS);
 static	void	  print_mdoc_head(struct termp *, const void *);
 static	void	  print_mdoc_foot(struct termp *, const void *);
-static	void	  synopsis_pre(struct termp *, 
+static	void	  synopsis_pre(struct termp *,
 			const struct mdoc_node *);
 
 static	void	  termp____post(DECL_ARGS);
 static	void	  termp__t_post(DECL_ARGS);
-static	void	  termp_an_post(DECL_ARGS);
 static	void	  termp_bd_post(DECL_ARGS);
 static	void	  termp_bk_post(DECL_ARGS);
 static	void	  termp_bl_post(DECL_ARGS);
+static	void	  termp_eo_post(DECL_ARGS);
 static	void	  termp_fd_post(DECL_ARGS);
 static	void	  termp_fo_post(DECL_ARGS);
 static	void	  termp_in_post(DECL_ARGS);
@@ -94,6 +92,7 @@ static	int	  termp_bt_pre(DECL_ARGS);
 static	int	  termp_bx_pre(DECL_ARGS);
 static	int	  termp_cd_pre(DECL_ARGS);
 static	int	  termp_d1_pre(DECL_ARGS);
+static	int	  termp_eo_pre(DECL_ARGS);
 static	int	  termp_ex_pre(DECL_ARGS);
 static	int	  termp_fa_pre(DECL_ARGS);
 static	int	  termp_fd_pre(DECL_ARGS);
@@ -104,6 +103,7 @@ static	int	  termp_ft_pre(DECL_ARGS);
 static	int	  termp_in_pre(DECL_ARGS);
 static	int	  termp_it_pre(DECL_ARGS);
 static	int	  termp_li_pre(DECL_ARGS);
+static	int	  termp_ll_pre(DECL_ARGS);
 static	int	  termp_lk_pre(DECL_ARGS);
 static	int	  termp_nd_pre(DECL_ARGS);
 static	int	  termp_nm_pre(DECL_ARGS);
@@ -112,6 +112,7 @@ static	int	  termp_quote_pre(DECL_ARGS);
 static	int	  termp_rs_pre(DECL_ARGS);
 static	int	  termp_rv_pre(DECL_ARGS);
 static	int	  termp_sh_pre(DECL_ARGS);
+static	int	  termp_skip_pre(DECL_ARGS);
 static	int	  termp_sm_pre(DECL_ARGS);
 static	int	  termp_sp_pre(DECL_ARGS);
 static	int	  termp_ss_pre(DECL_ARGS);
@@ -127,8 +128,8 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ NULL, NULL }, /* Dt */
 	{ NULL, NULL }, /* Os */
 	{ termp_sh_pre, termp_sh_post }, /* Sh */
-	{ termp_ss_pre, termp_ss_post }, /* Ss */ 
-	{ termp_sp_pre, NULL }, /* Pp */ 
+	{ termp_ss_pre, termp_ss_post }, /* Ss */
+	{ termp_sp_pre, NULL }, /* Pp */
 	{ termp_d1_pre, termp_bl_post }, /* D1 */
 	{ termp_d1_pre, termp_bl_post }, /* Dl */
 	{ termp_bd_pre, termp_bd_post }, /* Bd */
@@ -136,30 +137,30 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ termp_bl_pre, termp_bl_post }, /* Bl */
 	{ NULL, NULL }, /* El */
 	{ termp_it_pre, termp_it_post }, /* It */
-	{ termp_under_pre, NULL }, /* Ad */ 
-	{ termp_an_pre, termp_an_post }, /* An */
+	{ termp_under_pre, NULL }, /* Ad */
+	{ termp_an_pre, NULL }, /* An */
 	{ termp_under_pre, NULL }, /* Ar */
 	{ termp_cd_pre, NULL }, /* Cd */
 	{ termp_bold_pre, NULL }, /* Cm */
-	{ NULL, NULL }, /* Dv */ 
-	{ NULL, NULL }, /* Er */ 
-	{ NULL, NULL }, /* Ev */ 
+	{ NULL, NULL }, /* Dv */
+	{ NULL, NULL }, /* Er */
+	{ NULL, NULL }, /* Ev */
 	{ termp_ex_pre, NULL }, /* Ex */
-	{ termp_fa_pre, NULL }, /* Fa */ 
-	{ termp_fd_pre, termp_fd_post }, /* Fd */ 
+	{ termp_fa_pre, NULL }, /* Fa */
+	{ termp_fd_pre, termp_fd_post }, /* Fd */
 	{ termp_fl_pre, NULL }, /* Fl */
-	{ termp_fn_pre, NULL }, /* Fn */ 
-	{ termp_ft_pre, NULL }, /* Ft */ 
-	{ termp_bold_pre, NULL }, /* Ic */ 
-	{ termp_in_pre, termp_in_post }, /* In */ 
+	{ termp_fn_pre, NULL }, /* Fn */
+	{ termp_ft_pre, NULL }, /* Ft */
+	{ termp_bold_pre, NULL }, /* Ic */
+	{ termp_in_pre, termp_in_post }, /* In */
 	{ termp_li_pre, NULL }, /* Li */
-	{ termp_nd_pre, NULL }, /* Nd */ 
-	{ termp_nm_pre, termp_nm_post }, /* Nm */ 
+	{ termp_nd_pre, NULL }, /* Nd */
+	{ termp_nm_pre, termp_nm_post }, /* Nm */
 	{ termp_quote_pre, termp_quote_post }, /* Op */
-	{ NULL, NULL }, /* Ot */
+	{ termp_ft_pre, NULL }, /* Ot */
 	{ termp_under_pre, NULL }, /* Pa */
 	{ termp_rv_pre, NULL }, /* Rv */
-	{ NULL, NULL }, /* St */ 
+	{ NULL, NULL }, /* St */
 	{ termp_under_pre, NULL }, /* Va */
 	{ termp_vt_pre, NULL }, /* Vt */
 	{ termp_xr_pre, NULL }, /* Xr */
@@ -179,22 +180,22 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ termp_quote_pre, termp_quote_post }, /* Aq */
 	{ NULL, NULL }, /* At */
 	{ NULL, NULL }, /* Bc */
-	{ termp_bf_pre, NULL }, /* Bf */ 
+	{ termp_bf_pre, NULL }, /* Bf */
 	{ termp_quote_pre, termp_quote_post }, /* Bo */
 	{ termp_quote_pre, termp_quote_post }, /* Bq */
 	{ termp_xx_pre, NULL }, /* Bsx */
 	{ termp_bx_pre, NULL }, /* Bx */
-	{ NULL, NULL }, /* Db */
+	{ termp_skip_pre, NULL }, /* Db */
 	{ NULL, NULL }, /* Dc */
 	{ termp_quote_pre, termp_quote_post }, /* Do */
 	{ termp_quote_pre, termp_quote_post }, /* Dq */
 	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
 	{ NULL, NULL }, /* Ef */
-	{ termp_under_pre, NULL }, /* Em */ 
-	{ termp_quote_pre, termp_quote_post }, /* Eo */
+	{ termp_under_pre, NULL }, /* Em */
+	{ termp_eo_pre, termp_eo_post }, /* Eo */
 	{ termp_xx_pre, NULL }, /* Fx */
 	{ termp_bold_pre, NULL }, /* Ms */
-	{ NULL, NULL }, /* No */
+	{ termp_li_pre, NULL }, /* No */
 	{ termp_ns_pre, NULL }, /* Ns */
 	{ termp_xx_pre, NULL }, /* Nx */
 	{ termp_xx_pre, NULL }, /* Ox */
@@ -218,77 +219,86 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ termp_xx_pre, NULL }, /* Ux */
 	{ NULL, NULL }, /* Xc */
 	{ NULL, NULL }, /* Xo */
-	{ termp_fo_pre, termp_fo_post }, /* Fo */ 
-	{ NULL, NULL }, /* Fc */ 
+	{ termp_fo_pre, termp_fo_post }, /* Fo */
+	{ NULL, NULL }, /* Fc */
 	{ termp_quote_pre, termp_quote_post }, /* Oo */
 	{ NULL, NULL }, /* Oc */
 	{ termp_bk_pre, termp_bk_post }, /* Bk */
 	{ NULL, NULL }, /* Ek */
 	{ termp_bt_pre, NULL }, /* Bt */
 	{ NULL, NULL }, /* Hf */
-	{ NULL, NULL }, /* Fr */
+	{ termp_under_pre, NULL }, /* Fr */
 	{ termp_ud_pre, NULL }, /* Ud */
 	{ NULL, termp_lb_post }, /* Lb */
-	{ termp_sp_pre, NULL }, /* Lp */ 
-	{ termp_lk_pre, NULL }, /* Lk */ 
-	{ termp_under_pre, NULL }, /* Mt */ 
-	{ termp_quote_pre, termp_quote_post }, /* Brq */ 
-	{ termp_quote_pre, termp_quote_post }, /* Bro */ 
-	{ NULL, NULL }, /* Brc */ 
-	{ NULL, termp____post }, /* %C */ 
-	{ NULL, NULL }, /* Es */ /* TODO */
-	{ NULL, NULL }, /* En */ /* TODO */
-	{ termp_xx_pre, NULL }, /* Dx */ 
-	{ NULL, termp____post }, /* %Q */ 
+	{ termp_sp_pre, NULL }, /* Lp */
+	{ termp_lk_pre, NULL }, /* Lk */
+	{ termp_under_pre, NULL }, /* Mt */
+	{ termp_quote_pre, termp_quote_post }, /* Brq */
+	{ termp_quote_pre, termp_quote_post }, /* Bro */
+	{ NULL, NULL }, /* Brc */
+	{ NULL, termp____post }, /* %C */
+	{ termp_skip_pre, NULL }, /* Es */
+	{ termp_quote_pre, termp_quote_post }, /* En */
+	{ termp_xx_pre, NULL }, /* Dx */
+	{ NULL, termp____post }, /* %Q */
 	{ termp_sp_pre, NULL }, /* br */
-	{ termp_sp_pre, NULL }, /* sp */ 
-	{ NULL, termp____post }, /* %U */ 
-	{ NULL, NULL }, /* Ta */ 
+	{ termp_sp_pre, NULL }, /* sp */
+	{ NULL, termp____post }, /* %U */
+	{ NULL, NULL }, /* Ta */
+	{ termp_ll_pre, NULL }, /* ll */
 };
 
 
 void
 terminal_mdoc(void *arg, const struct mdoc *mdoc)
 {
-	const struct mdoc_node	*n;
 	const struct mdoc_meta	*meta;
+	struct mdoc_node	*n;
 	struct termp		*p;
 
 	p = (struct termp *)arg;
 
-	if (0 == p->defindent)
-		p->defindent = 5;
-
 	p->overstep = 0;
-	p->maxrmargin = p->defrmargin;
+	p->rmargin = p->maxrmargin = p->defrmargin;
 	p->tabwidth = term_len(p, 5);
 
-	if (NULL == p->symtab)
-		p->symtab = mchars_alloc();
-
-	n = mdoc_node(mdoc);
+	n = mdoc_node(mdoc)->child;
 	meta = mdoc_meta(mdoc);
 
-	term_begin(p, print_mdoc_head, print_mdoc_foot, meta);
-
-	if (n->child)
-		print_mdoc_nodelist(p, NULL, meta, n->child);
-
-	term_end(p);
+	if (p->synopsisonly) {
+		while (n != NULL) {
+			if (n->tok == MDOC_Sh && n->sec == SEC_SYNOPSIS) {
+				if (n->child->next->child != NULL)
+					print_mdoc_nodelist(p, NULL,
+					    meta, n->child->next->child);
+				term_newln(p);
+				break;
+			}
+			n = n->next;
+		}
+	} else {
+		if (p->defindent == 0)
+			p->defindent = 5;
+		term_begin(p, print_mdoc_head, print_mdoc_foot, meta);
+		if (n != NULL) {
+			if (n->tok != MDOC_Sh)
+				term_vspace(p);
+			print_mdoc_nodelist(p, NULL, meta, n);
+		}
+		term_end(p);
+	}
 }
 
-
 static void
 print_mdoc_nodelist(DECL_ARGS)
 {
 
-	print_mdoc_node(p, pair, meta, n);
-	if (n->next)
-		print_mdoc_nodelist(p, pair, meta, n->next);
+	while (n != NULL) {
+		print_mdoc_node(p, pair, meta, n);
+		n = n->next;
+	}
 }
 
-
-/* ARGSUSED */
 static void
 print_mdoc_node(DECL_ARGS)
 {
@@ -299,7 +309,8 @@ print_mdoc_node(DECL_ARGS)
 	chld = 1;
 	offset = p->offset;
 	rmargin = p->rmargin;
-	n->prev_font = term_fontq(p);
+	n->flags &= ~MDOC_ENDED;
+	n->prev_font = p->fonti;
 
 	memset(&npair, 0, sizeof(struct termpair));
 	npair.ppair = pair;
@@ -309,12 +320,9 @@ print_mdoc_node(DECL_ARGS)
 	 * invoked in a prior line, revert it to PREKEEP.
 	 */
 
-	if (TERMP_KEEP & p->flags) {
-		if (n->prev ? (n->prev->lastline != n->line) :
-		    (n->parent && n->parent->line != n->line)) {
-			p->flags &= ~TERMP_KEEP;
-			p->flags |= TERMP_PREKEEP;
-		}
+	if (p->flags & TERMP_KEEP && n->flags & MDOC_LINE) {
+		p->flags &= ~TERMP_KEEP;
+		p->flags |= TERMP_PREKEEP;
 	}
 
 	/*
@@ -323,7 +331,7 @@ print_mdoc_node(DECL_ARGS)
 	 */
 
 	switch (n->type) {
-	case (MDOC_TEXT):
+	case MDOC_TEXT:
 		if (' ' == *n->string && MDOC_LINE & n->flags)
 			term_newln(p);
 		if (MDOC_DELIMC & n->flags)
@@ -332,14 +340,21 @@ print_mdoc_node(DECL_ARGS)
 		if (MDOC_DELIMO & n->flags)
 			p->flags |= TERMP_NOSPACE;
 		break;
-	case (MDOC_EQN):
+	case MDOC_EQN:
+		if ( ! (n->flags & MDOC_LINE))
+			p->flags |= TERMP_NOSPACE;
 		term_eqn(p, n->eqn);
+		if (n->next != NULL && ! (n->next->flags & MDOC_LINE))
+			p->flags |= TERMP_NOSPACE;
 		break;
-	case (MDOC_TBL):
+	case MDOC_TBL:
+		if (p->tbl.cols == NULL)
+			term_newln(p);
 		term_tbl(p, n->span);
 		break;
 	default:
-		if (termacts[n->tok].pre && ENDBODY_NOT == n->end)
+		if (termacts[n->tok].pre &&
+		    (n->end == ENDBODY_NOT || n->nchild))
 			chld = (*termacts[n->tok].pre)
 				(p, &npair, meta, n);
 		break;
@@ -349,14 +364,14 @@ print_mdoc_node(DECL_ARGS)
 		print_mdoc_nodelist(p, &npair, meta, n->child);
 
 	term_fontpopq(p,
-	    (ENDBODY_NOT == n->end ? n : n->pending)->prev_font);
+	    (ENDBODY_NOT == n->end ? n : n->body)->prev_font);
 
 	switch (n->type) {
-	case (MDOC_TEXT):
+	case MDOC_TEXT:
 		break;
-	case (MDOC_TBL):
+	case MDOC_TBL:
 		break;
-	case (MDOC_EQN):
+	case MDOC_EQN:
 		break;
 	default:
 		if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
@@ -369,7 +384,7 @@ print_mdoc_node(DECL_ARGS)
 		 * that it must not call the post handler again.
 		 */
 		if (ENDBODY_NOT != n->end)
-			n->pending->flags |= MDOC_ENDED;
+			n->body->flags |= MDOC_ENDED;
 
 		/*
 		 * End of line terminating an implicit block
@@ -384,21 +399,23 @@ print_mdoc_node(DECL_ARGS)
 	if (MDOC_EOS & n->flags)
 		p->flags |= TERMP_SENTENCE;
 
-	p->offset = offset;
-	p->rmargin = rmargin;
+	if (MDOC_ll != n->tok) {
+		p->offset = offset;
+		p->rmargin = rmargin;
+	}
 }
 
-
 static void
 print_mdoc_foot(struct termp *p, const void *arg)
 {
 	const struct mdoc_meta *meta;
+	size_t sz;
 
 	meta = (const struct mdoc_meta *)arg;
 
 	term_fontrepl(p, TERMFONT_NONE);
 
-	/* 
+	/*
 	 * Output the footer in new-groff style, that is, three columns
 	 * with the middle being the manual date and flanking columns
 	 * being the operating system:
@@ -409,8 +426,9 @@ print_mdoc_foot(struct termp *p, const void *arg)
 	term_vspace(p);
 
 	p->offset = 0;
-	p->rmargin = (p->maxrmargin - 
-			term_strlen(p, meta->date) + term_len(p, 1)) / 2;
+	sz = term_strlen(p, meta->date);
+	p->rmargin = p->maxrmargin > sz ?
+	    (p->maxrmargin + term_len(p, 1) - sz) / 2 : 0;
 	p->trailspace = 1;
 	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
 
@@ -418,7 +436,8 @@ print_mdoc_foot(struct termp *p, const void *arg)
 	term_flushln(p);
 
 	p->offset = p->rmargin;
-	p->rmargin = p->maxrmargin - term_strlen(p, meta->os);
+	sz = term_strlen(p, meta->os);
+	p->rmargin = p->maxrmargin > sz ? p->maxrmargin - sz : 0;
 	p->flags |= TERMP_NOSPACE;
 
 	term_word(p, meta->date);
@@ -438,13 +457,12 @@ print_mdoc_foot(struct termp *p, const void *arg)
 	p->flags = 0;
 }
 
-
 static void
 print_mdoc_head(struct termp *p, const void *arg)
 {
-	char		buf[BUFSIZ], title[BUFSIZ];
-	size_t		buflen, titlen;
-	const struct mdoc_meta *meta;
+	const struct mdoc_meta	*meta;
+	char			*volume, *title;
+	size_t			 vollen, titlen;
 
 	meta = (const struct mdoc_meta *)arg;
 
@@ -461,39 +479,37 @@ print_mdoc_head(struct termp *p, const void *arg)
 	 * switches on the manual section.
 	 */
 
-	p->offset = 0;
-	p->rmargin = p->maxrmargin;
-
 	assert(meta->vol);
-	strlcpy(buf, meta->vol, BUFSIZ);
-	buflen = term_strlen(p, buf);
-
-	if (meta->arch) {
-		strlcat(buf, " (", BUFSIZ);
-		strlcat(buf, meta->arch, BUFSIZ);
-		strlcat(buf, ")", BUFSIZ);
-	}
+	if (NULL == meta->arch)
+		volume = mandoc_strdup(meta->vol);
+	else
+		mandoc_asprintf(&volume, "%s (%s)",
+		    meta->vol, meta->arch);
+	vollen = term_strlen(p, volume);
 
-	snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
+	if (NULL == meta->msec)
+		title = mandoc_strdup(meta->title);
+	else
+		mandoc_asprintf(&title, "%s(%s)",
+		    meta->title, meta->msec);
 	titlen = term_strlen(p, title);
 
 	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
 	p->trailspace = 1;
 	p->offset = 0;
-	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
-	    (p->maxrmargin -
-	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
-	    p->maxrmargin - buflen;
+	p->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
+	    (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
+	    vollen < p->maxrmargin ?  p->maxrmargin - vollen : 0;
 
 	term_word(p, title);
 	term_flushln(p);
 
 	p->flags |= TERMP_NOSPACE;
 	p->offset = p->rmargin;
-	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
+	p->rmargin = p->offset + vollen + titlen < p->maxrmargin ?
 	    p->maxrmargin - titlen : p->maxrmargin;
 
-	term_word(p, buf);
+	term_word(p, volume);
 	term_flushln(p);
 
 	p->flags &= ~TERMP_NOBREAK;
@@ -509,65 +525,31 @@ print_mdoc_head(struct termp *p, const void *arg)
 	p->flags &= ~TERMP_NOSPACE;
 	p->offset = 0;
 	p->rmargin = p->maxrmargin;
+	free(title);
+	free(volume);
 }
 
-
-static size_t
-a2height(const struct termp *p, const char *v)
-{
-	struct roffsu	 su;
-
-
-	assert(v);
-	if ( ! a2roffsu(v, &su, SCALE_VS))
-		SCALE_VS_INIT(&su, atoi(v));
-
-	return(term_vspan(p, &su));
-}
-
-
-static size_t
+static int
 a2width(const struct termp *p, const char *v)
 {
 	struct roffsu	 su;
 
-	assert(v);
-	if ( ! a2roffsu(v, &su, SCALE_MAX))
+	if (a2roffsu(v, &su, SCALE_MAX) < 2) {
 		SCALE_HS_INIT(&su, term_strlen(p, v));
-
-	return(term_hspan(p, &su));
-}
-
-
-static size_t
-a2offs(const struct termp *p, const char *v)
-{
-	struct roffsu	 su;
-
-	if ('\0' == *v)
-		return(0);
-	else if (0 == strcmp(v, "left"))
-		return(0);
-	else if (0 == strcmp(v, "indent"))
-		return(term_len(p, p->defindent + 1));
-	else if (0 == strcmp(v, "indent-two"))
-		return(term_len(p, (p->defindent + 1) * 2));
-	else if ( ! a2roffsu(v, &su, SCALE_MAX))
-		SCALE_HS_INIT(&su, term_strlen(p, v));
-
+		su.scale /= term_strlen(p, "0");
+	}
 	return(term_hspan(p, &su));
 }
 
-
 /*
  * Determine how much space to print out before block elements of `It'
  * (and thus `Bl') and `Bd'.  And then go ahead and print that space,
  * too.
  */
 static void
-print_bvspace(struct termp *p, 
-		const struct mdoc_node *bl, 
-		const struct mdoc_node *n)
+print_bvspace(struct termp *p,
+	const struct mdoc_node *bl,
+	const struct mdoc_node *n)
 {
 	const struct mdoc_node	*nn;
 
@@ -582,16 +564,18 @@ print_bvspace(struct termp *p,
 
 	/* Do not vspace directly after Ss/Sh. */
 
-	for (nn = n; nn; nn = nn->parent) {
-		if (MDOC_BLOCK != nn->type)
-			continue;
-		if (MDOC_Ss == nn->tok)
-			return;
-		if (MDOC_Sh == nn->tok)
+	nn = n;
+	while (nn->prev == NULL) {
+		do {
+			nn = nn->parent;
+			if (nn->type == MDOC_ROOT)
+				return;
+		} while (nn->type != MDOC_BLOCK);
+		if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
 			return;
-		if (NULL == nn->prev)
-			continue;
-		break;
+		if (nn->tok == MDOC_It &&
+		    nn->parent->parent->norm->Bl.type != LIST_item)
+			break;
 	}
 
 	/* A `-column' does not assert vspace within the list. */
@@ -613,14 +597,21 @@ print_bvspace(struct termp *p,
 }
 
 
-/* ARGSUSED */
+static int
+termp_ll_pre(DECL_ARGS)
+{
+
+	term_setwidth(p, n->nchild ? n->child->string : NULL);
+	return(0);
+}
+
 static int
 termp_it_pre(DECL_ARGS)
 {
+	char			buf[24];
 	const struct mdoc_node *bl, *nn;
-	char		        buf[7];
-	int		        i;
-	size_t		        width, offset, ncols, dcol;
+	size_t			ncols, dcol;
+	int			i, offset, width;
 	enum mdoc_list		type;
 
 	if (MDOC_BLOCK == n->type) {
@@ -631,19 +622,50 @@ termp_it_pre(DECL_ARGS)
 	bl = n->parent->parent->parent;
 	type = bl->norm->Bl.type;
 
-	/* 
+	/*
+	 * Defaults for specific list types.
+	 */
+
+	switch (type) {
+	case LIST_bullet:
+		/* FALLTHROUGH */
+	case LIST_dash:
+		/* FALLTHROUGH */
+	case LIST_hyphen:
+		/* FALLTHROUGH */
+	case LIST_enum:
+		width = term_len(p, 2);
+		break;
+	case LIST_hang:
+		width = term_len(p, 8);
+		break;
+	case LIST_column:
+		/* FALLTHROUGH */
+	case LIST_tag:
+		width = term_len(p, 10);
+		break;
+	default:
+		width = 0;
+		break;
+	}
+	offset = 0;
+
+	/*
 	 * First calculate width and offset.  This is pretty easy unless
 	 * we're a -column list, in which case all prior columns must
 	 * be accounted for.
 	 */
 
-	width = offset = 0;
-
-	if (bl->norm->Bl.offs)
-		offset = a2offs(p, bl->norm->Bl.offs);
+	if (bl->norm->Bl.offs != NULL) {
+		offset = a2width(p, bl->norm->Bl.offs);
+		if (offset < 0 && (size_t)(-offset) > p->offset)
+			offset = -p->offset;
+		else if (offset > SHRT_MAX)
+			offset = 0;
+	}
 
 	switch (type) {
-	case (LIST_column):
+	case LIST_column:
 		if (MDOC_HEAD == n->type)
 			break;
 
@@ -657,21 +679,19 @@ termp_it_pre(DECL_ARGS)
 		 * - For more than 5 columns, add only one column.
 		 */
 		ncols = bl->norm->Bl.ncols;
-
-		/* LINTED */
-		dcol = ncols < 5 ? term_len(p, 4) : 
-			ncols == 5 ? term_len(p, 3) : term_len(p, 1);
+		dcol = ncols < 5 ? term_len(p, 4) :
+		    ncols == 5 ? term_len(p, 3) : term_len(p, 1);
 
 		/*
 		 * Calculate the offset by applying all prior MDOC_BODY,
 		 * so we stop at the MDOC_HEAD (NULL == nn->prev).
 		 */
 
-		for (i = 0, nn = n->prev; 
-				nn->prev && i < (int)ncols; 
-				nn = nn->prev, i++)
-			offset += dcol + a2width
-				(p, bl->norm->Bl.cols[i]);
+		for (i = 0, nn = n->prev;
+		    nn->prev && i < (int)ncols;
+		    nn = nn->prev, i++)
+			offset += dcol + a2width(p,
+			    bl->norm->Bl.cols[i]);
 
 		/*
 		 * When exceeding the declared number of columns, leave
@@ -692,48 +712,20 @@ termp_it_pre(DECL_ARGS)
 		if (NULL == bl->norm->Bl.width)
 			break;
 
-		/* 
+		/*
 		 * Note: buffer the width by 2, which is groff's magic
 		 * number for buffering single arguments.  See the above
 		 * handling for column for how this changes.
 		 */
-		assert(bl->norm->Bl.width);
 		width = a2width(p, bl->norm->Bl.width) + term_len(p, 2);
+		if (width < 0 && (size_t)(-width) > p->offset)
+			width = -p->offset;
+		else if (width > SHRT_MAX)
+			width = 0;
 		break;
 	}
 
-	/* 
-	 * List-type can override the width in the case of fixed-head
-	 * values (bullet, dash/hyphen, enum).  Tags need a non-zero
-	 * offset.
-	 */
-
-	switch (type) {
-	case (LIST_bullet):
-		/* FALLTHROUGH */
-	case (LIST_dash):
-		/* FALLTHROUGH */
-	case (LIST_hyphen):
-		/* FALLTHROUGH */
-	case (LIST_enum):
-		if (width < term_len(p, 2))
-			width = term_len(p, 2);
-		break;
-	case (LIST_hang):
-		if (0 == width)
-			width = term_len(p, 8);
-		break;
-	case (LIST_column):
-		/* FALLTHROUGH */
-	case (LIST_tag):
-		if (0 == width)
-			width = term_len(p, 10);
-		break;
-	default:
-		break;
-	}
-
-	/* 
+	/*
 	 * Whitespace control.  Inset bodies need an initial space,
 	 * while diagonal bodies need two.
 	 */
@@ -741,12 +733,12 @@ termp_it_pre(DECL_ARGS)
 	p->flags |= TERMP_NOSPACE;
 
 	switch (type) {
-	case (LIST_diag):
+	case LIST_diag:
 		if (MDOC_BODY == n->type)
 			term_word(p, "\\ \\ ");
 		break;
-	case (LIST_inset):
-		if (MDOC_BODY == n->type) 
+	case LIST_inset:
+		if (MDOC_BODY == n->type && n->parent->head->nchild)
 			term_word(p, "\\ ");
 		break;
 	default:
@@ -756,7 +748,7 @@ termp_it_pre(DECL_ARGS)
 	p->flags |= TERMP_NOSPACE;
 
 	switch (type) {
-	case (LIST_diag):
+	case LIST_diag:
 		if (MDOC_HEAD == n->type)
 			term_fontpush(p, TERMFONT_BOLD);
 		break;
@@ -772,25 +764,25 @@ termp_it_pre(DECL_ARGS)
 	 */
 
 	switch (type) {
-	case (LIST_enum):
+	case LIST_enum:
 		/*
 		 * Weird special case.
-		 * Very narrow enum lists actually hang.
+		 * Some very narrow lists actually hang.
 		 */
-		if (width == term_len(p, 2))
-			p->flags |= TERMP_HANG;
 		/* FALLTHROUGH */
-	case (LIST_bullet):
+	case LIST_bullet:
 		/* FALLTHROUGH */
-	case (LIST_dash):
+	case LIST_dash:
 		/* FALLTHROUGH */
-	case (LIST_hyphen):
+	case LIST_hyphen:
+		if (width <= (int)term_len(p, 2))
+			p->flags |= TERMP_HANG;
 		if (MDOC_HEAD != n->type)
 			break;
 		p->flags |= TERMP_NOBREAK;
 		p->trailspace = 1;
 		break;
-	case (LIST_hang):
+	case LIST_hang:
 		if (MDOC_HEAD != n->type)
 			break;
 
@@ -800,25 +792,26 @@ termp_it_pre(DECL_ARGS)
 		 * the "overstep" effect in term_flushln() and treat
 		 * this as a `-ohang' list instead.
 		 */
-		if (n->next->child && 
-				(MDOC_Bl == n->next->child->tok ||
-				 MDOC_Bd == n->next->child->tok))
+		if (NULL != n->next &&
+		    NULL != n->next->child &&
+		    (MDOC_Bl == n->next->child->tok ||
+		     MDOC_Bd == n->next->child->tok))
 			break;
 
-		p->flags |= TERMP_NOBREAK | TERMP_HANG;
+		p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
 		p->trailspace = 1;
 		break;
-	case (LIST_tag):
+	case LIST_tag:
 		if (MDOC_HEAD != n->type)
 			break;
 
-		p->flags |= TERMP_NOBREAK;
+		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
 		p->trailspace = 2;
 
 		if (NULL == n->next || NULL == n->next->child)
 			p->flags |= TERMP_DANGLE;
 		break;
-	case (LIST_column):
+	case LIST_column:
 		if (MDOC_HEAD == n->type)
 			break;
 
@@ -831,17 +824,17 @@ termp_it_pre(DECL_ARGS)
 		}
 
 		break;
-	case (LIST_diag):
+	case LIST_diag:
 		if (MDOC_HEAD != n->type)
 			break;
-		p->flags |= TERMP_NOBREAK;
+		p->flags |= TERMP_NOBREAK | TERMP_BRIND;
 		p->trailspace = 1;
 		break;
 	default:
 		break;
 	}
 
-	/* 
+	/*
 	 * Margin control.  Set-head-width lists have their right
 	 * margins shortened.  The body for these lists has the offset
 	 * necessarily lengthened.  Everybody gets the offset.
@@ -850,36 +843,37 @@ termp_it_pre(DECL_ARGS)
 	p->offset += offset;
 
 	switch (type) {
-	case (LIST_hang):
+	case LIST_hang:
 		/*
 		 * Same stipulation as above, regarding `-hang'.  We
 		 * don't want to recalculate rmargin and offsets when
 		 * using `Bd' or `Bl' within `-hang' overstep lists.
 		 */
-		if (MDOC_HEAD == n->type && n->next->child &&
-				(MDOC_Bl == n->next->child->tok || 
-				 MDOC_Bd == n->next->child->tok))
+		if (MDOC_HEAD == n->type &&
+		    NULL != n->next &&
+		    NULL != n->next->child &&
+		    (MDOC_Bl == n->next->child->tok ||
+		     MDOC_Bd == n->next->child->tok))
 			break;
 		/* FALLTHROUGH */
-	case (LIST_bullet):
+	case LIST_bullet:
 		/* FALLTHROUGH */
-	case (LIST_dash):
+	case LIST_dash:
 		/* FALLTHROUGH */
-	case (LIST_enum):
+	case LIST_enum:
 		/* FALLTHROUGH */
-	case (LIST_hyphen):
+	case LIST_hyphen:
 		/* FALLTHROUGH */
-	case (LIST_tag):
-		assert(width);
+	case LIST_tag:
 		if (MDOC_HEAD == n->type)
 			p->rmargin = p->offset + width;
-		else 
+		else
 			p->offset += width;
 		break;
-	case (LIST_column):
+	case LIST_column:
 		assert(width);
 		p->rmargin = p->offset + width;
-		/* 
+		/*
 		 * XXX - this behaviour is not documented: the
 		 * right-most column is filled to the right margin.
 		 */
@@ -892,53 +886,53 @@ termp_it_pre(DECL_ARGS)
 		break;
 	}
 
-	/* 
+	/*
 	 * The dash, hyphen, bullet and enum lists all have a special
-	 * HEAD character (temporarily bold, in some cases).  
+	 * HEAD character (temporarily bold, in some cases).
 	 */
 
 	if (MDOC_HEAD == n->type)
 		switch (type) {
-		case (LIST_bullet):
+		case LIST_bullet:
 			term_fontpush(p, TERMFONT_BOLD);
 			term_word(p, "\\[bu]");
 			term_fontpop(p);
 			break;
-		case (LIST_dash):
+		case LIST_dash:
 			/* FALLTHROUGH */
-		case (LIST_hyphen):
+		case LIST_hyphen:
 			term_fontpush(p, TERMFONT_BOLD);
 			term_word(p, "\\(hy");
 			term_fontpop(p);
 			break;
-		case (LIST_enum):
+		case LIST_enum:
 			(pair->ppair->ppair->count)++;
-			snprintf(buf, sizeof(buf), "%d.", 
-					pair->ppair->ppair->count);
+			(void)snprintf(buf, sizeof(buf), "%d.",
+			    pair->ppair->ppair->count);
 			term_word(p, buf);
 			break;
 		default:
 			break;
 		}
 
-	/* 
+	/*
 	 * If we're not going to process our children, indicate so here.
 	 */
 
 	switch (type) {
-	case (LIST_bullet):
+	case LIST_bullet:
 		/* FALLTHROUGH */
-	case (LIST_item):
+	case LIST_item:
 		/* FALLTHROUGH */
-	case (LIST_dash):
+	case LIST_dash:
 		/* FALLTHROUGH */
-	case (LIST_hyphen):
+	case LIST_hyphen:
 		/* FALLTHROUGH */
-	case (LIST_enum):
+	case LIST_enum:
 		if (MDOC_HEAD == n->type)
 			return(0);
 		break;
-	case (LIST_column):
+	case LIST_column:
 		if (MDOC_HEAD == n->type)
 			return(0);
 		break;
@@ -949,8 +943,6 @@ termp_it_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp_it_post(DECL_ARGS)
 {
@@ -962,15 +954,15 @@ termp_it_post(DECL_ARGS)
 	type = n->parent->parent->parent->norm->Bl.type;
 
 	switch (type) {
-	case (LIST_item):
+	case LIST_item:
 		/* FALLTHROUGH */
-	case (LIST_diag):
+	case LIST_diag:
 		/* FALLTHROUGH */
-	case (LIST_inset):
+	case LIST_inset:
 		if (MDOC_BODY == n->type)
 			term_newln(p);
 		break;
-	case (LIST_column):
+	case LIST_column:
 		if (MDOC_BODY == n->type)
 			term_flushln(p);
 		break;
@@ -979,23 +971,21 @@ termp_it_post(DECL_ARGS)
 		break;
 	}
 
-	/* 
+	/*
 	 * Now that our output is flushed, we can reset our tags.  Since
 	 * only `It' sets these flags, we're free to assume that nobody
 	 * has munged them in the meanwhile.
 	 */
 
-	p->flags &= ~TERMP_DANGLE;
-	p->flags &= ~TERMP_NOBREAK;
-	p->flags &= ~TERMP_HANG;
+	p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
+			TERMP_DANGLE | TERMP_HANG);
 	p->trailspace = 0;
 }
 
-
-/* ARGSUSED */
 static int
 termp_nm_pre(DECL_ARGS)
 {
+	const char	*cp;
 
 	if (MDOC_BLOCK == n->type) {
 		p->flags |= TERMP_PREKEEP;
@@ -1006,12 +996,15 @@ termp_nm_pre(DECL_ARGS)
 		if (NULL == n->child)
 			return(0);
 		p->flags |= TERMP_NOSPACE;
-		p->offset += term_len(p, 1) +
-		    (NULL == n->prev->child ?
-		     term_strlen(p, meta->name) :
-		     MDOC_TEXT == n->prev->child->type ?
-		     term_strlen(p, n->prev->child->string) :
-		     term_len(p, 5));
+		cp = NULL;
+		if (n->prev->child != NULL)
+		    cp = n->prev->child->string;
+		if (cp == NULL)
+			cp = meta->name;
+		if (cp == NULL)
+			p->offset += term_len(p, 6);
+		else
+			p->offset += term_len(p, 1) + term_strlen(p, cp);
 		return(1);
 	}
 
@@ -1021,8 +1014,9 @@ termp_nm_pre(DECL_ARGS)
 	if (MDOC_HEAD == n->type)
 		synopsis_pre(p, n->parent);
 
-	if (MDOC_HEAD == n->type && n->next->child) {
-		p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+	if (MDOC_HEAD == n->type &&
+	    NULL != n->next && NULL != n->next->child) {
+		p->flags |= TERMP_NOSPACE | TERMP_NOBREAK | TERMP_BRIND;
 		p->trailspace = 1;
 		p->rmargin = p->offset + term_len(p, 1);
 		if (NULL == n->child) {
@@ -1043,24 +1037,21 @@ termp_nm_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp_nm_post(DECL_ARGS)
 {
 
 	if (MDOC_BLOCK == n->type) {
 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
-	} else if (MDOC_HEAD == n->type && n->next->child) {
+	} else if (MDOC_HEAD == n->type &&
+	    NULL != n->next && NULL != n->next->child) {
 		term_flushln(p);
-		p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
+		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
 		p->trailspace = 0;
 	} else if (MDOC_BODY == n->type && n->child)
 		term_flushln(p);
 }
 
-		
-/* ARGSUSED */
 static int
 termp_fl_pre(DECL_ARGS)
 {
@@ -1068,16 +1059,15 @@ termp_fl_pre(DECL_ARGS)
 	term_fontpush(p, TERMFONT_BOLD);
 	term_word(p, "\\-");
 
-	if (n->child)
-		p->flags |= TERMP_NOSPACE;
-	else if (n->next && n->next->line == n->line)
+	if ( ! (n->nchild == 0 &&
+	    (n->next == NULL ||
+	     n->next->type == MDOC_TEXT ||
+	     n->next->flags & MDOC_LINE)))
 		p->flags |= TERMP_NOSPACE;
 
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp__a_pre(DECL_ARGS)
 {
@@ -1089,66 +1079,30 @@ termp__a_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_an_pre(DECL_ARGS)
 {
 
-	if (NULL == n->child)
-		return(1);
-
-	/*
-	 * If not in the AUTHORS section, `An -split' will cause
-	 * newlines to occur before the author name.  If in the AUTHORS
-	 * section, by default, the first `An' invocation is nosplit,
-	 * then all subsequent ones, regardless of whether interspersed
-	 * with other macros/text, are split.  -split, in this case,
-	 * will override the condition of the implied first -nosplit.
-	 */
-	
-	if (n->sec == SEC_AUTHORS) {
-		if ( ! (TERMP_ANPREC & p->flags)) {
-			if (TERMP_SPLIT & p->flags)
-				term_newln(p);
-			return(1);
-		}
-		if (TERMP_NOSPLIT & p->flags)
-			return(1);
-		term_newln(p);
-		return(1);
-	}
-
-	if (TERMP_SPLIT & p->flags)
-		term_newln(p);
-
-	return(1);
-}
-
-
-/* ARGSUSED */
-static void
-termp_an_post(DECL_ARGS)
-{
-
-	if (n->child) {
-		if (SEC_AUTHORS == n->sec)
-			p->flags |= TERMP_ANPREC;
-		return;
-	}
-
-	if (AUTH_split == n->norm->An.auth) {
+	if (n->norm->An.auth == AUTH_split) {
 		p->flags &= ~TERMP_NOSPLIT;
 		p->flags |= TERMP_SPLIT;
-	} else if (AUTH_nosplit == n->norm->An.auth) {
+		return(0);
+	}
+	if (n->norm->An.auth == AUTH_nosplit) {
 		p->flags &= ~TERMP_SPLIT;
 		p->flags |= TERMP_NOSPLIT;
+		return(0);
 	}
 
-}
+	if (p->flags & TERMP_SPLIT)
+		term_newln(p);
 
+	if (n->sec == SEC_AUTHORS && ! (p->flags & TERMP_NOSPLIT))
+		p->flags |= TERMP_SPLIT;
+
+	return(1);
+}
 
-/* ARGSUSED */
 static int
 termp_ns_pre(DECL_ARGS)
 {
@@ -1158,8 +1112,6 @@ termp_ns_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_rs_pre(DECL_ARGS)
 {
@@ -1171,54 +1123,59 @@ termp_rs_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_rv_pre(DECL_ARGS)
 {
 	int		 nchild;
 
 	term_newln(p);
-	term_word(p, "The");
 
 	nchild = n->nchild;
-	for (n = n->child; n; n = n->next) {
-		term_fontpush(p, TERMFONT_BOLD);
-		term_word(p, n->string);
-		term_fontpop(p);
+	if (nchild > 0) {
+		term_word(p, "The");
 
-		p->flags |= TERMP_NOSPACE;
-		term_word(p, "()");
+		for (n = n->child; n; n = n->next) {
+			term_fontpush(p, TERMFONT_BOLD);
+			term_word(p, n->string);
+			term_fontpop(p);
 
-		if (nchild > 2 && n->next) {
 			p->flags |= TERMP_NOSPACE;
-			term_word(p, ",");
+			term_word(p, "()");
+
+			if (n->next == NULL)
+				continue;
+
+			if (nchild > 2) {
+				p->flags |= TERMP_NOSPACE;
+				term_word(p, ",");
+			}
+			if (n->next->next == NULL)
+				term_word(p, "and");
 		}
 
-		if (n->next && NULL == n->next->next)
-			term_word(p, "and");
-	}
+		if (nchild > 1)
+			term_word(p, "functions return");
+		else
+			term_word(p, "function returns");
 
-	if (nchild > 1)
-		term_word(p, "functions return");
-	else
-		term_word(p, "function returns");
+		term_word(p, "the value\\~0 if successful;");
+	} else
+		term_word(p, "Upon successful completion,"
+		    " the value\\~0 is returned;");
 
-       	term_word(p, "the value 0 if successful; otherwise the value "
-			"-1 is returned and the global variable");
+	term_word(p, "otherwise the value\\~\\-1 is returned"
+	    " and the global variable");
 
 	term_fontpush(p, TERMFONT_UNDER);
 	term_word(p, "errno");
 	term_fontpop(p);
 
-       	term_word(p, "is set to indicate the error.");
+	term_word(p, "is set to indicate the error.");
 	p->flags |= TERMP_SENTENCE;
 
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_ex_pre(DECL_ARGS)
 {
@@ -1243,35 +1200,25 @@ termp_ex_pre(DECL_ARGS)
 	}
 
 	if (nchild > 1)
-		term_word(p, "utilities exit");
+		term_word(p, "utilities exit\\~0");
 	else
-		term_word(p, "utility exits");
+		term_word(p, "utility exits\\~0");
 
-       	term_word(p, "0 on success, and >0 if an error occurs.");
+	term_word(p, "on success, and\\~>0 if an error occurs.");
 
 	p->flags |= TERMP_SENTENCE;
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_nd_pre(DECL_ARGS)
 {
 
-	if (MDOC_BODY != n->type)
-		return(1);
-
-#if defined(__OpenBSD__) || defined(__linux__)
-	term_word(p, "\\(en");
-#else
-	term_word(p, "\\(em");
-#endif
+	if (n->type == MDOC_BODY)
+		term_word(p, "\\(en");
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_bl_pre(DECL_ARGS)
 {
@@ -1279,8 +1226,6 @@ termp_bl_pre(DECL_ARGS)
 	return(MDOC_HEAD != n->type);
 }
 
-
-/* ARGSUSED */
 static void
 termp_bl_post(DECL_ARGS)
 {
@@ -1289,7 +1234,6 @@ termp_bl_post(DECL_ARGS)
 		term_newln(p);
 }
 
-/* ARGSUSED */
 static int
 termp_xr_pre(DECL_ARGS)
 {
@@ -1300,7 +1244,7 @@ termp_xr_pre(DECL_ARGS)
 	assert(MDOC_TEXT == n->type);
 	term_word(p, n->string);
 
-	if (NULL == (n = n->next)) 
+	if (NULL == (n = n->next))
 		return(0);
 
 	p->flags |= TERMP_NOSPACE;
@@ -1324,7 +1268,7 @@ termp_xr_pre(DECL_ARGS)
 static void
 synopsis_pre(struct termp *p, const struct mdoc_node *n)
 {
-	/* 
+	/*
 	 * Obviously, if we're not in a SYNOPSIS or no prior macros
 	 * exist, do nothing.
 	 */
@@ -1336,10 +1280,10 @@ synopsis_pre(struct termp *p, const struct mdoc_node *n)
 	 * newline and return.  UNLESS we're `Fo', `Fn', `Fn', in which
 	 * case we soldier on.
 	 */
-	if (n->prev->tok == n->tok && 
-			MDOC_Ft != n->tok && 
-			MDOC_Fo != n->tok && 
-			MDOC_Fn != n->tok) {
+	if (n->prev->tok == n->tok &&
+	    MDOC_Ft != n->tok &&
+	    MDOC_Fo != n->tok &&
+	    MDOC_Fn != n->tok) {
 		term_newln(p);
 		return;
 	}
@@ -1350,18 +1294,18 @@ synopsis_pre(struct termp *p, const struct mdoc_node *n)
 	 * vertical space, else only newline and move on.
 	 */
 	switch (n->prev->tok) {
-	case (MDOC_Fd):
+	case MDOC_Fd:
 		/* FALLTHROUGH */
-	case (MDOC_Fn):
+	case MDOC_Fn:
 		/* FALLTHROUGH */
-	case (MDOC_Fo):
+	case MDOC_Fo:
 		/* FALLTHROUGH */
-	case (MDOC_In):
+	case MDOC_In:
 		/* FALLTHROUGH */
-	case (MDOC_Vt):
+	case MDOC_Vt:
 		term_vspace(p);
 		break;
-	case (MDOC_Ft):
+	case MDOC_Ft:
 		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
 			term_vspace(p);
 			break;
@@ -1373,7 +1317,6 @@ synopsis_pre(struct termp *p, const struct mdoc_node *n)
 	}
 }
 
-
 static int
 termp_vt_pre(DECL_ARGS)
 {
@@ -1390,8 +1333,6 @@ termp_vt_pre(DECL_ARGS)
 	return(termp_under_pre(p, pair, meta, n));
 }
 
-
-/* ARGSUSED */
 static int
 termp_bold_pre(DECL_ARGS)
 {
@@ -1400,8 +1341,6 @@ termp_bold_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_fd_pre(DECL_ARGS)
 {
@@ -1410,8 +1349,6 @@ termp_fd_pre(DECL_ARGS)
 	return(termp_bold_pre(p, pair, meta, n));
 }
 
-
-/* ARGSUSED */
 static void
 termp_fd_post(DECL_ARGS)
 {
@@ -1419,25 +1356,26 @@ termp_fd_post(DECL_ARGS)
 	term_newln(p);
 }
 
-
-/* ARGSUSED */
 static int
 termp_sh_pre(DECL_ARGS)
 {
 
-	/* No vspace between consecutive `Sh' calls. */
-
 	switch (n->type) {
-	case (MDOC_BLOCK):
-		if (n->prev && MDOC_Sh == n->prev->tok)
-			if (NULL == n->prev->body->child)
-				break;
-		term_vspace(p);
+	case MDOC_BLOCK:
+		/*
+		 * Vertical space before sections, except
+		 * when the previous section was empty.
+		 */
+		if (n->prev == NULL ||
+		    MDOC_Sh != n->prev->tok ||
+		    (n->prev->body != NULL &&
+		     n->prev->body->child != NULL))
+			term_vspace(p);
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		term_fontpush(p, TERMFONT_BOLD);
 		break;
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		p->offset = term_len(p, p->defindent);
 		if (SEC_AUTHORS == n->sec)
 			p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
@@ -1448,17 +1386,15 @@ termp_sh_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp_sh_post(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		term_newln(p);
 		break;
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		term_newln(p);
 		p->offset = 0;
 		break;
@@ -1467,8 +1403,6 @@ termp_sh_post(DECL_ARGS)
 	}
 }
 
-
-/* ARGSUSED */
 static int
 termp_bt_pre(DECL_ARGS)
 {
@@ -1478,8 +1412,6 @@ termp_bt_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static void
 termp_lb_post(DECL_ARGS)
 {
@@ -1488,8 +1420,6 @@ termp_lb_post(DECL_ARGS)
 		term_newln(p);
 }
 
-
-/* ARGSUSED */
 static int
 termp_ud_pre(DECL_ARGS)
 {
@@ -1499,8 +1429,6 @@ termp_ud_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_d1_pre(DECL_ARGS)
 {
@@ -1512,8 +1440,6 @@ termp_d1_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_ft_pre(DECL_ARGS)
 {
@@ -1524,8 +1450,6 @@ termp_ft_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_fn_pre(DECL_ARGS)
 {
@@ -1542,7 +1466,7 @@ termp_fn_pre(DECL_ARGS)
 	if (pretty) {
 		rmargin = p->rmargin;
 		p->rmargin = p->offset + term_len(p, 4);
-		p->flags |= TERMP_NOBREAK | TERMP_HANG;
+		p->flags |= TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG;
 	}
 
 	assert(MDOC_TEXT == n->type);
@@ -1552,7 +1476,7 @@ termp_fn_pre(DECL_ARGS)
 
 	if (pretty) {
 		term_flushln(p);
-		p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
+		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
 		p->offset = p->rmargin;
 		p->rmargin = rmargin;
 	}
@@ -1587,8 +1511,6 @@ termp_fn_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_fa_pre(DECL_ARGS)
 {
@@ -1614,13 +1536,12 @@ termp_fa_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_bd_pre(DECL_ARGS)
 {
-	size_t			 tabwidth, rm, rmax;
+	size_t			 tabwidth, lm, len, rm, rmax;
 	struct mdoc_node	*nn;
+	int			 offset;
 
 	if (MDOC_BLOCK == n->type) {
 		print_bvspace(p, n, n);
@@ -1628,8 +1549,22 @@ termp_bd_pre(DECL_ARGS)
 	} else if (MDOC_HEAD == n->type)
 		return(0);
 
-	if (n->norm->Bd.offs)
-		p->offset += a2offs(p, n->norm->Bd.offs);
+	/* Handle the -offset argument. */
+
+	if (n->norm->Bd.offs == NULL ||
+	    ! strcmp(n->norm->Bd.offs, "left"))
+		/* nothing */;
+	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
+		p->offset += term_len(p, p->defindent + 1);
+	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
+		p->offset += term_len(p, (p->defindent + 1) * 2);
+	else {
+		offset = a2width(p, n->norm->Bd.offs);
+		if (offset < 0 && (size_t)(-offset) > p->offset)
+			p->offset = 0;
+		else if (offset < SHRT_MAX)
+			p->offset += offset;
+	}
 
 	/*
 	 * If -ragged or -filled are specified, the block does nothing
@@ -1638,20 +1573,31 @@ termp_bd_pre(DECL_ARGS)
 	 * for macro lines, a newline is appended to the line.  Blank
 	 * lines are allowed.
 	 */
-	
-	if (DISP_literal != n->norm->Bd.type && 
-			DISP_unfilled != n->norm->Bd.type)
+
+	if (DISP_literal != n->norm->Bd.type &&
+	    DISP_unfilled != n->norm->Bd.type &&
+	    DISP_centered != n->norm->Bd.type)
 		return(1);
 
 	tabwidth = p->tabwidth;
 	if (DISP_literal == n->norm->Bd.type)
 		p->tabwidth = term_len(p, 8);
 
+	lm = p->offset;
 	rm = p->rmargin;
 	rmax = p->maxrmargin;
 	p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
 
 	for (nn = n->child; nn; nn = nn->next) {
+		if (DISP_centered == n->norm->Bd.type) {
+			if (MDOC_TEXT == nn->type) {
+				len = term_strlen(p, nn->string);
+				p->offset = len >= rm ? 0 :
+				    lm + len >= rm ? rm - len :
+				    (lm + rm - len) / 2;
+			} else
+				p->offset = lm;
+		}
 		print_mdoc_node(p, pair, meta, nn);
 		/*
 		 * If the printed node flushes its own line, then we
@@ -1660,26 +1606,27 @@ termp_bd_pre(DECL_ARGS)
 		 * anyway, so don't sweat it.
 		 */
 		switch (nn->tok) {
-		case (MDOC_Sm):
+		case MDOC_Sm:
 			/* FALLTHROUGH */
-		case (MDOC_br):
+		case MDOC_br:
 			/* FALLTHROUGH */
-		case (MDOC_sp):
+		case MDOC_sp:
 			/* FALLTHROUGH */
-		case (MDOC_Bl):
+		case MDOC_Bl:
 			/* FALLTHROUGH */
-		case (MDOC_D1):
+		case MDOC_D1:
 			/* FALLTHROUGH */
-		case (MDOC_Dl):
+		case MDOC_Dl:
 			/* FALLTHROUGH */
-		case (MDOC_Lp):
+		case MDOC_Lp:
 			/* FALLTHROUGH */
-		case (MDOC_Pp):
+		case MDOC_Pp:
 			continue;
 		default:
 			break;
 		}
-		if (nn->next && nn->next->line == nn->line)
+		if (p->flags & TERMP_NONEWLINE ||
+		    (nn->next && ! (nn->next->flags & MDOC_LINE)))
 			continue;
 		term_flushln(p);
 		p->flags |= TERMP_NOSPACE;
@@ -1691,21 +1638,19 @@ termp_bd_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static void
 termp_bd_post(DECL_ARGS)
 {
 	size_t		 rm, rmax;
 
-	if (MDOC_BODY != n->type) 
+	if (MDOC_BODY != n->type)
 		return;
 
 	rm = p->rmargin;
 	rmax = p->maxrmargin;
 
-	if (DISP_literal == n->norm->Bd.type || 
-			DISP_unfilled == n->norm->Bd.type)
+	if (DISP_literal == n->norm->Bd.type ||
+	    DISP_unfilled == n->norm->Bd.type)
 		p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
 
 	p->flags |= TERMP_NOSPACE;
@@ -1715,8 +1660,6 @@ termp_bd_post(DECL_ARGS)
 	p->maxrmargin = rmax;
 }
 
-
-/* ARGSUSED */
 static int
 termp_bx_pre(DECL_ARGS)
 {
@@ -1740,8 +1683,6 @@ termp_bx_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_xx_pre(DECL_ARGS)
 {
@@ -1750,22 +1691,22 @@ termp_xx_pre(DECL_ARGS)
 
 	pp = NULL;
 	switch (n->tok) {
-	case (MDOC_Bsx):
+	case MDOC_Bsx:
 		pp = "BSD/OS";
 		break;
-	case (MDOC_Dx):
+	case MDOC_Dx:
 		pp = "DragonFly";
 		break;
-	case (MDOC_Fx):
+	case MDOC_Fx:
 		pp = "FreeBSD";
 		break;
-	case (MDOC_Nx):
+	case MDOC_Nx:
 		pp = "NetBSD";
 		break;
-	case (MDOC_Ox):
+	case MDOC_Ox:
 		pp = "OpenBSD";
 		break;
-	case (MDOC_Ux):
+	case MDOC_Ux:
 		pp = "UNIX";
 		break;
 	default:
@@ -1783,31 +1724,31 @@ termp_xx_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static void
 termp_pf_post(DECL_ARGS)
 {
 
-	p->flags |= TERMP_NOSPACE;
+	if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
+		p->flags |= TERMP_NOSPACE;
 }
 
-
-/* ARGSUSED */
 static int
 termp_ss_pre(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		term_newln(p);
 		if (n->prev)
 			term_vspace(p);
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		term_fontpush(p, TERMFONT_BOLD);
 		p->offset = term_len(p, (p->defindent+1)/2);
 		break;
+	case MDOC_BODY:
+		p->offset = term_len(p, p->defindent);
+		break;
 	default:
 		break;
 	}
@@ -1815,18 +1756,14 @@ termp_ss_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp_ss_post(DECL_ARGS)
 {
 
-	if (MDOC_HEAD == n->type)
+	if (n->type == MDOC_HEAD || n->type == MDOC_BODY)
 		term_newln(p);
 }
 
-
-/* ARGSUSED */
 static int
 termp_cd_pre(DECL_ARGS)
 {
@@ -1836,8 +1773,6 @@ termp_cd_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_in_pre(DECL_ARGS)
 {
@@ -1857,8 +1792,6 @@ termp_in_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp_in_post(DECL_ARGS)
 {
@@ -1873,18 +1806,22 @@ termp_in_post(DECL_ARGS)
 		term_fontpop(p);
 }
 
-
-/* ARGSUSED */
 static int
 termp_sp_pre(DECL_ARGS)
 {
-	size_t		 i, len;
+	struct roffsu	 su;
+	int		 i, len;
 
 	switch (n->tok) {
-	case (MDOC_sp):
-		len = n->child ? a2height(p, n->child->string) : 1;
-		break;
-	case (MDOC_br):
+	case MDOC_sp:
+		if (n->child) {
+			if ( ! a2roffsu(n->child->string, &su, SCALE_VS))
+				su.scale = 1.0;
+			len = term_vspan(p, &su);
+		} else
+			len = 1;
+		break;
+	case MDOC_br:
 		len = 0;
 		break;
 	default:
@@ -1894,14 +1831,22 @@ termp_sp_pre(DECL_ARGS)
 
 	if (0 == len)
 		term_newln(p);
-	for (i = 0; i < len; i++)
-		term_vspace(p);
+	else if (len < 0)
+		p->skipvsp -= len;
+	else
+		for (i = 0; i < len; i++)
+			term_vspace(p);
 
 	return(0);
 }
 
+static int
+termp_skip_pre(DECL_ARGS)
+{
+
+	return(0);
+}
 
-/* ARGSUSED */
 static int
 termp_quote_pre(DECL_ARGS)
 {
@@ -1910,49 +1855,54 @@ termp_quote_pre(DECL_ARGS)
 		return(1);
 
 	switch (n->tok) {
-	case (MDOC_Ao):
+	case MDOC_Ao:
 		/* FALLTHROUGH */
-	case (MDOC_Aq):
-		term_word(p, "<");
+	case MDOC_Aq:
+		term_word(p, n->nchild == 1 &&
+		    n->child->tok == MDOC_Mt ? "<" : "\\(la");
 		break;
-	case (MDOC_Bro):
+	case MDOC_Bro:
 		/* FALLTHROUGH */
-	case (MDOC_Brq):
+	case MDOC_Brq:
 		term_word(p, "{");
 		break;
-	case (MDOC_Oo):
+	case MDOC_Oo:
 		/* FALLTHROUGH */
-	case (MDOC_Op):
+	case MDOC_Op:
 		/* FALLTHROUGH */
-	case (MDOC_Bo):
+	case MDOC_Bo:
 		/* FALLTHROUGH */
-	case (MDOC_Bq):
+	case MDOC_Bq:
 		term_word(p, "[");
 		break;
-	case (MDOC_Do):
+	case MDOC_Do:
 		/* FALLTHROUGH */
-	case (MDOC_Dq):
-		term_word(p, "\\(lq");
+	case MDOC_Dq:
+		term_word(p, "\\(Lq");
 		break;
-	case (MDOC_Eo):
+	case MDOC_En:
+		if (NULL == n->norm->Es ||
+		    NULL == n->norm->Es->child)
+			return(1);
+		term_word(p, n->norm->Es->child->string);
 		break;
-	case (MDOC_Po):
+	case MDOC_Po:
 		/* FALLTHROUGH */
-	case (MDOC_Pq):
+	case MDOC_Pq:
 		term_word(p, "(");
 		break;
-	case (MDOC__T):
+	case MDOC__T:
 		/* FALLTHROUGH */
-	case (MDOC_Qo):
+	case MDOC_Qo:
 		/* FALLTHROUGH */
-	case (MDOC_Qq):
+	case MDOC_Qq:
 		term_word(p, "\"");
 		break;
-	case (MDOC_Ql):
+	case MDOC_Ql:
 		/* FALLTHROUGH */
-	case (MDOC_So):
+	case MDOC_So:
 		/* FALLTHROUGH */
-	case (MDOC_Sq):
+	case MDOC_Sq:
 		term_word(p, "\\(oq");
 		break;
 	default:
@@ -1964,61 +1914,66 @@ termp_quote_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp_quote_post(DECL_ARGS)
 {
 
-	if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
+	if (n->type != MDOC_BODY && n->type != MDOC_ELEM)
 		return;
 
 	p->flags |= TERMP_NOSPACE;
 
 	switch (n->tok) {
-	case (MDOC_Ao):
+	case MDOC_Ao:
 		/* FALLTHROUGH */
-	case (MDOC_Aq):
-		term_word(p, ">");
+	case MDOC_Aq:
+		term_word(p, n->nchild == 1 &&
+		    n->child->tok == MDOC_Mt ? ">" : "\\(ra");
 		break;
-	case (MDOC_Bro):
+	case MDOC_Bro:
 		/* FALLTHROUGH */
-	case (MDOC_Brq):
+	case MDOC_Brq:
 		term_word(p, "}");
 		break;
-	case (MDOC_Oo):
+	case MDOC_Oo:
 		/* FALLTHROUGH */
-	case (MDOC_Op):
+	case MDOC_Op:
 		/* FALLTHROUGH */
-	case (MDOC_Bo):
+	case MDOC_Bo:
 		/* FALLTHROUGH */
-	case (MDOC_Bq):
+	case MDOC_Bq:
 		term_word(p, "]");
 		break;
-	case (MDOC_Do):
+	case MDOC_Do:
 		/* FALLTHROUGH */
-	case (MDOC_Dq):
-		term_word(p, "\\(rq");
+	case MDOC_Dq:
+		term_word(p, "\\(Rq");
 		break;
-	case (MDOC_Eo):
+	case MDOC_En:
+		if (n->norm->Es == NULL ||
+		    n->norm->Es->child == NULL ||
+		    n->norm->Es->child->next == NULL)
+			p->flags &= ~TERMP_NOSPACE;
+		else
+			term_word(p, n->norm->Es->child->next->string);
 		break;
-	case (MDOC_Po):
+	case MDOC_Po:
 		/* FALLTHROUGH */
-	case (MDOC_Pq):
+	case MDOC_Pq:
 		term_word(p, ")");
 		break;
-	case (MDOC__T):
+	case MDOC__T:
 		/* FALLTHROUGH */
-	case (MDOC_Qo):
+	case MDOC_Qo:
 		/* FALLTHROUGH */
-	case (MDOC_Qq):
+	case MDOC_Qq:
 		term_word(p, "\"");
 		break;
-	case (MDOC_Ql):
+	case MDOC_Ql:
 		/* FALLTHROUGH */
-	case (MDOC_So):
+	case MDOC_So:
 		/* FALLTHROUGH */
-	case (MDOC_Sq):
+	case MDOC_Sq:
 		term_word(p, "\\(cq");
 		break;
 	default:
@@ -2027,8 +1982,50 @@ termp_quote_post(DECL_ARGS)
 	}
 }
 
+static int
+termp_eo_pre(DECL_ARGS)
+{
+
+	if (n->type != MDOC_BODY)
+		return(1);
+
+	if (n->end == ENDBODY_NOT &&
+	    n->parent->head->child == NULL &&
+	    n->child != NULL &&
+	    n->child->end != ENDBODY_NOT)
+		term_word(p, "\\&");
+	else if (n->end != ENDBODY_NOT ? n->child != NULL :
+	     n->parent->head->child != NULL && (n->child != NULL ||
+	     (n->parent->tail != NULL && n->parent->tail->child != NULL)))
+		p->flags |= TERMP_NOSPACE;
+
+	return(1);
+}
+
+static void
+termp_eo_post(DECL_ARGS)
+{
+	int	 body, tail;
+
+	if (n->type != MDOC_BODY)
+		return;
+
+	if (n->end != ENDBODY_NOT) {
+		p->flags &= ~TERMP_NOSPACE;
+		return;
+	}
+
+	body = n->child != NULL || n->parent->head->child != NULL;
+	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
+
+	if (body && tail)
+		p->flags |= TERMP_NOSPACE;
+	else if ( ! (body || tail))
+		term_word(p, "\\&");
+	else if ( ! tail)
+		p->flags &= ~TERMP_NOSPACE;
+}
 
-/* ARGSUSED */
 static int
 termp_fo_pre(DECL_ARGS)
 {
@@ -2044,14 +2041,16 @@ termp_fo_pre(DECL_ARGS)
 		if (pretty) {
 			rmargin = p->rmargin;
 			p->rmargin = p->offset + term_len(p, 4);
-			p->flags |= TERMP_NOBREAK | TERMP_HANG;
+			p->flags |= TERMP_NOBREAK | TERMP_BRIND |
+					TERMP_HANG;
 		}
 		p->flags |= TERMP_NOSPACE;
 		term_word(p, "(");
 		p->flags |= TERMP_NOSPACE;
 		if (pretty) {
 			term_flushln(p);
-			p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
+			p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
+					TERMP_HANG);
 			p->offset = p->rmargin;
 			p->rmargin = rmargin;
 		}
@@ -2069,13 +2068,11 @@ termp_fo_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static void
 termp_fo_post(DECL_ARGS)
 {
 
-	if (MDOC_BODY != n->type) 
+	if (MDOC_BODY != n->type)
 		return;
 
 	p->flags |= TERMP_NOSPACE;
@@ -2088,8 +2085,6 @@ termp_fo_post(DECL_ARGS)
 	}
 }
 
-
-/* ARGSUSED */
 static int
 termp_bf_pre(DECL_ARGS)
 {
@@ -2099,35 +2094,33 @@ termp_bf_pre(DECL_ARGS)
 	else if (MDOC_BODY != n->type)
 		return(1);
 
-	if (FONT_Em == n->norm->Bf.font) 
+	if (FONT_Em == n->norm->Bf.font)
 		term_fontpush(p, TERMFONT_UNDER);
-	else if (FONT_Sy == n->norm->Bf.font) 
+	else if (FONT_Sy == n->norm->Bf.font)
 		term_fontpush(p, TERMFONT_BOLD);
-	else 
+	else
 		term_fontpush(p, TERMFONT_NONE);
 
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_sm_pre(DECL_ARGS)
 {
 
-	assert(n->child && MDOC_TEXT == n->child->type);
-	if (0 == strcmp("on", n->child->string)) {
-		if (p->col)
-			p->flags &= ~TERMP_NOSPACE;
+	if (NULL == n->child)
+		p->flags ^= TERMP_NONOSPACE;
+	else if (0 == strcmp("on", n->child->string))
 		p->flags &= ~TERMP_NONOSPACE;
-	} else
+	else
 		p->flags |= TERMP_NONOSPACE;
 
+	if (p->col && ! (TERMP_NONOSPACE & p->flags))
+		p->flags &= ~TERMP_NOSPACE;
+
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_ap_pre(DECL_ARGS)
 {
@@ -2138,8 +2131,6 @@ termp_ap_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp____post(DECL_ARGS)
 {
@@ -2167,8 +2158,6 @@ termp____post(DECL_ARGS)
 		term_word(p, ",");
 }
 
-
-/* ARGSUSED */
 static int
 termp_li_pre(DECL_ARGS)
 {
@@ -2177,8 +2166,6 @@ termp_li_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static int
 termp_lk_pre(DECL_ARGS)
 {
@@ -2205,18 +2192,16 @@ termp_lk_pre(DECL_ARGS)
 	return(0);
 }
 
-
-/* ARGSUSED */
 static int
 termp_bk_pre(DECL_ARGS)
 {
 
 	switch (n->type) {
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		return(0);
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		if (n->parent->args || 0 == n->prev->nchild)
 			p->flags |= TERMP_PREKEEP;
 		break;
@@ -2228,8 +2213,6 @@ termp_bk_pre(DECL_ARGS)
 	return(1);
 }
 
-
-/* ARGSUSED */
 static void
 termp_bk_post(DECL_ARGS)
 {
@@ -2238,7 +2221,6 @@ termp_bk_post(DECL_ARGS)
 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
 }
 
-/* ARGSUSED */
 static void
 termp__t_post(DECL_ARGS)
 {
@@ -2248,13 +2230,12 @@ termp__t_post(DECL_ARGS)
 	 * us instead of underlining us (for disambiguation).
 	 */
 	if (n->parent && MDOC_Rs == n->parent->tok &&
-			n->parent->norm->Rs.quote_T)
+	    n->parent->norm->Rs.quote_T)
 		termp_quote_post(p, pair, meta, n);
 
 	termp____post(p, pair, meta, n);
 }
 
-/* ARGSUSED */
 static int
 termp__t_pre(DECL_ARGS)
 {
@@ -2264,14 +2245,13 @@ termp__t_pre(DECL_ARGS)
 	 * us instead of underlining us (for disambiguation).
 	 */
 	if (n->parent && MDOC_Rs == n->parent->tok &&
-			n->parent->norm->Rs.quote_T)
+	    n->parent->norm->Rs.quote_T)
 		return(termp_quote_pre(p, pair, meta, n));
 
 	term_fontpush(p, TERMFONT_UNDER);
 	return(1);
 }
 
-/* ARGSUSED */
 static int
 termp_under_pre(DECL_ARGS)
 {
diff --git a/usr/src/cmd/mandoc/mdoc_validate.c b/usr/src/cmd/mandoc/mdoc_validate.c
index 4cfd620448..eb531e8289 100644
--- a/usr/src/cmd/mandoc/mdoc_validate.c
+++ b/usr/src/cmd/mandoc/mdoc_validate.c
@@ -1,7 +1,8 @@
-/*	$Id: mdoc_validate.c,v 1.198 2013/12/15 21:23:52 schwarze Exp $ */
+/*	$Id: mdoc_validate.c,v 1.283 2015/02/23 13:55:55 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010-2015 Ingo Schwarze 
+ * Copyright (c) 2010 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,16 +16,13 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
+#include 
 #ifndef OSNAME
 #include 
 #endif
 
-#include 
-
 #include 
 #include 
 #include 
@@ -35,6 +33,7 @@
 
 #include "mdoc.h"
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmdoc.h"
 #include "libmandoc.h"
 
@@ -43,223 +42,162 @@
 #define	PRE_ARGS  struct mdoc *mdoc, struct mdoc_node *n
 #define	POST_ARGS struct mdoc *mdoc
 
-#define	NUMSIZ	  32
-#define	DATESIZE  32
-
 enum	check_ineq {
 	CHECK_LT,
 	CHECK_GT,
 	CHECK_EQ
 };
 
-enum	check_lvl {
-	CHECK_WARN,
-	CHECK_ERROR,
-};
-
-typedef	int	(*v_pre)(PRE_ARGS);
-typedef	int	(*v_post)(POST_ARGS);
+typedef	void	(*v_pre)(PRE_ARGS);
+typedef	void	(*v_post)(POST_ARGS);
 
 struct	valids {
-	v_pre	*pre;
-	v_post	*post;
+	v_pre	 pre;
+	v_post	 post;
 };
 
-static	int	 check_count(struct mdoc *, enum mdoc_type, 
-			enum check_lvl, enum check_ineq, int);
-static	int	 check_parent(PRE_ARGS, enum mdoct, enum mdoc_type);
 static	void	 check_text(struct mdoc *, int, int, char *);
-static	void	 check_argv(struct mdoc *, 
+static	void	 check_argv(struct mdoc *,
 			struct mdoc_node *, struct mdoc_argv *);
 static	void	 check_args(struct mdoc *, struct mdoc_node *);
-static	int	 concat(char *, const struct mdoc_node *, size_t);
+static	int	 child_an(const struct mdoc_node *);
 static	enum mdoc_sec	a2sec(const char *);
 static	size_t		macro2len(enum mdoct);
-
-static	int	 ebool(POST_ARGS);
-static	int	 berr_ge1(POST_ARGS);
-static	int	 bwarn_ge1(POST_ARGS);
-static	int	 ewarn_eq0(POST_ARGS);
-static	int	 ewarn_eq1(POST_ARGS);
-static	int	 ewarn_ge1(POST_ARGS);
-static	int	 ewarn_le1(POST_ARGS);
-static	int	 hwarn_eq0(POST_ARGS);
-static	int	 hwarn_eq1(POST_ARGS);
-static	int	 hwarn_ge1(POST_ARGS);
-static	int	 hwarn_le1(POST_ARGS);
-
-static	int	 post_an(POST_ARGS);
-static	int	 post_at(POST_ARGS);
-static	int	 post_bf(POST_ARGS);
-static	int	 post_bl(POST_ARGS);
-static	int	 post_bl_block(POST_ARGS);
-static	int	 post_bl_block_width(POST_ARGS);
-static	int	 post_bl_block_tag(POST_ARGS);
-static	int	 post_bl_head(POST_ARGS);
-static	int	 post_bx(POST_ARGS);
-static	int	 post_defaults(POST_ARGS);
-static	int	 post_dd(POST_ARGS);
-static	int	 post_dt(POST_ARGS);
-static	int	 post_eoln(POST_ARGS);
-static	int	 post_hyph(POST_ARGS);
-static	int	 post_ignpar(POST_ARGS);
-static	int	 post_it(POST_ARGS);
-static	int	 post_lb(POST_ARGS);
-static	int	 post_literal(POST_ARGS);
-static	int	 post_nm(POST_ARGS);
-static	int	 post_ns(POST_ARGS);
-static	int	 post_os(POST_ARGS);
-static	int	 post_par(POST_ARGS);
-static	int	 post_prol(POST_ARGS);
-static	int	 post_root(POST_ARGS);
-static	int	 post_rs(POST_ARGS);
-static	int	 post_sh(POST_ARGS);
-static	int	 post_sh_body(POST_ARGS);
-static	int	 post_sh_head(POST_ARGS);
-static	int	 post_st(POST_ARGS);
-static	int	 post_std(POST_ARGS);
-static	int	 post_vt(POST_ARGS);
-static	int	 pre_an(PRE_ARGS);
-static	int	 pre_bd(PRE_ARGS);
-static	int	 pre_bl(PRE_ARGS);
-static	int	 pre_dd(PRE_ARGS);
-static	int	 pre_display(PRE_ARGS);
-static	int	 pre_dt(PRE_ARGS);
-static	int	 pre_it(PRE_ARGS);
-static	int	 pre_literal(PRE_ARGS);
-static	int	 pre_os(PRE_ARGS);
-static	int	 pre_par(PRE_ARGS);
-static	int	 pre_sh(PRE_ARGS);
-static	int	 pre_ss(PRE_ARGS);
-static	int	 pre_std(PRE_ARGS);
-
-static	v_post	 posts_an[] = { post_an, NULL };
-static	v_post	 posts_at[] = { post_at, post_defaults, NULL };
-static	v_post	 posts_bd[] = { post_literal, hwarn_eq0, bwarn_ge1, NULL };
-static	v_post	 posts_bf[] = { hwarn_le1, post_bf, NULL };
-static	v_post	 posts_bk[] = { hwarn_eq0, bwarn_ge1, NULL };
-static	v_post	 posts_bl[] = { bwarn_ge1, post_bl, NULL };
-static	v_post	 posts_bx[] = { post_bx, NULL };
-static	v_post	 posts_bool[] = { ebool, NULL };
-static	v_post	 posts_eoln[] = { post_eoln, NULL };
-static	v_post	 posts_defaults[] = { post_defaults, NULL };
-static	v_post	 posts_d1[] = { bwarn_ge1, post_hyph, NULL };
-static	v_post	 posts_dd[] = { post_dd, post_prol, NULL };
-static	v_post	 posts_dl[] = { post_literal, bwarn_ge1, NULL };
-static	v_post	 posts_dt[] = { post_dt, post_prol, NULL };
-static	v_post	 posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL };
-static	v_post	 posts_hyph[] = { post_hyph, NULL };
-static	v_post	 posts_hyphtext[] = { ewarn_ge1, post_hyph, NULL };
-static	v_post	 posts_it[] = { post_it, NULL };
-static	v_post	 posts_lb[] = { post_lb, NULL };
-static	v_post	 posts_nd[] = { berr_ge1, post_hyph, NULL };
-static	v_post	 posts_nm[] = { post_nm, NULL };
-static	v_post	 posts_notext[] = { ewarn_eq0, NULL };
-static	v_post	 posts_ns[] = { post_ns, NULL };
-static	v_post	 posts_os[] = { post_os, post_prol, NULL };
-static	v_post	 posts_pp[] = { post_par, ewarn_eq0, NULL };
-static	v_post	 posts_rs[] = { post_rs, NULL };
-static	v_post	 posts_sh[] = { post_ignpar,hwarn_ge1,post_sh,post_hyph,NULL };
-static	v_post	 posts_sp[] = { post_par, ewarn_le1, NULL };
-static	v_post	 posts_ss[] = { post_ignpar, hwarn_ge1, post_hyph, NULL };
-static	v_post	 posts_st[] = { post_st, NULL };
-static	v_post	 posts_std[] = { post_std, NULL };
-static	v_post	 posts_text[] = { ewarn_ge1, NULL };
-static	v_post	 posts_text1[] = { ewarn_eq1, NULL };
-static	v_post	 posts_vt[] = { post_vt, NULL };
-static	v_pre	 pres_an[] = { pre_an, NULL };
-static	v_pre	 pres_bd[] = { pre_display, pre_bd, pre_literal, pre_par, NULL };
-static	v_pre	 pres_bl[] = { pre_bl, pre_par, NULL };
-static	v_pre	 pres_d1[] = { pre_display, NULL };
-static	v_pre	 pres_dl[] = { pre_literal, pre_display, NULL };
-static	v_pre	 pres_dd[] = { pre_dd, NULL };
-static	v_pre	 pres_dt[] = { pre_dt, NULL };
-static	v_pre	 pres_it[] = { pre_it, pre_par, NULL };
-static	v_pre	 pres_os[] = { pre_os, NULL };
-static	v_pre	 pres_pp[] = { pre_par, NULL };
-static	v_pre	 pres_sh[] = { pre_sh, NULL };
-static	v_pre	 pres_ss[] = { pre_ss, NULL };
-static	v_pre	 pres_std[] = { pre_std, NULL };
+static	void	 rewrite_macro2len(char **);
+
+static	void	 post_an(POST_ARGS);
+static	void	 post_at(POST_ARGS);
+static	void	 post_bf(POST_ARGS);
+static	void	 post_bk(POST_ARGS);
+static	void	 post_bl(POST_ARGS);
+static	void	 post_bl_block(POST_ARGS);
+static	void	 post_bl_block_tag(POST_ARGS);
+static	void	 post_bl_head(POST_ARGS);
+static	void	 post_bx(POST_ARGS);
+static	void	 post_d1(POST_ARGS);
+static	void	 post_defaults(POST_ARGS);
+static	void	 post_dd(POST_ARGS);
+static	void	 post_dt(POST_ARGS);
+static	void	 post_en(POST_ARGS);
+static	void	 post_es(POST_ARGS);
+static	void	 post_eoln(POST_ARGS);
+static	void	 post_ex(POST_ARGS);
+static	void	 post_fa(POST_ARGS);
+static	void	 post_fn(POST_ARGS);
+static	void	 post_fname(POST_ARGS);
+static	void	 post_fo(POST_ARGS);
+static	void	 post_hyph(POST_ARGS);
+static	void	 post_ignpar(POST_ARGS);
+static	void	 post_it(POST_ARGS);
+static	void	 post_lb(POST_ARGS);
+static	void	 post_literal(POST_ARGS);
+static	void	 post_nd(POST_ARGS);
+static	void	 post_nm(POST_ARGS);
+static	void	 post_ns(POST_ARGS);
+static	void	 post_os(POST_ARGS);
+static	void	 post_par(POST_ARGS);
+static	void	 post_root(POST_ARGS);
+static	void	 post_rs(POST_ARGS);
+static	void	 post_sh(POST_ARGS);
+static	void	 post_sh_head(POST_ARGS);
+static	void	 post_sh_name(POST_ARGS);
+static	void	 post_sh_see_also(POST_ARGS);
+static	void	 post_sh_authors(POST_ARGS);
+static	void	 post_sm(POST_ARGS);
+static	void	 post_st(POST_ARGS);
+static	void	 post_vt(POST_ARGS);
+
+static	void	 pre_an(PRE_ARGS);
+static	void	 pre_bd(PRE_ARGS);
+static	void	 pre_bl(PRE_ARGS);
+static	void	 pre_dd(PRE_ARGS);
+static	void	 pre_display(PRE_ARGS);
+static	void	 pre_dt(PRE_ARGS);
+static	void	 pre_literal(PRE_ARGS);
+static	void	 pre_obsolete(PRE_ARGS);
+static	void	 pre_os(PRE_ARGS);
+static	void	 pre_par(PRE_ARGS);
+static	void	 pre_std(PRE_ARGS);
 
 static	const struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* Ap */
-	{ pres_dd, posts_dd },			/* Dd */
-	{ pres_dt, posts_dt },			/* Dt */
-	{ pres_os, posts_os },			/* Os */
-	{ pres_sh, posts_sh },			/* Sh */ 
-	{ pres_ss, posts_ss },			/* Ss */ 
-	{ pres_pp, posts_pp },			/* Pp */ 
-	{ pres_d1, posts_d1 },			/* D1 */
-	{ pres_dl, posts_dl },			/* Dl */
-	{ pres_bd, posts_bd },			/* Bd */
+	{ pre_dd, post_dd },			/* Dd */
+	{ pre_dt, post_dt },			/* Dt */
+	{ pre_os, post_os },			/* Os */
+	{ NULL, post_sh },			/* Sh */
+	{ NULL, post_ignpar },			/* Ss */
+	{ pre_par, post_par },			/* Pp */
+	{ pre_display, post_d1 },		/* D1 */
+	{ pre_literal, post_literal },		/* Dl */
+	{ pre_bd, post_literal },		/* Bd */
 	{ NULL, NULL },				/* Ed */
-	{ pres_bl, posts_bl },			/* Bl */ 
+	{ pre_bl, post_bl },			/* Bl */
 	{ NULL, NULL },				/* El */
-	{ pres_it, posts_it },			/* It */
-	{ NULL, NULL },				/* Ad */ 
-	{ pres_an, posts_an },			/* An */ 
-	{ NULL, posts_defaults },		/* Ar */
-	{ NULL, NULL },				/* Cd */ 
+	{ pre_par, post_it },			/* It */
+	{ NULL, NULL },				/* Ad */
+	{ pre_an, post_an },			/* An */
+	{ NULL, post_defaults },		/* Ar */
+	{ NULL, NULL },				/* Cd */
 	{ NULL, NULL },				/* Cm */
-	{ NULL, NULL },				/* Dv */ 
-	{ NULL, NULL },				/* Er */ 
-	{ NULL, NULL },				/* Ev */ 
-	{ pres_std, posts_std },		/* Ex */ 
-	{ NULL, NULL },				/* Fa */ 
-	{ NULL, posts_text },			/* Fd */
+	{ NULL, NULL },				/* Dv */
+	{ NULL, NULL },				/* Er */
+	{ NULL, NULL },				/* Ev */
+	{ pre_std, post_ex },			/* Ex */
+	{ NULL, post_fa },			/* Fa */
+	{ NULL, NULL },				/* Fd */
 	{ NULL, NULL },				/* Fl */
-	{ NULL, NULL },				/* Fn */ 
-	{ NULL, NULL },				/* Ft */ 
-	{ NULL, NULL },				/* Ic */ 
-	{ NULL, posts_text1 },			/* In */ 
-	{ NULL, posts_defaults },		/* Li */
-	{ NULL, posts_nd },			/* Nd */
-	{ NULL, posts_nm },			/* Nm */
+	{ NULL, post_fn },			/* Fn */
+	{ NULL, NULL },				/* Ft */
+	{ NULL, NULL },				/* Ic */
+	{ NULL, NULL },				/* In */
+	{ NULL, post_defaults },		/* Li */
+	{ NULL, post_nd },			/* Nd */
+	{ NULL, post_nm },			/* Nm */
 	{ NULL, NULL },				/* Op */
-	{ NULL, NULL },				/* Ot */
-	{ NULL, posts_defaults },		/* Pa */
-	{ pres_std, posts_std },		/* Rv */
-	{ NULL, posts_st },			/* St */ 
+	{ pre_obsolete, NULL },			/* Ot */
+	{ NULL, post_defaults },		/* Pa */
+	{ pre_std, NULL },			/* Rv */
+	{ NULL, post_st },			/* St */
 	{ NULL, NULL },				/* Va */
-	{ NULL, posts_vt },			/* Vt */ 
-	{ NULL, posts_text },			/* Xr */ 
-	{ NULL, posts_text },			/* %A */
-	{ NULL, posts_hyphtext },		/* %B */ /* FIXME: can be used outside Rs/Re. */
-	{ NULL, posts_text },			/* %D */
-	{ NULL, posts_text },			/* %I */
-	{ NULL, posts_text },			/* %J */
-	{ NULL, posts_hyphtext },		/* %N */
-	{ NULL, posts_hyphtext },		/* %O */
-	{ NULL, posts_text },			/* %P */
-	{ NULL, posts_hyphtext },		/* %R */
-	{ NULL, posts_hyphtext },		/* %T */ /* FIXME: can be used outside Rs/Re. */
-	{ NULL, posts_text },			/* %V */
+	{ NULL, post_vt },			/* Vt */
+	{ NULL, NULL },				/* Xr */
+	{ NULL, NULL },				/* %A */
+	{ NULL, post_hyph },			/* %B */ /* FIXME: can be used outside Rs/Re. */
+	{ NULL, NULL },				/* %D */
+	{ NULL, NULL },				/* %I */
+	{ NULL, NULL },				/* %J */
+	{ NULL, post_hyph },			/* %N */
+	{ NULL, post_hyph },			/* %O */
+	{ NULL, NULL },				/* %P */
+	{ NULL, post_hyph },			/* %R */
+	{ NULL, post_hyph },			/* %T */ /* FIXME: can be used outside Rs/Re. */
+	{ NULL, NULL },				/* %V */
 	{ NULL, NULL },				/* Ac */
 	{ NULL, NULL },				/* Ao */
 	{ NULL, NULL },				/* Aq */
-	{ NULL, posts_at },			/* At */ 
+	{ NULL, post_at },			/* At */
 	{ NULL, NULL },				/* Bc */
-	{ NULL, posts_bf },			/* Bf */
+	{ NULL, post_bf },			/* Bf */
 	{ NULL, NULL },				/* Bo */
 	{ NULL, NULL },				/* Bq */
 	{ NULL, NULL },				/* Bsx */
-	{ NULL, posts_bx },			/* Bx */
-	{ NULL, posts_bool },			/* Db */
+	{ NULL, post_bx },			/* Bx */
+	{ pre_obsolete, NULL },			/* Db */
 	{ NULL, NULL },				/* Dc */
 	{ NULL, NULL },				/* Do */
 	{ NULL, NULL },				/* Dq */
 	{ NULL, NULL },				/* Ec */
-	{ NULL, NULL },				/* Ef */ 
-	{ NULL, NULL },				/* Em */ 
+	{ NULL, NULL },				/* Ef */
+	{ NULL, NULL },				/* Em */
 	{ NULL, NULL },				/* Eo */
 	{ NULL, NULL },				/* Fx */
-	{ NULL, NULL },				/* Ms */ 
-	{ NULL, posts_notext },			/* No */
-	{ NULL, posts_ns },			/* Ns */
+	{ NULL, NULL },				/* Ms */
+	{ NULL, NULL },				/* No */
+	{ NULL, post_ns },			/* Ns */
 	{ NULL, NULL },				/* Nx */
 	{ NULL, NULL },				/* Ox */
 	{ NULL, NULL },				/* Pc */
-	{ NULL, posts_text1 },			/* Pf */
+	{ NULL, NULL },				/* Pf */
 	{ NULL, NULL },				/* Po */
 	{ NULL, NULL },				/* Pq */
 	{ NULL, NULL },				/* Qc */
@@ -267,43 +205,44 @@ static	const struct valids mdoc_valids[MDOC_MAX] = {
 	{ NULL, NULL },				/* Qo */
 	{ NULL, NULL },				/* Qq */
 	{ NULL, NULL },				/* Re */
-	{ NULL, posts_rs },			/* Rs */
+	{ NULL, post_rs },			/* Rs */
 	{ NULL, NULL },				/* Sc */
 	{ NULL, NULL },				/* So */
 	{ NULL, NULL },				/* Sq */
-	{ NULL, posts_bool },			/* Sm */ 
-	{ NULL, posts_hyph },			/* Sx */
+	{ NULL, post_sm },			/* Sm */
+	{ NULL, post_hyph },			/* Sx */
 	{ NULL, NULL },				/* Sy */
 	{ NULL, NULL },				/* Tn */
 	{ NULL, NULL },				/* Ux */
 	{ NULL, NULL },				/* Xc */
 	{ NULL, NULL },				/* Xo */
-	{ NULL, posts_fo },			/* Fo */ 
-	{ NULL, NULL },				/* Fc */ 
+	{ NULL, post_fo },			/* Fo */
+	{ NULL, NULL },				/* Fc */
 	{ NULL, NULL },				/* Oo */
 	{ NULL, NULL },				/* Oc */
-	{ NULL, posts_bk },			/* Bk */
+	{ NULL, post_bk },			/* Bk */
 	{ NULL, NULL },				/* Ek */
-	{ NULL, posts_eoln },			/* Bt */
+	{ NULL, post_eoln },			/* Bt */
 	{ NULL, NULL },				/* Hf */
-	{ NULL, NULL },				/* Fr */
-	{ NULL, posts_eoln },			/* Ud */
-	{ NULL, posts_lb },			/* Lb */
-	{ pres_pp, posts_pp },			/* Lp */ 
-	{ NULL, NULL },				/* Lk */ 
-	{ NULL, posts_defaults },		/* Mt */ 
-	{ NULL, NULL },				/* Brq */ 
-	{ NULL, NULL },				/* Bro */ 
-	{ NULL, NULL },				/* Brc */ 
-	{ NULL, posts_text },			/* %C */
-	{ NULL, NULL },				/* Es */
-	{ NULL, NULL },				/* En */
+	{ pre_obsolete, NULL },			/* Fr */
+	{ NULL, post_eoln },			/* Ud */
+	{ NULL, post_lb },			/* Lb */
+	{ pre_par, post_par },			/* Lp */
+	{ NULL, NULL },				/* Lk */
+	{ NULL, post_defaults },		/* Mt */
+	{ NULL, NULL },				/* Brq */
+	{ NULL, NULL },				/* Bro */
+	{ NULL, NULL },				/* Brc */
+	{ NULL, NULL },				/* %C */
+	{ pre_obsolete, post_es },		/* Es */
+	{ pre_obsolete, post_en },		/* En */
 	{ NULL, NULL },				/* Dx */
-	{ NULL, posts_text },			/* %Q */
-	{ NULL, posts_pp },			/* br */
-	{ NULL, posts_sp },			/* sp */
-	{ NULL, posts_text1 },			/* %U */
+	{ NULL, NULL },				/* %Q */
+	{ NULL, post_par },			/* br */
+	{ NULL, post_par },			/* sp */
+	{ NULL, NULL },				/* %U */
 	{ NULL, NULL },				/* Ta */
+	{ NULL, NULL },				/* ll */
 };
 
 #define	RSORD_MAX 14 /* Number of `Rs' blocks. */
@@ -331,6 +270,7 @@ static	const char * const secnames[SEC__MAX] = {
 	"LIBRARY",
 	"SYNOPSIS",
 	"DESCRIPTION",
+	"CONTEXT",
 	"IMPLEMENTATION NOTES",
 	"RETURN VALUES",
 	"ENVIRONMENT",
@@ -350,169 +290,74 @@ static	const char * const secnames[SEC__MAX] = {
 	NULL
 };
 
-int
+
+void
 mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
 {
-	v_pre		*p;
-	int		 line, pos;
-	char		*tp;
+	v_pre	 p;
 
 	switch (n->type) {
-	case (MDOC_TEXT):
-		tp = n->string;
-		line = n->line;
-		pos = n->pos;
-		check_text(mdoc, line, pos, tp);
+	case MDOC_TEXT:
+		if (n->sec != SEC_SYNOPSIS || n->parent->tok != MDOC_Fd)
+			check_text(mdoc, n->line, n->pos, n->string);
 		/* FALLTHROUGH */
-	case (MDOC_TBL):
+	case MDOC_TBL:
 		/* FALLTHROUGH */
-	case (MDOC_EQN):
+	case MDOC_EQN:
 		/* FALLTHROUGH */
-	case (MDOC_ROOT):
-		return(1);
+	case MDOC_ROOT:
+		return;
 	default:
 		break;
 	}
 
 	check_args(mdoc, n);
-
-	if (NULL == mdoc_valids[n->tok].pre)
-		return(1);
-	for (p = mdoc_valids[n->tok].pre; *p; p++)
-		if ( ! (*p)(mdoc, n)) 
-			return(0);
-	return(1);
+	p = mdoc_valids[n->tok].pre;
+	if (*p)
+		(*p)(mdoc, n);
 }
 
-
-int
+void
 mdoc_valid_post(struct mdoc *mdoc)
 {
-	v_post		*p;
+	struct mdoc_node *n;
+	v_post p;
 
-	if (MDOC_VALID & mdoc->last->flags)
-		return(1);
-	mdoc->last->flags |= MDOC_VALID;
+	n = mdoc->last;
+	if (n->flags & MDOC_VALID)
+		return;
+	n->flags |= MDOC_VALID | MDOC_ENDED;
 
-	switch (mdoc->last->type) {
-	case (MDOC_TEXT):
+	switch (n->type) {
+	case MDOC_TEXT:
 		/* FALLTHROUGH */
-	case (MDOC_EQN):
+	case MDOC_EQN:
 		/* FALLTHROUGH */
-	case (MDOC_TBL):
-		return(1);
-	case (MDOC_ROOT):
-		return(post_root(mdoc));
-	default:
-		break;
-	}
-
-	if (NULL == mdoc_valids[mdoc->last->tok].post)
-		return(1);
-	for (p = mdoc_valids[mdoc->last->tok].post; *p; p++)
-		if ( ! (*p)(mdoc)) 
-			return(0);
-
-	return(1);
-}
-
-static int
-check_count(struct mdoc *mdoc, enum mdoc_type type, 
-		enum check_lvl lvl, enum check_ineq ineq, int val)
-{
-	const char	*p;
-	enum mandocerr	 t;
-
-	if (mdoc->last->type != type)
-		return(1);
-	
-	switch (ineq) {
-	case (CHECK_LT):
-		p = "less than ";
-		if (mdoc->last->nchild < val)
-			return(1);
-		break;
-	case (CHECK_GT):
-		p = "more than ";
-		if (mdoc->last->nchild > val)
-			return(1);
+	case MDOC_TBL:
 		break;
-	case (CHECK_EQ):
-		p = "";
-		if (val == mdoc->last->nchild)
-			return(1);
+	case MDOC_ROOT:
+		post_root(mdoc);
 		break;
 	default:
-		abort();
-		/* NOTREACHED */
-	}
-
-	t = lvl == CHECK_WARN ? MANDOCERR_ARGCWARN : MANDOCERR_ARGCOUNT;
-	mandoc_vmsg(t, mdoc->parse, mdoc->last->line, mdoc->last->pos,
-			"want %s%d children (have %d)",
-			p, val, mdoc->last->nchild);
-	return(1);
-}
-
-static int
-berr_ge1(POST_ARGS)
-{
-
-	return(check_count(mdoc, MDOC_BODY, CHECK_ERROR, CHECK_GT, 0));
-}
-
-static int
-bwarn_ge1(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0));
-}
-
-static int
-ewarn_eq0(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0));
-}
-
-static int
-ewarn_eq1(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1));
-}
 
-static int
-ewarn_ge1(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0));
-}
-
-static int
-ewarn_le1(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2));
-}
-
-static int
-hwarn_eq0(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0));
-}
+		/*
+		 * Closing delimiters are not special at the
+		 * beginning of a block, opening delimiters
+		 * are not special at the end.
+		 */
 
-static int
-hwarn_eq1(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 1));
-}
+		if (n->child != NULL)
+			n->child->flags &= ~MDOC_DELIMC;
+		if (n->last != NULL)
+			n->last->flags &= ~MDOC_DELIMO;
 
-static int
-hwarn_ge1(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_GT, 0));
-}
+		/* Call the macro's postprocessor. */
 
-static int
-hwarn_le1(POST_ARGS)
-{
-	return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_LT, 2));
+		p = mdoc_valids[n->tok].post;
+		if (*p)
+			(*p)(mdoc);
+		break;
+	}
 }
 
 static void
@@ -535,12 +380,6 @@ check_argv(struct mdoc *mdoc, struct mdoc_node *n, struct mdoc_argv *v)
 
 	for (i = 0; i < (int)v->sz; i++)
 		check_text(mdoc, v->line, v->pos, v->value[i]);
-
-	/* FIXME: move to post_std(). */
-
-	if (MDOC_Std == v->arg)
-		if ( ! (v->sz || mdoc->meta.name))
-			mdoc_nmsg(mdoc, n, MANDOCERR_NONAME);
 }
 
 static void
@@ -552,188 +391,167 @@ check_text(struct mdoc *mdoc, int ln, int pos, char *p)
 		return;
 
 	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
-		mdoc_pmsg(mdoc, ln, pos + (int)(p - cp), MANDOCERR_BADTAB);
+		mandoc_msg(MANDOCERR_FI_TAB, mdoc->parse,
+		    ln, pos + (int)(p - cp), NULL);
 }
 
-static int
-check_parent(PRE_ARGS, enum mdoct tok, enum mdoc_type t)
-{
-
-	assert(n->parent);
-	if ((MDOC_ROOT == t || tok == n->parent->tok) &&
-			(t == n->parent->type))
-		return(1);
-
-	mandoc_vmsg(MANDOCERR_SYNTCHILD, mdoc->parse, n->line, 
-			n->pos, "want parent %s", MDOC_ROOT == t ? 
-			"" : mdoc_macronames[tok]);
-	return(0);
-}
-
-
-static int
+static void
 pre_display(PRE_ARGS)
 {
 	struct mdoc_node *node;
 
 	if (MDOC_BLOCK != n->type)
-		return(1);
+		return;
 
-	for (node = mdoc->last->parent; node; node = node->parent) 
+	for (node = mdoc->last->parent; node; node = node->parent)
 		if (MDOC_BLOCK == node->type)
 			if (MDOC_Bd == node->tok)
 				break;
 
 	if (node)
-		mdoc_nmsg(mdoc, n, MANDOCERR_NESTEDDISP);
-
-	return(1);
+		mandoc_vmsg(MANDOCERR_BD_NEST,
+		    mdoc->parse, n->line, n->pos,
+		    "%s in Bd", mdoc_macronames[n->tok]);
 }
 
-
-static int
+static void
 pre_bl(PRE_ARGS)
 {
-	int		  i, comp, dup;
-	const char	 *offs, *width;
+	struct mdoc_argv *argv, *wa;
+	int		  i;
+	enum mdocargt	  mdoclt;
 	enum mdoc_list	  lt;
-	struct mdoc_node *np;
-
-	if (MDOC_BLOCK != n->type) {
-		if (ENDBODY_NOT != n->end) {
-			assert(n->pending);
-			np = n->pending->parent;
-		} else
-			np = n->parent;
 
-		assert(np);
-		assert(MDOC_BLOCK == np->type);
-		assert(MDOC_Bl == np->tok);
-		return(1);
-	}
+	if (n->type != MDOC_BLOCK)
+		return;
 
-	/* 
+	/*
 	 * First figure out which kind of list to use: bind ourselves to
 	 * the first mentioned list type and warn about any remaining
 	 * ones.  If we find no list type, we default to LIST_item.
 	 */
 
-	/* LINTED */
+	wa = (n->args == NULL) ? NULL : n->args->argv;
+	mdoclt = MDOC_ARG_MAX;
 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
+		argv = n->args->argv + i;
 		lt = LIST__NONE;
-		dup = comp = 0;
-		width = offs = NULL;
-		switch (n->args->argv[i].arg) {
+		switch (argv->arg) {
 		/* Set list types. */
-		case (MDOC_Bullet):
+		case MDOC_Bullet:
 			lt = LIST_bullet;
 			break;
-		case (MDOC_Dash):
+		case MDOC_Dash:
 			lt = LIST_dash;
 			break;
-		case (MDOC_Enum):
+		case MDOC_Enum:
 			lt = LIST_enum;
 			break;
-		case (MDOC_Hyphen):
+		case MDOC_Hyphen:
 			lt = LIST_hyphen;
 			break;
-		case (MDOC_Item):
+		case MDOC_Item:
 			lt = LIST_item;
 			break;
-		case (MDOC_Tag):
+		case MDOC_Tag:
 			lt = LIST_tag;
 			break;
-		case (MDOC_Diag):
+		case MDOC_Diag:
 			lt = LIST_diag;
 			break;
-		case (MDOC_Hang):
+		case MDOC_Hang:
 			lt = LIST_hang;
 			break;
-		case (MDOC_Ohang):
+		case MDOC_Ohang:
 			lt = LIST_ohang;
 			break;
-		case (MDOC_Inset):
+		case MDOC_Inset:
 			lt = LIST_inset;
 			break;
-		case (MDOC_Column):
+		case MDOC_Column:
 			lt = LIST_column;
 			break;
 		/* Set list arguments. */
-		case (MDOC_Compact):
-			dup = n->norm->Bl.comp;
-			comp = 1;
+		case MDOC_Compact:
+			if (n->norm->Bl.comp)
+				mandoc_msg(MANDOCERR_ARG_REP,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bl -compact");
+			n->norm->Bl.comp = 1;
 			break;
-		case (MDOC_Width):
-			/* NB: this can be empty! */
-			if (n->args->argv[i].sz) {
-				width = n->args->argv[i].value[0];
-				dup = (NULL != n->norm->Bl.width);
+		case MDOC_Width:
+			wa = argv;
+			if (0 == argv->sz) {
+				mandoc_msg(MANDOCERR_ARG_EMPTY,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bl -width");
+				n->norm->Bl.width = "0n";
 				break;
 			}
-			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+			if (NULL != n->norm->Bl.width)
+				mandoc_vmsg(MANDOCERR_ARG_REP,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bl -width %s",
+				    argv->value[0]);
+			rewrite_macro2len(argv->value);
+			n->norm->Bl.width = argv->value[0];
 			break;
-		case (MDOC_Offset):
-			/* NB: this can be empty! */
-			if (n->args->argv[i].sz) {
-				offs = n->args->argv[i].value[0];
-				dup = (NULL != n->norm->Bl.offs);
+		case MDOC_Offset:
+			if (0 == argv->sz) {
+				mandoc_msg(MANDOCERR_ARG_EMPTY,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bl -offset");
 				break;
 			}
-			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+			if (NULL != n->norm->Bl.offs)
+				mandoc_vmsg(MANDOCERR_ARG_REP,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bl -offset %s",
+				    argv->value[0]);
+			rewrite_macro2len(argv->value);
+			n->norm->Bl.offs = argv->value[0];
 			break;
 		default:
 			continue;
 		}
-
-		/* Check: duplicate auxiliary arguments. */
-
-		if (dup)
-			mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP);
-
-		if (comp && ! dup)
-			n->norm->Bl.comp = comp;
-		if (offs && ! dup)
-			n->norm->Bl.offs = offs;
-		if (width && ! dup)
-			n->norm->Bl.width = width;
+		if (LIST__NONE == lt)
+			continue;
+		mdoclt = argv->arg;
 
 		/* Check: multiple list types. */
 
-		if (LIST__NONE != lt && n->norm->Bl.type != LIST__NONE)
-			mdoc_nmsg(mdoc, n, MANDOCERR_LISTREP);
-
-		/* Assign list type. */
-
-		if (LIST__NONE != lt && n->norm->Bl.type == LIST__NONE) {
-			n->norm->Bl.type = lt;
-			/* Set column information, too. */
-			if (LIST_column == lt) {
-				n->norm->Bl.ncols = 
-					n->args->argv[i].sz;
-				n->norm->Bl.cols = (void *)
-					n->args->argv[i].value;
-			}
+		if (LIST__NONE != n->norm->Bl.type) {
+			mandoc_vmsg(MANDOCERR_BL_REP,
+			    mdoc->parse, n->line, n->pos,
+			    "Bl -%s", mdoc_argnames[argv->arg]);
+			continue;
 		}
 
 		/* The list type should come first. */
 
-		if (n->norm->Bl.type == LIST__NONE)
-			if (n->norm->Bl.width || 
-					n->norm->Bl.offs || 
-					n->norm->Bl.comp)
-				mdoc_nmsg(mdoc, n, MANDOCERR_LISTFIRST);
-
-		continue;
+		if (n->norm->Bl.width ||
+		    n->norm->Bl.offs ||
+		    n->norm->Bl.comp)
+			mandoc_vmsg(MANDOCERR_BL_LATETYPE,
+			    mdoc->parse, n->line, n->pos, "Bl -%s",
+			    mdoc_argnames[n->args->argv[0].arg]);
+
+		n->norm->Bl.type = lt;
+		if (LIST_column == lt) {
+			n->norm->Bl.ncols = argv->sz;
+			n->norm->Bl.cols = (void *)argv->value;
+		}
 	}
 
 	/* Allow lists to default to LIST_item. */
 
 	if (LIST__NONE == n->norm->Bl.type) {
-		mdoc_nmsg(mdoc, n, MANDOCERR_LISTTYPE);
+		mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
+		    n->line, n->pos, "Bl");
 		n->norm->Bl.type = LIST_item;
 	}
 
-	/* 
+	/*
 	 * Validate the width field.  Some list types don't need width
 	 * types and should be warned about them.  Others should have it
 	 * and must also be warned.  Yet others have a default and need
@@ -741,247 +559,216 @@ pre_bl(PRE_ARGS)
 	 */
 
 	switch (n->norm->Bl.type) {
-	case (LIST_tag):
+	case LIST_tag:
 		if (NULL == n->norm->Bl.width)
-			mdoc_nmsg(mdoc, n, MANDOCERR_NOWIDTHARG);
+			mandoc_msg(MANDOCERR_BL_NOWIDTH, mdoc->parse,
+			    n->line, n->pos, "Bl -tag");
 		break;
-	case (LIST_column):
+	case LIST_column:
 		/* FALLTHROUGH */
-	case (LIST_diag):
+	case LIST_diag:
 		/* FALLTHROUGH */
-	case (LIST_ohang):
+	case LIST_ohang:
 		/* FALLTHROUGH */
-	case (LIST_inset):
+	case LIST_inset:
 		/* FALLTHROUGH */
-	case (LIST_item):
+	case LIST_item:
 		if (n->norm->Bl.width)
-			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+			mandoc_vmsg(MANDOCERR_BL_SKIPW, mdoc->parse,
+			    wa->line, wa->pos, "Bl -%s",
+			    mdoc_argnames[mdoclt]);
 		break;
-	case (LIST_bullet):
+	case LIST_bullet:
 		/* FALLTHROUGH */
-	case (LIST_dash):
+	case LIST_dash:
 		/* FALLTHROUGH */
-	case (LIST_hyphen):
+	case LIST_hyphen:
 		if (NULL == n->norm->Bl.width)
 			n->norm->Bl.width = "2n";
 		break;
-	case (LIST_enum):
+	case LIST_enum:
 		if (NULL == n->norm->Bl.width)
 			n->norm->Bl.width = "3n";
 		break;
 	default:
 		break;
 	}
-
-	return(1);
+	pre_par(mdoc, n);
 }
 
-
-static int
+static void
 pre_bd(PRE_ARGS)
 {
-	int		  i, dup, comp;
-	enum mdoc_disp 	  dt;
-	const char	 *offs;
-	struct mdoc_node *np;
+	struct mdoc_argv *argv;
+	int		  i;
+	enum mdoc_disp	  dt;
 
-	if (MDOC_BLOCK != n->type) {
-		if (ENDBODY_NOT != n->end) {
-			assert(n->pending);
-			np = n->pending->parent;
-		} else
-			np = n->parent;
+	pre_literal(mdoc, n);
 
-		assert(np);
-		assert(MDOC_BLOCK == np->type);
-		assert(MDOC_Bd == np->tok);
-		return(1);
-	}
+	if (n->type != MDOC_BLOCK)
+		return;
 
-	/* LINTED */
 	for (i = 0; n->args && i < (int)n->args->argc; i++) {
+		argv = n->args->argv + i;
 		dt = DISP__NONE;
-		dup = comp = 0;
-		offs = NULL;
 
-		switch (n->args->argv[i].arg) {
-		case (MDOC_Centred):
-			dt = DISP_centred;
+		switch (argv->arg) {
+		case MDOC_Centred:
+			dt = DISP_centered;
 			break;
-		case (MDOC_Ragged):
+		case MDOC_Ragged:
 			dt = DISP_ragged;
 			break;
-		case (MDOC_Unfilled):
+		case MDOC_Unfilled:
 			dt = DISP_unfilled;
 			break;
-		case (MDOC_Filled):
+		case MDOC_Filled:
 			dt = DISP_filled;
 			break;
-		case (MDOC_Literal):
+		case MDOC_Literal:
 			dt = DISP_literal;
 			break;
-		case (MDOC_File):
-			mdoc_nmsg(mdoc, n, MANDOCERR_BADDISP);
-			return(0);
-		case (MDOC_Offset):
-			/* NB: this can be empty! */
-			if (n->args->argv[i].sz) {
-				offs = n->args->argv[i].value[0];
-				dup = (NULL != n->norm->Bd.offs);
+		case MDOC_File:
+			mandoc_msg(MANDOCERR_BD_FILE, mdoc->parse,
+			    n->line, n->pos, NULL);
+			break;
+		case MDOC_Offset:
+			if (0 == argv->sz) {
+				mandoc_msg(MANDOCERR_ARG_EMPTY,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bd -offset");
 				break;
 			}
-			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+			if (NULL != n->norm->Bd.offs)
+				mandoc_vmsg(MANDOCERR_ARG_REP,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bd -offset %s",
+				    argv->value[0]);
+			rewrite_macro2len(argv->value);
+			n->norm->Bd.offs = argv->value[0];
 			break;
-		case (MDOC_Compact):
-			comp = 1;
-			dup = n->norm->Bd.comp;
+		case MDOC_Compact:
+			if (n->norm->Bd.comp)
+				mandoc_msg(MANDOCERR_ARG_REP,
+				    mdoc->parse, argv->line,
+				    argv->pos, "Bd -compact");
+			n->norm->Bd.comp = 1;
 			break;
 		default:
 			abort();
 			/* NOTREACHED */
 		}
+		if (DISP__NONE == dt)
+			continue;
 
-		/* Check whether we have duplicates. */
-
-		if (dup)
-			mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP);
-
-		/* Make our auxiliary assignments. */
-
-		if (offs && ! dup)
-			n->norm->Bd.offs = offs;
-		if (comp && ! dup)
-			n->norm->Bd.comp = comp;
-
-		/* Check whether a type has already been assigned. */
-
-		if (DISP__NONE != dt && n->norm->Bd.type != DISP__NONE)
-			mdoc_nmsg(mdoc, n, MANDOCERR_DISPREP);
-
-		/* Make our type assignment. */
-
-		if (DISP__NONE != dt && n->norm->Bd.type == DISP__NONE)
+		if (DISP__NONE == n->norm->Bd.type)
 			n->norm->Bd.type = dt;
+		else
+			mandoc_vmsg(MANDOCERR_BD_REP,
+			    mdoc->parse, n->line, n->pos,
+			    "Bd -%s", mdoc_argnames[argv->arg]);
 	}
 
 	if (DISP__NONE == n->norm->Bd.type) {
-		mdoc_nmsg(mdoc, n, MANDOCERR_DISPTYPE);
+		mandoc_msg(MANDOCERR_BD_NOTYPE, mdoc->parse,
+		    n->line, n->pos, "Bd");
 		n->norm->Bd.type = DISP_ragged;
 	}
-
-	return(1);
-}
-
-
-static int
-pre_ss(PRE_ARGS)
-{
-
-	if (MDOC_BLOCK != n->type)
-		return(1);
-	return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY));
-}
-
-
-static int
-pre_sh(PRE_ARGS)
-{
-
-	if (MDOC_BLOCK != n->type)
-		return(1);
-	return(check_parent(mdoc, n, MDOC_MAX, MDOC_ROOT));
+	pre_par(mdoc, n);
 }
 
-
-static int
-pre_it(PRE_ARGS)
-{
-
-	if (MDOC_BLOCK != n->type)
-		return(1);
-
-	return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY));
-}
-
-
-static int
+static void
 pre_an(PRE_ARGS)
 {
-	int		 i;
+	struct mdoc_argv *argv;
+	size_t	 i;
 
-	if (NULL == n->args)
-		return(1);
-	
-	for (i = 1; i < (int)n->args->argc; i++)
-		mdoc_pmsg(mdoc, n->args->argv[i].line, 
-			n->args->argv[i].pos, MANDOCERR_IGNARGV);
+	if (n->args == NULL)
+		return;
 
-	if (MDOC_Split == n->args->argv[0].arg)
+	for (i = 1; i < n->args->argc; i++) {
+		argv = n->args->argv + i;
+		mandoc_vmsg(MANDOCERR_AN_REP,
+		    mdoc->parse, argv->line, argv->pos,
+		    "An -%s", mdoc_argnames[argv->arg]);
+	}
+
+	argv = n->args->argv;
+	if (argv->arg == MDOC_Split)
 		n->norm->An.auth = AUTH_split;
-	else if (MDOC_Nosplit == n->args->argv[0].arg)
+	else if (argv->arg == MDOC_Nosplit)
 		n->norm->An.auth = AUTH_nosplit;
 	else
 		abort();
-
-	return(1);
 }
 
-static int
+static void
 pre_std(PRE_ARGS)
 {
 
 	if (n->args && 1 == n->args->argc)
 		if (MDOC_Std == n->args->argv[0].arg)
-			return(1);
+			return;
 
-	mdoc_nmsg(mdoc, n, MANDOCERR_NOARGV);
-	return(1);
+	mandoc_msg(MANDOCERR_ARG_STD, mdoc->parse,
+	    n->line, n->pos, mdoc_macronames[n->tok]);
 }
 
-static int
-pre_dt(PRE_ARGS)
+static void
+pre_obsolete(PRE_ARGS)
 {
 
-	if (NULL == mdoc->meta.date || mdoc->meta.os)
-		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
+	if (MDOC_ELEM == n->type || MDOC_BLOCK == n->type)
+		mandoc_msg(MANDOCERR_MACRO_OBS, mdoc->parse,
+		    n->line, n->pos, mdoc_macronames[n->tok]);
+}
 
-	if (mdoc->meta.title)
-		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP);
+static void
+pre_dt(PRE_ARGS)
+{
 
-	return(1);
+	if (mdoc->meta.title != NULL)
+		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+		    n->line, n->pos, "Dt");
+	else if (mdoc->meta.os != NULL)
+		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+		    n->line, n->pos, "Dt after Os");
 }
 
-static int
+static void
 pre_os(PRE_ARGS)
 {
 
-	if (NULL == mdoc->meta.title || NULL == mdoc->meta.date)
-		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
-
-	if (mdoc->meta.os)
-		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP);
-
-	return(1);
+	if (mdoc->meta.os != NULL)
+		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+		    n->line, n->pos, "Os");
+	else if (mdoc->flags & MDOC_PBODY)
+		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
+		    n->line, n->pos, "Os");
 }
 
-static int
+static void
 pre_dd(PRE_ARGS)
 {
 
-	if (mdoc->meta.title || mdoc->meta.os)
-		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
-
-	if (mdoc->meta.date)
-		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP);
-
-	return(1);
+	if (mdoc->meta.date != NULL)
+		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
+		    n->line, n->pos, "Dd");
+	else if (mdoc->flags & MDOC_PBODY)
+		mandoc_msg(MANDOCERR_PROLOG_LATE, mdoc->parse,
+		    n->line, n->pos, "Dd");
+	else if (mdoc->meta.title != NULL)
+		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+		    n->line, n->pos, "Dd after Dt");
+	else if (mdoc->meta.os != NULL)
+		mandoc_msg(MANDOCERR_PROLOG_ORDER, mdoc->parse,
+		    n->line, n->pos, "Dd after Os");
 }
 
-
-static int
+static void
 post_bf(POST_ARGS)
 {
-	struct mdoc_node *np;
+	struct mdoc_node *np, *nch;
 	enum mdocargt	  arg;
 
 	/*
@@ -989,40 +776,30 @@ post_bf(POST_ARGS)
 	 * element, which contains the goods.
 	 */
 
-	if (MDOC_HEAD != mdoc->last->type) {
-		if (ENDBODY_NOT != mdoc->last->end) {
-			assert(mdoc->last->pending);
-			np = mdoc->last->pending->parent->head;
-		} else if (MDOC_BLOCK != mdoc->last->type) {
-			np = mdoc->last->parent->head;
-		} else 
-			np = mdoc->last->head;
-
-		assert(np);
-		assert(MDOC_HEAD == np->type);
-		assert(MDOC_Bf == np->tok);
-		return(1);
-	}
-
 	np = mdoc->last;
+	if (MDOC_HEAD != np->type)
+		return;
+
 	assert(MDOC_BLOCK == np->parent->type);
 	assert(MDOC_Bf == np->parent->tok);
 
-	/* 
-	 * Cannot have both argument and parameter.
-	 * If neither is specified, let it through with a warning. 
-	 */
+	/* Check the number of arguments. */
 
-	if (np->parent->args && np->child) {
-		mdoc_nmsg(mdoc, np, MANDOCERR_SYNTARGVCOUNT);
-		return(0);
-	} else if (NULL == np->parent->args && NULL == np->child) {
-		mdoc_nmsg(mdoc, np, MANDOCERR_FONTTYPE);
-		return(1);
+	nch = np->child;
+	if (NULL == np->parent->args) {
+		if (NULL == nch) {
+			mandoc_msg(MANDOCERR_BF_NOFONT, mdoc->parse,
+			    np->line, np->pos, "Bf");
+			return;
+		}
+		nch = nch->next;
 	}
+	if (NULL != nch)
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
+		    nch->line, nch->pos, "Bf ... %s", nch->string);
 
 	/* Extract argument into data. */
-	
+
 	if (np->parent->args) {
 		arg = np->parent->args->argv[0].arg;
 		if (MDOC_Emphasis == arg)
@@ -1033,7 +810,7 @@ post_bf(POST_ARGS)
 			np->norm->Bf.font = FONT_Sy;
 		else
 			abort();
-		return(1);
+		return;
 	}
 
 	/* Extract parameter into data. */
@@ -1044,57 +821,116 @@ post_bf(POST_ARGS)
 		np->norm->Bf.font = FONT_Li;
 	else if (0 == strcmp(np->child->string, "Sy"))
 		np->norm->Bf.font = FONT_Sy;
-	else 
-		mdoc_nmsg(mdoc, np, MANDOCERR_FONTTYPE);
-
-	return(1);
+	else
+		mandoc_vmsg(MANDOCERR_BF_BADFONT, mdoc->parse,
+		    np->child->line, np->child->pos,
+		    "Bf %s", np->child->string);
 }
 
-static int
+static void
 post_lb(POST_ARGS)
 {
-	const char	*p;
-	char		*buf;
-	size_t		 sz;
+	struct mdoc_node	*n;
+	const char		*stdlibname;
+	char			*libname;
 
-	check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1);
+	n = mdoc->last->child;
+	assert(MDOC_TEXT == n->type);
 
-	assert(mdoc->last->child);
-	assert(MDOC_TEXT == mdoc->last->child->type);
+	if (NULL == (stdlibname = mdoc_a2lib(n->string)))
+		mandoc_asprintf(&libname,
+		    "library \\(Lq%s\\(Rq", n->string);
+	else
+		libname = mandoc_strdup(stdlibname);
 
-	p = mdoc_a2lib(mdoc->last->child->string);
+	free(n->string);
+	n->string = libname;
+}
 
-	/* If lookup ok, replace with table value. */
+static void
+post_eoln(POST_ARGS)
+{
+	const struct mdoc_node *n;
 
-	if (p) {
-		free(mdoc->last->child->string);
-		mdoc->last->child->string = mandoc_strdup(p);
-		return(1);
-	}
+	n = mdoc->last;
+	if (n->child)
+		mandoc_vmsg(MANDOCERR_ARG_SKIP,
+		    mdoc->parse, n->line, n->pos,
+		    "%s %s", mdoc_macronames[n->tok],
+		    n->child->string);
+}
 
-	/* If not, use "library ``xxxx''. */
+static void
+post_fname(POST_ARGS)
+{
+	const struct mdoc_node	*n;
+	const char		*cp;
+	size_t			 pos;
 
-	sz = strlen(mdoc->last->child->string) +
-		2 + strlen("\\(lqlibrary\\(rq");
-	buf = mandoc_malloc(sz);
-	snprintf(buf, sz, "library \\(lq%s\\(rq", 
-			mdoc->last->child->string);
-	free(mdoc->last->child->string);
-	mdoc->last->child->string = buf;
-	return(1);
+	n = mdoc->last->child;
+	pos = strcspn(n->string, "()");
+	cp = n->string + pos;
+	if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
+		mandoc_msg(MANDOCERR_FN_PAREN, mdoc->parse,
+		    n->line, n->pos + pos, n->string);
 }
 
-static int
-post_eoln(POST_ARGS)
+static void
+post_fn(POST_ARGS)
 {
 
-	if (mdoc->last->child)
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST);
-	return(1);
+	post_fname(mdoc);
+	post_fa(mdoc);
 }
 
+static void
+post_fo(POST_ARGS)
+{
+	const struct mdoc_node	*n;
 
-static int
+	n = mdoc->last;
+
+	if (n->type != MDOC_HEAD)
+		return;
+
+	if (n->child == NULL) {
+		mandoc_msg(MANDOCERR_FO_NOHEAD, mdoc->parse,
+		    n->line, n->pos, "Fo");
+		return;
+	}
+	if (n->child != n->last) {
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
+		    n->child->next->line, n->child->next->pos,
+		    "Fo ... %s", n->child->next->string);
+		while (n->child != n->last)
+			mdoc_node_delete(mdoc, n->last);
+	}
+
+	post_fname(mdoc);
+}
+
+static void
+post_fa(POST_ARGS)
+{
+	const struct mdoc_node *n;
+	const char *cp;
+
+	for (n = mdoc->last->child; n != NULL; n = n->next) {
+		for (cp = n->string; *cp != '\0'; cp++) {
+			/* Ignore callbacks and alterations. */
+			if (*cp == '(' || *cp == '{')
+				break;
+			if (*cp != ',')
+				continue;
+			mandoc_msg(MANDOCERR_FA_COMMA, mdoc->parse,
+			    n->line, n->pos + (cp - n->string),
+			    n->string);
+			break;
+		}
+	}
+}
+
+static void
 post_vt(POST_ARGS)
 {
 	const struct mdoc_node *n;
@@ -1108,66 +944,93 @@ post_vt(POST_ARGS)
 	 */
 
 	if (MDOC_BODY != mdoc->last->type)
-		return(1);
-	
-	for (n = mdoc->last->child; n; n = n->next)
-		if (MDOC_TEXT != n->type) 
-			mdoc_nmsg(mdoc, n, MANDOCERR_CHILD);
+		return;
 
-	return(1);
+	for (n = mdoc->last->child; n; n = n->next)
+		if (MDOC_TEXT != n->type)
+			mandoc_msg(MANDOCERR_VT_CHILD, mdoc->parse,
+			    n->line, n->pos, mdoc_macronames[n->tok]);
 }
 
-
-static int
+static void
 post_nm(POST_ARGS)
 {
-	char		 buf[BUFSIZ];
-	int		 c;
+	struct mdoc_node	*n;
+
+	n = mdoc->last;
+
+	if (n->last != NULL &&
+	    (n->last->tok == MDOC_Pp ||
+	     n->last->tok == MDOC_Lp))
+		mdoc_node_relink(mdoc, n->last);
 
 	if (NULL != mdoc->meta.name)
-		return(1);
+		return;
 
-	/* Try to use our children for setting the meta name. */
+	mdoc_deroff(&mdoc->meta.name, n);
 
-	if (NULL != mdoc->last->child) {
-		buf[0] = '\0';
-		c = concat(buf, mdoc->last->child, BUFSIZ);
-	} else
-		c = 0;
-
-	switch (c) {
-	case (-1):
-		mdoc_nmsg(mdoc, mdoc->last->child, MANDOCERR_MEM);
-		return(0);
-	case (0):
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME);
-		mdoc->meta.name = mandoc_strdup("UNKNOWN");
-		break;
-	default:
-		mdoc->meta.name = mandoc_strdup(buf);
-		break;
-	}
-	return(1);
+	if (NULL == mdoc->meta.name)
+		mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
+		    n->line, n->pos, "Nm");
 }
 
-static int
+static void
+post_nd(POST_ARGS)
+{
+	struct mdoc_node	*n;
+
+	n = mdoc->last;
+
+	if (n->type != MDOC_BODY)
+		return;
+
+	if (n->child == NULL)
+		mandoc_msg(MANDOCERR_ND_EMPTY, mdoc->parse,
+		    n->line, n->pos, "Nd");
+
+	post_hyph(mdoc);
+}
+
+static void
+post_d1(POST_ARGS)
+{
+	struct mdoc_node	*n;
+
+	n = mdoc->last;
+
+	if (n->type != MDOC_BODY)
+		return;
+
+	if (n->child == NULL)
+		mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
+		    n->line, n->pos, "D1");
+
+	post_hyph(mdoc);
+}
+
+static void
 post_literal(POST_ARGS)
 {
-	
-	/*
-	 * The `Dl' (note "el" not "one") and `Bd' macros unset the
-	 * MDOC_LITERAL flag as they leave.  Note that `Bd' only sets
-	 * this in literal mode, but it doesn't hurt to just switch it
-	 * off in general since displays can't be nested.
-	 */
+	struct mdoc_node	*n;
 
-	if (MDOC_BODY == mdoc->last->type)
-		mdoc->flags &= ~MDOC_LITERAL;
+	n = mdoc->last;
+
+	if (n->type != MDOC_BODY)
+		return;
 
-	return(1);
+	if (n->child == NULL)
+		mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
+		    n->line, n->pos, mdoc_macronames[n->tok]);
+
+	if (n->tok == MDOC_Bd &&
+	    n->norm->Bd.type != DISP_literal &&
+	    n->norm->Bd.type != DISP_unfilled)
+		return;
+
+	mdoc->flags &= ~MDOC_LITERAL;
 }
 
-static int
+static void
 post_defaults(POST_ARGS)
 {
 	struct mdoc_node *nn;
@@ -1179,178 +1042,163 @@ post_defaults(POST_ARGS)
 	 */
 
 	if (mdoc->last->child)
-		return(1);
-	
+		return;
+
 	nn = mdoc->last;
 	mdoc->next = MDOC_NEXT_CHILD;
 
 	switch (nn->tok) {
-	case (MDOC_Ar):
-		if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "file"))
-			return(0);
-		if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "..."))
-			return(0);
-		break;
-	case (MDOC_At):
-		if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "AT&T"))
-			return(0);
-		if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "UNIX"))
-			return(0);
-		break;
-	case (MDOC_Li):
-		if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, ""))
-			return(0);
+	case MDOC_Ar:
+		mdoc_word_alloc(mdoc, nn->line, nn->pos, "file");
+		mdoc_word_alloc(mdoc, nn->line, nn->pos, "...");
 		break;
-	case (MDOC_Pa):
+	case MDOC_Pa:
 		/* FALLTHROUGH */
-	case (MDOC_Mt):
-		if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "~"))
-			return(0);
+	case MDOC_Mt:
+		mdoc_word_alloc(mdoc, nn->line, nn->pos, "~");
 		break;
 	default:
 		abort();
 		/* NOTREACHED */
-	} 
-
+	}
 	mdoc->last = nn;
-	return(1);
 }
 
-static int
+static void
 post_at(POST_ARGS)
 {
-	const char	 *p, *q;
-	char		 *buf;
-	size_t		  sz;
+	struct mdoc_node	*n;
+	const char		*std_att;
+	char			*att;
+
+	n = mdoc->last;
+	if (n->child == NULL) {
+		mdoc->next = MDOC_NEXT_CHILD;
+		mdoc_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
+		mdoc->last = n;
+		return;
+	}
 
 	/*
 	 * If we have a child, look it up in the standard keys.  If a
 	 * key exist, use that instead of the child; if it doesn't,
 	 * prefix "AT&T UNIX " to the existing data.
 	 */
-	
-	if (NULL == mdoc->last->child)
-		return(1);
-
-	assert(MDOC_TEXT == mdoc->last->child->type);
-	p = mdoc_a2att(mdoc->last->child->string);
 
-	if (p) {
-		free(mdoc->last->child->string);
-		mdoc->last->child->string = mandoc_strdup(p);
-	} else {
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADATT);
-		p = "AT&T UNIX ";
-		q = mdoc->last->child->string;
-		sz = strlen(p) + strlen(q) + 1;
-		buf = mandoc_malloc(sz);
-		strlcpy(buf, p, sz);
-		strlcat(buf, q, sz);
-		free(mdoc->last->child->string);
-		mdoc->last->child->string = buf;
-	}
+	n = n->child;
+	assert(MDOC_TEXT == n->type);
+	if (NULL == (std_att = mdoc_a2att(n->string))) {
+		mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
+		    n->line, n->pos, "At %s", n->string);
+		mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
+	} else
+		att = mandoc_strdup(std_att);
 
-	return(1);
+	free(n->string);
+	n->string = att;
 }
 
-static int
+static void
 post_an(POST_ARGS)
 {
-	struct mdoc_node *np;
+	struct mdoc_node *np, *nch;
 
 	np = mdoc->last;
-	if (AUTH__NONE == np->norm->An.auth) {
-		if (0 == np->child)
-			check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0);
-	} else if (np->child)
-		check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0);
+	nch = np->child;
+	if (np->norm->An.auth == AUTH__NONE) {
+		if (nch == NULL)
+			mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+			    np->line, np->pos, "An");
+	} else if (nch != NULL)
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
+		    nch->line, nch->pos, "An ... %s", nch->string);
+}
+
+static void
+post_en(POST_ARGS)
+{
 
-	return(1);
+	if (MDOC_BLOCK == mdoc->last->type)
+		mdoc->last->norm->Es = mdoc->last_es;
 }
 
+static void
+post_es(POST_ARGS)
+{
 
-static int
+	mdoc->last_es = mdoc->last;
+}
+
+static void
 post_it(POST_ARGS)
 {
 	int		  i, cols;
 	enum mdoc_list	  lt;
-	struct mdoc_node *n, *c;
-	enum mandocerr	  er;
-
-	if (MDOC_BLOCK != mdoc->last->type)
-		return(1);
+	struct mdoc_node *nbl, *nit, *nch;
 
-	n = mdoc->last->parent->parent;
-	lt = n->norm->Bl.type;
+	nit = mdoc->last;
+	if (nit->type != MDOC_BLOCK)
+		return;
 
-	if (LIST__NONE == lt) {
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_LISTTYPE);
-		return(1);
-	}
+	nbl = nit->parent->parent;
+	lt = nbl->norm->Bl.type;
 
 	switch (lt) {
-	case (LIST_tag):
-		if (mdoc->last->head->child)
-			break;
-		/* FIXME: give this a dummy value. */
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS);
-		break;
-	case (LIST_hang):
+	case LIST_tag:
 		/* FALLTHROUGH */
-	case (LIST_ohang):
+	case LIST_hang:
 		/* FALLTHROUGH */
-	case (LIST_inset):
+	case LIST_ohang:
 		/* FALLTHROUGH */
-	case (LIST_diag):
-		if (NULL == mdoc->last->head->child)
-			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS);
+	case LIST_inset:
+		/* FALLTHROUGH */
+	case LIST_diag:
+		if (nit->head->child == NULL)
+			mandoc_vmsg(MANDOCERR_IT_NOHEAD,
+			    mdoc->parse, nit->line, nit->pos,
+			    "Bl -%s It",
+			    mdoc_argnames[nbl->args->argv[0].arg]);
 		break;
-	case (LIST_bullet):
+	case LIST_bullet:
 		/* FALLTHROUGH */
-	case (LIST_dash):
+	case LIST_dash:
 		/* FALLTHROUGH */
-	case (LIST_enum):
+	case LIST_enum:
 		/* FALLTHROUGH */
-	case (LIST_hyphen):
-		if (NULL == mdoc->last->body->child)
-			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY);
+	case LIST_hyphen:
+		if (nit->body == NULL || nit->body->child == NULL)
+			mandoc_vmsg(MANDOCERR_IT_NOBODY,
+			    mdoc->parse, nit->line, nit->pos,
+			    "Bl -%s It",
+			    mdoc_argnames[nbl->args->argv[0].arg]);
 		/* FALLTHROUGH */
-	case (LIST_item):
-		if (mdoc->last->head->child)
-			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST);
+	case LIST_item:
+		if (nit->head->child != NULL)
+			mandoc_vmsg(MANDOCERR_ARG_SKIP,
+			    mdoc->parse, nit->line, nit->pos,
+			    "It %s", nit->head->child->string);
 		break;
-	case (LIST_column):
-		cols = (int)n->norm->Bl.ncols;
-
-		assert(NULL == mdoc->last->head->child);
+	case LIST_column:
+		cols = (int)nbl->norm->Bl.ncols;
 
-		if (NULL == mdoc->last->body->child)
-			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY);
+		assert(nit->head->child == NULL);
 
-		for (i = 0, c = mdoc->last->child; c; c = c->next)
-			if (MDOC_BODY == c->type)
+		for (i = 0, nch = nit->child; nch; nch = nch->next)
+			if (nch->type == MDOC_BODY)
 				i++;
 
-		if (i < cols)
-			er = MANDOCERR_ARGCOUNT;
-		else if (i == cols || i == cols + 1)
-			break;
-		else
-			er = MANDOCERR_SYNTARGCOUNT;
-
-		mandoc_vmsg(er, mdoc->parse, mdoc->last->line, 
-				mdoc->last->pos, 
-				"columns == %d (have %d)", cols, i);
-		return(MANDOCERR_ARGCOUNT == er);
-	default:
+		if (i < cols || i > cols + 1)
+			mandoc_vmsg(MANDOCERR_BL_COL,
+			    mdoc->parse, nit->line, nit->pos,
+			    "%d columns, %d cells", cols, i);
 		break;
+	default:
+		abort();
 	}
-
-	return(1);
 }
 
-static int
-post_bl_block(POST_ARGS) 
+static void
+post_bl_block(POST_ARGS)
 {
 	struct mdoc_node *n, *ni, *nc;
 
@@ -1364,14 +1212,9 @@ post_bl_block(POST_ARGS)
 
 	n = mdoc->last;
 
-	if (LIST_tag == n->norm->Bl.type && 
-			NULL == n->norm->Bl.width) {
-		if ( ! post_bl_block_tag(mdoc))
-			return(0);
-		assert(n->norm->Bl.width);
-	} else if (NULL != n->norm->Bl.width) {
-		if ( ! post_bl_block_width(mdoc))
-			return(0);
+	if (LIST_tag == n->norm->Bl.type &&
+	    NULL == n->norm->Bl.width) {
+		post_bl_block_tag(mdoc);
 		assert(n->norm->Bl.width);
 	}
 
@@ -1381,93 +1224,71 @@ post_bl_block(POST_ARGS)
 		nc = ni->body->last;
 		while (NULL != nc) {
 			switch (nc->tok) {
-			case (MDOC_Pp):
+			case MDOC_Pp:
 				/* FALLTHROUGH */
-			case (MDOC_Lp):
+			case MDOC_Lp:
 				/* FALLTHROUGH */
-			case (MDOC_br):
+			case MDOC_br:
 				break;
 			default:
 				nc = NULL;
 				continue;
 			}
 			if (NULL == ni->next) {
-				mdoc_nmsg(mdoc, nc, MANDOCERR_MOVEPAR);
-				if ( ! mdoc_node_relink(mdoc, nc))
-					return(0);
+				mandoc_msg(MANDOCERR_PAR_MOVE,
+				    mdoc->parse, nc->line, nc->pos,
+				    mdoc_macronames[nc->tok]);
+				mdoc_node_relink(mdoc, nc);
 			} else if (0 == n->norm->Bl.comp &&
 			    LIST_column != n->norm->Bl.type) {
-				mdoc_nmsg(mdoc, nc, MANDOCERR_IGNPAR);
+				mandoc_vmsg(MANDOCERR_PAR_SKIP,
+				    mdoc->parse, nc->line, nc->pos,
+				    "%s before It",
+				    mdoc_macronames[nc->tok]);
 				mdoc_node_delete(mdoc, nc);
 			} else
 				break;
 			nc = ni->body->last;
 		}
 	}
-	return(1);
 }
 
-static int
-post_bl_block_width(POST_ARGS)
+/*
+ * If the argument of -offset or -width is a macro,
+ * replace it with the associated default width.
+ */
+void
+rewrite_macro2len(char **arg)
 {
 	size_t		  width;
-	int		  i;
 	enum mdoct	  tok;
-	struct mdoc_node *n;
-	char		  buf[NUMSIZ];
 
-	n = mdoc->last;
-
-	/*
-	 * Calculate the real width of a list from the -width string,
-	 * which may contain a macro (with a known default width), a
-	 * literal string, or a scaling width.
-	 *
-	 * If the value to -width is a macro, then we re-write it to be
-	 * the macro's width as set in share/tmac/mdoc/doc-common.
-	 */
-
-	if (0 == strcmp(n->norm->Bl.width, "Ds"))
+	if (*arg == NULL)
+		return;
+	else if ( ! strcmp(*arg, "Ds"))
 		width = 6;
-	else if (MDOC_MAX == (tok = mdoc_hash_find(n->norm->Bl.width)))
-		return(1);
-	else if (0 == (width = macro2len(tok)))  {
-		mdoc_nmsg(mdoc, n, MANDOCERR_BADWIDTH);
-		return(1);
-	}
-
-	/* The value already exists: free and reallocate it. */
-
-	assert(n->args);
-
-	for (i = 0; i < (int)n->args->argc; i++) 
-		if (MDOC_Width == n->args->argv[i].arg)
-			break;
-
-	assert(i < (int)n->args->argc);
-
-	snprintf(buf, NUMSIZ, "%un", (unsigned int)width);
-	free(n->args->argv[i].value[0]);
-	n->args->argv[i].value[0] = mandoc_strdup(buf);
+	else if ((tok = mdoc_hash_find(*arg)) == MDOC_MAX)
+		return;
+	else
+		width = macro2len(tok);
 
-	/* Set our width! */
-	n->norm->Bl.width = n->args->argv[i].value[0];
-	return(1);
+	free(*arg);
+	mandoc_asprintf(arg, "%zun", width);
 }
 
-static int
+static void
 post_bl_block_tag(POST_ARGS)
 {
 	struct mdoc_node *n, *nn;
 	size_t		  sz, ssz;
 	int		  i;
-	char		  buf[NUMSIZ];
+	char		  buf[24];
 
 	/*
 	 * Calculate the -width for a `Bl -tag' list if it hasn't been
 	 * provided.  Uses the first head macro.  NOTE AGAIN: this is
 	 * ONLY if the -width argument has NOT been provided.  See
-	 * post_bl_block_width() for converting the -width string.
+	 * rewrite_macro2len() for converting the -width string.
 	 */
 
 	sz = 10;
@@ -1492,11 +1313,11 @@ post_bl_block_tag(POST_ARGS)
 			sz = ssz;
 
 		break;
-	} 
+	}
 
 	/* Defaults to ten ens. */
 
-	snprintf(buf, NUMSIZ, "%un", (unsigned int)sz);
+	(void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
 
 	/*
 	 * We have to dynamically add this to the macro's argument list.
@@ -1506,8 +1327,8 @@ post_bl_block_tag(POST_ARGS)
 	assert(n->args);
 	i = (int)(n->args->argc)++;
 
-	n->args->argv = mandoc_realloc(n->args->argv, 
-			n->args->argc * sizeof(struct mdoc_argv));
+	n->args->argv = mandoc_reallocarray(n->args->argv,
+	    n->args->argc, sizeof(struct mdoc_argv));
 
 	n->args->argv[i].arg = MDOC_Width;
 	n->args->argv[i].line = n->line;
@@ -1518,48 +1339,44 @@ post_bl_block_tag(POST_ARGS)
 
 	/* Set our width! */
 	n->norm->Bl.width = n->args->argv[i].value[0];
-	return(1);
 }
 
-
-static int
-post_bl_head(POST_ARGS) 
+static void
+post_bl_head(POST_ARGS)
 {
-	struct mdoc_node *np, *nn, *nnp;
+	struct mdoc_node *nbl, *nh, *nch, *nnext;
+	struct mdoc_argv *argv;
 	int		  i, j;
 
-	if (LIST_column != mdoc->last->norm->Bl.type)
-		/* FIXME: this should be ERROR class... */
-		return(hwarn_eq0(mdoc));
+	nh = mdoc->last;
+
+	if (nh->norm->Bl.type != LIST_column) {
+		if ((nch = nh->child) == NULL)
+			return;
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
+		    nch->line, nch->pos, "Bl ... %s", nch->string);
+		while (nch != NULL) {
+			mdoc_node_delete(mdoc, nch);
+			nch = nh->child;
+		}
+		return;
+	}
 
 	/*
-	 * Convert old-style lists, where the column width specifiers
+	 * Append old-style lists, where the column width specifiers
 	 * trail as macro parameters, to the new-style ("normal-form")
 	 * lists where they're argument values following -column.
 	 */
 
-	/* First, disallow both types and allow normal-form. */
-
-	/* 
-	 * TODO: technically, we can accept both and just merge the two
-	 * lists, but I'll leave that for another day.
-	 */
-
-	if (mdoc->last->norm->Bl.ncols && mdoc->last->nchild) {
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_COLUMNS);
-		return(0);
-	} else if (NULL == mdoc->last->child)
-		return(1);
-
-	np = mdoc->last->parent;
-	assert(np->args);
+	if (nh->child == NULL)
+		return;
 
-	for (j = 0; j < (int)np->args->argc; j++) 
-		if (MDOC_Column == np->args->argv[j].arg)
+	nbl = nh->parent;
+	for (j = 0; j < (int)nbl->args->argc; j++)
+		if (nbl->args->argv[j].arg == MDOC_Column)
 			break;
 
-	assert(j < (int)np->args->argc);
-	assert(0 == np->args->argv[j].sz);
+	assert(j < (int)nbl->args->argc);
 
 	/*
 	 * Accommodate for new-style groff column syntax.  Shuffle the
@@ -1567,28 +1384,26 @@ post_bl_head(POST_ARGS)
 	 * column field.  Then, delete the head children.
 	 */
 
-	np->args->argv[j].sz = (size_t)mdoc->last->nchild;
-	np->args->argv[j].value = mandoc_malloc
-		((size_t)mdoc->last->nchild * sizeof(char *));
+	argv = nbl->args->argv + j;
+	i = argv->sz;
+	argv->sz += nh->nchild;
+	argv->value = mandoc_reallocarray(argv->value,
+	    argv->sz, sizeof(char *));
 
-	mdoc->last->norm->Bl.ncols = np->args->argv[j].sz;
-	mdoc->last->norm->Bl.cols = (void *)np->args->argv[j].value;
+	nh->norm->Bl.ncols = argv->sz;
+	nh->norm->Bl.cols = (void *)argv->value;
 
-	for (i = 0, nn = mdoc->last->child; nn; i++) {
-		np->args->argv[j].value[i] = nn->string;
-		nn->string = NULL;
-		nnp = nn;
-		nn = nn->next;
-		mdoc_node_delete(NULL, nnp);
+	for (nch = nh->child; nch != NULL; nch = nnext) {
+		argv->value[i++] = nch->string;
+		nch->string = NULL;
+		nnext = nch->next;
+		mdoc_node_delete(NULL, nch);
 	}
-
-	mdoc->last->nchild = 0;
-	mdoc->last->child = NULL;
-
-	return(1);
+	nh->nchild = 0;
+	nh->child = NULL;
 }
 
-static int
+static void
 post_bl(POST_ARGS)
 {
 	struct mdoc_node	*nparent, *nprev; /* of the Bl block */
@@ -1597,24 +1412,36 @@ post_bl(POST_ARGS)
 
 	nbody = mdoc->last;
 	switch (nbody->type) {
-	case (MDOC_BLOCK):
-		return(post_bl_block(mdoc));
-	case (MDOC_HEAD):
-		return(post_bl_head(mdoc));
-	case (MDOC_BODY):
+	case MDOC_BLOCK:
+		post_bl_block(mdoc);
+		return;
+	case MDOC_HEAD:
+		post_bl_head(mdoc);
+		return;
+	case MDOC_BODY:
 		break;
 	default:
-		return(1);
+		return;
 	}
 
 	nchild = nbody->child;
-	while (NULL != nchild) {
-		if (MDOC_It == nchild->tok || MDOC_Sm == nchild->tok) {
+	if (nchild == NULL) {
+		mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
+		    nbody->line, nbody->pos, "Bl");
+		return;
+	}
+	while (nchild != NULL) {
+		if (nchild->tok == MDOC_It ||
+		    (nchild->tok == MDOC_Sm &&
+		     nchild->next != NULL &&
+		     nchild->next->tok == MDOC_It)) {
 			nchild = nchild->next;
 			continue;
 		}
 
-		mdoc_nmsg(mdoc, nchild, MANDOCERR_CHILD);
+		mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
+		    nchild->line, nchild->pos,
+		    mdoc_macronames[nchild->tok]);
 
 		/*
 		 * Move the node out of the Bl block.
@@ -1657,178 +1484,169 @@ post_bl(POST_ARGS)
 
 		nchild = nnext;
 	}
+}
+
+static void
+post_bk(POST_ARGS)
+{
+	struct mdoc_node	*n;
+
+	n = mdoc->last;
 
-	return(1);
+	if (n->type == MDOC_BLOCK && n->body->child == NULL) {
+		mandoc_msg(MANDOCERR_BLK_EMPTY,
+		    mdoc->parse, n->line, n->pos, "Bk");
+		mdoc_node_delete(mdoc, n);
+	}
 }
 
-static int
-ebool(struct mdoc *mdoc)
+static void
+post_sm(struct mdoc *mdoc)
 {
+	struct mdoc_node	*nch;
 
-	if (NULL == mdoc->last->child) {
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY);
-		mdoc_node_delete(mdoc, mdoc->last);
-		return(1);
+	nch = mdoc->last->child;
+
+	if (nch == NULL) {
+		mdoc->flags ^= MDOC_SMOFF;
+		return;
 	}
-	check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1);
 
-	assert(MDOC_TEXT == mdoc->last->child->type);
+	assert(nch->type == MDOC_TEXT);
 
-	if (0 == strcmp(mdoc->last->child->string, "on")) {
-		if (MDOC_Sm == mdoc->last->tok)
-			mdoc->flags &= ~MDOC_SMOFF;
-		return(1);
+	if ( ! strcmp(nch->string, "on")) {
+		mdoc->flags &= ~MDOC_SMOFF;
+		return;
 	}
-	if (0 == strcmp(mdoc->last->child->string, "off")) {
-		if (MDOC_Sm == mdoc->last->tok)
-			mdoc->flags |= MDOC_SMOFF;
-		return(1);
+	if ( ! strcmp(nch->string, "off")) {
+		mdoc->flags |= MDOC_SMOFF;
+		return;
 	}
 
-	mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADBOOL);
-	return(1);
+	mandoc_vmsg(MANDOCERR_SM_BAD,
+	    mdoc->parse, nch->line, nch->pos,
+	    "%s %s", mdoc_macronames[mdoc->last->tok], nch->string);
+	mdoc_node_relink(mdoc, nch);
+	return;
 }
 
-static int
+static void
 post_root(POST_ARGS)
 {
-	int		  erc;
 	struct mdoc_node *n;
 
-	erc = 0;
+	/* Add missing prologue data. */
 
-	/* Check that we have a finished prologue. */
+	if (mdoc->meta.date == NULL)
+		mdoc->meta.date = mdoc->quick ?
+		    mandoc_strdup("") :
+		    mandoc_normdate(mdoc->parse, NULL, 0, 0);
 
-	if ( ! (MDOC_PBODY & mdoc->flags)) {
-		erc++;
-		mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCPROLOG);
+	if (mdoc->meta.title == NULL) {
+		mandoc_msg(MANDOCERR_DT_NOTITLE,
+		    mdoc->parse, 0, 0, "EOF");
+		mdoc->meta.title = mandoc_strdup("UNTITLED");
 	}
 
-	n = mdoc->first;
-	assert(n);
-	
-	/* Check that we begin with a proper `Sh'. */
+	if (mdoc->meta.vol == NULL)
+		mdoc->meta.vol = mandoc_strdup("LOCAL");
 
-	if (NULL == n->child) {
-		erc++;
-		mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY);
-	} else if (MDOC_BLOCK != n->child->type || 
-			MDOC_Sh != n->child->tok) {
-		erc++;
-		/* Can this be lifted?  See rxdebug.1 for example. */
-		mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY);
+	if (mdoc->meta.os == NULL) {
+		mandoc_msg(MANDOCERR_OS_MISSING,
+		    mdoc->parse, 0, 0, NULL);
+		mdoc->meta.os = mandoc_strdup("");
 	}
 
-	return(erc ? 0 : 1);
+	/* Check that we begin with a proper `Sh'. */
+
+	n = mdoc->first->child;
+	while (n != NULL && mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
+		n = n->next;
+
+	if (n == NULL)
+		mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
+	else if (n->tok != MDOC_Sh)
+		mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
+		    n->line, n->pos, mdoc_macronames[n->tok]);
 }
 
-static int
+static void
 post_st(POST_ARGS)
 {
-	struct mdoc_node	 *ch;
+	struct mdoc_node	 *n, *nch;
 	const char		 *p;
 
-	if (NULL == (ch = mdoc->last->child)) {
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY);
-		mdoc_node_delete(mdoc, mdoc->last);
-		return(1);
-	}
+	n = mdoc->last;
+	nch = n->child;
 
-	assert(MDOC_TEXT == ch->type);
+	assert(MDOC_TEXT == nch->type);
 
-	if (NULL == (p = mdoc_a2st(ch->string))) {
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADSTANDARD);
-		mdoc_node_delete(mdoc, mdoc->last);
+	if (NULL == (p = mdoc_a2st(nch->string))) {
+		mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
+		    nch->line, nch->pos, "St %s", nch->string);
+		mdoc_node_delete(mdoc, n);
 	} else {
-		free(ch->string);
-		ch->string = mandoc_strdup(p);
+		free(nch->string);
+		nch->string = mandoc_strdup(p);
 	}
-
-	return(1);
 }
 
-static int
+static void
 post_rs(POST_ARGS)
 {
-	struct mdoc_node *nn, *next, *prev;
+	struct mdoc_node *np, *nch, *next, *prev;
 	int		  i, j;
 
-	switch (mdoc->last->type) {
-	case (MDOC_HEAD):
-		check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0);
-		return(1);
-	case (MDOC_BODY):
-		if (mdoc->last->child)
-			break;
-		check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0);
-		return(1);
-	default:
-		return(1);
-	}
-
-	/*
-	 * Make sure only certain types of nodes are allowed within the
-	 * the `Rs' body.  Delete offending nodes and raise a warning.
-	 * Do this before re-ordering for the sake of clarity.
-	 */
-
-	next = NULL;
-	for (nn = mdoc->last->child; nn; nn = next) {
-		for (i = 0; i < RSORD_MAX; i++)
-			if (nn->tok == rsord[i])
-				break;
+	np = mdoc->last;
 
-		if (i < RSORD_MAX) {
-			if (MDOC__J == rsord[i] || MDOC__B == rsord[i])
-				mdoc->last->norm->Rs.quote_T++;
-			next = nn->next;
-			continue;
-		}
+	if (np->type != MDOC_BODY)
+		return;
 
-		next = nn->next;
-		mdoc_nmsg(mdoc, nn, MANDOCERR_CHILD);
-		mdoc_node_delete(mdoc, nn);
+	if (np->child == NULL) {
+		mandoc_msg(MANDOCERR_RS_EMPTY, mdoc->parse,
+		    np->line, np->pos, "Rs");
+		return;
 	}
 
-	/*
-	 * Nothing to sort if only invalid nodes were found
-	 * inside the `Rs' body.
-	 */
-
-	if (NULL == mdoc->last->child)
-		return(1);
-
 	/*
 	 * The full `Rs' block needs special handling to order the
 	 * sub-elements according to `rsord'.  Pick through each element
-	 * and correctly order it.  This is a insertion sort.
+	 * and correctly order it.  This is an insertion sort.
 	 */
 
 	next = NULL;
-	for (nn = mdoc->last->child->next; nn; nn = next) {
-		/* Determine order of `nn'. */
+	for (nch = np->child->next; nch != NULL; nch = next) {
+		/* Determine order number of this child. */
 		for (i = 0; i < RSORD_MAX; i++)
-			if (rsord[i] == nn->tok)
+			if (rsord[i] == nch->tok)
 				break;
 
-		/* 
-		 * Remove `nn' from the chain.  This somewhat
+		if (i == RSORD_MAX) {
+			mandoc_msg(MANDOCERR_RS_BAD,
+			    mdoc->parse, nch->line, nch->pos,
+			    mdoc_macronames[nch->tok]);
+			i = -1;
+		} else if (nch->tok == MDOC__J || nch->tok == MDOC__B)
+			np->norm->Rs.quote_T++;
+
+		/*
+		 * Remove this child from the chain.  This somewhat
 		 * repeats mdoc_node_unlink(), but since we're
 		 * just re-ordering, there's no need for the
 		 * full unlink process.
 		 */
-		
-		if (NULL != (next = nn->next))
-			next->prev = nn->prev;
 
-		if (NULL != (prev = nn->prev))
-			prev->next = nn->next;
+		if ((next = nch->next) != NULL)
+			next->prev = nch->prev;
 
-		nn->prev = nn->next = NULL;
+		if ((prev = nch->prev) != NULL)
+			prev->next = nch->next;
 
-		/* 
+		nch->prev = nch->next = NULL;
+
+		/*
 		 * Scan back until we reach a node that's
-		 * ordered before `nn'.
+		 * to be ordered before this child.
 		 */
 
 		for ( ; prev ; prev = prev->prev) {
@@ -1836,137 +1654,222 @@ post_rs(POST_ARGS)
 			for (j = 0; j < RSORD_MAX; j++)
 				if (rsord[j] == prev->tok)
 					break;
+			if (j == RSORD_MAX)
+				j = -1;
 
 			if (j <= i)
 				break;
 		}
 
 		/*
-		 * Set `nn' back into its correct place in front
-		 * of the `prev' node.
+		 * Set this child back into its correct place
+		 * in front of the `prev' node.
 		 */
 
-		nn->prev = prev;
+		nch->prev = prev;
 
-		if (prev) {
-			if (prev->next)
-				prev->next->prev = nn;
-			nn->next = prev->next;
-			prev->next = nn;
+		if (prev == NULL) {
+			np->child->prev = nch;
+			nch->next = np->child;
+			np->child = nch;
 		} else {
-			mdoc->last->child->prev = nn;
-			nn->next = mdoc->last->child;
-			mdoc->last->child = nn;
+			if (prev->next)
+				prev->next->prev = nch;
+			nch->next = prev->next;
+			prev->next = nch;
 		}
 	}
-
-	return(1);
 }
 
 /*
  * For some arguments of some macros,
  * convert all breakable hyphens into ASCII_HYPH.
  */
-static int
+static void
 post_hyph(POST_ARGS)
 {
-	struct mdoc_node	*n, *nch;
+	struct mdoc_node	*nch;
 	char			*cp;
 
-	n = mdoc->last;
-	switch (n->type) {
-	case (MDOC_HEAD):
-		if (MDOC_Sh == n->tok || MDOC_Ss == n->tok)
-			break;
-		return(1);
-	case (MDOC_BODY):
-		if (MDOC_D1 == n->tok || MDOC_Nd == n->tok)
-			break;
-		return(1);
-	case (MDOC_ELEM):
-		break;
-	default:
-		return(1);
-	}
-
-	for (nch = n->child; nch; nch = nch->next) {
-		if (MDOC_TEXT != nch->type)
+	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
+		if (nch->type != MDOC_TEXT)
 			continue;
 		cp = nch->string;
-		if (3 > strnlen(cp, 3))
+		if (*cp == '\0')
 			continue;
-		while ('\0' != *(++cp))
-			if ('-' == *cp &&
+		while (*(++cp) != '\0')
+			if (*cp == '-' &&
 			    isalpha((unsigned char)cp[-1]) &&
 			    isalpha((unsigned char)cp[1]))
 				*cp = ASCII_HYPH;
 	}
-	return(1);
 }
 
-static int
+static void
 post_ns(POST_ARGS)
 {
 
 	if (MDOC_LINE & mdoc->last->flags)
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNNS);
-	return(1);
+		mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos, NULL);
 }
 
-static int
+static void
 post_sh(POST_ARGS)
 {
 
-	if (MDOC_HEAD == mdoc->last->type)
-		return(post_sh_head(mdoc));
-	if (MDOC_BODY == mdoc->last->type)
-		return(post_sh_body(mdoc));
+	post_ignpar(mdoc);
 
-	return(1);
+	switch (mdoc->last->type) {
+	case MDOC_HEAD:
+		post_sh_head(mdoc);
+		break;
+	case MDOC_BODY:
+		switch (mdoc->lastsec)  {
+		case SEC_NAME:
+			post_sh_name(mdoc);
+			break;
+		case SEC_SEE_ALSO:
+			post_sh_see_also(mdoc);
+			break;
+		case SEC_AUTHORS:
+			post_sh_authors(mdoc);
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
 }
 
-static int
-post_sh_body(POST_ARGS)
+static void
+post_sh_name(POST_ARGS)
 {
 	struct mdoc_node *n;
+	int hasnm, hasnd;
 
-	if (SEC_NAME != mdoc->lastsec)
-		return(1);
-
-	/*
-	 * Warn if the NAME section doesn't contain the `Nm' and `Nd'
-	 * macros (can have multiple `Nm' and one `Nd').  Note that the
-	 * children of the BODY declaration can also be "text".
-	 */
+	hasnm = hasnd = 0;
 
-	if (NULL == (n = mdoc->last->child)) {
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
-		return(1);
+	for (n = mdoc->last->child; n != NULL; n = n->next) {
+		switch (n->tok) {
+		case MDOC_Nm:
+			hasnm = 1;
+			break;
+		case MDOC_Nd:
+			hasnd = 1;
+			if (n->next != NULL)
+				mandoc_msg(MANDOCERR_NAMESEC_ND,
+				    mdoc->parse, n->line, n->pos, NULL);
+			break;
+		case MDOC_MAX:
+			if (hasnm)
+				break;
+			/* FALLTHROUGH */
+		default:
+			mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+			    n->line, n->pos, mdoc_macronames[n->tok]);
+			break;
+		}
 	}
 
-	for ( ; n && n->next; n = n->next) {
-		if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
-			continue;
-		if (MDOC_TEXT == n->type)
+	if ( ! hasnm)
+		mandoc_msg(MANDOCERR_NAMESEC_NONM, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos, NULL);
+	if ( ! hasnd)
+		mandoc_msg(MANDOCERR_NAMESEC_NOND, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos, NULL);
+}
+
+static void
+post_sh_see_also(POST_ARGS)
+{
+	const struct mdoc_node	*n;
+	const char		*name, *sec;
+	const char		*lastname, *lastsec, *lastpunct;
+	int			 cmp;
+
+	n = mdoc->last->child;
+	lastname = lastsec = lastpunct = NULL;
+	while (n != NULL) {
+		if (n->tok != MDOC_Xr || n->nchild < 2)
+			break;
+
+		/* Process one .Xr node. */
+
+		name = n->child->string;
+		sec = n->child->next->string;
+		if (lastsec != NULL) {
+			if (lastpunct[0] != ',' || lastpunct[1] != '\0')
+				mandoc_vmsg(MANDOCERR_XR_PUNCT,
+				    mdoc->parse, n->line, n->pos,
+				    "%s before %s(%s)", lastpunct,
+				    name, sec);
+			cmp = strcmp(lastsec, sec);
+			if (cmp > 0)
+				mandoc_vmsg(MANDOCERR_XR_ORDER,
+				    mdoc->parse, n->line, n->pos,
+				    "%s(%s) after %s(%s)", name,
+				    sec, lastname, lastsec);
+			else if (cmp == 0 &&
+			    strcasecmp(lastname, name) > 0)
+				mandoc_vmsg(MANDOCERR_XR_ORDER,
+				    mdoc->parse, n->line, n->pos,
+				    "%s after %s", name, lastname);
+		}
+		lastname = name;
+		lastsec = sec;
+
+		/* Process the following node. */
+
+		n = n->next;
+		if (n == NULL)
+			break;
+		if (n->tok == MDOC_Xr) {
+			lastpunct = "none";
 			continue;
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
+		}
+		if (n->type != MDOC_TEXT)
+			break;
+		for (name = n->string; *name != '\0'; name++)
+			if (isalpha((const unsigned char)*name))
+				return;
+		lastpunct = n->string;
+		if (n->next == NULL)
+			mandoc_vmsg(MANDOCERR_XR_PUNCT, mdoc->parse,
+			    n->line, n->pos, "%s after %s(%s)",
+			    lastpunct, lastname, lastsec);
+		n = n->next;
 	}
+}
 
-	assert(n);
-	if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
-		return(1);
+static int
+child_an(const struct mdoc_node *n)
+{
 
-	mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
-	return(1);
+	for (n = n->child; n != NULL; n = n->next)
+		if ((n->tok == MDOC_An && n->nchild) || child_an(n))
+			return(1);
+	return(0);
 }
 
-static int
+static void
+post_sh_authors(POST_ARGS)
+{
+
+	if ( ! child_an(mdoc->last))
+		mandoc_msg(MANDOCERR_AN_MISSING, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos, NULL);
+}
+
+static void
 post_sh_head(POST_ARGS)
 {
-	char		 buf[BUFSIZ];
 	struct mdoc_node *n;
+	const char	*goodsec;
+	char		*secname;
 	enum mdoc_sec	 sec;
-	int		 c;
 
 	/*
 	 * Process a new section.  Sections are either "named" or
@@ -1975,18 +1878,17 @@ post_sh_head(POST_ARGS)
 	 * manual sections.
 	 */
 
+	secname = NULL;
 	sec = SEC_CUSTOM;
-	buf[0] = '\0';
-	if (-1 == (c = concat(buf, mdoc->last->child, BUFSIZ))) {
-		mdoc_nmsg(mdoc, mdoc->last->child, MANDOCERR_MEM);
-		return(0);
-	} else if (1 == c)
-		sec = a2sec(buf);
+	mdoc_deroff(&secname, mdoc->last);
+	sec = NULL == secname ? SEC_CUSTOM : a2sec(secname);
 
 	/* The NAME should be first. */
 
 	if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST);
+		mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos,
+		    "Sh %s", secname);
 
 	/* The SYNOPSIS gets special attention in other areas. */
 
@@ -2018,8 +1920,10 @@ post_sh_head(POST_ARGS)
 
 	/* We don't care about custom sections after this. */
 
-	if (SEC_CUSTOM == sec)
-		return(1);
+	if (SEC_CUSTOM == sec) {
+		free(secname);
+		return;
+	}
 
 	/*
 	 * Check whether our non-custom section is being repeated or is
@@ -2027,10 +1931,14 @@ post_sh_head(POST_ARGS)
 	 */
 
 	if (sec == mdoc->lastnamed)
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP);
+		mandoc_vmsg(MANDOCERR_SEC_REP, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos,
+		    "Sh %s", secname);
 
 	if (sec < mdoc->lastnamed)
-		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO);
+		mandoc_vmsg(MANDOCERR_SEC_ORDER, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos,
+		    "Sh %s", secname);
 
 	/* Mark the last named section. */
 
@@ -2038,63 +1946,87 @@ post_sh_head(POST_ARGS)
 
 	/* Check particular section/manual conventions. */
 
-	assert(mdoc->meta.msec);
+	if (mdoc->meta.msec == NULL) {
+		free(secname);
+		return;
+	}
 
+	goodsec = NULL;
 	switch (sec) {
-	case (SEC_RETURN_VALUES):
+	case SEC_ERRORS:
+		if (*mdoc->meta.msec == '4')
+			break;
+		goodsec = "2, 3, 4, 9";
 		/* FALLTHROUGH */
-	case (SEC_ERRORS):
+	case SEC_RETURN_VALUES:
 		/* FALLTHROUGH */
-	case (SEC_LIBRARY):
+	case SEC_LIBRARY:
 		if (*mdoc->meta.msec == '2')
 			break;
 		if (*mdoc->meta.msec == '3')
 			break;
+		if (NULL == goodsec)
+			goodsec = "2, 3, 9";
+		/* FALLTHROUGH */
+	case SEC_CONTEXT:
 		if (*mdoc->meta.msec == '9')
 			break;
-		mandoc_msg(MANDOCERR_SECMSEC, mdoc->parse,
-				mdoc->last->line, mdoc->last->pos, buf);
+		if (NULL == goodsec)
+			goodsec = "9";
+		mandoc_vmsg(MANDOCERR_SEC_MSEC, mdoc->parse,
+		    mdoc->last->line, mdoc->last->pos,
+		    "Sh %s for %s only", secname, goodsec);
 		break;
 	default:
 		break;
 	}
-
-	return(1);
+	free(secname);
 }
 
-static int
+static void
 post_ignpar(POST_ARGS)
 {
 	struct mdoc_node *np;
 
-	if (MDOC_BODY != mdoc->last->type)
-		return(1);
+	switch (mdoc->last->type) {
+	case MDOC_HEAD:
+		post_hyph(mdoc);
+		return;
+	case MDOC_BODY:
+		break;
+	default:
+		return;
+	}
 
 	if (NULL != (np = mdoc->last->child))
 		if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
-			mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR);
+			mandoc_vmsg(MANDOCERR_PAR_SKIP,
+			    mdoc->parse, np->line, np->pos,
+			    "%s after %s", mdoc_macronames[np->tok],
+			    mdoc_macronames[mdoc->last->tok]);
 			mdoc_node_delete(mdoc, np);
 		}
 
 	if (NULL != (np = mdoc->last->last))
 		if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
-			mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR);
+			mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+			    np->line, np->pos, "%s at the end of %s",
+			    mdoc_macronames[np->tok],
+			    mdoc_macronames[mdoc->last->tok]);
 			mdoc_node_delete(mdoc, np);
 		}
-
-	return(1);
 }
 
-static int
+static void
 pre_par(PRE_ARGS)
 {
 
 	if (NULL == mdoc->last)
-		return(1);
+		return;
 	if (MDOC_ELEM != n->type && MDOC_BLOCK != n->type)
-		return(1);
+		return;
 
-	/* 
+	/*
 	 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
 	 * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
 	 */
@@ -2102,51 +2034,62 @@ pre_par(PRE_ARGS)
 	if (MDOC_Pp != mdoc->last->tok &&
 	    MDOC_Lp != mdoc->last->tok &&
 	    MDOC_br != mdoc->last->tok)
-		return(1);
+		return;
 	if (MDOC_Bl == n->tok && n->norm->Bl.comp)
-		return(1);
+		return;
 	if (MDOC_Bd == n->tok && n->norm->Bd.comp)
-		return(1);
+		return;
 	if (MDOC_It == n->tok && n->parent->norm->Bl.comp)
-		return(1);
+		return;
 
-	mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR);
+	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+	    mdoc->last->line, mdoc->last->pos,
+	    "%s before %s", mdoc_macronames[mdoc->last->tok],
+	    mdoc_macronames[n->tok]);
 	mdoc_node_delete(mdoc, mdoc->last);
-	return(1);
 }
 
-static int
+static void
 post_par(POST_ARGS)
 {
+	struct mdoc_node *np;
 
-	if (MDOC_ELEM != mdoc->last->type &&
-	    MDOC_BLOCK != mdoc->last->type)
-		return(1);
+	np = mdoc->last;
 
-	if (NULL == mdoc->last->prev) {
-		if (MDOC_Sh != mdoc->last->parent->tok &&
-		    MDOC_Ss != mdoc->last->parent->tok)
-			return(1);
-	} else {
-		if (MDOC_Pp != mdoc->last->prev->tok &&
-		    MDOC_Lp != mdoc->last->prev->tok &&
-		    (MDOC_br != mdoc->last->tok ||
-		     (MDOC_sp != mdoc->last->prev->tok &&
-		      MDOC_br != mdoc->last->prev->tok)))
-			return(1);
-	}
+	if (np->tok == MDOC_sp) {
+		if (np->nchild > 1)
+			mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
+			    np->child->next->line, np->child->next->pos,
+			    "sp ... %s", np->child->next->string);
+	} else if (np->child != NULL)
+		mandoc_vmsg(MANDOCERR_ARG_SKIP,
+		    mdoc->parse, np->line, np->pos, "%s %s",
+		    mdoc_macronames[np->tok], np->child->string);
+
+	if (NULL == (np = mdoc->last->prev)) {
+		np = mdoc->last->parent;
+		if (MDOC_Sh != np->tok && MDOC_Ss != np->tok)
+			return;
+	} else if (MDOC_Pp != np->tok && MDOC_Lp != np->tok &&
+	    (MDOC_br != mdoc->last->tok ||
+	     (MDOC_sp != np->tok && MDOC_br != np->tok)))
+		return;
 
-	mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR);
+	mandoc_vmsg(MANDOCERR_PAR_SKIP, mdoc->parse,
+	    mdoc->last->line, mdoc->last->pos,
+	    "%s after %s", mdoc_macronames[mdoc->last->tok],
+	    mdoc_macronames[np->tok]);
 	mdoc_node_delete(mdoc, mdoc->last);
-	return(1);
 }
 
-static int
+static void
 pre_literal(PRE_ARGS)
 {
 
+	pre_display(mdoc, n);
+
 	if (MDOC_BODY != n->type)
-		return(1);
+		return;
 
 	/*
 	 * The `Dl' (note "el" not "one") and `Bd -literal' and `Bd
@@ -2154,10 +2097,10 @@ pre_literal(PRE_ARGS)
 	 */
 
 	switch (n->tok) {
-	case (MDOC_Dl):
+	case MDOC_Dl:
 		mdoc->flags |= MDOC_LITERAL;
 		break;
-	case (MDOC_Bd):
+	case MDOC_Bd:
 		if (DISP_literal == n->norm->Bd.type)
 			mdoc->flags |= MDOC_LITERAL;
 		if (DISP_unfilled == n->norm->Bd.type)
@@ -2167,41 +2110,38 @@ pre_literal(PRE_ARGS)
 		abort();
 		/* NOTREACHED */
 	}
-	
-	return(1);
 }
 
-static int
+static void
 post_dd(POST_ARGS)
 {
-	char		  buf[DATESIZE];
 	struct mdoc_node *n;
-	int		  c;
+	char		 *datestr;
 
 	if (mdoc->meta.date)
 		free(mdoc->meta.date);
 
 	n = mdoc->last;
 	if (NULL == n->child || '\0' == n->child->string[0]) {
-		mdoc->meta.date = mandoc_normdate
-			(mdoc->parse, NULL, n->line, n->pos);
-		return(1);
+		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
+		    mandoc_normdate(mdoc->parse, NULL, n->line, n->pos);
+		goto out;
 	}
 
-	buf[0] = '\0';
-	if (-1 == (c = concat(buf, n->child, DATESIZE))) {
-		mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM);
-		return(0);
+	datestr = NULL;
+	mdoc_deroff(&datestr, n);
+	if (mdoc->quick)
+		mdoc->meta.date = datestr;
+	else {
+		mdoc->meta.date = mandoc_normdate(mdoc->parse,
+		    datestr, n->line, n->pos);
+		free(datestr);
 	}
-
-	assert(c);
-	mdoc->meta.date = mandoc_normdate
-		(mdoc->parse, buf, n->line, n->pos);
-
-	return(1);
+out:
+	mdoc_node_delete(mdoc, n);
 }
 
-static int
+static void
 post_dt(POST_ARGS)
 {
 	struct mdoc_node *nn, *n;
@@ -2210,127 +2150,88 @@ post_dt(POST_ARGS)
 
 	n = mdoc->last;
 
-	if (mdoc->meta.title)
-		free(mdoc->meta.title);
-	if (mdoc->meta.vol)
-		free(mdoc->meta.vol);
-	if (mdoc->meta.arch)
-		free(mdoc->meta.arch);
+	free(mdoc->meta.title);
+	free(mdoc->meta.msec);
+	free(mdoc->meta.vol);
+	free(mdoc->meta.arch);
 
-	mdoc->meta.title = mdoc->meta.vol = mdoc->meta.arch = NULL;
+	mdoc->meta.title = NULL;
+	mdoc->meta.msec = NULL;
+	mdoc->meta.vol = NULL;
+	mdoc->meta.arch = NULL;
 
-	/* First make all characters uppercase. */
-
-	if (NULL != (nn = n->child))
-		for (p = nn->string; *p; p++) {
-			if (toupper((unsigned char)*p) == *p)
-				continue;
+	/* Mandatory first argument: title. */
 
-			/* 
-			 * FIXME: don't be lazy: have this make all
-			 * characters be uppercase and just warn once.
-			 */
-			mdoc_nmsg(mdoc, nn, MANDOCERR_UPPERCASE);
-			break;
-		}
+	nn = n->child;
+	if (nn == NULL || *nn->string == '\0') {
+		mandoc_msg(MANDOCERR_DT_NOTITLE,
+		    mdoc->parse, n->line, n->pos, "Dt");
+		mdoc->meta.title = mandoc_strdup("UNTITLED");
+	} else {
+		mdoc->meta.title = mandoc_strdup(nn->string);
 
-	/* Handles: `.Dt' 
-	 *   --> title = unknown, volume = local, msec = 0, arch = NULL
-	 */
+		/* Check that all characters are uppercase. */
 
-	if (NULL == (nn = n->child)) {
-		/* XXX: make these macro values. */
-		/* FIXME: warn about missing values. */
-		mdoc->meta.title = mandoc_strdup("UNKNOWN");
-		mdoc->meta.vol = mandoc_strdup("LOCAL");
-		mdoc->meta.msec = mandoc_strdup("1");
-		return(1);
+		for (p = nn->string; *p != '\0'; p++)
+			if (islower((unsigned char)*p)) {
+				mandoc_vmsg(MANDOCERR_TITLE_CASE,
+				    mdoc->parse, nn->line,
+				    nn->pos + (p - nn->string),
+				    "Dt %s", nn->string);
+				break;
+			}
 	}
 
-	/* Handles: `.Dt TITLE' 
-	 *   --> title = TITLE, volume = local, msec = 0, arch = NULL
-	 */
+	/* Mandatory second argument: section. */
 
-	mdoc->meta.title = mandoc_strdup
-		('\0' == nn->string[0] ? "UNKNOWN" : nn->string);
+	if (nn != NULL)
+		nn = nn->next;
 
-	if (NULL == (nn = nn->next)) {
-		/* FIXME: warn about missing msec. */
-		/* XXX: make this a macro value. */
+	if (nn == NULL) {
+		mandoc_vmsg(MANDOCERR_MSEC_MISSING,
+		    mdoc->parse, n->line, n->pos,
+		    "Dt %s", mdoc->meta.title);
 		mdoc->meta.vol = mandoc_strdup("LOCAL");
-		mdoc->meta.msec = mandoc_strdup("1");
-		return(1);
+		goto out;  /* msec and arch remain NULL. */
 	}
 
-	/* Handles: `.Dt TITLE SEC'
-	 *   --> title = TITLE, volume = SEC is msec ? 
-	 *           format(msec) : SEC,
-	 *       msec = SEC is msec ? atoi(msec) : 0,
-	 *       arch = NULL
-	 */
+	mdoc->meta.msec = mandoc_strdup(nn->string);
+
+	/* Infer volume title from section number. */
 
 	cp = mandoc_a2msec(nn->string);
-	if (cp) {
-		mdoc->meta.vol = mandoc_strdup(cp);
-		mdoc->meta.msec = mandoc_strdup(nn->string);
-	} else {
-		mdoc_nmsg(mdoc, n, MANDOCERR_BADMSEC);
+	if (cp == NULL) {
+		mandoc_vmsg(MANDOCERR_MSEC_BAD, mdoc->parse,
+		    nn->line, nn->pos, "Dt ... %s", nn->string);
 		mdoc->meta.vol = mandoc_strdup(nn->string);
-		mdoc->meta.msec = mandoc_strdup(nn->string);
-	} 
+	} else
+		mdoc->meta.vol = mandoc_strdup(cp);
 
-	if (NULL == (nn = nn->next))
-		return(1);
+	/* Optional third argument: architecture. */
 
-	/* Handles: `.Dt TITLE SEC VOL'
-	 *   --> title = TITLE, volume = VOL is vol ?
-	 *       format(VOL) : 
-	 *           VOL is arch ? format(arch) : 
-	 *               VOL
-	 */
+	if ((nn = nn->next) == NULL)
+		goto out;
 
-	cp = mdoc_a2vol(nn->string);
-	if (cp) {
-		free(mdoc->meta.vol);
-		mdoc->meta.vol = mandoc_strdup(cp);
-	} else {
-		cp = mdoc_a2arch(nn->string);
-		if (NULL == cp) {
-			mdoc_nmsg(mdoc, nn, MANDOCERR_BADVOLARCH);
-			free(mdoc->meta.vol);
-			mdoc->meta.vol = mandoc_strdup(nn->string);
-		} else 
-			mdoc->meta.arch = mandoc_strdup(cp);
-	}	
-
-	/* Ignore any subsequent parameters... */
-	/* FIXME: warn about subsequent parameters. */
-
-	return(1);
-}
+	for (p = nn->string; *p != '\0'; p++)
+		*p = tolower((unsigned char)*p);
+	mdoc->meta.arch = mandoc_strdup(nn->string);
 
-static int
-post_prol(POST_ARGS)
-{
-	/*
-	 * Remove prologue macros from the document after they're
-	 * processed.  The final document uses mdoc_meta for these
-	 * values and discards the originals.
-	 */
+	/* Ignore fourth and later arguments. */
 
-	mdoc_node_delete(mdoc, mdoc->last);
-	if (mdoc->meta.title && mdoc->meta.date && mdoc->meta.os)
-		mdoc->flags |= MDOC_PBODY;
+	if ((nn = nn->next) != NULL)
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
+		    nn->line, nn->pos, "Dt ... %s", nn->string);
 
-	return(1);
+out:
+	mdoc_node_delete(mdoc, n);
 }
 
-static int
+static void
 post_bx(POST_ARGS)
 {
 	struct mdoc_node	*n;
 
-	/* 
+	/*
 	 * Make `Bx's second argument always start with an uppercase
 	 * letter.  Groff checks if it's an "accepted" term, but we just
 	 * uppercase blindly.
@@ -2338,21 +2239,17 @@ post_bx(POST_ARGS)
 
 	n = mdoc->last->child;
 	if (n && NULL != (n = n->next))
-		*n->string = (char)toupper
-			((unsigned char)*n->string);
-
-	return(1);
+		*n->string = (char)toupper((unsigned char)*n->string);
 }
 
-static int
+static void
 post_os(POST_ARGS)
 {
-	struct mdoc_node *n;
-	char		  buf[BUFSIZ];
-	int		  c;
 #ifndef OSNAME
 	struct utsname	  utsname;
+	static char	 *defbuf;
 #endif
+	struct mdoc_node *n;
 
 	n = mdoc->last;
 
@@ -2363,112 +2260,69 @@ post_os(POST_ARGS)
 	 * 2. the -Ios=foo command line argument, if provided
 	 * 3. -DOSNAME="\"foo\"", if provided during compilation
 	 * 4. "sysname release" from uname(3)
- 	 */
+	 */
 
 	free(mdoc->meta.os);
+	mdoc->meta.os = NULL;
+	mdoc_deroff(&mdoc->meta.os, n);
+	if (mdoc->meta.os)
+		goto out;
 
-	buf[0] = '\0';
-	if (-1 == (c = concat(buf, n->child, BUFSIZ))) {
-		mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM);
-		return(0);
+	if (mdoc->defos) {
+		mdoc->meta.os = mandoc_strdup(mdoc->defos);
+		goto out;
 	}
 
-	assert(c);
-
-	if ('\0' == buf[0]) {
-		if (mdoc->defos) {
-			mdoc->meta.os = mandoc_strdup(mdoc->defos);
-			return(1);
-		}
 #ifdef OSNAME
-		if (strlcat(buf, OSNAME, BUFSIZ) >= BUFSIZ) {
-			mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-			return(0);
-		}
+	mdoc->meta.os = mandoc_strdup(OSNAME);
 #else /*!OSNAME */
+	if (NULL == defbuf) {
 		if (-1 == uname(&utsname)) {
-			mdoc_nmsg(mdoc, n, MANDOCERR_UNAME);
-                        mdoc->meta.os = mandoc_strdup("UNKNOWN");
-                        return(post_prol(mdoc));
-                }
-
-		if (strlcat(buf, utsname.sysname, BUFSIZ) >= BUFSIZ) {
-			mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-			return(0);
-		}
-		if (strlcat(buf, " ", BUFSIZ) >= BUFSIZ) {
-			mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-			return(0);
-		}
-		if (strlcat(buf, utsname.release, BUFSIZ) >= BUFSIZ) {
-			mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
-			return(0);
-		}
-#endif /*!OSNAME*/
+			mandoc_msg(MANDOCERR_OS_UNAME, mdoc->parse,
+			    n->line, n->pos, "Os");
+			defbuf = mandoc_strdup("UNKNOWN");
+		} else
+			mandoc_asprintf(&defbuf, "%s %s",
+			    utsname.sysname, utsname.release);
 	}
+	mdoc->meta.os = mandoc_strdup(defbuf);
+#endif /*!OSNAME*/
 
-	mdoc->meta.os = mandoc_strdup(buf);
-	return(1);
+out:
+	mdoc_node_delete(mdoc, n);
 }
 
-static int
-post_std(POST_ARGS)
+/*
+ * If no argument is provided,
+ * fill in the name of the current manual page.
+ */
+static void
+post_ex(POST_ARGS)
 {
-	struct mdoc_node *nn, *n;
+	struct mdoc_node *n;
 
 	n = mdoc->last;
 
-	/*
-	 * Macros accepting `-std' as an argument have the name of the
-	 * current document (`Nm') filled in as the argument if it's not
-	 * provided.
-	 */
-
 	if (n->child)
-		return(1);
-
-	if (NULL == mdoc->meta.name)
-		return(1);
-	
-	nn = n;
-	mdoc->next = MDOC_NEXT_CHILD;
-
-	if ( ! mdoc_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name))
-		return(0);
-
-	mdoc->last = nn;
-	return(1);
-}
-
-/*
- * Concatenate a node, stopping at the first non-text.
- * Concatenation is separated by a single whitespace.  
- * Returns -1 on fatal (string overrun) error, 0 if child nodes were
- * encountered, 1 otherwise.
- */
-static int
-concat(char *p, const struct mdoc_node *n, size_t sz)
-{
+		return;
 
-	for ( ; NULL != n; n = n->next) {
-		if (MDOC_TEXT != n->type) 
-			return(0);
-		if ('\0' != p[0] && strlcat(p, " ", sz) >= sz)
-			return(-1);
-		if (strlcat(p, n->string, sz) >= sz)
-			return(-1);
-		concat(p, n->child, sz);
+	if (mdoc->meta.name == NULL) {
+		mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
+		    n->line, n->pos, "Ex");
+		return;
 	}
 
-	return(1);
+	mdoc->next = MDOC_NEXT_CHILD;
+	mdoc_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
+	mdoc->last = n;
 }
 
-static enum mdoc_sec 
+static enum mdoc_sec
 a2sec(const char *p)
 {
 	int		 i;
 
-	for (i = 0; i < (int)SEC__MAX; i++) 
+	for (i = 0; i < (int)SEC__MAX; i++)
 		if (secnames[i] && 0 == strcmp(p, secnames[i]))
 			return((enum mdoc_sec)i);
 
@@ -2480,87 +2334,87 @@ macro2len(enum mdoct macro)
 {
 
 	switch (macro) {
-	case(MDOC_Ad):
+	case MDOC_Ad:
 		return(12);
-	case(MDOC_Ao):
+	case MDOC_Ao:
 		return(12);
-	case(MDOC_An):
+	case MDOC_An:
 		return(12);
-	case(MDOC_Aq):
+	case MDOC_Aq:
 		return(12);
-	case(MDOC_Ar):
+	case MDOC_Ar:
 		return(12);
-	case(MDOC_Bo):
+	case MDOC_Bo:
 		return(12);
-	case(MDOC_Bq):
+	case MDOC_Bq:
 		return(12);
-	case(MDOC_Cd):
+	case MDOC_Cd:
 		return(12);
-	case(MDOC_Cm):
+	case MDOC_Cm:
 		return(10);
-	case(MDOC_Do):
+	case MDOC_Do:
 		return(10);
-	case(MDOC_Dq):
+	case MDOC_Dq:
 		return(12);
-	case(MDOC_Dv):
+	case MDOC_Dv:
 		return(12);
-	case(MDOC_Eo):
+	case MDOC_Eo:
 		return(12);
-	case(MDOC_Em):
+	case MDOC_Em:
 		return(10);
-	case(MDOC_Er):
+	case MDOC_Er:
 		return(17);
-	case(MDOC_Ev):
+	case MDOC_Ev:
 		return(15);
-	case(MDOC_Fa):
+	case MDOC_Fa:
 		return(12);
-	case(MDOC_Fl):
+	case MDOC_Fl:
 		return(10);
-	case(MDOC_Fo):
+	case MDOC_Fo:
 		return(16);
-	case(MDOC_Fn):
+	case MDOC_Fn:
 		return(16);
-	case(MDOC_Ic):
+	case MDOC_Ic:
 		return(10);
-	case(MDOC_Li):
+	case MDOC_Li:
 		return(16);
-	case(MDOC_Ms):
+	case MDOC_Ms:
 		return(6);
-	case(MDOC_Nm):
+	case MDOC_Nm:
 		return(10);
-	case(MDOC_No):
+	case MDOC_No:
 		return(12);
-	case(MDOC_Oo):
+	case MDOC_Oo:
 		return(10);
-	case(MDOC_Op):
+	case MDOC_Op:
 		return(14);
-	case(MDOC_Pa):
+	case MDOC_Pa:
 		return(32);
-	case(MDOC_Pf):
+	case MDOC_Pf:
 		return(12);
-	case(MDOC_Po):
+	case MDOC_Po:
 		return(12);
-	case(MDOC_Pq):
+	case MDOC_Pq:
 		return(12);
-	case(MDOC_Ql):
+	case MDOC_Ql:
 		return(16);
-	case(MDOC_Qo):
+	case MDOC_Qo:
 		return(12);
-	case(MDOC_So):
+	case MDOC_So:
 		return(12);
-	case(MDOC_Sq):
+	case MDOC_Sq:
 		return(12);
-	case(MDOC_Sy):
+	case MDOC_Sy:
 		return(6);
-	case(MDOC_Sx):
+	case MDOC_Sx:
 		return(16);
-	case(MDOC_Tn):
+	case MDOC_Tn:
 		return(10);
-	case(MDOC_Va):
+	case MDOC_Va:
 		return(12);
-	case(MDOC_Vt):
+	case MDOC_Vt:
 		return(12);
-	case(MDOC_Xr):
+	case MDOC_Xr:
 		return(10);
 	default:
 		break;
diff --git a/usr/src/cmd/mandoc/msec.c b/usr/src/cmd/mandoc/msec.c
index dd7d11c650..d49d2975b6 100644
--- a/usr/src/cmd/mandoc/msec.c
+++ b/usr/src/cmd/mandoc/msec.c
@@ -1,4 +1,4 @@
-/*	$Id: msec.c,v 1.10 2011/12/02 01:37:14 schwarze Exp $ */
+/*	$Id: msec.c,v 1.14 2014/12/21 14:14:35 schwarze Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  *
@@ -14,11 +14,10 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
-#include 
+#include 
+
 #include 
 
 #include "mandoc.h"
diff --git a/usr/src/cmd/mandoc/out.c b/usr/src/cmd/mandoc/out.c
index c931664977..53b93fbe00 100644
--- a/usr/src/cmd/mandoc/out.c
+++ b/usr/src/cmd/mandoc/out.c
@@ -1,7 +1,7 @@
-/*	$Id: out.c,v 1.46 2013/10/05 20:30:05 schwarze Exp $ */
+/*	$Id: out.c,v 1.59 2015/01/30 04:11:50 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2011 Ingo Schwarze 
+ * Copyright (c) 2011, 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,19 +15,16 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
-#include 
-#include 
 #include 
 #include 
 #include 
 
+#include "mandoc_aux.h"
 #include "mandoc.h"
 #include "out.h"
 
@@ -38,98 +35,66 @@ static	void	tblcalc_literal(struct rofftbl *, struct roffcol *,
 static	void	tblcalc_number(struct rofftbl *, struct roffcol *,
 			const struct tbl_opts *, const struct tbl_dat *);
 
-/* 
- * Convert a `scaling unit' to a consistent form, or fail.  Scaling
- * units are documented in groff.7, mdoc.7, man.7.
+
+/*
+ * Parse the *src string and store a scaling unit into *dst.
+ * If the string doesn't specify the unit, use the default.
+ * If no default is specified, fail.
+ * Return 2 on complete success, 1 when a conversion was done,
+ * but there was trailing garbage, and 0 on total failure.
  */
 int
 a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
 {
-	char		 buf[BUFSIZ], hasd;
-	int		 i;
-	enum roffscale	 unit;
+	char		*endptr;
 
-	if ('\0' == *src)
+	dst->unit = def == SCALE_MAX ? SCALE_BU : def;
+	dst->scale = strtod(src, &endptr);
+	if (endptr == src)
 		return(0);
 
-	i = hasd = 0;
-
-	switch (*src) {
-	case ('+'):
-		src++;
+	switch (*endptr++) {
+	case 'c':
+		dst->unit = SCALE_CM;
 		break;
-	case ('-'):
-		buf[i++] = *src++;
+	case 'i':
+		dst->unit = SCALE_IN;
 		break;
-	default:
+	case 'f':
+		dst->unit = SCALE_FS;
 		break;
-	}
-
-	if ('\0' == *src)
-		return(0);
-
-	while (i < BUFSIZ) {
-		if ( ! isdigit((unsigned char)*src)) {
-			if ('.' != *src)
-				break;
-			else if (hasd)
-				break;
-			else
-				hasd = 1;
-		}
-		buf[i++] = *src++;
-	}
-
-	if (BUFSIZ == i || (*src && *(src + 1)))
-		return(0);
-
-	buf[i] = '\0';
-
-	switch (*src) {
-	case ('c'):
-		unit = SCALE_CM;
+	case 'M':
+		dst->unit = SCALE_MM;
 		break;
-	case ('i'):
-		unit = SCALE_IN;
+	case 'm':
+		dst->unit = SCALE_EM;
 		break;
-	case ('P'):
-		unit = SCALE_PC;
+	case 'n':
+		dst->unit = SCALE_EN;
 		break;
-	case ('p'):
-		unit = SCALE_PT;
+	case 'P':
+		dst->unit = SCALE_PC;
 		break;
-	case ('f'):
-		unit = SCALE_FS;
+	case 'p':
+		dst->unit = SCALE_PT;
 		break;
-	case ('v'):
-		unit = SCALE_VS;
+	case 'u':
+		dst->unit = SCALE_BU;
 		break;
-	case ('m'):
-		unit = SCALE_EM;
+	case 'v':
+		dst->unit = SCALE_VS;
 		break;
-	case ('\0'):
+	case '\0':
+		endptr--;
+		/* FALLTHROUGH */
+	default:
 		if (SCALE_MAX == def)
 			return(0);
-		unit = SCALE_BU;
-		break;
-	case ('u'):
-		unit = SCALE_BU;
-		break;
-	case ('M'):
-		unit = SCALE_MM;
+		dst->unit = def;
 		break;
-	case ('n'):
-		unit = SCALE_EN;
-		break;
-	default:
-		return(0);
 	}
 
-	/* FIXME: do this in the caller. */
-	if ((dst->scale = atof(buf)) < 0)
-		dst->scale = 0;
-	dst->unit = unit;
-	return(1);
+	return(*endptr == '\0' ? 2 : 1);
 }
 
 /*
@@ -139,11 +104,15 @@ a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
  * used for the actual width calculations.
  */
 void
-tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
+tblcalc(struct rofftbl *tbl, const struct tbl_span *sp,
+	size_t totalwidth)
 {
+	const struct tbl_opts	*opts;
 	const struct tbl_dat	*dp;
 	struct roffcol		*col;
+	size_t			 ewidth, xwidth;
 	int			 spans;
+	int			 icol, maxcol, necol, nxcol, quirkcol;
 
 	/*
 	 * Allocate the master column specifiers.  These will hold the
@@ -152,10 +121,11 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
 	 */
 
 	assert(NULL == tbl->cols);
-	tbl->cols = mandoc_calloc
-		((size_t)sp->opts->cols, sizeof(struct roffcol));
+	tbl->cols = mandoc_calloc((size_t)sp->opts->cols,
+	    sizeof(struct roffcol));
+	opts = sp->opts;
 
-	for ( ; sp; sp = sp->next) {
+	for (maxcol = -1; sp; sp = sp->next) {
 		if (TBL_SPAN_DATA != sp->pos)
 			continue;
 		spans = 1;
@@ -170,9 +140,92 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
 			spans = dp->spans;
 			if (1 < spans)
 				continue;
-			assert(dp->layout);
-			col = &tbl->cols[dp->layout->head->ident];
-			tblcalc_data(tbl, col, sp->opts, dp);
+			icol = dp->layout->col;
+			if (maxcol < icol)
+				maxcol = icol;
+			col = tbl->cols + icol;
+			col->flags |= dp->layout->flags;
+			if (dp->layout->flags & TBL_CELL_WIGN)
+				continue;
+			tblcalc_data(tbl, col, opts, dp);
+		}
+	}
+
+	/*
+	 * Count columns to equalize and columns to maximize.
+	 * Find maximum width of the columns to equalize.
+	 * Find total width of the columns *not* to maximize.
+	 */
+
+	necol = nxcol = 0;
+	ewidth = xwidth = 0;
+	for (icol = 0; icol <= maxcol; icol++) {
+		col = tbl->cols + icol;
+		if (col->flags & TBL_CELL_EQUAL) {
+			necol++;
+			if (ewidth < col->width)
+				ewidth = col->width;
+		}
+		if (col->flags & TBL_CELL_WMAX)
+			nxcol++;
+		else
+			xwidth += col->width;
+	}
+
+	/*
+	 * Equalize columns, if requested for any of them.
+	 * Update total width of the columns not to maximize.
+	 */
+
+	if (necol) {
+		for (icol = 0; icol <= maxcol; icol++) {
+			col = tbl->cols + icol;
+			if ( ! (col->flags & TBL_CELL_EQUAL))
+				continue;
+			if (col->width == ewidth)
+				continue;
+			if (nxcol && totalwidth)
+				xwidth += ewidth - col->width;
+			col->width = ewidth;
+		}
+	}
+
+	/*
+	 * If there are any columns to maximize, find the total
+	 * available width, deducting 3n margins between columns.
+	 * Distribute the available width evenly.
+	 */
+
+	if (nxcol && totalwidth) {
+		xwidth = totalwidth - xwidth - 3*maxcol -
+		    (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ?
+		     2 : !!opts->lvert + !!opts->rvert);
+
+		/*
+		 * Emulate a bug in GNU tbl width calculation that
+		 * manifests itself for large numbers of x-columns.
+		 * Emulating it for 5 x-columns gives identical
+		 * behaviour for up to 6 x-columns.
+		 */
+
+		if (nxcol == 5) {
+			quirkcol = xwidth % nxcol + 2;
+			if (quirkcol != 3 && quirkcol != 4)
+				quirkcol = -1;
+		} else
+			quirkcol = -1;
+
+		necol = 0;
+		ewidth = 0;
+		for (icol = 0; icol <= maxcol; icol++) {
+			col = tbl->cols + icol;
+			if ( ! (col->flags & TBL_CELL_WMAX))
+				continue;
+			col->width = (double)xwidth * ++necol / nxcol
+			    - ewidth + 0.4995;
+			if (necol == quirkcol)
+				col->width--;
+			ewidth += col->width;
 		}
 	}
 }
@@ -186,26 +239,26 @@ tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
 	/* Branch down into data sub-types. */
 
 	switch (dp->layout->pos) {
-	case (TBL_CELL_HORIZ):
+	case TBL_CELL_HORIZ:
 		/* FALLTHROUGH */
-	case (TBL_CELL_DHORIZ):
+	case TBL_CELL_DHORIZ:
 		sz = (*tbl->len)(1, tbl->arg);
 		if (col->width < sz)
 			col->width = sz;
 		break;
-	case (TBL_CELL_LONG):
+	case TBL_CELL_LONG:
 		/* FALLTHROUGH */
-	case (TBL_CELL_CENTRE):
+	case TBL_CELL_CENTRE:
 		/* FALLTHROUGH */
-	case (TBL_CELL_LEFT):
+	case TBL_CELL_LEFT:
 		/* FALLTHROUGH */
-	case (TBL_CELL_RIGHT):
+	case TBL_CELL_RIGHT:
 		tblcalc_literal(tbl, col, dp);
 		break;
-	case (TBL_CELL_NUMBER):
+	case TBL_CELL_NUMBER:
 		tblcalc_number(tbl, col, opts, dp);
 		break;
-	case (TBL_CELL_DOWN):
+	case TBL_CELL_DOWN:
 		break;
 	default:
 		abort();
@@ -231,7 +284,7 @@ static void
 tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
 		const struct tbl_opts *opts, const struct tbl_dat *dp)
 {
-	int 		 i;
+	int		 i;
 	size_t		 sz, psz, ssz, d;
 	const char	*str;
 	char		*cp;
diff --git a/usr/src/cmd/mandoc/out.h b/usr/src/cmd/mandoc/out.h
index 1c18c6c314..cc218f4fec 100644
--- a/usr/src/cmd/mandoc/out.h
+++ b/usr/src/cmd/mandoc/out.h
@@ -1,4 +1,4 @@
-/*	$Id: out.h,v 1.21 2011/07/17 15:24:25 kristaps Exp $ */
+/*	$Id: out.h,v 1.26 2014/12/01 08:05:52 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
  *
@@ -14,8 +14,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef OUT_H
-#define OUT_H
 
 enum	roffscale {
 	SCALE_CM, /* centimeters (c) */
@@ -34,6 +32,7 @@ enum	roffscale {
 struct	roffcol {
 	size_t		 width; /* width of cell */
 	size_t		 decimal; /* decimal position in cell */
+	int		 flags; /* layout flags, see tbl_cell */
 };
 
 struct	roffsu {
@@ -51,21 +50,22 @@ struct	rofftbl {
 	void		*arg; /* passed to slen and len */
 };
 
-__BEGIN_DECLS
-
 #define	SCALE_VS_INIT(p, v) \
 	do { (p)->unit = SCALE_VS; \
 	     (p)->scale = (v); } \
 	while (/* CONSTCOND */ 0)
 
 #define	SCALE_HS_INIT(p, v) \
-	do { (p)->unit = SCALE_BU; \
+	do { (p)->unit = SCALE_EN; \
 	     (p)->scale = (v); } \
 	while (/* CONSTCOND */ 0)
 
-int	  	  a2roffsu(const char *, struct roffsu *, enum roffscale);
-void	  	  tblcalc(struct rofftbl *tbl, const struct tbl_span *);
+__BEGIN_DECLS
+
+struct	tbl_span;
 
-__END_DECLS
+int		  a2roffsu(const char *, struct roffsu *, enum roffscale);
+void		  tblcalc(struct rofftbl *tbl,
+			const struct tbl_span *, size_t);
 
-#endif /*!OUT_H*/
+__END_DECLS
diff --git a/usr/src/cmd/mandoc/preconv.c b/usr/src/cmd/mandoc/preconv.c
index 7595887dd2..87e65ea0c6 100644
--- a/usr/src/cmd/mandoc/preconv.c
+++ b/usr/src/cmd/mandoc/preconv.c
@@ -1,6 +1,7 @@
-/*	$Id: preconv.c,v 1.6 2013/06/02 03:52:21 schwarze Exp $ */
+/*	$Id: preconv.c,v 1.14 2015/03/06 09:24:59 kristaps Exp $ */
 /*
  * Copyright (c) 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,325 +15,115 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
-#ifdef HAVE_MMAP
-#include 
-#include 
-#endif
+#include 
 
 #include 
-#include 
 #include 
-#include 
 #include 
-#include 
+#include "mandoc.h"
+#include "libmandoc.h"
 
-/* 
- * The read_whole_file() and resize_buf() functions are copied from
- * read.c, including all dependency code.
- */
-
-enum	enc {
-	ENC_UTF_8, /* UTF-8 */
-	ENC_US_ASCII, /* US-ASCII */
-	ENC_LATIN_1, /* Latin-1 */
-	ENC__MAX
-};
-
-struct	buf {
-	char		 *buf; /* binary input buffer */
-	size_t	 	  sz; /* size of binary buffer */
-	size_t		  offs; /* starting buffer offset */
-};
-
-struct	encode {
-	const char	 *name;
-	int		(*conv)(const struct buf *);
-};
-
-static	int	 cue_enc(const struct buf *, size_t *, enum enc *);
-static	int	 conv_latin_1(const struct buf *);
-static	int	 conv_us_ascii(const struct buf *);
-static	int	 conv_utf_8(const struct buf *);
-static	int	 read_whole_file(const char *, int, 
-			struct buf *, int *);
-static	void	 resize_buf(struct buf *, size_t);
-static	void	 usage(void);
-
-static	const struct encode encs[ENC__MAX] = {
-	{ "utf-8", conv_utf_8 }, /* ENC_UTF_8 */
-	{ "us-ascii", conv_us_ascii }, /* ENC_US_ASCII */
-	{ "latin-1", conv_latin_1 }, /* ENC_LATIN_1 */
-};
-
-static	const char	 *progname;
-
-static void
-usage(void)
-{
-
-	fprintf(stderr, "usage: %s "
-			"[-D enc] "
-			"[-e ENC] "
-			"[file]\n", progname);
-}
-
-static int
-conv_latin_1(const struct buf *b)
-{
-	size_t		 i;
-	unsigned char	 cu;
-	const char	*cp;
-
-	cp = b->buf + (int)b->offs;
-
-	/*
-	 * Latin-1 falls into the first 256 code-points of Unicode, so
-	 * there's no need for any sort of translation.  Just make the
-	 * 8-bit characters use the Unicode escape.
-	 * Note that binary values 128 < v < 160 are passed through
-	 * unmodified to mandoc.
-	 */
-
-	for (i = b->offs; i < b->sz; i++) {
-		cu = (unsigned char)*cp++;
-		cu < 160U ? putchar(cu) : printf("\\[u%.4X]", cu);
-	}
-
-	return(1);
-}
-
-static int
-conv_us_ascii(const struct buf *b)
-{
-
-	/*
-	 * US-ASCII has no conversion since it falls into the first 128
-	 * bytes of Unicode.
-	 */
-
-	fwrite(b->buf, 1, b->sz, stdout);
-	return(1);
-}
-
-static int
-conv_utf_8(const struct buf *b)
+int
+preconv_encode(struct buf *ib, size_t *ii, struct buf *ob, size_t *oi,
+    int *filenc)
 {
-	int		 state, be;
+	unsigned char	*cu;
+	int		 nby;
 	unsigned int	 accum;
-	size_t		 i;
-	unsigned char	 cu;
-	const char	*cp;
-	const long	 one = 1L;
-
-	cp = b->buf + (int)b->offs;
-	state = 0;
-	accum = 0U;
-	be = 0;
-
-	/* Quick test for big-endian value. */
-
-	if ( ! (*((const char *)(&one))))
-		be = 1;
-
-	for (i = b->offs; i < b->sz; i++) {
-		cu = (unsigned char)*cp++;
-		if (state) {
-			if ( ! (cu & 128) || (cu & 64)) {
-				/* Bad sequence header. */
-				return(0);
-			}
-
-			/* Accept only legitimate bit patterns. */
-
-			if (cu > 191 || cu < 128) {
-				/* Bad in-sequence bits. */
-				return(0);
-			}
-
-			accum |= (cu & 63) << --state * 6;
-
-			/*
-			 * Accum is held in little-endian order as
-			 * stipulated by the UTF-8 sequence coding.  We
-			 * need to convert to a native big-endian if our
-			 * architecture requires it.
-			 */
-
-			if (0 == state && be) 
-				accum = (accum >> 24) | 
-					((accum << 8) & 0x00FF0000) |
-					((accum >> 8) & 0x0000FF00) |
-					(accum << 24);
-
-			if (0 == state) {
-				accum < 128U ? putchar(accum) : 
-					printf("\\[u%.4X]", accum);
-				accum = 0U;
-			}
-		} else if (cu & (1 << 7)) {
-			/*
-			 * Entering a UTF-8 state:  if we encounter a
-			 * UTF-8 bitmask, calculate the expected UTF-8
-			 * state from it.
-			 */
-			for (state = 0; state < 7; state++) 
-				if ( ! (cu & (1 << (7 - state))))
-					break;
-
-			/* Accept only legitimate bit patterns. */
 
-			switch (state) {
-			case (4):
-				if (cu <= 244 && cu >= 240) {
-					accum = (cu & 7) << 18;
-					break;
-				}
-				/* Bad 4-sequence start bits. */
-				return(0);
-			case (3):
-				if (cu <= 239 && cu >= 224) {
-					accum = (cu & 15) << 12;
-					break;
-				}
-				/* Bad 3-sequence start bits. */
-				return(0);
-			case (2):
-				if (cu <= 223 && cu >= 194) {
-					accum = (cu & 31) << 6;
-					break;
-				}
-				/* Bad 2-sequence start bits. */
-				return(0);
-			default:
-				/* Bad sequence bit mask. */
-				return(0);
-			}
-			state--;
-		} else
-			putchar(cu);
+	cu = (unsigned char *)ib->buf + *ii;
+	assert(*cu & 0x80);
+
+	if ( ! (*filenc & MPARSE_UTF8))
+		goto latin;
+
+	nby = 1;
+	while (nby < 5 && *cu & (1 << (7 - nby)))
+		nby++;
+
+	switch (nby) {
+	case 2:
+		accum = *cu & 0x1f;
+		if (accum < 0x02)  /* Obfuscated ASCII. */
+			goto latin;
+		break;
+	case 3:
+		accum = *cu & 0x0f;
+		break;
+	case 4:
+		accum = *cu & 0x07;
+		if (accum > 0x04) /* Beyond Unicode. */
+			goto latin;
+		break;
+	default:  /* Bad sequence header. */
+		goto latin;
 	}
 
-	if (0 != state) {
-		/* Bad trailing bits. */
-		return(0);
+	cu++;
+	switch (nby) {
+	case 3:
+		if ((accum == 0x00 && ! (*cu & 0x20)) ||  /* Use 2-byte. */
+		    (accum == 0x0d && *cu & 0x20))  /* Surrogates. */
+			goto latin;
+		break;
+	case 4:
+		if ((accum == 0x00 && ! (*cu & 0x30)) ||  /* Use 3-byte. */
+		    (accum == 0x04 && *cu & 0x30))  /* Beyond Unicode. */
+			goto latin;
+		break;
+	default:
+		break;
 	}
 
-	return(1);
-}
-
-static void
-resize_buf(struct buf *buf, size_t initial)
-{
-
-	buf->sz = buf->sz > initial / 2 ? 
-		2 * buf->sz : initial;
-
-	buf->buf = realloc(buf->buf, buf->sz);
-	if (NULL == buf->buf) {
-		perror(NULL);
-		exit(EXIT_FAILURE);
+	while (--nby) {
+		if ((*cu & 0xc0) != 0x80)  /* Invalid continuation. */
+			goto latin;
+		accum <<= 6;
+		accum += *cu & 0x3f;
+		cu++;
 	}
-}
-
-static int
-read_whole_file(const char *f, int fd, 
-		struct buf *fb, int *with_mmap)
-{
-	size_t		 off;
-	ssize_t		 ssz;
 
-#ifdef	HAVE_MMAP
-	struct stat	 st;
-	if (-1 == fstat(fd, &st)) {
-		perror(f);
-		return(0);
-	}
+	assert(accum > 0x7f);
+	assert(accum < 0x110000);
+	assert(accum < 0xd800 || accum > 0xdfff);
 
-	/*
-	 * If we're a regular file, try just reading in the whole entry
-	 * via mmap().  This is faster than reading it into blocks, and
-	 * since each file is only a few bytes to begin with, I'm not
-	 * concerned that this is going to tank any machines.
-	 */
+	*oi += snprintf(ob->buf + *oi, 11, "\\[u%.4X]", accum);
+	*ii = (char *)cu - ib->buf;
+	*filenc &= ~MPARSE_LATIN1;
+	return(1);
 
-	if (S_ISREG(st.st_mode) && st.st_size >= (1U << 31)) {
-		fprintf(stderr, "%s: input too large\n", f);
+latin:
+	if ( ! (*filenc & MPARSE_LATIN1))
 		return(0);
-	} 
-	
-	if (S_ISREG(st.st_mode)) {
-		*with_mmap = 1;
-		fb->sz = (size_t)st.st_size;
-		fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0);
-		if (fb->buf != MAP_FAILED)
-			return(1);
-	}
-#endif
 
-	/*
-	 * If this isn't a regular file (like, say, stdin), then we must
-	 * go the old way and just read things in bit by bit.
-	 */
-
-	*with_mmap = 0;
-	off = 0;
-	fb->sz = 0;
-	fb->buf = NULL;
-	for (;;) {
-		if (off == fb->sz && fb->sz == (1U << 31)) {
-			fprintf(stderr, "%s: input too large\n", f);
-			break;
-		} 
-		
-		if (off == fb->sz)
-			resize_buf(fb, 65536);
+	*oi += snprintf(ob->buf + *oi, 11,
+	    "\\[u%.4X]", (unsigned char)ib->buf[(*ii)++]);
 
-		ssz = read(fd, fb->buf + (int)off, fb->sz - off);
-		if (ssz == 0) {
-			fb->sz = off;
-			return(1);
-		}
-		if (ssz == -1) {
-			perror(f);
-			break;
-		}
-		off += (size_t)ssz;
-	}
-
-	free(fb->buf);
-	fb->buf = NULL;
-	return(0);
+	*filenc &= ~MPARSE_UTF8;
+	return(1);
 }
 
-static int
-cue_enc(const struct buf *b, size_t *offs, enum enc *enc)
+int
+preconv_cue(const struct buf *b, size_t offset)
 {
 	const char	*ln, *eoln, *eoph;
-	size_t		 sz, phsz, nsz;
-	int		 i;
+	size_t		 sz, phsz;
 
-	ln = b->buf + (int)*offs;
-	sz = b->sz - *offs;
+	ln = b->buf + offset;
+	sz = b->sz - offset;
 
 	/* Look for the end-of-line. */
 
 	if (NULL == (eoln = memchr(ln, '\n', sz)))
-		return(-1);
-
-	/* Set next-line marker. */
-
-	*offs = (size_t)((eoln + 1) - b->buf);
+		eoln = ln + sz;
 
 	/* Check if we have the correct header/trailer. */
 
-	if ((sz = (size_t)(eoln - ln)) < 10 || 
-			memcmp(ln, ".\\\" -*-", 7) ||
-			memcmp(eoln - 3, "-*-", 3))
-		return(0);
+	if ((sz = (size_t)(eoln - ln)) < 10 ||
+	    memcmp(ln, ".\\\" -*-", 7) || memcmp(eoln - 3, "-*-", 3))
+		return(MPARSE_UTF8 | MPARSE_LATIN1);
 
 	/* Move after the header and adjust for the trailer. */
 
@@ -356,12 +147,12 @@ cue_enc(const struct buf *b, size_t *offs, enum enc *enc)
 
 		/* Only account for the "coding" phrase. */
 
-		if ((phsz = (size_t)(eoph - ln)) < 7 ||
-				strncasecmp(ln, "coding:", 7)) {
+		if ((phsz = eoph - ln) < 7 ||
+		    strncasecmp(ln, "coding:", 7)) {
 			sz -= phsz;
 			ln += phsz;
 			continue;
-		} 
+		}
 
 		sz -= 7;
 		ln += 7;
@@ -371,153 +162,15 @@ cue_enc(const struct buf *b, size_t *offs, enum enc *enc)
 			sz--;
 		}
 		if (0 == sz)
-			break;
+			return(0);
 
 		/* Check us against known encodings. */
 
-		for (i = 0; i < (int)ENC__MAX; i++) {
-			nsz = strlen(encs[i].name);
-			if (phsz < nsz)
-				continue;
-			if (strncasecmp(ln, encs[i].name, nsz))
-				continue;
-
-			*enc = (enum enc)i;
-			return(1);
-		}
-
-		/* Unknown encoding. */
-
-		*enc = ENC__MAX;
-		return(1);
-	}
-
-	return(0);
-}
-
-int
-main(int argc, char *argv[])
-{
-	int	 	 i, ch, map, fd, rc;
-	struct buf	 b;
-	const char	*fn;
-	enum enc	 enc, def;
-	unsigned char 	 bom[3] = { 0xEF, 0xBB, 0xBF };
-	size_t		 offs;
-	extern int	 optind;
-	extern char	*optarg;
-
-	progname = strrchr(argv[0], '/');
-	if (progname == NULL)
-		progname = argv[0];
-	else
-		++progname;
-
-	fn = "";
-	fd = STDIN_FILENO;
-	rc = EXIT_FAILURE;
-	enc = def = ENC__MAX;
-	map = 0;
-
-	memset(&b, 0, sizeof(struct buf));
-
-	while (-1 != (ch = getopt(argc, argv, "D:e:rdvh")))
-		switch (ch) {
-		case ('D'):
-			/* FALLTHROUGH */
-		case ('e'):
-			for (i = 0; i < (int)ENC__MAX; i++) {
-				if (strcasecmp(optarg, encs[i].name))
-					continue;
-				break;
-			}
-			if (i < (int)ENC__MAX) {
-				if ('D' == ch)
-					def = (enum enc)i;
-				else
-					enc = (enum enc)i;
-				break;
-			}
-
-			fprintf(stderr, "%s: Bad encoding\n", optarg);
-			return(EXIT_FAILURE);
-		case ('r'):
-			/* FALLTHROUGH */
-		case ('d'):
-			/* FALLTHROUGH */
-		case ('v'):
-			/* Compatibility with GNU preconv. */
-			break;
-		case ('h'):
-			/* Compatibility with GNU preconv. */
-			/* FALLTHROUGH */
-		default:
-			usage();
-			return(EXIT_FAILURE);
-		}
-
-	argc -= optind;
-	argv += optind;
-	
-	/* 
-	 * Open and read the first argument on the command-line.
-	 * If we don't have one, we default to stdin.
-	 */
-
-	if (argc > 0) {
-		fn = *argv;
-		fd = open(fn, O_RDONLY, 0);
-		if (-1 == fd) {
-			perror(fn);
-			return(EXIT_FAILURE);
-		}
-	}
-
-	if ( ! read_whole_file(fn, fd, &b, &map))
-		goto out;
-
-	/* Try to read the UTF-8 BOM. */
-
-	if (ENC__MAX == enc)
-		if (b.sz > 3 && 0 == memcmp(b.buf, bom, 3)) {
-			b.offs = 3;
-			enc = ENC_UTF_8;
-		}
-
-	/* Try reading from the "-*-" cue. */
-
-	if (ENC__MAX == enc) {
-		offs = b.offs;
-		ch = cue_enc(&b, &offs, &enc);
-		if (0 == ch)
-			ch = cue_enc(&b, &offs, &enc);
-	}
-
-	/*
-	 * No encoding has been detected.
-	 * Thus, we either fall into our default encoder, if specified,
-	 * or use Latin-1 if all else fails.
-	 */
-
-	if (ENC__MAX == enc) 
-		enc = ENC__MAX == def ? ENC_LATIN_1 : def;
-
-	if ( ! (*encs[(int)enc].conv)(&b)) {
-		fprintf(stderr, "%s: Bad encoding\n", fn);
-		goto out;
+		if (phsz > 4 && !strncasecmp(ln, "utf-8", 5))
+			return(MPARSE_UTF8);
+		if (phsz > 10 && !strncasecmp(ln, "iso-latin-1", 11))
+			return(MPARSE_LATIN1);
+		return(0);
 	}
-
-	rc = EXIT_SUCCESS;
-out:
-#ifdef	HAVE_MMAP
-	if (map)
-		munmap(b.buf, b.sz);
-	else 
-#endif
-		free(b.buf);
-
-	if (fd > STDIN_FILENO)
-		close(fd);
-
-	return(rc);
+	return(MPARSE_UTF8 | MPARSE_LATIN1);
 }
diff --git a/usr/src/cmd/mandoc/read.c b/usr/src/cmd/mandoc/read.c
index 511ba7dc46..471d415019 100644
--- a/usr/src/cmd/mandoc/read.c
+++ b/usr/src/cmd/mandoc/read.c
@@ -1,7 +1,8 @@
-/*	$Id: read.c,v 1.39 2013/09/16 00:25:07 schwarze Exp $ */
+/*	$Id: read.c,v 1.131 2015/03/11 13:05:20 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010-2015 Ingo Schwarze 
+ * Copyright (c) 2010, 2012 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,17 +16,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
-#ifdef HAVE_MMAP
-# include 
-# include 
+#include 
+#if HAVE_MMAP
+#include 
+#include 
 #endif
+#include 
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -35,40 +37,40 @@
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmandoc.h"
 #include "mdoc.h"
 #include "man.h"
-#include "main.h"
 
 #define	REPARSE_LIMIT	1000
 
-struct	buf {
-	char	 	 *buf; /* binary input buffer */
-	size_t		  sz; /* size of binary buffer */
-};
-
 struct	mparse {
-	enum mandoclevel  file_status; /* status of current parse */
-	enum mandoclevel  wlevel; /* ignore messages below this */
-	int		  line; /* line number in the file */
-	enum mparset	  inttype; /* which parser to use */
 	struct man	 *pman; /* persistent man parser */
 	struct mdoc	 *pmdoc; /* persistent mdoc parser */
 	struct man	 *man; /* man parser */
 	struct mdoc	 *mdoc; /* mdoc parser */
 	struct roff	 *roff; /* roff parser (!NULL) */
-	int		  reparse_count; /* finite interp. stack */
+	const struct mchars *mchars; /* character table */
+	char		 *sodest; /* filename pointed to by .so */
+	const char	 *file; /* filename of current input file */
+	struct buf	 *primary; /* buffer currently being parsed */
+	struct buf	 *secondary; /* preprocessed copy of input */
+	const char	 *defos; /* default operating system */
 	mandocmsg	  mmsg; /* warning/error message handler */
-	void		 *arg; /* argument to mmsg */
-	const char	 *file; 
-	struct buf	 *secondary;
-	char		 *defos; /* default operating system */
+	enum mandoclevel  file_status; /* status of current parse */
+	enum mandoclevel  wlevel; /* ignore messages below this */
+	int		  options; /* parser options */
+	int		  filenc; /* encoding of the current file */
+	int		  reparse_count; /* finite interp. stack */
+	int		  line; /* line number in the file */
+	pid_t		  child; /* the gunzip(1) process */
 };
 
+static	void	  choose_parser(struct mparse *);
 static	void	  resize_buf(struct buf *, size_t);
-static	void	  mparse_buf_r(struct mparse *, struct buf, int);
-static	void	  pset(const char *, int, struct mparse *);
-static	int	  read_whole_file(const char *, int, struct buf *, int *);
+static	void	  mparse_buf_r(struct mparse *, struct buf, size_t, int);
+static	int	  read_whole_file(struct mparse *, const char *, int,
+				struct buf *, int *);
 static	void	  mparse_end(struct mparse *);
 static	void	  mparse_parse_buffer(struct mparse *, struct buf,
 			const char *);
@@ -78,7 +80,7 @@ static	const enum mandocerr	mandoclimits[MANDOCLEVEL_MAX] = {
 	MANDOCERR_WARNING,
 	MANDOCERR_WARNING,
 	MANDOCERR_ERROR,
-	MANDOCERR_FATAL,
+	MANDOCERR_UNSUPP,
 	MANDOCERR_MAX,
 	MANDOCERR_MAX
 };
@@ -89,119 +91,150 @@ static	const char * const	mandocerrs[MANDOCERR_MAX] = {
 	"generic warning",
 
 	/* related to the prologue */
-	"no title in document",
-	"document title should be all caps",
+	"missing manual title, using UNTITLED",
+	"missing manual title, using \"\"",
+	"lower case character in document title",
+	"missing manual section, using \"\"",
 	"unknown manual section",
-	"unknown manual volume or arch",
-	"date missing, using today's date",
+	"missing date, using today's date",
 	"cannot parse date, using it verbatim",
-	"prologue macros out of order",
+	"missing Os macro, using \"\"",
 	"duplicate prologue macro",
-	"macro not allowed in prologue",
-	"macro not allowed in body",
+	"late prologue macro",
+	"skipping late title macro",
+	"prologue macros out of order",
 
 	/* related to document structure */
 	".so is fragile, better use ln(1)",
-	"NAME section must come first",
-	"bad NAME section contents",
+	"no document body",
+	"content before first section header",
+	"first section is not \"NAME\"",
+	"NAME section without name",
+	"NAME section without description",
+	"description not at the end of NAME",
+	"bad NAME section content",
+	"missing description line, using \"\"",
 	"sections out of conventional order",
-	"duplicate section name",
-	"section header suited to sections 2, 3, and 9 only",
+	"duplicate section title",
+	"unexpected section",
+	"unusual Xr order",
+	"unusual Xr punctuation",
+	"AUTHORS section without An macro",
 
 	/* related to macros and nesting */
-	"skipping obsolete macro",
+	"obsolete macro",
+	"macro neither callable nor escaped",
 	"skipping paragraph macro",
 	"moving paragraph macro out of list",
 	"skipping no-space macro",
 	"blocks badly nested",
-	"child violates parent syntax",
 	"nested displays are not portable",
-	"already in literal mode",
+	"moving content out of list",
+	".Vt block has child macro",
+	"fill mode already enabled, skipping",
+	"fill mode already disabled, skipping",
 	"line scope broken",
 
 	/* related to missing macro arguments */
+	"skipping empty request",
+	"conditional request controls empty scope",
 	"skipping empty macro",
-	"argument count wrong",
-	"missing display type",
-	"list type must come first",
-	"tag lists require a width argument",
-	"missing font type",
-	"skipping end of block that is not open",
+	"empty block",
+	"empty argument, using 0n",
+	"missing display type, using -ragged",
+	"list type is not the first argument",
+	"missing -width in -tag list, using 8n",
+	"missing utility name, using \"\"",
+	"missing function name, using \"\"",
+	"empty head in list item",
+	"empty list item",
+	"missing font type, using \\fR",
+	"unknown font type, using \\fR",
+	"nothing follows prefix",
+	"empty reference block",
+	"missing -std argument, adding it",
+	"missing option string, using \"\"",
+	"missing resource identifier, using \"\"",
+	"missing eqn box, using \"\"",
 
 	/* related to bad macro arguments */
-	"skipping argument",
+	"unterminated quoted argument",
 	"duplicate argument",
-	"duplicate display type",
-	"duplicate list type",
+	"skipping duplicate argument",
+	"skipping duplicate display type",
+	"skipping duplicate list type",
+	"skipping -width argument",
+	"wrong number of cells",
 	"unknown AT&T UNIX version",
-	"bad Boolean value",
-	"unknown font",
-	"unknown standard specifier",
-	"bad width argument",
+	"comma in function argument",
+	"parenthesis in function name",
+	"invalid content in Rs block",
+	"invalid Boolean argument",
+	"unknown font, skipping request",
+	"odd number of characters in request",
 
 	/* related to plain text */
-	"blank line in non-literal context",
-	"tab in non-literal context",
-	"end of line whitespace",
+	"blank line in fill mode, using .sp",
+	"tab in filled text",
+	"whitespace at end of input line",
 	"bad comment style",
-	"bad escape sequence",
-	"unterminated quoted string",
+	"invalid escape sequence",
+	"undefined string, using \"\"",
 
-	/* related to equations */
-	"unexpected literal in equation",
-	
-	"generic error",
+	/* related to tables */
+	"tbl line starts with span",
+	"tbl column starts with span",
+	"skipping vertical bar in tbl layout",
 
-	/* related to equations */
-	"unexpected equation scope closure",
-	"equation scope open on exit",
-	"overlapping equation scopes",
-	"unexpected end of equation",
-	"equation syntax error",
+	"generic error",
 
 	/* related to tables */
-	"bad table syntax",
-	"bad table option",
-	"bad table layout",
-	"no table layout cells specified",
-	"no table data cells specified",
-	"ignore data in cell",
-	"data block still open",
-	"ignoring extra data cells",
-
+	"non-alphabetic character in tbl options",
+	"skipping unknown tbl option",
+	"missing tbl option argument",
+	"wrong tbl option argument size",
+	"empty tbl layout",
+	"invalid character in tbl layout",
+	"unmatched parenthesis in tbl layout",
+	"tbl without any data cells",
+	"ignoring data in spanned tbl cell",
+	"ignoring extra tbl data cells",
+	"data block open at end of tbl",
+
+	/* related to document structure and macros */
+	NULL,
 	"input stack limit exceeded, infinite loop?",
 	"skipping bad character",
-	"escaped character not allowed in a name",
-	"manual name not yet set",
-	"skipping text before the first section header",
 	"skipping unknown macro",
-	"NOT IMPLEMENTED, please use groff: skipping request",
-	"argument count wrong",
+	"skipping insecure request",
+	"skipping item outside list",
 	"skipping column outside column list",
 	"skipping end of block that is not open",
-	"missing end of block",
-	"scope open on exit",
-	"uname(3) system call failed",
-	"macro requires line argument(s)",
-	"macro requires body argument(s)",
-	"macro requires argument(s)",
-	"request requires a numeric argument",
-	"missing list type",
-	"line argument(s) will be lost",
-	"body argument(s) will be lost",
-
-	"generic fatal error",
-
-	"not a manual",
-	"column syntax is inconsistent",
-	"NOT IMPLEMENTED: .Bd -file",
-	"argument count wrong, violates syntax",
-	"child violates parent syntax",
-	"argument count wrong, violates syntax",
+	"fewer RS blocks open, skipping",
+	"inserting missing end of block",
+	"appending missing end of block",
+
+	/* related to request and macro arguments */
+	"escaped character not allowed in a name",
+	"NOT IMPLEMENTED: Bd -file",
+	"missing list type, using -item",
+	"missing manual name, using \"\"",
+	"uname(3) system call failed, using UNKNOWN",
+	"unknown standard specifier",
+	"skipping request without numeric argument",
 	"NOT IMPLEMENTED: .so with absolute path or \"..\"",
-	"no document body",
-	"no document prologue",
-	"static buffer exhausted",
+	".so request failed",
+	"skipping all arguments",
+	"skipping excess arguments",
+	"divide by zero",
+
+	"unsupported feature",
+	"input too large",
+	"unsupported control character",
+	"unsupported roff request",
+	"eqn delim option in tbl",
+	"unsupported tbl layout modifier",
+	"ignoring macro in table",
 };
 
 static	const char * const	mandoclevels[MANDOCLEVEL_MAX] = {
@@ -209,11 +242,12 @@ static	const char * const	mandoclevels[MANDOCLEVEL_MAX] = {
 	"RESERVED",
 	"WARNING",
 	"ERROR",
-	"FATAL",
+	"UNSUPP",
 	"BADARG",
 	"SYSERR"
 };
 
+
 static void
 resize_buf(struct buf *buf, size_t initial)
 {
@@ -223,93 +257,101 @@ resize_buf(struct buf *buf, size_t initial)
 }
 
 static void
-pset(const char *buf, int pos, struct mparse *curp)
+choose_parser(struct mparse *curp)
 {
-	int		 i;
+	char		*cp, *ep;
+	int		 format;
 
 	/*
-	 * Try to intuit which kind of manual parser should be used.  If
-	 * passed in by command-line (-man, -mdoc), then use that
-	 * explicitly.  If passed as -mandoc, then try to guess from the
-	 * line: either skip dot-lines, use -mdoc when finding `.Dt', or
-	 * default to -man, which is more lenient.
-	 *
-	 * Separate out pmdoc/pman from mdoc/man: the first persists
-	 * through all parsers, while the latter is used per-parse.
+	 * If neither command line arguments -mdoc or -man select
+	 * a parser nor the roff parser found a .Dd or .TH macro
+	 * yet, look ahead in the main input buffer.
 	 */
 
-	if ('.' == buf[0] || '\'' == buf[0]) {
-		for (i = 1; buf[i]; i++)
-			if (' ' != buf[i] && '\t' != buf[i])
+	if ((format = roff_getformat(curp->roff)) == 0) {
+		cp = curp->primary->buf;
+		ep = cp + curp->primary->sz;
+		while (cp < ep) {
+			if (*cp == '.' || *cp == '\'') {
+				cp++;
+				if (cp[0] == 'D' && cp[1] == 'd') {
+					format = MPARSE_MDOC;
+					break;
+				}
+				if (cp[0] == 'T' && cp[1] == 'H') {
+					format = MPARSE_MAN;
+					break;
+				}
+			}
+			cp = memchr(cp, '\n', ep - cp);
+			if (cp == NULL)
 				break;
-		if ('\0' == buf[i])
-			return;
+			cp++;
+		}
 	}
 
-	switch (curp->inttype) {
-	case (MPARSE_MDOC):
-		if (NULL == curp->pmdoc) 
-			curp->pmdoc = mdoc_alloc(curp->roff, curp,
-					curp->defos);
+	if (format == MPARSE_MDOC) {
+		if (NULL == curp->pmdoc)
+			curp->pmdoc = mdoc_alloc(
+			    curp->roff, curp, curp->defos,
+			    MPARSE_QUICK & curp->options ? 1 : 0);
 		assert(curp->pmdoc);
 		curp->mdoc = curp->pmdoc;
 		return;
-	case (MPARSE_MAN):
-		if (NULL == curp->pman) 
-			curp->pman = man_alloc(curp->roff, curp);
-		assert(curp->pman);
-		curp->man = curp->pman;
-		return;
-	default:
-		break;
 	}
 
-	if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3))  {
-		if (NULL == curp->pmdoc) 
-			curp->pmdoc = mdoc_alloc(curp->roff, curp,
-					curp->defos);
-		assert(curp->pmdoc);
-		curp->mdoc = curp->pmdoc;
-		return;
-	} 
+	/* Fall back to man(7) as a last resort. */
 
-	if (NULL == curp->pman) 
-		curp->pman = man_alloc(curp->roff, curp);
+	if (NULL == curp->pman)
+		curp->pman = man_alloc(
+		    curp->roff, curp, curp->defos,
+		    MPARSE_QUICK & curp->options ? 1 : 0);
 	assert(curp->pman);
 	curp->man = curp->pman;
 }
 
 /*
- * Main parse routine for an opened file.  This is called for each
- * opened file and simply loops around the full input file, possibly
- * nesting (i.e., with `so').
+ * Main parse routine for a buffer.
+ * It assumes encoding and line numbering are already set up.
+ * It can recurse directly (for invocations of user-defined
+ * macros, inline equations, and input line traps)
+ * and indirectly (for .so file inclusion).
  */
 static void
-mparse_buf_r(struct mparse *curp, struct buf blk, int start)
+mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 {
 	const struct tbl_span	*span;
 	struct buf	 ln;
+	const char	*save_file;
+	char		*cp;
+	size_t		 pos; /* byte number in the ln buffer */
 	enum rofferr	 rr;
-	int		 i, of, rc;
-	int		 pos; /* byte number in the ln buffer */
+	int		 of;
 	int		 lnn; /* line number in the real file */
+	int		 fd;
+	pid_t		 save_child;
 	unsigned char	 c;
 
-	memset(&ln, 0, sizeof(struct buf));
+	memset(&ln, 0, sizeof(ln));
 
-	lnn = curp->line; 
-	pos = 0; 
+	lnn = curp->line;
+	pos = 0;
 
-	for (i = 0; i < (int)blk.sz; ) {
+	while (i < blk.sz) {
 		if (0 == pos && '\0' == blk.buf[i])
 			break;
 
 		if (start) {
 			curp->line = lnn;
 			curp->reparse_count = 0;
+
+			if (lnn < 3 &&
+			    curp->filenc & MPARSE_UTF8 &&
+			    curp->filenc & MPARSE_LATIN1)
+				curp->filenc = preconv_cue(&blk, i);
 		}
 
-		while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) {
+		while (i < blk.sz && (start || blk.buf[i] != '\0')) {
 
 			/*
 			 * When finding an unescaped newline character,
@@ -317,7 +359,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 			 * Skip a preceding carriage return, if any.
 			 */
 
-			if ('\r' == blk.buf[i] && i + 1 < (int)blk.sz &&
+			if ('\r' == blk.buf[i] && i + 1 < blk.sz &&
 			    '\n' == blk.buf[i + 1])
 				++i;
 			if ('\n' == blk.buf[i]) {
@@ -327,37 +369,47 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 			}
 
 			/*
-			 * Make sure we have space for at least
-			 * one backslash and one other character
-			 * and the trailing NUL byte.
+			 * Make sure we have space for the worst
+			 * case of 11 bytes: "\\[u10ffff]\0"
 			 */
 
-			if (pos + 2 >= (int)ln.sz)
+			if (pos + 11 > ln.sz)
 				resize_buf(&ln, 256);
 
-			/* 
-			 * Warn about bogus characters.  If you're using
-			 * non-ASCII encoding, you're screwing your
-			 * readers.  Since I'd rather this not happen,
-			 * I'll be helpful and replace these characters
-			 * with "?", so we don't display gibberish.
-			 * Note to manual writers: use special characters.
+			/*
+			 * Encode 8-bit input.
 			 */
 
-			c = (unsigned char) blk.buf[i];
+			c = blk.buf[i];
+			if (c & 0x80) {
+				if ( ! (curp->filenc && preconv_encode(
+				    &blk, &i, &ln, &pos, &curp->filenc))) {
+					mandoc_vmsg(MANDOCERR_CHAR_BAD, curp,
+					    curp->line, pos, "0x%x", c);
+					ln.buf[pos++] = '?';
+					i++;
+				}
+				continue;
+			}
 
-			if ( ! (isascii(c) && 
-					(isgraph(c) || isblank(c)))) {
-				mandoc_msg(MANDOCERR_BADCHAR, curp,
-						curp->line, pos, NULL);
+			/*
+			 * Exclude control characters.
+			 */
+
+			if (c == 0x7f || (c < 0x20 && c != 0x09)) {
+				mandoc_vmsg(c == 0x00 || c == 0x04 ||
+				    c > 0x0a ? MANDOCERR_CHAR_BAD :
+				    MANDOCERR_CHAR_UNSUPP,
+				    curp, curp->line, pos, "0x%x", c);
 				i++;
-				ln.buf[pos++] = '?';
+				if (c != '\r')
+					ln.buf[pos++] = '?';
 				continue;
 			}
 
 			/* Trailing backslash = a plain char. */
 
-			if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) {
+			if (blk.buf[i] != '\\' || i + 1 == blk.sz) {
 				ln.buf[pos++] = blk.buf[i++];
 				continue;
 			}
@@ -369,7 +421,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 			 * skip that one as well.
 			 */
 
-			if ('\r' == blk.buf[i + 1] && i + 2 < (int)blk.sz &&
+			if ('\r' == blk.buf[i + 1] && i + 2 < blk.sz &&
 			    '\n' == blk.buf[i + 2])
 				++i;
 			if ('\n' == blk.buf[i + 1]) {
@@ -381,7 +433,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 			if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) {
 				i += 2;
 				/* Comment, skip to end of line */
-				for (; i < (int)blk.sz; ++i) {
+				for (; i < blk.sz; ++i) {
 					if ('\n' == blk.buf[i]) {
 						++i;
 						++lnn;
@@ -403,10 +455,10 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 
 			c = (unsigned char) blk.buf[i+1];
 
-			if ( ! (isascii(c) && 
-					(isgraph(c) || isblank(c)))) {
-				mandoc_msg(MANDOCERR_BADCHAR, curp,
-						curp->line, pos, NULL);
+			if ( ! (isascii(c) &&
+			    (isgraph(c) || isblank(c)))) {
+				mandoc_vmsg(MANDOCERR_CHAR_BAD, curp,
+				    curp->line, pos, "0x%x", c);
 				i += 2;
 				ln.buf[pos++] = '?';
 				continue;
@@ -418,7 +470,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 			ln.buf[pos++] = blk.buf[i++];
 		}
 
- 		if (pos >= (int)ln.sz)
+		if (pos >= ln.sz)
 			resize_buf(&ln, 256);
 
 		ln.buf[pos] = '\0';
@@ -441,13 +493,12 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 		 */
 
 		if (curp->secondary) {
-			curp->secondary->buf = 
-				mandoc_realloc
-				(curp->secondary->buf, 
-				 curp->secondary->sz + pos + 2);
-			memcpy(curp->secondary->buf + 
-					curp->secondary->sz, 
-					ln.buf, pos);
+			curp->secondary->buf = mandoc_realloc(
+			    curp->secondary->buf,
+			    curp->secondary->sz + pos + 2);
+			memcpy(curp->secondary->buf +
+			    curp->secondary->sz,
+			    ln.buf, pos);
 			curp->secondary->sz += pos;
 			curp->secondary->buf
 				[curp->secondary->sz] = '\n';
@@ -456,55 +507,65 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 				[curp->secondary->sz] = '\0';
 		}
 rerun:
-		rr = roff_parseln
-			(curp->roff, curp->line, 
-			 &ln.buf, &ln.sz, of, &of);
+		rr = roff_parseln(curp->roff, curp->line, &ln, &of);
 
 		switch (rr) {
-		case (ROFF_REPARSE):
+		case ROFF_REPARSE:
 			if (REPARSE_LIMIT >= ++curp->reparse_count)
-				mparse_buf_r(curp, ln, 0);
+				mparse_buf_r(curp, ln, of, 0);
 			else
 				mandoc_msg(MANDOCERR_ROFFLOOP, curp,
-					curp->line, pos, NULL);
+				    curp->line, pos, NULL);
 			pos = 0;
 			continue;
-		case (ROFF_APPEND):
-			pos = (int)strlen(ln.buf);
+		case ROFF_APPEND:
+			pos = strlen(ln.buf);
 			continue;
-		case (ROFF_RERUN):
+		case ROFF_RERUN:
 			goto rerun;
-		case (ROFF_IGN):
+		case ROFF_IGN:
 			pos = 0;
 			continue;
-		case (ROFF_ERR):
-			assert(MANDOCLEVEL_FATAL <= curp->file_status);
-			break;
-		case (ROFF_SO):
+		case ROFF_SO:
+			if ( ! (curp->options & MPARSE_SO) &&
+			    (i >= blk.sz || blk.buf[i] == '\0')) {
+				curp->sodest = mandoc_strdup(ln.buf + of);
+				free(ln.buf);
+				return;
+			}
 			/*
 			 * We remove `so' clauses from our lookaside
 			 * buffer because we're going to descend into
 			 * the file recursively.
 			 */
-			if (curp->secondary) 
+			if (curp->secondary)
 				curp->secondary->sz -= pos + 1;
-			mparse_readfd(curp, -1, ln.buf + of);
-			if (MANDOCLEVEL_FATAL <= curp->file_status)
-				break;
+			save_file = curp->file;
+			save_child = curp->child;
+			if (mparse_open(curp, &fd, ln.buf + of) ==
+			    MANDOCLEVEL_OK) {
+				mparse_readfd(curp, fd, ln.buf + of);
+				curp->file = save_file;
+			} else {
+				curp->file = save_file;
+				mandoc_vmsg(MANDOCERR_SO_FAIL,
+				    curp, curp->line, pos,
+				    ".so %s", ln.buf + of);
+				ln.sz = mandoc_asprintf(&cp,
+				    ".sp\nSee the file %s.\n.sp",
+				    ln.buf + of);
+				free(ln.buf);
+				ln.buf = cp;
+				of = 0;
+				mparse_buf_r(curp, ln, of, 0);
+			}
+			curp->child = save_child;
 			pos = 0;
 			continue;
 		default:
 			break;
 		}
 
-		/*
-		 * If we encounter errors in the recursive parse, make
-		 * sure we don't continue parsing.
-		 */
-
-		if (MANDOCLEVEL_FATAL <= curp->file_status)
-			break;
-
 		/*
 		 * If input parsers have not been allocated, do so now.
 		 * We keep these instanced between parsers, but set them
@@ -513,12 +574,10 @@ rerun:
 		 */
 
 		if ( ! (curp->man || curp->mdoc))
-			pset(ln.buf + of, pos - of, curp);
+			choose_parser(curp);
 
-		/* 
-		 * Lastly, push down into the parsers themselves.  One
-		 * of these will have already been set in the pset()
-		 * routine.
+		/*
+		 * Lastly, push down into the parsers themselves.
 		 * If libroff returns ROFF_TBL, then add it to the
 		 * currently open parse.  Since we only get here if
 		 * there does exist data (see tbl_data.c), we're
@@ -526,33 +585,21 @@ rerun:
 		 * Do the same for ROFF_EQN.
 		 */
 
-		rc = -1;
-
-		if (ROFF_TBL == rr)
-			while (NULL != (span = roff_span(curp->roff))) {
-				rc = curp->man ?
-					man_addspan(curp->man, span) :
+		if (rr == ROFF_TBL) {
+			while ((span = roff_span(curp->roff)) != NULL)
+				if (curp->man == NULL)
 					mdoc_addspan(curp->mdoc, span);
-				if (0 == rc)
-					break;
-			}
-		else if (ROFF_EQN == rr)
-			rc = curp->mdoc ? 
-				mdoc_addeqn(curp->mdoc, 
-					roff_eqn(curp->roff)) :
-				man_addeqn(curp->man,
-					roff_eqn(curp->roff));
-		else if (curp->man || curp->mdoc)
-			rc = curp->man ?
-				man_parseln(curp->man, 
-					curp->line, ln.buf, of) :
-				mdoc_parseln(curp->mdoc, 
-					curp->line, ln.buf, of);
-
-		if (0 == rc) {
-			assert(MANDOCLEVEL_FATAL <= curp->file_status);
-			break;
-		}
+				else
+					man_addspan(curp->man, span);
+		} else if (rr == ROFF_EQN) {
+			if (curp->man == NULL)
+				mdoc_addeqn(curp->mdoc, roff_eqn(curp->roff));
+			else
+				man_addeqn(curp->man, roff_eqn(curp->roff));
+		} else if ((curp->man == NULL ?
+		    mdoc_parseln(curp->mdoc, curp->line, ln.buf, of) :
+		    man_parseln(curp->man, curp->line, ln.buf, of)) == 2)
+				break;
 
 		/* Temporary buffers typically are not full. */
 
@@ -568,16 +615,17 @@ rerun:
 }
 
 static int
-read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
+read_whole_file(struct mparse *curp, const char *file, int fd,
+		struct buf *fb, int *with_mmap)
 {
 	size_t		 off;
 	ssize_t		 ssz;
 
-#ifdef	HAVE_MMAP
+#if HAVE_MMAP
 	struct stat	 st;
 	if (-1 == fstat(fd, &st)) {
 		perror(file);
-		return(0);
+		exit((int)MANDOCLEVEL_SYSERR);
 	}
 
 	/*
@@ -588,8 +636,8 @@ read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
 	 */
 
 	if (S_ISREG(st.st_mode)) {
-		if (st.st_size >= (1U << 31)) {
-			fprintf(stderr, "%s: input too large\n", file);
+		if (st.st_size > 0x7fffffff) {
+			mandoc_msg(MANDOCERR_TOOLARGE, curp, 0, 0, NULL);
 			return(0);
 		}
 		*with_mmap = 1;
@@ -612,7 +660,8 @@ read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
 	for (;;) {
 		if (off == fb->sz) {
 			if (fb->sz == (1U << 31)) {
-				fprintf(stderr, "%s: input too large\n", file);
+				mandoc_msg(MANDOCERR_TOOLARGE, curp,
+				    0, 0, NULL);
 				break;
 			}
 			resize_buf(fb, 65536);
@@ -624,7 +673,7 @@ read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
 		}
 		if (ssz == -1) {
 			perror(file);
-			break;
+			exit((int)MANDOCLEVEL_SYSERR);
 		}
 		off += (size_t)ssz;
 	}
@@ -638,32 +687,32 @@ static void
 mparse_end(struct mparse *curp)
 {
 
-	if (MANDOCLEVEL_FATAL <= curp->file_status)
-		return;
-
-	if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
-		assert(MANDOCLEVEL_FATAL <= curp->file_status);
-		return;
-	}
-
-	if (curp->man && ! man_endparse(curp->man)) {
-		assert(MANDOCLEVEL_FATAL <= curp->file_status);
-		return;
-	}
-
-	if ( ! (curp->man || curp->mdoc)) {
-		mandoc_msg(MANDOCERR_NOTMANUAL, curp, 1, 0, NULL);
-		curp->file_status = MANDOCLEVEL_FATAL;
-		return;
+	if (curp->mdoc == NULL &&
+	    curp->man == NULL &&
+	    curp->sodest == NULL) {
+		if (curp->options & MPARSE_MDOC)
+			curp->mdoc = curp->pmdoc;
+		else {
+			if (curp->pman == NULL)
+				curp->pman = man_alloc(
+				    curp->roff, curp, curp->defos,
+				    curp->options & MPARSE_QUICK ? 1 : 0);
+			curp->man = curp->pman;
+		}
 	}
-
+	if (curp->mdoc)
+		mdoc_endparse(curp->mdoc);
+	if (curp->man)
+		man_endparse(curp->man);
 	roff_endparse(curp->roff);
 }
 
 static void
 mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file)
 {
+	struct buf	*svprimary;
 	const char	*svfile;
+	size_t		 offset;
 	static int	 recursion_depth;
 
 	if (64 < recursion_depth) {
@@ -674,86 +723,189 @@ mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file)
 	/* Line number is per-file. */
 	svfile = curp->file;
 	curp->file = file;
+	svprimary = curp->primary;
+	curp->primary = &blk;
 	curp->line = 1;
 	recursion_depth++;
 
-	mparse_buf_r(curp, blk, 1);
+	/* Skip an UTF-8 byte order mark. */
+	if (curp->filenc & MPARSE_UTF8 && blk.sz > 2 &&
+	    (unsigned char)blk.buf[0] == 0xef &&
+	    (unsigned char)blk.buf[1] == 0xbb &&
+	    (unsigned char)blk.buf[2] == 0xbf) {
+		offset = 3;
+		curp->filenc &= ~MPARSE_LATIN1;
+	} else
+		offset = 0;
 
-	if (0 == --recursion_depth && MANDOCLEVEL_FATAL > curp->file_status)
+	mparse_buf_r(curp, blk, offset, 1);
+
+	if (--recursion_depth == 0)
 		mparse_end(curp);
 
+	curp->primary = svprimary;
 	curp->file = svfile;
 }
 
 enum mandoclevel
-mparse_readmem(struct mparse *curp, const void *buf, size_t len,
+mparse_readmem(struct mparse *curp, void *buf, size_t len,
 		const char *file)
 {
 	struct buf blk;
 
-	blk.buf = UNCONST(buf);
+	blk.buf = buf;
 	blk.sz = len;
 
 	mparse_parse_buffer(curp, blk, file);
 	return(curp->file_status);
 }
 
+/*
+ * Read the whole file into memory and call the parsers.
+ * Called recursively when an .so request is encountered.
+ */
 enum mandoclevel
 mparse_readfd(struct mparse *curp, int fd, const char *file)
 {
 	struct buf	 blk;
 	int		 with_mmap;
+	int		 save_filenc;
+
+	if (read_whole_file(curp, file, fd, &blk, &with_mmap)) {
+		save_filenc = curp->filenc;
+		curp->filenc = curp->options &
+		    (MPARSE_UTF8 | MPARSE_LATIN1);
+		mparse_parse_buffer(curp, blk, file);
+		curp->filenc = save_filenc;
+#if HAVE_MMAP
+		if (with_mmap)
+			munmap(blk.buf, blk.sz);
+		else
+#endif
+			free(blk.buf);
+	}
 
-	if (-1 == fd)
-		if (-1 == (fd = open(file, O_RDONLY, 0))) {
-			perror(file);
-			curp->file_status = MANDOCLEVEL_SYSERR;
-			goto out;
-		}
-	/*
-	 * Run for each opened file; may be called more than once for
-	 * each full parse sequence if the opened file is nested (i.e.,
-	 * from `so').  Simply sucks in the whole file and moves into
-	 * the parse phase for the file.
-	 */
+	if (fd != STDIN_FILENO && close(fd) == -1)
+		perror(file);
 
-	if ( ! read_whole_file(file, fd, &blk, &with_mmap)) {
-		curp->file_status = MANDOCLEVEL_SYSERR;
-		goto out;
+	mparse_wait(curp);
+	return(curp->file_status);
+}
+
+enum mandoclevel
+mparse_open(struct mparse *curp, int *fd, const char *file)
+{
+	int		  pfd[2];
+	int		  save_errno;
+	char		 *cp;
+
+	curp->file = file;
+
+	/* Unless zipped, try to just open the file. */
+
+	if ((cp = strrchr(file, '.')) == NULL ||
+	    strcmp(cp + 1, "gz")) {
+		curp->child = 0;
+		if ((*fd = open(file, O_RDONLY)) != -1)
+			return(MANDOCLEVEL_OK);
+
+		/* Open failed; try to append ".gz". */
+
+		mandoc_asprintf(&cp, "%s.gz", file);
+		file = cp;
+	} else
+		cp = NULL;
+
+	/* Before forking, make sure the file can be read. */
+
+	save_errno = errno;
+	if (access(file, R_OK) == -1) {
+		if (cp != NULL)
+			errno = save_errno;
+		free(cp);
+		*fd = -1;
+		curp->child = 0;
+		mandoc_msg(MANDOCERR_FILE, curp, 0, 0, strerror(errno));
+		return(MANDOCLEVEL_ERROR);
 	}
 
-	mparse_parse_buffer(curp, blk, file);
+	/* Run gunzip(1). */
 
-#ifdef	HAVE_MMAP
-	if (with_mmap)
-		munmap(blk.buf, blk.sz);
-	else
-#endif
-		free(blk.buf);
+	if (pipe(pfd) == -1) {
+		perror("pipe");
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
 
-	if (STDIN_FILENO != fd && -1 == close(fd))
-		perror(file);
-out:
-	return(curp->file_status);
+	switch (curp->child = fork()) {
+	case -1:
+		perror("fork");
+		exit((int)MANDOCLEVEL_SYSERR);
+	case 0:
+		close(pfd[0]);
+		if (dup2(pfd[1], STDOUT_FILENO) == -1) {
+			perror("dup");
+			exit((int)MANDOCLEVEL_SYSERR);
+		}
+		execlp("gunzip", "gunzip", "-c", file, NULL);
+		perror("exec");
+		exit((int)MANDOCLEVEL_SYSERR);
+	default:
+		close(pfd[1]);
+		*fd = pfd[0];
+		return(MANDOCLEVEL_OK);
+	}
+}
+
+enum mandoclevel
+mparse_wait(struct mparse *curp)
+{
+	int	  status;
+
+	if (curp->child == 0)
+		return(MANDOCLEVEL_OK);
+
+	if (waitpid(curp->child, &status, 0) == -1) {
+		perror("wait");
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+	curp->child = 0;
+	if (WIFSIGNALED(status)) {
+		mandoc_vmsg(MANDOCERR_FILE, curp, 0, 0,
+		    "gunzip died from signal %d", WTERMSIG(status));
+		return(MANDOCLEVEL_ERROR);
+	}
+	if (WEXITSTATUS(status)) {
+		mandoc_vmsg(MANDOCERR_FILE, curp, 0, 0,
+		    "gunzip failed with code %d", WEXITSTATUS(status));
+		return(MANDOCLEVEL_ERROR);
+	}
+	return(MANDOCLEVEL_OK);
 }
 
 struct mparse *
-mparse_alloc(enum mparset inttype, enum mandoclevel wlevel,
-		mandocmsg mmsg, void *arg, char *defos)
+mparse_alloc(int options, enum mandoclevel wlevel, mandocmsg mmsg,
+    const struct mchars *mchars, const char *defos)
 {
 	struct mparse	*curp;
 
-	assert(wlevel <= MANDOCLEVEL_FATAL);
-
 	curp = mandoc_calloc(1, sizeof(struct mparse));
 
+	curp->options = options;
 	curp->wlevel = wlevel;
 	curp->mmsg = mmsg;
-	curp->arg = arg;
-	curp->inttype = inttype;
 	curp->defos = defos;
 
-	curp->roff = roff_alloc(inttype, curp);
+	curp->mchars = mchars;
+	curp->roff = roff_alloc(curp, curp->mchars, options);
+	if (curp->options & MPARSE_MDOC)
+		curp->pmdoc = mdoc_alloc(
+		    curp->roff, curp, curp->defos,
+		    curp->options & MPARSE_QUICK ? 1 : 0);
+	if (curp->options & MPARSE_MAN)
+		curp->pman = man_alloc(
+		    curp->roff, curp, curp->defos,
+		    curp->options & MPARSE_QUICK ? 1 : 0);
+
 	return(curp);
 }
 
@@ -773,6 +925,9 @@ mparse_reset(struct mparse *curp)
 	curp->file_status = MANDOCLEVEL_OK;
 	curp->mdoc = NULL;
 	curp->man = NULL;
+
+	free(curp->sodest);
+	curp->sodest = NULL;
 }
 
 void
@@ -789,13 +944,20 @@ mparse_free(struct mparse *curp)
 		free(curp->secondary->buf);
 
 	free(curp->secondary);
+	free(curp->sodest);
 	free(curp);
 }
 
 void
-mparse_result(struct mparse *curp, struct mdoc **mdoc, struct man **man)
+mparse_result(struct mparse *curp,
+	struct mdoc **mdoc, struct man **man, char **sodest)
 {
 
+	if (sodest && NULL != (*sodest = curp->sodest)) {
+		*mdoc = NULL;
+		*man = NULL;
+		return;
+	}
 	if (mdoc)
 		*mdoc = curp->mdoc;
 	if (man)
@@ -810,23 +972,23 @@ mandoc_vmsg(enum mandocerr t, struct mparse *m,
 	va_list		 ap;
 
 	va_start(ap, fmt);
-	vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
+	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
 	va_end(ap);
 
 	mandoc_msg(t, m, ln, pos, buf);
 }
 
 void
-mandoc_msg(enum mandocerr er, struct mparse *m, 
+mandoc_msg(enum mandocerr er, struct mparse *m,
 		int ln, int col, const char *msg)
 {
 	enum mandoclevel level;
 
-	level = MANDOCLEVEL_FATAL;
+	level = MANDOCLEVEL_UNSUPP;
 	while (er < mandoclimits[level])
 		level--;
 
-	if (level < m->wlevel)
+	if (level < m->wlevel && er != MANDOCERR_FILE)
 		return;
 
 	if (m->mmsg)
diff --git a/usr/src/cmd/mandoc/roff.c b/usr/src/cmd/mandoc/roff.c
index 42240d21fe..3c920137ea 100644
--- a/usr/src/cmd/mandoc/roff.c
+++ b/usr/src/cmd/mandoc/roff.c
@@ -1,7 +1,7 @@
-/*	$Id: roff.c,v 1.189 2013/12/30 18:44:06 schwarze Exp $ */
+/*	$Id: roff.c,v 1.263 2015/02/21 14:46:58 schwarze Exp $ */
 /*
- * Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons 
- * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010, 2011, 2012, 2014 Kristaps Dzonsons 
+ * Copyright (c) 2010-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,19 +15,21 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
 
 #include "mandoc.h"
-#include "libroff.h"
+#include "mandoc_aux.h"
 #include "libmandoc.h"
+#include "libroff.h"
 
 /* Maximum number of nested if-else conditionals. */
 #define	RSTACK_MAX	128
@@ -36,50 +38,252 @@
 #define	EXPAND_LIMIT	1000
 
 enum	rofft {
+	ROFF_ab,
 	ROFF_ad,
+	ROFF_af,
+	ROFF_aln,
+	ROFF_als,
 	ROFF_am,
-	ROFF_ami,
 	ROFF_am1,
+	ROFF_ami,
+	ROFF_ami1,
+	ROFF_as,
+	ROFF_as1,
+	ROFF_asciify,
+	ROFF_backtrace,
+	ROFF_bd,
+	ROFF_bleedat,
+	ROFF_blm,
+	ROFF_box,
+	ROFF_boxa,
+	ROFF_bp,
+	ROFF_BP,
+	/* MAN_br, MDOC_br */
+	ROFF_break,
+	ROFF_breakchar,
+	ROFF_brnl,
+	ROFF_brp,
+	ROFF_brpnl,
+	ROFF_c2,
 	ROFF_cc,
+	ROFF_ce,
+	ROFF_cf,
+	ROFF_cflags,
+	ROFF_ch,
+	ROFF_char,
+	ROFF_chop,
+	ROFF_class,
+	ROFF_close,
+	ROFF_CL,
+	ROFF_color,
+	ROFF_composite,
+	ROFF_continue,
+	ROFF_cp,
+	ROFF_cropat,
+	ROFF_cs,
+	ROFF_cu,
+	ROFF_da,
+	ROFF_dch,
+	ROFF_Dd,
 	ROFF_de,
-	ROFF_dei,
 	ROFF_de1,
+	ROFF_defcolor,
+	ROFF_dei,
+	ROFF_dei1,
+	ROFF_device,
+	ROFF_devicem,
+	ROFF_di,
+	ROFF_do,
 	ROFF_ds,
+	ROFF_ds1,
+	ROFF_dwh,
+	ROFF_dt,
+	ROFF_ec,
+	ROFF_ecr,
+	ROFF_ecs,
 	ROFF_el,
+	ROFF_em,
+	ROFF_EN,
+	ROFF_eo,
+	ROFF_EP,
+	ROFF_EQ,
+	ROFF_errprint,
+	ROFF_ev,
+	ROFF_evc,
+	ROFF_ex,
+	ROFF_fallback,
 	ROFF_fam,
+	ROFF_fc,
+	ROFF_fchar,
+	ROFF_fcolor,
+	ROFF_fdeferlig,
+	ROFF_feature,
+	/* MAN_fi; ignored in mdoc(7) */
+	ROFF_fkern,
+	ROFF_fl,
+	ROFF_flig,
+	ROFF_fp,
+	ROFF_fps,
+	ROFF_fschar,
+	ROFF_fspacewidth,
+	ROFF_fspecial,
+	/* MAN_ft; ignored in mdoc(7) */
+	ROFF_ftr,
+	ROFF_fzoom,
+	ROFF_gcolor,
+	ROFF_hc,
+	ROFF_hcode,
+	ROFF_hidechar,
+	ROFF_hla,
+	ROFF_hlm,
+	ROFF_hpf,
+	ROFF_hpfa,
+	ROFF_hpfcode,
 	ROFF_hw,
 	ROFF_hy,
+	ROFF_hylang,
+	ROFF_hylen,
+	ROFF_hym,
+	ROFF_hypp,
+	ROFF_hys,
 	ROFF_ie,
 	ROFF_if,
 	ROFF_ig,
+	/* MAN_in; ignored in mdoc(7) */
+	ROFF_index,
 	ROFF_it,
+	ROFF_itc,
+	ROFF_IX,
+	ROFF_kern,
+	ROFF_kernafter,
+	ROFF_kernbefore,
+	ROFF_kernpair,
+	ROFF_lc,
+	ROFF_lc_ctype,
+	ROFF_lds,
+	ROFF_length,
+	ROFF_letadj,
+	ROFF_lf,
+	ROFF_lg,
+	ROFF_lhang,
+	ROFF_linetabs,
+	/* MAN_ll, MDOC_ll */
+	ROFF_lnr,
+	ROFF_lnrf,
+	ROFF_lpfx,
+	ROFF_ls,
+	ROFF_lsm,
+	ROFF_lt,
+	ROFF_mc,
+	ROFF_mediasize,
+	ROFF_minss,
+	ROFF_mk,
+	ROFF_mso,
+	ROFF_na,
 	ROFF_ne,
+	/* MAN_nf; ignored in mdoc(7) */
 	ROFF_nh,
+	ROFF_nhychar,
+	ROFF_nm,
+	ROFF_nn,
+	ROFF_nop,
 	ROFF_nr,
+	ROFF_nrf,
+	ROFF_nroff,
 	ROFF_ns,
+	ROFF_nx,
+	ROFF_open,
+	ROFF_opena,
+	ROFF_os,
+	ROFF_output,
+	ROFF_padj,
+	ROFF_papersize,
+	ROFF_pc,
+	ROFF_pev,
+	ROFF_pi,
+	ROFF_PI,
+	ROFF_pl,
+	ROFF_pm,
+	ROFF_pn,
+	ROFF_pnr,
+	ROFF_po,
 	ROFF_ps,
+	ROFF_psbb,
+	ROFF_pshape,
+	ROFF_pso,
+	ROFF_ptr,
+	ROFF_pvs,
+	ROFF_rchar,
+	ROFF_rd,
+	ROFF_recursionlimit,
+	ROFF_return,
+	ROFF_rfschar,
+	ROFF_rhang,
+	ROFF_rj,
 	ROFF_rm,
+	ROFF_rn,
+	ROFF_rnn,
+	ROFF_rr,
+	ROFF_rs,
+	ROFF_rt,
+	ROFF_schar,
+	ROFF_sentchar,
+	ROFF_shc,
+	ROFF_shift,
+	ROFF_sizes,
 	ROFF_so,
+	/* MAN_sp, MDOC_sp */
+	ROFF_spacewidth,
+	ROFF_special,
+	ROFF_spreadwarn,
+	ROFF_ss,
+	ROFF_sty,
+	ROFF_substring,
+	ROFF_sv,
+	ROFF_sy,
+	ROFF_T_,
 	ROFF_ta,
-	ROFF_tr,
-	ROFF_Dd,
+	ROFF_tc,
+	ROFF_TE,
 	ROFF_TH,
+	ROFF_ti,
+	ROFF_tkf,
+	ROFF_tl,
+	ROFF_tm,
+	ROFF_tm1,
+	ROFF_tmc,
+	ROFF_tr,
+	ROFF_track,
+	ROFF_transchar,
+	ROFF_trf,
+	ROFF_trimat,
+	ROFF_trin,
+	ROFF_trnt,
+	ROFF_troff,
 	ROFF_TS,
-	ROFF_TE,
-	ROFF_T_,
-	ROFF_EQ,
-	ROFF_EN,
+	ROFF_uf,
+	ROFF_ul,
+	ROFF_unformat,
+	ROFF_unwatch,
+	ROFF_unwatchn,
+	ROFF_vpt,
+	ROFF_vs,
+	ROFF_warn,
+	ROFF_warnscale,
+	ROFF_watch,
+	ROFF_watchlength,
+	ROFF_watchn,
+	ROFF_wh,
+	ROFF_while,
+	ROFF_write,
+	ROFF_writec,
+	ROFF_writem,
+	ROFF_xflag,
 	ROFF_cblock,
-	ROFF_ccond,
 	ROFF_USERDEF,
 	ROFF_MAX
 };
 
-enum	roffrule {
-	ROFFRULE_DENY,
-	ROFFRULE_ALLOW
-};
-
 /*
  * An incredibly-simple string buffer.
  */
@@ -107,12 +311,10 @@ struct	roffreg {
 };
 
 struct	roff {
-	enum mparset	 parsetype; /* requested parse type */
 	struct mparse	*parse; /* parse point */
+	const struct mchars *mchars; /* character table */
 	struct roffnode	*last; /* leaf of stack */
-	enum roffrule	 rstack[RSTACK_MAX]; /* stack of !`ie' rules */
-	char		 control; /* control character */
-	int		 rstackpos; /* position in rstack */
+	int		*rstack; /* stack of inverted `ie' values */
 	struct roffreg	*regtab; /* number registers */
 	struct roffkv	*strtab; /* user-defined strings & macros */
 	struct roffkv	*xmbtab; /* multi-byte trans table (`tr') */
@@ -124,6 +326,12 @@ struct	roff {
 	struct eqn_node	*last_eqn; /* last equation parsed */
 	struct eqn_node	*first_eqn; /* first equation parsed */
 	struct eqn_node	*eqn; /* current equation being parsed */
+	int		 eqn_inline; /* current equation is inline */
+	int		 options; /* parse options */
+	int		 rstacksz; /* current size limit of rstack */
+	int		 rstackpos; /* position in rstack */
+	int		 format; /* current file in mdoc or man format */
+	char		 control; /* control character */
 };
 
 struct	roffnode {
@@ -134,13 +342,12 @@ struct	roffnode {
 	char		*name; /* node name, e.g. macro name */
 	char		*end; /* end-rules: custom token */
 	int		 endspan; /* end-rules: next-line or infty */
-	enum roffrule	 rule; /* current evaluation rule */
+	int		 rule; /* current evaluation rule */
 };
 
 #define	ROFF_ARGS	 struct roff *r, /* parse ctx */ \
 			 enum rofft tok, /* tok of macro */ \
-		 	 char **bufp, /* input buffer */ \
-			 size_t *szp, /* size of input buffer */ \
+			 struct buf *buf, /* input buffer */ \
 			 int ln, /* parse line */ \
 			 int ppos, /* original pos in buffer */ \
 			 int pos, /* current pos in buffer */ \
@@ -175,37 +382,46 @@ static	void		 roffnode_push(struct roff *, enum rofft,
 static	enum rofferr	 roff_block(ROFF_ARGS);
 static	enum rofferr	 roff_block_text(ROFF_ARGS);
 static	enum rofferr	 roff_block_sub(ROFF_ARGS);
+static	enum rofferr	 roff_brp(ROFF_ARGS);
 static	enum rofferr	 roff_cblock(ROFF_ARGS);
 static	enum rofferr	 roff_cc(ROFF_ARGS);
-static	enum rofferr	 roff_ccond(ROFF_ARGS);
+static	void		 roff_ccond(struct roff *, int, int);
 static	enum rofferr	 roff_cond(ROFF_ARGS);
 static	enum rofferr	 roff_cond_text(ROFF_ARGS);
 static	enum rofferr	 roff_cond_sub(ROFF_ARGS);
 static	enum rofferr	 roff_ds(ROFF_ARGS);
-static	enum roffrule	 roff_evalcond(const char *, int *);
+static	enum rofferr	 roff_eqndelim(struct roff *, struct buf *, int);
+static	int		 roff_evalcond(struct roff *r, int,
+				const char *, int *);
+static	int		 roff_evalnum(struct roff *, int,
+				const char *, int *, int *, int);
+static	int		 roff_evalpar(struct roff *, int,
+				const char *, int *, int *, int);
+static	int		 roff_evalstrcond(const char *, int *);
 static	void		 roff_free1(struct roff *);
 static	void		 roff_freereg(struct roffreg *);
 static	void		 roff_freestr(struct roffkv *);
-static	char		*roff_getname(struct roff *, char **, int, int);
-static	int		 roff_getnum(const char *, int *, int *);
+static	size_t		 roff_getname(struct roff *, char **, int, int);
+static	int		 roff_getnum(const char *, int *, int *, int);
 static	int		 roff_getop(const char *, int *, char *);
 static	int		 roff_getregn(const struct roff *,
 				const char *, size_t);
-static	const char	*roff_getstrn(const struct roff *, 
+static	int		 roff_getregro(const char *name);
+static	const char	*roff_getstrn(const struct roff *,
 				const char *, size_t);
+static	enum rofferr	 roff_insec(ROFF_ARGS);
 static	enum rofferr	 roff_it(ROFF_ARGS);
 static	enum rofferr	 roff_line_ignore(ROFF_ARGS);
 static	enum rofferr	 roff_nr(ROFF_ARGS);
-static	void		 roff_openeqn(struct roff *, const char *,
-				int, int, const char *);
-static	enum rofft	 roff_parse(struct roff *, const char *, int *);
-static	enum rofferr	 roff_parsetext(char **, size_t *, int, int *);
-static	enum rofferr	 roff_res(struct roff *, 
-				char **, size_t *, int, int);
+static	enum rofft	 roff_parse(struct roff *, char *, int *,
+				int, int);
+static	enum rofferr	 roff_parsetext(struct buf *, int, int *);
+static	enum rofferr	 roff_res(struct roff *, struct buf *, int, int);
 static	enum rofferr	 roff_rm(ROFF_ARGS);
+static	enum rofferr	 roff_rr(ROFF_ARGS);
 static	void		 roff_setstr(struct roff *,
 				const char *, const char *, int);
-static	void		 roff_setstrn(struct roffkv **, const char *, 
+static	void		 roff_setstrn(struct roffkv **, const char *,
 				size_t, const char *, size_t, int);
 static	enum rofferr	 roff_so(ROFF_ARGS);
 static	enum rofferr	 roff_tr(ROFF_ARGS);
@@ -216,6 +432,7 @@ static	enum rofferr	 roff_TS(ROFF_ARGS);
 static	enum rofferr	 roff_EQ(ROFF_ARGS);
 static	enum rofferr	 roff_EN(ROFF_ARGS);
 static	enum rofferr	 roff_T_(ROFF_ARGS);
+static	enum rofferr	 roff_unsupp(ROFF_ARGS);
 static	enum rofferr	 roff_userdef(ROFF_ARGS);
 
 /* See roffhash_find() */
@@ -224,75 +441,280 @@ static	enum rofferr	 roff_userdef(ROFF_ARGS);
 #define	ASCII_LO	 33
 #define	HASHWIDTH	(ASCII_HI - ASCII_LO + 1)
 
+#define	ROFFNUM_SCALE	(1 << 0)  /* Honour scaling in roff_getnum(). */
+#define	ROFFNUM_WHITE	(1 << 1)  /* Skip whitespace in roff_evalnum(). */
+
 static	struct roffmac	*hash[HASHWIDTH];
 
 static	struct roffmac	 roffs[ROFF_MAX] = {
+	{ "ab", roff_unsupp, NULL, NULL, 0, NULL },
 	{ "ad", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "af", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "aln", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "als", roff_unsupp, NULL, NULL, 0, NULL },
 	{ "am", roff_block, roff_block_text, roff_block_sub, 0, NULL },
-	{ "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL },
 	{ "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "ami1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "as", roff_ds, NULL, NULL, 0, NULL },
+	{ "as1", roff_ds, NULL, NULL, 0, NULL },
+	{ "asciify", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "backtrace", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "bd", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "bleedat", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "blm", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "box", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "boxa", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "bp", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "BP", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "break", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "breakchar", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "brnl", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "brp", roff_brp, NULL, NULL, 0, NULL },
+	{ "brpnl", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "c2", roff_unsupp, NULL, NULL, 0, NULL },
 	{ "cc", roff_cc, NULL, NULL, 0, NULL },
+	{ "ce", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "cf", roff_insec, NULL, NULL, 0, NULL },
+	{ "cflags", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ch", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "char", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "chop", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "class", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "close", roff_insec, NULL, NULL, 0, NULL },
+	{ "CL", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "color", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "composite", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "continue", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "cp", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "cropat", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "cs", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "cu", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "da", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "dch", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "Dd", roff_Dd, NULL, NULL, 0, NULL },
 	{ "de", roff_block, roff_block_text, roff_block_sub, 0, NULL },
-	{ "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL },
 	{ "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "defcolor", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "dei1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "device", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "devicem", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "di", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "do", roff_unsupp, NULL, NULL, 0, NULL },
 	{ "ds", roff_ds, NULL, NULL, 0, NULL },
+	{ "ds1", roff_ds, NULL, NULL, 0, NULL },
+	{ "dwh", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "dt", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "ec", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "ecr", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "ecs", roff_unsupp, NULL, NULL, 0, NULL },
 	{ "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
+	{ "em", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "EN", roff_EN, NULL, NULL, 0, NULL },
+	{ "eo", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "EP", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "EQ", roff_EQ, NULL, NULL, 0, NULL },
+	{ "errprint", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ev", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "evc", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "ex", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "fallback", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "fam", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fc", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "fchar", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "fcolor", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fdeferlig", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "feature", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fkern", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fl", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "flig", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fp", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fps", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fschar", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "fspacewidth", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fspecial", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ftr", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "fzoom", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "gcolor", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hc", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hcode", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hidechar", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hla", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hlm", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hpf", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hpfa", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hpfcode", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "hw", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "hy", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hylang", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hylen", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hym", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hypp", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "hys", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
 	{ "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
 	{ "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL },
+	{ "index", roff_unsupp, NULL, NULL, 0, NULL },
 	{ "it", roff_it, NULL, NULL, 0, NULL },
+	{ "itc", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "IX", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "kern", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "kernafter", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "kernbefore", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "kernpair", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "lc", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "lc_ctype", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "lds", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "length", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "letadj", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "lf", roff_insec, NULL, NULL, 0, NULL },
+	{ "lg", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "lhang", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "linetabs", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "lnr", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "lnrf", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "lpfx", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "ls", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "lsm", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "lt", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "mc", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "mediasize", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "minss", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "mk", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "mso", roff_insec, NULL, NULL, 0, NULL },
+	{ "na", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "ne", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "nh", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "nhychar", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "nm", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "nn", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "nop", roff_unsupp, NULL, NULL, 0, NULL },
 	{ "nr", roff_nr, NULL, NULL, 0, NULL },
+	{ "nrf", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "nroff", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "ns", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "nx", roff_insec, NULL, NULL, 0, NULL },
+	{ "open", roff_insec, NULL, NULL, 0, NULL },
+	{ "opena", roff_insec, NULL, NULL, 0, NULL },
+	{ "os", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "output", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "padj", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "papersize", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "pc", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "pev", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "pi", roff_insec, NULL, NULL, 0, NULL },
+	{ "PI", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "pl", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "pm", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "pn", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "pnr", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "po", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "ps", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "psbb", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "pshape", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "pso", roff_insec, NULL, NULL, 0, NULL },
+	{ "ptr", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "pvs", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "rchar", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "rd", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "recursionlimit", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "return", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "rfschar", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "rhang", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "rj", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "rm", roff_rm, NULL, NULL, 0, NULL },
+	{ "rn", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "rnn", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "rr", roff_rr, NULL, NULL, 0, NULL },
+	{ "rs", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "rt", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "schar", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "sentchar", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "shc", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "shift", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "sizes", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "so", roff_so, NULL, NULL, 0, NULL },
-	{ "ta", roff_line_ignore, NULL, NULL, 0, NULL },
-	{ "tr", roff_tr, NULL, NULL, 0, NULL },
-	{ "Dd", roff_Dd, NULL, NULL, 0, NULL },
+	{ "spacewidth", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "special", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "spreadwarn", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ss", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "sty", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "substring", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "sv", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "sy", roff_insec, NULL, NULL, 0, NULL },
+	{ "T&", roff_T_, NULL, NULL, 0, NULL },
+	{ "ta", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "tc", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "TE", roff_TE, NULL, NULL, 0, NULL },
 	{ "TH", roff_TH, NULL, NULL, 0, NULL },
+	{ "ti", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "tkf", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "tl", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "tm", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "tm1", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "tmc", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "tr", roff_tr, NULL, NULL, 0, NULL },
+	{ "track", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "transchar", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "trf", roff_insec, NULL, NULL, 0, NULL },
+	{ "trimat", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "trin", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "trnt", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "troff", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ "TS", roff_TS, NULL, NULL, 0, NULL },
-	{ "TE", roff_TE, NULL, NULL, 0, NULL },
-	{ "T&", roff_T_, NULL, NULL, 0, NULL },
-	{ "EQ", roff_EQ, NULL, NULL, 0, NULL },
-	{ "EN", roff_EN, NULL, NULL, 0, NULL },
+	{ "uf", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ul", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "unformat", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "unwatch", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "unwatchn", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "vpt", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "vs", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "warn", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "warnscale", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "watch", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "watchlength", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "watchn", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "wh", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "while", roff_unsupp, NULL, NULL, 0, NULL },
+	{ "write", roff_insec, NULL, NULL, 0, NULL },
+	{ "writec", roff_insec, NULL, NULL, 0, NULL },
+	{ "writem", roff_insec, NULL, NULL, 0, NULL },
+	{ "xflag", roff_line_ignore, NULL, NULL, 0, NULL },
 	{ ".", roff_cblock, NULL, NULL, 0, NULL },
-	{ "\\}", roff_ccond, NULL, NULL, 0, NULL },
 	{ NULL, roff_userdef, NULL, NULL, 0, NULL },
 };
 
+/* not currently implemented: Ds em Eq LP Me PP pp Or Rd Sf SH */
 const	char *const __mdoc_reserved[] = {
 	"Ac", "Ad", "An", "Ao", "Ap", "Aq", "Ar", "At",
 	"Bc", "Bd", "Bf", "Bk", "Bl", "Bo", "Bq",
 	"Brc", "Bro", "Brq", "Bsx", "Bt", "Bx",
 	"Cd", "Cm", "Db", "Dc", "Dd", "Dl", "Do", "Dq",
-	"Ds", "Dt", "Dv", "Dx", "D1",
-	"Ec", "Ed", "Ef", "Ek", "El", "Em", "em",
-	"En", "Eo", "Eq", "Er", "Es", "Ev", "Ex",
+	"Dt", "Dv", "Dx", "D1",
+	"Ec", "Ed", "Ef", "Ek", "El", "Em",
+	"En", "Eo", "Er", "Es", "Ev", "Ex",
 	"Fa", "Fc", "Fd", "Fl", "Fn", "Fo", "Fr", "Ft", "Fx",
-	"Hf", "Ic", "In", "It", "Lb", "Li", "Lk", "Lp", "LP",
-	"Me", "Ms", "Mt", "Nd", "Nm", "No", "Ns", "Nx",
+	"Hf", "Ic", "In", "It", "Lb", "Li", "Lk", "Lp",
+	"Ms", "Mt", "Nd", "Nm", "No", "Ns", "Nx",
 	"Oc", "Oo", "Op", "Os", "Ot", "Ox",
-	"Pa", "Pc", "Pf", "Po", "Pp", "PP", "pp", "Pq",
-	"Qc", "Ql", "Qo", "Qq", "Or", "Rd", "Re", "Rs", "Rv",
-	"Sc", "Sf", "Sh", "SH", "Sm", "So", "Sq",
+	"Pa", "Pc", "Pf", "Po", "Pp", "Pq",
+	"Qc", "Ql", "Qo", "Qq", "Re", "Rs", "Rv",
+	"Sc", "Sh", "Sm", "So", "Sq",
 	"Ss", "St", "Sx", "Sy",
 	"Ta", "Tn", "Ud", "Ux", "Va", "Vt", "Xc", "Xo", "Xr",
-	"%A", "%B", "%D", "%I", "%J", "%N", "%O",
+	"%A", "%B", "%C", "%D", "%I", "%J", "%N", "%O",
 	"%P", "%Q", "%R", "%T", "%U", "%V",
 	NULL
 };
 
+/* not currently implemented: BT DE DS ME MT PT SY TQ YS */
 const	char *const __man_reserved[] = {
-	"AT", "B", "BI", "BR", "BT", "DE", "DS", "DT",
-	"EE", "EN", "EQ", "EX", "HF", "HP", "I", "IB", "IP", "IR",
-	"LP", "ME", "MT", "OP", "P", "PD", "PP", "PT",
-	"R", "RB", "RE", "RI", "RS", "SB", "SH", "SM", "SS", "SY",
-	"TE", "TH", "TP", "TQ", "TS", "T&", "UC", "UE", "UR", "YS",
+	"AT", "B", "BI", "BR", "DT",
+	"EE", "EN", "EQ", "EX", "HP", "I", "IB", "IP", "IR",
+	"LP", "OP", "P", "PD", "PP",
+	"R", "RB", "RE", "RI", "RS", "SB", "SH", "SM", "SS",
+	"TE", "TH", "TP", "TS", "T&", "UC", "UE", "UR",
 	NULL
 };
 
@@ -308,6 +730,7 @@ static	const struct predef predefs[PREDEFS_MAX] = {
 static	int	 roffit_lines;  /* number of lines to delay */
 static	char	*roffit_macro;  /* nil-terminated macro line */
 
+
 static void
 roffhash_init(void)
 {
@@ -360,7 +783,6 @@ roffhash_find(const char *p, size_t s)
 	return(ROFF_MAX);
 }
 
-
 /*
  * Pop the current node off of the stack of roff instructions currently
  * pending.
@@ -371,7 +793,7 @@ roffnode_pop(struct roff *r)
 	struct roffnode	*p;
 
 	assert(r->last);
-	p = r->last; 
+	p = r->last;
 
 	r->last = r->last->parent;
 	free(p->name);
@@ -379,7 +801,6 @@ roffnode_pop(struct roff *r)
 	free(p);
 }
 
-
 /*
  * Push a roff node onto the instruction stack.  This must later be
  * removed with roffnode_pop().
@@ -397,12 +818,11 @@ roffnode_push(struct roff *r, enum rofft tok, const char *name,
 	p->parent = r->last;
 	p->line = line;
 	p->col = col;
-	p->rule = p->parent ? p->parent->rule : ROFFRULE_DENY;
+	p->rule = p->parent ? p->parent->rule : 0;
 
 	r->last = p;
 }
 
-
 static void
 roff_free1(struct roff *r)
 {
@@ -414,32 +834,32 @@ roff_free1(struct roff *r)
 		r->first_tbl = tbl->next;
 		tbl_free(tbl);
 	}
-
 	r->first_tbl = r->last_tbl = r->tbl = NULL;
 
 	while (NULL != (e = r->first_eqn)) {
 		r->first_eqn = e->next;
 		eqn_free(e);
 	}
-
 	r->first_eqn = r->last_eqn = r->eqn = NULL;
 
 	while (r->last)
 		roffnode_pop(r);
 
-	roff_freestr(r->strtab);
-	roff_freestr(r->xmbtab);
-
-	r->strtab = r->xmbtab = NULL;
+	free (r->rstack);
+	r->rstack = NULL;
+	r->rstacksz = 0;
+	r->rstackpos = -1;
 
 	roff_freereg(r->regtab);
-
 	r->regtab = NULL;
 
+	roff_freestr(r->strtab);
+	roff_freestr(r->xmbtab);
+	r->strtab = r->xmbtab = NULL;
+
 	if (r->xtab)
 		for (i = 0; i < 128; i++)
 			free(r->xtab[i].p);
-
 	free(r->xtab);
 	r->xtab = NULL;
 }
@@ -447,17 +867,12 @@ roff_free1(struct roff *r)
 void
 roff_reset(struct roff *r)
 {
-	int		 i;
 
 	roff_free1(r);
-
+	r->format = r->options & (MPARSE_MDOC | MPARSE_MAN);
 	r->control = 0;
-
-	for (i = 0; i < PREDEFS_MAX; i++) 
-		roff_setstr(r, predefs[i].name, predefs[i].str, 0);
 }
 
-
 void
 roff_free(struct roff *r)
 {
@@ -466,78 +881,100 @@ roff_free(struct roff *r)
 	free(r);
 }
 
-
 struct roff *
-roff_alloc(enum mparset type, struct mparse *parse)
+roff_alloc(struct mparse *parse, const struct mchars *mchars, int options)
 {
 	struct roff	*r;
-	int		 i;
 
 	r = mandoc_calloc(1, sizeof(struct roff));
-	r->parsetype = type;
 	r->parse = parse;
+	r->mchars = mchars;
+	r->options = options;
+	r->format = options & (MPARSE_MDOC | MPARSE_MAN);
 	r->rstackpos = -1;
-	
-	roffhash_init();
 
-	for (i = 0; i < PREDEFS_MAX; i++) 
-		roff_setstr(r, predefs[i].name, predefs[i].str, 0);
+	roffhash_init();
 
 	return(r);
 }
 
 /*
- * In the current line, expand user-defined strings ("\*")
- * and references to number registers ("\n").
- * Also check the syntax of other escape sequences.
+ * In the current line, expand escape sequences that tend to get
+ * used in numerical expressions and conditional requests.
+ * Also check the syntax of the remaining escape sequences.
  */
 static enum rofferr
-roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos)
+roff_res(struct roff *r, struct buf *buf, int ln, int pos)
 {
-	char		 ubuf[12]; /* buffer to print the number */
-	const char	*stesc;	/* start of an escape sequence ('\\') */
+	char		 ubuf[24]; /* buffer to print the number */
+	const char	*start;	/* start of the string to process */
+	char		*stesc;	/* start of an escape sequence ('\\') */
 	const char	*stnam;	/* start of the name, after "[(*" */
 	const char	*cp;	/* end of the name, e.g. before ']' */
 	const char	*res;	/* the string to be substituted */
-	char		*nbuf;	/* new buffer to copy bufp to */
-	size_t		 nsz;	/* size of the new buffer */
+	char		*nbuf;	/* new buffer to copy buf->buf to */
 	size_t		 maxl;  /* expected length of the escape name */
 	size_t		 naml;	/* actual length of the escape name */
+	enum mandoc_esc	 esc;	/* type of the escape sequence */
+	int		 inaml;	/* length returned from mandoc_escape() */
 	int		 expand_count;	/* to avoid infinite loops */
+	int		 npos;	/* position in numeric expression */
+	int		 arg_complete; /* argument not interrupted by eol */
+	char		 term;	/* character terminating the escape */
 
 	expand_count = 0;
+	start = buf->buf + pos;
+	stesc = strchr(start, '\0') - 1;
+	while (stesc-- > start) {
 
-again:
-	cp = *bufp + pos;
-	while (NULL != (cp = strchr(cp, '\\'))) {
-		stesc = cp++;
+		/* Search backwards for the next backslash. */
 
-		/*
-		 * The second character must be an asterisk or an n.
-		 * If it isn't, skip it anyway:  It is escaped,
-		 * so it can't start another escape sequence.
-		 */
+		if (*stesc != '\\')
+			continue;
+
+		/* If it is escaped, skip it. */
+
+		for (cp = stesc - 1; cp >= start; cp--)
+			if (*cp != '\\')
+				break;
+
+		if ((stesc - cp) % 2 == 0) {
+			stesc = (char *)cp;
+			continue;
+		}
 
-		if ('\0' == *cp)
-			return(ROFF_CONT);
+		/* Decide whether to expand or to check only. */
 
+		term = '\0';
+		cp = stesc + 1;
 		switch (*cp) {
-		case ('*'):
+		case '*':
 			res = NULL;
 			break;
-		case ('n'):
+		case 'B':
+			/* FALLTHROUGH */
+		case 'w':
+			term = cp[1];
+			/* FALLTHROUGH */
+		case 'n':
 			res = ubuf;
 			break;
 		default:
-			if (ESCAPE_ERROR != mandoc_escape(&cp, NULL, NULL))
-				continue;
-			mandoc_msg
-				(MANDOCERR_BADESCAPE, r->parse, 
-				 ln, (int)(stesc - *bufp), NULL);
-			return(ROFF_CONT);
+			esc = mandoc_escape(&cp, &stnam, &inaml);
+			if (esc == ESCAPE_ERROR ||
+			    (esc == ESCAPE_SPECIAL &&
+			     mchars_spec2cp(r->mchars, stnam, inaml) < 0))
+				mandoc_vmsg(MANDOCERR_ESC_BAD,
+				    r->parse, ln, (int)(stesc - buf->buf),
+				    "%.*s", (int)(cp - stesc), stesc);
+			continue;
 		}
 
-		cp++;
+		if (EXPAND_LIMIT < ++expand_count) {
+			mandoc_msg(MANDOCERR_ROFFLOOP, r->parse,
+			    ln, (int)(stesc - buf->buf), NULL);
+			return(ROFF_IGN);
+		}
 
 		/*
 		 * The third character decides the length
@@ -545,35 +982,62 @@ again:
 		 * Save a pointer to the name.
 		 */
 
-		switch (*cp) {
-		case ('\0'):
-			return(ROFF_CONT);
-		case ('('):
-			cp++;
-			maxl = 2;
-			break;
-		case ('['):
-			cp++;
+		if (term == '\0') {
+			switch (*++cp) {
+			case '\0':
+				maxl = 0;
+				break;
+			case '(':
+				cp++;
+				maxl = 2;
+				break;
+			case '[':
+				cp++;
+				term = ']';
+				maxl = 0;
+				break;
+			default:
+				maxl = 1;
+				break;
+			}
+		} else {
+			cp += 2;
 			maxl = 0;
-			break;
-		default:
-			maxl = 1;
-			break;
 		}
 		stnam = cp;
 
 		/* Advance to the end of the name. */
 
-		for (naml = 0; 0 == maxl || naml < maxl; naml++, cp++) {
-			if ('\0' == *cp) {
-				mandoc_msg
-					(MANDOCERR_BADESCAPE, 
-					 r->parse, ln, 
-					 (int)(stesc - *bufp), NULL);
-				return(ROFF_CONT);
+		naml = 0;
+		arg_complete = 1;
+		while (maxl == 0 || naml < maxl) {
+			if (*cp == '\0') {
+				mandoc_msg(MANDOCERR_ESC_BAD, r->parse,
+				    ln, (int)(stesc - buf->buf), stesc);
+				arg_complete = 0;
+				break;
+			}
+			if (maxl == 0 && *cp == term) {
+				cp++;
+				break;
+			}
+			if (*cp++ != '\\' || stesc[1] != 'w') {
+				naml++;
+				continue;
 			}
-			if (0 == maxl && ']' == *cp)
+			switch (mandoc_escape(&cp, NULL, NULL)) {
+			case ESCAPE_SPECIAL:
+				/* FALLTHROUGH */
+			case ESCAPE_UNICODE:
+				/* FALLTHROUGH */
+			case ESCAPE_NUMBERED:
+				/* FALLTHROUGH */
+			case ESCAPE_OVERSTRIKE:
+				naml++;
+				break;
+			default:
 				break;
+			}
 		}
 
 		/*
@@ -581,41 +1045,56 @@ again:
 		 * undefined, resume searching for escapes.
 		 */
 
-		if (NULL == res)
-			res = roff_getstrn(r, stnam, naml);
-		else
-			snprintf(ubuf, sizeof(ubuf), "%d",
-			    roff_getregn(r, stnam, naml));
-
-		if (NULL == res) {
-			mandoc_msg
-				(MANDOCERR_BADESCAPE, r->parse, 
-				 ln, (int)(stesc - *bufp), NULL);
+		switch (stesc[1]) {
+		case '*':
+			if (arg_complete)
+				res = roff_getstrn(r, stnam, naml);
+			break;
+		case 'B':
+			npos = 0;
+			ubuf[0] = arg_complete &&
+			    roff_evalnum(r, ln, stnam, &npos,
+			      NULL, ROFFNUM_SCALE) &&
+			    stnam + npos + 1 == cp ? '1' : '0';
+			ubuf[1] = '\0';
+			break;
+		case 'n':
+			if (arg_complete)
+				(void)snprintf(ubuf, sizeof(ubuf), "%d",
+				    roff_getregn(r, stnam, naml));
+			else
+				ubuf[0] = '\0';
+			break;
+		case 'w':
+			/* use even incomplete args */
+			(void)snprintf(ubuf, sizeof(ubuf), "%d",
+			    24 * (int)naml);
+			break;
+		}
+
+		if (res == NULL) {
+			mandoc_vmsg(MANDOCERR_STR_UNDEF,
+			    r->parse, ln, (int)(stesc - buf->buf),
+			    "%.*s", (int)naml, stnam);
 			res = "";
+		} else if (buf->sz + strlen(res) > SHRT_MAX) {
+			mandoc_msg(MANDOCERR_ROFFLOOP, r->parse,
+			    ln, (int)(stesc - buf->buf), NULL);
+			return(ROFF_IGN);
 		}
 
 		/* Replace the escape sequence by the string. */
 
-		pos = stesc - *bufp;
-
-		nsz = *szp + strlen(res) + 1;
-		nbuf = mandoc_malloc(nsz);
+		*stesc = '\0';
+		buf->sz = mandoc_asprintf(&nbuf, "%s%s%s",
+		    buf->buf, res, cp) + 1;
 
-		strlcpy(nbuf, *bufp, (size_t)(stesc - *bufp + 1));
-		strlcat(nbuf, res, nsz);
-		strlcat(nbuf, cp + (maxl ? 0 : 1), nsz);
+		/* Prepare for the next replacement. */
 
-		free(*bufp);
-
-		*bufp = nbuf;
-		*szp = nsz;
-
-		if (EXPAND_LIMIT >= ++expand_count)
-			goto again;
-
-		/* Just leave the string unexpanded. */
-		mandoc_msg(MANDOCERR_ROFFLOOP, r->parse, ln, pos, NULL);
-		return(ROFF_IGN);
+		start = nbuf + pos;
+		stesc = nbuf + (stesc - buf->buf) + strlen(res);
+		free(buf->buf);
+		buf->buf = nbuf;
 	}
 	return(ROFF_CONT);
 }
@@ -626,7 +1105,7 @@ again:
  * Decrement and spring input line trap.
  */
 static enum rofferr
-roff_parsetext(char **bufp, size_t *szp, int pos, int *offs)
+roff_parsetext(struct buf *buf, int pos, int *offs)
 {
 	size_t		 sz;
 	const char	*start;
@@ -634,20 +1113,20 @@ roff_parsetext(char **bufp, size_t *szp, int pos, int *offs)
 	int		 isz;
 	enum mandoc_esc	 esc;
 
-	start = p = *bufp + pos;
+	start = p = buf->buf + pos;
 
-	while ('\0' != *p) {
+	while (*p != '\0') {
 		sz = strcspn(p, "-\\");
 		p += sz;
 
-		if ('\0' == *p)
+		if (*p == '\0')
 			break;
 
-		if ('\\' == *p) {
+		if (*p == '\\') {
 			/* Skip over escapes. */
 			p++;
 			esc = mandoc_escape((const char **)&p, NULL, NULL);
-			if (ESCAPE_ERROR == esc)
+			if (esc == ESCAPE_ERROR)
 				break;
 			continue;
 		} else if (p == start) {
@@ -662,69 +1141,83 @@ roff_parsetext(char **bufp, size_t *szp, int pos, int *offs)
 	}
 
 	/* Spring the input line trap. */
-	if (1 == roffit_lines) {
-		isz = asprintf(&p, "%s\n.%s", *bufp, roffit_macro);
-		if (-1 == isz) {
-			perror(NULL);
-			exit((int)MANDOCLEVEL_SYSERR);
-		}
-		free(*bufp);
-		*bufp = p;
-		*szp = isz + 1;
+	if (roffit_lines == 1) {
+		isz = mandoc_asprintf(&p, "%s\n.%s", buf->buf, roffit_macro);
+		free(buf->buf);
+		buf->buf = p;
+		buf->sz = isz + 1;
 		*offs = 0;
 		free(roffit_macro);
 		roffit_lines = 0;
 		return(ROFF_REPARSE);
-	} else if (1 < roffit_lines)
+	} else if (roffit_lines > 1)
 		--roffit_lines;
 	return(ROFF_CONT);
 }
 
 enum rofferr
-roff_parseln(struct roff *r, int ln, char **bufp, 
-		size_t *szp, int pos, int *offs)
+roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
 {
 	enum rofft	 t;
 	enum rofferr	 e;
-	int		 ppos, ctl;
+	int		 pos;	/* parse point */
+	int		 spos;	/* saved parse point for messages */
+	int		 ppos;	/* original offset in buf->buf */
+	int		 ctl;	/* macro line (boolean) */
 
-	/*
-	 * Run the reserved-word filter only if we have some reserved
-	 * words to fill in.
-	 */
+	ppos = pos = *offs;
+
+	/* Handle in-line equation delimiters. */
+
+	if (r->tbl == NULL &&
+	    r->last_eqn != NULL && r->last_eqn->delim &&
+	    (r->eqn == NULL || r->eqn_inline)) {
+		e = roff_eqndelim(r, buf, pos);
+		if (e == ROFF_REPARSE)
+			return(e);
+		assert(e == ROFF_CONT);
+	}
 
-	e = roff_res(r, bufp, szp, ln, pos);
-	if (ROFF_IGN == e)
+	/* Expand some escape sequences. */
+
+	e = roff_res(r, buf, ln, pos);
+	if (e == ROFF_IGN)
 		return(e);
-	assert(ROFF_CONT == e);
+	assert(e == ROFF_CONT);
 
-	ppos = pos;
-	ctl = roff_getcontrol(r, *bufp, &pos);
+	ctl = roff_getcontrol(r, buf->buf, &pos);
 
 	/*
 	 * First, if a scope is open and we're not a macro, pass the
-	 * text through the macro's filter.  If a scope isn't open and
-	 * we're not a macro, just let it through.
-	 * Finally, if there's an equation scope open, divert it into it
-	 * no matter our state.
+	 * text through the macro's filter.
+	 * Equations process all content themselves.
+	 * Tables process almost all content themselves, but we want
+	 * to warn about macros before passing it there.
 	 */
 
-	if (r->last && ! ctl) {
+	if (r->last != NULL && ! ctl) {
 		t = r->last->tok;
 		assert(roffs[t].text);
-		e = (*roffs[t].text)
-			(r, t, bufp, szp, ln, pos, pos, offs);
-		assert(ROFF_IGN == e || ROFF_CONT == e);
-		if (ROFF_CONT != e)
+		e = (*roffs[t].text)(r, t, buf, ln, pos, pos, offs);
+		assert(e == ROFF_IGN || e == ROFF_CONT);
+		if (e != ROFF_CONT)
 			return(e);
 	}
-	if (r->eqn)
-		return(eqn_read(&r->eqn, ln, *bufp, ppos, offs));
-	if ( ! ctl) {
-		if (r->tbl)
-			return(tbl_read(r->tbl, ln, *bufp, pos));
-		return(roff_parsetext(bufp, szp, pos, offs));
-	}
+	if (r->eqn != NULL)
+		return(eqn_read(&r->eqn, ln, buf->buf, ppos, offs));
+	if (r->tbl != NULL && ( ! ctl || buf->buf[pos] == '\0'))
+		return(tbl_read(r->tbl, ln, buf->buf, ppos));
+	if ( ! ctl)
+		return(roff_parsetext(buf, pos, offs));
+
+	/* Skip empty request lines. */
+
+	if (buf->buf[pos] == '"') {
+		mandoc_msg(MANDOCERR_COMMENT_BAD, r->parse,
+		    ln, pos, NULL);
+		return(ROFF_IGN);
+	} else if (buf->buf[pos] == '\0')
+		return(ROFF_IGN);
 
 	/*
 	 * If a scope is open, go to the child handler for that macro,
@@ -735,44 +1228,60 @@ roff_parseln(struct roff *r, int ln, char **bufp,
 	if (r->last) {
 		t = r->last->tok;
 		assert(roffs[t].sub);
-		return((*roffs[t].sub)
-				(r, t, bufp, szp, 
-				 ln, ppos, pos, offs));
+		return((*roffs[t].sub)(r, t, buf, ln, ppos, pos, offs));
+	}
+
+	/* No scope is open.  This is a new request or macro. */
+
+	spos = pos;
+	t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+	/* Tables ignore most macros. */
+
+	if (r->tbl != NULL && (t == ROFF_MAX || t == ROFF_TS)) {
+		mandoc_msg(MANDOCERR_TBLMACRO, r->parse,
+		    ln, pos, buf->buf + spos);
+		if (t == ROFF_TS)
+			return(ROFF_IGN);
+		while (buf->buf[pos] != '\0' && buf->buf[pos] != ' ')
+			pos++;
+		while (buf->buf[pos] != '\0' && buf->buf[pos] == ' ')
+			pos++;
+		return(tbl_read(r->tbl, ln, buf->buf, pos));
 	}
 
 	/*
-	 * Lastly, as we've no scope open, try to look up and execute
-	 * the new macro.  If no macro is found, simply return and let
-	 * the compilers handle it.
+	 * This is neither a roff request nor a user-defined macro.
+	 * Let the standard macro set parsers handle it.
 	 */
 
-	if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos)))
+	if (t == ROFF_MAX)
 		return(ROFF_CONT);
 
+	/* Execute a roff request or a user defined macro. */
+
 	assert(roffs[t].proc);
-	return((*roffs[t].proc)
-			(r, t, bufp, szp, 
-			 ln, ppos, pos, offs));
+	return((*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs));
 }
 
-
 void
 roff_endparse(struct roff *r)
 {
 
 	if (r->last)
-		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
-				r->last->line, r->last->col, NULL);
+		mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+		    r->last->line, r->last->col,
+		    roffs[r->last->tok].name);
 
 	if (r->eqn) {
-		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, 
-				r->eqn->eqn.ln, r->eqn->eqn.pos, NULL);
+		mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+		    r->eqn->eqn.ln, r->eqn->eqn.pos, "EQ");
 		eqn_end(&r->eqn);
 	}
 
 	if (r->tbl) {
-		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, 
-				r->tbl->line, r->tbl->pos, NULL);
+		mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+		    r->tbl->line, r->tbl->pos, "TS");
 		tbl_end(&r->tbl);
 	}
 }
@@ -782,37 +1291,30 @@ roff_endparse(struct roff *r)
  * form of ".foo xxx" in the usual way.
  */
 static enum rofft
-roff_parse(struct roff *r, const char *buf, int *pos)
+roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos)
 {
+	char		*cp;
 	const char	*mac;
 	size_t		 maclen;
 	enum rofft	 t;
 
-	if ('\0' == buf[*pos] || '"' == buf[*pos] || 
-			'\t' == buf[*pos] || ' ' == buf[*pos])
-		return(ROFF_MAX);
+	cp = buf + *pos;
 
-	/*
-	 * We stop the macro parse at an escape, tab, space, or nil.
-	 * However, `\}' is also a valid macro, so make sure we don't
-	 * clobber it by seeing the `\' as the end of token.
-	 */
+	if ('\0' == *cp || '"' == *cp || '\t' == *cp || ' ' == *cp)
+		return(ROFF_MAX);
 
-	mac = buf + *pos;
-	maclen = strcspn(mac + 1, " \\\t\0") + 1;
+	mac = cp;
+	maclen = roff_getname(r, &cp, ln, ppos);
 
 	t = (r->current_string = roff_getstrn(r, mac, maclen))
 	    ? ROFF_USERDEF : roffhash_find(mac, maclen);
 
-	*pos += (int)maclen;
-
-	while (buf[*pos] && ' ' == buf[*pos])
-		(*pos)++;
+	if (ROFF_MAX != t)
+		*pos = cp - buf;
 
 	return(t);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_cblock(ROFF_ARGS)
 {
@@ -822,32 +1324,34 @@ roff_cblock(ROFF_ARGS)
 	 * ignore macro, otherwise raise a warning and just ignore it.
 	 */
 
-	if (NULL == r->last) {
-		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+	if (r->last == NULL) {
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+		    ln, ppos, "..");
 		return(ROFF_IGN);
 	}
 
 	switch (r->last->tok) {
-	case (ROFF_am):
+	case ROFF_am:
+		/* ROFF_am1 is remapped to ROFF_am in roff_block(). */
 		/* FALLTHROUGH */
-	case (ROFF_ami):
+	case ROFF_ami:
 		/* FALLTHROUGH */
-	case (ROFF_am1):
-		/* FALLTHROUGH */
-	case (ROFF_de):
+	case ROFF_de:
 		/* ROFF_de1 is remapped to ROFF_de in roff_block(). */
 		/* FALLTHROUGH */
-	case (ROFF_dei):
+	case ROFF_dei:
 		/* FALLTHROUGH */
-	case (ROFF_ig):
+	case ROFF_ig:
 		break;
 	default:
-		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+		    ln, ppos, "..");
 		return(ROFF_IGN);
 	}
 
-	if ((*bufp)[pos])
-		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
+	if (buf->buf[pos] != '\0')
+		mandoc_vmsg(MANDOCERR_ARG_SKIP, r->parse, ln, pos,
+		    ".. %s", buf->buf + pos);
 
 	roffnode_pop(r);
 	roffnode_cleanscope(r);
@@ -855,7 +1359,6 @@ roff_cblock(ROFF_ARGS)
 
 }
 
-
 static void
 roffnode_cleanscope(struct roff *r)
 {
@@ -867,77 +1370,87 @@ roffnode_cleanscope(struct roff *r)
 	}
 }
 
-
-/* ARGSUSED */
-static enum rofferr
-roff_ccond(ROFF_ARGS)
+static void
+roff_ccond(struct roff *r, int ln, int ppos)
 {
 
 	if (NULL == r->last) {
-		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
-		return(ROFF_IGN);
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+		    ln, ppos, "\\}");
+		return;
 	}
 
 	switch (r->last->tok) {
-	case (ROFF_el):
+	case ROFF_el:
 		/* FALLTHROUGH */
-	case (ROFF_ie):
+	case ROFF_ie:
 		/* FALLTHROUGH */
-	case (ROFF_if):
+	case ROFF_if:
 		break;
 	default:
-		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
-		return(ROFF_IGN);
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+		    ln, ppos, "\\}");
+		return;
 	}
 
 	if (r->last->endspan > -1) {
-		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
-		return(ROFF_IGN);
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+		    ln, ppos, "\\}");
+		return;
 	}
 
-	if ((*bufp)[pos])
-		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
-
 	roffnode_pop(r);
 	roffnode_cleanscope(r);
-	return(ROFF_IGN);
+	return;
 }
 
-
-/* ARGSUSED */
 static enum rofferr
 roff_block(ROFF_ARGS)
 {
-	int		sv;
-	size_t		sz;
-	char		*name;
-
-	name = NULL;
-
-	if (ROFF_ig != tok) {
-		if ('\0' == (*bufp)[pos]) {
-			mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
-			return(ROFF_IGN);
-		}
-
-		/*
-		 * Re-write `de1', since we don't really care about
-		 * groff's strange compatibility mode, into `de'.
-		 */
+	const char	*name;
+	char		*iname, *cp;
+	size_t		 namesz;
+
+	/* Ignore groff compatibility mode for now. */
+
+	if (tok == ROFF_de1)
+		tok = ROFF_de;
+	else if (tok == ROFF_dei1)
+		tok = ROFF_dei;
+	else if (tok == ROFF_am1)
+		tok = ROFF_am;
+	else if (tok == ROFF_ami1)
+		tok = ROFF_ami;
+
+	/* Parse the macro name argument. */
+
+	cp = buf->buf + pos;
+	if (tok == ROFF_ig) {
+		iname = NULL;
+		namesz = 0;
+	} else {
+		iname = cp;
+		namesz = roff_getname(r, &cp, ln, ppos);
+		iname[namesz] = '\0';
+	}
 
-		if (ROFF_de1 == tok)
-			tok = ROFF_de;
-		if (ROFF_de == tok)
-			name = *bufp + pos;
-		else
-			mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos,
-			    roffs[tok].name);
+	/* Resolve the macro name argument if it is indirect. */
 
-		while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos]))
-			pos++;
+	if (namesz && (tok == ROFF_dei || tok == ROFF_ami)) {
+		if ((name = roff_getstrn(r, iname, namesz)) == NULL) {
+			mandoc_vmsg(MANDOCERR_STR_UNDEF,
+			    r->parse, ln, (int)(iname - buf->buf),
+			    "%.*s", (int)namesz, iname);
+			namesz = 0;
+		} else
+			namesz = strlen(name);
+	} else
+		name = iname;
 
-		while (isspace((unsigned char)(*bufp)[pos]))
-			(*bufp)[pos++] = '\0';
+	if (namesz == 0 && tok != ROFF_ig) {
+		mandoc_msg(MANDOCERR_REQ_EMPTY, r->parse,
+		    ln, ppos, roffs[tok].name);
+		return(ROFF_IGN);
 	}
 
 	roffnode_push(r, tok, name, ln, ppos);
@@ -945,46 +1458,43 @@ roff_block(ROFF_ARGS)
 	/*
 	 * At the beginning of a `de' macro, clear the existing string
 	 * with the same name, if there is one.  New content will be
-	 * added from roff_block_text() in multiline mode.
+	 * appended from roff_block_text() in multiline mode.
 	 */
 
-	if (ROFF_de == tok)
-		roff_setstr(r, name, "", 0);
+	if (tok == ROFF_de || tok == ROFF_dei)
+		roff_setstrn(&r->strtab, name, namesz, "", 0, 0);
 
-	if ('\0' == (*bufp)[pos])
+	if (*cp == '\0')
 		return(ROFF_IGN);
 
-	/* If present, process the custom end-of-line marker. */
+	/* Get the custom end marker. */
 
-	sv = pos;
-	while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos]))
-		pos++;
-
-	/*
-	 * Note: groff does NOT like escape characters in the input.
-	 * Instead of detecting this, we're just going to let it fly and
-	 * to hell with it.
-	 */
+	iname = cp;
+	namesz = roff_getname(r, &cp, ln, ppos);
 
-	assert(pos > sv);
-	sz = (size_t)(pos - sv);
+	/* Resolve the end marker if it is indirect. */
 
-	if (1 == sz && '.' == (*bufp)[sv])
-		return(ROFF_IGN);
-
-	r->last->end = mandoc_malloc(sz + 1);
+	if (namesz && (tok == ROFF_dei || tok == ROFF_ami)) {
+		if ((name = roff_getstrn(r, iname, namesz)) == NULL) {
+			mandoc_vmsg(MANDOCERR_STR_UNDEF,
+			    r->parse, ln, (int)(iname - buf->buf),
+			    "%.*s", (int)namesz, iname);
+			namesz = 0;
+		} else
+			namesz = strlen(name);
+	} else
+		name = iname;
 
-	memcpy(r->last->end, *bufp + sv, sz);
-	r->last->end[(int)sz] = '\0';
+	if (namesz)
+		r->last->end = mandoc_strndup(name, namesz);
 
-	if ((*bufp)[pos])
-		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
+	if (*cp != '\0')
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, r->parse,
+		    ln, pos, ".%s ... %s", roffs[tok].name, cp);
 
 	return(ROFF_IGN);
 }
 
-
-/* ARGSUSED */
 static enum rofferr
 roff_block_sub(ROFF_ARGS)
 {
@@ -1002,21 +1512,22 @@ roff_block_sub(ROFF_ARGS)
 
 	if (r->last->end) {
 		for (i = pos, j = 0; r->last->end[j]; j++, i++)
-			if ((*bufp)[i] != r->last->end[j])
+			if (buf->buf[i] != r->last->end[j])
 				break;
 
-		if ('\0' == r->last->end[j] && 
-				('\0' == (*bufp)[i] ||
-				 ' ' == (*bufp)[i] ||
-				 '\t' == (*bufp)[i])) {
+		if (r->last->end[j] == '\0' &&
+		    (buf->buf[i] == '\0' ||
+		     buf->buf[i] == ' ' ||
+		     buf->buf[i] == '\t')) {
 			roffnode_pop(r);
 			roffnode_cleanscope(r);
 
-			while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
+			while (buf->buf[i] == ' ' || buf->buf[i] == '\t')
 				i++;
 
 			pos = i;
-			if (ROFF_MAX != roff_parse(r, *bufp, &pos))
+			if (roff_parse(r, buf->buf, &pos, ln, ppos) !=
+			    ROFF_MAX)
 				return(ROFF_RERUN);
 			return(ROFF_IGN);
 		}
@@ -1027,220 +1538,259 @@ roff_block_sub(ROFF_ARGS)
 	 * pulling it out of the hashtable.
 	 */
 
-	t = roff_parse(r, *bufp, &pos);
+	t = roff_parse(r, buf->buf, &pos, ln, ppos);
 
-	/*
-	 * Macros other than block-end are only significant
-	 * in `de' blocks; elsewhere, simply throw them away.
-	 */
-	if (ROFF_cblock != t) {
-		if (ROFF_de == tok)
-			roff_setstr(r, r->last->name, *bufp + ppos, 1);
+	if (t != ROFF_cblock) {
+		if (tok != ROFF_ig)
+			roff_setstr(r, r->last->name, buf->buf + ppos, 2);
 		return(ROFF_IGN);
 	}
 
 	assert(roffs[t].proc);
-	return((*roffs[t].proc)(r, t, bufp, szp, 
-				ln, ppos, pos, offs));
+	return((*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs));
 }
 
-
-/* ARGSUSED */
 static enum rofferr
 roff_block_text(ROFF_ARGS)
 {
 
-	if (ROFF_de == tok)
-		roff_setstr(r, r->last->name, *bufp + pos, 1);
+	if (tok != ROFF_ig)
+		roff_setstr(r, r->last->name, buf->buf + pos, 2);
 
 	return(ROFF_IGN);
 }
 
-
-/* ARGSUSED */
 static enum rofferr
 roff_cond_sub(ROFF_ARGS)
 {
 	enum rofft	 t;
-	enum roffrule	 rr;
 	char		*ep;
+	int		 rr;
 
 	rr = r->last->rule;
 	roffnode_cleanscope(r);
-	t = roff_parse(r, *bufp, &pos);
+	t = roff_parse(r, buf->buf, &pos, ln, ppos);
 
 	/*
 	 * Fully handle known macros when they are structurally
 	 * required or when the conditional evaluated to true.
 	 */
 
-	if ((ROFF_MAX != t) &&
-	    (ROFF_ccond == t || ROFFRULE_ALLOW == rr ||
-	     ROFFMAC_STRUCT & roffs[t].flags)) {
+	if ((t != ROFF_MAX) &&
+	    (rr || roffs[t].flags & ROFFMAC_STRUCT)) {
 		assert(roffs[t].proc);
-		return((*roffs[t].proc)(r, t, bufp, szp,
-					ln, ppos, pos, offs));
+		return((*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs));
 	}
 
-	/* Always check for the closing delimiter `\}'. */
-
-	ep = &(*bufp)[pos];
-	while (NULL != (ep = strchr(ep, '\\'))) {
-		if ('}' != *(++ep))
-			continue;
+	/*
+	 * If `\}' occurs on a macro line without a preceding macro,
+	 * drop the line completely.
+	 */
 
-		/*
-		 * If we're at the end of line, then just chop
-		 * off the \} and resize the buffer.
-		 * If we aren't, then convert it to spaces.
-		 */
+	ep = buf->buf + pos;
+	if (ep[0] == '\\' && ep[1] == '}')
+		rr = 0;
 
-		if ('\0' == *(ep + 1)) {
-			*--ep = '\0';
-			*szp -= 2;
-		} else
-			*(ep - 1) = *ep = ' ';
+	/* Always check for the closing delimiter `\}'. */
 
-		roff_ccond(r, ROFF_ccond, bufp, szp, 
-				ln, pos, pos + 2, offs);
-		break;
+	while ((ep = strchr(ep, '\\')) != NULL) {
+		if (*(++ep) == '}') {
+			*ep = '&';
+			roff_ccond(r, ln, ep - buf->buf - 1);
+		}
+		if (*ep != '\0')
+			++ep;
 	}
-	return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
+	return(rr ? ROFF_CONT : ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_cond_text(ROFF_ARGS)
 {
 	char		*ep;
-	enum roffrule	 rr;
+	int		 rr;
 
 	rr = r->last->rule;
 	roffnode_cleanscope(r);
 
-	ep = &(*bufp)[pos];
-	for ( ; NULL != (ep = strchr(ep, '\\')); ep++) {
-		ep++;
-		if ('}' != *ep)
-			continue;
-		*ep = '&';
-		roff_ccond(r, ROFF_ccond, bufp, szp, 
-				ln, pos, pos + 2, offs);
+	ep = buf->buf + pos;
+	while ((ep = strchr(ep, '\\')) != NULL) {
+		if (*(++ep) == '}') {
+			*ep = '&';
+			roff_ccond(r, ln, ep - buf->buf - 1);
+		}
+		if (*ep != '\0')
+			++ep;
 	}
-	return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
+	return(rr ? ROFF_CONT : ROFF_IGN);
 }
 
+/*
+ * Parse a single signed integer number.  Stop at the first non-digit.
+ * If there is at least one digit, return success and advance the
+ * parse point, else return failure and let the parse point unchanged.
+ * Ignore overflows, treat them just like the C language.
+ */
 static int
-roff_getnum(const char *v, int *pos, int *res)
+roff_getnum(const char *v, int *pos, int *res, int flags)
 {
-	int p, n;
+	int	 myres, scaled, n, p;
+
+	if (NULL == res)
+		res = &myres;
 
 	p = *pos;
 	n = v[p] == '-';
-	if (n)
+	if (n || v[p] == '+')
 		p++;
 
+	if (flags & ROFFNUM_WHITE)
+		while (isspace((unsigned char)v[p]))
+			p++;
+
 	for (*res = 0; isdigit((unsigned char)v[p]); p++)
-		*res += 10 * *res + v[p] - '0';
+		*res = 10 * *res + v[p] - '0';
 	if (p == *pos + n)
 		return 0;
 
 	if (n)
 		*res = -*res;
 
-	*pos = p;
-	return 1;
-}
-
-static int
-roff_getop(const char *v, int *pos, char *res)
-{
-	int e;
-
-	*res = v[*pos];
-	e = v[*pos + 1] == '=';
+	/* Each number may be followed by one optional scaling unit. */
 
-	switch (*res) {
-	case '=':
+	switch (v[p]) {
+	case 'f':
+		scaled = *res * 65536;
 		break;
-	case '>':
-		if (e)
-			*res = 'g';
+	case 'i':
+		scaled = *res * 240;
 		break;
-	case '<':
-		if (e)
-			*res = 'l';
+	case 'c':
+		scaled = *res * 240 / 2.54;
+		break;
+	case 'v':
+		/* FALLTROUGH */
+	case 'P':
+		scaled = *res * 40;
+		break;
+	case 'm':
+		/* FALLTROUGH */
+	case 'n':
+		scaled = *res * 24;
+		break;
+	case 'p':
+		scaled = *res * 10 / 3;
+		break;
+	case 'u':
+		scaled = *res;
+		break;
+	case 'M':
+		scaled = *res * 6 / 25;
 		break;
 	default:
-		return(0);
+		scaled = *res;
+		p--;
+		break;
 	}
+	if (flags & ROFFNUM_SCALE)
+		*res = scaled;
 
-	*pos += 1 + e;
+	*pos = p + 1;
+	return(1);
+}
 
-	return(*res);
+/*
+ * Evaluate a string comparison condition.
+ * The first character is the delimiter.
+ * Succeed if the string up to its second occurrence
+ * matches the string up to its third occurence.
+ * Advance the cursor after the third occurrence
+ * or lacking that, to the end of the line.
+ */
+static int
+roff_evalstrcond(const char *v, int *pos)
+{
+	const char	*s1, *s2, *s3;
+	int		 match;
+
+	match = 0;
+	s1 = v + *pos;		/* initial delimiter */
+	s2 = s1 + 1;		/* for scanning the first string */
+	s3 = strchr(s2, *s1);	/* for scanning the second string */
+
+	if (NULL == s3)		/* found no middle delimiter */
+		goto out;
+
+	while ('\0' != *++s3) {
+		if (*s2 != *s3) {  /* mismatch */
+			s3 = strchr(s3, *s1);
+			break;
+		}
+		if (*s3 == *s1) {  /* found the final delimiter */
+			match = 1;
+			break;
+		}
+		s2++;
+	}
+
+out:
+	if (NULL == s3)
+		s3 = strchr(s2, '\0');
+	else if (*s3 != '\0')
+		s3++;
+	*pos = s3 - v;
+	return(match);
 }
 
-static enum roffrule
-roff_evalcond(const char *v, int *pos)
+/*
+ * Evaluate an optionally negated single character, numerical,
+ * or string condition.
+ */
+static int
+roff_evalcond(struct roff *r, int ln, const char *v, int *pos)
 {
-	int	 not, lh, rh;
-	char	 op;
+	int	 number, savepos, wanttrue;
+
+	if ('!' == v[*pos]) {
+		wanttrue = 0;
+		(*pos)++;
+	} else
+		wanttrue = 1;
 
 	switch (v[*pos]) {
-	case ('n'):
+	case '\0':
+		return(0);
+	case 'n':
+		/* FALLTHROUGH */
+	case 'o':
 		(*pos)++;
-		return(ROFFRULE_ALLOW);
-	case ('e'):
+		return(wanttrue);
+	case 'c':
 		/* FALLTHROUGH */
-	case ('o'):
+	case 'd':
 		/* FALLTHROUGH */
-	case ('t'):
-		(*pos)++;
-		return(ROFFRULE_DENY);
-	case ('!'):
+	case 'e':
+		/* FALLTHROUGH */
+	case 'r':
+		/* FALLTHROUGH */
+	case 't':
+		/* FALLTHROUGH */
+	case 'v':
 		(*pos)++;
-		not = 1;
-		break;
+		return(!wanttrue);
 	default:
-		not = 0;
 		break;
 	}
 
-	if (!roff_getnum(v, pos, &lh))
-		return ROFFRULE_DENY;
-	if (!roff_getop(v, pos, &op)) {
-		if (lh < 0)
-			lh = 0;
-		goto out;
-	}
-	if (!roff_getnum(v, pos, &rh))
-		return ROFFRULE_DENY;
-	switch (op) {
-	case 'g':
-		lh = lh >= rh;
-		break;
-	case 'l':
-		lh = lh <= rh;
-		break;
-	case '=':
-		lh = lh == rh;
-		break;
-	case '>':
-		lh = lh > rh;
-		break;
-	case '<':
-		lh = lh < rh;
-		break;
-	default:
-		return ROFFRULE_DENY;
-	}
-out:
-	if (not)
-		lh = !lh;
-	return lh ? ROFFRULE_ALLOW : ROFFRULE_DENY;
+	savepos = *pos;
+	if (roff_evalnum(r, ln, v, pos, &number, ROFFNUM_SCALE))
+		return((number > 0) == wanttrue);
+	else if (*pos == savepos)
+		return(roff_evalstrcond(v, pos) == wanttrue);
+	else
+		return (0);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_line_ignore(ROFF_ARGS)
 {
@@ -1248,46 +1798,60 @@ roff_line_ignore(ROFF_ARGS)
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
+static enum rofferr
+roff_insec(ROFF_ARGS)
+{
+
+	mandoc_msg(MANDOCERR_REQ_INSEC, r->parse,
+	    ln, ppos, roffs[tok].name);
+	return(ROFF_IGN);
+}
+
+static enum rofferr
+roff_unsupp(ROFF_ARGS)
+{
+
+	mandoc_msg(MANDOCERR_REQ_UNSUPP, r->parse,
+	    ln, ppos, roffs[tok].name);
+	return(ROFF_IGN);
+}
+
 static enum rofferr
 roff_cond(ROFF_ARGS)
 {
 
 	roffnode_push(r, tok, NULL, ln, ppos);
 
-	/* 
+	/*
 	 * An `.el' has no conditional body: it will consume the value
 	 * of the current rstack entry set in prior `ie' calls or
-	 * defaults to DENY.  
+	 * defaults to DENY.
 	 *
 	 * If we're not an `el', however, then evaluate the conditional.
 	 */
 
-	r->last->rule = ROFF_el == tok ?
-		(r->rstackpos < 0 ? 
-		 ROFFRULE_DENY : r->rstack[r->rstackpos--]) :
-		roff_evalcond(*bufp, &pos);
+	r->last->rule = tok == ROFF_el ?
+	    (r->rstackpos < 0 ? 0 : r->rstack[r->rstackpos--]) :
+	    roff_evalcond(r, ln, buf->buf, &pos);
 
 	/*
 	 * An if-else will put the NEGATION of the current evaluated
 	 * conditional into the stack of rules.
 	 */
 
-	if (ROFF_ie == tok) {
-		if (r->rstackpos == RSTACK_MAX - 1) {
-			mandoc_msg(MANDOCERR_MEM, 
-				r->parse, ln, ppos, NULL);
-			return(ROFF_ERR);
+	if (tok == ROFF_ie) {
+		if (r->rstackpos + 1 == r->rstacksz) {
+			r->rstacksz += 16;
+			r->rstack = mandoc_reallocarray(r->rstack,
+			    r->rstacksz, sizeof(int));
 		}
-		r->rstack[++r->rstackpos] = 
-			ROFFRULE_DENY == r->last->rule ?
-			ROFFRULE_ALLOW : ROFFRULE_DENY;
+		r->rstack[++r->rstackpos] = !r->last->rule;
 	}
 
 	/* If the parent has false as its rule, then so do we. */
 
-	if (r->last->parent && ROFFRULE_DENY == r->last->parent->rule)
-		r->last->rule = ROFFRULE_DENY;
+	if (r->last->parent && !r->last->parent->rule)
+		r->last->rule = 0;
 
 	/*
 	 * Determine scope.
@@ -1295,21 +1859,21 @@ roff_cond(ROFF_ARGS)
 	 * not even whitespace, use next-line scope.
 	 */
 
-	if ('\0' == (*bufp)[pos]) {
+	if (buf->buf[pos] == '\0') {
 		r->last->endspan = 2;
 		goto out;
 	}
 
-	while (' ' == (*bufp)[pos])
+	while (buf->buf[pos] == ' ')
 		pos++;
 
 	/* An opening brace requests multiline scope. */
 
-	if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) {
+	if (buf->buf[pos] == '\\' && buf->buf[pos + 1] == '{') {
 		r->last->endspan = -1;
 		pos += 2;
 		goto out;
-	} 
+	}
 
 	/*
 	 * Anything else following the conditional causes
@@ -1317,8 +1881,9 @@ roff_cond(ROFF_ARGS)
 	 * nothing but trailing whitespace.
 	 */
 
-	if ('\0' == (*bufp)[pos])
-		mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
+	if (buf->buf[pos] == '\0')
+		mandoc_msg(MANDOCERR_COND_EMPTY, r->parse,
+		    ln, ppos, roffs[tok].name);
 
 	r->last->endspan = 1;
 
@@ -1327,37 +1892,256 @@ out:
 	return(ROFF_RERUN);
 }
 
-
-/* ARGSUSED */
 static enum rofferr
 roff_ds(ROFF_ARGS)
 {
-	char		*name, *string;
+	char		*string;
+	const char	*name;
+	size_t		 namesz;
+
+	/* Ignore groff compatibility mode for now. */
+
+	if (tok == ROFF_ds1)
+		tok = ROFF_ds;
+	else if (tok == ROFF_as1)
+		tok = ROFF_as;
 
 	/*
-	 * A symbol is named by the first word following the macro
-	 * invocation up to a space.  Its value is anything after the
-	 * name's trailing whitespace and optional double-quote.  Thus,
-	 *
-	 *  [.ds foo "bar  "     ]
-	 *
-	 * will have `bar  "     ' as its value.
+	 * The first word is the name of the string.
+	 * If it is empty or terminated by an escape sequence,
+	 * abort the `ds' request without defining anything.
 	 */
 
-	string = *bufp + pos;
-	name = roff_getname(r, &string, ln, pos);
-	if ('\0' == *name)
+	name = string = buf->buf + pos;
+	if (*name == '\0')
+		return(ROFF_IGN);
+
+	namesz = roff_getname(r, &string, ln, pos);
+	if (name[namesz] == '\\')
 		return(ROFF_IGN);
 
-	/* Read past initial double-quote. */
-	if ('"' == *string)
+	/* Read past the initial double-quote, if any. */
+	if (*string == '"')
 		string++;
 
 	/* The rest is the value. */
-	roff_setstr(r, name, string, 0);
+	roff_setstrn(&r->strtab, name, namesz, string, strlen(string),
+	    ROFF_as == tok);
 	return(ROFF_IGN);
 }
 
+/*
+ * Parse a single operator, one or two characters long.
+ * If the operator is recognized, return success and advance the
+ * parse point, else return failure and let the parse point unchanged.
+ */
+static int
+roff_getop(const char *v, int *pos, char *res)
+{
+
+	*res = v[*pos];
+
+	switch (*res) {
+	case '+':
+		/* FALLTHROUGH */
+	case '-':
+		/* FALLTHROUGH */
+	case '*':
+		/* FALLTHROUGH */
+	case '/':
+		/* FALLTHROUGH */
+	case '%':
+		/* FALLTHROUGH */
+	case '&':
+		/* FALLTHROUGH */
+	case ':':
+		break;
+	case '<':
+		switch (v[*pos + 1]) {
+		case '=':
+			*res = 'l';
+			(*pos)++;
+			break;
+		case '>':
+			*res = '!';
+			(*pos)++;
+			break;
+		case '?':
+			*res = 'i';
+			(*pos)++;
+			break;
+		default:
+			break;
+		}
+		break;
+	case '>':
+		switch (v[*pos + 1]) {
+		case '=':
+			*res = 'g';
+			(*pos)++;
+			break;
+		case '?':
+			*res = 'a';
+			(*pos)++;
+			break;
+		default:
+			break;
+		}
+		break;
+	case '=':
+		if ('=' == v[*pos + 1])
+			(*pos)++;
+		break;
+	default:
+		return(0);
+	}
+	(*pos)++;
+
+	return(*res);
+}
+
+/*
+ * Evaluate either a parenthesized numeric expression
+ * or a single signed integer number.
+ */
+static int
+roff_evalpar(struct roff *r, int ln,
+	const char *v, int *pos, int *res, int flags)
+{
+
+	if ('(' != v[*pos])
+		return(roff_getnum(v, pos, res, flags));
+
+	(*pos)++;
+	if ( ! roff_evalnum(r, ln, v, pos, res, flags | ROFFNUM_WHITE))
+		return(0);
+
+	/*
+	 * Omission of the closing parenthesis
+	 * is an error in validation mode,
+	 * but ignored in evaluation mode.
+	 */
+
+	if (')' == v[*pos])
+		(*pos)++;
+	else if (NULL == res)
+		return(0);
+
+	return(1);
+}
+
+/*
+ * Evaluate a complete numeric expression.
+ * Proceed left to right, there is no concept of precedence.
+ */
+static int
+roff_evalnum(struct roff *r, int ln, const char *v,
+	int *pos, int *res, int flags)
+{
+	int		 mypos, operand2;
+	char		 operator;
+
+	if (NULL == pos) {
+		mypos = 0;
+		pos = &mypos;
+	}
+
+	if (flags & ROFFNUM_WHITE)
+		while (isspace((unsigned char)v[*pos]))
+			(*pos)++;
+
+	if ( ! roff_evalpar(r, ln, v, pos, res, flags))
+		return(0);
+
+	while (1) {
+		if (flags & ROFFNUM_WHITE)
+			while (isspace((unsigned char)v[*pos]))
+				(*pos)++;
+
+		if ( ! roff_getop(v, pos, &operator))
+			break;
+
+		if (flags & ROFFNUM_WHITE)
+			while (isspace((unsigned char)v[*pos]))
+				(*pos)++;
+
+		if ( ! roff_evalpar(r, ln, v, pos, &operand2, flags))
+			return(0);
+
+		if (flags & ROFFNUM_WHITE)
+			while (isspace((unsigned char)v[*pos]))
+				(*pos)++;
+
+		if (NULL == res)
+			continue;
+
+		switch (operator) {
+		case '+':
+			*res += operand2;
+			break;
+		case '-':
+			*res -= operand2;
+			break;
+		case '*':
+			*res *= operand2;
+			break;
+		case '/':
+			if (operand2 == 0) {
+				mandoc_msg(MANDOCERR_DIVZERO,
+					r->parse, ln, *pos, v);
+				*res = 0;
+				break;
+			}
+			*res /= operand2;
+			break;
+		case '%':
+			if (operand2 == 0) {
+				mandoc_msg(MANDOCERR_DIVZERO,
+					r->parse, ln, *pos, v);
+				*res = 0;
+				break;
+			}
+			*res %= operand2;
+			break;
+		case '<':
+			*res = *res < operand2;
+			break;
+		case '>':
+			*res = *res > operand2;
+			break;
+		case 'l':
+			*res = *res <= operand2;
+			break;
+		case 'g':
+			*res = *res >= operand2;
+			break;
+		case '=':
+			*res = *res == operand2;
+			break;
+		case '!':
+			*res = *res != operand2;
+			break;
+		case '&':
+			*res = *res && operand2;
+			break;
+		case ':':
+			*res = *res || operand2;
+			break;
+		case 'i':
+			if (operand2 < *res)
+				*res = operand2;
+			break;
+		case 'a':
+			if (operand2 > *res)
+				*res = operand2;
+			break;
+		default:
+			abort();
+		}
+	}
+	return(1);
+}
+
 void
 roff_setreg(struct roff *r, const char *name, int val, char sign)
 {
@@ -1387,10 +2171,45 @@ roff_setreg(struct roff *r, const char *name, int val, char sign)
 		reg->val = val;
 }
 
+/*
+ * Handle some predefined read-only number registers.
+ * For now, return -1 if the requested register is not predefined;
+ * in case a predefined read-only register having the value -1
+ * were to turn up, another special value would have to be chosen.
+ */
+static int
+roff_getregro(const char *name)
+{
+
+	switch (*name) {
+	case 'A':  /* ASCII approximation mode is always off. */
+		return(0);
+	case 'g':  /* Groff compatibility mode is always on. */
+		return(1);
+	case 'H':  /* Fixed horizontal resolution. */
+		return (24);
+	case 'j':  /* Always adjust left margin only. */
+		return(0);
+	case 'T':  /* Some output device is always defined. */
+		return(1);
+	case 'V':  /* Fixed vertical resolution. */
+		return (40);
+	default:
+		return (-1);
+	}
+}
+
 int
 roff_getreg(const struct roff *r, const char *name)
 {
 	struct roffreg	*reg;
+	int		 val;
+
+	if ('.' == name[0] && '\0' != name[1] && '\0' == name[2]) {
+		val = roff_getregro(name + 1);
+		if (-1 != val)
+			return (val);
+	}
 
 	for (reg = r->regtab; reg; reg = reg->next)
 		if (0 == strcmp(name, reg->key.p))
@@ -1403,6 +2222,13 @@ static int
 roff_getregn(const struct roff *r, const char *name, size_t len)
 {
 	struct roffreg	*reg;
+	int		 val;
+
+	if ('.' == name[0] && 2 == len) {
+		val = roff_getregro(name + 1);
+		if (-1 != val)
+			return (val);
+	}
 
 	for (reg = r->regtab; reg; reg = reg->next)
 		if (len == reg->key.sz &&
@@ -1425,182 +2251,274 @@ roff_freereg(struct roffreg *reg)
 	}
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_nr(ROFF_ARGS)
 {
-	const char	*key;
-	char		*val;
-	size_t		 sz;
+	char		*key, *val;
+	size_t		 keysz;
 	int		 iv;
 	char		 sign;
 
-	val = *bufp + pos;
-	key = roff_getname(r, &val, ln, pos);
+	key = val = buf->buf + pos;
+	if (*key == '\0')
+		return(ROFF_IGN);
+
+	keysz = roff_getname(r, &val, ln, pos);
+	if (key[keysz] == '\\')
+		return(ROFF_IGN);
+	key[keysz] = '\0';
 
 	sign = *val;
-	if ('+' == sign || '-' == sign)
+	if (sign == '+' || sign == '-')
 		val++;
 
-	sz = strspn(val, "0123456789");
-	iv = sz ? mandoc_strntoi(val, sz, 10) : 0;
+	if (roff_evalnum(r, ln, val, NULL, &iv, ROFFNUM_SCALE))
+		roff_setreg(r, key, iv, sign);
+
+	return(ROFF_IGN);
+}
 
-	roff_setreg(r, key, iv, sign);
+static enum rofferr
+roff_rr(ROFF_ARGS)
+{
+	struct roffreg	*reg, **prev;
+	char		*name, *cp;
+	size_t		 namesz;
+
+	name = cp = buf->buf + pos;
+	if (*name == '\0')
+		return(ROFF_IGN);
+	namesz = roff_getname(r, &cp, ln, pos);
+	name[namesz] = '\0';
 
+	prev = &r->regtab;
+	while (1) {
+		reg = *prev;
+		if (reg == NULL || !strcmp(name, reg->key.p))
+			break;
+		prev = ®->next;
+	}
+	if (reg != NULL) {
+		*prev = reg->next;
+		free(reg->key.p);
+		free(reg);
+	}
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_rm(ROFF_ARGS)
 {
 	const char	 *name;
 	char		 *cp;
+	size_t		  namesz;
 
-	cp = *bufp + pos;
-	while ('\0' != *cp) {
-		name = roff_getname(r, &cp, ln, (int)(cp - *bufp));
-		if ('\0' != *name)
-			roff_setstr(r, name, NULL, 0);
+	cp = buf->buf + pos;
+	while (*cp != '\0') {
+		name = cp;
+		namesz = roff_getname(r, &cp, ln, (int)(cp - buf->buf));
+		roff_setstrn(&r->strtab, name, namesz, NULL, 0, 0);
+		if (name[namesz] == '\\')
+			break;
 	}
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_it(ROFF_ARGS)
 {
-	char		*cp;
-	size_t		 len;
 	int		 iv;
 
 	/* Parse the number of lines. */
-	cp = *bufp + pos;
-	len = strcspn(cp, " \t");
-	cp[len] = '\0';
-	if ((iv = mandoc_strntoi(cp, len, 10)) <= 0) {
-		mandoc_msg(MANDOCERR_NUMERIC, r->parse,
-				ln, ppos, *bufp + 1);
+
+	if ( ! roff_evalnum(r, ln, buf->buf, &pos, &iv, 0)) {
+		mandoc_msg(MANDOCERR_IT_NONUM, r->parse,
+		    ln, ppos, buf->buf + 1);
 		return(ROFF_IGN);
 	}
-	cp += len + 1;
 
-	/* Arm the input line trap. */
+	while (isspace((unsigned char)buf->buf[pos]))
+		pos++;
+
+	/*
+	 * Arm the input line trap.
+	 * Special-casing "an-trap" is an ugly workaround to cope
+	 * with DocBook stupidly fiddling with man(7) internals.
+	 */
+
 	roffit_lines = iv;
-	roffit_macro = mandoc_strdup(cp);
+	roffit_macro = mandoc_strdup(iv != 1 ||
+	    strcmp(buf->buf + pos, "an-trap") ?
+	    buf->buf + pos : "br");
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_Dd(ROFF_ARGS)
 {
 	const char *const	*cp;
 
-	if (MPARSE_MDOC != r->parsetype)
+	if ((r->options & (MPARSE_MDOC | MPARSE_QUICK)) == 0)
 		for (cp = __mdoc_reserved; *cp; cp++)
 			roff_setstr(r, *cp, NULL, 0);
 
+	if (r->format == 0)
+		r->format = MPARSE_MDOC;
+
 	return(ROFF_CONT);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_TH(ROFF_ARGS)
 {
 	const char *const	*cp;
 
-	if (MPARSE_MDOC != r->parsetype)
+	if ((r->options & MPARSE_QUICK) == 0)
 		for (cp = __man_reserved; *cp; cp++)
 			roff_setstr(r, *cp, NULL, 0);
 
+	if (r->format == 0)
+		r->format = MPARSE_MAN;
+
 	return(ROFF_CONT);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_TE(ROFF_ARGS)
 {
 
 	if (NULL == r->tbl)
-		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
-	else
-		tbl_end(&r->tbl);
-
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+		    ln, ppos, "TE");
+	else if ( ! tbl_end(&r->tbl)) {
+		free(buf->buf);
+		buf->buf = mandoc_strdup(".sp");
+		buf->sz = 4;
+		return(ROFF_REPARSE);
+	}
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_T_(ROFF_ARGS)
 {
 
 	if (NULL == r->tbl)
-		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+		    ln, ppos, "T&");
 	else
 		tbl_restart(ppos, ln, r->tbl);
 
 	return(ROFF_IGN);
 }
 
-#if 0
-static int
-roff_closeeqn(struct roff *r)
+/*
+ * Handle in-line equation delimiters.
+ */
+static enum rofferr
+roff_eqndelim(struct roff *r, struct buf *buf, int pos)
 {
+	char		*cp1, *cp2;
+	const char	*bef_pr, *bef_nl, *mac, *aft_nl, *aft_pr;
 
-	return(r->eqn && ROFF_EQN == eqn_end(&r->eqn) ? 1 : 0);
-}
-#endif
+	/*
+	 * Outside equations, look for an opening delimiter.
+	 * If we are inside an equation, we already know it is
+	 * in-line, or this function wouldn't have been called;
+	 * so look for a closing delimiter.
+	 */
 
-static void
-roff_openeqn(struct roff *r, const char *name, int line, 
-		int offs, const char *buf)
-{
-	struct eqn_node *e;
-	int		 poff;
+	cp1 = buf->buf + pos;
+	cp2 = strchr(cp1, r->eqn == NULL ?
+	    r->last_eqn->odelim : r->last_eqn->cdelim);
+	if (cp2 == NULL)
+		return(ROFF_CONT);
 
-	assert(NULL == r->eqn);
-	e = eqn_alloc(name, offs, line, r->parse);
+	*cp2++ = '\0';
+	bef_pr = bef_nl = aft_nl = aft_pr = "";
 
-	if (r->last_eqn)
-		r->last_eqn->next = e;
-	else
-		r->first_eqn = r->last_eqn = e;
+	/* Handle preceding text, protecting whitespace. */
 
-	r->eqn = r->last_eqn = e;
+	if (*buf->buf != '\0') {
+		if (r->eqn == NULL)
+			bef_pr = "\\&";
+		bef_nl = "\n";
+	}
+
+	/*
+	 * Prepare replacing the delimiter with an equation macro
+	 * and drop leading white space from the equation.
+	 */
+
+	if (r->eqn == NULL) {
+		while (*cp2 == ' ')
+			cp2++;
+		mac = ".EQ";
+	} else
+		mac = ".EN";
+
+	/* Handle following text, protecting whitespace. */
 
-	if (buf) {
-		poff = 0;
-		eqn_read(&r->eqn, line, buf, offs, &poff);
+	if (*cp2 != '\0') {
+		aft_nl = "\n";
+		if (r->eqn != NULL)
+			aft_pr = "\\&";
 	}
+
+	/* Do the actual replacement. */
+
+	buf->sz = mandoc_asprintf(&cp1, "%s%s%s%s%s%s%s", buf->buf,
+	    bef_pr, bef_nl, mac, aft_nl, aft_pr, cp2) + 1;
+	free(buf->buf);
+	buf->buf = cp1;
+
+	/* Toggle the in-line state of the eqn subsystem. */
+
+	r->eqn_inline = r->eqn == NULL;
+	return(ROFF_REPARSE);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_EQ(ROFF_ARGS)
 {
+	struct eqn_node *e;
+
+	assert(r->eqn == NULL);
+	e = eqn_alloc(ppos, ln, r->parse);
+
+	if (r->last_eqn) {
+		r->last_eqn->next = e;
+		e->delim = r->last_eqn->delim;
+		e->odelim = r->last_eqn->odelim;
+		e->cdelim = r->last_eqn->cdelim;
+	} else
+		r->first_eqn = r->last_eqn = e;
+
+	r->eqn = r->last_eqn = e;
+
+	if (buf->buf[pos] != '\0')
+		mandoc_vmsg(MANDOCERR_ARG_SKIP, r->parse, ln, pos,
+		    ".EQ %s", buf->buf + pos);
 
-	roff_openeqn(r, *bufp + pos, ln, ppos, NULL);
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_EN(ROFF_ARGS)
 {
 
-	mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+	mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse, ln, ppos, "EN");
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_TS(ROFF_ARGS)
 {
 	struct tbl_node	*tbl;
 
 	if (r->tbl) {
-		mandoc_msg(MANDOCERR_SCOPEBROKEN, r->parse, ln, ppos, NULL);
+		mandoc_msg(MANDOCERR_BLK_BROKEN, r->parse,
+		    ln, ppos, "TS breaks TS");
 		tbl_end(&r->tbl);
 	}
 
@@ -1615,24 +2533,31 @@ roff_TS(ROFF_ARGS)
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
+static enum rofferr
+roff_brp(ROFF_ARGS)
+{
+
+	buf->buf[pos - 1] = '\0';
+	return(ROFF_CONT);
+}
+
 static enum rofferr
 roff_cc(ROFF_ARGS)
 {
 	const char	*p;
 
-	p = *bufp + pos;
+	p = buf->buf + pos;
 
-	if ('\0' == *p || '.' == (r->control = *p++))
+	if (*p == '\0' || (r->control = *p++) == '.')
 		r->control = 0;
 
-	if ('\0' != *p)
-		mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL);
+	if (*p != '\0')
+		mandoc_vmsg(MANDOCERR_ARG_EXCESS, r->parse,
+		    ln, p - buf->buf, "cc ... %s", p);
 
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_tr(ROFF_ARGS)
 {
@@ -1640,54 +2565,52 @@ roff_tr(ROFF_ARGS)
 	size_t		 fsz, ssz;
 	enum mandoc_esc	 esc;
 
-	p = *bufp + pos;
+	p = buf->buf + pos;
 
-	if ('\0' == *p) {
-		mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL);
+	if (*p == '\0') {
+		mandoc_msg(MANDOCERR_REQ_EMPTY, r->parse, ln, ppos, "tr");
 		return(ROFF_IGN);
 	}
 
-	while ('\0' != *p) {
+	while (*p != '\0') {
 		fsz = ssz = 1;
 
 		first = p++;
-		if ('\\' == *first) {
+		if (*first == '\\') {
 			esc = mandoc_escape(&p, NULL, NULL);
-			if (ESCAPE_ERROR == esc) {
-				mandoc_msg
-					(MANDOCERR_BADESCAPE, r->parse, 
-					 ln, (int)(p - *bufp), NULL);
+			if (esc == ESCAPE_ERROR) {
+				mandoc_msg(MANDOCERR_ESC_BAD, r->parse,
+				    ln, (int)(p - buf->buf), first);
 				return(ROFF_IGN);
 			}
 			fsz = (size_t)(p - first);
 		}
 
 		second = p++;
-		if ('\\' == *second) {
+		if (*second == '\\') {
 			esc = mandoc_escape(&p, NULL, NULL);
-			if (ESCAPE_ERROR == esc) {
-				mandoc_msg
-					(MANDOCERR_BADESCAPE, r->parse, 
-					 ln, (int)(p - *bufp), NULL);
+			if (esc == ESCAPE_ERROR) {
+				mandoc_msg(MANDOCERR_ESC_BAD, r->parse,
+				    ln, (int)(p - buf->buf), second);
 				return(ROFF_IGN);
 			}
 			ssz = (size_t)(p - second);
-		} else if ('\0' == *second) {
-			mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, 
-					ln, (int)(p - *bufp), NULL);
+		} else if (*second == '\0') {
+			mandoc_vmsg(MANDOCERR_TR_ODD, r->parse,
+			    ln, first - buf->buf, "tr %s", first);
 			second = " ";
 			p--;
 		}
 
 		if (fsz > 1) {
-			roff_setstrn(&r->xmbtab, first, 
-					fsz, second, ssz, 0);
+			roff_setstrn(&r->xmbtab, first, fsz,
+			    second, ssz, 0);
 			continue;
 		}
 
-		if (NULL == r->xtab)
-			r->xtab = mandoc_calloc
-				(128, sizeof(struct roffstr));
+		if (r->xtab == NULL)
+			r->xtab = mandoc_calloc(128,
+			    sizeof(struct roffstr));
 
 		free(r->xtab[(int)*first].p);
 		r->xtab[(int)*first].p = mandoc_strndup(second, ssz);
@@ -1697,13 +2620,13 @@ roff_tr(ROFF_ARGS)
 	return(ROFF_IGN);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_so(ROFF_ARGS)
 {
-	char *name;
+	char *name, *cp;
 
-	mandoc_msg(MANDOCERR_SO, r->parse, ln, ppos, NULL);
+	name = buf->buf + pos;
+	mandoc_vmsg(MANDOCERR_SO, r->parse, ln, ppos, "so %s", name);
 
 	/*
 	 * Handle `so'.  Be EXTREMELY careful, as we shouldn't be
@@ -1712,122 +2635,188 @@ roff_so(ROFF_ARGS)
 	 * or using absolute paths.
 	 */
 
-	name = *bufp + pos;
-	if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) {
-		mandoc_msg(MANDOCERR_SOPATH, r->parse, ln, pos, NULL);
-		return(ROFF_ERR);
+	if (*name == '/' || strstr(name, "../") || strstr(name, "/..")) {
+		mandoc_vmsg(MANDOCERR_SO_PATH, r->parse, ln, ppos,
+		    ".so %s", name);
+		buf->sz = mandoc_asprintf(&cp,
+		    ".sp\nSee the file %s.\n.sp", name) + 1;
+		free(buf->buf);
+		buf->buf = cp;
+		*offs = 0;
+		return(ROFF_REPARSE);
 	}
 
 	*offs = pos;
 	return(ROFF_SO);
 }
 
-/* ARGSUSED */
 static enum rofferr
 roff_userdef(ROFF_ARGS)
 {
-	const char	 *arg[9];
+	const char	 *arg[9], *ap;
 	char		 *cp, *n1, *n2;
 	int		  i;
+	size_t		  asz, rsz;
 
 	/*
 	 * Collect pointers to macro argument strings
 	 * and NUL-terminate them.
 	 */
-	cp = *bufp + pos;
+
+	cp = buf->buf + pos;
 	for (i = 0; i < 9; i++)
-		arg[i] = '\0' == *cp ? "" :
+		arg[i] = *cp == '\0' ? "" :
 		    mandoc_getarg(r->parse, &cp, ln, &pos);
 
 	/*
 	 * Expand macro arguments.
 	 */
-	*szp = 0;
-	n1 = cp = mandoc_strdup(r->current_string);
-	while (NULL != (cp = strstr(cp, "\\$"))) {
-		i = cp[2] - '1';
-		if (0 > i || 8 < i) {
-			/* Not an argument invocation. */
-			cp += 2;
+
+	buf->sz = strlen(r->current_string) + 1;
+	n1 = cp = mandoc_malloc(buf->sz);
+	memcpy(n1, r->current_string, buf->sz);
+	while (*cp != '\0') {
+
+		/* Scan ahead for the next argument invocation. */
+
+		if (*cp++ != '\\')
+			continue;
+		if (*cp++ != '$')
 			continue;
+		i = *cp - '1';
+		if (0 > i || 8 < i)
+			continue;
+		cp -= 2;
+
+		/*
+		 * Determine the size of the expanded argument,
+		 * taking escaping of quotes into account.
+		 */
+
+		asz = 0;
+		for (ap = arg[i]; *ap != '\0'; ap++) {
+			asz++;
+			if (*ap == '"')
+				asz += 3;
 		}
+		if (asz != 3) {
+
+			/*
+			 * Determine the size of the rest of the
+			 * unexpanded macro, including the NUL.
+			 */
+
+			rsz = buf->sz - (cp - n1) - 3;
 
-		*szp = strlen(n1) - 3 + strlen(arg[i]) + 1;
-		n2 = mandoc_malloc(*szp);
+			/*
+			 * When shrinking, move before
+			 * releasing the storage.
+			 */
+
+			if (asz < 3)
+				memmove(cp + asz, cp + 3, rsz);
+
+			/*
+			 * Resize the storage for the macro
+			 * and readjust the parse pointer.
+			 */
 
-		strlcpy(n2, n1, (size_t)(cp - n1 + 1));
-		strlcat(n2, arg[i], *szp);
-		strlcat(n2, cp + 3, *szp);
+			buf->sz += asz - 3;
+			n2 = mandoc_realloc(n1, buf->sz);
+			cp = n2 + (cp - n1);
+			n1 = n2;
 
-		cp = n2 + (cp - n1);
-		free(n1);
-		n1 = n2;
+			/*
+			 * When growing, make room
+			 * for the expanded argument.
+			 */
+
+			if (asz > 3)
+				memmove(cp + asz, cp + 3, rsz);
+		}
+
+		/* Copy the expanded argument, escaping quotes. */
+
+		n2 = cp;
+		for (ap = arg[i]; *ap != '\0'; ap++) {
+			if (*ap == '"') {
+				memcpy(n2, "\\(dq", 4);
+				n2 += 4;
+			} else
+				*n2++ = *ap;
+		}
 	}
 
 	/*
 	 * Replace the macro invocation
 	 * by the expanded macro.
 	 */
-	free(*bufp);
-	*bufp = n1;
-	if (0 == *szp)
-		*szp = strlen(*bufp) + 1;
 
-	return(*szp > 1 && '\n' == (*bufp)[(int)*szp - 2] ?
+	free(buf->buf);
+	buf->buf = n1;
+	*offs = 0;
+
+	return(buf->sz > 1 && buf->buf[buf->sz - 2] == '\n' ?
 	   ROFF_REPARSE : ROFF_APPEND);
 }
 
-static char *
+static size_t
 roff_getname(struct roff *r, char **cpp, int ln, int pos)
 {
 	char	 *name, *cp;
+	size_t	  namesz;
 
 	name = *cpp;
 	if ('\0' == *name)
-		return(name);
+		return(0);
 
-	/* Read until end of name. */
-	for (cp = name; '\0' != *cp && ' ' != *cp; cp++) {
+	/* Read until end of name and terminate it with NUL. */
+	for (cp = name; 1; cp++) {
+		if ('\0' == *cp || ' ' == *cp) {
+			namesz = cp - name;
+			break;
+		}
 		if ('\\' != *cp)
 			continue;
+		namesz = cp - name;
+		if ('{' == cp[1] || '}' == cp[1])
+			break;
 		cp++;
 		if ('\\' == *cp)
 			continue;
-		mandoc_msg(MANDOCERR_NAMESC, r->parse, ln, pos, NULL);
-		*cp = '\0';
-		name = cp;
+		mandoc_vmsg(MANDOCERR_NAMESC, r->parse, ln, pos,
+		    "%.*s", (int)(cp - name + 1), name);
+		mandoc_escape((const char **)&cp, NULL, NULL);
+		break;
 	}
 
-	/* Nil-terminate name. */
-	if ('\0' != *cp)
-		*(cp++) = '\0';
-
 	/* Read past spaces. */
 	while (' ' == *cp)
 		cp++;
 
 	*cpp = cp;
-	return(name);
+	return(namesz);
 }
 
 /*
  * Store *string into the user-defined string called *name.
- * In multiline mode, append to an existing entry and append '\n';
- * else replace the existing entry, if there is one.
  * To clear an existing entry, call with (*r, *name, NULL, 0).
+ * append == 0: replace mode
+ * append == 1: single-line append mode
+ * append == 2: multiline append mode, append '\n' after each call
  */
 static void
 roff_setstr(struct roff *r, const char *name, const char *string,
-	int multiline)
+	int append)
 {
 
 	roff_setstrn(&r->strtab, name, strlen(name), string,
-			string ? strlen(string) : 0, multiline);
+	    string ? strlen(string) : 0, append);
 }
 
 static void
 roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
-		const char *string, size_t stringsz, int multiline)
+		const char *string, size_t stringsz, int append)
 {
 	struct roffkv	*n;
 	char		*c;
@@ -1837,7 +2826,8 @@ roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
 	/* Search for an existing string with the same name. */
 	n = *r;
 
-	while (n && strcmp(name, n->key.p))
+	while (n && (namesz != n->key.sz ||
+			strncmp(n->key.p, name, namesz)))
 		n = n->next;
 
 	if (NULL == n) {
@@ -1849,8 +2839,7 @@ roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
 		n->val.sz = 0;
 		n->next = *r;
 		*r = n;
-	} else if (0 == multiline) {
-		/* In multiline mode, append; else replace. */
+	} else if (0 == append) {
 		free(n->val.p);
 		n->val.p = NULL;
 		n->val.sz = 0;
@@ -1863,7 +2852,7 @@ roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
 	 * One additional byte for the '\n' in multiline mode,
 	 * and one for the terminating '\0'.
 	 */
-	newch = stringsz + (multiline ? 2u : 1u);
+	newch = stringsz + (1 < append ? 2u : 1u);
 
 	if (NULL == n->val.p) {
 		n->val.p = mandoc_malloc(newch);
@@ -1890,7 +2879,7 @@ roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
 	}
 
 	/* Append terminating bytes. */
-	if (multiline)
+	if (1 < append)
 		*c++ = '\n';
 
 	*c = '\0';
@@ -1901,12 +2890,18 @@ static const char *
 roff_getstrn(const struct roff *r, const char *name, size_t len)
 {
 	const struct roffkv *n;
+	int i;
 
 	for (n = r->strtab; n; n = n->next)
-		if (0 == strncmp(name, n->key.p, len) && 
-				'\0' == n->key.p[(int)len])
+		if (0 == strncmp(name, n->key.p, len) &&
+		    '\0' == n->key.p[(int)len])
 			return(n->val.p);
 
+	for (i = 0; i < PREDEFS_MAX; i++)
+		if (0 == strncmp(name, predefs[i].name, len) &&
+				'\0' == predefs[i].name[(int)len])
+			return(predefs[i].str);
+
 	return(NULL);
 }
 
@@ -1926,14 +2921,14 @@ roff_freestr(struct roffkv *r)
 const struct tbl_span *
 roff_span(const struct roff *r)
 {
-	
+
 	return(r->tbl ? tbl_span(r->tbl) : NULL);
 }
 
 const struct eqn *
 roff_eqn(const struct roff *r)
 {
-	
+
 	return(r->last_eqn ? &r->last_eqn->eqn : NULL);
 }
 
@@ -1992,8 +2987,8 @@ roff_strdup(const struct roff *r, const char *p)
 			 * Append the match to the array and move
 			 * forward by its keysize.
 			 */
-			res = mandoc_realloc
-				(res, ssz + cp->val.sz + 1);
+			res = mandoc_realloc(res,
+			    ssz + cp->val.sz + 1);
 			memcpy(res + ssz, cp->val.p, cp->val.sz);
 			ssz += cp->val.sz;
 			p += (int)cp->key.sz;
@@ -2014,8 +3009,8 @@ roff_strdup(const struct roff *r, const char *p)
 			memcpy(res + ssz, pp, sz);
 			break;
 		}
-		/* 
-		 * We bail out on bad escapes. 
+		/*
+		 * We bail out on bad escapes.
 		 * No need to warn: we already did so when
 		 * roff_res() was called.
 		 */
@@ -2029,8 +3024,15 @@ roff_strdup(const struct roff *r, const char *p)
 	return(res);
 }
 
+int
+roff_getformat(const struct roff *r)
+{
+
+	return(r->format);
+}
+
 /*
- * Find out whether a line is a macro line or not.  
+ * Find out whether a line is a macro line or not.
  * If it is, adjust the current position and return one; if it isn't,
  * return zero and don't change the current position.
  * If the control character has been set with `.cc', then let that grain
diff --git a/usr/src/cmd/mandoc/st.c b/usr/src/cmd/mandoc/st.c
index 70c21a269e..172403ad18 100644
--- a/usr/src/cmd/mandoc/st.c
+++ b/usr/src/cmd/mandoc/st.c
@@ -1,4 +1,4 @@
-/*	$Id: st.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*	$Id: st.c,v 1.11 2014/08/10 23:54:41 schwarze Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  *
@@ -14,16 +14,13 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
-#include 
+#include 
+
 #include 
-#include 
 
 #include "mdoc.h"
-#include "mandoc.h"
 #include "libmdoc.h"
 
 #define LINE(x, y) \
diff --git a/usr/src/cmd/mandoc/st.in b/usr/src/cmd/mandoc/st.in
index c52ddab9ba..e70680f3ab 100644
--- a/usr/src/cmd/mandoc/st.in
+++ b/usr/src/cmd/mandoc/st.in
@@ -1,4 +1,4 @@
-/*	$Id: st.in,v 1.22 2013/12/25 14:09:32 schwarze Exp $ */
+/*	$Id: st.in,v 1.28 2015/02/17 20:37:17 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010 Kristaps Dzonsons 
  *
@@ -22,61 +22,56 @@
  * the formatted output string.
  *
  * Be sure to escape strings.
- * The non-breaking blanks prevent ending an output line right before 
+ * The non-breaking blanks prevent ending an output line right before
  * a number.  Groff prevent line breaks at the same places.
  *
  * REMEMBER TO ADD NEW STANDARDS TO MDOC.7!
  */
 
-LINE("-p1003.1-88",	"IEEE Std 1003.1-1988 (\\(lqPOSIX.1\\(rq)")
-LINE("-p1003.1-90",	"IEEE Std 1003.1-1990 (\\(lqPOSIX.1\\(rq)")
-LINE("-p1003.1-96",	"ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)")
-LINE("-p1003.1-2001",	"IEEE Std 1003.1-2001 (\\(lqPOSIX.1\\(rq)")
-LINE("-p1003.1-2004",	"IEEE Std 1003.1-2004 (\\(lqPOSIX.1\\(rq)")
-LINE("-p1003.1-2008",	"IEEE Std 1003.1-2008 (\\(lqPOSIX.1\\(rq)")
-LINE("-p1003.1",	"IEEE Std 1003.1 (\\(lqPOSIX.1\\(rq)")
-LINE("-p1003.1b",	"IEEE Std 1003.1b (\\(lqPOSIX.1b\\(rq)")
-LINE("-p1003.1b-93",	"IEEE Std 1003.1b-1993 (\\(lqPOSIX.1b\\(rq)")
-LINE("-p1003.1c-95",	"IEEE Std 1003.1c-1995 (\\(lqPOSIX.1c\\(rq)")
-LINE("-p1003.1d-99",	"IEEE Std 1003.1d-1999 (\\(lqPOSIX.1d\\(rq)")
-LINE("-p1003.1g-2000",	"IEEE Std 1003.1g-2000 (\\(lqPOSIX.1g\\(rq)")
-LINE("-p1003.1i-95",	"IEEE Std 1003.1i-1995 (\\(lqPOSIX.1i\\(rq)")
-LINE("-p1003.1j-2000",	"IEEE Std 1003.1j-2000 (\\(lqPOSIX.1j\\(rq)")
-LINE("-p1003.1q-2000",	"IEEE Std 1003.1q-2000 (\\(lqPOSIX.1q\\(rq)")
-LINE("-p1003.2",	"IEEE Std 1003.2 (\\(lqPOSIX.2\\(rq)")
-LINE("-p1003.2-92",	"IEEE Std 1003.2-1992 (\\(lqPOSIX.2\\(rq)")
-LINE("-p1003.2a-92",	"IEEE Std 1003.2a-1992 (\\(lqPOSIX.2\\(rq)")
-LINE("-p1387.2",	"IEEE Std 1387.2 (\\(lqPOSIX.7.2\\(rq)")
-LINE("-p1387.2-95",	"IEEE Std 1387.2-1995 (\\(lqPOSIX.7.2\\(rq)")
-LINE("-isoC",		"ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)")
-LINE("-isoC-90",	"ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)")
-LINE("-isoC-amd1",	"ISO/IEC 9899/AMD1:1995 (\\(lqISO\\~C90, Amendment 1\\(rq)")
-LINE("-isoC-tcor1",	"ISO/IEC 9899/TCOR1:1994 (\\(lqISO\\~C90, Technical Corrigendum 1\\(rq)")
-LINE("-isoC-tcor2",	"ISO/IEC 9899/TCOR2:1995 (\\(lqISO\\~C90, Technical Corrigendum 2\\(rq)")
-LINE("-isoC-99",	"ISO/IEC 9899:1999 (\\(lqISO\\~C99\\(rq)")
-LINE("-isoC-2011",	"ISO/IEC 9899:2011 (\\(lqISO\\~C11\\(rq)")
-LINE("-iso9945-1-90",	"ISO/IEC 9945-1:1990 (\\(lqPOSIX.1\\(rq)")
-LINE("-iso9945-1-96",	"ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)")
-LINE("-iso9945-2-93",	"ISO/IEC 9945-2:1993 (\\(lqPOSIX.2\\(rq)")
-LINE("-ansiC",		"ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)")
-LINE("-ansiC-89",	"ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)")
-LINE("-ansiC-99",	"ANSI/ISO/IEC 9899-1999 (\\(lqANSI\\~C99\\(rq)")
+LINE("-p1003.1-88",	"IEEE Std 1003.1-1988 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1-90",	"IEEE Std 1003.1-1990 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1-96",	"ISO/IEC 9945-1:1996 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1-2001",	"IEEE Std 1003.1-2001 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1-2004",	"IEEE Std 1003.1-2004 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1-2008",	"IEEE Std 1003.1-2008 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1-2013",	"IEEE Std 1003.1-2008/Cor 1-2013 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1",	"IEEE Std 1003.1 (\\(LqPOSIX.1\\(Rq)")
+LINE("-p1003.1b",	"IEEE Std 1003.1b (\\(LqPOSIX.1b\\(Rq)")
+LINE("-p1003.1b-93",	"IEEE Std 1003.1b-1993 (\\(LqPOSIX.1b\\(Rq)")
+LINE("-p1003.1c-95",	"IEEE Std 1003.1c-1995 (\\(LqPOSIX.1c\\(Rq)")
+LINE("-p1003.1g-2000",	"IEEE Std 1003.1g-2000 (\\(LqPOSIX.1g\\(Rq)")
+LINE("-p1003.1i-95",	"IEEE Std 1003.1i-1995 (\\(LqPOSIX.1i\\(Rq)")
+LINE("-p1003.2",	"IEEE Std 1003.2 (\\(LqPOSIX.2\\(Rq)")
+LINE("-p1003.2-92",	"IEEE Std 1003.2-1992 (\\(LqPOSIX.2\\(Rq)")
+LINE("-p1003.2a-92",	"IEEE Std 1003.2a-1992 (\\(LqPOSIX.2\\(Rq)")
+LINE("-isoC",		"ISO/IEC 9899:1990 (\\(LqISO\\~C90\\(Rq)")
+LINE("-isoC-90",	"ISO/IEC 9899:1990 (\\(LqISO\\~C90\\(Rq)")
+LINE("-isoC-amd1",	"ISO/IEC 9899/AMD1:1995 (\\(LqISO\\~C90, Amendment 1\\(Rq)")
+LINE("-isoC-tcor1",	"ISO/IEC 9899/TCOR1:1994 (\\(LqISO\\~C90, Technical Corrigendum 1\\(Rq)")
+LINE("-isoC-tcor2",	"ISO/IEC 9899/TCOR2:1995 (\\(LqISO\\~C90, Technical Corrigendum 2\\(Rq)")
+LINE("-isoC-99",	"ISO/IEC 9899:1999 (\\(LqISO\\~C99\\(Rq)")
+LINE("-isoC-2011",	"ISO/IEC 9899:2011 (\\(LqISO\\~C11\\(Rq)")
+LINE("-iso9945-1-90",	"ISO/IEC 9945-1:1990 (\\(LqPOSIX.1\\(Rq)")
+LINE("-iso9945-1-96",	"ISO/IEC 9945-1:1996 (\\(LqPOSIX.1\\(Rq)")
+LINE("-iso9945-2-93",	"ISO/IEC 9945-2:1993 (\\(LqPOSIX.2\\(Rq)")
+LINE("-ansiC",		"ANSI X3.159-1989 (\\(LqANSI\\~C89\\(Rq)")
+LINE("-ansiC-89",	"ANSI X3.159-1989 (\\(LqANSI\\~C89\\(Rq)")
 LINE("-ieee754",	"IEEE Std 754-1985")
 LINE("-iso8802-3",	"ISO 8802-3: 1989")
 LINE("-iso8601",	"ISO 8601")
-LINE("-ieee1275-94",	"IEEE Std 1275-1994 (\\(lqOpen Firmware\\(rq)")
-LINE("-xpg3",		"X/Open Portability Guide Issue\\~3 (\\(lqXPG3\\(rq)")
-LINE("-xpg4",		"X/Open Portability Guide Issue\\~4 (\\(lqXPG4\\(rq)")
-LINE("-xpg4.2",		"X/Open Portability Guide Issue\\~4, Version\\~2 (\\(lqXPG4.2\\(rq)")
-LINE("-xpg4.3",		"X/Open Portability Guide Issue\\~4, Version\\~3 (\\(lqXPG4.3\\(rq)")
-LINE("-xbd5",		"X/Open Base Definitions Issue\\~5 (\\(lqXBD5\\(rq)")
-LINE("-xcu5",		"X/Open Commands and Utilities Issue\\~5 (\\(lqXCU5\\(rq)")
-LINE("-xsh4.2",		"X/Open System Interfaces and Headers Issue\\~4, Version\\~2 (\\(lqXSH4.2\\(rq)")
-LINE("-xsh5",		"X/Open System Interfaces and Headers Issue\\~5 (\\(lqXSH5\\(rq)")
-LINE("-xns5",		"X/Open Networking Services Issue\\~5 (\\(lqXNS5\\(rq)")
-LINE("-xns5.2",		"X/Open Networking Services Issue\\~5.2 (\\(lqXNS5.2\\(rq)")
-LINE("-xns5.2d2.0",	"X/Open Networking Services Issue\\~5.2 Draft\\~2.0 (\\(lqXNS5.2D2.0\\(rq)")
-LINE("-xcurses4.2",	"X/Open Curses Issue\\~4, Version\\~2 (\\(lqXCURSES4.2\\(rq)")
-LINE("-susv2",		"Version\\~2 of the Single UNIX Specification (\\(lqSUSv2\\(rq)")
-LINE("-susv3",		"Version\\~3 of the Single UNIX Specification (\\(lqSUSv3\\(rq)")
-LINE("-svid4",		"System\\~V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)")
+LINE("-ieee1275-94",	"IEEE Std 1275-1994 (\\(LqOpen Firmware\\(Rq)")
+LINE("-xpg3",		"X/Open Portability Guide Issue\\~3 (\\(LqXPG3\\(Rq)")
+LINE("-xpg4",		"X/Open Portability Guide Issue\\~4 (\\(LqXPG4\\(Rq)")
+LINE("-xpg4.2",		"X/Open Portability Guide Issue\\~4, Version\\~2 (\\(LqXPG4.2\\(Rq)")
+LINE("-xbd5",		"X/Open Base Definitions Issue\\~5 (\\(LqXBD5\\(Rq)")
+LINE("-xcu5",		"X/Open Commands and Utilities Issue\\~5 (\\(LqXCU5\\(Rq)")
+LINE("-xsh4.2",		"X/Open System Interfaces and Headers Issue\\~4, Version\\~2 (\\(LqXSH4.2\\(Rq)")
+LINE("-xsh5",		"X/Open System Interfaces and Headers Issue\\~5 (\\(LqXSH5\\(Rq)")
+LINE("-xns5",		"X/Open Networking Services Issue\\~5 (\\(LqXNS5\\(Rq)")
+LINE("-xns5.2",		"X/Open Networking Services Issue\\~5.2 (\\(LqXNS5.2\\(Rq)")
+LINE("-xcurses4.2",	"X/Open Curses Issue\\~4, Version\\~2 (\\(LqXCURSES4.2\\(Rq)")
+LINE("-susv1",		"Version\\~1 of the Single UNIX Specification (\\(LqSUSv1\\(Rq)")
+LINE("-susv2",		"Version\\~2 of the Single UNIX Specification (\\(LqSUSv2\\(Rq)")
+LINE("-susv3",		"Version\\~3 of the Single UNIX Specification (\\(LqSUSv3\\(Rq)")
+LINE("-susv4",		"Version\\~4 of the Single UNIX Specification (\\(LqSUSv4\\(Rq)")
+LINE("-svid4",		"System\\~V Interface Definition, Fourth Edition (\\(LqSVID4\\(Rq)")
diff --git a/usr/src/cmd/mandoc/tbl.c b/usr/src/cmd/mandoc/tbl.c
index b244ac80ac..00ee466125 100644
--- a/usr/src/cmd/mandoc/tbl.c
+++ b/usr/src/cmd/mandoc/tbl.c
@@ -1,7 +1,7 @@
-/*	$Id: tbl.c,v 1.27 2013/05/31 22:08:09 schwarze Exp $ */
+/*	$Id: tbl.c,v 1.39 2015/01/30 17:32:16 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2011 Ingo Schwarze 
+ * Copyright (c) 2011, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -26,47 +26,64 @@
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmandoc.h"
 #include "libroff.h"
 
+
 enum rofferr
-tbl_read(struct tbl_node *tbl, int ln, const char *p, int offs)
+tbl_read(struct tbl_node *tbl, int ln, const char *p, int pos)
 {
-	int		 len;
 	const char	*cp;
-
-	cp = &p[offs];
-	len = (int)strlen(cp);
+	int		 active;
 
 	/*
-	 * If we're in the options section and we don't have a
-	 * terminating semicolon, assume we've moved directly into the
-	 * layout section.  No need to report a warning: this is,
-	 * apparently, standard behaviour.
+	 * In the options section, proceed to the layout section
+	 * after a semicolon, or right away if there is no semicolon.
+	 * Ignore semicolons in arguments.
 	 */
 
-	if (TBL_PART_OPTS == tbl->part && len)
-		if (';' != cp[len - 1])
-			tbl->part = TBL_PART_LAYOUT;
+	if (tbl->part == TBL_PART_OPTS) {
+		tbl->part = TBL_PART_LAYOUT;
+		active = 1;
+		for (cp = p + pos; *cp != '\0'; cp++) {
+			switch (*cp) {
+			case '(':
+				active = 0;
+				continue;
+			case ')':
+				active = 1;
+				continue;
+			case ';':
+				if (active)
+					break;
+				continue;
+			default:
+				continue;
+			}
+			break;
+		}
+		if (*cp == ';') {
+			tbl_option(tbl, ln, p, &pos);
+			if (p[pos] == '\0')
+				return(ROFF_IGN);
+		}
+	}
 
-	/* Now process each logical section of the table.  */
+	/* Process the other section types.  */
 
 	switch (tbl->part) {
-	case (TBL_PART_OPTS):
-		return(tbl_option(tbl, ln, p) ? ROFF_IGN : ROFF_ERR);
-	case (TBL_PART_LAYOUT):
-		return(tbl_layout(tbl, ln, p) ? ROFF_IGN : ROFF_ERR);
-	case (TBL_PART_CDATA):
-		return(tbl_cdata(tbl, ln, p) ? ROFF_TBL : ROFF_IGN);
+	case TBL_PART_LAYOUT:
+		tbl_layout(tbl, ln, p, pos);
+		return(ROFF_IGN);
+	case TBL_PART_CDATA:
+		return(tbl_cdata(tbl, ln, p, pos) ? ROFF_TBL : ROFF_IGN);
 	default:
 		break;
 	}
 
-	/*
-	 * This only returns zero if the line is empty, so we ignore it
-	 * and continue on.
-	 */
-	return(tbl_data(tbl, ln, p) ? ROFF_TBL : ROFF_IGN);
+	tbl_data(tbl, ln, p, pos);
+	return(ROFF_TBL);
 }
 
 struct tbl_node *
@@ -74,13 +91,12 @@ tbl_alloc(int pos, int line, struct mparse *parse)
 {
 	struct tbl_node	*tbl;
 
-	tbl = mandoc_calloc(1, sizeof(struct tbl_node));
+	tbl = mandoc_calloc(1, sizeof(*tbl));
 	tbl->line = line;
 	tbl->pos = pos;
 	tbl->parse = parse;
 	tbl->part = TBL_PART_OPTS;
 	tbl->opts.tab = '\t';
-	tbl->opts.linesize = 12;
 	tbl->opts.decimal = '.';
 	return(tbl);
 }
@@ -92,11 +108,10 @@ tbl_free(struct tbl_node *tbl)
 	struct tbl_cell	*cp;
 	struct tbl_span	*sp;
 	struct tbl_dat	*dp;
-	struct tbl_head	*hp;
 
-	while (NULL != (rp = tbl->first_row)) {
+	while ((rp = tbl->first_row) != NULL) {
 		tbl->first_row = rp->next;
-		while (rp->first) {
+		while (rp->first != NULL) {
 			cp = rp->first;
 			rp->first = cp->next;
 			free(cp);
@@ -104,40 +119,30 @@ tbl_free(struct tbl_node *tbl)
 		free(rp);
 	}
 
-	while (NULL != (sp = tbl->first_span)) {
+	while ((sp = tbl->first_span) != NULL) {
 		tbl->first_span = sp->next;
-		while (sp->first) {
+		while (sp->first != NULL) {
 			dp = sp->first;
 			sp->first = dp->next;
-			if (dp->string)
-				free(dp->string);
+			free(dp->string);
 			free(dp);
 		}
 		free(sp);
 	}
 
-	while (NULL != (hp = tbl->first_head)) {
-		tbl->first_head = hp->next;
-		free(hp);
-	}
-
 	free(tbl);
 }
 
 void
 tbl_restart(int line, int pos, struct tbl_node *tbl)
 {
-	if (TBL_PART_CDATA == tbl->part)
-		mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse, 
-				tbl->line, tbl->pos, NULL);
+	if (tbl->part == TBL_PART_CDATA)
+		mandoc_msg(MANDOCERR_TBLDATA_BLK, tbl->parse,
+		    line, pos, "T&");
 
 	tbl->part = TBL_PART_LAYOUT;
 	tbl->line = line;
 	tbl->pos = pos;
-
-	if (NULL == tbl->first_span || NULL == tbl->first_span->first)
-		mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse,
-				tbl->line, tbl->pos, NULL);
 }
 
 const struct tbl_span *
@@ -153,23 +158,26 @@ tbl_span(struct tbl_node *tbl)
 	return(span);
 }
 
-void
+int
 tbl_end(struct tbl_node **tblp)
 {
 	struct tbl_node	*tbl;
+	struct tbl_span *sp;
 
 	tbl = *tblp;
 	*tblp = NULL;
 
-	if (NULL == tbl->first_span || NULL == tbl->first_span->first)
-		mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse, 
-				tbl->line, tbl->pos, NULL);
-
-	if (tbl->last_span)
-		tbl->last_span->flags |= TBL_SPAN_LAST;
-
-	if (TBL_PART_CDATA == tbl->part)
-		mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse, 
-				tbl->line, tbl->pos, NULL);
+	if (tbl->part == TBL_PART_CDATA)
+		mandoc_msg(MANDOCERR_TBLDATA_BLK, tbl->parse,
+		    tbl->line, tbl->pos, "TE");
+
+	sp = tbl->first_span;
+	while (sp != NULL && sp->first == NULL)
+		sp = sp->next;
+	if (sp == NULL) {
+		mandoc_msg(MANDOCERR_TBLDATA_NONE, tbl->parse,
+		    tbl->line, tbl->pos, NULL);
+		return(0);
+	}
+	return(1);
 }
-
diff --git a/usr/src/cmd/mandoc/tbl_data.c b/usr/src/cmd/mandoc/tbl_data.c
index 7413aa2d83..e2be64eb81 100644
--- a/usr/src/cmd/mandoc/tbl_data.c
+++ b/usr/src/cmd/mandoc/tbl_data.c
@@ -1,7 +1,7 @@
-/*	$Id: tbl_data.c,v 1.27 2013/06/01 04:56:50 schwarze Exp $ */
+/*	$Id: tbl_data.c,v 1.39 2015/01/30 17:32:16 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2011 Ingo Schwarze 
+ * Copyright (c) 2011, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -26,34 +26,28 @@
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmandoc.h"
 #include "libroff.h"
 
-static	int		 data(struct tbl_node *, struct tbl_span *, 
+static	void		 getdata(struct tbl_node *, struct tbl_span *,
 				int, const char *, int *);
-static	struct tbl_span	*newspan(struct tbl_node *, int, 
+static	struct tbl_span	*newspan(struct tbl_node *, int,
 				struct tbl_row *);
 
-static int
-data(struct tbl_node *tbl, struct tbl_span *dp, 
+
+static void
+getdata(struct tbl_node *tbl, struct tbl_span *dp,
 		int ln, const char *p, int *pos)
 {
 	struct tbl_dat	*dat;
 	struct tbl_cell	*cp;
-	int		 sv, spans;
-
-	cp = NULL;
-	if (dp->last && dp->last->layout)
-		cp = dp->last->layout->next;
-	else if (NULL == dp->last)
-		cp = dp->layout->first;
+	int		 sv;
 
-	/* 
-	 * Skip over spanners, since
-	 * we want to match data with data layout cells in the header.
-	 */
+	/* Advance to the next layout cell, skipping spanners. */
 
-	while (cp && TBL_CELL_SPAN == cp->pos)
+	cp = dp->last == NULL ? dp->layout->first : dp->last->layout->next;
+	while (cp != NULL && cp->pos == TBL_CELL_SPAN)
 		cp = cp->next;
 
 	/*
@@ -61,34 +55,30 @@ data(struct tbl_node *tbl, struct tbl_span *dp,
 	 * cells.  This means that we have extra input.
 	 */
 
-	if (NULL == cp) {
-		mandoc_msg(MANDOCERR_TBLEXTRADAT, 
-				tbl->parse, ln, *pos, NULL);
+	if (cp == NULL) {
+		mandoc_msg(MANDOCERR_TBLDATA_EXTRA, tbl->parse,
+		    ln, *pos, p + *pos);
 		/* Skip to the end... */
 		while (p[*pos])
 			(*pos)++;
-		return(1);
+		return;
 	}
 
-	dat = mandoc_calloc(1, sizeof(struct tbl_dat));
+	dat = mandoc_calloc(1, sizeof(*dat));
 	dat->layout = cp;
 	dat->pos = TBL_DATA_NONE;
-
-	assert(TBL_CELL_SPAN != cp->pos);
-
-	for (spans = 0, cp = cp->next; cp; cp = cp->next)
-		if (TBL_CELL_SPAN == cp->pos)
-			spans++;
+	dat->spans = 0;
+	for (cp = cp->next; cp != NULL; cp = cp->next)
+		if (cp->pos == TBL_CELL_SPAN)
+			dat->spans++;
 		else
 			break;
-	
-	dat->spans = spans;
 
-	if (dp->last) {
+	if (dp->last == NULL)
+		dp->first = dat;
+	else
 		dp->last->next = dat;
-		dp->last = dat;
-	} else
-		dp->last = dp->first = dat;
+	dp->last = dat;
 
 	sv = *pos;
 	while (p[*pos] && p[*pos] != tbl->opts.tab)
@@ -100,16 +90,12 @@ data(struct tbl_node *tbl, struct tbl_span *dp,
 	 * until a standalone `T}', are included in our cell.
 	 */
 
-	if (*pos - sv == 2 && 'T' == p[sv] && '{' == p[sv + 1]) {
+	if (*pos - sv == 2 && p[sv] == 'T' && p[sv + 1] == '{') {
 		tbl->part = TBL_PART_CDATA;
-		return(1);
+		return;
 	}
 
-	assert(*pos - sv >= 0);
-
-	dat->string = mandoc_malloc((size_t)(*pos - sv + 1));
-	memcpy(dat->string, &p[sv], (size_t)(*pos - sv));
-	dat->string[*pos - sv] = '\0';
+	dat->string = mandoc_strndup(p + sv, *pos - sv);
 
 	if (p[*pos])
 		(*pos)++;
@@ -125,25 +111,19 @@ data(struct tbl_node *tbl, struct tbl_span *dp,
 	else
 		dat->pos = TBL_DATA_DATA;
 
-	if (TBL_CELL_HORIZ == dat->layout->pos ||
-			TBL_CELL_DHORIZ == dat->layout->pos ||
-			TBL_CELL_DOWN == dat->layout->pos)
-		if (TBL_DATA_DATA == dat->pos && '\0' != *dat->string)
-			mandoc_msg(MANDOCERR_TBLIGNDATA, 
-					tbl->parse, ln, sv, NULL);
-
-	return(1);
+	if ((dat->layout->pos == TBL_CELL_HORIZ ||
+	    dat->layout->pos == TBL_CELL_DHORIZ ||
+	    dat->layout->pos == TBL_CELL_DOWN) &&
+	    dat->pos == TBL_DATA_DATA && *dat->string != '\0')
+		mandoc_msg(MANDOCERR_TBLDATA_SPAN,
+		    tbl->parse, ln, sv, dat->string);
 }
 
-/* ARGSUSED */
 int
-tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
+tbl_cdata(struct tbl_node *tbl, int ln, const char *p, int pos)
 {
 	struct tbl_dat	*dat;
-	size_t	 	 sz;
-	int		 pos;
-
-	pos = 0;
+	size_t		 sz;
 
 	dat = tbl->last_span->last;
 
@@ -152,8 +132,9 @@ tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
 		if (p[pos] == tbl->opts.tab) {
 			tbl->part = TBL_PART_DATA;
 			pos++;
-			return(data(tbl, tbl->last_span, ln, p, &pos));
-		} else if ('\0' == p[pos]) {
+			getdata(tbl, tbl->last_span, ln, p, &pos);
+			return(1);
+		} else if (p[pos] == '\0') {
 			tbl->part = TBL_PART_DATA;
 			return(1);
 		}
@@ -163,17 +144,17 @@ tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
 
 	dat->pos = TBL_DATA_DATA;
 
-	if (dat->string) {
-		sz = strlen(p) + strlen(dat->string) + 2;
+	if (dat->string != NULL) {
+		sz = strlen(p + pos) + strlen(dat->string) + 2;
 		dat->string = mandoc_realloc(dat->string, sz);
-		strlcat(dat->string, " ", sz);
-		strlcat(dat->string, p, sz);
+		(void)strlcat(dat->string, " ", sz);
+		(void)strlcat(dat->string, p + pos, sz);
 	} else
-		dat->string = mandoc_strdup(p);
+		dat->string = mandoc_strdup(p + pos);
 
-	if (TBL_CELL_DOWN == dat->layout->pos) 
-		mandoc_msg(MANDOCERR_TBLIGNDATA, 
-				tbl->parse, ln, pos, NULL);
+	if (dat->layout->pos == TBL_CELL_DOWN)
+		mandoc_msg(MANDOCERR_TBLDATA_SPAN, tbl->parse,
+		    ln, pos, dat->string);
 
 	return(0);
 }
@@ -183,39 +164,29 @@ newspan(struct tbl_node *tbl, int line, struct tbl_row *rp)
 {
 	struct tbl_span	*dp;
 
-	dp = mandoc_calloc(1, sizeof(struct tbl_span));
+	dp = mandoc_calloc(1, sizeof(*dp));
 	dp->line = line;
 	dp->opts = &tbl->opts;
 	dp->layout = rp;
-	dp->head = tbl->first_head;
+	dp->prev = tbl->last_span;
 
-	if (tbl->last_span) {
-		tbl->last_span->next = dp;
-		tbl->last_span = dp;
-	} else {
-		tbl->last_span = tbl->first_span = dp;
+	if (dp->prev == NULL) {
+		tbl->first_span = dp;
 		tbl->current_span = NULL;
-		dp->flags |= TBL_SPAN_FIRST;
-	}
+	} else
+		dp->prev->next = dp;
+	tbl->last_span = dp;
 
 	return(dp);
 }
 
-int
-tbl_data(struct tbl_node *tbl, int ln, const char *p)
+void
+tbl_data(struct tbl_node *tbl, int ln, const char *p, int pos)
 {
 	struct tbl_span	*dp;
 	struct tbl_row	*rp;
-	int		 pos;
-
-	pos = 0;
 
-	if ('\0' == p[pos]) {
-		mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, pos, NULL);
-		return(0);
-	}
-
-	/* 
+	/*
 	 * Choose a layout row: take the one following the last parsed
 	 * span's.  If that doesn't exist, use the last parsed span's.
 	 * If there's no last parsed span, use the first row.  Lastly,
@@ -223,17 +194,17 @@ tbl_data(struct tbl_node *tbl, int ln, const char *p)
 	 * (it doesn't "consume" the layout).
 	 */
 
-	if (tbl->last_span) {
-		assert(tbl->last_span->layout);
+	if (tbl->last_span != NULL) {
 		if (tbl->last_span->pos == TBL_SPAN_DATA) {
 			for (rp = tbl->last_span->layout->next;
-					rp && rp->first; rp = rp->next) {
+			     rp != NULL && rp->first != NULL;
+			     rp = rp->next) {
 				switch (rp->first->pos) {
-				case (TBL_CELL_HORIZ):
+				case TBL_CELL_HORIZ:
 					dp = newspan(tbl, ln, rp);
 					dp->pos = TBL_SPAN_HORIZ;
 					continue;
-				case (TBL_CELL_DHORIZ):
+				case TBL_CELL_DHORIZ:
 					dp = newspan(tbl, ln, rp);
 					dp->pos = TBL_SPAN_DHORIZ;
 					continue;
@@ -245,7 +216,7 @@ tbl_data(struct tbl_node *tbl, int ln, const char *p)
 		} else
 			rp = tbl->last_span->layout;
 
-		if (NULL == rp)
+		if (rp == NULL)
 			rp = tbl->last_span->layout;
 	} else
 		rp = tbl->first_row;
@@ -256,19 +227,14 @@ tbl_data(struct tbl_node *tbl, int ln, const char *p)
 
 	if ( ! strcmp(p, "_")) {
 		dp->pos = TBL_SPAN_HORIZ;
-		return(1);
+		return;
 	} else if ( ! strcmp(p, "=")) {
 		dp->pos = TBL_SPAN_DHORIZ;
-		return(1);
+		return;
 	}
 
 	dp->pos = TBL_SPAN_DATA;
 
-	/* This returns 0 when TBL_PART_CDATA is entered. */
-
-	while ('\0' != p[pos])
-		if ( ! data(tbl, dp, ln, p, &pos))
-			return(0);
-
-	return(1);
+	while (p[pos] != '\0')
+		getdata(tbl, dp, ln, p, &pos);
 }
diff --git a/usr/src/cmd/mandoc/tbl_html.c b/usr/src/cmd/mandoc/tbl_html.c
index 6b8ced716b..e7940381d2 100644
--- a/usr/src/cmd/mandoc/tbl_html.c
+++ b/usr/src/cmd/mandoc/tbl_html.c
@@ -1,4 +1,4 @@
-/*	$Id: tbl_html.c,v 1.10 2012/05/27 17:54:54 schwarze Exp $ */
+/*	$Id: tbl_html.c,v 1.16 2015/01/30 17:32:16 schwarze Exp $ */
 /*
  * Copyright (c) 2011 Kristaps Dzonsons 
  *
@@ -14,9 +14,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -31,15 +31,14 @@ static	void	 html_tblopen(struct html *, const struct tbl_span *);
 static	size_t	 html_tbl_len(size_t, void *);
 static	size_t	 html_tbl_strlen(const char *, void *);
 
-/* ARGSUSED */
+
 static size_t
 html_tbl_len(size_t sz, void *arg)
 {
-	
+
 	return(sz);
 }
 
-/* ARGSUSED */
 static size_t
 html_tbl_strlen(const char *p, void *arg)
 {
@@ -50,24 +49,24 @@ html_tbl_strlen(const char *p, void *arg)
 static void
 html_tblopen(struct html *h, const struct tbl_span *sp)
 {
-	const struct tbl_head *hp;
 	struct htmlpair	 tag;
 	struct roffsu	 su;
 	struct roffcol	*col;
+	int		 ic;
 
-	if (TBL_SPAN_FIRST & sp->flags) {
+	if (h->tbl.cols == NULL) {
 		h->tbl.len = html_tbl_len;
 		h->tbl.slen = html_tbl_strlen;
-		tblcalc(&h->tbl, sp);
+		tblcalc(&h->tbl, sp, 0);
 	}
 
 	assert(NULL == h->tblt);
 	PAIR_CLASS_INIT(&tag, "tbl");
 	h->tblt = print_otag(h, TAG_TABLE, 1, &tag);
 
-	for (hp = sp->head; hp; hp = hp->next) {
+	for (ic = 0; ic < sp->opts->cols; ic++) {
 		bufinit(h);
-		col = &h->tbl.cols[hp->ident];
+		col = h->tbl.cols + ic;
 		SCALE_HS_INIT(&su, col->width);
 		bufcat_su(h, "width", &su);
 		PAIR_STYLE_INIT(&tag, h);
@@ -89,14 +88,14 @@ print_tblclose(struct html *h)
 void
 print_tbl(struct html *h, const struct tbl_span *sp)
 {
-	const struct tbl_head *hp;
 	const struct tbl_dat *dp;
 	struct htmlpair	 tag;
 	struct tag	*tt;
+	int		 ic;
 
 	/* Inhibit printing of spaces: we do padding ourselves. */
 
-	if (NULL == h->tblt)
+	if (h->tblt == NULL)
 		html_tblopen(h, sp);
 
 	assert(h->tblt);
@@ -107,22 +106,22 @@ print_tbl(struct html *h, const struct tbl_span *sp)
 	tt = print_otag(h, TAG_TR, 0, NULL);
 
 	switch (sp->pos) {
-	case (TBL_SPAN_HORIZ):
+	case TBL_SPAN_HORIZ:
 		/* FALLTHROUGH */
-	case (TBL_SPAN_DHORIZ):
+	case TBL_SPAN_DHORIZ:
 		PAIR_INIT(&tag, ATTR_COLSPAN, "0");
 		print_otag(h, TAG_TD, 1, &tag);
 		break;
 	default:
 		dp = sp->first;
-		for (hp = sp->head; hp; hp = hp->next) {
+		for (ic = 0; ic < sp->opts->cols; ic++) {
 			print_stagq(h, tt);
 			print_otag(h, TAG_TD, 0, NULL);
 
-			if (NULL == dp)
-				break;
-			if (TBL_CELL_DOWN != dp->layout->pos)
-				if (dp->string)
+			if (dp == NULL || dp->layout->col > ic)
+				continue;
+			if (dp->layout->pos != TBL_CELL_DOWN)
+				if (dp->string != NULL)
 					print_text(h, dp->string);
 			dp = dp->next;
 		}
@@ -133,7 +132,7 @@ print_tbl(struct html *h, const struct tbl_span *sp)
 
 	h->flags &= ~HTML_NONOSPACE;
 
-	if (TBL_SPAN_LAST & sp->flags) {
+	if (sp->next == NULL) {
 		assert(h->tbl.cols);
 		free(h->tbl.cols);
 		h->tbl.cols = NULL;
diff --git a/usr/src/cmd/mandoc/tbl_layout.c b/usr/src/cmd/mandoc/tbl_layout.c
index 6cce977fa2..ed9acc9c0a 100644
--- a/usr/src/cmd/mandoc/tbl_layout.c
+++ b/usr/src/cmd/mandoc/tbl_layout.c
@@ -1,7 +1,7 @@
-/*	$Id: tbl_layout.c,v 1.23 2012/05/27 17:54:54 schwarze Exp $ */
+/*	$Id: tbl_layout.c,v 1.38 2015/02/10 11:03:13 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2012 Ingo Schwarze 
+ * Copyright (c) 2012, 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,17 +15,17 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
-#include 
+#include 
+
 #include 
 #include 
 #include 
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmandoc.h"
 #include "libroff.h"
 
@@ -34,15 +34,7 @@ struct	tbl_phrase {
 	enum tbl_cellt	 key;
 };
 
-/*
- * FIXME: we can make this parse a lot nicer by, when an error is
- * encountered in a layout key, bailing to the next key (i.e. to the
- * next whitespace then continuing).
- */
-
-#define	KEYS_MAX	 11
-
-static	const struct tbl_phrase keys[KEYS_MAX] = {
+static	const struct tbl_phrase keys[] = {
 	{ 'c',		 TBL_CELL_CENTRE },
 	{ 'r',		 TBL_CELL_RIGHT },
 	{ 'l',		 TBL_CELL_LEFT },
@@ -55,55 +47,30 @@ static	const struct tbl_phrase keys[KEYS_MAX] = {
 	{ '=',		 TBL_CELL_DHORIZ }
 };
 
-static	int		 mods(struct tbl_node *, struct tbl_cell *, 
+#define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
+
+static	void		 mods(struct tbl_node *, struct tbl_cell *,
 				int, const char *, int *);
-static	int		 cell(struct tbl_node *, struct tbl_row *, 
+static	void		 cell(struct tbl_node *, struct tbl_row *,
 				int, const char *, int *);
-static	void		 row(struct tbl_node *, int, const char *, int *);
 static	struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
-				enum tbl_cellt, int vert);
+				enum tbl_cellt);
 
-static int
-mods(struct tbl_node *tbl, struct tbl_cell *cp, 
+
+static void
+mods(struct tbl_node *tbl, struct tbl_cell *cp,
 		int ln, const char *p, int *pos)
 {
-	char		 buf[5];
-	int		 i;
+	char		*endptr;
 
-	/* Not all types accept modifiers. */
+mod:
+	while (p[*pos] == ' ' || p[*pos] == '\t')
+		(*pos)++;
 
-	switch (cp->pos) {
-	case (TBL_CELL_DOWN):
-		/* FALLTHROUGH */
-	case (TBL_CELL_HORIZ):
-		/* FALLTHROUGH */
-	case (TBL_CELL_DHORIZ):
-		return(1);
-	default:
-		break;
-	}
+	/* Row delimiters and cell specifiers end modifier lists. */
 
-mod:
-	/* 
-	 * XXX: since, at least for now, modifiers are non-conflicting
-	 * (are separable by value, regardless of position), we let
-	 * modifiers come in any order.  The existing tbl doesn't let
-	 * this happen.
-	 */
-	switch (p[*pos]) {
-	case ('\0'):
-		/* FALLTHROUGH */
-	case (' '):
-		/* FALLTHROUGH */
-	case ('\t'):
-		/* FALLTHROUGH */
-	case (','):
-		/* FALLTHROUGH */
-	case ('.'):
-		return(1);
-	default:
-		break;
-	}
+	if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
+		return;
 
 	/* Throw away parenthesised expression. */
 
@@ -115,276 +82,277 @@ mod:
 			(*pos)++;
 			goto mod;
 		}
-		mandoc_msg(MANDOCERR_TBLLAYOUT, 
-				tbl->parse, ln, *pos, NULL);
-		return(0);
+		mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, tbl->parse,
+		    ln, *pos, NULL);
+		return;
 	}
 
 	/* Parse numerical spacing from modifier string. */
 
 	if (isdigit((unsigned char)p[*pos])) {
-		for (i = 0; i < 4; i++) {
-			if ( ! isdigit((unsigned char)p[*pos + i]))
-				break;
-			buf[i] = p[*pos + i];
-		}
-		buf[i] = '\0';
-
-		/* No greater than 4 digits. */
-
-		if (4 == i) {
-			mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
-					ln, *pos, NULL);
-			return(0);
-		}
-
-		*pos += i;
-		cp->spacing = (size_t)atoi(buf);
-
+		cp->spacing = strtoull(p + *pos, &endptr, 10);
+		*pos = endptr - p;
 		goto mod;
-		/* NOTREACHED */
-	} 
-
-	/* TODO: GNU has many more extensions. */
+	}
 
 	switch (tolower((unsigned char)p[(*pos)++])) {
-	case ('z'):
-		cp->flags |= TBL_CELL_WIGN;
+	case 'b':
+		cp->flags |= TBL_CELL_BOLD;
 		goto mod;
-	case ('u'):
-		cp->flags |= TBL_CELL_UP;
+	case 'd':
+		cp->flags |= TBL_CELL_BALIGN;
 		goto mod;
-	case ('e'):
+	case 'e':
 		cp->flags |= TBL_CELL_EQUAL;
 		goto mod;
-	case ('t'):
+	case 'f':
+		break;
+	case 'i':
+		cp->flags |= TBL_CELL_ITALIC;
+		goto mod;
+	case 'm':
+		mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, tbl->parse,
+		    ln, *pos, "m");
+		goto mod;
+	case 'p':
+		/* FALLTHROUGH */
+	case 'v':
+		if (p[*pos] == '-' || p[*pos] == '+')
+			(*pos)++;
+		while (isdigit((unsigned char)p[*pos]))
+			(*pos)++;
+		goto mod;
+	case 't':
 		cp->flags |= TBL_CELL_TALIGN;
 		goto mod;
-	case ('d'):
-		cp->flags |= TBL_CELL_BALIGN;
+	case 'u':
+		cp->flags |= TBL_CELL_UP;
 		goto mod;
-	case ('w'):  /* XXX for now, ignore minimal column width */
+	case 'w':  /* XXX for now, ignore minimal column width */
+		goto mod;
+	case 'x':
+		cp->flags |= TBL_CELL_WMAX;
+		goto mod;
+	case 'z':
+		cp->flags |= TBL_CELL_WIGN;
+		goto mod;
+	case '|':
+		if (cp->vert < 2)
+			cp->vert++;
+		else
+			mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
+			    tbl->parse, ln, *pos - 1, NULL);
 		goto mod;
-	case ('f'):
-		break;
-	case ('r'):
-		/* FALLTHROUGH */
-	case ('b'):
-		/* FALLTHROUGH */
-	case ('i'):
-		(*pos)--;
-		break;
 	default:
-		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
-				ln, *pos - 1, NULL);
-		return(0);
+		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
+		    ln, *pos - 1, "%c", p[*pos - 1]);
+		goto mod;
 	}
 
-	switch (tolower((unsigned char)p[(*pos)++])) {
-	case ('3'):
+	/* Ignore parenthised font names for now. */
+
+	if (p[*pos] == '(')
+		goto mod;
+
+	/* Support only one-character font-names for now. */
+
+	if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
+		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
+		    ln, *pos, "TS %s", p + *pos - 1);
+		if (p[*pos] != '\0')
+			(*pos)++;
+		if (p[*pos] != '\0')
+			(*pos)++;
+		goto mod;
+	}
+
+	switch (p[(*pos)++]) {
+	case '3':
 		/* FALLTHROUGH */
-	case ('b'):
+	case 'B':
 		cp->flags |= TBL_CELL_BOLD;
 		goto mod;
-	case ('2'):
+	case '2':
 		/* FALLTHROUGH */
-	case ('i'):
+	case 'I':
 		cp->flags |= TBL_CELL_ITALIC;
 		goto mod;
-	case ('1'):
+	case '1':
 		/* FALLTHROUGH */
-	case ('r'):
+	case 'R':
 		goto mod;
 	default:
-		break;
+		mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse,
+		    ln, *pos - 1, "TS f%c", p[*pos - 1]);
+		goto mod;
 	}
-
-	mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
-			ln, *pos - 1, NULL);
-	return(0);
 }
 
-static int
-cell(struct tbl_node *tbl, struct tbl_row *rp, 
+static void
+cell(struct tbl_node *tbl, struct tbl_row *rp,
 		int ln, const char *p, int *pos)
 {
-	int		 vert, i;
+	int		 i;
 	enum tbl_cellt	 c;
 
-	/* Handle vertical lines. */
+	/* Handle leading vertical lines */
+
+	while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
+		if (p[*pos] == '|') {
+			if (rp->vert < 2)
+				rp->vert++;
+			else
+				mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
+				    tbl->parse, ln, *pos, NULL);
+		}
+		(*pos)++;
+	}
 
-	for (vert = 0; '|' == p[*pos]; ++*pos)
-		vert++;
-	while (' ' == p[*pos])
+again:
+	while (p[*pos] == ' ' || p[*pos] == '\t')
 		(*pos)++;
 
+	if (p[*pos] == '.' || p[*pos] == '\0')
+		return;
+
 	/* Parse the column position (`c', `l', `r', ...). */
 
 	for (i = 0; i < KEYS_MAX; i++)
 		if (tolower((unsigned char)p[*pos]) == keys[i].name)
 			break;
 
-	if (KEYS_MAX == i) {
-		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 
-				ln, *pos, NULL);
-		return(0);
+	if (i == KEYS_MAX) {
+		mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse,
+		    ln, *pos, "%c", p[*pos]);
+		(*pos)++;
+		goto again;
 	}
-
 	c = keys[i].key;
 
-	/*
-	 * If a span cell is found first, raise a warning and abort the
-	 * parse.  If a span cell is found and the last layout element
-	 * isn't a "normal" layout, bail.
-	 *
-	 * FIXME: recover from this somehow?
-	 */
-
-	if (TBL_CELL_SPAN == c) {
-		if (NULL == rp->first) {
-			mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
-					ln, *pos, NULL);
-			return(0);
-		} else if (rp->last)
-			switch (rp->last->pos) {
-			case (TBL_CELL_HORIZ):
-			case (TBL_CELL_DHORIZ):
-				mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
-						ln, *pos, NULL);
-				return(0);
-			default:
-				break;
-			}
-	}
+	/* Special cases of spanners. */
 
-	/*
-	 * If a vertical spanner is found, we may not be in the first
-	 * row.
-	 */
-
-	if (TBL_CELL_DOWN == c && rp == tbl->first_row) {
-		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL);
-		return(0);
-	}
+	if (c == TBL_CELL_SPAN) {
+		if (rp->last == NULL)
+			mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN,
+			    tbl->parse, ln, *pos, NULL);
+		else if (rp->last->pos == TBL_CELL_HORIZ ||
+		    rp->last->pos == TBL_CELL_DHORIZ)
+			c = rp->last->pos;
+	} else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
+		mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN,
+		    tbl->parse, ln, *pos, NULL);
 
 	(*pos)++;
 
-	/* Disallow adjacent spacers. */
-
-	if (vert > 2) {
-		mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL);
-		return(0);
-	}
-
 	/* Allocate cell then parse its modifiers. */
 
-	return(mods(tbl, cell_alloc(tbl, rp, c, vert), ln, p, pos));
+	mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
 }
 
-
-static void
-row(struct tbl_node *tbl, int ln, const char *p, int *pos)
+void
+tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
 {
 	struct tbl_row	*rp;
 
-row:	/*
-	 * EBNF describing this section:
-	 *
-	 * row		::= row_list [:space:]* [.]?[\n]
-	 * row_list	::= [:space:]* row_elem row_tail
-	 * row_tail	::= [:space:]*[,] row_list |
-	 *                  epsilon
-	 * row_elem	::= [\t\ ]*[:alpha:]+
-	 */
-
-	rp = mandoc_calloc(1, sizeof(struct tbl_row));
-	if (tbl->last_row)
-		tbl->last_row->next = rp;
-	else
-		tbl->first_row = rp;
-	tbl->last_row = rp;
-
-cell:
-	while (isspace((unsigned char)p[*pos]))
-		(*pos)++;
+	rp = NULL;
+	for (;;) {
+		/* Skip whitespace before and after each cell. */
+
+		while (p[pos] == ' ' || p[pos] == '\t')
+			pos++;
+
+		switch (p[pos]) {
+		case ',':  /* Next row on this input line. */
+			pos++;
+			rp = NULL;
+			continue;
+		case '\0':  /* Next row on next input line. */
+			return;
+		case '.':  /* End of layout. */
+			pos++;
+			tbl->part = TBL_PART_DATA;
+
+			/*
+			 * When the layout is completely empty,
+			 * default to one left-justified column.
+			 */
+
+			if (tbl->first_row == NULL) {
+				tbl->first_row = tbl->last_row =
+				    mandoc_calloc(1, sizeof(*rp));
+			}
+			if (tbl->first_row->first == NULL) {
+				mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
+				    tbl->parse, ln, pos, NULL);
+				cell_alloc(tbl, tbl->first_row,
+				    TBL_CELL_LEFT);
+				return;
+			}
 
-	/* Safely exit layout context. */
+			/*
+			 * Search for the widest line
+			 * along the left and right margins.
+			 */
+
+			for (rp = tbl->first_row; rp; rp = rp->next) {
+				if (tbl->opts.lvert < rp->vert)
+					tbl->opts.lvert = rp->vert;
+				if (rp->last != NULL &&
+				    rp->last->col + 1 == tbl->opts.cols &&
+				    tbl->opts.rvert < rp->last->vert)
+					tbl->opts.rvert = rp->last->vert;
+
+				/* If the last line is empty, drop it. */
+
+				if (rp->next != NULL &&
+				    rp->next->first == NULL) {
+					free(rp->next);
+					rp->next = NULL;
+				}
+			}
+			return;
+		default:  /* Cell. */
+			break;
+		}
 
-	if ('.' == p[*pos]) {
-		tbl->part = TBL_PART_DATA;
-		if (NULL == tbl->first_row) 
-			mandoc_msg(MANDOCERR_TBLNOLAYOUT, tbl->parse, 
-					ln, *pos, NULL);
-		(*pos)++;
-		return;
+		/*
+		 * If the last line had at least one cell,
+		 * start a new one; otherwise, continue it.
+		 */
+
+		if (rp == NULL) {
+			if (tbl->last_row == NULL ||
+			    tbl->last_row->first != NULL) {
+				rp = mandoc_calloc(1, sizeof(*rp));
+				if (tbl->last_row)
+					tbl->last_row->next = rp;
+				else
+					tbl->first_row = rp;
+				tbl->last_row = rp;
+			} else
+				rp = tbl->last_row;
+		}
+		cell(tbl, rp, ln, p, &pos);
 	}
-
-	/* End (and possibly restart) a row. */
-
-	if (',' == p[*pos]) {
-		(*pos)++;
-		goto row;
-	} else if ('\0' == p[*pos])
-		return;
-
-	if ( ! cell(tbl, rp, ln, p, pos))
-		return;
-
-	goto cell;
-	/* NOTREACHED */
-}
-
-int
-tbl_layout(struct tbl_node *tbl, int ln, const char *p)
-{
-	int		 pos;
-
-	pos = 0;
-	row(tbl, ln, p, &pos);
-
-	/* Always succeed. */
-	return(1);
 }
 
 static struct tbl_cell *
-cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos,
-		int vert)
+cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
 {
 	struct tbl_cell	*p, *pp;
-	struct tbl_head	*h, *hp;
 
-	p = mandoc_calloc(1, sizeof(struct tbl_cell));
+	p = mandoc_calloc(1, sizeof(*p));
+	p->pos = pos;
 
-	if (NULL != (pp = rp->last)) {
+	if ((pp = rp->last) != NULL) {
 		pp->next = p;
-		h = pp->head->next;
-	} else {
+		p->col = pp->col + 1;
+	} else
 		rp->first = p;
-		h = tbl->first_head;
-	}
 	rp->last = p;
 
-	p->pos = pos;
-	p->vert = vert;
-
-	/* Re-use header. */
-
-	if (h) {
-		p->head = h;
-		return(p);
-	}
-
-	hp = mandoc_calloc(1, sizeof(struct tbl_head));
-	hp->ident = tbl->opts.cols++;
-	hp->vert = vert;
-
-	if (tbl->last_head) {
-		hp->prev = tbl->last_head;
-		tbl->last_head->next = hp;
-	} else
-		tbl->first_head = hp;
-	tbl->last_head = hp;
+	if (tbl->opts.cols <= p->col)
+		tbl->opts.cols = p->col + 1;
 
-	p->head = hp;
 	return(p);
 }
diff --git a/usr/src/cmd/mandoc/tbl_opts.c b/usr/src/cmd/mandoc/tbl_opts.c
index 5bd67f80ee..c012a3c885 100644
--- a/usr/src/cmd/mandoc/tbl_opts.c
+++ b/usr/src/cmd/mandoc/tbl_opts.c
@@ -1,6 +1,7 @@
-/*	$Id: tbl_opts.c,v 1.12 2011/09/18 14:14:15 schwarze Exp $ */
+/*	$Id: tbl_opts.c,v 1.20 2015/01/28 17:32:07 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,9 +15,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -27,244 +28,147 @@
 #include "libmandoc.h"
 #include "libroff.h"
 
-enum	tbl_ident {
-	KEY_CENTRE = 0,
-	KEY_DELIM,
-	KEY_EXPAND,
-	KEY_BOX,
-	KEY_DBOX,
-	KEY_ALLBOX,
-	KEY_TAB,
-	KEY_LINESIZE,
-	KEY_NOKEEP,
-	KEY_DPOINT,
-	KEY_NOSPACE,
-	KEY_FRAME,
-	KEY_DFRAME,
-	KEY_MAX
-};
+#define	KEY_DPOINT	0
+#define	KEY_DELIM	1
+#define	KEY_LINESIZE	2
+#define	KEY_TAB		3
 
 struct	tbl_phrase {
 	const char	*name;
 	int		 key;
-	enum tbl_ident	 ident;
 };
 
-/* Handle Commonwealth/American spellings. */
-#define	KEY_MAXKEYS	 14
+static	const struct tbl_phrase keys[] = {
+	{"decimalpoint", 0},
+	{"delim",	 0},
+	{"linesize",	 0},
+	{"tab",		 0},
+	{"allbox",	 TBL_OPT_ALLBOX | TBL_OPT_BOX},
+	{"box",		 TBL_OPT_BOX},
+	{"frame",	 TBL_OPT_BOX},
+	{"center",	 TBL_OPT_CENTRE},
+	{"centre",	 TBL_OPT_CENTRE},
+	{"doublebox",	 TBL_OPT_DBOX},
+	{"doubleframe",  TBL_OPT_DBOX},
+	{"expand",	 TBL_OPT_EXPAND},
+	{"nokeep",	 TBL_OPT_NOKEEP},
+	{"nospaces",	 TBL_OPT_NOSPACE},
+	{"nowarn",	 TBL_OPT_NOWARN},
+};
 
-/* Maximum length of key name string. */
-#define	KEY_MAXNAME	 13
+#define KEY_MAXKEYS ((int)(sizeof(keys)/sizeof(keys[0])))
 
-/* Maximum length of key number size. */
-#define	KEY_MAXNUMSZ	 10
+static	void	 arg(struct tbl_node *, int, const char *, int *, int);
 
-static	const struct tbl_phrase keys[KEY_MAXKEYS] = {
-	{ "center",	 TBL_OPT_CENTRE,	KEY_CENTRE},
-	{ "centre",	 TBL_OPT_CENTRE,	KEY_CENTRE},
-	{ "delim",	 0,	       		KEY_DELIM},
-	{ "expand",	 TBL_OPT_EXPAND,	KEY_EXPAND},
-	{ "box",	 TBL_OPT_BOX,   	KEY_BOX},
-	{ "doublebox",	 TBL_OPT_DBOX,  	KEY_DBOX},
-	{ "allbox",	 TBL_OPT_ALLBOX,	KEY_ALLBOX},
-	{ "frame",	 TBL_OPT_BOX,		KEY_FRAME},
-	{ "doubleframe", TBL_OPT_DBOX,		KEY_DFRAME},
-	{ "tab",	 0,			KEY_TAB},
-	{ "linesize",	 0,			KEY_LINESIZE},
-	{ "nokeep",	 TBL_OPT_NOKEEP,	KEY_NOKEEP},
-	{ "decimalpoint", 0,			KEY_DPOINT},
-	{ "nospaces",	 TBL_OPT_NOSPACE,	KEY_NOSPACE},
-};
 
-static	int		 arg(struct tbl_node *, int, 
-				const char *, int *, enum tbl_ident);
-static	void		 opt(struct tbl_node *, int, 
-				const char *, int *);
-
-static int
-arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key)
+static void
+arg(struct tbl_node *tbl, int ln, const char *p, int *pos, int key)
 {
-	int		 i;
-	char		 buf[KEY_MAXNUMSZ];
+	int		 len, want;
 
-	while (isspace((unsigned char)p[*pos]))
+	while (p[*pos] == ' ' || p[*pos] == '\t')
 		(*pos)++;
 
-	/* Arguments always begin with a parenthesis. */
+	/* Arguments are enclosed in parentheses. */
 
-	if ('(' != p[*pos]) {
-		mandoc_msg(MANDOCERR_TBL, tbl->parse, 
-				ln, *pos, NULL);
-		return(0);
+	len = 0;
+	if (p[*pos] == '(') {
+		(*pos)++;
+		while (p[*pos + len] != ')')
+			len++;
 	}
 
-	(*pos)++;
-
-	/*
-	 * The arguments can be ANY value, so we can't just stop at the
-	 * next close parenthesis (the argument can be a closed
-	 * parenthesis itself).
-	 */
-
 	switch (key) {
-	case (KEY_DELIM):
-		if ('\0' == p[(*pos)++]) {
-			mandoc_msg(MANDOCERR_TBL, tbl->parse,
-					ln, *pos - 1, NULL);
-			return(0);
-		} 
-
-		if ('\0' == p[(*pos)++]) {
-			mandoc_msg(MANDOCERR_TBL, tbl->parse,
-					ln, *pos - 1, NULL);
-			return(0);
-		} 
+	case KEY_DELIM:
+		mandoc_vmsg(MANDOCERR_TBLOPT_EQN, tbl->parse,
+		    ln, *pos, "%.*s", len, p + *pos);
+		want = 2;
+		break;
+	case KEY_TAB:
+		want = 1;
+		if (len == want)
+			tbl->opts.tab = p[*pos];
+		break;
+	case KEY_LINESIZE:
+		want = 0;
+		break;
+	case KEY_DPOINT:
+		want = 1;
+		if (len == want)
+			tbl->opts.decimal = p[*pos];
 		break;
-	case (KEY_TAB):
-		if ('\0' != (tbl->opts.tab = p[(*pos)++]))
-			break;
-
-		mandoc_msg(MANDOCERR_TBL, tbl->parse,
-				ln, *pos - 1, NULL);
-		return(0);
-	case (KEY_LINESIZE):
-		for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) {
-			buf[i] = p[*pos];
-			if ( ! isdigit((unsigned char)buf[i]))
-				break;
-		}
-
-		if (i < KEY_MAXNUMSZ) {
-			buf[i] = '\0';
-			tbl->opts.linesize = atoi(buf);
-			break;
-		}
-
-		mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
-		return(0);
-	case (KEY_DPOINT):
-		if ('\0' != (tbl->opts.decimal = p[(*pos)++]))
-			break;
-
-		mandoc_msg(MANDOCERR_TBL, tbl->parse, 
-				ln, *pos - 1, NULL);
-		return(0);
 	default:
 		abort();
 		/* NOTREACHED */
 	}
 
-	/* End with a close parenthesis. */
+	if (len == 0)
+		mandoc_msg(MANDOCERR_TBLOPT_NOARG,
+		    tbl->parse, ln, *pos, keys[key].name);
+	else if (want && len != want)
+		mandoc_vmsg(MANDOCERR_TBLOPT_ARGSZ,
+		    tbl->parse, ln, *pos, "%s want %d have %d",
+		    keys[key].name, want, len);
 
-	if (')' == p[(*pos)++])
-		return(1);
-
-	mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos - 1, NULL);
-	return(0);
+	*pos += len;
+	if (p[*pos] == ')')
+		(*pos)++;
 }
 
-static void
-opt(struct tbl_node *tbl, int ln, const char *p, int *pos)
+/*
+ * Parse one line of options up to the semicolon.
+ * Each option can be preceded by blanks and/or commas,
+ * and some options are followed by arguments.
+ */
+void
+tbl_option(struct tbl_node *tbl, int ln, const char *p, int *offs)
 {
-	int		 i, sv;
-	char		 buf[KEY_MAXNAME];
-
-	/*
-	 * Parse individual options from the stream as surrounded by
-	 * this goto.  Each pass through the routine parses out a single
-	 * option and registers it.  Option arguments are processed in
-	 * the arg() function.
-	 */
+	int		 i, pos, len;
 
-again:	/*
-	 * EBNF describing this section:
-	 *
-	 * options	::= option_list [:space:]* [;][\n]
-	 * option_list	::= option option_tail
-	 * option_tail	::= [:space:]+ option_list |
-	 * 		::= epsilon
-	 * option	::= [:alpha:]+ args
-	 * args		::= [:space:]* [(] [:alpha:]+ [)]
-	 */
+	pos = *offs;
+	for (;;) {
+		while (p[pos] == ' ' || p[pos] == '\t' || p[pos] == ',')
+			pos++;
 
-	while (isspace((unsigned char)p[*pos]))
-		(*pos)++;
-
-	/* Safe exit point. */
-
-	if (';' == p[*pos])
-		return;
-
-	/* Copy up to first non-alpha character. */
-
-	for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) {
-		buf[i] = (char)tolower((unsigned char)p[*pos]);
-		if ( ! isalpha((unsigned char)buf[i]))
-			break;
-	}
+		if (p[pos] == ';') {
+			*offs = pos + 1;
+			return;
+		}
 
-	/* Exit if buffer is empty (or overrun). */
+		/* Parse one option name. */
 
-	if (KEY_MAXNAME == i || 0 == i) {
-		mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
-		return;
-	}
+		len = 0;
+		while (isalpha((unsigned char)p[pos + len]))
+			len++;
 
-	buf[i] = '\0';
+		if (len == 0) {
+			mandoc_vmsg(MANDOCERR_TBLOPT_ALPHA,
+			    tbl->parse, ln, pos, "%c", p[pos]);
+			pos++;
+			continue;
+		}
 
-	while (isspace((unsigned char)p[*pos]))
-		(*pos)++;
+		/* Look up the option name. */
 
-	/* 
-	 * Look through all of the available keys to find one that
-	 * matches the input.  FIXME: hashtable this.
-	 */
+		i = 0;
+		while (i < KEY_MAXKEYS &&
+		    (strncasecmp(p + pos, keys[i].name, len) ||
+		     keys[i].name[len] != '\0'))
+			i++;
 
-	for (i = 0; i < KEY_MAXKEYS; i++) {
-		if (strcmp(buf, keys[i].name))
+		if (i == KEY_MAXKEYS) {
+			mandoc_vmsg(MANDOCERR_TBLOPT_BAD, tbl->parse,
+			    ln, pos, "%.*s", len, p + pos);
+			pos += len;
 			continue;
+		}
 
-		/*
-		 * Note: this is more difficult to recover from, as we
-		 * can be anywhere in the option sequence and it's
-		 * harder to jump to the next.  Meanwhile, just bail out
-		 * of the sequence altogether.
-		 */
+		/* Handle the option. */
 
-		if (keys[i].key) 
+		pos += len;
+		if (keys[i].key)
 			tbl->opts.opts |= keys[i].key;
-		else if ( ! arg(tbl, ln, p, pos, keys[i].ident))
-			return;
-
-		break;
+		else
+			arg(tbl, ln, p, &pos, i);
 	}
-
-	/* 
-	 * Allow us to recover from bad options by continuing to another
-	 * parse sequence.
-	 */
-
-	if (KEY_MAXKEYS == i)
-		mandoc_msg(MANDOCERR_TBLOPT, tbl->parse, ln, sv, NULL);
-
-	goto again;
-	/* NOTREACHED */
-}
-
-int
-tbl_option(struct tbl_node *tbl, int ln, const char *p)
-{
-	int		 pos;
-
-	/*
-	 * Table options are always on just one line, so automatically
-	 * switch into the next input mode here.
-	 */
-	tbl->part = TBL_PART_LAYOUT;
-
-	pos = 0;
-	opt(tbl, ln, p, &pos);
-
-	/* Always succeed. */
-	return(1);
 }
diff --git a/usr/src/cmd/mandoc/tbl_term.c b/usr/src/cmd/mandoc/tbl_term.c
index e8411ffece..1276776668 100644
--- a/usr/src/cmd/mandoc/tbl_term.c
+++ b/usr/src/cmd/mandoc/tbl_term.c
@@ -1,7 +1,7 @@
-/*	$Id: tbl_term.c,v 1.25 2013/05/31 21:37:17 schwarze Exp $ */
+/*	$Id: tbl_term.c,v 1.40 2015/03/06 15:48:53 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2011 Kristaps Dzonsons 
- * Copyright (c) 2011, 2012 Ingo Schwarze 
+ * Copyright (c) 2011, 2012, 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -32,17 +32,15 @@ static	size_t	term_tbl_len(size_t, void *);
 static	size_t	term_tbl_strlen(const char *, void *);
 static	void	tbl_char(struct termp *, char, size_t);
 static	void	tbl_data(struct termp *, const struct tbl_opts *,
-			const struct tbl_dat *, 
+			const struct tbl_dat *,
 			const struct roffcol *);
-static	size_t	tbl_rulewidth(struct termp *, const struct tbl_head *);
-static	void	tbl_hframe(struct termp *, const struct tbl_span *, int);
-static	void	tbl_literal(struct termp *, const struct tbl_dat *, 
+static	void	tbl_literal(struct termp *, const struct tbl_dat *,
 			const struct roffcol *);
-static	void	tbl_number(struct termp *, const struct tbl_opts *, 
-			const struct tbl_dat *, 
+static	void	tbl_number(struct termp *, const struct tbl_opts *,
+			const struct tbl_dat *,
 			const struct roffcol *);
-static	void	tbl_hrule(struct termp *, const struct tbl_span *);
-static	void	tbl_vrule(struct termp *, const struct tbl_head *);
+static	void	tbl_hrule(struct termp *, const struct tbl_span *, int);
+static	void	tbl_word(struct termp *, const struct tbl_dat *);
 
 
 static size_t
@@ -62,11 +60,11 @@ term_tbl_len(size_t sz, void *arg)
 void
 term_tbl(struct termp *tp, const struct tbl_span *sp)
 {
-	const struct tbl_head	*hp;
+	const struct tbl_cell	*cp;
 	const struct tbl_dat	*dp;
-	struct roffcol		*col;
-	int			 spans;
-	size_t		   	 rmargin, maxrmargin;
+	static size_t		 offset;
+	size_t			 rmargin, maxrmargin, tsz;
+	int			 ic, horiz, spans, vert;
 
 	rmargin = tp->rmargin;
 	maxrmargin = tp->maxrmargin;
@@ -83,85 +81,105 @@ term_tbl(struct termp *tp, const struct tbl_span *sp)
 	 * calculate the table widths and decimal positions.
 	 */
 
-	if (TBL_SPAN_FIRST & sp->flags) {
-		term_flushln(tp);
-
+	if (tp->tbl.cols == NULL) {
 		tp->tbl.len = term_tbl_len;
 		tp->tbl.slen = term_tbl_strlen;
 		tp->tbl.arg = tp;
 
-		tblcalc(&tp->tbl, sp);
-	}
+		tblcalc(&tp->tbl, sp, rmargin - tp->offset);
+
+		/* Center the table as a whole. */
+
+		offset = tp->offset;
+		if (sp->opts->opts & TBL_OPT_CENTRE) {
+			tsz = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)
+			    ? 2 : !!sp->opts->lvert + !!sp->opts->rvert;
+			for (ic = 0; ic < sp->opts->cols; ic++)
+				tsz += tp->tbl.cols[ic].width + 3;
+			tsz -= 3;
+			if (offset + tsz > rmargin)
+				tsz -= 1;
+			tp->offset = (offset + rmargin > tsz) ?
+			    (offset + rmargin - tsz) / 2 : 0;
+		}
 
-	/* Horizontal frame at the start of boxed tables. */
+		/* Horizontal frame at the start of boxed tables. */
 
-	if (TBL_SPAN_FIRST & sp->flags) {
-		if (TBL_OPT_DBOX & sp->opts->opts)
-			tbl_hframe(tp, sp, 1);
-		if (TBL_OPT_DBOX & sp->opts->opts ||
-		    TBL_OPT_BOX  & sp->opts->opts)
-			tbl_hframe(tp, sp, 0);
+		if (sp->opts->opts & TBL_OPT_DBOX)
+			tbl_hrule(tp, sp, 2);
+		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX))
+			tbl_hrule(tp, sp, 1);
 	}
 
 	/* Vertical frame at the start of each row. */
 
-	if (TBL_OPT_BOX & sp->opts->opts || TBL_OPT_DBOX & sp->opts->opts)
-		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
-			TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
+	horiz = sp->pos == TBL_SPAN_HORIZ || sp->pos == TBL_SPAN_DHORIZ;
+
+	if (sp->layout->vert ||
+	    (sp->prev != NULL && sp->prev->layout->vert) ||
+	    sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX))
+		term_word(tp, horiz ? "+" : "|");
+	else if (sp->opts->lvert)
+		tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1);
 
 	/*
 	 * Now print the actual data itself depending on the span type.
-	 * Spanner spans get a horizontal rule; data spanners have their
-	 * data printed by matching data to header.
+	 * Match data cells to column numbers.
 	 */
 
-	switch (sp->pos) {
-	case (TBL_SPAN_HORIZ):
-		/* FALLTHROUGH */
-	case (TBL_SPAN_DHORIZ):
-		tbl_hrule(tp, sp);
-		break;
-	case (TBL_SPAN_DATA):
-		/* Iterate over template headers. */
+	if (sp->pos == TBL_SPAN_DATA) {
+		cp = sp->layout->first;
 		dp = sp->first;
 		spans = 0;
-		for (hp = sp->head; hp; hp = hp->next) {
+		for (ic = 0; ic < sp->opts->cols; ic++) {
 
-			/* 
-			 * If the current data header is invoked during
-			 * a spanner ("spans" > 0), don't emit anything
-			 * at all.
+			/*
+			 * Remeber whether we need a vertical bar
+			 * after this cell.
 			 */
 
-			if (--spans >= 0)
-				continue;
-
-			/* Separate columns. */
+			vert = cp == NULL ? 0 : cp->vert;
 
-			if (NULL != hp->prev)
-				tbl_vrule(tp, hp);
-
-			col = &tp->tbl.cols[hp->ident];
-			tbl_data(tp, sp->opts, dp, col);
+			/*
+			 * Print the data and advance to the next cell.
+			 */
 
-			/* 
-			 * Go to the next data cell and assign the
-			 * number of subsequent spans, if applicable.
+			if (spans == 0) {
+				tbl_data(tp, sp->opts, dp, tp->tbl.cols + ic);
+				if (dp != NULL) {
+					spans = dp->spans;
+					dp = dp->next;
+				}
+			} else
+				spans--;
+			if (cp != NULL)
+				cp = cp->next;
+
+			/*
+			 * Separate columns, except in the middle
+			 * of spans and after the last cell.
 			 */
 
-			if (dp) {
-				spans = dp->spans;
-				dp = dp->next;
-			}
+			if (ic + 1 == sp->opts->cols || spans)
+				continue;
+
+			tbl_char(tp, ASCII_NBRSP, 1);
+			if (vert > 0)
+				tbl_char(tp, '|', vert);
+			if (vert < 2)
+				tbl_char(tp, ASCII_NBRSP, 2 - vert);
 		}
-		break;
-	}
+	} else if (horiz)
+		tbl_hrule(tp, sp, 0);
 
 	/* Vertical frame at the end of each row. */
 
-	if (TBL_OPT_BOX & sp->opts->opts || TBL_OPT_DBOX & sp->opts->opts)
-		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
-			TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
+	if (sp->layout->last->vert ||
+	    (sp->prev != NULL && sp->prev->layout->last->vert) ||
+	    (sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX)))
+		term_word(tp, horiz ? "+" : " |");
+	else if (sp->opts->rvert)
+		tbl_char(tp, horiz ? '-' : ASCII_NBRSP, 1);
 	term_flushln(tp);
 
 	/*
@@ -169,140 +187,118 @@ term_tbl(struct termp *tp, const struct tbl_span *sp)
 	 * existing table configuration and set it to NULL.
 	 */
 
-	if (TBL_SPAN_LAST & sp->flags) {
-		if (TBL_OPT_DBOX & sp->opts->opts ||
-		    TBL_OPT_BOX  & sp->opts->opts) {
-			tbl_hframe(tp, sp, 0);
+	if (sp->next == NULL) {
+		if (sp->opts->opts & (TBL_OPT_DBOX | TBL_OPT_BOX)) {
+			tbl_hrule(tp, sp, 1);
 			tp->skipvsp = 1;
 		}
-		if (TBL_OPT_DBOX & sp->opts->opts) {
-			tbl_hframe(tp, sp, 1);
+		if (sp->opts->opts & TBL_OPT_DBOX) {
+			tbl_hrule(tp, sp, 2);
 			tp->skipvsp = 2;
 		}
 		assert(tp->tbl.cols);
 		free(tp->tbl.cols);
 		tp->tbl.cols = NULL;
+		tp->offset = offset;
 	}
 
 	tp->flags &= ~TERMP_NONOSPACE;
 	tp->rmargin = rmargin;
 	tp->maxrmargin = maxrmargin;
-
 }
 
 /*
- * Horizontal rules extend across the entire table.
- * Calculate the width by iterating over columns.
- */
-static size_t
-tbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
-{
-	size_t		 width;
-
-	width = tp->tbl.cols[hp->ident].width;
-
-	/* Account for leading blanks. */
-	if (hp->prev)
-		width += 2 - hp->vert;
-
-	/* Account for trailing blank. */
-	width++;
-
-	return(width);
-}
-
-/*
- * Rules inside the table can be single or double
- * and have crossings with vertical rules marked with pluses.
+ * Kinds of horizontal rulers:
+ * 0: inside the table (single or double line with crossings)
+ * 1: inner frame (single line with crossings and ends)
+ * 2: outer frame (single line without crossings with ends)
  */
 static void
-tbl_hrule(struct termp *tp, const struct tbl_span *sp)
+tbl_hrule(struct termp *tp, const struct tbl_span *sp, int kind)
 {
-	const struct tbl_head *hp;
-	char		 c;
-
-	c = '-';
-	if (TBL_SPAN_DHORIZ == sp->pos)
-		c = '=';
-
-	for (hp = sp->head; hp; hp = hp->next) {
-		if (hp->prev && hp->vert)
-			tbl_char(tp, '+', hp->vert);
-		tbl_char(tp, c, tbl_rulewidth(tp, hp));
+	const struct tbl_cell *c1, *c2;
+	int	 vert;
+	char	 line, cross;
+
+	line = (kind == 0 && TBL_SPAN_DHORIZ == sp->pos) ? '=' : '-';
+	cross = (kind < 2) ? '+' : '-';
+
+	if (kind)
+		term_word(tp, "+");
+	c1 = sp->layout->first;
+	c2 = sp->prev == NULL ? NULL : sp->prev->layout->first;
+	if (c2 == c1)
+		c2 = NULL;
+	for (;;) {
+		tbl_char(tp, line, tp->tbl.cols[c1->col].width + 1);
+		vert = c1->vert;
+		if ((c1 = c1->next) == NULL)
+			 break;
+		if (c2 != NULL) {
+			if (vert < c2->vert)
+				vert = c2->vert;
+			c2 = c2->next;
+		}
+		if (vert)
+			tbl_char(tp, cross, vert);
+		if (vert < 2)
+			tbl_char(tp, line, 2 - vert);
 	}
-}
-
-/*
- * Rules above and below the table are always single
- * and have an additional plus at the beginning and end.
- * For double frames, this function is called twice,
- * and the outer one does not have crossings.
- */
-static void
-tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
-{
-	const struct tbl_head *hp;
-
-	term_word(tp, "+");
-	for (hp = sp->head; hp; hp = hp->next) {
-		if (hp->prev && hp->vert)
-			tbl_char(tp, (outer ? '-' : '+'), hp->vert);
-		tbl_char(tp, '-', tbl_rulewidth(tp, hp));
+	if (kind) {
+		term_word(tp, "+");
+		term_flushln(tp);
 	}
-	term_word(tp, "+");
-	term_flushln(tp);
 }
 
 static void
 tbl_data(struct termp *tp, const struct tbl_opts *opts,
-		const struct tbl_dat *dp, 
-		const struct roffcol *col)
+	const struct tbl_dat *dp,
+	const struct roffcol *col)
 {
 
-	if (NULL == dp) {
+	if (dp == NULL) {
 		tbl_char(tp, ASCII_NBRSP, col->width);
 		return;
 	}
-	assert(dp->layout);
 
 	switch (dp->pos) {
-	case (TBL_DATA_NONE):
+	case TBL_DATA_NONE:
 		tbl_char(tp, ASCII_NBRSP, col->width);
 		return;
-	case (TBL_DATA_HORIZ):
+	case TBL_DATA_HORIZ:
 		/* FALLTHROUGH */
-	case (TBL_DATA_NHORIZ):
+	case TBL_DATA_NHORIZ:
 		tbl_char(tp, '-', col->width);
 		return;
-	case (TBL_DATA_NDHORIZ):
+	case TBL_DATA_NDHORIZ:
 		/* FALLTHROUGH */
-	case (TBL_DATA_DHORIZ):
+	case TBL_DATA_DHORIZ:
 		tbl_char(tp, '=', col->width);
 		return;
 	default:
 		break;
 	}
-	
+
 	switch (dp->layout->pos) {
-	case (TBL_CELL_HORIZ):
+	case TBL_CELL_HORIZ:
 		tbl_char(tp, '-', col->width);
 		break;
-	case (TBL_CELL_DHORIZ):
+	case TBL_CELL_DHORIZ:
 		tbl_char(tp, '=', col->width);
 		break;
-	case (TBL_CELL_LONG):
+	case TBL_CELL_LONG:
 		/* FALLTHROUGH */
-	case (TBL_CELL_CENTRE):
+	case TBL_CELL_CENTRE:
 		/* FALLTHROUGH */
-	case (TBL_CELL_LEFT):
+	case TBL_CELL_LEFT:
 		/* FALLTHROUGH */
-	case (TBL_CELL_RIGHT):
+	case TBL_CELL_RIGHT:
 		tbl_literal(tp, dp, col);
 		break;
-	case (TBL_CELL_NUMBER):
+	case TBL_CELL_NUMBER:
 		tbl_number(tp, opts, dp, col);
 		break;
-	case (TBL_CELL_DOWN):
+	case TBL_CELL_DOWN:
 		tbl_char(tp, ASCII_NBRSP, col->width);
 		break;
 	default:
@@ -311,17 +307,6 @@ tbl_data(struct termp *tp, const struct tbl_opts *opts,
 	}
 }
 
-static void
-tbl_vrule(struct termp *tp, const struct tbl_head *hp)
-{
-
-	tbl_char(tp, ASCII_NBRSP, 1);
-	if (0 < hp->vert)
-		tbl_char(tp, '|', hp->vert);
-	if (2 > hp->vert)
-		tbl_char(tp, ASCII_NBRSP, 2 - hp->vert);
-}
-
 static void
 tbl_char(struct termp *tp, char c, size_t len)
 {
@@ -338,36 +323,35 @@ tbl_char(struct termp *tp, char c, size_t len)
 }
 
 static void
-tbl_literal(struct termp *tp, const struct tbl_dat *dp, 
+tbl_literal(struct termp *tp, const struct tbl_dat *dp,
 		const struct roffcol *col)
 {
-	struct tbl_head		*hp;
-	size_t			 width, len, padl, padr;
-	int			 spans;
+	size_t		 len, padl, padr, width;
+	int		 ic, spans;
 
 	assert(dp->string);
 	len = term_strlen(tp, dp->string);
-
-	hp = dp->layout->head->next;
 	width = col->width;
-	for (spans = dp->spans; spans--; hp = hp->next)
-		width += tp->tbl.cols[hp->ident].width + 3;
+	ic = dp->layout->col;
+	spans = dp->spans;
+	while (spans--)
+		width += tp->tbl.cols[++ic].width + 3;
 
 	padr = width > len ? width - len : 0;
 	padl = 0;
 
 	switch (dp->layout->pos) {
-	case (TBL_CELL_LONG):
+	case TBL_CELL_LONG:
 		padl = term_len(tp, 1);
 		padr = padr > padl ? padr - padl : 0;
 		break;
-	case (TBL_CELL_CENTRE):
+	case TBL_CELL_CENTRE:
 		if (2 > padr)
 			break;
 		padl = padr / 2;
 		padr -= padl;
 		break;
-	case (TBL_CELL_RIGHT):
+	case TBL_CELL_RIGHT:
 		padl = padr;
 		padr = 0;
 		break;
@@ -376,7 +360,7 @@ tbl_literal(struct termp *tp, const struct tbl_dat *dp,
 	}
 
 	tbl_char(tp, ASCII_NBRSP, padl);
-	term_word(tp, dp->string);
+	tbl_word(tp, dp);
 	tbl_char(tp, ASCII_NBRSP, padr);
 }
 
@@ -404,8 +388,7 @@ tbl_number(struct termp *tp, const struct tbl_opts *opts,
 
 	psz = term_strlen(tp, buf);
 
-	if (NULL != (cp = strrchr(dp->string, opts->decimal))) {
-		buf[1] = '\0';
+	if ((cp = strrchr(dp->string, opts->decimal)) != NULL) {
 		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
 			buf[0] = dp->string[i];
 			ssz += term_strlen(tp, buf);
@@ -414,11 +397,30 @@ tbl_number(struct termp *tp, const struct tbl_opts *opts,
 	} else
 		d = sz + psz;
 
-	padl = col->decimal - d;
-
-	tbl_char(tp, ASCII_NBRSP, padl);
-	term_word(tp, dp->string);
+	if (col->decimal > d && col->width > sz) {
+		padl = col->decimal - d;
+		if (padl + sz > col->width)
+			padl = col->width - sz;
+		tbl_char(tp, ASCII_NBRSP, padl);
+	} else
+		padl = 0;
+	tbl_word(tp, dp);
 	if (col->width > sz + padl)
 		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
 }
 
+static void
+tbl_word(struct termp *tp, const struct tbl_dat *dp)
+{
+	int		 prev_font;
+
+	prev_font = tp->fonti;
+	if (dp->layout->flags & TBL_CELL_BOLD)
+		term_fontpush(tp, TERMFONT_BOLD);
+	else if (dp->layout->flags & TBL_CELL_ITALIC)
+		term_fontpush(tp, TERMFONT_UNDER);
+
+	term_word(tp, dp->string);
+
+	term_fontpopq(tp, prev_font);
+}
diff --git a/usr/src/cmd/mandoc/term.c b/usr/src/cmd/mandoc/term.c
index e7b9557875..ee2af9e625 100644
--- a/usr/src/cmd/mandoc/term.c
+++ b/usr/src/cmd/mandoc/term.c
@@ -1,7 +1,7 @@
-/*	$Id: term.c,v 1.214 2013/12/25 00:39:31 schwarze Exp $ */
+/*	$Id: term.c,v 1.245 2015/03/06 13:02:43 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2010-2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,20 +15,18 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "out.h"
 #include "term.h"
 #include "main.h"
@@ -39,21 +37,18 @@ static	void		 bufferc(struct termp *, char);
 static	void		 encode(struct termp *, const char *, size_t);
 static	void		 encode1(struct termp *, int);
 
+
 void
 term_free(struct termp *p)
 {
 
-	if (p->buf)
-		free(p->buf);
-	if (p->symtab)
-		mchars_free(p->symtab);
-
+	free(p->buf);
+	free(p->fontq);
 	free(p);
 }
 
-
 void
-term_begin(struct termp *p, term_margin head, 
+term_begin(struct termp *p, term_margin head,
 		term_margin foot, const void *arg)
 {
 
@@ -63,7 +58,6 @@ term_begin(struct termp *p, term_margin head,
 	(*p->begin)(p);
 }
 
-
 void
 term_end(struct termp *p)
 {
@@ -72,34 +66,27 @@ term_end(struct termp *p)
 }
 
 /*
- * Flush a line of text.  A "line" is loosely defined as being something
- * that should be followed by a newline, regardless of whether it's
- * broken apart by newlines getting there.  A line can also be a
- * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does
- * not have a trailing newline.
- *
+ * Flush a chunk of text.  By default, break the output line each time
+ * the right margin is reached, and continue output on the next line
+ * at the same offset as the chunk itself.  By default, also break the
+ * output line at the end of the chunk.
  * The following flags may be specified:
  *
- *  - TERMP_NOBREAK: this is the most important and is used when making
- *    columns.  In short: don't print a newline and instead expect the
- *    next call to do the padding up to the start of the next column.
- *    p->trailspace may be set to 0, 1, or 2, depending on how many
- *    space characters are required at the end of the column.
- *
- *  - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
- *    the line is overrun, and don't pad-right if it's underrun.
- *
- *  - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
- *    overrunning, instead save the position and continue at that point
- *    when the next invocation.
- *
- *  In-line line breaking:
- *
- *  If TERMP_NOBREAK is specified and the line overruns the right
- *  margin, it will break and pad-right to the right margin after
- *  writing.  If maxrmargin is violated, it will break and continue
- *  writing from the right-margin, which will lead to the above scenario
- *  upon exit.  Otherwise, the line will break at the right margin.
+ *  - TERMP_NOBREAK: Do not break the output line at the right margin,
+ *    but only at the max right margin.  Also, do not break the output
+ *    line at the end of the chunk, such that the next call can pad to
+ *    the next column.  However, if less than p->trailspace blanks,
+ *    which can be 0, 1, or 2, remain to the right margin, the line
+ *    will be broken.
+ *  - TERMP_BRIND: If the chunk does not fit and the output line has
+ *    to be broken, start the next line at the right margin instead
+ *    of at the offset.  Used together with TERMP_NOBREAK for the tags
+ *    in various kinds of tagged lists.
+ *  - TERMP_DANGLE: Do not break the output line at the right margin,
+ *    append the next chunk after it even if this one is too long.
+ *    To be used together with TERMP_NOBREAK.
+ *  - TERMP_HANG: Like TERMP_DANGLE, and also suppress padding before
+ *    the next chunk if this column is not full.
  */
 void
 term_flushln(struct termp *p)
@@ -114,7 +101,6 @@ term_flushln(struct termp *p)
 	size_t		 j;     /* temporary loop index for p->buf */
 	size_t		 jhy;	/* last hyph before overflow w/r/t j */
 	size_t		 maxvis; /* output position of visible boundary */
-	size_t		 mmax; /* used in calculating bp */
 
 	/*
 	 * First, establish the maximum columns of "visible" content.
@@ -127,13 +113,16 @@ term_flushln(struct termp *p)
 	 * is negative, it gets sign extended.  Subtracting that
 	 * very large size_t effectively adds a small number to dv.
 	 */
-	assert  (p->rmargin >= p->offset);
-	dv     = p->rmargin - p->offset;
+	dv = p->rmargin > p->offset ? p->rmargin - p->offset : 0;
 	maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
-	dv     = p->maxrmargin - p->offset;
-	mmax   = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0;
 
-	bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
+	if (p->flags & TERMP_NOBREAK) {
+		dv = p->maxrmargin > p->offset ?
+		     p->maxrmargin - p->offset : 0;
+		bp = (int)dv > p->overstep ?
+		     dv - (size_t)p->overstep : 0;
+	} else
+		bp = maxvis;
 
 	/*
 	 * Calculate the required amount of padding.
@@ -178,10 +167,18 @@ term_flushln(struct termp *p)
 
 			/* Regular word. */
 			/* Break at the hyphen point if we overrun. */
-			if (vend > vis && vend < bp && 
-					ASCII_HYPH == p->buf[j])
+			if (vend > vis && vend < bp &&
+			    (ASCII_HYPH == p->buf[j] ||
+			     ASCII_BREAK == p->buf[j]))
 				jhy = j;
 
+			/*
+			 * Hyphenation now decided, put back a real
+			 * hyphen such that we get the correct width.
+			 */
+			if (ASCII_HYPH == p->buf[j])
+				p->buf[j] = '-';
+
 			vend += (*p->width)(p, p->buf[j]);
 		}
 
@@ -193,9 +190,10 @@ term_flushln(struct termp *p)
 			vend -= vis;
 			(*p->endline)(p);
 			p->viscol = 0;
-			if (TERMP_NOBREAK & p->flags) {
+			if (TERMP_BRIND & p->flags) {
 				vbl = p->rmargin;
-				vend += p->rmargin - p->offset;
+				vend += p->rmargin;
+				vend -= p->offset;
 			} else
 				vbl = p->offset;
 
@@ -222,7 +220,7 @@ term_flushln(struct termp *p)
 				break;
 			if (' ' == p->buf[i]) {
 				j = i;
-				while (' ' == p->buf[i])
+				while (i < p->col && ' ' == p->buf[i])
 					i++;
 				dv = (i - j) * (*p->width)(p, ' ');
 				vbl += dv;
@@ -233,6 +231,8 @@ term_flushln(struct termp *p)
 				vbl += (*p->width)(p, ' ');
 				continue;
 			}
+			if (ASCII_BREAK == p->buf[i])
+				continue;
 
 			/*
 			 * Now we definitely know there will be
@@ -245,16 +245,10 @@ term_flushln(struct termp *p)
 				vbl = 0;
 			}
 
-			if (ASCII_HYPH == p->buf[i]) {
-				(*p->letter)(p, '-');
-				p->viscol += (*p->width)(p, '-');
-				continue;
-			}
-
 			(*p->letter)(p, p->buf[i]);
 			if (8 == p->buf[i])
 				p->viscol -= (*p->width)(p, p->buf[i-1]);
-			else 
+			else
 				p->viscol += (*p->width)(p, p->buf[i]);
 		}
 		vis = vend;
@@ -264,8 +258,10 @@ term_flushln(struct termp *p)
 	 * If there was trailing white space, it was not printed;
 	 * so reset the cursor position accordingly.
 	 */
-	if (vis)
+	if (vis > vbl)
 		vis -= vbl;
+	else
+		vis = 0;
 
 	p->col = 0;
 	p->overstep = 0;
@@ -277,8 +273,8 @@ term_flushln(struct termp *p)
 	}
 
 	if (TERMP_HANG & p->flags) {
-		p->overstep = (int)(vis - maxvis +
-				p->trailspace * (*p->width)(p, ' '));
+		p->overstep += (int)(p->offset + vis - p->rmargin +
+		    p->trailspace * (*p->width)(p, ' '));
 
 		/*
 		 * If we have overstepped the margin, temporarily move
@@ -301,8 +297,7 @@ term_flushln(struct termp *p)
 	}
 }
 
-
-/* 
+/*
  * A newline only breaks an existing line; it won't assert vertical
  * space.  All data in the output buffer is flushed prior to the newline
  * assertion.
@@ -316,7 +311,6 @@ term_newln(struct termp *p)
 		term_flushln(p);
 }
 
-
 /*
  * Asserts a vertical space (a full, empty line-break between lines).
  * Note that if used twice, this will cause two blank spaces and so on.
@@ -335,6 +329,7 @@ term_vspace(struct termp *p)
 		(*p->endline)(p);
 }
 
+/* Swap current and previous font; for \fP and .ft P */
 void
 term_fontlast(struct termp *p)
 {
@@ -345,7 +340,7 @@ term_fontlast(struct termp *p)
 	p->fontq[p->fonti] = f;
 }
 
-
+/* Set font, save current, discard previous; for \f, .ft, .B etc. */
 void
 term_fontrepl(struct termp *p, enum termfont f)
 {
@@ -354,43 +349,31 @@ term_fontrepl(struct termp *p, enum termfont f)
 	p->fontq[p->fonti] = f;
 }
 
-
+/* Set font, save previous. */
 void
 term_fontpush(struct termp *p, enum termfont f)
 {
 
-	assert(p->fonti + 1 < 10);
 	p->fontl = p->fontq[p->fonti];
-	p->fontq[++p->fonti] = f;
-}
-
-
-const void *
-term_fontq(struct termp *p)
-{
-
-	return(&p->fontq[p->fonti]);
-}
-
-
-enum termfont
-term_fonttop(struct termp *p)
-{
-
-	return(p->fontq[p->fonti]);
+	if (++p->fonti == p->fontsz) {
+		p->fontsz += 8;
+		p->fontq = mandoc_reallocarray(p->fontq,
+		    p->fontsz, sizeof(enum termfont *));
+	}
+	p->fontq[p->fonti] = f;
 }
 
-
+/* Flush to make the saved pointer current again. */
 void
-term_fontpopq(struct termp *p, const void *key)
+term_fontpopq(struct termp *p, int i)
 {
 
-	while (p->fonti >= 0 && key < (void *)(p->fontq + p->fonti))
-		p->fonti--;
-	assert(p->fonti >= 0);
+	assert(i >= 0);
+	if (p->fonti > i)
+		p->fonti = i;
 }
 
-
+/* Pop one font off the stack. */
 void
 term_fontpop(struct termp *p)
 {
@@ -409,7 +392,6 @@ term_word(struct termp *p, const char *word)
 {
 	const char	 nbrsp[2] = { ASCII_NBRSP, 0 };
 	const char	*seq, *cp;
-	char		 c;
 	int		 sz, uc;
 	size_t		 ssz;
 	enum mandoc_esc	 esc;
@@ -430,7 +412,8 @@ term_word(struct termp *p, const char *word)
 	else
 		p->flags |= TERMP_NOSPACE;
 
-	p->flags &= ~TERMP_SENTENCE;
+	p->flags &= ~(TERMP_SENTENCE | TERMP_NONEWLINE);
+	p->skipvsp = 0;
 
 	while ('\0' != *word) {
 		if ('\\' != *word) {
@@ -456,70 +439,83 @@ term_word(struct termp *p, const char *word)
 		word++;
 		esc = mandoc_escape(&word, &seq, &sz);
 		if (ESCAPE_ERROR == esc)
-			break;
-
-		if (TERMENC_ASCII != p->enc)
-			switch (esc) {
-			case (ESCAPE_UNICODE):
-				uc = mchars_num2uc(seq + 1, sz - 1);
-				if ('\0' == uc)
-					break;
-				encode1(p, uc);
-				continue;
-			case (ESCAPE_SPECIAL):
-				uc = mchars_spec2cp(p->symtab, seq, sz);
-				if (uc <= 0)
-					break;
-				encode1(p, uc);
-				continue;
-			default:
-				break;
-			}
+			continue;
 
 		switch (esc) {
-		case (ESCAPE_UNICODE):
-			encode1(p, '?');
+		case ESCAPE_UNICODE:
+			uc = mchars_num2uc(seq + 1, sz - 1);
 			break;
-		case (ESCAPE_NUMBERED):
-			c = mchars_num2char(seq, sz);
-			if ('\0' != c)
-				encode(p, &c, 1);
-			break;
-		case (ESCAPE_SPECIAL):
-			cp = mchars_spec2str(p->symtab, seq, sz, &ssz);
-			if (NULL != cp) 
-				encode(p, cp, ssz);
-			else if (1 == ssz)
-				encode(p, seq, sz);
+		case ESCAPE_NUMBERED:
+			uc = mchars_num2char(seq, sz);
+			if (uc < 0)
+				continue;
 			break;
-		case (ESCAPE_FONTBOLD):
+		case ESCAPE_SPECIAL:
+			if (p->enc == TERMENC_ASCII) {
+				cp = mchars_spec2str(p->symtab,
+				    seq, sz, &ssz);
+				if (cp != NULL)
+					encode(p, cp, ssz);
+			} else {
+				uc = mchars_spec2cp(p->symtab, seq, sz);
+				if (uc > 0)
+					encode1(p, uc);
+			}
+			continue;
+		case ESCAPE_FONTBOLD:
 			term_fontrepl(p, TERMFONT_BOLD);
-			break;
-		case (ESCAPE_FONTITALIC):
+			continue;
+		case ESCAPE_FONTITALIC:
 			term_fontrepl(p, TERMFONT_UNDER);
-			break;
-		case (ESCAPE_FONTBI):
+			continue;
+		case ESCAPE_FONTBI:
 			term_fontrepl(p, TERMFONT_BI);
-			break;
-		case (ESCAPE_FONT):
+			continue;
+		case ESCAPE_FONT:
 			/* FALLTHROUGH */
-		case (ESCAPE_FONTROMAN):
+		case ESCAPE_FONTROMAN:
 			term_fontrepl(p, TERMFONT_NONE);
-			break;
-		case (ESCAPE_FONTPREV):
+			continue;
+		case ESCAPE_FONTPREV:
 			term_fontlast(p);
-			break;
-		case (ESCAPE_NOSPACE):
+			continue;
+		case ESCAPE_NOSPACE:
 			if (TERMP_SKIPCHAR & p->flags)
 				p->flags &= ~TERMP_SKIPCHAR;
 			else if ('\0' == *word)
-				p->flags |= TERMP_NOSPACE;
-			break;
-		case (ESCAPE_SKIPCHAR):
+				p->flags |= (TERMP_NOSPACE | TERMP_NONEWLINE);
+			continue;
+		case ESCAPE_SKIPCHAR:
 			p->flags |= TERMP_SKIPCHAR;
-			break;
+			continue;
+		case ESCAPE_OVERSTRIKE:
+			cp = seq + sz;
+			while (seq < cp) {
+				if (*seq == '\\') {
+					mandoc_escape(&seq, NULL, NULL);
+					continue;
+				}
+				encode1(p, *seq++);
+				if (seq < cp)
+					encode(p, "\b", 1);
+			}
 		default:
-			break;
+			continue;
+		}
+
+		/*
+		 * Common handling for Unicode and numbered
+		 * character escape sequences.
+		 */
+
+		if (p->enc == TERMENC_ASCII) {
+			cp = ascii_uc2str(uc);
+			encode(p, cp, strlen(cp));
+		} else {
+			if ((uc < 0x20 && uc != 0x09) ||
+			    (uc > 0x7E && uc < 0xA0))
+				uc = 0xFFFD;
+			encode1(p, uc);
 		}
 	}
 	p->flags &= ~TERMP_NBRWORD;
@@ -534,7 +530,7 @@ adjbuf(struct termp *p, size_t sz)
 	while (sz >= p->maxcols)
 		p->maxcols <<= 2;
 
-	p->buf = mandoc_realloc(p->buf, sizeof(int) * p->maxcols);
+	p->buf = mandoc_reallocarray(p->buf, p->maxcols, sizeof(int));
 }
 
 static void
@@ -565,7 +561,7 @@ encode1(struct termp *p, int c)
 	if (p->col + 6 >= p->maxcols)
 		adjbuf(p, p->col + 6);
 
-	f = term_fonttop(p);
+	f = p->fontq[p->fonti];
 
 	if (TERMFONT_UNDER == f || TERMFONT_BI == f) {
 		p->buf[p->col++] = '_';
@@ -597,8 +593,8 @@ encode(struct termp *p, const char *word, size_t sz)
 	 * character by character.
 	 */
 
-	if (TERMFONT_NONE == term_fonttop(p)) {
-		if (p->col + sz >= p->maxcols) 
+	if (p->fontq[p->fonti] == TERMFONT_NONE) {
+		if (p->col + sz >= p->maxcols)
 			adjbuf(p, p->col + sz);
 		for (i = 0; i < sz; i++)
 			p->buf[p->col++] = word[i];
@@ -619,6 +615,36 @@ encode(struct termp *p, const char *word, size_t sz)
 	}
 }
 
+void
+term_setwidth(struct termp *p, const char *wstr)
+{
+	struct roffsu	 su;
+	size_t		 width;
+	int		 iop;
+
+	iop = 0;
+	width = 0;
+	if (NULL != wstr) {
+		switch (*wstr) {
+		case '+':
+			iop = 1;
+			wstr++;
+			break;
+		case '-':
+			iop = -1;
+			wstr++;
+			break;
+		default:
+			break;
+		}
+		if (a2roffsu(wstr, &su, SCALE_MAX))
+			width = term_hspan(p, &su);
+		else
+			iop = 0;
+	}
+	(*p->setwidth)(p, iop, width);
+}
+
 size_t
 term_len(const struct termp *p, size_t sz)
 {
@@ -641,10 +667,11 @@ size_t
 term_strlen(const struct termp *p, const char *cp)
 {
 	size_t		 sz, rsz, i;
-	int		 ssz, skip, c;
+	int		 ssz, skip, uc;
 	const char	*seq, *rhs;
 	enum mandoc_esc	 esc;
-	static const char rej[] = { '\\', ASCII_HYPH, ASCII_NBRSP, '\0' };
+	static const char rej[] = { '\\', ASCII_NBRSP, ASCII_HYPH,
+			ASCII_BREAK, '\0' };
 
 	/*
 	 * Account for escaped sequences within string length
@@ -659,80 +686,98 @@ term_strlen(const struct termp *p, const char *cp)
 		for (i = 0; i < rsz; i++)
 			sz += cond_width(p, *cp++, &skip);
 
-		c = 0;
 		switch (*cp) {
-		case ('\\'):
+		case '\\':
 			cp++;
 			esc = mandoc_escape(&cp, &seq, &ssz);
 			if (ESCAPE_ERROR == esc)
-				return(sz);
-
-			if (TERMENC_ASCII != p->enc)
-				switch (esc) {
-				case (ESCAPE_UNICODE):
-					c = mchars_num2uc
-						(seq + 1, ssz - 1);
-					if ('\0' == c)
-						break;
-					sz += cond_width(p, c, &skip);
-					continue;
-				case (ESCAPE_SPECIAL):
-					c = mchars_spec2cp
-						(p->symtab, seq, ssz);
-					if (c <= 0)
-						break;
-					sz += cond_width(p, c, &skip);
-					continue;
-				default:
-					break;
-				}
+				continue;
 
 			rhs = NULL;
 
 			switch (esc) {
-			case (ESCAPE_UNICODE):
-				sz += cond_width(p, '?', &skip);
+			case ESCAPE_UNICODE:
+				uc = mchars_num2uc(seq + 1, ssz - 1);
 				break;
-			case (ESCAPE_NUMBERED):
-				c = mchars_num2char(seq, ssz);
-				if ('\0' != c)
-					sz += cond_width(p, c, &skip);
-				break;
-			case (ESCAPE_SPECIAL):
-				rhs = mchars_spec2str
-					(p->symtab, seq, ssz, &rsz);
-
-				if (ssz != 1 || rhs)
-					break;
-
-				rhs = seq;
-				rsz = ssz;
+			case ESCAPE_NUMBERED:
+				uc = mchars_num2char(seq, ssz);
+				if (uc < 0)
+					continue;
 				break;
-			case (ESCAPE_SKIPCHAR):
+			case ESCAPE_SPECIAL:
+				if (p->enc == TERMENC_ASCII) {
+					rhs = mchars_spec2str(p->symtab,
+					    seq, ssz, &rsz);
+					if (rhs != NULL)
+						break;
+				} else {
+					uc = mchars_spec2cp(p->symtab,
+					    seq, ssz);
+					if (uc > 0)
+						sz += cond_width(p, uc, &skip);
+				}
+				continue;
+			case ESCAPE_SKIPCHAR:
 				skip = 1;
-				break;
+				continue;
+			case ESCAPE_OVERSTRIKE:
+				rsz = 0;
+				rhs = seq + ssz;
+				while (seq < rhs) {
+					if (*seq == '\\') {
+						mandoc_escape(&seq, NULL, NULL);
+						continue;
+					}
+					i = (*p->width)(p, *seq++);
+					if (rsz < i)
+						rsz = i;
+				}
+				sz += rsz;
+				continue;
 			default:
-				break;
+				continue;
 			}
 
-			if (NULL == rhs)
-				break;
+			/*
+			 * Common handling for Unicode and numbered
+			 * character escape sequences.
+			 */
+
+			if (rhs == NULL) {
+				if (p->enc == TERMENC_ASCII) {
+					rhs = ascii_uc2str(uc);
+					rsz = strlen(rhs);
+				} else {
+					if ((uc < 0x20 && uc != 0x09) ||
+					    (uc > 0x7E && uc < 0xA0))
+						uc = 0xFFFD;
+					sz += cond_width(p, uc, &skip);
+					continue;
+				}
+			}
 
 			if (skip) {
 				skip = 0;
 				break;
 			}
 
+			/*
+			 * Common handling for all escape sequences
+			 * printing more than one character.
+			 */
+
 			for (i = 0; i < rsz; i++)
 				sz += (*p->width)(p, *rhs++);
 			break;
-		case (ASCII_NBRSP):
+		case ASCII_NBRSP:
 			sz += cond_width(p, ' ', &skip);
 			cp++;
 			break;
-		case (ASCII_HYPH):
+		case ASCII_HYPH:
 			sz += cond_width(p, '-', &skip);
 			cp++;
+			/* FALLTHROUGH */
+		case ASCII_BREAK:
 			break;
 		default:
 			break;
@@ -742,50 +787,55 @@ term_strlen(const struct termp *p, const char *cp)
 	return(sz);
 }
 
-/* ARGSUSED */
-size_t
+int
 term_vspan(const struct termp *p, const struct roffsu *su)
 {
 	double		 r;
+	int		 ri;
 
 	switch (su->unit) {
-	case (SCALE_CM):
-		r = su->scale * 2;
+	case SCALE_BU:
+		r = su->scale / 40.0;
+		break;
+	case SCALE_CM:
+		r = su->scale * 6.0 / 2.54;
+		break;
+	case SCALE_FS:
+		r = su->scale * 65536.0 / 40.0;
+		break;
+	case SCALE_IN:
+		r = su->scale * 6.0;
 		break;
-	case (SCALE_IN):
-		r = su->scale * 6;
+	case SCALE_MM:
+		r = su->scale * 0.006;
 		break;
-	case (SCALE_PC):
+	case SCALE_PC:
 		r = su->scale;
 		break;
-	case (SCALE_PT):
-		r = su->scale / 8;
+	case SCALE_PT:
+		r = su->scale / 12.0;
 		break;
-	case (SCALE_MM):
-		r = su->scale / 1000;
+	case SCALE_EN:
+		/* FALLTHROUGH */
+	case SCALE_EM:
+		r = su->scale * 0.6;
 		break;
-	case (SCALE_VS):
+	case SCALE_VS:
 		r = su->scale;
 		break;
 	default:
-		r = su->scale - 1;
-		break;
+		abort();
+		/* NOTREACHED */
 	}
-
-	if (r < 0.0)
-		r = 0.0;
-	return(/* LINTED */(size_t)
-			r);
+	ri = r > 0.0 ? r + 0.4995 : r - 0.4995;
+	return(ri < 66 ? ri : 1);
 }
 
-size_t
+int
 term_hspan(const struct termp *p, const struct roffsu *su)
 {
 	double		 v;
 
-	v = ((*p->hspan)(p, su));
-	if (v < 0.0)
-		v = 0.0;
-	return((size_t) /* LINTED */
-			v);
+	v = (*p->hspan)(p, su);
+	return(v > 0.0 ? v + 0.0005 : v - 0.0005);
 }
diff --git a/usr/src/cmd/mandoc/term.h b/usr/src/cmd/mandoc/term.h
index 8cad4be838..b65524b610 100644
--- a/usr/src/cmd/mandoc/term.h
+++ b/usr/src/cmd/mandoc/term.h
@@ -1,7 +1,7 @@
-/*	$Id: term.h,v 1.97 2013/12/25 00:39:31 schwarze Exp $ */
+/*	$Id: term.h,v 1.111 2015/01/31 00:12:41 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2011, 2012, 2013 Ingo Schwarze 
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,12 +15,6 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifndef TERM_H
-#define TERM_H
-
-__BEGIN_DECLS
-
-struct	termp;
 
 enum	termenc {
 	TERMENC_ASCII,
@@ -44,6 +38,8 @@ enum	termfont {
 
 #define	TERM_MAXMARGIN	  100000 /* FIXME */
 
+struct	termp;
+
 typedef void	(*term_margin)(struct termp *, const void *);
 
 struct	termp_tbl {
@@ -54,9 +50,11 @@ struct	termp_tbl {
 struct	termp {
 	enum termtype	  type;
 	struct rofftbl	  tbl;		/* table configuration */
+	int		  synopsisonly; /* print the synopsis only */
 	int		  mdocstyle;	/* imitate mdoc(7) output */
 	size_t		  defindent;	/* Default indent for text. */
 	size_t		  defrmargin;	/* Right margin of the device. */
+	size_t		  lastrmargin;	/* Right margin before the last ll. */
 	size_t		  rmargin;	/* Current right margin. */
 	size_t		  maxrmargin;	/* Max right margin. */
 	size_t		  maxcols;	/* Max size of buf. */
@@ -76,16 +74,18 @@ struct	termp {
 #define	TERMP_PREKEEP	 (1 << 6)	/* ...starting with the next one. */
 #define	TERMP_SKIPCHAR	 (1 << 7)	/* Skip the next character. */
 #define	TERMP_NOBREAK	 (1 << 8)	/* See term_flushln(). */
-#define	TERMP_DANGLE	 (1 << 9)	/* See term_flushln(). */
-#define	TERMP_HANG	 (1 << 10)	/* See term_flushln(). */
-#define	TERMP_NOSPLIT	 (1 << 11)	/* See termp_an_pre/post(). */
-#define	TERMP_SPLIT	 (1 << 12)	/* See termp_an_pre/post(). */
-#define	TERMP_ANPREC	 (1 << 13)	/* See termp_an_pre(). */
+#define	TERMP_BRIND	 (1 << 9)	/* See term_flushln(). */
+#define	TERMP_DANGLE	 (1 << 10)	/* See term_flushln(). */
+#define	TERMP_HANG	 (1 << 11)	/* See term_flushln(). */
+#define	TERMP_NOSPLIT	 (1 << 12)	/* Do not break line before .An. */
+#define	TERMP_SPLIT	 (1 << 13)	/* Break line before .An. */
+#define	TERMP_NONEWLINE	 (1 << 14)	/* No line break in nofill mode. */
 	int		 *buf;		/* Output buffer. */
 	enum termenc	  enc;		/* Type of encoding. */
-	struct mchars	 *symtab;	/* Encoded-symbol table. */
+	const struct mchars *symtab;	/* Character table. */
 	enum termfont	  fontl;	/* Last font set. */
-	enum termfont	  fontq[10];	/* Symmetric fonts. */
+	enum termfont	 *fontq;	/* Symmetric fonts. */
+	int		  fontsz;	/* Allocated size of font stack */
 	int		  fonti;	/* Index of font stack. */
 	term_margin	  headf;	/* invoked to print head */
 	term_margin	  footf;	/* invoked to print foot */
@@ -94,6 +94,7 @@ struct	termp {
 	void		(*end)(struct termp *);
 	void		(*endline)(struct termp *);
 	void		(*advance)(struct termp *, size_t);
+	void		(*setwidth)(struct termp *, int, size_t);
 	size_t		(*width)(const struct termp *, int);
 	double		(*hspan)(const struct termp *,
 				const struct roffsu *);
@@ -101,6 +102,13 @@ struct	termp {
 	struct termp_ps	 *ps;
 };
 
+__BEGIN_DECLS
+
+struct	tbl_span;
+struct	eqn;
+
+const char	 *ascii_uc2str(int);
+
 void		  term_eqn(struct termp *, const struct eqn *);
 void		  term_tbl(struct termp *, const struct tbl_span *);
 void		  term_free(struct termp *);
@@ -108,25 +116,20 @@ void		  term_newln(struct termp *);
 void		  term_vspace(struct termp *);
 void		  term_word(struct termp *, const char *);
 void		  term_flushln(struct termp *);
-void		  term_begin(struct termp *, term_margin, 
+void		  term_begin(struct termp *, term_margin,
 			term_margin, const void *);
 void		  term_end(struct termp *);
 
-size_t		  term_hspan(const struct termp *, 
-			const struct roffsu *);
-size_t		  term_vspan(const struct termp *,
-			const struct roffsu *);
+void		  term_setwidth(struct termp *, const char *);
+int		  term_hspan(const struct termp *, const struct roffsu *);
+int		  term_vspan(const struct termp *, const struct roffsu *);
 size_t		  term_strlen(const struct termp *, const char *);
 size_t		  term_len(const struct termp *, size_t);
 
-enum termfont	  term_fonttop(struct termp *);
-const void	 *term_fontq(struct termp *);
 void		  term_fontpush(struct termp *, enum termfont);
 void		  term_fontpop(struct termp *);
-void		  term_fontpopq(struct termp *, const void *);
+void		  term_fontpopq(struct termp *, int);
 void		  term_fontrepl(struct termp *, enum termfont);
 void		  term_fontlast(struct termp *);
 
 __END_DECLS
-
-#endif /*!TERM_H*/
diff --git a/usr/src/cmd/mandoc/term_ascii.c b/usr/src/cmd/mandoc/term_ascii.c
index cb7ac29405..4ce4b686c4 100644
--- a/usr/src/cmd/mandoc/term_ascii.c
+++ b/usr/src/cmd/mandoc/term_ascii.c
@@ -1,6 +1,7 @@
-/*	$Id: term_ascii.c,v 1.21 2013/06/01 14:27:20 schwarze Exp $ */
+/*	$Id: term_ascii.c,v 1.43 2015/02/16 14:11:41 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,41 +15,30 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
 #include 
-#ifdef USE_WCHAR
-# include 
+#if HAVE_WCHAR
+#include 
 #endif
 #include 
 #include 
 #include 
 #include 
-#ifdef USE_WCHAR
-# include 
+#if HAVE_WCHAR
+#include 
 #endif
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "out.h"
 #include "term.h"
 #include "main.h"
 
-/* 
- * Sadly, this doesn't seem to be defined on systems even when they
- * support it.  For the time being, remove it and let those compiling
- * the software decide for themselves what to use.
- */
-#if 0
-#if ! defined(__STDC_ISO_10646__)
-# undef USE_WCHAR
-#endif
-#endif
-
-static	struct termp	 *ascii_init(enum termenc, char *);
+static	struct termp	 *ascii_init(enum termenc,
+				const struct mchars *, char *);
 static	double		  ascii_hspan(const struct termp *,
 				const struct roffsu *);
 static	size_t		  ascii_width(const struct termp *, int);
@@ -57,25 +47,33 @@ static	void		  ascii_begin(struct termp *);
 static	void		  ascii_end(struct termp *);
 static	void		  ascii_endline(struct termp *);
 static	void		  ascii_letter(struct termp *, int);
+static	void		  ascii_setwidth(struct termp *, int, size_t);
 
-#ifdef	USE_WCHAR
+#if HAVE_WCHAR
 static	void		  locale_advance(struct termp *, size_t);
 static	void		  locale_endline(struct termp *);
 static	void		  locale_letter(struct termp *, int);
 static	size_t		  locale_width(const struct termp *, int);
 #endif
 
+
 static struct termp *
-ascii_init(enum termenc enc, char *outopts)
+ascii_init(enum termenc enc, const struct mchars *mchars, char *outopts)
 {
-	const char	*toks[4];
+	const char	*toks[5];
 	char		*v;
 	struct termp	*p;
+	const char	*errstr;
+	int		num;
 
 	p = mandoc_calloc(1, sizeof(struct termp));
 
+	p->symtab = mchars;
 	p->tabwidth = 5;
-	p->defrmargin = 78;
+	p->defrmargin = p->lastrmargin = 78;
+	p->fontq = mandoc_reallocarray(NULL,
+	     (p->fontsz = 8), sizeof(enum termfont));
+	p->fontq[0] = p->fontl = TERMFONT_NONE;
 
 	p->begin = ascii_begin;
 	p->end = ascii_end;
@@ -86,13 +84,14 @@ ascii_init(enum termenc enc, char *outopts)
 	p->advance = ascii_advance;
 	p->endline = ascii_endline;
 	p->letter = ascii_letter;
+	p->setwidth = ascii_setwidth;
 	p->width = ascii_width;
 
-#ifdef	USE_WCHAR
+#if HAVE_WCHAR
 	if (TERMENC_ASCII != enc) {
 		v = TERMENC_LOCALE == enc ?
-			setlocale(LC_ALL, "") :
-			setlocale(LC_CTYPE, "en_US.UTF-8");
+		    setlocale(LC_ALL, "") :
+		    setlocale(LC_CTYPE, "en_US.UTF-8");
 		if (NULL != v && MB_CUR_MAX > 1) {
 			p->enc = enc;
 			p->advance = locale_advance;
@@ -106,17 +105,22 @@ ascii_init(enum termenc enc, char *outopts)
 	toks[0] = "indent";
 	toks[1] = "width";
 	toks[2] = "mdoc";
-	toks[3] = NULL;
+	toks[3] = "synopsis";
+	toks[4] = NULL;
 
 	while (outopts && *outopts)
 		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
-		case (0):
-			p->defindent = (size_t)atoi(v);
+		case 0:
+			num = strtonum(v, 0, 1000, &errstr);
+			if (!errstr)
+				p->defindent = num;
 			break;
-		case (1):
-			p->defrmargin = (size_t)atoi(v);
+		case 1:
+			num = strtonum(v, 0, 1000, &errstr);
+			if (!errstr)
+				p->defrmargin = num;
 			break;
-		case (2):
+		case 2:
 			/*
 			 * Temporary, undocumented mode
 			 * to imitate mdoc(7) output style.
@@ -124,6 +128,9 @@ ascii_init(enum termenc enc, char *outopts)
 			p->mdocstyle = 1;
 			p->defindent = 5;
 			break;
+		case 3:
+			p->synopsisonly = 1;
+			break;
 		default:
 			break;
 		}
@@ -136,28 +143,57 @@ ascii_init(enum termenc enc, char *outopts)
 }
 
 void *
-ascii_alloc(char *outopts)
+ascii_alloc(const struct mchars *mchars, char *outopts)
 {
 
-	return(ascii_init(TERMENC_ASCII, outopts));
+	return(ascii_init(TERMENC_ASCII, mchars, outopts));
 }
 
 void *
-utf8_alloc(char *outopts)
+utf8_alloc(const struct mchars *mchars, char *outopts)
 {
 
-	return(ascii_init(TERMENC_UTF8, outopts));
+	return(ascii_init(TERMENC_UTF8, mchars, outopts));
 }
 
-
 void *
-locale_alloc(char *outopts)
+locale_alloc(const struct mchars *mchars, char *outopts)
+{
+
+	return(ascii_init(TERMENC_LOCALE, mchars, outopts));
+}
+
+static void
+ascii_setwidth(struct termp *p, int iop, size_t width)
+{
+
+	p->rmargin = p->defrmargin;
+	if (iop > 0)
+		p->defrmargin += width;
+	else if (iop == 0)
+		p->defrmargin = width ? width : p->lastrmargin;
+	else if (p->defrmargin > width)
+		p->defrmargin -= width;
+	else
+		p->defrmargin = 0;
+	p->lastrmargin = p->rmargin;
+	p->rmargin = p->maxrmargin = p->defrmargin;
+}
+
+void
+ascii_sepline(void *arg)
 {
+	struct termp	*p;
+	size_t		 i;
 
-	return(ascii_init(TERMENC_LOCALE, outopts));
+	p = (struct termp *)arg;
+	putchar('\n');
+	for (i = 0; i < p->defrmargin; i++)
+		putchar('-');
+	putchar('\n');
+	putchar('\n');
 }
 
-/* ARGSUSED */
 static size_t
 ascii_width(const struct termp *p, int c)
 {
@@ -172,11 +208,10 @@ ascii_free(void *arg)
 	term_free((struct termp *)arg);
 }
 
-/* ARGSUSED */
 static void
 ascii_letter(struct termp *p, int c)
 {
-	
+
 	putchar(c);
 }
 
@@ -194,7 +229,6 @@ ascii_end(struct termp *p)
 	(*p->footf)(p, p->argf);
 }
 
-/* ARGSUSED */
 static void
 ascii_endline(struct termp *p)
 {
@@ -202,75 +236,162 @@ ascii_endline(struct termp *p)
 	putchar('\n');
 }
 
-/* ARGSUSED */
 static void
 ascii_advance(struct termp *p, size_t len)
 {
-	size_t	 	i;
+	size_t		i;
 
 	for (i = 0; i < len; i++)
 		putchar(' ');
 }
 
-/* ARGSUSED */
 static double
 ascii_hspan(const struct termp *p, const struct roffsu *su)
 {
 	double		 r;
 
 	/*
-	 * Approximate based on character width.  These are generated
-	 * entirely by eyeballing the screen, but appear to be correct.
+	 * Approximate based on character width.
+	 * None of these will be actually correct given that an inch on
+	 * the screen depends on character size, terminal, etc., etc.
 	 */
-
 	switch (su->unit) {
-	case (SCALE_CM):
-		r = 4 * su->scale;
+	case SCALE_BU:
+		r = su->scale * 10.0 / 240.0;
 		break;
-	case (SCALE_IN):
-		r = 10 * su->scale;
+	case SCALE_CM:
+		r = su->scale * 10.0 / 2.54;
 		break;
-	case (SCALE_PC):
-		r = (10 * su->scale) / 6;
+	case SCALE_FS:
+		r = su->scale * 2730.666;
 		break;
-	case (SCALE_PT):
-		r = (10 * su->scale) / 72;
+	case SCALE_IN:
+		r = su->scale * 10.0;
 		break;
-	case (SCALE_MM):
-		r = su->scale / 1000;
+	case SCALE_MM:
+		r = su->scale / 100.0;
 		break;
-	case (SCALE_VS):
-		r = su->scale * 2 - 1;
+	case SCALE_PC:
+		r = su->scale * 10.0 / 6.0;
 		break;
-	default:
+	case SCALE_PT:
+		r = su->scale * 10.0 / 72.0;
+		break;
+	case SCALE_VS:
+		r = su->scale * 2.0 - 1.0;
+		break;
+	case SCALE_EN:
+		/* FALLTHROUGH */
+	case SCALE_EM:
 		r = su->scale;
 		break;
+	default:
+		abort();
+		/* NOTREACHED */
 	}
 
 	return(r);
 }
 
-#ifdef USE_WCHAR
-/* ARGSUSED */
+const char *
+ascii_uc2str(int uc)
+{
+	static const char nbrsp[2] = { ASCII_NBRSP, '\0' };
+	static const char *tab[] = {
+	"","","","","","","","",
+	"",	"\t",	"",	"",	"",	"",	"",	"",
+	"","","","","","","","",
+	"","",	"","","",	"",	"",	"",
+	" ",	"!",	"\"",	"#",	"$",	"%",	"&",	"'",
+	"(",	")",	"*",	"+",	",",	"-",	".",	"/",
+	"0",	"1",	"2",	"3",	"4",	"5",	"6",	"7",
+	"8",	"9",	":",	";",	"<",	"=",	">",	"?",
+	"@",	"A",	"B",	"C",	"D",	"E",	"F",	"G",
+	"H",	"I",	"J",	"K",	"L",	"M",	"N",	"O",
+	"P",	"Q",	"R",	"S",	"T",	"U",	"V",	"W",
+	"X",	"Y",	"Z",	"[",	"\\",	"]",	"^",	"_",
+	"`",	"a",	"b",	"c",	"d",	"e",	"f",	"g",
+	"h",	"i",	"j",	"k",	"l",	"m",	"n",	"o",
+	"p",	"q",	"r",	"s",	"t",	"u",	"v",	"w",
+	"x",	"y",	"z",	"{",	"|",	"}",	"~",	"",
+	"<80>",	"<81>",	"<82>",	"<83>",	"<84>",	"<85>",	"<86>",	"<87>",
+	"<88>",	"<89>",	"<8A>",	"<8B>",	"<8C>",	"<8D>",	"<8E>",	"<8F>",
+	"<90>",	"<91>",	"<92>",	"<93>",	"<94>",	"<95>",	"<96>",	"<97>",
+	"<99>",	"<99>",	"<9A>",	"<9B>",	"<9C>",	"<9D>",	"<9E>",	"<9F>",
+	nbrsp,	"!",	"/\bc",	"GBP",	"o\bx",	"=\bY",	"|",	"",
+	"\"",	"(C)",	"_\ba",	"<<",	"~",	"",	"(R)",	"-",
+	"","+-",	"2",	"3",	"'",	",\bu",	"",".",
+	",",	"1",	"_\bo",	">>",	"1/4",	"1/2",	"3/4",	"?",
+	"`\bA",	"'\bA",	"^\bA",	"~\bA",	"\"\bA","o\bA",	"AE",	",\bC",
+	"`\bE",	"'\bE",	"^\bE",	"\"\bE","`\bI",	"'\bI",	"^\bI",	"\"\bI",
+	"-\bD",	"~\bN",	"`\bO",	"'\bO",	"^\bO",	"~\bO",	"\"\bO","x",
+	"/\bO",	"`\bU",	"'\bU",	"^\bU",	"\"\bU","'\bY",	"Th",	"ss",
+	"`\ba",	"'\ba",	"^\ba",	"~\ba",	"\"\ba","o\ba",	"ae",	",\bc",
+	"`\be",	"'\be",	"^\be",	"\"\be","`\bi",	"'\bi",	"^\bi",	"\"\bi",
+	"d",	"~\bn",	"`\bo",	"'\bo",	"^\bo",	"~\bo",	"\"\bo","-:-",
+	"/\bo",	"`\bu",	"'\bu",	"^\bu",	"\"\bu","'\by",	"th",	"\"\by",
+	"A",	"a",	"A",	"a",	"A",	"a",	"'\bC",	"'\bc",
+	"^\bC",	"^\bc",	"C",	"c",	"C",	"c",	"D",	"d",
+	"/\bD",	"/\bd",	"E",	"e",	"E",	"e",	"E",	"e",
+	"E",	"e",	"E",	"e",	"^\bG",	"^\bg",	"G",	"g",
+	"G",	"g",	",\bG",	",\bg",	"^\bH",	"^\bh",	"/\bH",	"/\bh",
+	"~\bI",	"~\bi",	"I",	"i",	"I",	"i",	"I",	"i",
+	"I",	"i",	"IJ",	"ij",	"^\bJ",	"^\bj",	",\bK",	",\bk",
+	"q",	"'\bL",	"'\bl",	",\bL",	",\bl",	"L",	"l",	"L",
+	"l",	"/\bL",	"/\bl",	"'\bN",	"'\bn",	",\bN",	",\bn",	"N",
+	"n",	"'n",	"Ng",	"ng",	"O",	"o",	"O",	"o",
+	"O",	"o",	"OE",	"oe",	"'\bR",	"'\br",	",\bR",	",\br",
+	"R",	"r",	"'\bS",	"'\bs",	"^\bS",	"^\bs",	",\bS",	",\bs",
+	"S",	"s",	",\bT",	",\bt",	"T",	"t",	"/\bT",	"/\bt",
+	"~\bU",	"~\bu",	"U",	"u",	"U",	"u",	"U",	"u",
+	"U",	"u",	"U",	"u",	"^\bW",	"^\bw",	"^\bY",	"^\by",
+	"\"\bY","'\bZ",	"'\bz",	"Z",	"z",	"Z",	"z",	"s",
+	"b",	"B",	"B",	"b",	"6",	"6",	"O",	"C",
+	"c",	"D",	"D",	"D",	"d",	"d",	"3",	"@",
+	"E",	"F",	",\bf",	"G",	"G",	"hv",	"I",	"/\bI",
+	"K",	"k",	"/\bl",	"l",	"W",	"N",	"n",	"~\bO",
+	"O",	"o",	"OI",	"oi",	"P",	"p",	"YR",	"2",
+	"2",	"SH",	"sh",	"t",	"T",	"t",	"T",	"U",
+	"u",	"Y",	"V",	"Y",	"y",	"/\bZ",	"/\bz",	"ZH",
+	"ZH",	"zh",	"zh",	"/\b2",	"5",	"5",	"ts",	"w",
+	"|",	"||",	"|=",	"!",	"DZ",	"Dz",	"dz",	"LJ",
+	"Lj",	"lj",	"NJ",	"Nj",	"nj",	"A",	"a",	"I",
+	"i",	"O",	"o",	"U",	"u",	"U",	"u",	"U",
+	"u",	"U",	"u",	"U",	"u",	"@",	"A",	"a",
+	"A",	"a",	"AE",	"ae",	"/\bG",	"/\bg",	"G",	"g",
+	"K",	"k",	"O",	"o",	"O",	"o",	"ZH",	"zh",
+	"j",	"DZ",	"Dz",	"dz",	"'\bG",	"'\bg",	"HV",	"W",
+	"`\bN",	"`\bn",	"A",	"a",	"'\bAE","'\bae","O",	"o"};
+
+	assert(uc >= 0);
+	if ((size_t)uc < sizeof(tab)/sizeof(tab[0]))
+		return(tab[uc]);
+	return(mchars_uc2str(uc));
+}
+
+#if HAVE_WCHAR
 static size_t
 locale_width(const struct termp *p, int c)
 {
 	int		rc;
 
-	return((rc = wcwidth(c)) < 0 ? 0 : rc);
+	if (c == ASCII_NBRSP)
+		c = ' ';
+	rc = wcwidth(c);
+	if (rc < 0)
+		rc = 0;
+	return(rc);
 }
 
-/* ARGSUSED */
 static void
 locale_advance(struct termp *p, size_t len)
 {
-	size_t	 	i;
+	size_t		i;
 
 	for (i = 0; i < len; i++)
 		putwchar(L' ');
 }
 
-/* ARGSUSED */
 static void
 locale_endline(struct termp *p)
 {
@@ -278,11 +399,10 @@ locale_endline(struct termp *p)
 	putwchar(L'\n');
 }
 
-/* ARGSUSED */
 static void
 locale_letter(struct termp *p, int c)
 {
-	
+
 	putwchar(c);
 }
 #endif
diff --git a/usr/src/cmd/mandoc/term_ps.c b/usr/src/cmd/mandoc/term_ps.c
index e8a906858a..12ca7d6716 100644
--- a/usr/src/cmd/mandoc/term_ps.c
+++ b/usr/src/cmd/mandoc/term_ps.c
@@ -1,6 +1,7 @@
-/*	$Id: term_ps.c,v 1.54 2011/10/16 12:20:34 schwarze Exp $ */
+/*	$Id: term_ps.c,v 1.72 2015/01/21 19:40:54 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
+ * Copyright (c) 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,9 +15,7 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #include 
 
@@ -26,23 +25,22 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 
-#include "mandoc.h"
+#include "mandoc_aux.h"
 #include "out.h"
-#include "main.h"
 #include "term.h"
+#include "main.h"
 
 /* These work the buffer used by the header and footer. */
 #define	PS_BUFSLOP	  128
 
 /* Convert PostScript point "x" to an AFM unit. */
-#define	PNT2AFM(p, x) /* LINTED */ \
+#define	PNT2AFM(p, x) \
 	(size_t)((double)(x) * (1000.0 / (double)(p)->ps->scale))
 
 /* Convert an AFM unit "x" to a PostScript points */
-#define	AFM2PNT(p, x) /* LINTED */ \
+#define	AFM2PNT(p, x) \
 	((double)(x) / (1000.0 / (double)(p)->ps->scale))
 
 struct	glyph {
@@ -60,13 +58,16 @@ struct	termp_ps {
 #define	PS_INLINE	 (1 << 0)	/* we're in a word */
 #define	PS_MARGINS	 (1 << 1)	/* we're in the margins */
 #define	PS_NEWPAGE	 (1 << 2)	/* new page, no words yet */
+#define	PS_BACKSP	 (1 << 3)	/* last character was backspace */
 	size_t		  pscol;	/* visible column (AFM units) */
+	size_t		  pscolnext;	/* used for overstrike */
 	size_t		  psrow;	/* visible row (AFM units) */
 	char		 *psmarg;	/* margin buf */
 	size_t		  psmargsz;	/* margin buf size */
 	size_t		  psmargcur;	/* cur index in margin buf */
-	char		  last;		/* character buffer */
+	char		  last;		/* last non-backspace seen */
 	enum termfont	  lastf;	/* last set font */
+	enum termfont	  nextf;	/* building next font here */
 	size_t		  scale;	/* font scaling factor */
 	size_t		  pages;	/* number of pages shown */
 	size_t		  lineheight;	/* line height (AFM units) */
@@ -74,10 +75,11 @@ struct	termp_ps {
 	size_t		  bottom;	/* body bottom (AFM units) */
 	size_t		  height;	/* page height (AFM units */
 	size_t		  width;	/* page width (AFM units) */
+	size_t		  lastwidth;	/* page width before last ll */
 	size_t		  left;		/* body left (AFM units) */
 	size_t		  header;	/* header pos (AFM units) */
 	size_t		  footer;	/* footer pos (AFM units) */
-	size_t		  pdfbytes; 	/* current output byte */
+	size_t		  pdfbytes;	/* current output byte */
 	size_t		  pdflastpg;	/* byte of last page mark */
 	size_t		  pdfbody;	/* start of body object */
 	size_t		 *pdfobjs;	/* table of object offsets */
@@ -97,10 +99,14 @@ static	void		  ps_growbuf(struct termp *, size_t);
 static	void		  ps_letter(struct termp *, int);
 static	void		  ps_pclose(struct termp *);
 static	void		  ps_pletter(struct termp *, int);
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 2, 3)))
+#endif
 static	void		  ps_printf(struct termp *, const char *, ...);
 static	void		  ps_putchar(struct termp *, char);
 static	void		  ps_setfont(struct termp *, enum termfont);
-static	struct termp	 *pspdf_alloc(char *);
+static	void		  ps_setwidth(struct termp *, int, size_t);
+static	struct termp	 *pspdf_alloc(const struct mchars *, char *);
 static	void		  pdf_obj(struct termp *, size_t);
 
 /*
@@ -401,32 +407,129 @@ static	const struct font fonts[TERMFONT__MAX] = {
 		{  400 },
 		{  541 },
 	} },
+	{ "Times-BoldItalic", {
+		{  250 },
+		{  389 },
+		{  555 },
+		{  500 },
+		{  500 },
+		{  833 },
+		{  778 },
+		{  333 },
+		{  333 },
+		{  333 },
+		{  500 },
+		{  570 },
+		{  250 },
+		{  333 },
+		{  250 },
+		{  278 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  333 },
+		{  333 },
+		{  570 },
+		{  570 },
+		{  570 },
+		{  500 },
+		{  832 },
+		{  667 },
+		{  667 },
+		{  667 },
+		{  722 },
+		{  667 },
+		{  667 },
+		{  722 },
+		{  778 },
+		{  389 },
+		{  500 },
+		{  667 },
+		{  611 },
+		{  889 },
+		{  722 },
+		{  722 },
+		{  611 },
+		{  722 },
+		{  667 },
+		{  556 },
+		{  611 },
+		{  722 },
+		{  667 },
+		{  889 },
+		{  667 },
+		{  611 },
+		{  611 },
+		{  333 },
+		{  278 },
+		{  333 },
+		{  570 },
+		{  500 },
+		{  333 },
+		{  500 },
+		{  500 },
+		{  444 },
+		{  500 },
+		{  444 },
+		{  333 },
+		{  500 },
+		{  556 },
+		{  278 },
+		{  278 },
+		{  500 },
+		{  278 },
+		{  778 },
+		{  556 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  389 },
+		{  389 },
+		{  278 },
+		{  556 },
+		{  444 },
+		{  667 },
+		{  500 },
+		{  444 },
+		{  389 },
+		{  348 },
+		{  220 },
+		{  348 },
+		{  570 },
+	} },
 };
 
 void *
-pdf_alloc(char *outopts)
+pdf_alloc(const struct mchars *mchars, char *outopts)
 {
 	struct termp	*p;
 
-	if (NULL != (p = pspdf_alloc(outopts)))
+	if (NULL != (p = pspdf_alloc(mchars, outopts)))
 		p->type = TERMTYPE_PDF;
 
 	return(p);
 }
 
 void *
-ps_alloc(char *outopts)
+ps_alloc(const struct mchars *mchars, char *outopts)
 {
 	struct termp	*p;
 
-	if (NULL != (p = pspdf_alloc(outopts)))
+	if (NULL != (p = pspdf_alloc(mchars, outopts)))
 		p->type = TERMTYPE_PS;
 
 	return(p);
 }
 
 static struct termp *
-pspdf_alloc(char *outopts)
+pspdf_alloc(const struct mchars *mchars, char *outopts)
 {
 	struct termp	*p;
 	unsigned int	 pagex, pagey;
@@ -436,7 +539,11 @@ pspdf_alloc(char *outopts)
 	char		*v;
 
 	p = mandoc_calloc(1, sizeof(struct termp));
+	p->symtab = mchars;
 	p->enc = TERMENC_ASCII;
+	p->fontq = mandoc_reallocarray(NULL,
+	    (p->fontsz = 8), sizeof(enum termfont));
+	p->fontq[0] = p->fontl = TERMFONT_NONE;
 	p->ps = mandoc_calloc(1, sizeof(struct termp_ps));
 
 	p->advance = ps_advance;
@@ -445,8 +552,9 @@ pspdf_alloc(char *outopts)
 	p->endline = ps_endline;
 	p->hspan = ps_hspan;
 	p->letter = ps_letter;
+	p->setwidth = ps_setwidth;
 	p->width = ps_width;
-	
+
 	toks[0] = "paper";
 	toks[1] = NULL;
 
@@ -454,7 +562,7 @@ pspdf_alloc(char *outopts)
 
 	while (outopts && *outopts)
 		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
-		case (0):
+		case 0:
 			pp = v;
 			break;
 		default:
@@ -490,7 +598,7 @@ pspdf_alloc(char *outopts)
 			fprintf(stderr, "%s: Unknown paper\n", pp);
 	}
 
-	/* 
+	/*
 	 * This MUST be defined before any PNT2AFM or AFM2PNT
 	 * calculations occur.
 	 */
@@ -504,16 +612,14 @@ pspdf_alloc(char *outopts)
 
 	/* Margins are 1/9 the page x and y. */
 
-	marginx = /* LINTED */
-		(size_t)((double)pagex / 9.0);
-	marginy = /* LINTED */
-		(size_t)((double)pagey / 9.0);
+	marginx = (size_t)((double)pagex / 9.0);
+	marginy = (size_t)((double)pagey / 9.0);
 
 	/* Line-height is 1.4em. */
 
 	lineheight = PNT2AFM(p, ((double)p->ps->scale * 1.4));
 
-	p->ps->width = (size_t)pagex;
+	p->ps->width = p->ps->lastwidth = (size_t)pagex;
 	p->ps->height = (size_t)pagey;
 	p->ps->header = pagey - (marginy / 2) - (lineheight / 2);
 	p->ps->top = pagey - marginy;
@@ -526,6 +632,22 @@ pspdf_alloc(char *outopts)
 	return(p);
 }
 
+static void
+ps_setwidth(struct termp *p, int iop, size_t width)
+{
+	size_t	 lastwidth;
+
+	lastwidth = p->ps->width;
+	if (iop > 0)
+		p->ps->width += width;
+	else if (iop == 0)
+		p->ps->width = width ? width : p->ps->lastwidth;
+	else if (p->ps->width > width)
+		p->ps->width -= width;
+	else
+		p->ps->width = 0;
+	p->ps->lastwidth = lastwidth;
+}
 
 void
 pspdf_free(void *arg)
@@ -543,7 +665,6 @@ pspdf_free(void *arg)
 	term_free(p);
 }
 
-
 static void
 ps_printf(struct termp *p, const char *fmt, ...)
 {
@@ -561,12 +682,11 @@ ps_printf(struct termp *p, const char *fmt, ...)
 	if ( ! (PS_MARGINS & p->ps->flags)) {
 		len = vprintf(fmt, ap);
 		va_end(ap);
-		p->ps->pdfbytes += /* LINTED */
-			len < 0 ? 0 : (size_t)len;
+		p->ps->pdfbytes += len < 0 ? 0 : (size_t)len;
 		return;
 	}
 
-	/* 
+	/*
 	 * XXX: I assume that the in-margin print won't exceed
 	 * PS_BUFSLOP (128 bytes), which is reasonable but still an
 	 * assumption that will cause pukeage if it's not the case.
@@ -582,7 +702,6 @@ ps_printf(struct termp *p, const char *fmt, ...)
 	p->ps->psmargcur = strlen(p->ps->psmarg);
 }
 
-
 static void
 ps_putchar(struct termp *p, char c)
 {
@@ -591,7 +710,6 @@ ps_putchar(struct termp *p, char c)
 	/* See ps_printf(). */
 
 	if ( ! (PS_MARGINS & p->ps->flags)) {
-		/* LINTED */
 		putchar(c);
 		p->ps->pdfbytes++;
 		return;
@@ -604,7 +722,6 @@ ps_putchar(struct termp *p, char c)
 	p->ps->psmarg[pos] = '\0';
 }
 
-
 static void
 pdf_obj(struct termp *p, size_t obj)
 {
@@ -613,20 +730,14 @@ pdf_obj(struct termp *p, size_t obj)
 
 	if ((obj - 1) >= p->ps->pdfobjsz) {
 		p->ps->pdfobjsz = obj + 128;
-		p->ps->pdfobjs = realloc
-			(p->ps->pdfobjs, 
-			 p->ps->pdfobjsz * sizeof(size_t));
-		if (NULL == p->ps->pdfobjs) {
-			perror(NULL);
-			exit((int)MANDOCLEVEL_SYSERR);
-		}
+		p->ps->pdfobjs = mandoc_reallocarray(p->ps->pdfobjs,
+		    p->ps->pdfobjsz, sizeof(size_t));
 	}
 
 	p->ps->pdfobjs[(int)obj - 1] = p->ps->pdfbytes;
 	ps_printf(p, "%zu 0 obj\n", obj);
 }
 
-
 static void
 ps_closepage(struct termp *p)
 {
@@ -659,7 +770,7 @@ ps_closepage(struct termp *p)
 		pdf_obj(p, base + 2);
 		ps_printf(p, "<<\n/ProcSet [/PDF /Text]\n");
 		ps_printf(p, "/Font <<\n");
-		for (i = 0; i < (int)TERMFONT__MAX; i++) 
+		for (i = 0; i < (int)TERMFONT__MAX; i++)
 			ps_printf(p, "/F%d %d 0 R\n", i, 3 + i);
 		ps_printf(p, ">>\n>>\n");
 
@@ -680,8 +791,6 @@ ps_closepage(struct termp *p)
 	p->ps->flags |= PS_NEWPAGE;
 }
 
-
-/* ARGSUSED */
 static void
 ps_end(struct termp *p)
 {
@@ -704,7 +813,7 @@ ps_end(struct termp *p)
 		ps_printf(p, "%%%%Pages: %zu\n", p->ps->pages);
 		ps_printf(p, "%%%%EOF\n");
 		return;
-	} 
+	}
 
 	pdf_obj(p, 2);
 	ps_printf(p, "<<\n/Type /Pages\n");
@@ -716,11 +825,9 @@ ps_end(struct termp *p)
 	ps_printf(p, "/Kids [");
 
 	for (i = 0; i < p->ps->pages; i++)
-		ps_printf(p, " %zu 0 R", i * 4 +
-				p->ps->pdfbody + 3);
+		ps_printf(p, " %zu 0 R", i * 4 + p->ps->pdfbody + 3);
 
-	base = (p->ps->pages - 1) * 4 + 
-		p->ps->pdfbody + 4;
+	base = (p->ps->pages - 1) * 4 + p->ps->pdfbody + 4;
 
 	ps_printf(p, "]\n>>\nendobj\n");
 	pdf_obj(p, base);
@@ -734,8 +841,8 @@ ps_end(struct termp *p)
 	ps_printf(p, "0000000000 65535 f \n");
 
 	for (i = 0; i < base; i++)
-		ps_printf(p, "%.10zu 00000 n \n", 
-				p->ps->pdfobjs[(int)i]);
+		ps_printf(p, "%.10zu 00000 n \n",
+		    p->ps->pdfobjs[(int)i]);
 
 	ps_printf(p, "trailer\n");
 	ps_printf(p, "<<\n");
@@ -748,14 +855,12 @@ ps_end(struct termp *p)
 	ps_printf(p, "%%%%EOF\n");
 }
 
-
 static void
 ps_begin(struct termp *p)
 {
-	time_t		 t;
 	int		 i;
 
-	/* 
+	/*
 	 * Print margins into margin buffer.  Nothing gets output to the
 	 * screen yet, so we don't need to initialise the primary state.
 	 */
@@ -788,24 +893,21 @@ ps_begin(struct termp *p)
 	assert(p->ps->psmarg);
 	assert('\0' != p->ps->psmarg[0]);
 
-	/* 
+	/*
 	 * Print header and initialise page state.  Following this,
 	 * stuff gets printed to the screen, so make sure we're sane.
 	 */
 
-	t = time(NULL);
-
 	if (TERMTYPE_PS == p->type) {
 		ps_printf(p, "%%!PS-Adobe-3.0\n");
-		ps_printf(p, "%%%%CreationDate: %s", ctime(&t));
 		ps_printf(p, "%%%%DocumentData: Clean7Bit\n");
 		ps_printf(p, "%%%%Orientation: Portrait\n");
 		ps_printf(p, "%%%%Pages: (atend)\n");
 		ps_printf(p, "%%%%PageOrder: Ascend\n");
 		ps_printf(p, "%%%%DocumentMedia: "
-				"Default %zu %zu 0 () ()\n",
-				(size_t)AFM2PNT(p, p->ps->width),
-				(size_t)AFM2PNT(p, p->ps->height));
+		    "Default %zu %zu 0 () ()\n",
+		    (size_t)AFM2PNT(p, p->ps->width),
+		    (size_t)AFM2PNT(p, p->ps->height));
 		ps_printf(p, "%%%%DocumentNeededResources: font");
 
 		for (i = 0; i < (int)TERMFONT__MAX; i++)
@@ -824,7 +926,7 @@ ps_begin(struct termp *p)
 			ps_printf(p, "<<\n");
 			ps_printf(p, "/Type /Font\n");
 			ps_printf(p, "/Subtype /Type1\n");
-			ps_printf(p, "/Name /F%zu\n", i);
+			ps_printf(p, "/Name /F%d\n", i);
 			ps_printf(p, "/BaseFont /%s\n", fonts[i].name);
 			ps_printf(p, ">>\n");
 		}
@@ -837,7 +939,6 @@ ps_begin(struct termp *p)
 	ps_setfont(p, TERMFONT_NONE);
 }
 
-
 static void
 ps_pletter(struct termp *p, int c)
 {
@@ -850,25 +951,23 @@ ps_pletter(struct termp *p, int c)
 
 	if (PS_NEWPAGE & p->ps->flags) {
 		if (TERMTYPE_PS == p->type) {
-			ps_printf(p, "%%%%Page: %zu %zu\n", 
-					p->ps->pages + 1, 
-					p->ps->pages + 1);
-			ps_printf(p, "/%s %zu selectfont\n", 
-					fonts[(int)p->ps->lastf].name, 
-					p->ps->scale);
+			ps_printf(p, "%%%%Page: %zu %zu\n",
+			    p->ps->pages + 1, p->ps->pages + 1);
+			ps_printf(p, "/%s %zu selectfont\n",
+			    fonts[(int)p->ps->lastf].name,
+			    p->ps->scale);
 		} else {
-			pdf_obj(p, p->ps->pdfbody + 
-					p->ps->pages * 4);
+			pdf_obj(p, p->ps->pdfbody +
+			    p->ps->pages * 4);
 			ps_printf(p, "<<\n");
-			ps_printf(p, "/Length %zu 0 R\n", 
-					p->ps->pdfbody + 1 +
-					p->ps->pages * 4);
+			ps_printf(p, "/Length %zu 0 R\n",
+			    p->ps->pdfbody + 1 + p->ps->pages * 4);
 			ps_printf(p, ">>\nstream\n");
 		}
 		p->ps->pdflastpg = p->ps->pdfbytes;
 		p->ps->flags &= ~PS_NEWPAGE;
 	}
-	
+
 	/*
 	 * If we're not in a PostScript "word" context, then open one
 	 * now at the current cursor.
@@ -876,16 +975,15 @@ ps_pletter(struct termp *p, int c)
 
 	if ( ! (PS_INLINE & p->ps->flags)) {
 		if (TERMTYPE_PS != p->type) {
-			ps_printf(p, "BT\n/F%d %zu Tf\n", 
-					(int)p->ps->lastf,
-					p->ps->scale);
+			ps_printf(p, "BT\n/F%d %zu Tf\n",
+			    (int)p->ps->lastf, p->ps->scale);
 			ps_printf(p, "%.3f %.3f Td\n(",
-					AFM2PNT(p, p->ps->pscol),
-					AFM2PNT(p, p->ps->psrow));
+			    AFM2PNT(p, p->ps->pscol),
+			    AFM2PNT(p, p->ps->psrow));
 		} else
-			ps_printf(p, "%.3f %.3f moveto\n(", 
-					AFM2PNT(p, p->ps->pscol),
-					AFM2PNT(p, p->ps->psrow));
+			ps_printf(p, "%.3f %.3f moveto\n(",
+			    AFM2PNT(p, p->ps->pscol),
+			    AFM2PNT(p, p->ps->psrow));
 		p->ps->flags |= PS_INLINE;
 	}
 
@@ -899,11 +997,11 @@ ps_pletter(struct termp *p, int c)
 	 */
 
 	switch (c) {
-	case ('('):
+	case '(':
 		/* FALLTHROUGH */
-	case (')'):
+	case ')':
 		/* FALLTHROUGH */
-	case ('\\'):
+	case '\\':
 		ps_putchar(p, '\\');
 		break;
 	default:
@@ -914,23 +1012,19 @@ ps_pletter(struct termp *p, int c)
 
 	f = (int)p->ps->lastf;
 
-	if (c <= 32 || (c - 32 >= MAXCHAR)) {
-		ps_putchar(p, ' ');
-		p->ps->pscol += (size_t)fonts[f].gly[0].wx;
-		return;
-	} 
+	if (c <= 32 || c - 32 >= MAXCHAR)
+		c = 32;
 
 	ps_putchar(p, (char)c);
 	c -= 32;
 	p->ps->pscol += (size_t)fonts[f].gly[c].wx;
 }
 
-
 static void
 ps_pclose(struct termp *p)
 {
 
-	/* 
+	/*
 	 * Spit out that we're exiting a word context (this is a
 	 * "partial close" because we don't check the last-char buffer
 	 * or anything).
@@ -938,7 +1032,7 @@ ps_pclose(struct termp *p)
 
 	if ( ! (PS_INLINE & p->ps->flags))
 		return;
-	
+
 	if (TERMTYPE_PS != p->type) {
 		ps_printf(p, ") Tj\nET\n");
 	} else
@@ -947,7 +1041,6 @@ ps_pclose(struct termp *p)
 	p->ps->flags &= ~PS_INLINE;
 }
 
-
 static void
 ps_fclose(struct termp *p)
 {
@@ -960,11 +1053,13 @@ ps_fclose(struct termp *p)
 	 * Following this, close out any scope that's open.
 	 */
 
-	if ('\0' != p->ps->last) {
-		if (p->ps->lastf != TERMFONT_NONE) {
+	if (p->ps->last != '\0') {
+		assert( ! (p->ps->flags & PS_BACKSP));
+		if (p->ps->nextf != p->ps->lastf) {
 			ps_pclose(p);
-			ps_setfont(p, TERMFONT_NONE);
+			ps_setfont(p, p->ps->nextf);
 		}
+		p->ps->nextf = TERMFONT_NONE;
 		ps_pletter(p, p->ps->last);
 		p->ps->last = '\0';
 	}
@@ -975,57 +1070,132 @@ ps_fclose(struct termp *p)
 	ps_pclose(p);
 }
 
-
 static void
 ps_letter(struct termp *p, int arg)
 {
-	char		cc, c;
+	size_t		savecol, wx;
+	char		c;
 
-	/* LINTED */
 	c = arg >= 128 || arg <= 0 ? '?' : arg;
 
 	/*
-	 * State machine dictates whether to buffer the last character
-	 * or not.  Basically, encoded words are detected by checking if
-	 * we're an "8" and switching on the buffer.  Then we put "8" in
-	 * our buffer, and on the next charater, flush both character
-	 * and buffer.  Thus, "regular" words are detected by having a
-	 * regular character and a regular buffer character.
+	 * When receiving a backspace, merely flag it.
+	 * We don't know yet whether it is
+	 * a font instruction or an overstrike.
 	 */
 
-	if ('\0' == p->ps->last) {
-		assert(8 != c);
-		p->ps->last = c;
+	if (c == '\b') {
+		assert(p->ps->last != '\0');
+		assert( ! (p->ps->flags & PS_BACKSP));
+		p->ps->flags |= PS_BACKSP;
 		return;
-	} else if (8 == p->ps->last) {
-		assert(8 != c);
-		p->ps->last = '\0';
-	} else if (8 == c) {
-		assert(8 != p->ps->last);
-		if ('_' == p->ps->last) {
-			if (p->ps->lastf != TERMFONT_UNDER) {
-				ps_pclose(p);
-				ps_setfont(p, TERMFONT_UNDER);
+	}
+
+	/*
+	 * Decode font instructions.
+	 */
+
+	if (p->ps->flags & PS_BACKSP) {
+		if (p->ps->last == '_') {
+			switch (p->ps->nextf) {
+			case TERMFONT_BI:
+				break;
+			case TERMFONT_BOLD:
+				p->ps->nextf = TERMFONT_BI;
+				break;
+			default:
+				p->ps->nextf = TERMFONT_UNDER;
+			}
+			p->ps->last = c;
+			p->ps->flags &= ~PS_BACKSP;
+			return;
+		}
+		if (p->ps->last == c) {
+			switch (p->ps->nextf) {
+			case TERMFONT_BI:
+				break;
+			case TERMFONT_UNDER:
+				p->ps->nextf = TERMFONT_BI;
+				break;
+			default:
+				p->ps->nextf = TERMFONT_BOLD;
 			}
-		} else if (p->ps->lastf != TERMFONT_BOLD) {
+			p->ps->flags &= ~PS_BACKSP;
+			return;
+		}
+
+		/*
+		 * This is not a font instruction, but rather
+		 * the next character.  Prepare for overstrike.
+		 */
+
+		savecol = p->ps->pscol;
+	} else
+		savecol = SIZE_MAX;
+
+	/*
+	 * We found the next character, so the font instructions
+	 * for the previous one are complete.
+	 * Use them and print it.
+	 */
+
+	if (p->ps->last != '\0') {
+		if (p->ps->nextf != p->ps->lastf) {
 			ps_pclose(p);
-			ps_setfont(p, TERMFONT_BOLD);
+			ps_setfont(p, p->ps->nextf);
 		}
-		p->ps->last = c;
-		return;
-	} else {
-		if (p->ps->lastf != TERMFONT_NONE) {
+		p->ps->nextf = TERMFONT_NONE;
+
+		/*
+		 * For an overstrike, if a previous character
+		 * was wider, advance to center the new one.
+		 */
+
+		if (p->ps->pscolnext) {
+			wx = fonts[p->ps->lastf].gly[(int)p->ps->last-32].wx;
+			if (p->ps->pscol + wx < p->ps->pscolnext)
+				p->ps->pscol = (p->ps->pscol +
+				    p->ps->pscolnext - wx) / 2;
+		}
+
+		ps_pletter(p, p->ps->last);
+
+		/*
+		 * For an overstrike, if a previous character
+		 * was wider, advance to the end of the old one.
+		 */
+
+		if (p->ps->pscol < p->ps->pscolnext) {
 			ps_pclose(p);
-			ps_setfont(p, TERMFONT_NONE);
+			p->ps->pscol = p->ps->pscolnext;
 		}
-		cc = p->ps->last;
-		p->ps->last = c;
-		c = cc;
 	}
 
-	ps_pletter(p, c);
-}
+	/*
+	 * Do not print the current character yet because font
+	 * instructions might follow; only remember it.
+	 * For the first character, nothing else is done.
+	 * The final character will get printed from ps_fclose().
+	 */
+
+	p->ps->last = c;
+
+	/*
+	 * For an overstrike, back up to the previous position.
+	 * If the previous character is wider than any it overstrikes,
+	 * remember the current position, because it might also be
+	 * wider than all that will overstrike it.
+	 */
 
+	if (savecol != SIZE_MAX) {
+		if (p->ps->pscolnext < p->ps->pscol)
+			p->ps->pscolnext = p->ps->pscol;
+		ps_pclose(p);
+		p->ps->pscol = savecol;
+		p->ps->flags &= ~PS_BACKSP;
+	} else
+		p->ps->pscolnext = 0;
+}
 
 static void
 ps_advance(struct termp *p, size_t len)
@@ -1042,7 +1212,6 @@ ps_advance(struct termp *p, size_t len)
 	p->ps->pscol += len;
 }
 
-
 static void
 ps_endline(struct termp *p)
 {
@@ -1054,7 +1223,7 @@ ps_endline(struct termp *p)
 	/*
 	 * If we're in the margin, don't try to recalculate our current
 	 * row.  XXX: if the column tries to be fancy with multiple
-	 * lines, we'll do nasty stuff. 
+	 * lines, we'll do nasty stuff.
 	 */
 
 	if (PS_MARGINS & p->ps->flags)
@@ -1074,8 +1243,7 @@ ps_endline(struct termp *p)
 	 * showpage and restart our row.
 	 */
 
-	if (p->ps->psrow >= p->ps->lineheight + 
-			p->ps->bottom) {
+	if (p->ps->psrow >= p->ps->lineheight + p->ps->bottom) {
 		p->ps->psrow -= p->ps->lineheight;
 		return;
 	}
@@ -1083,14 +1251,13 @@ ps_endline(struct termp *p)
 	ps_closepage(p);
 }
 
-
 static void
 ps_setfont(struct termp *p, enum termfont f)
 {
 
 	assert(f < TERMFONT__MAX);
 	p->ps->lastf = f;
-	
+
 	/*
 	 * If we're still at the top of the page, let the font-setting
 	 * be delayed until we actually have stuff to print.
@@ -1100,64 +1267,70 @@ ps_setfont(struct termp *p, enum termfont f)
 		return;
 
 	if (TERMTYPE_PS == p->type)
-		ps_printf(p, "/%s %zu selectfont\n", 
-				fonts[(int)f].name, 
-				p->ps->scale);
+		ps_printf(p, "/%s %zu selectfont\n",
+		    fonts[(int)f].name, p->ps->scale);
 	else
-		ps_printf(p, "/F%d %zu Tf\n", 
-				(int)f, 
-				p->ps->scale);
+		ps_printf(p, "/F%d %zu Tf\n",
+		    (int)f, p->ps->scale);
 }
 
-
-/* ARGSUSED */
 static size_t
 ps_width(const struct termp *p, int c)
 {
 
 	if (c <= 32 || c - 32 >= MAXCHAR)
-		return((size_t)fonts[(int)TERMFONT_NONE].gly[0].wx);
+		c = 0;
+	else
+		c -= 32;
 
-	c -= 32;
 	return((size_t)fonts[(int)TERMFONT_NONE].gly[c].wx);
 }
 
-
 static double
 ps_hspan(const struct termp *p, const struct roffsu *su)
 {
 	double		 r;
-	
+
 	/*
 	 * All of these measurements are derived by converting from the
 	 * native measurement to AFM units.
 	 */
-
 	switch (su->unit) {
-	case (SCALE_CM):
-		r = PNT2AFM(p, su->scale * 28.34);
+	case SCALE_BU:
+		/*
+		 * Traditionally, the default unit is fixed to the
+		 * output media.  So this would refer to the point.  In
+		 * mandoc(1), however, we stick to the default terminal
+		 * scaling unit so that output is the same regardless
+		 * the media.
+		 */
+		r = PNT2AFM(p, su->scale * 72.0 / 240.0);
 		break;
-	case (SCALE_IN):
-		r = PNT2AFM(p, su->scale * 72);
+	case SCALE_CM:
+		r = PNT2AFM(p, su->scale * 72.0 / 2.54);
 		break;
-	case (SCALE_PC):
-		r = PNT2AFM(p, su->scale * 12);
-		break;
-	case (SCALE_PT):
-		r = PNT2AFM(p, su->scale * 100);
+	case SCALE_EM:
+		r = su->scale *
+		    fonts[(int)TERMFONT_NONE].gly[109 - 32].wx;
 		break;
-	case (SCALE_EM):
+	case SCALE_EN:
 		r = su->scale *
-			fonts[(int)TERMFONT_NONE].gly[109 - 32].wx;
+		    fonts[(int)TERMFONT_NONE].gly[110 - 32].wx;
 		break;
-	case (SCALE_MM):
-		r = PNT2AFM(p, su->scale * 2.834);
+	case SCALE_IN:
+		r = PNT2AFM(p, su->scale * 72.0);
 		break;
-	case (SCALE_EN):
+	case SCALE_MM:
 		r = su->scale *
-			fonts[(int)TERMFONT_NONE].gly[110 - 32].wx;
+		    fonts[(int)TERMFONT_NONE].gly[109 - 32].wx / 100.0;
 		break;
-	case (SCALE_VS):
+	case SCALE_PC:
+		r = PNT2AFM(p, su->scale * 12.0);
+		break;
+	case SCALE_PT:
+		r = PNT2AFM(p, su->scale * 1.0);
+		break;
+	case SCALE_VS:
 		r = su->scale * p->ps->lineheight;
 		break;
 	default:
@@ -1178,8 +1351,5 @@ ps_growbuf(struct termp *p, size_t sz)
 		sz = PS_BUFSLOP;
 
 	p->ps->psmargsz += sz;
-
-	p->ps->psmarg = mandoc_realloc
-		(p->ps->psmarg, p->ps->psmargsz);
+	p->ps->psmarg = mandoc_realloc(p->ps->psmarg, p->ps->psmargsz);
 }
-
diff --git a/usr/src/cmd/mandoc/tree.c b/usr/src/cmd/mandoc/tree.c
index fdb70e1b93..a5a7f2c7d2 100644
--- a/usr/src/cmd/mandoc/tree.c
+++ b/usr/src/cmd/mandoc/tree.c
@@ -1,7 +1,7 @@
-/*	$Id: tree.c,v 1.50 2013/12/24 19:11:46 schwarze Exp $ */
+/*	$Id: tree.c,v 1.62 2015/02/05 00:14:13 schwarze Exp $ */
 /*
- * Copyright (c) 2008, 2009, 2011 Kristaps Dzonsons 
- * Copyright (c) 2013 Ingo Schwarze 
+ * Copyright (c) 2008, 2009, 2011, 2014 Kristaps Dzonsons 
+ * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,9 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+
+#include 
 
 #include 
 #include 
@@ -36,24 +36,20 @@ static	void	print_mdoc(const struct mdoc_node *, int);
 static	void	print_span(const struct tbl_span *, int);
 
 
-/* ARGSUSED */
 void
 tree_mdoc(void *arg, const struct mdoc *mdoc)
 {
 
-	print_mdoc(mdoc_node(mdoc), 0);
+	print_mdoc(mdoc_node(mdoc)->child, 0);
 }
 
-
-/* ARGSUSED */
 void
 tree_man(void *arg, const struct man *man)
 {
 
-	print_man(man_node(man), 0);
+	print_man(man_node(man)->child, 0);
 }
 
-
 static void
 print_mdoc(const struct mdoc_node *n, int indent)
 {
@@ -62,38 +58,42 @@ print_mdoc(const struct mdoc_node *n, int indent)
 	size_t		  argc;
 	struct mdoc_argv *argv;
 
+	if (n == NULL)
+		return;
+
 	argv = NULL;
 	argc = 0;
 	t = p = NULL;
 
 	switch (n->type) {
-	case (MDOC_ROOT):
+	case MDOC_ROOT:
 		t = "root";
 		break;
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		t = "block";
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		t = "block-head";
 		break;
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		if (n->end)
 			t = "body-end";
 		else
 			t = "block-body";
 		break;
-	case (MDOC_TAIL):
+	case MDOC_TAIL:
 		t = "block-tail";
 		break;
-	case (MDOC_ELEM):
+	case MDOC_ELEM:
 		t = "elem";
 		break;
-	case (MDOC_TEXT):
+	case MDOC_TEXT:
 		t = "text";
 		break;
-	case (MDOC_TBL):
-		/* FALLTHROUGH */
-	case (MDOC_EQN):
+	case MDOC_TBL:
+		break;
+	case MDOC_EQN:
+		t = "eqn";
 		break;
 	default:
 		abort();
@@ -101,37 +101,38 @@ print_mdoc(const struct mdoc_node *n, int indent)
 	}
 
 	switch (n->type) {
-	case (MDOC_TEXT):
+	case MDOC_TEXT:
 		p = n->string;
 		break;
-	case (MDOC_BODY):
+	case MDOC_BODY:
 		p = mdoc_macronames[n->tok];
 		break;
-	case (MDOC_HEAD):
+	case MDOC_HEAD:
 		p = mdoc_macronames[n->tok];
 		break;
-	case (MDOC_TAIL):
+	case MDOC_TAIL:
 		p = mdoc_macronames[n->tok];
 		break;
-	case (MDOC_ELEM):
+	case MDOC_ELEM:
 		p = mdoc_macronames[n->tok];
 		if (n->args) {
 			argv = n->args->argv;
 			argc = n->args->argc;
 		}
 		break;
-	case (MDOC_BLOCK):
+	case MDOC_BLOCK:
 		p = mdoc_macronames[n->tok];
 		if (n->args) {
 			argv = n->args->argv;
 			argc = n->args->argc;
 		}
 		break;
-	case (MDOC_TBL):
-		/* FALLTHROUGH */
-	case (MDOC_EQN):
+	case MDOC_TBL:
+		break;
+	case MDOC_EQN:
+		p = "EQ";
 		break;
-	case (MDOC_ROOT):
+	case MDOC_ROOT:
 		p = "root";
 		break;
 	default:
@@ -142,12 +143,9 @@ print_mdoc(const struct mdoc_node *n, int indent)
 	if (n->span) {
 		assert(NULL == p && NULL == t);
 		print_span(n->span, indent);
-	} else if (n->eqn) {
-		assert(NULL == p && NULL == t);
-		print_box(n->eqn->root, indent);
 	} else {
 		for (i = 0; i < indent; i++)
-			putchar('\t');
+			putchar(' ');
 
 		printf("%s (%s)", p, t);
 
@@ -164,52 +162,52 @@ print_mdoc(const struct mdoc_node *n, int indent)
 		putchar(' ');
 		if (MDOC_LINE & n->flags)
 			putchar('*');
-		printf("%d:%d", n->line, n->pos);
-		if (n->lastline != n->line)
-			printf("-%d", n->lastline);
-		putchar('\n');
+		printf("%d:%d\n", n->line, n->pos + 1);
 	}
 
+	if (n->eqn)
+		print_box(n->eqn->root->first, indent + 4);
 	if (n->child)
-		print_mdoc(n->child, indent + 1);
+		print_mdoc(n->child, indent +
+		    (n->type == MDOC_BLOCK ? 2 : 4));
 	if (n->next)
 		print_mdoc(n->next, indent);
 }
 
-
 static void
 print_man(const struct man_node *n, int indent)
 {
 	const char	 *p, *t;
 	int		  i;
 
+	if (n == NULL)
+		return;
+
 	t = p = NULL;
 
 	switch (n->type) {
-	case (MAN_ROOT):
+	case MAN_ROOT:
 		t = "root";
 		break;
-	case (MAN_ELEM):
+	case MAN_ELEM:
 		t = "elem";
 		break;
-	case (MAN_TEXT):
+	case MAN_TEXT:
 		t = "text";
 		break;
-	case (MAN_BLOCK):
+	case MAN_BLOCK:
 		t = "block";
 		break;
-	case (MAN_HEAD):
+	case MAN_HEAD:
 		t = "block-head";
 		break;
-	case (MAN_BODY):
+	case MAN_BODY:
 		t = "block-body";
 		break;
-	case (MAN_TAIL):
-		t = "block-tail";
+	case MAN_TBL:
 		break;
-	case (MAN_TBL):
-		/* FALLTHROUGH */
-	case (MAN_EQN):
+	case MAN_EQN:
+		t = "eqn";
 		break;
 	default:
 		abort();
@@ -217,26 +215,25 @@ print_man(const struct man_node *n, int indent)
 	}
 
 	switch (n->type) {
-	case (MAN_TEXT):
+	case MAN_TEXT:
 		p = n->string;
 		break;
-	case (MAN_ELEM):
-		/* FALLTHROUGH */
-	case (MAN_BLOCK):
+	case MAN_ELEM:
 		/* FALLTHROUGH */
-	case (MAN_HEAD):
+	case MAN_BLOCK:
 		/* FALLTHROUGH */
-	case (MAN_TAIL):
+	case MAN_HEAD:
 		/* FALLTHROUGH */
-	case (MAN_BODY):
+	case MAN_BODY:
 		p = man_macronames[n->tok];
 		break;
-	case (MAN_ROOT):
+	case MAN_ROOT:
 		p = "root";
 		break;
-	case (MAN_TBL):
-		/* FALLTHROUGH */
-	case (MAN_EQN):
+	case MAN_TBL:
+		break;
+	case MAN_EQN:
+		p = "EQ";
 		break;
 	default:
 		abort();
@@ -246,17 +243,20 @@ print_man(const struct man_node *n, int indent)
 	if (n->span) {
 		assert(NULL == p && NULL == t);
 		print_span(n->span, indent);
-	} else if (n->eqn) {
-		assert(NULL == p && NULL == t);
-		print_box(n->eqn->root, indent);
 	} else {
 		for (i = 0; i < indent; i++)
-			putchar('\t');
-		printf("%s (%s) %d:%d\n", p, t, n->line, n->pos);
+			putchar(' ');
+		printf("%s (%s) ", p, t);
+		if (MAN_LINE & n->flags)
+			putchar('*');
+		printf("%d:%d\n", n->line, n->pos + 1);
 	}
 
+	if (n->eqn)
+		print_box(n->eqn->root->first, indent + 4);
 	if (n->child)
-		print_man(n->child, indent + 1);
+		print_man(n->child, indent +
+		    (n->type == MAN_BLOCK ? 2 : 4));
 	if (n->next)
 		print_man(n->next, indent);
 }
@@ -267,39 +267,63 @@ print_box(const struct eqn_box *ep, int indent)
 	int		 i;
 	const char	*t;
 
+	static const char *posnames[] = {
+	    NULL, "sup", "subsup", "sub",
+	    "to", "from", "fromto",
+	    "over", "sqrt", NULL };
+
 	if (NULL == ep)
 		return;
 	for (i = 0; i < indent; i++)
-		putchar('\t');
+		putchar(' ');
 
 	t = NULL;
 	switch (ep->type) {
-	case (EQN_ROOT):
+	case EQN_ROOT:
 		t = "eqn-root";
 		break;
-	case (EQN_LIST):
+	case EQN_LISTONE:
+	case EQN_LIST:
 		t = "eqn-list";
 		break;
-	case (EQN_SUBEXPR):
+	case EQN_SUBEXPR:
 		t = "eqn-expr";
 		break;
-	case (EQN_TEXT):
+	case EQN_TEXT:
 		t = "eqn-text";
 		break;
-	case (EQN_MATRIX):
+	case EQN_PILE:
+		t = "eqn-pile";
+		break;
+	case EQN_MATRIX:
 		t = "eqn-matrix";
 		break;
 	}
 
-	assert(t);
-	printf("%s(%d, %d, %d, %d, %d, \"%s\", \"%s\") %s\n", 
-		t, EQN_DEFSIZE == ep->size ? 0 : ep->size,
-		ep->pos, ep->font, ep->mark, ep->pile, 
-		ep->left ? ep->left : "",
-		ep->right ? ep->right : "",
-		ep->text ? ep->text : "");
-
-	print_box(ep->first, indent + 1);
+	fputs(t, stdout);
+	if (ep->pos)
+		printf(" pos=%s", posnames[ep->pos]);
+	if (ep->left)
+		printf(" left=\"%s\"", ep->left);
+	if (ep->right)
+		printf(" right=\"%s\"", ep->right);
+	if (ep->top)
+		printf(" top=\"%s\"", ep->top);
+	if (ep->bottom)
+		printf(" bottom=\"%s\"", ep->bottom);
+	if (ep->text)
+		printf(" text=\"%s\"", ep->text);
+	if (ep->font)
+		printf(" font=%d", ep->font);
+	if (ep->size != EQN_DEFSIZE)
+		printf(" size=%d", ep->size);
+	if (ep->expectargs != UINT_MAX && ep->expectargs != ep->args)
+		printf(" badargs=%zu(%zu)", ep->args, ep->expectargs);
+	else if (ep->args)
+		printf(" args=%zu", ep->args);
+	putchar('\n');
+
+	print_box(ep->first, indent + 4);
 	print_box(ep->next, indent);
 }
 
@@ -310,13 +334,13 @@ print_span(const struct tbl_span *sp, int indent)
 	int		 i;
 
 	for (i = 0; i < indent; i++)
-		putchar('\t');
+		putchar(' ');
 
 	switch (sp->pos) {
-	case (TBL_SPAN_HORIZ):
+	case TBL_SPAN_HORIZ:
 		putchar('-');
 		return;
-	case (TBL_SPAN_DHORIZ):
+	case TBL_SPAN_DHORIZ:
 		putchar('=');
 		return;
 	default:
@@ -325,14 +349,14 @@ print_span(const struct tbl_span *sp, int indent)
 
 	for (dp = sp->first; dp; dp = dp->next) {
 		switch (dp->pos) {
-		case (TBL_DATA_HORIZ):
+		case TBL_DATA_HORIZ:
 			/* FALLTHROUGH */
-		case (TBL_DATA_NHORIZ):
+		case TBL_DATA_NHORIZ:
 			putchar('-');
 			continue;
-		case (TBL_DATA_DHORIZ):
+		case TBL_DATA_DHORIZ:
 			/* FALLTHROUGH */
-		case (TBL_DATA_NDHORIZ):
+		case TBL_DATA_NDHORIZ:
 			putchar('=');
 			continue;
 		default:
diff --git a/usr/src/cmd/mandoc/vol.c b/usr/src/cmd/mandoc/vol.c
deleted file mode 100644
index 3ea7441a42..0000000000
--- a/usr/src/cmd/mandoc/vol.c
+++ /dev/null
@@ -1,39 +0,0 @@
-/*	$Id: vol.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
-/*
- * Copyright (c) 2009 Kristaps Dzonsons 
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include 
-#include 
-#include 
-
-#include "mdoc.h"
-#include "mandoc.h"
-#include "libmdoc.h"
-
-#define LINE(x, y) \
-	if (0 == strcmp(p, x)) return(y);
-
-const char *
-mdoc_a2vol(const char *p)
-{
-
-#include "vol.in"
-
-	return(NULL);
-}
diff --git a/usr/src/cmd/mandoc/vol.in b/usr/src/cmd/mandoc/vol.in
deleted file mode 100644
index 7650b57a14..0000000000
--- a/usr/src/cmd/mandoc/vol.in
+++ /dev/null
@@ -1,35 +0,0 @@
-/*	$Id: vol.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */
-/*
- * Copyright (c) 2009 Kristaps Dzonsons 
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-/*
- * This file defines volume titles for .Dt.
- *
- * Be sure to escape strings.
- */
-
-LINE("USD",		"User\'s Supplementary Documents")
-LINE("PS1",		"Programmer\'s Supplementary Documents")
-LINE("AMD",		"Ancestral Manual Documents")
-LINE("SMM",		"System Manager\'s Manual")
-LINE("URM",		"User\'s Reference Manual")
-LINE("PRM",		"Programmer\'s Manual")
-LINE("KM",		"Kernel Manual")
-LINE("IND",		"Manual Master Index")
-LINE("MMI",		"Manual Master Index")
-LINE("LOCAL",		"Local Manual")
-LINE("LOC",		"Local Manual")
-LINE("CON",		"Contributed Software Manual")
diff --git a/usr/src/man/man1/mandoc.1 b/usr/src/man/man1/mandoc.1
index 8f2f8b5e84..c60353d2e0 100644
--- a/usr/src/man/man1/mandoc.1
+++ b/usr/src/man/man1/mandoc.1
@@ -1,3 +1,7 @@
+.\"	$Id: mandoc.1,v 1.155 2015/02/23 13:31:03 schwarze Exp $
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+.\" Copyright (c) 2012, 2014, 2015 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -11,12 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.\"
-.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
-.\" Copyright 2014 Garrett D'Amore 
-.\"
-.Dd Jul 30, 2014
+.Dd $Mdocdate: February 23 2015 $
 .Dt MANDOC 1
 .Os
 .Sh NAME
@@ -24,7 +23,11 @@
 .Nd format and display UNIX manuals
 .Sh SYNOPSIS
 .Nm mandoc
-.Op Fl V
+.Op Fl acfhkl
+.Sm off
+.Op Fl I Cm os Li = Ar name
+.Sm on
+.Op Fl K Ns Ar encoding
 .Op Fl m Ns Ar format
 .Op Fl O Ns Ar option
 .Op Fl T Ns Ar output
@@ -46,11 +49,78 @@ or
 text from stdin, implying
 .Fl m Ns Cm andoc ,
 and produces
-.Fl T Ns Cm ascii
+.Fl T Ns Cm locale
 output.
 .Pp
-The arguments are as follows:
+The options are as follows:
 .Bl -tag -width Ds
+.It Fl a
+If the standard output is a terminal device and
+.Fl c
+is not specified, use
+.Xr more 1
+to paginate the output, just like
+.Xr man 1
+would.
+.It Fl c
+Copy the formatted manual pages to the standard output without using
+.Xr more 1
+to paginate them.
+This is the default.
+It can be specified to override
+.Fl a .
+.It Fl f
+A synonym for
+.Xr whatis 1 .
+This overrides any earlier
+.Fl k
+and
+.Fl l
+options.
+.Sm off
+.It Fl I Cm os Li = Ar name
+.Sm on
+Override the default operating system
+.Ar name
+for the
+.Xr mdoc 5
+.Sq \&Os
+and for the
+.Xr man 5
+.Sq \&TH
+macro.
+.It Fl h
+Display only the SYNOPSIS lines.
+Implies
+.Fl c .
+.It Fl K Ns Ar encoding
+Specify the input encoding.
+The supported
+.Ar encoding
+arguments are
+.Cm us-ascii ,
+.Cm iso-8859-1 ,
+and
+.Cm utf-8 .
+If not specified, autodetection uses the first match:
+.Bl -tag -width iso-8859-1
+.It Cm utf-8
+if the first three bytes of the input file
+are the UTF-8 byte order mark (BOM, 0xefbbbf)
+.It Ar encoding
+if the first or second line of the input file matches the
+.Sy emacs
+mode line format
+.Pp
+.D1 .\e" -*- Oo ...; Oc coding: Ar encoding ; No -*-
+.It Cm utf-8
+if the first non-ASCII byte in the file introduces a valid UTF-8 sequence
+.It Cm iso-8859-1
+otherwise
+.El
+.It Fl l
+A synonym for
+.Fl a .
 .It Fl m Ns Ar format
 Input format.
 See
@@ -66,9 +136,7 @@ See
 .Sx Output Formats
 for available formats.
 Defaults to
-.Fl T Ns Cm ascii .
-.It Fl V
-Print version and exit.
+.Fl T Ns Cm locale .
 .It Fl W Ns Ar level
 Specify the minimum message
 .Ar level
@@ -79,12 +147,13 @@ can be
 .Cm warning ,
 .Cm error ,
 or
-.Cm fatal .
-The default is
-.Fl W Ns Cm fatal ;
-.Fl W Ns Cm all
+.Cm unsupp ;
+.Cm all
 is an alias for
-.Fl W Ns Cm warning .
+.Cm warning .
+By default,
+.Nm
+is silent.
 See
 .Sx EXIT STATUS
 and
@@ -163,11 +232,10 @@ arguments, which correspond to output modes:
 .Bl -tag -width "-Tlocale"
 .It Fl T Ns Cm ascii
 Produce 7-bit ASCII output.
-This is the default.
 See
 .Sx ASCII Output .
 .It Fl T Ns Cm html
-Produce strict CSS1/HTML-4.01 output.
+Produce HTML5, CSS1, and MathML output.
 See
 .Sx HTML Output .
 .It Fl T Ns Cm lint
@@ -176,6 +244,7 @@ Implies
 .Fl W Ns Cm warning .
 .It Fl T Ns Cm locale
 Encode output using the current locale.
+This is the default.
 See
 .Sx Locale Output .
 .It Fl T Ns Cm man
@@ -199,17 +268,16 @@ Encode output in the UTF\-8 multi-byte format.
 See
 .Sx UTF\-8 Output .
 .It Fl T Ns Cm xhtml
-Produce strict CSS1/XHTML-1.0 output.
-See
-.Sx XHTML Output .
+This is a synonym for
+.Fl T Ns Cm html .
 .El
 .Pp
 If multiple input files are specified, these will be processed by the
 corresponding filter in-order.
 .Ss ASCII Output
 Output produced by
-.Fl T Ns Cm ascii ,
-which is the default, is rendered in standard 7-bit ASCII documented in
+.Fl T Ns Cm ascii
+is rendered in standard 7-bit ASCII documented in
 .Xr ascii 5 .
 .Pp
 Font styles are applied by using back-spaced encoding such that an
@@ -226,9 +294,6 @@ Emboldened characters are rendered as
 The special characters documented in
 .Xr mandoc_char 5
 are rendered best-effort in an ASCII equivalent.
-If no equivalent is found,
-.Sq \&?
-is used instead.
 .Pp
 Output width is limited to 78 visible columns unless literal input lines
 exceed this limit.
@@ -249,12 +314,16 @@ for example overfull lines or ugly line breaks.
 .It Cm width Ns = Ns Ar width
 The output width is set to
 .Ar width ,
-which will normalise to \(>=60.
+which will normalise to \(>=58.
 .El
 .Ss HTML Output
 Output produced by
 .Fl T Ns Cm html
-conforms to HTML-4.01 strict.
+conforms to HTML5 using optional self-closing tags.
+Default styles use only CSS1.
+Equations rendered from
+.Xr eqn 5
+blocks use MathML.
 .Pp
 The
 .Pa example.style.css
@@ -262,7 +331,8 @@ file documents style-sheet classes available for customising output.
 If a style-sheet is not specified with
 .Fl O Ns Ar style ,
 .Fl T Ns Cm html
-defaults to simple output readable in any graphical or text-based web
+defaults to simple output (via an embedded style-sheet)
+readable in any graphical or text-based web
 browser.
 .Pp
 Special characters are rendered in decimal-encoded UTF\-8.
@@ -272,16 +342,8 @@ The following
 arguments are accepted:
 .Bl -tag -width Ds
 .It Cm fragment
-Omit the
-.Aq !DOCTYPE
-declaration and the
-.Aq html ,
-.Aq head ,
-and
-.Aq body
-elements and only emit the subtree below the
-.Aq body
-element.
+Omit the  declaration and the , , and 
+elements and only emit the subtree below the  element.
 The
 .Cm style
 argument will be ignored.
@@ -325,13 +387,7 @@ relative URI.
 .Ss Locale Output
 Locale-depending output encoding is triggered with
 .Fl T Ns Cm locale .
-This option is not available on all systems: systems without locale
-support, or those whose internal representation is not natively UCS-4,
-will fall back to
-.Fl T Ns Cm ascii .
-See
-.Sx ASCII Output
-for font style specification and available command-line arguments.
+This is the default.
 .Ss Man Output
 Translate input format into
 .Xr man 5
@@ -405,15 +461,6 @@ to force a UTF\-8 locale.
 See
 .Sx Locale Output
 for details and options.
-.Ss XHTML Output
-Output produced by
-.Fl T Ns Cm xhtml
-conforms to XHTML-1.0 strict.
-.Pp
-See
-.Sx HTML Output
-for details; beyond generating XHTML tags instead of HTML tags, these
-output modes are identical.
 .Sh EXIT STATUS
 The
 .Nm
@@ -433,19 +480,25 @@ At least one warning occurred, but no error, and
 .Fl W Ns Cm warning
 was specified.
 .It 3
-At least one parsing error occurred, but no fatal error, and
+At least one parsing error occurred,
+but no unsupported feature was encountered, and
 .Fl W Ns Cm error
 or
 .Fl W Ns Cm warning
 was specified.
 .It 4
-A fatal parsing error occurred.
+At least one unsupported feature was encountered, and
+.Fl W Ns Cm unsupp ,
+.Fl W Ns Cm error
+or
+.Fl W Ns Cm warning
+was specified.
 .It 5
 Invalid command line arguments were specified.
 No input files have been read.
 .It 6
-An operating system error occurred, for example memory exhaustion or an
-error accessing input files.
+An operating system error occurred, for example exhaustion
+of memory, file descriptors, or process table entries.
 Such errors cause
 .Nm
 to exit at once, possibly in the middle of parsing or formatting a file.
@@ -459,7 +512,7 @@ output mode implies
 To page manuals to the terminal:
 .Pp
 .Dl $ mandoc \-Wall,stop mandoc.1 2\*(Gt&1 | less
-.Dl $ mandoc mandoc.1 mdoc.5 | less
+.Dl $ mandoc mandoc.1 mdoc.3 mdoc.5 | less
 .Pp
 To produce HTML manuals with
 .Ar style.css
@@ -485,43 +538,53 @@ parser:
 .Pp
 .Dl $ mandoc \-Tman foo.mdoc \*(Gt foo.man
 .Sh DIAGNOSTICS
-Standard error messages reporting parsing errors are prefixed by
+Messages displayed by
+.Nm
+follow this format:
 .Pp
-.Sm off
-.D1 Ar file : line : column : \ level :
-.Sm on
+.D1 Nm Ns : Ar file : Ns Ar line : Ns Ar column : level : message : macro args
 .Pp
-where the fields have the following meanings:
-.Bl -tag -width "column"
-.It Ar file
-The name of the input file causing the message.
-.It Ar line
-The line number in that input file.
-Line numbering starts at 1.
-.It Ar column
-The column number in that input file.
-Column numbering starts at 1.
-If the issue is caused by a word, the column number usually
-points to the first character of the word.
-.It Ar level
-The message level, printed in capital letters.
-.El
+Line and column numbers start at 1.
+Both are omitted for messages referring to an input file as a whole.
+Macro names and arguments are omitted where meaningless.
+Fatal messages about invalid command line arguments
+or operating system errors, for example when memory is exhausted,
+may also omit the
+.Ar file
+and
+.Ar level
+fields.
 .Pp
 Message levels have the following meanings:
 .Bl -tag -width "warning"
-.It Cm fatal
-The parser is unable to parse a given input file at all.
-No formatted output is produced from that input file.
-.It Cm error
-An input file contains syntax that cannot be safely interpreted,
-either because it is invalid or because
+.It Cm unsupp
+An input file uses unsupported low-level
+.Xr mandoc_roff 5
+features.
+The output may be incomplete and/or misformatted,
+so using GNU troff instead of
 .Nm
-does not implement it yet.
+to process the file may be preferable.
+.It Cm error
+An input file contains invalid syntax that cannot be safely interpreted.
 By discarding part of the input or inserting missing tokens,
 the parser is able to continue, and the error does not prevent
 generation of formatted output, but typically, preparing that
 output involves information loss, broken document structure
-or unintended formatting.
+or unintended formatting, no matter whether
+.Nm
+or GNU troff is used.
+In many cases, the output of
+.Nm
+and GNU troff is identical, but in some,
+.Nm
+is more resilient than GNU troff with respect to malformed input.
+.Pp
+Non-existent or unreadable input files are also reported on the
+.Cm error
+level.
+In that case, the parser cannot even be started and no output
+is produced from those input files.
 .It Cm warning
 An input file uses obsolete, discouraged or non-portable syntax.
 All the same, the meaning of the input is unambiguous and a correct
@@ -532,147 +595,1140 @@ formatting tools instead of
 .El
 .Pp
 Messages of the
-.Cm warning
+.Cm warning ,
+.Cm error ,
 and
-.Cm error
-levels are hidden unless their level, or a lower level, is requested using a
+.Cm unsupp
+levels except those about non-existent or unreadable input files
+are hidden unless their level, or a lower level, is requested using a
 .Fl W
 option or
 .Fl T Ns Cm lint
 output mode.
-.Pp
-The
-.Nm
-utility may also print messages related to invalid command line arguments
-or operating system errors, for example when memory is exhausted or
-input files cannot be read.
-Such messages do not carry the prefix described above.
-.Sh COMPATIBILITY
-This section summarises
-.Nm
-compatibility with GNU troff.
-Each input and output format is separately noted.
-.Ss ASCII Compatibility
-.Bl -bullet -compact
-.It
-Unrenderable unicode codepoints specified with
-.Sq \e[uNNNN]
-escapes are printed as
-.Sq \&?
-in mandoc.
-In GNU troff, these raise an error.
-.It
+.Ss Warnings related to the document prologue
+.Bl -ohang
+.It Sy "missing manual title, using UNTITLED"
+.Pq mdoc
+A
+.Ic \&Dt
+macro has no arguments, or there is no
+.Ic \&Dt
+macro before the first non-prologue macro.
+.It Sy "missing manual title, using \(dq\(dq"
+.Pq man
+There is no
+.Ic \&TH
+macro, or it has no arguments.
+.It Sy "lower case character in document title"
+.Pq mdoc , man
+The title is still used as given in the
+.Ic \&Dt
+or
+.Ic \&TH
+macro.
+.It Sy "missing manual section, using \(dq\(dq"
+.Pq mdoc , man
+A
+.Ic \&Dt
+or
+.Ic \&TH
+macro lacks the mandatory section argument.
+.It Sy "unknown manual section"
+.Pq mdoc
+The section number in a
+.Ic \&Dt
+line is invalid, but still used.
+.It Sy "missing date, using today's date"
+.Pq mdoc, man
+The document was parsed as
+.Xr mdoc 5
+and it has no
+.Ic \&Dd
+macro, or the
+.Ic \&Dd
+macro has no arguments or only empty arguments;
+or the document was parsed as
+.Xr man 5
+and it has no
+.Ic \&TH
+macro, or the
+.Ic \&TH
+macro has less than three arguments or its third argument is empty.
+.It Sy "cannot parse date, using it verbatim"
+.Pq mdoc , man
+The date given in a
+.Ic \&Dd
+or
+.Ic \&TH
+macro does not follow the conventional format.
+.It Sy "missing Os macro, using \(dq\(dq"
+.Pq mdoc
+The default or current system is not shown in this case.
+.It Sy "duplicate prologue macro"
+.Pq mdoc
+One of the prologue macros occurs more than once.
+The last instance overrides all previous ones.
+.It Sy "late prologue macro"
+.Pq mdoc
+A
+.Ic \&Dd
+or
+.Ic \&Os
+macro occurs after some non-prologue macro, but still takes effect.
+.It Sy "skipping late title macro"
+.Pq mdoc
 The
-.Sq \&Bd \-literal
+.Ic \&Dt
+macro appears after the first non-prologue macro.
+Traditional formatters cannot handle this because
+they write the page header before parsing the document body.
+Even though this technical restriction does not apply to
+.Nm ,
+traditional semantics is preserved.
+The late macro is discarded including its arguments.
+.It Sy "prologue macros out of order"
+.Pq mdoc
+The prologue macros are not given in the conventional order
+.Ic \&Dd ,
+.Ic \&Dt ,
+.Ic \&Os .
+All three macros are used even when given in another order.
+.El
+.Ss Warnings regarding document structure
+.Bl -ohang
+.It Sy ".so is fragile, better use ln(1)"
+.Pq roff
+Including files only works when the parser program runs with the correct
+current working directory.
+.It Sy "no document body"
+.Pq mdoc , man
+The document body contains neither text nor macros.
+An empty document is shown, consisting only of a header and a footer line.
+.It Sy "content before first section header"
+.Pq mdoc , man
+Some macros or text precede the first
+.Ic \&Sh
+or
+.Ic \&SH
+section header.
+The offending macros and text are parsed and added to the top level
+of the syntax tree, outside any section block.
+.It Sy "first section is not NAME"
+.Pq mdoc
+The argument of the first
+.Ic \&Sh
+macro is not
+.Sq NAME .
+This may confuse
+.Xr makewhatis 8
+and
+.Xr apropos 1 .
+.It Sy "NAME section without name"
+.Pq mdoc
+The NAME section does not contain any
+.Ic \&Nm
+child macro.
+.It Sy "NAME section without description"
+.Pq mdoc
+The NAME section lacks the mandatory
+.Ic \&Nd
+child macro.
+.It Sy "description not at the end of NAME"
+.Pq mdoc
+The NAME section does contain an
+.Ic \&Nd
+child macro, but other content follows it.
+.It Sy "bad NAME section content"
+.Pq mdoc
+The NAME section contains plain text or macros other than
+.Ic \&Nm
 and
-.Sq \&Bd \-unfilled
-macros of
+.Ic \&Nd .
+.It Sy "missing description line, using \(dq\(dq"
+.Pq mdoc
+The
+.Ic \&Nd
+macro lacks the required argument.
+The title line of the manual will end after the dash.
+.It Sy "sections out of conventional order"
+.Pq mdoc
+A standard section occurs after another section it usually precedes.
+All section titles are used as given,
+and the order of sections is not changed.
+.It Sy "duplicate section title"
+.Pq mdoc
+The same standard section title occurs more than once.
+.It Sy "unexpected section"
+.Pq mdoc
+A standard section header occurs in a section of the manual
+where it normally isn't useful.
+.It Sy "unusual Xr order"
+.Pq mdoc
+In the SEE ALSO section, an
+.Ic \&Xr
+macro with a lower section number follows one with a higher number,
+or two
+.Ic \&Xr
+macros refering to the same section are out of alphabetical order.
+.It Sy "unusual Xr punctuation"
+.Pq mdoc
+In the SEE ALSO section, punctuation between two
+.Ic \&Xr
+macros differs from a single comma, or there is trailing punctuation
+after the last
+.Ic \&Xr
+macro.
+.It Sy "AUTHORS section without An macro"
+.Pq mdoc
+An AUTHORS sections contains no
+.Ic \&An
+macros, or only empty ones.
+Probably, there are author names lacking markup.
+.El
+.Ss "Warnings related to macros and nesting"
+.Bl -ohang
+.It Sy "obsolete macro"
+.Pq mdoc
+See the
 .Xr mdoc 5
-in
-.Fl T Ns Cm ascii
-are synonyms, as are \-filled and \-ragged.
-.It
-In historic GNU troff, the
-.Sq \&Pa
+manual for replacements.
+.It Sy "macro neither callable nor escaped"
+.Pq mdoc
+The name of a macro that is not callable appears on a macro line.
+It is printed verbatim.
+If the intention is to call it, move it to its own input line;
+otherwise, escape it by prepending
+.Sq \e& .
+.It Sy "skipping paragraph macro"
+In
 .Xr mdoc 5
-macro does not underline when scoped under an
-.Sq \&It
-in the FILES section.
-This behaves correctly in
-.Nm .
+documents, this happens
+.Bl -dash -compact
 .It
-A list or display following the
-.Sq \&Ss
-.Xr mdoc 5
-macro in
-.Fl T Ns Cm ascii
-does not assert a prior vertical break, just as it doesn't with
-.Sq \&Sh .
+at the beginning and end of sections and subsections
 .It
-The
-.Sq \&na
+right before non-compact lists and displays
+.It
+at the end of items in non-column, non-compact lists
+.It
+and for multiple consecutive paragraph macros.
+.El
+In
 .Xr man 5
-macro in
-.Fl T Ns Cm ascii
-has no effect.
+documents, it happens
+.Bl -dash -compact
+.It
+for empty
+.Ic \&P ,
+.Ic \&PP ,
+and
+.Ic \&LP
+macros
+.It
+for
+.Ic \&IP
+macros having neither head nor body arguments
 .It
-Words aren't hyphenated.
+for
+.Ic \&br
+or
+.Ic \&sp
+right after
+.Ic \&SH
+or
+.Ic \&SS
+.El
+.It Sy "moving paragraph macro out of list"
+.Pq mdoc
+A list item in a
+.Ic \&Bl
+list contains a trailing paragraph macro.
+The paragraph macro is moved after the end of the list.
+.It Sy "skipping no-space macro"
+.Pq mdoc
+An input line begins with an
+.Ic \&Ns
+macro.
+The macro is ignored.
+.It Sy "blocks badly nested"
+.Pq mdoc
+If two blocks intersect, one should completely contain the other.
+Otherwise, rendered output is likely to look strange in any output
+format, and rendering in SGML-based output formats is likely to be
+outright wrong because such languages do not support badly nested
+blocks at all.
+Typical examples of badly nested blocks are
+.Qq Ic \&Ao \&Bo \&Ac \&Bc
+and
+.Qq Ic \&Ao \&Bq \&Ac .
+In these examples,
+.Ic \&Ac
+breaks
+.Ic \&Bo
+and
+.Ic \&Bq ,
+respectively.
+.It Sy "nested displays are not portable"
+.Pq mdoc
+A
+.Ic \&Bd ,
+.Ic \&D1 ,
+or
+.Ic \&Dl
+display occurs nested inside another
+.Ic \&Bd
+display.
+This works with
+.Nm ,
+but fails with most other implementations.
+.It Sy "moving content out of list"
+.Pq mdoc
+A
+.Ic \&Bl
+list block contains text or macros before the first
+.Ic \&It
+macro.
+The offending children are moved before the beginning of the list.
+.It Sy ".Vt block has child macro"
+.Pq mdoc
+The
+.Ic \&Vt
+macro supports plain text arguments only.
+Formatting may be ugly and semantic searching
+for the affected content might not work.
+.It Sy "fill mode already enabled, skipping"
+.Pq man
+A
+.Ic \&fi
+request occurs even though the document is still in fill mode,
+or already switched back to fill mode.
+It has no effect.
+.It Sy "fill mode already disabled, skipping"
+.Pq man
+An
+.Ic \&nf
+request occurs even though the document already switched to no-fill mode
+and did not switch back to fill mode yet.
+It has no effect.
+.It Sy "line scope broken"
+.Pq man
+While parsing the next-line scope of the previous macro,
+another macro is found that prematurely terminates the previous one.
+The previous, interrupted macro is deleted from the parse tree.
 .El
-.Ss HTML/XHTML Compatibility
-.Bl -bullet -compact
+.Ss "Warnings related to missing arguments"
+.Bl -ohang
+.It Sy "skipping empty request"
+.Pq roff , eqn
+The macro name is missing from a macro definition request,
+or an
+.Xr eqn 5
+control statement or operation keyword lacks its required argument.
+.It Sy "conditional request controls empty scope"
+.Pq roff
+A conditional request is only useful if any of the following
+follows it on the same logical input line:
+.Bl -dash -compact
 .It
 The
-.Sq \efP
-escape will revert the font to the previous
-.Sq \ef
-escape, not to the last rendered decoration, which is now dictated by
-CSS instead of hard-coded.
-It also will not span past the current scope,
-for the same reason.
-Note that in
-.Sx ASCII Output
-mode, this will work fine.
+.Sq \e{
+keyword to open a multi-line scope.
 .It
+A request or macro or some text, resulting in a single-line scope.
+.It
+The immediate end of the logical line without any intervening whitespace,
+resulting in next-line scope.
+.El
+Here, a conditional request is followed by trailing whitespace only,
+and there is no other content on its logical input line.
+Note that it doesn't matter whether the logical input line is split
+across multiple physical input lines using
+.Sq \e
+line continuation characters.
+This is one of the rare cases
+where trailing whitespace is syntactically significant.
+The conditional request controls a scope containing whitespace only,
+so it is unlikely to have a significant effect,
+except that it may control a following
+.Ic \&el
+clause.
+.It Sy "skipping empty macro"
+.Pq mdoc
+The indicated macro has no arguments and hence no effect.
+.It Sy "empty block"
+.Pq mdoc , man
+A
+.Ic \&Bd ,
+.Ic \&Bk ,
+.Ic \&Bl ,
+.Ic \&D1 ,
+.Ic \&Dl ,
+.Ic \&RS ,
+or
+.Ic \&UR
+block contains nothing in its body and will produce no output.
+.It Sy "empty argument, using 0n"
+.Pq mdoc
+The required width is missing after
+.Ic \&Bd
+or
+.Ic \&Bl
+.Fl offset
+or
+.Fl width.
+.It Sy "missing display type, using -ragged"
+.Pq mdoc
+The
+.Ic \&Bd
+macro is invoked without the required display type.
+.It Sy "list type is not the first argument"
+.Pq mdoc
+In a
+.Ic \&Bl
+macro, at least one other argument precedes the type argument.
 The
+.Nm
+utility copes with any argument order, but some other
 .Xr mdoc 5
-.Sq \&Bl \-hang
+implementations do not.
+.It Sy "missing -width in -tag list, using 8n"
+.Pq mdoc
+Every
+.Ic \&Bl
+macro having the
+.Fl tag
+argument requires
+.Fl width ,
+too.
+.It Sy "missing utility name, using \(dq\(dq"
+.Pq mdoc
+The
+.Ic \&Ex Fl std
+macro is called without an argument before
+.Ic \&Nm
+has first been called with an argument.
+.It Sy "missing function name, using \(dq\(dq"
+.Pq mdoc
+The
+.Ic \&Fo
+macro is called without an argument.
+No function name is printed.
+.It Sy "empty head in list item"
+.Pq mdoc
+In a
+.Ic \&Bl
+.Fl diag ,
+.Fl hang ,
+.Fl inset ,
+.Fl ohang ,
+or
+.Fl tag
+list, an
+.Ic \&It
+macro lacks the required argument.
+The item head is left empty.
+.It Sy "empty list item"
+.Pq mdoc
+In a
+.Ic \&Bl
+.Fl bullet ,
+.Fl dash ,
+.Fl enum ,
+or
+.Fl hyphen
+list, an
+.Ic \&It
+block is empty.
+An empty list item is shown.
+.It Sy "missing font type, using \efR"
+.Pq mdoc
+A
+.Ic \&Bf
+macro has no argument.
+It switches to the default font.
+.It Sy "unknown font type, using \efR"
+.Pq mdoc
+The
+.Ic \&Bf
+argument is invalid.
+The default font is used instead.
+.It Sy "nothing follows prefix"
+.Pq mdoc
+A
+.Ic \&Pf
+macro has no argument, or only one argument and no macro follows
+on the same input line.
+This defeats its purpose; in particular, spacing is not suppressed
+before the text or macros following on the next input line.
+.It Sy "empty reference block"
+.Pq mdoc
+An
+.Ic \&Rs
+macro is immediately followed by an
+.Ic \&Re
+macro on the next input line.
+Such an empty block does not produce any output.
+.It Sy "missing -std argument, adding it"
+.Pq mdoc
+An
+.Ic \&Ex
+or
+.Ic \&Rv
+macro lacks the required
+.Fl std
+argument.
+The
+.Nm
+utility assumes
+.Fl std
+even when it is not specified, but other implementations may not.
+.It Sy "missing option string, using \(dq\(dq"
+.Pq man
+The
+.Ic \&OP
+macro is invoked without any argument.
+An empty pair of square brackets is shown.
+.It Sy "missing resource identifier, using \(dq\(dq"
+.Pq man
+The
+.Ic \&UR
+macro is invoked without any argument.
+An empty pair of angle brackets is shown.
+.It Sy "missing eqn box, using \(dq\(dq"
+.Pq eqn
+A diacritic mark or a binary operator is found,
+but there is nothing to the left of it.
+An empty box is inserted.
+.El
+.Ss "Warnings related to bad macro arguments"
+.Bl -ohang
+.It Sy "unterminated quoted argument"
+.Pq roff
+Macro arguments can be enclosed in double quote characters
+such that space characters and macro names contained in the quoted
+argument need not be escaped.
+The closing quote of the last argument of a macro can be omitted.
+However, omitting it is not recommended because it makes the code
+harder to read.
+.It Sy "duplicate argument"
+.Pq mdoc
+A
+.Ic \&Bd
+or
+.Ic \&Bl
+macro has more than one
+.Fl compact ,
+more than one
+.Fl offset ,
+or more than one
+.Fl width
+argument.
+All but the last instances of these arguments are ignored.
+.It Sy "skipping duplicate argument"
+.Pq mdoc
+An
+.Ic \&An
+macro has more than one
+.Fl split
+or
+.Fl nosplit
+argument.
+All but the first of these arguments are ignored.
+.It Sy "skipping duplicate display type"
+.Pq mdoc
+A
+.Ic \&Bd
+macro has more than one type argument; the first one is used.
+.It Sy "skipping duplicate list type"
+.Pq mdoc
+A
+.Ic \&Bl
+macro has more than one type argument; the first one is used.
+.It Sy "skipping -width argument"
+.Pq mdoc
+A
+.Ic \&Bl
+.Fl column ,
+.Fl diag ,
+.Fl ohang ,
+.Fl inset ,
+or
+.Fl item
+list has a
+.Fl width
+argument.
+That has no effect.
+.It Sy "wrong number of cells"
+In a line of a
+.Ic \&Bl Fl column
+list, the number of tabs or
+.Ic \&Ta
+macros is less than the number expected from the list header line
+or exceeds the expected number by more than one.
+Missing cells remain empty, and all cells exceeding the number of
+columns are joined into one single cell.
+.It Sy "unknown AT&T UNIX version"
+.Pq mdoc
+An
+.Ic \&At
+macro has an invalid argument.
+It is used verbatim, with
+.Qq "AT&T UNIX "
+prefixed to it.
+.It Sy "comma in function argument"
+.Pq mdoc
+An argument of an
+.Ic \&Fa
+or
+.Ic \&Fn
+macro contains a comma; it should probably be split into two arguments.
+.It Sy "parenthesis in function name"
+.Pq mdoc
+The first argument of an
+.Ic \&Fc
+or
+.Ic \&Fn
+macro contains an opening or closing parenthesis; that's probably wrong,
+parentheses are added automatically.
+.It Sy "invalid content in Rs block"
+.Pq mdoc
+An
+.Ic \&Rs
+block contains plain text or non-% macros.
+The bogus content is left in the syntax tree.
+Formatting may be poor.
+.It Sy "invalid Boolean argument"
+.Pq mdoc
+An
+.Ic \&Sm
+macro has an argument other than
+.Cm on
+or
+.Cm off .
+The invalid argument is moved out of the macro, which leaves the macro
+empty, causing it to toggle the spacing mode.
+.It Sy "unknown font, skipping request"
+.Pq man , tbl
+A
+.Xr mandoc_roff 5
+.Ic \&ft
+request or a
+.Xr tbl 5
+.Ic \&f
+layout modifier has an unknown
+.Ar font
+argument.
+.It Sy "odd number of characters in request"
+.Pq roff
+A
+.Ic \&tr
+request contains an odd number of characters.
+The last character is mapped to the blank character.
+.El
+.Ss "Warnings related to plain text"
+.Bl -ohang
+.It Sy "blank line in fill mode, using .sp"
+.Pq mdoc
+The meaning of blank input lines is only well-defined in non-fill mode:
+In fill mode, line breaks of text input lines are not supposed to be
+significant.
+However, for compatibility with groff, blank lines in fill mode
+are replaced with
+.Ic \&sp
+requests.
+.It Sy "tab in filled text"
+.Pq mdoc , man
+The meaning of tab characters is only well-defined in non-fill mode:
+In fill mode, whitespace is not supposed to be significant
+on text input lines.
+As an implementation dependent choice, tab characters on text lines
+are passed through to the formatters in any case.
+Given that the text before the tab character will be filled,
+it is hard to predict which tab stop position the tab will advance to.
+.It Sy "whitespace at end of input line"
+.Pq mdoc , man , roff
+Whitespace at the end of input lines is almost never semantically
+significant \(em but in the odd case where it might be, it is
+extremely confusing when reviewing and maintaining documents.
+.It Sy "bad comment style"
+.Pq roff
+Comment lines start with a dot, a backslash, and a double-quote character.
+The
+.Nm
+utility treats the line as a comment line even without the backslash,
+but leaving out the backslash might not be portable.
+.It Sy "invalid escape sequence"
+.Pq roff
+An escape sequence has an invalid opening argument delimiter, lacks the
+closing argument delimiter, or the argument has too few characters.
+If the argument is incomplete,
+.Ic \e*
+and
+.Ic \en
+expand to an empty string,
+.Ic \eB
+to the digit
+.Sq 0 ,
 and
-.Sq \&Bl \-tag
-list types render similarly (no break following overreached left-hand
-side) due to the expressive constraints of HTML.
+.Ic \ew
+to the length of the incomplete argument.
+All other invalid escape sequences are ignored.
+.It Sy "undefined string, using \(dq\(dq"
+.Pq roff
+If a string is used without being defined before,
+its value is implicitly set to the empty string.
+However, defining strings explicitly before use
+keeps the code more readable.
+.El
+.Ss "Warnings related to tables"
+.Bl -ohang
+.It Sy "tbl line starts with span"
+.Pq tbl
+The first cell in a table layout line is a horizontal span
+.Pq Sq Cm s .
+Data provided for this cell is ignored, and nothing is printed in the cell.
+.It Sy "tbl column starts with span"
+.Pq tbl
+The first line of a table layout specification
+requests a vertical span
+.Pq Sq Cm ^ .
+Data provided for this cell is ignored, and nothing is printed in the cell.
+.It Sy "skipping vertical bar in tbl layout"
+.Pq tbl
+A table layout specification contains more than two consecutive vertical bars.
+A double bar is printed, all additional bars are discarded.
+.El
+.Ss "Errors related to tables"
+.Bl -ohang
+.It Sy "non-alphabetic character in tbl options"
+.Pq tbl
+The table options line contains a character other than a letter,
+blank, or comma where the beginning of an option name is expected.
+The character is ignored.
+.It Sy "skipping unknown tbl option"
+.Pq tbl
+The table options line contains a string of letters that does not
+match any known option name.
+The word is ignored.
+.It Sy "missing tbl option argument"
+.Pq tbl
+A table option that requires an argument is not followed by an
+opening parenthesis, or the opening parenthesis is immediately
+followed by a closing parenthesis.
+The option is ignored.
+.It Sy "wrong tbl option argument size"
+.Pq tbl
+A table option argument contains an invalid number of characters.
+Both the option and the argument are ignored.
+.It Sy "empty tbl layout"
+.Pq tbl
+A table layout specification is completely empty,
+specifying zero lines and zero columns.
+As a fallback, a single left-justified column is used.
+.It Sy "invalid character in tbl layout"
+.Pq tbl
+A table layout specification contains a character that can neither
+be interpreted as a layout key character nor as a layout modifier,
+or a modifier precedes the first key.
+The invalid character is discarded.
+.It Sy "unmatched parenthesis in tbl layout"
+.Pq tbl
+A table layout specification contains an opening parenthesis,
+but no matching closing parenthesis.
+The rest of the input line, starting from the parenthesis, has no effect.
+.It Sy "tbl without any data cells"
+.Pq tbl
+A table does not contain any data cells.
+It will probably produce no output.
+.It Sy "ignoring data in spanned tbl cell"
+.Pq tbl
+A table cell is marked as a horizontal span
+.Pq Sq Cm s
+or vertical span
+.Pq Sq Cm ^
+in the table layout, but it contains data.
+The data is ignored.
+.It Sy "ignoring extra tbl data cells"
+.Pq tbl
+A data line contains more cells than the corresponding layout line.
+The data in the extra cells is ignored.
+.It Sy "data block open at end of tbl"
+.Pq tbl
+A data block is opened with
+.Cm T{ ,
+but never closed with a matching
+.Cm T} .
+The remaining data lines of the table are all put into one cell,
+and any remaining cells stay empty.
+.El
+.Ss "Errors related to roff, mdoc, and man code"
+.Bl -ohang
+.It Sy "input stack limit exceeded, infinite loop?"
+.Pq roff
+Explicit recursion limits are implemented for the following features,
+in order to prevent infinite loops:
+.Bl -dash -compact
 .It
+expansion of nested escape sequences
+including expansion of strings and number registers,
+.It
+expansion of nested user-defined macros,
+.It
+and
+.Ic \&so
+file inclusion.
+.El
+When a limit is hit, the output is incorrect, typically losing
+some content, but the parser can continue.
+.It Sy "skipping bad character"
+.Pq mdoc , man , roff
+The input file contains a byte that is not a printable
+.Xr ascii 5
+character.
+The message mentions the character number.
+The offending byte is replaced with a question mark
+.Pq Sq \&? .
+Consider editing the input file to replace the byte with an ASCII
+transliteration of the intended character.
+.It Sy "skipping unknown macro"
+.Pq mdoc , man , roff
+The first identifier on a request or macro line is neither recognized as a
+.Xr mandoc_roff 5
+request, nor as a user-defined macro, nor, respectively, as an
+.Xr mdoc 5
+or
+.Xr man 5
+macro.
+It may be mistyped or unsupported.
+The request or macro is discarded including its arguments.
+.It Sy "skipping insecure request"
+.Pq roff
+An input file attempted to run a shell command
+or to read or write an external file.
+Such attempts are denied for security reasons.
+.It Sy "skipping item outside list"
+.Pq mdoc , eqn
+An
+.Ic \&It
+macro occurs outside any
+.Ic \&Bl
+list, or an
+.Xr eqn 5
+.Ic above
+delimiter occurs outside any pile.
+It is discarded including its arguments.
+.It Sy "skipping column outside column list"
+.Pq mdoc
+A
+.Ic \&Ta
+macro occurs outside any
+.Ic \&Bl Fl column
+block.
+It is discarded including its arguments.
+.It Sy "skipping end of block that is not open"
+.Pq mdoc , man , eqn , tbl , roff
+Various syntax elements can only be used to explicitly close blocks
+that have previously been opened.
+An
+.Xr mdoc 5
+block closing macro, a
+.Xr man 5
+.Ic \&RE
+or
+.Ic \&UE
+macro, an
+.Xr eqn 5
+right delimiter or closing brace, or the end of an equation, table, or
+.Xr mandoc_roff 5
+conditional request is encountered but no matching block is open.
+The offending request or macro is discarded.
+.It Sy "fewer RS blocks open, skipping"
+.Pq man
 The
+.Ic \&RE
+macro is invoked with an argument, but less than the specified number of
+.Ic \&RS
+blocks is open.
+The
+.Ic \&RE
+macro is discarded.
+.It Sy "inserting missing end of block"
+.Pq mdoc , tbl
+Various
+.Xr mdoc 5
+macros as well as tables require explicit closing by dedicated macros.
+A block that doesn't support bad nesting
+ends before all of its children are properly closed.
+The open child nodes are closed implicitly.
+.It Sy "appending missing end of block"
+.Pq mdoc , man , eqn , tbl , roff
+At the end of the document, an explicit
+.Xr mdoc 5
+block, a
 .Xr man 5
-.Sq IP
+next-line scope or
+.Ic \&RS
+or
+.Ic \&UR
+block, an equation, table, or
+.Xr mandoc_roff 5
+conditional or ignore block is still open.
+The open block is closed implicitly.
+.It Sy "escaped character not allowed in a name"
+.Pq roff
+Macro, string and register identifiers consist of printable,
+non-whitespace ASCII characters.
+Escape sequences and characters and strings expressed in terms of them
+cannot form part of a name.
+The first argument of an
+.Ic \&am ,
+.Ic \&as ,
+.Ic \&de ,
+.Ic \&ds ,
+.Ic \&nr ,
+or
+.Ic \&rr
+request, or any argument of an
+.Ic \&rm
+request, or the name of a request or user defined macro being called,
+is terminated by an escape sequence.
+In the cases of
+.Ic \&as ,
+.Ic \&ds ,
 and
-.Sq TP
-lists render similarly.
-.El
-.Sh INTERFACE STABILITY
+.Ic \&nr ,
+the request has no effect at all.
+In the cases of
+.Ic \&am ,
+.Ic \&de ,
+.Ic \&rr ,
+and
+.Ic \&rm ,
+what was parsed up to this point is used as the arguments to the request,
+and the rest of the input line is discarded including the escape sequence.
+When parsing for a request or a user-defined macro name to be called,
+only the escape sequence is discarded.
+The characters preceding it are used as the request or macro name,
+the characters following it are used as the arguments to the request or macro.
+.It Sy "NOT IMPLEMENTED: Bd -file"
+.Pq mdoc
+For security reasons, the
+.Ic \&Bd
+macro does not support the
+.Fl file
+argument.
+By requesting the inclusion of a sensitive file, a malicious document
+might otherwise trick a privileged user into inadvertently displaying
+the file on the screen, revealing the file content to bystanders.
+The argument is ignored including the file name following it.
+.It Sy "missing list type, using -item"
+.Pq mdoc
+A
+.Ic \&Bl
+macro fails to specify the list type.
+.It Sy "missing manual name, using \(dq\(dq"
+.Pq mdoc
+The first call to
+.Ic \&Nm
+lacks the required argument.
+.It Sy "uname(3) system call failed, using UNKNOWN"
+.Pq mdoc
 The
+.Ic \&Os
+macro is called without arguments, and the
+.Xr uname 3
+system call failed.
+As a workaround,
+.Nm
+can be compiled with
+.Sm off
+.Fl D Cm OSNAME=\(dq\e\(dq Ar string Cm \e\(dq\(dq .
+.Sm on
+.It Sy "unknown standard specifier"
+.Pq mdoc
+An
+.Ic \&St
+macro has an unknown argument and is discarded.
+.It Sy "skipping request without numeric argument"
+.Pq roff , eqn
+An
+.Ic \&it
+request or an
+.Xr eqn 5
+.Ic \&size
+or
+.Ic \&gsize
+statement has a non-numeric or negative argument or no argument at all.
+The invalid request or statement is ignored.
+.It Sy "NOT IMPLEMENTED: .so with absolute path or \(dq..\(dq"
+.Pq roff
+For security reasons,
+.Nm
+allows
+.Ic \&so
+file inclusion requests only with relative paths
+and only without ascending to any parent directory.
+By requesting the inclusion of a sensitive file, a malicious document
+might otherwise trick a privileged user into inadvertently displaying
+the file on the screen, revealing the file content to bystanders.
+.Nm
+only shows the path as it appears behind
+.Ic \&so .
+.It Sy ".so request failed"
+.Pq roff
+Servicing a
+.Ic \&so
+request requires reading an external file, but the file could not be
+opened.
+.Nm
+only shows the path as it appears behind
+.Ic \&so .
+.It Sy "skipping all arguments"
+.Pq mdoc , man , eqn , roff
+An
+.Xr mdoc 5
+.Ic \&Bt ,
+.Ic \&Ed ,
+.Ic \&Ef ,
+.Ic \&Ek ,
+.Ic \&El ,
+.Ic \&Lp ,
+.Ic \&Pp ,
+.Ic \&Re ,
+.Ic \&Rs ,
+or
+.Ic \&Ud
+macro, an
+.Ic \&It
+macro in a list that don't support item heads, a
+.Xr man 5
+.Ic \&LP ,
+.Ic \&P ,
+or
+.Ic \&PP
+macro, an
+.Xr eqn 5
+.Ic \&EQ
+or
+.Ic \&EN
+macro, or a
+.Xr mandoc_roff 5
+.Ic \&br ,
+.Ic \&fi ,
+or
+.Ic \&nf
+request or
+.Sq \&..
+block closing request is invoked with at least one argument.
+All arguments are ignored.
+.It Sy "skipping excess arguments"
+.Pq mdoc , man , roff
+A macro or request is invoked with too many arguments:
+.Bl -dash -offset 2n -width 2n -compact
+.It
+.Ic \&Fo ,
+.Ic \&PD ,
+.Ic \&RS ,
+.Ic \&UR ,
+.Ic \&ft ,
+or
+.Ic \&sp
+with more than one argument
+.It
+.Ic \&An
+with another argument after
+.Fl split
+or
+.Fl nosplit
+.It
+.Ic \&RE
+with more than one argument or with a non-integer argument
+.It
+.Ic \&OP
+or a request of the
+.Ic \&de
+family with more than two arguments
+.It
+.Ic \&Dt
+with more than three arguments
+.It
+.Ic \&TH
+with more than five arguments
+.It
+.Ic \&Bd ,
+.Ic \&Bk ,
+or
+.Ic \&Bl
+with invalid arguments
+.El
+The excess arguments are ignored.
+.El
+.Ss Unsupported features
+.Bl -ohang
+.It Sy "input too large"
+.Pq mdoc , man
+Currently,
+.Nm
+cannot handle input files larger than its arbitrary size limit
+of 2^31 bytes (2 Gigabytes).
+Since useful manuals are always small, this is not a problem in practice.
+Parsing is aborted as soon as the condition is detected.
+.It Sy "unsupported control character"
+.Pq roff
+An ASCII control character supported by other
+.Xr mandoc_roff 5
+implementations but not by
 .Nm
-utility is
-.Sy Committed ,
-but the details of specific output formats other than ASCII are
-.Nm Uncommitted .
+was found in an input file.
+It is replaced by a question mark.
+.It Sy "unsupported roff request"
+.Pq roff
+An input file contains a
+.Xr mandoc_roff 5
+request supported by GNU troff or Heirloom troff but not by
+.Nm ,
+and it is likely that this will cause information loss
+or considerable misformatting.
+.It Sy "eqn delim option in tbl"
+.Pq eqn , tbl
+The options line of a table defines equation delimiters.
+Any equation source code contained in the table will be printed unformatted.
+.It Sy "unsupported table layout modifier"
+.Pq tbl
+A table layout specification contains an
+.Sq Cm m
+modifier.
+The modifier is discarded.
+.It Sy "ignoring macro in table"
+.Pq tbl , mdoc , man
+A table contains an invocation of an
+.Xr mdoc 5
+or
+.Xr man 5
+macro or of an undefined macro.
+The macro is ignored, and its arguments are handled
+as if they were a text line.
+.El
 .Sh SEE ALSO
 .Xr eqn 5 ,
 .Xr man 5 ,
 .Xr mandoc_char 5 ,
-.Xr mdoc 5 ,
 .Xr mandoc_roff 5 ,
+.Xr mdoc 5 ,
 .Xr tbl 5
 .Sh AUTHORS
 The
 .Nm
 utility was written by
-.An Kristaps Dzonsons ,
-.Mt kristaps@bsd.lv .
-.Sh CAVEATS
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and is maintained by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
+.Sh BUGS
 In
-.Fl T Ns Cm html
-and
-.Fl T Ns Cm xhtml ,
+.Fl T Ns Cm html ,
 the maximum size of an element attribute is determined by
 .Dv BUFSIZ ,
 which is usually 1024 bytes.
 Be aware of this when setting long link
 formats such as
 .Fl O Ns Cm style Ns = Ns Ar really/long/link .
-.Pp
-Nesting elements within next-line element scopes of
-.Fl m Ns Cm an ,
-such as
-.Sq br
-within an empty
-.Sq B ,
-will confuse
-.Fl T Ns Cm html
-and
-.Fl T Ns Cm xhtml
-and cause them to forget the formatting of the prior next-line scope.
-.Pp
-The
-.Sq \(aq
-control character is an alias for the standard macro control character
-and does not emit a line-break as stipulated in GNU troff.
diff --git a/usr/src/man/man5/eqn.5 b/usr/src/man/man5/eqn.5
index cb25ee2759..bfdb0cd58c 100644
--- a/usr/src/man/man5/eqn.5
+++ b/usr/src/man/man5/eqn.5
@@ -1,3 +1,7 @@
+.\"	$Id: eqn.7,v 1.34 2015/03/09 20:17:23 schwarze Exp $
+.\"
+.\" Copyright (c) 2011 Kristaps Dzonsons 
+.\" Copyright (c) 2014 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -11,11 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.\"
-.\" Copyright (c) 2011 Kristaps Dzonsons 
-.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
-.\"
-.Dd Jul 19, 2014
+.Dd $Mdocdate: March 9 2015 $
 .Dt EQN 5
 .Os
 .Sh NAME
@@ -38,7 +38,9 @@ This manual describes the
 .Nm
 language accepted by the
 .Xr mandoc 1
-utility, which corresponds to the Second Edition eqn specification (see
+utility, which corresponds to the Second Edition
+.Nm
+specification (see
 .Sx SEE ALSO
 for references).
 .Pp
@@ -61,7 +63,7 @@ and
 strings.
 .Em Note :
 these are not the same as
-.Xr roff 5
+.Xr mandoc_roff 5
 macros, and may only be invoked as
 .Sq \&.EQ .
 .Pp
@@ -70,37 +72,49 @@ case-sensitive literals in the input:
 .Bd -literal -offset indent
 eqn     : box | eqn box
 box     : text
-        | \*q{\*q eqn \*q}\*q
-        | \*qdefine\*q text text
-        | \*qndefine\*q text text
-        | \*qtdefine\*q text text
-        | \*qgfont\*q text
-        | \*qgsize\*q text
-        | \*qset\*q text text
-        | \*qundef\*q text
+        | \(dq{\(dq eqn \(dq}\(dq
+        | \(dqdefine\(dq text text
+        | \(dqndefine\(dq text text
+        | \(dqtdefine\(dq text text
+        | \(dqgfont\(dq text
+        | \(dqgsize\(dq text
+        | \(dqset\(dq text text
+        | \(dqundef\(dq text
+        | \(dqsqrt\(dq box
         | box pos box
         | box mark
-        | \*qmatrix\*q \*q{\*q [col \*q{\*q list \*q}\*q ]*
-        | pile \*q{\*q list \*q}\*q
+        | \(dqmatrix\(dq \(dq{\(dq [col \(dq{\(dq list \(dq}\(dq ]*
+        | pile \(dq{\(dq list \(dq}\(dq
         | font box
-        | \*qsize\*q text box
-        | \*qleft\*q text eqn [\*qright\*q text]
-col     : \*qlcol\*q | \*qrcol\*q | \*qccol\*q | \*qcol\*q
-text    : [^space\e\*q]+ | \e\*q.*\e\*q
-pile    : \*qlpile\*q | \*qcpile\*q | \*qrpile\*q | \*qpile\*q
-pos     : \*qover\*q | \*qsup\*q | \*qsub\*q | \*qto\*q | \*qfrom\*q
-mark	: \*qdot\*q | \*qdotdot\*q | \*qhat\*q | \*qtilde\*q | \*qvec\*q
-        | \*qdyad\*q | \*qbar\*q | \*qunder\*q
-font    : \*qroman\*q | \*qitalic\*q | \*qbold\*q | \*qfat\*q
+        | \(dqsize\(dq text box
+        | \(dqleft\(dq text eqn [\(dqright\(dq text]
+col     : \(dqlcol\(dq | \(dqrcol\(dq | \(dqccol\(dq | \(dqcol\(dq
+text    : [^space\e\(dq]+ | \e\(dq.*\e\(dq
+pile    : \(dqlpile\(dq | \(dqcpile\(dq | \(dqrpile\(dq | \(dqpile\(dq
+pos     : \(dqover\(dq | \(dqsup\(dq | \(dqsub\(dq | \(dqto\(dq | \(dqfrom\(dq
+mark	: \(dqdot\(dq | \(dqdotdot\(dq | \(dqhat\(dq | \(dqtilde\(dq | \(dqvec\(dq
+        | \(dqdyad\(dq | \(dqbar\(dq | \(dqunder\(dq
+font    : \(dqroman\(dq | \(dqitalic\(dq | \(dqbold\(dq | \(dqfat\(dq
 list    : eqn
-        | list \*qabove\*q eqn
+        | list \(dqabove\(dq eqn
 space   : [\e^~ \et]
 .Ed
 .Pp
 White-space consists of the space, tab, circumflex, and tilde
 characters.
+It is required to delimit tokens consisting of alphabetic characters
+and it is ignored at other places.
+Braces and quotes also delimit tokens.
 If within a quoted string, these space characters are retained.
-Quoted strings are also not scanned for replacement definitions.
+Quoted strings are also not scanned for keywords, glyph names,
+and expansion of definitions.
+To print a literal quote character, it can be prepended with a
+backslash or expressed with the \e(dq escape sequence.
+.Pp
+Subequations can be enclosed in braces to pass them as arguments
+to operation keywords, overriding standard operation precedence.
+Braces can be nested.
+To set a brace verbatim, it needs to be enclosed in quotes.
 .Pp
 The following text terms are translated into a rendered glyph, if
 available: alpha, beta, chi, delta, epsilon, eta, gamma, iota, kappa,
@@ -108,12 +122,15 @@ lambda, mu, nu, omega, omicron, phi, pi, psi, rho, sigma, tau, theta,
 upsilon, xi, zeta, DELTA, GAMMA, LAMBDA, OMEGA, PHI, PI, PSI, SIGMA,
 THETA, UPSILON, XI, inter (intersection), union (union), prod (product),
 int (integral), sum (summation), grad (gradient), del (vector
-differential), times (multiply), cdot (centre-dot), nothing (zero-width
+differential), times (multiply), cdot (center-dot), nothing (zero-width
 space), approx (approximately equals), prime (prime), half (one-half),
 partial (partial differential), inf (infinity), >> (much greater), <<
-(much less), \-> (left arrow), <\- (right arrow), += (plus-minus), !=
+(much less), \-> (left arrow), <\- (right arrow), +\- (plus-minus), !=
 (not equal), == (equivalence), <= (less-than-equal), and >=
 (more-than-equal).
+The character escape sequences documented in
+.Xr mandoc_char 5
+can be used, too.
 .Pp
 The following control statements are available:
 .Bl -tag -width Ds
@@ -121,7 +138,7 @@ The following control statements are available:
 Replace all occurrences of a key with a value.
 Its syntax is as follows:
 .Pp
-.D1 define Ar key cvalc
+.D1 Cm define Ar key cvalc
 .Pp
 The first character of the value string,
 .Ar c ,
@@ -129,8 +146,8 @@ is used as the delimiter for the value
 .Ar val .
 This allows for arbitrary enclosure of terms (not just quotes), such as
 .Pp
-.D1 define Ar foo 'bar baz'
-.D1 define Ar foo cbar bazc
+.D1 Cm define Ar foo 'bar baz'
+.D1 Cm define Ar foo cbar bazc
 .Pp
 It is an error to have an empty
 .Ar key
@@ -165,28 +182,26 @@ is discarded.
 Set the default font of subsequent output.
 Its syntax is as follows:
 .Pp
-.D1 gfont Ar font
+.D1 Cm gfont Ar font
 .Pp
-In
-.Xr mandoc 1 ,
-this value is discarded.
+In mandoc, this value is discarded.
 .It Cm gsize
 Set the default size of subsequent output.
 Its syntax is as follows:
 .Pp
-.D1 gsize Ar size
+.D1 Cm gsize Oo +|\- Oc Ns Ar size
 .Pp
 The
 .Ar size
 value should be an integer.
+If prepended by a sign,
+the font size is changed relative to the current size.
 .It Cm set
 Set an equation mode.
-In
-.Xr mandoc 1 ,
-both arguments are thrown away.
+In mandoc, both arguments are thrown away.
 Its syntax is as follows:
 .Pp
-.D1 set Ar key val
+.D1 Cm set Ar key val
 .Pp
 The
 .Ar key
@@ -198,7 +213,7 @@ This statement is a GNU extension.
 Unset a previously-defined key.
 Its syntax is as follows:
 .Pp
-.D1 define Ar key
+.D1 Cm define Ar key
 .Pp
 Once invoked, the definition for
 .Ar key
@@ -208,44 +223,233 @@ The
 is not expanded for replacements.
 This statement is a GNU extension.
 .El
-.Sh COMPATIBILITY
-This section documents the compatibility of
+.Pp
+Operation keywords have the following semantics:
+.Bl -tag -width Ds
+.It Cm above
+See
+.Cm pile .
+.It Cm bar
+Draw a line over the preceding box.
+.It Cm bold
+Set the following box using bold font.
+.It Cm ccol
+Like
+.Cm cpile ,
+but for use in
+.Cm matrix .
+.It Cm cpile
+Like
+.Cm pile ,
+but with slightly increased vertical spacing.
+.It Cm dot
+Set a single dot over the preceding box.
+.It Cm dotdot
+Set two dots (dieresis) over the preceding box.
+.It Cm dyad
+Set a dyad symbol (left-right arrow) over the preceding box.
+.It Cm fat
+A synonym for
+.Cm bold .
+.It Cm font
+Set the second argument using the font specified by the first argument;
+currently not recognized by the
 .Xr mandoc 1
 .Nm
-and the
-.Xr troff 1
+parser.
+.It Cm from
+Set the following box below the preceding box,
+using a slightly smaller font.
+Used for sums, integrals, limits, and the like.
+.It Cm hat
+Set a hat (circumflex) over the preceding box.
+.It Cm italic
+Set the following box using italic font.
+.It Cm lcol
+Like
+.Cm lpile ,
+but for use in
+.Cm matrix .
+.It Cm left
+Set the first argument as a big left delimiter before the second argument.
+As an optional third argument,
+.Cm right
+can follow.
+In that case, the fourth argument is set as a big right delimiter after
+the second argument.
+.It Cm lpile
+Like
+.Cm cpile ,
+but subequations are left-justified.
+.It Cm matrix
+Followed by a list of columns enclosed in braces.
+All columns need to have the same number of subequations.
+The columns are set as a matrix.
+The difference compared to multiple subsequent
+.Cm pile
+operators is that in a
+.Cm matrix ,
+corresponding subequations in all columns line up horizontally,
+while each
+.Cm pile
+does vertical spacing independently.
+.It Cm over
+Set a fraction.
+The preceding box is the numerator, the following box is the denominator.
+.It Cm pile
+Followed by a list of subequations enclosed in braces,
+the subequations being separated by
+.Cm above
+keywords.
+Sets the subequations one above the other, each of them centered.
+Typically used to represent vectors in coordinate representation.
+.It Cm rcol
+Like
+.Cm rpile ,
+but for use in
+.Cm matrix .
+.It Cm right
+See
+.Cm left ;
+.Cm right
+cannot be used without
+.Cm left .
+To set a big right delimiter without a big left delimiter, the following
+construction can be used:
+.Pp
+.D1 Cm left No \(dq\(dq Ar box Cm right Ar delimiter
+.It Cm roman
+Set the following box using the default font.
+.It Cm rpile
+Like
+.Cm cpile ,
+but subequations are right-justified.
+.It Cm size
+Set the second argument with the font size specified by the first
+argument; currently ignored by
+.Xr mandoc 1 .
+By prepending a plus or minus sign to the first argument,
+the font size can be selected relative to the current size.
+.It Cm sqrt
+Set the square root of the following box.
+.It Cm sub
+Set the following box as a subscript to the preceding box.
+.It Cm sup
+Set the following box as a superscript to the preceding box.
+As a special case, if a
+.Cm sup
+clause immediately follows a
+.Cm sub
+clause as in
+.Pp
+.D1 Ar mainbox Cm sub Ar subbox Cm sup Ar supbox
+.Pp
+both are set with respect to the same
+.Ar mainbox ,
+that is,
+.Ar supbox
+is set above
+.Ar subbox .
+.It Cm tilde
+Set a tilde over the preceding box.
+.It Cm to
+Set the following box above the preceding box,
+using a slightly smaller font.
+Used for sums and integrals and the like.
+As a special case, if a
+.Cm to
+clause immediately follows a
+.Cm from
+clause as in
+.Pp
+.D1 Ar mainbox Cm from Ar frombox Cm to Ar tobox
+.Pp
+both are set below and above the same
+.Ar mainbox .
+.It Cm under
+Underline the preceding box.
+.It Cm vec
+Set a vector symbol (right arrow) over the preceding box.
+.El
+.Pp
+The binary operations
+.Cm from ,
+.Cm to ,
+.Cm sub ,
+and
+.Cm sup
+group to the right, that is,
+.Pp
+.D1 Ar mainbox Cm sup Ar supbox Cm sub Ar subbox
+.Pp
+is the same as
+.Pp
+.D1 Ar mainbox Cm sup Brq Ar supbox Cm sub Ar subbox
+.Pp
+and different from
+.Pp
+.D1 Bro Ar mainbox Cm sup Ar supbox Brc Cm sub Ar subbox .
+.Pp
+By contrast,
+.Cm over
+groups to the left.
+.Pp
+In the following list, earlier operations bind more tightly than
+later operations:
+.Pp
+.Bl -enum -compact
+.It
+.Cm dyad ,
+.Cm vec ,
+.Cm under ,
+.Cm bar ,
+.Cm tilde ,
+.Cm hat ,
+.Cm dot ,
+.Cm dotdot
+.It
+.Cm fat ,
+.Cm roman ,
+.Cm italic ,
+.Cm bold ,
+.Cm size
+.It
+.Cm sub ,
+.Cm sup
+.It
+.Cm sqrt
+.It
+.Cm over
+.It
+.Cm from ,
+.Cm to
+.El
+.Sh COMPATIBILITY
+This section documents the compatibility of mandoc
+.Nm
+and the troff
 .Nm
 implementation (including GNU troff).
 .Pp
 .Bl -dash -compact
 .It
 The text string
-.Sq \e\*q
-is interpreted as a literal quote in
-.Xr troff 1 .
-In
-.Xr mandoc 1 ,
-this is interpreted as a comment.
+.Sq \e\(dq
+is interpreted as a literal quote in troff.
+In mandoc, this is interpreted as a comment.
 .It
-In
-.Xr troff 1 ,
-The circumflex and tilde white-space symbols map to
+In troff, The circumflex and tilde white-space symbols map to
 fixed-width spaces.
-In
-.Xr mandoc 1 ,
-these characters are synonyms for the space character.
+In mandoc, these characters are synonyms for the space character.
 .It
-The
-.Xr troff 1 ,
-implementation of
+The troff implementation of
 .Nm
 allows for equation alignment with the
 .Cm mark
 and
 .Cm lineup
 tokens.
-.Xr mandoc 1
-discards these tokens.
+mandoc discards these tokens.
 The
 .Cm back Ar n ,
 .Cm fwd Ar n ,
@@ -258,8 +462,8 @@ commands are also ignored.
 .Xr mandoc 1 ,
 .Xr man 5 ,
 .Xr mandoc_char 5 ,
-.Xr mdoc 5 ,
-.Xr roff 5
+.Xr mandoc_roff 5 ,
+.Xr mdoc 5
 .Rs
 .%A Brian W. Kernighan
 .%A Lorinda L. Cherry
@@ -293,5 +497,4 @@ was added in 2011.
 This
 .Nm
 reference was written by
-.An Kristaps Dzonsons ,
-.Mt kristaps@bsd.lv .
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
diff --git a/usr/src/man/man5/mandoc_char.5 b/usr/src/man/man5/mandoc_char.5
index 15b421474d..48f8348c8e 100644
--- a/usr/src/man/man5/mandoc_char.5
+++ b/usr/src/man/man5/mandoc_char.5
@@ -1,3 +1,8 @@
+.\"	$Id: mandoc_char.7,v 1.59 2015/01/20 19:39:34 schwarze Exp $
+.\"
+.\" Copyright (c) 2003 Jason McIntyre 
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+.\" Copyright (c) 2011 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -11,13 +16,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.\"
-.\" Copyright (c) 2003 Jason McIntyre 
-.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2011 Ingo Schwarze 
-.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
-.\"
-.Dd Nov 23, 2011
+.Dd $Mdocdate: January 20 2015 $
 .Dt MANDOC_CHAR 5
 .Os
 .Sh NAME
@@ -25,7 +24,7 @@
 .Nd mandoc special characters
 .Sh DESCRIPTION
 This page documents the
-.Xr roff 5
+.Xr mandoc_roff 5
 escape sequences accepted by
 .Xr mandoc 1
 to represent special characters in
@@ -99,26 +98,27 @@ in literal context, and when none of the following special cases apply,
 just use the normal space character
 .Pq Sq \  .
 .Pp
-When filling text, lines may be broken between words, i.e. at space
+When filling text, output lines may be broken between words, i.e. at space
 characters.
 To prevent a line break between two particular words,
-use the non-breaking space escape sequence
-.Pq Sq \e~
+use the unpaddable non-breaking space escape sequence
+.Pq Sq \e\ \&
 instead of the normal space character.
 For example, the input string
-.Dq number\e~1
+.Dq number\e\ 1
 will be kept together as
-.Dq number\~1
+.Dq number\ 1
 on the same output line.
 .Pp
 On request and macro lines, the normal space character serves as an
 argument delimiter.
-To include whitespace into arguments, quoting is usually the best choice.
-In some cases, using either the non-breaking
-.Pq Sq \e~
-or the breaking
+To include whitespace into arguments, quoting is usually the best choice;
+see the MACRO SYNTAX section in
+.Xr mandoc_roff 5 .
+In some cases, using the non-breaking space escape sequence
 .Pq Sq \e\ \&
-space escape sequence may be preferable.
+may be preferable.
+.Pp
 To escape macro names and to protect whitespace at the end
 of input lines, the zero-width space
 .Pq Sq \e&
@@ -147,7 +147,7 @@ The period
 .Pq Sq \&.
 is handled specially at the beginning of an input line,
 where it introduces a
-.Xr roff 5
+.Xr mandoc_roff 5
 request or a macro, and when appearing alone as a macro argument in
 .Xr mdoc 5 .
 In such situations, prepend a zero-width space
@@ -171,11 +171,11 @@ is not the right way to output a backslash.
 Because
 .Xr mandoc 1
 does not implement full
-.Xr roff 5
+.Xr mandoc_roff 5
 functionality, it may work with
 .Xr mandoc 1 ,
 but it may have weird effects on complete
-.Xr roff 5
+.Xr mandoc_roff 5
 implementations.
 .Sh SPECIAL CHARACTERS
 Special characters are encoded as
@@ -189,20 +189,19 @@ and
 For details, see the
 .Em Special Characters
 subsection of the
-.Xr roff 5
+.Xr mandoc_roff 5
 manual.
 .Pp
 Spacing:
 .Bl -column "Input" "Description" -offset indent -compact
 .It Em Input Ta Em Description
-.It \e~      Ta non-breaking, non-collapsing space
-.It \e       Ta breaking, non-collapsing n-width space
-.It \e^      Ta zero-width space
-.It \e%      Ta zero-width space
+.It Sq \e\ \& Ta unpaddable non-breaking space
+.It \e~      Ta paddable non-breaking space
+.It \e0      Ta unpaddable, breaking digit-width space
+.It \e|      Ta one-sixth \e(em narrow space, zero width in nroff mode
+.It \e^      Ta one-twelfth \e(em half-narrow space, zero width in nroff
 .It \e&      Ta zero-width space
-.It \e|      Ta zero-width space
-.It \e0      Ta breaking, non-collapsing digit-width space
-.It \ec      Ta removes any trailing space (if applicable)
+.It \e%      Ta zero-width space allowing hyphenation
 .El
 .Pp
 Lines:
@@ -211,7 +210,7 @@ Lines:
 .It \e(ba    Ta \(ba        Ta bar
 .It \e(br    Ta \(br        Ta box rule
 .It \e(ul    Ta \(ul        Ta underscore
-.It \e(rl    Ta \(rl        Ta overline
+.It \e(rn    Ta \(rn        Ta overline
 .It \e(bb    Ta \(bb        Ta broken bar
 .It \e(sl    Ta \(sl        Ta forward slash
 .It \e(rs    Ta \(rs        Ta backward slash
@@ -274,7 +273,7 @@ Quotes:
 .El
 .Pp
 Brackets:
-.Bl -column "xxbracketrightbpx" Rendered Description -offset indent -compact
+.Bl -column "xxbracketrightbtx" Rendered Description -offset indent -compact
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e(lB    Ta \(lB        Ta left bracket
 .It \e(rB    Ta \(rB        Ta right bracket
@@ -285,30 +284,30 @@ Brackets:
 .It \e(bv    Ta \(bv        Ta brace extension
 .It \e[braceex] Ta \[braceex] Ta brace extension
 .It \e[bracketlefttp] Ta \[bracketlefttp] Ta top-left hooked bracket
-.It \e[bracketleftbp] Ta \[bracketleftbp] Ta bottom-left hooked bracket
+.It \e[bracketleftbt] Ta \[bracketleftbt] Ta bottom-left hooked bracket
 .It \e[bracketleftex] Ta \[bracketleftex] Ta left hooked bracket extension
 .It \e[bracketrighttp] Ta \[bracketrighttp] Ta top-right hooked bracket
-.It \e[bracketrightbp] Ta \[bracketrightbp] Ta bottom-right hooked bracket
+.It \e[bracketrightbt] Ta \[bracketrightbt] Ta bottom-right hooked bracket
 .It \e[bracketrightex] Ta \[bracketrightex] Ta right hooked bracket extension
 .It \e(lt    Ta \(lt        Ta top-left hooked brace
 .It \e[bracelefttp] Ta \[bracelefttp] Ta top-left hooked brace
 .It \e(lk    Ta \(lk        Ta mid-left hooked brace
 .It \e[braceleftmid] Ta \[braceleftmid] Ta mid-left hooked brace
 .It \e(lb    Ta \(lb        Ta bottom-left hooked brace
-.It \e[braceleftbp] Ta \[braceleftbp] Ta bottom-left hooked brace
+.It \e[braceleftbt] Ta \[braceleftbt] Ta bottom-left hooked brace
 .It \e[braceleftex] Ta \[braceleftex] Ta left hooked brace extension
 .It \e(rt    Ta \(rt        Ta top-left hooked brace
 .It \e[bracerighttp] Ta \[bracerighttp] Ta top-right hooked brace
 .It \e(rk    Ta \(rk        Ta mid-right hooked brace
 .It \e[bracerightmid] Ta \[bracerightmid] Ta mid-right hooked brace
 .It \e(rb    Ta \(rb        Ta bottom-right hooked brace
-.It \e[bracerightbp] Ta \[bracerightbp] Ta bottom-right hooked brace
+.It \e[bracerightbt] Ta \[bracerightbt] Ta bottom-right hooked brace
 .It \e[bracerightex] Ta \[bracerightex] Ta right hooked brace extension
 .It \e[parenlefttp] Ta \[parenlefttp] Ta top-left hooked parenthesis
-.It \e[parenleftbp] Ta \[parenleftbp] Ta bottom-left hooked parenthesis
+.It \e[parenleftbt] Ta \[parenleftbt] Ta bottom-left hooked parenthesis
 .It \e[parenleftex] Ta \[parenleftex] Ta left hooked parenthesis extension
 .It \e[parenrighttp] Ta \[parenrighttp] Ta top-right hooked parenthesis
-.It \e[parenrightbp] Ta \[parenrightbp] Ta bottom-right hooked parenthesis
+.It \e[parenrightbt] Ta \[parenrightbt] Ta bottom-right hooked parenthesis
 .It \e[parenrightex] Ta \[parenrightex] Ta right hooked parenthesis extension
 .El
 .Pp
@@ -353,7 +352,7 @@ Mathematical:
 .It \e(-+    Ta \(-+        Ta minus-plus
 .It \e(+-    Ta \(+-        Ta plus-minus
 .It \e[t+-]  Ta \[t+-]      Ta plus-minus (text)
-.It \e(pc    Ta \(pc        Ta centre-dot
+.It \e(pc    Ta \(pc        Ta center-dot
 .It \e(mu    Ta \(mu        Ta multiply
 .It \e[tmu]  Ta \[tmu]      Ta multiply (text)
 .It \e(c*    Ta \(c*        Ta circle-multiply
@@ -370,11 +369,11 @@ Mathematical:
 .It \e(!=    Ta \(!=        Ta not equal
 .It \e(==    Ta \(==        Ta equivalent
 .It \e(ne    Ta \(ne        Ta not equivalent
-.It \e(=~    Ta \(=~        Ta congruent
-.It \e(-~    Ta \(-~        Ta asymptotically congruent
-.It \e(ap    Ta \(ap        Ta asymptotically similar
-.It \e(~~    Ta \(~~        Ta approximately similar
-.It \e(~=    Ta \(~=        Ta approximately equal
+.It \e(ap    Ta \(ap        Ta tilde operator
+.It \e(|=    Ta \(|=        Ta asymptotically equal
+.It \e(=~    Ta \(=~        Ta approximately equal
+.It \e(~~    Ta \(~~        Ta almost equal
+.It \e(~=    Ta \(~=        Ta almost equal
 .It \e(pt    Ta \(pt        Ta proportionate
 .It \e(es    Ta \(es        Ta empty set
 .It \e(mo    Ta \(mo        Ta element
@@ -622,7 +621,7 @@ and
 For details, see the
 .Em Predefined Strings
 subsection of the
-.Xr roff 5
+.Xr mandoc_roff 5
 manual.
 .Bl -column "Input" "Rendered" "Description" -offset indent
 .It Em Input Ta Em Rendered Ta Em Description
@@ -656,13 +655,18 @@ manual.
 .It \e*(Ai   Ta \*(Ai       Ta ANSI standard name
 .El
 .Sh UNICODE CHARACTERS
-The escape sequence
+The escape sequences
 .Pp
-.Dl \e[uXXXX]
+.Dl \e[uXXXX] and \eC'uXXXX'
 .Pp
-is interpreted as a Unicode codepoint.
+are interpreted as Unicode codepoints.
 The codepoint must be in the range above U+0080 and less than U+10FFFF.
-For compatibility, points must be zero-padded to four characters; if
+For compatibility, the hexadecimal digits
+.Sq A
+to
+.Sq F
+must be given as uppercase characters,
+and points must be zero-padded to four characters; if
 greater than four characters, no zero padding is allowed.
 Unicode surrogates are not allowed.
 .\" .Pp
@@ -685,7 +689,7 @@ For example, do not use \eN'34', use \e(dq, or even the plain
 .Sq \(dq
 character where possible.
 .Sh COMPATIBILITY
-This section documents compatibility between mandoc and other other
+This section documents compatibility between mandoc and other
 troff implementations, at this time limited to GNU troff
 .Pq Qq groff .
 .Pp
@@ -723,18 +727,17 @@ known representation.
 .Sh SEE ALSO
 .Xr mandoc 1 ,
 .Xr man 5 ,
-.Xr mdoc 5 ,
-.Xr roff 5
+.Xr mandoc_roff 5 ,
+.Xr mdoc 5
 .Sh AUTHORS
 The
 .Nm
 manual page was written by
-.An Kristaps Dzonsons ,
-.Mt kristaps@bsd.lv .
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
 .Sh CAVEATS
-The
+The predefined string
 .Sq \e*(Ba
-escape mimics the behaviour of the
+mimics the behaviour of the
 .Sq \&|
 character in
 .Xr mdoc 5 ;
diff --git a/usr/src/man/man5/mandoc_roff.5 b/usr/src/man/man5/mandoc_roff.5
index 731f93e134..bef11f27cf 100644
--- a/usr/src/man/man5/mandoc_roff.5
+++ b/usr/src/man/man5/mandoc_roff.5
@@ -1,3 +1,7 @@
+.\"	$Id: roff.7,v 1.70 2015/02/17 17:16:52 schwarze Exp $
+.\"
+.\" Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons 
+.\" Copyright (c) 2010, 2011, 2013, 2014 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -11,17 +15,11 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.\"
-.\" Copyright (c) 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2010, 2011 Ingo Schwarze 
-.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
-.\" Copyright 2014 Garrett D'Amore 
-.\"
-.Dd Jul 13, 2014
+.Dd $Mdocdate: February 17 2015 $
 .Dt MANDOC_ROFF 5
 .Os
 .Sh NAME
-.Nm mandoc_roff
+.Nm roff
 .Nd roff language reference for mandoc
 .Sh DESCRIPTION
 The
@@ -34,7 +32,7 @@ and
 manual formatting languages are based on it,
 many real-world manuals use small numbers of
 .Nm
-requests intermixed with their
+requests and escape sequences intermixed with their
 .Xr mdoc 5
 or
 .Xr man 5
@@ -43,8 +41,8 @@ To properly format such manuals, the
 .Xr mandoc 1
 utility supports a tiny subset of
 .Nm
-requests.
-Only these requests supported by
+requests and escapes.
+Only these requests and escapes supported by
 .Xr mandoc 1
 are documented in the present manual,
 together with the basic language syntax shared by
@@ -82,12 +80,12 @@ Lines not beginning with control characters are called
 They provide free-form text to be printed; the formatting of the text
 depends on the respective processing context.
 .Sh LANGUAGE SYNTAX
-.Nm
+.Nm roff
 documents may contain only graphable 7-bit ASCII characters, the space
 character, and, in certain circumstances, the tab character.
-The back-space character
+The backslash character
 .Sq \e
-indicates the start of an escape sequence for
+indicates the start of an escape sequence, used for example for
 .Sx Comments ,
 .Sx Special Characters ,
 .Sx Predefined Strings ,
@@ -95,6 +93,9 @@ and
 user-defined strings defined using the
 .Sx ds
 request.
+For a listing of escape sequences, consult the
+.Sx ESCAPE SEQUENCE REFERENCE
+below.
 .Ss Comments
 Text following an escaped double-quote
 .Sq \e\(dq ,
@@ -148,12 +149,19 @@ respectively) may be used instead.
 The indicator or numerical representative may be preceded by C
 (constant-width), which is ignored.
 .Pp
+The two-character indicator
+.Sq BI
+requests a font that is both bold and italic.
+It may not be portable to old roff implementations.
+.Pp
 Examples:
 .Bl -tag -width Ds -offset indent -compact
 .It Li \efBbold\efR
-Write in bold, then switch to regular font mode.
+Write in \fBbold\fP, then switch to regular font mode.
 .It Li \efIitalic\efP
-Write in italic, then return to previous font mode.
+Write in \fIitalic\fP, then return to previous font mode.
+.It Li \ef(BIbold italic\efP
+Write in \f(BIbold italic\fP, then return to previous font mode.
 .El
 .Pp
 Text decoration is
@@ -231,8 +239,9 @@ pica (~1/6 inch)
 .It p
 point (~1/72 inch)
 .It f
-synonym for
+scale
 .Sq u
+by 65536
 .It v
 default vertical span
 .It m
@@ -246,7 +255,7 @@ width of rendered
 .Pq en
 character
 .It u
-default horizontal span
+default horizontal span for the terminal
 .It M
 mini-em (~1/100 em)
 .El
@@ -254,7 +263,6 @@ mini-em (~1/100 em)
 Using anything other than
 .Sq m ,
 .Sq n ,
-.Sq u ,
 or
 .Sq v
 is necessarily non-portable across output media.
@@ -387,38 +395,199 @@ The
 .Xr mandoc 1
 .Nm
 parser recognises the following requests.
-Note that the
-.Nm
-language defines many more requests not implemented in
-.Xr mandoc 1 .
+For requests marked as "ignored" or "unsupported", any arguments are
+ignored, and the number of arguments is not checked.
+.Ss \&ab
+Abort processing.
+Currently unsupported.
 .Ss \&ad
 Set line adjustment mode.
-This line-scoped request is intended to have one argument to select
-normal, left, right, or centre adjustment for subsequent text.
-Currently, it is ignored including its arguments,
-and the number of arguments is not checked.
+It takes one argument to select normal, left, right,
+or center adjustment for subsequent text.
+Currently ignored.
+.Ss \&af
+Assign an output format to a number register.
+Currently ignored.
+.Ss \&aln
+Create an alias for a number register.
+Currently unsupported.
+.Ss \&als
+Create an alias for a request, string, macro, or diversion.
+Currently unsupported.
 .Ss \&am
 Append to a macro definition.
 The syntax of this request is the same as that of
 .Sx \&de .
-It is currently ignored by
-.Xr mandoc 1 ,
-as are its children.
-.Ss \&ami
-Append to a macro definition, specifying the macro name indirectly.
-The syntax of this request is the same as that of
-.Sx \&dei .
-It is currently ignored by
-.Xr mandoc 1 ,
-as are its children.
 .Ss \&am1
 Append to a macro definition, switching roff compatibility mode off
-during macro execution.
+during macro execution (groff extension).
 The syntax of this request is the same as that of
 .Sx \&de1 .
-It is currently ignored by
-.Xr mandoc 1 ,
-as are its children.
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&am .
+.Ss \&ami
+Append to a macro definition, specifying the macro name indirectly
+(groff extension).
+The syntax of this request is the same as that of
+.Sx \&dei .
+.Ss \&ami1
+Append to a macro definition, specifying the macro name indirectly
+and switching roff compatibility mode off during macro execution
+(groff extension).
+The syntax of this request is the same as that of
+.Sx \&dei1 .
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&ami .
+.Ss \&as
+Append to a user-defined string.
+The syntax of this request is the same as that of
+.Sx \&ds .
+If a user-defined string with the specified name does not yet exist,
+it is set to the empty string before appending.
+.Ss \&as1
+Append to a user-defined string, switching roff compatibility mode off
+during macro execution (groff extension).
+The syntax of this request is the same as that of
+.Sx \&ds1 .
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&as .
+.Ss \&asciify
+Fully unformat a diversion.
+Currently unsupported.
+.Ss \&backtrace
+Print a backtrace of the input stack.
+This is a groff extension and currently ignored.
+.Ss \&bd
+Artificially embolden by repeated printing with small shifts.
+Currently ignored.
+.Ss \&bleedat
+Set the BleedBox page parameter for PDF generation.
+This is a Heirloom extension and currently ignored.
+.Ss \&blm
+Set a blank line trap.
+Currently unsupported.
+.Ss \&box
+Begin a diversion without including a partially filled line.
+Currently unsupported.
+.Ss \&boxa
+Add to a diversion without including a partially filled line.
+Currently unsupported.
+.Ss \&bp
+Begin new page.
+Currently ignored.
+.Ss \&BP
+Define a frame and place a picture in it.
+This is a Heirloom extension and currently unsupported.
+.Ss \&br
+Break the output line.
+See
+.Xr man 5
+and
+.Xr mdoc 5 .
+.Ss \&break
+Break out of a
+.Sx \&while
+loop.
+Currently unsupported.
+.Ss \&breakchar
+Optional line break characters.
+This is a Heirloom extension and currently ignored.
+.Ss \&brnl
+Break output line after next N input lines.
+This is a Heirloom extension and currently ignored.
+.Ss \&brp
+Break and spread output line.
+Currently, this is implemented as an alias for
+.Sx \&br .
+.Ss \&brpnl
+Break and spread output line after next N input lines.
+This is a Heirloom extension and currently ignored.
+.Ss \&c2
+Change the no-break control character.
+Currently unsupported.
+.Ss \&cc
+Change the control character.
+Its syntax is as follows:
+.Bd -literal -offset indent
+.Pf . Cm \&cc Op Ar c
+.Ed
+.Pp
+If
+.Ar c
+is not specified, the control character is reset to
+.Sq \&. .
+Trailing characters are ignored.
+.Ss \&ce
+Center some lines.
+It takes one integer argument, specifying how many lines to center.
+Currently ignored.
+.Ss \&cf
+Output the contents of a file.
+Ignored because insecure.
+.Ss \&cflags
+Set character flags.
+This is a groff extension and currently ignored.
+.Ss \&ch
+Change a trap location.
+Currently ignored.
+.Ss \&char
+Define a new glyph.
+Currently unsupported.
+.Ss \&chop
+Remove the last character from a macro, string, or diversion.
+Currently unsupported.
+.Ss \&class
+Define a character class.
+This is a groff extension and currently ignored.
+.Ss \&close
+Close an open file.
+Ignored because insecure.
+.Ss \&CL
+Print text in color.
+This is a Heirloom extension and currently unsupported.
+.Ss \&color
+Activate or deactivate colors.
+This is a groff extension and currently ignored.
+.Ss \&composite
+Define a name component for composite glyph names.
+This is a groff extension and currently unsupported.
+.Ss \&continue
+Immediately start the next iteration of a
+.Sx \&while
+loop.
+Currently unsupported.
+.Ss \&cp
+Switch
+.Nm
+compatibility mode on or off.
+Currently ignored.
+.Ss \&cropat
+Set the CropBox page parameter for PDF generation.
+This is a Heirloom extension and currently ignored.
+.Ss \&cs
+Constant character spacing mode.
+Currently ignored.
+.Ss \&cu
+Underline including whitespace.
+Currently ignored.
+.Ss \&da
+Append to a diversion.
+Currently unsupported.
+.Ss \&dch
+Change a trap location in the current diversion.
+This is a Heirloom extension and currently unsupported.
 .Ss \&de
 Define a
 .Nm
@@ -514,32 +683,66 @@ one explicit newline character.
 In order to prevent endless recursion, both groff and
 .Xr mandoc 1
 limit the stack depth for expanding macros and strings
-to a large, but finite number.
-Do not rely on the exact value of this limit.
+to a large, but finite number, and
+.Xr mandoc 1
+also limits the length of the expanded input line.
+Do not rely on the exact values of these limits.
+.Ss \&de1
+Define a
+.Nm
+macro that will be executed with
+.Nm
+compatibility mode switched off during macro execution.
+This is a groff extension.
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&de .
+.Ss \&defcolor
+Define a color name.
+This is a groff extension and currently ignored.
 .Ss \&dei
 Define a
 .Nm
-macro, specifying the macro name indirectly.
+macro, specifying the macro name indirectly (groff extension).
 The syntax of this request is the same as that of
 .Sx \&de .
-It is currently ignored by
-.Xr mandoc 1 ,
-as are its children.
-.Ss \&de1
+The request
+.Pp
+.D1 Pf . Cm \&dei Ar name Op Ar end
+.Pp
+has the same effect as:
+.Pp
+.D1 Pf . Cm \&de No \e* Ns Bo Ar name Bc Op \e* Ns Bq Ar end
+.Ss \&dei1
 Define a
 .Nm
 macro that will be executed with
 .Nm
-compatibility mode switched off during macro execution.
-This is a GNU extension not available in traditional
-.Nm
-implementations and not even in older versions of groff.
+compatibility mode switched off during macro execution,
+specifying the macro name indirectly (groff extension).
 Since
 .Xr mandoc 1
 does not implement
 .Nm
 compatibility mode at all, it handles this request as an alias for
-.Sx \&de .
+.Sx \&dei .
+.Ss \&device
+This request only makes sense with the groff-specific intermediate
+output format and is unsupported.
+.Ss \&devicem
+This request only makes sense with the groff-specific intermediate
+output format and is unsupported.
+.Ss \&di
+Begin a diversion.
+Currently unsupported.
+.Ss \&do
+Execute
+.Nm
+request or macro line with compatibility mode disabled.
+Currently unsupported.
 .Ss \&ds
 Define a user-defined string.
 Its syntax is as follows:
@@ -598,6 +801,32 @@ macro when used in a
 .Xr man 5
 document.
 Such abuse is of course strongly discouraged.
+.Ss \&ds1
+Define a user-defined string that will be expanded with
+.Nm
+compatibility mode switched off during string expansion.
+This is a groff extension.
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&ds .
+.Ss \&dwh
+Set a location trap in the current diversion.
+This is a Heirloom extension and currently unsupported.
+.Ss \&dt
+Set a trap within a diversion.
+Currently unsupported.
+.Ss \&ec
+Change the escape character.
+Currently unsupported.
+.Ss \&ecs
+Restore the escape character.
+Currently unsupported.
+.Ss \&ecr
+Save the escape character.
+Currently unsupported.
 .Ss \&el
 The
 .Qq else
@@ -612,18 +841,171 @@ then false is assumed.
 The syntax of this request is similar to
 .Sx \&if
 except that the conditional is missing.
+.Ss \&em
+Set a trap at the end of input.
+Currently unsupported.
 .Ss \&EN
 End an equation block.
 See
 .Sx \&EQ .
+.Ss \&eo
+Disable the escape mechanism completely.
+Currently unsupported.
+.Ss \&EP
+End a picture started by
+.Sx \&BP .
+This is a Heirloom extension and currently unsupported.
 .Ss \&EQ
 Begin an equation block.
 See
 .Xr eqn 5
 for a description of the equation language.
+.Ss \&errprint
+Print a string like an error message.
+This is a Heirloom extension and currently ignored.
+.Ss \&ev
+Switch to another environment.
+Currently unsupported.
+.Ss \&evc
+Copy an environment into the current environment.
+Currently unsupported.
+.Ss \&ex
+Abort processing and exit.
+Currently unsupported.
+.Ss \&fallback
+Select the fallback sequence for a font.
+This is a Heirloom extension and currently ignored.
+.Ss \&fam
+Change the font family.
+Takes one argument specifying the font family to be selected.
+It is a groff extension and currently ignored.
+.Ss \&fc
+Define a delimiting and a padding character for fields.
+Currently unsupported.
+.Ss \&fchar
+Define a fallback glyph.
+Currently unsupported.
+.Ss \&fcolor
+Set the fill color for \eD objects.
+This is a groff extension and currently ignored.
+.Ss \&fdeferlig
+Defer ligature building.
+This is a Heirloom extension and currently ignored.
+.Ss \&feature
+Enable or disable an OpenType feature.
+This is a Heirloom extension and currently ignored.
+.Ss \&fi
+Switch to fill mode.
+See
+.Xr man 5 .
+Ignored in
+.Xr mdoc 5 .
+.Ss \&fkern
+Control the use of kerning tables for a font.
+This is a Heirloom extension and currently ignored.
+.Ss \&fl
+Flush output.
+Currently ignored.
+.Ss \&flig
+Define ligatures.
+This is a Heirloom extension and currently ignored.
+.Ss \&fp
+Assign font position.
+Currently ignored.
+.Ss \&fps
+Mount a font with a special character map.
+This is a Heirloom extension and currently ignored.
+.Ss \&fschar
+Define a font-specific fallback glyph.
+This is a groff extension and currently unsupported.
+.Ss \&fspacewidth
+Set a font-specific width for the space character.
+This is a Heirloom extension and currently ignored.
+.Ss \&fspecial
+Conditionally define a special font.
+This is a groff extension and currently ignored.
+.Ss \&ft
+Change the font.
+Its syntax is as follows:
+.Pp
+.D1 Pf . Cm \&ft Op Ar font
+.Pp
+The following
+.Ar font
+arguments are supported:
+.Bl -tag -width 4n -offset indent
+.It Cm B , BI , 3 , 4
+switches to
+.Sy bold
+font
+.It Cm I , 2
+switches to
+.Em underlined
+font
+.It Cm R , CW , 1
+switches to normal font
+.It Cm P No "or no argument"
+switches back to the previous font
+.El
+.Pp
+This request takes effect only locally, may be overridden by macros
+and escape sequences, and is only supported in
+.Xr man 5
+for now.
+.Ss \&ftr
+Translate font name.
+This is a groff extension and currently ignored.
+.Ss \&fzoom
+Zoom font size.
+Currently ignored.
+.Ss \&gcolor
+Set glyph color.
+This is a groff extension and currently ignored.
+.Ss \&hc
+Set the hyphenation character.
+Currently ignored.
+.Ss \&hcode
+Set hyphenation codes of characters.
+Currently ignored.
+.Ss \&hidechar
+Hide characters in a font.
+This is a Heirloom extension and currently ignored.
+.Ss \&hla
+Set hyphenation language.
+This is a groff extension and currently ignored.
+.Ss \&hlm
+Set maximum number of consecutive hyphenated lines.
+Currently ignored.
+.Ss \&hpf
+Load hyphenation pattern file.
+This is a groff extension and currently ignored.
+.Ss \&hpfa
+Load hyphenation pattern file, appending to the current patterns.
+This is a groff extension and currently ignored.
+.Ss \&hpfcode
+Define mapping values for character codes in hyphenation patterns.
+This is a groff extension and currently ignored.
+.Ss \&hw
+Specify hyphenation points in words.
+Currently ignored.
 .Ss \&hy
 Set automatic hyphenation mode.
-This line-scoped request is currently ignored.
+Currently ignored.
+.Ss \&hylang
+Set hyphenation language.
+This is a Heirloom extension and currently ignored.
+.Ss \&hylen
+Minimum word length for hyphenation.
+This is a Heirloom extension and currently ignored.
+.Ss \&hym
+Set hyphenation margin.
+This is a groff extension and currently ignored.
+.Ss \&hypp
+Define hyphenation penalties.
+This is a Heirloom extension and currently ignored.
+.Ss \&hys
+Set hyphenation space.
+This is a groff extension and currently ignored.
 .Ss \&ie
 The
 .Qq if
@@ -636,10 +1018,69 @@ Its syntax is equivalent to
 .Sx \&if .
 .Ss \&if
 Begins a conditional.
-Right now, the conditional evaluates to true
-if and only if it starts with the letter
-.Sy n ,
-indicating processing in nroff style as opposed to troff style.
+This request has the following syntax:
+.Bd -literal -offset indent
+\&.if COND BODY
+.Ed
+.Bd -literal -offset indent
+\&.if COND \e{BODY
+BODY...\e}
+.Ed
+.Bd -literal -offset indent
+\&.if COND \e{\e
+BODY...
+\&.\e}
+.Ed
+.Pp
+COND is a conditional statement.
+Currently,
+.Xr mandoc 1
+supports the following subset of roff conditionals:
+.Bl -bullet
+.It
+If
+.Sq \&!
+is prefixed to COND, the condition is logically inverted.
+.It
+If the first character of COND is
+.Sq n
+.Pq nroff mode
+or
+.Sq o
+.Pq odd page ,
+COND evaluates to true.
+.It
+If the first character of COND is
+.Sq c
+.Pq character available ,
+.Sq d
+.Pq string defined ,
+.Sq e
+.Pq even page ,
+.Sq r
+.Pq register accessed ,
+.Sq t
+.Pq troff mode ,
+or
+.Sq v
+.Pq vroff mode ,
+COND evaluates to false.
+.It
+If COND starts with a parenthesis or with an optionally signed
+integer number, it is evaluated according to the rules of
+.Sx Numerical expressions
+explained below.
+It evaluates to true if the result is positive,
+or to false if the result is zero or negative.
+.It
+Otherwise, the first character of COND is regarded as a delimiter
+and COND evaluates to true if the string extending from its first
+to its second occurrence is equal to the string extending from its
+second to its third occurrence.
+.It
+If COND cannot be parsed, it evaluates to false.
+.El
+.Pp
 If a conditional is false, its children are not processed, but are
 syntactically interpreted to preserve the integrity of the input
 document.
@@ -657,44 +1098,12 @@ will continue to syntactically interpret to the block close of the final
 conditional.
 Sub-conditionals, in this case, obviously inherit the truth value of
 the parent.
-This request has the following syntax:
-.Bd -literal -offset indent
-\&.if COND \e{\e
-BODY...
-\&.\e}
-.Ed
-.Bd -literal -offset indent
-\&.if COND \e{ BODY
-BODY... \e}
-.Ed
-.Bd -literal -offset indent
-\&.if COND \e{ BODY
-BODY...
-\&.\e}
-.Ed
-.Bd -literal -offset indent
-\&.if COND \e
-BODY
-.Ed
-.Pp
-COND is a conditional statement.
-roff allows for complicated conditionals; mandoc is much simpler.
-At this time, mandoc supports only
-.Sq n ,
-evaluating to true;
-and
-.Sq t ,
-.Sq e ,
-and
-.Sq o ,
-evaluating to false.
-All other invocations are read up to the next end of line or space and
-evaluate as false.
 .Pp
 If the BODY section is begun by an escaped brace
 .Sq \e{ ,
-scope continues until a closing-brace escape sequence
-.Sq \.\e} .
+scope continues until the end of the input line containing the
+matching closing-brace escape sequence
+.Sq \e} .
 If the BODY is not enclosed in braces, scope continues until
 the end of the line.
 If the COND is followed by a BODY on the same line, whether after a
@@ -774,33 +1183,181 @@ Otherwise, it only terminates the
 and arguments following it or the
 .Sq \&..
 request are discarded.
+.Ss \&in
+Change indentation.
+See
+.Xr man 5 .
+Ignored in
+.Xr mdoc 5 .
+.Ss \&index
+Find a substring in a string.
+This is a Heirloom extension and currently unsupported.
+.Ss \&it
+Set an input line trap.
+Its syntax is as follows:
+.Pp
+.D1 Pf . Cm it Ar expression macro
+.Pp
+The named
+.Ar macro
+will be invoked after processing the number of input text lines
+specified by the numerical
+.Ar expression .
+While evaluating the
+.Ar expression ,
+the unit suffixes described below
+.Sx Scaling Widths
+are ignored.
+.Ss \&itc
+Set an input line trap, not counting lines ending with \ec.
+Currently unsupported.
+.Ss \&IX
+To support the generation of a table of contents,
+.Xr pod2man 1
+emits this user-defined macro, usually without defining it.
+To avoid reporting large numbers of spurious errors,
+.Xr mandoc 1
+ignores it.
+.Ss \&kern
+Switch kerning on or off.
+Currently ignored.
+.Ss \&kernafter
+Increase kerning after some characters.
+This is a Heirloom extension and currently ignored.
+.Ss \&kernbefore
+Increase kerning before some characters.
+This is a Heirloom extension and currently ignored.
+.Ss \&kernpair
+Add a kerning pair to the kerning table.
+This is a Heirloom extension and currently ignored.
+.Ss \&lc
+Define a leader repetition character.
+Currently unsupported.
+.Ss \&lc_ctype
+Set the
+.Dv LC_CTYPE
+locale.
+This is a Heirloom extension and currently unsupported.
+.Ss \&lds
+Define a local string.
+This is a Heirloom extension and currently unsupported.
+.Ss \&length
+Count the number of input characters in a user-defined string.
+Currently unsupported.
+.Ss \&letadj
+Dynamic letter spacing and reshaping.
+This is a Heirloom extension and currently ignored.
+.Ss \&lf
+Change the line number for error messages.
+Ignored because insecure.
+.Ss \&lg
+Switch the ligature mechanism on or off.
+Currently ignored.
+.Ss \&lhang
+Hang characters at left margin.
+This is a Heirloom extension and currently ignored.
+.Ss \&linetabs
+Enable or disable line-tabs mode.
+This is a groff extension and currently unsupported.
+.Ss \&ll
+Change the output line length.
+Its syntax is as follows:
+.Pp
+.D1 Pf . Cm \&ll Op Oo +|- Oc Ns Ar width
+.Pp
+If the
+.Ar width
+argument is omitted, the line length is reset to its previous value.
+The default setting for terminal output is 58n.
+If a sign is given, the line length is added to or subtracted from;
+otherwise, it is set to the provided value.
+Using this request in new manuals is discouraged for several reasons,
+among others because it overrides the
+.Xr mandoc 1
+.Fl O Cm width
+command line option.
+.Ss \&lnr
+Set local number register.
+This is a Heirloom extension and currently unsupported.
+.Ss \&lnrf
+Set local floating-point register.
+This is a Heirloom extension and currently unsupported.
+.Ss \&lpfx
+Set a line prefix.
+This is a Heirloom extension and currently unsupported.
+.Ss \&ls
+Set line spacing.
+It takes one integer argument specifying the vertical distance of
+subsequent output text lines measured in v units.
+Currently ignored.
+.Ss \&lsm
+Set a leading spaces trap.
+This is a groff extension and currently unsupported.
+.Ss \<
+Set title line length.
+Currently ignored.
+.Ss \&mc
+Print margin character in the right margin.
+Currently ignored.
+.Ss \&mediasize
+Set the device media size.
+This is a Heirloom extension and currently ignored.
+.Ss \&minss
+Set minimum word space.
+This is a Heirloom extension and currently ignored.
+.Ss \&mk
+Mark vertical position.
+Currently ignored.
+.Ss \&mso
+Load a macro file.
+Ignored because insecure.
+.Ss \&na
+Disable adjusting without changing the adjustment mode.
+Currently ignored.
 .Ss \&ne
 Declare the need for the specified minimum vertical space
 before the next trap or the bottom of the page.
-This line-scoped request is currently ignored.
+Currently ignored.
+.Ss \&nf
+Switch to no-fill mode.
+See
+.Xr man 5 .
+Ignored by
+.Xr mdoc 5 .
 .Ss \&nh
 Turn off automatic hyphenation mode.
-This line-scoped request is currently ignored.
-.Ss \&rm
-Remove a request, macro or string.
-This request is intended to have one argument,
-the name of the request, macro or string to be undefined.
-Currently, it is ignored including its arguments,
-and the number of arguments is not checked.
+Currently ignored.
+.Ss \&nhychar
+Define hyphenation-inhibiting characters.
+This is a Heirloom extension and currently ignored.
+.Ss \&nm
+Print line numbers.
+Currently unsupported.
+.Ss \&nn
+Temporarily turn off line numbering.
+Currently unsupported.
+.Ss \&nop
+Exexute the rest of the input line as a request or macro line.
+Currently unsupported.
 .Ss \&nr
-Define a register.
+Define or change a register.
 A register is an arbitrary string value that defines some sort of state,
 which influences parsing and/or formatting.
 Its syntax is as follows:
 .Pp
-.D1 Pf \. Cm \&nr Ar name Ar value
+.D1 Pf \. Cm \&nr Ar name Oo +|- Oc Ns Ar expression
 .Pp
-The
-.Ar value
-may, at the moment, only be an integer.
-So far, only the following register
+For the syntax of
+.Ar expression ,
+see
+.Sx Numerical expressions
+below.
+If it is prefixed by a sign, the register will be
+incremented or decremented instead of assigned to.
+.Pp
+The following register
 .Ar name
-is recognised:
+is handled specially:
 .Bl -tag -width Ds
 .It Cm nS
 If set to a positive integer value, certain
@@ -819,16 +1376,142 @@ section with the
 .Cm \&Sh
 macro will reset this register.
 .El
+.Ss \&nrf
+Define or change a floating-point register.
+This is a Heirloom extension and currently unsupported.
+.Ss \&nroff
+Force nroff mode.
+This is a groff extension and currently ignored.
 .Ss \&ns
 Turn on no-space mode.
-This line-scoped request is intended to take no arguments.
-Currently, it is ignored including its arguments,
-and the number of arguments is not checked.
+Currently ignored.
+.Ss \&nx
+Abort processing of the current input file and process another one.
+Ignored because insecure.
+.Ss \&open
+Open a file for writing.
+Ignored because insecure.
+.Ss \&opena
+Open a file for appending.
+Ignored because insecure.
+.Ss \&os
+Output saved vertical space.
+Currently ignored.
+.Ss \&output
+Output directly to intermediate output.
+Not supported.
+.Ss \&padj
+Globally control paragraph-at-once adjustment.
+This is a Heirloom extension and currently ignored.
+.Ss \&papersize
+Set the paper size.
+This is a Heirloom extension and currently ignored.
+.Ss \&pc
+Change the page number character.
+Currently ignored.
+.Ss \&pev
+Print environments.
+This is a groff extension and currently ignored.
+.Ss \&pi
+Pipe output to a shell command.
+Ignored because insecure.
+.Ss \&PI
+Low-level request used by
+.Sx \&BP .
+This is a Heirloom extension and currently unsupported.
+.Ss \&pl
+Change page length.
+Takes one height argument.
+Currently ignored.
+.Ss \&pm
+Print names and sizes of macros, strings, and diversions.
+Currently ignored.
+.Ss \&pn
+Change page number of the next page.
+Currently ignored.
+.Ss \&pnr
+Print all number registers.
+Currently ignored.
+.Ss \&po
+Set horizontal page offset.
+Currently ignored.
 .Ss \&ps
 Change point size.
-This line-scoped request is intended to take one numerical argument.
-Currently, it is ignored including its arguments,
-and the number of arguments is not checked.
+Takes one numerical argument.
+Currently ignored.
+.Ss \&psbb
+Retrieve the bounding box of a PostScript file.
+Currently unsupported.
+.Ss \&pshape
+Set a special shape for the current paragraph.
+This is a Heirloom extension and currently unsupported.
+.Ss \&pso
+Include output of a shell command.
+Ignored because insecure.
+.Ss \&ptr
+Print the names and positions of all traps.
+This is a groff extension and currently ignored.
+.Ss \&pvs
+Change post-vertical spacing.
+This is a groff extension and currently ignored.
+.Ss \&rchar
+Remove glyph definitions.
+Currently unsupported.
+.Ss \&rd
+Read from standard input.
+Currently ignored.
+.Ss \&recursionlimit
+Set the maximum stack depth for recursive macros.
+This is a Heirloom extension and currently ignored.
+.Ss \&return
+Exit a macro and return to the caller.
+Currently unsupported.
+.Ss \&rfschar
+Remove font-specific fallback glyph definitions.
+Currently unsupported.
+.Ss \&rhang
+Hang characters at right margin.
+This is a Heirloom extension and currently ignored.
+.Ss \&rj
+Justify unfilled text to the right margin.
+Currently ignored.
+.Ss \&rm
+Remove a request, macro or string.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&rm Ar name
+.Ss \&rn
+Rename a request, macro, diversion, or string.
+Currently unsupported.
+.Ss \&rnn
+Rename a number register.
+Currently unsupported.
+.Ss \&rr
+Remove a register.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&rr Ar name
+.Ss \&rs
+End no-space mode.
+Currently ignored.
+.Ss \&rt
+Return to marked vertical position.
+Currently ignored.
+.Ss \&schar
+Define global fallback glyph.
+This is a groff extension and currently unsupported.
+.Ss \&sentchar
+Define sentence-ending characters.
+This is a Heirloom extension and currently ignored.
+.Ss \&shc
+Change the soft hyphen character.
+Currently ignored.
+.Ss \&shift
+Shift macro arguments.
+Currently unsupported.
+.Ss \&sizes
+Define permissible point sizes.
+This is a groff extension and currently ignored.
 .Ss \&so
 Include a source file.
 Its syntax is as follows:
@@ -862,10 +1545,64 @@ is discouraged.
 Use
 .Xr ln 1
 instead.
+.Ss \&spacewidth
+Set the space width from the font metrics file.
+This is a Heirloom extension and currently ignored.
+.Ss \&special
+Define a special font.
+This is a groff extension and currently ignored.
+.Ss \&spreadwarn
+Warn about wide spacing between words.
+Currently ignored.
+.Ss \&ss
+Set space character size.
+Currently ignored.
+.Ss \&sty
+Associate style with a font position.
+This is a groff extension and currently ignored.
+.Ss \&substring
+Replace a user-defined string with a substring.
+Currently unsupported.
+.Ss \&sv
+Save vertical space.
+Currently ignored.
+.Ss \&sy
+Execute shell command.
+Ignored because insecure.
+.Ss \&T&
+Re-start a table layout, retaining the options of the prior table
+invocation.
+See
+.Sx \&TS .
 .Ss \&ta
 Set tab stops.
-This line-scoped request can take an arbitrary number of arguments.
-Currently, it is ignored including its arguments.
+Takes an arbitrary number of arguments.
+Currently unsupported.
+.Ss \&tc
+Change tab repetion character.
+Currently unsupported.
+.Ss \&TE
+End a table context.
+See
+.Sx \&TS .
+.Ss \&ti
+Temporary indent.
+Currently unsupported.
+.Ss \&tkf
+Enable track kerning for a font.
+Currently ignored.
+.Ss \&tl
+Print a title line.
+Currently unsupported.
+.Ss \&tm
+Print to standard error output.
+Currently ignored.
+.Ss \&tm1
+Print to standard error output, allowing leading blanks.
+This is a groff extension and currently ignored.
+.Ss \&tmc
+Print to standard error output without a trailing newline.
+This is a groff extension and currently ignored.
 .Ss \&tr
 Output character translation.
 Its syntax is as follows:
@@ -883,59 +1620,479 @@ Replacement (or origin) characters may also be character escapes; thus,
 .Dl tr \e(xx\e(yy
 .Pp
 replaces all invocations of \e(xx with \e(yy.
-.Ss \&T&
-Re-start a table layout, retaining the options of the prior table
-invocation.
-See
-.Sx \&TS .
-.Ss \&TE
-End a table context.
-See
-.Sx \&TS .
+.Ss \&track
+Static letter space tracking.
+This is a Heirloom extension and currently ignored.
+.Ss \&transchar
+Define transparent characters for sentence-ending.
+This is a Heirloom extension and currently ignored.
+.Ss \&trf
+Output the contents of a file, disallowing invalid characters.
+This is a groff extension and ignored because insecure.
+.Ss \&trimat
+Set the TrimBox page parameter for PDF generation.
+This is a Heirloom extension and currently ignored.
+.Ss \&trin
+Output character translation, ignored by
+.Cm \&asciify .
+Currently unsupported.
+.Ss \&trnt
+Output character translation, ignored by \e!.
+Currently unsupported.
+.Ss \&troff
+Force troff mode.
+This is a groff extension and currently ignored.
 .Ss \&TS
 Begin a table, which formats input in aligned rows and columns.
 See
 .Xr tbl 5
 for a description of the tbl language.
+.Ss \&uf
+Globally set the underline font.
+Currently ignored.
+.Ss \&ul
+Underline.
+Currently ignored.
+.Ss \&unformat
+Unformat spaces and tabs in a diversion.
+Currently unsupported.
+.Ss \&unwatch
+Disable notification for string or macro.
+This is a Heirloom extension and currently ignored.
+.Ss \&unwatchn
+Disable notification for register.
+This is a Heirloom extension and currently ignored.
+.Ss \&vpt
+Enable or disable vertical position traps.
+This is a groff extension and currently ignored.
+.Ss \&vs
+Change vertical spacing.
+Currently ignored.
+.Ss \&warn
+Set warning level.
+Currently ignored.
+.Ss \&warnscale
+Set the scaling indicator used in warnings.
+This is a groff extension and currently ignored.
+.Ss \&watch
+Notify on change of string or macro.
+This is a Heirloom extension and currently ignored.
+.Ss \&watchlength
+On change, report the contents of macros and strings
+up to the sepcified length.
+This is a Heirloom extension and currently ignored.
+.Ss \&watchn
+Notify on change of register.
+This is a Heirloom extension and currently ignored.
+.Ss \&wh
+Set a page location trap.
+Currently unsupported.
+.Ss \&while
+Repeated execution while a condition is true.
+Currently unsupported.
+.Ss \&write
+Write to an open file.
+Ignored because insecure.
+.Ss \&writec
+Write to an open file without appending a newline.
+Ignored because insecure.
+.Ss \&writem
+Write macro or string to an open file.
+Ignored because insecure.
+.Ss \&xflag
+Set the extension level.
+This is a Heirloom extension and currently ignored.
+.Ss Numerical expressions
+The
+.Sx \&nr ,
+.Sx \&if ,
+and
+.Sx \&ie
+requests accept integer numerical expressions as arguments.
+These are always evaluated using the C
+.Vt int
+type; integer overflow works the same way as in the C language.
+Numbers consist of an arbitrary number of digits
+.Sq 0
+to
+.Sq 9
+prefixed by an optional sign
+.Sq +
+or
+.Sq - .
+Each number may be followed by one optional scaling unit described below
+.Sx Scaling Widths .
+The following equations hold:
+.Bd -literal -offset indent
+1i = 6v = 6P = 10m = 10n = 52p = 1000M = 240u = 240
+254c = 100i = 24000u = 24000
+1f = 65536u = 65536
+.Ed
+.Pp
+The following binary operators are implemented.
+Unless otherwise stated, they behave as in the C language:
+.Pp
+.Bl -tag -width 2n -compact
+.It Ic +
+addition
+.It Ic -
+subtraction
+.It Ic *
+multiplication
+.It Ic /
+division
+.It Ic %
+remainder of division
+.It Ic <
+less than
+.It Ic >
+greater than
+.It Ic ==
+equal to
+.It Ic =
+equal to, same effect as
+.Ic ==
+(this differs from C)
+.It Ic <=
+less than or equal to
+.It Ic >=
+greater than or equal to
+.It Ic <>
+not equal to (corresponds to C
+.Ic != ;
+this one is of limited portability, it is supported by Heirloom roff,
+but not by groff)
+.It Ic &
+logical and (corresponds to C
+.Ic && )
+.It Ic \&:
+logical or (corresponds to C
+.Ic \&|| )
+.It Ic ?
+maximum (not available in C)
+.El
+.Pp
+There is no concept of precendence; evaluation proceeds from left to right,
+except when subexpressions are enclosed in parantheses.
+Inside parentheses, whitespace is ignored.
+.Sh ESCAPE SEQUENCE REFERENCE
+The
+.Xr mandoc 1
+.Nm
+parser recognises the following escape sequences.
+Note that the
+.Nm
+language defines more escape sequences not implemented in
+.Xr mandoc 1 .
+In
+.Xr mdoc 5
+and
+.Xr man 5
+documents, using escape sequences is discouraged except for those
+described in the
+.Sx LANGUAGE SYNTAX
+section above.
+.Pp
+A backslash followed by any character not listed here
+simply prints that character itself.
+.Ss \e
+A backslash at the end of an input line can be used to continue the
+logical input line on the next physical input line, joining the text
+on both lines together as if it were on a single input line.
+.Ss \e
+The escape sequence backslash-space
+.Pq Sq \e\ \&
+is an unpaddable space-sized non-breaking space character; see
+.Sx Whitespace .
+.Ss \e\(dq
+The rest of the input line is treated as
+.Sx Comments .
+.Ss \e%
+Hyphenation allowed at this point of the word; ignored by
+.Xr mandoc 1 .
+.Ss \e&
+Non-printing zero-width character; see
+.Sx Whitespace .
+.Ss \e\(aq
+Acute accent special character; use
+.Sq \e(aa
+instead.
+.Ss \e( Ns Ar cc
+.Sx Special Characters
+with two-letter names, see
+.Xr mandoc_char 5 .
+.Ss \e*[ Ns Ar name ]
+Interpolate the string with the
+.Ar name ;
+see
+.Sx Predefined Strings
+and
+.Sx ds .
+For short names, there are variants
+.No \e* Ns Ar c
+and
+.No \e*( Ns Ar cc .
+.Ss \e-
+Special character
+.Dq mathematical minus sign .
+.Ss \e[ Ns Ar name ]
+.Sx Special Characters
+with names of arbitrary length, see
+.Xr mandoc_char 5 .
+.Ss \e^
+One-twelfth em half-narrow space character, effectively zero-width in
+.Xr mandoc 1 .
+.Ss \e`
+Grave accent special character; use
+.Sq \e(ga
+instead.
+.Ss \e{
+Begin conditional input; see
+.Sx if .
+.Ss \e\(ba
+One-sixth em narrow space character, effectively zero-width in
+.Xr mandoc 1 .
+.Ss \e}
+End conditional input; see
+.Sx if .
+.Ss \e~
+Paddable non-breaking space character.
+.Ss \e0
+Digit width space character.
+.Ss \eA\(aq Ns Ar string Ns \(aq
+Anchor definition; ignored by
+.Xr mandoc 1 .
+.Ss \eB\(aq Ns Ar string Ns \(aq
+Interpolate
+.Sq 1
+if
+.Ar string
+conforms to the syntax of
+.Sx Numerical expressions
+explained above and
+.Sq 0
+otherwise.
+.Ss \eb\(aq Ns Ar string Ns \(aq
+Bracket building function; ignored by
+.Xr mandoc 1 .
+.Ss \eC\(aq Ns Ar name Ns \(aq
+.Sx Special Characters
+with names of arbitrary length.
+.Ss \ec
+When encountered at the end of an input text line,
+the next input text line is considered to continue that line,
+even if there are request or macro lines in between.
+No whitespace is inserted.
+.Ss \eD\(aq Ns Ar string Ns \(aq
+Draw graphics function; ignored by
+.Xr mandoc 1 .
+.Ss \ed
+Move down by half a line; ignored by
+.Xr mandoc 1 .
+.Ss \ee
+Backslash special character.
+.Ss \eF[ Ns Ar name ]
+Switch font family (groff extension); ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eF Ns Ar c
+and
+.No \eF( Ns Ar cc .
+.Ss \ef[ Ns Ar name ]
+Switch to the font
+.Ar name ,
+see
+.Sx Text Decoration .
+For short names, there are variants
+.No \ef Ns Ar c
+and
+.No \ef( Ns Ar cc .
+.Ss \eg[ Ns Ar name ]
+Interpolate the format of a number register; ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eg Ns Ar c
+and
+.No \eg( Ns Ar cc .
+.Ss \eH\(aq Ns Oo +|- Oc Ns Ar number Ns \(aq
+Set the height of the current font; ignored by
+.Xr mandoc 1 .
+.Ss \eh\(aq Ns Ar number Ns \(aq
+Horizontal motion; ignored by
+.Xr mandoc 1 .
+.Ss \ek[ Ns Ar name ]
+Mark horizontal input place in register; ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \ek Ns Ar c
+and
+.No \ek( Ns Ar cc .
+.Ss \eL\(aq Ns Ar number Ns Oo Ar c Oc Ns \(aq
+Vertical line drawing function; ignored by
+.Xr mandoc 1 .
+.Ss \el\(aq Ns Ar number Ns Oo Ar c Oc Ns \(aq
+Horizontal line drawing function; ignored by
+.Xr mandoc 1 .
+.Ss \eM[ Ns Ar name ]
+Set fill (background) color (groff extension); ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eM Ns Ar c
+and
+.No \eM( Ns Ar cc .
+.Ss \em[ Ns Ar name ]
+Set glyph drawing color (groff extension); ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \em Ns Ar c
+and
+.No \em( Ns Ar cc .
+.Ss \eN\(aq Ns Ar number Ns \(aq
+Character
+.Ar number
+on the current font.
+.Ss \en[ Ns Ar name ]
+Interpolate the number register
+.Ar name .
+For short names, there are variants
+.No \en Ns Ar c
+and
+.No \en( Ns Ar cc .
+.Ss \eo\(aq Ns Ar string Ns \(aq
+Overstrike, writing all the characters contained in the
+.Ar string
+to the same output position.
+In terminal and HTML output modes,
+only the last one of the characters is visible.
+.Ss \eR\(aq Ns Ar name Oo +|- Oc Ns Ar number Ns \(aq
+Set number register; ignored by
+.Xr mandoc 1 .
+.Ss \eS\(aq Ns Ar number Ns \(aq
+Slant output; ignored by
+.Xr mandoc 1 .
+.Ss \es\(aq Ns Oo +|- Oc Ns Ar number Ns \(aq
+Change point size; ignored by
+.Xr mandoc 1 .
+Alternative forms
+.No \es Ns Oo +|- Oc Ns Ar n ,
+.No \es Ns Oo +|- Oc Ns \(aq Ns Ar number Ns \(aq ,
+.No \es Ns [ Oo +|- Oc Ns Ar number ] ,
+and
+.No \es Ns Oo +|- Oc Ns [ Ar number Ns ]
+are also parsed and ignored.
+.Ss \et
+Horizontal tab; ignored by
+.Xr mandoc 1 .
+.Ss \eu
+Move up by half a line; ignored by
+.Xr mandoc 1 .
+.Ss \eV[ Ns Ar name ]
+Interpolate an environment variable; ignored by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eV Ns Ar c
+and
+.No \eV( Ns Ar cc .
+.Ss \ev\(aq Ns Ar number Ns \(aq
+Vertical motion; ignored by
+.Xr mandoc 1 .
+.Ss \ew\(aq Ns Ar string Ns \(aq
+Interpolate the width of the
+.Ar string .
+The
+.Xr mandoc 1
+implementation assumes that after expansion of user-defined strings, the
+.Ar string
+only contains normal characters, no escape sequences, and that each
+character has a width of 24 basic units.
+.Ss \eX\(aq Ns Ar string Ns \(aq
+Output
+.Ar string
+as device control function; ignored in nroff mode and by
+.Xr mandoc 1 .
+.Ss \ex\(aq Ns Ar number Ns \(aq
+Extra line space function; ignored by
+.Xr mandoc 1 .
+.Ss \eY[ Ns Ar name ]
+Output a string as a device control function; ignored in nroff mode and by
+.Xr mandoc 1 .
+For short names, there are variants
+.No \eY Ns Ar c
+and
+.No \eY( Ns Ar cc .
+.Ss \eZ\(aq Ns Ar string Ns \(aq
+Print
+.Ar string
+with zero width and height; ignored by
+.Xr mandoc 1 .
+.Ss \ez
+Output the next character without advancing the cursor position;
+approximated in
+.Xr mandoc 1
+by simply skipping the next character.
 .Sh COMPATIBILITY
-This section documents compatibility between mandoc and other other
+The
+.Xr mandoc 1
+implementation of the
 .Nm
-implementations, at this time limited to GNU troff
-.Pq Qq groff .
-The term
-.Qq historic groff
-refers to groff version 1.15.
+language is intentionally incomplete.
+Unimplemented features include:
 .Pp
 .Bl -dash -compact
 .It
-In mandoc, the
-.Sx \&EQ ,
-.Sx \&TE ,
-.Sx \&TS ,
-and
-.Sx \&T& ,
-macros are considered regular macros.
-In all other
-.Nm
-implementations, these are special macros that must be specified without
-spacing between the control character (which must be a period) and the
-macro name.
+For security reasons,
+.Xr mandoc 1
+never reads or writes external files except via
+.Sx \&so
+requests with safe relative paths.
+.It
+There is no automatic hyphenation, no adjustment to the right margin,
+and no centering; the output is always set flush-left.
+.It
+Support for setting tabulator positions
+and tabulator and leader characters is missing,
+and support for manually changing indentation is limited.
 .It
 The
-.Cm nS
-register is only compatible with OpenBSD's groff-1.15.
+.Sq u
+scaling unit is the default terminal unit.
+In traditional troff systems, this unit changes depending on the
+output media.
 .It
-Historic groff did not accept white-space before a custom
-.Ar end
-macro for the
-.Sx \&ig
-request.
+Width measurements are implemented in a crude way
+and often yield wrong results.
+Explicit movement requests and escapes are ignored.
+.It
+There is no concept of output pages, no support for floats,
+graphics drawing, and picture inclusion;
+terminal output is always continuous.
+.It
+Requests regarding color, font families, and glyph manipulation
+are ignored.
+Font support is very limited.
+Kerning is not implemented, and no ligatures are produced.
 .It
 The
-.Sx \&if
-and family would print funny white-spaces with historic groff when
-using the next-line syntax.
+.Qq \(aq
+macro control character does not suppress output line breaks.
+.It
+Diversions are not implemented,
+and support for traps is very incomplete.
+.It
+While recursion is supported,
+.Sx \&while
+loops are not.
 .El
+.Pp
+The special semantics of the
+.Cm nS
+number register is an idiosyncracy of
+.Ox
+manuals and not supported by other
+.Xr mdoc 5
+implementations.
 .Sh SEE ALSO
 .Xr mandoc 1 ,
 .Xr eqn 5 ,
@@ -984,8 +2141,6 @@ In 1989, James Clarke re-implemented troff in C++, naming it groff.
 This
 .Nm
 reference was written by
-.An Kristaps Dzonsons ,
-.Mt kristaps@bsd.lv ;
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
 and
-.An Ingo Schwarze ,
-.Mt schwarze@openbsd.org .
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/usr/src/man/man5/mdoc.5 b/usr/src/man/man5/mdoc.5
index 901a2848cd..a981edd5ff 100644
--- a/usr/src/man/man5/mdoc.5
+++ b/usr/src/man/man5/mdoc.5
@@ -1,3 +1,7 @@
+.\"	$Id: mdoc.7,v 1.252 2015/02/23 13:31:04 schwarze Exp $
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
+.\" Copyright (c) 2010, 2011, 2013 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -12,12 +16,10 @@
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
 .\"
-.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2010, 2011 Ingo Schwarze 
-.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
 .\" Copyright 2014 Garrett D'Amore 
+.\" Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 .\"
-.Dd Jul 19, 2014
+.Dd Oct 18, 2015
 .Dt MDOC 5
 .Os
 .Sh NAME
@@ -73,17 +75,17 @@ Text lines are interpreted within the current state.
 Many aspects of the basic syntax of the
 .Nm
 language are based on the
-.Xr roff 5
+.Xr mandoc_roff 5
 language; see the
 .Em LANGUAGE SYNTAX
 and
 .Em MACRO SYNTAX
 sections in the
-.Xr roff 5
+.Xr mandoc_roff 5
 manual for details, in particular regarding
 comments, escape sequences, whitespace, and quoting.
 However, using
-.Xr roff 5
+.Xr mandoc_roff 5
 requests in
 .Nm
 documents is discouraged;
@@ -125,9 +127,9 @@ file for a utility
 \&.Os
 \&.Sh NAME
 \&.Nm progname
-\&.Nd one line description
+\&.Nd one line about what it does
 \&.\e\(dq .Sh LIBRARY
-\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq For sections 2, 3, and 9 only.
 \&.Sh SYNOPSIS
 \&.Nm progname
 \&.Op Fl options
@@ -138,7 +140,9 @@ The
 utility processes files ...
 \&.\e\(dq .Sh IMPLEMENTATION NOTES
 \&.\e\(dq .Sh RETURN VALUES
-\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq For sections 2, 3, and 9 only.
+\&.\e\(dq .Sh CONTEXT
+\&.\e\(dq For section 9 functions only.
 \&.\e\(dq .Sh ENVIRONMENT
 \&.\e\(dq For sections 1, 1M, and 5.
 \&.\e\(dq .Sh FILES
@@ -147,13 +151,13 @@ utility processes files ...
 \&.\e\(dq .Sh EXAMPLES
 \&.\e\(dq .Sh DIAGNOSTICS
 \&.\e\(dq .Sh ERRORS
-\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq For sections 2, 3, and 9 only.
 \&.\e\(dq .Sh ARCHITECTURE
 \&.\e\(dq .Sh CODE SET INDEPENDENCE
-\&.\e\(dq For sections 1, 1M, & 3 only.
+\&.\e\(dq For sections 1, 1M, and 3 only.
 \&.\e\(dq .Sh INTERFACE STABILITY
 \&.\e\(dq .Sh MT-LEVEL
-\&.\e\(dq For sections 2 & 3 only.
+\&.\e\(dq For sections 2 and 3 only.
 \&.\e\(dq .Sh SECURITY
 \&.\e\(dq .Sh SEE ALSO
 \&.\e\(dq .Xr foobar 1
@@ -331,6 +335,9 @@ return values of functions in sections 2, 3, and 9.
 .Pp
 See
 .Sx \&Rv .
+.It Em CONTEXT
+This section lists the contexts in which functions can be called in section 9.
+The contexts are user, kernel, or interrupt.
 .It Em ENVIRONMENT
 Lists the environment variables used by the utility,
 and explains the syntax and semantics of their values.
@@ -495,7 +502,7 @@ Documents any security precautions that operators should consider.
 References other manuals with related topics.
 This section should exist for most manuals.
 Cross-references should conventionally be ordered first by section, then
-alphabetically.
+alphabetically (ignoring case).
 .Pp
 References to other documentation concerning the topic of the manual page,
 for example authoritative books or journal articles, may also be
@@ -538,7 +545,7 @@ in the alphabetical
 .Ss Document preamble and NAME section macros
 .Bl -column "Brq, Bro, Brc" description
 .It Sx \&Dd Ta document date: Ar month day , year
-.It Sx \&Dt Ta document title: Ar TITLE SECTION Op Ar volume | arch
+.It Sx \&Dt Ta document title: Ar TITLE SECTION Op Ar arch
 .It Sx \&Os Ta operating system version: Op Ar system Op Ar version
 .It Sx \&Nm Ta document name (one argument)
 .It Sx \&Nd Ta document description (one line)
@@ -559,6 +566,7 @@ in the alphabetical
 .Op Fl compact
 .It Sx \&D1 Ta indented display (one line)
 .It Sx \&Dl Ta indented literal display (one line)
+.It Sx \&Ql Ta in-line literal display: Ql text
 .It Sx \&Bl , \&El Ta list block:
 .Fl Ar type
 .Op Fl width Ar val
@@ -573,7 +581,7 @@ in the alphabetical
 .It Sx \&Pf Ta prefix, no following horizontal space (one argument)
 .It Sx \&Ns Ta roman font, no preceding horizontal space (no arguments)
 .It Sx \&Ap Ta apostrophe without surrounding whitespace (no arguments)
-.It Sx \&Sm Ta switch horizontal spacing mode: Cm on | off
+.It Sx \&Sm Ta switch horizontal spacing mode: Op Cm on | off
 .It Sx \&Bk , \&Ek Ta keep block: Fl words
 .It Sx \&br Ta force output line break in text mode (no arguments)
 .It Sx \&sp Ta force vertical space: Op Ar height
@@ -593,6 +601,7 @@ in the alphabetical
 .Bl -column "Brq, Bro, Brc" description
 .It Sx \&Lb Ta function library (one argument)
 .It Sx \&In Ta include file (one argument)
+.It Sx \&Fd Ta other preprocessor directive (>0 arguments)
 .It Sx \&Ft Ta function type (>0 arguments)
 .It Sx \&Fo , \&Fc Ta function block: Ar funcname
 .It Sx \&Fn Ta function name:
@@ -617,7 +626,6 @@ in the alphabetical
 .It Sx \&Cd Ta kernel configuration declaration (>0 arguments)
 .It Sx \&Ad Ta memory address (>0 arguments)
 .It Sx \&Ms Ta mathematical symbol (>0 arguments)
-.It Sx \&Tn Ta tradename (>0 arguments)
 .El
 .Ss Physical markup
 .Bl -column "Brq, Bro, Brc" description
@@ -633,7 +641,6 @@ in the alphabetical
 .It Sx \&Dq , \&Do , \&Dc Ta enclose in typographic double quotes: Dq text
 .It Sx \&Qq , \&Qo , \&Qc Ta enclose in typewriter double quotes: Qq text
 .It Sx \&Sq , \&So , \&Sc Ta enclose in single quotes: Sq text
-.It Sx \&Ql Ta single-quoted literal text: Ql text
 .It Sx \&Pq , \&Po , \&Pc Ta enclose in parentheses: Pq text
 .It Sx \&Bq , \&Bo , \&Bc Ta enclose in square brackets: Bq text
 .It Sx \&Brq , \&Bro , \&Brc Ta enclose in curly braces: Brq text
@@ -645,7 +652,6 @@ in the alphabetical
 .It Sx \&Ex Fl std Ta standard command exit values: Op Ar utility ...
 .It Sx \&Rv Fl std Ta standard function return values: Op Ar function ...
 .It Sx \&St Ta reference to a standards document (one argument)
-.It Sx \&Ux Ta Ux
 .It Sx \&At Ta At
 .It Sx \&Bx Ta Bx
 .It Sx \&Bsx Ta Bsx
@@ -773,7 +779,7 @@ for all other author listings.
 .Pp
 Examples:
 .Dl \&.An -nosplit
-.Dl \&.An Kristaps Dzonsons \&Aq kristaps@bsd.lv
+.Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv
 .Ss \&Ao
 Begin a block enclosed by angle brackets.
 Does not have any head arguments.
@@ -827,7 +833,9 @@ for fixed strings to be passed verbatim as arguments, use
 or
 .Sx \&Cm .
 .Ss \&At
-Formats an AT&T version.
+Formats an
+.At
+version.
 Accepts one optional argument:
 .Pp
 .Bl -tag -width "v[1-7] | 32vX" -offset indent -compact
@@ -854,9 +862,8 @@ See also
 .Sx \&Dx ,
 .Sx \&Fx ,
 .Sx \&Nx ,
-.Sx \&Ox ,
 and
-.Sx \&Ux .
+.Sx \&Ox .
 .Ss \&Bc
 Close a
 .Sx \&Bo
@@ -882,7 +889,7 @@ The
 must be one of the following:
 .Bl -tag -width 13n -offset indent
 .It Fl centered
-Produce one output line from each input line, and centre-justify each line.
+Produce one output line from each input line, and center-justify each line.
 Using this display type is not recommended; many
 .Nm
 implementations render it poorly.
@@ -927,7 +934,7 @@ which has no effect;
 .Cm right ,
 which justifies to the right margin; or
 .Cm center ,
-which aligns around an imagined centre axis.
+which aligns around an imagined center axis.
 .It
 A macro invocation, which selects a predefined width
 associated with that macro.
@@ -936,8 +943,8 @@ The most popular is the imaginary macro
 which resolves to
 .Sy 6n .
 .It
-A width using the syntax described in
-.Sx Scaling Widths .
+A scaling width as described in
+.Xr mandoc_roff 5 .
 .It
 An arbitrary string, which indents by the length of this string.
 .El
@@ -1042,8 +1049,11 @@ The
 .Fl width
 and
 .Fl offset
-arguments accept
-.Sx Scaling Widths
+arguments accept macro names as described for
+.Sx \&Bd
+.Fl offset ,
+scaling widths as described in
+.Xr mandoc_roff 5 ,
 or use the length of the given string.
 The
 .Fl offset
@@ -1072,9 +1082,9 @@ A columnated list.
 The
 .Fl width
 argument has no effect; instead, each argument specifies the width
-of one column, using either the
-.Sx Scaling Widths
-syntax or the string length of the argument.
+of one column, using either the scaling width syntax described in
+.Xr mandoc_roff 5
+or the string length of the argument.
 If the first line of the body of a
 .Fl column
 list is not an
@@ -1205,7 +1215,9 @@ Examples:
 See also
 .Sx \&Bro .
 .Ss \&Bsx
-Format the BSD/OS version provided as an argument, or a default value if
+Format the
+.Bsx
+version provided as an argument, or a default value if
 no argument is provided.
 .Pp
 Examples:
@@ -1218,14 +1230,16 @@ See also
 .Sx \&Dx ,
 .Sx \&Fx ,
 .Sx \&Nx ,
-.Sx \&Ox ,
 and
-.Sx \&Ux .
+.Sx \&Ox .
 .Ss \&Bt
+Supported only for compatibility, do not use this in new manuals.
 Prints
 .Dq is currently in beta test.
 .Ss \&Bx
-Format the BSD version provided as an argument, or a default value if no
+Format the
+.Bx
+version provided as an argument, or a default value if no
 argument is provided.
 .Pp
 Examples:
@@ -1239,9 +1253,8 @@ See also
 .Sx \&Dx ,
 .Sx \&Fx ,
 .Sx \&Nx ,
-.Sx \&Ox ,
 and
-.Sx \&Ux .
+.Sx \&Ox .
 .Ss \&Cd
 Kernel configuration declaration.  It is found in pages for
 .Bx
@@ -1283,20 +1296,19 @@ See also
 and
 .Sx \&Dl .
 .Ss \&Db
-Switch debugging mode.
-Its syntax is as follows:
-.Pp
-.D1 Pf \. Sx \&Db Cm on | off
-.Pp
-This macro is ignored by
-.Xr mandoc 1 .
+This macro is obsolete.
+No replacement is needed.
+It is ignored by
+.Xr mandoc 1
+and groff including its arguments.
+It was formerly used to toggle a debugging mode.
 .Ss \&Dc
 Close a
 .Sx \&Do
 block.
 Does not have any tail arguments.
 .Ss \&Dd
-Document date.
+Document date for display in the page footer.
 This is the mandatory first macro of any
 .Nm
 manual.
@@ -1325,8 +1337,11 @@ the special string
 .Dq $\&Mdocdate$
 can be given as an argument.
 .It
-A few alternative date formats are accepted as well
-and converted to the standard form.
+The traditional, purely numeric
+.Xr man 5
+format
+.Ar year Ns \(en Ns Ar month Ns \(en Ns Ar day
+is accepted, too.
 .It
 If a date string cannot be parsed, it is used verbatim.
 .It
@@ -1343,7 +1358,7 @@ See also
 and
 .Sx \&Os .
 .Ss \&Dl
-One-line intended display.
+One-line indented display.
 This is formatted as literal text and is useful for commands and
 invocations.
 It is followed by a newline.
@@ -1352,7 +1367,9 @@ Examples:
 .Dl \&.Dl % mandoc mdoc.5 \e(ba less
 .Pp
 See also
+.Sx \&Ql ,
 .Sx \&Bd
+.Fl literal ,
 and
 .Sx \&D1 .
 .Ss \&Do
@@ -1386,39 +1403,33 @@ See also
 and
 .Sx \&Do .
 .Ss \&Dt
-Document title.
+Document title for display in the page header.
 This is the mandatory second macro of any
 .Nm
 file.
 Its syntax is as follows:
 .Bd -ragged -offset indent
 .Pf \. Sx \&Dt
-.Oo
-.Ar title
-.Oo
+.Ar TITLE
 .Ar section
-.Op Ar volume
 .Op Ar arch
-.Oc
-.Oc
 .Ed
 .Pp
 Its arguments are as follows:
-.Bl -tag -width Ds -offset Ds
-.It Ar title
+.Bl -tag -width section -offset 2n
+.It Ar TITLE
 The document's title (name), defaulting to
-.Dq UNKNOWN
-if unspecified.
-It should be capitalised.
-.It Ar section
-The manual section. It should correspond to the manual's filename suffix
-and defaults to
-.Dq 1
+.Dq UNTITLED
 if unspecified.
-.It Ar volume
-This overrides the volume inferred from
-.Ar section .
+To achieve a uniform appearance of page header lines,
+it should by convention be all caps.
+.It Ar SECTION
+The manual section.
+It should correspond to the manual's filename suffix and defaults to
+the empty string if unspecified.
 This field is optional.
+To achieve a uniform appearance of page header lines,
+it should by convention be all caps.
 .It Ar arch
 This specifies the machine architecture a manual page applies to,
 where relevant.
@@ -1436,11 +1447,16 @@ See also
 .Sx \&Er
 and
 .Sx \&Ev
-for special-purpose constants and
+for special-purpose constants,
 .Sx \&Va
-for variable symbols.
+for variable symbols, and
+.Sx \&Fd
+for listing preprocessor variable definitions in the
+.Em SYNOPSIS .
 .Ss \&Dx
-Format the DragonFly BSD version provided as an argument, or a default
+Format the
+.Dx
+version provided as an argument, or a default
 value if no argument is provided.
 .Pp
 Examples:
@@ -1453,9 +1469,8 @@ See also
 .Sx \&Bx ,
 .Sx \&Fx ,
 .Sx \&Nx ,
-.Sx \&Ox ,
 and
-.Sx \&Ux .
+.Sx \&Ox .
 .Ss \&Ec
 Close a scope started by
 .Sx \&Eo .
@@ -1486,16 +1501,29 @@ See also
 and
 .Sx \&It .
 .Ss \&Em
-Denotes text that should be
-.Em emphasised .
-Note that this is a presentation term and should not be used for
-stylistically decorating technical terms.
-Depending on the output device, this is usually represented
-using an italic font or underlined characters.
+Request an italic font.
+If the output device does not provide that, underline.
+.Pp
+This is most often used for stress emphasis (not to be confused with
+importance, see
+.Sx \&Sy ) .
+In the rare cases where none of the semantic markup macros fit,
+it can also be used for technical terms and placeholders, except
+that for syntax elements,
+.Sx \&Sy
+and
+.Sx \&Ar
+are preferred, respectively.
 .Pp
 Examples:
-.Dl \&.Em Warnings!
-.Dl \&.Em Remarks :
+.Bd -literal -compact -offset indent
+Selected lines are those
+\&.Em not
+matching any of the specified patterns.
+Some of the functions use a
+\&.Em hold space
+to save the pattern space for subsequent retrieval.
+.Ed
 .Pp
 See also
 .Sx \&Bf ,
@@ -1504,8 +1532,14 @@ See also
 and
 .Sx \&Sy .
 .Ss \&En
-This macro is obsolete and not implemented in
-.Xr mandoc 1 .
+This macro is obsolete.
+Use
+.Sx \&Eo
+or any of the other enclosure macros.
+.Pp
+It encloses its argument in the delimiters specified by the last
+.Sx \&Es
+macro.
 .Ss \&Eo
 An arbitrary enclosure.
 Its syntax is as follows:
@@ -1531,7 +1565,14 @@ See also
 .Sx \&Dv
 for general constants.
 .Ss \&Es
-This macro is obsolete and not implemented.
+This macro is obsolete.
+Use
+.Sx \&Eo
+or any of the other enclosure macros.
+.Pp
+It takes two arguments, defining the delimiters to be used by subsequent
+.Sx \&En
+macros.
 .Ss \&Ev
 Environmental variables such as those specified in
 .Xr environ 5 .
@@ -1563,23 +1604,35 @@ arguments are treated as separate utilities.
 See also
 .Sx \&Rv .
 .Ss \&Fa
-Function argument.
+Function argument or parameter.
 Its syntax is as follows:
 .Bd -ragged -offset indent
 .Pf \. Sx \&Fa
-.Op Cm argtype
-.Cm argname
+.Qo
+.Op Ar argtype
+.Op Ar argname
+.Qc Ar \&...
 .Ed
 .Pp
-This may be invoked for names with or without the corresponding type.
-It is also used to specify the field name of a structure.
+Each argument may be a name and a type (recommended for the
+.Em SYNOPSIS
+section), a name alone (for function invocations),
+or a type alone (for function prototypes).
+If both a type and a name are given or if the type consists of multiple
+words, all words belonging to the same function argument have to be
+given in a single argument to the
+.Sx \&Fa
+macro.
+.Pp
+This macro is also used to specify the field name of a structure.
+.Pp
 Most often, the
 .Sx \&Fa
 macro is used in the
 .Em SYNOPSIS
 within
 .Sx \&Fo
-section when documenting multi-line function prototypes.
+blocks when documenting multi-line function prototypes.
 If invoked with multiple arguments, the arguments are separated by a
 comma.
 Furthermore, if the following macro is another
@@ -1589,7 +1642,7 @@ the last argument will also have a trailing comma.
 Examples:
 .Dl \&.Fa \(dqconst char *p\(dq
 .Dl \&.Fa \(dqint a\(dq \(dqint b\(dq \(dqint c\(dq
-.Dl \&.Fa foo
+.Dl \&.Fa \(dqchar *\(dq size_t
 .Pp
 See also
 .Sx \&Fo .
@@ -1597,15 +1650,32 @@ See also
 End a function context started by
 .Sx \&Fo .
 .Ss \&Fd
-Historically used to document include files.
-This usage has been deprecated in favour of
+Preprocessor directive, in particular for listing it in the
+.Em SYNOPSIS .
+Historically, it was also used to document include files.
+The latter usage has been deprecated in favour of
 .Sx \&In .
-Do not use this macro.
+.Pp
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Fd
+.Li # Ns Ar directive
+.Op Ar argument ...
+.Ed
+.Pp
+Examples:
+.Dl \&.Fd #define sa_handler __sigaction_u.__sa_handler
+.Dl \&.Fd #define SIO_MAXNFDS
+.Dl \&.Fd #ifdef FS_DEBUG
+.Dl \&.Ft void
+.Dl \&.Fn dbg_open \(dqconst char *\(dq
+.Dl \&.Fd #endif
 .Pp
 See also
-.Sx MANUAL STRUCTURE
+.Sx MANUAL STRUCTURE ,
+.Sx \&In ,
 and
-.Sx \&In .
+.Sx \&Dv .
 .Ss \&Fl
 Command-line flag or option.
 Used when listing arguments to command-line utilities.
@@ -1675,7 +1745,7 @@ Invocations usually occur in the following context:
 .br
 .Pf \. Sx \&Fo Ar funcname
 .br
-.Pf \. Sx \&Fa Oo Ar argtype Oc Ar argname
+.Pf \. Sx \&Fa Qq Ar argtype Ar argname
 .br
 \&.\.\.
 .br
@@ -1694,13 +1764,10 @@ See also
 and
 .Sx \&Ft .
 .Ss \&Fr
-This macro is obsolete and not implemented in
-.Xr mandoc 1 .
-.Pp
-It was used to show function return values.
-The syntax was:
+This macro is obsolete.
+No replacement markup is needed.
 .Pp
-.Dl Pf . Sx \&Fr Ar value
+It was used to show numerical function return values in an italic font.
 .Ss \&Ft
 A function type.
 Its syntax is as follows:
@@ -1739,9 +1806,8 @@ See also
 .Sx \&Bx ,
 .Sx \&Dx ,
 .Sx \&Nx ,
-.Sx \&Ox ,
 and
-.Sx \&Ux .
+.Sx \&Ox .
 .Ss \&Hf
 This macro is not implemented in
 .Xr mandoc 1 .
@@ -1769,17 +1835,18 @@ is preferred for displaying code; the
 .Sx \&Ic
 macro is used when referring to specific instructions.
 .Ss \&In
-An
-.Dq include
-file.
+The name of an include file.
+This macro is most often used in section 2, 3, and 9 manual pages.
+.Pp
 When invoked as the first macro on an input line in the
 .Em SYNOPSIS
 section, the argument is displayed in angle brackets
 and preceded by
-.Dq #include ,
+.Qq #include ,
 and a blank line is inserted in front if there is a preceding
 function declaration.
-This is most often used in section 2, 3, and 9 manual pages.
+In other sections, it only encloses its argument in angle brackets
+and causes no line break.
 .Pp
 Examples:
 .Dl \&.In sys/types.h
@@ -1937,13 +2004,12 @@ Its syntax is as follows:
 .Pp
 Examples:
 .Dl \&.Mt discuss@manpages.bsd.lv
+.Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv
 .Ss \&Nd
 A one line description of the manual's content.
-This may only be invoked in the
-.Em SYNOPSIS
-section subsequent the
-.Sx \&Nm
-macro.
+This is the mandatory last macro of the
+.Em NAME
+section and not appropriate for other sections.
 .Pp
 Examples:
 .Dl Pf . Sx \&Nd mdoc language reference
@@ -1963,7 +2029,7 @@ See also
 .Sx \&Nm .
 .Ss \&Nm
 The name of the manual page, or \(em in particular in section 1
-and 1M pages \(em of an additional command or feature documented in
+pages \(em of an additional command or feature documented in
 the manual page.
 When first invoked, the
 .Sx \&Nm
@@ -2058,9 +2124,8 @@ See also
 .Sx \&Bx ,
 .Sx \&Dx ,
 .Sx \&Fx ,
-.Sx \&Ox ,
 and
-.Sx \&Ux .
+.Sx \&Ox .
 .Ss \&Oc
 Close multi-line
 .Sx \&Oo
@@ -2089,7 +2154,7 @@ Examples:
 See also
 .Sx \&Oo .
 .Ss \&Os
-Document operating system version.
+Operating system version for display in the page footer.
 This is the mandatory third macro of
 any
 .Nm
@@ -2101,8 +2166,16 @@ Its syntax is as follows:
 The optional
 .Ar system
 parameter specifies the relevant operating system or environment.
-Left unspecified, it defaults to the local operating system version.
-This is the suggested form.
+It is suggested to leave it unspecified, in which case
+.Xr mandoc 1
+uses its
+.Fl Ios
+argument, or, if that isn't specified either,
+.Fa sysname
+and
+.Fa release
+as returned by
+.Xr uname 3 .
 .Pp
 Examples:
 .Dl \&.Os
@@ -2114,11 +2187,15 @@ See also
 and
 .Sx \&Dt .
 .Ss \&Ot
-This macro is obsolete and not implemented in
-.Xr mandoc 1 .
+This macro is obsolete.
+Use
+.Sx \&Ft
+instead; with
+.Xr mandoc 1 ,
+both have the same effect.
 .Pp
 Historical
-.Xr mdoc 5
+.Nm
 packages described it as
 .Dq "old function type (FORTRAN)" .
 .Ss \&Ox
@@ -2137,9 +2214,8 @@ See also
 .Sx \&Bx ,
 .Sx \&Dx ,
 .Sx \&Fx ,
-.Sx \&Nx ,
 and
-.Sx \&Ux .
+.Sx \&Nx .
 .Ss \&Pa
 An absolute or relative file system path, or a file or directory name.
 If an argument is not provided, the character
@@ -2203,11 +2279,21 @@ See also
 Close quoted context opened by
 .Sx \&Qo .
 .Ss \&Ql
-Format a single-quoted literal.
+In-line literal display.
+This can for example be used for complete command invocations and
+for multi-word code fragments when more specific markup is not
+appropriate and an indented display is not desired.
+While
+.Xr mandoc 1
+always encloses the arguments in single quotes, other formatters
+usually omit the quotes on non-terminal output devices when the
+arguments have three or more characters.
+.Pp
 See also
-.Sx \&Qq
+.Sx \&Dl
 and
-.Sx \&Sq .
+.Sx \&Bd
+.Fl literal .
 .Ss \&Qo
 Multi-line version of
 .Sx \&Qq .
@@ -2313,7 +2399,7 @@ and
 Switches the spacing mode for output generated from macros.
 Its syntax is as follows:
 .Pp
-.D1 Pf \. Sx \&Sm Cm on | off
+.D1 Pf \. Sx \&Sm Op Cm on | off
 .Pp
 By default, spacing is
 .Cm on .
@@ -2322,6 +2408,11 @@ When switched
 no white space is inserted between macro arguments and between the
 output generated from adjacent macros, but text lines
 still get normal spacing between words and sentences.
+.Pp
+When called without an argument, the
+.Sx \&Sm
+macro toggles the spacing mode.
+Using this is not recommended because it makes the code harder to read.
 .Ss \&So
 Multi-line version of
 .Sx \&Sq .
@@ -2359,105 +2450,218 @@ and
 .Sx \&Sx .
 .Ss \&St
 Replace an abbreviation for a standard with the full form.
-The following standards are recognised:
-.Pp
-.Bl -tag -width "-p1003.1g-2000X" -compact
-.It \-p1003.1-88
-.St -p1003.1-88
-.It \-p1003.1-90
-.St -p1003.1-90
-.It \-p1003.1-96
-.St -p1003.1-96
-.It \-p1003.1-2001
-.St -p1003.1-2001
-.It \-p1003.1-2004
-.St -p1003.1-2004
-.It \-p1003.1-2008
-.St -p1003.1-2008
-.It \-p1003.1
-.St -p1003.1
-.It \-p1003.1b
-.St -p1003.1b
-.It \-p1003.1b-93
-.St -p1003.1b-93
-.It \-p1003.1c-95
-.St -p1003.1c-95
-.It \-p1003.1g-2000
-.St -p1003.1g-2000
-.It \-p1003.1i-95
-.St -p1003.1i-95
-.It \-p1003.2-92
-.St -p1003.2-92
-.It \-p1003.2a-92
-.St -p1003.2a-92
-.It \-p1387.2-95
-.St -p1387.2-95
-.It \-p1003.2
-.St -p1003.2
-.It \-p1387.2
-.St -p1387.2
+The following standards are recognised.
+Where multiple lines are given without a blank line in between,
+they all refer to the same standard, and using the first form
+is recommended.
+.Bl -tag -width 1n
+.It C language standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-ansiC
+.St -ansiC
+.It \-ansiC-89
+.St -ansiC-89
 .It \-isoC
 .St -isoC
 .It \-isoC-90
 .St -isoC-90
+.br
+The original C standard.
+.Pp
 .It \-isoC-amd1
 .St -isoC-amd1
+.Pp
 .It \-isoC-tcor1
 .St -isoC-tcor1
+.Pp
 .It \-isoC-tcor2
 .St -isoC-tcor2
+.Pp
 .It \-isoC-99
 .St -isoC-99
+.br
+The second major version of the C language standard.
+.Pp
 .It \-isoC-2011
 .St -isoC-2011
+.br
+The third major version of the C language standard.
+.El
+.It POSIX.1 before the Single UNIX Specification
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-p1003.1-88
+.St -p1003.1-88
+.It \-p1003.1
+.St -p1003.1
+.br
+The original POSIX standard, based on ANSI C.
+.Pp
+.It \-p1003.1-90
+.St -p1003.1-90
 .It \-iso9945-1-90
 .St -iso9945-1-90
+.br
+The first update of POSIX.1.
+.Pp
+.It \-p1003.1b-93
+.St -p1003.1b-93
+.It \-p1003.1b
+.St -p1003.1b
+.br
+Real-time extensions.
+.Pp
+.It \-p1003.1c-95
+.St -p1003.1c-95
+.br
+POSIX thread interfaces.
+.Pp
+.It \-p1003.1i-95
+.St -p1003.1i-95
+.br
+Technical Corrigendum.
+.Pp
+.It \-p1003.1-96
+.St -p1003.1-96
 .It \-iso9945-1-96
 .St -iso9945-1-96
-.It \-iso9945-2-93
-.St -iso9945-2-93
-.It \-ansiC
-.St -ansiC
-.It \-ansiC-89
-.St -ansiC-89
-.It \-ansiC-99
-.St -ansiC-99
-.It \-ieee754
-.St -ieee754
-.It \-iso8802-3
-.St -iso8802-3
-.It \-iso8601
-.St -iso8601
-.It \-ieee1275-94
-.St -ieee1275-94
+.br
+Includes POSIX.1-1990, 1b, 1c, and 1i.
+.El
+.It X/Open Portability Guide version 4 and related standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
 .It \-xpg3
 .St -xpg3
+.br
+An XPG4 precursor, published in 1989.
+.Pp
+.It \-p1003.2
+.St -p1003.2
+.It \-p1003.2-92
+.St -p1003.2-92
+.It \-iso9945-2-93
+.St -iso9945-2-93
+.br
+An XCU4 precursor.
+.Pp
+.It \-p1003.2a-92
+.St -p1003.2a-92
+.br
+Updates to POSIX.2.
+.Pp
 .It \-xpg4
 .St -xpg4
+.br
+Based on POSIX.1 and POSIX.2, published in 1992.
+.El
+.It Single UNIX Specification version 1 and related standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-susv1
+.St -susv1
 .It \-xpg4.2
 .St -xpg4.2
-.It \-xpg4.3
-.St -xpg4.3
+.br
+This standard was published in 1994.
+It was used as the basis for UNIX 95 certification.
+The following three refer to parts of it.
+.Pp
+.It \-xsh4.2
+.St -xsh4.2
+.Pp
+.It \-xcurses4.2
+.St -xcurses4.2
+.Pp
+.It \-p1003.1g-2000
+.St -p1003.1g-2000
+.br
+Networking APIs, including sockets.
+.Pp
+.It \-svid4
+.St -svid4 ,
+.br
+Published in 1995.
+.El
+.It Single UNIX Specification version 2 and related standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-susv2
+.St -susv2
+This Standard was published in 1997
+and is also called X/Open Portability Guide version 5.
+It was used as the basis for UNIX 98 certification.
+The following refer to parts of it.
+.Pp
 .It \-xbd5
 .St -xbd5
-.It \-xcu5
-.St -xcu5
+.Pp
 .It \-xsh5
 .St -xsh5
+.Pp
+.It \-xcu5
+.St -xcu5
+.Pp
 .It \-xns5
 .St -xns5
 .It \-xns5.2
 .St -xns5.2
-.It \-xns5.2d2.0
-.St -xns5.2d2.0
-.It \-xcurses4.2
-.St -xcurses4.2
-.It \-susv2
-.St -susv2
+.El
+.It Single UNIX Specification version 3
+.Pp
+.Bl -tag -width "-p1003.1-2001" -compact
+.It \-p1003.1-2001
+.St -p1003.1-2001
 .It \-susv3
 .St -susv3
-.It \-svid4
-.St -svid4
+.br
+This standard is based on C99, SUSv2, POSIX.1-1996, 1d, and 1j.
+It is also called X/Open Portability Guide version 6.
+It is used as the basis for UNIX 03 certification.
+.Pp
+.It \-p1003.1-2004
+.St -p1003.1-2004
+.br
+The second and last Technical Corrigendum.
+.El
+.It Single UNIX Specification version 4
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-p1003.1-2008
+.St -p1003.1-2008
+.It \-susv4
+.St -susv4
+.br
+This standard is also called
+X/Open Portability Guide version 7.
+.Pp
+.It \-p1003.1-2013
+.St -p1003.1-2013
+.br
+This is the first Technical Corrigendum.
+.El
+.It Other standards
+.Pp
+.Bl -tag -width "-p1003.1g-2000" -compact
+.It \-ieee754
+.St -ieee754
+.br
+Floating-point arithmetic.
+.Pp
+.It \-iso8601
+.St -iso8601
+.br
+Representation of dates and times, published in 1988.
+.Pp
+.It \-iso8802-3
+.St -iso8802-3
+.br
+Ethernet local area networks.
+.Pp
+.It \-ieee1275-94
+.St -ieee1275-94
+.El
 .El
 .Ss \&Sx
 Reference a section or subsection in the same manual page.
@@ -2472,10 +2676,24 @@ See also
 and
 .Sx \&Ss .
 .Ss \&Sy
-Format enclosed arguments in symbolic
-.Pq Dq boldface .
-Note that this is a presentation term and should not be used for
-stylistically decorating technical terms.
+Request a boldface font.
+.Pp
+This is most often used to indicate importance or seriousness (not to be
+confused with stress emphasis, see
+.Sx \&Em ) .
+When none of the semantic macros fit, it is also adequate for syntax
+elements that have to be given or that appear verbatim.
+.Pp
+Examples:
+.Bd -literal -compact -offset indent
+\&.Sy Warning :
+If
+\&.Sy s
+appears in the owner permissions, set-user-ID mode is set.
+This utility replaces the former
+\&.Sy dumpdir
+program.
+.Ed
 .Pp
 See also
 .Sx \&Bf ,
@@ -2489,42 +2707,36 @@ Table cell separator in
 lists; can only be used below
 .Sx \&It .
 .Ss \&Tn
-Format a tradename.
-.Pp
-Since this macro is often implemented to use a small caps font,
-it has historically been used for acronyms (like ASCII) as well.
-Such usage is not recommended because it would use the same macro
-sometimes for semantical annotation, sometimes for physical formatting.
-.Pp
-Examples:
-.Dl \&.Tn IBM
+Supported only for compatibility, do not use this in new manuals.
+Even though the macro name
+.Pq Dq tradename
+suggests a semantic function, historic usage is inconsistent, mostly
+using it as a presentation-level macro to request a small caps font.
 .Ss \&Ud
+Supported only for compatibility, do not use this in new manuals.
 Prints out
 .Dq currently under development.
 .Ss \&Ux
-Format the UNIX name.
-Accepts no argument.
-.Pp
-Examples:
-.Dl \&.Ux
-.Pp
-See also
-.Sx \&At ,
-.Sx \&Bsx ,
-.Sx \&Bx ,
-.Sx \&Dx ,
-.Sx \&Fx ,
-.Sx \&Nx ,
-and
-.Sx \&Ox .
+Supported only for compatibility, do not use this in new manuals.
+Prints out
+.Dq Ux .
 .Ss \&Va
 A variable name.
 .Pp
 Examples:
 .Dl \&.Va foo
 .Dl \&.Va const char *bar ;
+.Pp
+For function arguments and parameters, use
+.Sx \&Fa
+instead.
+For declarations of global variables in the
+.Em SYNOPSIS
+section, use
+.Sx \&Vt .
 .Ss \&Vt
 A variable type.
+.Pp
 This is also used for indicating global variables in the
 .Em SYNOPSIS
 section, in which case a variable name is also specified.
@@ -2539,18 +2751,21 @@ In the former case, this macro starts a new output line,
 and a blank line is inserted in front if there is a preceding
 function definition or include directive.
 .Pp
-Note that this should not be confused with
-.Sx \&Ft ,
-which is used for function return types.
-.Pp
 Examples:
 .Dl \&.Vt unsigned char
 .Dl \&.Vt extern const char * const sys_signame[] \&;
 .Pp
+For parameters in function prototypes, use
+.Sx \&Fa
+instead, for function return types
+.Sx \&Ft ,
+and for variable names outside the
+.Em SYNOPSIS
+section
+.Sx \&Va ,
+even when including a type with the name.
 See also
-.Sx MANUAL STRUCTURE
-and
-.Sx \&Va .
+.Sx MANUAL STRUCTURE .
 .Ss \&Xc
 Close a scope opened by
 .Sx \&Xo .
@@ -2561,26 +2776,20 @@ macro or the body of a partial-implicit block macro
 beyond the end of the input line.
 This macro originally existed to work around the 9-argument limit
 of historic
-.Xr roff 5 .
+.Xr mandoc_roff 5 .
 .Ss \&Xr
 Link to another manual
 .Pq Qq cross-reference .
 Its syntax is as follows:
 .Pp
-.D1 Pf \. Sx \&Xr Ar name section
+.D1 Pf \. Sx \&Xr Ar name Op section
 .Pp
-The
+Cross reference the
 .Ar name
 and
 .Ar section
-are the name and section of the linked manual.
-If
-.Ar section
-is followed by non-punctuation, an
-.Sx \&Ns
-is inserted into the token stream.
-This behaviour is for compatibility with
-GNU troff.
+number of another man page;
+omitting the section number is rarely useful.
 .Pp
 Examples:
 .Dl \&.Xr mandoc 1
@@ -2604,8 +2813,8 @@ Its syntax is as follows:
 .Pp
 The
 .Ar height
-argument must be formatted as described in
-.Sx Scaling Widths .
+argument is a scaling width as described in
+.Xr mandoc_roff 5 .
 If unspecified,
 .Sx \&sp
 asserts a single vertical space.
@@ -2774,6 +2983,7 @@ end of the line.
 .It Sx \&D1  Ta    \&No     Ta    \&Yes
 .It Sx \&Dl  Ta    \&No     Ta    Yes
 .It Sx \&Dq  Ta    Yes      Ta    Yes
+.It Sx \&En  Ta    Yes      Ta    Yes
 .It Sx \&Op  Ta    Yes      Ta    Yes
 .It Sx \&Pq  Ta    Yes      Ta    Yes
 .It Sx \&Ql  Ta    Yes      Ta    Yes
@@ -2851,16 +3061,15 @@ then the macro accepts an arbitrary number of arguments.
 .It Sx \&Dv  Ta    Yes      Ta    Yes      Ta    >0
 .It Sx \&Dx  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Em  Ta    Yes      Ta    Yes      Ta    >0
-.It Sx \&En  Ta    \&No     Ta    \&No     Ta    0
 .It Sx \&Er  Ta    Yes      Ta    Yes      Ta    >0
-.It Sx \&Es  Ta    \&No     Ta    \&No     Ta    0
+.It Sx \&Es  Ta    Yes      Ta    Yes      Ta    2
 .It Sx \&Ev  Ta    Yes      Ta    Yes      Ta    >0
 .It Sx \&Ex  Ta    \&No     Ta    \&No     Ta    n
 .It Sx \&Fa  Ta    Yes      Ta    Yes      Ta    >0
 .It Sx \&Fd  Ta    \&No     Ta    \&No     Ta    >0
 .It Sx \&Fl  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Fn  Ta    Yes      Ta    Yes      Ta    >0
-.It Sx \&Fr  Ta    \&No     Ta    \&No     Ta    n
+.It Sx \&Fr  Ta    Yes      Ta    Yes      Ta    >0
 .It Sx \&Ft  Ta    Yes      Ta    Yes      Ta    >0
 .It Sx \&Fx  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Hf  Ta    \&No     Ta    \&No     Ta    n
@@ -2877,13 +3086,13 @@ then the macro accepts an arbitrary number of arguments.
 .It Sx \&Ns  Ta    Yes      Ta    Yes      Ta    0
 .It Sx \&Nx  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Os  Ta    \&No     Ta    \&No     Ta    n
-.It Sx \&Ot  Ta    \&No     Ta    \&No     Ta    n
+.It Sx \&Ot  Ta    Yes      Ta    Yes      Ta    >0
 .It Sx \&Ox  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Pa  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Pf  Ta    Yes      Ta    Yes      Ta    1
 .It Sx \&Pp  Ta    \&No     Ta    \&No     Ta    0
 .It Sx \&Rv  Ta    \&No     Ta    \&No     Ta    n
-.It Sx \&Sm  Ta    \&No     Ta    \&No     Ta    1
+.It Sx \&Sm  Ta    \&No     Ta    \&No     Ta    <2
 .It Sx \&St  Ta    \&No     Ta    Yes      Ta    1
 .It Sx \&Sx  Ta    Yes      Ta    Yes      Ta    >0
 .It Sx \&Sy  Ta    Yes      Ta    Yes      Ta    >0
@@ -2991,58 +3200,22 @@ macros.
 Whenever any
 .Nm
 macro switches the
-.Xr roff 5
+.Xr mandoc_roff 5
 font mode, it will automatically restore the previous font when exiting
 its scope.
 Manually switching the font using the
-.Xr roff 5
+.Xr mandoc_roff 5
 .Ql \ef
 font escape sequences is never required.
 .Sh COMPATIBILITY
-This section documents compatibility between mandoc and other other
-troff implementations, at this time limited to GNU troff
+This section provides an incomplete list of compatibility issues
+between mandoc and GNU troff
 .Pq Qq groff .
-The term
-.Qq historic groff
-refers to groff versions before 1.17,
-which featured a significant update of the
-.Pa doc.tmac
-file.
-.Pp
-Heirloom troff, the other significant troff implementation accepting
-\-mdoc, is similar to historic groff.
 .Pp
 The following problematic behaviour is found in groff:
-.ds hist (Historic groff only.)
 .Pp
 .Bl -dash -compact
 .It
-Display macros
-.Po
-.Sx \&Bd ,
-.Sx \&Dl ,
-and
-.Sx \&D1
-.Pc
-may not be nested.
-\*[hist]
-.It
-.Sx \&At
-with unknown arguments produces no output at all.
-\*[hist]
-Newer groff and mandoc print
-.Qq AT&T UNIX
-and the arguments.
-.It
-.Sx \&Bl Fl column
-does not recognise trailing punctuation characters when they immediately
-precede tabulator characters, but treats them as normal text and
-outputs a space before them.
-.It
-.Sx \&Bd Fl ragged compact
-does not start a new line.
-\*[hist]
-.It
 .Sx \&Dd
 with non-standard arguments behaves very strangely.
 When there are three arguments, they are printed verbatim.
@@ -3051,53 +3224,6 @@ but without any arguments the string
 .Dq Epoch
 is printed.
 .It
-.Sx \&Fl
-does not print a dash for an empty argument.
-\*[hist]
-.It
-.Sx \&Fn
-does not start a new line unless invoked as the line macro in the
-.Em SYNOPSIS
-section.
-\*[hist]
-.It
-.Sx \&Fo
-with
-.Pf non- Sx \&Fa
-children causes inconsistent spacing between arguments.
-In mandoc, a single space is always inserted between arguments.
-.It
-.Sx \&Ft
-in the
-.Em SYNOPSIS
-causes inconsistent vertical spacing, depending on whether a prior
-.Sx \&Fn
-has been invoked.
-See
-.Sx \&Ft
-and
-.Sx \&Fn
-for the normalised behaviour in mandoc.
-.It
-.Sx \&In
-ignores additional arguments and is not treated specially in the
-.Em SYNOPSIS .
-\*[hist]
-.It
-.Sx \&It
-sometimes requires a
-.Fl nested
-flag.
-\*[hist]
-In new groff and mandoc, any list may be nested by default and
-.Fl enum
-lists will restart the sequence only for the sub-list.
-.It
-.Sx \&Li
-followed by a delimiter is incorrectly used in some manuals
-instead of properly quoting that character, which sometimes works with
-historic groff.
-.It
 .Sx \&Lk
 only accepts a single link-name argument; the remainder is misformatted.
 .It
@@ -3109,25 +3235,12 @@ certain list types.
 can only be called by other macros, but not at the beginning of a line.
 .It
 .Sx \&%C
-is not implemented.
-.It
-Historic groff only allows up to eight or nine arguments per macro input
-line, depending on the exact situation.
-Providing more arguments causes garbled output.
-The number of arguments on one input line is not limited with mandoc.
-.It
-Historic groff has many un-callable macros.
-Most of these (excluding some block-level macros) are callable
-in new groff and mandoc.
-.It
-.Sq \(ba
-(vertical bar) is not fully supported as a delimiter.
-\*[hist]
+is not implemented (up to and including groff-1.22.2).
 .It
 .Sq \ef
 .Pq font face
 and
-.Sq \ef
+.Sq \eF
 .Pq font family face
 .Sx Text Decoration
 escapes behave irregularly when specified within line-macro scopes.
@@ -3141,44 +3254,28 @@ The following features are unimplemented in mandoc:
 .Bl -dash -compact
 .It
 .Sx \&Bd
-.Fl file Ar file .
+.Fl file Ar file
+is unsupported for security reasons.
 .It
 .Sx \&Bd
-.Fl offset Ar center
-and
-.Fl offset Ar right .
-Groff does not implement centred and flush-right rendering either,
-but produces large indentations.
-.It
-The
-.Sq \eh
-.Pq horizontal position ,
-.Sq \ev
-.Pq vertical position ,
-.Sq \em
-.Pq text colour ,
-.Sq \eM
-.Pq text filling colour ,
-.Sq \ez
-.Pq zero-length character ,
-.Sq \ew
-.Pq string length ,
-.Sq \ek
-.Pq horizontal position marker ,
-.Sq \eo
-.Pq text overstrike ,
-and
-.Sq \es
-.Pq text size
-escape sequences are all discarded in mandoc.
+.Fl filled
+does not adjust the right margin, but is an alias for
+.Sx \&Bd
+.Fl ragged .
 .It
-The
-.Sq \ef
-scaling unit is accepted by mandoc, but rendered as the default unit.
+.Sx \&Bd
+.Fl literal
+does not use a literal font, but is an alias for
+.Sx \&Bd
+.Fl unfilled .
 .It
-In quoted literals, groff allows pairwise double-quotes to produce a
-standalone double-quote in formatted output.
-This is not supported by mandoc.
+.Sx \&Bd
+.Fl offset Cm center
+and
+.Fl offset Cm right
+don't work.
+Groff does not implement centered and flush-right rendering either,
+but produces large indentations.
 .El
 .Sh SEE ALSO
 .Xr man 1 ,
@@ -3186,7 +3283,7 @@ This is not supported by mandoc.
 .Xr eqn 5 ,
 .Xr man 5 ,
 .Xr mandoc_char 5 ,
-.Xr roff 5 ,
+.Xr mandoc_roff 5 ,
 .Xr tbl 5
 .Sh HISTORY
 The
@@ -3203,5 +3300,4 @@ utility written by Kristaps Dzonsons appeared in
 The
 .Nm
 reference was written by
-.An Kristaps Dzonsons ,
-.Mt kristaps@bsd.lv .
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
diff --git a/usr/src/man/man5/tbl.5 b/usr/src/man/man5/tbl.5
index fe142208e8..d0238e4154 100644
--- a/usr/src/man/man5/tbl.5
+++ b/usr/src/man/man5/tbl.5
@@ -1,3 +1,7 @@
+.\"	$Id: tbl.7,v 1.26 2015/01/29 00:33:57 schwarze Exp $
+.\"
+.\" Copyright (c) 2010, 2011 Kristaps Dzonsons 
+.\" Copyright (c) 2014, 2015 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -11,11 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.\"
-.\" Copyright (c) 2010, 2011 Kristaps Dzonsons 
-.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
-.\"
-.Dd Sep 3, 2011
+.Dd $Mdocdate: January 29 2015 $
 .Dt TBL 5
 .Os
 .Sh NAME
@@ -46,11 +46,11 @@ are enclosed by the
 and
 .Sq TE
 macro tags, whose precise syntax is documented in
-.Xr roff 5 .
+.Xr mandoc_roff 5 .
 Tables consist of a series of options on a single line, followed by the
 table layout, followed by data.
 .Pp
-For example, the following creates a boxed table with digits centred in
+For example, the following creates a boxed table with digits centered in
 the cells.
 .Bd -literal -offset indent
 \&.TS
@@ -70,19 +70,12 @@ c5 c5 c5.
 4:5:6
 .TE
 .Ed
-.Pp
-The
-.Nm
-implementation in
-.Xr mandoc 1
-is
-.Ud
 .Sh TABLE STRUCTURE
 Tables are enclosed by the
 .Sq TS
 and
 .Sq TE
-.Xr roff 5
+.Xr mandoc_roff 5
 macros.
 A table consists of an optional single line of table
 .Sx Options
@@ -107,7 +100,7 @@ Table data is
 that is, data rows are parsed then inserted into the underlying stream
 of input data.
 This allows data rows to be interspersed by arbitrary
-.Xr roff 5 ,
+.Xr mandoc_roff 5 ,
 .Xr mdoc 5 ,
 and
 .Xr man 5
@@ -140,57 +133,59 @@ c c c.
 in the case of
 .Xr man 5 .
 .Ss Options
-The first line of a table consists of space-separated option keys and
-modifiers terminated by a semicolon.
+The first line of a table may contain options separated by spaces, tabs,
+or commas and terminated by a semicolon.
 If the first line does not have a terminating semicolon, it is assumed
 that no options are specified and instead a
 .Sx Layout
 is processed.
-Some options accept arguments enclosed by parenthesis.
+Some options require arguments enclosed by parentheses.
 The following case-insensitive options are available:
 .Bl -tag -width Ds
-.It Cm center
-This option is not supported by
-.Xr mandoc 1 .
-This may also be invoked with
-.Cm centre .
-.It Cm delim
-Accepts a two-character argument.
-This option is not supported by
-.Xr mandoc 1 .
-.It Cm expand
-This option is not supported by
-.Xr mandoc 1 .
+.It Cm allbox
+Draw a single-line box around each table cell.
+Currently treated as a synonym for
+.Cm box .
 .It Cm box
 Draw a single-line box around the table.
-This may also be invoked with
+For GNU compatibility, this may also be invoked with
 .Cm frame .
+.It Cm center
+Center the table instead of left-adjusting it.
+For GNU compatibility, this may also be invoked with
+.Cm centre .
+.It Cm decimalpoint
+Use the single-character argument as the decimal point with the
+.Cm n
+layout key.
+This is a GNU extension.
+.It Cm delim
+Use the two characters of the argument as
+.Xr eqn 5
+delimiters.
+Currently unsupported.
 .It Cm doublebox
 Draw a double-line box around the table.
-This may also be invoked with
+For GNU compatibility, this may also be invoked with
 .Cm doubleframe .
-.It Cm allbox
-This option is not supported by
-.Xr mandoc 1 .
-.It Cm tab
-Accepts a single-character argument.
-This character is used as a delimiter between data cells, which otherwise
-defaults to the tab character.
+.It Cm expand
+Increase the width of the table to the current line length.
+Currently ignored.
 .It Cm linesize
-Accepts a natural number (all digits).
-This option is not supported by
-.Xr mandoc 1 .
+Draw lines with the point size given by the unsigned integer argument.
+Currently ignored.
 .It Cm nokeep
-This option is not supported by
-.Xr mandoc 1 .
-.It Cm decimalpoint
-Accepts a single-character argument.
-This character will be used as the decimal point with the
-.Cm n
-layout key.
+Allow page breaks within the table.
+This is a GNU extension and currently ignored.
 .It Cm nospaces
-This option is not supported by
-.Xr mandoc 1 .
+Ignore leading and trailing spaces in data cells.
+This is a GNU extension and currently ignored.
+.It Cm nowarn
+Suppress warnings about tables exceeding the current line length.
+This is a GNU extension and currently ignored.
+.It Cm tab
+Use the single-character argument as a delimiter between data cells.
+By default, the tab character is used.
 .El
 .Ss Layout
 The table layout follows
@@ -203,9 +198,9 @@ Each layout line corresponds to a line of data; the last layout line
 applies to all remaining data lines.
 Layout lines may also be separated by a comma.
 Each layout cell consists of one of the following case-insensitive keys:
-.Bl -tag -width Ds
+.Bl -tag -width 2n
 .It Cm c
-Centre a literal string within its column.
+Center a literal string within its column.
 .It Cm r
 Right-justify a literal string within its column.
 .It Cm l
@@ -252,35 +247,60 @@ Keys may be followed by a set of modifiers.
 A modifier is either a modifier key or a natural number for specifying
 the minimum width of a column.
 The following case-insensitive modifier keys are available:
-.Cm z ,
-.Cm u ,
-.Cm e ,
-.Cm t ,
-.Cm d ,
-.Cm b ,
-.Cm i ,
-.Cm r ,
-and
-.Cm f
-.Po
-followed by
-.Cm b ,
-.Cm i ,
-.Cm r ,
-.Cm 3 ,
-.Cm 2 ,
-or
-.Cm 1
-.Pc .
-All of these are ignored by
-.Xr mandoc 1 .
+.Bl -tag -width 2n
+.It Cm b
+Use a bold font for the contents of this column.
+.It Cm d
+Move cell content down to the last cell of a vertical span.
+Currently ignored.
+.It Cm e
+Make this column wider to match the maximum width
+of any other column also having the
+.Cm e
+modifier.
+.It Cm f
+The next character selects the font to use for this column.
+See the
+.Xr mandoc_roff 5
+manual for supported one-character font names.
+.It Cm i
+Use an italic font for the contents of this column.
+.It Cm m
+Specify a cell start macro.
+This is a GNU extension and currently unsupported.
+.It Cm p
+Set the point size to the following unsigned argument,
+or change it by the following signed argument.
+Currently ignored.
+.It Cm v
+Set the vertical line spacing to the following unsigned argument,
+or change it by the following signed argument.
+Currently ignored.
+.It Cm t
+Do not vertically center cell content in the vertical span,
+leave it at the top.
+Currently ignored.
+.It Cm u
+Move cell content up by half a table line.
+Currently ignored.
+.It Cm w
+Specify minimum column width.
+Currently ignored.
+.It Cm x
+After determining the width of all other columns, distribute the
+rest of the line length among all columns having the
+.Cm x
+modifier.
+.It Cm z
+Do not use this cell for determining the width of this column.
+.El
 .Pp
-For example, the following layout specifies a centre-justified column of
+For example, the following layout specifies a center-justified column of
 minimum width 10, followed by vertical bar, followed by a left-justified
-column of minimum width 10, another vertical bar, then a column
-justified about the decimal point in numbers:
+column of minimum width 10, another vertical bar, then a column using
+bold font justified about the decimal point in numbers:
 .Pp
-.Dl c10 | l10 | n
+.Dl c10 | l10 | nfB
 .Ss Data
 The data section follows the last layout row.
 By default, cells in a data section are delimited by a tab.
@@ -308,24 +328,23 @@ It may then be followed by a tab
 .Pq or as designated by Cm tab
 or an end-of-line to terminate the row.
 .Sh COMPATIBILITY
-This section documents compatibility between mandoc and other
-.Nm
-implementations, at this time limited to GNU tbl.
-.Pp
-.Bl -dash -compact
-.It
-In GNU tbl, comments and macros are disallowed prior to the data block
-of a table.
 The
 .Xr mandoc 1
-implementation allows them.
-.El
+implementation of
+.Nm
+doesn't support
+.Xr mdoc 5
+and
+.Xr man 5
+macros and
+.Xr eqn 5
+equations inside tables.
 .Sh SEE ALSO
 .Xr mandoc 1 ,
 .Xr man 5 ,
 .Xr mandoc_char 5 ,
-.Xr mdoc 5 ,
-.Xr roff 5
+.Xr mandoc_roff 5 ,
+.Xr mdoc 5
 .Rs
 .%A M. E. Lesk
 .%T Tbl\(emA Program to Format Tables
@@ -345,5 +364,4 @@ utility.
 This
 .Nm
 reference was written by
-.An Kristaps Dzonsons ,
-.Mt kristaps@bsd.lv .
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
diff --git a/usr/src/pkg/manifests/system-man.mf b/usr/src/pkg/manifests/system-man.mf
index 619ed4496e..31bccf54db 100644
--- a/usr/src/pkg/manifests/system-man.mf
+++ b/usr/src/pkg/manifests/system-man.mf
@@ -10,8 +10,8 @@
 #
 
 #
-# Copyright 2012 Nexenta Systems, Inc. All rights reserved.
 # Copyright 2014 Garrett D'Amore 
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
 set name=pkg.fmri value=pkg:/system/man@$(PKGVERS)
@@ -22,7 +22,6 @@ set name=info.classification \
     value="org.opensolaris.category.2008:System/Text Tools"
 set name=variant.arch value=$(ARCH)
 dir path=usr/bin
-dir path=usr/lib
 dir path=usr/share
 dir path=usr/share/man
 dir path=usr/share/man/man1
@@ -30,7 +29,6 @@ dir path=usr/share/man/man1m
 dir path=usr/share/man/man5
 file path=usr/bin/man mode=0555
 file path=usr/bin/mandoc mode=0555
-file path=usr/lib/mandoc_preconv mode=0555
 file path=usr/share/man/man1/apropos.1
 file path=usr/share/man/man1/man.1
 file path=usr/share/man/man1/mandoc.1
@@ -53,7 +51,7 @@ license usr/src/cmd/mandoc/THIRDPARTYLICENSE \
 link path=usr/man target=./share/man
 link path=usr/share/man/man1/whatis.1 target=apropos.1
 # arguably we also need lp, for man -t support, but really we don't
-# want to make this mandatory, so we don't express teh dependency here.
+# want to make this mandatory, so we don't express the dependency here.
 # gzcat/bzcat are used for displaying compressed manpages.  However,
 # as we don't format such pages this way by default, lets leave the
 # dependency out.
diff --git a/usr/src/tools/mandoc/Makefile b/usr/src/tools/mandoc/Makefile
index 36cd11b462..eba26e75ba 100644
--- a/usr/src/tools/mandoc/Makefile
+++ b/usr/src/tools/mandoc/Makefile
@@ -10,42 +10,28 @@
 #
 
 #
-# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 #
 
+CMDDIR=		$(SRC)/cmd/mandoc
 
-PROG=		mandoc
+include		../Makefile.tools
+include		$(CMDDIR)/Makefile.common
 
-CMDDIR=	$(SRC)/cmd/mandoc
-
-include ../Makefile.tools
+.KEEP_STATE:
 
-include $(SRC)/cmd/mandoc/Makefile.common
+all:		$(PROG)
 
-SRCS=	$(mandoc_OBJS:%.o=$(CMDDIR)/%.c)
-CLEANFILES=	$(PROG) $(OBJS)
-OBJS= $(mandoc_OBJS)
-PROG= mandoc
+install:	all .WAIT $(ROOTONBLDMACHPROG)
 
-.KEEP_STATE:
-
-install: all .WAIT $(ROOTONBLDMACHPROG)
+clean:
+		$(RM) $(PROG) $(OBJS)
 
 $(PROG):	$(OBJS)
-		$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+		$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
 		$(POST_PROCESS)
 
-all:		$(PROG)
-
-
 %.o:		$(CMDDIR)/%.c
 		$(COMPILE.c) -o $@ $<
 
-%.o:		$(LIBDIR)/%.c
-		$(COMPILE.c) -o $@ $<
-
-clean:
-		$(RM) $(CLEANFILES)
-
-include ../Makefile.targ
-
+include		../Makefile.targ
-- 
cgit v1.2.3


From 6357b94b54238e954e002562d0e89a2fefd982e1 Mon Sep 17 00:00:00 2001
From: Keith M Wesolowski 
Date: Thu, 18 Sep 2014 18:03:35 +0000
Subject: 6415 sockets created via t_open/t_bind not shown by pfiles 6432 TLI
 sockets aren't detected correctly by pfiles in a NGZ Reviewed by: Robert
 Mustacchi  Reviewed by: Garrett D'Amore 
 Reviewed by: Mohamed A. Khalfella  Approved by: Matthew
 Ahrens 

---
 usr/src/cmd/ptools/pfiles/pfiles.c | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)

(limited to 'usr/src')

diff --git a/usr/src/cmd/ptools/pfiles/pfiles.c b/usr/src/cmd/ptools/pfiles/pfiles.c
index fbe54fc0dd..0b84047806 100644
--- a/usr/src/cmd/ptools/pfiles/pfiles.c
+++ b/usr/src/cmd/ptools/pfiles/pfiles.c
@@ -271,15 +271,14 @@ show_file(void *data, prfdinfo_t *info)
 		    (mode & S_IFMT) == S_IFDOOR);
 
 		if (Pstate(Pr) != PS_DEAD) {
-			char *dev;
+			char *dev = NULL;
 
 			if ((mode & S_IFMT) == S_IFSOCK)
 				dosocket(Pr, info->pr_fd);
 			else if ((mode & S_IFMT) == S_IFIFO)
 				dofifo(Pr, info->pr_fd);
 
-			if ((mode & S_IFMT) == S_IFCHR &&
-			    (dev = strrchr(info->pr_path, ':')) != NULL) {
+			if ((mode & S_IFMT) == S_IFCHR) {
 				/*
 				 * There's no elegant way to determine
 				 * if a character device supports TLI,
@@ -291,11 +290,20 @@ show_file(void *data, prfdinfo_t *info)
 				    "tcp", "tcp6", "udp", "udp6", NULL
 				};
 
-				dev++; /* skip past the `:' */
-				for (i = 0; tlidevs[i] != NULL; i++) {
-					if (strcmp(dev, tlidevs[i]) == 0) {
-						dotli(Pr, info->pr_fd);
-						break;
+				/* global zone: /devices paths */
+				dev = strrchr(info->pr_path, ':');
+				/* also check the /dev path for zones */
+				if (dev == NULL)
+					dev = strrchr(info->pr_path, '/');
+				if (dev != NULL) {
+					dev++; /* skip past the `:' or '/' */
+
+					for (i = 0; tlidevs[i] != NULL; i++) {
+						if (strcmp(dev, tlidevs[i]) ==
+						    0) {
+							dotli(Pr, info->pr_fd);
+							break;
+						}
 					}
 				}
 			}
@@ -796,9 +804,11 @@ dotli(struct ps_prochandle *Pr, int fd)
 
 	strcmd.sc_cmd = TI_GETMYNAME;
 	if (pr_ioctl(Pr, fd, _I_CMD, &strcmd, sizeof (strcmd)) == 0)
-		show_sockaddr("sockname", (void *)&strcmd.sc_buf, 0);
+		show_sockaddr("sockname", (void *)&strcmd.sc_buf,
+		    (size_t)strcmd.sc_len);
 
 	strcmd.sc_cmd = TI_GETPEERNAME;
 	if (pr_ioctl(Pr, fd, _I_CMD, &strcmd, sizeof (strcmd)) == 0)
-		show_sockaddr("peername", (void *)&strcmd.sc_buf, 0);
+		show_sockaddr("peername", (void *)&strcmd.sc_buf,
+		    (size_t)strcmd.sc_len);
 }
-- 
cgit v1.2.3