--- old/exception_lists/packaging	Fri Jul 18 08:34:30 2014
+++ new/exception_lists/packaging	Fri Jul 18 08:34:30 2014
@@ -946,6 +946,8 @@
 #
 opt/onbld/bin/i386/elfsign	i386
 opt/onbld/bin/sparc/elfsign	sparc
+opt/onbld/bin/i386/mandoc	i386
+opt/onbld/bin/sparc/mandoc	sparc
 
 #
 # Private libdwarf
--- old/usr/src/Makefile	Fri Jul 18 08:34:31 2014
+++ new/usr/src/Makefile	Fri Jul 18 08:34:31 2014
@@ -65,6 +65,9 @@
 # The check target also causes smf(5) service manifests to be validated.
 CHKMFSTSUBDIRS=	cmd
 
+# And man page formats
+CHKMANSUBDIRS = man
+
 MSGSUBDIRS=	cmd ucbcmd lib
 DOMAINS= \
 	SUNW_OST_ADMIN \
@@ -205,7 +208,7 @@
 	@cd cmd/fm; pwd; $(MAKE) install_h
 	@cd cmd/mdb; pwd; $(MAKE) install_h
 
-check:	$(CHKHDRSUBDIRS) $(CHKMFSTSUBDIRS)
+check:	$(CHKHDRSUBDIRS) $(CHKMFSTSUBDIRS) $(CHKMANSUBDIRS)
 
 #
 # Cross-reference customization: skip all of the subdirectories that
--- old/usr/src/cmd/Makefile	Fri Jul 18 08:34:31 2014
+++ new/usr/src/cmd/Makefile	Fri Jul 18 08:34:31 2014
@@ -24,11 +24,12 @@
 # Copyright (c) 2012 Joyent, Inc.  All rights reserved.
 # Copyright (c) 2012 by Delphix. All rights reserved.
 # Copyright (c) 2013 DEY Storage Systems, Inc. All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 
 include ../Makefile.master
 
 #
-#	Note that the commands 'agents', 'lp', 'perl', and 'man' are first in
+#	Note that the commands 'lp', and 'perl' are first in
 #	the list, violating alphabetical order.  This is because they are very
 #	long-running and should be given the most wall-clock time for a
 #	parallel build.
@@ -52,7 +53,6 @@
 	availdevs	\
 	lp		\
 	perl		\
-	man		\
 	Adm		\
 	abi		\
 	adbgen		\
@@ -252,6 +252,8 @@
 	mail		\
 	mailx		\
 	makekey		\
+	man		\
+	mandoc		\
 	mdb		\
 	mesg		\
 	mkdir		\
@@ -498,12 +500,11 @@
 	vntsd
 
 #
-# Commands that are messaged.  Note that 'lp' and 'man' come first
-# (see previous comment about 'lp' and 'man').
+# Commands that are messaged.  Note that 'lp' comes first
+# (see previous comment about 'lp'.)
 #
 MSGSUBDIRS=		\
 	lp		\
-	man		\
 	abi		\
 	acctadm		\
 	allocate	\
@@ -617,6 +618,7 @@
 	luxadm		\
 	lvm		\
 	mailx		\
+	man		\
 	mesg		\
 	mkdir		\
 	mkpwdict	\
--- old/usr/src/cmd/man/Makefile	Fri Jul 18 08:34:31 2014
+++ new/usr/src/cmd/man/Makefile	Fri Jul 18 08:34:31 2014
@@ -1,61 +1,46 @@
 #
-# CDDL HEADER START
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
 #
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License").  You may not use this file except in compliance
-# with the License.
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
 #
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
+
 #
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
+# Copyright 2012 Nexenta Systems, Inc. All rights reserved.
 #
-# CDDL HEADER END
-#
-#
-#ident	"%Z%%M%	%I%	%E% SMI"
-#
-# Copyright (c) 1990 by Sun Microsystems, Inc.
-#
-# cmd/man/Makefile
 
-include ../Makefile.cmd
-SUBDIRS = src
+PROG=		man
+LINKS=		apropos whatis catman
+LIBLINKS =	makewhatis
+OBJS=		makewhatis.o man.o stringlist.o
+SRCS=		$(OBJS:%.o=%.c)
 
-all :=		TARGET= all
-install :=	TARGET= install
-clean :=	TARGET= clean
-clobber :=	TARGET= clobber
-lint :=		TARGET= lint
-_msg :=         TARGET= catalog
+include		$(SRC)/cmd/Makefile.cmd
 
-#for message catalog files
-POFILE = man.po
-POFILES = src/src.po
+CFLAGS +=	$(CCVERBOSE)
 
-.KEEP_STATE:
+ROOTLINKS=	$(LINKS:%=$(ROOTBIN)/%) $(LIBLINKS:%=$(ROOTLIB)/%)
 
-all install clean lint: $(SUBDIRS)
+.KEEP_STATE :
 
-clobber: $(SUBDIRS)	local_clobber
+all:		$(PROG)
 
-local_clobber:
-	$(RM)	$(CLOBBERFILES)
+clean:
+		$(RM) $(OBJS)
 
-_msg:   $(SUBDIRS)
-	$(RM)	$(POFILE)
-	cat $(POFILES)      > $(POFILE)
-	$(RM)  $(MSGDOMAIN)/$(POFILE)
-	cp $(POFILE) $(MSGDOMAIN)
+install:	all $(ROOTPROG) $(ROOTLINKS)
 
-$(SUBDIRS): FRC
-	@cd $@; pwd; $(MAKE) $(TARGET)
+lint: 		lint_SRCS
 
-FRC:
+$(PROG):	$(OBJS)
+		$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
+		$(POST_PROCESS)
+
+$(ROOTLINKS):	$(ROOTPROG)
+		$(RM) $@; $(LN) $(ROOTPROG) $@
+
+include		$(SRC)/cmd/Makefile.targ
--- /dev/null	Fri Jul 18 08:34:31 2014
+++ new/usr/src/cmd/man/THIRDPARTYLICENSE	Fri Jul 18 08:34:31 2014
@@ -0,0 +1,92 @@
+man.c:
+
+Copyright (c) 1980 Regents of the University of California.  
+All rights reserved.
+
+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. All advertising materials mentioning features or use of this
+       software must display the following acknowledgement:
+           This product includes software developed by the University
+           of California, Berkeley and its contributors.
+    4. Neither the name of the University 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 REGENTS 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 COPYRIGHT OWNER 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.
+
+
+makewhatis.c:
+
+Copyright (c) 2002 John Rochester
+All rights reserved.
+
+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,
+   in this position and unchanged.
+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. The name of the author may not be used to endorse or promote products
+   derived from this software without specific prior written permission
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+
+
+stringlist.c, stringlist.h:
+
+Copyright (c) 1994 Christos Zoulas
+All rights reserved.
+
+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.
+4. The name of the author may not be used to endorse or promote products
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
--- old/usr/src/cmd/man/src/THIRDPARTYLICENSE.descrip	Fri Jul 18 08:34:31 2014
+++ /dev/null	Fri Jul 18 08:34:31 2014
@@ -1,1 +0,0 @@
-PORTIONS OF MAN COMMAND FUNCTIONALITY
--- /dev/null	Fri Jul 18 08:34:31 2014
+++ new/usr/src/cmd/man/THIRDPARTYLICENSE.descrip	Fri Jul 18 08:34:31 2014
@@ -0,0 +1,1 @@
+PORTIONS OF MAN COMMAND FUNCTIONALITY
--- /dev/null	Fri Jul 18 08:34:31 2014
+++ new/usr/src/cmd/man/makewhatis.c	Fri Jul 18 08:34:31 2014
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2002 John Rochester
+ * All rights reserved.
+ *
+ * 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,
+ *    in this position and unchanged.
+ * 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. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "man.h"
+#include "stringlist.h"
+
+
+/* Information collected about each man page in a section */
+struct page_info {
+	char	*filename;
+	char	*name;
+	char	*suffix;
+	ino_t	inode;
+};
+
+/* An expanding string */
+struct sbuf {
+	char	*content;	/* the start of the buffer */
+	char	*end;		/* just past the end of the content */
+	char	*last;		/* the last allocated character */
+};
+
+/* Remove the last amount characters from the sbuf */
+#define	sbuf_retract(sbuf, amount) ((sbuf)->end -= (amount))
+/* Return the length of the sbuf content */
+#define	sbuf_length(sbuf) ((sbuf)->end - (sbuf)->content)
+
+typedef char *edited_copy(char *from, char *to, int length);
+
+/*
+ * While the whatis line is being formed, it is stored in whatis_proto.
+ * When finished, it is reformatted into whatis_final and then appended
+ * to whatis_lines.
+ */
+static struct sbuf	*whatis_proto;
+static struct sbuf	*whatis_final;
+static stringlist	*whatis_lines;	/* collected output lines */
+
+static char tempfile[MAXPATHLEN];	/* path of temporary file, if any */
+
+#define	MDOC_COMMANDS	"ArDvErEvFlLiNmPa"
+
+
+/* Free a struct page_info and its content */
+static void
+free_page_info(struct page_info *info)
+{
+
+	free(info->filename);
+	free(info->name);
+	free(info->suffix);
+	free(info);
+}
+
+/*
+ * Allocate and fill in a new struct page_info given the
+ * name of the man section directory and the dirent of the file.
+ * If the file is not a man page, return NULL.
+ */
+static struct page_info *
+new_page_info(char *dir, struct dirent *dirent)
+{
+	struct page_info *info;
+	int		basename_length;
+	char		*suffix;
+	struct stat	st;
+
+	if ((info = malloc(sizeof (struct page_info))) == NULL)
+		err(1, "malloc");
+	basename_length = strlen(dirent->d_name);
+	suffix = &dirent->d_name[basename_length];
+	if (asprintf(&info->filename, "%s/%s", dir, dirent->d_name) == -1)
+		err(1, "asprintf");
+	for (;;) {
+		if (--suffix == dirent->d_name || !isalnum(*suffix)) {
+			if (*suffix == '.')
+				break;
+			free(info->filename);
+			free(info);
+			return (NULL);
+		}
+	}
+	*suffix++ = '\0';
+	info->name = strdup(dirent->d_name);
+	info->suffix = strdup(suffix);
+	if (stat(info->filename, &st) < 0) {
+		warn("%s", info->filename);
+		free_page_info(info);
+		return (NULL);
+	}
+	if (!S_ISREG(st.st_mode)) {
+		free_page_info(info);
+		return (NULL);
+	}
+	info->inode = st.st_ino;
+	return (info);
+}
+
+/*
+ * Reset sbuf length to 0.
+ */
+static void
+sbuf_clear(struct sbuf *sbuf)
+{
+
+	sbuf->end = sbuf->content;
+}
+
+/*
+ * Allocate a new sbuf.
+ */
+static struct sbuf *
+new_sbuf(void)
+{
+	struct sbuf	*sbuf;
+
+	if ((sbuf = malloc(sizeof (struct sbuf))) == NULL)
+		err(1, "malloc");
+	if ((sbuf->content = (char *)malloc(LINE_ALLOC)) == NULL)
+		err(1, "malloc");
+	sbuf->last = sbuf->content + LINE_ALLOC - 1;
+	sbuf_clear(sbuf);
+
+	return (sbuf);
+}
+
+/*
+ * Ensure that there is enough room in the sbuf
+ * for nchars more characters.
+ */
+static void
+sbuf_need(struct sbuf *sbuf, int nchars)
+{
+	char *new_content;
+	size_t size, cntsize;
+
+	/* Double the size of the allocation until the buffer is big enough */
+	while (sbuf->end + nchars > sbuf->last) {
+		size = sbuf->last + 1 - sbuf->content;
+		size *= 2;
+		cntsize = sbuf->end - sbuf->content;
+
+		new_content = (char *)malloc(size);
+		(void) memcpy(new_content, sbuf->content, cntsize);
+		free(sbuf->content);
+		sbuf->content = new_content;
+		sbuf->end = new_content + cntsize;
+		sbuf->last = new_content + size - 1;
+	}
+}
+
+/*
+ * Append a string of a given length to the sbuf.
+ */
+static void
+sbuf_append(struct sbuf *sbuf, const char *text, int length)
+{
+	if (length > 0) {
+		sbuf_need(sbuf, length);
+		(void) memcpy(sbuf->end, text, length);
+		sbuf->end += length;
+	}
+}
+
+/*
+ * Append a null-terminated string to the sbuf.
+ */
+static void
+sbuf_append_str(struct sbuf *sbuf, char *text)
+{
+
+	sbuf_append(sbuf, text, strlen(text));
+}
+
+/*
+ * Append an edited null-terminated string to the sbuf.
+ */
+static void
+sbuf_append_edited(struct sbuf *sbuf, char *text, edited_copy copy)
+{
+	int	length;
+
+	if ((length = strlen(text)) > 0) {
+		sbuf_need(sbuf, length);
+		sbuf->end = copy(text, sbuf->end, length);
+	}
+}
+
+/*
+ * Strip any of a set of chars from the end of the sbuf.
+ */
+static void
+sbuf_strip(struct sbuf *sbuf, const char *set)
+{
+
+	while (sbuf->end > sbuf->content && strchr(set, sbuf->end[-1]) != NULL)
+		sbuf->end--;
+}
+
+/*
+ * Return the null-terminated string built by the sbuf.
+ */
+static char *
+sbuf_content(struct sbuf *sbuf)
+{
+
+	*sbuf->end = '\0';
+	return (sbuf->content);
+}
+
+/*
+ * Return true if no man page exists in the directory with
+ * any of the names in the stringlist.
+ */
+static int
+no_page_exists(char *dir, stringlist *names, char *suffix)
+{
+	char	path[MAXPATHLEN];
+	size_t	i;
+
+	for (i = 0; i < names->sl_cur; i++) {
+		(void) snprintf(path, MAXPATHLEN, "%s/%s.%s.gz",
+		    dir, names->sl_str[i], suffix);
+		if (access(path, F_OK) < 0) {
+			path[strlen(path) - 3] = '\0';
+			if (access(path, F_OK) < 0)
+				continue;
+		}
+		return (0);
+	}
+	return (1);
+}
+
+/* ARGSUSED sig */
+static void
+trap_signal(int sig)
+{
+
+	if (tempfile[0] != '\0')
+		(void) unlink(tempfile);
+
+	exit(1);
+}
+
+/*
+ * Attempt to open an output file.
+ * Return NULL if unsuccessful.
+ */
+static FILE *
+open_output(char *name)
+{
+	FILE	*output;
+
+	whatis_lines = sl_init();
+	(void) snprintf(tempfile, MAXPATHLEN, "%s.tmp", name);
+	name = tempfile;
+	if ((output = fopen(name, "w")) == NULL) {
+		warn("%s", name);
+		return (NULL);
+	}
+	return (output);
+}
+
+static int
+linesort(const void *a, const void *b)
+{
+
+	return (strcmp((*(const char * const *)a), (*(const char * const *)b)));
+}
+
+/*
+ * Write the unique sorted lines to the output file.
+ */
+static void
+finish_output(FILE *output, char *name)
+{
+	size_t	i;
+	char	*prev = NULL;
+
+	qsort(whatis_lines->sl_str, whatis_lines->sl_cur, sizeof (char *),
+	    linesort);
+	for (i = 0; i < whatis_lines->sl_cur; i++) {
+		char *line = whatis_lines->sl_str[i];
+		if (i > 0 && strcmp(line, prev) == 0)
+			continue;
+		prev = line;
+		(void) fputs(line, output);
+		(void) putc('\n', output);
+	}
+	(void) fclose(output);
+	sl_free(whatis_lines, 1);
+	(void) rename(tempfile, name);
+	(void) unlink(tempfile);
+}
+
+static FILE *
+open_whatis(char *mandir)
+{
+	char	filename[MAXPATHLEN];
+
+	(void) snprintf(filename, MAXPATHLEN, "%s/%s", mandir, WHATIS);
+	return (open_output(filename));
+}
+
+static void
+finish_whatis(FILE *output, char *mandir)
+{
+	char	filename[MAXPATHLEN];
+
+	(void) snprintf(filename, MAXPATHLEN, "%s/%s", mandir, WHATIS);
+	finish_output(output, filename);
+}
+
+/*
+ * Remove trailing spaces from a string, returning a pointer to just
+ * beyond the new last character.
+ */
+static char *
+trim_rhs(char *str)
+{
+	char	*rhs;
+
+	rhs = &str[strlen(str)];
+	while (--rhs > str && isspace(*rhs))
+		;
+	*++rhs = '\0';
+	return (rhs);
+}
+
+/*
+ * Return a pointer to the next non-space character in the string.
+ */
+static char *
+skip_spaces(char *s)
+{
+
+	while (*s != '\0' && isspace(*s))
+		s++;
+
+	return (s);
+}
+
+/*
+ * Return whether the line is of one of the forms:
+ *	.Sh NAME
+ *	.Sh "NAME"
+ *	etc.
+ * assuming that section_start is ".Sh".
+ */
+static int
+name_section_line(char *line, const char *section_start)
+{
+	char		*rhs;
+
+	if (strncmp(line, section_start, 3) != 0)
+		return (0);
+	line = skip_spaces(line + 3);
+	rhs = trim_rhs(line);
+	if (*line == '"') {
+		line++;
+		if (*--rhs == '"')
+			*rhs = '\0';
+	}
+	if (strcmp(line, "NAME") == 0)
+		return (1);
+
+	return (0);
+}
+
+/*
+ * Copy characters while removing the most common nroff/troff markup:
+ *	\(em, \(mi, \s[+-N], \&
+ *	\fF, \f(fo, \f[font]
+ *	\*s, \*(st, \*[stringvar]
+ */
+static char *
+de_nroff_copy(char *from, char *to, int fromlen)
+{
+	char	*from_end = &from[fromlen];
+
+	while (from < from_end) {
+		switch (*from) {
+		case '\\':
+			switch (*++from) {
+			case '(':
+				if (strncmp(&from[1], "em", 2) == 0 ||
+				    strncmp(&from[1], "mi", 2) == 0) {
+					from += 3;
+					continue;
+				}
+				break;
+			case 's':
+				if (*++from == '-')
+					from++;
+				while (isdigit(*from))
+					from++;
+				continue;
+			case 'f':
+			case '*':
+				if (*++from == '(') {
+					from += 3;
+				} else if (*from == '[') {
+					while (*++from != ']' &&
+					    from < from_end)
+						;
+					from++;
+				} else {
+					from++;
+				}
+				continue;
+			case '&':
+				from++;
+				continue;
+			}
+			break;
+		}
+		*to++ = *from++;
+	}
+	return (to);
+}
+
+/*
+ * Append a string with the nroff formatting removed.
+ */
+static void
+add_nroff(char *text)
+{
+
+	sbuf_append_edited(whatis_proto, text, de_nroff_copy);
+}
+
+/*
+ * Appends "name(suffix), " to whatis_final
+ */
+static void
+add_whatis_name(char *name, char *suffix)
+{
+
+	if (*name != '\0') {
+		sbuf_append_str(whatis_final, name);
+		sbuf_append(whatis_final, "(", 1);
+		sbuf_append_str(whatis_final, suffix);
+		sbuf_append(whatis_final, "), ", 3);
+	}
+}
+
+/*
+ * Processes an old-style man(7) line. This ignores commands with only
+ * a single number argument.
+ */
+static void
+process_man_line(char *line)
+{
+	char	*p;
+
+	if (*line == '.') {
+		while (isalpha(*++line))
+			;
+		p = line = skip_spaces(line);
+		while (*p != '\0') {
+			if (!isdigit(*p))
+				break;
+			p++;
+		}
+		if (*p == '\0')
+			return;
+	} else
+		line = skip_spaces(line);
+	if (*line != '\0') {
+		add_nroff(line);
+		sbuf_append(whatis_proto, " ", 1);
+	}
+}
+
+/*
+ * Processes a new-style mdoc(7) line.
+ */
+static void
+process_mdoc_line(char *line)
+{
+	int	xref;
+	int	arg = 0;
+	char	*line_end = &line[strlen(line)];
+	int	orig_length = sbuf_length(whatis_proto);
+	char	*next;
+
+	if (*line == '\0')
+		return;
+	if (line[0] != '.' || !isupper(line[1]) || !islower(line[2])) {
+		add_nroff(skip_spaces(line));
+		sbuf_append(whatis_proto, " ", 1);
+		return;
+	}
+	xref = strncmp(line, ".Xr", 3) == 0;
+	line += 3;
+	while ((line = skip_spaces(line)) < line_end) {
+		if (*line == '"') {
+			next = ++line;
+			for (;;) {
+				next = strchr(next, '"');
+				if (next == NULL)
+					break;
+				(void) memmove(next, next + 1, strlen(next));
+				line_end--;
+				if (*next != '"')
+					break;
+				next++;
+			}
+		} else {
+			next = strpbrk(line, " \t");
+		}
+		if (next != NULL)
+			*next++ = '\0';
+		else
+			next = line_end;
+		if (isupper(*line) && islower(line[1]) && line[2] == '\0') {
+			if (strcmp(line, "Ns") == 0) {
+				arg = 0;
+				line = next;
+				continue;
+			}
+			if (strstr(line, MDOC_COMMANDS) != NULL) {
+				line = next;
+				continue;
+			}
+		}
+		if (arg > 0 && strchr(",.:;?!)]", *line) == 0) {
+			if (xref) {
+				sbuf_append(whatis_proto, "(", 1);
+				add_nroff(line);
+				sbuf_append(whatis_proto, ")", 1);
+				xref = 0;
+			} else {
+				sbuf_append(whatis_proto, " ", 1);
+			}
+		}
+		add_nroff(line);
+		arg++;
+		line = next;
+	}
+	if (sbuf_length(whatis_proto) > orig_length)
+		sbuf_append(whatis_proto, " ", 1);
+}
+
+/*
+ * Collect a list of comma-separated names from the text.
+ */
+static void
+collect_names(stringlist *names, char *text)
+{
+	char	*arg;
+
+	for (;;) {
+		arg = text;
+		text = strchr(text, ',');
+		if (text != NULL)
+			*text++ = '\0';
+		(void) sl_add(names, arg);
+		if (text == NULL)
+			return;
+		if (*text == ' ')
+			text++;
+	}
+}
+
+enum { STATE_UNKNOWN, STATE_MANSTYLE, STATE_MDOCNAME, STATE_MDOCDESC };
+
+/*
+ * Process a man page source into a single whatis line and add it
+ * to whatis_lines.
+ */
+static void
+process_page(struct page_info *page, char *section_dir)
+{
+	FILE		*fp;
+	stringlist	*names;
+	char		*descr;
+	int		state = STATE_UNKNOWN;
+	size_t		i;
+	char		*line = NULL;
+	size_t		linecap = 0;
+
+	sbuf_clear(whatis_proto);
+	if ((fp = fopen(page->filename, "r")) == NULL) {
+		warn("%s", page->filename);
+		return;
+	}
+	while (getline(&line, &linecap, fp) > 0) {
+		/* Skip comments */
+		if (strncmp(line, ".\\\"", 3) == 0)
+			continue;
+		switch (state) {
+		/* Haven't reached the NAME section yet */
+		case STATE_UNKNOWN:
+			if (name_section_line(line, ".SH"))
+				state = STATE_MANSTYLE;
+			else if (name_section_line(line, ".Sh"))
+				state = STATE_MDOCNAME;
+			continue;
+		/* Inside an old-style .SH NAME section */
+		case STATE_MANSTYLE:
+			if (strncmp(line, ".SH", 3) == 0 ||
+			    strncmp(line, ".SS", 3) == 0)
+				break;
+			(void) trim_rhs(line);
+			if (strcmp(line, ".") == 0)
+				continue;
+			if (strncmp(line, ".IX", 3) == 0) {
+				line += 3;
+				line = skip_spaces(line);
+			}
+			process_man_line(line);
+			continue;
+		/* Inside a new-style .Sh NAME section (the .Nm part) */
+		case STATE_MDOCNAME:
+			(void) trim_rhs(line);
+			if (strncmp(line, ".Nm", 3) == 0) {
+				process_mdoc_line(line);
+				continue;
+			} else {
+				if (strcmp(line, ".") == 0)
+					continue;
+				sbuf_append(whatis_proto, "- ", 2);
+				state = STATE_MDOCDESC;
+			}
+			/* FALLTHROUGH */
+		/* Inside a new-style .Sh NAME section (after the .Nm-s) */
+		case STATE_MDOCDESC:
+			if (strncmp(line, ".Sh", 3) == 0)
+				break;
+			(void) trim_rhs(line);
+			if (strcmp(line, ".") == 0)
+				continue;
+			process_mdoc_line(line);
+			continue;
+		}
+		break;
+	}
+	(void) fclose(fp);
+	sbuf_strip(whatis_proto, " \t.-");
+	line = sbuf_content(whatis_proto);
+	/*
+	 * Line now contains the appropriate data, but without the
+	 * proper indentation or the section appended to each name.
+	 */
+	descr = strstr(line, " - ");
+	if (descr == NULL) {
+		descr = strchr(line, ' ');
+		if (descr == NULL)
+			return;
+		*descr++ = '\0';
+	} else {
+		*descr = '\0';
+		descr += 3;
+	}
+	names = sl_init();
+	collect_names(names, line);
+	sbuf_clear(whatis_final);
+	if (!sl_find(names, page->name) &&
+	    no_page_exists(section_dir, names, page->suffix)) {
+		/*
+		 * Add the page name since that's the only
+		 * thing that man(1) will find.
+		 */
+		add_whatis_name(page->name, page->suffix);
+	}
+	for (i = 0; i < names->sl_cur; i++)
+		add_whatis_name(names->sl_str[i], page->suffix);
+	sl_free(names, 0);
+	/* Remove last ", " */
+	sbuf_retract(whatis_final, 2);
+	while (sbuf_length(whatis_final) < INDENT)
+		sbuf_append(whatis_final, " ", 1);
+	sbuf_append(whatis_final, " - ", 3);
+	sbuf_append_str(whatis_final, skip_spaces(descr));
+	(void) sl_add(whatis_lines, strdup(sbuf_content(whatis_final)));
+}
+
+/*
+ * Sort pages first by inode number, then by name.
+ */
+static int
+pagesort(const void *a, const void *b)
+{
+	const struct page_info *p1 = *(struct page_info * const *) a;
+	const struct page_info *p2 = *(struct page_info * const *) b;
+
+	if (p1->inode == p2->inode)
+		return (strcmp(p1->name, p2->name));
+
+	return (p1->inode - p2->inode);
+}
+
+/*
+ * Process a single man section.
+ */
+static void
+process_section(char *section_dir)
+{
+	struct dirent	**entries;
+	int		nentries;
+	struct page_info **pages;
+	int		npages = 0;
+	int		i;
+	ino_t		prev_inode = 0;
+
+	/* Scan the man section directory for pages */
+	nentries = scandir(section_dir, &entries, NULL, alphasort);
+
+	/* Collect information about man pages */
+	pages = (struct page_info **)calloc(nentries,
+	    sizeof (struct page_info *));
+	for (i = 0; i < nentries; i++) {
+		struct page_info *info = new_page_info(section_dir, entries[i]);
+		if (info != NULL)
+			pages[npages++] = info;
+		free(entries[i]);
+	}
+	free(entries);
+	qsort(pages, npages, sizeof (struct page_info *), pagesort);
+
+	/* Process each unique page */
+	for (i = 0; i < npages; i++) {
+		struct page_info *page = pages[i];
+		if (page->inode != prev_inode) {
+			prev_inode = page->inode;
+			process_page(page, section_dir);
+		}
+		free_page_info(page);
+	}
+	free(pages);
+}
+
+/*
+ * Return whether the directory entry is a man page section.
+ */
+static int
+select_sections(const struct dirent *entry)
+{
+	const char	*p = &entry->d_name[3];
+
+	if (strncmp(entry->d_name, "man", 3) != 0)
+		return (0);
+	while (*p != '\0') {
+		if (!isalnum(*p++))
+			return (0);
+	}
+	return (1);
+}
+
+/*
+ * Process a single top-level man directory by finding all the
+ * sub-directories named man* and processing each one in turn.
+ */
+void
+mwpath(char *path)
+{
+	FILE		*fp = NULL;
+	struct dirent	**entries;
+	int		nsections;
+	int		i;
+
+	(void) signal(SIGINT, trap_signal);
+	(void) signal(SIGHUP, trap_signal);
+	(void) signal(SIGQUIT, trap_signal);
+	(void) signal(SIGTERM, trap_signal);
+
+	whatis_proto = new_sbuf();
+	whatis_final = new_sbuf();
+
+	nsections = scandir(path, &entries, select_sections, alphasort);
+	if ((fp = open_whatis(path)) == NULL)
+		return;
+	for (i = 0; i < nsections; i++) {
+		char	section_dir[MAXPATHLEN];
+
+		(void) snprintf(section_dir, MAXPATHLEN, "%s/%s",
+		    path, entries[i]->d_name);
+		process_section(section_dir);
+		free(entries[i]);
+	}
+	free(entries);
+	finish_whatis(fp, path);
+}
--- /dev/null	Fri Jul 18 08:34:31 2014
+++ new/usr/src/cmd/man/man.c	Fri Jul 18 08:34:31 2014
@@ -0,0 +1,1603 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012, Josef 'Jeff' Sipek <jeffpc@31bits.net>. All rights reserved.
+ * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989  AT&T.	*/
+/*		All rights reserved.					*/
+
+/*
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ */
+
+/*
+ * Find and display reference manual pages. This version includes makewhatis
+ * functionality as well.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/termios.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <locale.h>
+#include <malloc.h>
+#include <memory.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "man.h"
+
+
+/* Mapping of old directories to new directories */
+static const struct map_entry {
+	char	*old_name;
+	char	*new_name;
+} map[] = {
+	{ "3b",		"3ucb"		},
+	{ "3e",		"3elf"		},
+	{ "3g",		"3gen"		},
+	{ "3k",		"3kstat"	},
+	{ "3n",		"3socket"	},
+	{ "3r",		"3rt"		},
+	{ "3s",		"3c"		},
+	{ "3t",		"3thr"		},
+	{ "3x",		"3curses"	},
+	{ "3xc",	"3xcurses"	},
+	{ "3xn",	"3xnet"		}
+};
+
+struct suffix {
+	char *ds;
+	char *fs;
+};
+
+/*
+ * Flags that control behavior of build_manpath()
+ *
+ *   BMP_ISPATH 		pathv is a vector constructed from PATH.
+ *                		Perform appropriate path translations for
+ * 				manpath.
+ *   BMP_APPEND_DEFMANDIR	Add DEFMANDIR to the end if it hasn't
+ *				already appeared earlier.
+ *   BMP_FALLBACK_DEFMANDIR	Append /usr/share/man only if no other
+ *				manpath (including derived from PATH)
+ * 				elements are valid.
+ */
+#define	BMP_ISPATH		1
+#define	BMP_APPEND_DEFMANDIR	2
+#define	BMP_FALLBACK_DEFMANDIR	4
+
+/*
+ * When doing equality comparisons of directories, device and inode
+ * comparisons are done.  The secnode and dupnode structures are used
+ * to form a list of lists for this processing.
+ */
+struct secnode {
+	char		*secp;
+	struct secnode	*next;
+};
+struct dupnode {
+	dev_t		dev;	/* from struct stat st_dev */
+	ino_t		ino;	/* from struct stat st_ino */
+	struct secnode	*secl;	/* sections already considered */
+	struct dupnode	*next;
+};
+
+/*
+ * Map directories that may appear in PATH to the corresponding
+ * man directory.
+ */
+static struct pathmap {
+	char	*bindir;
+	char	*mandir;
+	dev_t	dev;
+	ino_t	ino;
+} bintoman[] = {
+	{ "/sbin",		"/usr/share/man,1m",		0, 0 },
+	{ "/usr/sbin",		"/usr/share/man,1m",		0, 0 },
+	{ "/usr/ucb",		"/usr/share/man,1b",		0, 0 },
+	{ "/usr/bin",		"/usr/share/man,1,1m,1s,1t,1c", 0, 0 },
+	{ "/usr/xpg4/bin",	"/usr/share/man,1",		0, 0 },
+	{ "/usr/xpg6/bin",	"/usr/share/man,1",		0, 0 },
+	{ NULL,			NULL,				0, 0 }
+};
+
+struct man_node {
+	char		*path;		/* mandir path */
+	char		**secv;		/* submandir suffices */
+	int		defsrch;	/* hint for man -p */
+	int		frompath;	/* hint for man -d */
+	struct man_node *next;
+};
+
+static int	all = 0;
+static int	apropos = 0;
+static int	debug = 0;
+static int	found = 0;
+static int	list = 0;
+static int	makewhatis = 0;
+static int	printmp = 0;
+static int	sargs = 0;
+static int	psoutput = 0;
+static int	whatis = 0;
+static int	makewhatishere = 0;
+
+static char	*mansec;
+static char	*pager;
+
+static char	*addlocale(char *);
+static struct man_node *build_manpath(char **, int);
+static void	do_makewhatis(struct man_node *);
+static char	*check_config(char *);
+static int	cmp(const void *, const void *);
+static int	dupcheck(struct man_node *, struct dupnode **);
+static int	format(char *, char *, char *, char *);
+static void	free_dupnode(struct dupnode *);
+static void	free_manp(struct man_node *manp);
+static void	freev(char **);
+static void	fullpaths(struct man_node **);
+static void	get_all_sect(struct man_node *);
+static int	getdirs(char *, char ***, int);
+static void	getpath(struct man_node *, char **);
+static void	getsect(struct man_node *, char **);
+static void	init_bintoman(void);
+static void	lower(char *);
+static void	mandir(char **, char *, char *, int);
+static int	manual(struct man_node *, char *);
+static char	*map_section(char *, char *);
+static char	*path_to_manpath(char *);
+static void	print_manpath(struct man_node *);
+static void	search_whatis(char *, char *);
+static int	searchdir(char *, char *, char *);
+static void	sortdir(DIR *, char ***);
+static char	**split(char *, char);
+static void	usage_man(void);
+static void	usage_whatapro(void);
+static void	usage_catman(void);
+static void	usage_makewhatis(void);
+static void	whatapro(struct man_node *, char *);
+
+static char	language[MAXPATHLEN]; 	/* LC_MESSAGES */
+static char	localedir[MAXPATHLEN];	/* locale specific path component */
+
+static char	*newsection = NULL;
+
+static int	manwidth = 0;
+
+extern const char	*__progname;
+
+int
+main(int argc, char **argv)
+{
+	int		c, i;
+	char		**pathv;
+	char		*manpath = NULL;
+	static struct man_node *mandirs = NULL;
+	int		bmp_flags = 0;
+	int		ret = 0;
+	char		*opts;
+	char		*mwstr;
+	int		catman = 0;
+
+	(void) setlocale(LC_ALL, "");
+	(void) strcpy(language, setlocale(LC_MESSAGES, (char *)NULL));
+	if (strcmp("C", language) != 0)
+		(void) strlcpy(localedir, language, MAXPATHLEN);
+
+#if !defined(TEXT_DOMAIN)
+#define	TEXT_DOMAIN "SYS_TEST"
+#endif
+	(void) textdomain(TEXT_DOMAIN);
+
+	if (strcmp(__progname, "apropos") == 0) {
+		apropos++;
+		opts = "M:ds:";
+	} else if (strcmp(__progname, "whatis") == 0) {
+		apropos++;
+		whatis++;
+		opts = "M:ds:";
+	} else if (strcmp(__progname, "catman") == 0) {
+		catman++;
+		makewhatis++;
+		opts = "M:w";
+	} else if (strcmp(__progname, "makewhatis") == 0) {
+		makewhatis++;
+		makewhatishere++;
+		manpath = ".";
+		opts = "";
+	} else {
+		opts = "M:adfklps:tw";
+	}
+
+	opterr = 0;
+	while ((c = getopt(argc, argv, opts)) != -1) {
+		switch (c) {
+		case 'M':	/* Respecify path for man pages */
+			manpath = optarg;
+			break;
+		case 'a':
+			all++;
+			break;
+		case 'd':
+			debug++;
+			break;
+		case 'f':
+			whatis++;
+			/*FALLTHROUGH*/
+		case 'k':
+			apropos++;
+			break;
+		case 'l':
+			list++;
+			/*FALLTHROUGH*/
+		case 'p':
+			printmp++;
+			break;
+		case 's':
+			mansec = optarg;
+			sargs++;
+			break;
+		case 't':
+			psoutput++;
+			break;
+		case 'w':
+			makewhatis++;
+			break;
+		case '?':
+		default:
+			if (apropos)
+				usage_whatapro();
+			else if (catman)
+				usage_catman();
+			else if (makewhatishere)
+				usage_makewhatis();
+			else
+				usage_man();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 0) {
+		if (apropos) {
+			(void) fprintf(stderr, gettext("%s what?\n"),
+			    __progname);
+			exit(1);
+		} else if (!printmp && !makewhatis) {
+			(void) fprintf(stderr,
+			    gettext("What manual page do you want?\n"));
+			exit(1);
+		}
+	}
+
+	init_bintoman();
+	if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) {
+		if ((manpath = getenv("PATH")) != NULL)
+			bmp_flags = BMP_ISPATH | BMP_APPEND_DEFMANDIR;
+		else
+			manpath = DEFMANDIR;
+	}
+	pathv = split(manpath, ':');
+	mandirs = build_manpath(pathv, bmp_flags);
+	freev(pathv);
+	fullpaths(&mandirs);
+
+	if (makewhatis) {
+		do_makewhatis(mandirs);
+		exit(0);
+	}
+
+	if (printmp) {
+		print_manpath(mandirs);
+		exit(0);
+	}
+
+	/* Collect environment information */
+	if (isatty(STDOUT_FILENO) && (mwstr = getenv("MANWIDTH")) != NULL &&
+	    *mwstr != '\0') {
+		if (strcasecmp(mwstr, "tty") == 0) {
+			struct winsize	ws;
+
+			if (ioctl(0, TIOCGWINSZ, &ws) != 0)
+				warn("TIOCGWINSZ");
+			else
+				manwidth = ws.ws_col;
+		} else {
+			manwidth = (int)strtol(mwstr, (char **)NULL, 10);
+			if (manwidth < 0)
+				manwidth = 0;
+		}
+	}
+	if (manwidth != 0) {
+		DPRINTF("-- Using non-standard page width: %d\n", manwidth);
+	}
+
+	if ((pager = getenv("PAGER")) == NULL || *pager == '\0')
+		pager = PAGER;
+	DPRINTF("-- Using pager: %s\n", pager);
+
+	for (i = 0; i < argc; i++) {
+		char		*cmd;
+		static struct man_node *mp;
+		char		*pv[2];
+
+		/*
+		 * If full path to command specified, customize
+		 * the manpath accordingly.
+		 */
+		if ((cmd = strrchr(argv[i], '/')) != NULL) {
+			*cmd = '\0';
+			if ((pv[0] = strdup(argv[i])) == NULL)
+				err(1, "strdup");
+			pv[1] = NULL;
+			*cmd = '/';
+			mp = build_manpath(pv,
+			    BMP_ISPATH | BMP_FALLBACK_DEFMANDIR);
+		} else {
+			mp = mandirs;
+		}
+
+		if (apropos)
+			whatapro(mp, argv[i]);
+		else
+			ret += manual(mp, argv[i]);
+
+		if (mp != NULL && mp != mandirs) {
+			free(pv[0]);
+			free_manp(mp);
+		}
+	}
+
+	return (ret == 0 ? 0 : 1);
+}
+
+/*
+ * This routine builds the manpage structure from MANPATH or PATH,
+ * depending on flags.  See BMP_* definitions above for valid
+ * flags.
+ */
+static struct man_node *
+build_manpath(char **pathv, int flags)
+{
+	struct man_node *manpage = NULL;
+	struct man_node *currp = NULL;
+	struct man_node *lastp = NULL;
+	char		**p;
+	char		**q;
+	char		*mand = NULL;
+	char		*mandir = DEFMANDIR;
+	int		s;
+	struct dupnode	*didup = NULL;
+	struct stat	sb;
+
+	s = sizeof (struct man_node);
+	for (p = pathv; *p != NULL; ) {
+		if (flags & BMP_ISPATH) {
+			if ((mand = path_to_manpath(*p)) == NULL)
+				goto next;
+			free(*p);
+			*p = mand;
+		}
+		q = split(*p, ',');
+		if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) {
+			freev(q);
+			goto next;
+		}
+
+		if (access(q[0], R_OK | X_OK) == 0) {
+			/*
+			 * Some element exists.  Do not append DEFMANDIR as a
+			 * fallback.
+			 */
+			flags &= ~BMP_FALLBACK_DEFMANDIR;
+
+			if ((currp = (struct man_node *)calloc(1, s)) == NULL)
+				err(1, "calloc");
+
+			currp->frompath = (flags & BMP_ISPATH);
+
+			if (manpage == NULL)
+				lastp = manpage = currp;
+
+			getpath(currp, p);
+			getsect(currp, p);
+
+			/*
+			 * If there are no new elements in this path,
+			 * do not add it to the manpage list.
+			 */
+			if (dupcheck(currp, &didup) != 0) {
+				freev(currp->secv);
+				free(currp);
+			} else {
+				currp->next = NULL;
+				if (currp != manpage)
+					lastp->next = currp;
+				lastp = currp;
+			}
+		}
+		freev(q);
+next:
+		/*
+		 * Special handling of appending DEFMANDIR. After all pathv
+		 * elements have been processed, append DEFMANDIR if needed.
+		 */
+		if (p == &mandir)
+			break;
+		p++;
+		if (*p != NULL)
+			continue;
+		if (flags & (BMP_APPEND_DEFMANDIR | BMP_FALLBACK_DEFMANDIR)) {
+			p = &mandir;
+			flags &= ~BMP_ISPATH;
+		}
+	}
+
+	free_dupnode(didup);
+
+	return (manpage);
+}
+
+/*
+ * Store the mandir path into the manp structure.
+ */
+static void
+getpath(struct man_node *manp, char **pv)
+{
+	char	*s = *pv;
+	int	i = 0;
+
+	while (*s != '\0' && *s != ',')
+		i++, s++;
+
+	if ((manp->path = (char *)malloc(i + 1)) == NULL)
+		err(1, "malloc");
+	(void) strlcpy(manp->path, *pv, i + 1);
+}
+
+/*
+ * Store the mandir's corresponding sections (submandir
+ * directories) into the manp structure.
+ */
+static void
+getsect(struct man_node *manp, char **pv)
+{
+	char	*sections;
+	char	**sectp;
+
+	/* Just store all sections when doing makewhatis or apropos/whatis */
+	if (makewhatis || apropos) {
+		manp->defsrch = 1;
+		DPRINTF("-- Adding %s\n", manp->path);
+		manp->secv = NULL;
+		get_all_sect(manp);
+	} else if (sargs) {
+		manp->secv = split(mansec, ',');
+		for (sectp = manp->secv; *sectp; sectp++)
+			lower(*sectp);
+	} else if ((sections = strchr(*pv, ',')) != NULL) {
+		DPRINTF("-- Adding %s: MANSECTS=%s\n", manp->path, sections);
+		manp->secv = split(++sections, ',');
+		for (sectp = manp->secv; *sectp; sectp++)
+			lower(*sectp);
+		if (*manp->secv == NULL)
+			get_all_sect(manp);
+	} else if ((sections = check_config(*pv)) != NULL) {
+		manp->defsrch = 1;
+		DPRINTF("-- Adding %s: from %s, MANSECTS=%s\n", manp->path,
+		    CONFIG, sections);
+		manp->secv = split(sections, ',');
+		for (sectp = manp->secv; *sectp; sectp++)
+			lower(*sectp);
+		if (*manp->secv == NULL)
+			get_all_sect(manp);
+	} else {
+		manp->defsrch = 1;
+		DPRINTF("-- Adding %s: default sort order\n", manp->path);
+		manp->secv = NULL;
+		get_all_sect(manp);
+	}
+}
+
+/*
+ * Get suffices of all sub-mandir directories in a mandir.
+ */
+static void
+get_all_sect(struct man_node *manp)
+{
+	DIR	*dp;
+	char	**dirv;
+	char	**dv;
+	char	**p;
+	char	*prev = NULL;
+	char 	*tmp = NULL;
+	int	maxentries = MAXTOKENS;
+	int	entries = 0;
+
+	if ((dp = opendir(manp->path)) == 0)
+		return;
+
+	sortdir(dp, &dirv);
+
+	(void) closedir(dp);
+
+	if (manp->secv == NULL) {
+		if ((manp->secv = malloc(maxentries * sizeof (char *))) == NULL)
+			err(1, "malloc");
+	}
+
+	for (dv = dirv, p = manp->secv; *dv; dv++) {
+		if (strcmp(*dv, CONFIG) == 0) {
+			free(*dv);
+			continue;
+		}
+
+		free(tmp);
+		if ((tmp = strdup(*dv + 3)) == NULL)
+			err(1, "strdup");
+
+		if (prev != NULL && strcmp(prev, tmp) == 0) {
+			free(*dv);
+			continue;
+		}
+
+		free(prev);
+		if ((prev = strdup(*dv + 3)) == NULL)
+			err(1, "strdup");
+
+		if ((*p = strdup(*dv + 3)) == NULL)
+			err(1, "strdup");
+
+		p++; entries++;
+
+		if (entries == maxentries) {
+			maxentries += MAXTOKENS;
+			if ((manp->secv = realloc(manp->secv,
+			    sizeof (char *) * maxentries)) == NULL)
+				err(1, "realloc");
+			p = manp->secv + entries;
+		}
+		free(*dv);
+	}
+	free(tmp);
+	free(prev);
+	*p = NULL;
+	free(dirv);
+}
+
+/*
+ * Build whatis databases.
+ */
+static void
+do_makewhatis(struct man_node *manp)
+{
+	struct man_node *p;
+	char		*ldir;
+
+	for (p = manp; p != NULL; p = p->next) {
+		ldir = addlocale(p->path);
+		if (*localedir != '\0' && getdirs(ldir, NULL, 0) > 0)
+			mwpath(ldir);
+		free(ldir);
+		mwpath(p->path);
+	}
+}
+
+/*
+ * Count mandirs under the given manpath
+ */
+static int
+getdirs(char *path, char ***dirv, int flag)
+{
+	DIR		*dp;
+	struct dirent	*d;
+	int		n = 0;
+	int		maxentries = MAXDIRS;
+	char		**dv = NULL;
+
+	if ((dp = opendir(path)) == NULL)
+		return (0);
+
+	if (flag) {
+		if ((*dirv = malloc(sizeof (char *) *
+		    maxentries)) == NULL)
+			err(1, "malloc");
+		dv = *dirv;
+	}
+	while ((d = readdir(dp))) {
+		if (strncmp(d->d_name, "man", 3) != 0)
+			continue;
+		n++;
+
+		if (flag) {
+			if ((*dv = strdup(d->d_name + 3)) == NULL)
+				err(1, "strdup");
+			dv++;
+			if ((dv - *dirv) == maxentries) {
+				int	entries = maxentries;
+
+				maxentries += MAXTOKENS;
+				if ((*dirv = realloc(*dirv,
+				    sizeof (char *) * maxentries)) == NULL)
+					err(1, "realloc");
+				dv = *dirv + entries;
+			}
+		}
+	}
+
+	(void) closedir(dp);
+	return (n);
+}
+
+
+/*
+ * Find matching whatis or apropos entries.
+ */
+static void
+whatapro(struct man_node *manp, char *word)
+{
+	char		whatpath[MAXPATHLEN];
+	struct man_node *b;
+	char		*ldir;
+
+	for (b = manp; b != NULL; b = b->next) {
+		if (*localedir != '\0') {
+			ldir = addlocale(b->path);
+			if (getdirs(ldir, NULL, 0) != 0) {
+				(void) snprintf(whatpath, sizeof (whatpath),
+				    "%s/%s", ldir, WHATIS);
+				search_whatis(whatpath, word);
+			}
+			free(ldir);
+		}
+		(void) snprintf(whatpath, sizeof (whatpath), "%s/%s", b->path,
+		    WHATIS);
+		search_whatis(whatpath, word);
+	}
+}
+
+static void
+search_whatis(char *whatpath, char *word)
+{
+	FILE		*fp;
+	char		*line = NULL;
+	size_t		linecap = 0;
+	char		*pkwd;
+	regex_t		preg;
+	char		**ss = NULL;
+	char		s[MAXNAMELEN];
+	int		i;
+
+	if ((fp = fopen(whatpath, "r")) == NULL) {
+		perror(whatpath);
+		return;
+	}
+
+	DPRINTF("-- Found %s: %s\n", WHATIS, whatpath);
+
+	/* Build keyword regex */
+	if (asprintf(&pkwd, "%s%s%s", (whatis) ? "\\<" : "",
+	    word, (whatis) ? "\\>" : "") == -1)
+		err(1, "asprintf");
+
+	if (regcomp(&preg, pkwd, REG_BASIC | REG_ICASE | REG_NOSUB) != 0)
+		err(1, "regcomp");
+
+	if (sargs)
+		ss = split(mansec, ',');
+
+	while (getline(&line, &linecap, fp) > 0) {
+		if (regexec(&preg, line, 0, NULL, 0) == 0) {
+			if (sargs) {
+				/* Section-restricted search */
+				for (i = 0; ss[i] != NULL; i++) {
+					(void) snprintf(s, sizeof (s), "(%s)",
+					    ss[i]);
+					if (strstr(line, s) != NULL) {
+						(void) printf("%s", line);
+						break;
+					}
+				}
+			} else {
+				(void) printf("%s", line);
+			}
+		}
+	}
+
+	if (ss != NULL)
+		freev(ss);
+	free(pkwd);
+	(void) fclose(fp);
+}
+
+
+/*
+ * Split a string by specified separator.
+ */
+static char **
+split(char *s1, char sep)
+{
+	char	**tokv, **vp;
+	char	*mp = s1, *tp;
+	int	maxentries = MAXTOKENS;
+	int	entries = 0;
+
+	if ((tokv = vp = malloc(maxentries * sizeof (char *))) == NULL)
+		err(1, "malloc");
+
+	for (; mp && *mp; mp = tp) {
+		tp = strchr(mp, sep);
+		if (mp == tp) {
+			tp++;
+			continue;
+		}
+		if (tp) {
+			size_t	len;
+
+			len = tp - mp;
+			if ((*vp = (char *)malloc(sizeof (char) *
+			    len + 1)) == NULL)
+				err(1, "malloc");
+			(void) strncpy(*vp, mp, len);
+			*(*vp + len) = '\0';
+			tp++;
+			vp++;
+		} else {
+			if ((*vp = strdup(mp)) == NULL)
+				err(1, "strdup");
+			vp++;
+		}
+		entries++;
+		if (entries == maxentries) {
+			maxentries += MAXTOKENS;
+			if ((tokv = realloc(tokv,
+			    maxentries * sizeof (char *))) == NULL)
+				err(1, "realloc");
+			vp = tokv + entries;
+		}
+	}
+	*vp = 0;
+
+	return (tokv);
+}
+
+/*
+ * Free a vector allocated by split()
+ */
+static void
+freev(char **v)
+{
+	int i;
+	if (v != NULL) {
+		for (i = 0; v[i] != NULL; i++) {
+			free(v[i]);
+		}
+		free(v);
+	}
+}
+
+/*
+ * Convert paths to full paths if necessary
+ */
+static void
+fullpaths(struct man_node **manp_head)
+{
+	char		*cwd = NULL;
+	char		*p;
+	int		cwd_gotten = 0;
+	struct man_node *manp = *manp_head;
+	struct man_node *b;
+	struct man_node *prev = NULL;
+
+	for (b = manp; b != NULL; b = b->next) {
+		if (*(b->path) == '/') {
+			prev = b;
+			continue;
+		}
+
+		if (!cwd_gotten) {
+			cwd = getcwd(NULL, MAXPATHLEN);
+			cwd_gotten = 1;
+		}
+
+		if (cwd) {
+			/* Relative manpath with cwd: make absolute */
+			if (asprintf(&p, "%s/%s", cwd, b->path) == -1)
+				err(1, "asprintf");
+			free(b->path);
+			b->path = p;
+		} else {
+			/* Relative manpath but no cwd: omit path entry */
+			if (prev)
+				prev->next = b->next;
+			else
+				*manp_head = b->next;
+
+			free_manp(b);
+		}
+	}
+	free(cwd);
+}
+
+/*
+ * Free a man_node structure and its contents
+ */
+static void
+free_manp(struct man_node *manp)
+{
+	char	**p;
+
+	free(manp->path);
+	p = manp->secv;
+	while ((p != NULL) && (*p != NULL)) {
+		free(*p);
+		p++;
+	}
+	free(manp->secv);
+	free(manp);
+}
+
+
+/*
+ * Map (in place) to lower case.
+ */
+static void
+lower(char *s)
+{
+
+	if (s == 0)
+		return;
+	while (*s) {
+		if (isupper(*s))
+			*s = tolower(*s);
+		s++;
+	}
+}
+
+
+/*
+ * Compare function for qsort().
+ * Sort first by section, then by prefix.
+ */
+static int
+cmp(const void *arg1, const void *arg2)
+{
+	int	n;
+	char	**p1 = (char **)arg1;
+	char	**p2 = (char **)arg2;
+
+	/* By section */
+	if ((n = strcmp(*p1 + 3, *p2 + 3)) != 0)
+		return (n);
+
+	/* By prefix reversed */
+	return (strncmp(*p2, *p1, 3));
+}
+
+
+/*
+ * Find a manpage.
+ */
+static int
+manual(struct man_node *manp, char *name)
+{
+	struct man_node *p;
+	struct man_node *local;
+	int		ndirs = 0;
+	char		*ldir;
+	char		*ldirs[2];
+	char		*fullname = name;
+	char		*slash;
+
+	if ((slash = strrchr(name, '/')) != NULL)
+		name = slash + 1;
+
+	/* For each path in MANPATH */
+	found = 0;
+
+	for (p = manp; p != NULL; p = p->next) {
+		DPRINTF("-- Searching mandir: %s\n", p->path);
+
+		if (*localedir != '\0') {
+			ldir = addlocale(p->path);
+			ndirs = getdirs(ldir, NULL, 0);
+			if (ndirs != 0) {
+				ldirs[0] = ldir;
+				ldirs[1] = NULL;
+				local = build_manpath(ldirs, 0);
+				DPRINTF("-- Locale specific subdir: %s\n",
+				    ldir);
+				mandir(local->secv, ldir, name, 1);
+				free_manp(local);
+			}
+			free(ldir);
+		}
+
+		/*
+		 * Locale mandir not valid, man page in locale
+		 * mandir not found, or -a option present
+		 */
+		if (ndirs == 0 || !found || all)
+			mandir(p->secv, p->path, name, 0);
+
+		if (found && !all)
+			break;
+	}
+
+	if (!found) {
+		if (sargs) {
+			(void) fprintf(stderr, gettext(
+			    "No manual entry for %s in section(s) %s\n"),
+			    fullname, mansec);
+		} else {
+			(void) fprintf(stderr,
+			    gettext("No manual entry for %s\n"), fullname);
+		}
+
+	}
+
+	return (!found);
+}
+
+
+/*
+ * For a specified manual directory, read, store and sort section subdirs.
+ * For each section specified, find and search matching subdirs.
+ */
+static void
+mandir(char **secv, char *path, char *name, int lspec)
+{
+	DIR	*dp;
+	char	**dirv;
+	char	**dv, **pdv;
+	int	len, dslen;
+
+	if ((dp = opendir(path)) == NULL)
+		return;
+
+	if (lspec)
+		DPRINTF("-- Searching mandir: %s\n", path);
+
+	sortdir(dp, &dirv);
+
+	/* Search in the order specified by MANSECTS */
+	for (; *secv; secv++) {
+		len = strlen(*secv);
+		for (dv = dirv; *dv; dv++) {
+			dslen = strlen(*dv + 3);
+			if (dslen > len)
+				len = dslen;
+			if (**secv == '\\') {
+				if (strcmp(*secv + 1, *dv + 3) != 0)
+					continue;
+			} else if (strncasecmp(*secv, *dv + 3, len) != 0) {
+				if (!all &&
+				    (newsection = map_section(*secv, path))
+				    == NULL) {
+					continue;
+				}
+				if (newsection == NULL)
+					newsection = "";
+				if (strncmp(newsection, *dv + 3, len) != 0) {
+					continue;
+				}
+			}
+
+			if (searchdir(path, *dv, name) == 0)
+				continue;
+
+			if (!all) {
+				pdv = dirv;
+				while (*pdv) {
+					free(*pdv);
+					pdv++;
+				}
+				(void) closedir(dp);
+				free(dirv);
+				return;
+			}
+
+			if (all && **dv == 'm' && *(dv + 1) &&
+			    strcmp(*(dv + 1) + 3, *dv + 3) == 0)
+					dv++;
+		}
+	}
+	pdv = dirv;
+	while (*pdv != NULL) {
+		free(*pdv);
+		pdv++;
+	}
+	free(dirv);
+	(void) closedir(dp);
+}
+
+/*
+ * Sort directories.
+ */
+static void
+sortdir(DIR *dp, char ***dirv)
+{
+	struct dirent	*d;
+	char		**dv;
+	int		maxentries = MAXDIRS;
+	int		entries = 0;
+
+	if ((dv = *dirv = malloc(sizeof (char *) *
+	    maxentries)) == NULL)
+		err(1, "malloc");
+	dv = *dirv;
+
+	while ((d = readdir(dp))) {
+		if (strcmp(d->d_name, ".") == 0 ||
+		    strcmp(d->d_name, "..") == 0)
+			continue;
+
+		if (strncmp(d->d_name, "man", 3) == 0 ||
+		    strncmp(d->d_name, "cat", 3) == 0) {
+			if ((*dv = strdup(d->d_name)) == NULL)
+				err(1, "strdup");
+			dv++;
+			entries++;
+			if (entries == maxentries) {
+				maxentries += MAXDIRS;
+				if ((*dirv = realloc(*dirv,
+				    sizeof (char *) * maxentries)) == NULL)
+					err(1, "realloc");
+				dv = *dirv + entries;
+			}
+		}
+	}
+	*dv = 0;
+
+	qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp);
+
+}
+
+
+/*
+ * Search a section subdir for a given manpage.
+ */
+static int
+searchdir(char *path, char *dir, char *name)
+{
+	DIR		*sdp;
+	struct dirent	*sd;
+	char		sectpath[MAXPATHLEN];
+	char		file[MAXNAMLEN];
+	char		dname[MAXPATHLEN];
+	char		*last;
+	int		nlen;
+
+	(void) snprintf(sectpath, sizeof (sectpath), "%s/%s", path, dir);
+	(void) snprintf(file, sizeof (file), "%s.", name);
+
+	if ((sdp = opendir(sectpath)) == NULL)
+		return (0);
+
+	while ((sd = readdir(sdp))) {
+		char	*pname;
+
+		if ((pname = strdup(sd->d_name)) == NULL)
+			err(1, "strdup");
+		if ((last = strrchr(pname, '.')) != NULL &&
+		    (strcmp(last, ".gz") == 0 || strcmp(last, ".bz2") == 0))
+			*last = '\0';
+		last = strrchr(pname, '.');
+		nlen = last - pname;
+		(void) snprintf(dname, sizeof (dname), "%.*s.", nlen, pname);
+		if (strcmp(dname, file) == 0 ||
+		    strcmp(pname, name) == 0) {
+			(void) format(path, dir, name, sd->d_name);
+			(void) closedir(sdp);
+			free(pname);
+			return (1);
+		}
+		free(pname);
+	}
+	(void) closedir(sdp);
+
+	return (0);
+}
+
+/*
+ * Check the hash table of old directory names to see if there is a
+ * new directory name.
+ */
+static char *
+map_section(char *section, char *path)
+{
+	int	i;
+	int	len;
+	char	fullpath[MAXPATHLEN];
+
+	if (list)  /* -l option fall through */
+		return (NULL);
+
+	for (i = 0; i <= ((sizeof (map)/sizeof (map[0]) - 1)); i++) {
+		if (strlen(section) > strlen(map[i].new_name)) {
+			len = strlen(section);
+		} else {
+			len = strlen(map[i].new_name);
+		}
+		if (strncmp(section, map[i].old_name, len) == 0) {
+			(void) snprintf(fullpath, sizeof (fullpath),
+			    "%s/sman%s", path, map[i].new_name);
+			if (!access(fullpath, R_OK | X_OK)) {
+				return (map[i].new_name);
+			} else {
+				return (NULL);
+			}
+		}
+	}
+
+	return (NULL);
+}
+
+/*
+ * Format the manpage.
+ */
+static int
+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++;
+
+	if (list) {
+		(void) printf(gettext("%s(%s)\t-M %s\n"), name, dir + 3, path);
+		return (-1);
+	}
+
+	(void) snprintf(manpname, sizeof (manpname), "%s/man%s/%s", path,
+	    dir + 3, pg);
+	(void) snprintf(catpname, sizeof (catpname), "%s/cat%s/%s", path,
+	    dir + 3, pg);
+
+	/* Can't do PS output if manpage doesn't exist */
+	if (stat(manpname, &sbman) != 0 && psoutput)
+		return (-1);
+
+	/*
+	 * If both manpage and catpage do not exist, manpname is
+	 * broken symlink, most likely.
+	 */
+	if (stat(catpname, &sbcat) != 0 && stat(manpname, &sbman) != 0)
+		err(1, "%s", manpname);
+
+	/* Setup cattool */
+	if (fnmatch("*.gz", manpname, 0) == 0)
+		cattool = "gzcat";
+	else if (fnmatch("*.bz2", manpname, 0) == 0)
+		cattool = "bzcat";
+	else
+		cattool = "gzcat -f";
+
+	/* 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" : "");
+		DPRINTF("-- Using manpage: %s\n", manpname);
+		goto cmd;
+	}
+
+	/*
+	 * Output catpage if:
+	 * - manpage doesn't exist
+	 * - output width is standard and catpage is recent enough
+	 */
+	if (stat(manpname, &sbman) != 0 || (manwidth == 0 &&
+	    stat(catpname, &sbcat) == 0 && sbcat.st_mtime >= sbman.st_mtime)) {
+		DPRINTF("-- Using catpage: %s\n", catpname);
+		(void) snprintf(cmdbuf, BUFSIZ, "%s %s", pager, catpname);
+		goto cmd;
+	}
+
+	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);
+
+cmd:
+	DPRINTF("-- Command: %s\n", cmdbuf);
+
+	if (!debug)
+		return (system(cmdbuf) == 0);
+	else
+		return (0);
+}
+
+/*
+ * Add <localedir> to the path.
+ */
+static char *
+addlocale(char *path)
+{
+	char	*tmp;
+
+	if (asprintf(&tmp, "%s/%s", path, localedir) == -1)
+		err(1, "asprintf");
+
+	return (tmp);
+}
+
+/*
+ * Get the order of sections from man.cf.
+ */
+static char *
+check_config(char *path)
+{
+	FILE		*fp;
+	char		*rc = NULL;
+	char		*sect;
+	char		fname[MAXPATHLEN];
+	char		*line = NULL;
+	size_t		linecap = 0;
+
+	(void) snprintf(fname, MAXPATHLEN, "%s/%s", path, CONFIG);
+
+	if ((fp = fopen(fname, "r")) == NULL)
+		return (NULL);
+
+	while (getline(&line, &linecap, fp) > 0) {
+		if ((rc = strstr(line, "MANSECTS")) != NULL)
+			break;
+	}
+
+	(void) fclose(fp);
+
+	if (rc == NULL || (sect = strchr(line, '=')) == NULL)
+		return (NULL);
+	else
+		return (++sect);
+}
+
+
+/*
+ * Initialize the bintoman array with appropriate device and inode info.
+ */
+static void
+init_bintoman(void)
+{
+	int i;
+	struct stat sb;
+
+	for (i = 0; bintoman[i].bindir != NULL; i++) {
+		if (stat(bintoman[i].bindir, &sb) == 0) {
+			bintoman[i].dev = sb.st_dev;
+			bintoman[i].ino = sb.st_ino;
+		} else {
+			bintoman[i].dev = NODEV;
+		}
+	}
+}
+
+/*
+ * If a duplicate is found, return 1.
+ * If a duplicate is not found, add it to the dupnode list and return 0.
+ */
+static int
+dupcheck(struct man_node *mnp, struct dupnode **dnp)
+{
+	struct dupnode	*curdnp;
+	struct secnode	*cursnp;
+	struct stat 	sb;
+	int 		i;
+	int		rv = 1;
+	int		dupfound;
+
+	/* If the path doesn't exist, treat it as a duplicate */
+	if (stat(mnp->path, &sb) != 0)
+		return (1);
+
+	/* If no sections were found in the man dir, treat it as duplicate */
+	if (mnp->secv == NULL)
+		return (1);
+
+	/*
+	 * Find the dupnode structure for the previous time this directory
+	 * was looked at.  Device and inode numbers are compared so that
+	 * directories that are reached via different paths (e.g. /usr/man and
+	 * /usr/share/man) are treated as equivalent.
+	 */
+	for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) {
+		if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino)
+			break;
+	}
+
+	/*
+	 * First time this directory has been seen. Add a new node to the
+	 * head of the list. Since all entries are guaranteed to be unique
+	 * copy all sections to new node.
+	 */
+	if (curdnp == NULL) {
+		if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL)
+			err(1, "calloc");
+		for (i = 0; mnp->secv[i] != NULL; i++) {
+			if ((cursnp = calloc(1, sizeof (struct secnode)))
+			    == NULL)
+				err(1, "calloc");
+			cursnp->next = curdnp->secl;
+			curdnp->secl = cursnp;
+			if ((cursnp->secp = strdup(mnp->secv[i])) == NULL)
+				err(1, "strdup");
+		}
+		curdnp->dev = sb.st_dev;
+		curdnp->ino = sb.st_ino;
+		curdnp->next = *dnp;
+		*dnp = curdnp;
+		return (0);
+	}
+
+	/*
+	 * Traverse the section vector in the man_node and the section list
+	 * in dupnode cache to eliminate all duplicates from man_node.
+	 */
+	for (i = 0; mnp->secv[i] != NULL; i++) {
+		dupfound = 0;
+		for (cursnp = curdnp->secl; cursnp != NULL;
+		    cursnp = cursnp->next) {
+			if (strcmp(mnp->secv[i], cursnp->secp) == 0) {
+				dupfound = 1;
+				break;
+			}
+		}
+		if (dupfound) {
+			mnp->secv[i][0] = '\0';
+			continue;
+		}
+
+
+		/*
+		 * Update curdnp and set return value to indicate that this
+		 * was not all duplicates.
+		 */
+		if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL)
+			err(1, "calloc");
+		cursnp->next = curdnp->secl;
+		curdnp->secl = cursnp;
+		if ((cursnp->secp = strdup(mnp->secv[i])) == NULL)
+			err(1, "strdup");
+		rv = 0;
+	}
+
+	return (rv);
+}
+
+/*
+ * Given a bindir, return corresponding mandir.
+ */
+static char *
+path_to_manpath(char *bindir)
+{
+	char		*mand, *p;
+	int		i;
+	struct stat	sb;
+
+	/* First look for known translations for specific bin paths */
+	if (stat(bindir, &sb) != 0) {
+		return (NULL);
+	}
+	for (i = 0; bintoman[i].bindir != NULL; i++) {
+		if (sb.st_dev == bintoman[i].dev &&
+		    sb.st_ino == bintoman[i].ino) {
+			if ((mand = strdup(bintoman[i].mandir)) == NULL)
+				err(1, "strdup");
+			if ((p = strchr(mand, ',')) != NULL)
+				*p = '\0';
+			if (stat(mand, &sb) != 0) {
+				free(mand);
+				return (NULL);
+			}
+			if (p != NULL)
+				*p = ',';
+			return (mand);
+		}
+	}
+
+	/*
+	 * No specific translation found.  Try `dirname $bindir`/share/man
+	 * and `dirname $bindir`/man
+	 */
+	if ((mand = malloc(MAXPATHLEN)) == NULL)
+		err(1, "malloc");
+	if (strlcpy(mand, bindir, MAXPATHLEN) >= MAXPATHLEN) {
+		free(mand);
+		return (NULL);
+	}
+
+	/*
+	 * Advance to end of buffer, strip trailing /'s then remove last
+	 * directory component.
+	 */
+	for (p = mand; *p != '\0'; p++)
+		;
+	for (; p > mand && *p == '/'; p--)
+		;
+	for (; p > mand && *p != '/'; p--)
+		;
+	if (p == mand && *p == '.') {
+		if (realpath("..", mand) == NULL) {
+			free(mand);
+			return (NULL);
+		}
+		for (; *p != '\0'; p++)
+			;
+	} else {
+		*p = '\0';
+	}
+
+	if (strlcat(mand, "/share/man", MAXPATHLEN) >= MAXPATHLEN) {
+		free(mand);
+		return (NULL);
+	}
+
+	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
+		return (mand);
+	}
+
+	/*
+	 * Strip the /share/man off and try /man
+	 */
+	*p = '\0';
+	if (strlcat(mand, "/man", MAXPATHLEN) >= MAXPATHLEN) {
+		free(mand);
+		return (NULL);
+	}
+	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
+		return (mand);
+	}
+
+	/*
+	 * No man or share/man directory found
+	 */
+	free(mand);
+	return (NULL);
+}
+
+/*
+ * Free a linked list of dupnode structs.
+ */
+void
+free_dupnode(struct dupnode *dnp) {
+	struct dupnode *dnp2;
+	struct secnode *snp;
+
+	while (dnp != NULL) {
+		dnp2 = dnp;
+		dnp = dnp->next;
+		while (dnp2->secl != NULL) {
+			snp = dnp2->secl;
+			dnp2->secl = dnp2->secl->next;
+			free(snp->secp);
+			free(snp);
+		}
+		free(dnp2);
+	}
+}
+
+/*
+ * Print manp linked list to stdout.
+ */
+void
+print_manpath(struct man_node *manp)
+{
+	char	colon[2] = "\0\0";
+	char	**secp;
+
+	for (; manp != NULL; manp = manp->next) {
+		(void) printf("%s%s", colon, manp->path);
+		colon[0] = ':';
+
+		/*
+		 * If man.cf or a directory scan was used to create section
+		 * list, do not print section list again.  If the output of
+		 * man -p is used to set MANPATH, subsequent runs of man
+		 * will re-read man.cf and/or scan man directories as
+		 * required.
+		 */
+		if (manp->defsrch != 0)
+			continue;
+
+		for (secp = manp->secv; *secp != NULL; secp++) {
+			/*
+			 * Section deduplication may have eliminated some
+			 * sections from the vector. Avoid displaying this
+			 * detail which would appear as ",," in output
+			 */
+			if ((*secp)[0] != '\0')
+				(void) printf(",%s", *secp);
+		}
+	}
+	(void) printf("\n");
+}
+
+static void
+usage_man(void)
+{
+
+	(void) fprintf(stderr, gettext(
+"usage: man [-alptw] [-M path] [-s section] name ...\n"
+"       man [-M path] [-s section] -k keyword -- emulate apropos\n"
+"       man [-M path] [-s section] -f keyword -- emulate whatis\n"));
+
+	exit(1);
+}
+
+static void
+usage_whatapro(void)
+{
+
+	(void) fprintf(stderr, gettext(
+"usage: %s [-M path] [-s section] keyword ...\n"),
+	    whatis ? "whatis" : "apropos");
+
+	exit(1);
+}
+
+static void
+usage_catman(void)
+{
+	(void) fprintf(stderr, gettext(
+"usage: catman [-M path] [-w]\n"));
+
+	exit(1);
+}
+
+static void
+usage_makewhatis(void)
+{
+	(void) fprintf(stderr, gettext("usage: makewhatis\n"));
+
+	exit(1);
+}
--- /dev/null	Fri Jul 18 08:34:31 2014
+++ new/usr/src/cmd/man/man.h	Fri Jul 18 08:34:31 2014
@@ -0,0 +1,40 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * Common definitions
+ */
+
+#ifndef _MAN_H_
+#define	_MAN_H_
+
+#define	CONFIG		"man.cf"
+#define	DEFMANDIR	"/usr/share/man"
+#define	INDENT		24
+#define	PAGER		"less -ins"
+#define	WHATIS		"whatis"
+#define	PRECONV		"/usr/lib/mandoc_preconv"
+
+#define	LINE_ALLOC	4096
+#define	MAXDIRS		128
+#define	MAXTOKENS	64
+
+#define	DPRINTF		if (debug) \
+				(void) printf
+
+void	mwpath(char *path);
+
+#endif	/* _MAN_H_ */
--- old/usr/src/cmd/man/src/Makefile	Fri Jul 18 08:34:31 2014
+++ /dev/null	Fri Jul 18 08:34:31 2014
@@ -1,88 +0,0 @@
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
-#
-# Makefile for cmd/man/src
-#
-
-include		../../Makefile.cmd
-
-BINPROG =	man
-LIBPROG =	getNAME
-LIBSHELL =	makewhatis
-LNKPROG =	catman apropos whatis
-
-PROG =		$(BINPROG) $(LIBPROG)
-
-SRCS =		man.c getNAME.c
-
-SHSRCS =	makewhatis.sh
-
-OBJS =		$(SRCS:%.c=%.o)
-
-
-CLOBBERFILES +=	$(LNKPROG) $(LIBSHELL)
-
-ROOTPROG =	$(BINPROG:%=$(ROOTBIN)/%)
-ROOTLIBPROG =	$(LIBPROG:%=$(ROOTLIB)/%)
-ROOTLIBSHELL =	$(LIBSHELL:%=$(ROOTLIB)/%)
-ROOTLNKPROG =	$(LNKPROG:%=$(ROOTBIN)/%)
-
-CERRWARN += -_gcc=-Wno-implicit-function-declaration
-CERRWARN += -_gcc=-Wno-uninitialized
-CERRWARN += -_gcc=-Wno-unused-variable
-
-# for messaging catalog files
-POFILE= src.po
-POFILES= $(SRCS:%.c=%.po)
-
-.KEEP_STATE :
-
-all :		$(PROG) $(LIBSHELL) $(LNKPROG)
-
-makewhatis :	$$@.sh
-		cat $@.sh > $@
-
-install :	all $(ROOTPROG) $(ROOTLIBPROG)	\
-		$(ROOTLIBSHELL) $(ROOTLNKPROG)
-
-$(ROOTLNKPROG) :	$(ROOTBIN)/man
-		-$(RM) $@; $(LN) $(ROOTBIN)/man $@
-
-$(LNKPROG) :	man
-		-$(RM) $@; $(LN) man $@
-
-catalog:        $(POFILE)
-
-$(POFILE):      $(POFILES)
-	$(RM)	$@
-	cat     $(POFILES)      > $@
-
-
-clean :
-
-strip :
-		$(STRIP) $(PROG)
-
-lint : 		lint_SRCS
-
-include		../../Makefile.targ
--- old/usr/src/cmd/man/src/THIRDPARTYLICENSE	Fri Jul 18 08:34:31 2014
+++ /dev/null	Fri Jul 18 08:34:31 2014
@@ -1,32 +0,0 @@
-Copyright (c) 1980 Regents of the University of California.  
-All rights reserved.
-
-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. All advertising materials mentioning features or use of this
-       software must display the following acknowledgement:
-           This product includes software developed by the University
-           of California, Berkeley and its contributors.
-    4. Neither the name of the University 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 REGENTS 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 COPYRIGHT OWNER 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.
--- old/usr/src/cmd/man/src/getNAME.c	Fri Jul 18 08:34:31 2014
+++ /dev/null	Fri Jul 18 08:34:31 2014
@@ -1,789 +0,0 @@
-/*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
- */
-
-/*	Copyright 1984, 1986, 1987, 1988, 1989 AT&T */
-/*	All Rights Reserved   */
-
-/*
- * Copyright 1980 Regents of the University of California.
- * All rights reserved.  The Berkeley software License Agreement
- * specifies the terms and conditions for redistribution.
- */
-
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
-/*
- * Get name sections from manual pages.
- *	-t	for building toc
- *	-i	for building intro entries
- *	other	apropos database
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-#include <unistd.h>
-#include <limits.h>
-#include <locale.h>
-#include <wchar.h>
-#include <errno.h>
-#include <sys/param.h>
-
-#define	PLEN	3	/* prefix length "man" */
-
-static char path[MAXPATHLEN+1];
-static int tocrc;
-static int intro;
-static char *progname;
-
-static void trimln(char *);
-static void roff_trim(char *cp);
-static void doname(char *);
-static void section(char *, char *);
-static void split(char *, char *);
-static void dorefname(char *);
-static void troffpage(char *);
-static void sgmlpage(char *);
-
-/*
- * Test to see if this is an SGML manpage or a regular manpage
- * Unless the first line begins with <!DOCTYPE, we assume it isn't.
- */
-static int
-issgml(FILE *fp)
-{
-	static const char magic[] = "<!DOCTYPE";
-	char buf[sizeof (magic)];
-	size_t n = sizeof (magic) - 1;
-
-	if (read(fileno(fp), buf, n) != n ||
-	    lseek(fileno(fp), 0, SEEK_SET) != 0)
-		return (0);
-	return (strncmp(magic, buf, n) == 0);
-}
-
-int
-main(int argc, char *argv[])
-{
-	int c;
-
-	(void) setlocale(LC_ALL, "");
-
-	progname = argv[0];
-
-	while ((c = getopt(argc, argv, "it")) != EOF)
-		switch (c) {
-		case 't':
-			tocrc++;
-			break;
-		case 'i':
-			intro++;
-			break;
-		case '?':
-		default:
-			(void) fprintf(stderr,
-			    "usage: %s [-i][-t] files..\n", progname);
-			exit(1);
-		}
-
-	if (getcwd(path, sizeof (path)) == NULL) {
-		(void) fprintf(stderr, "%s: getcwd: %s\n", progname, path);
-		exit(1);
-	}
-
-	for (; optind < argc; optind++) {
-		char *name = argv[optind];
-
-		if (freopen(name, "r", stdin) == 0) {
-			(void) fprintf(stderr,
-			    "%s: %s: %s\n", progname, name, strerror(errno));
-			continue;
-		}
-
-		/*
-		 * Most of the info we care about is in the first kbyte
-		 */
-		(void) setvbuf(stdin, NULL, _IOFBF, 1024);
-
-		if (issgml(stdin))
-			sgmlpage(name);
-		else
-			troffpage(name);
-	}
-
-	return (0);
-}
-
-/*
- * Parse a troff-format manpage
- */
-static void
-troffpage(char *name)
-{
-	char headbuf[BUFSIZ];
-	char linbuf[BUFSIZ];
-	char *strptr;
-	int i = 0;
-
-	for (;;) {
-		if (fgets(headbuf, sizeof (headbuf), stdin) == NULL)
-			return;
-		if (headbuf[0] != '.')
-			continue;
-		if (headbuf[1] == 'T' && headbuf[2] == 'H')
-			break;
-		if (headbuf[1] == 't' && headbuf[2] == 'h')
-			break;
-	}
-	for (;;) {
-		if (fgets(linbuf, sizeof (linbuf), stdin) == NULL)
-			return;
-		if (linbuf[0] != '.')
-			continue;
-		if (linbuf[1] == 'S' && linbuf[2] == 'H')
-			break;
-		if (linbuf[1] == 's' && linbuf[2] == 'h')
-			break;
-	}
-	trimln(headbuf);
-	if (tocrc)
-		doname(name);
-	if (!intro)
-		section(name, headbuf);
-	for (;;) {
-		if (fgets(linbuf, sizeof (linbuf), stdin) == NULL)
-			break;
-		if (linbuf[0] == '.') {
-			if (linbuf[1] == 'S' && linbuf[2] == 'H')
-				break;
-			if (linbuf[1] == 's' && linbuf[2] == 'h')
-				break;
-			if (linbuf[1] == '\\' && linbuf[2] == '"')
-				continue;
-		}
-		trimln(linbuf);
-		roff_trim(linbuf);
-		if (intro) {
-			split(linbuf, name);
-			continue;
-		}
-		if (i != 0)
-			(void) printf(" ");
-		i++;
-		(void) printf("%s", linbuf);
-	}
-	(void) printf("\n");
-}
-
-
-/*
- * Substitute section defined in page with new section spec
- * of the form xx/yy where xx is the section suffix of the
- * directory and yy is the filename extension (unless xx
- * and yy are equal, in which case xx is the section).
- * Pages should be placed in their proper directory with the
- * proper name to simplify things.
- *
- * For example take the following names:
- *    man1/ar.1v	(1/1V)
- *    man1/find.1	(1)
- *    man1/loco		(1/)
- *
- */
-static void
-section(char *name, char *buf)
-{
-	char scratch[MAXPATHLEN+1];
-	char *p = buf;
-	char *dir, *fname;
-	char *dp, *np;
-	int i;
-	int plen = PLEN;
-
-	/*
-	 * split dirname and filename
-	 */
-	(void) strcpy(scratch, name);
-	if ((fname = strrchr(scratch, '/')) == NULL) {
-		fname = name;
-		dir = path;
-	} else {
-		dir = scratch;
-		*fname = 0;
-		fname++;
-	}
-	dp = strrchr(dir, '/');
-
-	if (*(dp+1) == 's')
-		plen = PLEN + 1;
-
-	if (dp != NULL) {
-		dp = dp+plen+1;
-	} else {
-		dp = dir+plen;
-	}
-	np = strrchr(fname, '.');
-	if (np != NULL) {
-		++np;
-	} else {
-		np = "";
-	}
-	for (i = 0; i < 2; i++) {
-		while (*p && *p != ' ' && *p != '\t')
-			p++;
-		if (!*p)
-			break;
-		while (*p && (*p == ' ' || *p == '\t'))
-			p++;
-		if (!*p)
-			break;
-	}
-	*p++ = 0;
-	(void) printf("%s", buf);
-	if (strcmp(np, dp) == 0)
-		(void) printf("%s", dp);
-	else
-		(void) printf("%s/%s", dp, np);
-	while (*p && *p != ' ' && *p != '\t')
-		p++;
-	(void) printf("%s\t", p);
-}
-
-static void
-trimln(char *cp)
-{
-	while (*cp)
-		cp++;
-	if (*--cp == '\n')
-		*cp = 0;
-}
-
-static void
-roff_trim(char *cp)
-{
-	if (*cp == '.') {
-		while ((*cp != ' ') && (*cp != '\0')) {
-			strcpy(cp, cp+1);
-		}
-		strcpy(cp, cp+1);
-	}
-	while (*cp) {
-		if (strncmp(cp, "\\f", 2) == 0) {
-			if ((*(cp+2) >= 48) && (*(cp+2) <= 57)) {
-				strcpy(cp, cp+3);
-			}
-			if (*(cp+2) == '(') {
-				strcpy(cp, cp+5);
-			}
-		}
-		cp++;
-	}
-}
-
-static void
-doname(char *name)
-{
-	char *dp = name, *ep;
-
-again:
-	while (*dp && *dp != '.')
-		(void) putchar(*dp++);
-	if (*dp)
-		for (ep = dp+1; *ep; ep++)
-			if (*ep == '.') {
-				(void) putchar(*dp++);
-				goto again;
-			}
-	(void) putchar('(');
-	if (*dp)
-		dp++;
-	while (*dp)
-		(void) putchar(*dp++);
-	(void) putchar(')');
-	(void) putchar(' ');
-}
-
-static void
-split(char *line, char *name)
-{
-	char *cp, *dp;
-	char *sp, *sep;
-
-	cp = strchr(line, '-');
-	if (cp == 0)
-		return;
-	sp = cp + 1;
-	for (--cp; *cp == ' ' || *cp == '\t' || *cp == '\\'; cp--)
-		;
-	*++cp = '\0';
-	while (*sp && (*sp == ' ' || *sp == '\t'))
-		sp++;
-	for (sep = "", dp = line; dp && *dp; dp = cp, sep = "\n") {
-		cp = strchr(dp, ',');
-		if (cp) {
-			char *tp;
-
-			for (tp = cp - 1; *tp == ' ' || *tp == '\t'; tp--)
-				;
-			*++tp = '\0';
-			for (++cp; *cp == ' ' || *cp == '\t'; cp++)
-				;
-		}
-		(void) printf("%s%s\t", sep, dp);
-		dorefname(name);
-		(void) printf("\t%s", sp);
-	}
-}
-
-static void
-dorefname(char *name)
-{
-	char *dp = name, *ep;
-
-again:
-	while (*dp && *dp != '.')
-		(void) putchar(*dp++);
-	if (*dp)
-		for (ep = dp+1; *ep; ep++)
-			if (*ep == '.') {
-				(void) putchar(*dp++);
-				goto again;
-			}
-	(void) putchar('.');
-	if (*dp)
-		dp++;
-	while (*dp)
-		(void) putchar(*dp++);
-}
-
-/*
- * The rest of the routines in the file form a simplistic parser
- * for SGML manpages.  We assume the input is syntactically correct
- * SGML, and that the fields occur in the input file in order.
- */
-
-/*
- * Some utilities for constructing arbitrary length wide character strings
- */
-
-typedef struct {
-	wchar_t *str;
-	size_t size;
-	long index;
-} string_t;
-
-#define	DEF_STR_SIZE	16
-#define	DEF_STR_GROWTH	16
-
-static void
-outofspace(char *where)
-{
-	(void) fprintf(stderr, "%s: '%s' - out of memory\n", progname, where);
-	exit(1);
-}
-
-static string_t *
-newstring(size_t initial)
-{
-	string_t *s = malloc(sizeof (*s));
-
-	if (s == NULL)
-		outofspace("new s");
-
-	initial *= sizeof (wchar_t);
-	if (initial < DEF_STR_SIZE)
-		initial = DEF_STR_SIZE;
-
-	s->str = malloc(initial);
-	if (s->str == NULL)
-		outofspace("new str");
-
-	s->size = initial;
-	s->index = 0;
-	*s->str = L'\0';
-	return (s);
-}
-
-static void
-delstring(string_t **s)
-{
-	free((*s)->str);
-	(*s)->str = NULL;
-	free(*s);
-	*s = NULL;
-}
-
-static wchar_t *
-getwstring(string_t *s)
-{
-	static const wchar_t wnull = L'\0';
-
-	if (s)
-		return (s->str);
-	return ((wchar_t *)&wnull);
-}
-
-static char *
-getcstring(string_t *s)
-{
-	size_t len = (wcslen(s->str) + 1) * MB_CUR_MAX;
-	char *cstr = malloc(len);
-	char *p = cstr;
-	wchar_t *wp = s->str;
-
-	if (p == NULL)
-		outofspace("getc");
-	while (*wp)
-		p += wctomb(p, *wp++);
-	*p = '\0';
-	return (cstr);
-}
-
-static void
-appendwstring(string_t *s, const wchar_t *str)
-{
-	size_t len = wcslen(str) + 1;
-
-	s->size += sizeof (wchar_t) * len;
-	s->str = realloc(s->str, s->size);
-	if (s->str == NULL)
-		outofspace("appendw");
-	(void) wcscat(s->str, str);
-	s->index = wcslen(s->str) + 1;
-}
-
-static void
-putwstring(string_t *s, wchar_t wc)
-{
-	if ((s->index + 1) * sizeof (wchar_t) >= s->size) {
-		s->size += DEF_STR_GROWTH;
-		s->str = realloc(s->str, s->size);
-		if (s->str == NULL)
-			outofspace("put");
-	}
-	s->str[s->index++] = wc;
-}
-
-/*
- * Find the closing > of an SGML comment block
- * (allowing for multibyte, embedded, comments)
- */
-static void
-eatcomments(void)
-{
-	int pending = 1;
-
-	while (pending)
-		switch (getwchar()) {
-		default:
-			break;
-		case L'<':
-			pending++;
-			break;
-		case L'>':
-			pending--;
-			break;
-		case WEOF:
-			return;
-		}
-}
-
-/*
- * Find the next token on stdin.
- * Handles nested comment strings, and removes any trailing newlines
- * from the stream after the closing '>'.
- */
-static int
-find_token(char *tokbuf, size_t tokbuflen)
-{
-	int c;
-	wint_t wc;
-	char *tokp;
-
-top:
-	while ((wc = getwchar()) != WEOF)
-		if (wc == L'<')
-			break;
-
-	if (wc == WEOF && errno == EILSEQ)
-		return (0);
-
-	switch (c = getchar()) {
-	case EOF:
-		return (0);
-	default:
-		(void) ungetc(c, stdin);
-		break;
-	case '!':
-		eatcomments();
-		goto top;
-	}
-
-	tokp = tokbuf;
-
-	while ((c = getchar()) != EOF) {
-		if (c == '>') {
-			while ((c = getchar()) != EOF)
-				if (c != '\n') {
-					(void) ungetc(c, stdin);
-					break;
-				}
-			*tokp = '\0';
-			return (1);
-		}
-		if (tokp - tokbuf < tokbuflen)
-			*tokp++ = (char)c;
-	}
-
-	return (0);
-}
-
-/*
- * This structure is filled out during the parsing of each page we encounter
- */
-typedef struct {
-	char *name;
-	string_t *title;
-	string_t *volnum;
-	string_t *date;
-	string_t *names;
-	string_t *purpose;
-} manpage_t;
-
-static void
-warning(manpage_t *m, const char *fmt, ...)
-{
-	va_list ap;
-	va_start(ap, fmt);
-	(void) fprintf(stderr, "%s: %s - ", progname, m->name);
-	(void) vfprintf(stderr, fmt, ap);
-	va_end(ap);
-}
-
-/*
- * Fetch a string from stdin, terminated by the endtoken.
- * These strings may be localized, so do this with wide characters.
- * Hack: skip over (completely ignore) all other tokens
- * Hack: map all &blort; constructs to spaces.
- */
-static string_t *
-filestring(manpage_t *m, size_t initial, char *endtoken)
-{
-	char tokbuf[BUFSIZ * MB_LEN_MAX];
-	string_t *s = newstring(initial);
-	wint_t wc;
-
-	while ((wc = getwchar()) != WEOF)
-		switch (wc) {
-		case L'\n':
-			if ((wc = getwchar()) != WEOF)
-				(void) ungetwc(wc, stdin);
-			if (wc != L'<')
-				putwstring(s, L' ');
-			break;
-		case L'<':
-			(void) ungetwc(wc, stdin);
-			if (!find_token(tokbuf, sizeof (tokbuf)) ||
-			    strcasecmp(endtoken, tokbuf) == 0)
-				goto done;
-			break;
-		case L'&':
-			while ((wc = getwchar()) != WEOF)
-				if (wc == L';')
-					break;
-			wc = L' ';
-			/* FALLTHROUGH */
-		default:
-			putwstring(s, wc);
-			break;
-		}
-
-	if (errno == EILSEQ)
-		warning(m, "%s while parsing %s\n", strerror(errno), endtoken);
-done:
-	putwstring(s, L'\0');
-	return (s);
-}
-
-/*
- * <refentrytitle> TITLE </refentrytitle>
- */
-static int
-refentrytitle(manpage_t *m)
-{
-	if (m->title != NULL)
-		warning(m, "repeated refentrytitle\n");
-	m->title = filestring(m, 8, "/refentrytitle");
-	return (1);
-}
-
-/*
- * <manvolnum> MANVOLNUM </manvolnum>
- */
-static int
-manvolnum(manpage_t *m)
-{
-	if (m->volnum != NULL)
-		warning(m, "repeated manvolnum\n");
-	m->volnum = filestring(m, 3, "/manvolnum");
-	return (1);
-}
-
-/*
- * <refmiscinfo class="date"> DATE </refmiscinfo>
- */
-static int
-refmiscinfo_date(manpage_t *m)
-{
-	if (m->date != NULL)
-		warning(m, "repeated date\n");
-	m->date = filestring(m, 11, "/refmiscinfo");
-	return (1);
-}
-
-/*
- * .. </refmeta>
- */
-static int
-print_refmeta(manpage_t *m)
-{
-	char headbuf[BUFSIZ];
-
-	(void) snprintf(headbuf, sizeof (headbuf), ".TH %ws %ws \"%ws\"",
-	    getwstring(m->title), getwstring(m->volnum), getwstring(m->date));
-
-	trimln(headbuf);
-	if (tocrc)
-		doname(m->name);
-	if (!intro)
-		section(m->name, headbuf);
-
-	if (m->title)
-		delstring(&m->title);
-	if (m->volnum)
-		delstring(&m->volnum);
-	if (m->date)
-		delstring(&m->date);
-
-	return (1);
-}
-
-static int
-appendname(manpage_t *m, char *term)
-{
-	string_t *r = filestring(m, 0, term);
-
-	if (m->names) {
-		appendwstring(m->names, L", ");
-		appendwstring(m->names, getwstring(r));
-		delstring(&r);
-	} else
-		m->names = r;
-	return (1);
-}
-
-/*
- * <refdescriptor> REFDESCRIPTOR </refdescriptor>
- */
-static int
-refdescriptor(manpage_t *m)
-{
-	return (appendname(m, "/refdescriptor"));
-}
-
-/*
- * <refname> REFNAME </refname>
- */
-static int
-refname(manpage_t *m)
-{
-	return (appendname(m, "/refname"));
-}
-
-/*
- * <refpurpose> PURPOSE </refpurpose>
- */
-static int
-refpurpose(manpage_t *m)
-{
-	if (m->purpose != NULL)
-		warning(m, "repeated refpurpose\n");
-	m->purpose = filestring(m, 0, "/refpurpose");
-	return (1);
-}
-
-/*
- * .. </refnamediv> - this is our chance to bail out.
- */
-static int
-terminate(manpage_t *m)
-{
-	if (m->names) {
-		appendwstring(m->names, L" \\- ");
-		appendwstring(m->names, getwstring(m->purpose));
-		if (intro) {
-			char *buf = getcstring(m->names);
-			split(buf, m->name);
-			free(buf);
-		} else
-			(void) printf("%ws", getwstring(m->names));
-	}
-
-	if (m->names)
-		delstring(&m->names);
-	if (m->purpose)
-		delstring(&m->purpose);
-
-	(void) printf("\n");
-	return (0);
-}
-
-
-/*
- * Basic control structure of the SGML "parser".
- * It's very simplistic - when named tags are encountered in the
- * input stream, control is transferred to the corresponding routine.
- * No checking is done for correct pairing of tags.  A few other hacks
- * are sneaked into the lexical routines above.
- * Output is generated after seeing the /refmeta and /refnamediv
- * closing tags.
- */
-static const struct {
-	char *name;
-	int (*action)(manpage_t *);
-} acts[] = {
-	{ "refentrytitle",		refentrytitle },
-	{ "manvolnum",			manvolnum },
-	{ "refmiscinfo class=\"date\"",	refmiscinfo_date },
-	{ "/refmeta",			print_refmeta },
-	{ "refdescriptor",		refdescriptor },
-	{ "refname",			refname },
-	{ "refpurpose",			refpurpose },
-	{ "/refnamediv",		terminate },
-	{ 0 }
-};
-
-static void
-sgmlpage(char *name)
-{
-	int rc = 1, a;
-	char tokbuf[BUFSIZ];
-	manpage_t manpage, *m = &manpage;
-
-	(void) memset(m, 0, sizeof (*m));
-	m->name = name;
-
-	do {
-		if (!find_token(tokbuf, sizeof (tokbuf)))
-			break;
-		for (a = 0; acts[a].name; a++) {
-			if (strcasecmp(acts[a].name, tokbuf) != 0)
-				continue;
-			rc = acts[a].action(m);
-			break;
-		}
-	} while (rc);
-}
--- old/usr/src/cmd/man/src/makewhatis.sh	Fri Jul 18 08:34:32 2014
+++ /dev/null	Fri Jul 18 08:34:31 2014
@@ -1,85 +0,0 @@
-#!/bin/sh -
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-#
-# Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
-#
-#	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
-#	  All Rights Reserved
-#
-
-# University Copyright- Copyright (c) 1982, 1986, 1988
-# The Regents of the University of California
-# All Rights Reserved
-#
-# University Acknowledgment- Portions of this document are derived from
-# software developed by the University of California, Berkeley, and its
-# contributors
-
-PATH=/usr/xpg4/bin:$PATH
-
-tmpdir=/tmp/whatis.$$
-trap "rm -rf $tmpdir; exit 1" 1 2 13 15
-
-mkdir -m 700 $tmpdir || {
-	echo "${0}: could not create temporary directory" 1&>2
-	exit 1
-}
-
-[ -d $1 ] || exit 1
-
-cd $1
-top=`pwd`
-for i in man?* sman?*
-do
-	if [ -d $i ] ; then
-		cd $i
-		if test "`echo *`" != "*" ; then
-			/usr/lib/getNAME *
-		fi
-		cd $top
-	fi
-done >$tmpdir/whatisx
-sed  <$tmpdir/whatisx \
-	-e 's/\\-/-/' \
-	-e 's/\\\*-/-/' \
-	-e 's/ VAX-11//' \
-	-e 's/\\f[PRIB01234]//g' \
-	-e 's/\\s[-+0-9]*//g' \
-	-e 's/\\&//g' \
-	-e '/ - /!d' \
-	-e 's/.TH [^ ]* \([^ 	]*\).*	\(.*\) -/\2 (\1)	 -/' \
-	-e 's/	 /	/g' | \
-awk '{	title = substr($0, 1, index($0, "- ") - 1)
-	synop = substr($0, index($0, "- "))
-	count = split(title, n, " ")
-	for (i=1; i<count; i++) {
-		if ( (pos = index(n[i], ",")) || (pos = index(n[i], ":")) )
-			n[i] = substr(n[i], 1, pos-1)
-		printf("%s\t%s %s\t%s\n", n[i], n[1], n[count], synop)
-	}
-}' >$tmpdir/whatis
-/usr/bin/expand -16,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100 \
-	$tmpdir/whatis | LC_CTYPE=C LC_COLLATE=C sort -u | \
-	/usr/bin/unexpand -a > windex
-chmod 644 windex >/dev/null 2>&1
-rm -rf $tmpdir
-exit 0
--- old/usr/src/cmd/man/src/man.c	Fri Jul 18 08:34:32 2014
+++ /dev/null	Fri Jul 18 08:34:31 2014
@@ -1,3336 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License (the "License").
- * You may not use this file except in compliance with the License.
- *
- * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
- * or http://www.opensolaris.org/os/licensing.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information: Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- */
-/*
- * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2012, Josef 'Jeff' Sipek <jeffpc@31bits.net>. All rights reserved.
- */
-
-/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989  AT&T.	*/
-/*		All rights reserved.					*/
-
-/*
- * University Copyright- Copyright (c) 1982, 1986, 1988
- * The Regents of the University of California
- * All Rights Reserved
- *
- * University Acknowledgment- Portions of this document are derived from
- * software developed by the University of California, Berkeley, and its
- * contributors.
- */
-
-
-/*
- * man
- * links to apropos, whatis, and catman
- * This version uses more for underlining and paging.
- */
-
-#include <stdio.h>
-#include <ctype.h>
-#include <sgtty.h>
-#include <sys/param.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <signal.h>
-#include <string.h>
-#include <malloc.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <locale.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <memory.h>
-#include <limits.h>
-#include <wchar.h>
-
-#define	MACROF 	"tmac.an"		/* name of <locale> macro file */
-#define	TMAC_AN	"-man"		/* default macro file */
-
-/*
- * The default search path for man subtrees.
- */
-
-#define	MANDIR		"/usr/share/man" 	/* default mandir */
-#define	MAKEWHATIS	"/usr/lib/makewhatis"
-#define	WHATIS		"windex"
-#define	TEMPLATE	"/tmp/mpXXXXXX"
-#define	CONFIG		"man.cf"
-
-/*
- * Names for formatting and display programs.  The values given
- * below are reasonable defaults, but sites with source may
- * wish to modify them to match the local environment.  The
- * value for TCAT is particularly problematic as there's no
- * accepted standard value available for it.  (The definition
- * below assumes C.A.T. troff output and prints it).
- */
-
-#define	MORE	"more -s" 		/* default paging filter */
-#define	CAT_S	"/usr/bin/cat -s"	/* for '-' opt (no more) */
-#define	CAT_	"/usr/bin/cat"		/* for when output is not a tty */
-#define	TROFF	"troff"			/* local name for troff */
-#define	TCAT	"lp -c -T troff"	/* command to "display" troff output */
-
-#define	SOLIMIT		10	/* maximum allowed .so chain length */
-#define	MAXDIRS		128	/* max # of subdirs per manpath */
-#define	MAXPAGES	128	/* max # for multiple pages */
-#define	PLEN		3	/* prefix length {man, cat, fmt} */
-#define	TMPLEN		7	/* length of tmpfile prefix */
-#define	MAXTOKENS 	64
-
-#define	DOT_SO		".so "
-#define	PREPROC_SPEC	"'\\\" "
-
-#define	DPRINTF		if (debug && !catmando) \
-				(void) printf
-
-#define	sys(s)		(debug ? ((void)puts(s), 0) : system(s))
-#define	eq(a, b)	(strcmp(a, b) == 0)
-#define	match(a, b, c)	(strncmp(a, b, c) == 0)
-
-#define	ISDIR(A)	((A.st_mode & S_IFMT) == S_IFDIR)
-
-#define	SROFF_CMD	"/usr/lib/sgml/sgml2roff" /* sgml converter */
-#define	MANDIRNAME	"man"			  /* man directory */
-#define	SGMLDIR		"sman"			  /* sman directory */
-#define	SGML_SYMBOL	"<!DOCTYPE"	/* a sgml file should contain this */
-#define	SGML_SYMBOL_LEN		9	/* length of SGML_SYMBOL */
-
-/*
- * Directory mapping of old directories to new directories
- */
-
-typedef struct {
-	char *old_name;
-	char *new_name;
-} map_entry;
-
-static const map_entry map[] = {
-					{ "3b", "3ucb" },
-					{ "3e", "3elf" },
-					{ "3g", "3gen" },
-					{ "3k", "3kstat" },
-					{ "3n", "3socket" },
-					{ "3r", "3rt" },
-					{ "3s", "3c" },
-					{ "3t", "3thr" },
-					{ "3x", "3curses" },
-					{ "3xc", "3xcurses" },
-					{ "3xn", "3xnet" }
-};
-
-/*
- * A list of known preprocessors to precede the formatter itself
- * in the formatting pipeline.  Preprocessors are specified by
- * starting a manual page with a line of the form:
- *	'\" X
- * where X is a string consisting of letters from the p_tag fields
- * below.
- */
-static const struct preprocessor {
-	char	p_tag;
-	char	*p_nroff,
-		*p_troff,
-		*p_stdin_char;
-} preprocessors [] = {
-	{'c',	"cw",				"cw",		"-"},
-	{'e',	"neqn /usr/share/lib/pub/eqnchar",
-			"eqn /usr/share/lib/pub/eqnchar",	"-"},
-	{'p',	"gpic",				"gpic",		"-"},
-	{'r',	"refer",			"refer",	"-"},
-	{'t',	"tbl",				"tbl",		""},
-	{'v',	"vgrind -f",			"vgrind -f",	"-"},
-	{0,	0,				0,		0}
-};
-
-struct suffix {
-	char *ds;
-	char *fs;
-};
-
-/*
- * Flags that control behavior of build_manpath()
- *
- *   BMP_ISPATH 	pathv is a vector constructed from PATH.
- *                	Perform appropriate path translations for
- * 			manpath.
- *   BMP_APPEND_MANDIR	Add /usr/share/man to the end if it
- *			hasn't already appeared earlier.
- *   BMP_FALLBACK_MANDIR Append /usr/share/man only if no other
- *			manpath (including derived from PATH)
- * 			elements are valid.
- */
-#define	BMP_ISPATH		1
-#define	BMP_APPEND_MANDIR	2
-#define	BMP_FALLBACK_MANDIR	4
-
-/*
- * When doing equality comparisons of directories, device and inode
- * comparisons are done.  The dupsec and dupnode structures are used
- * to form a list of lists for this processing.
- */
-struct secnode {
-	char		*secp;
-	struct secnode	*next;
-};
-struct dupnode {
-	dev_t		dev;	/* from struct stat st_dev */
-	ino_t		ino;	/* from struct stat st_ino */
-	struct secnode	*secl;	/* sections already considered */
-	struct dupnode	*next;
-};
-
-/*
- * Map directories that may appear in PATH to the corresponding
- * man directory
- */
-static struct pathmap {
-	char	*bindir;
-	char	*mandir;
-	dev_t	dev;
-	ino_t	ino;
-} bintoman[] = {
-	{"/sbin",		"/usr/share/man,1m",			0, 0},
-	{"/usr/sbin",		"/usr/share/man,1m",			0, 0},
-	{"/usr/ucb",		"/usr/share/man,1b",			0, 0},
-	{"/usr/bin/X11",	"/usr/X11/share/man",			0, 0},
-	/*
-	 * Restrict to section 1 so that whatis /usr/{,xpg4,xpg6}/bin/ls
-	 * does not confuse users with section 1 and 1b
-	 */
-	{"/usr/bin",		"/usr/share/man,1,1m,1s,1t,1c", 	0, 0},
-	{"/usr/xpg4/bin",	"/usr/share/man,1",			0, 0},
-	{"/usr/xpg6/bin",	"/usr/share/man,1",			0, 0},
-	{NULL,			NULL,					0, 0}
-};
-
-/*
- * Subdirectories to search for unformatted/formatted man page
- * versions, in nroff and troff variations.  The searching
- * code in manual() is structured to expect there to be two
- * subdirectories apiece, the first for unformatted files
- * and the second for formatted ones.
- */
-static char	*nroffdirs[] = { "man", "cat", 0 };
-static char	*troffdirs[] = { "man", "fmt", 0 };
-
-#define	MAN_USAGE "\
-usage:\tman [-] [-adFlprt] [-M path] [-T macro-package ] [ -s section ] \
-name ...\n\
-\tman [-M path] -k keyword ...\n\tman [-M path] -f file ..."
-#define	CATMAN_USAGE "\
-usage:\tcatman [-p] [-c|-ntw] [-M path] [-T macro-package ] [sections]"
-
-static char *opts[] = {
-	"FfkrpP:M:T:ts:lad",	/* man */
-	"wpnP:M:T:tc"		/* catman */
-};
-
-struct man_node {
-	char *path;		/* mandir path */
-	char **secv;		/* submandir suffices */
-	int  defsrch;		/* hint for man -p to avoid section list */
-	int  frompath;		/* hint for man -d and catman -p */
-	struct man_node *next;
-};
-
-static char	*pages[MAXPAGES];
-static char	**endp = pages;
-
-/*
- * flags (options)
- */
-static int	nomore;
-static int	troffit;
-static int	debug;
-static int	Tflag;
-static int	sargs;
-static int	margs;
-static int	force;
-static int	found;
-static int	list;
-static int	all;
-static int	whatis;
-static int	apropos;
-static int	catmando;
-static int	nowhatis;
-static int	whatonly;
-static int	compargs;	/* -c option for catman */
-static int	printmp;
-
-static char	*CAT	= CAT_;
-static char	macros[MAXPATHLEN];
-static char	*mansec;
-static char	*pager;
-static char	*troffcmd;
-static char	*troffcat;
-static char	**subdirs;
-
-static char *check_config(char *);
-static struct man_node *build_manpath(char **, int);
-static void getpath(struct man_node *, char **);
-static void getsect(struct man_node *, char **);
-static void get_all_sect(struct man_node *);
-static void catman(struct man_node *, char **, int);
-static int makecat(char *, char **, int);
-static int getdirs(char *, char ***, short);
-static void whatapro(struct man_node *, char *, int);
-static void lookup_windex(char *, char *, char **);
-static int icmp(wchar_t *, wchar_t *);
-static void more(char **, int);
-static void cleanup(char **);
-static void bye(int);
-static char **split(char *, char);
-static void freev(char **);
-static void fullpaths(struct man_node **);
-static void lower(char *);
-static int cmp(const void *, const void *);
-static int manual(struct man_node *, char *);
-static void mandir(char **, char *, char *);
-static void sortdir(DIR *, char ***);
-static int searchdir(char *, char *, char *);
-static int windex(char **, char *, char *);
-static void section(struct suffix *, char *);
-static int bfsearch(FILE *, char **, char *, char **);
-static int compare(char *, char *, char **);
-static int format(char *, char *, char *, char *);
-static char *addlocale(char *);
-static int get_manconfig(FILE *, char *);
-static void	malloc_error(void);
-static int	sgmlcheck(const char *);
-static char *map_section(char *, char *);
-static void free_manp(struct man_node *manp);
-static void init_bintoman(void);
-static char *path_to_manpath(char *);
-static int dupcheck(struct man_node *, struct dupnode **);
-static void free_dupnode(struct dupnode *);
-static void print_manpath(struct man_node *, char *);
-
-/*
- * This flag is used when the SGML-to-troff converter
- * is absent - all the SGML searches are bypassed.
- */
-static int no_sroff = 0;
-
-/*
- * This flag is used to describe the case where we've found
- * an SGML formatted manpage in the sman directory, we haven't
- * found a troff formatted manpage, and we don't have the SGML to troff
- * conversion utility on the system.
- */
-static int sman_no_man_no_sroff;
-
-static char language[PATH_MAX + 1]; 	/* LC_MESSAGES */
-static char localedir[PATH_MAX + 1];	/* locale specific path component */
-
-static int	defaultmandir = 1;	/* if processing default mandir, 1 */
-
-static char *newsection = NULL;
-
-int
-main(int argc, char *argv[])
-{
-	int badopts = 0;
-	int c;
-	char **pathv;
-	char *cmdname;
-	char *manpath = NULL;
-	static struct man_node	*manpage = NULL;
-	int bmp_flags = 0;
-	int err = 0;
-
-	if (access(SROFF_CMD, F_OK | X_OK) != 0)
-		no_sroff = 1;
-
-	(void) setlocale(LC_ALL, "");
-	(void) strcpy(language, setlocale(LC_MESSAGES, (char *)0));
-	if (strcmp("C", language) != 0)
-		(void) sprintf(localedir, "%s", language);
-
-#if !defined(TEXT_DOMAIN)
-#define	TEXT_DOMAIN "SYS_TEST"
-#endif
-	(void) textdomain(TEXT_DOMAIN);
-
-	(void) strcpy(macros, TMAC_AN);
-
-	/*
-	 * get base part of command name
-	 */
-	if ((cmdname = strrchr(argv[0], '/')) != NULL)
-		cmdname++;
-	else
-		cmdname = argv[0];
-
-	if (eq(cmdname, "apropos") || eq(cmdname, "whatis")) {
-		whatis++;
-		apropos = (*cmdname == 'a');
-		if ((optind = 1) == argc) {
-			(void) fprintf(stderr, gettext("%s what?\n"), cmdname);
-			exit(2);
-		}
-		goto doargs;
-	} else if (eq(cmdname, "catman"))
-		catmando++;
-
-	opterr = 0;
-	while ((c = getopt(argc, argv, opts[catmando])) != -1)
-		switch (c) {
-
-		/*
-		 * man specific options
-		 */
-		case 'k':
-			apropos++;
-			/*FALLTHROUGH*/
-		case 'f':
-			whatis++;
-			break;
-		case 'F':
-			force++;	/* do lookups the hard way */
-			break;
-		case 's':
-			mansec = optarg;
-			sargs++;
-			break;
-		case 'r':
-			nomore++, troffit++;
-			break;
-		case 'l':
-			list++;		/* implies all */
-			/*FALLTHROUGH*/
-		case 'a':
-			all++;
-			break;
-		case 'd':
-			debug++;
-			break;
-		/*
-		 * man and catman use -p differently.  In catman it
-		 * enables debug mode and in man it prints the (possibly
-		 * derived from PATH or name operand) MANPATH.
-		 */
-		case 'p':
-			if (catmando == 0) {
-				printmp++;
-			} else {
-				debug++;
-			}
-			break;
-		case 'n':
-			nowhatis++;
-			break;
-		case 'w':
-			whatonly++;
-			break;
-		case 'c':	/* n|troff compatibility */
-			if (no_sroff)
-				(void) fprintf(stderr, gettext(
-				    "catman: SGML conversion not "
-				    "available -- -c flag ignored\n"));
-			else
-				compargs++;
-			continue;
-
-		/*
-		 * shared options
-		 */
-		case 'P':	/* Backwards compatibility */
-		case 'M':	/* Respecify path for man pages. */
-			manpath = optarg;
-			margs++;
-			break;
-		case 'T':	/* Respecify man macros */
-			(void) strcpy(macros, optarg);
-			Tflag++;
-			break;
-		case 't':
-			troffit++;
-			break;
-		case '?':
-			badopts++;
-		}
-
-	/*
-	 *  Bad options or no args?
-	 *	(man -p and catman don't need args)
-	 */
-	if (badopts || (!catmando && !printmp && optind == argc)) {
-		(void) fprintf(stderr, "%s\n", catmando ?
-		    gettext(CATMAN_USAGE) : gettext(MAN_USAGE));
-		exit(2);
-	}
-
-	if (compargs && (nowhatis || whatonly || troffit)) {
-		(void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE));
-		(void) fprintf(stderr, gettext(
-		    "-c option cannot be used with [-w][-n][-t]\n"));
-		exit(2);
-	}
-
-	if (sargs && margs && catmando) {
-		(void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE));
-		exit(2);
-	}
-
-	if (troffit == 0 && nomore == 0 && !isatty(fileno(stdout)))
-		nomore++;
-
-	/*
-	 * Collect environment information.
-	 */
-	if (troffit) {
-		if ((troffcmd = getenv("TROFF")) == NULL)
-			troffcmd = TROFF;
-		if ((troffcat = getenv("TCAT")) == NULL)
-			troffcat = TCAT;
-	} else {
-		if (((pager = getenv("PAGER")) == NULL) ||
-		    (*pager == NULL))
-			pager = MORE;
-	}
-
-doargs:
-	subdirs = troffit ? troffdirs : nroffdirs;
-
-	init_bintoman();
-
-	if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) {
-		if ((manpath = getenv("PATH")) != NULL) {
-			bmp_flags = BMP_ISPATH | BMP_APPEND_MANDIR;
-		} else {
-			manpath = MANDIR;
-		}
-	}
-
-	pathv = split(manpath, ':');
-
-	manpage = build_manpath(pathv, bmp_flags);
-
-	/* release pathv allocated by split() */
-	freev(pathv);
-
-	/*
-	 * Since we can't make use of GNU troff, set the path to ensure we
-	 * find the one in /usr/bin first.
-	 */
-	if (putenv("PATH=/usr/bin") != 0) {
-		perror("putenv");
-		exit(1);
-	}
-
-	fullpaths(&manpage);
-
-	if (catmando) {
-		catman(manpage, argv+optind, argc-optind);
-		exit(0);
-	}
-
-	/*
-	 * The manual routine contains windows during which
-	 * termination would leave a temp file behind.  Thus
-	 * we blanket the whole thing with a clean-up routine.
-	 */
-	if (signal(SIGINT, SIG_IGN) == SIG_DFL) {
-		(void) signal(SIGINT, bye);
-		(void) signal(SIGQUIT, bye);
-		(void) signal(SIGTERM, bye);
-	}
-
-	/*
-	 * "man -p" without operands
-	 */
-	if ((printmp != 0) && (optind == argc)) {
-		print_manpath(manpage, NULL);
-		exit(0);
-	}
-
-	for (; optind < argc; optind++) {
-		if (strcmp(argv[optind], "-") == 0) {
-			nomore++;
-			CAT = CAT_S;
-		} else {
-			char *cmd;
-			static struct man_node *mp;
-			char *pv[2];
-
-			/*
-			 * If full path to command specified, customize
-			 * manpath accordingly
-			 */
-			if ((cmd = strrchr(argv[optind], '/')) != NULL) {
-				*cmd = '\0';
-				if ((pv[0] = strdup(argv[optind])) == NULL) {
-					malloc_error();
-				}
-				pv[1] = NULL;
-				*cmd = '/';
-				mp = build_manpath(pv,
-				    BMP_ISPATH|BMP_FALLBACK_MANDIR);
-			} else {
-				mp = manpage;
-			}
-
-			if (whatis) {
-				whatapro(mp, argv[optind], apropos);
-			} else if (printmp != 0) {
-				print_manpath(mp, argv[optind]);
-			} else {
-				err += manual(mp, argv[optind]);
-			}
-
-			if (mp != NULL && mp != manpage) {
-				free(pv[0]);
-				free_manp(mp);
-			}
-		}
-	}
-	return (err == 0 ? 0 : 1);
-	/*NOTREACHED*/
-}
-
-/*
- * This routine builds the manpage structure from MANPATH or PATH,
- * depending on flags.  See BMP_* definitions above for valid
- * flags.
- *
- * Assumes pathv elements were malloc'd, as done by split().
- * Elements may be freed and reallocated to have different contents.
- */
-
-static struct man_node *
-build_manpath(char **pathv, int flags)
-{
-	struct man_node *manpage = NULL;
-	struct man_node *currp = NULL;
-	struct man_node *lastp = NULL;
-	char **p;
-	char **q;
-	char *mand = NULL;
-	char *mandir = MANDIR;
-	int s;
-	struct dupnode *didup = NULL;
-	struct stat sb;
-
-	s = sizeof (struct man_node);
-	for (p = pathv; *p; ) {
-
-		if (flags & BMP_ISPATH) {
-			if ((mand = path_to_manpath(*p)) == NULL) {
-				goto next;
-			}
-			free(*p);
-			*p = mand;
-		}
-		q = split(*p, ',');
-		if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) {
-			freev(q);
-			goto next;
-		}
-
-		if (access(q[0], R_OK|X_OK) != 0) {
-			if (catmando) {
-				(void) fprintf(stderr,
-				    gettext("%s is not accessible.\n"),
-				    q[0]);
-				(void) fflush(stderr);
-			}
-		} else {
-
-			/*
-			 * Some element exists.  Do not append MANDIR as a
-			 * fallback.
-			 */
-			flags &= ~BMP_FALLBACK_MANDIR;
-
-			if ((currp = (struct man_node *)calloc(1, s)) == NULL) {
-				malloc_error();
-			}
-
-			currp->frompath = (flags & BMP_ISPATH);
-
-			if (manpage == NULL) {
-				lastp = manpage = currp;
-			}
-
-			getpath(currp, p);
-			getsect(currp, p);
-
-			/*
-			 * If there are no new elements in this path,
-			 * do not add it to the manpage list
-			 */
-			if (dupcheck(currp, &didup) != 0) {
-				freev(currp->secv);
-				free(currp);
-			} else {
-				currp->next = NULL;
-				if (currp != manpage) {
-					lastp->next = currp;
-				}
-				lastp = currp;
-			}
-		}
-		freev(q);
-next:
-		/*
-		 * Special handling of appending MANDIR.
-		 * After all pathv elements have been processed, append MANDIR
-		 * if needed.
-		 */
-		if (p == &mandir) {
-			break;
-		}
-		p++;
-		if (*p != NULL) {
-			continue;
-		}
-		if (flags & (BMP_APPEND_MANDIR|BMP_FALLBACK_MANDIR)) {
-			p = &mandir;
-			flags &= ~BMP_ISPATH;
-		}
-	}
-
-	free_dupnode(didup);
-
-	return (manpage);
-}
-
-/*
- * Stores the mandir path into the manp structure.
- */
-
-static void
-getpath(struct man_node *manp, char **pv)
-{
-	char *s;
-	int i = 0;
-
-	s = *pv;
-
-	while (*s != NULL && *s != ',')
-		i++, s++;
-
-	manp->path = (char *)malloc(i+1);
-	if (manp->path == NULL)
-		malloc_error();
-	(void) strncpy(manp->path, *pv, i);
-	*(manp->path + i) = '\0';
-}
-
-/*
- * Stores the mandir's corresponding sections (submandir
- * directories) into the manp structure.
- */
-
-static void
-getsect(struct man_node *manp, char **pv)
-{
-	char *sections;
-	char **sectp;
-
-	if (sargs) {
-		manp->secv = split(mansec, ',');
-
-		for (sectp = manp->secv; *sectp; sectp++)
-			lower(*sectp);
-	} else if ((sections = strchr(*pv, ',')) != NULL) {
-		if (debug) {
-			if (manp->frompath != 0) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. /usr/share/man: derived from PATH, MANSECTS=,1b
- */
-				(void) printf(gettext(
-				    "%s: derived from PATH, MANSECTS=%s\n"),
-				    manp->path, sections);
-			} else {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. /usr/share/man: from -M option, MANSECTS=,1,2,3c
- */
-				(void) fprintf(stdout, gettext(
-				    "%s: from -M option, MANSECTS=%s\n"),
-				    manp->path, sections);
-			}
-		}
-		manp->secv = split(++sections, ',');
-		for (sectp = manp->secv; *sectp; sectp++)
-			lower(*sectp);
-
-		if (*manp->secv == NULL)
-			get_all_sect(manp);
-	} else if ((sections = check_config(*pv)) != NULL) {
-		manp->defsrch = 1;
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. /usr/share/man: from man.cf, MANSECTS=1,1m,1c
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "%s: from %s, MANSECTS=%s\n"),
-			    manp->path, CONFIG, sections);
-		manp->secv = split(sections, ',');
-
-		for (sectp = manp->secv; *sectp; sectp++)
-			lower(*sectp);
-
-		if (*manp->secv == NULL)
-			get_all_sect(manp);
-	} else {
-		manp->defsrch = 1;
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * if man.cf has not been found or sections has not been specified
- * man/catman searches the sections lexicographically.
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "%s: search the sections lexicographically\n"),
-			    manp->path);
-		manp->secv = NULL;
-		get_all_sect(manp);
-	}
-}
-
-/*
- * Get suffices of all sub-mandir directories in a mandir.
- */
-
-static void
-get_all_sect(struct man_node *manp)
-{
-	DIR *dp;
-	char **dirv;
-	char **dv;
-	char **p;
-	char *prev = NULL;
-	char *tmp = NULL;
-	int  plen;
-	int	maxentries = MAXTOKENS;
-	int	entries = 0;
-
-	if ((dp = opendir(manp->path)) == 0)
-		return;
-
-	/*
-	 * sortdir() allocates memory for dirv and dirv[].
-	 */
-	sortdir(dp, &dirv);
-
-	(void) closedir(dp);
-
-	if (manp->secv == NULL) {
-		/*
-		 * allocates memory for manp->secv only if it's NULL
-		 */
-		manp->secv = (char **)malloc(maxentries * sizeof (char *));
-		if (manp->secv == NULL)
-			malloc_error();
-	}
-
-	for (dv = dirv, p = manp->secv; *dv; dv++) {
-		plen = PLEN;
-		if (match(*dv, SGMLDIR, PLEN+1))
-			++plen;
-
-		if (strcmp(*dv, CONFIG) == 0) {
-			/* release memory allocated by sortdir */
-			free(*dv);
-			continue;
-		}
-
-		if (tmp != NULL)
-			free(tmp);
-		tmp = strdup(*dv + plen);
-		if (tmp == NULL)
-			malloc_error();
-		(void) sprintf(tmp, "%s", *dv + plen);
-
-		if (prev != NULL) {
-			if (strcmp(prev, tmp) == 0) {
-				/* release memory allocated by sortdir */
-				free(*dv);
-				continue;
-			}
-		}
-
-		if (prev != NULL)
-			free(prev);
-		prev = strdup(*dv + plen);
-		if (prev == NULL)
-			malloc_error();
-		(void) sprintf(prev, "%s", *dv + plen);
-		/*
-		 * copy the string in (*dv + plen) to *p
-		 */
-		*p = strdup(*dv + plen);
-		if (*p == NULL)
-			malloc_error();
-		p++;
-		entries++;
-		if (entries == maxentries) {
-			maxentries += MAXTOKENS;
-			manp->secv = (char **)realloc(manp->secv,
-			    sizeof (char *) * maxentries);
-			if (manp->secv == NULL)
-				malloc_error();
-			p = manp->secv + entries;
-		}
-		/* release memory allocated by sortdir */
-		free(*dv);
-	}
-	*p = 0;
-	/* release memory allocated by sortdir */
-	free(dirv);
-}
-
-/*
- * Format man pages (build cat pages); if no
- * sections are specified, build all of them.
- * When building cat pages:
- *	catman() tries to build cat pages for locale specific
- *	man dirs first.  Then, catman() tries to build cat pages
- *	for the default man dir (for C locale like /usr/share/man)
- *	regardless of the locale.
- * When building windex file:
- *	catman() tries to build windex file for locale specific
- *	man dirs first.  Then, catman() tries to build windex file
- *	for the default man dir (for C locale like /usr/share/man)
- *	regardless of the locale.
- */
-
-static void
-catman(struct man_node *manp, char **argv, int argc)
-{
-	char cmdbuf[BUFSIZ];
-	char **dv;
-	int changed;
-	struct man_node *p;
-	int ndirs = 0;
-	char *ldir;
-	int	i;
-	struct dupnode *dnp = NULL;
-	char   **realsecv;
-	/*
-	 * May be overwritten in dupcheck() so must be kept out of .rodata.
-	 */
-	char	fakename[] = " catman ";
-	char	*fakesecv[2];
-
-	fakesecv[0] = fakename;
-	fakesecv[1] = NULL;
-
-	for (p = manp; p != NULL; p = p->next) {
-		/*
-		 * prevent catman from doing very heavy lifting multiple
-		 * times on some directory
-		 */
-		realsecv = p->secv;
-		p->secv = fakesecv;
-		if (dupcheck(p, &dnp) != 0) {
-			p->secv = realsecv;
-			continue;
-		}
-
-/*
- * TRANSLATION_NOTE - message for catman -p
- * ex. mandir path = /usr/share/man
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "\nmandir path = %s\n"), p->path);
-		ndirs = 0;
-
-		/*
-		 * Build cat pages
-		 * addlocale() allocates memory and returns it
-		 */
-		ldir = addlocale(p->path);
-		if (!whatonly) {
-			if (*localedir != '\0') {
-				if (defaultmandir)
-					defaultmandir = 0;
-				/* getdirs allocate memory for dv */
-				ndirs = getdirs(ldir, &dv, 1);
-				if (ndirs != 0) {
-					changed = argc ?
-					    makecat(ldir, argv, argc) :
-					    makecat(ldir, dv, ndirs);
-					/* release memory by getdirs */
-					for (i = 0; i < ndirs; i++) {
-						free(dv[i]);
-					}
-					free(dv);
-				}
-			}
-
-			/* default man dir is always processed */
-			defaultmandir = 1;
-			ndirs = getdirs(p->path, &dv, 1);
-			changed = argc ?
-			    makecat(p->path, argv, argc) :
-			    makecat(p->path, dv, ndirs);
-			/* release memory allocated by getdirs */
-			for (i = 0; i < ndirs; i++) {
-				free(dv[i]);
-			}
-			free(dv);
-		}
-		/*
-		 * Build whatis database
-		 *  print error message if locale is set and man dir not found
-		 *  won't build it at all if -c option is on
-		 */
-		if (!compargs && (whatonly || (!nowhatis && changed))) {
-			if (*localedir != '\0') {
-				/* just count the number of ndirs */
-				if ((ndirs = getdirs(ldir, NULL, 0)) != 0) {
-					(void) sprintf(cmdbuf,
-					    "/usr/bin/sh %s %s",
-					    MAKEWHATIS, ldir);
-					(void) sys(cmdbuf);
-				}
-			}
-			/* whatis database of the default man dir */
-			/* will be always built in C locale. */
-			(void) sprintf(cmdbuf,
-			    "/usr/bin/sh %s %s",
-			    MAKEWHATIS, p->path);
-			(void) sys(cmdbuf);
-		}
-		/* release memory allocated by addlocale() */
-		free(ldir);
-	}
-	free_dupnode(dnp);
-}
-
-/*
- * Build cat pages for given sections
- */
-
-static int
-makecat(char *path, char **dv, int ndirs)
-{
-	DIR *dp, *sdp;
-	struct dirent *d;
-	struct stat sbuf;
-	char mandir[MAXPATHLEN+1];
-	char smandir[MAXPATHLEN+1];
-	char catdir[MAXPATHLEN+1];
-	char *dirp, *sdirp;
-	int i, fmt;
-	int manflag, smanflag;
-
-	for (i = fmt = 0; i < ndirs; i++) {
-		(void) snprintf(mandir, MAXPATHLEN, "%s/%s%s",
-		    path, MANDIRNAME, dv[i]);
-		(void) snprintf(smandir, MAXPATHLEN, "%s/%s%s",
-		    path, SGMLDIR, dv[i]);
-		(void) snprintf(catdir, MAXPATHLEN, "%s/%s%s",
-		    path, subdirs[1], dv[i]);
-		dirp = strrchr(mandir, '/') + 1;
-		sdirp = strrchr(smandir, '/') + 1;
-
-		manflag = smanflag = 0;
-
-		if ((dp = opendir(mandir)) != NULL)
-			manflag = 1;
-
-		if (!no_sroff && (sdp = opendir(smandir)) != NULL)
-			smanflag = 1;
-
-		if (dp == 0 && sdp == 0) {
-			if (strcmp(mandir, CONFIG) == 0)
-				perror(mandir);
-			continue;
-		}
-/*
- * TRANSLATION_NOTE - message for catman -p
- * ex. Building cat pages for mandir = /usr/share/man/ja
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "Building cat pages for mandir = %s\n"), path);
-
-		if (!compargs && stat(catdir, &sbuf) < 0) {
-			(void) umask(02);
-/*
- * TRANSLATION_NOTE - message for catman -p
- * ex. mkdir /usr/share/man/ja/cat3c
- */
-			if (debug)
-				(void) fprintf(stdout, gettext("mkdir %s\n"),
-				    catdir);
-			else {
-				if (mkdir(catdir, 0755) < 0) {
-					perror(catdir);
-					continue;
-				}
-				(void) chmod(catdir, 0755);
-			}
-		}
-
-		/*
-		 * if it is -c option of catman, if there is no
-		 * coresponding man dir for sman files to go to,
-		 * make the man dir
-		 */
-
-		if (compargs && !manflag) {
-			if (mkdir(mandir, 0755) < 0) {
-				perror(mandir);
-				continue;
-			}
-			(void) chmod(mandir, 0755);
-		}
-
-		if (smanflag) {
-			while ((d = readdir(sdp))) {
-				if (eq(".", d->d_name) || eq("..", d->d_name))
-					continue;
-
-				if (format(path, sdirp, (char *)0, d->d_name)
-				    > 0)
-					fmt++;
-			}
-		}
-
-		if (manflag && !compargs) {
-			while ((d = readdir(dp))) {
-				if (eq(".", d->d_name) || eq("..", d->d_name))
-					continue;
-
-				if (format(path, dirp, (char *)0, d->d_name)
-				    > 0)
-					fmt++;
-			}
-		}
-
-		if (manflag)
-			(void) closedir(dp);
-
-		if (smanflag)
-			(void) closedir(sdp);
-
-	}
-	return (fmt);
-}
-
-
-/*
- * Get all "man" and "sman" dirs under a given manpath
- * and return the number found
- * If -c option is on, only count sman dirs
- */
-
-static int
-getdirs(char *path, char ***dirv, short flag)
-{
-	DIR *dp;
-	struct dirent *d;
-	int n = 0;
-	int plen, sgml_flag, man_flag;
-	int i = 0;
-	int	maxentries = MAXDIRS;
-	char	**dv;
-
-	if ((dp = opendir(path)) == 0) {
-		if (debug) {
-			if (*localedir != '\0')
-				(void) printf(gettext("\
-locale is %s, search in %s\n"), localedir, path);
-			perror(path);
-		}
-		return (0);
-	}
-
-	if (flag) {
-		/* allocate memory for dirv */
-		*dirv = (char **)malloc(sizeof (char *) *
-		    maxentries);
-		if (*dirv == NULL)
-			malloc_error();
-		dv = *dirv;
-	}
-	while ((d = readdir(dp))) {
-		plen = PLEN;
-		man_flag = sgml_flag = 0;
-		if (match(d->d_name, SGMLDIR, PLEN+1)) {
-			plen = PLEN + 1;
-			sgml_flag = 1;
-			i++;
-		}
-
-		if (match(subdirs[0], d->d_name, PLEN))
-			man_flag = 1;
-
-		if (compargs && sgml_flag) {
-			if (flag) {
-				*dv = strdup(d->d_name+plen);
-				if (*dv == NULL)
-					malloc_error();
-				dv++;
-				n = i;
-			}
-		} else if (!compargs && (sgml_flag || man_flag)) {
-			if (flag) {
-				*dv = strdup(d->d_name+plen);
-				if (*dv == NULL)
-					malloc_error();
-				dv++;
-			}
-			n++;
-		}
-		if (flag) {
-			if ((dv - *dirv) == maxentries) {
-				int entries = maxentries;
-				maxentries += MAXTOKENS;
-				*dirv = (char **)realloc(*dirv,
-				    sizeof (char *) * maxentries);
-				if (*dirv == NULL)
-					malloc_error();
-				dv = *dirv + entries;
-			}
-		}
-	}
-
-	(void) closedir(dp);
-	return (n);
-}
-
-
-/*
- * Find matching whatis or apropos entries
- * whatapro() tries to handle the windex file of the locale specific
- * man dirs first, then tries to handle the windex file of the default
- * man dir (of C locale like /usr/share/man).
- */
-
-static void
-whatapro(struct man_node *manp, char *word, int apropos)
-{
-	char whatpath[MAXPATHLEN+1];
-	char *p;
-	struct man_node *b;
-	int ndirs = 0;
-	char *ldir;
-
-
-/*
- * TRANSLATION_NOTE - message for man -d
- * %s takes a parameter to -k option.
- */
-	DPRINTF(gettext("word = %s \n"), word);
-
-	/*
-	 * get base part of name
-	 */
-	if (!apropos) {
-		if ((p = strrchr(word, '/')) == NULL)
-			p = word;
-		else
-			p++;
-	} else {
-		p = word;
-	}
-
-	for (b = manp; b != NULL; b = b->next) {
-
-		if (*localedir != '\0') {
-			/* addlocale() allocates memory and returns it */
-			ldir = addlocale(b->path);
-			if (defaultmandir)
-				defaultmandir = 0;
-			ndirs = getdirs(ldir, NULL, 0);
-			if (ndirs != 0) {
-				(void) sprintf(whatpath, "%s/%s", ldir, WHATIS);
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. mandir path = /usr/share/man/ja
- */
-				DPRINTF(gettext("\nmandir path = %s\n"), ldir);
-				lookup_windex(whatpath, p, b->secv);
-			}
-			/* release memory allocated by addlocale() */
-			free(ldir);
-		}
-
-		defaultmandir = 1;
-		(void) sprintf(whatpath, "%s/%s", b->path, WHATIS);
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. mandir path = /usr/share/man
- */
-		DPRINTF(gettext("\nmandir path = %s\n"), b->path);
-
-		lookup_windex(whatpath, p, b->secv);
-	}
-}
-
-
-static void
-lookup_windex(char *whatpath, char *word, char **secv)
-{
-	FILE *fp;
-	char *matches[MAXPAGES];
-	char **pp;
-	wchar_t	wbuf[BUFSIZ];
-	wchar_t *word_wchar = NULL;
-	wchar_t	*ws;
-	size_t	word_len, ret;
-
-	if ((fp = fopen(whatpath, "r")) == NULL) {
-		perror(whatpath);
-		return;
-	}
-
-	if (apropos) {
-		word_len = strlen(word) + 1;
-		if ((word_wchar = (wchar_t *)malloc(sizeof (wchar_t) *
-		    word_len)) == NULL) {
-			malloc_error();
-		}
-		ret = mbstowcs(word_wchar, (const char *)word, word_len);
-		if (ret == (size_t)-1) {
-			(void) fprintf(stderr, gettext(
-			    "Invalid character in keyword\n"));
-			exit(1);
-		}
-		while (fgetws(wbuf, BUFSIZ, fp) != NULL)
-			for (ws = wbuf; *ws; ws++)
-				if (icmp(word_wchar, ws) == 0) {
-					(void) printf("%ws", wbuf);
-					break;
-				}
-	} else {
-		if (bfsearch(fp, matches, word, secv))
-			for (pp = matches; *pp; pp++) {
-				(void) printf("%s", *pp);
-				/*
-				 * release memory allocated by
-				 * strdup() in bfsearch()
-				 */
-				free(*pp);
-			}
-	}
-	(void) fclose(fp);
-	if (word_wchar)
-		free(word_wchar);
-
-}
-
-
-/*
- * case-insensitive compare unless upper case is used
- * ie)	"mount" matches mount, Mount, MOUNT
- *	"Mount" matches Mount, MOUNT
- *	"MOUNT" matches MOUNT only
- *	If matched return 0.  Otherwise, return 1.
- */
-
-static int
-icmp(wchar_t *ws, wchar_t *wt)
-{
-	for (; (*ws == 0) ||
-	    (*ws == (iswupper(*ws) ? *wt: towlower(*wt)));
-	    ws++, wt++)
-		if (*ws == 0)
-			return (0);
-
-	return (1);
-}
-
-
-/*
- * Invoke PAGER with all matching man pages
- */
-
-static void
-more(char **pages, int plain)
-{
-	char cmdbuf[BUFSIZ];
-	char **vp;
-
-	/*
-	 * Dont bother.
-	 */
-	if (list || (*pages == 0))
-		return;
-
-	if (plain && troffit) {
-		cleanup(pages);
-		return;
-	}
-	(void) sprintf(cmdbuf, "%s", troffit ? troffcat :
-	    plain ? CAT : pager);
-
-	/*
-	 * Build arg list
-	 */
-	for (vp = pages; vp < endp; vp++) {
-		(void) strcat(cmdbuf, " ");
-		(void) strcat(cmdbuf, *vp);
-	}
-	(void) sys(cmdbuf);
-	cleanup(pages);
-}
-
-
-/*
- * Get rid of dregs.
- */
-
-static void
-cleanup(char **pages)
-{
-	char **vp;
-
-	for (vp = pages; vp < endp; vp++) {
-		if (match(TEMPLATE, *vp, TMPLEN))
-			(void) unlink(*vp);
-		free(*vp);
-	}
-
-	endp = pages;	/* reset */
-}
-
-
-/*
- * Clean things up after receiving a signal.
- */
-
-/*ARGSUSED*/
-static void
-bye(int sig)
-{
-	cleanup(pages);
-	exit(1);
-	/*NOTREACHED*/
-}
-
-
-/*
- * Split a string by specified separator.
- *    ignore empty components/adjacent separators.
- *    returns vector to all tokens
- */
-
-static char **
-split(char *s1, char sep)
-{
-	char **tokv, **vp;
-	char *mp, *tp;
-	int maxentries = MAXTOKENS;
-	int entries = 0;
-
-	tokv = vp = (char **)malloc(maxentries * sizeof (char *));
-	if (tokv == NULL)
-		malloc_error();
-	mp = s1;
-	for (; mp && *mp; mp = tp) {
-		tp = strchr(mp, sep);
-		if (mp == tp) {		/* empty component */
-			tp++;			/* ignore */
-			continue;
-		}
-		if (tp) {
-			/* a component found */
-			size_t	len;
-
-			len = tp - mp;
-			*vp = (char *)malloc(sizeof (char) * len + 1);
-			if (*vp == NULL)
-				malloc_error();
-			(void) strncpy(*vp, mp, len);
-			*(*vp + len) = '\0';
-			tp++;
-			vp++;
-		} else {
-			/* the last component */
-			*vp = strdup(mp);
-			if (*vp == NULL)
-				malloc_error();
-			vp++;
-		}
-		entries++;
-		if (entries == maxentries) {
-			maxentries += MAXTOKENS;
-			tokv = (char **)realloc(tokv,
-			    maxentries * sizeof (char *));
-			if (tokv == NULL)
-				malloc_error();
-			vp = tokv + entries;
-		}
-	}
-	*vp = 0;
-	return (tokv);
-}
-
-/*
- * Free a vector allocated by split();
- */
-static void
-freev(char **v)
-{
-	int i;
-	if (v != NULL) {
-		for (i = 0; v[i] != NULL; i++) {
-			free(v[i]);
-		}
-		free(v);
-	}
-}
-
-/*
- * Convert paths to full paths if necessary
- *
- */
-
-static void
-fullpaths(struct man_node **manp_head)
-{
-	char *cwd = NULL;
-	char *p;
-	char cwd_gotten = 0;
-	struct man_node *manp = *manp_head;
-	struct man_node *b;
-	struct man_node *prev = NULL;
-
-	for (b = manp; b != NULL; b = b->next) {
-		if (*(b->path) == '/') {
-			prev = b;
-			continue;
-		}
-
-		/* try to get cwd if haven't already */
-		if (!cwd_gotten) {
-			cwd = getcwd(NULL, MAXPATHLEN+1);
-			cwd_gotten = 1;
-		}
-
-		if (cwd) {
-			/* case: relative manpath with cwd: make absolute */
-			if ((p = malloc(strlen(b->path)+strlen(cwd)+2)) ==
-			    NULL) {
-				malloc_error();
-			}
-			(void) sprintf(p, "%s/%s", cwd, b->path);
-			/*
-			 * resetting b->path
-			 */
-			free(b->path);
-			b->path = p;
-		} else {
-			/* case: relative manpath but no cwd: omit path entry */
-			if (prev)
-				prev->next = b->next;
-			else
-				*manp_head = b->next;
-
-			free_manp(b);
-		}
-	}
-	/*
-	 * release memory allocated by getcwd()
-	 */
-	free(cwd);
-}
-
-/*
- * Free a man_node structure and its contents
- */
-
-static void
-free_manp(struct man_node *manp)
-{
-	char **p;
-
-	free(manp->path);
-	p = manp->secv;
-	while ((p != NULL) && (*p != NULL)) {
-		free(*p);
-		p++;
-	}
-	free(manp->secv);
-	free(manp);
-}
-
-
-/*
- * Map (in place) to lower case
- */
-
-static void
-lower(char *s)
-{
-	if (s == 0)
-		return;
-	while (*s) {
-		if (isupper(*s))
-			*s = tolower(*s);
-		s++;
-	}
-}
-
-
-/*
- * compare for sort()
- * sort first by section-spec, then by prefix {sman, man, cat, fmt}
- *	note: prefix is reverse sorted so that "sman" and "man" always
- * 	comes before {cat, fmt}
- */
-
-static int
-cmp(const void *arg1, const void *arg2)
-{
-	int n;
-	char **p1 = (char **)arg1;
-	char **p2 = (char **)arg2;
-
-
-	/* by section; sman always before man dirs */
-	if ((n = strcmp(*p1 + PLEN + (**p1 == 's' ? 1 : 0),
-	    *p2 + PLEN + (**p2 == 's' ? 1 : 0))))
-		return (n);
-
-	/* by prefix reversed */
-	return (strncmp(*p2, *p1, PLEN));
-}
-
-
-/*
- * Find a man page ...
- *   Loop through each path specified,
- *   first try the lookup method (whatis database),
- *   and if it doesn't exist, do the hard way.
- */
-
-static int
-manual(struct man_node *manp, char *name)
-{
-	struct man_node *p;
-	struct man_node *local;
-	int ndirs = 0;
-	char *ldir;
-	char *ldirs[2];
-	char *fullname = name;
-	char *slash;
-
-	if ((slash = strrchr(name, '/')) != NULL) {
-		name = slash + 1;
-	}
-
-	/*
-	 *  for each path in MANPATH
-	 */
-	found = 0;
-
-	for (p = manp; p != NULL; p = p->next) {
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. mandir path = /usr/share/man
- */
-		DPRINTF(gettext("\nmandir path = %s\n"), p->path);
-
-		if (*localedir != '\0') {
-			/* addlocale() allocates memory and returns it */
-			ldir = addlocale(p->path);
-			if (defaultmandir)
-				defaultmandir = 0;
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. localedir = ja, ldir = /usr/share/man/ja
- */
-			if (debug)
-				(void) printf(gettext(
-				    "localedir = %s, ldir = %s\n"),
-				    localedir, ldir);
-			ndirs = getdirs(ldir, NULL, 0);
-			if (ndirs != 0) {
-				ldirs[0] = ldir;
-				ldirs[1] = NULL;
-				local = build_manpath(ldirs, 0);
-				if (force ||
-				    windex(local->secv, ldir, name) < 0)
-					mandir(local->secv, ldir, name);
-				free_manp(local);
-			}
-			/* release memory allocated by addlocale() */
-			free(ldir);
-		}
-
-		defaultmandir = 1;
-		/*
-		 * locale mandir not valid, man page in locale
-		 * mandir not found, or -a option present
-		 */
-		if (ndirs == 0 || !found || all) {
-			if (force || windex(p->secv, p->path, name) < 0)
-				mandir(p->secv, p->path, name);
-		}
-
-		if (found && !all)
-			break;
-	}
-
-	if (found) {
-		more(pages, nomore);
-	} else {
-		if (sargs) {
-			(void) fprintf(stderr, gettext("No entry for %s in "
-			    "section(s) %s of the manual.\n"),
-			    fullname, mansec);
-		} else {
-			(void) fprintf(stderr, gettext(
-			    "No manual entry for %s.\n"), fullname, mansec);
-		}
-
-		if (sman_no_man_no_sroff)
-			(void) fprintf(stderr, gettext("(An SGML manpage was "
-			    "found for '%s' but it cannot be displayed.)\n"),
-			    fullname, mansec);
-	}
-	sman_no_man_no_sroff = 0;
-	return (!found);
-}
-
-
-/*
- * For a specified manual directory,
- *	read, store, & sort section subdirs,
- *	for each section specified
- *		find and search matching subdirs
- */
-
-static void
-mandir(char **secv, char *path, char *name)
-{
-	DIR *dp;
-	char **dirv;
-	char **dv, **pdv;
-	int len, dslen, plen = PLEN;
-
-	if ((dp = opendir(path)) == 0) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * opendir(%s) returned 0
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    " opendir on %s failed\n"), path);
-		return;
-	}
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. mandir path = /usr/share/man/ja
- */
-	if (debug)
-		(void) printf(gettext("mandir path = %s\n"), path);
-
-	/*
-	 * sordir() allocates memory for dirv and dirv[].
-	 */
-	sortdir(dp, &dirv);
-	/*
-	 * Search in the order specified by MANSECTS
-	 */
-	for (; *secv; secv++) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  section = 3c
- */
-		DPRINTF(gettext("  section = %s\n"), *secv);
-		len = strlen(*secv);
-		for (dv = dirv; *dv; dv++) {
-			plen = PLEN;
-			if (*dv[0] == 's')
-				plen++;
-			dslen = strlen(*dv+plen);
-			if (dslen > len)
-				len = dslen;
-			if (**secv == '\\') {
-				if (!eq(*secv + 1, *dv+plen))
-					continue;
-			} else if (strncasecmp(*secv, *dv+plen, len) != 0) {
-				/* check to see if directory name changed */
-				if (!all &&
-				    (newsection = map_section(*secv, path))
-				    == NULL) {
-					continue;
-				}
-				if (newsection == NULL)
-					newsection = "";
-				if (!match(newsection, *dv+plen, len)) {
-					continue;
-				}
-			}
-
-			if (searchdir(path, *dv, name) == 0)
-				continue;
-
-			if (!all) {
-				/* release memory allocated by sortdir() */
-				pdv = dirv;
-				while (*pdv) {
-					free(*pdv);
-					pdv++;
-				}
-				(void) closedir(dp);
-				/* release memory allocated by sortdir() */
-				free(dirv);
-				return;
-			}
-			/*
-			 * if we found a match in the man dir skip
-			 * the corresponding cat dir if it exists
-			 */
-			if (all && **dv == 'm' && *(dv+1) &&
-			    eq(*(dv+1)+plen, *dv+plen))
-					dv++;
-		}
-	}
-	/* release memory allocated by sortdir() */
-	pdv = dirv;
-	while (*pdv) {
-		free(*pdv);
-		pdv++;
-	}
-	free(dirv);
-	(void) closedir(dp);
-}
-
-/*
- * Sort directories.
- */
-
-static void
-sortdir(DIR *dp, char ***dirv)
-{
-	struct dirent *d;
-	char **dv;
-	int	maxentries = MAXDIRS;
-	int	entries = 0;
-
-	*dirv = (char **)malloc(sizeof (char *) * maxentries);
-	dv = *dirv;
-	while ((d = readdir(dp))) {	/* store dirs */
-		if (eq(d->d_name, ".") || eq(d->d_name, ".."))	/* ignore */
-			continue;
-
-		/* check if it matches sman, man, cat format */
-		if (match(d->d_name, SGMLDIR, PLEN+1) ||
-		    match(d->d_name, subdirs[0], PLEN) ||
-		    match(d->d_name, subdirs[1], PLEN)) {
-			*dv = malloc(strlen(d->d_name) + 1);
-			if (*dv == NULL)
-				malloc_error();
-			(void) strcpy(*dv, d->d_name);
-			dv++;
-			entries++;
-			if (entries == maxentries) {
-				maxentries += MAXDIRS;
-				*dirv = (char **)realloc(*dirv,
-				    sizeof (char *) * maxentries);
-				if (*dirv == NULL)
-					malloc_error();
-				dv = *dirv + entries;
-			}
-		}
-	}
-	*dv = 0;
-
-	qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp);
-
-}
-
-
-/*
- * Search a section subdirectory for a
- * given man page, return 1 for success
- */
-
-static int
-searchdir(char *path, char *dir, char *name)
-{
-	DIR *sdp;
-	struct dirent *sd;
-	char sectpath[MAXPATHLEN+1];
-	char file[MAXNAMLEN+1];
-	char dname[MAXPATHLEN+1];
-	char *last;
-	int nlen;
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.   scanning = man3c
- */
-	DPRINTF(gettext("    scanning = %s\n"), dir);
-	(void) sprintf(sectpath, "%s/%s", path, dir);
-	(void) snprintf(file, MAXPATHLEN, "%s.", name);
-
-	if ((sdp = opendir(sectpath)) == 0) {
-		if (errno != ENOTDIR)	/* ignore matching cruft */
-			perror(sectpath);
-		return (0);
-	}
-	while ((sd = readdir(sdp))) {
-		last = strrchr(sd->d_name, '.');
-		nlen = last - sd->d_name;
-		(void) sprintf(dname, "%.*s.", nlen, sd->d_name);
-		if (eq(dname, file) || eq(sd->d_name, name)) {
-			if (no_sroff && *dir == 's') {
-				sman_no_man_no_sroff = 1;
-				return (0);
-			}
-			(void) format(path, dir, name, sd->d_name);
-			(void) closedir(sdp);
-			return (1);
-		}
-	}
-	(void) closedir(sdp);
-	return (0);
-}
-
-/*
- * Check the hash table of old directory names to see if there is a
- * new directory name.
- * Returns new directory name if a match; after checking to be sure
- * directory exists.
- * Otherwise returns NULL
- */
-
-static char *
-map_section(char *section, char *path)
-{
-	int i;
-	int len;
-	char fullpath[MAXPATHLEN];
-
-	if (list)  /* -l option fall through */
-		return (NULL);
-
-	for (i = 0; i <= ((sizeof (map)/sizeof (map[0]) - 1)); i++) {
-		if (strlen(section) > strlen(map[i].new_name)) {
-			len = strlen(section);
-		} else {
-			len = strlen(map[i].new_name);
-		}
-		if (match(section, map[i].old_name, len)) {
-			(void) sprintf(fullpath,
-			    "%s/sman%s", path, map[i].new_name);
-			if (!access(fullpath, R_OK | X_OK)) {
-				return (map[i].new_name);
-			} else {
-				return (NULL);
-			}
-		}
-	}
-
-	return (NULL);
-}
-
-
-/*
- * Use windex database for quick lookup of man pages
- * instead of mandir() (brute force search)
- */
-
-static int
-windex(char **secv, char *path, char *name)
-{
-	FILE *fp;
-	struct stat sbuf;
-	struct suffix *sp;
-	struct suffix	psecs[MAXPAGES+1];
-	char whatfile[MAXPATHLEN+1];
-	char page[MAXPATHLEN+1];
-	char *matches[MAXPAGES];
-	char *file, *dir;
-	char **sv, **vp;
-	int len, dslen, exist, i;
-	int	found_in_windex = 0;
-	char *tmp[] = {0, 0, 0, 0};
-
-
-	(void) sprintf(whatfile, "%s/%s", path, WHATIS);
-	if ((fp = fopen(whatfile, "r")) == NULL) {
-		if (errno == ENOENT)
-			return (-1);
-		return (0);
-	}
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. search in = /usr/share/man/ja/windex file
- */
-	if (debug)
-		(void) fprintf(stdout, gettext(
-		    " search in = %s file\n"), whatfile);
-
-	if (bfsearch(fp, matches, name, NULL) == 0) {
-		(void) fclose(fp);
-		return (-1); /* force search in mandir */
-	}
-
-	(void) fclose(fp);
-
-	/*
-	 * Save and split sections
-	 * section() allocates memory for sp->ds
-	 */
-	for (sp = psecs, vp = matches; *vp; vp++, sp++) {
-		if ((sp - psecs) < MAXPAGES) {
-			section(sp, *vp);
-		} else {
-			if (debug)
-				(void) fprintf(stderr, gettext(
-				    "too many sections in %s windex entry\n"),
-				    name);
-
-			/* Setting sp->ds to NULL signifies end-of-data. */
-			sp->ds = 0;
-			goto finish;
-		}
-	}
-
-	sp->ds = 0;
-
-	/*
-	 * Search in the order specified
-	 * by MANSECTS
-	 */
-	for (; *secv; secv++) {
-		len = strlen(*secv);
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  search an entry to match printf.3c
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "  search an entry to match %s.%s\n"), name, *secv);
-		/*
-		 * For every whatis entry that
-		 * was matched
-		 */
-		for (sp = psecs; sp->ds; sp++) {
-			dslen = strlen(sp->ds);
-			if (dslen > len)
-				len = dslen;
-			if (**secv == '\\') {
-				if (!eq(*secv + 1, sp->ds))
-					continue;
-			} else if (!match(*secv, sp->ds, len)) {
-				/* check to see if directory name changed */
-				if (!all &&
-				    (newsection = map_section(*secv, path))
-				    == NULL) {
-					continue;
-				}
-				if (newsection == NULL)
-					newsection = "";
-				if (!match(newsection, sp->ds, len)) {
-					continue;
-				}
-			}
-			/*
-			 * here to form "sman", "man", "cat"|"fmt" in
-			 * order
-			 */
-			if (!no_sroff) {
-				tmp[0] = SGMLDIR;
-				for (i = 1; i < 4; i++)
-					tmp[i] = subdirs[i-1];
-			} else {
-				for (i = 0; i < 3; i++)
-					tmp[i] = subdirs[i];
-			}
-
-			for (sv = tmp; *sv; sv++) {
-				(void) sprintf(page,
-				    "%s/%s%s/%s%s%s", path, *sv,
-				    sp->ds, name, *sp->fs ? "." : "",
-				    sp->fs);
-				exist = (stat(page, &sbuf) == 0);
-				if (exist)
-					break;
-			}
-			if (!exist) {
-				(void) fprintf(stderr, gettext(
-				    "%s entry incorrect:  %s(%s) not found.\n"),
-				    WHATIS, name, sp->ds);
-				continue;
-			}
-
-			file = strrchr(page, '/'), *file = 0;
-			dir = strrchr(page, '/');
-
-			/*
-			 * By now we have a match
-			 */
-			found_in_windex = 1;
-			(void) format(path, ++dir, name, ++file);
-
-			if (!all)
-				goto finish;
-		}
-	}
-finish:
-	/*
-	 * release memory allocated by section()
-	 */
-	sp = psecs;
-	while (sp->ds) {
-		free(sp->ds);
-		sp->ds = NULL;
-		sp++;
-	}
-
-	/*
-	 * If we didn't find a match, return failure as if we didn't find
-	 * the windex at all. Why? Well, if you create a windex, then upgrade
-	 * to a later release that contains new man pages, and forget to
-	 * recreate the windex (since we don't do that automatically), you
-	 * won't see any new man pages since they aren't in the windex.
-	 * Pretending we didn't see a windex at all if there are no matches
-	 * forces a search of the underlying directory. After all, the
-	 * goal of the windex is to enable searches (man -k) and speed things
-	 * up, not to _prevent_ you from seeing new man pages, so this seems
-	 * ok. The only problem is when there are multiple entries (different
-	 * sections), and some are in and some are out. Say you do 'man ls',
-	 * and ls(1) isn't in the windex, but ls(1B) is. In that case, we
-	 * will find a match in ls(1B), and you'll see that man page.
-	 * That doesn't seem bad since if you specify the section the search
-	 * will be restricted too. So in the example above, if you do
-	 * 'man -s 1 ls' you'll get ls(1).
-	 */
-	if (found_in_windex)
-		return (0);
-	else
-		return (-1);
-}
-
-
-/*
- * Return pointers to the section-spec
- * and file-suffix of a whatis entry
- */
-
-static void
-section(struct suffix *sp, char *s)
-{
-	char *lp, *p;
-
-	lp = strchr(s, '(');
-	p = strchr(s, ')');
-
-	if (++lp == 0 || p == 0 || lp == p) {
-		(void) fprintf(stderr,
-		    gettext("mangled windex entry:\n\t%s\n"), s);
-		return;
-	}
-	*p = 0;
-
-	/*
-	 * copy the string pointed to by lp
-	 */
-	lp = strdup(lp);
-	if (lp == NULL)
-		malloc_error();
-	/*
-	 * release memory in s
-	 * s has been allocated memory in bfsearch()
-	 */
-	free(s);
-
-	lower(lp);
-
-	/*
-	 * split section-specifier if file-name
-	 * suffix differs from section-suffix
-	 */
-	sp->ds = lp;
-	if ((p = strchr(lp, '/'))) {
-		*p++ = 0;
-		sp->fs = p;
-	} else
-		sp->fs = lp;
-}
-
-
-/*
- * Binary file search to find matching man
- *   pages in whatis database.
- */
-
-static int
-bfsearch(FILE *fp, char **matchv, char *key, char **secv)
-{
-	char entry[BUFSIZ];
-	char **vp;
-	long top, bot, mid;
-	int	c;
-
-	vp = matchv;
-	bot = 0;
-	(void) fseek(fp, 0L, 2);
-	top = ftell(fp);
-	for (;;) {
-		mid = (top+bot)/2;
-		(void) fseek(fp, mid, 0);
-		do {
-			c = getc(fp);
-			mid++;
-		} while (c != EOF && c != '\n');
-		if (fgets(entry, sizeof (entry), fp) == NULL)
-			break;
-		switch (compare(key, entry, secv)) {
-		case -2:
-		case -1:
-		case 0:
-			if (top <= mid)
-				break;
-			top = mid;
-			continue;
-		case 1:
-		case 2:
-			bot = mid;
-			continue;
-		}
-		break;
-	}
-	(void) fseek(fp, bot, 0);
-	while (ftell(fp) < top) {
-		if (fgets(entry, sizeof (entry), fp) == NULL) {
-			*matchv = 0;
-			return (matchv - vp);
-		}
-		switch (compare(key, entry, secv)) {
-		case -2:
-			*matchv = 0;
-			return (matchv - vp);
-		case -1:
-		case 0:
-			*matchv = strdup(entry);
-			if (*matchv == NULL)
-				malloc_error();
-			else
-				matchv++;
-			break;
-		case 1:
-		case 2:
-			continue;
-		}
-		break;
-	}
-	while (fgets(entry, sizeof (entry), fp)) {
-		switch (compare(key, entry, secv)) {
-		case -1:
-		case 0:
-			*matchv = strdup(entry);
-			if (*matchv == NULL)
-				malloc_error();
-			else
-				matchv++;
-			continue;
-		}
-		break;
-	}
-	*matchv = 0;
-	return (matchv - vp);
-}
-
-static int
-compare(char *key, char *entry, char **secv)
-{
-	char	*entbuf;
-	char	*s;
-	int	comp, mlen;
-	int	mbcurmax = MB_CUR_MAX;
-	char 	*secp = NULL;
-	int	rv;
-	int	eblen;
-
-	entbuf = strdup(entry);
-	if (entbuf == NULL) {
-		malloc_error();
-	}
-	eblen = strlen(entbuf);
-
-	s = entbuf;
-	while (*s) {
-		if (*s == '\t' || *s == ' ') {
-			*s = '\0';
-			break;
-		}
-		mlen = mblen(s, mbcurmax);
-		if (mlen == -1) {
-			(void) fprintf(stderr, gettext(
-			    "Invalid character in windex file.\n"));
-			exit(1);
-		}
-		s += mlen;
-	}
-	/*
-	 * Find the section within parantheses
-	 */
-	if (secv != NULL && (s - entbuf) < eblen) {
-		if ((secp = strchr(s + 1, ')')) != NULL) {
-			*secp = '\0';
-			if ((secp = strchr(s + 1, '(')) != NULL) {
-				secp++;
-			}
-		}
-	}
-
-	comp = strcmp(key, entbuf);
-	if (comp == 0) {
-		if (secp == NULL) {
-			rv = 0;
-		} else {
-			while (*secv != NULL) {
-				if ((strcmp(*secv, secp)) == 0) {
-					rv = 0;
-					break;
-				}
-				secv++;
-			}
-		}
-	} else if (comp < 0) {
-		rv = -2;
-	} else {
-		rv = 2;
-	}
-	free(entbuf);
-	return (rv);
-}
-
-
-/*
- * Format a man page and follow .so references
- * if necessary.
- */
-
-static int
-format(char *path, char *dir, char *name, char *pg)
-{
-	char manpname[MAXPATHLEN+1], catpname[MAXPATHLEN+1];
-	char manpname_sgml[MAXPATHLEN+1], smantmpname[MAXPATHLEN+1];
-	char soed[MAXPATHLEN+1], soref[MAXPATHLEN+1];
-	char manbuf[BUFSIZ], cmdbuf[BUFSIZ], tmpbuf[BUFSIZ];
-	char tmpdir[MAXPATHLEN+1];
-	int socount, updatedcat, regencat;
-	struct stat mansb, catsb, smansb;
-	char *tmpname;
-	int catonly = 0;
-	struct stat statb;
-	int plen = PLEN;
-	FILE *md;
-	int tempfd;
-	ssize_t	count;
-	int	temp, sgml_flag = 0, check_flag = 0;
-	char prntbuf[BUFSIZ + 1];
-	char *ptr;
-	char *new_m;
-	char	*tmpsubdir;
-
-	found++;
-
-	if (*dir != 'm' && *dir != 's')
-		catonly++;
-
-
-	if (*dir == 's') {
-		tmpsubdir = SGMLDIR;
-		++plen;
-		(void) sprintf(manpname_sgml, "%s/man%s/%s",
-		    path, dir+plen, pg);
-	} else
-		tmpsubdir = MANDIRNAME;
-
-	if (list) {
-		(void) printf(gettext("%s (%s)\t-M %s\n"),
-		    name, dir+plen, path);
-		return (-1);
-	}
-
-	(void) sprintf(manpname, "%s/%s%s/%s", path, tmpsubdir, dir+plen, pg);
-	(void) sprintf(catpname, "%s/%s%s/%s", path, subdirs[1], dir+plen, pg);
-
-	(void) sprintf(smantmpname, "%s/%s%s/%s", path, SGMLDIR, dir+plen, pg);
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  unformatted = /usr/share/man/ja/man3s/printf.3s
- */
-	DPRINTF(gettext(
-	    "      unformatted = %s\n"), catonly ? "" : manpname);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  formatted = /usr/share/man/ja/cat3s/printf.3s
- */
-	DPRINTF(gettext(
-	    "      formatted = %s\n"), catpname);
-
-	/*
-	 * Take care of indirect references to other man pages;
-	 * i.e., resolve files containing only ".so manx/file.x".
-	 * We follow .so chains, replacing title with the .so'ed
-	 * file at each stage, and keeping track of how many times
-	 * we've done so, so that we can avoid looping.
-	 */
-	*soed = 0;
-	socount = 0;
-	for (;;) {
-		FILE *md;
-		char *cp;
-		char *s;
-		char *new_s;
-
-		if (catonly)
-			break;
-		/*
-		 * Grab manpname's first line, stashing it in manbuf.
-		 */
-
-
-		if ((md = fopen(manpname, "r")) == NULL) {
-			if (*soed && errno == ENOENT) {
-				(void) fprintf(stderr,
-				    gettext("Can't find referent of "
-				    ".so in %s\n"), soed);
-				(void) fflush(stderr);
-				return (-1);
-			}
-			perror(manpname);
-			return (-1);
-		}
-
-		/*
-		 * If this is a directory, just ignore it.
-		 */
-		if (fstat(fileno(md), &statb) == NULL) {
-			if (S_ISDIR(statb.st_mode)) {
-				if (debug) {
-					(void) fprintf(stderr,
-					    "\tignoring directory %s\n",
-					    manpname);
-					(void) fflush(stderr);
-				}
-				(void) fclose(md);
-				return (-1);
-			}
-		}
-
-		if (fgets(manbuf, BUFSIZ-1, md) == NULL) {
-			(void) fclose(md);
-			(void) fprintf(stderr, gettext("%s: null file\n"),
-			    manpname);
-			(void) fflush(stderr);
-			return (-1);
-		}
-		(void) fclose(md);
-
-		if (strncmp(manbuf, DOT_SO, sizeof (DOT_SO) - 1))
-			break;
-so_again:	if (++socount > SOLIMIT) {
-			(void) fprintf(stderr, gettext(".so chain too long\n"));
-			(void) fflush(stderr);
-			return (-1);
-		}
-		s = manbuf + sizeof (DOT_SO) - 1;
-		if ((check_flag == 1) && ((new_s = strrchr(s, '/')) != NULL)) {
-				new_s++;
-				(void) sprintf(s, "%s%s/%s",
-				    tmpsubdir, dir+plen, new_s);
-		}
-
-		cp = strrchr(s, '\n');
-		if (cp)
-			*cp = '\0';
-		/*
-		 * Compensate for sloppy typists by stripping
-		 * trailing white space.
-		 */
-		cp = s + strlen(s);
-		while (--cp >= s && (*cp == ' ' || *cp == '\t'))
-			*cp = '\0';
-
-		/*
-		 * Go off and find the next link in the chain.
-		 */
-		(void) strcpy(soed, manpname);
-		(void) strcpy(soref, s);
-		(void) sprintf(manpname, "%s/%s", path, s);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  .so ref = man3c/string.3c
- */
-		DPRINTF(gettext(".so ref = %s\n"), s);
-	}
-
-	/*
-	 * Make symlinks if so'ed and cattin'
-	 */
-	if (socount && catmando) {
-		(void) sprintf(cmdbuf, "cd %s; rm -f %s; ln -s ../%s%s %s",
-		    path, catpname, subdirs[1], soref+plen, catpname);
-		(void) sys(cmdbuf);
-		return (1);
-	}
-
-	/*
-	 * Obtain the cat page that corresponds to the man page.
-	 * If it already exists, is up to date, and if we haven't
-	 * been told not to use it, use it as it stands.
-	 */
-	regencat = updatedcat = 0;
-	if (compargs || (!catonly && stat(manpname, &mansb) >= 0 &&
-	    (stat(catpname, &catsb) < 0 || catsb.st_mtime < mansb.st_mtime)) ||
-	    (access(catpname, R_OK) != 0)) {
-		/*
-		 * Construct a shell command line for formatting manpname.
-		 * The resulting file goes initially into /tmp.  If possible,
-		 * it will later be moved to catpname.
-		 */
-
-		int pipestage = 0;
-		int needcol = 0;
-		char *cbp = cmdbuf;
-
-		regencat = updatedcat = 1;
-
-		if (!catmando && !debug && !check_flag) {
-			(void) fprintf(stderr, gettext(
-			    "Reformatting page.  Please Wait..."));
-			if (sargs && (newsection != NULL) &&
-			    (*newsection != '\0')) {
-				(void) fprintf(stderr, gettext(
-				    "\nThe directory name has been changed "
-				    "to %s\n"), newsection);
-			}
-			(void) fflush(stderr);
-		}
-
-		/*
-		 * in catman command, if the file exists in sman dir already,
-		 * don't need to convert the file in man dir to cat dir
-		 */
-
-		if (!no_sroff && catmando &&
-		    match(tmpsubdir, MANDIRNAME, PLEN) &&
-		    stat(smantmpname, &smansb) >= 0)
-			return (1);
-
-		/*
-		 * cd to path so that relative .so commands will work
-		 * correctly
-		 */
-		(void) sprintf(cbp, "cd %s; ", path);
-		cbp += strlen(cbp);
-
-
-		/*
-		 * check to see whether it is a sgml file
-		 * assume sgml symbol(>!DOCTYPE) can be found in the first
-		 * BUFSIZ bytes
-		 */
-
-		if ((temp = open(manpname, 0)) == -1) {
-				perror(manpname);
-				return (-1);
-		}
-
-		if ((count = read(temp, prntbuf, BUFSIZ)) <= 0) {
-				perror(manpname);
-				return (-1);
-		}
-
-		prntbuf[count] = '\0';	/* null terminate */
-		ptr = prntbuf;
-		if (sgmlcheck((const char *)ptr) == 1) {
-			sgml_flag = 1;
-			if (defaultmandir && *localedir) {
-				(void) sprintf(cbp, "LC_MESSAGES=C %s %s ",
-				    SROFF_CMD, manpname);
-			} else {
-				(void) sprintf(cbp, "%s %s ",
-				    SROFF_CMD, manpname);
-			}
-			cbp += strlen(cbp);
-		} else if (*dir == 's') {
-			(void) close(temp);
-			return (-1);
-		}
-		(void) close(temp);
-
-		/*
-		 * Check for special formatting requirements by examining
-		 * manpname's first line preprocessor specifications.
-		 */
-
-		if (strncmp(manbuf, PREPROC_SPEC,
-		    sizeof (PREPROC_SPEC) - 1) == 0) {
-			char *ptp;
-
-			ptp = manbuf + sizeof (PREPROC_SPEC) - 1;
-			while (*ptp && *ptp != '\n') {
-				const struct preprocessor *pp;
-
-				/*
-				 * Check for a preprocessor we know about.
-				 */
-				for (pp = preprocessors; pp->p_tag; pp++) {
-					if (pp->p_tag == *ptp)
-						break;
-				}
-				if (pp->p_tag == 0) {
-					(void) fprintf(stderr,
-					    gettext("unknown preprocessor "
-					    "specifier %c\n"), *ptp);
-					(void) fflush(stderr);
-					return (-1);
-				}
-
-				/*
-				 * Add it to the pipeline.
-				 */
-				(void) sprintf(cbp, "%s %s |",
-				    troffit ? pp->p_troff : pp->p_nroff,
-				    pipestage++ == 0 ? manpname :
-				    pp->p_stdin_char);
-				cbp += strlen(cbp);
-
-				/*
-				 * Special treatment: if tbl is among the
-				 * preprocessors and we'll process with
-				 * nroff, we have to pass things through
-				 * col at the end of the pipeline.
-				 */
-				if (pp->p_tag == 't' && !troffit)
-					needcol++;
-
-				ptp++;
-			}
-		}
-
-		/*
-		 * if catman, use the cat page name
-		 * otherwise, dup template and create another
-		 * (needed for multiple pages)
-		 */
-		if (catmando)
-			tmpname = catpname;
-		else {
-			tmpname = strdup(TEMPLATE);
-			if (tmpname == NULL)
-				malloc_error();
-			(void) close(mkstemp(tmpname));
-		}
-
-		if (! Tflag) {
-			if (*localedir != '\0') {
-				(void) sprintf(macros, "%s/%s", path, MACROF);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  locale macros = /usr/share/man/ja/tmac.an
- */
-				if (debug)
-					(void) printf(gettext(
-					    "\nlocale macros = %s "),
-					    macros);
-				if (stat(macros, &statb) < 0)
-					(void) strcpy(macros, TMAC_AN);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  macros = /usr/share/man/ja/tman.an
- */
-				if (debug)
-					(void) printf(gettext(
-					    "\nmacros = %s\n"),
-					    macros);
-			}
-		}
-
-		tmpdir[0] = '\0';
-		if (sgml_flag == 1) {
-			if (check_flag == 0) {
-				strcpy(tmpdir, "/tmp/sman_XXXXXX");
-				if ((tempfd = mkstemp(tmpdir)) == -1) {
-					(void) fprintf(stderr, gettext(
-					    "%s: null file\n"), tmpdir);
-					(void) fflush(stderr);
-					return (-1);
-				}
-
-				if (debug)
-					close(tempfd);
-
-				(void) sprintf(tmpbuf, "%s > %s",
-				    cmdbuf, tmpdir);
-				if (sys(tmpbuf)) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * Error message if sys(%s) failed
- */
-					(void) fprintf(stderr, gettext(
-					    "sys(%s) fail!\n"), tmpbuf);
-					(void) fprintf(stderr,
-					    gettext(" aborted (sorry)\n"));
-					(void) fflush(stderr);
-					/* release memory for tmpname */
-					if (!catmando) {
-						(void) unlink(tmpdir);
-						(void) unlink(tmpname);
-						free(tmpname);
-					}
-					return (-1);
-				} else if (debug == 0) {
-					if ((md = fdopen(tempfd, "r"))
-					    == NULL) {
-						(void) fprintf(stderr, gettext(
-						    "%s: null file\n"), tmpdir);
-						(void) fflush(stderr);
-						close(tempfd);
-						/* release memory for tmpname */
-						if (!catmando)
-							free(tmpname);
-						return (-1);
-					}
-
-					/* if the file is empty, */
-					/* it's a fragment, do nothing */
-					if (fgets(manbuf, BUFSIZ-1, md)
-					    == NULL) {
-						(void) fclose(md);
-						/* release memory for tmpname */
-						if (!catmando)
-							free(tmpname);
-						return (1);
-					}
-					(void) fclose(md);
-
-					if (strncmp(manbuf, DOT_SO,
-					    sizeof (DOT_SO) - 1) == 0) {
-						if (!compargs) {
-						check_flag = 1;
-						(void) unlink(tmpdir);
-						(void) unlink(tmpname);
-						/* release memory for tmpname */
-						if (!catmando)
-							free(tmpname);
-						goto so_again;
-						} else {
-							(void) unlink(tmpdir);
-						strcpy(tmpdir,
-						    "/tmp/sman_XXXXXX");
-						tempfd = mkstemp(tmpdir);
-						if ((tempfd == -1) ||
-						    (md = fdopen(tempfd, "w"))
-						    == NULL) {
-							(void) fprintf(stderr,
-							    gettext(
-							    "%s: null file\n"),
-							    tmpdir);
-							(void) fflush(stderr);
-							if (tempfd != -1)
-								close(tempfd);
-						/* release memory for tmpname */
-							if (!catmando)
-								free(tmpname);
-							return (-1);
-						}
-				if ((new_m = strrchr(manbuf, '/')) != NULL) {
-		(void) fprintf(md, ".so man%s%s\n", dir+plen, new_m);
-							} else {
-/*
- * TRANSLATION_NOTE - message for catman -c
- * Error message if unable to get file name
- */
-				(void) fprintf(stderr,
-				    gettext("file not found\n"));
-				(void) fflush(stderr);
-				return (-1);
-				}
-							(void) fclose(md);
-						}
-					}
-				}
-				if (catmando && compargs)
-					(void) sprintf(cmdbuf, "cat %s > %s",
-					    tmpdir, manpname_sgml);
-				else
-	(void) sprintf(cmdbuf, " cat %s | tbl | eqn | %s %s - %s > %s",
-	    tmpdir, troffit ? troffcmd : "nroff -u0 -Tlp",
-	    macros, troffit ? "" : " | col -x", tmpname);
-			} else
-				if (catmando && compargs)
-					(void) sprintf(cbp, " > %s",
-					    manpname_sgml);
-				else
-	(void) sprintf(cbp, " | tbl | eqn | %s %s - %s > %s",
-	    troffit ? troffcmd : "nroff -u0 -Tlp",
-	    macros, troffit ? "" : " | col -x", tmpname);
-
-		} else
-	(void) sprintf(cbp, "%s %s %s%s > %s",
-	    troffit ? troffcmd : "nroff -u0 -Tlp",
-	    macros, pipestage == 0 ? manpname : "-",
-	    troffit ? "" : " | col -x", tmpname);
-
-		/* Reformat the page. */
-		if (sys(cmdbuf)) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * Error message if sys(%s) failed
- */
-			(void) fprintf(stderr, gettext(
-			    "sys(%s) fail!\n"), cmdbuf);
-			(void) fprintf(stderr, gettext(" aborted (sorry)\n"));
-			(void) fflush(stderr);
-			(void) unlink(tmpname);
-			/* release memory for tmpname */
-			if (!catmando)
-				free(tmpname);
-			return (-1);
-		}
-
-		if (tmpdir[0] != '\0')
-			(void) unlink(tmpdir);
-
-		if (catmando)
-			return (1);
-
-		/*
-		 * Attempt to move the cat page to its proper home.
-		 */
-		(void) sprintf(cmdbuf,
-		    "trap '' 1 15; /usr/bin/mv -f %s %s 2> /dev/null",
-		    tmpname,
-		    catpname);
-		if (sys(cmdbuf))
-			updatedcat = 0;
-		else if (debug == 0)
-			(void) chmod(catpname, 0644);
-
-		if (debug) {
-			/* release memory for tmpname */
-			if (!catmando)
-				free(tmpname);
-			(void) unlink(tmpname);
-			return (1);
-		}
-
-		(void) fprintf(stderr, gettext(" done\n"));
-		(void) fflush(stderr);
-	}
-
-	/*
-	 * Save file name (dup if necessary)
-	 * to view later
-	 * fix for 1123802 - don't save names if we are invoked as catman
-	 */
-	if (!catmando) {
-		char	**tmpp;
-		int	dup;
-		char	*newpage;
-
-		if (regencat && !updatedcat)
-			newpage = tmpname;
-		else {
-			newpage = strdup(catpname);
-			if (newpage == NULL)
-				malloc_error();
-		}
-		/* make sure we don't add a dup */
-		dup = 0;
-		for (tmpp = pages; tmpp < endp; tmpp++) {
-			if (strcmp(*tmpp, newpage) == 0) {
-				dup = 1;
-				break;
-			}
-		}
-		if (!dup)
-			*endp++ = newpage;
-		if (endp >= &pages[MAXPAGES]) {
-			fprintf(stderr,
-			    gettext("Internal pages array overflow!\n"));
-			exit(1);
-		}
-	}
-
-	return (regencat);
-}
-
-/*
- * Add <localedir> to the path.
- */
-
-static char *
-addlocale(char *path)
-{
-
-	char *tmp;
-
-	tmp = malloc(strlen(path) + strlen(localedir) + 2);
-	if (tmp == NULL)
-		malloc_error();
-	(void) sprintf(tmp, "%s/%s", path, localedir);
-	return (tmp);
-
-}
-
-/*
- * From the configuration file "man.cf", get the order of suffices of
- * sub-mandirs to be used in the search path for a given mandir.
- */
-
-static char *
-check_config(char *path)
-{
-	FILE *fp;
-	static char submandir[BUFSIZ];
-	char *sect;
-	char fname[MAXPATHLEN];
-
-	(void) sprintf(fname, "%s/%s", path, CONFIG);
-
-	if ((fp = fopen(fname, "r")) == NULL)
-		return (NULL);
-	else {
-		if (get_manconfig(fp, submandir) == -1) {
-			(void) fclose(fp);
-			return (NULL);
-		}
-
-		(void) fclose(fp);
-
-		sect = strchr(submandir, '=');
-		if (sect != NULL)
-			return (++sect);
-		else
-			return (NULL);
-	}
-}
-
-/*
- *  This routine is for getting the MANSECTS entry from man.cf.
- *  It sets submandir to the line in man.cf that contains
- *	MANSECTS=sections[,sections]...
- */
-
-static int
-get_manconfig(FILE *fp, char *submandir)
-{
-	char *s, *t, *rc;
-	char buf[BUFSIZ];
-
-	while ((rc = fgets(buf, sizeof (buf), fp)) != NULL) {
-
-		/*
-		 * skip leading blanks
-		 */
-		for (t = buf; *t != '\0'; t++) {
-			if (!isspace(*t))
-				break;
-		}
-		/*
-		 * skip line that starts with '#' or empty line
-		 */
-		if (*t == '#' || *t == '\0')
-			continue;
-
-		if (strstr(buf, "MANSECTS") != NULL)
-			break;
-	}
-
-	/*
-	 * the man.cf file doesn't have a MANSECTS entry
-	 */
-	if (rc == NULL)
-		return (-1);
-
-	s = strchr(buf, '\n');
-	*s = '\0';	/* replace '\n' with '\0' */
-
-	(void) strcpy(submandir, buf);
-	return (0);
-}
-
-static void
-malloc_error(void)
-{
-	(void) fprintf(stderr, gettext(
-	    "Memory allocation failed.\n"));
-	exit(1);
-}
-
-static int
-sgmlcheck(const char *s1)
-{
-	const char	*s2 = SGML_SYMBOL;
-	int	len;
-
-	while (*s1) {
-		/*
-		 * Assume the first character of SGML_SYMBOL(*s2) is '<'.
-		 * Therefore, not necessary to do toupper(*s1) here.
-		 */
-		if (*s1 == *s2) {
-			/*
-			 * *s1 is '<'.  Check the following substring matches
-			 * with "!DOCTYPE".
-			 */
-			s1++;
-			if (strncasecmp(s1, s2 + 1, SGML_SYMBOL_LEN - 1)
-			    == 0) {
-				/*
-				 * SGML_SYMBOL found
-				 */
-				return (1);
-			}
-			continue;
-		} else if (isascii(*s1)) {
-			/*
-			 * *s1 is an ASCII char
-			 * Skip one character
-			 */
-			s1++;
-			continue;
-		} else {
-			/*
-			 * *s1 is a non-ASCII char or
-			 * the first byte of the multibyte char.
-			 * Skip one character
-			 */
-			len = mblen(s1, MB_CUR_MAX);
-			if (len == -1)
-				len = 1;
-			s1 += len;
-			continue;
-		}
-	}
-	/*
-	 * SGML_SYMBOL not found
-	 */
-	return (0);
-}
-
-/*
- * Initializes the bintoman array with appropriate device and inode info
- */
-
-static void
-init_bintoman(void)
-{
-	int i;
-	struct stat sb;
-
-	for (i = 0; bintoman[i].bindir != NULL; i++) {
-		if (stat(bintoman[i].bindir, &sb) == 0) {
-			bintoman[i].dev = sb.st_dev;
-			bintoman[i].ino = sb.st_ino;
-		} else {
-			bintoman[i].dev = NODEV;
-		}
-	}
-}
-
-/*
- * If a duplicate is found, return 1
- * If a duplicate is not found, add it to the dupnode list and return 0
- */
-static int
-dupcheck(struct man_node *mnp, struct dupnode **dnp)
-{
-	struct dupnode	*curdnp;
-	struct secnode	*cursnp;
-	struct stat 	sb;
-	int 		i;
-	int		rv = 1;
-	int		dupfound;
-
-	/*
-	 * If the path doesn't exist, treat it as a duplicate
-	 */
-	if (stat(mnp->path, &sb) != 0) {
-		return (1);
-	}
-
-	/*
-	 * If no sections were found in the man dir, treat it as duplicate
-	 */
-	if (mnp->secv == NULL) {
-		return (1);
-	}
-
-	/*
-	 * Find the dupnode structure for the previous time this directory
-	 * was looked at.  Device and inode numbers are compared so that
-	 * directories that are reached via different paths (e.g. /usr/man vs.
-	 * /usr/share/man) are treated as equivalent.
-	 */
-	for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) {
-		if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino) {
-			break;
-		}
-	}
-
-	/*
-	 * First time this directory has been seen.  Add a new node to the
-	 * head of the list.  Since all entries are guaranteed to be unique
-	 * copy all sections to new node.
-	 */
-	if (curdnp == NULL) {
-		if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL) {
-			malloc_error();
-		}
-		for (i = 0; mnp->secv[i] != NULL; i++) {
-			if ((cursnp = calloc(1, sizeof (struct secnode)))
-			    == NULL) {
-				malloc_error();
-			}
-			cursnp->next = curdnp->secl;
-			curdnp->secl = cursnp;
-			if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) {
-				malloc_error();
-			}
-		}
-		curdnp->dev = sb.st_dev;
-		curdnp->ino = sb.st_ino;
-		curdnp->next = *dnp;
-		*dnp = curdnp;
-		return (0);
-	}
-
-	/*
-	 * Traverse the section vector in the man_node and the section list
-	 * in dupnode cache to eliminate all duplicates from man_node
-	 */
-	for (i = 0; mnp->secv[i] != NULL; i++) {
-		dupfound = 0;
-		for (cursnp = curdnp->secl; cursnp != NULL;
-		    cursnp = cursnp->next) {
-			if (strcmp(mnp->secv[i], cursnp->secp) == 0) {
-				dupfound = 1;
-				break;
-			}
-		}
-		if (dupfound) {
-			mnp->secv[i][0] = '\0';
-			continue;
-		}
-
-
-		/*
-		 * Update curdnp and set return value to indicate that this
-		 * was not all duplicates.
-		 */
-		if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) {
-			malloc_error();
-		}
-		cursnp->next = curdnp->secl;
-		curdnp->secl = cursnp;
-		if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) {
-			malloc_error();
-		}
-		rv = 0;
-	}
-
-	return (rv);
-}
-
-/*
- * Given a bin directory, return the corresponding man directory.
- * Return string must be free()d by the caller.
- *
- * NULL will be returned if no matching man directory can be found.
- */
-
-static char *
-path_to_manpath(char *bindir)
-{
-	char	*mand, *p;
-	int	i;
-	struct stat	sb;
-
-	/*
-	 * First look for known translations for specific bin paths
-	 */
-	if (stat(bindir, &sb) != 0) {
-		return (NULL);
-	}
-	for (i = 0; bintoman[i].bindir != NULL; i++) {
-		if (sb.st_dev == bintoman[i].dev &&
-		    sb.st_ino == bintoman[i].ino) {
-			if ((mand = strdup(bintoman[i].mandir)) == NULL) {
-				malloc_error();
-			}
-			if ((p = strchr(mand, ',')) != NULL) {
-				*p = '\0';
-			}
-			if (stat(mand, &sb) != 0) {
-				free(mand);
-				return (NULL);
-			}
-			if (p != NULL) {
-				*p = ',';
-			}
-			return (mand);
-		}
-	}
-
-	/*
-	 * No specific translation found.  Try `dirname $bindir`/man
-	 * and `dirname $bindir`/share/man
-	 */
-	if ((mand = malloc(PATH_MAX)) == NULL) {
-		malloc_error();
-	}
-
-	if (strlcpy(mand, bindir, PATH_MAX) >= PATH_MAX) {
-		free(mand);
-		return (NULL);
-	}
-
-	/*
-	 * Advance to end of buffer, strip trailing /'s then remove last
-	 * directory component.
-	 */
-	for (p = mand; *p != '\0'; p++)
-		;
-	for (; p > mand && *p == '/'; p--)
-		;
-	for (; p > mand && *p != '/'; p--)
-		;
-	if (p == mand && *p == '.') {
-		if (realpath("..", mand) == NULL) {
-			free(mand);
-			return (NULL);
-		}
-		for (; *p != '\0'; p++)
-			;
-	} else {
-		*p = '\0';
-	}
-
-	if (strlcat(mand, "/man", PATH_MAX) >= PATH_MAX) {
-		free(mand);
-		return (NULL);
-	}
-
-	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
-		return (mand);
-	}
-
-	/*
-	 * Strip the /man off and try /share/man
-	 */
-	*p = '\0';
-	if (strlcat(mand, "/share/man", PATH_MAX) >= PATH_MAX) {
-		free(mand);
-		return (NULL);
-	}
-	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
-		return (mand);
-	}
-
-	/*
-	 * No man or share/man directory found
-	 */
-	free(mand);
-	return (NULL);
-}
-
-/*
- * Free a linked list of dupnode structs
- */
-void
-free_dupnode(struct dupnode *dnp) {
-	struct dupnode *dnp2;
-	struct secnode *snp;
-
-	while (dnp != NULL) {
-		dnp2 = dnp;
-		dnp = dnp->next;
-		while (dnp2->secl != NULL) {
-			snp = dnp2->secl;
-			dnp2->secl = dnp2->secl->next;
-			free(snp->secp);
-			free(snp);
-		}
-		free(dnp2);
-	}
-}
-
-/*
- * prints manp linked list to stdout.
- *
- * If namep is NULL, output can be used for setting MANPATH.
- *
- * If namep is not NULL output is two columns.  First column is the string
- * pointed to by namep.  Second column is a MANPATH-compatible representation
- * of manp linked list.
- */
-void
-print_manpath(struct man_node *manp, char *namep)
-{
-	char colon[2];
-	char **secp;
-
-	if (namep != NULL) {
-		(void) printf("%s ", namep);
-	}
-
-	colon[0] = '\0';
-	colon[1] = '\0';
-
-	for (; manp != NULL; manp = manp->next) {
-		(void) printf("%s%s", colon, manp->path);
-		colon[0] = ':';
-
-		/*
-		 * If man.cf or a directory scan was used to create section
-		 * list, do not print section list again.  If the output of
-		 * man -p is used to set MANPATH, subsequent runs of man
-		 * will re-read man.cf and/or scan man directories as
-		 * required.
-		 */
-		if (manp->defsrch != 0) {
-			continue;
-		}
-
-		for (secp = manp->secv; *secp != NULL; secp++) {
-			/*
-			 * Section deduplication may have eliminated some
-			 * sections from the vector. Avoid displaying this
-			 * detail which would appear as ",," in output
-			 */
-			if ((*secp)[0] != '\0') {
-				(void) printf(",%s", *secp);
-			}
-		}
-	}
-	(void) printf("\n");
-}
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/man/stringlist.c	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 1994 Christos Zoulas
+ * All rights reserved.
+ *
+ * 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.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "stringlist.h"
+
+#define	_SL_CHUNKSIZE	20
+
+stringlist *
+sl_init(void)
+{
+	stringlist	*sl;
+
+	if ((sl = malloc(sizeof (stringlist))) == NULL)
+		err(1, "malloc");
+
+	sl->sl_cur = 0;
+	sl->sl_max = _SL_CHUNKSIZE;
+	sl->sl_str = malloc(sl->sl_max * sizeof (char *));
+	if (sl->sl_str == NULL)
+		err(1, "malloc");
+
+	return (sl);
+}
+
+int
+sl_add(stringlist *sl, char *name)
+{
+
+	if (sl->sl_cur == sl->sl_max - 1) {
+		sl->sl_max += _SL_CHUNKSIZE;
+		sl->sl_str = realloc(sl->sl_str, sl->sl_max * sizeof (char *));
+		if (sl->sl_str == NULL)
+			return (-1);
+	}
+	sl->sl_str[sl->sl_cur++] = name;
+
+	return (0);
+}
+
+
+void
+sl_free(stringlist *sl, int all)
+{
+	size_t i;
+
+	if (sl == NULL)
+		return;
+	if (sl->sl_str) {
+		if (all)
+			for (i = 0; i < sl->sl_cur; i++)
+				free(sl->sl_str[i]);
+		free(sl->sl_str);
+	}
+	free(sl);
+}
+
+
+char *
+sl_find(stringlist *sl, char *name)
+{
+	size_t	i;
+
+	for (i = 0; i < sl->sl_cur; i++)
+		if (strcmp(sl->sl_str[i], name) == 0)
+			return (sl->sl_str[i]);
+
+	return (NULL);
+}
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/man/stringlist.h	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1994 Christos Zoulas
+ * All rights reserved.
+ *
+ * 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. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Christos Zoulas.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#ifndef _STRINGLIST_H_
+#define	_STRINGLIST_H_
+
+#include <sys/types.h>
+
+typedef struct _stringlist {
+	char	**sl_str;
+	size_t	sl_max;
+	size_t	sl_cur;
+} stringlist;
+
+stringlist	*sl_init(void);
+int		sl_add(stringlist *, char *);
+void		sl_free(stringlist *, int);
+char		*sl_find(stringlist *, char *);
+
+#endif	/* _STRINGLIST_H_ */
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/Makefile	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,47 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+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)
+
+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)
+
+mandoc:		$(mandoc_OBJS)
+		$(LINK.c) $(mandoc_OBJS) -o $@ $(LDLIBS)
+		$(POST_PROCESS)
+
+clean:
+		$(RM) $(OBJS)
+
+install:	all $(ROOTPROGS)
+
+include		$(SRC)/cmd/Makefile.targ
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/Makefile.common	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,33 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+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
+
+preconv_OBJS =	preconv.o
+
+CFLAGS +=	$(CC_VERBOSE)
+
+CPPFLAGS +=	-DHAVE_CONFIG_H -DUSE_WCHAR	\
+		-DOSNAME="\"illumos\""	\
+		-DVERSION="\"1.12.1\""
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/THIRDPARTYLICENSE	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,14 @@
+Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+
+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.
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,1 @@
+MANDOC - FORMAT AND DISPLAY UNIX MANUALS
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/arch.c	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,39 @@
+/*	$Id: arch.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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);
+}
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/arch.in	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,111 @@
+/*	$Id: arch.in,v 1.12 2012/01/28 14:02:17 joerg Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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("aviion",		"AViiON")
+LINE("atari",		"ATARI")
+LINE("beagle",		"Beagle")
+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("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")
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/att.c	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,39 @@
+/*	$Id: att.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+	if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2att(const char *p)
+{
+
+#include "att.in" 
+
+	return(NULL);
+}
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/att.in	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,40 @@
+/*	$Id: att.in,v 1.8 2011/07/31 17:30:33 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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")
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/chars.c	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,167 @@
+/*	$Id: chars.c,v 1.52 2011/11/08 00:15:23 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+
+#define	PRINT_HI	 126
+#define	PRINT_LO	 32
+
+struct	ln {
+	struct ln	 *next;
+	const char	 *code;
+	const char	 *ascii;
+	int		  unicode;
+};
+
+#define	LINES_MAX	  328
+
+#define CHAR(in, ch, code) \
+	{ NULL, (in), (ch), (code) },
+
+#define	CHAR_TBL_START	  static struct ln lines[LINES_MAX] = {
+#define	CHAR_TBL_END	  };
+
+#include "chars.in"
+
+struct	mchars {
+	struct ln	**htab;
+};
+
+static	const struct ln	 *find(const struct mchars *, 
+				const char *, size_t);
+
+void
+mchars_free(struct mchars *arg)
+{
+
+	free(arg->htab);
+	free(arg);
+}
+
+struct mchars *
+mchars_alloc(void)
+{
+	struct mchars	 *tab;
+	struct ln	**htab;
+	struct ln	 *pp;
+	int		  i, hash;
+
+	/*
+	 * Constructs a very basic chaining hashtable.  The hash routine
+	 * is simply the integral value of the first character.
+	 * Subsequent entries are chained in the order they're processed.
+	 */
+
+	tab = mandoc_malloc(sizeof(struct mchars));
+	htab = mandoc_calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln **));
+
+	for (i = 0; i < LINES_MAX; i++) {
+		hash = (int)lines[i].code[0] - PRINT_LO;
+
+		if (NULL == (pp = htab[hash])) {
+			htab[hash] = &lines[i];
+			continue;
+		}
+
+		for ( ; pp->next; pp = pp->next)
+			/* Scan ahead. */ ;
+		pp->next = &lines[i];
+	}
+
+	tab->htab = htab;
+	return(tab);
+}
+
+int
+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);
+}
+
+char
+mchars_num2char(const char *p, size_t sz)
+{
+	int		  i;
+
+	if ((i = mandoc_strntoi(p, sz, 10)) < 0)
+		return('\0');
+	return(i > 0 && i < 256 && isprint(i) ? 
+			/* LINTED */ i : '\0');
+}
+
+int
+mchars_num2uc(const char *p, size_t sz)
+{
+	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');
+}
+
+const char *
+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) {
+		*rsz = 1;
+		return(NULL);
+	}
+
+	*rsz = strlen(ln->ascii);
+	return(ln->ascii);
+}
+
+static const struct ln *
+find(const struct mchars *tab, const char *p, size_t sz)
+{
+	const struct ln	 *pp;
+	int		  hash;
+
+	assert(p);
+
+	if (0 == sz || p[0] < PRINT_LO || p[0] > PRINT_HI)
+		return(NULL);
+
+	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])
+			return(pp);
+
+	return(NULL);
+}
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/chars.in	Fri Jul 18 08:34:32 2014
@@ -0,0 +1,397 @@
+/*	$Id: chars.in,v 1.42 2011/10/02 10:02:26 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+
+/*
+ * 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
+ * side is what's produced by the front-end, with the fourth element
+ * being its length.
+ *
+ * XXX - C-escape strings!
+ * XXX - update LINES_MAX if adding more!
+ */
+
+/* Non-breaking, non-collapsing space uses unit separator. */
+static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\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)
+CHAR("}",			"",		0)
+
+/* Accents. */
+CHAR("a\"",			"\"",		779)
+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("ah",			"v",		711)
+CHAR("ao",			"o",		730)
+CHAR("a~",			"~",		771)
+CHAR("ho",			",",		808)
+CHAR("ha",			"^",		94)
+CHAR("ti",			"~",		126)
+
+/* Quotes. */
+CHAR("Bq",			",,",		8222)
+CHAR("bq",			",",		8218)
+CHAR("lq",			"``",		8220)
+CHAR("rq",			"\'\'",		8221)
+CHAR("oq",			"`",		8216)
+CHAR("cq",			"\'",		8217)
+CHAR("aq",			"\'",		39)
+CHAR("dq",			"\"",		34)
+CHAR("Fo",			"<<",		171)
+CHAR("Fc",			">>",		187)
+CHAR("fo",			"<",		8249)
+CHAR("fc",			">",		8250)
+
+/* Brackets. */
+CHAR("lB",			"[",		91)
+CHAR("rB",			"]",		93)
+CHAR("lC",			"{",		123)
+CHAR("rC",			"}",		125)
+CHAR("la",			"<",		60)
+CHAR("ra",			">",		62)
+CHAR("bv",			"|",		9130)
+CHAR("braceex",			"|",		9130)
+CHAR("bracketlefttp",		"|",		9121)
+CHAR("bracketleftbp",		"|",		9123)
+CHAR("bracketleftex",		"|",		9122)
+CHAR("bracketrighttp",		"|",		9124)
+CHAR("bracketrightbp",		"|",		9126)
+CHAR("bracketrightex",		"|",		9125)
+CHAR("lt",			",-",		9127)
+CHAR("bracelefttp",		",-",		9127)
+CHAR("lk",			"{",		9128)
+CHAR("braceleftmid",		"{",		9128)
+CHAR("lb",			",-",		9129)
+CHAR("braceleftbp",		"`-",		9129)
+CHAR("braceleftex",		"|",		9130)
+CHAR("rt",			"-.",		9131)
+CHAR("bracerighttp",		"-.",		9131)
+CHAR("rk",			"}",		9132)
+CHAR("bracerightmid",		"}",		9132)
+CHAR("rb",			"-\'",		9133)
+CHAR("bracerightbp",		"-\'",		9133)
+CHAR("bracerightex",		"|",		9130)
+CHAR("parenlefttp",		"/",		9115)
+CHAR("parenleftbp",		"\\",		9117)
+CHAR("parenleftex",		"|",		9116)
+CHAR("parenrighttp",		"\\",		9118)
+CHAR("parenrightbp",		"/",		9120)
+CHAR("parenrightex",		"|",		9119)
+
+/* Greek characters. */
+CHAR("*A",			"A",		913)
+CHAR("*B",			"B",		914)
+CHAR("*G",			"|",		915)
+CHAR("*D",			"/\\",		916)
+CHAR("*E",			"E",		917)
+CHAR("*Z",			"Z",		918)
+CHAR("*Y",			"H",		919)
+CHAR("*H",			"O",		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("*O",			"O",		927)
+CHAR("*P",			"TT",		928)
+CHAR("*R",			"P",		929)
+CHAR("*S",			">",		931)
+CHAR("*T",			"T",		932)
+CHAR("*U",			"Y",		933)
+CHAR("*F",			"O_",		934)
+CHAR("*X",			"X",		935)
+CHAR("*Q",			"Y",		936)
+CHAR("*W",			"O",		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("*y",			"n",		951)
+CHAR("*h",			"0",		952)
+CHAR("*i",			"i",		953)
+CHAR("*k",			"k",		954)
+CHAR("*l",			"\\",		955)
+CHAR("*m",			"u",		956)
+CHAR("*n",			"v",		957)
+CHAR("*c",			"E",		958)
+CHAR("*o",			"o",		959)
+CHAR("*p",			"n",		960)
+CHAR("*r",			"p",		961)
+CHAR("*s",			"o",		963)
+CHAR("*t",			"t",		964)
+CHAR("*u",			"u",		965)
+CHAR("*f",			"o",		981)
+CHAR("*x",			"x",		967)
+CHAR("*q",			"u",		968)
+CHAR("*w",			"w",		969)
+CHAR("+h",			"0",		977)
+CHAR("+f",			"o",		966)
+CHAR("+p",			"w",		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)
+
+/* Arrows and lines. */
+CHAR("<-",			"<-",		8592)
+CHAR("->",			"->",		8594)
+CHAR("<>",			"<>",		8596)
+CHAR("da",			"v",		8595)
+CHAR("ua",			"^",		8593)
+CHAR("va",			"^v",		8597)
+CHAR("lA",			"<=",		8656)
+CHAR("rA",			"=>",		8658)
+CHAR("hA",			"<=>",		8660)
+CHAR("dA",			"v",		8659)
+CHAR("uA",			"^",		8657)
+CHAR("vA",			"^=v",		8661)
+
+/* Logic. */
+CHAR("AN",			"^",		8743)
+CHAR("OR",			"v",		8744)
+CHAR("no",			"~",		172)
+CHAR("tno",			"~",		172)
+CHAR("te",			"3",		8707)
+CHAR("fa",			"V",		8704)
+CHAR("st",			"-)",		8715)
+CHAR("tf",			".:.",		8756)
+CHAR("3d",			".:.",		8756)
+CHAR("or",			"|",		124)
+
+/* Mathematicals. */
+CHAR("pl",			"+",		43)
+CHAR("mi",			"-",		8722)
+CHAR("-",			"-",		45)
+CHAR("-+",			"-+",		8723)
+CHAR("+-",			"+-",		177)
+CHAR("t+-",			"+-",		177)
+CHAR("pc",			".",		183)
+CHAR("md",			".",		8901)
+CHAR("mu",			"x",		215)
+CHAR("tmu",			"x",		215)
+CHAR("c*",			"x",		8855)
+CHAR("c+",			"+",		8853)
+CHAR("di",			"-:-",		247)
+CHAR("tdi",			"-:-",		247)
+CHAR("f/",			"/",		8260)
+CHAR("**",			"*",		8727)
+CHAR("<=",			"<=",		8804)
+CHAR(">=",			">=",		8805)
+CHAR("<<",			"<<",		8810)
+CHAR(">>",			">>",		8811)
+CHAR("eq",			"=",		61)
+CHAR("!=",			"!=",		8800)
+CHAR("==",			"==",		8801)
+CHAR("ne",			"!==",		8802)
+CHAR("=~",			"=~",		8773)
+CHAR("-~",			"-~",		8771)
+CHAR("ap",			"~",		8764)
+CHAR("~~",			"~~",		8776)
+CHAR("~=",			"~=",		8780)
+CHAR("pt",			"oc",		8733)
+CHAR("es",			"{}",		8709)
+CHAR("mo",			"E",		8712)
+CHAR("nm",			"!E",		8713)
+CHAR("sb",			"(=",		8834)
+CHAR("nb",			"(!=",		8836)
+CHAR("sp",			"=)",		8835)
+CHAR("nc",			"!=)",		8837)
+CHAR("ib",			"(=",		8838)
+CHAR("ip",			"=)",		8839)
+CHAR("ca",			"(^)",		8745)
+CHAR("cu",			"U",		8746)
+CHAR("/_",			"/_",		8736)
+CHAR("pp",			"_|_",		8869)
+CHAR("is",			"I",		8747)
+CHAR("integral",		"I",		8747)
+CHAR("sum",			"E",		8721)
+CHAR("product",			"TT",		8719)
+CHAR("coproduct",		"U",		8720)
+CHAR("gr",			"V",		8711)
+CHAR("sr",			"\\/",		8730)
+CHAR("sqrt",			"\\/",		8730)
+CHAR("lc",			"|~",		8968)
+CHAR("rc",			"~|",		8969)
+CHAR("lf",			"|_",		8970)
+CHAR("rf",			"_|",		8971)
+CHAR("if",			"oo",		8734)
+CHAR("Ah",			"N",		8501)
+CHAR("Im",			"I",		8465)
+CHAR("Re",			"R",		8476)
+CHAR("pd",			"a",		8706)
+CHAR("-h",			"/h",		8463)
+CHAR("12",			"1/2",		189)
+CHAR("14",			"1/4",		188)
+CHAR("34",			"3/4",		190)
+
+/* Ligatures. */
+CHAR("ff",			"ff",		64256)
+CHAR("fi",			"fi",		64257)
+CHAR("fl",			"fl",		64258)
+CHAR("Fi",			"ffi",		64259)
+CHAR("Fl",			"ffl",		64260)
+CHAR("AE",			"AE",		198)
+CHAR("ae",			"ae",		230)
+CHAR("OE",			"OE",		338)
+CHAR("oe",			"oe",		339)
+CHAR("ss",			"ss",		223)
+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(".i",			"i",		305)
+CHAR(".j",			"j",		567)
+
+/* Currency. */
+CHAR("Do",			"$",		36)
+CHAR("ct",			"c",		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)
+
+/* Lines. */
+CHAR("ba",			"|",		124)
+CHAR("br",			"|",		9474)
+CHAR("ul",			"_",		95)
+CHAR("rl",			"-",		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("lz",			"<>",		9674)
+CHAR("sq",			"[]",		9633)
+CHAR("ps",			"9|",		182)
+CHAR("sc",			"S",		167)
+CHAR("lh",			"<=",		9756)
+CHAR("rh",			"=>",		9758)
+CHAR("at",			"@",		64)
+CHAR("sh",			"#",		35)
+CHAR("CR",			"_|",		8629)
+CHAR("OK",			"\\/",		10003)
+
+/* Legal symbols. */
+CHAR("co",			"(C)",		169)
+CHAR("rg",			"(R)",		174)
+CHAR("tm",			"tm",		8482)
+
+/* Punctuation. */
+CHAR(".",			".",		46)
+CHAR("r!",			"i",		161)
+CHAR("r?",			"c",		191)
+CHAR("em",			"--",		8212)
+CHAR("en",			"-",		8211)
+CHAR("hy",			"-",		8208)
+CHAR("e",			"\\",		92)
+
+/* Units. */
+CHAR("de",			"o",		176)
+CHAR("%0",			"%o",		8240)
+CHAR("fm",			"\'",		8242)
+CHAR("sd",			"\"",		8243)
+CHAR("mc",			"mu",		181)
+
+CHAR_TBL_END
--- /dev/null	Fri Jul 18 08:34:32 2014
+++ new/usr/src/cmd/mandoc/config.h	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,56 @@
+#ifndef	MANDOC_CONFIG_H
+#define	MANDOC_CONFIG_H
+
+#if defined(__linux__) || defined(__MINT__)
+# define _GNU_SOURCE /* strptime(), getsubopt() */
+#endif
+
+#include <stdio.h>
+
+#define HAVE_STRPTIME
+#define HAVE_GETSUBOPT
+#define HAVE_STRLCAT
+#define HAVE_STRLCPY
+
+#include <sys/types.h>
+
+#if !defined(__BEGIN_DECLS)
+#  ifdef __cplusplus
+#  define	__BEGIN_DECLS		extern "C" {
+#  else
+#  define	__BEGIN_DECLS
+#  endif
+#endif
+#if !defined(__END_DECLS)
+#  ifdef __cplusplus
+#  define	__END_DECLS		}
+#  else
+#  define	__END_DECLS
+#  endif
+#endif
+
+#if defined(__APPLE__)
+# define htobe32(x) OSSwapHostToBigInt32(x)
+# define betoh32(x) OSSwapBigToHostInt32(x)
+# define htobe64(x) OSSwapHostToBigInt64(x)
+# define betoh64(x) OSSwapBigToHostInt64(x)
+#elif defined(__linux__)
+# define betoh32(x) be32toh(x)
+# define betoh64(x) be64toh(x)
+#endif
+
+#ifndef HAVE_STRLCAT
+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
+
+#endif /* MANDOC_CONFIG_H */
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/eqn.c	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,949 @@
+/*	$Id: eqn.c,v 1.38 2011/07/25 15:37:00 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.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)
+
+enum	eqn_rest {
+	EQN_DESCOPE,
+	EQN_ERR,
+	EQN_OK,
+	EQN_EOF
+};
+
+enum	eqn_symt {
+	EQNSYM_alpha,
+	EQNSYM_beta,
+	EQNSYM_chi,
+	EQNSYM_delta,
+	EQNSYM_epsilon,
+	EQNSYM_eta,
+	EQNSYM_gamma,
+	EQNSYM_iota,
+	EQNSYM_kappa,
+	EQNSYM_lambda,
+	EQNSYM_mu,
+	EQNSYM_nu,
+	EQNSYM_omega,
+	EQNSYM_omicron,
+	EQNSYM_phi,
+	EQNSYM_pi,
+	EQNSYM_ps,
+	EQNSYM_rho,
+	EQNSYM_sigma,
+	EQNSYM_tau,
+	EQNSYM_theta,
+	EQNSYM_upsilon,
+	EQNSYM_xi,
+	EQNSYM_zeta,
+	EQNSYM_DELTA,
+	EQNSYM_GAMMA,
+	EQNSYM_LAMBDA,
+	EQNSYM_OMEGA,
+	EQNSYM_PHI,
+	EQNSYM_PI,
+	EQNSYM_PSI,
+	EQNSYM_SIGMA,
+	EQNSYM_THETA,
+	EQNSYM_UPSILON,
+	EQNSYM_XI,
+	EQNSYM_inter,
+	EQNSYM_union,
+	EQNSYM_prod,
+	EQNSYM_int,
+	EQNSYM_sum,
+	EQNSYM_grad,
+	EQNSYM_del,
+	EQNSYM_times,
+	EQNSYM_cdot,
+	EQNSYM_nothing,
+	EQNSYM_approx,
+	EQNSYM_prime,
+	EQNSYM_half,
+	EQNSYM_partial,
+	EQNSYM_inf,
+	EQNSYM_muchgreat,
+	EQNSYM_muchless,
+	EQNSYM_larrow,
+	EQNSYM_rarrow,
+	EQNSYM_pm,
+	EQNSYM_nequal,
+	EQNSYM_equiv,
+	EQNSYM_lessequal,
+	EQNSYM_moreequal,
+	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	*sym;
+};
+
+
+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	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	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 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, 
+		const char *p, int pos, int *offs)
+{
+	size_t		 sz;
+	struct eqn_node	*ep;
+	enum rofferr	 er;
+
+	ep = *epp;
+
+	/*
+	 * If we're the terminating mark, unset our equation status and
+	 * validate the full equation.
+	 */
+
+	if (0 == strncmp(p, ".EN", 3)) {
+		er = eqn_end(epp);
+		p += 3;
+		while (' ' == *p || '\t' == *p)
+			p++;
+		if ('\0' == *p) 
+			return(er);
+		mandoc_msg(MANDOCERR_ARGSLOST, ep->parse, ln, pos, NULL);
+		return(er);
+	}
+
+	/*
+	 * Build up the full string, replacing all newlines with regular
+	 * whitespace.
+	 */
+
+	sz = strlen(p + pos) + 1;
+	ep->data = mandoc_realloc(ep->data, ep->sz + sz + 1);
+
+	/* First invocation: nil terminate the string. */
+
+	if (0 == ep->sz)
+		*ep->data = '\0';
+
+	ep->sz += sz;
+	strlcat(ep->data, p + pos, ep->sz + 1);
+	strlcat(ep->data, " ", ep->sz + 1);
+	return(ROFF_IGN);
+}
+
+struct eqn_node *
+eqn_alloc(const char *name, 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;
+	p->gsize = EQN_DEFSIZE;
+
+	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)
+{
+	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)
+{
+
+	ep->cur = ep->rew;
+}
+
+static const char *
+eqn_next(struct eqn_node *ep, char quote, size_t *sz, int repl)
+{
+	char		*start, *next;
+	int		 q, diff, lim;
+	size_t		 ssz, dummy;
+	struct eqn_def	*def;
+
+	if (NULL == sz)
+		sz = &dummy;
+
+	lim = 0;
+	ep->rew = ep->cur;
+again:
+	/* Prevent self-definitions. */
+
+	if (lim >= EQN_NEST_MAX) {
+		EQN_MSG(MANDOCERR_ROFFLOOP, ep);
+		return(NULL);
+	}
+
+	ep->cur = ep->rew;
+	start = &ep->data[(int)ep->cur];
+	q = 0;
+
+	if ('\0' == *start)
+		return(NULL);
+
+	if (quote == *start) {
+		ep->cur++;
+		q = 1;
+	}
+
+	start = &ep->data[(int)ep->cur];
+
+	if ( ! q) {
+		if ('{' == *start || '}' == *start)
+			ssz = 1;
+		else
+			ssz = strcspn(start + 1, " ^~\"{}\t") + 1;
+		next = start + (int)ssz;
+		if ('\0' == *next)
+			next = NULL;
+	} else
+		next = strchr(start, quote);
+
+	if (NULL != next) {
+		*sz = (size_t)(next - start);
+		ep->cur += *sz;
+		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])
+			ep->cur++;
+	} else {
+		if (q)
+			EQN_MSG(MANDOCERR_BADQUOTE, ep);
+		next = strchr(start, '\0');
+		*sz = (size_t)(next - start);
+		ep->cur += *sz;
+	}
+
+	/* Quotes aren't expanded for values. */
+
+	if (q || ! repl)
+		return(start);
+
+	if (NULL != (def = eqn_def_find(ep, start, *sz))) {
+		diff = def->valsz - *sz;
+
+		if (def->valsz > *sz) {
+			ep->sz += diff;
+			ep->data = mandoc_realloc(ep->data, ep->sz + 1);
+			ep->data[ep->sz] = '\0';
+			start = &ep->data[(int)ep->rew];
+		}
+
+		diff = def->valsz - *sz;
+		memmove(start + *sz + diff, start + *sz, 
+				(strlen(start) - *sz) + 1);
+		memcpy(start, def->val, def->valsz);
+		goto again;
+	}
+
+	return(start);
+}
+
+static int
+eqn_do_ign1(struct eqn_node *ep)
+{
+
+	if (NULL == eqn_nextrawtok(ep, NULL))
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+	else
+		return(1);
+
+	return(0);
+}
+
+static int
+eqn_do_ign2(struct eqn_node *ep)
+{
+
+	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);
+
+	return(0);
+}
+
+static int
+eqn_do_tdefine(struct eqn_node *ep)
+{
+
+	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);
+
+	return(0);
+}
+
+static int
+eqn_do_define(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);
+	}
+
+	/* 
+	 * 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++)
+			if (0 == ep->defs[i].keysz)
+				break;
+
+		if (i == (int)ep->defsz) {
+			ep->defsz++;
+			ep->defs = mandoc_realloc
+				(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];
+	}
+
+	start = eqn_next(ep, ep->data[(int)ep->cur], &sz, 0);
+
+	if (NULL == start) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	}
+
+	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)
+{
+
+	if (NULL == eqn_nextrawtok(ep, NULL)) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	} 
+	return(1);
+}
+
+static int
+eqn_do_gsize(struct eqn_node *ep)
+{
+	const char	*start;
+	size_t		 sz;
+
+	if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	} 
+	ep->gsize = mandoc_strntoi(start, sz, 10);
+	return(1);
+}
+
+static int
+eqn_do_undef(struct eqn_node *ep)
+{
+	const char	*start;
+	struct eqn_def	*def;
+	size_t		 sz;
+
+	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;
+
+	return(1);
+}
+
+static struct eqn_def *
+eqn_def_find(struct eqn_node *ep, const char *key, size_t sz)
+{
+	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]);
+
+	return(NULL);
+}
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/eqn_html.c	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,81 @@
+/*	$Id: eqn_html.c,v 1.2 2011/07/24 10:09:03 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#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 *, const struct eqn_box *);
+
+void
+print_eqn(struct html *p, const struct eqn *ep)
+{
+	struct htmlpair	 tag;
+	struct tag	*t;
+
+	PAIR_CLASS_INIT(&tag, "eqn");
+	t = print_otag(p, TAG_SPAN, 1, &tag);
+
+	p->flags |= HTML_NONOSPACE;
+	eqn_box(p, ep->root);
+	p->flags &= ~HTML_NONOSPACE;
+
+	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);
+}
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/eqn_term.c	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,76 @@
+/*	$Id: eqn_term.c,v 1.4 2011/07/24 10:09:03 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "term.h"
+
+static	const enum termfont fontmap[EQNFONT__MAX] = {
+	TERMFONT_NONE, /* EQNFONT_NONE */
+	TERMFONT_NONE, /* EQNFONT_ROMAN */
+	TERMFONT_BOLD, /* EQNFONT_BOLD */
+	TERMFONT_BOLD, /* EQNFONT_FAT */
+	TERMFONT_UNDER /* EQNFONT_ITALIC */
+};
+
+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;
+}
+
+static void
+eqn_box(struct termp *p, const struct eqn_box *bp)
+{
+
+	if (EQNFONT_NONE != bp->font)
+		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)
+		term_word(p, bp->text);
+
+	if (bp->first)
+		eqn_box(p, bp->first);
+
+	if (EQN_SUBEXPR == bp->type)
+		term_word(p, ")");
+	if (bp->right)
+		term_word(p, bp->right);
+	if (EQNFONT_NONE != bp->font) 
+		term_fontpop(p);
+
+	if (bp->next)
+		eqn_box(p, bp->next);
+}
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/html.c	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,699 @@
+/*	$Id: html.c,v 1.150 2011/10/05 21:35:17 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "out.h"
+#include "html.h"
+#include "main.h"
+
+struct	htmldata {
+	const char	 *name;
+	int		  flags;
+#define	HTML_CLRLINE	 (1 << 0)
+#define	HTML_NOSTACK	 (1 << 1)
+#define	HTML_AUTOCLOSE	 (1 << 2) /* Tag has auto-closure. */
+};
+
+static	const struct htmldata htmltags[TAG_MAX] = {
+	{"html",	HTML_CLRLINE}, /* TAG_HTML */
+	{"head",	HTML_CLRLINE}, /* TAG_HEAD */
+	{"body",	HTML_CLRLINE}, /* TAG_BODY */
+	{"meta",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */
+	{"title",	HTML_CLRLINE}, /* TAG_TITLE */
+	{"div",		HTML_CLRLINE}, /* TAG_DIV */
+	{"h1",		0}, /* TAG_H1 */
+	{"h2",		0}, /* TAG_H2 */
+	{"span",	0}, /* TAG_SPAN */
+	{"link",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */
+	{"br",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */
+	{"a",		0}, /* TAG_A */
+	{"table",	HTML_CLRLINE}, /* TAG_TABLE */
+	{"tbody",	HTML_CLRLINE}, /* TAG_TBODY */
+	{"col",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */
+	{"tr",		HTML_CLRLINE}, /* TAG_TR */
+	{"td",		HTML_CLRLINE}, /* TAG_TD */
+	{"li",		HTML_CLRLINE}, /* TAG_LI */
+	{"ul",		HTML_CLRLINE}, /* TAG_UL */
+	{"ol",		HTML_CLRLINE}, /* TAG_OL */
+	{"dl",		HTML_CLRLINE}, /* TAG_DL */
+	{"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 */
+};
+
+static	const char	*const htmlattrs[ATTR_MAX] = {
+	"http-equiv", /* ATTR_HTTPEQUIV */
+	"content", /* ATTR_CONTENT */
+	"name", /* ATTR_NAME */
+	"rel", /* ATTR_REL */
+	"href", /* ATTR_HREF */
+	"type", /* ATTR_TYPE */
+	"media", /* ATTR_MEDIA */
+	"class", /* ATTR_CLASS */
+	"style", /* ATTR_STYLE */
+	"width", /* ATTR_WIDTH */
+	"id", /* ATTR_ID */
+	"summary", /* ATTR_SUMMARY */
+	"align", /* ATTR_ALIGN */
+	"colspan", /* ATTR_COLSPAN */
+};
+
+static	const char	*const roffscales[SCALE_MAX] = {
+	"cm", /* SCALE_CM */
+	"in", /* SCALE_IN */
+	"pc", /* SCALE_PC */
+	"pt", /* SCALE_PT */
+	"em", /* SCALE_EM */
+	"em", /* SCALE_MM */
+	"ex", /* SCALE_EN */
+	"ex", /* SCALE_BU */
+	"em", /* SCALE_VS */
+	"ex", /* SCALE_FS */
+};
+
+static	void	 bufncat(struct html *, const char *, size_t);
+static	void	 print_ctag(struct html *, enum htmltag);
+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)
+{
+	struct html	*h;
+	const char	*toks[5];
+	char		*v;
+
+	toks[0] = "style";
+	toks[1] = "man";
+	toks[2] = "includes";
+	toks[3] = "fragment";
+	toks[4] = NULL;
+
+	h = mandoc_calloc(1, sizeof(struct html));
+
+	h->type = type;
+	h->tags.head = NULL;
+	h->symtab = mchars_alloc();
+
+	while (outopts && *outopts)
+		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
+		case (0):
+			h->style = v;
+			break;
+		case (1):
+			h->base_man = v;
+			break;
+		case (2):
+			h->base_includes = v;
+			break;
+		case (3):
+			h->oflags |= HTML_FRAGMENT;
+			break;
+		default:
+			break;
+		}
+
+	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)
+{
+	struct tag	*tag;
+	struct html	*h;
+
+	h = (struct html *)p;
+
+	while ((tag = h->tags.head) != NULL) {
+		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];
+
+	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_NAME;
+	tag[0].val = "resource-type";
+	tag[1].key = ATTR_CONTENT;
+	tag[1].val = "document";
+	print_otag(h, TAG_META, 2, tag);
+
+	if (h->style) {
+		tag[0].key = ATTR_REL;
+		tag[0].val = "stylesheet";
+		tag[1].key = ATTR_HREF;
+		tag[1].val = h->style;
+		tag[2].key = ATTR_TYPE;
+		tag[2].val = "text/css";
+		tag[3].key = ATTR_MEDIA;
+		tag[3].val = "all";
+		print_otag(h, TAG_LINK, 4, tag);
+	}
+}
+
+static void
+print_metaf(struct html *h, enum mandoc_esc deco)
+{
+	enum htmlfont	 font;
+
+	switch (deco) {
+	case (ESCAPE_FONTPREV):
+		font = h->metal;
+		break;
+	case (ESCAPE_FONTITALIC):
+		font = HTMLFONT_ITALIC;
+		break;
+	case (ESCAPE_FONTBOLD):
+		font = HTMLFONT_BOLD;
+		break;
+	case (ESCAPE_FONT):
+		/* FALLTHROUGH */
+	case (ESCAPE_FONTROMAN):
+		font = HTMLFONT_NONE;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	if (h->metaf) {
+		print_tagq(h, h->metaf);
+		h->metaf = NULL;
+	}
+
+	h->metal = h->metac;
+	h->metac = font;
+
+	if (HTMLFONT_NONE != font)
+		h->metaf = HTMLFONT_BOLD == font ?
+			print_otag(h, TAG_B, 0, NULL) :
+			print_otag(h, TAG_I, 0, NULL);
+}
+
+int
+html_strlen(const char *cp)
+{
+	int		 ssz, sz;
+	const char	*seq, *p;
+
+	/*
+	 * Account for escaped sequences within string length
+	 * calculations.  This follows the logic in term_strlen() as we
+	 * must calculate the width of produced strings.
+	 * Assume that characters are always width of "1".  This is
+	 * hacky, but it gets the job done for approximation of widths.
+	 */
+
+	sz = 0;
+	while (NULL != (p = strchr(cp, '\\'))) {
+		sz += (int)(p - cp);
+		++cp;
+		switch (mandoc_escape(&cp, &seq, &ssz)) {
+		case (ESCAPE_ERROR):
+			return(sz);
+		case (ESCAPE_UNICODE):
+			/* FALLTHROUGH */
+		case (ESCAPE_NUMBERED):
+			/* FALLTHROUGH */
+		case (ESCAPE_SPECIAL):
+			sz++;
+			break;
+		default:
+			break;
+		}
+	}
+
+	assert(sz >= 0);
+	return(sz + strlen(cp));
+}
+
+static int
+print_encode(struct html *h, const char *p, int norecurse)
+{
+	size_t		 sz;
+	int		 c, len, nospace;
+	const char	*seq;
+	enum mandoc_esc	 esc;
+	static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' };
+
+	nospace = 0;
+
+	while ('\0' != *p) {
+		sz = strcspn(p, rejs);
+
+		fwrite(p, 1, sz, stdout);
+		p += (int)sz;
+
+		if ('\0' == *p)
+			break;
+
+		switch (*p++) {
+		case ('<'):
+			printf("&lt;");
+			continue;
+		case ('>'):
+			printf("&gt;");
+			continue;
+		case ('&'):
+			printf("&amp;");
+			continue;
+		case (ASCII_HYPH):
+			putchar('-');
+			continue;
+		default:
+			break;
+		}
+
+		esc = mandoc_escape(&p, &seq, &len);
+		if (ESCAPE_ERROR == esc)
+			break;
+
+		switch (esc) {
+		case (ESCAPE_UNICODE):
+			/* Skip passed "u" header. */
+			c = mchars_num2uc(seq + 1, len - 1);
+			if ('\0' != c)
+				printf("&#x%x;", c);
+			break;
+		case (ESCAPE_NUMBERED):
+			c = mchars_num2char(seq, len);
+			if ('\0' != c)
+				putchar(c);
+			break;
+		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);
+			break;
+		case (ESCAPE_FONT):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTPREV):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTBOLD):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTITALIC):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTROMAN):
+			if (norecurse)
+				break;
+			print_metaf(h, esc);
+			break;
+		case (ESCAPE_NOSPACE):
+			if ('\0' == *p)
+				nospace = 1;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return(nospace);
+}
+
+
+static void
+print_attr(struct html *h, const char *key, const char *val)
+{
+	printf(" %s=\"", key);
+	(void)print_encode(h, val, 1);
+	putchar('\"');
+}
+
+
+struct tag *
+print_otag(struct html *h, enum htmltag tag, 
+		int sz, const struct htmlpair *p)
+{
+	int		 i;
+	struct tag	*t;
+
+	/* Push this tags onto the stack of open scopes. */
+
+	if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
+		t = mandoc_malloc(sizeof(struct tag));
+		t->tag = tag;
+		t->next = h->tags.head;
+		h->tags.head = t;
+	} else
+		t = NULL;
+
+	if ( ! (HTML_NOSPACE & h->flags))
+		if ( ! (HTML_CLRLINE & htmltags[tag].flags)) {
+			/* Manage keeps! */
+			if ( ! (HTML_KEEP & h->flags)) {
+				if (HTML_PREKEEP & h->flags)
+					h->flags |= HTML_KEEP;
+				putchar(' ');
+			} else
+				printf("&#160;");
+		}
+
+	if ( ! (h->flags & HTML_NONOSPACE))
+		h->flags &= ~HTML_NOSPACE;
+	else
+		h->flags |= HTML_NOSPACE;
+
+	/* Print out the tag name and attributes. */
+
+	printf("<%s", htmltags[tag].name);
+	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. */
+
+	if (HTML_AUTOCLOSE & htmltags[tag].flags)
+		switch (h->type) {
+		case (HTML_XHTML_1_0_STRICT):
+			putchar('/');
+			break;
+		default:
+			break;
+		}
+
+	putchar('>');
+
+	h->flags |= HTML_NOSPACE;
+
+	if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags)
+		putchar('\n');
+
+	return(t);
+}
+
+
+static void
+print_ctag(struct html *h, enum htmltag tag)
+{
+	
+	printf("</%s>", htmltags[tag].name);
+	if (HTML_CLRLINE & htmltags[tag].flags) {
+		h->flags |= HTML_NOSPACE;
+		putchar('\n');
+	} 
+}
+
+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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+		name = "html";
+		doctype = "-//W3C//DTD XHTML 1.0 Strict//EN";
+		dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";
+		break;
+	}
+
+	printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n", 
+			name, doctype, dtd);
+}
+
+void
+print_text(struct html *h, const char *word)
+{
+
+	if ( ! (HTML_NOSPACE & h->flags)) {
+		/* Manage keeps! */
+		if ( ! (HTML_KEEP & h->flags)) {
+			if (HTML_PREKEEP & h->flags)
+				h->flags |= HTML_KEEP;
+			putchar(' ');
+		} else
+			printf("&#160;");
+	}
+
+	assert(NULL == h->metaf);
+	if (HTMLFONT_NONE != h->metac)
+		h->metaf = HTMLFONT_BOLD == h->metac ?
+			print_otag(h, TAG_B, 0, NULL) :
+			print_otag(h, TAG_I, 0, NULL);
+
+	assert(word);
+	if ( ! print_encode(h, word, 0)) {
+		if ( ! (h->flags & HTML_NONOSPACE))
+			h->flags &= ~HTML_NOSPACE;
+	} else
+		h->flags |= HTML_NOSPACE;
+
+	if (h->metaf) {
+		print_tagq(h, h->metaf);
+		h->metaf = NULL;
+	}
+
+	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);
+		if (until && tag == until)
+			return;
+	}
+}
+
+
+void
+print_stagq(struct html *h, const struct tag *suntil)
+{
+	struct tag	*tag;
+
+	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);
+	}
+}
+
+void
+bufinit(struct html *h)
+{
+
+	h->buf[0] = '\0';
+	h->buflen = 0;
+}
+
+void
+bufcat_style(struct html *h, const char *key, const char *val)
+{
+
+	bufcat(h, key);
+	bufcat(h, ":");
+	bufcat(h, val);
+	bufcat(h, ";");
+}
+
+void
+bufcat(struct html *h, const char *p)
+{
+
+	h->buflen = strlcat(h->buf, p, BUFSIZ);
+	assert(h->buflen < BUFSIZ);
+}
+
+void
+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);
+	va_end(ap);
+	h->buflen = strlen(h->buf);
+}
+
+static void
+bufncat(struct html *h, const char *p, size_t sz)
+{
+
+	assert(h->buflen + sz + 1 < BUFSIZ);
+	strncat(h->buf, p, sz);
+	h->buflen += sz;
+}
+
+void
+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'):
+			bufcat(h, name);
+			break;
+		default:
+			bufncat(h, p, 2);
+			break;
+		}
+		pp = p + 2;
+	}
+	if (pp)
+		bufcat(h, pp);
+}
+
+void
+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'):
+			bufcat(h, sec ? sec : "1");
+			break;
+		case('N'):
+			bufcat_fmt(h, name);
+			break;
+		default:
+			bufncat(h, p, 2);
+			break;
+		}
+		pp = p + 2;
+	}
+	if (pp)
+		bufcat(h, pp);
+}
+
+void
+bufcat_su(struct html *h, const char *p, const struct roffsu *su)
+{
+	double		 v;
+
+	v = su->scale;
+	if (SCALE_MM == su->unit && 0.0 == (v /= 100.0))
+		v = 1.0;
+
+	bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]);
+}
+
+void
+bufcat_id(struct html *h, const char *src)
+{
+
+	/* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */
+
+	while ('\0' != *src)
+		bufcat_fmt(h, "%.2x", *src++);
+}
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/html.h	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,164 @@
+/*	$Id: html.h,v 1.47 2011/10/05 21:35:17 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef HTML_H
+#define HTML_H
+
+__BEGIN_DECLS
+
+enum	htmltag {
+	TAG_HTML,
+	TAG_HEAD,
+	TAG_BODY,
+	TAG_META,
+	TAG_TITLE,
+	TAG_DIV,
+	TAG_H1,
+	TAG_H2,
+	TAG_SPAN,
+	TAG_LINK,
+	TAG_BR,
+	TAG_A,
+	TAG_TABLE,
+	TAG_TBODY,
+	TAG_COL,
+	TAG_TR,
+	TAG_TD,
+	TAG_LI,
+	TAG_UL,
+	TAG_OL,
+	TAG_DL,
+	TAG_DT,
+	TAG_DD,
+	TAG_BLOCKQUOTE,
+	TAG_P,
+	TAG_PRE,
+	TAG_B,
+	TAG_I,
+	TAG_CODE,
+	TAG_SMALL,
+	TAG_MAX
+};
+
+enum	htmlattr {
+	ATTR_HTTPEQUIV,
+	ATTR_CONTENT,
+	ATTR_NAME,
+	ATTR_REL,
+	ATTR_HREF,
+	ATTR_TYPE,
+	ATTR_MEDIA,
+	ATTR_CLASS,
+	ATTR_STYLE,
+	ATTR_WIDTH,
+	ATTR_ID,
+	ATTR_SUMMARY,
+	ATTR_ALIGN,
+	ATTR_COLSPAN,
+	ATTR_MAX
+};
+
+enum	htmlfont {
+	HTMLFONT_NONE = 0,
+	HTMLFONT_BOLD,
+	HTMLFONT_ITALIC,
+	HTMLFONT_MAX
+};
+
+struct	tag {
+	struct tag	 *next;
+	enum htmltag	  tag;
+};
+
+struct tagq {
+	struct tag	 *head;
+};
+
+struct	htmlpair {
+	enum htmlattr	  key;
+	const char	 *val;
+};
+
+#define	PAIR_INIT(p, t, v) \
+	do { \
+		(p)->key = (t); \
+		(p)->val = (v); \
+	} while (/* CONSTCOND */ 0)
+
+#define	PAIR_ID_INIT(p, v)	PAIR_INIT(p, ATTR_ID, v)
+#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;
+#define	HTML_NOSPACE	 (1 << 0) /* suppress next space */
+#define	HTML_IGNDELIM	 (1 << 1)
+#define	HTML_KEEP	 (1 << 2)
+#define	HTML_PREKEEP	 (1 << 3)
+#define	HTML_NONOSPACE	 (1 << 4) /* never add spaces */
+#define	HTML_LITERAL	 (1 << 5) /* literal (e.g., <PRE>) context */
+	struct tagq	  tags; /* stack of open tags */
+	struct rofftbl	  tbl; /* current table */
+	struct tag	 *tblt; /* current open table scope */
+	struct mchars	 *symtab; /* character-escapes */
+	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; 
+	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 */
+};
+
+void		  print_gen_decls(struct html *);
+void		  print_gen_head(struct html *);
+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 *);
+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		  bufcat_fmt(struct html *, const char *, ...);
+void		  bufcat(struct html *, const char *);
+void		  bufcat_id(struct html *, const char *);
+void		  bufcat_style(struct html *, 
+			const char *, const char *);
+void		  bufcat_su(struct html *, const char *, 
+			const struct roffsu *);
+void		  bufinit(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*/
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/lib.c	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,39 @@
+/*	$Id: lib.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+	if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2lib(const char *p)
+{
+
+#include "lib.in"
+
+	return(NULL);
+}
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/lib.in	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,18 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * TBD
+ */
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/libman.h	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,85 @@
+/*	$Id: libman.h,v 1.55 2011/11/07 01:24:40 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBMAN_H
+#define LIBMAN_H
+
+enum	man_next {
+	MAN_NEXT_SIBLING = 0,
+	MAN_NEXT_CHILD
+};
+
+struct	man {
+	struct mparse	*parse; /* parse pointer */
+	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 */
+	struct man_node	*first; /* the first parsed node */
+	struct man_meta	 meta; /* document meta-data */
+	struct roff	*roff;
+};
+
+#define	MACRO_PROT_ARGS	  struct man *m, \
+			  enum mant tok, \
+			  int line, \
+			  int ppos, \
+			  int *pos, \
+			  char *buf
+
+struct	man_macro {
+	int		(*fp)(MACRO_PROT_ARGS);
+	int		  flags;
+#define	MAN_SCOPED	 (1 << 0)
+#define	MAN_EXPLICIT	 (1 << 1)	/* See blk_imp(). */
+#define	MAN_FSCOPED	 (1 << 2)	/* See blk_imp(). */
+#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. */
+};
+
+extern	const struct man_macro *const man_macros;
+
+__BEGIN_DECLS
+
+#define		  man_pmsg(m, l, p, t) \
+		  mandoc_msg((t), (m)->parse, (l), (p), NULL)
+#define		  man_nmsg(m, n, t) \
+		  mandoc_msg((t), (m)->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_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);
+
+__END_DECLS
+
+#endif /*!LIBMAN_H*/
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/libmandoc.h	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,92 @@
+/*	$Id: libmandoc.h,v 1.29 2011/12/02 01:37:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBMANDOC_H
+#define LIBMANDOC_H
+
+enum	rofferr {
+	ROFF_CONT, /* continue processing line */
+	ROFF_RERUN, /* re-run roff interpreter with offset */
+	ROFF_APPEND, /* re-run main parser, appending next line */
+	ROFF_REPARSE, /* re-run main parser on the result */
+	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 */
+};
+
+enum	regs {
+	REG_nS = 0, /* nS register */
+	REG__MAX
+};
+
+__BEGIN_DECLS
+
+struct	roff;
+struct	mdoc;
+struct	man;
+
+void		 mandoc_msg(enum mandocerr, struct mparse *, 
+			int, int, const char *);
+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_getcontrol(const char *, int *);
+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 *);
+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 *);
+
+void	 	 man_free(struct man *);
+struct	man	*man_alloc(struct roff *, struct mparse *);
+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 *);
+
+void	 	 roff_free(struct roff *);
+struct roff	*roff_alloc(struct mparse *);
+void		 roff_reset(struct roff *);
+enum rofferr	 roff_parseln(struct roff *, int, 
+			char **, size_t *, int, int *);
+void		 roff_endparse(struct roff *);
+int		 roff_regisset(const struct roff *, enum regs);
+unsigned int	 roff_regget(const struct roff *, enum regs);
+void		 roff_regunset(struct roff *, enum regs);
+char		*roff_strdup(const struct roff *, const char *);
+#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
+
+const struct tbl_span	*roff_span(const struct roff *);
+const struct eqn	*roff_eqn(const struct roff *);
+
+__END_DECLS
+
+#endif /*!LIBMANDOC_H*/
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/libmdoc.h	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,141 @@
+/*	$Id: libmdoc.h,v 1.78 2011/12/02 01:37:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBMDOC_H
+#define LIBMDOC_H
+
+enum	mdoc_next {
+	MDOC_NEXT_SIBLING = 0,
+	MDOC_NEXT_CHILD
+};
+
+struct	mdoc {
+	struct mparse	 *parse; /* parse pointer */
+	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 */
+#define	MDOC_PHRASELIT	 (1 << 4) /* literal within a partila phrase */
+#define	MDOC_PPHRASE	 (1 << 5) /* within a partial phrase */
+#define	MDOC_FREECOL	 (1 << 6) /* `It' invocation should close */
+#define	MDOC_SYNOPSIS	 (1 << 7) /* SYNOPSIS-style formatting */
+	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_meta  meta; /* document meta-data */
+	enum mdoc_sec	  lastnamed;
+	enum mdoc_sec	  lastsec;
+	struct roff	 *roff;
+};
+
+#define	MACRO_PROT_ARGS	struct mdoc *m, \
+			enum mdoct tok, \
+			int line, \
+			int ppos, \
+			int *pos, \
+			char *buf
+
+struct	mdoc_macro {
+	int		(*fp)(MACRO_PROT_ARGS);
+	int		  flags;
+#define	MDOC_CALLABLE	 (1 << 0)
+#define	MDOC_PARSED	 (1 << 1)
+#define	MDOC_EXPLICIT	 (1 << 2)
+#define	MDOC_PROLOGUE	 (1 << 3)
+#define	MDOC_IGNDELIM	 (1 << 4) 
+	/* Reserved words in arguments treated as text. */
+};
+
+enum	margserr {
+	ARGS_ERROR,
+	ARGS_EOLN, /* end-of-line */
+	ARGS_WORD, /* normal word */
+	ARGS_PUNCT, /* series of punctuation */
+	ARGS_QWORD, /* quoted word */
+	ARGS_PHRASE, /* Ta'd phrase (-column) */
+	ARGS_PPHRASE, /* tabbed phrase (-column) */
+	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.
+ * Opening punctuation (e.g., the opening parenthesis) suppresses the
+ * following space; closing punctuation (e.g., the closing parenthesis)
+ * suppresses the leading space; middle punctuation (e.g., the vertical
+ * bar) can do either.  The middle punctuation delimiter bends the rules
+ * depending on usage.
+ */
+enum	mdelim {
+	DELIM_NONE = 0,
+	DELIM_OPEN,
+	DELIM_MIDDLE,
+	DELIM_CLOSE,
+	DELIM_MAX
+};
+
+extern	const struct mdoc_macro *const mdoc_macros;
+
+__BEGIN_DECLS
+
+#define		  mdoc_pmsg(m, l, p, t) \
+		  mandoc_msg((t), (m)->parse, (l), (p), NULL)
+#define		  mdoc_nmsg(m, n, t) \
+		  mandoc_msg((t), (m)->parse, (n)->line, (n)->pos, NULL)
+int		  mdoc_macro(MACRO_PROT_ARGS);
+int		  mdoc_word_alloc(struct mdoc *, 
+			int, int, const char *);
+int		  mdoc_elem_alloc(struct mdoc *, int, int, 
+			enum mdoct, struct mdoc_arg *);
+int		  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 *m, int line, int pos,
+			enum mdoct tok, struct mdoc_node *body,
+			enum mdoc_endbody end);
+void		  mdoc_node_delete(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,
+			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 *);
+enum mdelim	  mdoc_isdelim(const char *);
+
+__END_DECLS
+
+#endif /*!LIBMDOC_H*/
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/libroff.h	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,84 @@
+/*	$Id: libroff.h,v 1.27 2011/07/25 15:37:00 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBROFF_H
+#define LIBROFF_H
+
+__BEGIN_DECLS
+
+enum	tbl_part {
+	TBL_PART_OPTS, /* in options (first line) */
+	TBL_PART_LAYOUT, /* describing layout */
+	TBL_PART_DATA, /* creating data rows */
+	TBL_PART_CDATA /* continue previous row */
+};
+
+struct	tbl_node {
+	struct mparse	 *parse; /* parse point */
+	int		  pos; /* invocation column */
+	int		  line; /* invocation line */
+	enum tbl_part	  part;
+	struct tbl	  opts;
+	struct tbl_row	 *first_row;
+	struct tbl_row	 *last_row;
+	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_def {
+	char		 *key;
+	size_t		  keysz;
+	char		 *val;
+	size_t		  valsz;
+};
+
+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 *);
+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 *);
+enum rofferr	 eqn_end(struct eqn_node **);
+void		 eqn_free(struct eqn_node *);
+enum rofferr 	 eqn_read(struct eqn_node **, int, 
+			const char *, int, int *);
+
+__END_DECLS
+
+#endif /*LIBROFF_H*/
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/main.c	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,401 @@
+/*	$Id: main.c,v 1.165 2011/10/06 22:29:12 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "main.h"
+#include "mdoc.h"
+#include "man.h"
+
+#if !defined(__GNUC__) || (__GNUC__ < 2)
+# if !defined(lint)
+#  define __attribute__(x)
+# endif
+#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
+
+typedef	void		(*out_mdoc)(void *, const struct mdoc *);
+typedef	void		(*out_man)(void *, const struct man *);
+typedef	void		(*out_free)(void *);
+
+enum	outt {
+	OUTT_ASCII = 0,	/* -Tascii */
+	OUTT_LOCALE,	/* -Tlocale */
+	OUTT_UTF8,	/* -Tutf8 */
+	OUTT_TREE,	/* -Ttree */
+	OUTT_MAN,	/* -Tman */
+	OUTT_HTML,	/* -Thtml */
+	OUTT_XHTML,	/* -Txhtml */
+	OUTT_LINT,	/* -Tlint */
+	OUTT_PS,	/* -Tps */
+	OUTT_PDF	/* -Tpdf */
+};
+
+struct	curparse {
+	struct mparse	 *mp;
+	enum mandoclevel  wlevel;	/* ignore messages below this */
+	int		  wstop;	/* stop after a file with a warning */
+	enum outt	  outtype; 	/* which output to use */
+	out_mdoc	  outmdoc;	/* mdoc 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	void		  mmsg(enum mandocerr, enum mandoclevel,
+				const char *, int, int, const char *);
+static	void		  parse(struct curparse *, int, 
+				const char *, enum mandoclevel *);
+static	int		  toptions(struct curparse *, char *);
+static	void		  usage(void) __attribute__((noreturn));
+static	void		  version(void) __attribute__((noreturn));
+static	int		  woptions(struct curparse *, char *);
+
+static	const char	 *progname;
+
+int
+main(int argc, char *argv[])
+{
+	int		 c;
+	struct curparse	 curp;
+	enum mparset	 type;
+	enum mandoclevel rc;
+
+	progname = strrchr(argv[0], '/');
+	if (progname == NULL)
+		progname = argv[0];
+	else
+		++progname;
+
+	memset(&curp, 0, sizeof(struct curparse));
+
+	type = MPARSE_AUTO;
+	curp.outtype = OUTT_ASCII;
+	curp.wlevel  = MANDOCLEVEL_FATAL;
+
+	/* LINTED */
+	while (-1 != (c = getopt(argc, argv, "m:O:T:VW:")))
+		switch (c) {
+		case ('m'):
+			if ( ! moptions(&type, optarg))
+				return((int)MANDOCLEVEL_BADARG);
+			break;
+		case ('O'):
+			(void)strlcat(curp.outopts, optarg, BUFSIZ);
+			(void)strlcat(curp.outopts, ",", BUFSIZ);
+			break;
+		case ('T'):
+			if ( ! toptions(&curp, optarg))
+				return((int)MANDOCLEVEL_BADARG);
+			break;
+		case ('W'):
+			if ( ! woptions(&curp, optarg))
+				return((int)MANDOCLEVEL_BADARG);
+			break;
+		case ('V'):
+			version();
+			/* NOTREACHED */
+		default:
+			usage();
+			/* NOTREACHED */
+		}
+
+	curp.mp = mparse_alloc(type, curp.wlevel, mmsg, &curp);
+
+	/*
+	 * Conditionally start up the lookaside buffer before parsing.
+	 */
+	if (OUTT_MAN == curp.outtype)
+		mparse_keep(curp.mp);
+
+	argc -= optind;
+	argv += optind;
+
+	rc = MANDOCLEVEL_OK;
+
+	if (NULL == *argv)
+		parse(&curp, STDIN_FILENO, "<stdin>", &rc);
+
+	while (*argv) {
+		parse(&curp, -1, *argv, &rc);
+		if (MANDOCLEVEL_OK != rc && curp.wstop)
+			break;
+		++argv;
+	}
+
+	if (curp.outfree)
+		(*curp.outfree)(curp.outdata);
+	if (curp.mp)
+		mparse_free(curp.mp);
+
+	return((int)rc);
+}
+
+static void
+version(void)
+{
+
+	printf("%s %s\n", progname, VERSION);
+	exit((int)MANDOCLEVEL_OK);
+}
+
+static void
+usage(void)
+{
+
+	fprintf(stderr, "usage: %s "
+			"[-V] "
+			"[-foption] "
+			"[-mformat] "
+			"[-Ooption] "
+			"[-Toutput] "
+			"[-Wlevel] "
+			"[file...]\n", 
+			progname);
+
+	exit((int)MANDOCLEVEL_BADARG);
+}
+
+static void
+parse(struct curparse *curp, int fd, 
+		const char *file, enum mandoclevel *level)
+{
+	enum mandoclevel  rc;
+	struct mdoc	 *mdoc;
+	struct man	 *man;
+
+	/* Begin by parsing the file itself. */
+
+	assert(file);
+	assert(fd >= -1);
+
+	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.
+	 */
+
+	if (MANDOCLEVEL_OK != rc && curp->wstop)
+		goto cleanup;
+
+	/* If unset, allocate output dev now (if applicable). */
+
+	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);
+			curp->outfree = html_free;
+			break;
+		case (OUTT_UTF8):
+			curp->outdata = utf8_alloc(curp->outopts);
+			curp->outfree = ascii_free;
+			break;
+		case (OUTT_LOCALE):
+			curp->outdata = locale_alloc(curp->outopts);
+			curp->outfree = ascii_free;
+			break;
+		case (OUTT_ASCII):
+			curp->outdata = ascii_alloc(curp->outopts);
+			curp->outfree = ascii_free;
+			break;
+		case (OUTT_PDF):
+			curp->outdata = pdf_alloc(curp->outopts);
+			curp->outfree = pspdf_free;
+			break;
+		case (OUTT_PS):
+			curp->outdata = ps_alloc(curp->outopts);
+			curp->outfree = pspdf_free;
+			break;
+		default:
+			break;
+		}
+
+		switch (curp->outtype) {
+		case (OUTT_HTML):
+			/* FALLTHROUGH */
+		case (OUTT_XHTML):
+			curp->outman = html_man;
+			curp->outmdoc = html_mdoc;
+			break;
+		case (OUTT_TREE):
+			curp->outman = tree_man;
+			curp->outmdoc = tree_mdoc;
+			break;
+		case (OUTT_MAN):
+			curp->outmdoc = man_mdoc;
+			curp->outman = man_man;
+			break;
+		case (OUTT_PDF):
+			/* FALLTHROUGH */
+		case (OUTT_ASCII):
+			/* FALLTHROUGH */
+		case (OUTT_UTF8):
+			/* FALLTHROUGH */
+		case (OUTT_LOCALE):
+			/* FALLTHROUGH */
+		case (OUTT_PS):
+			curp->outman = terminal_man;
+			curp->outmdoc = terminal_mdoc;
+			break;
+		default:
+			break;
+		}
+	}
+
+	mparse_result(curp->mp, &mdoc, &man);
+
+	/* Execute the out device, if it exists. */
+
+	if (man && curp->outman)
+		(*curp->outman)(curp->outdata, man);
+	if (mdoc && curp->outmdoc)
+		(*curp->outmdoc)(curp->outdata, mdoc);
+
+ cleanup:
+
+	mparse_reset(curp->mp);
+
+	if (*level < rc)
+		*level = rc;
+}
+
+static int
+moptions(enum mparset *tflags, char *arg)
+{
+
+	if (0 == strcmp(arg, "doc"))
+		*tflags = MPARSE_MDOC;
+	else if (0 == strcmp(arg, "andoc"))
+		*tflags = MPARSE_AUTO;
+	else if (0 == strcmp(arg, "an"))
+		*tflags = MPARSE_MAN;
+	else {
+		fprintf(stderr, "%s: Bad argument\n", arg);
+		return(0);
+	}
+
+	return(1);
+}
+
+static int
+toptions(struct curparse *curp, char *arg)
+{
+
+	if (0 == strcmp(arg, "ascii"))
+		curp->outtype = OUTT_ASCII;
+	else if (0 == strcmp(arg, "lint")) {
+		curp->outtype = OUTT_LINT;
+		curp->wlevel  = MANDOCLEVEL_WARNING;
+	} else if (0 == strcmp(arg, "tree"))
+		curp->outtype = OUTT_TREE;
+	else if (0 == strcmp(arg, "man"))
+		curp->outtype = OUTT_MAN;
+	else if (0 == strcmp(arg, "html"))
+		curp->outtype = OUTT_HTML;
+	else if (0 == strcmp(arg, "utf8"))
+		curp->outtype = OUTT_UTF8;
+	else if (0 == strcmp(arg, "locale"))
+		curp->outtype = OUTT_LOCALE;
+	else if (0 == strcmp(arg, "xhtml"))
+		curp->outtype = OUTT_XHTML;
+	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);
+		return(0);
+	}
+
+	return(1);
+}
+
+static int
+woptions(struct curparse *curp, char *arg)
+{
+	char		*v, *o;
+	const char	*toks[6]; 
+
+	toks[0] = "stop";
+	toks[1] = "all";
+	toks[2] = "warning";
+	toks[3] = "error";
+	toks[4] = "fatal";
+	toks[5] = NULL;
+
+	while (*arg) {
+		o = arg;
+		switch (getsubopt(&arg, UNCONST(toks), &v)) {
+		case (0):
+			curp->wstop = 1;
+			break;
+		case (1):
+			/* FALLTHROUGH */
+		case (2):
+			curp->wlevel = MANDOCLEVEL_WARNING;
+			break;
+		case (3):
+			curp->wlevel = MANDOCLEVEL_ERROR;
+			break;
+		case (4):
+			curp->wlevel = MANDOCLEVEL_FATAL;
+			break;
+		default:
+			fprintf(stderr, "-W%s: Bad argument\n", o);
+			return(0);
+		}
+	}
+
+	return(1);
+}
+
+static void
+mmsg(enum mandocerr t, enum mandoclevel lvl, 
+		const char *file, int line, int col, const char *msg)
+{
+
+	fprintf(stderr, "%s:%d:%d: %s: %s", 
+			file, line, col + 1, 
+			mparse_strlevel(lvl),
+			mparse_strerror(t));
+
+	if (msg)
+		fprintf(stderr, ": %s", msg);
+
+	fputc('\n', stderr);
+}
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/main.h	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,61 @@
+/*	$Id: main.h,v 1.15 2011/10/06 22:29:12 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef	MAIN_H
+#define	MAIN_H
+
+__BEGIN_DECLS
+
+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_mdoc(void *, const struct mdoc *);
+void		  html_man(void *, const struct man *);
+void		  html_free(void *);
+
+void		  tree_mdoc(void *, const struct mdoc *);
+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		  ascii_free(void *);
+
+void		 *pdf_alloc(char *);
+void		 *ps_alloc(char *);
+void		  pspdf_free(void *);
+
+void		  terminal_mdoc(void *, const struct mdoc *);
+void		  terminal_man(void *, const struct man *);
+
+__END_DECLS
+
+#endif /*!MAIN_H*/
--- /dev/null	Fri Jul 18 08:34:33 2014
+++ new/usr/src/cmd/mandoc/man.c	Fri Jul 18 08:34:33 2014
@@ -0,0 +1,690 @@
+/*	$Id: man.c,v 1.115 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libman.h"
+#include "libmandoc.h"
+
+const	char *const __man_macronames[MAN_MAX] = {		 
+	"br",		"TH",		"SH",		"SS",
+	"TP", 		"LP",		"PP",		"P",
+	"IP",		"HP",		"SM",		"SB",
+	"BI",		"IB",		"BR",		"RB",
+	"R",		"B",		"I",		"IR",
+	"RI",		"na",		"sp",		"nf",
+	"fi",		"RE",		"RS",		"DT",
+	"UC",		"PD",		"AT",		"in",
+	"ft",		"OP"
+	};
+
+const	char * const *man_macronames = __man_macronames;
+
+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_free(struct man_node *);
+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 *m)
+{
+
+	assert( ! (MAN_HALT & m->flags));
+	return(m->first);
+}
+
+
+const struct man_meta *
+man_meta(const struct man *m)
+{
+
+	assert( ! (MAN_HALT & m->flags));
+	return(&m->meta);
+}
+
+
+void
+man_reset(struct man *man)
+{
+
+	man_free1(man);
+	man_alloc1(man);
+}
+
+
+void
+man_free(struct man *man)
+{
+
+	man_free1(man);
+	free(man);
+}
+
+
+struct man *
+man_alloc(struct roff *roff, struct mparse *parse)
+{
+	struct man	*p;
+
+	p = mandoc_calloc(1, sizeof(struct man));
+
+	man_hash_init();
+	p->parse = parse;
+	p->roff = roff;
+
+	man_alloc1(p);
+	return(p);
+}
+
+
+int
+man_endparse(struct man *m)
+{
+
+	assert( ! (MAN_HALT & m->flags));
+	if (man_macroend(m))
+		return(1);
+	m->flags |= MAN_HALT;
+	return(0);
+}
+
+
+int
+man_parseln(struct man *m, int ln, char *buf, int offs)
+{
+
+	m->flags |= MAN_NEWLINE;
+
+	assert( ! (MAN_HALT & m->flags));
+
+	return (mandoc_getcontrol(buf, &offs) ?
+			man_pmacro(m, ln, buf, offs) : 
+			man_ptext(m, 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);
+}
+
+
+static void
+man_alloc1(struct man *m)
+{
+
+	memset(&m->meta, 0, sizeof(struct man_meta));
+	m->flags = 0;
+	m->last = mandoc_calloc(1, sizeof(struct man_node));
+	m->first = m->last;
+	m->last->type = MAN_ROOT;
+	m->last->tok = MAN_MAX;
+	m->next = MAN_NEXT_CHILD;
+}
+
+
+static int
+man_node_append(struct man *man, struct man_node *p)
+{
+
+	assert(man->last);
+	assert(man->first);
+	assert(MAN_ROOT != p->type);
+
+	switch (man->next) {
+	case (MAN_NEXT_SIBLING):
+		man->last->next = p;
+		p->prev = man->last;
+		p->parent = man->last->parent;
+		break;
+	case (MAN_NEXT_CHILD):
+		man->last->child = p;
+		p->parent = man->last;
+		break;
+	default:
+		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;
+		break;
+	case (MAN_TAIL):
+		assert(MAN_BLOCK == p->parent->type);
+		p->parent->tail = p;
+		break;
+	case (MAN_BODY):
+		assert(MAN_BLOCK == p->parent->type);
+		p->parent->body = p;
+		break;
+	default:
+		break;
+	}
+
+	man->last = p;
+
+	switch (p->type) {
+	case (MAN_TBL):
+		/* FALLTHROUGH */
+	case (MAN_TEXT):
+		if ( ! man_valid_post(man))
+			return(0);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+static struct man_node *
+man_node_alloc(struct man *m, int line, int pos, 
+		enum man_type type, enum mant tok)
+{
+	struct man_node *p;
+
+	p = mandoc_calloc(1, sizeof(struct man_node));
+	p->line = line;
+	p->pos = pos;
+	p->type = type;
+	p->tok = tok;
+
+	if (MAN_NEWLINE & m->flags)
+		p->flags |= MAN_LINE;
+	m->flags &= ~MAN_NEWLINE;
+	return(p);
+}
+
+
+int
+man_elem_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_ELEM, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_tail_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_TAIL, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_head_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_HEAD, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_body_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_BODY, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_block_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_BLOCK, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+int
+man_word_alloc(struct man *m, int line, int pos, const char *word)
+{
+	struct man_node	*n;
+
+	n = man_node_alloc(m, line, pos, MAN_TEXT, MAN_MAX);
+	n->string = roff_strdup(m->roff, word);
+
+	if ( ! man_node_append(m, n))
+		return(0);
+
+	m->next = MAN_NEXT_SIBLING;
+	return(1);
+}
+
+
+/*
+ * Free all of the resources held by a node.  This does NOT unlink a
+ * node from its context; for that, see man_node_unlink().
+ */
+static void
+man_node_free(struct man_node *p)
+{
+
+	if (p->string)
+		free(p->string);
+	free(p);
+}
+
+
+void
+man_node_delete(struct man *m, struct man_node *p)
+{
+
+	while (p->child)
+		man_node_delete(m, p->child);
+
+	man_node_unlink(m, p);
+	man_node_free(p);
+}
+
+int
+man_addeqn(struct man *m, const struct eqn *ep)
+{
+	struct man_node	*n;
+
+	assert( ! (MAN_HALT & m->flags));
+
+	n = man_node_alloc(m, ep->ln, ep->pos, MAN_EQN, MAN_MAX);
+	n->eqn = ep;
+
+	if ( ! man_node_append(m, n))
+		return(0);
+
+	m->next = MAN_NEXT_SIBLING;
+	return(man_descope(m, ep->ln, ep->pos));
+}
+
+int
+man_addspan(struct man *m, const struct tbl_span *sp)
+{
+	struct man_node	*n;
+
+	assert( ! (MAN_HALT & m->flags));
+
+	n = man_node_alloc(m, sp->line, 0, MAN_TBL, MAN_MAX);
+	n->span = sp;
+
+	if ( ! man_node_append(m, n))
+		return(0);
+
+	m->next = MAN_NEXT_SIBLING;
+	return(man_descope(m, sp->line, 0));
+}
+
+static int
+man_descope(struct man *m, int line, int offs)
+{
+	/*
+	 * Co-ordinate what happens with having a next-line scope open:
+	 * first close out the element scope (if applicable), then close
+	 * out the block scope (also if applicable).
+	 */
+
+	if (MAN_ELINE & m->flags) {
+		m->flags &= ~MAN_ELINE;
+		if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX))
+			return(0);
+	}
+
+	if ( ! (MAN_BLINE & m->flags))
+		return(1);
+	m->flags &= ~MAN_BLINE;
+
+	if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX))
+		return(0);
+	return(man_body_alloc(m, line, offs, m->last->tok));
+}
+
+static int
+man_ptext(struct man *m, int line, char *buf, int offs)
+{
+	int		 i;
+
+	/* Literal free-form text whitespace is preserved. */
+
+	if (MAN_LITERAL & m->flags) {
+		if ( ! man_word_alloc(m, line, offs, buf + offs))
+			return(0);
+		return(man_descope(m, line, offs));
+	}
+
+	/* Pump blank lines directly into the backend. */
+
+	for (i = offs; ' ' == buf[i]; i++)
+		/* Skip leading whitespace. */ ;
+
+	if ('\0' == buf[i]) {
+		/* Allocate a blank entry. */
+		if ( ! man_word_alloc(m, line, offs, ""))
+			return(0);
+		return(man_descope(m, line, offs));
+	}
+
+	/* 
+	 * Warn if the last un-escaped character is whitespace. Then
+	 * strip away the remaining spaces (tabs stay!).   
+	 */
+
+	i = (int)strlen(buf);
+	assert(i);
+
+	if (' ' == buf[i - 1] || '\t' == buf[i - 1]) {
+		if (i > 1 && '\\' != buf[i - 2])
+			man_pmsg(m, line, i - 1, MANDOCERR_EOLNSPACE);
+
+		for (--i; i && ' ' == buf[i]; i--)
+			/* Spin back to non-space. */ ;
+
+		/* Jump ahead of escaped whitespace. */
+		i += '\\' == buf[i] ? 2 : 1;
+
+		buf[i] = '\0';
+	}
+
+	if ( ! man_word_alloc(m, line, offs, buf + offs))
+		return(0);
+
+	/*
+	 * End-of-sentence check.  If the last character is an unescaped
+	 * EOS character, then flag the node as being the end of a
+	 * sentence.  The front-end will know how to interpret this.
+	 */
+
+	assert(i);
+	if (mandoc_eos(buf, (size_t)i, 0))
+		m->last->flags |= MAN_EOS;
+
+	return(man_descope(m, line, offs));
+}
+
+static int
+man_pmacro(struct man *m, int ln, char *buf, int offs)
+{
+	int		 i, ppos;
+	enum mant	 tok;
+	char		 mac[5];
+	struct man_node	*n;
+
+	if ('"' == buf[offs]) {
+		man_pmsg(m, 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.
+	 */
+
+	i = 0;
+	while (i < 4 && '\0' != buf[offs] && 
+			' ' != buf[offs] && '\t' != buf[offs])
+		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, m->parse, ln, 
+				ppos, "%s", buf + ppos - 1);
+		return(1);
+	}
+
+	/* The macro is sane.  Jump to the next 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(m, ln, offs - 1, MANDOCERR_EOLNSPACE);
+
+	/* 
+	 * 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.
+	 */
+
+	if ( ! (MAN_NSCOPED & man_macros[tok].flags) &&
+			m->flags & MAN_ELINE) {
+		n = m->last;
+		assert(MAN_TEXT != n->type);
+
+		/* Remove repeated NSCOPED macros causing ELINE. */
+
+		if (MAN_NSCOPED & man_macros[n->tok].flags)
+			n = n->parent;
+
+		mandoc_vmsg(MANDOCERR_LINESCOPE, m->parse, n->line, 
+		    n->pos, "%s breaks %s", man_macronames[tok],
+		    man_macronames[n->tok]);
+
+		man_node_delete(m, n);
+		m->flags &= ~MAN_ELINE;
+	}
+
+	/*
+	 * Remove prior BLINE macro that is being clobbered.
+	 */
+	if ((m->flags & MAN_BLINE) &&
+	    (MAN_BSCOPE & man_macros[tok].flags)) {
+		n = m->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;
+
+		assert(MAN_HEAD == n->type);
+		n = n->parent;
+		assert(MAN_BLOCK == n->type);
+		assert(MAN_SCOPED & man_macros[n->tok].flags);
+
+		mandoc_vmsg(MANDOCERR_LINESCOPE, m->parse, n->line, 
+		    n->pos, "%s breaks %s", man_macronames[tok],
+		    man_macronames[n->tok]);
+
+		man_node_delete(m, n);
+		m->flags &= ~MAN_BLINE;
+	}
+
+	/*
+	 * 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.
+	 */
+
+	if (MAN_BLINE & m->flags)
+		m->flags |= MAN_BPLINE;
+
+	/* Call to handler... */
+
+	assert(man_macros[tok].fp);
+	if ( ! (*man_macros[tok].fp)(m, tok, ln, ppos, &offs, buf))
+		goto err;
+
+	/* 
+	 * We weren't in a block-line scope when entering the
+	 * above-parsed macro, so return.
+	 */
+
+	if ( ! (MAN_BPLINE & m->flags)) {
+		m->flags &= ~MAN_ILINE; 
+		return(1);
+	}
+	m->flags &= ~MAN_BPLINE;
+
+	/*
+	 * If we're in a block scope, then allow this macro to slip by
+	 * without closing scope around it.
+	 */
+
+	if (MAN_ILINE & m->flags) {
+		m->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 & m->flags)
+		return(1);
+
+	/* Close out the block scope opened in the prior line.  */
+
+	assert(MAN_BLINE & m->flags);
+	m->flags &= ~MAN_BLINE;
+
+	if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX))
+		return(0);
+	return(man_body_alloc(m, ln, ppos, m->last->tok));
+
+err:	/* Error out. */
+
+	m->flags |= MAN_HALT;
+	return(0);
+}
+
+/*
+ * Unlink a node from its context.  If "m" is provided, the last parse
+ * point will also be adjusted accordingly.
+ */
+static void
+man_node_unlink(struct man *m, struct man_node *n)
+{
+
+	/* Adjust siblings. */
+
+	if (n->prev)
+		n->prev->next = n->next;
+	if (n->next)
+		n->next->prev = n->prev;
+
+	/* Adjust parent. */
+
+	if (n->parent) {
+		n->parent->nchild--;
+		if (n->parent->child == n)
+			n->parent->child = n->prev ? n->prev : n->next;
+	}
+
+	/* Adjust parse point, if applicable. */
+
+	if (m && m->last == n) {
+		/*XXX: this can occur when bailing from validation. */
+		/*assert(NULL == n->next);*/
+		if (n->prev) {
+			m->last = n->prev;
+			m->next = MAN_NEXT_SIBLING;
+		} else {
+			m->last = n->parent;
+			m->next = MAN_NEXT_CHILD;
+		}
+	}
+
+	if (m && m->first == n)
+		m->first = NULL;
+}
+
+const struct mparse *
+man_mparse(const struct man *m)
+{
+
+	assert(m && m->parse);
+	return(m->parse);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/man.h	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,113 @@
+/*	$Id: man.h,v 1.60 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef MAN_H
+#define MAN_H
+
+enum	mant {
+	MAN_br = 0,
+	MAN_TH,
+	MAN_SH,
+	MAN_SS,
+	MAN_TP,
+	MAN_LP,
+	MAN_PP,
+	MAN_P,
+	MAN_IP,
+	MAN_HP,
+	MAN_SM,
+	MAN_SB,
+	MAN_BI,
+	MAN_IB,
+	MAN_BR,
+	MAN_RB,
+	MAN_R,
+	MAN_B,
+	MAN_I,
+	MAN_IR,
+	MAN_RI,
+	MAN_na,
+	MAN_sp,
+	MAN_nf,
+	MAN_fi,
+	MAN_RE,
+	MAN_RS,
+	MAN_DT,
+	MAN_UC,
+	MAN_PD,
+	MAN_AT,
+	MAN_in,
+	MAN_ft,
+	MAN_OP,
+	MAN_MAX
+};
+
+enum	man_type {
+	MAN_TEXT,
+	MAN_ELEM,
+	MAN_ROOT,
+	MAN_BLOCK,
+	MAN_HEAD,
+	MAN_BODY,
+	MAN_TAIL,
+	MAN_TBL,
+	MAN_EQN
+};
+
+struct	man_meta {
+	char		*msec; /* `TH' section (1, 3p, etc.) */
+	char		*date; /* `TH' normalised date */
+	char		*vol; /* `TH' volume */
+	char		*title; /* `TH' title (e.g., FOO) */
+	char		*source; /* `TH' source (e.g., GNU) */
+};
+
+struct	man_node {
+	struct man_node	*parent; /* parent AST node */
+	struct man_node	*child; /* first child AST node */
+	struct man_node	*next; /* sibling AST node */
+	struct man_node	*prev; /* prior sibling AST node */
+	int		 nchild; /* number children */
+	int		 line;
+	int		 pos;
+	enum mant	 tok; /* tok or MAN__MAX if none */
+	int		 flags;
+#define	MAN_VALID	(1 << 0) /* has been validated */
+#define	MAN_EOS		(1 << 2) /* at sentence boundary */
+#define	MAN_LINE	(1 << 3) /* first macro/text on line */
+	enum man_type	 type; /* AST node type */
+	char		*string; /* TEXT node argument */
+	struct man_node	*head; /* BLOCK node HEAD ptr */
+	struct man_node *tail; /* BLOCK node TAIL ptr */
+	struct man_node	*body; /* BLOCK node BODY ptr */
+	const struct tbl_span *span; /* TBL */
+	const struct eqn *eqn; /* EQN */
+};
+
+/* Names of macros.  Index is enum mant. */
+extern	const char *const *man_macronames;
+
+__BEGIN_DECLS
+
+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 *);
+
+__END_DECLS
+
+#endif /*!MAN_H*/
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/man_hash.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,107 @@
+/*	$Id: man_hash.c,v 1.25 2011/07/24 18:15:14 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libman.h"
+
+#define	HASH_DEPTH	 6
+
+#define	HASH_ROW(x) do { \
+		if (isupper((unsigned char)(x))) \
+			(x) -= 65; \
+		else \
+			(x) -= 97; \
+		(x) *= HASH_DEPTH; \
+	} while (/* CONSTCOND */ 0)
+
+/*
+ * Lookup table is indexed first by lower-case first letter (plus one
+ * for the period, which is stored in the last row), then by lower or
+ * uppercase second letter.  Buckets correspond to the index of the
+ * macro (the integer value of the enum stored as a char to save a bit
+ * of space).
+ */
+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.
+ */
+void
+man_hash_init(void)
+{
+	int		 i, j, x;
+
+	memset(table, UCHAR_MAX, sizeof(table));
+
+	assert(/* LINTED */ 
+			MAN_MAX < UCHAR_MAX);
+
+	for (i = 0; i < (int)MAN_MAX; i++) {
+		x = man_macronames[i][0];
+
+		assert(isalpha((unsigned char)x));
+
+		HASH_ROW(x);
+
+		for (j = 0; j < HASH_DEPTH; j++)
+			if (UCHAR_MAX == table[x + j]) {
+				table[x + j] = (unsigned char)i;
+				break;
+			}
+
+		assert(j < HASH_DEPTH);
+	}
+}
+
+
+enum mant
+man_hash_find(const char *tmp)
+{
+	int		 x, y, i;
+	enum mant	 tok;
+
+	if ('\0' == (x = tmp[0]))
+		return(MAN_MAX);
+	if ( ! (isalpha((unsigned char)x)))
+		return(MAN_MAX);
+
+	HASH_ROW(x);
+
+	for (i = 0; i < HASH_DEPTH; i++) {
+		if (UCHAR_MAX == (y = table[x + i]))
+			return(MAN_MAX);
+
+		tok = (enum mant)y;
+		if (0 == strcmp(tmp, man_macronames[tok]))
+			return(tok);
+	}
+
+	return(MAN_MAX);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/man_html.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,688 @@
+/*	$Id: man_html.c,v 1.86 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "html.h"
+#include "man.h"
+#include "main.h"
+
+/* TODO: preserve ident widths. */
+/* FIXME: have PD set the default vspace width. */
+
+#define	INDENT		  5
+
+#define	MAN_ARGS	  const struct man_meta *m, \
+			  const struct man_node *n, \
+			  struct mhtml *mh, \
+			  struct html *h
+
+struct	mhtml {
+	int		  fl;
+#define	MANH_LITERAL	 (1 << 0) /* literal context */
+};
+
+struct	htmlman {
+	int		(*pre)(MAN_ARGS);
+	int		(*post)(MAN_ARGS);
+};
+
+static	void		  print_bvspace(struct html *, 
+				const struct man_node *);
+static	void		  print_man(MAN_ARGS);
+static	void		  print_man_head(MAN_ARGS);
+static	void		  print_man_nodelist(MAN_ARGS);
+static	void		  print_man_node(MAN_ARGS);
+static	int		  a2width(const struct man_node *,
+				struct roffsu *);
+static	int		  man_B_pre(MAN_ARGS);
+static	int		  man_HP_pre(MAN_ARGS);
+static	int		  man_IP_pre(MAN_ARGS);
+static	int		  man_I_pre(MAN_ARGS);
+static	int		  man_OP_pre(MAN_ARGS);
+static	int		  man_PP_pre(MAN_ARGS);
+static	int		  man_RS_pre(MAN_ARGS);
+static	int		  man_SH_pre(MAN_ARGS);
+static	int		  man_SM_pre(MAN_ARGS);
+static	int		  man_SS_pre(MAN_ARGS);
+static	int		  man_alt_pre(MAN_ARGS);
+static	int		  man_br_pre(MAN_ARGS);
+static	int		  man_ign_pre(MAN_ARGS);
+static	int		  man_in_pre(MAN_ARGS);
+static	int		  man_literal_pre(MAN_ARGS);
+static	void		  man_root_post(MAN_ARGS);
+static	void		  man_root_pre(MAN_ARGS);
+
+static	const struct htmlman mans[MAN_MAX] = {
+	{ man_br_pre, NULL }, /* br */
+	{ NULL, NULL }, /* TH */
+	{ man_SH_pre, NULL }, /* SH */
+	{ man_SS_pre, NULL }, /* SS */
+	{ man_IP_pre, NULL }, /* TP */
+	{ man_PP_pre, NULL }, /* LP */
+	{ man_PP_pre, NULL }, /* PP */
+	{ man_PP_pre, NULL }, /* P */
+	{ man_IP_pre, NULL }, /* IP */
+	{ man_HP_pre, NULL }, /* HP */ 
+	{ man_SM_pre, NULL }, /* SM */
+	{ man_SM_pre, NULL }, /* SB */
+	{ man_alt_pre, NULL }, /* BI */
+	{ man_alt_pre, NULL }, /* IB */
+	{ man_alt_pre, NULL }, /* BR */
+	{ man_alt_pre, NULL }, /* RB */
+	{ NULL, NULL }, /* R */
+	{ man_B_pre, NULL }, /* B */
+	{ 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 */
+	{ NULL, NULL }, /* RE */
+	{ man_RS_pre, NULL }, /* RS */
+	{ man_ign_pre, NULL }, /* DT */
+	{ man_ign_pre, NULL }, /* UC */
+	{ man_ign_pre, NULL }, /* PD */
+	{ man_ign_pre, NULL }, /* AT */
+	{ man_in_pre, NULL }, /* in */
+	{ man_ign_pre, NULL }, /* ft */
+	{ man_OP_pre, NULL }, /* OP */
+};
+
+/*
+ * Printing leading vertical space before a block.
+ * This is used for the paragraph macros.
+ * The rules are pretty simple, since there's very little nesting going
+ * on here.  Basically, if we're the first within another block (SS/SH),
+ * then don't emit vertical space.  If we are (RS), then do.  If not the
+ * first, print it.
+ */
+static void
+print_bvspace(struct html *h, const struct man_node *n)
+{
+
+	if (n->body && n->body->child)
+		if (MAN_TBL == n->body->child->type)
+			return;
+
+	if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
+		if (NULL == n->prev)
+			return;
+
+	print_otag(h, TAG_P, 0, NULL);
+}
+
+void
+html_man(void *arg, const struct man *m)
+{
+	struct mhtml	 mh;
+
+	memset(&mh, 0, sizeof(struct mhtml));
+	print_man(man_meta(m), man_node(m), &mh, (struct html *)arg);
+	putchar('\n');
+}
+
+static void
+print_man(MAN_ARGS) 
+{
+	struct tag	*t, *tt;
+	struct htmlpair	 tag;
+
+	PAIR_CLASS_INIT(&tag, "mandoc");
+
+	if ( ! (HTML_FRAGMENT & h->oflags)) {
+		print_gen_decls(h);
+		t = print_otag(h, TAG_HTML, 0, NULL);
+		tt = print_otag(h, TAG_HEAD, 0, NULL);
+		print_man_head(m, n, mh, h);
+		print_tagq(h, tt);
+		print_otag(h, TAG_BODY, 0, NULL);
+		print_otag(h, TAG_DIV, 1, &tag);
+	} else 
+		t = print_otag(h, TAG_DIV, 1, &tag);
+
+	print_man_nodelist(m, n, mh, h);
+	print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static void
+print_man_head(MAN_ARGS)
+{
+
+	print_gen_head(h);
+	assert(m->title);
+	assert(m->msec);
+	bufcat_fmt(h, "%s(%s)", m->title, m->msec);
+	print_otag(h, TAG_TITLE, 0, NULL);
+	print_text(h, h->buf);
+}
+
+
+static void
+print_man_nodelist(MAN_ARGS)
+{
+
+	print_man_node(m, n, mh, h);
+	if (n->next)
+		print_man_nodelist(m, n->next, mh, h);
+}
+
+
+static void
+print_man_node(MAN_ARGS)
+{
+	int		 child;
+	struct tag	*t;
+
+	child = 1;
+	t = h->tags.head;
+
+	switch (n->type) {
+	case (MAN_ROOT):
+		man_root_pre(m, 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.
+		 */
+		if ('\0' == *n->string) {
+			print_otag(h, TAG_P, 0, NULL);
+			return;
+		}
+
+		if (' ' == *n->string && MAN_LINE & n->flags)
+			print_otag(h, TAG_BR, 0, NULL);
+		else if (MANH_LITERAL & mh->fl && n->prev)
+			print_otag(h, TAG_BR, 0, NULL);
+
+		print_text(h, n->string);
+		return;
+	case (MAN_EQN):
+		print_eqn(h, n->eqn);
+		break;
+	case (MAN_TBL):
+		/*
+		 * This will take care of initialising all of the table
+		 * state data for the first table, then tearing it down
+		 * for the last one.
+		 */
+		print_tbl(h, n->span);
+		return;
+	default:
+		/* 
+		 * Close out scope of font prior to opening a macro
+		 * scope.
+		 */
+		if (HTMLFONT_NONE != h->metac) {
+			h->metal = h->metac;
+			h->metac = HTMLFONT_NONE;
+		}
+
+		/*
+		 * Close out the current table, if it's open, and unset
+		 * the "meta" table state.  This will be reopened on the
+		 * next table element.
+		 */
+		if (h->tblt) {
+			print_tblclose(h);
+			t = h->tags.head;
+		}
+		if (mans[n->tok].pre)
+			child = (*mans[n->tok].pre)(m, n, mh, h);
+		break;
+	}
+
+	if (child && n->child)
+		print_man_nodelist(m, n->child, mh, h);
+
+	/* This will automatically close out any font scope. */
+	print_stagq(h, t);
+
+	switch (n->type) {
+	case (MAN_ROOT):
+		man_root_post(m, n, mh, h);
+		break;
+	case (MAN_EQN):
+		break;
+	default:
+		if (mans[n->tok].post)
+			(*mans[n->tok].post)(m, n, mh, h);
+		break;
+	}
+}
+
+
+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))
+		return(1);
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static void
+man_root_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag[3];
+	struct tag	*t, *tt;
+	char		 b[BUFSIZ], title[BUFSIZ];
+
+	b[0] = 0;
+	if (m->vol)
+		(void)strlcat(b, m->vol, BUFSIZ);
+
+	assert(m->title);
+	assert(m->msec);
+	snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->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);
+
+	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);
+	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);
+	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);
+	print_text(h, title);
+	print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static void
+man_root_post(MAN_ARGS)
+{
+	struct htmlpair	 tag[3];
+	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);
+
+	tt = print_otag(h, TAG_TR, 0, NULL);
+
+	PAIR_CLASS_INIT(&tag[0], "foot-date");
+	print_otag(h, TAG_TD, 1, tag);
+
+	assert(m->date);
+	print_text(h, m->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);
+
+	if (m->source)
+		print_text(h, m->source);
+	print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static int
+man_br_pre(MAN_ARGS)
+{
+	struct roffsu	 su;
+	struct htmlpair	 tag;
+
+	SCALE_VS_INIT(&su, 1);
+
+	if (MAN_sp == n->tok) {
+		if (NULL != (n = n->child))
+			if ( ! a2roffsu(n->string, &su, SCALE_VS))
+				SCALE_VS_INIT(&su, atoi(n->string));
+	} else
+		su.scale = 0;
+
+	bufinit(h);
+	bufcat_su(h, "height", &su);
+	PAIR_STYLE_INIT(&tag, h);
+	print_otag(h, TAG_DIV, 1, &tag);
+
+	/* So the div isn't empty: */
+	print_text(h, "\\~");
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_SH_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MAN_BLOCK == n->type) {
+		mh->fl &= ~MANH_LITERAL;
+		PAIR_CLASS_INIT(&tag, "section");
+		print_otag(h, TAG_DIV, 1, &tag);
+		return(1);
+	} else if (MAN_BODY == n->type)
+		return(1);
+
+	print_otag(h, TAG_H1, 0, NULL);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_alt_pre(MAN_ARGS)
+{
+	const struct man_node	*nn;
+	int		 i, savelit;
+	enum htmltag	 fp;
+	struct tag	*t;
+
+	if ((savelit = mh->fl & MANH_LITERAL)) 
+		print_otag(h, TAG_BR, 0, NULL);
+
+	mh->fl &= ~MANH_LITERAL;
+
+	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+		t = NULL;
+		switch (n->tok) {
+		case (MAN_BI):
+			fp = i % 2 ? TAG_I : TAG_B;
+			break;
+		case (MAN_IB):
+			fp = i % 2 ? TAG_B : TAG_I;
+			break;
+		case (MAN_RI):
+			fp = i % 2 ? TAG_I : TAG_MAX;
+			break;
+		case (MAN_IR):
+			fp = i % 2 ? TAG_MAX : TAG_I;
+			break;
+		case (MAN_BR):
+			fp = i % 2 ? TAG_MAX : TAG_B;
+			break;
+		case (MAN_RB):
+			fp = i % 2 ? TAG_B : TAG_MAX;
+			break;
+		default:
+			abort();
+			/* NOTREACHED */
+		}
+
+		if (i)
+			h->flags |= HTML_NOSPACE;
+
+		if (TAG_MAX != fp)
+			t = print_otag(h, fp, 0, NULL);
+
+		print_man_node(m, nn, mh, h);
+
+		if (t)
+			print_tagq(h, t);
+	}
+
+	if (savelit)
+		mh->fl |= MANH_LITERAL;
+
+	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)
+{
+	struct htmlpair	 tag;
+
+	if (MAN_BLOCK == n->type) {
+		mh->fl &= ~MANH_LITERAL;
+		PAIR_CLASS_INIT(&tag, "subsection");
+		print_otag(h, TAG_DIV, 1, &tag);
+		return(1);
+	} else if (MAN_BODY == n->type)
+		return(1);
+
+	print_otag(h, TAG_H2, 0, NULL);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_PP_pre(MAN_ARGS)
+{
+
+	if (MAN_HEAD == n->type)
+		return(0);
+	else if (MAN_BLOCK == n->type)
+		print_bvspace(h, n);
+
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_IP_pre(MAN_ARGS)
+{
+	const struct man_node	*nn;
+
+	if (MAN_BODY == n->type) { 
+		print_otag(h, TAG_DD, 0, NULL);
+		return(1);
+	} else if (MAN_HEAD != n->type) {
+		print_otag(h, TAG_DL, 0, NULL);
+		return(1);
+	}
+
+	/* FIXME: width specification. */
+
+	print_otag(h, TAG_DT, 0, NULL);
+
+	/* For IP, only print the first header element. */
+
+	if (MAN_IP == n->tok && n->child)
+		print_man_node(m, n->child, mh, h);
+
+	/* 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(m, nn, mh, h);
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_HP_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag;
+	struct roffsu	 su;
+	const struct man_node *np;
+
+	if (MAN_HEAD == n->type)
+		return(0);
+	else if (MAN_BLOCK != n->type)
+		return(1);
+
+	np = n->head->child;
+
+	if (NULL == np || ! a2width(np, &su))
+		SCALE_HS_INIT(&su, INDENT);
+
+	bufinit(h);
+
+	print_bvspace(h, n);
+	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);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_OP_pre(MAN_ARGS)
+{
+	struct tag	*tt;
+	struct htmlpair	 tag;
+
+	print_text(h, "[");
+	h->flags |= HTML_NOSPACE;
+	PAIR_CLASS_INIT(&tag, "opt");
+	tt = print_otag(h, TAG_SPAN, 1, &tag);
+
+	if (NULL != (n = n->child)) {
+		print_otag(h, TAG_B, 0, NULL);
+		print_text(h, n->string);
+	}
+
+	print_stagq(h, tt);
+
+	if (NULL != n && NULL != n->next) {
+		print_otag(h, TAG_I, 0, NULL);
+		print_text(h, n->next->string);
+	}
+
+	print_stagq(h, tt);
+	h->flags |= HTML_NOSPACE;
+	print_text(h, "]");
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+man_B_pre(MAN_ARGS)
+{
+
+	print_otag(h, TAG_B, 0, NULL);
+	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)
+{
+
+	if (MAN_nf != n->tok) {
+		print_otag(h, TAG_BR, 0, NULL);
+		mh->fl &= ~MANH_LITERAL;
+	} else
+		mh->fl |= MANH_LITERAL;
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_in_pre(MAN_ARGS)
+{
+
+	print_otag(h, TAG_BR, 0, NULL);
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_ign_pre(MAN_ARGS)
+{
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_RS_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag;
+	struct roffsu	 su;
+
+	if (MAN_HEAD == n->type)
+		return(0);
+	else if (MAN_BODY == n->type)
+		return(1);
+
+	SCALE_HS_INIT(&su, INDENT);
+	if (n->head->child)
+		a2width(n->head->child, &su);
+
+	bufinit(h);
+	bufcat_su(h, "margin-left", &su);
+	PAIR_STYLE_INIT(&tag, h);
+	print_otag(h, TAG_DIV, 1, &tag);
+	return(1);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/man_macro.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,484 @@
+/*	$Id: man_macro.c,v 1.71 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libman.h"
+
+enum	rew {
+	REW_REWIND,
+	REW_NOHALT,
+	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, 
+				int *, char *, char **);
+
+static	int		 rew_scope(enum man_type, 
+				struct man *, enum mant);
+static	enum rew	 rew_dohalt(enum mant, enum man_type, 
+				const struct man_node *);
+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 */
+	{ in_line_eoln, MAN_BSCOPE }, /* TH */
+	{ blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SH */
+	{ blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SS */
+	{ blk_imp, MAN_BSCOPE | MAN_SCOPED | MAN_FSCOPED }, /* TP */
+	{ blk_imp, MAN_BSCOPE }, /* LP */
+	{ blk_imp, MAN_BSCOPE }, /* PP */
+	{ 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, 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, 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_exp, MAN_EXPLICIT }, /* RS */
+	{ in_line_eoln, 0 }, /* DT */
+	{ in_line_eoln, 0 }, /* UC */
+	{ in_line_eoln, 0 }, /* PD */
+	{ in_line_eoln, 0 }, /* AT */
+	{ in_line_eoln, 0 }, /* in */
+	{ in_line_eoln, 0 }, /* ft */
+	{ in_line_eoln, 0 }, /* OP */
+};
+
+const	struct man_macro * const man_macros = __man_macros;
+
+
+/*
+ * Warn when "n" is an explicit non-roff macro.
+ */
+static void
+rew_warn(struct man *m, 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(m, 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 *m, const struct man_node *to, 
+		enum mandocerr er)
+{
+	struct man_node	*n;
+
+	assert(to);
+
+	m->next = MAN_NEXT_SIBLING;
+
+	/* LINTED */
+	while (m->last != to) {
+		/*
+		 * Save the parent here, because we may delete the
+		 * m->last node in the post-validation phase and reset
+		 * it to m->last->parent, causing a step in the closing
+		 * out to be lost.
+		 */
+		n = m->last->parent;
+		rew_warn(m, m->last, er);
+		if ( ! man_valid_post(m))
+			return(0);
+		m->last = n;
+		assert(m->last);
+	}
+
+	rew_warn(m, m->last, er);
+	if ( ! man_valid_post(m))
+		return(0);
+
+	return(1);
+}
+
+
+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)
+		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 
+rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
+{
+	enum rew	 c;
+
+	/* We cannot progress beyond the root ever. */
+	if (MAN_ROOT == n->type)
+		return(REW_HALT);
+
+	assert(n->parent);
+
+	/* Normal nodes shouldn't go to the level of the root. */
+	if (MAN_ROOT == n->parent->type)
+		return(REW_REWIND);
+
+	/* Already-validated nodes should be closed out. */
+	if (MAN_VALID & n->flags)
+		return(REW_NOHALT);
+
+	/* First: rewind to ourselves. */
+	if (type == n->type && tok == n->tok)
+		return(REW_REWIND);
+
+	/* 
+	 * Next follow the implicit scope-smashings as defined by man.7:
+	 * section, sub-section, etc.
+	 */
+
+	switch (tok) {
+	case (MAN_SH):
+		break;
+	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):
+		/* Rewind to a subsection, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SS, type, n)))
+			return(c);
+		/* Rewind to a section, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+			return(c);
+		break;
+	default:
+		/* Rewind to an offsetter, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_RS, type, n)))
+			return(c);
+		/* Rewind to a subsection, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SS, type, n)))
+			return(c);
+		/* Rewind to a section, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+			return(c);
+		break;
+	}
+
+	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
+rew_scope(enum man_type type, struct man *m, enum mant tok)
+{
+	struct man_node	*n;
+	enum rew	 c;
+
+	/* LINTED */
+	for (n = m->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);
+		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(m, n, MANDOCERR_MAX));
+}
+
+
+/*
+ * Close out a generic explicit macro.
+ */
+/* ARGSUSED */
+int
+blk_close(MACRO_PROT_ARGS)
+{
+	enum mant	 	 ntok;
+	const struct man_node	*nn;
+
+	switch (tok) {
+	case (MAN_RE):
+		ntok = MAN_RS;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	for (nn = m->last->parent; nn; nn = nn->parent)
+		if (ntok == nn->tok)
+			break;
+
+	if (NULL == nn)
+		man_pmsg(m, line, ppos, MANDOCERR_NOSCOPE);
+
+	if ( ! rew_scope(MAN_BODY, m, ntok))
+		return(0);
+	if ( ! rew_scope(MAN_BLOCK, m, ntok))
+		return(0);
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+int
+blk_exp(MACRO_PROT_ARGS)
+{
+	int		 la;
+	char		*p;
+
+	/* 
+	 * Close out prior scopes.  "Regular" explicit macros cannot be
+	 * nested, but we allow roff macros to be placed just about
+	 * anywhere.
+	 */
+
+	if ( ! man_block_alloc(m, line, ppos, tok))
+		return(0);
+	if ( ! man_head_alloc(m, line, ppos, tok))
+		return(0);
+
+	for (;;) {
+		la = *pos;
+		if ( ! man_args(m, line, pos, buf, &p))
+			break;
+		if ( ! man_word_alloc(m, line, la, p))
+			return(0);
+	}
+
+	assert(m);
+	assert(tok != MAN_MAX);
+
+	if ( ! rew_scope(MAN_HEAD, m, tok))
+		return(0);
+	return(man_body_alloc(m, 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
+blk_imp(MACRO_PROT_ARGS)
+{
+	int		 la;
+	char		*p;
+	struct man_node	*n;
+
+	/* Close out prior scopes. */
+
+	if ( ! rew_scope(MAN_BODY, m, tok))
+		return(0);
+	if ( ! rew_scope(MAN_BLOCK, m, tok))
+		return(0);
+
+	/* Allocate new block & head scope. */
+
+	if ( ! man_block_alloc(m, line, ppos, tok))
+		return(0);
+	if ( ! man_head_alloc(m, line, ppos, tok))
+		return(0);
+
+	n = m->last;
+
+	/* Add line arguments. */
+
+	for (;;) {
+		la = *pos;
+		if ( ! man_args(m, line, pos, buf, &p))
+			break;
+		if ( ! man_word_alloc(m, line, la, p))
+			return(0);
+	}
+
+	/* Close out head and open body (unless MAN_SCOPE). */
+
+	if (MAN_SCOPED & man_macros[tok].flags) {
+		/* If we're forcing scope (`TP'), keep it open. */
+		if (MAN_FSCOPED & man_macros[tok].flags) {
+			m->flags |= MAN_BLINE;
+			return(1);
+		} else if (n == m->last) {
+			m->flags |= MAN_BLINE;
+			return(1);
+		}
+	}
+
+	if ( ! rew_scope(MAN_HEAD, m, tok))
+		return(0);
+	return(man_body_alloc(m, line, ppos, tok));
+}
+
+
+/* ARGSUSED */
+int
+in_line_eoln(MACRO_PROT_ARGS)
+{
+	int		 la;
+	char		*p;
+	struct man_node	*n;
+
+	if ( ! man_elem_alloc(m, line, ppos, tok))
+		return(0);
+
+	n = m->last;
+
+	for (;;) {
+		la = *pos;
+		if ( ! man_args(m, line, pos, buf, &p))
+			break;
+		if ( ! man_word_alloc(m, line, la, p))
+			return(0);
+	}
+
+	/*
+	 * If no arguments are specified and this is MAN_SCOPED (i.e.,
+	 * next-line scoped), then set our mode to indicate that we're
+	 * waiting for terms to load into our context.
+	 */
+
+	if (n == m->last && MAN_SCOPED & man_macros[tok].flags) {
+		assert( ! (MAN_NSCOPED & man_macros[tok].flags));
+		m->flags |= MAN_ELINE;
+		return(1);
+	} 
+
+	/* Set ignorable context, if applicable. */
+
+	if (MAN_NSCOPED & man_macros[tok].flags) {
+		assert( ! (MAN_SCOPED & man_macros[tok].flags));
+		m->flags |= MAN_ILINE;
+	}
+
+	assert(MAN_ROOT != m->last->type);
+	m->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
+	 * its sibling.
+	 */
+
+	for ( ; m->last; m->last = m->last->parent) {
+		if (m->last == n)
+			break;
+		if (m->last->type == MAN_ROOT)
+			break;
+		if ( ! man_valid_post(m))
+			return(0);
+	}
+
+	assert(m->last);
+
+	/*
+	 * Same here regarding whether we're back at the root. 
+	 */
+
+	if (m->last->type != MAN_ROOT && ! man_valid_post(m))
+		return(0);
+
+	return(1);
+}
+
+
+int
+man_macroend(struct man *m)
+{
+
+	return(man_unscope(m, m->first, MANDOCERR_SCOPEEXIT));
+}
+
+static int
+man_args(struct man *m, int line, int *pos, char *buf, char **v)
+{
+	char	 *start;
+
+	assert(*pos);
+	*v = start = buf + *pos;
+	assert(' ' != *start);
+
+	if ('\0' == *start)
+		return(0);
+
+	*v = mandoc_getarg(m->parse, v, line, pos);
+	return(1);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/man_term.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,1117 @@
+/*	$Id: man_term.c,v 1.127 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "man.h"
+#include "term.h"
+#include "main.h"
+
+#define	MAXMARGINS	  64 /* maximum number of indented scopes */
+
+/* FIXME: have PD set the default vspace width. */
+
+struct	mtermp {
+	int		  fl;
+#define	MANT_LITERAL	 (1 << 0)
+	size_t		  lmargin[MAXMARGINS]; /* margins (incl. visible page) */
+	int		  lmargincur; /* index of current margin */
+	int		  lmarginsz; /* actual number of nested margins */
+	size_t		  offset; /* default offset to visible page */
+};
+
+#define	DECL_ARGS 	  struct termp *p, \
+			  struct mtermp *mt, \
+			  const struct man_node *n, \
+			  const struct man_meta *m
+
+struct	termact {
+	int		(*pre)(DECL_ARGS);
+	void		(*post)(DECL_ARGS);
+	int		  flags;
+#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 *, 
+				const struct man_node *);
+
+static	int		  pre_B(DECL_ARGS);
+static	int		  pre_HP(DECL_ARGS);
+static	int		  pre_I(DECL_ARGS);
+static	int		  pre_IP(DECL_ARGS);
+static	int		  pre_OP(DECL_ARGS);
+static	int		  pre_PP(DECL_ARGS);
+static	int		  pre_RS(DECL_ARGS);
+static	int		  pre_SH(DECL_ARGS);
+static	int		  pre_SS(DECL_ARGS);
+static	int		  pre_TP(DECL_ARGS);
+static	int		  pre_alternate(DECL_ARGS);
+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_sp(DECL_ARGS);
+
+static	void		  post_IP(DECL_ARGS);
+static	void		  post_HP(DECL_ARGS);
+static	void		  post_RS(DECL_ARGS);
+static	void		  post_SH(DECL_ARGS);
+static	void		  post_SS(DECL_ARGS);
+static	void		  post_TP(DECL_ARGS);
+
+static	const struct termact termacts[MAN_MAX] = {
+	{ pre_sp, NULL, MAN_NOTEXT }, /* br */
+	{ NULL, NULL, 0 }, /* TH */
+	{ pre_SH, post_SH, 0 }, /* SH */
+	{ pre_SS, post_SS, 0 }, /* SS */
+	{ pre_TP, post_TP, 0 }, /* TP */
+	{ pre_PP, NULL, 0 }, /* LP */
+	{ pre_PP, NULL, 0 }, /* PP */
+	{ pre_PP, NULL, 0 }, /* P */
+	{ pre_IP, post_IP, 0 }, /* IP */
+	{ pre_HP, post_HP, 0 }, /* HP */ 
+	{ NULL, NULL, 0 }, /* SM */
+	{ pre_B, NULL, 0 }, /* SB */
+	{ pre_alternate, NULL, 0 }, /* BI */
+	{ pre_alternate, NULL, 0 }, /* IB */
+	{ pre_alternate, NULL, 0 }, /* BR */
+	{ pre_alternate, NULL, 0 }, /* RB */
+	{ NULL, NULL, 0 }, /* R */
+	{ pre_B, NULL, 0 }, /* B */
+	{ 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, 0 }, /* PD */
+	{ pre_ign, NULL, 0 }, /* AT */
+	{ pre_in, NULL, MAN_NOTEXT }, /* in */
+	{ pre_ft, NULL, MAN_NOTEXT }, /* ft */
+	{ pre_OP, NULL, 0 }, /* OP */
+};
+
+
+
+void
+terminal_man(void *arg, const struct man *man)
+{
+	struct termp		*p;
+	const struct man_node	*n;
+	const struct man_meta	*m;
+	struct mtermp		 mt;
+
+	p = (struct termp *)arg;
+
+	if (0 == p->defindent)
+		p->defindent = 7;
+
+	p->overstep = 0;
+	p->maxrmargin = p->defrmargin;
+	p->tabwidth = term_len(p, 5);
+
+	if (NULL == p->symtab)
+		p->symtab = mchars_alloc();
+
+	n = man_node(man);
+	m = man_meta(man);
+
+	term_begin(p, print_man_head, print_man_foot, m);
+	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);
+
+	if (n->child)
+		print_man_nodelist(p, &mt, n->child, m);
+
+	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));
+}
+
+/*
+ * Printing leading vertical space before a block.
+ * This is used for the paragraph macros.
+ * The rules are pretty simple, since there's very little nesting going
+ * on here.  Basically, if we're the first within another block (SS/SH),
+ * then don't emit vertical space.  If we are (RS), then do.  If not the
+ * first, print it.
+ */
+static void
+print_bvspace(struct termp *p, const struct man_node *n)
+{
+
+	term_newln(p);
+
+	if (n->body && n->body->child)
+		if (MAN_TBL == n->body->child->type)
+			return;
+
+	if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
+		if (NULL == n->prev)
+			return;
+
+	term_vspace(p);
+}
+
+/* ARGSUSED */
+static int
+pre_ign(DECL_ARGS)
+{
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_I(DECL_ARGS)
+{
+
+	term_fontrepl(p, TERMFONT_UNDER);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+pre_literal(DECL_ARGS)
+{
+
+	term_newln(p);
+
+	if (MAN_nf == n->tok)
+		mt->fl |= MANT_LITERAL;
+	else
+		mt->fl &= ~MANT_LITERAL;
+
+	/*
+	 * Unlike .IP and .TP, .HP does not have a HEAD.
+	 * So in case a second call to term_flushln() is needed,
+	 * indentation has to be set up explicitly.
+	 */
+	if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) {
+		p->offset = p->rmargin;
+		p->rmargin = p->maxrmargin;
+		p->flags &= ~(TERMP_NOBREAK | TERMP_TWOSPACE);
+		p->flags |= TERMP_NOSPACE;
+	}
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_alternate(DECL_ARGS)
+{
+	enum termfont		 font[2];
+	const struct man_node	*nn;
+	int			 savelit, i;
+
+	switch (n->tok) {
+	case (MAN_RB):
+		font[0] = TERMFONT_NONE;
+		font[1] = TERMFONT_BOLD;
+		break;
+	case (MAN_RI):
+		font[0] = TERMFONT_NONE;
+		font[1] = TERMFONT_UNDER;
+		break;
+	case (MAN_BR):
+		font[0] = TERMFONT_BOLD;
+		font[1] = TERMFONT_NONE;
+		break;
+	case (MAN_BI):
+		font[0] = TERMFONT_BOLD;
+		font[1] = TERMFONT_UNDER;
+		break;
+	case (MAN_IR):
+		font[0] = TERMFONT_UNDER;
+		font[1] = TERMFONT_NONE;
+		break;
+	case (MAN_IB):
+		font[0] = TERMFONT_UNDER;
+		font[1] = TERMFONT_BOLD;
+		break;
+	default:
+		abort();
+	}
+
+	savelit = MANT_LITERAL & mt->fl;
+	mt->fl &= ~MANT_LITERAL;
+
+	for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
+		term_fontrepl(p, font[i]);
+		if (savelit && NULL == nn->next)
+			mt->fl |= MANT_LITERAL;
+		print_man_node(p, mt, nn, m);
+		if (nn->next)
+			p->flags |= TERMP_NOSPACE;
+	}
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_B(DECL_ARGS)
+{
+
+	term_fontrepl(p, TERMFONT_BOLD);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+pre_OP(DECL_ARGS)
+{
+
+	term_word(p, "[");
+	p->flags |= TERMP_NOSPACE;
+
+	if (NULL != (n = n->child)) {
+		term_fontrepl(p, TERMFONT_BOLD);
+		term_word(p, n->string);
+	}
+	if (NULL != n && NULL != n->next) {
+		term_fontrepl(p, TERMFONT_UNDER);
+		term_word(p, n->next->string);
+	}
+
+	term_fontrepl(p, TERMFONT_NONE);
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, "]");
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_ft(DECL_ARGS)
+{
+	const char	*cp;
+
+	if (NULL == n->child) {
+		term_fontlast(p);
+		return(0);
+	}
+
+	cp = n->child->string;
+	switch (*cp) {
+	case ('4'):
+		/* FALLTHROUGH */
+	case ('3'):
+		/* FALLTHROUGH */
+	case ('B'):
+		term_fontrepl(p, TERMFONT_BOLD);
+		break;
+	case ('2'):
+		/* FALLTHROUGH */
+	case ('I'):
+		term_fontrepl(p, TERMFONT_UNDER);
+		break;
+	case ('P'):
+		term_fontlast(p);
+		break;
+	case ('1'):
+		/* FALLTHROUGH */
+	case ('C'):
+		/* FALLTHROUGH */
+	case ('R'):
+		term_fontrepl(p, TERMFONT_NONE);
+		break;
+	default:
+		break;
+	}
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_in(DECL_ARGS)
+{
+	int		 len, less;
+	size_t		 v;
+	const char	*cp;
+
+	term_newln(p);
+
+	if (NULL == n->child) {
+		p->offset = mt->offset;
+		return(0);
+	}
+
+	cp = n->child->string;
+	less = 0;
+
+	if ('-' == *cp)
+		less = -1;
+	else if ('+' == *cp)
+		less = 1;
+	else
+		cp--;
+
+	if ((len = a2width(p, ++cp)) < 0)
+		return(0);
+
+	v = (size_t)len;
+
+	if (less < 0)
+		p->offset -= p->offset > v ? v : p->offset;
+	else if (less > 0)
+		p->offset += v;
+	else 
+		p->offset = v;
+
+	/* Don't let this creep beyond the right margin. */
+
+	if (p->offset > p->rmargin)
+		p->offset = p->rmargin;
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_sp(DECL_ARGS)
+{
+	size_t		 i, len;
+
+	if ((NULL == n->prev && n->parent)) {
+		if (MAN_SS == n->parent->tok)
+			return(0);
+		if (MAN_SH == n->parent->tok)
+			return(0);
+	}
+
+	switch (n->tok) {
+	case (MAN_br):
+		len = 0;
+		break;
+	default:
+		len = n->child ? a2height(p, n->child->string) : 1;
+		break;
+	}
+
+	if (0 == len)
+		term_newln(p);
+	for (i = 0; i < len; i++)
+		term_vspace(p);
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_HP(DECL_ARGS)
+{
+	size_t			 len, one;
+	int			 ival;
+	const struct man_node	*nn;
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		print_bvspace(p, n);
+		return(1);
+	case (MAN_BODY):
+		p->flags |= TERMP_NOBREAK;
+		p->flags |= TERMP_TWOSPACE;
+		break;
+	default:
+		return(0);
+	}
+
+	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;
+
+	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_BLOCK):
+		term_flushln(p);
+		break;
+	case (MAN_BODY):
+		term_flushln(p);
+		p->flags &= ~TERMP_NOBREAK;
+		p->flags &= ~TERMP_TWOSPACE;
+		p->offset = mt->offset;
+		p->rmargin = p->maxrmargin;
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_PP(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+		print_bvspace(p, n);
+		break;
+	default:
+		p->offset = mt->offset;
+		break;
+	}
+
+	return(MAN_HEAD != n->type);
+}
+
+
+/* ARGSUSED */
+static int
+pre_IP(DECL_ARGS)
+{
+	const struct man_node	*nn;
+	size_t			 len;
+	int			 savelit, ival;
+
+	switch (n->type) {
+	case (MAN_BODY):
+		p->flags |= TERMP_NOSPACE;
+		break;
+	case (MAN_HEAD):
+		p->flags |= TERMP_NOBREAK;
+		break;
+	case (MAN_BLOCK):
+		print_bvspace(p, n);
+		/* 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;
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		/* Handle zero-width lengths. */
+		if (0 == len)
+			len = term_len(p, 1);
+
+		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;
+
+		if (n->child)
+			print_man_node(p, mt, n->child, m);
+
+		if (savelit)
+			mt->fl |= MANT_LITERAL;
+
+		return(0);
+	case (MAN_BODY):
+		p->offset = mt->offset + len;
+		p->rmargin = p->maxrmargin;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_IP(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_flushln(p);
+		p->flags &= ~TERMP_NOBREAK;
+		p->rmargin = p->maxrmargin;
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_TP(DECL_ARGS)
+{
+	const struct man_node	*nn;
+	size_t			 len;
+	int			 savelit, ival;
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		p->flags |= TERMP_NOBREAK;
+		break;
+	case (MAN_BODY):
+		p->flags |= TERMP_NOSPACE;
+		break;
+	case (MAN_BLOCK):
+		print_bvspace(p, n);
+		/* 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;
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		/* Handle zero-length properly. */
+		if (0 == len)
+			len = term_len(p, 1);
+
+		p->offset = mt->offset;
+		p->rmargin = mt->offset + len;
+
+		savelit = MANT_LITERAL & mt->fl;
+		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, m);
+
+		if (savelit)
+			mt->fl |= MANT_LITERAL;
+		if (ival >= 0)
+			mt->lmargin[mt->lmargincur] = (size_t)ival;
+
+		return(0);
+	case (MAN_BODY):
+		p->offset = mt->offset + len;
+		p->rmargin = p->maxrmargin;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_TP(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_flushln(p);
+		p->flags &= ~TERMP_NOBREAK;
+		p->flags &= ~TERMP_TWOSPACE;
+		p->rmargin = p->maxrmargin;
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_SS(DECL_ARGS)
+{
+
+	switch (n->type) {
+	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)
+			break;
+		term_vspace(p);
+		break;
+	case (MAN_HEAD):
+		term_fontrepl(p, TERMFONT_BOLD);
+		p->offset = term_len(p, p->defindent/2);
+		break;
+	case (MAN_BODY):
+		p->offset = mt->offset;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_SS(DECL_ARGS)
+{
+	
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_newln(p);
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_SH(DECL_ARGS)
+{
+
+	switch (n->type) {
+	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)
+			break;
+		term_vspace(p);
+		break;
+	case (MAN_HEAD):
+		term_fontrepl(p, TERMFONT_BOLD);
+		p->offset = 0;
+		break;
+	case (MAN_BODY):
+		p->offset = mt->offset;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_SH(DECL_ARGS)
+{
+	
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_newln(p);
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+/* ARGSUSED */
+static int
+pre_RS(DECL_ARGS)
+{
+	int		 ival;
+	size_t		 sz;
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		term_newln(p);
+		return(1);
+	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;
+
+	mt->offset += sz;
+	p->rmargin = p->maxrmargin;
+	p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
+
+	if (++mt->lmarginsz < MAXMARGINS)
+		mt->lmargincur = mt->lmarginsz;
+
+	mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1];
+	return(1);
+}
+
+/* ARGSUSED */
+static void
+post_RS(DECL_ARGS)
+{
+	int		 ival;
+	size_t		 sz;
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		return;
+	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;
+	p->offset = mt->offset;
+
+	if (--mt->lmarginsz < MAXMARGINS)
+		mt->lmargincur = mt->lmarginsz;
+}
+
+static void
+print_man_node(DECL_ARGS)
+{
+	size_t		 rm, rmax;
+	int		 c;
+
+	switch (n->type) {
+	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.
+		 */
+		if ('\0' == *n->string) {
+			term_vspace(p);
+			return;
+		} else if (' ' == *n->string && MAN_LINE & n->flags)
+			term_newln(p);
+
+		term_word(p, n->string);
+
+		/*
+		 * If we're in a literal context, make sure that words
+		 * togehter on the same line stay together.  This is a
+		 * POST-printing call, so we check the NEXT word.  Since
+		 * -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)) {
+			rm = p->rmargin;
+			rmax = p->maxrmargin;
+			p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
+			p->flags |= TERMP_NOSPACE;
+			term_flushln(p);
+			p->rmargin = rm;
+			p->maxrmargin = rmax;
+		}
+
+		if (MAN_EOS & n->flags)
+			p->flags |= TERMP_SENTENCE;
+		return;
+	case (MAN_EQN):
+		term_eqn(p, n->eqn);
+		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);
+		term_tbl(p, n->span);
+		return;
+	default:
+		break;
+	}
+
+	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
+		term_fontrepl(p, TERMFONT_NONE);
+
+	c = 1;
+	if (termacts[n->tok].pre)
+		c = (*termacts[n->tok].pre)(p, mt, n, m);
+
+	if (c && n->child)
+		print_man_nodelist(p, mt, n->child, m);
+
+	if (termacts[n->tok].post)
+		(*termacts[n->tok].post)(p, mt, n, m);
+	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
+		term_fontrepl(p, TERMFONT_NONE);
+
+	if (MAN_EOS & n->flags)
+		p->flags |= TERMP_SENTENCE;
+}
+
+
+static void
+print_man_nodelist(DECL_ARGS)
+{
+
+	print_man_node(p, mt, n, m);
+	if ( ! n->next)
+		return;
+	print_man_nodelist(p, mt, n->next, m);
+}
+
+
+static void
+print_man_foot(struct termp *p, const void *arg)
+{
+	char		title[BUFSIZ];
+	size_t		datelen;
+	const struct man_meta *meta;
+
+	meta = (const struct man_meta *)arg;
+	assert(meta->title);
+	assert(meta->msec);
+	assert(meta->date);
+
+	term_fontrepl(p, TERMFONT_NONE);
+
+	term_vspace(p);
+
+	/*
+	 * Temporary, undocumented option to imitate mdoc(7) output.
+	 * In the bottom right corner, use the source instead of
+	 * the title.
+	 */
+
+	if ( ! p->mdocstyle) {
+		term_vspace(p);
+		term_vspace(p);
+		snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
+	} else if (meta->source) {
+		strlcpy(title, meta->source, BUFSIZ);
+	} else {
+		title[0] = '\0';
+	}
+	datelen = term_strlen(p, meta->date);
+
+	/* Bottom left corner: manual source. */
+
+	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+	p->offset = 0;
+	p->rmargin = (p->maxrmargin - datelen + term_len(p, 1)) / 2;
+
+	if (meta->source)
+		term_word(p, meta->source);
+	term_flushln(p);
+
+	/* 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;
+
+	term_word(p, meta->date);
+	term_flushln(p);
+
+	/* Bottom right corner: manual title and section. */
+
+	p->flags &= ~TERMP_NOBREAK;
+	p->flags |= TERMP_NOSPACE;
+	p->offset = p->rmargin;
+	p->rmargin = p->maxrmargin;
+
+	term_word(p, title);
+	term_flushln(p);
+}
+
+
+static void
+print_man_head(struct termp *p, const void *arg)
+{
+	char		buf[BUFSIZ], title[BUFSIZ];
+	size_t		buflen, titlen;
+	const struct man_meta *m;
+
+	m = (const struct man_meta *)arg;
+	assert(m->title);
+	assert(m->msec);
+
+	if (m->vol)
+		strlcpy(buf, m->vol, BUFSIZ);
+	else
+		buf[0] = '\0';
+	buflen = term_strlen(p, buf);
+
+	/* Top left corner: manual title and section. */
+
+	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
+	titlen = term_strlen(p, title);
+
+	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
+	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;
+
+	term_word(p, title);
+	term_flushln(p);
+
+	/* At the top in the middle: manual volume. */
+
+	p->flags |= TERMP_NOSPACE;
+	p->offset = p->rmargin;
+	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
+	    p->maxrmargin - titlen : p->maxrmargin;
+
+	term_word(p, buf);
+	term_flushln(p);
+
+	/* Top right corner: title and section, again. */
+
+	p->flags &= ~TERMP_NOBREAK;
+	if (p->rmargin + titlen <= p->maxrmargin) {
+		p->flags |= TERMP_NOSPACE;
+		p->offset = p->rmargin;
+		p->rmargin = p->maxrmargin;
+		term_word(p, title);
+		term_flushln(p);
+	}
+
+	p->flags &= ~TERMP_NOSPACE;
+	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.
+	 */
+
+	term_vspace(p);
+	if ( ! p->mdocstyle) {
+		term_vspace(p);
+		term_vspace(p);
+	}
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/man_validate.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,550 @@
+/*	$Id: man_validate.c,v 1.80 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libman.h"
+#include "libmandoc.h"
+
+#define	CHKARGS	  struct man *m, struct man_node *n
+
+typedef	int	(*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_par(CHKARGS);
+static	int	  check_part(CHKARGS);
+static	int	  check_root(CHKARGS);
+static	void	  check_text(CHKARGS);
+
+static	int	  post_AT(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_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	  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, NULL }, /* 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, NULL }, /* PD */
+	{ NULL, posts_at }, /* AT */
+	{ NULL, NULL }, /* in */
+	{ NULL, posts_ft }, /* ft */
+	{ NULL, posts_eq2 }, /* OP */
+};
+
+
+int
+man_valid_pre(struct man *m, struct man_node *n)
+{
+	v_check		*cp;
+
+	switch (n->type) {
+	case (MAN_TEXT):
+		/* FALLTHROUGH */
+	case (MAN_ROOT):
+		/* FALLTHROUGH */
+	case (MAN_EQN):
+		/* FALLTHROUGH */
+	case (MAN_TBL):
+		return(1);
+	default:
+		break;
+	}
+
+	if (NULL == (cp = man_valids[n->tok].pres))
+		return(1);
+	for ( ; *cp; cp++)
+		if ( ! (*cp)(m, n)) 
+			return(0);
+	return(1);
+}
+
+
+int
+man_valid_post(struct man *m)
+{
+	v_check		*cp;
+
+	if (MAN_VALID & m->last->flags)
+		return(1);
+	m->last->flags |= MAN_VALID;
+
+	switch (m->last->type) {
+	case (MAN_TEXT): 
+		check_text(m, m->last);
+		return(1);
+	case (MAN_ROOT):
+		return(check_root(m, m->last));
+	case (MAN_EQN):
+		/* FALLTHROUGH */
+	case (MAN_TBL):
+		return(1);
+	default:
+		break;
+	}
+
+	if (NULL == (cp = man_valids[m->last->tok].posts))
+		return(1);
+	for ( ; *cp; cp++)
+		if ( ! (*cp)(m, m->last))
+			return(0);
+
+	return(1);
+}
+
+
+static int
+check_root(CHKARGS) 
+{
+
+	if (MAN_BLINE & m->flags)
+		man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
+	else if (MAN_ELINE & m->flags)
+		man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
+
+	m->flags &= ~MAN_BLINE;
+	m->flags &= ~MAN_ELINE;
+
+	if (NULL == m->first->child) {
+		man_nmsg(m, n, MANDOCERR_NODOCBODY);
+		return(0);
+	} else if (NULL == m->meta.title) {
+		man_nmsg(m, n, MANDOCERR_NOTITLE);
+
+		/*
+		 * If a title hasn't been set, do so now (by
+		 * implication, date and section also aren't set).
+		 */
+
+	        m->meta.title = mandoc_strdup("unknown");
+		m->meta.msec = mandoc_strdup("1");
+		m->meta.date = mandoc_normdate
+			(m->parse, NULL, n->line, n->pos);
+	}
+
+	return(1);
+}
+
+static void
+check_text(CHKARGS)
+{
+	char		*cp, *p;
+
+	if (MAN_LITERAL & m->flags)
+		return;
+
+	cp = n->string;
+	for (p = cp; NULL != (p = strchr(p, '\t')); p++)
+		man_pmsg(m, n->line, (int)(p - cp), MANDOCERR_BADTAB);
+}
+
+#define	INEQ_DEFINE(x, ineq, name) \
+static int \
+check_##name(CHKARGS) \
+{ \
+	if (n->nchild ineq (x)) \
+		return(1); \
+	mandoc_vmsg(MANDOCERR_ARGCOUNT, m->parse, n->line, n->pos, \
+			"line arguments %s %d (have %d)", \
+			#ineq, (x), n->nchild); \
+	return(1); \
+}
+
+INEQ_DEFINE(0, ==, eq0)
+INEQ_DEFINE(2, ==, eq2)
+INEQ_DEFINE(1, <=, le1)
+INEQ_DEFINE(2, >=, ge2)
+INEQ_DEFINE(5, <=, le5)
+
+static int
+post_ft(CHKARGS)
+{
+	char	*cp;
+	int	 ok;
+
+	if (0 == n->nchild)
+		return(1);
+
+	ok = 0;
+	cp = n->child->string;
+	switch (*cp) {
+	case ('1'):
+		/* FALLTHROUGH */
+	case ('2'):
+		/* FALLTHROUGH */
+	case ('3'):
+		/* FALLTHROUGH */
+	case ('4'):
+		/* FALLTHROUGH */
+	case ('I'):
+		/* FALLTHROUGH */
+	case ('P'):
+		/* FALLTHROUGH */
+	case ('R'):
+		if ('\0' == cp[1])
+			ok = 1;
+		break;
+	case ('B'):
+		if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
+			ok = 1;
+		break;
+	case ('C'):
+		if ('W' == cp[1] && '\0' == cp[2])
+			ok = 1;
+		break;
+	default:
+		break;
+	}
+
+	if (0 == ok) {
+		mandoc_vmsg
+			(MANDOCERR_BADFONT, m->parse,
+			 n->line, n->pos, "%s", cp);
+		*cp = '\0';
+	}
+
+	if (1 < n->nchild)
+		mandoc_vmsg
+			(MANDOCERR_ARGCOUNT, m->parse, n->line, 
+			 n->pos, "want one child (have %d)", 
+			 n->nchild);
+
+	return(1);
+}
+
+static int
+pre_sec(CHKARGS)
+{
+
+	if (MAN_BLOCK == n->type)
+		m->flags &= ~MAN_LITERAL;
+	return(1);
+}
+
+static int
+post_sec(CHKARGS)
+{
+
+	if ( ! (MAN_HEAD == n->type && 0 == n->nchild)) 
+		return(1);
+
+	man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT);
+	return(0);
+}
+
+static int
+check_part(CHKARGS)
+{
+
+	if (MAN_BODY == n->type && 0 == n->nchild)
+		mandoc_msg(MANDOCERR_ARGCWARN, m->parse, n->line, 
+				n->pos, "want children (have none)");
+
+	return(1);
+}
+
+
+static int
+check_par(CHKARGS)
+{
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		if (0 == n->body->nchild)
+			man_node_delete(m, n);
+		break;
+	case (MAN_BODY):
+		if (0 == n->nchild)
+			man_nmsg(m, n, MANDOCERR_IGNPAR);
+		break;
+	case (MAN_HEAD):
+		if (n->nchild)
+			man_nmsg(m, n, MANDOCERR_ARGSLOST);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+static int
+post_TH(CHKARGS)
+{
+	const char	*p;
+	int		 line, pos;
+
+	if (m->meta.title)
+		free(m->meta.title);
+	if (m->meta.vol)
+		free(m->meta.vol);
+	if (m->meta.source)
+		free(m->meta.source);
+	if (m->meta.msec)
+		free(m->meta.msec);
+	if (m->meta.date)
+		free(m->meta.date);
+
+	line = n->line;
+	pos = n->pos;
+	m->meta.title = m->meta.vol = m->meta.date =
+		m->meta.msec = m->meta.source = NULL;
+
+	/* ->TITLE<- MSEC DATE SOURCE VOL */
+
+	n = n->child;
+	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(m, n, MANDOCERR_UPPERCASE);
+				break;
+			}
+		}
+		m->meta.title = mandoc_strdup(n->string);
+	} else
+		m->meta.title = mandoc_strdup("");
+
+	/* TITLE ->MSEC<- DATE SOURCE VOL */
+
+	if (n)
+		n = n->next;
+	if (n && n->string)
+		m->meta.msec = mandoc_strdup(n->string);
+	else
+		m->meta.msec = mandoc_strdup("");
+
+	/* TITLE MSEC ->DATE<- SOURCE VOL */
+
+	if (n)
+		n = n->next;
+	if (n && n->string && '\0' != n->string[0]) {
+		pos = n->pos;
+		m->meta.date = mandoc_normdate
+		    (m->parse, n->string, line, pos);
+	} else
+		m->meta.date = mandoc_strdup("");
+
+	/* TITLE MSEC DATE ->SOURCE<- VOL */
+
+	if (n && (n = n->next))
+		m->meta.source = mandoc_strdup(n->string);
+
+	/* TITLE MSEC DATE SOURCE ->VOL<- */
+	/* If missing, use the default VOL name for MSEC. */
+
+	if (n && (n = n->next))
+		m->meta.vol = mandoc_strdup(n->string);
+	else if ('\0' != m->meta.msec[0] &&
+	    (NULL != (p = mandoc_a2msec(m->meta.msec))))
+		m->meta.vol = mandoc_strdup(p);
+
+	/*
+	 * Remove the `TH' node after we've processed it for our
+	 * meta-data.
+	 */
+	man_node_delete(m, m->last);
+	return(1);
+}
+
+static int
+post_nf(CHKARGS)
+{
+
+	if (MAN_LITERAL & m->flags)
+		man_nmsg(m, n, MANDOCERR_SCOPEREP);
+
+	m->flags |= MAN_LITERAL;
+	return(1);
+}
+
+static int
+post_fi(CHKARGS)
+{
+
+	if ( ! (MAN_LITERAL & m->flags))
+		man_nmsg(m, n, MANDOCERR_WNOSCOPE);
+
+	m->flags &= ~MAN_LITERAL;
+	return(1);
+}
+
+static int
+post_UC(CHKARGS)
+{
+	static const char * const bsd_versions[] = {
+	    "3rd Berkeley Distribution",
+	    "4th Berkeley Distribution",
+	    "4.2 Berkeley Distribution",
+	    "4.3 Berkeley Distribution",
+	    "4.4 Berkeley Distribution",
+	};
+
+	const char	*p, *s;
+
+	n = n->child;
+
+	if (NULL == n || MAN_TEXT != n->type)
+		p = bsd_versions[0];
+	else {
+		s = n->string;
+		if (0 == strcmp(s, "3"))
+			p = bsd_versions[0];
+		else if (0 == strcmp(s, "4"))
+			p = bsd_versions[1];
+		else if (0 == strcmp(s, "5"))
+			p = bsd_versions[2];
+		else if (0 == strcmp(s, "6"))
+			p = bsd_versions[3];
+		else if (0 == strcmp(s, "7"))
+			p = bsd_versions[4];
+		else
+			p = bsd_versions[0];
+	}
+
+	if (m->meta.source)
+		free(m->meta.source);
+
+	m->meta.source = mandoc_strdup(p);
+	return(1);
+}
+
+static int
+post_AT(CHKARGS)
+{
+	static const char * const unix_versions[] = {
+	    "7th Edition",
+	    "System III",
+	    "System V",
+	    "System V Release 2",
+	};
+
+	const char	*p, *s;
+	struct man_node	*nn;
+
+	n = n->child;
+
+	if (NULL == n || MAN_TEXT != n->type)
+		p = unix_versions[0];
+	else {
+		s = n->string;
+		if (0 == strcmp(s, "3"))
+			p = unix_versions[0];
+		else if (0 == strcmp(s, "4"))
+			p = unix_versions[1];
+		else if (0 == strcmp(s, "5")) {
+			nn = n->next;
+			if (nn && MAN_TEXT == nn->type && nn->string[0])
+				p = unix_versions[3];
+			else
+				p = unix_versions[2];
+		} else
+			p = unix_versions[0];
+	}
+
+	if (m->meta.source)
+		free(m->meta.source);
+
+	m->meta.source = mandoc_strdup(p);
+	return(1);
+}
+
+static int
+post_vs(CHKARGS)
+{
+
+	/* 
+	 * Don't warn about this because it occurs in pod2man and would
+	 * cause considerable (unfixable) warnage.
+	 */
+	if (NULL == n->prev && MAN_ROOT == n->parent->type)
+		man_node_delete(m, n);
+
+	return(1);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/mandoc.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,735 @@
+/*	$Id: mandoc.c,v 1.62 2011/12/03 16:08:51 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+
+#define DATESIZE 32
+
+static	int	 a2time(time_t *, const char *, const char *);
+static	char	*time2a(time_t);
+static	int	 numescape(const char *);
+
+/*
+ * Pass over recursive numerical expressions.  This context of this
+ * function is important: it's only called within character-terminating
+ * escapes (e.g., \s[xxxyyy]), so all we need to do is handle initial
+ * recursion: we don't care about what's in these blocks. 
+ * This returns the number of characters skipped or -1 if an error
+ * occurs (the caller should bail).
+ */
+static int
+numescape(const char *start)
+{
+	int		 i;
+	size_t		 sz;
+	const char	*cp;
+
+	i = 0;
+
+	/* The expression consists of a subexpression. */
+
+	if ('\\' == start[i]) {
+		cp = &start[++i];
+		/*
+		 * Read past the end of the subexpression.
+		 * Bail immediately on errors.
+		 */
+		if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
+			return(-1);
+		return(i + cp - &start[i]);
+	} 
+
+	if ('(' != start[i++])
+		return(0);
+
+	/*
+	 * A parenthesised subexpression.  Read until the closing
+	 * parenthesis, making sure to handle any nested subexpressions
+	 * that might ruin our parse.
+	 */
+
+	while (')' != start[i]) {
+		sz = strcspn(&start[i], ")\\");
+		i += (int)sz;
+
+		if ('\0' == start[i])
+			return(-1);
+		else if ('\\' != start[i])
+			continue;
+
+		cp = &start[++i];
+		if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
+			return(-1);
+		i += cp - &start[i];
+	}
+
+	/* Read past the terminating ')'. */
+	return(++i);
+}
+
+enum mandoc_esc
+mandoc_escape(const char **end, const char **start, int *sz)
+{
+	char		 c, term, numeric;
+	int		 i, lim, ssz, rlim;
+	const char	*cp, *rstart;
+	enum mandoc_esc	 gly; 
+
+	cp = *end;
+	rstart = cp;
+	if (start)
+		*start = rstart;
+	i = lim = 0;
+	gly = ESCAPE_ERROR;
+	term = numeric = '\0';
+
+	switch ((c = cp[i++])) {
+	/*
+	 * First the glyphs.  There are several different forms of
+	 * these, but each eventually returns a substring of the glyph
+	 * name.
+	 */
+	case ('('):
+		gly = ESCAPE_SPECIAL;
+		lim = 2;
+		break;
+	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' == cp[i] && ']' != cp[i + 1])
+			gly = ESCAPE_UNICODE;
+		term = ']';
+		break;
+	case ('C'):
+		if ('\'' != cp[i])
+			return(ESCAPE_ERROR);
+		gly = ESCAPE_SPECIAL;
+		term = '\'';
+		break;
+
+	/*
+	 * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
+	 * 'X' is the trigger.  These have opaque sub-strings.
+	 */
+	case ('F'):
+		/* FALLTHROUGH */
+	case ('g'):
+		/* FALLTHROUGH */
+	case ('k'):
+		/* FALLTHROUGH */
+	case ('M'):
+		/* FALLTHROUGH */
+	case ('m'):
+		/* FALLTHROUGH */
+	case ('n'):
+		/* FALLTHROUGH */
+	case ('V'):
+		/* FALLTHROUGH */
+	case ('Y'):
+		gly = ESCAPE_IGNORE;
+		/* FALLTHROUGH */
+	case ('f'):
+		if (ESCAPE_ERROR == gly)
+			gly = ESCAPE_FONT;
+
+		rstart= &cp[i];
+		if (start) 
+			*start = rstart;
+
+		switch (cp[i++]) {
+		case ('('):
+			lim = 2;
+			break;
+		case ('['):
+			term = ']';
+			break;
+		default:
+			lim = 1;
+			i--;
+			break;
+		}
+		break;
+
+	/*
+	 * These escapes are of the form \X'Y', where 'X' is the trigger
+	 * and 'Y' is any string.  These have opaque sub-strings.
+	 */
+	case ('A'):
+		/* FALLTHROUGH */
+	case ('b'):
+		/* FALLTHROUGH */
+	case ('D'):
+		/* FALLTHROUGH */
+	case ('o'):
+		/* FALLTHROUGH */
+	case ('R'):
+		/* FALLTHROUGH */
+	case ('X'):
+		/* FALLTHROUGH */
+	case ('Z'):
+		if ('\'' != cp[i++])
+			return(ESCAPE_ERROR);
+		gly = ESCAPE_IGNORE;
+		term = '\'';
+		break;
+
+	/*
+	 * These escapes are of the form \X'N', where 'X' is the trigger
+	 * and 'N' resolves to a numerical expression.
+	 */
+	case ('B'):
+		/* FALLTHROUGH */
+	case ('h'):
+		/* FALLTHROUGH */
+	case ('H'):
+		/* FALLTHROUGH */
+	case ('L'):
+		/* FALLTHROUGH */
+	case ('l'):
+		gly = ESCAPE_NUMBERED;
+		/* FALLTHROUGH */
+	case ('S'):
+		/* FALLTHROUGH */
+	case ('v'):
+		/* FALLTHROUGH */
+	case ('w'):
+		/* FALLTHROUGH */
+	case ('x'):
+		if (ESCAPE_ERROR == gly)
+			gly = ESCAPE_IGNORE;
+		if ('\'' != cp[i++])
+			return(ESCAPE_ERROR);
+		term = numeric = '\'';
+		break;
+
+	/*
+	 * Special handling for the numbered character escape.
+	 * XXX Do any other escapes need similar handling?
+	 */
+	case ('N'):
+		if ('\0' == cp[i])
+			return(ESCAPE_ERROR);
+		*end = &cp[++i];
+		if (isdigit((unsigned char)cp[i-1]))
+			return(ESCAPE_IGNORE);
+		while (isdigit((unsigned char)**end))
+			(*end)++;
+		if (start)
+			*start = &cp[i];
+		if (sz)
+			*sz = *end - &cp[i];
+		if ('\0' != **end)
+			(*end)++;
+		return(ESCAPE_NUMBERED);
+
+	/* 
+	 * Sizes get a special category of their own.
+	 */
+	case ('s'):
+		gly = ESCAPE_IGNORE;
+
+		rstart = &cp[i];
+		if (start) 
+			*start = rstart;
+
+		/* See +/- counts as a sign. */
+		c = cp[i];
+		if ('+' == c || '-' == c || ASCII_HYPH == c)
+			++i;
+
+		switch (cp[i++]) {
+		case ('('):
+			lim = 2;
+			break;
+		case ('['):
+			term = numeric = ']';
+			break;
+		case ('\''):
+			term = numeric = '\'';
+			break;
+		default:
+			lim = 1;
+			i--;
+			break;
+		}
+
+		/* See +/- counts as a sign. */
+		c = cp[i];
+		if ('+' == c || '-' == c || ASCII_HYPH == c)
+			++i;
+
+		break;
+
+	/*
+	 * Anything else is assumed to be a glyph.
+	 */
+	default:
+		gly = ESCAPE_SPECIAL;
+		lim = 1;
+		i--;
+		break;
+	}
+
+	assert(ESCAPE_ERROR != gly);
+
+	rstart = &cp[i];
+	if (start)
+		*start = rstart;
+
+	/*
+	 * If a terminating block has been specified, we need to
+	 * handle the case of recursion, which could have their
+	 * own terminating blocks that mess up our parse.  This, by the
+	 * way, means that the "start" and "size" values will be
+	 * effectively meaningless.
+	 */
+
+	ssz = 0;
+	if (numeric && -1 == (ssz = numescape(&cp[i])))
+		return(ESCAPE_ERROR);
+
+	i += ssz;
+	rlim = -1;
+
+	/*
+	 * We have a character terminator.  Try to read up to that
+	 * character.  If we can't (i.e., we hit the nil), then return
+	 * an error; if we can, calculate our length, read past the
+	 * terminating character, and exit.
+	 */
+
+	if ('\0' != term) {
+		*end = strchr(&cp[i], term);
+		if ('\0' == *end)
+			return(ESCAPE_ERROR);
+
+		rlim = *end - &cp[i];
+		if (sz)
+			*sz = rlim;
+		(*end)++;
+		goto out;
+	}
+
+	assert(lim > 0);
+
+	/*
+	 * We have a numeric limit.  If the string is shorter than that,
+	 * stop and return an error.  Else adjust our endpoint, length,
+	 * and return the current glyph.
+	 */
+
+	if ((size_t)lim > strlen(&cp[i]))
+		return(ESCAPE_ERROR);
+
+	rlim = lim;
+	if (sz)
+		*sz = rlim;
+
+	*end = &cp[i] + lim;
+
+out:
+	assert(rlim >= 0 && rstart);
+
+	/* Run post-processors. */
+
+	switch (gly) {
+	case (ESCAPE_FONT):
+		/*
+		 * Pretend that the constant-width font modes are the
+		 * same as the regular font modes.
+		 */
+		if (2 == rlim && 'C' == *rstart)
+			rstart++;
+		else if (1 != rlim)
+			break;
+
+		switch (*rstart) {
+		case ('3'):
+			/* FALLTHROUGH */
+		case ('B'):
+			gly = ESCAPE_FONTBOLD;
+			break;
+		case ('2'):
+			/* FALLTHROUGH */
+		case ('I'):
+			gly = ESCAPE_FONTITALIC;
+			break;
+		case ('P'):
+			gly = ESCAPE_FONTPREV;
+			break;
+		case ('1'):
+			/* FALLTHROUGH */
+		case ('R'):
+			gly = ESCAPE_FONTROMAN;
+			break;
+		}
+		break;
+	case (ESCAPE_SPECIAL):
+		if (1 != rlim)
+			break;
+		if ('c' == *rstart)
+			gly = ESCAPE_NOSPACE;
+		break;
+	default:
+		break;
+	}
+
+	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
+ * pointer or advanced by one byte in case the argument is quoted.
+ * Null-terminate the argument in place.
+ * Collapse pairs of quotes inside quoted arguments.
+ * Advance the argument pointer to the next argument,
+ * or to the null byte terminating the argument line.
+ */
+char *
+mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
+{
+	char	 *start, *cp;
+	int	  quoted, pairs, white;
+
+	/* Quoting can only start with a new word. */
+	start = *cpp;
+	quoted = 0;
+	if ('"' == *start) {
+		quoted = 1;
+		start++;
+	} 
+
+	pairs = 0;
+	white = 0;
+	for (cp = start; '\0' != *cp; cp++) {
+		/* Move left after quoted quotes and escaped backslashes. */
+		if (pairs)
+			cp[-pairs] = cp[0];
+		if ('\\' == cp[0]) {
+			if ('\\' == cp[1]) {
+				/* Poor man's copy mode. */
+				pairs++;
+				cp++;
+			} else if (0 == quoted && ' ' == cp[1])
+				/* Skip escaped blanks. */
+				cp++;
+		} else if (0 == quoted) {
+			if (' ' == cp[0]) {
+				/* Unescaped blanks end unquoted args. */
+				white = 1;
+				break;
+			}
+		} else if ('"' == cp[0]) {
+			if ('"' == cp[1]) {
+				/* Quoted quotes collapse. */
+				pairs++;
+				cp++;
+			} else {
+				/* Unquoted quotes end quoted args. */
+				quoted = 2;
+				break;
+			}
+		}
+	}
+
+	/* Quoted argument without a closing quote. */
+	if (1 == quoted)
+		mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL);
+
+	/* Null-terminate this argument and move to the next one. */
+	if (pairs)
+		cp[-pairs] = '\0';
+	if ('\0' != *cp) {
+		*cp++ = '\0';
+		while (' ' == *cp)
+			cp++;
+	}
+	*pos += (int)(cp - start) + (quoted ? 1 : 0);
+	*cpp = cp;
+
+	if ('\0' == *cp && (white || ' ' == cp[-1]))
+		mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL);
+
+	return(start);
+}
+
+static int
+a2time(time_t *t, const char *fmt, const char *p)
+{
+	struct tm	 tm;
+	char		*pp;
+
+	memset(&tm, 0, sizeof(struct tm));
+
+	pp = NULL;
+#ifdef	HAVE_STRPTIME
+	pp = strptime(p, fmt, &tm);
+#endif
+	if (NULL != pp && '\0' == *pp) {
+		*t = mktime(&tm);
+		return(1);
+	}
+
+	return(0);
+}
+
+static char *
+time2a(time_t t)
+{
+	struct tm	*tm;
+	char		*buf, *p;
+	size_t		 ssz;
+	int		 isz;
+
+	tm = localtime(&t);
+
+	/*
+	 * Reserve space:
+	 * up to 9 characters for the month (September) + blank
+	 * up to 2 characters for the day + comma + blank
+	 * 4 characters for the year and a terminating '\0'
+	 */
+	p = buf = mandoc_malloc(10 + 4 + 4 + 1);
+
+	if (0 == (ssz = strftime(p, 10 + 1, "%B ", tm)))
+		goto fail;
+	p += (int)ssz;
+
+	if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)))
+		goto fail;
+	p += isz;
+
+	if (0 == strftime(p, 4 + 1, "%Y", tm))
+		goto fail;
+	return(buf);
+
+fail:
+	free(buf);
+	return(NULL);
+}
+
+char *
+mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
+{
+	char		*out;
+	time_t		 t;
+
+	if (NULL == in || '\0' == *in ||
+	    0 == strcmp(in, "$" "Mdocdate$")) {
+		mandoc_msg(MANDOCERR_NODATE, 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);
+		t = 0;
+	}
+	out = t ? time2a(t) : NULL;
+	return(out ? out : mandoc_strdup(in));
+}
+
+int
+mandoc_eos(const char *p, size_t sz, int enclosed)
+{
+	const char *q;
+	int found;
+
+	if (0 == sz)
+		return(0);
+
+	/*
+	 * End-of-sentence recognition must include situations where
+	 * some symbols, such as `)', allow prior EOS punctuation to
+	 * propagate outward.
+	 */
+
+	found = 0;
+	for (q = p + (int)sz - 1; q >= p; q--) {
+		switch (*q) {
+		case ('\"'):
+			/* FALLTHROUGH */
+		case ('\''):
+			/* FALLTHROUGH */
+		case (']'):
+			/* FALLTHROUGH */
+		case (')'):
+			if (0 == found)
+				enclosed = 1;
+			break;
+		case ('.'):
+			/* FALLTHROUGH */
+		case ('!'):
+			/* FALLTHROUGH */
+		case ('?'):
+			found = 1;
+			break;
+		default:
+			return(found && (!enclosed || isalnum((unsigned char)*q)));
+		}
+	}
+
+	return(found && !enclosed);
+}
+
+/*
+ * 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.
+ */
+int
+mandoc_getcontrol(const char *cp, int *ppos)
+{
+	int		pos;
+
+	pos = *ppos;
+
+	if ('\\' == cp[pos] && '.' == cp[pos + 1])
+		pos += 2;
+	else if ('.' == cp[pos] || '\'' == cp[pos])
+		pos++;
+	else
+		return(0);
+
+	while (' ' == cp[pos] || '\t' == cp[pos])
+		pos++;
+
+	*ppos = pos;
+	return(1);
+}
+
+/*
+ * Convert a string to a long that may not be <0.
+ * If the string is invalid, or is less than 0, return -1.
+ */
+int
+mandoc_strntoi(const char *p, size_t sz, int base)
+{
+	char		 buf[32];
+	char		*ep;
+	long		 v;
+
+	if (sz > 31)
+		return(-1);
+
+	memcpy(buf, p, sz);
+	buf[(int)sz] = '\0';
+
+	errno = 0;
+	v = strtol(buf, &ep, base);
+
+	if (buf[0] == '\0' || *ep != '\0')
+		return(-1);
+
+	if (v > INT_MAX)
+		v = INT_MAX;
+	if (v < INT_MIN)
+		v = INT_MIN;
+
+	return((int)v);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/mandoc.h	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,432 @@
+/*	$Id: mandoc.h,v 1.99 2012/02/16 20:51:31 joerg Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef MANDOC_H
+#define MANDOC_H
+
+#define ASCII_NBRSP	 31  /* non-breaking space */
+#define	ASCII_HYPH	 30  /* breakable hyphen */
+
+/*
+ * Status level.  This refers to both internal status (i.e., whilst
+ * running, when warnings/errors are reported) and an indicator of a
+ * threshold of when to halt (when said internal state exceeds the
+ * threshold).
+ */
+enum	mandoclevel {
+	MANDOCLEVEL_OK = 0,
+	MANDOCLEVEL_RESERVED,
+	MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */
+	MANDOCLEVEL_ERROR, /* input has been thrown away */
+	MANDOCLEVEL_FATAL, /* input is borked */
+	MANDOCLEVEL_BADARG, /* bad argument in invocation */
+	MANDOCLEVEL_SYSERR, /* system error */
+	MANDOCLEVEL_MAX
+};
+
+/*
+ * All possible things that can go wrong within a parse, be it libroff,
+ * libmdoc, or libman.
+ */
+enum	mandocerr {
+	MANDOCERR_OK,
+
+	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_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 */
+
+	/* 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_NONAME, /* manual name not yet set */
+	MANDOCERR_SECOOO, /* sections out of conventional order */
+	MANDOCERR_SECREP, /* duplicate section name */
+	MANDOCERR_SECMSEC, /* section not in conventional manual section */
+
+	/* related to macros and nesting */
+	MANDOCERR_MACROOBS, /* skipping obsolete macro */
+	MANDOCERR_IGNPAR, /* skipping paragraph macro */
+	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 */
+
+	/* 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 */
+
+	/* related to equations */
+	MANDOCERR_EQNQUOTE, /* unexpected literal in equation */
+
+	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_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
+	MANDOCERR_BADCHAR, /* skipping bad character */
+	MANDOCERR_NAMESC, /* escaped character not allowed in a name */
+	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_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_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_MAX
+};
+
+struct	tbl {
+	char		  tab; /* cell-separator */
+	char		  decimal; /* decimal point */
+	int		  linesize;
+	int		  opts;
+#define	TBL_OPT_CENTRE	 (1 << 0)
+#define	TBL_OPT_EXPAND	 (1 << 1)
+#define	TBL_OPT_BOX	 (1 << 2)
+#define	TBL_OPT_DBOX	 (1 << 3)
+#define	TBL_OPT_ALLBOX	 (1 << 4)
+#define	TBL_OPT_NOKEEP	 (1 << 5)
+#define	TBL_OPT_NOSPACE	 (1 << 6)
+	int		  cols; /* number of columns */
+};
+
+enum	tbl_headt {
+	TBL_HEAD_DATA, /* plug in data from tbl_dat */
+	TBL_HEAD_VERT, /* vertical spacer */
+	TBL_HEAD_DVERT  /* double-vertical spacer */
+};
+
+/*
+ * 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 {
+	enum tbl_headt	  pos;
+	int		  ident; /* 0 <= unique id < cols */
+	struct tbl_head	 *next;
+	struct tbl_head	 *prev;
+};
+
+enum	tbl_cellt {
+	TBL_CELL_CENTRE, /* c, C */
+	TBL_CELL_RIGHT, /* r, R */
+	TBL_CELL_LEFT, /* l, L */
+	TBL_CELL_NUMBER, /* n, N */
+	TBL_CELL_SPAN, /* s, S */
+	TBL_CELL_LONG, /* a, A */
+	TBL_CELL_DOWN, /* ^ */
+	TBL_CELL_HORIZ, /* _, - */
+	TBL_CELL_DHORIZ, /* = */
+	TBL_CELL_VERT, /* | */
+	TBL_CELL_DVERT, /* || */
+	TBL_CELL_MAX
+};
+
+/*
+ * A cell in a layout row.
+ */
+struct	tbl_cell {
+	struct tbl_cell	 *next;
+	enum tbl_cellt	  pos;
+	size_t		  spacing;
+	int		  flags;
+#define	TBL_CELL_TALIGN	 (1 << 0) /* t, T */
+#define	TBL_CELL_BALIGN	 (1 << 1) /* d, D */
+#define	TBL_CELL_BOLD	 (1 << 2) /* fB, B, b */
+#define	TBL_CELL_ITALIC	 (1 << 3) /* fI, I, i */
+#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;
+};
+
+/*
+ * A layout row.
+ */
+struct	tbl_row {
+	struct tbl_row	 *next;
+	struct tbl_cell	 *first;
+	struct tbl_cell	 *last;
+};
+
+enum	tbl_datt {
+	TBL_DATA_NONE, /* has no data */
+	TBL_DATA_DATA, /* consists of data/string */
+	TBL_DATA_HORIZ, /* horizontal line */
+	TBL_DATA_DHORIZ, /* double-horizontal line */
+	TBL_DATA_NHORIZ, /* squeezed horizontal line */
+	TBL_DATA_NDHORIZ /* squeezed double-horizontal line */
+};
+
+/*
+ * A cell within a row of data.  The "string" field contains the actual
+ * string value that's in the cell.  The rest is layout.
+ */
+struct	tbl_dat {
+	struct tbl_cell	 *layout; /* layout cell */
+	int		  spans; /* how many spans follow */
+	struct tbl_dat	 *next;
+	char		 *string; /* data (NULL if not TBL_DATA_DATA) */
+	enum tbl_datt	  pos;
+};
+
+enum	tbl_spant {
+	TBL_SPAN_DATA, /* span consists of data */
+	TBL_SPAN_HORIZ, /* span is horizontal line */
+	TBL_SPAN_DHORIZ /* span is double horizontal line */
+};
+
+/*
+ * A row of data in a table.
+ */
+struct	tbl_span {
+	struct tbl	 *tbl;
+	struct tbl_head	 *head;
+	struct tbl_row	 *layout; /* layout row */
+	struct tbl_dat	 *first;
+	struct tbl_dat	 *last;
+	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
+};
+
+enum	eqn_fontt {
+	EQNFONT_NONE = 0,
+	EQNFONT_ROMAN,
+	EQNFONT_BOLD,
+	EQNFONT_FAT,
+	EQNFONT_ITALIC,
+	EQNFONT__MAX
+};
+
+enum	eqn_post {
+	EQNPOS_NONE = 0,
+	EQNPOS_OVER,
+	EQNPOS_SUP,
+	EQNPOS_SUB,
+	EQNPOS_TO,
+	EQNPOS_FROM,
+	EQNPOS__MAX
+};
+
+enum	eqn_pilet {
+	EQNPILE_NONE = 0,
+	EQNPILE_PILE,
+	EQNPILE_CPILE,
+	EQNPILE_RPILE,
+	EQNPILE_LPILE,
+	EQNPILE_COL,
+	EQNPILE_CCOL,
+	EQNPILE_RCOL,
+	EQNPILE_LCOL,
+	EQNPILE__MAX
+};
+
+ /*
+ * A "box" is a parsed mathematical expression as defined by the eqn.7
+ * grammar.
+ */
+struct	eqn_box {
+	int		  size; /* font size of expression */
+#define	EQN_DEFSIZE	  INT_MIN
+	enum eqn_boxt	  type; /* type of node */
+	struct eqn_box	 *first; /* first child node */
+	struct eqn_box	 *last; /* last child node */
+	struct eqn_box	 *next; /* node sibling */
+	struct eqn_box	 *parent; /* node sibling */
+	char		 *text; /* text (or NULL) */
+	char		 *left;
+	char		 *right;
+	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. 
+ */
+struct	eqn {
+	char		 *name; /* identifier (or NULL) */
+	struct eqn_box	 *root; /* root mathematical expression */
+	int		  ln; /* invocation line */
+	int		  pos; /* invocation position */
+};
+
+/*
+ * 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.
+ */
+enum	mparset {
+	MPARSE_AUTO, /* magically determine the document type */
+	MPARSE_MDOC, /* assume -mdoc */
+	MPARSE_MAN /* assume -man */
+};
+
+enum	mandoc_esc {
+	ESCAPE_ERROR = 0, /* bail! unparsable escape */
+	ESCAPE_IGNORE, /* escape to be ignored */
+	ESCAPE_SPECIAL, /* a regular special character */
+	ESCAPE_FONT, /* a generic font mode */
+	ESCAPE_FONTBOLD, /* bold font mode */
+	ESCAPE_FONTITALIC, /* italic font mode */
+	ESCAPE_FONTROMAN, /* roman font mode */
+	ESCAPE_FONTPREV, /* previous font mode */
+	ESCAPE_NUMBERED, /* a numbered glyph */
+	ESCAPE_UNICODE, /* a unicode codepoint */
+	ESCAPE_NOSPACE /* suppress space if the last on a line */
+};
+
+typedef	void	(*mandocmsg)(enum mandocerr, enum mandoclevel,
+			const char *, int, int, const char *);
+
+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_num2uc(const char *, size_t);
+int		  mchars_spec2cp(const struct mchars *, 
+			const char *, size_t);
+const char	 *mchars_spec2str(const struct mchars *, 
+			const char *, size_t, size_t *);
+struct mparse	 *mparse_alloc(enum mparset, 
+			enum mandoclevel, mandocmsg, void *);
+void		  mparse_free(struct mparse *);
+void		  mparse_keep(struct mparse *);
+enum mandoclevel  mparse_readfd(struct mparse *, int, const char *);
+enum mandoclevel  mparse_readmem(struct mparse *, const void *, size_t,
+			const char *);
+void		  mparse_reset(struct mparse *);
+void		  mparse_result(struct mparse *, 
+			struct mdoc **, struct man **);
+const char	 *mparse_getkeep(const struct mparse *);
+const char	 *mparse_strerror(enum mandocerr);
+const char	 *mparse_strlevel(enum mandoclevel);
+
+__END_DECLS
+
+#endif /*!MANDOC_H*/
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/mdoc.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,987 @@
+/*	$Id: mdoc.c,v 1.196 2011/09/30 00:13:28 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+const	char *const __mdoc_macronames[MDOC_MAX] = {		 
+	"Ap",		"Dd",		"Dt",		"Os",
+	"Sh",		"Ss",		"Pp",		"D1",
+	"Dl",		"Bd",		"Ed",		"Bl",
+	"El",		"It",		"Ad",		"An",
+	"Ar",		"Cd",		"Cm",		"Dv",
+	"Er",		"Ev",		"Ex",		"Fa",
+	"Fd",		"Fl",		"Fn",		"Ft",
+	"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",
+	"Bsx",		"Bx",		"Db",		"Dc",
+	"Do",		"Dq",		"Ec",		"Ef",
+	"Em",		"Eo",		"Fx",		"Ms",
+	"No",		"Ns",		"Nx",		"Ox",
+	"Pc",		"Pf",		"Po",		"Pq",
+	"Qc",		"Ql",		"Qo",		"Qq",
+	"Re",		"Rs",		"Sc",		"So",
+	"Sq",		"Sm",		"Sx",		"Sy",
+	"Tn",		"Ux",		"Xc",		"Xo",
+	"Fo",		"Fc",		"Oo",		"Oc",
+	"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"
+	};
+
+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",	 
+	"filled",		"words",		"emphasis",
+	"symbolic",		"nested",		"centered"
+	};
+
+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 *, 
+				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, 
+				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	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 *m)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+	return(m->first);
+}
+
+
+const struct mdoc_meta *
+mdoc_meta(const struct mdoc *m)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+	return(&m->meta);
+}
+
+
+/*
+ * Frees volatile resources (parse tree, meta-data, fields).
+ */
+static void
+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);
+}
+
+
+/*
+ * Allocate all volatile resources (parse tree, meta-data, fields).
+ */
+static void
+mdoc_alloc1(struct mdoc *mdoc)
+{
+
+	memset(&mdoc->meta, 0, sizeof(struct mdoc_meta));
+	mdoc->flags = 0;
+	mdoc->lastnamed = mdoc->lastsec = SEC_NONE;
+	mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node));
+	mdoc->first = mdoc->last;
+	mdoc->last->type = MDOC_ROOT;
+	mdoc->last->tok = MDOC_MAX;
+	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
+ * and the parser is ready for re-invocation on a new tree; however,
+ * cross-parse non-volatile data is kept intact.
+ */
+void
+mdoc_reset(struct mdoc *mdoc)
+{
+
+	mdoc_free1(mdoc);
+	mdoc_alloc1(mdoc);
+}
+
+
+/*
+ * Completely free up all volatile and non-volatile parse resources.
+ * After invocation, the pointer is no longer usable.
+ */
+void
+mdoc_free(struct mdoc *mdoc)
+{
+
+	mdoc_free1(mdoc);
+	free(mdoc);
+}
+
+
+/*
+ * Allocate volatile and non-volatile parse resources.  
+ */
+struct mdoc *
+mdoc_alloc(struct roff *roff, struct mparse *parse)
+{
+	struct mdoc	*p;
+
+	p = mandoc_calloc(1, sizeof(struct mdoc));
+
+	p->parse = parse;
+	p->roff = roff;
+
+	mdoc_hash_init();
+	mdoc_alloc1(p);
+	return(p);
+}
+
+
+/*
+ * Climb back up the parse tree, validating open scopes.  Mostly calls
+ * through to macro_end() in macro.c.
+ */
+int
+mdoc_endparse(struct mdoc *m)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+	if (mdoc_macroend(m))
+		return(1);
+	m->flags |= MDOC_HALT;
+	return(0);
+}
+
+int
+mdoc_addeqn(struct mdoc *m, const struct eqn *ep)
+{
+	struct mdoc_node *n;
+
+	assert( ! (MDOC_HALT & m->flags));
+
+	/* No text before an initial macro. */
+
+	if (SEC_NONE == m->lastnamed) {
+		mdoc_pmsg(m, ep->ln, ep->pos, MANDOCERR_NOTEXT);
+		return(1);
+	}
+
+	n = node_alloc(m, ep->ln, ep->pos, MDOC_MAX, MDOC_EQN);
+	n->eqn = ep;
+
+	if ( ! node_append(m, n))
+		return(0);
+
+	m->next = MDOC_NEXT_SIBLING;
+	return(1);
+}
+
+int
+mdoc_addspan(struct mdoc *m, const struct tbl_span *sp)
+{
+	struct mdoc_node *n;
+
+	assert( ! (MDOC_HALT & m->flags));
+
+	/* No text before an initial macro. */
+
+	if (SEC_NONE == m->lastnamed) {
+		mdoc_pmsg(m, sp->line, 0, MANDOCERR_NOTEXT);
+		return(1);
+	}
+
+	n = node_alloc(m, sp->line, 0, MDOC_MAX, MDOC_TBL);
+	n->span = sp;
+
+	if ( ! node_append(m, n))
+		return(0);
+
+	m->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()).
+ */
+int
+mdoc_parseln(struct mdoc *m, int ln, char *buf, int offs)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+
+	m->flags |= MDOC_NEWLINE;
+
+	/*
+	 * Let the roff nS register switch SYNOPSIS mode early,
+	 * such that the parser knows at all times
+	 * whether this mode is on or off.
+	 * Note that this mode is also switched by the Sh macro.
+	 */
+	if (roff_regisset(m->roff, REG_nS)) {
+		if (roff_regget(m->roff, REG_nS))
+			m->flags |= MDOC_SYNOPSIS;
+		else
+			m->flags &= ~MDOC_SYNOPSIS;
+	}
+
+	return(mandoc_getcontrol(buf, &offs) ?
+			mdoc_pmacro(m, ln, buf, offs) :
+			mdoc_ptext(m, ln, buf, offs));
+}
+
+int
+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 & m->flags) {
+		mdoc_pmsg(m, line, ppos, MANDOCERR_BADBODY);
+		return(1);
+	}
+
+	/* If we're in the prologue, deny "body" macros.  */
+
+	if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && 
+			! (MDOC_PBODY & m->flags)) {
+		mdoc_pmsg(m, line, ppos, MANDOCERR_BADPROLOG);
+		if (NULL == m->meta.msec)
+			m->meta.msec = mandoc_strdup("1");
+		if (NULL == m->meta.title)
+			m->meta.title = mandoc_strdup("UNKNOWN");
+		if (NULL == m->meta.vol)
+			m->meta.vol = mandoc_strdup("LOCAL");
+		if (NULL == m->meta.os)
+			m->meta.os = mandoc_strdup("LOCAL");
+		if (NULL == m->meta.date)
+			m->meta.date = mandoc_normdate
+				(m->parse, NULL, line, ppos);
+		m->flags |= MDOC_PBODY;
+	}
+
+	return((*mdoc_macros[tok].fp)(m, tok, line, ppos, pos, buf));
+}
+
+
+static int
+node_append(struct mdoc *mdoc, struct mdoc_node *p)
+{
+
+	assert(mdoc->last);
+	assert(mdoc->first);
+	assert(MDOC_ROOT != p->type);
+
+	switch (mdoc->next) {
+	case (MDOC_NEXT_SIBLING):
+		mdoc->last->next = p;
+		p->prev = mdoc->last;
+		p->parent = mdoc->last->parent;
+		break;
+	case (MDOC_NEXT_CHILD):
+		mdoc->last->child = p;
+		p->parent = mdoc->last;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	p->parent->nchild++;
+
+	/*
+	 * Copy over the normalised-data pointer of our parent.  Not
+	 * everybody has one, but copying a null pointer is fine.
+	 */
+
+	switch (p->type) {
+	case (MDOC_BODY):
+		/* FALLTHROUGH */
+	case (MDOC_TAIL):
+		/* FALLTHROUGH */
+	case (MDOC_HEAD):
+		p->norm = p->parent->norm;
+		break;
+	default:
+		break;
+	}
+
+	if ( ! mdoc_valid_pre(mdoc, p))
+		return(0);
+
+	switch (p->type) {
+	case (MDOC_HEAD):
+		assert(MDOC_BLOCK == p->parent->type);
+		p->parent->head = p;
+		break;
+	case (MDOC_TAIL):
+		assert(MDOC_BLOCK == p->parent->type);
+		p->parent->tail = p;
+		break;
+	case (MDOC_BODY):
+		if (p->end)
+			break;
+		assert(MDOC_BLOCK == p->parent->type);
+		p->parent->body = p;
+		break;
+	default:
+		break;
+	}
+
+	mdoc->last = p;
+
+	switch (p->type) {
+	case (MDOC_TBL):
+		/* FALLTHROUGH */
+	case (MDOC_TEXT):
+		if ( ! mdoc_valid_post(mdoc))
+			return(0);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+static struct mdoc_node *
+node_alloc(struct mdoc *m, int line, int pos, 
+		enum mdoct tok, enum mdoc_type type)
+{
+	struct mdoc_node *p;
+
+	p = mandoc_calloc(1, sizeof(struct mdoc_node));
+	p->sec = m->lastsec;
+	p->line = line;
+	p->pos = pos;
+	p->tok = tok;
+	p->type = type;
+
+	/* Flag analysis. */
+
+	if (MDOC_SYNOPSIS & m->flags)
+		p->flags |= MDOC_SYNPRETTY;
+	else
+		p->flags &= ~MDOC_SYNPRETTY;
+	if (MDOC_NEWLINE & m->flags)
+		p->flags |= MDOC_LINE;
+	m->flags &= ~MDOC_NEWLINE;
+
+	return(p);
+}
+
+
+int
+mdoc_tail_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_TAIL);
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_head_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
+{
+	struct mdoc_node *p;
+
+	assert(m->first);
+	assert(m->last);
+
+	p = node_alloc(m, line, pos, tok, MDOC_HEAD);
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_body_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_BODY);
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_endbody_alloc(struct mdoc *m, int line, int pos, enum mdoct tok,
+		struct mdoc_node *body, enum mdoc_endbody end)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_BODY);
+	p->pending = body;
+	p->end = end;
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_SIBLING;
+	return(1);
+}
+
+
+int
+mdoc_block_alloc(struct mdoc *m, int line, int pos, 
+		enum mdoct tok, struct mdoc_arg *args)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_BLOCK);
+	p->args = args;
+	if (p->args)
+		(args->refcnt)++;
+
+	switch (tok) {
+	case (MDOC_Bd):
+		/* FALLTHROUGH */
+	case (MDOC_Bf):
+		/* FALLTHROUGH */
+	case (MDOC_Bl):
+		/* FALLTHROUGH */
+	case (MDOC_Rs):
+		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
+		break;
+	default:
+		break;
+	}
+
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_elem_alloc(struct mdoc *m, int line, int pos, 
+		enum mdoct tok, struct mdoc_arg *args)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_ELEM);
+	p->args = args;
+	if (p->args)
+		(args->refcnt)++;
+
+	switch (tok) {
+	case (MDOC_An):
+		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
+		break;
+	default:
+		break;
+	}
+
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+int
+mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p)
+{
+	struct mdoc_node *n;
+
+	n = node_alloc(m, line, pos, MDOC_MAX, MDOC_TEXT);
+	n->string = roff_strdup(m->roff, p);
+
+	if ( ! node_append(m, n))
+		return(0);
+
+	m->next = MDOC_NEXT_SIBLING;
+	return(1);
+}
+
+
+static void
+mdoc_node_free(struct mdoc_node *p)
+{
+
+	if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type)
+		free(p->norm);
+	if (p->string)
+		free(p->string);
+	if (p->args)
+		mdoc_argv_free(p->args);
+	free(p);
+}
+
+
+static void
+mdoc_node_unlink(struct mdoc *m, struct mdoc_node *n)
+{
+
+	/* Adjust siblings. */
+
+	if (n->prev)
+		n->prev->next = n->next;
+	if (n->next)
+		n->next->prev = n->prev;
+
+	/* Adjust parent. */
+
+	if (n->parent) {
+		n->parent->nchild--;
+		if (n->parent->child == n)
+			n->parent->child = n->prev ? n->prev : n->next;
+		if (n->parent->last == n)
+			n->parent->last = n->prev ? n->prev : NULL;
+	}
+
+	/* Adjust parse point, if applicable. */
+
+	if (m && m->last == n) {
+		if (n->prev) {
+			m->last = n->prev;
+			m->next = MDOC_NEXT_SIBLING;
+		} else {
+			m->last = n->parent;
+			m->next = MDOC_NEXT_CHILD;
+		}
+	}
+
+	if (m && m->first == n)
+		m->first = NULL;
+}
+
+
+void
+mdoc_node_delete(struct mdoc *m, struct mdoc_node *p)
+{
+
+	while (p->child) {
+		assert(p->nchild);
+		mdoc_node_delete(m, p->child);
+	}
+	assert(0 == p->nchild);
+
+	mdoc_node_unlink(m, p);
+	mdoc_node_free(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 *m, 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(m->roff)))
+			if (NULL != (start = strchr(buf + offs, delim)))
+				*start++ = '\0';
+
+		/* Parse text as normal. */
+		if ( ! mdoc_ptext(m, 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(m->roff, NULL, line, offs, buf);
+
+		/* Process a finished equation? */
+		if (roff_closeeqn(m->roff))
+			if ( ! mdoc_addeqn(m, roff_eqn(m->roff)))
+				return(0);
+		offs += (end - (buf + offs));
+	} 
+
+	return(1);
+}
+#endif
+
+/*
+ * Parse free-form text, that is, a line that does not begin with the
+ * control character.
+ */
+static int
+mdoc_ptext(struct mdoc *m, int line, char *buf, int offs)
+{
+	char		 *c, *ws, *end;
+	struct mdoc_node *n;
+
+	/* No text before an initial macro. */
+
+	if (SEC_NONE == m->lastnamed) {
+		mdoc_pmsg(m, line, offs, MANDOCERR_NOTEXT);
+		return(1);
+	}
+
+	assert(m->last);
+	n = m->last;
+
+	/*
+	 * Divert directly to list processing if we're encountering a
+	 * columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry
+	 * (a MDOC_BODY means it's already open, in which case we should
+	 * process within its context in the normal way).
+	 */
+
+	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
+			LIST_column == n->norm->Bl.type) {
+		/* `Bl' is open without any children. */
+		m->flags |= MDOC_FREECOL;
+		return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf));
+	}
+
+	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
+			NULL != n->parent &&
+			MDOC_Bl == n->parent->tok &&
+			LIST_column == n->parent->norm->Bl.type) {
+		/* `Bl' has block-level `It' children. */
+		m->flags |= MDOC_FREECOL;
+		return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf));
+	}
+
+	/*
+	 * Search for the beginning of unescaped trailing whitespace (ws)
+	 * and for the first character not to be output (end).
+	 */
+
+	/* FIXME: replace with strcspn(). */
+	ws = NULL;
+	for (c = end = buf + offs; *c; c++) {
+		switch (*c) {
+		case ' ':
+			if (NULL == ws)
+				ws = c;
+			continue;
+		case '\t':
+			/*
+			 * Always warn about trailing tabs,
+			 * even outside literal context,
+			 * where they should be put on the next line.
+			 */
+			if (NULL == ws)
+				ws = c;
+			/*
+			 * Strip trailing tabs in literal context only;
+			 * outside, they affect the next line.
+			 */
+			if (MDOC_LITERAL & m->flags)
+				continue;
+			break;
+		case '\\':
+			/* Skip the escaped character, too, if any. */
+			if (c[1])
+				c++;
+			/* FALLTHROUGH */
+		default:
+			ws = NULL;
+			break;
+		}
+		end = c + 1;
+	}
+	*end = '\0';
+
+	if (ws)
+		mdoc_pmsg(m, line, (int)(ws-buf), MANDOCERR_EOLNSPACE);
+
+	if ('\0' == buf[offs] && ! (MDOC_LITERAL & m->flags)) {
+		mdoc_pmsg(m, line, (int)(c-buf), MANDOCERR_NOBLANKLN);
+
+		/*
+		 * 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(m, line, offs, MDOC_sp, NULL))
+			return(0);
+
+		m->next = MDOC_NEXT_SIBLING;
+		return(1);
+	}
+
+	if ( ! mdoc_word_alloc(m, line, offs, buf+offs))
+		return(0);
+
+	if (MDOC_LITERAL & m->flags)
+		return(1);
+
+	/*
+	 * End-of-sentence check.  If the last character is an unescaped
+	 * EOS character, then flag the node as being the end of a
+	 * sentence.  The front-end will know how to interpret this.
+	 */
+
+	assert(buf < end);
+
+	if (mandoc_eos(buf+offs, (size_t)(end-buf-offs), 0))
+		m->last->flags |= MDOC_EOS;
+
+	return(1);
+}
+
+
+/*
+ * Parse a macro line, that is, a line beginning with the control
+ * character.
+ */
+static int
+mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs)
+{
+	enum mdoct	  tok;
+	int		  i, sv;
+	char		  mac[5];
+	struct mdoc_node *n;
+
+	/* Empty post-control lines are ignored. */
+
+	if ('"' == buf[offs]) {
+		mdoc_pmsg(m, 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.
+	 */
+
+	i = 0;
+	while (i < 4 && '\0' != buf[offs] && 
+			' ' != buf[offs] && '\t' != buf[offs])
+		mac[i++] = buf[offs++];
+
+	mac[i] = '\0';
+
+	tok = (i > 1 || i < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
+
+	if (MDOC_MAX == tok) {
+		mandoc_vmsg(MANDOCERR_MACRO, m->parse, 
+				ln, sv, "%s", buf + sv - 1);
+		return(1);
+	}
+
+	/* Disregard the first trailing tab, if applicable. */
+
+	if ('\t' == buf[offs])
+		offs++;
+
+	/* 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(m, ln, offs - 1, MANDOCERR_EOLNSPACE);
+
+	/*
+	 * If an initial macro or a list invocation, divert directly
+	 * into macro processing.
+	 */
+
+	if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) {
+		if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) 
+			goto err;
+		return(1);
+	}
+
+	n = m->last;
+	assert(m->last);
+
+	/*
+	 * If the first macro of a `Bl -column', open an `It' block
+	 * context around the parsed macro.
+	 */
+
+	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
+			LIST_column == n->norm->Bl.type) {
+		m->flags |= MDOC_FREECOL;
+		if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf))
+			goto err;
+		return(1);
+	}
+
+	/*
+	 * If we're following a block-level `It' within a `Bl -column'
+	 * context (perhaps opened in the above block or in ptext()),
+	 * then open an `It' block context around the parsed macro.
+	 */
+
+	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
+			NULL != n->parent &&
+			MDOC_Bl == n->parent->tok &&
+			LIST_column == n->parent->norm->Bl.type) {
+		m->flags |= MDOC_FREECOL;
+		if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) 
+			goto err;
+		return(1);
+	}
+
+	/* Normal processing of a macro. */
+
+	if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) 
+		goto err;
+
+	return(1);
+
+err:	/* Error out. */
+
+	m->flags |= MDOC_HALT;
+	return(0);
+}
+
+enum mdelim
+mdoc_isdelim(const char *p)
+{
+
+	if ('\0' == p[0])
+		return(DELIM_NONE);
+
+	if ('\0' == p[1])
+		switch (p[0]) {
+		case('('):
+			/* FALLTHROUGH */
+		case('['):
+			return(DELIM_OPEN);
+		case('|'):
+			return(DELIM_MIDDLE);
+		case('.'):
+			/* FALLTHROUGH */
+		case(','):
+			/* FALLTHROUGH */
+		case(';'):
+			/* FALLTHROUGH */
+		case(':'):
+			/* FALLTHROUGH */
+		case('?'):
+			/* FALLTHROUGH */
+		case('!'):
+			/* FALLTHROUGH */
+		case(')'):
+			/* FALLTHROUGH */
+		case(']'):
+			return(DELIM_CLOSE);
+		default:
+			return(DELIM_NONE);
+		}
+
+	if ('\\' != p[0])
+		return(DELIM_NONE);
+
+	if (0 == strcmp(p + 1, "."))
+		return(DELIM_CLOSE);
+	if (0 == strcmp(p + 1, "*(Ba"))
+		return(DELIM_MIDDLE);
+
+	return(DELIM_NONE);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/mdoc.h	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,392 @@
+/*	$Id: mdoc.h,v 1.122 2011/03/22 14:05:45 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef MDOC_H
+#define MDOC_H
+
+enum	mdoct {
+	MDOC_Ap = 0,
+	MDOC_Dd,
+	MDOC_Dt,
+	MDOC_Os,
+	MDOC_Sh,
+	MDOC_Ss,
+	MDOC_Pp,
+	MDOC_D1,
+	MDOC_Dl,
+	MDOC_Bd,
+	MDOC_Ed,
+	MDOC_Bl,
+	MDOC_El,
+	MDOC_It,
+	MDOC_Ad,
+	MDOC_An,
+	MDOC_Ar,
+	MDOC_Cd,
+	MDOC_Cm,
+	MDOC_Dv,
+	MDOC_Er,
+	MDOC_Ev,
+	MDOC_Ex,
+	MDOC_Fa,
+	MDOC_Fd,
+	MDOC_Fl,
+	MDOC_Fn,
+	MDOC_Ft,
+	MDOC_Ic,
+	MDOC_In,
+	MDOC_Li,
+	MDOC_Nd,
+	MDOC_Nm,
+	MDOC_Op,
+	MDOC_Ot,
+	MDOC_Pa,
+	MDOC_Rv,
+	MDOC_St,
+	MDOC_Va,
+	MDOC_Vt,
+	MDOC_Xr,
+	MDOC__A,
+	MDOC__B,
+	MDOC__D,
+	MDOC__I,
+	MDOC__J,
+	MDOC__N,
+	MDOC__O,
+	MDOC__P,
+	MDOC__R,
+	MDOC__T,
+	MDOC__V,
+	MDOC_Ac,
+	MDOC_Ao,
+	MDOC_Aq,
+	MDOC_At,
+	MDOC_Bc,
+	MDOC_Bf,
+	MDOC_Bo,
+	MDOC_Bq,
+	MDOC_Bsx,
+	MDOC_Bx,
+	MDOC_Db,
+	MDOC_Dc,
+	MDOC_Do,
+	MDOC_Dq,
+	MDOC_Ec,
+	MDOC_Ef,
+	MDOC_Em,
+	MDOC_Eo,
+	MDOC_Fx,
+	MDOC_Ms,
+	MDOC_No,
+	MDOC_Ns,
+	MDOC_Nx,
+	MDOC_Ox,
+	MDOC_Pc,
+	MDOC_Pf,
+	MDOC_Po,
+	MDOC_Pq,
+	MDOC_Qc,
+	MDOC_Ql,
+	MDOC_Qo,
+	MDOC_Qq,
+	MDOC_Re,
+	MDOC_Rs,
+	MDOC_Sc,
+	MDOC_So,
+	MDOC_Sq,
+	MDOC_Sm,
+	MDOC_Sx,
+	MDOC_Sy,
+	MDOC_Tn,
+	MDOC_Ux,
+	MDOC_Xc,
+	MDOC_Xo,
+	MDOC_Fo,
+	MDOC_Fc,
+	MDOC_Oo,
+	MDOC_Oc,
+	MDOC_Bk,
+	MDOC_Ek,
+	MDOC_Bt,
+	MDOC_Hf,
+	MDOC_Fr,
+	MDOC_Ud,
+	MDOC_Lb,
+	MDOC_Lp,
+	MDOC_Lk,
+	MDOC_Mt,
+	MDOC_Brq,
+	MDOC_Bro,
+	MDOC_Brc,
+	MDOC__C,
+	MDOC_Es,
+	MDOC_En,
+	MDOC_Dx,
+	MDOC__Q,
+	MDOC_br,
+	MDOC_sp,
+	MDOC__U,
+	MDOC_Ta,
+	MDOC_MAX
+};
+
+enum	mdocargt {
+	MDOC_Split, /* -split */
+	MDOC_Nosplit, /* -nospli */
+	MDOC_Ragged, /* -ragged */
+	MDOC_Unfilled, /* -unfilled */
+	MDOC_Literal, /* -literal */
+	MDOC_File, /* -file */
+	MDOC_Offset, /* -offset */
+	MDOC_Bullet, /* -bullet */
+	MDOC_Dash, /* -dash */
+	MDOC_Hyphen, /* -hyphen */
+	MDOC_Item, /* -item */
+	MDOC_Enum, /* -enum */
+	MDOC_Tag, /* -tag */
+	MDOC_Diag, /* -diag */
+	MDOC_Hang, /* -hang */
+	MDOC_Ohang, /* -ohang */
+	MDOC_Inset, /* -inset */
+	MDOC_Column, /* -column */
+	MDOC_Width, /* -width */
+	MDOC_Compact, /* -compact */
+	MDOC_Std, /* -std */
+	MDOC_Filled, /* -filled */
+	MDOC_Words, /* -words */
+	MDOC_Emphasis, /* -emphasis */
+	MDOC_Symbolic, /* -symbolic */
+	MDOC_Nested, /* -nested */
+	MDOC_Centred, /* -centered */
+	MDOC_ARG_MAX
+};
+
+enum	mdoc_type {
+	MDOC_TEXT,
+	MDOC_ELEM,
+	MDOC_HEAD,
+	MDOC_TAIL,
+	MDOC_BODY,
+	MDOC_BLOCK,
+	MDOC_TBL,
+	MDOC_EQN,
+	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
+ * to other sections.
+ */
+enum	mdoc_sec {
+	SEC_NONE = 0,
+	SEC_NAME, /* NAME */
+	SEC_LIBRARY, /* LIBRARY */
+	SEC_SYNOPSIS, /* SYNOPSIS */
+	SEC_DESCRIPTION, /* DESCRIPTION */
+	SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */
+	SEC_RETURN_VALUES, /* RETURN VALUES */
+	SEC_ENVIRONMENT,  /* ENVIRONMENT */
+	SEC_FILES, /* FILES */
+	SEC_EXIT_STATUS, /* EXIT STATUS */
+	SEC_EXAMPLES, /* EXAMPLES */
+	SEC_DIAGNOSTICS, /* DIAGNOSTICS */
+	SEC_COMPATIBILITY, /* COMPATIBILITY */
+	SEC_ERRORS, /* ERRORS */
+	SEC_SEE_ALSO, /* SEE ALSO */
+	SEC_STANDARDS, /* STANDARDS */
+	SEC_HISTORY, /* HISTORY */
+	SEC_AUTHORS, /* AUTHORS */
+	SEC_CAVEATS, /* CAVEATS */
+	SEC_BUGS, /* BUGS */
+	SEC_SECURITY, /* SECURITY */
+	SEC_CUSTOM, 
+	SEC__MAX
+};
+
+struct	mdoc_meta {
+	char		 *msec; /* `Dt' section (1, 3p, etc.) */
+	char		 *vol; /* `Dt' volume (implied) */
+	char		 *arch; /* `Dt' arch (i386, etc.) */
+	char		 *date; /* `Dd' normalised date */
+	char		 *title; /* `Dt' title (FOO, etc.) */
+	char		 *os; /* `Os' system (OpenBSD, etc.) */
+	char		 *name; /* leading `Nm' name */
+};
+
+/* 
+ * An argument to a macro (multiple values = `-column xxx yyy'). 
+ */
+struct	mdoc_argv {
+	enum mdocargt  	  arg; /* type of argument */
+	int		  line;
+	int		  pos;
+	size_t		  sz; /* elements in "value" */
+	char		**value; /* argument strings */
+};
+
+/*
+ * Reference-counted macro arguments.  These are refcounted because
+ * blocks have multiple instances of the same arguments spread across
+ * the HEAD, BODY, TAIL, and BLOCK node types.
+ */
+struct 	mdoc_arg {
+	size_t		  argc;
+	struct mdoc_argv *argv;
+	unsigned int	  refcnt;
+};
+
+/*
+ * Indicates that a BODY's formatting has ended, but the scope is still
+ * open.  Used for syntax-broken blocks.
+ */
+enum	mdoc_endbody {
+	ENDBODY_NOT = 0,
+	ENDBODY_SPACE, /* is broken: append a space */
+	ENDBODY_NOSPACE /* is broken: don't append a space */
+};
+
+enum	mdoc_list {
+	LIST__NONE = 0,
+	LIST_bullet, /* -bullet */
+	LIST_column, /* -column */
+	LIST_dash, /* -dash */
+	LIST_diag, /* -diag */
+	LIST_enum, /* -enum */
+	LIST_hang, /* -hang */
+	LIST_hyphen, /* -hyphen */
+	LIST_inset, /* -inset */
+	LIST_item, /* -item */
+	LIST_ohang, /* -ohang */
+	LIST_tag, /* -tag */
+	LIST_MAX
+};
+
+enum	mdoc_disp {
+	DISP__NONE = 0,
+	DISP_centred, /* -centered */
+	DISP_ragged, /* -ragged */
+	DISP_unfilled, /* -unfilled */
+	DISP_filled, /* -filled */
+	DISP_literal /* -literal */
+};
+
+enum	mdoc_auth {
+	AUTH__NONE = 0,
+	AUTH_split, /* -split */
+	AUTH_nosplit /* -nosplit */
+};
+
+enum	mdoc_font {
+	FONT__NONE = 0,
+	FONT_Em, /* Em, -emphasis */
+	FONT_Li, /* Li, -literal */
+	FONT_Sy /* Sy, -symbolic */
+};
+
+struct	mdoc_bd {
+	const char	 *offs; /* -offset */
+	enum mdoc_disp	  type; /* -ragged, etc. */
+	int		  comp; /* -compact */
+};
+
+struct	mdoc_bl {
+	const char	 *width; /* -width */
+	const char	 *offs; /* -offset */
+	enum mdoc_list	  type; /* -tag, -enum, etc. */
+	int		  comp; /* -compact */
+	size_t		  ncols; /* -column arg count */
+	const char	**cols; /* -column val ptr */
+};
+
+struct	mdoc_bf {
+	enum mdoc_font	  font; /* font */
+};
+
+struct	mdoc_an {
+	enum mdoc_auth	  auth; /* -split, etc. */
+};
+
+struct	mdoc_rs {
+	int		  quote_T; /* whether to quote %T */
+};
+
+/*
+ * Consists of normalised node arguments.  These should be used instead
+ * of iterating through the mdoc_arg pointers of a node: defaults are
+ * provided, etc.
+ */
+union	mdoc_data {
+	struct mdoc_an 	  An;
+	struct mdoc_bd	  Bd;
+	struct mdoc_bf	  Bf;
+	struct mdoc_bl	  Bl;
+	struct mdoc_rs	  Rs;
+};
+
+/* 
+ * Single node in tree-linked AST. 
+ */
+struct	mdoc_node {
+	struct mdoc_node *parent; /* parent AST node */
+	struct mdoc_node *child; /* first child AST node */
+	struct mdoc_node *last; /* last child AST node */
+	struct mdoc_node *next; /* sibling AST node */
+	struct mdoc_node *prev; /* prior sibling AST node */
+	int		  nchild; /* number children */
+	int		  line; /* parse line */
+	int		  pos; /* parse column */
+	enum mdoct	  tok; /* tok or MDOC__MAX if none */
+	int		  flags;
+#define	MDOC_VALID	 (1 << 0) /* has been validated */
+#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_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 */
+	/* 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 *tail; /* BLOCK */
+	char		 *string; /* TEXT */
+	const struct tbl_span *span; /* TBL */
+	const struct eqn *eqn; /* EQN */
+	enum mdoc_endbody end; /* BODY */
+};
+
+/* Names of macros.  Index is enum mdoct. */
+extern	const char *const *mdoc_macronames;
+
+/* Names of macro args.  Index is enum mdocargt. */
+extern	const char *const *mdoc_argnames;
+
+__BEGIN_DECLS
+
+struct	mdoc;
+
+const struct mdoc_node *mdoc_node(const struct mdoc *);
+const struct mdoc_meta *mdoc_meta(const struct mdoc *);
+
+__END_DECLS
+
+#endif /*!MDOC_H*/
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/mdoc_argv.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,716 @@
+/*	$Id: mdoc_argv.c,v 1.82 2012/03/23 05:50:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+#define	MULTI_STEP	 5 /* pre-allocate argument values */
+#define	DELIMSZ	  	 6 /* max possible size of a delimiter */
+
+enum	argsflag {
+	ARGSFL_NONE = 0,
+	ARGSFL_DELIM, /* handle delimiters of [[::delim::][ ]+]+ */
+	ARGSFL_TABSEP /* handle tab/`Ta' separated phrases */
+};
+
+enum	argvflag {
+	ARGV_NONE, /* no args to flag (e.g., -split) */
+	ARGV_SINGLE, /* one arg to flag (e.g., -file xxx)  */
+	ARGV_MULTI, /* multiple args (e.g., -column xxx yyy) */
+	ARGV_OPT_SINGLE /* optional arg (e.g., -offset [xxx]) */
+};
+
+struct	mdocarg {
+	enum argsflag	 flags;
+	const enum mdocargt *argvs;
+};
+
+static	void		 argn_free(struct mdoc_arg *, 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, 
+				struct mdoc_argv *, int *, char *);
+static	int		 argv_opt_single(struct mdoc *, int, 
+				struct mdoc_argv *, int *, char *);
+static	int		 argv_single(struct mdoc *, int, 
+				struct mdoc_argv *, int *, char *);
+
+static	const enum argvflag argvflags[MDOC_ARG_MAX] = {
+	ARGV_NONE,	/* MDOC_Split */
+	ARGV_NONE,	/* MDOC_Nosplit */
+	ARGV_NONE,	/* MDOC_Ragged */
+	ARGV_NONE,	/* MDOC_Unfilled */
+	ARGV_NONE,	/* MDOC_Literal */
+	ARGV_SINGLE,	/* MDOC_File */
+	ARGV_OPT_SINGLE, /* MDOC_Offset */
+	ARGV_NONE,	/* MDOC_Bullet */
+	ARGV_NONE,	/* MDOC_Dash */
+	ARGV_NONE,	/* MDOC_Hyphen */
+	ARGV_NONE,	/* MDOC_Item */
+	ARGV_NONE,	/* MDOC_Enum */
+	ARGV_NONE,	/* MDOC_Tag */
+	ARGV_NONE,	/* MDOC_Diag */
+	ARGV_NONE,	/* MDOC_Hang */
+	ARGV_NONE,	/* MDOC_Ohang */
+	ARGV_NONE,	/* MDOC_Inset */
+	ARGV_MULTI,	/* MDOC_Column */
+	ARGV_OPT_SINGLE, /* MDOC_Width */
+	ARGV_NONE,	/* MDOC_Compact */
+	ARGV_NONE,	/* MDOC_Std */
+	ARGV_NONE,	/* MDOC_Filled */
+	ARGV_NONE,	/* MDOC_Words */
+	ARGV_NONE,	/* MDOC_Emphasis */
+	ARGV_NONE,	/* MDOC_Symbolic */
+	ARGV_NONE	/* MDOC_Symbolic */
+};
+
+static	const enum mdocargt args_Ex[] = {
+	MDOC_Std,
+	MDOC_ARG_MAX
+};
+
+static	const enum mdocargt args_An[] = {
+	MDOC_Split,
+	MDOC_Nosplit,
+	MDOC_ARG_MAX
+};
+
+static	const enum mdocargt args_Bd[] = {
+	MDOC_Ragged,
+	MDOC_Unfilled,
+	MDOC_Filled,
+	MDOC_Literal,
+	MDOC_File,
+	MDOC_Offset,
+	MDOC_Compact,
+	MDOC_Centred,
+	MDOC_ARG_MAX
+};
+
+static	const enum mdocargt args_Bf[] = {
+	MDOC_Emphasis,
+	MDOC_Literal,
+	MDOC_Symbolic,
+	MDOC_ARG_MAX
+};
+
+static	const enum mdocargt args_Bk[] = {
+	MDOC_Words,
+	MDOC_ARG_MAX
+};
+
+static	const enum mdocargt args_Bl[] = {
+	MDOC_Bullet,
+	MDOC_Dash,
+	MDOC_Hyphen,
+	MDOC_Item,
+	MDOC_Enum,
+	MDOC_Tag,
+	MDOC_Diag,
+	MDOC_Hang,
+	MDOC_Ohang,
+	MDOC_Inset,
+	MDOC_Column,
+	MDOC_Width,
+	MDOC_Offset,
+	MDOC_Compact,
+	MDOC_Nested,
+	MDOC_ARG_MAX
+};
+
+static	const struct mdocarg mdocargs[MDOC_MAX] = {
+	{ ARGSFL_NONE, NULL }, /* Ap */
+	{ ARGSFL_NONE, NULL }, /* Dd */
+	{ ARGSFL_NONE, NULL }, /* Dt */
+	{ ARGSFL_NONE, NULL }, /* Os */
+	{ ARGSFL_NONE, NULL }, /* Sh */
+	{ ARGSFL_NONE, NULL }, /* Ss */ 
+	{ ARGSFL_NONE, NULL }, /* Pp */ 
+	{ ARGSFL_DELIM, NULL }, /* D1 */
+	{ ARGSFL_DELIM, NULL }, /* Dl */
+	{ ARGSFL_NONE, args_Bd }, /* Bd */
+	{ ARGSFL_NONE, NULL }, /* Ed */
+	{ ARGSFL_NONE, args_Bl }, /* Bl */
+	{ ARGSFL_NONE, NULL }, /* El */
+	{ ARGSFL_NONE, NULL }, /* It */
+	{ ARGSFL_DELIM, NULL }, /* Ad */ 
+	{ ARGSFL_DELIM, args_An }, /* An */
+	{ ARGSFL_DELIM, NULL }, /* Ar */
+	{ ARGSFL_NONE, NULL }, /* Cd */
+	{ ARGSFL_DELIM, NULL }, /* Cm */
+	{ 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 }, /* Fl */
+	{ ARGSFL_DELIM, NULL }, /* Fn */ 
+	{ ARGSFL_DELIM, NULL }, /* Ft */ 
+	{ ARGSFL_DELIM, NULL }, /* Ic */ 
+	{ ARGSFL_NONE, NULL }, /* In */ 
+	{ ARGSFL_DELIM, NULL }, /* Li */
+	{ ARGSFL_NONE, NULL }, /* Nd */ 
+	{ ARGSFL_DELIM, NULL }, /* Nm */ 
+	{ ARGSFL_DELIM, NULL }, /* Op */
+	{ ARGSFL_NONE, NULL }, /* Ot */
+	{ ARGSFL_DELIM, NULL }, /* Pa */
+	{ ARGSFL_NONE, args_Ex }, /* Rv */
+	{ ARGSFL_DELIM, NULL }, /* St */ 
+	{ ARGSFL_DELIM, NULL }, /* Va */
+	{ ARGSFL_DELIM, NULL }, /* Vt */ 
+	{ ARGSFL_DELIM, NULL }, /* Xr */
+	{ ARGSFL_NONE, NULL }, /* %A */
+	{ ARGSFL_NONE, NULL }, /* %B */
+	{ ARGSFL_NONE, NULL }, /* %D */
+	{ ARGSFL_NONE, NULL }, /* %I */
+	{ ARGSFL_NONE, NULL }, /* %J */
+	{ ARGSFL_NONE, NULL }, /* %N */
+	{ ARGSFL_NONE, NULL }, /* %O */
+	{ ARGSFL_NONE, NULL }, /* %P */
+	{ ARGSFL_NONE, NULL }, /* %R */
+	{ ARGSFL_NONE, NULL }, /* %T */
+	{ ARGSFL_NONE, NULL }, /* %V */
+	{ ARGSFL_DELIM, NULL }, /* Ac */
+	{ ARGSFL_NONE, NULL }, /* Ao */
+	{ ARGSFL_DELIM, NULL }, /* Aq */
+	{ ARGSFL_DELIM, NULL }, /* At */
+	{ ARGSFL_DELIM, NULL }, /* Bc */
+	{ ARGSFL_NONE, args_Bf }, /* Bf */ 
+	{ ARGSFL_NONE, NULL }, /* Bo */
+	{ ARGSFL_DELIM, NULL }, /* Bq */
+	{ ARGSFL_DELIM, NULL }, /* Bsx */
+	{ ARGSFL_DELIM, NULL }, /* Bx */
+	{ ARGSFL_NONE, NULL }, /* Db */
+	{ ARGSFL_DELIM, NULL }, /* Dc */
+	{ ARGSFL_NONE, NULL }, /* Do */
+	{ ARGSFL_DELIM, NULL }, /* Dq */
+	{ ARGSFL_DELIM, NULL }, /* Ec */
+	{ ARGSFL_NONE, NULL }, /* Ef */
+	{ ARGSFL_DELIM, NULL }, /* Em */ 
+	{ ARGSFL_NONE, NULL }, /* Eo */
+	{ ARGSFL_DELIM, NULL }, /* Fx */
+	{ ARGSFL_DELIM, NULL }, /* Ms */
+	{ ARGSFL_DELIM, NULL }, /* No */
+	{ ARGSFL_DELIM, NULL }, /* Ns */
+	{ ARGSFL_DELIM, NULL }, /* Nx */
+	{ ARGSFL_DELIM, NULL }, /* Ox */
+	{ ARGSFL_DELIM, NULL }, /* Pc */
+	{ ARGSFL_DELIM, NULL }, /* Pf */
+	{ ARGSFL_NONE, NULL }, /* Po */
+	{ ARGSFL_DELIM, NULL }, /* Pq */
+	{ ARGSFL_DELIM, NULL }, /* Qc */
+	{ ARGSFL_DELIM, NULL }, /* Ql */
+	{ ARGSFL_NONE, NULL }, /* Qo */
+	{ ARGSFL_DELIM, NULL }, /* Qq */
+	{ ARGSFL_NONE, NULL }, /* Re */
+	{ ARGSFL_NONE, NULL }, /* Rs */
+	{ ARGSFL_DELIM, NULL }, /* Sc */
+	{ ARGSFL_NONE, NULL }, /* So */
+	{ ARGSFL_DELIM, NULL }, /* Sq */
+	{ ARGSFL_NONE, NULL }, /* Sm */
+	{ ARGSFL_DELIM, NULL }, /* Sx */
+	{ ARGSFL_DELIM, NULL }, /* Sy */
+	{ ARGSFL_DELIM, NULL }, /* Tn */
+	{ ARGSFL_DELIM, NULL }, /* Ux */
+	{ ARGSFL_DELIM, NULL }, /* Xc */
+	{ ARGSFL_NONE, NULL }, /* Xo */
+	{ ARGSFL_NONE, NULL }, /* Fo */ 
+	{ ARGSFL_NONE, 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_NONE, NULL }, /* Ud */
+	{ ARGSFL_NONE, NULL }, /* Lb */
+	{ ARGSFL_NONE, NULL }, /* Lp */
+	{ ARGSFL_DELIM, NULL }, /* Lk */
+	{ ARGSFL_DELIM, NULL }, /* Mt */
+	{ ARGSFL_DELIM, NULL }, /* Brq */
+	{ ARGSFL_NONE, NULL }, /* Bro */
+	{ ARGSFL_DELIM, NULL }, /* Brc */
+	{ ARGSFL_NONE, NULL }, /* %C */
+	{ ARGSFL_NONE, NULL }, /* Es */
+	{ ARGSFL_NONE, NULL }, /* En */
+	{ ARGSFL_NONE, NULL }, /* Dx */
+	{ ARGSFL_NONE, NULL }, /* %Q */
+	{ ARGSFL_NONE, NULL }, /* br */
+	{ ARGSFL_NONE, NULL }, /* sp */
+	{ ARGSFL_NONE, NULL }, /* %U */
+	{ ARGSFL_NONE, NULL }, /* Ta */
+};
+
+
+/*
+ * 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.
+ */
+enum margverr
+mdoc_argv(struct mdoc *m, int line, enum mdoct tok,
+		struct mdoc_arg **v, int *pos, char *buf)
+{
+	char		 *p, sv;
+	struct mdoc_argv tmp;
+	struct mdoc_arg	 *arg;
+	const enum mdocargt *ap;
+
+	if ('\0' == buf[*pos])
+		return(ARGV_EOLN);
+	else if (NULL == (ap = mdocargs[tok].argvs))
+		return(ARGV_WORD);
+	else if ('-' != buf[*pos])
+		return(ARGV_WORD);
+
+	/* Seek to the first unescaped space. */
+
+	p = &buf[++(*pos)];
+
+	assert(*pos > 0);
+
+	for ( ; buf[*pos] ; (*pos)++)
+		if (' ' == buf[*pos] && '\\' != buf[*pos - 1])
+			break;
+
+	/* 
+	 * 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).
+	 */
+
+	if ('\0' != (sv = buf[*pos])) 
+		buf[(*pos)++] = '\0';
+
+	/*
+	 * Now look up the word as a flag.  Use temporary storage that
+	 * we'll copy into the node's flags, if necessary.
+	 */
+
+	memset(&tmp, 0, sizeof(struct mdoc_argv));
+
+	tmp.line = line;
+	tmp.pos = *pos;
+	tmp.arg = MDOC_ARG_MAX;
+
+	while (MDOC_ARG_MAX != (tmp.arg = *ap++))
+		if (0 == strcmp(p, mdoc_argnames[tmp.arg]))
+			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 argument). */
+
+	while (buf[*pos] && ' ' == buf[*pos])
+		(*pos)++;
+
+	switch (argvflags[tmp.arg]) {
+	case (ARGV_SINGLE):
+		if ( ! argv_single(m, line, &tmp, pos, buf))
+			return(ARGV_ERROR);
+		break;
+	case (ARGV_MULTI):
+		if ( ! argv_multi(m, line, &tmp, pos, buf))
+			return(ARGV_ERROR);
+		break;
+	case (ARGV_OPT_SINGLE):
+		if ( ! argv_opt_single(m, line, &tmp, pos, buf))
+			return(ARGV_ERROR);
+		break;
+	case (ARGV_NONE):
+		break;
+	}
+
+	if (NULL == (arg = *v))
+		arg = *v = mandoc_calloc(1, sizeof(struct mdoc_arg));
+
+	arg->argc++;
+	arg->argv = mandoc_realloc
+		(arg->argv, arg->argc * sizeof(struct mdoc_argv));
+
+	memcpy(&arg->argv[(int)arg->argc - 1], 
+			&tmp, sizeof(struct mdoc_argv));
+
+	return(ARGV_ARG);
+}
+
+void
+mdoc_argv_free(struct mdoc_arg *p)
+{
+	int		 i;
+
+	if (NULL == p)
+		return;
+
+	if (p->refcnt) {
+		--(p->refcnt);
+		if (p->refcnt)
+			return;
+	}
+	assert(p->argc);
+
+	for (i = (int)p->argc - 1; i >= 0; i--)
+		argn_free(p, i);
+
+	free(p->argv);
+	free(p);
+}
+
+static void
+argn_free(struct mdoc_arg *p, int iarg)
+{
+	struct mdoc_argv *arg;
+	int		  j;
+
+	arg = &p->argv[iarg];
+
+	if (arg->sz && arg->value) {
+		for (j = (int)arg->sz - 1; j >= 0; j--) 
+			free(arg->value[j]);
+		free(arg->value);
+	}
+
+	for (--p->argc; iarg < (int)p->argc; iarg++)
+		p->argv[iarg] = p->argv[iarg+1];
+}
+
+enum margserr
+mdoc_zargs(struct mdoc *m, int line, int *pos, char *buf, char **v)
+{
+
+	return(args(m, line, pos, buf, ARGSFL_NONE, v));
+}
+
+enum margserr
+mdoc_args(struct mdoc *m, int line, int *pos, 
+		char *buf, enum mdoct tok, char **v)
+{
+	enum argsflag	  fl;
+	struct mdoc_node *n;
+
+	fl = mdocargs[tok].flags;
+
+	if (MDOC_It != tok)
+		return(args(m, line, pos, buf, fl, v));
+
+	/*
+	 * We know that we're in an `It', so it's reasonable to expect
+	 * us to be sitting in a `Bl'.  Someday this may not be the case
+	 * (if we allow random `It's sitting out there), so provide a
+	 * safe fall-back into the default behaviour.
+	 */
+
+	for (n = m->last; n; n = n->parent)
+		if (MDOC_Bl == n->tok)
+			if (LIST_column == n->norm->Bl.type) {
+				fl = ARGSFL_TABSEP;
+				break;
+			}
+
+	return(args(m, line, pos, buf, fl, v));
+}
+
+static enum margserr
+args(struct mdoc *m, int line, int *pos, 
+		char *buf, enum argsflag fl, char **v)
+{
+	char		*p, *pp;
+	enum margserr	 rc;
+
+	if ('\0' == buf[*pos]) {
+		if (MDOC_PPHRASE & m->flags)
+			return(ARGS_EOLN);
+		/*
+		 * If we're not in a partial phrase and the flag for
+		 * being a phrase literal is still set, the punctuation
+		 * is unterminated.
+		 */
+		if (MDOC_PHRASELIT & m->flags)
+			mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE);
+
+		m->flags &= ~MDOC_PHRASELIT;
+		return(ARGS_EOLN);
+	}
+
+	*v = &buf[*pos];
+
+	if (ARGSFL_DELIM == fl)
+		if (args_checkpunct(buf, *pos))
+			return(ARGS_PUNCT);
+
+	/*
+	 * First handle TABSEP items, restricted to `Bl -column'.  This
+	 * ignores conventional token parsing and instead uses tabs or
+	 * `Ta' macros to separate phrases.  Phrases are parsed again
+	 * for arguments at a later phase.
+	 */
+
+	if (ARGSFL_TABSEP == fl) {
+		/* Scan ahead to tab (can't be escaped). */
+		p = strchr(*v, '\t');
+		pp = NULL;
+
+		/* Scan ahead to unescaped `Ta'. */
+		if ( ! (MDOC_PHRASELIT & m->flags)) 
+			for (pp = *v; ; pp++) {
+				if (NULL == (pp = strstr(pp, "Ta")))
+					break;
+				if (pp > *v && ' ' != *(pp - 1))
+					continue;
+				if (' ' == *(pp + 2) || '\0' == *(pp + 2))
+					break;
+			}
+
+		/* By default, assume a phrase. */
+		rc = ARGS_PHRASE;
+
+		/* 
+		 * Adjust new-buffer position to be beyond delimiter
+		 * mark (e.g., Ta -> end + 2).
+		 */
+		if (p && pp) {
+			*pos += pp < p ? 2 : 1;
+			rc = pp < p ? ARGS_PHRASE : ARGS_PPHRASE;
+			p = pp < p ? pp : p;
+		} else if (p && ! pp) {
+			rc = ARGS_PPHRASE;
+			*pos += 1;
+		} else if (pp && ! p) {
+			p = pp;
+			*pos += 2;
+		} else {
+			rc = ARGS_PEND;
+			p = strchr(*v, 0);
+		}
+
+		/* Whitespace check for eoln case... */
+		if ('\0' == *p && ' ' == *(p - 1))
+			mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE);
+
+		*pos += (int)(p - *v);
+
+		/* Strip delimiter's preceding whitespace. */
+		pp = p - 1;
+		while (pp > *v && ' ' == *pp) {
+			if (pp > *v && '\\' == *(pp - 1))
+				break;
+			pp--;
+		}
+		*(pp + 1) = 0;
+
+		/* Strip delimiter's proceeding whitespace. */
+		for (pp = &buf[*pos]; ' ' == *pp; pp++, (*pos)++)
+			/* Skip ahead. */ ;
+
+		return(rc);
+	} 
+
+	/* 
+	 * Process a quoted literal.  A quote begins with a double-quote
+	 * and ends with a double-quote NOT preceded by a double-quote.
+	 * Whitespace is NOT involved in literal termination.
+	 */
+
+	if (MDOC_PHRASELIT & m->flags || '\"' == buf[*pos]) {
+		if ( ! (MDOC_PHRASELIT & m->flags))
+			*v = &buf[++(*pos)];
+
+		if (MDOC_PPHRASE & m->flags)
+			m->flags |= MDOC_PHRASELIT;
+
+		for ( ; buf[*pos]; (*pos)++) {
+			if ('\"' != buf[*pos])
+				continue;
+			if ('\"' != buf[*pos + 1])
+				break;
+			(*pos)++;
+		}
+
+		if ('\0' == buf[*pos]) {
+			if (MDOC_PPHRASE & m->flags)
+				return(ARGS_QWORD);
+			mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE);
+			return(ARGS_QWORD);
+		}
+
+		m->flags &= ~MDOC_PHRASELIT;
+		buf[(*pos)++] = '\0';
+
+		if ('\0' == buf[*pos])
+			return(ARGS_QWORD);
+
+		while (' ' == buf[*pos])
+			(*pos)++;
+
+		if ('\0' == buf[*pos])
+			mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE);
+
+		return(ARGS_QWORD);
+	}
+
+	p = &buf[*pos];
+	*v = mandoc_getarg(m->parse, &p, line, 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
+ * whitespace may separate these tokens.
+ */
+static int
+args_checkpunct(const char *buf, int i)
+{
+	int		 j;
+	char		 dbuf[DELIMSZ];
+	enum mdelim	 d;
+
+	/* First token must be a close-delimiter. */
+
+	for (j = 0; buf[i] && ' ' != buf[i] && j < DELIMSZ; j++, i++)
+		dbuf[j] = buf[i];
+
+	if (DELIMSZ == j)
+		return(0);
+
+	dbuf[j] = '\0';
+	if (DELIM_CLOSE != mdoc_isdelim(dbuf))
+		return(0);
+
+	while (' ' == buf[i])
+		i++;
+
+	/* Remaining must NOT be open/none. */
+	
+	while (buf[i]) {
+		j = 0;
+		while (buf[i] && ' ' != buf[i] && j < DELIMSZ)
+			dbuf[j++] = buf[i++];
+
+		if (DELIMSZ == j)
+			return(0);
+
+		dbuf[j] = '\0';
+		d = mdoc_isdelim(dbuf);
+		if (DELIM_NONE == d || DELIM_OPEN == d)
+			return(0);
+
+		while (' ' == buf[i])
+			i++;
+	}
+
+	return('\0' == buf[i]);
+}
+
+static int
+argv_multi(struct mdoc *m, int line, 
+		struct mdoc_argv *v, int *pos, char *buf)
+{
+	enum margserr	 ac;
+	char		*p;
+
+	for (v->sz = 0; ; v->sz++) {
+		if ('-' == buf[*pos])
+			break;
+		ac = args(m, line, pos, buf, ARGSFL_NONE, &p);
+		if (ARGS_ERROR == ac)
+			return(0);
+		else if (ARGS_EOLN == ac)
+			break;
+
+		if (0 == v->sz % MULTI_STEP)
+			v->value = mandoc_realloc(v->value, 
+				(v->sz + MULTI_STEP) * sizeof(char *));
+
+		v->value[(int)v->sz] = mandoc_strdup(p);
+	}
+
+	return(1);
+}
+
+static int
+argv_opt_single(struct mdoc *m, int line, 
+		struct mdoc_argv *v, int *pos, char *buf)
+{
+	enum margserr	 ac;
+	char		*p;
+
+	if ('-' == buf[*pos])
+		return(1);
+
+	ac = args(m, line, pos, buf, ARGSFL_NONE, &p);
+	if (ARGS_ERROR == ac)
+		return(0);
+	if (ARGS_EOLN == ac)
+		return(1);
+
+	v->sz = 1;
+	v->value = mandoc_malloc(sizeof(char *));
+	v->value[0] = mandoc_strdup(p);
+
+	return(1);
+}
+
+static int
+argv_single(struct mdoc *m, int line, 
+		struct mdoc_argv *v, int *pos, char *buf)
+{
+	int		 ppos;
+	enum margserr	 ac;
+	char		*p;
+
+	ppos = *pos;
+
+	ac = args(m, line, pos, buf, ARGSFL_NONE, &p);
+	if (ARGS_EOLN == ac) {
+		mdoc_pmsg(m, line, ppos, MANDOCERR_SYNTARGVCOUNT);
+		return(0);
+	} else if (ARGS_ERROR == ac)
+		return(0);
+
+	v->sz = 1;
+	v->value = mandoc_malloc(sizeof(char *));
+	v->value[0] = mandoc_strdup(p);
+
+	return(1);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/mdoc_hash.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,94 @@
+/*	$Id: mdoc_hash.c,v 1.18 2011/07/24 18:15:14 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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.
+ */
+void
+mdoc_hash_init(void)
+{
+	int		 i, j, major;
+	const char	*p;
+
+	memset(table, UCHAR_MAX, sizeof(table));
+
+	for (i = 0; i < (int)MDOC_MAX; i++) {
+		p = mdoc_macronames[i];
+
+		if (isalpha((unsigned char)p[1]))
+			major = 12 * (tolower((unsigned char)p[1]) - 97);
+		else
+			major = 12 * 26;
+
+		for (j = 0; j < 12; j++)
+			if (UCHAR_MAX == table[major + j]) {
+				table[major + j] = (unsigned char)i;
+				break;
+			}
+
+		assert(j < 12);
+	}
+}
+
+enum mdoct
+mdoc_hash_find(const char *p)
+{
+	int		  major, i, j;
+
+	if (0 == p[0])
+		return(MDOC_MAX);
+	if ( ! isalpha((unsigned char)p[0]) && '%' != p[0])
+		return(MDOC_MAX);
+
+	if (isalpha((unsigned char)p[1]))
+		major = 12 * (tolower((unsigned char)p[1]) - 97);
+	else if ('1' == p[1])
+		major = 12 * 26;
+	else 
+		return(MDOC_MAX);
+
+	if (p[2] && p[3])
+		return(MDOC_MAX);
+
+	for (j = 0; j < 12; j++) {
+		if (UCHAR_MAX == (i = table[major + j]))
+			break;
+		if (0 == strcmp(p, mdoc_macronames[i]))
+			return((enum mdoct)i);
+	}
+
+	return(MDOC_MAX);
+}
--- /dev/null	Fri Jul 18 08:34:34 2014
+++ new/usr/src/cmd/mandoc/mdoc_html.c	Fri Jul 18 08:34:34 2014
@@ -0,0 +1,2284 @@
+/*	$Id: mdoc_html.c,v 1.182 2011/11/03 20:37:00 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "html.h"
+#include "mdoc.h"
+#include "main.h"
+
+#define	INDENT		 5
+
+#define	MDOC_ARGS	  const struct mdoc_meta *m, \
+			  const struct mdoc_node *n, \
+			  struct html *h
+
+#ifndef MIN
+#define	MIN(a,b)	((/*CONSTCOND*/(a)<(b))?(a):(b))
+#endif
+
+struct	htmlmdoc {
+	int		(*pre)(MDOC_ARGS);
+	void		(*post)(MDOC_ARGS);
+};
+
+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 *, 
+				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);
+
+static	void		  mdoc__x_post(MDOC_ARGS);
+static	int		  mdoc__x_pre(MDOC_ARGS);
+static	int		  mdoc_ad_pre(MDOC_ARGS);
+static	int		  mdoc_an_pre(MDOC_ARGS);
+static	int		  mdoc_ap_pre(MDOC_ARGS);
+static	int		  mdoc_ar_pre(MDOC_ARGS);
+static	int		  mdoc_bd_pre(MDOC_ARGS);
+static	int		  mdoc_bf_pre(MDOC_ARGS);
+static	void		  mdoc_bk_post(MDOC_ARGS);
+static	int		  mdoc_bk_pre(MDOC_ARGS);
+static	int		  mdoc_bl_pre(MDOC_ARGS);
+static	int		  mdoc_bt_pre(MDOC_ARGS);
+static	int		  mdoc_bx_pre(MDOC_ARGS);
+static	int		  mdoc_cd_pre(MDOC_ARGS);
+static	int		  mdoc_d1_pre(MDOC_ARGS);
+static	int		  mdoc_dv_pre(MDOC_ARGS);
+static	int		  mdoc_fa_pre(MDOC_ARGS);
+static	int		  mdoc_fd_pre(MDOC_ARGS);
+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	int		  mdoc_er_pre(MDOC_ARGS);
+static	int		  mdoc_ev_pre(MDOC_ARGS);
+static	int		  mdoc_ex_pre(MDOC_ARGS);
+static	void		  mdoc_fo_post(MDOC_ARGS);
+static	int		  mdoc_fo_pre(MDOC_ARGS);
+static	int		  mdoc_ic_pre(MDOC_ARGS);
+static	int		  mdoc_igndelim_pre(MDOC_ARGS);
+static	int		  mdoc_in_pre(MDOC_ARGS);
+static	int		  mdoc_it_pre(MDOC_ARGS);
+static	int		  mdoc_lb_pre(MDOC_ARGS);
+static	int		  mdoc_li_pre(MDOC_ARGS);
+static	int		  mdoc_lk_pre(MDOC_ARGS);
+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_ns_pre(MDOC_ARGS);
+static	int		  mdoc_pa_pre(MDOC_ARGS);
+static	void		  mdoc_pf_post(MDOC_ARGS);
+static	int		  mdoc_pp_pre(MDOC_ARGS);
+static	void		  mdoc_quote_post(MDOC_ARGS);
+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_sm_pre(MDOC_ARGS);
+static	int		  mdoc_sp_pre(MDOC_ARGS);
+static	int		  mdoc_ss_pre(MDOC_ARGS);
+static	int		  mdoc_sx_pre(MDOC_ARGS);
+static	int		  mdoc_sy_pre(MDOC_ARGS);
+static	int		  mdoc_ud_pre(MDOC_ARGS);
+static	int		  mdoc_va_pre(MDOC_ARGS);
+static	int		  mdoc_vt_pre(MDOC_ARGS);
+static	int		  mdoc_xr_pre(MDOC_ARGS);
+static	int		  mdoc_xx_pre(MDOC_ARGS);
+
+static	const struct htmlmdoc mdocs[MDOC_MAX] = {
+	{mdoc_ap_pre, NULL}, /* Ap */
+	{NULL, NULL}, /* Dd */
+	{NULL, NULL}, /* Dt */
+	{NULL, NULL}, /* Os */
+	{mdoc_sh_pre, NULL }, /* Sh */
+	{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 */
+	{NULL, NULL}, /* Ed */
+	{mdoc_bl_pre, NULL}, /* Bl */
+	{NULL, NULL}, /* El */
+	{mdoc_it_pre, NULL}, /* It */
+	{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_ex_pre, NULL}, /* Ex */
+	{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_li_pre, NULL}, /* Li */
+	{mdoc_nd_pre, NULL}, /* Nd */ 
+	{mdoc_nm_pre, NULL}, /* Nm */ 
+	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
+	{NULL, NULL}, /* Ot */
+	{mdoc_pa_pre, NULL}, /* Pa */
+	{mdoc_rv_pre, NULL}, /* Rv */
+	{NULL, NULL}, /* St */ 
+	{mdoc_va_pre, NULL}, /* Va */
+	{mdoc_vt_pre, NULL}, /* Vt */ 
+	{mdoc_xr_pre, NULL}, /* Xr */
+	{mdoc__x_pre, mdoc__x_post}, /* %A */
+	{mdoc__x_pre, mdoc__x_post}, /* %B */
+	{mdoc__x_pre, mdoc__x_post}, /* %D */
+	{mdoc__x_pre, mdoc__x_post}, /* %I */
+	{mdoc__x_pre, mdoc__x_post}, /* %J */
+	{mdoc__x_pre, mdoc__x_post}, /* %N */
+	{mdoc__x_pre, mdoc__x_post}, /* %O */
+	{mdoc__x_pre, mdoc__x_post}, /* %P */
+	{mdoc__x_pre, mdoc__x_post}, /* %R */
+	{mdoc__x_pre, mdoc__x_post}, /* %T */
+	{mdoc__x_pre, mdoc__x_post}, /* %V */
+	{NULL, NULL}, /* Ac */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Ao */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Aq */
+	{NULL, NULL}, /* At */
+	{NULL, NULL}, /* Bc */
+	{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 */
+	{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_xx_pre, NULL}, /* Fx */
+	{mdoc_ms_pre, NULL}, /* Ms */
+	{mdoc_igndelim_pre, NULL}, /* No */
+	{mdoc_ns_pre, NULL}, /* Ns */
+	{mdoc_xx_pre, NULL}, /* Nx */
+	{mdoc_xx_pre, NULL}, /* Ox */
+	{NULL, NULL}, /* Pc */
+	{mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Po */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Pq */
+	{NULL, NULL}, /* Qc */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Ql */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Qo */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Qq */
+	{NULL, NULL}, /* Re */
+	{mdoc_rs_pre, NULL}, /* Rs */
+	{NULL, NULL}, /* Sc */
+	{mdoc_quote_pre, mdoc_quote_post}, /* So */
+	{mdoc_quote_pre, mdoc_quote_post}, /* Sq */
+	{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_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_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_sp_pre, NULL}, /* br */
+	{mdoc_sp_pre, NULL}, /* sp */ 
+	{mdoc__x_pre, mdoc__x_post}, /* %U */ 
+	{NULL, NULL}, /* Ta */ 
+};
+
+static	const char * const lists[LIST_MAX] = {
+	NULL,
+	"list-bul",
+	"list-col",
+	"list-dash",
+	"list-diag",
+	"list-enum",
+	"list-hang",
+	"list-hyph",
+	"list-inset",
+	"list-item",
+	"list-ohang",
+	"list-tag"
+};
+
+void
+html_mdoc(void *arg, const struct mdoc *m)
+{
+
+	print_mdoc(mdoc_meta(m), mdoc_node(m), (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
+ * the value.
+ */
+static void
+a2width(const char *p, struct roffsu *su)
+{
+
+	if ( ! a2roffsu(p, su, SCALE_MAX)) {
+		su->unit = SCALE_BU;
+		su->scale = html_strlen(p);
+	}
+}
+
+
+/*
+ * See the same function in mdoc_term.c for documentation.
+ */
+static void
+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) {
+		print_otag(h, TAG_BR, 0, NULL);
+		return;
+	}
+
+	switch (n->prev->tok) {
+	case (MDOC_Fd):
+		/* FALLTHROUGH */
+	case (MDOC_Fn):
+		/* FALLTHROUGH */
+	case (MDOC_Fo):
+		/* FALLTHROUGH */
+	case (MDOC_In):
+		/* FALLTHROUGH */
+	case (MDOC_Vt):
+		print_otag(h, TAG_P, 0, NULL);
+		break;
+	case (MDOC_Ft):
+		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
+			print_otag(h, TAG_P, 0, NULL);
+			break;
+		}
+		/* FALLTHROUGH */
+	default:
+		print_otag(h, TAG_BR, 0, NULL);
+		break;
+	}
+}
+
+
+/*
+ * 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)
+{
+	struct tag	*t, *tt;
+	struct htmlpair	 tag;
+
+	PAIR_CLASS_INIT(&tag, "mandoc");
+
+	if ( ! (HTML_FRAGMENT & h->oflags)) {
+		print_gen_decls(h);
+		t = print_otag(h, TAG_HTML, 0, NULL);
+		tt = print_otag(h, TAG_HEAD, 0, NULL);
+		print_mdoc_head(m, n, h);
+		print_tagq(h, tt);
+		print_otag(h, TAG_BODY, 0, NULL);
+		print_otag(h, TAG_DIV, 1, &tag);
+	} else 
+		t = print_otag(h, TAG_DIV, 1, &tag);
+
+	print_mdoc_nodelist(m, 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)", m->title, m->msec);
+
+	if (m->arch)
+		bufcat_fmt(h, " (%s)", m->arch);
+
+	print_otag(h, TAG_TITLE, 0, NULL);
+	print_text(h, h->buf);
+}
+
+
+static void
+print_mdoc_nodelist(MDOC_ARGS)
+{
+
+	print_mdoc_node(m, n, h);
+	if (n->next)
+		print_mdoc_nodelist(m, n->next, h);
+}
+
+
+static void
+print_mdoc_node(MDOC_ARGS)
+{
+	int		 child;
+	struct tag	*t;
+
+	child = 1;
+	t = h->tags.head;
+
+	switch (n->type) {
+	case (MDOC_ROOT):
+		child = mdoc_root_pre(m, n, h);
+		break;
+	case (MDOC_TEXT):
+		/* No tables in this mode... */
+		assert(NULL == h->tblt);
+
+		/*
+		 * Make sure that if we're in a literal mode already
+		 * (i.e., within a <PRE>) don't print the newline.
+		 */
+		if (' ' == *n->string && MDOC_LINE & n->flags)
+			if ( ! (HTML_LITERAL & h->flags))
+				print_otag(h, TAG_BR, 0, NULL);
+		if (MDOC_DELIMC & n->flags)
+			h->flags |= HTML_NOSPACE;
+		print_text(h, n->string);
+		if (MDOC_DELIMO & n->flags)
+			h->flags |= HTML_NOSPACE;
+		return;
+	case (MDOC_EQN):
+		print_eqn(h, n->eqn);
+		break;
+	case (MDOC_TBL):
+		/*
+		 * This will take care of initialising all of the table
+		 * state data for the first table, then tearing it down
+		 * for the last one.
+		 */
+		print_tbl(h, n->span);
+		return;
+	default:
+		/*
+		 * Close out the current table, if it's open, and unset
+		 * the "meta" table state.  This will be reopened on the
+		 * next table element.
+		 */
+		if (h->tblt) {
+			print_tblclose(h);
+			t = h->tags.head;
+		}
+
+		assert(NULL == h->tblt);
+		if (mdocs[n->tok].pre && ENDBODY_NOT == n->end)
+			child = (*mdocs[n->tok].pre)(m, n, h);
+		break;
+	}
+
+	if (HTML_KEEP & h->flags) {
+		if (n->prev && n->prev->line != n->line) {
+			h->flags &= ~HTML_KEEP;
+			h->flags |= HTML_PREKEEP;
+		} else if (NULL == n->prev) {
+			if (n->parent && n->parent->line != n->line) {
+				h->flags &= ~HTML_KEEP;
+				h->flags |= HTML_PREKEEP;
+			}
+		}
+	}
+
+	if (child && n->child)
+		print_mdoc_nodelist(m, n->child, h);
+
+	print_stagq(h, t);
+
+	switch (n->type) {
+	case (MDOC_ROOT):
+		mdoc_root_post(m, n, h);
+		break;
+	case (MDOC_EQN):
+		break;
+	default:
+		if (mdocs[n->tok].post && ENDBODY_NOT == n->end)
+			(*mdocs[n->tok].post)(m, n, h);
+		break;
+	}
+}
+
+/* ARGSUSED */
+static void
+mdoc_root_post(MDOC_ARGS)
+{
+	struct htmlpair	 tag[3];
+	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);
+
+	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);
+	print_text(h, m->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);
+	print_text(h, m->os);
+	print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_root_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag[3];
+	struct tag	*t, *tt;
+	char		 b[BUFSIZ], title[BUFSIZ];
+
+	strlcpy(b, m->vol, BUFSIZ);
+
+	if (m->arch) {
+		strlcat(b, " (", BUFSIZ);
+		strlcat(b, m->arch, BUFSIZ);
+		strlcat(b, ")", BUFSIZ);
+	}
+
+	snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->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);
+
+	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);
+	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);
+	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);
+	print_text(h, title);
+	print_tagq(h, t);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_sh_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MDOC_BLOCK == n->type) {
+		PAIR_CLASS_INIT(&tag, "section");
+		print_otag(h, TAG_DIV, 1, &tag);
+		return(1);
+	} else if (MDOC_BODY == n->type)
+		return(1);
+
+	bufinit(h);
+	bufcat(h, "x");
+
+	for (n = n->child; n && MDOC_TEXT == n->type; ) {
+		bufcat_id(h, n->string);
+		if (NULL != (n = n->next))
+			bufcat_id(h, " ");
+	}
+
+	if (NULL == n) {
+		PAIR_ID_INIT(&tag, h->buf);
+		print_otag(h, TAG_H1, 1, &tag);
+	} else
+		print_otag(h, TAG_H1, 0, NULL);
+
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+mdoc_ss_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MDOC_BLOCK == n->type) {
+		PAIR_CLASS_INIT(&tag, "subsection");
+		print_otag(h, TAG_DIV, 1, &tag);
+		return(1);
+	} else if (MDOC_BODY == n->type)
+		return(1);
+
+	bufinit(h);
+	bufcat(h, "x");
+
+	for (n = n->child; n && MDOC_TEXT == n->type; ) {
+		bufcat_id(h, n->string);
+		if (NULL != (n = n->next))
+			bufcat_id(h, " ");
+	}
+
+	if (NULL == n) {
+		PAIR_ID_INIT(&tag, h->buf);
+		print_otag(h, TAG_H2, 1, &tag);
+	} else
+		print_otag(h, TAG_H2, 0, NULL);
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_fl_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	PAIR_CLASS_INIT(&tag, "flag");
+	print_otag(h, TAG_B, 1, &tag);
+
+	/* `Cm' has no leading hyphen. */
+
+	if (MDOC_Cm == n->tok)
+		return(1);
+
+	print_text(h, "\\-");
+
+	if (n->child)
+		h->flags |= HTML_NOSPACE;
+	else if (n->next && n->next->line == n->line)
+		h->flags |= HTML_NOSPACE;
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_nd_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MDOC_BODY != n->type)
+		return(1);
+
+	/* XXX: this tag in theory can contain block elements. */
+
+	print_text(h, "\\(em");
+	PAIR_CLASS_INIT(&tag, "desc");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+static int
+mdoc_nm_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+	struct roffsu	 su;
+	int		 len;
+
+	switch (n->type) {
+	case (MDOC_ELEM):
+		synopsis_pre(h, n);
+		PAIR_CLASS_INIT(&tag, "name");
+		print_otag(h, TAG_B, 1, &tag);
+		if (NULL == n->child && m->name)
+			print_text(h, m->name);
+		return(1);
+	case (MDOC_HEAD):
+		print_otag(h, TAG_TD, 0, NULL);
+		if (NULL == n->child && m->name)
+			print_text(h, m->name);
+		return(1);
+	case (MDOC_BODY):
+		print_otag(h, TAG_TD, 0, NULL);
+		return(1);
+	default:
+		break;
+	}
+
+	synopsis_pre(h, n);
+	PAIR_CLASS_INIT(&tag, "synopsis");
+	print_otag(h, TAG_TABLE, 1, &tag);
+
+	for (len = 0, n = n->child; n; n = n->next)
+		if (MDOC_TEXT == n->type)
+			len += html_strlen(n->string);
+
+	if (0 == len && m->name)
+		len = html_strlen(m->name);
+
+	SCALE_HS_INIT(&su, (double)len);
+	bufinit(h);
+	bufcat_su(h, "width", &su);
+	PAIR_STYLE_INIT(&tag, h);
+	print_otag(h, TAG_COL, 1, &tag);
+	print_otag(h, TAG_COL, 0, NULL);
+	print_otag(h, TAG_TBODY, 0, NULL);
+	print_otag(h, TAG_TR, 0, NULL);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_xr_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag[2];
+
+	if (NULL == n->child)
+		return(0);
+
+	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);
+		PAIR_HREF_INIT(&tag[1], h->buf);
+		print_otag(h, TAG_A, 2, tag);
+	} else
+		print_otag(h, TAG_A, 1, tag);
+
+	n = n->child;
+	print_text(h, n->string);
+
+	if (NULL == (n = n->next))
+		return(0);
+
+	h->flags |= HTML_NOSPACE;
+	print_text(h, "(");
+	h->flags |= HTML_NOSPACE;
+	print_text(h, n->string);
+	h->flags |= HTML_NOSPACE;
+	print_text(h, ")");
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ns_pre(MDOC_ARGS)
+{
+
+	if ( ! (MDOC_LINE & n->flags))
+		h->flags |= HTML_NOSPACE;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ar_pre(MDOC_ARGS)
+{
+	struct htmlpair tag;
+
+	PAIR_CLASS_INIT(&tag, "arg");
+	print_otag(h, TAG_I, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_xx_pre(MDOC_ARGS)
+{
+	const char	*pp;
+	struct htmlpair	 tag;
+	int		 flags;
+
+	switch (n->tok) {
+	case (MDOC_Bsx):
+		pp = "BSD/OS";
+		break;
+	case (MDOC_Dx):
+		pp = "DragonFly";
+		break;
+	case (MDOC_Fx):
+		pp = "FreeBSD";
+		break;
+	case (MDOC_Nx):
+		pp = "NetBSD";
+		break;
+	case (MDOC_Ox):
+		pp = "OpenBSD";
+		break;
+	case (MDOC_Ux):
+		pp = "UNIX";
+		break;
+	default:
+		return(1);
+	}
+
+	PAIR_CLASS_INIT(&tag, "unix");
+	print_otag(h, TAG_SPAN, 1, &tag);
+
+	print_text(h, pp);
+	if (n->child) {
+		flags = h->flags;
+		h->flags |= HTML_KEEP;
+		print_text(h, n->child->string);
+		h->flags = flags;
+	}
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_bx_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	PAIR_CLASS_INIT(&tag, "unix");
+	print_otag(h, TAG_SPAN, 1, &tag);
+
+	if (NULL != (n = n->child)) {
+		print_text(h, n->string);
+		h->flags |= HTML_NOSPACE;
+		print_text(h, "BSD");
+	} else {
+		print_text(h, "BSD");
+		return(0);
+	}
+
+	if (NULL != (n = n->next)) {
+		h->flags |= HTML_NOSPACE;
+		print_text(h, "-");
+		h->flags |= HTML_NOSPACE;
+		print_text(h, n->string);
+	}
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+mdoc_it_pre(MDOC_ARGS)
+{
+	struct roffsu	 su;
+	enum mdoc_list	 type;
+	struct htmlpair	 tag[2];
+	const struct mdoc_node *bl;
+
+	bl = n->parent;
+	while (bl && MDOC_Bl != bl->tok)
+		bl = bl->parent;
+
+	assert(bl);
+
+	type = bl->norm->Bl.type;
+
+	assert(lists[type]);
+	PAIR_CLASS_INIT(&tag[0], lists[type]);
+
+	bufinit(h);
+
+	if (MDOC_HEAD == n->type) {
+		switch (type) {
+		case(LIST_bullet):
+			/* FALLTHROUGH */
+		case(LIST_dash):
+			/* FALLTHROUGH */
+		case(LIST_item):
+			/* FALLTHROUGH */
+		case(LIST_hyphen):
+			/* FALLTHROUGH */
+		case(LIST_enum):
+			return(0);
+		case(LIST_diag):
+			/* FALLTHROUGH */
+		case(LIST_hang):
+			/* FALLTHROUGH */
+		case(LIST_inset):
+			/* FALLTHROUGH */
+		case(LIST_ohang):
+			/* FALLTHROUGH */
+		case(LIST_tag):
+			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
+			bufcat_su(h, "margin-top", &su);
+			PAIR_STYLE_INIT(&tag[1], h);
+			print_otag(h, TAG_DT, 2, tag);
+			if (LIST_diag != type)
+				break;
+			PAIR_CLASS_INIT(&tag[0], "diag");
+			print_otag(h, TAG_B, 1, tag);
+			break;
+		case(LIST_column):
+			break;
+		default:
+			break;
+		}
+	} else if (MDOC_BODY == n->type) {
+		switch (type) {
+		case(LIST_bullet):
+			/* FALLTHROUGH */
+		case(LIST_hyphen):
+			/* FALLTHROUGH */
+		case(LIST_dash):
+			/* FALLTHROUGH */
+		case(LIST_enum):
+			/* FALLTHROUGH */
+		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):
+			/* FALLTHROUGH */
+		case(LIST_hang):
+			/* FALLTHROUGH */
+		case(LIST_inset):
+			/* FALLTHROUGH */
+		case(LIST_ohang):
+			/* FALLTHROUGH */
+		case(LIST_tag):
+			if (NULL == bl->norm->Bl.width) {
+				print_otag(h, TAG_DD, 1, tag);
+				break;
+			}
+			a2width(bl->norm->Bl.width, &su);
+			bufcat_su(h, "margin-left", &su);
+			PAIR_STYLE_INIT(&tag[1], h);
+			print_otag(h, TAG_DD, 2, tag);
+			break;
+		case(LIST_column):
+			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
+			bufcat_su(h, "margin-top", &su);
+			PAIR_STYLE_INIT(&tag[1], h);
+			print_otag(h, TAG_TD, 2, tag);
+			break;
+		default:
+			break;
+		}
+	} else {
+		switch (type) {
+		case (LIST_column):
+			print_otag(h, TAG_TR, 1, tag);
+			break;
+		default:
+			break;
+		}
+	}
+
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+mdoc_bl_pre(MDOC_ARGS)
+{
+	int		 i;
+	struct htmlpair	 tag[3];
+	struct roffsu	 su;
+	char		 buf[BUFSIZ];
+
+	bufinit(h);
+
+	if (MDOC_BODY == n->type) {
+		if (LIST_column == n->norm->Bl.type)
+			print_otag(h, TAG_TBODY, 0, NULL);
+		return(1);
+	}
+
+	if (MDOC_HEAD == n->type) {
+		if (LIST_column != n->norm->Bl.type)
+			return(0);
+
+		/*
+		 * For each column, print out the <COL> tag with our
+		 * suggested width.  The last column gets min-width, as
+		 * in terminal mode it auto-sizes to the width of the
+		 * screen and we want to preserve that behaviour.
+		 */
+
+		for (i = 0; i < (int)n->norm->Bl.ncols; i++) {
+			a2width(n->norm->Bl.cols[i], &su);
+			if (i < (int)n->norm->Bl.ncols - 1)
+				bufcat_su(h, "width", &su);
+			else
+				bufcat_su(h, "min-width", &su);
+			PAIR_STYLE_INIT(&tag[0], h);
+			print_otag(h, TAG_COL, 1, tag);
+		}
+
+		return(0);
+	}
+
+	SCALE_VS_INIT(&su, 0);
+	bufcat_su(h, "margin-top", &su);
+	bufcat_su(h, "margin-bottom", &su);
+	PAIR_STYLE_INIT(&tag[0], h);
+
+	assert(lists[n->norm->Bl.type]);
+	strlcpy(buf, "list ", BUFSIZ);
+	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);
+		bufcat_su(h, "margin-left", &su);
+	}
+
+	switch (n->norm->Bl.type) {
+	case(LIST_bullet):
+		/* FALLTHROUGH */
+	case(LIST_dash):
+		/* FALLTHROUGH */
+	case(LIST_hyphen):
+		/* FALLTHROUGH */
+	case(LIST_item):
+		print_otag(h, TAG_UL, 2, tag);
+		break;
+	case(LIST_enum):
+		print_otag(h, TAG_OL, 2, tag);
+		break;
+	case(LIST_diag):
+		/* FALLTHROUGH */
+	case(LIST_hang):
+		/* FALLTHROUGH */
+	case(LIST_inset):
+		/* FALLTHROUGH */
+	case(LIST_ohang):
+		/* FALLTHROUGH */
+	case(LIST_tag):
+		print_otag(h, TAG_DL, 2, tag);
+		break;
+	case(LIST_column):
+		print_otag(h, TAG_TABLE, 2, tag);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+mdoc_ex_pre(MDOC_ARGS)
+{
+	struct tag	*t;
+	struct htmlpair	 tag;
+	int		 nchild;
+
+	if (n->prev)
+		print_otag(h, TAG_BR, 0, NULL);
+
+	PAIR_CLASS_INIT(&tag, "utility");
+
+	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 > 2 && n->next) {
+			h->flags |= HTML_NOSPACE;
+			print_text(h, ",");
+		}
+
+		if (n->next && NULL == n->next->next)
+			print_text(h, "and");
+	}
+
+	if (nchild > 1)
+		print_text(h, "utilities exit");
+	else
+		print_text(h, "utility exits");
+
+       	print_text(h, "0 on success, and >0 if an error occurs.");
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_em_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "emph");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_d1_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag[2];
+	struct roffsu	 su;
+
+	if (MDOC_BLOCK != n->type)
+		return(1);
+
+	SCALE_VS_INIT(&su, 0);
+	bufinit(h);
+	bufcat_su(h, "margin-top", &su);
+	bufcat_su(h, "margin-bottom", &su);
+	PAIR_STYLE_INIT(&tag[0], h);
+	print_otag(h, TAG_BLOCKQUOTE, 1, tag);
+
+	/* BLOCKQUOTE needs a block body. */
+
+	PAIR_CLASS_INIT(&tag[0], "display");
+	print_otag(h, TAG_DIV, 1, tag);
+
+	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)
+{
+	struct htmlpair	 tag[2];
+
+	bufinit(h);
+	bufcat(h, "#x");
+
+	for (n = n->child; n; ) {
+		bufcat_id(h, n->string);
+		if (NULL != (n = n->next))
+			bufcat_id(h, " ");
+	}
+
+	PAIR_CLASS_INIT(&tag[0], "link-sec");
+	PAIR_HREF_INIT(&tag[1], h->buf);
+
+	print_otag(h, TAG_I, 1, tag);
+	print_otag(h, TAG_A, 2, tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_bd_pre(MDOC_ARGS)
+{
+	struct htmlpair	 	 tag[2];
+	int		 	 comp, sv;
+	const struct mdoc_node	*nn;
+	struct roffsu		 su;
+
+	if (MDOC_HEAD == n->type)
+		return(0);
+
+	if (MDOC_BLOCK == n->type) {
+		comp = n->norm->Bd.comp;
+		for (nn = n; nn && ! comp; nn = nn->parent) {
+			if (MDOC_BLOCK != nn->type)
+				continue;
+			if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
+				comp = 1;
+			if (nn->prev)
+				break;
+		}
+		if ( ! comp)
+			print_otag(h, TAG_P, 0, NULL);
+		return(1);
+	}
+
+	SCALE_HS_INIT(&su, 0);
+	if (n->norm->Bd.offs)
+		a2offs(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) {
+		PAIR_CLASS_INIT(&tag[1], "display");
+		print_otag(h, TAG_DIV, 2, tag);
+		return(1);
+	}
+
+	PAIR_CLASS_INIT(&tag[1], "lit display");
+	print_otag(h, TAG_PRE, 2, tag);
+
+	/* This can be recursive: save & set our literal state. */
+
+	sv = h->flags & HTML_LITERAL;
+	h->flags |= HTML_LITERAL;
+
+	for (nn = n->child; nn; nn = nn->next) {
+		print_mdoc_node(m, nn, h);
+		/*
+		 * If the printed node flushes its own line, then we
+		 * needn't do it here as well.  This is hacky, but the
+		 * notion of selective eoln whitespace is pretty dumb
+		 * anyway, so don't sweat it.
+		 */
+		switch (nn->tok) {
+		case (MDOC_Sm):
+			/* FALLTHROUGH */
+		case (MDOC_br):
+			/* FALLTHROUGH */
+		case (MDOC_sp):
+			/* FALLTHROUGH */
+		case (MDOC_Bl):
+			/* FALLTHROUGH */
+		case (MDOC_D1):
+			/* FALLTHROUGH */
+		case (MDOC_Dl):
+			/* FALLTHROUGH */
+		case (MDOC_Lp):
+			/* FALLTHROUGH */
+		case (MDOC_Pp):
+			continue;
+		default:
+			break;
+		}
+		if (nn->next && nn->next->line == nn->line)
+			continue;
+		else if (nn->next)
+			print_text(h, "\n");
+
+		h->flags |= HTML_NOSPACE;
+	}
+
+	if (0 == sv)
+		h->flags &= ~HTML_LITERAL;
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_pa_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "file");
+	print_otag(h, TAG_I, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ad_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "addr");
+	print_otag(h, TAG_I, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_an_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	/* TODO: -split and -nosplit (see termp_an_pre()). */
+
+	PAIR_CLASS_INIT(&tag, "author");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_cd_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	synopsis_pre(h, n);
+	PAIR_CLASS_INIT(&tag, "config");
+	print_otag(h, TAG_B, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_dv_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "define");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ev_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "env");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_er_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "errno");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_fa_pre(MDOC_ARGS)
+{
+	const struct mdoc_node	*nn;
+	struct htmlpair		 tag;
+	struct tag		*t;
+
+	PAIR_CLASS_INIT(&tag, "farg");
+	if (n->parent->tok != MDOC_Fo) {
+		print_otag(h, TAG_I, 1, &tag);
+		return(1);
+	}
+
+	for (nn = n->child; nn; nn = nn->next) {
+		t = print_otag(h, TAG_I, 1, &tag);
+		print_text(h, nn->string);
+		print_tagq(h, t);
+		if (nn->next) {
+			h->flags |= HTML_NOSPACE;
+			print_text(h, ",");
+		}
+	}
+
+	if (n->child && n->next && n->next->tok == MDOC_Fa) {
+		h->flags |= HTML_NOSPACE;
+		print_text(h, ",");
+	}
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_fd_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag[2];
+	char		 buf[BUFSIZ];
+	size_t		 sz;
+	int		 i;
+	struct tag	*t;
+
+	synopsis_pre(h, n);
+
+	if (NULL == (n = n->child))
+		return(0);
+
+	assert(MDOC_TEXT == n->type);
+
+	if (strcmp(n->string, "#include")) {
+		PAIR_CLASS_INIT(&tag[0], "macro");
+		print_otag(h, TAG_B, 1, tag);
+		return(1);
+	}
+
+	PAIR_CLASS_INIT(&tag[0], "includes");
+	print_otag(h, TAG_B, 1, tag);
+	print_text(h, n->string);
+
+	if (NULL != (n = n->next)) {
+		assert(MDOC_TEXT == n->type);
+		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);
+		print_tagq(h, t);
+
+		n = n->next;
+	}
+
+	for ( ; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+		print_text(h, n->string);
+	}
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_vt_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MDOC_BLOCK == n->type) {
+		synopsis_pre(h, n);
+		return(1);
+	} else if (MDOC_ELEM == n->type) {
+		synopsis_pre(h, n);
+	} else if (MDOC_HEAD == n->type)
+		return(0);
+
+	PAIR_CLASS_INIT(&tag, "type");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ft_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	synopsis_pre(h, n);
+	PAIR_CLASS_INIT(&tag, "ftype");
+	print_otag(h, TAG_I, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_fn_pre(MDOC_ARGS)
+{
+	struct tag	*t;
+	struct htmlpair	 tag[2];
+	char		 nbuf[BUFSIZ];
+	const char	*sp, *ep;
+	int		 sz, i, pretty;
+
+	pretty = MDOC_SYNPRETTY & n->flags;
+	synopsis_pre(h, n);
+
+	/* Split apart into type and name. */
+	assert(n->child->string);
+	sp = n->child->string;
+
+	ep = strchr(sp, ' ');
+	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);
+			nbuf[sz] = '\0';
+			print_text(h, nbuf);
+			sp = ++ep;
+			ep = strchr(sp, ' ');
+		}
+		print_tagq(h, t);
+	}
+
+	PAIR_CLASS_INIT(&tag[0], "fname");
+
+	/*
+	 * FIXME: only refer to IDs that we know exist.
+	 */
+
+#if 0
+	if (MDOC_SYNPRETTY & n->flags) {
+		nbuf[0] = '\0';
+		html_idcat(nbuf, sp, BUFSIZ);
+		PAIR_ID_INIT(&tag[1], nbuf);
+	} else {
+		strlcpy(nbuf, "#", BUFSIZ);
+		html_idcat(nbuf, sp, BUFSIZ);
+		PAIR_HREF_INIT(&tag[1], nbuf);
+	}
+#endif
+
+	t = print_otag(h, TAG_B, 1, tag);
+
+	if (sp) {
+		strlcpy(nbuf, sp, BUFSIZ);
+		print_text(h, nbuf);
+	}
+
+	print_tagq(h, t);
+
+	h->flags |= HTML_NOSPACE;
+	print_text(h, "(");
+	h->flags |= HTML_NOSPACE;
+
+	PAIR_CLASS_INIT(&tag[0], "farg");
+	bufinit(h);
+	bufcat_style(h, "white-space", "nowrap");
+	PAIR_STYLE_INIT(&tag[1], h);
+
+	for (n = n->child->next; n; n = n->next) {
+		i = 1;
+		if (MDOC_SYNPRETTY & n->flags)
+			i = 2;
+		t = print_otag(h, TAG_I, i, tag);
+		print_text(h, n->string);
+		print_tagq(h, t);
+		if (n->next) {
+			h->flags |= HTML_NOSPACE;
+			print_text(h, ",");
+		}
+	}
+
+	h->flags |= HTML_NOSPACE;
+	print_text(h, ")");
+
+	if (pretty) {
+		h->flags |= HTML_NOSPACE;
+		print_text(h, ";");
+	}
+
+	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;
+		h->flags &= ~HTML_NONOSPACE;
+	} else
+		h->flags |= HTML_NONOSPACE;
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+mdoc_pp_pre(MDOC_ARGS)
+{
+
+	print_otag(h, TAG_P, 0, NULL);
+	return(0);
+
+}
+
+/* ARGSUSED */
+static int
+mdoc_sp_pre(MDOC_ARGS)
+{
+	struct roffsu	 su;
+	struct htmlpair	 tag;
+
+	SCALE_VS_INIT(&su, 1);
+
+	if (MDOC_sp == n->tok) {
+		if (NULL != (n = n->child))
+			if ( ! a2roffsu(n->string, &su, SCALE_VS))
+				SCALE_VS_INIT(&su, atoi(n->string));
+	} else
+		su.scale = 0;
+
+	bufinit(h);
+	bufcat_su(h, "height", &su);
+	PAIR_STYLE_INIT(&tag, h);
+	print_otag(h, TAG_DIV, 1, &tag);
+
+	/* So the div isn't empty: */
+	print_text(h, "\\~");
+
+	return(0);
+
+}
+
+/* ARGSUSED */
+static int
+mdoc_lk_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag[2];
+
+	if (NULL == (n = n->child))
+		return(0);
+
+	assert(MDOC_TEXT == n->type);
+
+	PAIR_CLASS_INIT(&tag[0], "link-ext");
+	PAIR_HREF_INIT(&tag[1], n->string);
+
+	print_otag(h, TAG_A, 2, tag);
+
+	if (NULL == n->next)
+		print_text(h, n->string);
+
+	for (n = n->next; n; n = n->next)
+		print_text(h, n->string);
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_mt_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag[2];
+	struct tag	*t;
+
+	PAIR_CLASS_INIT(&tag[0], "link-mail");
+
+	for (n = n->child; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+
+		bufinit(h);
+		bufcat(h, "mailto:");
+		bufcat(h, n->string);
+
+		PAIR_HREF_INIT(&tag[1], h->buf);
+		t = print_otag(h, TAG_A, 2, tag);
+		print_text(h, n->string);
+		print_tagq(h, t);
+	}
+	
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_fo_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+	struct tag	*t;
+
+	if (MDOC_BODY == n->type) {
+		h->flags |= HTML_NOSPACE;
+		print_text(h, "(");
+		h->flags |= HTML_NOSPACE;
+		return(1);
+	} else if (MDOC_BLOCK == n->type) {
+		synopsis_pre(h, n);
+		return(1);
+	}
+
+	/* XXX: we drop non-initial arguments as per groff. */
+
+	assert(n->child);
+	assert(n->child->string);
+
+	PAIR_CLASS_INIT(&tag, "fname");
+	t = print_otag(h, TAG_B, 1, &tag);
+	print_text(h, n->child->string);
+	print_tagq(h, t);
+	return(0);
+}
+
+
+/* ARGSUSED */
+static void
+mdoc_fo_post(MDOC_ARGS)
+{
+
+	if (MDOC_BODY != n->type)
+		return;
+	h->flags |= HTML_NOSPACE;
+	print_text(h, ")");
+	h->flags |= HTML_NOSPACE;
+	print_text(h, ";");
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_in_pre(MDOC_ARGS)
+{
+	struct tag	*t;
+	struct htmlpair	 tag[2];
+	int		 i;
+
+	synopsis_pre(h, n);
+
+	PAIR_CLASS_INIT(&tag[0], "includes");
+	print_otag(h, TAG_B, 1, tag);
+
+	/*
+	 * The first argument of the `In' gets special treatment as
+	 * being a linked value.  Subsequent values are printed
+	 * afterward.  groff does similarly.  This also handles the case
+	 * of no children.
+	 */
+
+	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags)
+		print_text(h, "#include");
+
+	print_text(h, "<");
+	h->flags |= HTML_NOSPACE;
+
+	if (NULL != (n = n->child)) {
+		assert(MDOC_TEXT == n->type);
+
+		PAIR_CLASS_INIT(&tag[0], "link-includes");
+
+		i = 1;
+		if (h->base_includes) {
+			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);
+		print_tagq(h, t);
+
+		n = n->next;
+	}
+
+	h->flags |= HTML_NOSPACE;
+	print_text(h, ">");
+
+	for ( ; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+		print_text(h, n->string);
+	}
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ic_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "cmd");
+	print_otag(h, TAG_B, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_rv_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+	struct tag	*t;
+	int		 nchild;
+
+	if (n->prev)
+		print_otag(h, TAG_BR, 0, NULL);
+
+	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);
+
+		h->flags |= HTML_NOSPACE;
+		print_text(h, "()");
+
+		if (nchild > 2 && n->next) {
+			h->flags |= HTML_NOSPACE;
+			print_text(h, ",");
+		}
+
+		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");
+
+       	print_text(h, "the value 0 if successful; 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.");
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_va_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "var");
+	print_otag(h, TAG_B, 1, &tag);
+	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)
+{
+	struct htmlpair	 tag[2];
+	struct roffsu	 su;
+
+	if (MDOC_HEAD == n->type)
+		return(0);
+	else if (MDOC_BODY != n->type)
+		return(1);
+
+	if (FONT_Em == n->norm->Bf.font) 
+		PAIR_CLASS_INIT(&tag[0], "emph");
+	else if (FONT_Sy == n->norm->Bf.font) 
+		PAIR_CLASS_INIT(&tag[0], "symb");
+	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. 
+	 */
+	bufinit(h);
+	bufcat_style(h, "display", "inline");
+	SCALE_HS_INIT(&su, 1);
+	/* Needs a left-margin for spacing. */
+	bufcat_su(h, "margin-left", &su);
+	PAIR_STYLE_INIT(&tag[1], h);
+	print_otag(h, TAG_DIV, 2, tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ms_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "symb");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_igndelim_pre(MDOC_ARGS)
+{
+
+	h->flags |= HTML_IGNDELIM;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+mdoc_pf_post(MDOC_ARGS)
+{
+
+	h->flags |= HTML_NOSPACE;
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_rs_pre(MDOC_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MDOC_BLOCK != n->type)
+		return(1);
+
+	if (n->prev && SEC_SEE_ALSO == n->sec)
+		print_otag(h, TAG_P, 0, NULL);
+
+	PAIR_CLASS_INIT(&tag, "ref");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+
+/* ARGSUSED */
+static int
+mdoc_li_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "lit");
+	print_otag(h, TAG_CODE, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_sy_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	PAIR_CLASS_INIT(&tag, "symb");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_bt_pre(MDOC_ARGS)
+{
+
+	print_text(h, "is currently in beta test.");
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_ud_pre(MDOC_ARGS)
+{
+
+	print_text(h, "currently under development.");
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_lb_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev)
+		print_otag(h, TAG_BR, 0, NULL);
+
+	PAIR_CLASS_INIT(&tag, "lib");
+	print_otag(h, TAG_SPAN, 1, &tag);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc__x_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag[2];
+	enum htmltag	t;
+
+	t = TAG_SPAN;
+
+	switch (n->tok) {
+	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):
+		PAIR_CLASS_INIT(&tag[0], "ref-book");
+		t = TAG_I;
+		break;
+	case(MDOC__C):
+		PAIR_CLASS_INIT(&tag[0], "ref-city");
+		break;
+	case(MDOC__D):
+		PAIR_CLASS_INIT(&tag[0], "ref-date");
+		break;
+	case(MDOC__I):
+		PAIR_CLASS_INIT(&tag[0], "ref-issue");
+		t = TAG_I;
+		break;
+	case(MDOC__J):
+		PAIR_CLASS_INIT(&tag[0], "ref-jrnl");
+		t = TAG_I;
+		break;
+	case(MDOC__N):
+		PAIR_CLASS_INIT(&tag[0], "ref-num");
+		break;
+	case(MDOC__O):
+		PAIR_CLASS_INIT(&tag[0], "ref-opt");
+		break;
+	case(MDOC__P):
+		PAIR_CLASS_INIT(&tag[0], "ref-page");
+		break;
+	case(MDOC__Q):
+		PAIR_CLASS_INIT(&tag[0], "ref-corp");
+		break;
+	case(MDOC__R):
+		PAIR_CLASS_INIT(&tag[0], "ref-rep");
+		break;
+	case(MDOC__T):
+		PAIR_CLASS_INIT(&tag[0], "ref-title");
+		break;
+	case(MDOC__U):
+		PAIR_CLASS_INIT(&tag[0], "link-ref");
+		break;
+	case(MDOC__V):
+		PAIR_CLASS_INIT(&tag[0], "ref-vol");
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	if (MDOC__U != n->tok) {
+		print_otag(h, t, 1, tag);
+		return(1);
+	}
+
+	PAIR_HREF_INIT(&tag[1], n->child->string);
+	print_otag(h, TAG_A, 2, tag);
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+mdoc__x_post(MDOC_ARGS)
+{
+
+	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
+		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
+			if (NULL == n->prev || MDOC__A != n->prev->tok)
+				return;
+
+	/* TODO: %U */
+
+	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
+		return;
+
+	h->flags |= HTML_NOSPACE;
+	print_text(h, n->next ? "," : ".");
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_bk_pre(MDOC_ARGS)
+{
+
+	switch (n->type) {
+	case (MDOC_BLOCK):
+		break;
+	case (MDOC_HEAD):
+		return(0);
+	case (MDOC_BODY):
+		if (n->parent->args || 0 == n->prev->nchild)
+			h->flags |= HTML_PREKEEP;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+mdoc_bk_post(MDOC_ARGS)
+{
+
+	if (MDOC_BODY == n->type)
+		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
+}
+
+
+/* ARGSUSED */
+static int
+mdoc_quote_pre(MDOC_ARGS)
+{
+	struct htmlpair	tag;
+
+	if (MDOC_BODY != n->type)
+		return(1);
+
+	switch (n->tok) {
+	case (MDOC_Ao):
+		/* FALLTHROUGH */
+	case (MDOC_Aq):
+		print_text(h, "\\(la");
+		break;
+	case (MDOC_Bro):
+		/* FALLTHROUGH */
+	case (MDOC_Brq):
+		print_text(h, "\\(lC");
+		break;
+	case (MDOC_Bo):
+		/* FALLTHROUGH */
+	case (MDOC_Bq):
+		print_text(h, "\\(lB");
+		break;
+	case (MDOC_Oo):
+		/* FALLTHROUGH */
+	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):
+		break;
+	case (MDOC_Do):
+		/* FALLTHROUGH */
+	case (MDOC_Dq):
+		/* FALLTHROUGH */
+	case (MDOC_Qo):
+		/* FALLTHROUGH */
+	case (MDOC_Qq):
+		print_text(h, "\\(lq");
+		break;
+	case (MDOC_Po):
+		/* FALLTHROUGH */
+	case (MDOC_Pq):
+		print_text(h, "(");
+		break;
+	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):
+		/* FALLTHROUGH */
+	case (MDOC_Sq):
+		print_text(h, "\\(oq");
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	h->flags |= HTML_NOSPACE;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+mdoc_quote_post(MDOC_ARGS)
+{
+
+	if (MDOC_BODY != n->type)
+		return;
+
+	h->flags |= HTML_NOSPACE;
+
+	switch (n->tok) {
+	case (MDOC_Ao):
+		/* FALLTHROUGH */
+	case (MDOC_Aq):
+		print_text(h, "\\(ra");
+		break;
+	case (MDOC_Bro):
+		/* FALLTHROUGH */
+	case (MDOC_Brq):
+		print_text(h, "\\(rC");
+		break;
+	case (MDOC_Oo):
+		/* FALLTHROUGH */
+	case (MDOC_Op):
+		/* FALLTHROUGH */
+	case (MDOC_Bo):
+		/* FALLTHROUGH */
+	case (MDOC_Bq):
+		print_text(h, "\\(rB");
+		break;
+	case (MDOC_Eo):
+		break;
+	case (MDOC_Qo):
+		/* FALLTHROUGH */
+	case (MDOC_Qq):
+		/* FALLTHROUGH */
+	case (MDOC_Do):
+		/* FALLTHROUGH */
+	case (MDOC_Dq):
+		print_text(h, "\\(rq");
+		break;
+	case (MDOC_Po):
+		/* FALLTHROUGH */
+	case (MDOC_Pq):
+		print_text(h, ")");
+		break;
+	case (MDOC_Ql):
+		/* FALLTHROUGH */
+	case (MDOC_So):
+		/* FALLTHROUGH */
+	case (MDOC_Sq):
+		print_text(h, "\\(aq");
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/mdoc_macro.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,1787 @@
+/*	$Id: mdoc_macro.c,v 1.115 2012/01/05 00:43:51 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#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 *, enum mdelim);
+static	int	  	append_delims(struct mdoc *, 
+				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);
+
+const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ap */
+	{ in_line_eoln, MDOC_PROLOGUE }, /* Dd */
+	{ in_line_eoln, MDOC_PROLOGUE }, /* Dt */
+	{ in_line_eoln, MDOC_PROLOGUE }, /* Os */
+	{ blk_full, MDOC_PARSED }, /* Sh */
+	{ blk_full, MDOC_PARSED }, /* Ss */ 
+	{ in_line_eoln, 0 }, /* Pp */ 
+	{ blk_part_imp, MDOC_PARSED }, /* D1 */
+	{ blk_part_imp, MDOC_PARSED }, /* Dl */
+	{ blk_full, MDOC_EXPLICIT }, /* Bd */
+	{ blk_exp_close, MDOC_EXPLICIT }, /* Ed */
+	{ blk_full, MDOC_EXPLICIT }, /* Bl */
+	{ blk_exp_close, MDOC_EXPLICIT }, /* El */
+	{ blk_full, MDOC_PARSED }, /* It */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */ 
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* An */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cd */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */ 
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Er */ 
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ev */ 
+	{ in_line_eoln, 0 }, /* Ex */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fa */ 
+	{ in_line_eoln, 0 }, /* Fd */ 
+	{ 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_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Li */
+	{ blk_full, 0 }, /* 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 }, /* Pa */
+	{ in_line_eoln, 0 }, /* Rv */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */ 
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Va */
+	{ ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */ 
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */
+	{ in_line_eoln, 0 }, /* %A */
+	{ in_line_eoln, 0 }, /* %B */
+	{ in_line_eoln, 0 }, /* %D */
+	{ in_line_eoln, 0 }, /* %I */
+	{ in_line_eoln, 0 }, /* %J */
+	{ in_line_eoln, 0 }, /* %N */
+	{ in_line_eoln, 0 }, /* %O */
+	{ in_line_eoln, 0 }, /* %P */
+	{ in_line_eoln, 0 }, /* %R */
+	{ in_line_eoln, 0 }, /* %T */
+	{ in_line_eoln, 0 }, /* %V */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ac */
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ao */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Aq */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* At */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Bc */
+	{ blk_full, MDOC_EXPLICIT }, /* Bf */ 
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bo */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Bq */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bsx */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bx */
+	{ in_line_eoln, 0 }, /* Db */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Dc */
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Do */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Dq */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ec */
+	{ blk_exp_close, MDOC_EXPLICIT }, /* Ef */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Em */ 
+	{ 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 }, /* No */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Ns */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ox */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Pc */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Pf */
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Po */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Pq */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Qc */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Ql */
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Qo */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Qq */
+	{ blk_exp_close, MDOC_EXPLICIT }, /* Re */
+	{ blk_full, MDOC_EXPLICIT }, /* Rs */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Sc */
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* So */
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Sq */
+	{ in_line_eoln, 0 }, /* Sm */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sx */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sy */
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ux */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Xc */
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Xo */
+	{ blk_full, MDOC_EXPLICIT | MDOC_CALLABLE }, /* Fo */ 
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Fc */ 
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Oo */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Oc */
+	{ blk_full, MDOC_EXPLICIT }, /* Bk */
+	{ blk_exp_close, MDOC_EXPLICIT }, /* Ek */
+	{ in_line_eoln, 0 }, /* Bt */
+	{ in_line_eoln, 0 }, /* Hf */
+	{ obsolete, 0 }, /* Fr */
+	{ in_line_eoln, 0 }, /* Ud */
+	{ in_line, 0 }, /* Lb */
+	{ in_line_eoln, 0 }, /* Lp */ 
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Lk */ 
+	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Mt */ 
+	{ blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Brq */
+	{ blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bro */
+	{ blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Brc */
+	{ in_line_eoln, 0 }, /* %C */
+	{ obsolete, 0 }, /* Es */
+	{ obsolete, 0 }, /* En */
+	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */
+	{ in_line_eoln, 0 }, /* %Q */
+	{ in_line_eoln, 0 }, /* br */
+	{ in_line_eoln, 0 }, /* sp */
+	{ in_line_eoln, 0 }, /* %U */
+	{ phrase_ta, MDOC_CALLABLE | MDOC_PARSED }, /* Ta */
+};
+
+const	struct mdoc_macro * const mdoc_macros = __mdoc_macros;
+
+
+/*
+ * This is called at the end of parsing.  It must traverse up the tree,
+ * closing out open [implicit] scopes.  Obviously, open explicit scopes
+ * are errors.
+ */
+int
+mdoc_macroend(struct mdoc *m)
+{
+	struct mdoc_node *n;
+
+	/* Scan for open explicit scopes. */
+
+	n = MDOC_VALID & m->last->flags ?  m->last->parent : m->last;
+
+	for ( ; n; n = n->parent)
+		if (MDOC_BLOCK == n->type &&
+		    MDOC_EXPLICIT & mdoc_macros[n->tok].flags)
+			mdoc_nmsg(m, n, MANDOCERR_SCOPEEXIT);
+
+	/* Rewind to the first. */
+
+	return(rew_last(m, m->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));
+}
+
+
+/*
+ * Lookup a macro following the initial line macro.
+ */
+static enum mdoct
+lookup_raw(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);
+	return(MDOC_MAX);
+}
+
+
+static int
+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
+		 * m->last node in the post-validation phase and reset
+		 * it to m->last->parent, causing a step in the closing
+		 * out to be lost.
+		 */
+		np = mdoc->last->parent;
+		if ( ! mdoc_valid_post(mdoc))
+			return(0);
+		n = mdoc->last;
+		mdoc->last = np;
+		assert(mdoc->last);
+		mdoc->last->last = n;
+	}
+
+	return(mdoc_valid_post(mdoc));
+}
+
+
+/*
+ * For a block closing macro, return the corresponding opening one.
+ * Otherwise, return the macro itself.
+ */
+static enum mdoct
+rew_alt(enum mdoct tok)
+{
+	switch (tok) {
+	case (MDOC_Ac):
+		return(MDOC_Ao);
+	case (MDOC_Bc):
+		return(MDOC_Bo);
+	case (MDOC_Brc):
+		return(MDOC_Bro);
+	case (MDOC_Dc):
+		return(MDOC_Do);
+	case (MDOC_Ec):
+		return(MDOC_Eo);
+	case (MDOC_Ed):
+		return(MDOC_Bd);
+	case (MDOC_Ef):
+		return(MDOC_Bf);
+	case (MDOC_Ek):
+		return(MDOC_Bk);
+	case (MDOC_El):
+		return(MDOC_Bl);
+	case (MDOC_Fc):
+		return(MDOC_Fo);
+	case (MDOC_Oc):
+		return(MDOC_Oo);
+	case (MDOC_Pc):
+		return(MDOC_Po);
+	case (MDOC_Qc):
+		return(MDOC_Qo);
+	case (MDOC_Re):
+		return(MDOC_Rs);
+	case (MDOC_Sc):
+		return(MDOC_So);
+	case (MDOC_Xc):
+		return(MDOC_Xo);
+	default:
+		return(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
+rew_elem(struct mdoc *mdoc, enum mdoct tok)
+{
+	struct mdoc_node *n;
+
+	n = mdoc->last;
+	if (MDOC_ELEM != n->type)
+		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 *m, 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, m->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 *m, 
+		enum mdoct tok, int line, int ppos)
+{
+	struct mdoc_node *n;
+
+	n = m->last;
+	while (n) {
+		switch (rew_dohalt(tok, t, n)) {
+		case (REWIND_NONE):
+			return(1);
+		case (REWIND_THIS):
+			break;
+		case (REWIND_FORCE):
+			mandoc_vmsg(MANDOCERR_SCOPEBROKEN, m->parse, 
+					line, ppos, "%s breaks %s", 
+					mdoc_macronames[tok],
+					mdoc_macronames[n->tok]);
+			/* FALLTHROUGH */
+		case (REWIND_MORE):
+			n = n->parent;
+			continue;
+		case (REWIND_LATER):
+			if (make_pending(n, tok, m, line, ppos) ||
+			    MDOC_BLOCK != t)
+				return(1);
+			/* FALLTHROUGH */
+		case (REWIND_ERROR):
+			mdoc_pmsg(m, line, ppos, MANDOCERR_NOSCOPE);
+			return(1);
+		}
+		break;
+	}
+
+	assert(n);
+	if ( ! rew_last(m, 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(m, n))
+			return(0);
+		if (MDOC_HEAD == n->type &&
+		    ! mdoc_body_alloc(m, n->line, n->pos, n->tok))
+			return(0);
+	}
+
+	return(1);
+}
+
+/*
+ * Allocate a word and check whether it's punctuation or not.
+ * Punctuation consists of those tokens found in mdoc_isdelim().
+ */
+static int
+dword(struct mdoc *m, int line, 
+		int col, const char *p, enum mdelim d)
+{
+	
+	if (DELIM_MAX == d)
+		d = mdoc_isdelim(p);
+
+	if ( ! mdoc_word_alloc(m, line, col, p))
+		return(0);
+
+	if (DELIM_OPEN == d)
+		m->last->flags |= MDOC_DELIMO;
+
+	/*
+	 * 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'!
+	 */
+
+	else if (DELIM_CLOSE == d && m->last->prev &&
+			m->last->prev->tok != MDOC_No)
+		m->last->flags |= MDOC_DELIMC;
+
+	return(1);
+}
+
+static int
+append_delims(struct mdoc *m, int line, int *pos, char *buf)
+{
+	int		 la;
+	enum margserr	 ac;
+	char		*p;
+
+	if ('\0' == buf[*pos])
+		return(1);
+
+	for (;;) {
+		la = *pos;
+		ac = mdoc_zargs(m, line, pos, buf, &p);
+
+		if (ARGS_ERROR == ac)
+			return(0);
+		else if (ARGS_EOLN == ac)
+			break;
+
+		dword(m, line, la, p, DELIM_MAX);
+
+		/*
+		 * If we encounter end-of-sentence symbols, then trigger
+		 * the double-space.
+		 *
+		 * XXX: it's easy to allow this to propagate outward to
+		 * the last symbol, such that `. )' will cause the
+		 * correct double-spacing.  However, (1) groff isn't
+		 * smart enough to do this and (2) it would require
+		 * knowing which symbols break this behaviour, for
+		 * example, `.  ;' shouldn't propagate the double-space.
+		 */
+		if (mandoc_eos(p, strlen(p), 0))
+			m->last->flags |= MDOC_EOS;
+	}
+
+	return(1);
+}
+
+
+/*
+ * Close out block partial/full explicit.  
+ */
+static int
+blk_exp_close(MACRO_PROT_ARGS)
+{
+	struct mdoc_node *body;		/* Our own body. */
+	struct mdoc_node *later;	/* A sub-block starting later. */
+	struct mdoc_node *n;		/* For searching backwards. */
+
+	int	 	 j, lastarg, maxargs, flushed, nl;
+	enum margserr	 ac;
+	enum mdoct	 atok, ntok;
+	char		*p;
+
+	nl = MDOC_NEWLINE & m->flags;
+
+	switch (tok) {
+	case (MDOC_Ec):
+		maxargs = 1;
+		break;
+	default:
+		maxargs = 0;
+		break;
+	}
+
+	/*
+	 * Search backwards for beginnings of blocks,
+	 * both of our own and of pending sub-blocks.
+	 */
+	atok = rew_alt(tok);
+	body = later = NULL;
+	for (n = m->last; n; n = n->parent) {
+		if (MDOC_VALID & n->flags)
+			continue;
+
+		/* Remember the start of our own body. */
+		if (MDOC_BODY == n->type && atok == n->tok) {
+			if (ENDBODY_NOT == n->end)
+				body = n;
+			continue;
+		}
+
+		if (MDOC_BLOCK != n->type || MDOC_Nm == n->tok)
+			continue;
+		if (atok == n->tok) {
+			assert(body);
+
+			/*
+			 * Found the start of our own block.
+			 * 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, m, line, ppos);
+
+			/*
+			 * Mark the place where the formatting - but not
+			 * the scope - of the current block ends.
+			 */
+			if ( ! mdoc_endbody_alloc(m, line, ppos,
+			    atok, body, ENDBODY_SPACE))
+				return(0);
+			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)
+			continue;
+		if (MDOC_CALLABLE & mdoc_macros[n->tok].flags)
+			later = n;
+	}
+
+	if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) {
+		/* FIXME: do this in validate */
+		if (buf[*pos]) 
+			mdoc_pmsg(m, line, ppos, MANDOCERR_ARGSLOST);
+
+		if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
+			return(0);
+		return(rew_sub(MDOC_BLOCK, m, tok, line, ppos));
+	}
+
+	if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
+		return(0);
+
+	if (NULL == later && maxargs > 0) 
+		if ( ! mdoc_tail_alloc(m, line, ppos, rew_alt(tok)))
+			return(0);
+
+	for (flushed = j = 0; ; j++) {
+		lastarg = *pos;
+
+		if (j == maxargs && ! flushed) {
+			if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos))
+				return(0);
+			flushed = 1;
+		}
+
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
+
+		if (ARGS_ERROR == ac)
+			return(0);
+		if (ARGS_PUNCT == ac)
+			break;
+		if (ARGS_EOLN == ac)
+			break;
+
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! dword(m, line, lastarg, p, DELIM_MAX))
+				return(0);
+			continue;
+		}
+
+		if ( ! flushed) {
+			if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos))
+				return(0);
+			flushed = 1;
+		}
+		if ( ! mdoc_macro(m, ntok, line, lastarg, pos, buf))
+			return(0);
+		break;
+	}
+
+	if ( ! flushed && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos))
+		return(0);
+
+	if ( ! nl)
+		return(1);
+	return(append_delims(m, line, pos, buf));
+}
+
+
+static int
+in_line(MACRO_PROT_ARGS)
+{
+	int		 la, scope, cnt, nc, nl;
+	enum margverr	 av;
+	enum mdoct	 ntok;
+	enum margserr	 ac;
+	enum mdelim	 d;
+	struct mdoc_arg	*arg;
+	char		*p;
+
+	nl = MDOC_NEWLINE & m->flags;
+
+	/*
+	 * Whether we allow ignored elements (those without content,
+	 * usually because of reserved words) to squeak by.
+	 */
+
+	switch (tok) {
+	case (MDOC_An):
+		/* FALLTHROUGH */
+	case (MDOC_Ar):
+		/* FALLTHROUGH */
+	case (MDOC_Fl):
+		/* FALLTHROUGH */
+	case (MDOC_Mt):
+		/* FALLTHROUGH */
+	case (MDOC_Nm):
+		/* FALLTHROUGH */
+	case (MDOC_Pa):
+		nc = 1;
+		break;
+	default:
+		nc = 0;
+		break;
+	}
+
+	for (arg = NULL;; ) {
+		la = *pos;
+		av = mdoc_argv(m, 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);
+	}
+
+	for (cnt = scope = 0;; ) {
+		la = *pos;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
+
+		if (ARGS_ERROR == ac)
+			return(0);
+		if (ARGS_EOLN == ac)
+			break;
+		if (ARGS_PUNCT == ac)
+			break;
+
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, 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(m, tok))
+				return(0);
+			if (nc && 0 == cnt) {
+				if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg))
+					return(0);
+				if ( ! rew_last(m, m->last))
+					return(0);
+			} else if ( ! nc && 0 == cnt) {
+				mdoc_argv_free(arg);
+				mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY);
+			}
+
+			if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
+				return(0);
+			if ( ! nl)
+				return(1);
+			return(append_delims(m, line, pos, buf));
+		} 
+
+		/* 
+		 * Non-quote-enclosed punctuation.  Set up our scope, if
+		 * a word; rewind the scope, if a delimiter; then append
+		 * the word. 
+		 */
+
+		d = ARGS_QWORD == ac ? 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
+			 * 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?).
+			 */
+			if (0 == cnt && (nc || MDOC_Li == tok) && 
+					DELIM_CLOSE == d && ! scope) {
+				if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg))
+					return(0);
+				if (MDOC_Ar == tok || MDOC_Li == tok || 
+						MDOC_Fl == tok)
+					cnt++;
+				scope = 1;
+			}
+			/*
+			 * Close out our scope, if one is open, before
+			 * any punctuation.
+			 */
+			if (scope && ! rew_elem(m, tok))
+				return(0);
+			scope = 0;
+		} else if ( ! scope) {
+			if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg))
+				return(0);
+			scope = 1;
+		}
+
+		if (DELIM_NONE == d)
+			cnt++;
+
+		if ( ! dword(m, line, la, p, d))
+			return(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(m, tok))
+				return(0);
+			scope = 0;
+		}
+	}
+
+	if (scope && ! rew_elem(m, tok))
+		return(0);
+
+	/*
+	 * If no elements have been collected and we're allowed to have
+	 * empties (nc), open a scope and close it out.  Otherwise,
+	 * raise a warning.
+	 */
+
+	if (nc && 0 == cnt) {
+		if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg))
+			return(0);
+		if ( ! rew_last(m, m->last))
+			return(0);
+	} else if ( ! nc && 0 == cnt) {
+		mdoc_argv_free(arg);
+		mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY);
+	}
+
+	if ( ! nl)
+		return(1);
+	return(append_delims(m, line, pos, buf));
+}
+
+
+static int
+blk_full(MACRO_PROT_ARGS)
+{
+	int		  la, nl, nparsed;
+	struct mdoc_arg	 *arg;
+	struct mdoc_node *head; /* save of head macro */
+	struct mdoc_node *body; /* save of body macro */
+	struct mdoc_node *n;
+	enum mdoc_type	  mtt;
+	enum mdoct	  ntok;
+	enum margserr	  ac, lac;
+	enum margverr	  av;
+	char		 *p;
+
+	nl = MDOC_NEWLINE & m->flags;
+
+	/* Close out prior implicit scope. */
+
+	if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) {
+		if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
+			return(0);
+		if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos))
+			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.
+	 */
+
+	for (arg = NULL;; ) {
+		la = *pos;
+		av = mdoc_argv(m, 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 ( ! mdoc_block_alloc(m, line, ppos, tok, arg))
+		return(0);
+
+	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 == m->last->parent->tok &&
+		LIST_diag == m->last->parent->norm->Bl.type;
+
+	/*
+	 * 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(m, line, ppos, tok))
+			return(0);
+		head = m->last;
+		if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+			return(0);
+		if ( ! mdoc_body_alloc(m, line, ppos, tok))
+			return(0);
+		body = m->last;
+	} 
+
+	ac = ARGS_ERROR;
+
+	for ( ; ; ) {
+		la = *pos;
+		/* Initialise last-phrase-type with ARGS_PEND. */
+		lac = ARGS_ERROR == ac ? ARGS_PEND : ac;
+		ac = mdoc_args(m, 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)
+				break;
+			/*
+			 * This is necessary: if the last token on a
+			 * line is a `Ta' or tab, then we'll get
+			 * ARGS_EOLN, so we must be smart enough to
+			 * reopen our scope if the last parse was a
+			 * phrase or partial phrase.
+			 */
+			if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
+				return(0);
+			if ( ! mdoc_body_alloc(m, line, ppos, tok))
+				return(0);
+			body = m->last;
+			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(m, line, la, p, DELIM_OPEN))
+				return(0);
+			continue;
+		}
+
+		/* Open a head if one hasn't been opened. */
+
+		if (NULL == head) {
+			if ( ! mdoc_head_alloc(m, line, ppos, tok))
+				return(0);
+			head = m->last;
+		}
+
+		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, m, tok, line, ppos))
+				return(0);
+			
+			/* Then allocate our body context. */
+
+			if ( ! mdoc_body_alloc(m, line, ppos, tok))
+				return(0);
+			body = m->last;
+
+			/*
+			 * Process phrases: set whether we're in a
+			 * partial-phrase (this effects line handling)
+			 * then call down into the phrase parser.
+			 */
+
+			if (ARGS_PPHRASE == ac)
+				m->flags |= MDOC_PPHRASE;
+			if (ARGS_PEND == ac && ARGS_PPHRASE == lac)
+				m->flags |= MDOC_PPHRASE;
+
+			if ( ! phrase(m, line, la, buf))
+				return(0);
+
+			m->flags &= ~MDOC_PPHRASE;
+			continue;
+		}
+
+		ntok = nparsed || ARGS_QWORD == ac ? 
+			MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! dword(m, line, la, p, DELIM_MAX))
+				return(0);
+			continue;
+		}
+
+		if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
+			return(0);
+		break;
+	}
+
+	if (NULL == head) {
+		if ( ! mdoc_head_alloc(m, line, ppos, tok))
+			return(0);
+		head = m->last;
+	}
+	
+	if (nl && ! append_delims(m, line, pos, buf))
+		return(0);
+
+	/* If we've already opened our body, exit now. */
+
+	if (NULL != body)
+		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
+	 * sub-block.
+	 */
+	for (n = m->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);
+		}
+	}
+
+	/* Close out scopes to remain in a consistent state. */
+
+	if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+		return(0);
+	if ( ! mdoc_body_alloc(m, line, ppos, tok))
+		return(0);
+
+out:
+	if ( ! (MDOC_FREECOL & m->flags))
+		return(1);
+
+	if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
+		return(0);
+	if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos))
+		return(0);
+
+	m->flags &= ~MDOC_FREECOL;
+	return(1);
+}
+
+
+static int
+blk_part_imp(MACRO_PROT_ARGS)
+{
+	int		  la, nl;
+	enum mdoct	  ntok;
+	enum margserr	  ac;
+	char		 *p;
+	struct mdoc_node *blk; /* saved block context */
+	struct mdoc_node *body; /* saved body context */
+	struct mdoc_node *n;
+
+	nl = MDOC_NEWLINE & m->flags;
+
+	/*
+	 * A macro that spans to the end of the line.  This is generally
+	 * (but not necessarily) called as the first macro.  The block
+	 * has a head as the immediate child, which is always empty,
+	 * followed by zero or more opening punctuation nodes, then the
+	 * body (which may be empty, depending on the macro), then zero
+	 * or more closing punctuation nodes.
+	 */
+
+	if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL))
+		return(0);
+
+	blk = m->last;
+
+	if ( ! mdoc_head_alloc(m, line, ppos, tok))
+		return(0);
+	if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+		return(0);
+
+	/*
+	 * Open the body scope "on-demand", that is, after we've
+	 * processed all our the leading delimiters (open parenthesis,
+	 * etc.).
+	 */
+
+	for (body = NULL; ; ) {
+		la = *pos;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
+
+		if (ARGS_ERROR == ac)
+			return(0);
+		if (ARGS_EOLN == ac)
+			break;
+		if (ARGS_PUNCT == ac)
+			break;
+
+		if (NULL == body && ARGS_QWORD != ac &&
+				DELIM_OPEN == mdoc_isdelim(p)) {
+			if ( ! dword(m, line, la, p, DELIM_OPEN))
+				return(0);
+			continue;
+		} 
+
+		if (NULL == body) {
+		       if ( ! mdoc_body_alloc(m, line, ppos, tok))
+			       return(0);
+			body = m->last;
+		}
+
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! dword(m, line, la, p, DELIM_MAX))
+				return(0);
+			continue;
+		}
+
+		if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
+			return(0);
+		break;
+	}
+
+	/* Clean-ups to leave in a consistent state. */
+
+	if (NULL == body) {
+		if ( ! mdoc_body_alloc(m, line, ppos, tok))
+			return(0);
+		body = m->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;
+
+	/* Up-propagate the end-of-space flag. */
+
+	if (n && (MDOC_EOS & n->flags)) {
+		body->flags |= MDOC_EOS;
+		body->parent->flags |= MDOC_EOS;
+	}
+
+	/*
+	 * 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.
+	 */
+	for (n = m->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, m, line, ppos);
+			if ( ! mdoc_endbody_alloc(m, line, ppos,
+			    tok, body, ENDBODY_NOSPACE))
+				return(0);
+			return(1);
+		}
+	}
+
+	/* 
+	 * 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, m->parse, line, ppos, 
+				"%s broken", mdoc_macronames[tok]);
+
+	if (n && ! rew_sub(MDOC_BODY, m, tok, line, ppos))
+		return(0);
+
+	/* Standard appending of delimiters. */
+
+	if (nl && ! append_delims(m, line, pos, buf))
+		return(0);
+
+	/* Rewind scope, if applicable. */
+
+	if (n && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos))
+		return(0);
+
+	return(1);
+}
+
+
+static int
+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 & m->flags;
+
+	/*
+	 * The opening of an explicit macro having zero or more leading
+	 * punctuation nodes; a head with optional single element (the
+	 * case of `Eo'); and a body that may be empty.
+	 */
+
+	if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL))
+		return(0); 
+
+	for (head = body = NULL; ; ) {
+		la = *pos;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
+
+		if (ARGS_ERROR == ac)
+			return(0);
+		if (ARGS_PUNCT == ac)
+			break;
+		if (ARGS_EOLN == ac)
+			break;
+
+		/* Flush out leading punctuation. */
+
+		if (NULL == head && ARGS_QWORD != ac &&
+				DELIM_OPEN == mdoc_isdelim(p)) {
+			assert(NULL == body);
+			if ( ! dword(m, line, la, p, DELIM_OPEN))
+				return(0);
+			continue;
+		} 
+
+		if (NULL == head) {
+			assert(NULL == body);
+			if ( ! mdoc_head_alloc(m, line, ppos, tok))
+				return(0);
+			head = m->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(m, line, la, p, DELIM_MAX))
+					return(0);
+
+			if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+				return(0);
+			if ( ! mdoc_body_alloc(m, line, ppos, tok))
+				return(0);
+			body = m->last;
+
+			if (MDOC_Eo == tok)
+				continue;
+		}
+
+		assert(NULL != head && NULL != body);
+
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! dword(m, line, la, p, DELIM_MAX))
+				return(0);
+			continue;
+		}
+
+		if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
+			return(0);
+		break;
+	}
+
+	/* Clean-up to leave in a consistent state. */
+
+	if (NULL == head)
+		if ( ! mdoc_head_alloc(m, line, ppos, tok))
+			return(0);
+
+	if (NULL == body) {
+		if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+			return(0);
+		if ( ! mdoc_body_alloc(m, line, ppos, tok))
+			return(0);
+	}
+
+	/* Standard appending of delimiters. */
+
+	if ( ! nl)
+		return(1);
+	return(append_delims(m, line, pos, buf));
+}
+
+
+/* ARGSUSED */
+static int
+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 mdoct	 ntok;
+
+	nl = MDOC_NEWLINE & m->flags;
+
+	/*
+	 * A line macro that has a fixed number of arguments (maxargs).
+	 * Only open the scope once the first non-leading-punctuation is
+	 * found (unless MDOC_IGNDELIM is noted, like in `Pf'), then
+	 * keep it open until the maximum number of arguments are
+	 * exhausted.
+	 */
+
+	switch (tok) {
+	case (MDOC_Ap):
+		/* FALLTHROUGH */
+	case (MDOC_No):
+		/* FALLTHROUGH */
+	case (MDOC_Ns):
+		/* FALLTHROUGH */
+	case (MDOC_Ux):
+		maxargs = 0;
+		break;
+	case (MDOC_Bx):
+		/* FALLTHROUGH */
+	case (MDOC_Xr):
+		maxargs = 2;
+		break;
+	default:
+		maxargs = 1;
+		break;
+	}
+
+	for (arg = NULL; ; ) {
+		la = *pos;
+		av = mdoc_argv(m, 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);
+	}
+
+	for (flushed = j = 0; ; ) {
+		la = *pos;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
+
+		if (ARGS_ERROR == ac)
+			return(0);
+		if (ARGS_PUNCT == ac)
+			break;
+		if (ARGS_EOLN == ac)
+			break;
+
+		if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && 
+				ARGS_QWORD != ac && 0 == j && 
+				DELIM_OPEN == mdoc_isdelim(p)) {
+			if ( ! dword(m, line, la, p, DELIM_OPEN))
+				return(0);
+			continue;
+		} else if (0 == j)
+		       if ( ! mdoc_elem_alloc(m, line, la, tok, arg))
+			       return(0);
+
+		if (j == maxargs && ! flushed) {
+			if ( ! rew_elem(m, tok))
+				return(0);
+			flushed = 1;
+		}
+
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX != ntok) {
+			if ( ! flushed && ! rew_elem(m, tok))
+				return(0);
+			flushed = 1;
+			if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
+				return(0);
+			j++;
+			break;
+		}
+
+		if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) &&
+				ARGS_QWORD != ac &&
+				! flushed &&
+				DELIM_NONE != mdoc_isdelim(p)) {
+			if ( ! rew_elem(m, tok))
+				return(0);
+			flushed = 1;
+		}
+
+		if ( ! dword(m, line, la, p, DELIM_MAX))
+			return(0);
+		j++;
+	}
+
+	if (0 == j && ! mdoc_elem_alloc(m, line, la, tok, arg))
+	       return(0);
+
+	/* Close out in a consistent state. */
+
+	if ( ! flushed && ! rew_elem(m, tok))
+		return(0);
+	if ( ! nl)
+		return(1);
+	return(append_delims(m, line, pos, buf));
+}
+
+
+static int
+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, m, MDOC_Nm, line, ppos);
+
+	/* Parse macro arguments. */
+
+	for (arg = NULL; ; ) {
+		la = *pos;
+		av = mdoc_argv(m, 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);
+	}
+
+	/* Open element scope. */
+
+	if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg))
+		return(0);
+
+	/* Parse argument terms. */
+
+	for (;;) {
+		la = *pos;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
+
+		if (ARGS_ERROR == ac)
+			return(0);
+		if (ARGS_EOLN == ac)
+			break;
+
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! dword(m, line, la, p, DELIM_MAX))
+				return(0);
+			continue;
+		}
+
+		if ( ! rew_elem(m, tok))
+			return(0);
+		return(mdoc_macro(m, ntok, line, la, pos, buf));
+	}
+
+	/* Close out (no delimiters). */
+
+	return(rew_elem(m, tok));
+}
+
+
+/* ARGSUSED */
+static int
+ctx_synopsis(MACRO_PROT_ARGS)
+{
+	int		 nl;
+
+	nl = MDOC_NEWLINE & m->flags;
+
+	/* If we're not in the SYNOPSIS, go straight to in-line. */
+	if ( ! (MDOC_SYNOPSIS & m->flags))
+		return(in_line(m, tok, line, ppos, pos, buf));
+
+	/* If we're a nested call, same place. */
+	if ( ! nl)
+		return(in_line(m, 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(m, tok, line, ppos, pos, buf));
+	assert(MDOC_Vt == tok);
+	return(blk_part_imp(m, tok, line, ppos, pos, buf));
+}
+
+
+/* ARGSUSED */
+static int
+obsolete(MACRO_PROT_ARGS)
+{
+
+	mdoc_pmsg(m, line, ppos, MANDOCERR_MACROOBS);
+	return(1);
+}
+
+
+/*
+ * 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 *m, int line, int ppos, char *buf)
+{
+	int		 la, pos;
+	enum margserr	 ac;
+	enum mdoct	 ntok;
+	char		*p;
+
+	for (pos = ppos; ; ) {
+		la = pos;
+
+		ac = mdoc_zargs(m, 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(m, line, la, p, DELIM_MAX))
+				return(0);
+			continue;
+		}
+
+		if ( ! mdoc_macro(m, ntok, line, la, &pos, buf))
+			return(0);
+		return(append_delims(m, line, &pos, buf));
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+phrase_ta(MACRO_PROT_ARGS)
+{
+	int		  la;
+	enum mdoct	  ntok;
+	enum margserr	  ac;
+	char		 *p;
+
+	/*
+	 * FIXME: this is overly restrictive: if the `Ta' is unexpected,
+	 * it should simply error out with ARGSLOST.
+	 */
+
+	if ( ! rew_sub(MDOC_BODY, m, MDOC_It, line, ppos))
+		return(0);
+	if ( ! mdoc_body_alloc(m, line, ppos, MDOC_It))
+		return(0);
+
+	for (;;) {
+		la = *pos;
+		ac = mdoc_zargs(m, 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(m, line, la, p, DELIM_MAX))
+				return(0);
+			continue;
+		}
+
+		if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
+			return(0);
+		return(append_delims(m, line, pos, buf));
+	}
+
+	return(1);
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/mdoc_man.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,637 @@
+/*	$Id: mdoc_man.c,v 1.9 2011/10/24 21:47:59 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <stdio.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "man.h"
+#include "mdoc.h"
+#include "main.h"
+
+#define	DECL_ARGS const struct mdoc_meta *m, \
+		  const struct mdoc_node *n, \
+		  struct mman *mm
+
+struct	mman {
+	int		  need_space; /* next word needs prior ws */
+	int		  need_nl; /* next word needs prior nl */
+};
+
+struct	manact {
+	int		(*cond)(DECL_ARGS); /* DON'T run actions */
+	int		(*pre)(DECL_ARGS); /* pre-node action */
+	void		(*post)(DECL_ARGS); /* post-node action */
+	const char	 *prefix; /* pre-node string constant */
+	const char	 *suffix; /* post-node string constant */
+};
+
+static	int	  cond_body(DECL_ARGS);
+static	int	  cond_head(DECL_ARGS);
+static	void	  post_bd(DECL_ARGS);
+static	void	  post_dl(DECL_ARGS);
+static	void	  post_enc(DECL_ARGS);
+static	void	  post_nm(DECL_ARGS);
+static	void	  post_percent(DECL_ARGS);
+static	void	  post_pf(DECL_ARGS);
+static	void	  post_sect(DECL_ARGS);
+static	void	  post_sp(DECL_ARGS);
+static	int	  pre_ap(DECL_ARGS);
+static	int	  pre_bd(DECL_ARGS);
+static	int	  pre_br(DECL_ARGS);
+static	int	  pre_bx(DECL_ARGS);
+static	int	  pre_dl(DECL_ARGS);
+static	int	  pre_enc(DECL_ARGS);
+static	int	  pre_it(DECL_ARGS);
+static	int	  pre_nm(DECL_ARGS);
+static	int	  pre_ns(DECL_ARGS);
+static	int	  pre_pp(DECL_ARGS);
+static	int	  pre_sp(DECL_ARGS);
+static	int	  pre_sect(DECL_ARGS);
+static	int	  pre_ux(DECL_ARGS);
+static	int	  pre_xr(DECL_ARGS);
+static	void	  print_word(struct mman *, const char *);
+static	void	  print_node(DECL_ARGS);
+
+static	const struct manact manacts[MDOC_MAX + 1] = {
+	{ NULL, pre_ap, NULL, NULL, NULL }, /* Ap */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Dd */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Dt */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Os */
+	{ NULL, pre_sect, post_sect, ".SH", NULL }, /* Sh */
+	{ NULL, pre_sect, post_sect, ".SS", NULL }, /* Ss */
+	{ NULL, pre_pp, NULL, NULL, NULL }, /* Pp */
+	{ cond_body, pre_dl, post_dl, NULL, NULL }, /* D1 */
+	{ cond_body, pre_dl, post_dl, NULL, NULL }, /* Dl */
+	{ cond_body, pre_bd, post_bd, NULL, NULL }, /* Bd */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Ed */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Bl */
+	{ NULL, NULL, NULL, NULL, NULL }, /* El */
+	{ NULL, pre_it, NULL, NULL, NULL }, /* _It */
+	{ NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Ad */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _An */
+	{ NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Ar */
+	{ NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Cd */
+	{ NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Cm */
+	{ NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Dv */
+	{ NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Er */
+	{ NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Ev */
+	{ NULL, pre_enc, post_enc, "The \\fB",
+	    "\\fP\nutility exits 0 on success, and >0 if an error occurs."
+	    }, /* Ex */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Fa */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Fd */
+	{ NULL, pre_enc, post_enc, "\\fB-", "\\fP" }, /* Fl */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Fn */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Ft */
+	{ NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Ic */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _In */
+	{ NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Li */
+	{ 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_enc, post_enc, "\\fI", "\\fP" }, /* 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, NULL, NULL, NULL, NULL }, /* St */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Va */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Vt */
+	{ NULL, pre_xr, NULL, NULL, NULL }, /* Xr */
+	{ NULL, NULL, post_percent, NULL, NULL }, /* _%A */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%B */
+	{ NULL, NULL, post_percent, NULL, NULL }, /* _%D */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%I */
+	{ NULL, pre_enc, post_percent, "\\fI", "\\fP" }, /* %J */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%N */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%O */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%P */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%R */
+	{ NULL, pre_enc, post_percent, "\"", "\"" }, /* %T */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%V */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Ac */
+	{ cond_body, pre_enc, post_enc, "<", ">" }, /* Ao */
+	{ cond_body, pre_enc, post_enc, "<", ">" }, /* Aq */
+	{ NULL, NULL, NULL, NULL, NULL }, /* At */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Bc */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Bf */
+	{ cond_body, pre_enc, post_enc, "[", "]" }, /* Bo */
+	{ 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, NULL, NULL, NULL, NULL }, /* Dc */
+	{ cond_body, pre_enc, post_enc, "``", "''" }, /* Do */
+	{ cond_body, pre_enc, post_enc, "``", "''" }, /* Dq */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Ec */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Ef */
+	{ NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Em */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Eo */
+	{ NULL, pre_ux, NULL, "FreeBSD", NULL }, /* Fx */
+	{ NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Ms */
+	{ NULL, NULL, NULL, NULL, NULL }, /* No */
+	{ NULL, pre_ns, NULL, NULL, NULL }, /* Ns */
+	{ NULL, pre_ux, NULL, "NetBSD", NULL }, /* Nx */
+	{ NULL, pre_ux, NULL, "OpenBSD", NULL }, /* Ox */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Pc */
+	{ NULL, NULL, post_pf, NULL, NULL }, /* Pf */
+	{ cond_body, pre_enc, post_enc, "(", ")" }, /* Po */
+	{ cond_body, pre_enc, post_enc, "(", ")" }, /* Pq */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Qc */
+	{ cond_body, pre_enc, post_enc, "`", "'" }, /* Ql */
+	{ cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qo */
+	{ cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qq */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Re */
+	{ cond_body, pre_pp, NULL, NULL, NULL }, /* Rs */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Sc */
+	{ cond_body, pre_enc, post_enc, "`", "'" }, /* So */
+	{ cond_body, pre_enc, post_enc, "`", "'" }, /* Sq */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Sm */
+	{ NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Sx */
+	{ NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Sy */
+	{ NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Tn */
+	{ NULL, pre_ux, NULL, "UNIX", NULL }, /* Ux */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Xc */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Xo */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Fo */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Fc */
+	{ cond_body, pre_enc, post_enc, "[", "]" }, /* Oo */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Oc */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Bk */
+	{ 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_ux, NULL, "currently under development.", NULL }, /* Ud */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Lb */
+	{ NULL, pre_pp, NULL, NULL, NULL }, /* Lp */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Lk */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Mt */
+	{ cond_body, pre_enc, post_enc, "{", "}" }, /* Brq */
+	{ cond_body, pre_enc, post_enc, "{", "}" }, /* Bro */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Brc */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%C */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Es */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _En */
+	{ NULL, pre_ux, NULL, "DragonFly", NULL }, /* Dx */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%Q */
+	{ NULL, pre_br, NULL, NULL, NULL }, /* br */
+	{ NULL, pre_sp, post_sp, NULL, NULL }, /* sp */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _%U */
+	{ NULL, NULL, NULL, NULL, NULL }, /* _Ta */
+	{ NULL, NULL, NULL, NULL, NULL }, /* ROOT */
+};
+
+static void
+print_word(struct mman *mm, const char *s)
+{
+
+	if (mm->need_nl) {
+		/* 
+		 * If we need a newline, print it now and start afresh.
+		 */
+		putchar('\n');
+		mm->need_space = 0;
+		mm->need_nl = 0;
+	} else if (mm->need_space && '\0' != s[0])
+		/*
+		 * If we need a space, only print it before
+		 * (1) a nonzero length word;
+		 * (2) a word that is non-punctuation; and
+		 * (3) if punctuation, non-terminating puncutation.
+		 */
+		if (NULL == strchr(".,:;)]?!", s[0]) || '\0' != s[1])
+			putchar(' ');
+
+	/*
+	 * Reassign needing space if we're not following opening
+	 * punctuation.
+	 */
+	mm->need_space = 
+		('(' != s[0] && '[' != s[0]) || '\0' != s[1];
+
+	for ( ; *s; s++) {
+		switch (*s) {
+		case (ASCII_NBRSP):
+			printf("\\~");
+			break;
+		case (ASCII_HYPH):
+			putchar('-');
+			break;
+		default:
+			putchar((unsigned char)*s);
+			break;
+		}
+	}
+}
+
+void
+man_man(void *arg, const struct man *man)
+{
+
+	/*
+	 * Dump the keep buffer.
+	 * We're guaranteed by now that this exists (is non-NULL).
+	 * Flush stdout afterward, just in case.
+	 */
+	fputs(mparse_getkeep(man_mparse(man)), stdout);
+	fflush(stdout);
+}
+
+void
+man_mdoc(void *arg, const struct mdoc *mdoc)
+{
+	const struct mdoc_meta *m;
+	const struct mdoc_node *n;
+	struct mman	        mm;
+
+	m = mdoc_meta(mdoc);
+	n = mdoc_node(mdoc);
+
+	printf(".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"",
+			m->title, m->msec, m->date, m->os, m->vol);
+
+	memset(&mm, 0, sizeof(struct mman));
+
+	mm.need_nl = 1;
+	print_node(m, n, &mm);
+	putchar('\n');
+}
+
+static void
+print_node(DECL_ARGS)
+{
+	const struct mdoc_node	*prev, *sub;
+	const struct manact	*act;
+	int			 cond, do_sub;
+	
+	/*
+	 * Break the line if we were parsed subsequent the current node.
+	 * This makes the page structure be more consistent.
+	 */
+	prev = n->prev ? n->prev : n->parent;
+	if (prev && prev->line < n->line)
+		mm->need_nl = 1;
+
+	act = NULL;
+	cond = 0;
+	do_sub = 1;
+
+	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 (mm->need_nl && ('.' == *n->string || 
+					'\'' == *n->string)) {
+			print_word(mm, "\\&");
+			mm->need_space = 0;
+		}
+		print_word(mm, n->string);
+	} else {
+		/*
+		 * Conditionally run the pre-node action handler for a
+		 * node.
+		 */
+		act = manacts + n->tok;
+		cond = NULL == act->cond || (*act->cond)(m, n, mm);
+		if (cond && act->pre)
+			do_sub = (*act->pre)(m, n, mm);
+	}
+
+	/* 
+	 * Conditionally run all child nodes.
+	 * Note that this iterates over children instead of using
+	 * recursion.  This prevents unnecessary depth in the stack.
+	 */
+	if (do_sub)
+		for (sub = n->child; sub; sub = sub->next)
+			print_node(m, sub, mm);
+
+	/*
+	 * Lastly, conditionally run the post-node handler.
+	 */
+	if (cond && act->post)
+		(*act->post)(m, n, mm);
+}
+
+static int
+cond_head(DECL_ARGS)
+{
+
+	return(MDOC_HEAD == n->type);
+}
+
+static int
+cond_body(DECL_ARGS)
+{
+
+	return(MDOC_BODY == n->type);
+}
+
+/*
+ * Output a font encoding before a node, e.g., \fR.
+ * This obviously has no trailing space.
+ */
+static int
+pre_enc(DECL_ARGS)
+{
+	const char	*prefix;
+
+	prefix = manacts[n->tok].prefix;
+	if (NULL == prefix)
+		return(1);
+	print_word(mm, prefix);
+	mm->need_space = 0;
+	return(1);
+}
+
+/*
+ * Output a font encoding subsequent a node, e.g., \fP.
+ */
+static void
+post_enc(DECL_ARGS)
+{
+	const char *suffix;
+
+	suffix = manacts[n->tok].suffix;
+	if (NULL == suffix)
+		return;
+	mm->need_space = 0;
+	print_word(mm, suffix);
+}
+
+/*
+ * Used in listings (percent = %A, e.g.).
+ * FIXME: this is incomplete. 
+ * It doesn't print a nice ", and" for lists.
+ */
+static void
+post_percent(DECL_ARGS)
+{
+
+	post_enc(m, n, mm);
+	if (n->next)
+		print_word(mm, ",");
+	else {
+		print_word(mm, ".");
+		mm->need_nl = 1;
+	}
+}
+
+/*
+ * Print before a section header.
+ */
+static int
+pre_sect(DECL_ARGS)
+{
+
+	if (MDOC_HEAD != n->type)
+		return(1);
+	mm->need_nl = 1;
+	print_word(mm, manacts[n->tok].prefix);
+	print_word(mm, "\"");
+	mm->need_space = 0;
+	return(1);
+}
+
+/*
+ * Print subsequent a section header.
+ */
+static void
+post_sect(DECL_ARGS)
+{
+
+	if (MDOC_HEAD != n->type)
+		return;
+	mm->need_space = 0;
+	print_word(mm, "\"");
+	mm->need_nl = 1;
+}
+
+static int
+pre_ap(DECL_ARGS)
+{
+
+	mm->need_space = 0;
+	print_word(mm, "'");
+	mm->need_space = 0;
+	return(0);
+}
+
+static int
+pre_bd(DECL_ARGS)
+{
+
+	if (DISP_unfilled == n->norm->Bd.type ||
+	    DISP_literal  == n->norm->Bd.type) {
+		mm->need_nl = 1;
+		print_word(mm, ".nf");
+	}
+	mm->need_nl = 1;
+	return(1);
+}
+
+static void
+post_bd(DECL_ARGS)
+{
+
+	if (DISP_unfilled == n->norm->Bd.type ||
+	    DISP_literal  == n->norm->Bd.type) {
+		mm->need_nl = 1;
+		print_word(mm, ".fi");
+	}
+	mm->need_nl = 1;
+}
+
+static int
+pre_br(DECL_ARGS)
+{
+
+	mm->need_nl = 1;
+	print_word(mm, ".br");
+	mm->need_nl = 1;
+	return(0);
+}
+
+static int
+pre_bx(DECL_ARGS)
+{
+
+	n = n->child;
+	if (n) {
+		print_word(mm, n->string);
+		mm->need_space = 0;
+		n = n->next;
+	}
+	print_word(mm, "BSD");
+	if (NULL == n)
+		return(0);
+	mm->need_space = 0;
+	print_word(mm, "-");
+	mm->need_space = 0;
+	print_word(mm, n->string);
+	return(0);
+}
+
+static int
+pre_dl(DECL_ARGS)
+{
+
+	mm->need_nl = 1;
+	print_word(mm, ".RS 6n");
+	mm->need_nl = 1;
+	return(1);
+}
+
+static void
+post_dl(DECL_ARGS)
+{
+
+	mm->need_nl = 1;
+	print_word(mm, ".RE");
+	mm->need_nl = 1;
+}
+
+static int
+pre_it(DECL_ARGS)
+{
+	const struct mdoc_node *bln;
+
+	if (MDOC_HEAD == n->type) {
+		mm->need_nl = 1;
+		print_word(mm, ".TP");
+		bln = n->parent->parent->prev;
+		switch (bln->norm->Bl.type) {
+		case (LIST_bullet):
+			print_word(mm, "4n");
+			mm->need_nl = 1;
+			print_word(mm, "\\fBo\\fP");
+			break;
+		default:
+			if (bln->norm->Bl.width)
+				print_word(mm, bln->norm->Bl.width);
+			break;
+		}
+		mm->need_nl = 1;
+	}
+	return(1);
+}
+
+static int
+pre_nm(DECL_ARGS)
+{
+
+	if (MDOC_ELEM != n->type && MDOC_HEAD != n->type)
+		return(1);
+	print_word(mm, "\\fB");
+	mm->need_space = 0;
+	if (NULL == n->child)
+		print_word(mm, m->name);
+	return(1);
+}
+
+static void
+post_nm(DECL_ARGS)
+{
+
+	if (MDOC_ELEM != n->type && MDOC_HEAD != n->type)
+		return;
+	mm->need_space = 0;
+	print_word(mm, "\\fP");
+}
+
+static int
+pre_ns(DECL_ARGS)
+{
+
+	mm->need_space = 0;
+	return(0);
+}
+
+static void
+post_pf(DECL_ARGS)
+{
+
+	mm->need_space = 0;
+}
+
+static int
+pre_pp(DECL_ARGS)
+{
+
+	mm->need_nl = 1;
+	if (MDOC_It == n->parent->tok)
+		print_word(mm, ".sp");
+	else
+		print_word(mm, ".PP");
+	mm->need_nl = 1;
+	return(1);
+}
+
+static int
+pre_sp(DECL_ARGS)
+{
+
+	mm->need_nl = 1;
+	print_word(mm, ".sp");
+	return(1);
+}
+
+static void
+post_sp(DECL_ARGS)
+{
+
+	mm->need_nl = 1;
+}
+
+static int
+pre_xr(DECL_ARGS)
+{
+
+	n = n->child;
+	if (NULL == n)
+		return(0);
+	print_node(m, n, mm);
+	n = n->next;
+	if (NULL == n)
+		return(0);
+	mm->need_space = 0;
+	print_word(mm, "(");
+	print_node(m, n, mm);
+	print_word(mm, ")");
+	return(0);
+}
+
+static int
+pre_ux(DECL_ARGS)
+{
+
+	print_word(mm, manacts[n->tok].prefix);
+	if (NULL == n->child)
+		return(0);
+	mm->need_space = 0;
+	print_word(mm, "\\~");
+	mm->need_space = 0;
+	return(1);
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/mdoc_term.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,2257 @@
+/*	$Id: mdoc_term.c,v 1.238 2011/11/13 13:15:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "term.h"
+#include "mdoc.h"
+#include "main.h"
+
+struct	termpair {
+	struct termpair	 *ppair;
+	int		  count;
+};
+
+#define	DECL_ARGS struct termp *p, \
+		  struct termpair *pair, \
+	  	  const struct mdoc_meta *m, \
+		  const struct mdoc_node *n
+
+struct	termact {
+	int	(*pre)(DECL_ARGS);
+	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	void	  print_bvspace(struct termp *,
+			const struct mdoc_node *,
+			const struct mdoc_node *);
+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 *, 
+			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_d1_post(DECL_ARGS);
+static	void	  termp_fo_post(DECL_ARGS);
+static	void	  termp_in_post(DECL_ARGS);
+static	void	  termp_it_post(DECL_ARGS);
+static	void	  termp_lb_post(DECL_ARGS);
+static	void	  termp_nm_post(DECL_ARGS);
+static	void	  termp_pf_post(DECL_ARGS);
+static	void	  termp_quote_post(DECL_ARGS);
+static	void	  termp_sh_post(DECL_ARGS);
+static	void	  termp_ss_post(DECL_ARGS);
+
+static	int	  termp__a_pre(DECL_ARGS);
+static	int	  termp__t_pre(DECL_ARGS);
+static	int	  termp_an_pre(DECL_ARGS);
+static	int	  termp_ap_pre(DECL_ARGS);
+static	int	  termp_bd_pre(DECL_ARGS);
+static	int	  termp_bf_pre(DECL_ARGS);
+static	int	  termp_bk_pre(DECL_ARGS);
+static	int	  termp_bl_pre(DECL_ARGS);
+static	int	  termp_bold_pre(DECL_ARGS);
+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_ex_pre(DECL_ARGS);
+static	int	  termp_fa_pre(DECL_ARGS);
+static	int	  termp_fd_pre(DECL_ARGS);
+static	int	  termp_fl_pre(DECL_ARGS);
+static	int	  termp_fn_pre(DECL_ARGS);
+static	int	  termp_fo_pre(DECL_ARGS);
+static	int	  termp_ft_pre(DECL_ARGS);
+static	int	  termp_igndelim_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_lk_pre(DECL_ARGS);
+static	int	  termp_nd_pre(DECL_ARGS);
+static	int	  termp_nm_pre(DECL_ARGS);
+static	int	  termp_ns_pre(DECL_ARGS);
+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_sm_pre(DECL_ARGS);
+static	int	  termp_sp_pre(DECL_ARGS);
+static	int	  termp_ss_pre(DECL_ARGS);
+static	int	  termp_under_pre(DECL_ARGS);
+static	int	  termp_ud_pre(DECL_ARGS);
+static	int	  termp_vt_pre(DECL_ARGS);
+static	int	  termp_xr_pre(DECL_ARGS);
+static	int	  termp_xx_pre(DECL_ARGS);
+
+static	const struct termact termacts[MDOC_MAX] = {
+	{ termp_ap_pre, NULL }, /* Ap */
+	{ NULL, NULL }, /* Dd */
+	{ 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_d1_pre, termp_d1_post }, /* D1 */
+	{ termp_d1_pre, termp_d1_post }, /* Dl */
+	{ termp_bd_pre, termp_bd_post }, /* Bd */
+	{ NULL, NULL }, /* Ed */
+	{ 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 }, /* Ar */
+	{ termp_cd_pre, NULL }, /* Cd */
+	{ termp_bold_pre, NULL }, /* Cm */
+	{ NULL, NULL }, /* Dv */ 
+	{ NULL, NULL }, /* Er */ 
+	{ NULL, NULL }, /* Ev */ 
+	{ termp_ex_pre, NULL }, /* Ex */
+	{ termp_fa_pre, NULL }, /* Fa */ 
+	{ termp_fd_pre, NULL }, /* 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_li_pre, NULL }, /* Li */
+	{ termp_nd_pre, NULL }, /* Nd */ 
+	{ termp_nm_pre, termp_nm_post }, /* Nm */ 
+	{ termp_quote_pre, termp_quote_post }, /* Op */
+	{ NULL, NULL }, /* Ot */
+	{ termp_under_pre, NULL }, /* Pa */
+	{ termp_rv_pre, NULL }, /* Rv */
+	{ NULL, NULL }, /* St */ 
+	{ termp_under_pre, NULL }, /* Va */
+	{ termp_vt_pre, NULL }, /* Vt */
+	{ termp_xr_pre, NULL }, /* Xr */
+	{ termp__a_pre, termp____post }, /* %A */
+	{ termp_under_pre, termp____post }, /* %B */
+	{ NULL, termp____post }, /* %D */
+	{ termp_under_pre, termp____post }, /* %I */
+	{ termp_under_pre, termp____post }, /* %J */
+	{ NULL, termp____post }, /* %N */
+	{ NULL, termp____post }, /* %O */
+	{ NULL, termp____post }, /* %P */
+	{ NULL, termp____post }, /* %R */
+	{ termp__t_pre, termp__t_post }, /* %T */
+	{ NULL, termp____post }, /* %V */
+	{ NULL, NULL }, /* Ac */
+	{ termp_quote_pre, termp_quote_post }, /* Ao */
+	{ termp_quote_pre, termp_quote_post }, /* Aq */
+	{ NULL, NULL }, /* At */
+	{ NULL, NULL }, /* Bc */
+	{ 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 */
+	{ 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_xx_pre, NULL }, /* Fx */
+	{ termp_bold_pre, NULL }, /* Ms */
+	{ termp_igndelim_pre, NULL }, /* No */
+	{ termp_ns_pre, NULL }, /* Ns */
+	{ termp_xx_pre, NULL }, /* Nx */
+	{ termp_xx_pre, NULL }, /* Ox */
+	{ NULL, NULL }, /* Pc */
+	{ termp_igndelim_pre, termp_pf_post }, /* Pf */
+	{ termp_quote_pre, termp_quote_post }, /* Po */
+	{ termp_quote_pre, termp_quote_post }, /* Pq */
+	{ NULL, NULL }, /* Qc */
+	{ termp_quote_pre, termp_quote_post }, /* Ql */
+	{ termp_quote_pre, termp_quote_post }, /* Qo */
+	{ termp_quote_pre, termp_quote_post }, /* Qq */
+	{ NULL, NULL }, /* Re */
+	{ termp_rs_pre, NULL }, /* Rs */
+	{ NULL, NULL }, /* Sc */
+	{ termp_quote_pre, termp_quote_post }, /* So */
+	{ termp_quote_pre, termp_quote_post }, /* Sq */
+	{ termp_sm_pre, NULL }, /* Sm */
+	{ termp_under_pre, NULL }, /* Sx */
+	{ termp_bold_pre, NULL }, /* Sy */
+	{ NULL, NULL }, /* Tn */
+	{ termp_xx_pre, NULL }, /* Ux */
+	{ NULL, NULL }, /* Xc */
+	{ NULL, NULL }, /* Xo */
+	{ 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_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 }, /* br */
+	{ termp_sp_pre, NULL }, /* sp */ 
+	{ termp_under_pre, termp____post }, /* %U */ 
+	{ NULL, NULL }, /* Ta */ 
+};
+
+
+void
+terminal_mdoc(void *arg, const struct mdoc *mdoc)
+{
+	const struct mdoc_node	*n;
+	const struct mdoc_meta	*m;
+	struct termp		*p;
+
+	p = (struct termp *)arg;
+
+	if (0 == p->defindent)
+		p->defindent = 5;
+
+	p->overstep = 0;
+	p->maxrmargin = p->defrmargin;
+	p->tabwidth = term_len(p, 5);
+
+	if (NULL == p->symtab)
+		p->symtab = mchars_alloc();
+
+	n = mdoc_node(mdoc);
+	m = mdoc_meta(mdoc);
+
+	term_begin(p, print_mdoc_head, print_mdoc_foot, m);
+
+	if (n->child)
+		print_mdoc_nodelist(p, NULL, m, n->child);
+
+	term_end(p);
+}
+
+
+static void
+print_mdoc_nodelist(DECL_ARGS)
+{
+
+	print_mdoc_node(p, pair, m, n);
+	if (n->next)
+		print_mdoc_nodelist(p, pair, m, n->next);
+}
+
+
+/* ARGSUSED */
+static void
+print_mdoc_node(DECL_ARGS)
+{
+	int		 chld;
+	const void	*font;
+	struct termpair	 npair;
+	size_t		 offset, rmargin;
+
+	chld = 1;
+	offset = p->offset;
+	rmargin = p->rmargin;
+	font = term_fontq(p);
+
+	memset(&npair, 0, sizeof(struct termpair));
+	npair.ppair = pair;
+
+	/*
+	 * Keeps only work until the end of a line.  If a keep was
+	 * invoked in a prior line, revert it to PREKEEP.
+	 *
+	 * Also let SYNPRETTY sections behave as if they were wrapped
+	 * in a `Bk' block.
+	 */
+
+	if (TERMP_KEEP & p->flags || MDOC_SYNPRETTY & n->flags) {
+		if (n->prev && n->prev->line != n->line) {
+			p->flags &= ~TERMP_KEEP;
+			p->flags |= TERMP_PREKEEP;
+		} else if (NULL == n->prev) {
+			if (n->parent && n->parent->line != n->line) {
+				p->flags &= ~TERMP_KEEP;
+				p->flags |= TERMP_PREKEEP;
+			}
+		}
+	}
+
+	/*
+	 * Since SYNPRETTY sections aren't "turned off" with `Ek',
+	 * we have to intuit whether we should disable formatting.
+	 */
+
+	if ( ! (MDOC_SYNPRETTY & n->flags) &&
+	    ((n->prev   && MDOC_SYNPRETTY & n->prev->flags) ||
+	     (n->parent && MDOC_SYNPRETTY & n->parent->flags)))
+		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
+
+	/*
+	 * After the keep flags have been set up, we may now
+	 * produce output.  Note that some pre-handlers do so.
+	 */
+
+	switch (n->type) {
+	case (MDOC_TEXT):
+		if (' ' == *n->string && MDOC_LINE & n->flags)
+			term_newln(p);
+		if (MDOC_DELIMC & n->flags)
+			p->flags |= TERMP_NOSPACE;
+		term_word(p, n->string);
+		if (MDOC_DELIMO & n->flags)
+			p->flags |= TERMP_NOSPACE;
+		break;
+	case (MDOC_EQN):
+		term_eqn(p, n->eqn);
+		break;
+	case (MDOC_TBL):
+		term_tbl(p, n->span);
+		break;
+	default:
+		if (termacts[n->tok].pre && ENDBODY_NOT == n->end)
+			chld = (*termacts[n->tok].pre)
+				(p, &npair, m, n);
+		break;
+	}
+
+	if (chld && n->child)
+		print_mdoc_nodelist(p, &npair, m, n->child);
+
+	term_fontpopq(p, font);
+
+	switch (n->type) {
+	case (MDOC_TEXT):
+		break;
+	case (MDOC_TBL):
+		break;
+	case (MDOC_EQN):
+		break;
+	default:
+		if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
+			break;
+		(void)(*termacts[n->tok].post)(p, &npair, m, n);
+
+		/*
+		 * Explicit end tokens not only call the post
+		 * handler, but also tell the respective block
+		 * that it must not call the post handler again.
+		 */
+		if (ENDBODY_NOT != n->end)
+			n->pending->flags |= MDOC_ENDED;
+
+		/*
+		 * End of line terminating an implicit block
+		 * while an explicit block is still open.
+		 * Continue the explicit block without spacing.
+		 */
+		if (ENDBODY_NOSPACE == n->end)
+			p->flags |= TERMP_NOSPACE;
+		break;
+	}
+
+	if (MDOC_EOS & n->flags)
+		p->flags |= TERMP_SENTENCE;
+
+	p->offset = offset;
+	p->rmargin = rmargin;
+}
+
+
+static void
+print_mdoc_foot(struct termp *p, const void *arg)
+{
+	const struct mdoc_meta *m;
+
+	m = (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:
+	 *
+	 * SYSTEM                  DATE                    SYSTEM
+	 */
+
+	term_vspace(p);
+
+	p->offset = 0;
+	p->rmargin = (p->maxrmargin - 
+			term_strlen(p, m->date) + term_len(p, 1)) / 2;
+	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+
+	term_word(p, m->os);
+	term_flushln(p);
+
+	p->offset = p->rmargin;
+	p->rmargin = p->maxrmargin - term_strlen(p, m->os);
+	p->flags |= TERMP_NOSPACE;
+
+	term_word(p, m->date);
+	term_flushln(p);
+
+	p->offset = p->rmargin;
+	p->rmargin = p->maxrmargin;
+	p->flags &= ~TERMP_NOBREAK;
+	p->flags |= TERMP_NOSPACE;
+
+	term_word(p, m->os);
+	term_flushln(p);
+
+	p->offset = 0;
+	p->rmargin = p->maxrmargin;
+	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 *m;
+
+	m = (const struct mdoc_meta *)arg;
+
+	/*
+	 * The header is strange.  It has three components, which are
+	 * really two with the first duplicated.  It goes like this:
+	 *
+	 * IDENTIFIER              TITLE                   IDENTIFIER
+	 *
+	 * The IDENTIFIER is NAME(SECTION), which is the command-name
+	 * (if given, or "unknown" if not) followed by the manual page
+	 * section.  These are given in `Dt'.  The TITLE is a free-form
+	 * string depending on the manual volume.  If not specified, it
+	 * switches on the manual section.
+	 */
+
+	p->offset = 0;
+	p->rmargin = p->maxrmargin;
+
+	assert(m->vol);
+	strlcpy(buf, m->vol, BUFSIZ);
+	buflen = term_strlen(p, buf);
+
+	if (m->arch) {
+		strlcat(buf, " (", BUFSIZ);
+		strlcat(buf, m->arch, BUFSIZ);
+		strlcat(buf, ")", BUFSIZ);
+	}
+
+	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
+	titlen = term_strlen(p, title);
+
+	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
+	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;
+
+	term_word(p, title);
+	term_flushln(p);
+
+	p->flags |= TERMP_NOSPACE;
+	p->offset = p->rmargin;
+	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
+	    p->maxrmargin - titlen : p->maxrmargin;
+
+	term_word(p, buf);
+	term_flushln(p);
+
+	p->flags &= ~TERMP_NOBREAK;
+	if (p->rmargin + titlen <= p->maxrmargin) {
+		p->flags |= TERMP_NOSPACE;
+		p->offset = p->rmargin;
+		p->rmargin = p->maxrmargin;
+		term_word(p, title);
+		term_flushln(p);
+	}
+
+	p->flags &= ~TERMP_NOSPACE;
+	p->offset = 0;
+	p->rmargin = p->maxrmargin;
+}
+
+
+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
+a2width(const struct termp *p, const char *v)
+{
+	struct roffsu	 su;
+
+	assert(v);
+	if ( ! a2roffsu(v, &su, SCALE_MAX))
+		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));
+
+	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)
+{
+	const struct mdoc_node	*nn;
+
+	assert(n);
+
+	term_newln(p);
+
+	if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
+		return;
+	if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
+		return;
+
+	/* 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)
+			return;
+		if (NULL == nn->prev)
+			continue;
+		break;
+	}
+
+	/* A `-column' does not assert vspace within the list. */
+
+	if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
+		if (n->prev && MDOC_It == n->prev->tok)
+			return;
+
+	/* A `-diag' without body does not vspace. */
+
+	if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
+		if (n->prev && MDOC_It == n->prev->tok) {
+			assert(n->prev->body);
+			if (NULL == n->prev->body->child)
+				return;
+		}
+
+	term_vspace(p);
+}
+
+
+/* ARGSUSED */
+static int
+termp_it_pre(DECL_ARGS)
+{
+	const struct mdoc_node *bl, *nn;
+	char		        buf[7];
+	int		        i;
+	size_t		        width, offset, ncols, dcol;
+	enum mdoc_list		type;
+
+	if (MDOC_BLOCK == n->type) {
+		print_bvspace(p, n->parent->parent, n);
+		return(1);
+	}
+
+	bl = n->parent->parent->parent;
+	type = bl->norm->Bl.type;
+
+	/* 
+	 * 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);
+
+	switch (type) {
+	case (LIST_column):
+		if (MDOC_HEAD == n->type)
+			break;
+
+		/*
+		 * Imitate groff's column handling:
+		 * - For each earlier column, add its width.
+		 * - For less than 5 columns, add four more blanks per
+		 *   column.
+		 * - For exactly 5 columns, add three more blank per
+		 *   column.
+		 * - 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);
+
+		/*
+		 * 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]);
+
+		/*
+		 * When exceeding the declared number of columns, leave
+		 * the remaining widths at 0.  This will later be
+		 * adjusted to the default width of 10, or, for the last
+		 * column, stretched to the right margin.
+		 */
+		if (i >= (int)ncols)
+			break;
+
+		/*
+		 * Use the declared column widths, extended as explained
+		 * in the preceding paragraph.
+		 */
+		width = a2width(p, bl->norm->Bl.cols[i]) + dcol;
+		break;
+	default:
+		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);
+		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):
+		if (width < term_len(p, 4))
+			width = term_len(p, 4);
+		break;
+	case (LIST_enum):
+		if (width < term_len(p, 5))
+			width = term_len(p, 5);
+		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.
+	 */
+
+	p->flags |= TERMP_NOSPACE;
+
+	switch (type) {
+	case (LIST_diag):
+		if (MDOC_BODY == n->type)
+			term_word(p, "\\ \\ ");
+		break;
+	case (LIST_inset):
+		if (MDOC_BODY == n->type) 
+			term_word(p, "\\ ");
+		break;
+	default:
+		break;
+	}
+
+	p->flags |= TERMP_NOSPACE;
+
+	switch (type) {
+	case (LIST_diag):
+		if (MDOC_HEAD == n->type)
+			term_fontpush(p, TERMFONT_BOLD);
+		break;
+	default:
+		break;
+	}
+
+	/*
+	 * Pad and break control.  This is the tricky part.  These flags
+	 * are documented in term_flushln() in term.c.  Note that we're
+	 * going to unset all of these flags in termp_it_post() when we
+	 * exit.
+	 */
+
+	switch (type) {
+	case (LIST_bullet):
+		/* FALLTHROUGH */
+	case (LIST_dash):
+		/* FALLTHROUGH */
+	case (LIST_enum):
+		/* FALLTHROUGH */
+	case (LIST_hyphen):
+		if (MDOC_HEAD == n->type)
+			p->flags |= TERMP_NOBREAK;
+		break;
+	case (LIST_hang):
+		if (MDOC_HEAD == n->type)
+			p->flags |= TERMP_NOBREAK;
+		else
+			break;
+
+		/*
+		 * This is ugly.  If `-hang' is specified and the body
+		 * is a `Bl' or `Bd', then we want basically to nullify
+		 * 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))
+			p->flags &= ~TERMP_NOBREAK;
+		else
+			p->flags |= TERMP_HANG;
+		break;
+	case (LIST_tag):
+		if (MDOC_HEAD == n->type)
+			p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE;
+
+		if (MDOC_HEAD != n->type)
+			break;
+		if (NULL == n->next || NULL == n->next->child)
+			p->flags |= TERMP_DANGLE;
+		break;
+	case (LIST_column):
+		if (MDOC_HEAD == n->type)
+			break;
+
+		if (NULL == n->next)
+			p->flags &= ~TERMP_NOBREAK;
+		else
+			p->flags |= TERMP_NOBREAK;
+
+		break;
+	case (LIST_diag):
+		if (MDOC_HEAD == n->type)
+			p->flags |= TERMP_NOBREAK;
+		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.
+	 */
+
+	p->offset += offset;
+
+	switch (type) {
+	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))
+			break;
+		/* FALLTHROUGH */
+	case (LIST_bullet):
+		/* FALLTHROUGH */
+	case (LIST_dash):
+		/* FALLTHROUGH */
+	case (LIST_enum):
+		/* FALLTHROUGH */
+	case (LIST_hyphen):
+		/* FALLTHROUGH */
+	case (LIST_tag):
+		assert(width);
+		if (MDOC_HEAD == n->type)
+			p->rmargin = p->offset + width;
+		else 
+			p->offset += width;
+		break;
+	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.
+		 */
+		if (MDOC_HEAD == n->type)
+			break;
+		if (NULL == n->next && p->rmargin < p->maxrmargin)
+			p->rmargin = p->maxrmargin;
+		break;
+	default:
+		break;
+	}
+
+	/* 
+	 * The dash, hyphen, bullet and enum lists all have a special
+	 * HEAD character (temporarily bold, in some cases).  
+	 */
+
+	if (MDOC_HEAD == n->type)
+		switch (type) {
+		case (LIST_bullet):
+			term_fontpush(p, TERMFONT_BOLD);
+			term_word(p, "\\[bu]");
+			term_fontpop(p);
+			break;
+		case (LIST_dash):
+			/* FALLTHROUGH */
+		case (LIST_hyphen):
+			term_fontpush(p, TERMFONT_BOLD);
+			term_word(p, "\\(hy");
+			term_fontpop(p);
+			break;
+		case (LIST_enum):
+			(pair->ppair->ppair->count)++;
+			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):
+		/* FALLTHROUGH */
+	case (LIST_item):
+		/* FALLTHROUGH */
+	case (LIST_dash):
+		/* FALLTHROUGH */
+	case (LIST_hyphen):
+		/* FALLTHROUGH */
+	case (LIST_enum):
+		if (MDOC_HEAD == n->type)
+			return(0);
+		break;
+	case (LIST_column):
+		if (MDOC_HEAD == n->type)
+			return(0);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_it_post(DECL_ARGS)
+{
+	enum mdoc_list	   type;
+
+	if (MDOC_BLOCK == n->type)
+		return;
+
+	type = n->parent->parent->parent->norm->Bl.type;
+
+	switch (type) {
+	case (LIST_item):
+		/* FALLTHROUGH */
+	case (LIST_diag):
+		/* FALLTHROUGH */
+	case (LIST_inset):
+		if (MDOC_BODY == n->type)
+			term_newln(p);
+		break;
+	case (LIST_column):
+		if (MDOC_BODY == n->type)
+			term_flushln(p);
+		break;
+	default:
+		term_newln(p);
+		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_TWOSPACE;
+	p->flags &= ~TERMP_HANG;
+}
+
+
+/* ARGSUSED */
+static int
+termp_nm_pre(DECL_ARGS)
+{
+
+	if (MDOC_BLOCK == n->type)
+		return(1);
+
+	if (MDOC_BODY == n->type) {
+		if (NULL == n->child)
+			return(0);
+		p->flags |= TERMP_NOSPACE;
+		p->offset += term_len(p, 1) +
+		    (NULL == n->prev->child ? term_strlen(p, m->name) :
+		     MDOC_TEXT == n->prev->child->type ?
+			term_strlen(p, n->prev->child->string) :
+		     term_len(p, 5));
+		return(1);
+	}
+
+	if (NULL == n->child && NULL == m->name)
+		return(0);
+
+	if (MDOC_HEAD == n->type)
+		synopsis_pre(p, n->parent);
+
+	if (MDOC_HEAD == n->type && n->next->child) {
+		p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+		p->rmargin = p->offset + term_len(p, 1);
+		if (NULL == n->child) {
+			p->rmargin += term_strlen(p, m->name);
+		} else if (MDOC_TEXT == n->child->type) {
+			p->rmargin += term_strlen(p, n->child->string);
+			if (n->child->next)
+				p->flags |= TERMP_HANG;
+		} else {
+			p->rmargin += term_len(p, 5);
+			p->flags |= TERMP_HANG;
+		}
+	}
+
+	term_fontpush(p, TERMFONT_BOLD);
+	if (NULL == n->child)
+		term_word(p, m->name);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_nm_post(DECL_ARGS)
+{
+
+	if (MDOC_HEAD == n->type && n->next->child) {
+		term_flushln(p);
+		p->flags &= ~(TERMP_NOBREAK | TERMP_HANG);
+	} else if (MDOC_BODY == n->type && n->child)
+		term_flushln(p);
+}
+
+		
+/* ARGSUSED */
+static int
+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)
+		p->flags |= TERMP_NOSPACE;
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+termp__a_pre(DECL_ARGS)
+{
+
+	if (n->prev && MDOC__A == n->prev->tok)
+		if (NULL == n->next || MDOC__A != n->next->tok)
+			term_word(p, "and");
+
+	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) {
+		p->flags &= ~TERMP_NOSPLIT;
+		p->flags |= TERMP_SPLIT;
+	} else if (AUTH_nosplit == n->norm->An.auth) {
+		p->flags &= ~TERMP_SPLIT;
+		p->flags |= TERMP_NOSPLIT;
+	}
+
+}
+
+
+/* ARGSUSED */
+static int
+termp_ns_pre(DECL_ARGS)
+{
+
+	if ( ! (MDOC_LINE & n->flags))
+		p->flags |= TERMP_NOSPACE;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+termp_rs_pre(DECL_ARGS)
+{
+
+	if (SEC_SEE_ALSO != n->sec)
+		return(1);
+	if (MDOC_BLOCK == n->type && n->prev)
+		term_vspace(p);
+	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);
+
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, "()");
+
+		if (nchild > 2 && n->next) {
+			p->flags |= TERMP_NOSPACE;
+			term_word(p, ",");
+		}
+
+		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");
+
+       	term_word(p, "the value 0 if successful; 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.");
+	p->flags |= TERMP_SENTENCE;
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_ex_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 > 2 && n->next) {
+			p->flags |= TERMP_NOSPACE;
+			term_word(p, ",");
+		}
+
+		if (n->next && NULL == n->next->next)
+			term_word(p, "and");
+	}
+
+	if (nchild > 1)
+		term_word(p, "utilities exit");
+	else
+		term_word(p, "utility exits");
+
+       	term_word(p, "0 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
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+termp_bl_pre(DECL_ARGS)
+{
+
+	return(MDOC_HEAD != n->type);
+}
+
+
+/* ARGSUSED */
+static void
+termp_bl_post(DECL_ARGS)
+{
+
+	if (MDOC_BLOCK == n->type)
+		term_newln(p);
+}
+
+/* ARGSUSED */
+static int
+termp_xr_pre(DECL_ARGS)
+{
+
+	if (NULL == (n = n->child))
+		return(0);
+
+	assert(MDOC_TEXT == n->type);
+	term_word(p, n->string);
+
+	if (NULL == (n = n->next)) 
+		return(0);
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, "(");
+	p->flags |= TERMP_NOSPACE;
+
+	assert(MDOC_TEXT == n->type);
+	term_word(p, n->string);
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, ")");
+
+	return(0);
+}
+
+/*
+ * This decides how to assert whitespace before any of the SYNOPSIS set
+ * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain
+ * macro combos).
+ */
+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.
+	 */
+	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
+		return;
+
+	/*
+	 * If we're the second in a pair of like elements, emit our
+	 * 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) {
+		term_newln(p);
+		return;
+	}
+
+	/*
+	 * If we're one of the SYNOPSIS set and non-like pair-wise after
+	 * another (or Fn/Fo, which we've let slip through) then assert
+	 * vertical space, else only newline and move on.
+	 */
+	switch (n->prev->tok) {
+	case (MDOC_Fd):
+		/* FALLTHROUGH */
+	case (MDOC_Fn):
+		/* FALLTHROUGH */
+	case (MDOC_Fo):
+		/* FALLTHROUGH */
+	case (MDOC_In):
+		/* FALLTHROUGH */
+	case (MDOC_Vt):
+		term_vspace(p);
+		break;
+	case (MDOC_Ft):
+		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
+			term_vspace(p);
+			break;
+		}
+		/* FALLTHROUGH */
+	default:
+		term_newln(p);
+		break;
+	}
+}
+
+
+static int
+termp_vt_pre(DECL_ARGS)
+{
+
+	if (MDOC_ELEM == n->type) {
+		synopsis_pre(p, n);
+		return(termp_under_pre(p, pair, m, n));
+	} else if (MDOC_BLOCK == n->type) {
+		synopsis_pre(p, n);
+		return(1);
+	} else if (MDOC_HEAD == n->type)
+		return(0);
+
+	return(termp_under_pre(p, pair, m, n));
+}
+
+
+/* ARGSUSED */
+static int
+termp_bold_pre(DECL_ARGS)
+{
+
+	term_fontpush(p, TERMFONT_BOLD);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+termp_fd_pre(DECL_ARGS)
+{
+
+	synopsis_pre(p, n);
+	return(termp_bold_pre(p, pair, m, n));
+}
+
+
+/* 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);
+		break;
+	case (MDOC_HEAD):
+		term_fontpush(p, TERMFONT_BOLD);
+		break;
+	case (MDOC_BODY):
+		p->offset = term_len(p, p->defindent);
+		break;
+	default:
+		break;
+	}
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_sh_post(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MDOC_HEAD):
+		term_newln(p);
+		break;
+	case (MDOC_BODY):
+		term_newln(p);
+		p->offset = 0;
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+termp_bt_pre(DECL_ARGS)
+{
+
+	term_word(p, "is currently in beta test.");
+	p->flags |= TERMP_SENTENCE;
+	return(0);
+}
+
+
+/* ARGSUSED */
+static void
+termp_lb_post(DECL_ARGS)
+{
+
+	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
+		term_newln(p);
+}
+
+
+/* ARGSUSED */
+static int
+termp_ud_pre(DECL_ARGS)
+{
+
+	term_word(p, "currently under development.");
+	p->flags |= TERMP_SENTENCE;
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_d1_pre(DECL_ARGS)
+{
+
+	if (MDOC_BLOCK != n->type)
+		return(1);
+	term_newln(p);
+	p->offset += term_len(p, p->defindent + 1);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_d1_post(DECL_ARGS)
+{
+
+	if (MDOC_BLOCK != n->type) 
+		return;
+	term_newln(p);
+}
+
+
+/* ARGSUSED */
+static int
+termp_ft_pre(DECL_ARGS)
+{
+
+	/* NB: MDOC_LINE does not effect this! */
+	synopsis_pre(p, n);
+	term_fontpush(p, TERMFONT_UNDER);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+termp_fn_pre(DECL_ARGS)
+{
+	int		 pretty;
+
+	pretty = MDOC_SYNPRETTY & n->flags;
+
+	synopsis_pre(p, n);
+
+	if (NULL == (n = n->child))
+		return(0);
+
+	assert(MDOC_TEXT == n->type);
+	term_fontpush(p, TERMFONT_BOLD);
+	term_word(p, n->string);
+	term_fontpop(p);
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, "(");
+	p->flags |= TERMP_NOSPACE;
+
+	for (n = n->next; n; n = n->next) {
+		assert(MDOC_TEXT == n->type);
+		term_fontpush(p, TERMFONT_UNDER);
+		term_word(p, n->string);
+		term_fontpop(p);
+
+		if (n->next) {
+			p->flags |= TERMP_NOSPACE;
+			term_word(p, ",");
+		}
+	}
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, ")");
+
+	if (pretty) {
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, ";");
+	}
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_fa_pre(DECL_ARGS)
+{
+	const struct mdoc_node	*nn;
+
+	if (n->parent->tok != MDOC_Fo) {
+		term_fontpush(p, TERMFONT_UNDER);
+		return(1);
+	}
+
+	for (nn = n->child; nn; nn = nn->next) {
+		term_fontpush(p, TERMFONT_UNDER);
+		term_word(p, nn->string);
+		term_fontpop(p);
+
+		if (nn->next) {
+			p->flags |= TERMP_NOSPACE;
+			term_word(p, ",");
+		}
+	}
+
+	if (n->child && n->next && n->next->tok == MDOC_Fa) {
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, ",");
+	}
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_bd_pre(DECL_ARGS)
+{
+	size_t			 tabwidth, rm, rmax;
+	const struct mdoc_node	*nn;
+
+	if (MDOC_BLOCK == n->type) {
+		print_bvspace(p, n, n);
+		return(1);
+	} else if (MDOC_HEAD == n->type)
+		return(0);
+
+	if (n->norm->Bd.offs)
+		p->offset += a2offs(p, n->norm->Bd.offs);
+
+	/*
+	 * If -ragged or -filled are specified, the block does nothing
+	 * but change the indentation.  If -unfilled or -literal are
+	 * specified, text is printed exactly as entered in the display:
+	 * 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)
+		return(1);
+
+	tabwidth = p->tabwidth;
+	if (DISP_literal == n->norm->Bd.type)
+		p->tabwidth = term_len(p, 8);
+
+	rm = p->rmargin;
+	rmax = p->maxrmargin;
+	p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
+
+	for (nn = n->child; nn; nn = nn->next) {
+		print_mdoc_node(p, pair, m, nn);
+		/*
+		 * If the printed node flushes its own line, then we
+		 * needn't do it here as well.  This is hacky, but the
+		 * notion of selective eoln whitespace is pretty dumb
+		 * anyway, so don't sweat it.
+		 */
+		switch (nn->tok) {
+		case (MDOC_Sm):
+			/* FALLTHROUGH */
+		case (MDOC_br):
+			/* FALLTHROUGH */
+		case (MDOC_sp):
+			/* FALLTHROUGH */
+		case (MDOC_Bl):
+			/* FALLTHROUGH */
+		case (MDOC_D1):
+			/* FALLTHROUGH */
+		case (MDOC_Dl):
+			/* FALLTHROUGH */
+		case (MDOC_Lp):
+			/* FALLTHROUGH */
+		case (MDOC_Pp):
+			continue;
+		default:
+			break;
+		}
+		if (nn->next && nn->next->line == nn->line)
+			continue;
+		term_flushln(p);
+		p->flags |= TERMP_NOSPACE;
+	}
+
+	p->tabwidth = tabwidth;
+	p->rmargin = rm;
+	p->maxrmargin = rmax;
+	return(0);
+}
+
+
+/* ARGSUSED */
+static void
+termp_bd_post(DECL_ARGS)
+{
+	size_t		 rm, rmax;
+
+	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)
+		p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
+
+	p->flags |= TERMP_NOSPACE;
+	term_newln(p);
+
+	p->rmargin = rm;
+	p->maxrmargin = rmax;
+}
+
+
+/* ARGSUSED */
+static int
+termp_bx_pre(DECL_ARGS)
+{
+
+	if (NULL != (n = n->child)) {
+		term_word(p, n->string);
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, "BSD");
+	} else {
+		term_word(p, "BSD");
+		return(0);
+	}
+
+	if (NULL != (n = n->next)) {
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, "-");
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, n->string);
+	}
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_xx_pre(DECL_ARGS)
+{
+	const char	*pp;
+	int		 flags;
+
+	pp = NULL;
+	switch (n->tok) {
+	case (MDOC_Bsx):
+		pp = "BSD/OS";
+		break;
+	case (MDOC_Dx):
+		pp = "DragonFly";
+		break;
+	case (MDOC_Fx):
+		pp = "FreeBSD";
+		break;
+	case (MDOC_Nx):
+		pp = "NetBSD";
+		break;
+	case (MDOC_Ox):
+		pp = "OpenBSD";
+		break;
+	case (MDOC_Ux):
+		pp = "UNIX";
+		break;
+	default:
+		break;
+	}
+
+	term_word(p, pp);
+	if (n->child) {
+		flags = p->flags;
+		p->flags |= TERMP_KEEP;
+		term_word(p, n->child->string);
+		p->flags = flags;
+	}
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_igndelim_pre(DECL_ARGS)
+{
+
+	p->flags |= TERMP_IGNDELIM;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_pf_post(DECL_ARGS)
+{
+
+	p->flags |= TERMP_NOSPACE;
+}
+
+
+/* ARGSUSED */
+static int
+termp_ss_pre(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MDOC_BLOCK):
+		term_newln(p);
+		if (n->prev)
+			term_vspace(p);
+		break;
+	case (MDOC_HEAD):
+		term_fontpush(p, TERMFONT_BOLD);
+		p->offset = term_len(p, (p->defindent+1)/2);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_ss_post(DECL_ARGS)
+{
+
+	if (MDOC_HEAD == n->type)
+		term_newln(p);
+}
+
+
+/* ARGSUSED */
+static int
+termp_cd_pre(DECL_ARGS)
+{
+
+	synopsis_pre(p, n);
+	term_fontpush(p, TERMFONT_BOLD);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+termp_in_pre(DECL_ARGS)
+{
+
+	synopsis_pre(p, n);
+
+	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
+		term_fontpush(p, TERMFONT_BOLD);
+		term_word(p, "#include");
+		term_word(p, "<");
+	} else {
+		term_word(p, "<");
+		term_fontpush(p, TERMFONT_UNDER);
+	}
+
+	p->flags |= TERMP_NOSPACE;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_in_post(DECL_ARGS)
+{
+
+	if (MDOC_SYNPRETTY & n->flags)
+		term_fontpush(p, TERMFONT_BOLD);
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, ">");
+
+	if (MDOC_SYNPRETTY & n->flags)
+		term_fontpop(p);
+}
+
+
+/* ARGSUSED */
+static int
+termp_sp_pre(DECL_ARGS)
+{
+	size_t		 i, len;
+
+	switch (n->tok) {
+	case (MDOC_sp):
+		len = n->child ? a2height(p, n->child->string) : 1;
+		break;
+	case (MDOC_br):
+		len = 0;
+		break;
+	default:
+		len = 1;
+		break;
+	}
+
+	if (0 == len)
+		term_newln(p);
+	for (i = 0; i < len; i++)
+		term_vspace(p);
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_quote_pre(DECL_ARGS)
+{
+
+	if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
+		return(1);
+
+	switch (n->tok) {
+	case (MDOC_Ao):
+		/* FALLTHROUGH */
+	case (MDOC_Aq):
+		term_word(p, "<");
+		break;
+	case (MDOC_Bro):
+		/* FALLTHROUGH */
+	case (MDOC_Brq):
+		term_word(p, "{");
+		break;
+	case (MDOC_Oo):
+		/* FALLTHROUGH */
+	case (MDOC_Op):
+		/* FALLTHROUGH */
+	case (MDOC_Bo):
+		/* FALLTHROUGH */
+	case (MDOC_Bq):
+		term_word(p, "[");
+		break;
+	case (MDOC_Do):
+		/* FALLTHROUGH */
+	case (MDOC_Dq):
+		term_word(p, "``");
+		break;
+	case (MDOC_Eo):
+		break;
+	case (MDOC_Po):
+		/* FALLTHROUGH */
+	case (MDOC_Pq):
+		term_word(p, "(");
+		break;
+	case (MDOC__T):
+		/* FALLTHROUGH */
+	case (MDOC_Qo):
+		/* FALLTHROUGH */
+	case (MDOC_Qq):
+		term_word(p, "\"");
+		break;
+	case (MDOC_Ql):
+		/* FALLTHROUGH */
+	case (MDOC_So):
+		/* FALLTHROUGH */
+	case (MDOC_Sq):
+		term_word(p, "`");
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	p->flags |= TERMP_NOSPACE;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_quote_post(DECL_ARGS)
+{
+
+	if (MDOC_BODY != n->type && MDOC_ELEM != n->type)
+		return;
+
+	p->flags |= TERMP_NOSPACE;
+
+	switch (n->tok) {
+	case (MDOC_Ao):
+		/* FALLTHROUGH */
+	case (MDOC_Aq):
+		term_word(p, ">");
+		break;
+	case (MDOC_Bro):
+		/* FALLTHROUGH */
+	case (MDOC_Brq):
+		term_word(p, "}");
+		break;
+	case (MDOC_Oo):
+		/* FALLTHROUGH */
+	case (MDOC_Op):
+		/* FALLTHROUGH */
+	case (MDOC_Bo):
+		/* FALLTHROUGH */
+	case (MDOC_Bq):
+		term_word(p, "]");
+		break;
+	case (MDOC_Do):
+		/* FALLTHROUGH */
+	case (MDOC_Dq):
+		term_word(p, "''");
+		break;
+	case (MDOC_Eo):
+		break;
+	case (MDOC_Po):
+		/* FALLTHROUGH */
+	case (MDOC_Pq):
+		term_word(p, ")");
+		break;
+	case (MDOC__T):
+		/* FALLTHROUGH */
+	case (MDOC_Qo):
+		/* FALLTHROUGH */
+	case (MDOC_Qq):
+		term_word(p, "\"");
+		break;
+	case (MDOC_Ql):
+		/* FALLTHROUGH */
+	case (MDOC_So):
+		/* FALLTHROUGH */
+	case (MDOC_Sq):
+		term_word(p, "'");
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+
+/* ARGSUSED */
+static int
+termp_fo_pre(DECL_ARGS)
+{
+
+	if (MDOC_BLOCK == n->type) {
+		synopsis_pre(p, n);
+		return(1);
+	} else if (MDOC_BODY == n->type) {
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, "(");
+		p->flags |= TERMP_NOSPACE;
+		return(1);
+	} 
+
+	if (NULL == n->child)
+		return(0);
+
+	/* XXX: we drop non-initial arguments as per groff. */
+
+	assert(n->child->string);
+	term_fontpush(p, TERMFONT_BOLD);
+	term_word(p, n->child->string);
+	return(0);
+}
+
+
+/* ARGSUSED */
+static void
+termp_fo_post(DECL_ARGS)
+{
+
+	if (MDOC_BODY != n->type) 
+		return;
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, ")");
+
+	if (MDOC_SYNPRETTY & n->flags) {
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, ";");
+	}
+}
+
+
+/* ARGSUSED */
+static int
+termp_bf_pre(DECL_ARGS)
+{
+
+	if (MDOC_HEAD == n->type)
+		return(0);
+	else if (MDOC_BLOCK != n->type)
+		return(1);
+
+	if (FONT_Em == n->norm->Bf.font) 
+		term_fontpush(p, TERMFONT_UNDER);
+	else if (FONT_Sy == n->norm->Bf.font) 
+		term_fontpush(p, TERMFONT_BOLD);
+	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;
+		p->flags &= ~TERMP_NONOSPACE;
+	} else
+		p->flags |= TERMP_NONOSPACE;
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_ap_pre(DECL_ARGS)
+{
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, "'");
+	p->flags |= TERMP_NOSPACE;
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp____post(DECL_ARGS)
+{
+
+	/*
+	 * Handle lists of authors.  In general, print each followed by
+	 * a comma.  Don't print the comma if there are only two
+	 * authors.
+	 */
+	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
+		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
+			if (NULL == n->prev || MDOC__A != n->prev->tok)
+				return;
+
+	/* TODO: %U. */
+
+	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
+		return;
+
+	p->flags |= TERMP_NOSPACE;
+	if (NULL == n->next) {
+		term_word(p, ".");
+		p->flags |= TERMP_SENTENCE;
+	} else
+		term_word(p, ",");
+}
+
+
+/* ARGSUSED */
+static int
+termp_li_pre(DECL_ARGS)
+{
+
+	term_fontpush(p, TERMFONT_NONE);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+termp_lk_pre(DECL_ARGS)
+{
+	const struct mdoc_node *nn, *sv;
+
+	term_fontpush(p, TERMFONT_UNDER);
+
+	nn = sv = n->child;
+
+	if (NULL == nn || NULL == nn->next)
+		return(1);
+
+	for (nn = nn->next; nn; nn = nn->next) 
+		term_word(p, nn->string);
+
+	term_fontpop(p);
+
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, ":");
+
+	term_fontpush(p, TERMFONT_BOLD);
+	term_word(p, sv->string);
+	term_fontpop(p);
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+termp_bk_pre(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MDOC_BLOCK):
+		break;
+	case (MDOC_HEAD):
+		return(0);
+	case (MDOC_BODY):
+		if (n->parent->args || 0 == n->prev->nchild)
+			p->flags |= TERMP_PREKEEP;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+termp_bk_post(DECL_ARGS)
+{
+
+	if (MDOC_BODY == n->type)
+		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
+}
+
+/* ARGSUSED */
+static void
+termp__t_post(DECL_ARGS)
+{
+
+	/*
+	 * If we're in an `Rs' and there's a journal present, then quote
+	 * us instead of underlining us (for disambiguation).
+	 */
+	if (n->parent && MDOC_Rs == n->parent->tok &&
+			n->parent->norm->Rs.quote_T)
+		termp_quote_post(p, pair, m, n);
+
+	termp____post(p, pair, m, n);
+}
+
+/* ARGSUSED */
+static int
+termp__t_pre(DECL_ARGS)
+{
+
+	/*
+	 * If we're in an `Rs' and there's a journal present, then quote
+	 * us instead of underlining us (for disambiguation).
+	 */
+	if (n->parent && MDOC_Rs == n->parent->tok &&
+			n->parent->norm->Rs.quote_T)
+		return(termp_quote_pre(p, pair, m, n));
+
+	term_fontpush(p, TERMFONT_UNDER);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+termp_under_pre(DECL_ARGS)
+{
+
+	term_fontpush(p, TERMFONT_UNDER);
+	return(1);
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/mdoc_validate.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,2403 @@
+/*	$Id: mdoc_validate.c,v 1.182 2012/03/23 05:50:25 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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
+
+#ifndef	OSNAME
+#include <sys/utsname.h>
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+/* FIXME: .Bl -diag can't have non-text children in HEAD. */
+
+#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);
+
+struct	valids {
+	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 *, 
+			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	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_dd(POST_ARGS);
+static	int	 post_dt(POST_ARGS);
+static	int	 post_defaults(POST_ARGS);
+static	int	 post_literal(POST_ARGS);
+static	int	 post_eoln(POST_ARGS);
+static	int	 post_it(POST_ARGS);
+static	int	 post_lb(POST_ARGS);
+static	int	 post_nm(POST_ARGS);
+static	int	 post_ns(POST_ARGS);
+static	int	 post_os(POST_ARGS);
+static	int	 post_ignpar(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_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_it[] = { post_it, NULL };
+static	v_post	 posts_lb[] = { post_lb, NULL };
+static	v_post	 posts_nd[] = { berr_ge1, 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_rs[] = { post_rs, NULL };
+static	v_post	 posts_sh[] = { post_ignpar, hwarn_ge1, post_sh, NULL };
+static	v_post	 posts_sp[] = { ewarn_le1, NULL };
+static	v_post	 posts_ss[] = { post_ignpar, hwarn_ge1, 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_post	 posts_wline[] = { bwarn_ge1, 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_er[] = { NULL, NULL };
+static	v_pre	 pres_fd[] = { NULL, 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	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_notext },		/* Pp */ 
+	{ pres_d1, posts_wline },		/* D1 */
+	{ pres_dl, posts_dl },			/* Dl */
+	{ pres_bd, posts_bd },			/* Bd */
+	{ NULL, NULL },				/* Ed */
+	{ pres_bl, posts_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 */ 
+	{ NULL, NULL },				/* Cm */
+	{ NULL, NULL },				/* Dv */ 
+	{ pres_er, NULL },			/* Er */ 
+	{ NULL, NULL },				/* Ev */ 
+	{ pres_std, posts_std },		/* Ex */ 
+	{ NULL, NULL },				/* Fa */ 
+	{ pres_fd, posts_text },		/* 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, NULL },				/* Op */
+	{ NULL, NULL },				/* Ot */
+	{ NULL, posts_defaults },		/* Pa */
+	{ pres_std, posts_std },		/* Rv */
+	{ NULL, posts_st },			/* St */ 
+	{ NULL, NULL },				/* Va */
+	{ NULL, posts_vt },			/* Vt */ 
+	{ NULL, posts_text },			/* Xr */ 
+	{ NULL, posts_text },			/* %A */
+	{ NULL, posts_text },			/* %B */ /* FIXME: can be used outside Rs/Re. */
+	{ NULL, posts_text },			/* %D */
+	{ NULL, posts_text },			/* %I */
+	{ NULL, posts_text },			/* %J */
+	{ NULL, posts_text },			/* %N */
+	{ NULL, posts_text },			/* %O */
+	{ NULL, posts_text },			/* %P */
+	{ NULL, posts_text },			/* %R */
+	{ NULL, posts_text },			/* %T */ /* FIXME: can be used outside Rs/Re. */
+	{ NULL, posts_text },			/* %V */
+	{ NULL, NULL },				/* Ac */
+	{ NULL, NULL },				/* Ao */
+	{ NULL, NULL },				/* Aq */
+	{ NULL, posts_at },			/* At */ 
+	{ NULL, NULL },				/* Bc */
+	{ NULL, posts_bf },			/* Bf */
+	{ NULL, NULL },				/* Bo */
+	{ NULL, NULL },				/* Bq */
+	{ NULL, NULL },				/* Bsx */
+	{ NULL, posts_bx },			/* Bx */
+	{ NULL, posts_bool },			/* Db */
+	{ NULL, NULL },				/* Dc */
+	{ NULL, NULL },				/* Do */
+	{ NULL, NULL },				/* Dq */
+	{ NULL, NULL },				/* Ec */
+	{ 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 },				/* Nx */
+	{ NULL, NULL },				/* Ox */
+	{ NULL, NULL },				/* Pc */
+	{ NULL, posts_text1 },			/* Pf */
+	{ NULL, NULL },				/* Po */
+	{ NULL, NULL },				/* Pq */
+	{ NULL, NULL },				/* Qc */
+	{ NULL, NULL },				/* Ql */
+	{ NULL, NULL },				/* Qo */
+	{ NULL, NULL },				/* Qq */
+	{ NULL, NULL },				/* Re */
+	{ NULL, posts_rs },			/* Rs */
+	{ NULL, NULL },				/* Sc */
+	{ NULL, NULL },				/* So */
+	{ NULL, NULL },				/* Sq */
+	{ NULL, posts_bool },			/* Sm */ 
+	{ NULL, NULL },				/* Sx */
+	{ NULL, NULL },				/* Sy */
+	{ NULL, NULL },				/* Tn */
+	{ NULL, NULL },				/* Ux */
+	{ NULL, NULL },				/* Xc */
+	{ NULL, NULL },				/* Xo */
+	{ NULL, posts_fo },			/* Fo */ 
+	{ NULL, NULL },				/* Fc */ 
+	{ NULL, NULL },				/* Oo */
+	{ NULL, NULL },				/* Oc */
+	{ NULL, posts_bk },			/* Bk */
+	{ NULL, NULL },				/* Ek */
+	{ NULL, posts_eoln },			/* Bt */
+	{ NULL, NULL },				/* Hf */
+	{ NULL, NULL },				/* Fr */
+	{ NULL, posts_eoln },			/* Ud */
+	{ NULL, posts_lb },			/* Lb */
+	{ NULL, posts_notext },			/* 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 */
+	{ NULL, NULL },				/* Dx */
+	{ NULL, posts_text },			/* %Q */
+	{ NULL, posts_notext },			/* br */
+	{ pres_pp, posts_sp },			/* sp */
+	{ NULL, posts_text1 },			/* %U */
+	{ NULL, NULL },				/* Ta */
+};
+
+#define	RSORD_MAX 14 /* Number of `Rs' blocks. */
+
+static	const enum mdoct rsord[RSORD_MAX] = {
+	MDOC__A,
+	MDOC__T,
+	MDOC__B,
+	MDOC__I,
+	MDOC__J,
+	MDOC__R,
+	MDOC__N,
+	MDOC__V,
+	MDOC__P,
+	MDOC__Q,
+	MDOC__D,
+	MDOC__O,
+	MDOC__C,
+	MDOC__U
+};
+
+static	const char * const secnames[SEC__MAX] = {
+	NULL,
+	"NAME",
+	"LIBRARY",
+	"SYNOPSIS",
+	"DESCRIPTION",
+	"IMPLEMENTATION NOTES",
+	"RETURN VALUES",
+	"ENVIRONMENT",
+	"FILES",
+	"EXIT STATUS",
+	"EXAMPLES",
+	"DIAGNOSTICS",
+	"COMPATIBILITY",
+	"ERRORS",
+	"SEE ALSO",
+	"STANDARDS",
+	"HISTORY",
+	"AUTHORS",
+	"CAVEATS",
+	"BUGS",
+	"SECURITY CONSIDERATIONS",
+	NULL
+};
+
+int
+mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
+{
+	v_pre		*p;
+	int		 line, pos;
+	char		*tp;
+
+	switch (n->type) {
+	case (MDOC_TEXT):
+		tp = n->string;
+		line = n->line;
+		pos = n->pos;
+		check_text(mdoc, line, pos, tp);
+		/* FALLTHROUGH */
+	case (MDOC_TBL):
+		/* FALLTHROUGH */
+	case (MDOC_EQN):
+		/* FALLTHROUGH */
+	case (MDOC_ROOT):
+		return(1);
+	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);
+}
+
+
+int
+mdoc_valid_post(struct mdoc *mdoc)
+{
+	v_post		*p;
+
+	if (MDOC_VALID & mdoc->last->flags)
+		return(1);
+	mdoc->last->flags |= MDOC_VALID;
+
+	switch (mdoc->last->type) {
+	case (MDOC_TEXT):
+		/* FALLTHROUGH */
+	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 *m, enum mdoc_type type, 
+		enum check_lvl lvl, enum check_ineq ineq, int val)
+{
+	const char	*p;
+	enum mandocerr	 t;
+
+	if (m->last->type != type)
+		return(1);
+	
+	switch (ineq) {
+	case (CHECK_LT):
+		p = "less than ";
+		if (m->last->nchild < val)
+			return(1);
+		break;
+	case (CHECK_GT):
+		p = "more than ";
+		if (m->last->nchild > val)
+			return(1);
+		break;
+	case (CHECK_EQ):
+		p = "";
+		if (val == m->last->nchild)
+			return(1);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	t = lvl == CHECK_WARN ? MANDOCERR_ARGCWARN : MANDOCERR_ARGCOUNT;
+	mandoc_vmsg(t, m->parse, m->last->line, m->last->pos,
+			"want %s%d children (have %d)",
+			p, val, m->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));
+}
+
+static int
+hwarn_eq1(POST_ARGS)
+{
+	return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 1));
+}
+
+static int
+hwarn_ge1(POST_ARGS)
+{
+	return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_GT, 0));
+}
+
+static int
+hwarn_le1(POST_ARGS)
+{
+	return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_LT, 2));
+}
+
+static void
+check_args(struct mdoc *m, struct mdoc_node *n)
+{
+	int		 i;
+
+	if (NULL == n->args)
+		return;
+
+	assert(n->args->argc);
+	for (i = 0; i < (int)n->args->argc; i++)
+		check_argv(m, n, &n->args->argv[i]);
+}
+
+static void
+check_argv(struct mdoc *m, struct mdoc_node *n, struct mdoc_argv *v)
+{
+	int		 i;
+
+	for (i = 0; i < (int)v->sz; i++)
+		check_text(m, v->line, v->pos, v->value[i]);
+
+	/* FIXME: move to post_std(). */
+
+	if (MDOC_Std == v->arg)
+		if ( ! (v->sz || m->meta.name))
+			mdoc_nmsg(m, n, MANDOCERR_NONAME);
+}
+
+static void
+check_text(struct mdoc *m, int ln, int pos, char *p)
+{
+	char		*cp;
+
+	if (MDOC_LITERAL & m->flags)
+		return;
+
+	for (cp = p; NULL != (p = strchr(p, '\t')); p++)
+		mdoc_pmsg(m, ln, pos + (int)(p - cp), MANDOCERR_BADTAB);
+}
+
+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 ? 
+			"<root>" : mdoc_macronames[tok]);
+	return(0);
+}
+
+
+static int
+pre_display(PRE_ARGS)
+{
+	struct mdoc_node *node;
+
+	if (MDOC_BLOCK != n->type)
+		return(1);
+
+	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);
+}
+
+
+static int
+pre_bl(PRE_ARGS)
+{
+	int		  i, comp, dup;
+	const char	 *offs, *width;
+	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);
+	}
+
+	/* 
+	 * 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 */
+	for (i = 0; n->args && i < (int)n->args->argc; i++) {
+		lt = LIST__NONE;
+		dup = comp = 0;
+		width = offs = NULL;
+		switch (n->args->argv[i].arg) {
+		/* Set list types. */
+		case (MDOC_Bullet):
+			lt = LIST_bullet;
+			break;
+		case (MDOC_Dash):
+			lt = LIST_dash;
+			break;
+		case (MDOC_Enum):
+			lt = LIST_enum;
+			break;
+		case (MDOC_Hyphen):
+			lt = LIST_hyphen;
+			break;
+		case (MDOC_Item):
+			lt = LIST_item;
+			break;
+		case (MDOC_Tag):
+			lt = LIST_tag;
+			break;
+		case (MDOC_Diag):
+			lt = LIST_diag;
+			break;
+		case (MDOC_Hang):
+			lt = LIST_hang;
+			break;
+		case (MDOC_Ohang):
+			lt = LIST_ohang;
+			break;
+		case (MDOC_Inset):
+			lt = LIST_inset;
+			break;
+		case (MDOC_Column):
+			lt = LIST_column;
+			break;
+		/* Set list arguments. */
+		case (MDOC_Compact):
+			dup = n->norm->Bl.comp;
+			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);
+				break;
+			}
+			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+			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);
+				break;
+			}
+			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+			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;
+
+		/* 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;
+			}
+		}
+
+		/* 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;
+	}
+
+	/* Allow lists to default to LIST_item. */
+
+	if (LIST__NONE == n->norm->Bl.type) {
+		mdoc_nmsg(mdoc, n, MANDOCERR_LISTTYPE);
+		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.
+	 */
+
+	switch (n->norm->Bl.type) {
+	case (LIST_tag):
+		if (n->norm->Bl.width)
+			break;
+		mdoc_nmsg(mdoc, n, MANDOCERR_NOWIDTHARG);
+		break;
+	case (LIST_column):
+		/* FALLTHROUGH */
+	case (LIST_diag):
+		/* FALLTHROUGH */
+	case (LIST_ohang):
+		/* FALLTHROUGH */
+	case (LIST_inset):
+		/* FALLTHROUGH */
+	case (LIST_item):
+		if (n->norm->Bl.width)
+			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+static int
+pre_bd(PRE_ARGS)
+{
+	int		  i, dup, comp;
+	enum mdoc_disp 	  dt;
+	const char	 *offs;
+	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_Bd == np->tok);
+		return(1);
+	}
+
+	/* LINTED */
+	for (i = 0; n->args && i < (int)n->args->argc; i++) {
+		dt = DISP__NONE;
+		dup = comp = 0;
+		offs = NULL;
+
+		switch (n->args->argv[i].arg) {
+		case (MDOC_Centred):
+			dt = DISP_centred;
+			break;
+		case (MDOC_Ragged):
+			dt = DISP_ragged;
+			break;
+		case (MDOC_Unfilled):
+			dt = DISP_unfilled;
+			break;
+		case (MDOC_Filled):
+			dt = DISP_filled;
+			break;
+		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);
+				break;
+			}
+			mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV);
+			break;
+		case (MDOC_Compact):
+			comp = 1;
+			dup = n->norm->Bd.comp;
+			break;
+		default:
+			abort();
+			/* NOTREACHED */
+		}
+
+		/* 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)
+			n->norm->Bd.type = dt;
+	}
+
+	if (DISP__NONE == n->norm->Bd.type) {
+		mdoc_nmsg(mdoc, n, MANDOCERR_DISPTYPE);
+		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);
+
+	roff_regunset(mdoc->roff, REG_nS);
+	return(check_parent(mdoc, n, MDOC_MAX, MDOC_ROOT));
+}
+
+
+static int
+pre_it(PRE_ARGS)
+{
+
+	if (MDOC_BLOCK != n->type)
+		return(1);
+
+	return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY));
+}
+
+
+static int
+pre_an(PRE_ARGS)
+{
+	int		 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 (MDOC_Split == n->args->argv[0].arg)
+		n->norm->An.auth = AUTH_split;
+	else if (MDOC_Nosplit == n->args->argv[0].arg)
+		n->norm->An.auth = AUTH_nosplit;
+	else
+		abort();
+
+	return(1);
+}
+
+static int
+pre_std(PRE_ARGS)
+{
+
+	if (n->args && 1 == n->args->argc)
+		if (MDOC_Std == n->args->argv[0].arg)
+			return(1);
+
+	mdoc_nmsg(mdoc, n, MANDOCERR_NOARGV);
+	return(1);
+}
+
+static int
+pre_dt(PRE_ARGS)
+{
+
+	if (NULL == mdoc->meta.date || mdoc->meta.os)
+		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO);
+
+	if (mdoc->meta.title)
+		mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP);
+
+	return(1);
+}
+
+static int
+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);
+}
+
+static int
+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);
+}
+
+
+static int
+post_bf(POST_ARGS)
+{
+	struct mdoc_node *np;
+	enum mdocargt	  arg;
+
+	/*
+	 * Unlike other data pointers, these are "housed" by the HEAD
+	 * 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;
+	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. 
+	 */
+
+	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);
+	}
+
+	/* Extract argument into data. */
+	
+	if (np->parent->args) {
+		arg = np->parent->args->argv[0].arg;
+		if (MDOC_Emphasis == arg)
+			np->norm->Bf.font = FONT_Em;
+		else if (MDOC_Literal == arg)
+			np->norm->Bf.font = FONT_Li;
+		else if (MDOC_Symbolic == arg)
+			np->norm->Bf.font = FONT_Sy;
+		else
+			abort();
+		return(1);
+	}
+
+	/* Extract parameter into data. */
+
+	if (0 == strcmp(np->child->string, "Em"))
+		np->norm->Bf.font = FONT_Em;
+	else if (0 == strcmp(np->child->string, "Li"))
+		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);
+}
+
+static int
+post_lb(POST_ARGS)
+{
+	const char	*p;
+	char		*buf;
+	size_t		 sz;
+
+	check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1);
+
+	assert(mdoc->last->child);
+	assert(MDOC_TEXT == mdoc->last->child->type);
+
+	p = mdoc_a2lib(mdoc->last->child->string);
+
+	/* If lookup ok, replace with table value. */
+
+	if (p) {
+		free(mdoc->last->child->string);
+		mdoc->last->child->string = mandoc_strdup(p);
+		return(1);
+	}
+
+	/* If not, use "library ``xxxx''. */
+
+	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);
+}
+
+static int
+post_eoln(POST_ARGS)
+{
+
+	if (mdoc->last->child)
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST);
+	return(1);
+}
+
+
+static int
+post_vt(POST_ARGS)
+{
+	const struct mdoc_node *n;
+
+	/*
+	 * The Vt macro comes in both ELEM and BLOCK form, both of which
+	 * have different syntaxes (yet more context-sensitive
+	 * behaviour).  ELEM types must have a child, which is already
+	 * guaranteed by the in_line parsing routine; BLOCK types,
+	 * specifically the BODY, should only have TEXT children.
+	 */
+
+	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(1);
+}
+
+
+static int
+post_nm(POST_ARGS)
+{
+	char		 buf[BUFSIZ];
+	int		 c;
+
+	/* If no child specified, make sure we have the meta name. */
+
+	if (NULL == mdoc->last->child && NULL == mdoc->meta.name) {
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME);
+		return(1);
+	} else if (mdoc->meta.name)
+		return(1);
+
+	/* If no meta name, set it from the child. */
+
+	buf[0] = '\0';
+	if (-1 == (c = concat(buf, mdoc->last->child, BUFSIZ))) {
+		mdoc_nmsg(mdoc, mdoc->last->child, MANDOCERR_MEM);
+		return(0);
+	}
+
+	assert(c);
+	mdoc->meta.name = mandoc_strdup(buf);
+	return(1);
+}
+
+static int
+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.
+	 */
+
+	if (MDOC_BODY == mdoc->last->type)
+		mdoc->flags &= ~MDOC_LITERAL;
+
+	return(1);
+}
+
+static int
+post_defaults(POST_ARGS)
+{
+	struct mdoc_node *nn;
+
+	/*
+	 * The `Ar' defaults to "file ..." if no value is provided as an
+	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
+	 * gets an empty string.
+	 */
+
+	if (mdoc->last->child)
+		return(1);
+	
+	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);
+		break;
+	case (MDOC_Pa):
+		/* FALLTHROUGH */
+	case (MDOC_Mt):
+		if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "~"))
+			return(0);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	} 
+
+	mdoc->last = nn;
+	return(1);
+}
+
+static int
+post_at(POST_ARGS)
+{
+	const char	 *p, *q;
+	char		 *buf;
+	size_t		  sz;
+
+	/*
+	 * 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;
+	}
+
+	return(1);
+}
+
+static int
+post_an(POST_ARGS)
+{
+	struct mdoc_node *np;
+
+	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);
+
+	return(1);
+}
+
+
+static int
+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);
+
+	n = mdoc->last->parent->parent;
+	lt = n->norm->Bl.type;
+
+	if (LIST__NONE == lt) {
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_LISTTYPE);
+		return(1);
+	}
+
+	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):
+		/* FALLTHROUGH */
+	case (LIST_ohang):
+		/* FALLTHROUGH */
+	case (LIST_inset):
+		/* FALLTHROUGH */
+	case (LIST_diag):
+		if (NULL == mdoc->last->head->child)
+			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS);
+		break;
+	case (LIST_bullet):
+		/* FALLTHROUGH */
+	case (LIST_dash):
+		/* FALLTHROUGH */
+	case (LIST_enum):
+		/* FALLTHROUGH */
+	case (LIST_hyphen):
+		if (NULL == mdoc->last->body->child)
+			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY);
+		/* FALLTHROUGH */
+	case (LIST_item):
+		if (mdoc->last->head->child)
+			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST);
+		break;
+	case (LIST_column):
+		cols = (int)n->norm->Bl.ncols;
+
+		assert(NULL == mdoc->last->head->child);
+
+		if (NULL == mdoc->last->body->child)
+			mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY);
+
+		for (i = 0, c = mdoc->last->child; c; c = c->next)
+			if (MDOC_BODY == c->type)
+				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:
+		break;
+	}
+
+	return(1);
+}
+
+static int
+post_bl_block(POST_ARGS) 
+{
+	struct mdoc_node *n;
+
+	/*
+	 * These are fairly complicated, so we've broken them into two
+	 * functions.  post_bl_block_tag() is called when a -tag is
+	 * specified, but no -width (it must be guessed).  The second
+	 * when a -width is specified (macro indicators must be
+	 * rewritten into real lengths).
+	 */
+
+	n = mdoc->last;
+
+	if (LIST_tag == n->norm->Bl.type && 
+			NULL == n->norm->Bl.width) {
+		if ( ! post_bl_block_tag(mdoc))
+			return(0);
+	} else if (NULL != n->norm->Bl.width) {
+		if ( ! post_bl_block_width(mdoc))
+			return(0);
+	} else 
+		return(1);
+
+	assert(n->norm->Bl.width);
+	return(1);
+}
+
+static int
+post_bl_block_width(POST_ARGS)
+{
+	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"))
+		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);
+
+	/* Set our width! */
+	n->norm->Bl.width = n->args->argv[i].value[0];
+	return(1);
+}
+
+static int
+post_bl_block_tag(POST_ARGS)
+{
+	struct mdoc_node *n, *nn;
+	size_t		  sz, ssz;
+	int		  i;
+	char		  buf[NUMSIZ];
+
+	/*
+	 * 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.
+	 */
+
+	sz = 10;
+	n = mdoc->last;
+
+	for (nn = n->body->child; nn; nn = nn->next) {
+		if (MDOC_It != nn->tok)
+			continue;
+
+		assert(MDOC_BLOCK == nn->type);
+		nn = nn->head->child;
+
+		if (nn == NULL)
+			break;
+
+		if (MDOC_TEXT == nn->type) {
+			sz = strlen(nn->string) + 1;
+			break;
+		}
+
+		if (0 != (ssz = macro2len(nn->tok)))
+			sz = ssz;
+
+		break;
+	} 
+
+	/* Defaults to ten ens. */
+
+	snprintf(buf, NUMSIZ, "%un", (unsigned int)sz);
+
+	/*
+	 * We have to dynamically add this to the macro's argument list.
+	 * We're guaranteed that a MDOC_Width doesn't already exist.
+	 */
+
+	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[i].arg = MDOC_Width;
+	n->args->argv[i].line = n->line;
+	n->args->argv[i].pos = n->pos;
+	n->args->argv[i].sz = 1;
+	n->args->argv[i].value = mandoc_malloc(sizeof(char *));
+	n->args->argv[i].value[0] = mandoc_strdup(buf);
+
+	/* Set our width! */
+	n->norm->Bl.width = n->args->argv[i].value[0];
+	return(1);
+}
+
+
+static int
+post_bl_head(POST_ARGS) 
+{
+	struct mdoc_node *np, *nn, *nnp;
+	int		  i, j;
+
+	if (LIST_column != mdoc->last->norm->Bl.type)
+		/* FIXME: this should be ERROR class... */
+		return(hwarn_eq0(mdoc));
+
+	/*
+	 * Convert 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);
+
+	for (j = 0; j < (int)np->args->argc; j++) 
+		if (MDOC_Column == np->args->argv[j].arg)
+			break;
+
+	assert(j < (int)np->args->argc);
+	assert(0 == np->args->argv[j].sz);
+
+	/*
+	 * Accommodate for new-style groff column syntax.  Shuffle the
+	 * child nodes, all of which must be TEXT, as arguments for the
+	 * 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 *));
+
+	mdoc->last->norm->Bl.ncols = np->args->argv[j].sz;
+	mdoc->last->norm->Bl.cols = (void *)np->args->argv[j].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);
+	}
+
+	mdoc->last->nchild = 0;
+	mdoc->last->child = NULL;
+
+	return(1);
+}
+
+static int
+post_bl(POST_ARGS)
+{
+	struct mdoc_node	*n;
+
+	if (MDOC_HEAD == mdoc->last->type) 
+		return(post_bl_head(mdoc));
+	if (MDOC_BLOCK == mdoc->last->type)
+		return(post_bl_block(mdoc));
+	if (MDOC_BODY != mdoc->last->type)
+		return(1);
+
+	for (n = mdoc->last->child; n; n = n->next) {
+		switch (n->tok) {
+		case (MDOC_Lp):
+			/* FALLTHROUGH */
+		case (MDOC_Pp):
+			mdoc_nmsg(mdoc, n, MANDOCERR_CHILD);
+			/* FALLTHROUGH */
+		case (MDOC_It):
+			/* FALLTHROUGH */
+		case (MDOC_Sm):
+			continue;
+		default:
+			break;
+		}
+
+		mdoc_nmsg(mdoc, n, MANDOCERR_SYNTCHILD);
+		return(0);
+	}
+
+	return(1);
+}
+
+static int
+ebool(struct mdoc *mdoc)
+{
+
+	if (NULL == mdoc->last->child) {
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY);
+		mdoc_node_delete(mdoc, mdoc->last);
+		return(1);
+	}
+	check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1);
+
+	assert(MDOC_TEXT == mdoc->last->child->type);
+
+	if (0 == strcmp(mdoc->last->child->string, "on"))
+		return(1);
+	if (0 == strcmp(mdoc->last->child->string, "off"))
+		return(1);
+
+	mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADBOOL);
+	return(1);
+}
+
+static int
+post_root(POST_ARGS)
+{
+	int		  erc;
+	struct mdoc_node *n;
+
+	erc = 0;
+
+	/* Check that we have a finished prologue. */
+
+	if ( ! (MDOC_PBODY & mdoc->flags)) {
+		erc++;
+		mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCPROLOG);
+	}
+
+	n = mdoc->first;
+	assert(n);
+	
+	/* Check that we begin with a proper `Sh'. */
+
+	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);
+	}
+
+	return(erc ? 0 : 1);
+}
+
+static int
+post_st(POST_ARGS)
+{
+	struct mdoc_node	 *ch;
+	const char		 *p;
+
+	if (NULL == (ch = mdoc->last->child)) {
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY);
+		mdoc_node_delete(mdoc, mdoc->last);
+		return(1);
+	}
+
+	assert(MDOC_TEXT == ch->type);
+
+	if (NULL == (p = mdoc_a2st(ch->string))) {
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADSTANDARD);
+		mdoc_node_delete(mdoc, mdoc->last);
+	} else {
+		free(ch->string);
+		ch->string = mandoc_strdup(p);
+	}
+
+	return(1);
+}
+
+static int
+post_rs(POST_ARGS)
+{
+	struct mdoc_node *nn, *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;
+
+		if (i < RSORD_MAX) {
+			if (MDOC__J == rsord[i] || MDOC__B == rsord[i])
+				mdoc->last->norm->Rs.quote_T++;
+			next = nn->next;
+			continue;
+		}
+
+		next = nn->next;
+		mdoc_nmsg(mdoc, nn, MANDOCERR_CHILD);
+		mdoc_node_delete(mdoc, nn);
+	}
+
+	/*
+	 * 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.
+	 */
+
+	next = NULL;
+	for (nn = mdoc->last->child->next; nn; nn = next) {
+		/* Determine order of `nn'. */
+		for (i = 0; i < RSORD_MAX; i++)
+			if (rsord[i] == nn->tok)
+				break;
+
+		/* 
+		 * Remove `nn' 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;
+
+		nn->prev = nn->next = NULL;
+
+		/* 
+		 * Scan back until we reach a node that's
+		 * ordered before `nn'.
+		 */
+
+		for ( ; prev ; prev = prev->prev) {
+			/* Determine order of `prev'. */
+			for (j = 0; j < RSORD_MAX; j++)
+				if (rsord[j] == prev->tok)
+					break;
+
+			if (j <= i)
+				break;
+		}
+
+		/*
+		 * Set `nn' back into its correct place in front
+		 * of the `prev' node.
+		 */
+
+		nn->prev = prev;
+
+		if (prev) {
+			if (prev->next)
+				prev->next->prev = nn;
+			nn->next = prev->next;
+			prev->next = nn;
+		} else {
+			mdoc->last->child->prev = nn;
+			nn->next = mdoc->last->child;
+			mdoc->last->child = nn;
+		}
+	}
+
+	return(1);
+}
+
+static int
+post_ns(POST_ARGS)
+{
+
+	if (MDOC_LINE & mdoc->last->flags)
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNNS);
+	return(1);
+}
+
+static int
+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));
+
+	return(1);
+}
+
+static int
+post_sh_body(POST_ARGS)
+{
+	struct mdoc_node *n;
+
+	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".
+	 */
+
+	if (NULL == (n = mdoc->last->child)) {
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
+		return(1);
+	}
+
+	for ( ; n && n->next; n = n->next) {
+		if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
+			continue;
+		if (MDOC_TEXT == n->type)
+			continue;
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
+	}
+
+	assert(n);
+	if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
+		return(1);
+
+	mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC);
+	return(1);
+}
+
+static int
+post_sh_head(POST_ARGS)
+{
+	char		 buf[BUFSIZ];
+	struct mdoc_node *n;
+	enum mdoc_sec	 sec;
+	int		 c;
+
+	/*
+	 * Process a new section.  Sections are either "named" or
+	 * "custom".  Custom sections are user-defined, while named ones
+	 * follow a conventional order and may only appear in certain
+	 * manual sections.
+	 */
+
+	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);
+
+	/* The NAME should be first. */
+
+	if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST);
+
+	/* The SYNOPSIS gets special attention in other areas. */
+
+	if (SEC_SYNOPSIS == sec)
+		mdoc->flags |= MDOC_SYNOPSIS;
+	else
+		mdoc->flags &= ~MDOC_SYNOPSIS;
+
+	/* Mark our last section. */
+
+	mdoc->lastsec = sec;
+
+	/*
+	 * Set the section attribute for the current HEAD, for its
+	 * parent BLOCK, and for the HEAD children; the latter can
+	 * only be TEXT nodes, so no recursion is needed.
+	 * For other blocks and elements, including .Sh BODY, this is
+	 * done when allocating the node data structures, but for .Sh
+	 * BLOCK and HEAD, the section is still unknown at that time.
+	 */
+
+	mdoc->last->parent->sec = sec;
+	mdoc->last->sec = sec;
+	for (n = mdoc->last->child; n; n = n->next)
+		n->sec = sec;
+
+	/* We don't care about custom sections after this. */
+
+	if (SEC_CUSTOM == sec)
+		return(1);
+
+	/*
+	 * Check whether our non-custom section is being repeated or is
+	 * out of order.
+	 */
+
+	if (sec == mdoc->lastnamed)
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP);
+
+	if (sec < mdoc->lastnamed)
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO);
+
+	/* Mark the last named section. */
+
+	mdoc->lastnamed = sec;
+
+	/* Check particular section/manual conventions. */
+
+	assert(mdoc->meta.msec);
+
+	switch (sec) {
+	case (SEC_RETURN_VALUES):
+		/* FALLTHROUGH */
+	case (SEC_ERRORS):
+		/* FALLTHROUGH */
+	case (SEC_LIBRARY):
+		if (*mdoc->meta.msec == '2')
+			break;
+		if (*mdoc->meta.msec == '3')
+			break;
+		if (*mdoc->meta.msec == '9')
+			break;
+		mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECMSEC);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+static int
+post_ignpar(POST_ARGS)
+{
+	struct mdoc_node *np;
+
+	if (MDOC_BODY != mdoc->last->type)
+		return(1);
+
+	if (NULL != (np = mdoc->last->child))
+		if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) {
+			mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR);
+			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);
+			mdoc_node_delete(mdoc, np);
+		}
+
+	return(1);
+}
+
+static int
+pre_par(PRE_ARGS)
+{
+
+	if (NULL == mdoc->last)
+		return(1);
+	if (MDOC_ELEM != n->type && MDOC_BLOCK != n->type)
+		return(1);
+
+	/* 
+	 * Don't allow prior `Lp' or `Pp' prior to a paragraph-type
+	 * block:  `Lp', `Pp', or non-compact `Bd' or `Bl'.
+	 */
+
+	if (MDOC_Pp != mdoc->last->tok && MDOC_Lp != mdoc->last->tok)
+		return(1);
+	if (MDOC_Bl == n->tok && n->norm->Bl.comp)
+		return(1);
+	if (MDOC_Bd == n->tok && n->norm->Bd.comp)
+		return(1);
+	if (MDOC_It == n->tok && n->parent->norm->Bl.comp)
+		return(1);
+
+	mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR);
+	mdoc_node_delete(mdoc, mdoc->last);
+	return(1);
+}
+
+static int
+pre_literal(PRE_ARGS)
+{
+
+	if (MDOC_BODY != n->type)
+		return(1);
+
+	/*
+	 * The `Dl' (note "el" not "one") and `Bd -literal' and `Bd
+	 * -unfilled' macros set MDOC_LITERAL on entrance to the body.
+	 */
+
+	switch (n->tok) {
+	case (MDOC_Dl):
+		mdoc->flags |= MDOC_LITERAL;
+		break;
+	case (MDOC_Bd):
+		if (DISP_literal == n->norm->Bd.type)
+			mdoc->flags |= MDOC_LITERAL;
+		if (DISP_unfilled == n->norm->Bd.type)
+			mdoc->flags |= MDOC_LITERAL;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+	
+	return(1);
+}
+
+static int
+post_dd(POST_ARGS)
+{
+	char		  buf[DATESIZE];
+	struct mdoc_node *n;
+	int		  c;
+
+	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);
+	}
+
+	buf[0] = '\0';
+	if (-1 == (c = concat(buf, n->child, DATESIZE))) {
+		mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM);
+		return(0);
+	}
+
+	assert(c);
+	mdoc->meta.date = mandoc_normdate
+		(mdoc->parse, buf, n->line, n->pos);
+
+	return(1);
+}
+
+static int
+post_dt(POST_ARGS)
+{
+	struct mdoc_node *nn, *n;
+	const char	 *cp;
+	char		 *p;
+
+	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);
+
+	mdoc->meta.title = mdoc->meta.vol = 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;
+
+			/* 
+			 * FIXME: don't be lazy: have this make all
+			 * characters be uppercase and just warn once.
+			 */
+			mdoc_nmsg(mdoc, nn, MANDOCERR_UPPERCASE);
+			break;
+		}
+
+	/* Handles: `.Dt' 
+	 *   --> title = unknown, volume = local, msec = 0, arch = NULL
+	 */
+
+	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);
+	}
+
+	/* Handles: `.Dt TITLE' 
+	 *   --> title = TITLE, volume = local, msec = 0, arch = NULL
+	 */
+
+	mdoc->meta.title = mandoc_strdup
+		('\0' == nn->string[0] ? "UNKNOWN" : nn->string);
+
+	if (NULL == (nn = nn->next)) {
+		/* FIXME: warn about missing msec. */
+		/* XXX: make this a macro value. */
+		mdoc->meta.vol = mandoc_strdup("LOCAL");
+		mdoc->meta.msec = mandoc_strdup("1");
+		return(1);
+	}
+
+	/* Handles: `.Dt TITLE SEC'
+	 *   --> title = TITLE, volume = SEC is msec ? 
+	 *           format(msec) : SEC,
+	 *       msec = SEC is msec ? atoi(msec) : 0,
+	 *       arch = NULL
+	 */
+
+	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);
+		mdoc->meta.vol = mandoc_strdup(nn->string);
+		mdoc->meta.msec = mandoc_strdup(nn->string);
+	} 
+
+	if (NULL == (nn = nn->next))
+		return(1);
+
+	/* Handles: `.Dt TITLE SEC VOL'
+	 *   --> title = TITLE, volume = VOL is vol ?
+	 *       format(VOL) : 
+	 *           VOL is arch ? format(arch) : 
+	 *               VOL
+	 */
+
+	cp = mdoc_a2vol(nn->string);
+	if (cp) {
+		free(mdoc->meta.vol);
+		mdoc->meta.vol = mandoc_strdup(cp);
+	} else {
+		/* FIXME: warn about bad arch. */
+		cp = mdoc_a2arch(nn->string);
+		if (NULL == cp) {
+			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);
+}
+
+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.
+	 */
+
+	mdoc_node_delete(mdoc, mdoc->last);
+	if (mdoc->meta.title && mdoc->meta.date && mdoc->meta.os)
+		mdoc->flags |= MDOC_PBODY;
+
+	return(1);
+}
+
+static int
+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.
+	 */
+
+	n = mdoc->last->child;
+	if (n && NULL != (n = n->next))
+		*n->string = (char)toupper
+			((unsigned char)*n->string);
+
+	return(1);
+}
+
+static int
+post_os(POST_ARGS)
+{
+	struct mdoc_node *n;
+	char		  buf[BUFSIZ];
+	int		  c;
+#ifndef OSNAME
+	struct utsname	  utsname;
+#endif
+
+	n = mdoc->last;
+
+	/*
+	 * Set the operating system by way of the `Os' macro.  Note that
+	 * if an argument isn't provided and -DOSNAME="\"foo\"" is
+	 * provided during compilation, this value will be used instead
+	 * of filling in "sysname release" from uname().
+ 	 */
+
+	if (mdoc->meta.os)
+		free(mdoc->meta.os);
+
+	buf[0] = '\0';
+	if (-1 == (c = concat(buf, n->child, BUFSIZ))) {
+		mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM);
+		return(0);
+	}
+
+	assert(c);
+
+	/* XXX: yes, these can all be dynamically-adjusted buffers, but
+	 * it's really not worth the extra hackery.
+	 */
+
+	if ('\0' == buf[0]) {
+#ifdef OSNAME
+		if (strlcat(buf, OSNAME, BUFSIZ) >= BUFSIZ) {
+			mdoc_nmsg(mdoc, n, MANDOCERR_MEM);
+			return(0);
+		}
+#else /*!OSNAME */
+		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*/
+	}
+
+	mdoc->meta.os = mandoc_strdup(buf);
+	return(1);
+}
+
+static int
+post_std(POST_ARGS)
+{
+	struct mdoc_node *nn, *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)
+{
+
+	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);
+	}
+
+	return(1);
+}
+
+static enum mdoc_sec 
+a2sec(const char *p)
+{
+	int		 i;
+
+	for (i = 0; i < (int)SEC__MAX; i++) 
+		if (secnames[i] && 0 == strcmp(p, secnames[i]))
+			return((enum mdoc_sec)i);
+
+	return(SEC_CUSTOM);
+}
+
+static size_t
+macro2len(enum mdoct macro)
+{
+
+	switch (macro) {
+	case(MDOC_Ad):
+		return(12);
+	case(MDOC_Ao):
+		return(12);
+	case(MDOC_An):
+		return(12);
+	case(MDOC_Aq):
+		return(12);
+	case(MDOC_Ar):
+		return(12);
+	case(MDOC_Bo):
+		return(12);
+	case(MDOC_Bq):
+		return(12);
+	case(MDOC_Cd):
+		return(12);
+	case(MDOC_Cm):
+		return(10);
+	case(MDOC_Do):
+		return(10);
+	case(MDOC_Dq):
+		return(12);
+	case(MDOC_Dv):
+		return(12);
+	case(MDOC_Eo):
+		return(12);
+	case(MDOC_Em):
+		return(10);
+	case(MDOC_Er):
+		return(17);
+	case(MDOC_Ev):
+		return(15);
+	case(MDOC_Fa):
+		return(12);
+	case(MDOC_Fl):
+		return(10);
+	case(MDOC_Fo):
+		return(16);
+	case(MDOC_Fn):
+		return(16);
+	case(MDOC_Ic):
+		return(10);
+	case(MDOC_Li):
+		return(16);
+	case(MDOC_Ms):
+		return(6);
+	case(MDOC_Nm):
+		return(10);
+	case(MDOC_No):
+		return(12);
+	case(MDOC_Oo):
+		return(10);
+	case(MDOC_Op):
+		return(14);
+	case(MDOC_Pa):
+		return(32);
+	case(MDOC_Pf):
+		return(12);
+	case(MDOC_Po):
+		return(12);
+	case(MDOC_Pq):
+		return(12);
+	case(MDOC_Ql):
+		return(16);
+	case(MDOC_Qo):
+		return(12);
+	case(MDOC_So):
+		return(12);
+	case(MDOC_Sq):
+		return(12);
+	case(MDOC_Sy):
+		return(6);
+	case(MDOC_Sx):
+		return(16);
+	case(MDOC_Tn):
+		return(10);
+	case(MDOC_Va):
+		return(12);
+	case(MDOC_Vt):
+		return(12);
+	case(MDOC_Xr):
+		return(10);
+	default:
+		break;
+	};
+	return(0);
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/msec.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,37 @@
+/*	$Id: msec.c,v 1.10 2011/12/02 01:37:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+
+#define LINE(x, y) \
+	if (0 == strcmp(p, x)) return(y);
+
+const char *
+mandoc_a2msec(const char *p)
+{
+
+#include "msec.in"
+
+	return(NULL);
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/msec.in	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,244 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+LINE("1",		"User Commands")
+LINE("1B",		"illumos/BSD Compatibility Package Commands")
+LINE("1b",		"illumos/BSD Compatibility Package Commands")
+LINE("1C",		"Communication Commands")
+LINE("1c",		"Communication Commands")
+LINE("1F",		"FMLI Commands")
+LINE("1f",		"FMLI Commands")
+LINE("1G",		"Graphics and CAD Commands")
+LINE("1g",		"Graphics and CAD Commands")
+LINE("1HAS",		"User Commands")
+LINE("1has",		"User Commands")
+LINE("1M",		"Maintenance Commands")
+LINE("1m",		"Maintenance Commands")
+LINE("1S",		"illumos Specific Commands")
+LINE("1s",		"illumos Specific Commands")
+LINE("2",		"System Calls")
+LINE("3",		"Introduction to Library Functions")
+LINE("3AIO",		"Asynchronous I/O Library Functions")
+LINE("3aio",		"Asynchronous I/O Library Functions")
+LINE("3BSM",		"Security and Auditing Library Functions")
+LINE("3bsm",		"Security and Auditing Library Functions")
+LINE("3C",		"Standard C Library Functions")
+LINE("3c",		"Standard C Library Functions")
+LINE("3C_DB",		"Threads Debugging Library Functions")
+LINE("3C_db",		"Threads Debugging Library Functions")
+LINE("3CFGADM",		"Configuration Administration Library Functions")
+LINE("3cfgadm",		"Configuration Administration Library Functions")
+LINE("3COMPPUTIL",	"Communication Protocol Parser Utilities Library Functions")
+LINE("3compputil",	"Communication Protocol Parser Utilities Library Functions")
+LINE("3CONTRACT",	"Contract Management Library Functions")
+LINE("3contract",	"Contract Management Library Functions")
+LINE("3CPC",		"CPU Performance Counters Library Functions")
+LINE("3cpc",		"CPU Performance Counters Library Functions")
+LINE("3CURSES",		"Curses Library Functions")
+LINE("3curses",		"Curses Library Functions")
+LINE("3DAT",		"Direct Access Transport Library Functions")
+LINE("3dat",		"Direct Access Transport Library Functions")
+LINE("3DEVID",		"Device ID Library Functions")
+LINE("3devid",		"Device ID Library Functions")
+LINE("3DEVINFO",	"Device Information Library Functions")
+LINE("3devinfo",	"Device Information Library Functions")
+LINE("3DL",		"Dynamic Linking Library Functions")
+LINE("3dl",		"Dynamic Linking Library Functions")
+LINE("3DLPI",		"Data Link Provider Interface Library Functions")
+LINE("3dlpi",		"Data Link Provider Interface Library Functions")
+LINE("3DMI",		"DMI Library Functions")
+LINE("3dmi",		"DMI Library Functions")
+LINE("3DNS_SD",		"DNS Service Discovery Library Functions")
+LINE("3dns_sd",		"DNS Service Discovery Library Functions")
+LINE("3DOOR",		"Door Library Functions")
+LINE("3door",		"Door Library Functions")
+LINE("3ELF",		"ELF Library Functions")
+LINE("3elf",		"ELF Library Functions")
+LINE("3EXACCT",		"Extended Accounting File Access Library Functions")
+LINE("3exacct",		"Extended Accounting File Access Library Functions")
+LINE("3EXT",		"Extended Library Functions")
+LINE("3ext",		"Extended Library Functions")
+LINE("3FCOE",		"FCoE Port Management Library Functions")
+LINE("3fcoe",		"FCoE Port Management Library Functions")
+LINE("3FSTYP",		"File System Type Identification Library Functions")
+LINE("3fstyp",		"File System Type Identification Library Functions")
+LINE("3GEN",		"String Pattern-Matching Library Functions")
+LINE("3gen",		"String Pattern-Matching Library Functions")
+LINE("3GSS",		"Generic Security Services API Library Functions")
+LINE("3gss",		"Generic Security Services API Library Functions")
+LINE("3HEAD",		"Headers")
+LINE("3head",		"Headers")
+LINE("3ISCSIT",		"iSCSI Management Library Functions")
+LINE("3iscsit",		"iSCSI Management Library Functions")
+LINE("3KRB",		"Kerberos Library Functions")
+LINE("3krb",		"Kerberos Library Functions")
+LINE("3KSTAT",		"Kernel Statistics Library Functions")
+LINE("3kstat",		"Kernel Statistics Library Functions")
+LINE("3KVM",		"Kernel VM Library Functions")
+LINE("3kvm",		"Kernel VM Library Functions")
+LINE("3LDAP",		"LDAP Library Functions")
+LINE("3ldap",		"LDAP Library Functions")
+LINE("3LGRP",		"Locality Group Library Functions")
+LINE("3lgrp",		"Locality Group Library Functions")
+LINE("3LIB",		"Interface Libraries")
+LINE("3lib",		"Interface Libraries")
+LINE("3LIBUCB",		"illumos/BSD Compatibility Interface Libraries")
+LINE("3libucb",		"illumos/BSD Compatibility Interface Libraries")
+LINE("3M",		"Mathematical Library Functions")
+LINE("3m",		"Mathematical Library Functions")
+LINE("3MAIL",		"User Mailbox Library Functions")
+LINE("3mail",		"User Mailbox Library Functions")
+LINE("3MALLOC",		"Memory Allocation Library Functions")
+LINE("3malloc",		"Memory Allocation Library Functions")
+LINE("3MP",		"Multiple Precision Library Functions")
+LINE("3mp",		"Multiple Precision Library Functions")
+LINE("3MPAPI",		"Common Multipath Management Library Functions")
+LINE("3mpapi",		"Common Multipath Management Library Functions")
+LINE("3NSL",		"Networking Services Library Functions")
+LINE("3nsl",		"Networking Services Library Functions")
+LINE("3NVPAIR",		"Name-value Pair Library Functions")
+LINE("3nvpair",		"Name-value Pair Library Functions")
+LINE("3PAM",		"PAM Library Functions")
+LINE("3pam",		"PAM Library Functions")
+LINE("3PAPI",		"PAPI Library Functions")
+LINE("3papi",		"PAPI Library Functions")
+LINE("3PERL",		"Perl Library Functions")
+LINE("3perl",		"Perl Library Functions")
+LINE("3PICL",		"PICL Library Functions")
+LINE("3picl",		"PICL Library Functions")
+LINE("3PICLTREE",	"PICL Plug-In Library Functions")
+LINE("3picltree",	"PICL Plug-In Library Functions")
+LINE("3PLOT",		"Graphics Interface Library Functions")
+LINE("3plot",		"Graphics Interface Library Functions")
+LINE("3POOL",		"Pool Configuration Manipulation Library Functions")
+LINE("3pool",		"Pool Configuration Manipulation Library Functions")
+LINE("3PROC",		"Process Control Library Functions")
+LINE("3proc",		"Process Control Library Functions")
+LINE("3PROJECT",	"Project Database Access Library Functions")
+LINE("3project",	"Project Database Access Library Functions")
+LINE("3RAC",		"Remote Asynchronous Calls Library Functions")
+LINE("3rac",		"Remote Asynchronous Calls Library Functions")
+LINE("3RESOLV",		"Resolver Library Functions")
+LINE("3resolv",		"Resolver Library Functions")
+LINE("3RPC",		"RPC Library Functions")
+LINE("3rpc",		"RPC Library Functions")
+LINE("3RSM",		"Remote Shared Memory Library Functions")
+LINE("3rsm",		"Remote Shared Memory Library Functions")
+LINE("3RT",		"Realtime Library Functions")
+LINE("3rt",		"Realtime Library Functions")
+LINE("3SASL",		"Simple Authentication Security Layer Library Functions")
+LINE("3sasl",		"Simple Authentication Security Layer Library Functions")
+LINE("3SCF",		"Service Configuration Facility Library Functions")
+LINE("3scf",		"Service Configuration Facility Library Functions")
+LINE("3SCHED",		"LWP Scheduling Library Functions")
+LINE("3sched",		"LWP Scheduling Library Functions")
+LINE("3SEC",		"File Access Control Library Functions")
+LINE("3sec",		"File Access Control Library Functions")
+LINE("3SECDB",		"Security Attributes Database Library Functions")
+LINE("3secdb",		"Security Attributes Database Library Functions")
+LINE("3SIP",		"Session Initiation Protocol Library Functions")
+LINE("3sip",		"Session Initiation Protocol Library Functions")
+LINE("3SLP",		"Service Location Protocol Library Functions")
+LINE("3slp",		"Service Location Protocol Library Functions")
+LINE("3SNMP",		"SNMP Library Functions")
+LINE("3snmp",		"SNMP Library Functions")
+LINE("3SOCKET",		"Sockets Library Functions")
+LINE("3socket",		"Sockets Library Functions")
+LINE("3STMF",		"SCSI Target Mode Framework Library Functions")
+LINE("3stmf",		"SCSI Target Mode Framework Library Functions")
+LINE("3SYSEVENT",	"System Event Library Functions")
+LINE("3sysevent",	"System Event Library Functions")
+LINE("3TECLA",		"Interactive Command-line Input Library Functions")
+LINE("3tecla",		"Interactive Command-line Input Library Functions")
+LINE("3THR",		"Threads Library Functions")
+LINE("3thr",		"Threads Library Functions")
+LINE("3TNF",		"TNF Library Functions")
+LINE("3tnf",		"TNF Library Functions")
+LINE("3TSOL",		"Trusted Extensions Library Functions")
+LINE("3tsol",		"Trusted Extensions Library Functions")
+LINE("3UCB",		"illumos/BSD Compatibility Library Functions")
+LINE("3ucb",		"illumos/BSD Compatibility Library Functions")
+LINE("3UUID",		"Universally Unique Identifier Library Functions")
+LINE("3uuid",		"Universally Unique Identifier Library Functions")
+LINE("3VOLMGT",		"Volume Management Library Functions")
+LINE("3volmgt",		"Volume Management Library Functions")
+LINE("3XCURSES",	"X/Open Curses Library Functions")
+LINE("3xcurses",	"X/Open Curses Library Functions")
+LINE("3XFN",		"XFN Interface Library Functions")
+LINE("3xfn",		"XFN Interface Library Functions")
+LINE("3XNET",		"X/Open Networking Services Library Functions")
+LINE("3xnet",		"X/Open Networking Services Library Functions")
+LINE("3B",		"illumos/BSD Compatibility Library Functions")
+LINE("3b",		"illumos/BSD Compatibility Library Functions")
+LINE("3E",		"C Library Functions")
+LINE("3e",		"C Library Functions")
+LINE("3F",		"Fortran Library Routines")
+LINE("3f",		"Fortran Library Routines")
+LINE("3G",		"C Library Functions")
+LINE("3g",		"C Library Functions")
+LINE("3K",		"Kernel VM Library Functions")
+LINE("3k",		"Kernel VM Library Functions")
+LINE("3L",		"Lightweight Processes Library")
+LINE("3l",		"Lightweight Processes Library")
+LINE("3N",		"Network Functions")
+LINE("3n",		"Network Functions")
+LINE("3R",		"Realtime Library")
+LINE("3r",		"Realtime Library")
+LINE("3S",		"Standard I/O Functions")
+LINE("3s",		"Standard I/O Functions")
+LINE("3T",		"Threads Library")
+LINE("3t",		"Threads Library")
+LINE("3W",		"C Library Functions")
+LINE("3w",		"C Library Functions")
+LINE("3X",		"Miscellaneous Library Functions")
+LINE("3x",		"Miscellaneous Library Functions")
+LINE("3XC",		"X/Open Curses Library Functions")
+LINE("3xc",		"X/Open Curses Library Functions")
+LINE("3XN",		"X/Open Networking Services Library Functions")
+LINE("3xn",		"X/Open Networking Services Library Functions")
+LINE("4",		"File Formats")
+LINE("4B",		"illumos/BSD Compatibility Package File Formats")
+LINE("4b",		"illumos/BSD Compatibility Package File Formats")
+LINE("5",		"Standards, Environments, and Macros")
+LINE("6",		"Games and Demos")
+LINE("7",		"Device and Network Interfaces")
+LINE("7B",		"illumos/BSD Compatibility Special Files")
+LINE("7b",		"illumos/BSD Compatibility Special Files")
+LINE("7D",		"Devices")
+LINE("7d",		"Devices")
+LINE("7FS",		"File Systems")
+LINE("7fs",		"File Systems")
+LINE("7I",		"Ioctl Requests")
+LINE("7i",		"Ioctl Requests")
+LINE("7IPP",		"IP Quality of Service Modules")
+LINE("7ipp",		"IP Quality of Service Modules")
+LINE("7M",		"STREAMS Modules")
+LINE("7m",		"STREAMS Modules")
+LINE("7P",		"Protocols")
+LINE("7p",		"Protocols")
+LINE("8",		"Maintenance Procedures")
+LINE("8C",		"Maintenance Procedures")
+LINE("8c",		"Maintenance Procedures")
+LINE("8S",		"Maintenance Procedures")
+LINE("8s",		"Maintenance Procedures")
+LINE("9",		"Device Driver Interfaces")
+LINE("9E",		"Driver Entry Points")
+LINE("9e",		"Driver Entry Points")
+LINE("9F",		"Kernel Functions for Drivers")
+LINE("9f",		"Kernel Functions for Drivers")
+LINE("9P",		"Kernel Properties for Drivers")
+LINE("9p",		"Kernel Properties for Drivers")
+LINE("9S",		"Data Structures for Drivers")
+LINE("9s",		"Data Structures for Drivers")
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/out.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,303 @@
+/*	$Id: out.c,v 1.43 2011/09/20 23:05:49 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "out.h"
+
+static	void	tblcalc_data(struct rofftbl *, struct roffcol *,
+			const struct tbl *, const struct tbl_dat *);
+static	void	tblcalc_literal(struct rofftbl *, struct roffcol *,
+			const struct tbl_dat *);
+static	void	tblcalc_number(struct rofftbl *, struct roffcol *,
+			const struct tbl *, 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.
+ */
+int
+a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
+{
+	char		 buf[BUFSIZ], hasd;
+	int		 i;
+	enum roffscale	 unit;
+
+	if ('\0' == *src)
+		return(0);
+
+	i = hasd = 0;
+
+	switch (*src) {
+	case ('+'):
+		src++;
+		break;
+	case ('-'):
+		buf[i++] = *src++;
+		break;
+	default:
+		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;
+		break;
+	case ('i'):
+		unit = SCALE_IN;
+		break;
+	case ('P'):
+		unit = SCALE_PC;
+		break;
+	case ('p'):
+		unit = SCALE_PT;
+		break;
+	case ('f'):
+		unit = SCALE_FS;
+		break;
+	case ('v'):
+		unit = SCALE_VS;
+		break;
+	case ('m'):
+		unit = SCALE_EM;
+		break;
+	case ('\0'):
+		if (SCALE_MAX == def)
+			return(0);
+		unit = SCALE_BU;
+		break;
+	case ('u'):
+		unit = SCALE_BU;
+		break;
+	case ('M'):
+		unit = SCALE_MM;
+		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);
+}
+
+/*
+ * Calculate the abstract widths and decimal positions of columns in a
+ * table.  This routine allocates the columns structures then runs over
+ * all rows and cells in the table.  The function pointers in "tbl" are
+ * used for the actual width calculations.
+ */
+void
+tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
+{
+	const struct tbl_dat	*dp;
+	const struct tbl_head	*hp;
+	struct roffcol		*col;
+	int			 spans;
+
+	/*
+	 * Allocate the master column specifiers.  These will hold the
+	 * widths and decimal positions for all cells in the column.  It
+	 * must be freed and nullified by the caller.
+	 */
+
+	assert(NULL == tbl->cols);
+	tbl->cols = mandoc_calloc
+		((size_t)sp->tbl->cols, sizeof(struct roffcol));
+
+	hp = sp->head;
+
+	for ( ; sp; sp = sp->next) {
+		if (TBL_SPAN_DATA != sp->pos)
+			continue;
+		spans = 1;
+		/*
+		 * Account for the data cells in the layout, matching it
+		 * to data cells in the data section.
+		 */
+		for (dp = sp->first; dp; dp = dp->next) {
+			/* Do not used spanned cells in the calculation. */
+			if (0 < --spans)
+				continue;
+			spans = dp->spans;
+			if (1 < spans)
+				continue;
+			assert(dp->layout);
+			col = &tbl->cols[dp->layout->head->ident];
+			tblcalc_data(tbl, col, sp->tbl, dp);
+		}
+	}
+
+	/* 
+	 * Calculate width of the spanners.  These get one space for a
+	 * vertical line, two for a double-vertical line. 
+	 */
+
+	for ( ; hp; hp = hp->next) {
+		col = &tbl->cols[hp->ident];
+		switch (hp->pos) {
+		case (TBL_HEAD_VERT):
+			col->width = (*tbl->len)(1, tbl->arg);
+			break;
+		case (TBL_HEAD_DVERT):
+			col->width = (*tbl->len)(2, tbl->arg);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static void
+tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
+		const struct tbl *tp, const struct tbl_dat *dp)
+{
+	size_t		 sz;
+
+	/* Branch down into data sub-types. */
+
+	switch (dp->layout->pos) {
+	case (TBL_CELL_HORIZ):
+		/* FALLTHROUGH */
+	case (TBL_CELL_DHORIZ):
+		sz = (*tbl->len)(1, tbl->arg);
+		if (col->width < sz)
+			col->width = sz;
+		break;
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		/* FALLTHROUGH */
+	case (TBL_CELL_LEFT):
+		/* FALLTHROUGH */
+	case (TBL_CELL_RIGHT):
+		tblcalc_literal(tbl, col, dp);
+		break;
+	case (TBL_CELL_NUMBER):
+		tblcalc_number(tbl, col, tp, dp);
+		break;
+	case (TBL_CELL_DOWN):
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+static void
+tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
+		const struct tbl_dat *dp)
+{
+	size_t		 sz;
+	const char	*str;
+
+	str = dp->string ? dp->string : "";
+	sz = (*tbl->slen)(str, tbl->arg);
+
+	if (col->width < sz)
+		col->width = sz;
+}
+
+static void
+tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
+		const struct tbl *tp, const struct tbl_dat *dp)
+{
+	int 		 i;
+	size_t		 sz, psz, ssz, d;
+	const char	*str;
+	char		*cp;
+	char		 buf[2];
+
+	/*
+	 * First calculate number width and decimal place (last + 1 for
+	 * non-decimal numbers).  If the stored decimal is subsequent to
+	 * ours, make our size longer by that difference
+	 * (right-"shifting"); similarly, if ours is subsequent the
+	 * stored, then extend the stored size by the difference.
+	 * Finally, re-assign the stored values.
+	 */
+
+	str = dp->string ? dp->string : "";
+	sz = (*tbl->slen)(str, tbl->arg);
+
+	/* FIXME: TBL_DATA_HORIZ et al.? */
+
+	buf[0] = tp->decimal;
+	buf[1] = '\0';
+
+	psz = (*tbl->slen)(buf, tbl->arg);
+
+	if (NULL != (cp = strrchr(str, tp->decimal))) {
+		buf[1] = '\0';
+		for (ssz = 0, i = 0; cp != &str[i]; i++) {
+			buf[0] = str[i];
+			ssz += (*tbl->slen)(buf, tbl->arg);
+		}
+		d = ssz + psz;
+	} else
+		d = sz + psz;
+
+	/* Adjust the settings for this column. */
+
+	if (col->decimal > d) {
+		sz += col->decimal - d;
+		d = col->decimal;
+	} else
+		col->width += d - col->decimal;
+
+	if (sz > col->width)
+		col->width = sz;
+	if (d > col->decimal)
+		col->decimal = d;
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/out.h	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,71 @@
+/*	$Id: out.h,v 1.21 2011/07/17 15:24:25 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef OUT_H
+#define OUT_H
+
+enum	roffscale {
+	SCALE_CM, /* centimeters (c) */
+	SCALE_IN, /* inches (i) */
+	SCALE_PC, /* pica (P) */
+	SCALE_PT, /* points (p) */
+	SCALE_EM, /* ems (m) */
+	SCALE_MM, /* mini-ems (M) */
+	SCALE_EN, /* ens (n) */
+	SCALE_BU, /* default horizontal (u) */
+	SCALE_VS, /* default vertical (v) */
+	SCALE_FS, /* syn. for u (f) */
+	SCALE_MAX
+};
+
+struct	roffcol {
+	size_t		 width; /* width of cell */
+	size_t		 decimal; /* decimal position in cell */
+};
+
+struct	roffsu {
+	enum roffscale	  unit;
+	double		  scale;
+};
+
+typedef	size_t	(*tbl_strlen)(const char *, void *);
+typedef	size_t	(*tbl_len)(size_t, void *);
+
+struct	rofftbl {
+	tbl_strlen	 slen; /* calculate string length */
+	tbl_len		 len; /* produce width of empty space */
+	struct roffcol	*cols; /* master column specifiers */
+	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; \
+	     (p)->scale = (v); } \
+	while (/* CONSTCOND */ 0)
+
+int	  	  a2roffsu(const char *, struct roffsu *, enum roffscale);
+void	  	  tblcalc(struct rofftbl *tbl, const struct tbl_span *);
+
+__END_DECLS
+
+#endif /*!OUT_H*/
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/preconv.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,528 @@
+/*	$Id: preconv.c,v 1.5 2011/07/24 18:15:14 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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
+
+#ifdef HAVE_MMAP
+#include <sys/stat.h>
+#include <sys/mman.h>
+#endif
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* 
+ * The read_whole_file() and resize_buf() functions are copied from
+ * read.c, including all dependency code (MAP_FILE, etc.).
+ */
+
+#ifndef MAP_FILE
+#define	MAP_FILE	0
+#endif
+
+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		 state, be;
+	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);
+	}
+
+	if (0 != state) {
+		/* Bad trailing bits. */
+		return(0);
+	}
+
+	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);
+	}
+}
+
+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);
+	}
+
+	/*
+	 * 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.
+	 */
+
+	if (S_ISREG(st.st_mode) && st.st_size >= (1U << 31)) {
+		fprintf(stderr, "%s: input too large\n", f);
+		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_FILE|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);
+
+		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);
+}
+
+static int
+cue_enc(const struct buf *b, size_t *offs, enum enc *enc)
+{
+	const char	*ln, *eoln, *eoph;
+	size_t		 sz, phsz, nsz;
+	int		 i;
+
+	ln = b->buf + (int)*offs;
+	sz = b->sz - *offs;
+
+	/* 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);
+
+	/* Check if we have the correct header/trailer. */
+
+	if ((sz = (size_t)(eoln - ln)) < 10 || 
+			memcmp(ln, ".\\\" -*-", 7) ||
+			memcmp(eoln - 3, "-*-", 3))
+		return(0);
+
+	/* Move after the header and adjust for the trailer. */
+
+	ln += 7;
+	sz -= 10;
+
+	while (sz > 0) {
+		while (sz > 0 && ' ' == *ln) {
+			ln++;
+			sz--;
+		}
+		if (0 == sz)
+			break;
+
+		/* Find the end-of-phrase marker (or eoln). */
+
+		if (NULL == (eoph = memchr(ln, ';', sz)))
+			eoph = eoln - 3;
+		else
+			eoph++;
+
+		/* Only account for the "coding" phrase. */
+
+		if ((phsz = (size_t)(eoph - ln)) < 7 ||
+				strncasecmp(ln, "coding:", 7)) {
+			sz -= phsz;
+			ln += phsz;
+			continue;
+		} 
+
+		sz -= 7;
+		ln += 7;
+
+		while (sz > 0 && ' ' == *ln) {
+			ln++;
+			sz--;
+		}
+		if (0 == sz)
+			break;
+
+		/* 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 = "<stdin>";
+	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;
+	}
+
+	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);
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/predefs.in	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,65 @@
+/*	$Id: predefs.in,v 1.3 2011/07/31 11:36:49 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+
+/*
+ * The predefined-string translation tables.  Each corresponds to a
+ * predefined strings from (e.g.) tmac/mdoc/doc-nroff.  The left-hand
+ * side corresponds to the input sequence (\*x, \*(xx and so on).  The
+ * right-hand side is what's produced by libroff.
+ *
+ * XXX - C-escape strings!
+ * XXX - update PREDEF_MAX in roff.c if adding more!
+ */
+
+PREDEF("Am", "&")
+PREDEF("Ba", "|")
+PREDEF("Ge", "\\(>=")
+PREDEF("Gt", ">")
+PREDEF("If", "infinity")
+PREDEF("Le", "\\(<=")
+PREDEF("Lq", "\\(lq")
+PREDEF("Lt", "<")
+PREDEF("Na", "NaN")
+PREDEF("Ne", "\\(!=")
+PREDEF("Pi", "pi")
+PREDEF("Pm", "\\(+-")
+PREDEF("Rq", "\\(rq")
+PREDEF("left-bracket", "[")
+PREDEF("left-parenthesis", "(")
+PREDEF("lp", "(")
+PREDEF("left-singlequote", "\\(oq")
+PREDEF("q", "\\(dq")
+PREDEF("quote-left", "\\(oq")
+PREDEF("quote-right", "\\(cq")
+PREDEF("R", "\\(rg")
+PREDEF("right-bracket", "]")
+PREDEF("right-parenthesis", ")")
+PREDEF("rp", ")")
+PREDEF("right-singlequote", "\\(cq")
+PREDEF("Tm", "(Tm)")
+PREDEF("Px", "POSIX")
+PREDEF("Ai", "ANSI")
+PREDEF("\'", "\\\'")
+PREDEF("aa", "\\(aa")
+PREDEF("ga", "\\(ga")
+PREDEF("`",  "\\`")
+PREDEF("lq", "\\(lq")
+PREDEF("rq", "\\(rq")
+PREDEF("ua", "\\(ua")
+PREDEF("va", "\\(va")
+PREDEF("<=", "\\(<=")
+PREDEF(">=", "\\(>=")
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/read.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,846 @@
+/*	$Id: read.c,v 1.28 2012/02/16 20:51:31 joerg Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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
+
+#ifdef HAVE_MMAP
+# include <sys/stat.h>
+# include <sys/mman.h>
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "mdoc.h"
+#include "man.h"
+#include "main.h"
+
+#ifndef MAP_FILE
+#define	MAP_FILE	0
+#endif
+
+#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 */
+	mandocmsg	  mmsg; /* warning/error message handler */
+	void		 *arg; /* argument to mmsg */
+	const char	 *file; 
+	struct buf	 *secondary;
+};
+
+static	void	  resize_buf(struct buf *, size_t);
+static	void	  mparse_buf_r(struct mparse *, struct buf, int);
+static	void	  mparse_readfd_r(struct mparse *, int, const char *, int);
+static	void	  pset(const char *, int, struct mparse *);
+static	int	  read_whole_file(const char *, int, struct buf *, int *);
+static	void	  mparse_end(struct mparse *);
+
+static	const enum mandocerr	mandoclimits[MANDOCLEVEL_MAX] = {
+	MANDOCERR_OK,
+	MANDOCERR_WARNING,
+	MANDOCERR_WARNING,
+	MANDOCERR_ERROR,
+	MANDOCERR_FATAL,
+	MANDOCERR_MAX,
+	MANDOCERR_MAX
+};
+
+static	const char * const	mandocerrs[MANDOCERR_MAX] = {
+	"ok",
+
+	"generic warning",
+
+	/* related to the prologue */
+	"no title in document",
+	"document title should be all caps",
+	"unknown manual section",
+	"date missing, using today's date",
+	"cannot parse date, using it verbatim",
+	"prologue macros out of order",
+	"duplicate prologue macro",
+	"macro not allowed in prologue",
+	"macro not allowed in body",
+
+	/* related to document structure */
+	".so is fragile, better use ln(1)",
+	"NAME section must come first",
+	"bad NAME section contents",
+	"manual name not yet set",
+	"sections out of conventional order",
+	"duplicate section name",
+	"section not in conventional manual section",
+
+	/* related to macros and nesting */
+	"skipping obsolete macro",
+	"skipping paragraph macro",
+	"skipping no-space macro",
+	"blocks badly nested",
+	"child violates parent syntax",
+	"nested displays are not portable",
+	"already in literal mode",
+	"line scope broken",
+
+	/* related to missing macro arguments */
+	"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",
+
+	/* related to bad macro arguments */
+	"skipping argument",
+	"duplicate argument",
+	"duplicate display type",
+	"duplicate list type",
+	"unknown AT&T UNIX version",
+	"bad Boolean value",
+	"unknown font",
+	"unknown standard specifier",
+	"bad width argument",
+
+	/* related to plain text */
+	"blank line in non-literal context",
+	"tab in non-literal context",
+	"end of line whitespace",
+	"bad comment style",
+	"bad escape sequence",
+	"unterminated quoted string",
+
+	/* related to equations */
+	"unexpected literal in equation",
+	
+	"generic error",
+
+	/* related to equations */
+	"unexpected equation scope closure",
+	"equation scope open on exit",
+	"overlapping equation scopes",
+	"unexpected end of equation",
+	"equation syntax 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",
+
+	"input stack limit exceeded, infinite loop?",
+	"skipping bad character",
+	"escaped character not allowed in a name",
+	"skipping text before the first section header",
+	"skipping unknown macro",
+	"NOT IMPLEMENTED, please use groff: skipping request",
+	"argument count wrong",
+	"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)",
+	"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",
+	"NOT IMPLEMENTED: .so with absolute path or \"..\"",
+	"no document body",
+	"no document prologue",
+	"static buffer exhausted",
+};
+
+static	const char * const	mandoclevels[MANDOCLEVEL_MAX] = {
+	"SUCCESS",
+	"RESERVED",
+	"WARNING",
+	"ERROR",
+	"FATAL",
+	"BADARG",
+	"SYSERR"
+};
+
+static void
+resize_buf(struct buf *buf, size_t initial)
+{
+
+	buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial;
+	buf->buf = mandoc_realloc(buf->buf, buf->sz);
+}
+
+static void
+pset(const char *buf, int pos, struct mparse *curp)
+{
+	int		 i;
+
+	/*
+	 * 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 ('.' == buf[0] || '\'' == buf[0]) {
+		for (i = 1; buf[i]; i++)
+			if (' ' != buf[i] && '\t' != buf[i])
+				break;
+		if ('\0' == buf[i])
+			return;
+	}
+
+	switch (curp->inttype) {
+	case (MPARSE_MDOC):
+		if (NULL == curp->pmdoc) 
+			curp->pmdoc = mdoc_alloc(curp->roff, curp);
+		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);
+		assert(curp->pmdoc);
+		curp->mdoc = curp->pmdoc;
+		return;
+	} 
+
+	if (NULL == curp->pman) 
+		curp->pman = man_alloc(curp->roff, curp);
+	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').
+ */
+static void
+mparse_buf_r(struct mparse *curp, struct buf blk, int start)
+{
+	const struct tbl_span	*span;
+	struct buf	 ln;
+	enum rofferr	 rr;
+	int		 i, of, rc;
+	int		 pos; /* byte number in the ln buffer */
+	int		 lnn; /* line number in the real file */
+	unsigned char	 c;
+
+	memset(&ln, 0, sizeof(struct buf));
+
+	lnn = curp->line; 
+	pos = 0; 
+
+	for (i = 0; i < (int)blk.sz; ) {
+		if (0 == pos && '\0' == blk.buf[i])
+			break;
+
+		if (start) {
+			curp->line = lnn;
+			curp->reparse_count = 0;
+		}
+
+		while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) {
+
+			/*
+			 * When finding an unescaped newline character,
+			 * leave the character loop to process the line.
+			 * Skip a preceding carriage return, if any.
+			 */
+
+			if ('\r' == blk.buf[i] && i + 1 < (int)blk.sz &&
+			    '\n' == blk.buf[i + 1])
+				++i;
+			if ('\n' == blk.buf[i]) {
+				++i;
+				++lnn;
+				break;
+			}
+
+			/* 
+			 * 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.
+			 */
+
+			c = (unsigned char) blk.buf[i];
+
+			if ( ! (isascii(c) && 
+					(isgraph(c) || isblank(c)))) {
+				mandoc_msg(MANDOCERR_BADCHAR, curp,
+						curp->line, pos, NULL);
+				i++;
+				if (pos >= (int)ln.sz)
+					resize_buf(&ln, 256);
+				ln.buf[pos++] = '?';
+				continue;
+			}
+
+			/* Trailing backslash = a plain char. */
+
+			if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) {
+				if (pos >= (int)ln.sz)
+					resize_buf(&ln, 256);
+				ln.buf[pos++] = blk.buf[i++];
+				continue;
+			}
+
+			/*
+			 * Found escape and at least one other character.
+			 * When it's a newline character, skip it.
+			 * When there is a carriage return in between,
+			 * skip that one as well.
+			 */
+
+			if ('\r' == blk.buf[i + 1] && i + 2 < (int)blk.sz &&
+			    '\n' == blk.buf[i + 2])
+				++i;
+			if ('\n' == blk.buf[i + 1]) {
+				i += 2;
+				++lnn;
+				continue;
+			}
+
+			if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) {
+				i += 2;
+				/* Comment, skip to end of line */
+				for (; i < (int)blk.sz; ++i) {
+					if ('\n' == blk.buf[i]) {
+						++i;
+						++lnn;
+						break;
+					}
+				}
+
+				/* Backout trailing whitespaces */
+				for (; pos > 0; --pos) {
+					if (ln.buf[pos - 1] != ' ')
+						break;
+					if (pos > 2 && ln.buf[pos - 2] == '\\')
+						break;
+				}
+				break;
+			}
+
+			/* Some other escape sequence, copy & cont. */
+
+			if (pos + 1 >= (int)ln.sz)
+				resize_buf(&ln, 256);
+
+			ln.buf[pos++] = blk.buf[i++];
+			ln.buf[pos++] = blk.buf[i++];
+		}
+
+ 		if (pos >= (int)ln.sz)
+			resize_buf(&ln, 256);
+
+		ln.buf[pos] = '\0';
+
+		/*
+		 * A significant amount of complexity is contained by
+		 * the roff preprocessor.  It's line-oriented but can be
+		 * expressed on one line, so we need at times to
+		 * readjust our starting point and re-run it.  The roff
+		 * preprocessor can also readjust the buffers with new
+		 * data, so we pass them in wholesale.
+		 */
+
+		of = 0;
+
+		/*
+		 * Maintain a lookaside buffer of all parsed lines.  We
+		 * only do this if mparse_keep() has been invoked (the
+		 * buffer may be accessed with mparse_getkeep()).
+		 */
+
+		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->sz += pos;
+			curp->secondary->buf
+				[curp->secondary->sz] = '\n';
+			curp->secondary->sz++;
+			curp->secondary->buf
+				[curp->secondary->sz] = '\0';
+		}
+rerun:
+		rr = roff_parseln
+			(curp->roff, curp->line, 
+			 &ln.buf, &ln.sz, of, &of);
+
+		switch (rr) {
+		case (ROFF_REPARSE):
+			if (REPARSE_LIMIT >= ++curp->reparse_count)
+				mparse_buf_r(curp, ln, 0);
+			else
+				mandoc_msg(MANDOCERR_ROFFLOOP, curp,
+					curp->line, pos, NULL);
+			pos = 0;
+			continue;
+		case (ROFF_APPEND):
+			pos = (int)strlen(ln.buf);
+			continue;
+		case (ROFF_RERUN):
+			goto rerun;
+		case (ROFF_IGN):
+			pos = 0;
+			continue;
+		case (ROFF_ERR):
+			assert(MANDOCLEVEL_FATAL <= curp->file_status);
+			break;
+		case (ROFF_SO):
+			/*
+			 * We remove `so' clauses from our lookaside
+			 * buffer because we're going to descend into
+			 * the file recursively.
+			 */
+			if (curp->secondary) 
+				curp->secondary->sz -= pos + 1;
+			mparse_readfd_r(curp, -1, ln.buf + of, 1);
+			if (MANDOCLEVEL_FATAL <= curp->file_status)
+				break;
+			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
+		 * locally per parse routine since we can use different
+		 * parsers with each one.
+		 */
+
+		if ( ! (curp->man || curp->mdoc))
+			pset(ln.buf + of, pos - of, curp);
+
+		/* 
+		 * Lastly, push down into the parsers themselves.  One
+		 * of these will have already been set in the pset()
+		 * routine.
+		 * 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
+		 * guaranteed that something's been allocated.
+		 * 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) :
+					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;
+		}
+
+		/* Temporary buffers typically are not full. */
+
+		if (0 == start && '\0' == blk.buf[i])
+			break;
+
+		/* Start the next input line. */
+
+		pos = 0;
+	}
+
+	free(ln.buf);
+}
+
+static int
+read_whole_file(const char *file, 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(file);
+		return(0);
+	}
+
+	/*
+	 * 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.
+	 */
+
+	if (S_ISREG(st.st_mode)) {
+		if (st.st_size >= (1U << 31)) {
+			fprintf(stderr, "%s: input too large\n", file);
+			return(0);
+		}
+		*with_mmap = 1;
+		fb->sz = (size_t)st.st_size;
+		fb->buf = mmap(NULL, fb->sz, PROT_READ, 
+				MAP_FILE|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) {
+			if (fb->sz == (1U << 31)) {
+				fprintf(stderr, "%s: input too large\n", file);
+				break;
+			}
+			resize_buf(fb, 65536);
+		}
+		ssz = read(fd, fb->buf + (int)off, fb->sz - off);
+		if (ssz == 0) {
+			fb->sz = off;
+			return(1);
+		}
+		if (ssz == -1) {
+			perror(file);
+			break;
+		}
+		off += (size_t)ssz;
+	}
+
+	free(fb->buf);
+	fb->buf = NULL;
+	return(0);
+}
+
+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;
+	}
+
+	roff_endparse(curp->roff);
+}
+
+static void
+mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file,
+		int re)
+{
+	const char	*svfile;
+
+	/* Line number is per-file. */
+	svfile = curp->file;
+	curp->file = file;
+	curp->line = 1;
+
+	mparse_buf_r(curp, blk, 1);
+
+	if (0 == re && MANDOCLEVEL_FATAL > curp->file_status)
+		mparse_end(curp);
+
+	curp->file = svfile;
+}
+
+enum mandoclevel
+mparse_readmem(struct mparse *curp, const void *buf, size_t len,
+		const char *file)
+{
+	struct buf blk;
+
+	blk.buf = UNCONST(buf);
+	blk.sz = len;
+
+	mparse_parse_buffer(curp, blk, file, 0);
+	return(curp->file_status);
+}
+
+static void
+mparse_readfd_r(struct mparse *curp, int fd, const char *file, int re)
+{
+	struct buf	 blk;
+	int		 with_mmap;
+
+	if (-1 == fd)
+		if (-1 == (fd = open(file, O_RDONLY, 0))) {
+			perror(file);
+			curp->file_status = MANDOCLEVEL_SYSERR;
+			return;
+		}
+	/*
+	 * 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 ( ! read_whole_file(file, fd, &blk, &with_mmap)) {
+		curp->file_status = MANDOCLEVEL_SYSERR;
+		return;
+	}
+
+	mparse_parse_buffer(curp, blk, file, re);
+
+#ifdef	HAVE_MMAP
+	if (with_mmap)
+		munmap(blk.buf, blk.sz);
+	else
+#endif
+		free(blk.buf);
+
+	if (STDIN_FILENO != fd && -1 == close(fd))
+		perror(file);
+}
+
+enum mandoclevel
+mparse_readfd(struct mparse *curp, int fd, const char *file)
+{
+
+	mparse_readfd_r(curp, fd, file, 0);
+	return(curp->file_status);
+}
+
+struct mparse *
+mparse_alloc(enum mparset inttype, enum mandoclevel wlevel, mandocmsg mmsg, void *arg)
+{
+	struct mparse	*curp;
+
+	assert(wlevel <= MANDOCLEVEL_FATAL);
+
+	curp = mandoc_calloc(1, sizeof(struct mparse));
+
+	curp->wlevel = wlevel;
+	curp->mmsg = mmsg;
+	curp->arg = arg;
+	curp->inttype = inttype;
+
+	curp->roff = roff_alloc(curp);
+	return(curp);
+}
+
+void
+mparse_reset(struct mparse *curp)
+{
+
+	roff_reset(curp->roff);
+
+	if (curp->mdoc)
+		mdoc_reset(curp->mdoc);
+	if (curp->man)
+		man_reset(curp->man);
+	if (curp->secondary)
+		curp->secondary->sz = 0;
+
+	curp->file_status = MANDOCLEVEL_OK;
+	curp->mdoc = NULL;
+	curp->man = NULL;
+}
+
+void
+mparse_free(struct mparse *curp)
+{
+
+	if (curp->pmdoc)
+		mdoc_free(curp->pmdoc);
+	if (curp->pman)
+		man_free(curp->pman);
+	if (curp->roff)
+		roff_free(curp->roff);
+	if (curp->secondary)
+		free(curp->secondary->buf);
+
+	free(curp->secondary);
+	free(curp);
+}
+
+void
+mparse_result(struct mparse *curp, struct mdoc **mdoc, struct man **man)
+{
+
+	if (mdoc)
+		*mdoc = curp->mdoc;
+	if (man)
+		*man = curp->man;
+}
+
+void
+mandoc_vmsg(enum mandocerr t, struct mparse *m,
+		int ln, int pos, const char *fmt, ...)
+{
+	char		 buf[256];
+	va_list		 ap;
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
+	va_end(ap);
+
+	mandoc_msg(t, m, ln, pos, buf);
+}
+
+void
+mandoc_msg(enum mandocerr er, struct mparse *m, 
+		int ln, int col, const char *msg)
+{
+	enum mandoclevel level;
+
+	level = MANDOCLEVEL_FATAL;
+	while (er < mandoclimits[level])
+		level--;
+
+	if (level < m->wlevel)
+		return;
+
+	if (m->mmsg)
+		(*m->mmsg)(er, level, m->file, ln, col, msg);
+
+	if (m->file_status < level)
+		m->file_status = level;
+}
+
+const char *
+mparse_strerror(enum mandocerr er)
+{
+
+	return(mandocerrs[er]);
+}
+
+const char *
+mparse_strlevel(enum mandoclevel lvl)
+{
+	return(mandoclevels[lvl]);
+}
+
+void
+mparse_keep(struct mparse *p)
+{
+
+	assert(NULL == p->secondary);
+	p->secondary = mandoc_calloc(1, sizeof(struct buf));
+}
+
+const char *
+mparse_getkeep(const struct mparse *p)
+{
+
+	assert(p->secondary);
+	return(p->secondary->sz ? p->secondary->buf : NULL);
+}
--- /dev/null	Fri Jul 18 08:34:35 2014
+++ new/usr/src/cmd/mandoc/roff.c	Fri Jul 18 08:34:35 2014
@@ -0,0 +1,1768 @@
+/*	$Id: roff.c,v 1.172 2011/10/24 21:41:45 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "libroff.h"
+#include "libmandoc.h"
+
+/* Maximum number of nested if-else conditionals. */
+#define	RSTACK_MAX	128
+
+/* Maximum number of string expansions per line, to break infinite loops. */
+#define	EXPAND_LIMIT	1000
+
+enum	rofft {
+	ROFF_ad,
+	ROFF_am,
+	ROFF_ami,
+	ROFF_am1,
+	ROFF_de,
+	ROFF_dei,
+	ROFF_de1,
+	ROFF_ds,
+	ROFF_el,
+	ROFF_hy,
+	ROFF_ie,
+	ROFF_if,
+	ROFF_ig,
+	ROFF_it,
+	ROFF_ne,
+	ROFF_nh,
+	ROFF_nr,
+	ROFF_ns,
+	ROFF_ps,
+	ROFF_rm,
+	ROFF_so,
+	ROFF_ta,
+	ROFF_tr,
+	ROFF_TS,
+	ROFF_TE,
+	ROFF_T_,
+	ROFF_EQ,
+	ROFF_EN,
+	ROFF_cblock,
+	ROFF_ccond,
+	ROFF_USERDEF,
+	ROFF_MAX
+};
+
+enum	roffrule {
+	ROFFRULE_ALLOW,
+	ROFFRULE_DENY
+};
+
+/*
+ * A single register entity.  If "set" is zero, the value of the
+ * register should be the default one, which is per-register.
+ * Registers are assumed to be unsigned ints for now.
+ */
+struct	reg {
+	int		 set; /* whether set or not */
+	unsigned int	 u; /* unsigned integer */
+};
+
+/*
+ * An incredibly-simple string buffer.
+ */
+struct	roffstr {
+	char		*p; /* nil-terminated buffer */
+	size_t		 sz; /* saved strlen(p) */
+};
+
+/*
+ * A key-value roffstr pair as part of a singly-linked list.
+ */
+struct	roffkv {
+	struct roffstr	 key;
+	struct roffstr	 val;
+	struct roffkv	*next; /* next in list */
+};
+
+struct	roff {
+	struct mparse	*parse; /* parse point */
+	struct roffnode	*last; /* leaf of stack */
+	enum roffrule	 rstack[RSTACK_MAX]; /* stack of !`ie' rules */
+	int		 rstackpos; /* position in rstack */
+	struct reg	 regs[REG__MAX];
+	struct roffkv	*strtab; /* user-defined strings & macros */
+	struct roffkv	*xmbtab; /* multi-byte trans table (`tr') */
+	struct roffstr	*xtab; /* single-byte trans table (`tr') */
+	const char	*current_string; /* value of last called user macro */
+	struct tbl_node	*first_tbl; /* first table parsed */
+	struct tbl_node	*last_tbl; /* last table parsed */
+	struct tbl_node	*tbl; /* current table being parsed */
+	struct eqn_node	*last_eqn; /* last equation parsed */
+	struct eqn_node	*first_eqn; /* first equation parsed */
+	struct eqn_node	*eqn; /* current equation being parsed */
+};
+
+struct	roffnode {
+	enum rofft	 tok; /* type of node */
+	struct roffnode	*parent; /* up one in stack */
+	int		 line; /* parse line */
+	int		 col; /* parse col */
+	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 */
+};
+
+#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 */ \
+			 int ln, /* parse line */ \
+			 int ppos, /* original pos in buffer */ \
+			 int pos, /* current pos in buffer */ \
+			 int *offs /* reset offset of buffer data */
+
+typedef	enum rofferr (*roffproc)(ROFF_ARGS);
+
+struct	roffmac {
+	const char	*name; /* macro name */
+	roffproc	 proc; /* process new macro */
+	roffproc	 text; /* process as child text of macro */
+	roffproc	 sub; /* process as child of macro */
+	int		 flags;
+#define	ROFFMAC_STRUCT	(1 << 0) /* always interpret */
+	struct roffmac	*next;
+};
+
+struct	predef {
+	const char	*name; /* predefined input name */
+	const char	*str; /* replacement symbol */
+};
+
+#define	PREDEF(__name, __str) \
+	{ (__name), (__str) },
+
+static	enum rofft	 roffhash_find(const char *, size_t);
+static	void		 roffhash_init(void);
+static	void		 roffnode_cleanscope(struct roff *);
+static	void		 roffnode_pop(struct roff *);
+static	void		 roffnode_push(struct roff *, enum rofft,
+				const char *, int, int);
+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_cblock(ROFF_ARGS);
+static	enum rofferr	 roff_ccond(ROFF_ARGS);
+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	void		 roff_free1(struct roff *);
+static	void		 roff_freestr(struct roffkv *);
+static	char		*roff_getname(struct roff *, char **, int, int);
+static	const char	*roff_getstrn(const struct roff *, 
+				const char *, size_t);
+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 *);
+static	enum rofferr	 roff_res(struct roff *, 
+				char **, size_t *, int, int);
+static	enum rofferr	 roff_rm(ROFF_ARGS);
+static	void		 roff_setstr(struct roff *,
+				const char *, const char *, int);
+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);
+static	enum rofferr	 roff_TE(ROFF_ARGS);
+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_userdef(ROFF_ARGS);
+
+/* See roffhash_find() */
+
+#define	ASCII_HI	 126
+#define	ASCII_LO	 33
+#define	HASHWIDTH	(ASCII_HI - ASCII_LO + 1)
+
+static	struct roffmac	*hash[HASHWIDTH];
+
+static	struct roffmac	 roffs[ROFF_MAX] = {
+	{ "ad", roff_line_ignore, 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 },
+	{ "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 },
+	{ "ds", roff_ds, NULL, NULL, 0, NULL },
+	{ "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
+	{ "hy", 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 },
+	{ "it", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ne", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "nh", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "nr", roff_nr, NULL, NULL, 0, NULL },
+	{ "ns", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "ps", roff_line_ignore, NULL, NULL, 0, NULL },
+	{ "rm", roff_rm, 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 },
+	{ "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 },
+	{ ".", roff_cblock, NULL, NULL, 0, NULL },
+	{ "\\}", roff_ccond, NULL, NULL, 0, NULL },
+	{ NULL, roff_userdef, NULL, NULL, 0, NULL },
+};
+
+/* Array of injected predefined strings. */
+#define	PREDEFS_MAX	 38
+static	const struct predef predefs[PREDEFS_MAX] = {
+#include "predefs.in"
+};
+
+/* See roffhash_find() */
+#define	ROFF_HASH(p)	(p[0] - ASCII_LO)
+
+static void
+roffhash_init(void)
+{
+	struct roffmac	 *n;
+	int		  buc, i;
+
+	for (i = 0; i < (int)ROFF_USERDEF; i++) {
+		assert(roffs[i].name[0] >= ASCII_LO);
+		assert(roffs[i].name[0] <= ASCII_HI);
+
+		buc = ROFF_HASH(roffs[i].name);
+
+		if (NULL != (n = hash[buc])) {
+			for ( ; n->next; n = n->next)
+				/* Do nothing. */ ;
+			n->next = &roffs[i];
+		} else
+			hash[buc] = &roffs[i];
+	}
+}
+
+/*
+ * Look up a roff token by its name.  Returns ROFF_MAX if no macro by
+ * the nil-terminated string name could be found.
+ */
+static enum rofft
+roffhash_find(const char *p, size_t s)
+{
+	int		 buc;
+	struct roffmac	*n;
+
+	/*
+	 * libroff has an extremely simple hashtable, for the time
+	 * being, which simply keys on the first character, which must
+	 * be printable, then walks a chain.  It works well enough until
+	 * optimised.
+	 */
+
+	if (p[0] < ASCII_LO || p[0] > ASCII_HI)
+		return(ROFF_MAX);
+
+	buc = ROFF_HASH(p);
+
+	if (NULL == (n = hash[buc]))
+		return(ROFF_MAX);
+	for ( ; n; n = n->next)
+		if (0 == strncmp(n->name, p, s) && '\0' == n->name[(int)s])
+			return((enum rofft)(n - roffs));
+
+	return(ROFF_MAX);
+}
+
+
+/*
+ * Pop the current node off of the stack of roff instructions currently
+ * pending.
+ */
+static void
+roffnode_pop(struct roff *r)
+{
+	struct roffnode	*p;
+
+	assert(r->last);
+	p = r->last; 
+
+	r->last = r->last->parent;
+	free(p->name);
+	free(p->end);
+	free(p);
+}
+
+
+/*
+ * Push a roff node onto the instruction stack.  This must later be
+ * removed with roffnode_pop().
+ */
+static void
+roffnode_push(struct roff *r, enum rofft tok, const char *name,
+		int line, int col)
+{
+	struct roffnode	*p;
+
+	p = mandoc_calloc(1, sizeof(struct roffnode));
+	p->tok = tok;
+	if (name)
+		p->name = mandoc_strdup(name);
+	p->parent = r->last;
+	p->line = line;
+	p->col = col;
+	p->rule = p->parent ? p->parent->rule : ROFFRULE_DENY;
+
+	r->last = p;
+}
+
+
+static void
+roff_free1(struct roff *r)
+{
+	struct tbl_node	*t;
+	struct eqn_node	*e;
+	int		 i;
+
+	while (NULL != (t = r->first_tbl)) {
+		r->first_tbl = t->next;
+		tbl_free(t);
+	}
+
+	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;
+
+	if (r->xtab)
+		for (i = 0; i < 128; i++)
+			free(r->xtab[i].p);
+
+	free(r->xtab);
+	r->xtab = NULL;
+}
+
+void
+roff_reset(struct roff *r)
+{
+	int		 i;
+
+	roff_free1(r);
+
+	memset(&r->regs, 0, sizeof(struct reg) * REG__MAX);
+
+	for (i = 0; i < PREDEFS_MAX; i++) 
+		roff_setstr(r, predefs[i].name, predefs[i].str, 0);
+}
+
+
+void
+roff_free(struct roff *r)
+{
+
+	roff_free1(r);
+	free(r);
+}
+
+
+struct roff *
+roff_alloc(struct mparse *parse)
+{
+	struct roff	*r;
+	int		 i;
+
+	r = mandoc_calloc(1, sizeof(struct roff));
+	r->parse = parse;
+	r->rstackpos = -1;
+	
+	roffhash_init();
+
+	for (i = 0; i < PREDEFS_MAX; i++) 
+		roff_setstr(r, predefs[i].name, predefs[i].str, 0);
+
+	return(r);
+}
+
+/*
+ * Pre-filter each and every line for reserved words (one beginning with
+ * `\*', e.g., `\*(ab').  These must be handled before the actual line
+ * is processed. 
+ * This also checks the syntax of regular escapes.
+ */
+static enum rofferr
+roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos)
+{
+	enum mandoc_esc	 esc;
+	const 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 */
+	int		 i, maxl, expand_count;
+	size_t		 nsz;
+	char		*n;
+
+	expand_count = 0;
+
+again:
+	cp = *bufp + pos;
+	while (NULL != (cp = strchr(cp, '\\'))) {
+		stesc = cp++;
+
+		/*
+		 * The second character must be an asterisk.
+		 * If it isn't, skip it anyway:  It is escaped,
+		 * so it can't start another escape sequence.
+		 */
+
+		if ('\0' == *cp)
+			return(ROFF_CONT);
+
+		if ('*' != *cp) {
+			res = cp;
+			esc = mandoc_escape(&cp, NULL, NULL);
+			if (ESCAPE_ERROR != esc)
+				continue;
+			cp = res;
+			mandoc_msg
+				(MANDOCERR_BADESCAPE, r->parse, 
+				 ln, (int)(stesc - *bufp), NULL);
+			return(ROFF_CONT);
+		}
+
+		cp++;
+
+		/*
+		 * The third character decides the length
+		 * of the name of the string.
+		 * Save a pointer to the name.
+		 */
+
+		switch (*cp) {
+		case ('\0'):
+			return(ROFF_CONT);
+		case ('('):
+			cp++;
+			maxl = 2;
+			break;
+		case ('['):
+			cp++;
+			maxl = 0;
+			break;
+		default:
+			maxl = 1;
+			break;
+		}
+		stnam = cp;
+
+		/* Advance to the end of the name. */
+
+		for (i = 0; 0 == maxl || i < maxl; i++, cp++) {
+			if ('\0' == *cp) {
+				mandoc_msg
+					(MANDOCERR_BADESCAPE, 
+					 r->parse, ln, 
+					 (int)(stesc - *bufp), NULL);
+				return(ROFF_CONT);
+			}
+			if (0 == maxl && ']' == *cp)
+				break;
+		}
+
+		/*
+		 * Retrieve the replacement string; if it is
+		 * undefined, resume searching for escapes.
+		 */
+
+		res = roff_getstrn(r, stnam, (size_t)i);
+
+		if (NULL == res) {
+			mandoc_msg
+				(MANDOCERR_BADESCAPE, r->parse, 
+				 ln, (int)(stesc - *bufp), NULL);
+			res = "";
+		}
+
+		/* Replace the escape sequence by the string. */
+
+		pos = stesc - *bufp;
+
+		nsz = *szp + strlen(res) + 1;
+		n = mandoc_malloc(nsz);
+
+		strlcpy(n, *bufp, (size_t)(stesc - *bufp + 1));
+		strlcat(n, res, nsz);
+		strlcat(n, cp + (maxl ? 0 : 1), nsz);
+
+		free(*bufp);
+
+		*bufp = n;
+		*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);
+	}
+	return(ROFF_CONT);
+}
+
+/*
+ * Process text streams: convert all breakable hyphens into ASCII_HYPH.
+ */
+static enum rofferr
+roff_parsetext(char *p)
+{
+	size_t		 sz;
+	const char	*start;
+	enum mandoc_esc	 esc;
+
+	start = p;
+
+	while ('\0' != *p) {
+		sz = strcspn(p, "-\\");
+		p += sz;
+
+		if ('\0' == *p)
+			break;
+
+		if ('\\' == *p) {
+			/* Skip over escapes. */
+			p++;
+			esc = mandoc_escape
+				((const char **)&p, NULL, NULL);
+			if (ESCAPE_ERROR == esc)
+				break;
+			continue;
+		} else if (p == start) {
+			p++;
+			continue;
+		}
+
+		if (isalpha((unsigned char)p[-1]) &&
+		    isalpha((unsigned char)p[1]))
+			*p = ASCII_HYPH;
+		p++;
+	}
+
+	return(ROFF_CONT);
+}
+
+enum rofferr
+roff_parseln(struct roff *r, int ln, char **bufp, 
+		size_t *szp, int pos, int *offs)
+{
+	enum rofft	 t;
+	enum rofferr	 e;
+	int		 ppos, ctl;
+
+	/*
+	 * Run the reserved-word filter only if we have some reserved
+	 * words to fill in.
+	 */
+
+	e = roff_res(r, bufp, szp, ln, pos);
+	if (ROFF_IGN == e)
+		return(e);
+	assert(ROFF_CONT == e);
+
+	ppos = pos;
+	ctl = mandoc_getcontrol(*bufp, &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.
+	 */
+
+	if (r->last && ! 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)
+			return(e);
+		if (r->eqn)
+			return(eqn_read(&r->eqn, ln, *bufp, pos, offs));
+		if (r->tbl)
+			return(tbl_read(r->tbl, ln, *bufp, pos));
+		return(roff_parsetext(*bufp + pos));
+	} else if ( ! ctl) {
+		if (r->eqn)
+			return(eqn_read(&r->eqn, ln, *bufp, pos, offs));
+		if (r->tbl)
+			return(tbl_read(r->tbl, ln, *bufp, pos));
+		return(roff_parsetext(*bufp + pos));
+	} else if (r->eqn)
+		return(eqn_read(&r->eqn, ln, *bufp, ppos, offs));
+
+	/*
+	 * If a scope is open, go to the child handler for that macro,
+	 * as it may want to preprocess before doing anything with it.
+	 * Don't do so if an equation is open.
+	 */
+
+	if (r->last) {
+		t = r->last->tok;
+		assert(roffs[t].sub);
+		return((*roffs[t].sub)
+				(r, t, bufp, szp, 
+				 ln, ppos, pos, offs));
+	}
+
+	/*
+	 * 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.
+	 */
+
+	if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos)))
+		return(ROFF_CONT);
+
+	assert(roffs[t].proc);
+	return((*roffs[t].proc)
+			(r, t, bufp, szp, 
+			 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);
+
+	if (r->eqn) {
+		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, 
+				r->eqn->eqn.ln, r->eqn->eqn.pos, NULL);
+		eqn_end(&r->eqn);
+	}
+
+	if (r->tbl) {
+		mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, 
+				r->tbl->line, r->tbl->pos, NULL);
+		tbl_end(&r->tbl);
+	}
+}
+
+/*
+ * Parse a roff node's type from the input buffer.  This must be in the
+ * form of ".foo xxx" in the usual way.
+ */
+static enum rofft
+roff_parse(struct roff *r, const char *buf, int *pos)
+{
+	const char	*mac;
+	size_t		 maclen;
+	enum rofft	 t;
+
+	if ('\0' == buf[*pos] || '"' == buf[*pos] || 
+			'\t' == buf[*pos] || ' ' == buf[*pos])
+		return(ROFF_MAX);
+
+	/*
+	 * 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.
+	 */
+
+	mac = buf + *pos;
+	maclen = strcspn(mac + 1, " \\\t\0") + 1;
+
+	t = (r->current_string = roff_getstrn(r, mac, maclen))
+	    ? ROFF_USERDEF : roffhash_find(mac, maclen);
+
+	*pos += (int)maclen;
+
+	while (buf[*pos] && ' ' == buf[*pos])
+		(*pos)++;
+
+	return(t);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_cblock(ROFF_ARGS)
+{
+
+	/*
+	 * A block-close `..' should only be invoked as a child of an
+	 * ignore macro, otherwise raise a warning and just ignore it.
+	 */
+
+	if (NULL == r->last) {
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+		return(ROFF_IGN);
+	}
+
+	switch (r->last->tok) {
+	case (ROFF_am):
+		/* FALLTHROUGH */
+	case (ROFF_ami):
+		/* FALLTHROUGH */
+	case (ROFF_am1):
+		/* FALLTHROUGH */
+	case (ROFF_de):
+		/* ROFF_de1 is remapped to ROFF_de in roff_block(). */
+		/* FALLTHROUGH */
+	case (ROFF_dei):
+		/* FALLTHROUGH */
+	case (ROFF_ig):
+		break;
+	default:
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+		return(ROFF_IGN);
+	}
+
+	if ((*bufp)[pos])
+		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
+
+	roffnode_pop(r);
+	roffnode_cleanscope(r);
+	return(ROFF_IGN);
+
+}
+
+
+static void
+roffnode_cleanscope(struct roff *r)
+{
+
+	while (r->last) {
+		if (--r->last->endspan < 0)
+			break;
+		roffnode_pop(r);
+	}
+}
+
+
+/* ARGSUSED */
+static enum rofferr
+roff_ccond(ROFF_ARGS)
+{
+
+	if (NULL == r->last) {
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+		return(ROFF_IGN);
+	}
+
+	switch (r->last->tok) {
+	case (ROFF_el):
+		/* FALLTHROUGH */
+	case (ROFF_ie):
+		/* FALLTHROUGH */
+	case (ROFF_if):
+		break;
+	default:
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+		return(ROFF_IGN);
+	}
+
+	if (r->last->endspan > -1) {
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+		return(ROFF_IGN);
+	}
+
+	if ((*bufp)[pos])
+		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
+
+	roffnode_pop(r);
+	roffnode_cleanscope(r);
+	return(ROFF_IGN);
+}
+
+
+/* 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'.
+		 */
+
+		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);
+
+		while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos]))
+			pos++;
+
+		while (isspace((unsigned char)(*bufp)[pos]))
+			(*bufp)[pos++] = '\0';
+	}
+
+	roffnode_push(r, tok, name, ln, ppos);
+
+	/*
+	 * 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.
+	 */
+
+	if (ROFF_de == tok)
+		roff_setstr(r, name, "", 0);
+
+	if ('\0' == (*bufp)[pos])
+		return(ROFF_IGN);
+
+	/* If present, process the custom end-of-line 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.
+	 */
+
+	assert(pos > sv);
+	sz = (size_t)(pos - sv);
+
+	if (1 == sz && '.' == (*bufp)[sv])
+		return(ROFF_IGN);
+
+	r->last->end = mandoc_malloc(sz + 1);
+
+	memcpy(r->last->end, *bufp + sv, sz);
+	r->last->end[(int)sz] = '\0';
+
+	if ((*bufp)[pos])
+		mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
+
+	return(ROFF_IGN);
+}
+
+
+/* ARGSUSED */
+static enum rofferr
+roff_block_sub(ROFF_ARGS)
+{
+	enum rofft	t;
+	int		i, j;
+
+	/*
+	 * First check whether a custom macro exists at this level.  If
+	 * it does, then check against it.  This is some of groff's
+	 * stranger behaviours.  If we encountered a custom end-scope
+	 * tag and that tag also happens to be a "real" macro, then we
+	 * need to try interpreting it again as a real macro.  If it's
+	 * not, then return ignore.  Else continue.
+	 */
+
+	if (r->last->end) {
+		for (i = pos, j = 0; r->last->end[j]; j++, i++)
+			if ((*bufp)[i] != r->last->end[j])
+				break;
+
+		if ('\0' == r->last->end[j] && 
+				('\0' == (*bufp)[i] ||
+				 ' ' == (*bufp)[i] ||
+				 '\t' == (*bufp)[i])) {
+			roffnode_pop(r);
+			roffnode_cleanscope(r);
+
+			while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
+				i++;
+
+			pos = i;
+			if (ROFF_MAX != roff_parse(r, *bufp, &pos))
+				return(ROFF_RERUN);
+			return(ROFF_IGN);
+		}
+	}
+
+	/*
+	 * If we have no custom end-query or lookup failed, then try
+	 * pulling it out of the hashtable.
+	 */
+
+	t = roff_parse(r, *bufp, &pos);
+
+	/*
+	 * 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);
+		return(ROFF_IGN);
+	}
+
+	assert(roffs[t].proc);
+	return((*roffs[t].proc)(r, t, bufp, szp, 
+				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);
+
+	return(ROFF_IGN);
+}
+
+
+/* ARGSUSED */
+static enum rofferr
+roff_cond_sub(ROFF_ARGS)
+{
+	enum rofft	 t;
+	enum roffrule	 rr;
+	char		*ep;
+
+	rr = r->last->rule;
+	roffnode_cleanscope(r);
+
+	/*
+	 * If the macro is unknown, first check if it contains a closing
+	 * delimiter `\}'.  If it does, close out our scope and return
+	 * the currently-scoped rule (ignore or continue).  Else, drop
+	 * into the currently-scoped rule.
+	 */
+
+	if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos))) {
+		ep = &(*bufp)[pos];
+		for ( ; NULL != (ep = strchr(ep, '\\')); ep++) {
+			ep++;
+			if ('}' != *ep)
+				continue;
+
+			/*
+			 * Make the \} go away.
+			 * This is a little haphazard, as it's not quite
+			 * clear how nroff does this.
+			 * If we're at the end of line, then just chop
+			 * off the \} and resize the buffer.
+			 * If we aren't, then conver it to spaces.
+			 */
+
+			if ('\0' == *(ep + 1)) {
+				*--ep = '\0';
+				*szp -= 2;
+			} else
+				*(ep - 1) = *ep = ' ';
+
+			roff_ccond(r, ROFF_ccond, bufp, szp, 
+					ln, pos, pos + 2, offs);
+			break;
+		}
+		return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
+	}
+
+	/*
+	 * A denied conditional must evaluate its children if and only
+	 * if they're either structurally required (such as loops and
+	 * conditionals) or a closing macro.
+	 */
+
+	if (ROFFRULE_DENY == rr)
+		if ( ! (ROFFMAC_STRUCT & roffs[t].flags))
+			if (ROFF_ccond != t)
+				return(ROFF_IGN);
+
+	assert(roffs[t].proc);
+	return((*roffs[t].proc)(r, t, bufp, szp, 
+				ln, ppos, pos, offs));
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_cond_text(ROFF_ARGS)
+{
+	char		*ep;
+	enum roffrule	 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);
+	}
+	return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
+}
+
+static enum roffrule
+roff_evalcond(const char *v, int *pos)
+{
+
+	switch (v[*pos]) {
+	case ('n'):
+		(*pos)++;
+		return(ROFFRULE_ALLOW);
+	case ('e'):
+		/* FALLTHROUGH */
+	case ('o'):
+		/* FALLTHROUGH */
+	case ('t'):
+		(*pos)++;
+		return(ROFFRULE_DENY);
+	default:
+		break;
+	}
+
+	while (v[*pos] && ' ' != v[*pos])
+		(*pos)++;
+	return(ROFFRULE_DENY);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_line_ignore(ROFF_ARGS)
+{
+
+	if (ROFF_it == tok)
+		mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos, "it");
+
+	return(ROFF_IGN);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_cond(ROFF_ARGS)
+{
+	int		 sv;
+	enum roffrule	 rule;
+
+	/* 
+	 * 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.  
+	 *
+	 * If we're not an `el', however, then evaluate the conditional.
+	 */
+
+	rule = ROFF_el == tok ?
+		(r->rstackpos < 0 ? 
+		 ROFFRULE_DENY : r->rstack[r->rstackpos--]) :
+		roff_evalcond(*bufp, &pos);
+
+	sv = pos;
+	while (' ' == (*bufp)[pos])
+		pos++;
+
+	/*
+	 * Roff is weird.  If we have just white-space after the
+	 * conditional, it's considered the BODY and we exit without
+	 * really doing anything.  Warn about this.  It's probably
+	 * wrong.
+	 */
+
+	if ('\0' == (*bufp)[pos] && sv != pos) {
+		mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
+		return(ROFF_IGN);
+	}
+
+	roffnode_push(r, tok, NULL, ln, ppos);
+
+	r->last->rule = rule;
+
+	/*
+	 * 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);
+		}
+		r->rstack[++r->rstackpos] = 
+			ROFFRULE_DENY == r->last->rule ?
+			ROFFRULE_ALLOW : ROFFRULE_DENY;
+	}
+
+	/* 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;
+
+	/*
+	 * Determine scope.  If we're invoked with "\{" trailing the
+	 * conditional, then we're in a multiline scope.  Else our scope
+	 * expires on the next line.
+	 */
+
+	r->last->endspan = 1;
+
+	if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) {
+		r->last->endspan = -1;
+		pos += 2;
+	} 
+
+	/*
+	 * If there are no arguments on the line, the next-line scope is
+	 * assumed.
+	 */
+
+	if ('\0' == (*bufp)[pos])
+		return(ROFF_IGN);
+
+	/* Otherwise re-run the roff parser after recalculating. */
+
+	*offs = pos;
+	return(ROFF_RERUN);
+}
+
+
+/* ARGSUSED */
+static enum rofferr
+roff_ds(ROFF_ARGS)
+{
+	char		*name, *string;
+
+	/*
+	 * 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.
+	 */
+
+	string = *bufp + pos;
+	name = roff_getname(r, &string, ln, pos);
+	if ('\0' == *name)
+		return(ROFF_IGN);
+
+	/* Read past initial double-quote. */
+	if ('"' == *string)
+		string++;
+
+	/* The rest is the value. */
+	roff_setstr(r, name, string, 0);
+	return(ROFF_IGN);
+}
+
+int
+roff_regisset(const struct roff *r, enum regs reg)
+{
+
+	return(r->regs[(int)reg].set);
+}
+
+unsigned int
+roff_regget(const struct roff *r, enum regs reg)
+{
+
+	return(r->regs[(int)reg].u);
+}
+
+void
+roff_regunset(struct roff *r, enum regs reg)
+{
+
+	r->regs[(int)reg].set = 0;
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_nr(ROFF_ARGS)
+{
+	const char	*key;
+	char		*val;
+	int		 iv;
+
+	val = *bufp + pos;
+	key = roff_getname(r, &val, ln, pos);
+
+	if (0 == strcmp(key, "nS")) {
+		r->regs[(int)REG_nS].set = 1;
+		if ((iv = mandoc_strntoi(val, strlen(val), 10)) >= 0)
+			r->regs[(int)REG_nS].u = (unsigned)iv;
+		else
+			r->regs[(int)REG_nS].u = 0u;
+	}
+
+	return(ROFF_IGN);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_rm(ROFF_ARGS)
+{
+	const char	 *name;
+	char		 *cp;
+
+	cp = *bufp + pos;
+	while ('\0' != *cp) {
+		name = roff_getname(r, &cp, ln, (int)(cp - *bufp));
+		if ('\0' != *name)
+			roff_setstr(r, name, NULL, 0);
+	}
+	return(ROFF_IGN);
+}
+
+/* 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);
+
+	return(ROFF_IGN);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_T_(ROFF_ARGS)
+{
+
+	if (NULL == r->tbl)
+		mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
+	else
+		tbl_restart(ppos, ln, r->tbl);
+
+	return(ROFF_IGN);
+}
+
+#if 0
+static int
+roff_closeeqn(struct roff *r)
+{
+
+	return(r->eqn && ROFF_EQN == eqn_end(&r->eqn) ? 1 : 0);
+}
+#endif
+
+static void
+roff_openeqn(struct roff *r, const char *name, int line, 
+		int offs, const char *buf)
+{
+	struct eqn_node *e;
+	int		 poff;
+
+	assert(NULL == r->eqn);
+	e = eqn_alloc(name, offs, line, r->parse);
+
+	if (r->last_eqn)
+		r->last_eqn->next = e;
+	else
+		r->first_eqn = r->last_eqn = e;
+
+	r->eqn = r->last_eqn = e;
+
+	if (buf) {
+		poff = 0;
+		eqn_read(&r->eqn, line, buf, offs, &poff);
+	}
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_EQ(ROFF_ARGS)
+{
+
+	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);
+	return(ROFF_IGN);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_TS(ROFF_ARGS)
+{
+	struct tbl_node	*t;
+
+	if (r->tbl) {
+		mandoc_msg(MANDOCERR_SCOPEBROKEN, r->parse, ln, ppos, NULL);
+		tbl_end(&r->tbl);
+	}
+
+	t = tbl_alloc(ppos, ln, r->parse);
+
+	if (r->last_tbl)
+		r->last_tbl->next = t;
+	else
+		r->first_tbl = r->last_tbl = t;
+
+	r->tbl = r->last_tbl = t;
+	return(ROFF_IGN);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_tr(ROFF_ARGS)
+{
+	const char	*p, *first, *second;
+	size_t		 fsz, ssz;
+	enum mandoc_esc	 esc;
+
+	p = *bufp + pos;
+
+	if ('\0' == *p) {
+		mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL);
+		return(ROFF_IGN);
+	}
+
+	while ('\0' != *p) {
+		fsz = ssz = 1;
+
+		first = p++;
+		if ('\\' == *first) {
+			esc = mandoc_escape(&p, NULL, NULL);
+			if (ESCAPE_ERROR == esc) {
+				mandoc_msg
+					(MANDOCERR_BADESCAPE, r->parse, 
+					 ln, (int)(p - *bufp), NULL);
+				return(ROFF_IGN);
+			}
+			fsz = (size_t)(p - first);
+		}
+
+		second = p++;
+		if ('\\' == *second) {
+			esc = mandoc_escape(&p, NULL, NULL);
+			if (ESCAPE_ERROR == esc) {
+				mandoc_msg
+					(MANDOCERR_BADESCAPE, r->parse, 
+					 ln, (int)(p - *bufp), NULL);
+				return(ROFF_IGN);
+			}
+			ssz = (size_t)(p - second);
+		} else if ('\0' == *second) {
+			mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, 
+					ln, (int)(p - *bufp), NULL);
+			second = " ";
+			p--;
+		}
+
+		if (fsz > 1) {
+			roff_setstrn(&r->xmbtab, first, 
+					fsz, second, ssz, 0);
+			continue;
+		}
+
+		if (NULL == r->xtab)
+			r->xtab = mandoc_calloc
+				(128, sizeof(struct roffstr));
+
+		free(r->xtab[(int)*first].p);
+		r->xtab[(int)*first].p = mandoc_strndup(second, ssz);
+		r->xtab[(int)*first].sz = ssz;
+	}
+
+	return(ROFF_IGN);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_so(ROFF_ARGS)
+{
+	char *name;
+
+	mandoc_msg(MANDOCERR_SO, r->parse, ln, ppos, NULL);
+
+	/*
+	 * Handle `so'.  Be EXTREMELY careful, as we shouldn't be
+	 * opening anything that's not in our cwd or anything beneath
+	 * it.  Thus, explicitly disallow traversing up the file-system
+	 * 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);
+	}
+
+	*offs = pos;
+	return(ROFF_SO);
+}
+
+/* ARGSUSED */
+static enum rofferr
+roff_userdef(ROFF_ARGS)
+{
+	const char	 *arg[9];
+	char		 *cp, *n1, *n2;
+	int		  i;
+
+	/*
+	 * Collect pointers to macro argument strings
+	 * and null-terminate them.
+	 */
+	cp = *bufp + pos;
+	for (i = 0; i < 9; i++)
+		arg[i] = '\0' == *cp ? "" :
+		    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;
+			continue;
+		}
+
+		*szp = strlen(n1) - 3 + strlen(arg[i]) + 1;
+		n2 = mandoc_malloc(*szp);
+
+		strlcpy(n2, n1, (size_t)(cp - n1 + 1));
+		strlcat(n2, arg[i], *szp);
+		strlcat(n2, cp + 3, *szp);
+
+		cp = n2 + (cp - n1);
+		free(n1);
+		n1 = n2;
+	}
+
+	/*
+	 * 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] ?
+	   ROFF_REPARSE : ROFF_APPEND);
+}
+
+static char *
+roff_getname(struct roff *r, char **cpp, int ln, int pos)
+{
+	char	 *name, *cp;
+
+	name = *cpp;
+	if ('\0' == *name)
+		return(name);
+
+	/* Read until end of name. */
+	for (cp = name; '\0' != *cp && ' ' != *cp; cp++) {
+		if ('\\' != *cp)
+			continue;
+		cp++;
+		if ('\\' == *cp)
+			continue;
+		mandoc_msg(MANDOCERR_NAMESC, r->parse, ln, pos, NULL);
+		*cp = '\0';
+		name = cp;
+	}
+
+	/* Nil-terminate name. */
+	if ('\0' != *cp)
+		*(cp++) = '\0';
+
+	/* Read past spaces. */
+	while (' ' == *cp)
+		cp++;
+
+	*cpp = cp;
+	return(name);
+}
+
+/*
+ * 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).
+ */
+static void
+roff_setstr(struct roff *r, const char *name, const char *string,
+	int multiline)
+{
+
+	roff_setstrn(&r->strtab, name, strlen(name), string,
+			string ? strlen(string) : 0, multiline);
+}
+
+static void
+roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
+		const char *string, size_t stringsz, int multiline)
+{
+	struct roffkv	*n;
+	char		*c;
+	int		 i;
+	size_t		 oldch, newch;
+
+	/* Search for an existing string with the same name. */
+	n = *r;
+
+	while (n && strcmp(name, n->key.p))
+		n = n->next;
+
+	if (NULL == n) {
+		/* Create a new string table entry. */
+		n = mandoc_malloc(sizeof(struct roffkv));
+		n->key.p = mandoc_strndup(name, namesz);
+		n->key.sz = namesz;
+		n->val.p = NULL;
+		n->val.sz = 0;
+		n->next = *r;
+		*r = n;
+	} else if (0 == multiline) {
+		/* In multiline mode, append; else replace. */
+		free(n->val.p);
+		n->val.p = NULL;
+		n->val.sz = 0;
+	}
+
+	if (NULL == string)
+		return;
+
+	/*
+	 * One additional byte for the '\n' in multiline mode,
+	 * and one for the terminating '\0'.
+	 */
+	newch = stringsz + (multiline ? 2u : 1u);
+
+	if (NULL == n->val.p) {
+		n->val.p = mandoc_malloc(newch);
+		*n->val.p = '\0';
+		oldch = 0;
+	} else {
+		oldch = n->val.sz;
+		n->val.p = mandoc_realloc(n->val.p, oldch + newch);
+	}
+
+	/* Skip existing content in the destination buffer. */
+	c = n->val.p + (int)oldch;
+
+	/* Append new content to the destination buffer. */
+	i = 0;
+	while (i < (int)stringsz) {
+		/*
+		 * Rudimentary roff copy mode:
+		 * Handle escaped backslashes.
+		 */
+		if ('\\' == string[i] && '\\' == string[i + 1])
+			i++;
+		*c++ = string[i++];
+	}
+
+	/* Append terminating bytes. */
+	if (multiline)
+		*c++ = '\n';
+
+	*c = '\0';
+	n->val.sz = (int)(c - n->val.p);
+}
+
+static const char *
+roff_getstrn(const struct roff *r, const char *name, size_t len)
+{
+	const struct roffkv *n;
+
+	for (n = r->strtab; n; n = n->next)
+		if (0 == strncmp(name, n->key.p, len) && 
+				'\0' == n->key.p[(int)len])
+			return(n->val.p);
+
+	return(NULL);
+}
+
+static void
+roff_freestr(struct roffkv *r)
+{
+	struct roffkv	 *n, *nn;
+
+	for (n = r; n; n = nn) {
+		free(n->key.p);
+		free(n->val.p);
+		nn = n->next;
+		free(n);
+	}
+}
+
+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);
+}
+
+/*
+ * Duplicate an input string, making the appropriate character
+ * conversations (as stipulated by `tr') along the way.
+ * Returns a heap-allocated string with all the replacements made.
+ */
+char *
+roff_strdup(const struct roff *r, const char *p)
+{
+	const struct roffkv *cp;
+	char		*res;
+	const char	*pp;
+	size_t		 ssz, sz;
+	enum mandoc_esc	 esc;
+
+	if (NULL == r->xmbtab && NULL == r->xtab)
+		return(mandoc_strdup(p));
+	else if ('\0' == *p)
+		return(mandoc_strdup(""));
+
+	/*
+	 * Step through each character looking for term matches
+	 * (remember that a `tr' can be invoked with an escape, which is
+	 * a glyph but the escape is multi-character).
+	 * We only do this if the character hash has been initialised
+	 * and the string is >0 length.
+	 */
+
+	res = NULL;
+	ssz = 0;
+
+	while ('\0' != *p) {
+		if ('\\' != *p && r->xtab && r->xtab[(int)*p].p) {
+			sz = r->xtab[(int)*p].sz;
+			res = mandoc_realloc(res, ssz + sz + 1);
+			memcpy(res + ssz, r->xtab[(int)*p].p, sz);
+			ssz += sz;
+			p++;
+			continue;
+		} else if ('\\' != *p) {
+			res = mandoc_realloc(res, ssz + 2);
+			res[ssz++] = *p++;
+			continue;
+		}
+
+		/* Search for term matches. */
+		for (cp = r->xmbtab; cp; cp = cp->next)
+			if (0 == strncmp(p, cp->key.p, cp->key.sz))
+				break;
+
+		if (NULL != cp) {
+			/*
+			 * A match has been found.
+			 * Append the match to the array and move
+			 * forward by its keysize.
+			 */
+			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;
+			continue;
+		}
+
+		/*
+		 * Handle escapes carefully: we need to copy
+		 * over just the escape itself, or else we might
+		 * do replacements within the escape itself.
+		 * Make sure to pass along the bogus string.
+		 */
+		pp = p++;
+		esc = mandoc_escape(&p, NULL, NULL);
+		if (ESCAPE_ERROR == esc) {
+			sz = strlen(pp);
+			res = mandoc_realloc(res, ssz + sz + 1);
+			memcpy(res + ssz, pp, sz);
+			break;
+		}
+		/* 
+		 * We bail out on bad escapes. 
+		 * No need to warn: we already did so when
+		 * roff_res() was called.
+		 */
+		sz = (int)(p - pp);
+		res = mandoc_realloc(res, ssz + sz + 1);
+		memcpy(res + ssz, pp, sz);
+		ssz += sz;
+	}
+
+	res[(int)ssz] = '\0';
+	return(res);
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/st.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,39 @@
+/*	$Id: st.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+	if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2st(const char *p)
+{
+
+#include "st.in"
+
+	return(NULL);
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/st.in	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,78 @@
+/*	$Id: st.in,v 1.19 2012/02/26 21:47:09 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 .St macro arguments.  If you add a new
+ * standard, make sure that the left-and side corresponds to the .St
+ * argument (like .St -p1003.1) and the right-hand side corresponds to
+ * the formatted output 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.
+ *
+ * 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.1\\(rq)")
+LINE("-p1003.1b-93",	"IEEE Std 1003.1b-1993 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1c-95",	"IEEE Std 1003.1c-1995 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1g-2000",	"IEEE Std 1003.1g-2000 (\\(lqPOSIX.1\\(rq)")
+LINE("-p1003.1i-95",	"IEEE Std 1003.1i-1995 (\\(lqPOSIX.1\\(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-95",	"IEEE Std 1387.2-1995 (\\(lqPOSIX.7.2\\(rq)")
+LINE("-p1003.2",	"IEEE Std 1003.2 (\\(lqPOSIX.2\\(rq)")
+LINE("-p1387.2",	"IEEE Std 1387.2 (\\(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("-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("-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")
+LINE("-susv3",		"Version\\~3 of the Single UNIX Specification")
+LINE("-svid4",		"System\\~V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)")
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/tbl.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,175 @@
+/*	$Id: tbl.c,v 1.26 2011/07/25 15:37:00 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+enum rofferr
+tbl_read(struct tbl_node *tbl, int ln, const char *p, int offs)
+{
+	int		 len;
+	const char	*cp;
+
+	cp = &p[offs];
+	len = (int)strlen(cp);
+
+	/*
+	 * 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.
+	 */
+
+	if (TBL_PART_OPTS == tbl->part && len)
+		if (';' != cp[len - 1])
+			tbl->part = TBL_PART_LAYOUT;
+
+	/* Now process each logical section of the table.  */
+
+	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);
+	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);
+}
+
+struct tbl_node *
+tbl_alloc(int pos, int line, struct mparse *parse)
+{
+	struct tbl_node	*p;
+
+	p = mandoc_calloc(1, sizeof(struct tbl_node));
+	p->line = line;
+	p->pos = pos;
+	p->parse = parse;
+	p->part = TBL_PART_OPTS;
+	p->opts.tab = '\t';
+	p->opts.linesize = 12;
+	p->opts.decimal = '.';
+	return(p);
+}
+
+void
+tbl_free(struct tbl_node *p)
+{
+	struct tbl_row	*rp;
+	struct tbl_cell	*cp;
+	struct tbl_span	*sp;
+	struct tbl_dat	*dp;
+	struct tbl_head	*hp;
+
+	while (NULL != (rp = p->first_row)) {
+		p->first_row = rp->next;
+		while (rp->first) {
+			cp = rp->first;
+			rp->first = cp->next;
+			free(cp);
+		}
+		free(rp);
+	}
+
+	while (NULL != (sp = p->first_span)) {
+		p->first_span = sp->next;
+		while (sp->first) {
+			dp = sp->first;
+			sp->first = dp->next;
+			if (dp->string)
+				free(dp->string);
+			free(dp);
+		}
+		free(sp);
+	}
+
+	while (NULL != (hp = p->first_head)) {
+		p->first_head = hp->next;
+		free(hp);
+	}
+
+	free(p);
+}
+
+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);
+
+	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 *
+tbl_span(struct tbl_node *tbl)
+{
+	struct tbl_span	 *span;
+
+	assert(tbl);
+	span = tbl->current_span ? tbl->current_span->next
+				 : tbl->first_span;
+	if (span)
+		tbl->current_span = span;
+	return(span);
+}
+
+void
+tbl_end(struct tbl_node **tblp)
+{
+	struct tbl_node	*tbl;
+
+	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);
+}
+
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/tbl_data.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,276 @@
+/*	$Id: tbl_data.c,v 1.24 2011/03/20 16:02:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+static	int		 data(struct tbl_node *, struct tbl_span *, 
+				int, const char *, int *);
+static	struct tbl_span	*newspan(struct tbl_node *, int, 
+				struct tbl_row *);
+
+static int
+data(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;
+
+	/* 
+	 * Skip over spanners and vertical lines to data formats, since
+	 * we want to match data with data layout cells in the header.
+	 */
+
+	while (cp && (TBL_CELL_VERT == cp->pos || 
+				TBL_CELL_DVERT == cp->pos ||
+				TBL_CELL_SPAN == cp->pos))
+		cp = cp->next;
+
+	/*
+	 * Stop processing when we reach the end of the available layout
+	 * cells.  This means that we have extra input.
+	 */
+
+	if (NULL == cp) {
+		mandoc_msg(MANDOCERR_TBLEXTRADAT, 
+				tbl->parse, ln, *pos, NULL);
+		/* Skip to the end... */
+		while (p[*pos])
+			(*pos)++;
+		return(1);
+	}
+
+	dat = mandoc_calloc(1, sizeof(struct tbl_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++;
+		else
+			break;
+	
+	dat->spans = spans;
+
+	if (dp->last) {
+		dp->last->next = dat;
+		dp->last = dat;
+	} else
+		dp->last = dp->first = dat;
+
+	sv = *pos;
+	while (p[*pos] && p[*pos] != tbl->opts.tab)
+		(*pos)++;
+
+	/*
+	 * Check for a continued-data scope opening.  This consists of a
+	 * trailing `T{' at the end of the line.  Subsequent lines,
+	 * until a standalone `T}', are included in our cell.
+	 */
+
+	if (*pos - sv == 2 && 'T' == p[sv] && '{' == p[sv + 1]) {
+		tbl->part = TBL_PART_CDATA;
+		return(0);
+	}
+
+	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';
+
+	if (p[*pos])
+		(*pos)++;
+
+	if ( ! strcmp(dat->string, "_"))
+		dat->pos = TBL_DATA_HORIZ;
+	else if ( ! strcmp(dat->string, "="))
+		dat->pos = TBL_DATA_DHORIZ;
+	else if ( ! strcmp(dat->string, "\\_"))
+		dat->pos = TBL_DATA_NHORIZ;
+	else if ( ! strcmp(dat->string, "\\="))
+		dat->pos = TBL_DATA_NDHORIZ;
+	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);
+}
+
+/* ARGSUSED */
+int
+tbl_cdata(struct tbl_node *tbl, int ln, const char *p)
+{
+	struct tbl_dat	*dat;
+	size_t	 	 sz;
+	int		 pos;
+
+	pos = 0;
+
+	dat = tbl->last_span->last;
+
+	if (p[pos] == 'T' && p[pos + 1] == '}') {
+		pos += 2;
+		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]) {
+			tbl->part = TBL_PART_DATA;
+			return(1);
+		}
+
+		/* Fallthrough: T} is part of a word. */
+	}
+
+	dat->pos = TBL_DATA_DATA;
+
+	if (dat->string) {
+		sz = strlen(p) + strlen(dat->string) + 2;
+		dat->string = mandoc_realloc(dat->string, sz);
+		strlcat(dat->string, " ", sz);
+		strlcat(dat->string, p, sz);
+	} else
+		dat->string = mandoc_strdup(p);
+
+	if (TBL_CELL_DOWN == dat->layout->pos) 
+		mandoc_msg(MANDOCERR_TBLIGNDATA, 
+				tbl->parse, ln, pos, NULL);
+
+	return(0);
+}
+
+static struct tbl_span *
+newspan(struct tbl_node *tbl, int line, struct tbl_row *rp)
+{
+	struct tbl_span	*dp;
+
+	dp = mandoc_calloc(1, sizeof(struct tbl_span));
+	dp->line = line;
+	dp->tbl = &tbl->opts;
+	dp->layout = rp;
+	dp->head = tbl->first_head;
+
+	if (tbl->last_span) {
+		tbl->last_span->next = dp;
+		tbl->last_span = dp;
+	} else {
+		tbl->last_span = tbl->first_span = dp;
+		tbl->current_span = NULL;
+		dp->flags |= TBL_SPAN_FIRST;
+	}
+
+	return(dp);
+}
+
+int
+tbl_data(struct tbl_node *tbl, int ln, const char *p)
+{
+	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,
+	 * if the last span was a horizontal line, use the same layout
+	 * (it doesn't "consume" the layout).
+	 */
+
+	if (tbl->last_span) {
+		assert(tbl->last_span->layout);
+		if (tbl->last_span->pos == TBL_SPAN_DATA) {
+			for (rp = tbl->last_span->layout->next;
+					rp && rp->first; rp = rp->next) {
+				switch (rp->first->pos) {
+				case (TBL_CELL_HORIZ):
+					dp = newspan(tbl, ln, rp);
+					dp->pos = TBL_SPAN_HORIZ;
+					continue;
+				case (TBL_CELL_DHORIZ):
+					dp = newspan(tbl, ln, rp);
+					dp->pos = TBL_SPAN_DHORIZ;
+					continue;
+				default:
+					break;
+				}
+				break;
+			}
+		} else
+			rp = tbl->last_span->layout;
+
+		if (NULL == rp)
+			rp = tbl->last_span->layout;
+	} else
+		rp = tbl->first_row;
+
+	assert(rp);
+
+	dp = newspan(tbl, ln, rp);
+
+	if ( ! strcmp(p, "_")) {
+		dp->pos = TBL_SPAN_HORIZ;
+		return(1);
+	} else if ( ! strcmp(p, "=")) {
+		dp->pos = TBL_SPAN_DHORIZ;
+		return(1);
+	}
+
+	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);
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/tbl_html.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,151 @@
+/*	$Id: tbl_html.c,v 1.9 2011/09/18 14:14:15 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "html.h"
+
+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)
+{
+
+	return(strlen(p));
+}
+
+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;
+
+	if (TBL_SPAN_FIRST & sp->flags) {
+		h->tbl.len = html_tbl_len;
+		h->tbl.slen = html_tbl_strlen;
+		tblcalc(&h->tbl, sp);
+	}
+
+	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) {
+		bufinit(h);
+		col = &h->tbl.cols[hp->ident];
+		SCALE_HS_INIT(&su, col->width);
+		bufcat_su(h, "width", &su);
+		PAIR_STYLE_INIT(&tag, h);
+		print_otag(h, TAG_COL, 1, &tag);
+	}
+
+	print_otag(h, TAG_TBODY, 0, NULL);
+}
+
+void
+print_tblclose(struct html *h)
+{
+
+	assert(h->tblt);
+	print_tagq(h, h->tblt);
+	h->tblt = NULL;
+}
+
+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;
+
+	/* Inhibit printing of spaces: we do padding ourselves. */
+
+	if (NULL == h->tblt)
+		html_tblopen(h, sp);
+
+	assert(h->tblt);
+
+	h->flags |= HTML_NONOSPACE;
+	h->flags |= HTML_NOSPACE;
+
+	tt = print_otag(h, TAG_TR, 0, NULL);
+
+	switch (sp->pos) {
+	case (TBL_SPAN_HORIZ):
+		/* FALLTHROUGH */
+	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) {
+			print_stagq(h, tt);
+			print_otag(h, TAG_TD, 0, NULL);
+
+			switch (hp->pos) {
+			case (TBL_HEAD_VERT):
+				/* FALLTHROUGH */
+			case (TBL_HEAD_DVERT):
+				continue;
+			case (TBL_HEAD_DATA):
+				if (NULL == dp)
+					break;
+				if (TBL_CELL_DOWN != dp->layout->pos)
+					if (dp->string)
+						print_text(h, dp->string);
+				dp = dp->next;
+				break;
+			}
+		}
+		break;
+	}
+
+	print_tagq(h, tt);
+
+	h->flags &= ~HTML_NONOSPACE;
+
+	if (TBL_SPAN_LAST & sp->flags) {
+		assert(h->tbl.cols);
+		free(h->tbl.cols);
+		h->tbl.cols = NULL;
+		print_tblclose(h);
+	}
+
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/tbl_layout.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,472 @@
+/*	$Id: tbl_layout.c,v 1.22 2011/09/18 14:14:15 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+struct	tbl_phrase {
+	char		 name;
+	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] = {
+	{ 'c',		 TBL_CELL_CENTRE },
+	{ 'r',		 TBL_CELL_RIGHT },
+	{ 'l',		 TBL_CELL_LEFT },
+	{ 'n',		 TBL_CELL_NUMBER },
+	{ 's',		 TBL_CELL_SPAN },
+	{ 'a',		 TBL_CELL_LONG },
+	{ '^',		 TBL_CELL_DOWN },
+	{ '-',		 TBL_CELL_HORIZ },
+	{ '_',		 TBL_CELL_HORIZ },
+	{ '=',		 TBL_CELL_DHORIZ },
+	{ '|',		 TBL_CELL_VERT }
+};
+
+static	int		 mods(struct tbl_node *, struct tbl_cell *, 
+				int, const char *, int *);
+static	int		 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);
+static	void		 head_adjust(const struct tbl_cell *, 
+				struct tbl_head *);
+
+static int
+mods(struct tbl_node *tbl, struct tbl_cell *cp, 
+		int ln, const char *p, int *pos)
+{
+	char		 buf[5];
+	int		 i;
+
+	/* Not all types accept modifiers. */
+
+	switch (cp->pos) {
+	case (TBL_CELL_DOWN):
+		/* FALLTHROUGH */
+	case (TBL_CELL_HORIZ):
+		/* FALLTHROUGH */
+	case (TBL_CELL_DHORIZ):
+		/* FALLTHROUGH */
+	case (TBL_CELL_VERT):
+		/* FALLTHROUGH */
+	case (TBL_CELL_DVERT):
+		return(1);
+	default:
+		break;
+	}
+
+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;
+	}
+
+	/* Throw away parenthesised expression. */
+
+	if ('(' == p[*pos]) {
+		(*pos)++;
+		while (p[*pos] && ')' != p[*pos])
+			(*pos)++;
+		if (')' == p[*pos]) {
+			(*pos)++;
+			goto mod;
+		}
+		mandoc_msg(MANDOCERR_TBLLAYOUT, 
+				tbl->parse, ln, *pos, NULL);
+		return(0);
+	}
+
+	/* 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);
+
+		goto mod;
+		/* NOTREACHED */
+	} 
+
+	/* TODO: GNU has many more extensions. */
+
+	switch (tolower((unsigned char)p[(*pos)++])) {
+	case ('z'):
+		cp->flags |= TBL_CELL_WIGN;
+		goto mod;
+	case ('u'):
+		cp->flags |= TBL_CELL_UP;
+		goto mod;
+	case ('e'):
+		cp->flags |= TBL_CELL_EQUAL;
+		goto mod;
+	case ('t'):
+		cp->flags |= TBL_CELL_TALIGN;
+		goto mod;
+	case ('d'):
+		cp->flags |= TBL_CELL_BALIGN;
+		goto mod;
+	case ('w'):  /* XXX for now, ignore minimal column width */
+		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);
+	}
+
+	switch (tolower((unsigned char)p[(*pos)++])) {
+	case ('3'):
+		/* FALLTHROUGH */
+	case ('b'):
+		cp->flags |= TBL_CELL_BOLD;
+		goto mod;
+	case ('2'):
+		/* FALLTHROUGH */
+	case ('i'):
+		cp->flags |= TBL_CELL_ITALIC;
+		goto mod;
+	case ('1'):
+		/* FALLTHROUGH */
+	case ('r'):
+		goto mod;
+	default:
+		break;
+	}
+
+	mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+			ln, *pos - 1, NULL);
+	return(0);
+}
+
+static int
+cell(struct tbl_node *tbl, struct tbl_row *rp, 
+		int ln, const char *p, int *pos)
+{
+	int		 i;
+	enum tbl_cellt	 c;
+
+	/* Parse the column position (`r', `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);
+	}
+
+	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_VERT):
+			case (TBL_CELL_DVERT):
+			case (TBL_CELL_HORIZ):
+			case (TBL_CELL_DHORIZ):
+				mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse,
+						ln, *pos, NULL);
+				return(0);
+			default:
+				break;
+			}
+	}
+
+	/*
+	 * 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);
+	}
+
+	(*pos)++;
+
+	/* Extra check for the double-vertical. */
+
+	if (TBL_CELL_VERT == c && '|' == p[*pos]) {
+		(*pos)++;
+		c = TBL_CELL_DVERT;
+	} 
+	
+	/* Disallow adjacent spacers. */
+
+	if (rp->last && (TBL_CELL_VERT == c || TBL_CELL_DVERT == c) &&
+			(TBL_CELL_VERT == rp->last->pos || 
+			 TBL_CELL_DVERT == rp->last->pos)) {
+		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), ln, p, pos));
+}
+
+
+static void
+row(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;
+		tbl->last_row = rp;
+	} else
+		tbl->last_row = tbl->first_row = rp;
+
+cell:
+	while (isspace((unsigned char)p[*pos]))
+		(*pos)++;
+
+	/* Safely exit layout context. */
+
+	if ('.' == p[*pos]) {
+		tbl->part = TBL_PART_DATA;
+		if (NULL == tbl->first_row) 
+			mandoc_msg(MANDOCERR_TBLNOLAYOUT, tbl->parse, 
+					ln, *pos, NULL);
+		(*pos)++;
+		return;
+	}
+
+	/* 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)
+{
+	struct tbl_cell	*p, *pp;
+	struct tbl_head	*h, *hp;
+
+	p = mandoc_calloc(1, sizeof(struct tbl_cell));
+
+	if (NULL != (pp = rp->last)) {
+		rp->last->next = p;
+		rp->last = p;
+	} else
+		rp->last = rp->first = p;
+
+	p->pos = pos;
+
+	/*
+	 * This is a little bit complicated.  Here we determine the
+	 * header the corresponds to a cell.  We add headers dynamically
+	 * when need be or re-use them, otherwise.  As an example, given
+	 * the following:
+	 *
+	 * 	1  c || l 
+	 * 	2  | c | l
+	 * 	3  l l
+	 * 	3  || c | l |.
+	 *
+	 * We first add the new headers (as there are none) in (1); then
+	 * in (2) we insert the first spanner (as it doesn't match up
+	 * with the header); then we re-use the prior data headers,
+	 * skipping over the spanners; then we re-use everything and add
+	 * a last spanner.  Note that VERT headers are made into DVERT
+	 * ones.
+	 */
+
+	h = pp ? pp->head->next : tbl->first_head;
+
+	if (h) {
+		/* Re-use data header. */
+		if (TBL_HEAD_DATA == h->pos && 
+				(TBL_CELL_VERT != p->pos &&
+				 TBL_CELL_DVERT != p->pos)) {
+			p->head = h;
+			return(p);
+		}
+
+		/* Re-use spanner header. */
+		if (TBL_HEAD_DATA != h->pos && 
+				(TBL_CELL_VERT == p->pos ||
+				 TBL_CELL_DVERT == p->pos)) {
+			head_adjust(p, h);
+			p->head = h;
+			return(p);
+		}
+
+		/* Right-shift headers with a new spanner. */
+		if (TBL_HEAD_DATA == h->pos && 
+				(TBL_CELL_VERT == p->pos ||
+				 TBL_CELL_DVERT == p->pos)) {
+			hp = mandoc_calloc(1, sizeof(struct tbl_head));
+			hp->ident = tbl->opts.cols++;
+			hp->prev = h->prev;
+			if (h->prev)
+				h->prev->next = hp;
+			if (h == tbl->first_head)
+				tbl->first_head = hp;
+			h->prev = hp;
+			hp->next = h;
+			head_adjust(p, hp);
+			p->head = hp;
+			return(p);
+		}
+
+		if (NULL != (h = h->next)) {
+			head_adjust(p, h);
+			p->head = h;
+			return(p);
+		}
+
+		/* Fall through to default case... */
+	}
+
+	hp = mandoc_calloc(1, sizeof(struct tbl_head));
+	hp->ident = tbl->opts.cols++;
+
+	if (tbl->last_head) {
+		hp->prev = tbl->last_head;
+		tbl->last_head->next = hp;
+		tbl->last_head = hp;
+	} else
+		tbl->last_head = tbl->first_head = hp;
+
+	head_adjust(p, hp);
+	p->head = hp;
+	return(p);
+}
+
+static void
+head_adjust(const struct tbl_cell *cellp, struct tbl_head *head)
+{
+	if (TBL_CELL_VERT != cellp->pos &&
+			TBL_CELL_DVERT != cellp->pos) {
+		head->pos = TBL_HEAD_DATA;
+		return;
+	}
+
+	if (TBL_CELL_VERT == cellp->pos)
+		if (TBL_HEAD_DVERT != head->pos)
+			head->pos = TBL_HEAD_VERT;
+
+	if (TBL_CELL_DVERT == cellp->pos)
+		head->pos = TBL_HEAD_DVERT;
+}
+
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/tbl_opts.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,270 @@
+/*	$Id: tbl_opts.c,v 1.12 2011/09/18 14:14:15 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#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
+};
+
+struct	tbl_phrase {
+	const char	*name;
+	int		 key;
+	enum tbl_ident	 ident;
+};
+
+/* Handle Commonwealth/American spellings. */
+#define	KEY_MAXKEYS	 14
+
+/* Maximum length of key name string. */
+#define	KEY_MAXNAME	 13
+
+/* Maximum length of key number size. */
+#define	KEY_MAXNUMSZ	 10
+
+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)
+{
+	int		 i;
+	char		 buf[KEY_MAXNUMSZ];
+
+	while (isspace((unsigned char)p[*pos]))
+		(*pos)++;
+
+	/* Arguments always begin with a parenthesis. */
+
+	if ('(' != p[*pos]) {
+		mandoc_msg(MANDOCERR_TBL, tbl->parse, 
+				ln, *pos, NULL);
+		return(0);
+	}
+
+	(*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);
+		} 
+		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 (')' == p[(*pos)++])
+		return(1);
+
+	mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos - 1, NULL);
+	return(0);
+}
+
+static void
+opt(struct tbl_node *tbl, int ln, const char *p, int *pos)
+{
+	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.
+	 */
+
+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:]+ [)]
+	 */
+
+	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;
+	}
+
+	/* Exit if buffer is empty (or overrun). */
+
+	if (KEY_MAXNAME == i || 0 == i) {
+		mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL);
+		return;
+	}
+
+	buf[i] = '\0';
+
+	while (isspace((unsigned char)p[*pos]))
+		(*pos)++;
+
+	/* 
+	 * Look through all of the available keys to find one that
+	 * matches the input.  FIXME: hashtable this.
+	 */
+
+	for (i = 0; i < KEY_MAXKEYS; i++) {
+		if (strcmp(buf, keys[i].name))
+			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.
+		 */
+
+		if (keys[i].key) 
+			tbl->opts.opts |= keys[i].key;
+		else if ( ! arg(tbl, ln, p, pos, keys[i].ident))
+			return;
+
+		break;
+	}
+
+	/* 
+	 * 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);
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/tbl_term.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,444 @@
+/*	$Id: tbl_term.c,v 1.21 2011/09/20 23:05:49 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "term.h"
+
+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 *,
+			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 *, 
+			const struct roffcol *);
+static	void	tbl_number(struct termp *, const struct tbl *, 
+			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 size_t
+term_tbl_strlen(const char *p, void *arg)
+{
+
+	return(term_strlen((const struct termp *)arg, p));
+}
+
+static size_t
+term_tbl_len(size_t sz, void *arg)
+{
+
+	return(term_len((const struct termp *)arg, sz));
+}
+
+void
+term_tbl(struct termp *tp, const struct tbl_span *sp)
+{
+	const struct tbl_head	*hp;
+	const struct tbl_dat	*dp;
+	struct roffcol		*col;
+	int			 spans;
+	size_t		   	 rmargin, maxrmargin;
+
+	rmargin = tp->rmargin;
+	maxrmargin = tp->maxrmargin;
+
+	tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
+
+	/* Inhibit printing of spaces: we do padding ourselves. */
+
+	tp->flags |= TERMP_NONOSPACE;
+	tp->flags |= TERMP_NOSPACE;
+
+	/*
+	 * The first time we're invoked for a given table block,
+	 * calculate the table widths and decimal positions.
+	 */
+
+	if (TBL_SPAN_FIRST & sp->flags) {
+		term_flushln(tp);
+
+		tp->tbl.len = term_tbl_len;
+		tp->tbl.slen = term_tbl_strlen;
+		tp->tbl.arg = tp;
+
+		tblcalc(&tp->tbl, sp);
+	}
+
+	/* Horizontal frame at the start of boxed tables. */
+
+	if (TBL_SPAN_FIRST & sp->flags) {
+		if (TBL_OPT_DBOX & sp->tbl->opts)
+			tbl_hframe(tp, sp, 1);
+		if (TBL_OPT_DBOX & sp->tbl->opts ||
+		    TBL_OPT_BOX  & sp->tbl->opts)
+			tbl_hframe(tp, sp, 0);
+	}
+
+	/* Vertical frame at the start of each row. */
+
+	if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
+		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
+			TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
+
+	/*
+	 * 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.
+	 */
+
+	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. */
+		dp = sp->first;
+		spans = 0;
+		for (hp = sp->head; hp; hp = hp->next) {
+			/* 
+			 * If the current data header is invoked during
+			 * a spanner ("spans" > 0), don't emit anything
+			 * at all.
+			 */
+			switch (hp->pos) {
+			case (TBL_HEAD_VERT):
+				/* FALLTHROUGH */
+			case (TBL_HEAD_DVERT):
+				if (spans <= 0)
+					tbl_vrule(tp, hp);
+				continue;
+			case (TBL_HEAD_DATA):
+				break;
+			}
+
+			if (--spans >= 0)
+				continue;
+
+			/*
+			 * All cells get a leading blank, except the
+			 * first one and those after double rulers.
+			 */
+
+			if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
+				tbl_char(tp, ASCII_NBRSP, 1);
+
+			col = &tp->tbl.cols[hp->ident];
+			tbl_data(tp, sp->tbl, dp, col);
+
+			/* No trailing blanks. */
+
+			if (NULL == hp->next)
+				break;
+
+			/*
+			 * Add another blank between cells,
+			 * or two when there is no vertical ruler.
+			 */
+
+			tbl_char(tp, ASCII_NBRSP,
+			    TBL_HEAD_VERT  == hp->next->pos ||
+			    TBL_HEAD_DVERT == hp->next->pos ? 1 : 2);
+
+			/* 
+			 * Go to the next data cell and assign the
+			 * number of subsequent spans, if applicable.
+			 */
+
+			if (dp) {
+				spans = dp->spans;
+				dp = dp->next;
+			}
+		}
+		break;
+	}
+
+	/* Vertical frame at the end of each row. */
+
+	if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
+		term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
+			TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
+	term_flushln(tp);
+
+	/*
+	 * If we're the last row, clean up after ourselves: clear the
+	 * existing table configuration and set it to NULL.
+	 */
+
+	if (TBL_SPAN_LAST & sp->flags) {
+		if (TBL_OPT_DBOX & sp->tbl->opts ||
+		    TBL_OPT_BOX  & sp->tbl->opts)
+			tbl_hframe(tp, sp, 0);
+		if (TBL_OPT_DBOX & sp->tbl->opts)
+			tbl_hframe(tp, sp, 1);
+		assert(tp->tbl.cols);
+		free(tp->tbl.cols);
+		tp->tbl.cols = NULL;
+	}
+
+	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;
+	if (TBL_HEAD_DATA == hp->pos) {
+		/* Account for leading blanks. */
+		if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
+			width++;
+		/* Account for trailing blanks. */
+		width++;
+		if (hp->next &&
+		    TBL_HEAD_VERT  != hp->next->pos &&
+		    TBL_HEAD_DVERT != hp->next->pos)
+			width++;
+	}
+	return(width);
+}
+
+/*
+ * Rules inside the table can be single or double
+ * and have crossings with vertical rules marked with pluses.
+ */
+static void
+tbl_hrule(struct termp *tp, const struct tbl_span *sp)
+{
+	const struct tbl_head *hp;
+	char		 c;
+
+	c = '-';
+	if (TBL_SPAN_DHORIZ == sp->pos)
+		c = '=';
+
+	for (hp = sp->head; hp; hp = hp->next)
+		tbl_char(tp,
+		    TBL_HEAD_DATA == hp->pos ? c : '+',
+		    tbl_rulewidth(tp, hp));
+}
+
+/*
+ * 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)
+		tbl_char(tp,
+		    outer || TBL_HEAD_DATA == hp->pos ? '-' : '+',
+		    tbl_rulewidth(tp, hp));
+	term_word(tp, "+");
+	term_flushln(tp);
+}
+
+static void
+tbl_data(struct termp *tp, const struct tbl *tbl,
+		const struct tbl_dat *dp, 
+		const struct roffcol *col)
+{
+
+	if (NULL == dp) {
+		tbl_char(tp, ASCII_NBRSP, col->width);
+		return;
+	}
+	assert(dp->layout);
+
+	switch (dp->pos) {
+	case (TBL_DATA_NONE):
+		tbl_char(tp, ASCII_NBRSP, col->width);
+		return;
+	case (TBL_DATA_HORIZ):
+		/* FALLTHROUGH */
+	case (TBL_DATA_NHORIZ):
+		tbl_char(tp, '-', col->width);
+		return;
+	case (TBL_DATA_NDHORIZ):
+		/* FALLTHROUGH */
+	case (TBL_DATA_DHORIZ):
+		tbl_char(tp, '=', col->width);
+		return;
+	default:
+		break;
+	}
+	
+	switch (dp->layout->pos) {
+	case (TBL_CELL_HORIZ):
+		tbl_char(tp, '-', col->width);
+		break;
+	case (TBL_CELL_DHORIZ):
+		tbl_char(tp, '=', col->width);
+		break;
+	case (TBL_CELL_LONG):
+		/* FALLTHROUGH */
+	case (TBL_CELL_CENTRE):
+		/* FALLTHROUGH */
+	case (TBL_CELL_LEFT):
+		/* FALLTHROUGH */
+	case (TBL_CELL_RIGHT):
+		tbl_literal(tp, dp, col);
+		break;
+	case (TBL_CELL_NUMBER):
+		tbl_number(tp, tbl, dp, col);
+		break;
+	case (TBL_CELL_DOWN):
+		tbl_char(tp, ASCII_NBRSP, col->width);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+}
+
+static void
+tbl_vrule(struct termp *tp, const struct tbl_head *hp)
+{
+
+	switch (hp->pos) {
+	case (TBL_HEAD_VERT):
+		term_word(tp, "|");
+		break;
+	case (TBL_HEAD_DVERT):
+		term_word(tp, "||");
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+tbl_char(struct termp *tp, char c, size_t len)
+{
+	size_t		i, sz;
+	char		cp[2];
+
+	cp[0] = c;
+	cp[1] = '\0';
+
+	sz = term_strlen(tp, cp);
+
+	for (i = 0; i < len; i += sz)
+		term_word(tp, cp);
+}
+
+static void
+tbl_literal(struct termp *tp, const struct tbl_dat *dp, 
+		const struct roffcol *col)
+{
+	size_t		 len, padl, padr;
+
+	assert(dp->string);
+	len = term_strlen(tp, dp->string);
+	padr = col->width > len ? col->width - len : 0;
+	padl = 0;
+
+	switch (dp->layout->pos) {
+	case (TBL_CELL_LONG):
+		padl = term_len(tp, 1);
+		padr = padr > padl ? padr - padl : 0;
+		break;
+	case (TBL_CELL_CENTRE):
+		if (2 > padr)
+			break;
+		padl = padr / 2;
+		padr -= padl;
+		break;
+	case (TBL_CELL_RIGHT):
+		padl = padr;
+		padr = 0;
+		break;
+	default:
+		break;
+	}
+
+	tbl_char(tp, ASCII_NBRSP, padl);
+	term_word(tp, dp->string);
+	tbl_char(tp, ASCII_NBRSP, padr);
+}
+
+static void
+tbl_number(struct termp *tp, const struct tbl *tbl,
+		const struct tbl_dat *dp,
+		const struct roffcol *col)
+{
+	char		*cp;
+	char		 buf[2];
+	size_t		 sz, psz, ssz, d, padl;
+	int		 i;
+
+	/*
+	 * See calc_data_number().  Left-pad by taking the offset of our
+	 * and the maximum decimal; right-pad by the remaining amount.
+	 */
+
+	assert(dp->string);
+
+	sz = term_strlen(tp, dp->string);
+
+	buf[0] = tbl->decimal;
+	buf[1] = '\0';
+
+	psz = term_strlen(tp, buf);
+
+	if (NULL != (cp = strrchr(dp->string, tbl->decimal))) {
+		buf[1] = '\0';
+		for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
+			buf[0] = dp->string[i];
+			ssz += term_strlen(tp, buf);
+		}
+		d = ssz + psz;
+	} else
+		d = sz + psz;
+
+	padl = col->decimal - d;
+
+	tbl_char(tp, ASCII_NBRSP, padl);
+	term_word(tp, dp->string);
+	if (col->width > sz + padl)
+		tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
+}
+
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/term.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,736 @@
+/*	$Id: term.c,v 1.201 2011/09/21 09:57:13 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "term.h"
+#include "main.h"
+
+static	void		 adjbuf(struct termp *p, int);
+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);
+}
+
+
+void
+term_begin(struct termp *p, term_margin head, 
+		term_margin foot, const void *arg)
+{
+
+	p->headf = head;
+	p->footf = foot;
+	p->argf = arg;
+	(*p->begin)(p);
+}
+
+
+void
+term_end(struct termp *p)
+{
+
+	(*p->end)(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.
+ *
+ * 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.
+ *
+ *  - TERMP_TWOSPACE: make sure there is room for at least two space
+ *    characters of padding.  Otherwise, rather break the line.
+ *
+ *  - 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.
+ */
+void
+term_flushln(struct termp *p)
+{
+	int		 i;     /* current input position in p->buf */
+	size_t		 vis;   /* current visual position on output */
+	size_t		 vbl;   /* number of blanks to prepend to output */
+	size_t		 vend;	/* end of word visual position on output */
+	size_t		 bp;    /* visual right border position */
+	size_t		 dv;    /* temporary for visual pos calculations */
+	int		 j;     /* temporary loop index for p->buf */
+	int		 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.
+	 * This is usually the difference between the right-margin and
+	 * an indentation, but can be, for tagged lists or columns, a
+	 * small set of values. 
+	 */
+	assert  (p->rmargin >= p->offset);
+	dv     = p->rmargin - p->offset;
+	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;
+
+	/*
+	 * Calculate the required amount of padding.
+	 */
+	vbl = p->offset + p->overstep > p->viscol ?
+	      p->offset + p->overstep - p->viscol : 0;
+
+	vis = vend = 0;
+	i = 0;
+
+	while (i < p->col) {
+		/*
+		 * Handle literal tab characters: collapse all
+		 * subsequent tabs into a single huge set of spaces.
+		 */
+		while (i < p->col && '\t' == p->buf[i]) {
+			vend = (vis / p->tabwidth + 1) * p->tabwidth;
+			vbl += vend - vis;
+			vis = vend;
+			i++;
+		}
+
+		/*
+		 * Count up visible word characters.  Control sequences
+		 * (starting with the CSI) aren't counted.  A space
+		 * generates a non-printing word, which is valid (the
+		 * space is printed according to regular spacing rules).
+		 */
+
+		for (j = i, jhy = 0; j < p->col; j++) {
+			if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j])
+				break;
+
+			/* Back over the the last printed character. */
+			if (8 == p->buf[j]) {
+				assert(j);
+				vend -= (*p->width)(p, p->buf[j - 1]);
+				continue;
+			}
+
+			/* Regular word. */
+			/* Break at the hyphen point if we overrun. */
+			if (vend > vis && vend < bp && 
+					ASCII_HYPH == p->buf[j])
+				jhy = j;
+
+			vend += (*p->width)(p, p->buf[j]);
+		}
+
+		/*
+		 * Find out whether we would exceed the right margin.
+		 * If so, break to the next line.
+		 */
+		if (vend > bp && 0 == jhy && vis > 0) {
+			vend -= vis;
+			(*p->endline)(p);
+			p->viscol = 0;
+			if (TERMP_NOBREAK & p->flags) {
+				vbl = p->rmargin;
+				vend += p->rmargin - p->offset;
+			} else
+				vbl = p->offset;
+
+			/* Remove the p->overstep width. */
+
+			bp += (size_t)p->overstep;
+			p->overstep = 0;
+		}
+
+		/* Write out the [remaining] word. */
+		for ( ; i < p->col; i++) {
+			if (vend > bp && jhy > 0 && i > jhy)
+				break;
+			if ('\t' == p->buf[i])
+				break;
+			if (' ' == p->buf[i]) {
+				j = i;
+				while (' ' == p->buf[i])
+					i++;
+				dv = (size_t)(i - j) * (*p->width)(p, ' ');
+				vbl += dv;
+				vend += dv;
+				break;
+			}
+			if (ASCII_NBRSP == p->buf[i]) {
+				vbl += (*p->width)(p, ' ');
+				continue;
+			}
+
+			/*
+			 * Now we definitely know there will be
+			 * printable characters to output,
+			 * so write preceding white space now.
+			 */
+			if (vbl) {
+				(*p->advance)(p, vbl);
+				p->viscol += vbl;
+				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 
+				p->viscol += (*p->width)(p, p->buf[i]);
+		}
+		vis = vend;
+	}
+
+	/*
+	 * If there was trailing white space, it was not printed;
+	 * so reset the cursor position accordingly.
+	 */
+	if (vis)
+		vis -= vbl;
+
+	p->col = 0;
+	p->overstep = 0;
+
+	if ( ! (TERMP_NOBREAK & p->flags)) {
+		p->viscol = 0;
+		(*p->endline)(p);
+		return;
+	}
+
+	if (TERMP_HANG & p->flags) {
+		/* We need one blank after the tag. */
+		p->overstep = (int)(vis - maxvis + (*p->width)(p, ' '));
+
+		/*
+		 * Behave exactly the same way as groff:
+		 * If we have overstepped the margin, temporarily move
+		 * it to the right and flag the rest of the line to be
+		 * shorter.
+		 * If we landed right at the margin, be happy.
+		 * If we are one step before the margin, temporarily
+		 * move it one step LEFT and flag the rest of the line
+		 * to be longer.
+		 */
+		if (p->overstep < -1)
+			p->overstep = 0;
+		return;
+
+	} else if (TERMP_DANGLE & p->flags)
+		return;
+
+	/* If the column was overrun, break the line. */
+	if (maxvis <= vis +
+	    ((TERMP_TWOSPACE & p->flags) ? (*p->width)(p, ' ') : 0)) {
+		(*p->endline)(p);
+		p->viscol = 0;
+	}
+}
+
+
+/* 
+ * 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.
+ */
+void
+term_newln(struct termp *p)
+{
+
+	p->flags |= TERMP_NOSPACE;
+	if (p->col || p->viscol)
+		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.
+ * All data in the output buffer is flushed prior to the newline
+ * assertion.
+ */
+void
+term_vspace(struct termp *p)
+{
+
+	term_newln(p);
+	p->viscol = 0;
+	(*p->endline)(p);
+}
+
+void
+term_fontlast(struct termp *p)
+{
+	enum termfont	 f;
+
+	f = p->fontl;
+	p->fontl = p->fontq[p->fonti];
+	p->fontq[p->fonti] = f;
+}
+
+
+void
+term_fontrepl(struct termp *p, enum termfont f)
+{
+
+	p->fontl = p->fontq[p->fonti];
+	p->fontq[p->fonti] = f;
+}
+
+
+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]);
+}
+
+
+void
+term_fontpopq(struct termp *p, const void *key)
+{
+
+	while (p->fonti >= 0 && key != &p->fontq[p->fonti])
+		p->fonti--;
+	assert(p->fonti >= 0);
+}
+
+
+void
+term_fontpop(struct termp *p)
+{
+
+	assert(p->fonti);
+	p->fonti--;
+}
+
+/*
+ * Handle pwords, partial words, which may be either a single word or a
+ * phrase that cannot be broken down (such as a literal string).  This
+ * handles word styling.
+ */
+void
+term_word(struct termp *p, const char *word)
+{
+	const char	*seq, *cp;
+	char		 c;
+	int		 sz, uc;
+	size_t		 ssz;
+	enum mandoc_esc	 esc;
+
+	if ( ! (TERMP_NOSPACE & p->flags)) {
+		if ( ! (TERMP_KEEP & p->flags)) {
+			if (TERMP_PREKEEP & p->flags)
+				p->flags |= TERMP_KEEP;
+			bufferc(p, ' ');
+			if (TERMP_SENTENCE & p->flags)
+				bufferc(p, ' ');
+		} else
+			bufferc(p, ASCII_NBRSP);
+	}
+
+	if ( ! (p->flags & TERMP_NONOSPACE))
+		p->flags &= ~TERMP_NOSPACE;
+	else
+		p->flags |= TERMP_NOSPACE;
+
+	p->flags &= ~(TERMP_SENTENCE | TERMP_IGNDELIM);
+
+	while ('\0' != *word) {
+		if ((ssz = strcspn(word, "\\")) > 0)
+			encode(p, word, ssz);
+
+		word += (int)ssz;
+		if ('\\' != *word)
+			continue;
+
+		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;
+			}
+
+		switch (esc) {
+		case (ESCAPE_UNICODE):
+			encode1(p, '?');
+			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);
+			break;
+		case (ESCAPE_FONTBOLD):
+			term_fontrepl(p, TERMFONT_BOLD);
+			break;
+		case (ESCAPE_FONTITALIC):
+			term_fontrepl(p, TERMFONT_UNDER);
+			break;
+		case (ESCAPE_FONT):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTROMAN):
+			term_fontrepl(p, TERMFONT_NONE);
+			break;
+		case (ESCAPE_FONTPREV):
+			term_fontlast(p);
+			break;
+		case (ESCAPE_NOSPACE):
+			if ('\0' == *word)
+				p->flags |= TERMP_NOSPACE;
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static void
+adjbuf(struct termp *p, int sz)
+{
+
+	if (0 == p->maxcols)
+		p->maxcols = 1024;
+	while (sz >= p->maxcols)
+		p->maxcols <<= 2;
+
+	p->buf = mandoc_realloc
+		(p->buf, sizeof(int) * (size_t)p->maxcols);
+}
+
+static void
+bufferc(struct termp *p, char c)
+{
+
+	if (p->col + 1 >= p->maxcols)
+		adjbuf(p, p->col + 1);
+
+	p->buf[p->col++] = c;
+}
+
+/*
+ * See encode().
+ * Do this for a single (probably unicode) value.
+ * Does not check for non-decorated glyphs.
+ */
+static void
+encode1(struct termp *p, int c)
+{
+	enum termfont	  f;
+
+	if (p->col + 4 >= p->maxcols)
+		adjbuf(p, p->col + 4);
+
+	f = term_fonttop(p);
+
+	if (TERMFONT_NONE == f) {
+		p->buf[p->col++] = c;
+		return;
+	} else if (TERMFONT_UNDER == f) {
+		p->buf[p->col++] = '_';
+	} else
+		p->buf[p->col++] = c;
+
+	p->buf[p->col++] = 8;
+	p->buf[p->col++] = c;
+}
+
+static void
+encode(struct termp *p, const char *word, size_t sz)
+{
+	enum termfont	  f;
+	int		  i, len;
+
+	/* LINTED */
+	len = sz;
+
+	/*
+	 * Encode and buffer a string of characters.  If the current
+	 * font mode is unset, buffer directly, else encode then buffer
+	 * character by character.
+	 */
+
+	if (TERMFONT_NONE == (f = term_fonttop(p))) {
+		if (p->col + len >= p->maxcols) 
+			adjbuf(p, p->col + len);
+		for (i = 0; i < len; i++)
+			p->buf[p->col++] = word[i];
+		return;
+	}
+
+	/* Pre-buffer, assuming worst-case. */
+
+	if (p->col + 1 + (len * 3) >= p->maxcols)
+		adjbuf(p, p->col + 1 + (len * 3));
+
+	for (i = 0; i < len; i++) {
+		if (ASCII_HYPH != word[i] &&
+		    ! isgraph((unsigned char)word[i])) {
+			p->buf[p->col++] = word[i];
+			continue;
+		}
+
+		if (TERMFONT_UNDER == f)
+			p->buf[p->col++] = '_';
+		else if (ASCII_HYPH == word[i])
+			p->buf[p->col++] = '-';
+		else
+			p->buf[p->col++] = word[i];
+
+		p->buf[p->col++] = 8;
+		p->buf[p->col++] = word[i];
+	}
+}
+
+size_t
+term_len(const struct termp *p, size_t sz)
+{
+
+	return((*p->width)(p, ' ') * sz);
+}
+
+
+size_t
+term_strlen(const struct termp *p, const char *cp)
+{
+	size_t		 sz, rsz, i;
+	int		 ssz, c;
+	const char	*seq, *rhs;
+	enum mandoc_esc	 esc;
+	static const char rej[] = { '\\', ASCII_HYPH, ASCII_NBRSP, '\0' };
+
+	/*
+	 * Account for escaped sequences within string length
+	 * calculations.  This follows the logic in term_word() as we
+	 * must calculate the width of produced strings.
+	 */
+
+	sz = 0;
+	while ('\0' != *cp) {
+		rsz = strcspn(cp, rej);
+		for (i = 0; i < rsz; i++)
+			sz += (*p->width)(p, *cp++);
+
+		c = 0;
+		switch (*cp) {
+		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 += (*p->width)(p, c);
+					continue;
+				case (ESCAPE_SPECIAL):
+					c = mchars_spec2cp
+						(p->symtab, seq, ssz);
+					if (c <= 0)
+						break;
+					sz += (*p->width)(p, c);
+					continue;
+				default:
+					break;
+				}
+
+			rhs = NULL;
+
+			switch (esc) {
+			case (ESCAPE_UNICODE):
+				sz += (*p->width)(p, '?');
+				break;
+			case (ESCAPE_NUMBERED):
+				c = mchars_num2char(seq, ssz);
+				if ('\0' != c)
+					sz += (*p->width)(p, c);
+				break;
+			case (ESCAPE_SPECIAL):
+				rhs = mchars_spec2str
+					(p->symtab, seq, ssz, &rsz);
+
+				if (ssz != 1 || rhs)
+					break;
+
+				rhs = seq;
+				rsz = ssz;
+				break;
+			default:
+				break;
+			}
+
+			if (NULL == rhs)
+				break;
+
+			for (i = 0; i < rsz; i++)
+				sz += (*p->width)(p, *rhs++);
+			break;
+		case (ASCII_NBRSP):
+			sz += (*p->width)(p, ' ');
+			cp++;
+			break;
+		case (ASCII_HYPH):
+			sz += (*p->width)(p, '-');
+			cp++;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return(sz);
+}
+
+/* ARGSUSED */
+size_t
+term_vspan(const struct termp *p, const struct roffsu *su)
+{
+	double		 r;
+
+	switch (su->unit) {
+	case (SCALE_CM):
+		r = su->scale * 2;
+		break;
+	case (SCALE_IN):
+		r = su->scale * 6;
+		break;
+	case (SCALE_PC):
+		r = su->scale;
+		break;
+	case (SCALE_PT):
+		r = su->scale / 8;
+		break;
+	case (SCALE_MM):
+		r = su->scale / 1000;
+		break;
+	case (SCALE_VS):
+		r = su->scale;
+		break;
+	default:
+		r = su->scale - 1;
+		break;
+	}
+
+	if (r < 0.0)
+		r = 0.0;
+	return(/* LINTED */(size_t)
+			r);
+}
+
+size_t
+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);
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/term.h	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,128 @@
+/*	$Id: term.h,v 1.90 2011/12/04 23:10:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef TERM_H
+#define TERM_H
+
+__BEGIN_DECLS
+
+struct	termp;
+
+enum	termenc {
+	TERMENC_ASCII,
+	TERMENC_LOCALE,
+	TERMENC_UTF8
+};
+
+enum	termtype {
+	TERMTYPE_CHAR,
+	TERMTYPE_PS,
+	TERMTYPE_PDF
+};
+
+enum	termfont {
+	TERMFONT_NONE = 0,
+	TERMFONT_BOLD,
+	TERMFONT_UNDER,
+	TERMFONT__MAX
+};
+
+#define	TERM_MAXMARGIN	  100000 /* FIXME */
+
+typedef void	(*term_margin)(struct termp *, const void *);
+
+struct	termp_tbl {
+	int		  width;	/* width in fixed chars */
+	int		  decimal;	/* decimal point position */
+};
+
+struct	termp {
+	enum termtype	  type;
+	struct rofftbl	  tbl;		/* table configuration */
+	int		  mdocstyle;	/* imitate mdoc(7) output */
+	size_t		  defindent;	/* Default indent for text. */
+	size_t		  defrmargin;	/* Right margin of the device. */
+	size_t		  rmargin;	/* Current right margin. */
+	size_t		  maxrmargin;	/* Max right margin. */
+	int		  maxcols;	/* Max size of buf. */
+	size_t		  offset;	/* Margin offest. */
+	size_t		  tabwidth;	/* Distance of tab positions. */
+	int		  col;		/* Bytes in buf. */
+	size_t		  viscol;	/* Chars on current line. */
+	int		  overstep;	/* See termp_flushln(). */
+	int		  flags;
+#define	TERMP_SENTENCE	 (1 << 1)	/* Space before a sentence. */
+#define	TERMP_NOSPACE	 (1 << 2)	/* No space before words. */
+#define	TERMP_NOBREAK	 (1 << 4)	/* See term_flushln(). */
+#define	TERMP_IGNDELIM	 (1 << 6)	/* Delims like regulars. */
+#define	TERMP_NONOSPACE	 (1 << 7)	/* No space (no autounset). */
+#define	TERMP_DANGLE	 (1 << 8)	/* See term_flushln(). */
+#define	TERMP_HANG	 (1 << 9)	/* See term_flushln(). */
+#define	TERMP_TWOSPACE	 (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_KEEP	 (1 << 14)	/* Keep words together. */
+#define	TERMP_PREKEEP	 (1 << 15)	/* ...starting with the next one. */
+	int		 *buf;		/* Output buffer. */
+	enum termenc	  enc;		/* Type of encoding. */
+	struct mchars	 *symtab;	/* Encoded-symbol table. */
+	enum termfont	  fontl;	/* Last font set. */
+	enum termfont	  fontq[10];	/* Symmetric fonts. */
+	int		  fonti;	/* Index of font stack. */
+	term_margin	  headf;	/* invoked to print head */
+	term_margin	  footf;	/* invoked to print foot */
+	void		(*letter)(struct termp *, int);
+	void		(*begin)(struct termp *);
+	void		(*end)(struct termp *);
+	void		(*endline)(struct termp *);
+	void		(*advance)(struct termp *, size_t);
+	size_t		(*width)(const struct termp *, int);
+	double		(*hspan)(const struct termp *,
+				const struct roffsu *);
+	const void	 *argf;		/* arg for headf/footf */
+	struct termp_ps	 *ps;
+};
+
+void		  term_eqn(struct termp *, const struct eqn *);
+void		  term_tbl(struct termp *, const struct tbl_span *);
+void		  term_free(struct termp *);
+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, 
+			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 *);
+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_fontrepl(struct termp *, enum termfont);
+void		  term_fontlast(struct termp *);
+
+__END_DECLS
+
+#endif /*!TERM_H*/
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/term_ascii.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,289 @@
+/*	$Id: term_ascii.c,v 1.20 2011/12/04 23:10:52 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#ifdef USE_WCHAR
+# include <locale.h>
+#endif
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#ifdef USE_WCHAR
+# include <wchar.h>
+#endif
+
+#include "mandoc.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	double		  ascii_hspan(const struct termp *,
+				const struct roffsu *);
+static	size_t		  ascii_width(const struct termp *, int);
+static	void		  ascii_advance(struct termp *, size_t);
+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);
+
+#ifdef	USE_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)
+{
+	const char	*toks[4];
+	char		*v;
+	struct termp	*p;
+
+	p = mandoc_calloc(1, sizeof(struct termp));
+	p->enc = enc;
+
+	p->tabwidth = 5;
+	p->defrmargin = 78;
+
+	p->begin = ascii_begin;
+	p->end = ascii_end;
+	p->hspan = ascii_hspan;
+	p->type = TERMTYPE_CHAR;
+
+	p->enc = TERMENC_ASCII;
+	p->advance = ascii_advance;
+	p->endline = ascii_endline;
+	p->letter = ascii_letter;
+	p->width = ascii_width;
+
+#ifdef	USE_WCHAR
+	if (TERMENC_ASCII != enc) {
+		v = TERMENC_LOCALE == enc ?
+			setlocale(LC_ALL, "") :
+			setlocale(LC_CTYPE, "UTF-8");
+		if (NULL != v && MB_CUR_MAX > 1) {
+			p->enc = enc;
+			p->advance = locale_advance;
+			p->endline = locale_endline;
+			p->letter = locale_letter;
+			p->width = locale_width;
+		}
+	}
+#endif
+
+	toks[0] = "indent";
+	toks[1] = "width";
+	toks[2] = "mdoc";
+	toks[3] = NULL;
+
+	while (outopts && *outopts)
+		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
+		case (0):
+			p->defindent = (size_t)atoi(v);
+			break;
+		case (1):
+			p->defrmargin = (size_t)atoi(v);
+			break;
+		case (2):
+			/*
+			 * Temporary, undocumented mode
+			 * to imitate mdoc(7) output style.
+			 */
+			p->mdocstyle = 1;
+			p->defindent = 5;
+			break;
+		default:
+			break;
+		}
+
+	/* Enforce a lower boundary. */
+	if (p->defrmargin < 58)
+		p->defrmargin = 58;
+
+	return(p);
+}
+
+void *
+ascii_alloc(char *outopts)
+{
+
+	return(ascii_init(TERMENC_ASCII, outopts));
+}
+
+void *
+utf8_alloc(char *outopts)
+{
+
+	return(ascii_init(TERMENC_UTF8, outopts));
+}
+
+
+void *
+locale_alloc(char *outopts)
+{
+
+	return(ascii_init(TERMENC_LOCALE, outopts));
+}
+
+/* ARGSUSED */
+static size_t
+ascii_width(const struct termp *p, int c)
+{
+
+	return(1);
+}
+
+void
+ascii_free(void *arg)
+{
+
+	term_free((struct termp *)arg);
+}
+
+/* ARGSUSED */
+static void
+ascii_letter(struct termp *p, int c)
+{
+	
+	putchar(c);
+}
+
+static void
+ascii_begin(struct termp *p)
+{
+
+	(*p->headf)(p, p->argf);
+}
+
+static void
+ascii_end(struct termp *p)
+{
+
+	(*p->footf)(p, p->argf);
+}
+
+/* ARGSUSED */
+static void
+ascii_endline(struct termp *p)
+{
+
+	putchar('\n');
+}
+
+/* ARGSUSED */
+static void
+ascii_advance(struct termp *p, size_t len)
+{
+	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.
+	 */
+
+	switch (su->unit) {
+	case (SCALE_CM):
+		r = 4 * su->scale;
+		break;
+	case (SCALE_IN):
+		r = 10 * su->scale;
+		break;
+	case (SCALE_PC):
+		r = (10 * su->scale) / 6;
+		break;
+	case (SCALE_PT):
+		r = (10 * su->scale) / 72;
+		break;
+	case (SCALE_MM):
+		r = su->scale / 1000;
+		break;
+	case (SCALE_VS):
+		r = su->scale * 2 - 1;
+		break;
+	default:
+		r = su->scale;
+		break;
+	}
+
+	return(r);
+}
+
+#ifdef USE_WCHAR
+/* ARGSUSED */
+static size_t
+locale_width(const struct termp *p, int c)
+{
+	int		rc;
+
+	return((rc = wcwidth(c)) < 0 ? 0 : rc);
+}
+
+/* ARGSUSED */
+static void
+locale_advance(struct termp *p, size_t len)
+{
+	size_t	 	i;
+
+	for (i = 0; i < len; i++)
+		putwchar(L' ');
+}
+
+/* ARGSUSED */
+static void
+locale_endline(struct termp *p)
+{
+
+	putwchar(L'\n');
+}
+
+/* ARGSUSED */
+static void
+locale_letter(struct termp *p, int c)
+{
+	
+	putwchar(c);
+}
+#endif
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/term_ps.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,1185 @@
+/*	$Id: term_ps.c,v 1.54 2011/10/16 12:20:34 schwarze Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/types.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "main.h"
+#include "term.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 */ \
+	(size_t)((double)(x) * (1000.0 / (double)(p)->ps->scale))
+
+/* Convert an AFM unit "x" to a PostScript points */
+#define	AFM2PNT(p, x) /* LINTED */ \
+	((double)(x) / (1000.0 / (double)(p)->ps->scale))
+
+struct	glyph {
+	unsigned short	  wx; /* WX in AFM */
+};
+
+struct	font {
+	const char	 *name; /* FontName in AFM */
+#define	MAXCHAR		  95 /* total characters we can handle */
+	struct glyph	  gly[MAXCHAR]; /* glyph metrics */
+};
+
+struct	termp_ps {
+	int		  flags;
+#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 */
+	size_t		  pscol;	/* visible column (AFM units) */
+	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 */
+	enum termfont	  lastf;	/* last set font */
+	size_t		  scale;	/* font scaling factor */
+	size_t		  pages;	/* number of pages shown */
+	size_t		  lineheight;	/* line height (AFM units) */
+	size_t		  top;		/* body top (AFM units) */
+	size_t		  bottom;	/* body bottom (AFM units) */
+	size_t		  height;	/* page height (AFM units */
+	size_t		  width;	/* page width (AFM units) */
+	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		  pdflastpg;	/* byte of last page mark */
+	size_t		  pdfbody;	/* start of body object */
+	size_t		 *pdfobjs;	/* table of object offsets */
+	size_t		  pdfobjsz;	/* size of pdfobjs */
+};
+
+static	double		  ps_hspan(const struct termp *,
+				const struct roffsu *);
+static	size_t		  ps_width(const struct termp *, int);
+static	void		  ps_advance(struct termp *, size_t);
+static	void		  ps_begin(struct termp *);
+static	void		  ps_closepage(struct termp *);
+static	void		  ps_end(struct termp *);
+static	void		  ps_endline(struct termp *);
+static	void		  ps_fclose(struct termp *);
+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);
+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		  pdf_obj(struct termp *, size_t);
+
+/*
+ * We define, for the time being, three fonts: bold, oblique/italic, and
+ * normal (roman).  The following table hard-codes the font metrics for
+ * ASCII, i.e., 32--127.
+ */
+
+static	const struct font fonts[TERMFONT__MAX] = {
+	{ "Times-Roman", {
+		{ 250 },
+		{ 333 },
+		{ 408 },
+		{ 500 },
+		{ 500 },
+		{ 833 },
+		{ 778 },
+		{ 333 },
+		{ 333 },
+		{ 333 },
+		{ 500 },
+		{ 564 },
+		{ 250 },
+		{ 333 },
+		{ 250 },
+		{ 278 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 500 },
+		{ 278 },
+		{ 278 },
+		{ 564 },
+		{ 564 },
+		{ 564 },
+		{ 444 },
+		{ 921 },
+		{ 722 },
+		{ 667 },
+		{ 667 },
+		{ 722 },
+		{ 611 },
+		{ 556 },
+		{ 722 },
+		{ 722 },
+		{ 333 },
+		{ 389 },
+		{ 722 },
+		{ 611 },
+		{ 889 },
+		{ 722 },
+		{ 722 },
+		{ 556 },
+		{ 722 },
+		{ 667 },
+		{ 556 },
+		{ 611 },
+		{ 722 },
+		{ 722 },
+		{ 944 },
+		{ 722 },
+		{ 722 },
+		{ 611 },
+		{ 333 },
+		{ 278 },
+		{ 333 },
+		{ 469 },
+		{ 500 },
+		{ 333 },
+		{ 444 },
+		{ 500 },
+		{ 444 },
+		{  500},
+		{  444},
+		{  333},
+		{  500},
+		{  500},
+		{  278},
+		{  278},
+		{  500},
+		{  278},
+		{  778},
+		{  500},
+		{  500},
+		{  500},
+		{  500},
+		{  333},
+		{  389},
+		{  278},
+		{  500},
+		{  500},
+		{  722},
+		{  500},
+		{  500},
+		{  444},
+		{  480},
+		{  200},
+		{  480},
+		{  541},
+	} },
+	{ "Times-Bold", {
+		{ 250  },
+		{ 333  },
+		{ 555  },
+		{ 500  },
+		{ 500  },
+		{ 1000 },
+		{ 833  },
+		{ 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  },
+		{ 930  },
+		{ 722  },
+		{ 667  },
+		{ 722  },
+		{ 722  },
+		{ 667  },
+		{ 611  },
+		{ 778  },
+		{ 778  },
+		{ 389  },
+		{ 500  },
+		{ 778  },
+		{ 667  },
+		{ 944  },
+		{ 722  },
+		{ 778  },
+		{ 611  },
+		{ 778  },
+		{ 722  },
+		{ 556  },
+		{ 667  },
+		{ 722  },
+		{ 722  },
+		{ 1000 },
+		{ 722  },
+		{ 722  },
+		{ 667  },
+		{ 333  },
+		{ 278  },
+		{ 333  },
+		{ 581  },
+		{ 500  },
+		{ 333  },
+		{ 500  },
+		{ 556  },
+		{ 444  },
+		{  556 },
+		{  444 },
+		{  333 },
+		{  500 },
+		{  556 },
+		{  278 },
+		{  333 },
+		{  556 },
+		{  278 },
+		{  833 },
+		{  556 },
+		{  500 },
+		{  556 },
+		{  556 },
+		{  444 },
+		{  389 },
+		{  333 },
+		{  556 },
+		{  500 },
+		{  722 },
+		{  500 },
+		{  500 },
+		{  444 },
+		{  394 },
+		{  220 },
+		{  394 },
+		{  520 },
+	} },
+	{ "Times-Italic", {
+		{ 250  },
+		{ 333  },
+		{ 420  },
+		{ 500  },
+		{ 500  },
+		{ 833  },
+		{ 778  },
+		{ 333  },
+		{ 333  },
+		{ 333  },
+		{ 500  },
+		{ 675  },
+		{ 250  },
+		{ 333  },
+		{ 250  },
+		{ 278  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 500  },
+		{ 333  },
+		{ 333  },
+		{ 675  },
+		{ 675  },
+		{ 675  },
+		{ 500  },
+		{ 920  },
+		{ 611  },
+		{ 611  },
+		{ 667  },
+		{ 722  },
+		{ 611  },
+		{ 611  },
+		{ 722  },
+		{ 722  },
+		{ 333  },
+		{ 444  },
+		{ 667  },
+		{ 556  },
+		{ 833  },
+		{ 667  },
+		{ 722  },
+		{ 611  },
+		{ 722  },
+		{ 611  },
+		{ 500  },
+		{ 556  },
+		{ 722  },
+		{ 611  },
+		{ 833  },
+		{ 611  },
+		{ 556  },
+		{ 556  },
+		{ 389  },
+		{ 278  },
+		{ 389  },
+		{ 422  },
+		{ 500  },
+		{ 333  },
+		{ 500  },
+		{ 500  },
+		{ 444  },
+		{  500 },
+		{  444 },
+		{  278 },
+		{  500 },
+		{  500 },
+		{  278 },
+		{  278 },
+		{  444 },
+		{  278 },
+		{  722 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  500 },
+		{  389 },
+		{  389 },
+		{  278 },
+		{  500 },
+		{  444 },
+		{  667 },
+		{  444 },
+		{  444 },
+		{  389 },
+		{  400 },
+		{  275 },
+		{  400 },
+		{  541 },
+	} },
+};
+
+void *
+pdf_alloc(char *outopts)
+{
+	struct termp	*p;
+
+	if (NULL != (p = pspdf_alloc(outopts)))
+		p->type = TERMTYPE_PDF;
+
+	return(p);
+}
+
+void *
+ps_alloc(char *outopts)
+{
+	struct termp	*p;
+
+	if (NULL != (p = pspdf_alloc(outopts)))
+		p->type = TERMTYPE_PS;
+
+	return(p);
+}
+
+static struct termp *
+pspdf_alloc(char *outopts)
+{
+	struct termp	*p;
+	unsigned int	 pagex, pagey;
+	size_t		 marginx, marginy, lineheight;
+	const char	*toks[2];
+	const char	*pp;
+	char		*v;
+
+	p = mandoc_calloc(1, sizeof(struct termp));
+	p->enc = TERMENC_ASCII;
+	p->ps = mandoc_calloc(1, sizeof(struct termp_ps));
+
+	p->advance = ps_advance;
+	p->begin = ps_begin;
+	p->end = ps_end;
+	p->endline = ps_endline;
+	p->hspan = ps_hspan;
+	p->letter = ps_letter;
+	p->width = ps_width;
+	
+	toks[0] = "paper";
+	toks[1] = NULL;
+
+	pp = NULL;
+
+	while (outopts && *outopts)
+		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
+		case (0):
+			pp = v;
+			break;
+		default:
+			break;
+		}
+
+	/* Default to US letter (millimetres). */
+
+	pagex = 216;
+	pagey = 279;
+
+	/*
+	 * The ISO-269 paper sizes can be calculated automatically, but
+	 * it would require bringing in -lm for pow() and I'd rather not
+	 * do that.  So just do it the easy way for now.  Since this
+	 * only happens once, I'm not terribly concerned.
+	 */
+
+	if (pp && strcasecmp(pp, "letter")) {
+		if (0 == strcasecmp(pp, "a3")) {
+			pagex = 297;
+			pagey = 420;
+		} else if (0 == strcasecmp(pp, "a4")) {
+			pagex = 210;
+			pagey = 297;
+		} else if (0 == strcasecmp(pp, "a5")) {
+			pagex = 148;
+			pagey = 210;
+		} else if (0 == strcasecmp(pp, "legal")) {
+			pagex = 216;
+			pagey = 356;
+		} else if (2 != sscanf(pp, "%ux%u", &pagex, &pagey))
+			fprintf(stderr, "%s: Unknown paper\n", pp);
+	}
+
+	/* 
+	 * This MUST be defined before any PNT2AFM or AFM2PNT
+	 * calculations occur.
+	 */
+
+	p->ps->scale = 11;
+
+	/* Remember millimetres -> AFM units. */
+
+	pagex = PNT2AFM(p, ((double)pagex * 2.834));
+	pagey = PNT2AFM(p, ((double)pagey * 2.834));
+
+	/* 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);
+
+	/* Line-height is 1.4em. */
+
+	lineheight = PNT2AFM(p, ((double)p->ps->scale * 1.4));
+
+	p->ps->width = (size_t)pagex;
+	p->ps->height = (size_t)pagey;
+	p->ps->header = pagey - (marginy / 2) - (lineheight / 2);
+	p->ps->top = pagey - marginy;
+	p->ps->footer = (marginy / 2) - (lineheight / 2);
+	p->ps->bottom = marginy;
+	p->ps->left = marginx;
+	p->ps->lineheight = lineheight;
+
+	p->defrmargin = pagex - (marginx * 2);
+	return(p);
+}
+
+
+void
+pspdf_free(void *arg)
+{
+	struct termp	*p;
+
+	p = (struct termp *)arg;
+
+	if (p->ps->psmarg)
+		free(p->ps->psmarg);
+	if (p->ps->pdfobjs)
+		free(p->ps->pdfobjs);
+
+	free(p->ps);
+	term_free(p);
+}
+
+
+static void
+ps_printf(struct termp *p, const char *fmt, ...)
+{
+	va_list		 ap;
+	int		 pos, len;
+
+	va_start(ap, fmt);
+
+	/*
+	 * If we're running in regular mode, then pipe directly into
+	 * vprintf().  If we're processing margins, then push the data
+	 * into our growable margin buffer.
+	 */
+
+	if ( ! (PS_MARGINS & p->ps->flags)) {
+		len = vprintf(fmt, ap);
+		va_end(ap);
+		p->ps->pdfbytes += /* LINTED */
+			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.
+	 */
+
+	ps_growbuf(p, PS_BUFSLOP);
+
+	pos = (int)p->ps->psmargcur;
+	vsnprintf(&p->ps->psmarg[pos], PS_BUFSLOP, fmt, ap);
+
+	va_end(ap);
+
+	p->ps->psmargcur = strlen(p->ps->psmarg);
+}
+
+
+static void
+ps_putchar(struct termp *p, char c)
+{
+	int		 pos;
+
+	/* See ps_printf(). */
+
+	if ( ! (PS_MARGINS & p->ps->flags)) {
+		/* LINTED */
+		putchar(c);
+		p->ps->pdfbytes++;
+		return;
+	}
+
+	ps_growbuf(p, 2);
+
+	pos = (int)p->ps->psmargcur++;
+	p->ps->psmarg[pos++] = c;
+	p->ps->psmarg[pos] = '\0';
+}
+
+
+static void
+pdf_obj(struct termp *p, size_t obj)
+{
+
+	assert(obj > 0);
+
+	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[(int)obj - 1] = p->ps->pdfbytes;
+	ps_printf(p, "%zu 0 obj\n", obj);
+}
+
+
+static void
+ps_closepage(struct termp *p)
+{
+	int		 i;
+	size_t		 len, base;
+
+	/*
+	 * Close out a page that we've already flushed to output.  In
+	 * PostScript, we simply note that the page must be showed.  In
+	 * PDF, we must now create the Length, Resource, and Page node
+	 * for the page contents.
+	 */
+
+	assert(p->ps->psmarg && p->ps->psmarg[0]);
+	ps_printf(p, "%s", p->ps->psmarg);
+
+	if (TERMTYPE_PS != p->type) {
+		ps_printf(p, "ET\n");
+
+		len = p->ps->pdfbytes - p->ps->pdflastpg;
+		base = p->ps->pages * 4 + p->ps->pdfbody;
+
+		ps_printf(p, "endstream\nendobj\n");
+
+		/* Length of content. */
+		pdf_obj(p, base + 1);
+		ps_printf(p, "%zu\nendobj\n", len);
+
+		/* Resource for content. */
+		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++) 
+			ps_printf(p, "/F%d %d 0 R\n", i, 3 + i);
+		ps_printf(p, ">>\n>>\n");
+
+		/* Page node. */
+		pdf_obj(p, base + 3);
+		ps_printf(p, "<<\n");
+		ps_printf(p, "/Type /Page\n");
+		ps_printf(p, "/Parent 2 0 R\n");
+		ps_printf(p, "/Resources %zu 0 R\n", base + 2);
+		ps_printf(p, "/Contents %zu 0 R\n", base);
+		ps_printf(p, ">>\nendobj\n");
+	} else
+		ps_printf(p, "showpage\n");
+
+	p->ps->pages++;
+	p->ps->psrow = p->ps->top;
+	assert( ! (PS_NEWPAGE & p->ps->flags));
+	p->ps->flags |= PS_NEWPAGE;
+}
+
+
+/* ARGSUSED */
+static void
+ps_end(struct termp *p)
+{
+	size_t		 i, xref, base;
+
+	/*
+	 * At the end of the file, do one last showpage.  This is the
+	 * same behaviour as groff(1) and works for multiple pages as
+	 * well as just one.
+	 */
+
+	if ( ! (PS_NEWPAGE & p->ps->flags)) {
+		assert(0 == p->ps->flags);
+		assert('\0' == p->ps->last);
+		ps_closepage(p);
+	}
+
+	if (TERMTYPE_PS == p->type) {
+		ps_printf(p, "%%%%Trailer\n");
+		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");
+	ps_printf(p, "/MediaBox [0 0 %zu %zu]\n",
+			(size_t)AFM2PNT(p, p->ps->width),
+			(size_t)AFM2PNT(p, p->ps->height));
+
+	ps_printf(p, "/Count %zu\n", p->ps->pages);
+	ps_printf(p, "/Kids [");
+
+	for (i = 0; i < p->ps->pages; i++)
+		ps_printf(p, " %zu 0 R", i * 4 +
+				p->ps->pdfbody + 3);
+
+	base = (p->ps->pages - 1) * 4 + 
+		p->ps->pdfbody + 4;
+
+	ps_printf(p, "]\n>>\nendobj\n");
+	pdf_obj(p, base);
+	ps_printf(p, "<<\n");
+	ps_printf(p, "/Type /Catalog\n");
+	ps_printf(p, "/Pages 2 0 R\n");
+	ps_printf(p, ">>\n");
+	xref = p->ps->pdfbytes;
+	ps_printf(p, "xref\n");
+	ps_printf(p, "0 %zu\n", base + 1);
+	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, "trailer\n");
+	ps_printf(p, "<<\n");
+	ps_printf(p, "/Size %zu\n", base + 1);
+	ps_printf(p, "/Root %zu 0 R\n", base);
+	ps_printf(p, "/Info 1 0 R\n");
+	ps_printf(p, ">>\n");
+	ps_printf(p, "startxref\n");
+	ps_printf(p, "%zu\n", xref);
+	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.
+	 */
+
+	if (p->ps->psmarg) {
+		assert(p->ps->psmargsz);
+		p->ps->psmarg[0] = '\0';
+	}
+
+	/*p->ps->pdfbytes = 0;*/
+	p->ps->psmargcur = 0;
+	p->ps->flags = PS_MARGINS;
+	p->ps->pscol = p->ps->left;
+	p->ps->psrow = p->ps->header;
+
+	ps_setfont(p, TERMFONT_NONE);
+
+	(*p->headf)(p, p->argf);
+	(*p->endline)(p);
+
+	p->ps->pscol = p->ps->left;
+	p->ps->psrow = p->ps->footer;
+
+	(*p->footf)(p, p->argf);
+	(*p->endline)(p);
+
+	p->ps->flags &= ~PS_MARGINS;
+
+	assert(0 == p->ps->flags);
+	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));
+		ps_printf(p, "%%%%DocumentNeededResources: font");
+
+		for (i = 0; i < (int)TERMFONT__MAX; i++)
+			ps_printf(p, " %s", fonts[i].name);
+
+		ps_printf(p, "\n%%%%EndComments\n");
+	} else {
+		ps_printf(p, "%%PDF-1.1\n");
+		pdf_obj(p, 1);
+		ps_printf(p, "<<\n");
+		ps_printf(p, ">>\n");
+		ps_printf(p, "endobj\n");
+
+		for (i = 0; i < (int)TERMFONT__MAX; i++) {
+			pdf_obj(p, (size_t)i + 3);
+			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, "/BaseFont /%s\n", fonts[i].name);
+			ps_printf(p, ">>\n");
+		}
+	}
+
+	p->ps->pdfbody = (size_t)TERMFONT__MAX + 3;
+	p->ps->pscol = p->ps->left;
+	p->ps->psrow = p->ps->top;
+	p->ps->flags |= PS_NEWPAGE;
+	ps_setfont(p, TERMFONT_NONE);
+}
+
+
+static void
+ps_pletter(struct termp *p, int c)
+{
+	int		 f;
+
+	/*
+	 * If we haven't opened a page context, then output that we're
+	 * in a new page and make sure the font is correctly set.
+	 */
+
+	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);
+		} else {
+			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, ">>\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.
+	 */
+
+	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, "%.3f %.3f Td\n(",
+					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));
+		p->ps->flags |= PS_INLINE;
+	}
+
+	assert( ! (PS_NEWPAGE & p->ps->flags));
+
+	/*
+	 * We need to escape these characters as per the PostScript
+	 * specification.  We would also escape non-graphable characters
+	 * (like tabs), but none of them would get to this point and
+	 * it's superfluous to abort() on them.
+	 */
+
+	switch (c) {
+	case ('('):
+		/* FALLTHROUGH */
+	case (')'):
+		/* FALLTHROUGH */
+	case ('\\'):
+		ps_putchar(p, '\\');
+		break;
+	default:
+		break;
+	}
+
+	/* Write the character and adjust where we are on the page. */
+
+	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;
+	} 
+
+	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).
+	 */
+
+	if ( ! (PS_INLINE & p->ps->flags))
+		return;
+	
+	if (TERMTYPE_PS != p->type) {
+		ps_printf(p, ") Tj\nET\n");
+	} else
+		ps_printf(p, ") show\n");
+
+	p->ps->flags &= ~PS_INLINE;
+}
+
+
+static void
+ps_fclose(struct termp *p)
+{
+
+	/*
+	 * Strong closure: if we have a last-char, spit it out after
+	 * checking that we're in the right font mode.  This will of
+	 * course open a new scope, if applicable.
+	 *
+	 * Following this, close out any scope that's open.
+	 */
+
+	if ('\0' != p->ps->last) {
+		if (p->ps->lastf != TERMFONT_NONE) {
+			ps_pclose(p);
+			ps_setfont(p, TERMFONT_NONE);
+		}
+		ps_pletter(p, p->ps->last);
+		p->ps->last = '\0';
+	}
+
+	if ( ! (PS_INLINE & p->ps->flags))
+		return;
+
+	ps_pclose(p);
+}
+
+
+static void
+ps_letter(struct termp *p, int arg)
+{
+	char		cc, 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.
+	 */
+
+	if ('\0' == p->ps->last) {
+		assert(8 != c);
+		p->ps->last = c;
+		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);
+			}
+		} else if (p->ps->lastf != TERMFONT_BOLD) {
+			ps_pclose(p);
+			ps_setfont(p, TERMFONT_BOLD);
+		}
+		p->ps->last = c;
+		return;
+	} else {
+		if (p->ps->lastf != TERMFONT_NONE) {
+			ps_pclose(p);
+			ps_setfont(p, TERMFONT_NONE);
+		}
+		cc = p->ps->last;
+		p->ps->last = c;
+		c = cc;
+	}
+
+	ps_pletter(p, c);
+}
+
+
+static void
+ps_advance(struct termp *p, size_t len)
+{
+
+	/*
+	 * Advance some spaces.  This can probably be made smarter,
+	 * i.e., to have multiple space-separated words in the same
+	 * scope, but this is easier:  just close out the current scope
+	 * and readjust our column settings.
+	 */
+
+	ps_fclose(p);
+	p->ps->pscol += len;
+}
+
+
+static void
+ps_endline(struct termp *p)
+{
+
+	/* Close out any scopes we have open: we're at eoln. */
+
+	ps_fclose(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. 
+	 */
+
+	if (PS_MARGINS & p->ps->flags)
+		return;
+
+	/* Left-justify. */
+
+	p->ps->pscol = p->ps->left;
+
+	/* If we haven't printed anything, return. */
+
+	if (PS_NEWPAGE & p->ps->flags)
+		return;
+
+	/*
+	 * Put us down a line.  If we're at the page bottom, spit out a
+	 * showpage and restart our row.
+	 */
+
+	if (p->ps->psrow >= p->ps->lineheight + 
+			p->ps->bottom) {
+		p->ps->psrow -= p->ps->lineheight;
+		return;
+	}
+
+	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.
+	 */
+
+	if (PS_NEWPAGE & p->ps->flags)
+		return;
+
+	if (TERMTYPE_PS == p->type)
+		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);
+}
+
+
+/* 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 -= 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);
+		break;
+	case (SCALE_IN):
+		r = PNT2AFM(p, su->scale * 72);
+		break;
+	case (SCALE_PC):
+		r = PNT2AFM(p, su->scale * 12);
+		break;
+	case (SCALE_PT):
+		r = PNT2AFM(p, su->scale * 100);
+		break;
+	case (SCALE_EM):
+		r = su->scale *
+			fonts[(int)TERMFONT_NONE].gly[109 - 32].wx;
+		break;
+	case (SCALE_MM):
+		r = PNT2AFM(p, su->scale * 2.834);
+		break;
+	case (SCALE_EN):
+		r = su->scale *
+			fonts[(int)TERMFONT_NONE].gly[110 - 32].wx;
+		break;
+	case (SCALE_VS):
+		r = su->scale * p->ps->lineheight;
+		break;
+	default:
+		r = su->scale;
+		break;
+	}
+
+	return(r);
+}
+
+static void
+ps_growbuf(struct termp *p, size_t sz)
+{
+	if (p->ps->psmargcur + sz <= p->ps->psmargsz)
+		return;
+
+	if (sz < PS_BUFSLOP)
+		sz = PS_BUFSLOP;
+
+	p->ps->psmargsz += sz;
+
+	p->ps->psmarg = mandoc_realloc
+		(p->ps->psmarg, p->ps->psmargsz);
+}
+
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/tree.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,349 @@
+/*	$Id: tree.c,v 1.47 2011/09/18 14:14:15 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "mdoc.h"
+#include "man.h"
+#include "main.h"
+
+static	void	print_box(const struct eqn_box *, int);
+static	void	print_man(const struct man_node *, int);
+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);
+}
+
+
+/* ARGSUSED */
+void
+tree_man(void *arg, const struct man *man)
+{
+
+	print_man(man_node(man), 0);
+}
+
+
+static void
+print_mdoc(const struct mdoc_node *n, int indent)
+{
+	const char	 *p, *t;
+	int		  i, j;
+	size_t		  argc, sz;
+	char		**params;
+	struct mdoc_argv *argv;
+
+	argv = NULL;
+	argc = sz = 0;
+	params = NULL;
+	t = p = NULL;
+
+	switch (n->type) {
+	case (MDOC_ROOT):
+		t = "root";
+		break;
+	case (MDOC_BLOCK):
+		t = "block";
+		break;
+	case (MDOC_HEAD):
+		t = "block-head";
+		break;
+	case (MDOC_BODY):
+		if (n->end)
+			t = "body-end";
+		else
+			t = "block-body";
+		break;
+	case (MDOC_TAIL):
+		t = "block-tail";
+		break;
+	case (MDOC_ELEM):
+		t = "elem";
+		break;
+	case (MDOC_TEXT):
+		t = "text";
+		break;
+	case (MDOC_TBL):
+		/* FALLTHROUGH */
+	case (MDOC_EQN):
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	switch (n->type) {
+	case (MDOC_TEXT):
+		p = n->string;
+		break;
+	case (MDOC_BODY):
+		p = mdoc_macronames[n->tok];
+		break;
+	case (MDOC_HEAD):
+		p = mdoc_macronames[n->tok];
+		break;
+	case (MDOC_TAIL):
+		p = mdoc_macronames[n->tok];
+		break;
+	case (MDOC_ELEM):
+		p = mdoc_macronames[n->tok];
+		if (n->args) {
+			argv = n->args->argv;
+			argc = n->args->argc;
+		}
+		break;
+	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):
+		break;
+	case (MDOC_ROOT):
+		p = "root";
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	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)", p, t);
+
+		for (i = 0; i < (int)argc; i++) {
+			printf(" -%s", mdoc_argnames[argv[i].arg]);
+			if (argv[i].sz > 0)
+				printf(" [");
+			for (j = 0; j < (int)argv[i].sz; j++)
+				printf(" [%s]", argv[i].value[j]);
+			if (argv[i].sz > 0)
+				printf(" ]");
+		}
+		
+		for (i = 0; i < (int)sz; i++)
+			printf(" [%s]", params[i]);
+
+		printf(" %d:%d\n", n->line, n->pos);
+	}
+
+	if (n->child)
+		print_mdoc(n->child, indent + 1);
+	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;
+
+	t = p = NULL;
+
+	switch (n->type) {
+	case (MAN_ROOT):
+		t = "root";
+		break;
+	case (MAN_ELEM):
+		t = "elem";
+		break;
+	case (MAN_TEXT):
+		t = "text";
+		break;
+	case (MAN_BLOCK):
+		t = "block";
+		break;
+	case (MAN_HEAD):
+		t = "block-head";
+		break;
+	case (MAN_BODY):
+		t = "block-body";
+		break;
+	case (MAN_TAIL):
+		t = "block-tail";
+		break;
+	case (MAN_TBL):
+		/* FALLTHROUGH */
+	case (MAN_EQN):
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	switch (n->type) {
+	case (MAN_TEXT):
+		p = n->string;
+		break;
+	case (MAN_ELEM):
+		/* FALLTHROUGH */
+	case (MAN_BLOCK):
+		/* FALLTHROUGH */
+	case (MAN_HEAD):
+		/* FALLTHROUGH */
+	case (MAN_TAIL):
+		/* FALLTHROUGH */
+	case (MAN_BODY):
+		p = man_macronames[n->tok];
+		break;
+	case (MAN_ROOT):
+		p = "root";
+		break;
+	case (MAN_TBL):
+		/* FALLTHROUGH */
+	case (MAN_EQN):
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	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);
+	}
+
+	if (n->child)
+		print_man(n->child, indent + 1);
+	if (n->next)
+		print_man(n->next, indent);
+}
+
+static void
+print_box(const struct eqn_box *ep, int indent)
+{
+	int		 i;
+	const char	*t;
+
+	if (NULL == ep)
+		return;
+	for (i = 0; i < indent; i++)
+		putchar('\t');
+
+	t = NULL;
+	switch (ep->type) {
+	case (EQN_ROOT):
+		t = "eqn-root";
+		break;
+	case (EQN_LIST):
+		t = "eqn-list";
+		break;
+	case (EQN_SUBEXPR):
+		t = "eqn-expr";
+		break;
+	case (EQN_TEXT):
+		t = "eqn-text";
+		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);
+	print_box(ep->next, indent);
+}
+
+static void
+print_span(const struct tbl_span *sp, int indent)
+{
+	const struct tbl_dat *dp;
+	int		 i;
+
+	for (i = 0; i < indent; i++)
+		putchar('\t');
+
+	switch (sp->pos) {
+	case (TBL_SPAN_HORIZ):
+		putchar('-');
+		return;
+	case (TBL_SPAN_DHORIZ):
+		putchar('=');
+		return;
+	default:
+		break;
+	}
+
+	for (dp = sp->first; dp; dp = dp->next) {
+		switch (dp->pos) {
+		case (TBL_DATA_HORIZ):
+			/* FALLTHROUGH */
+		case (TBL_DATA_NHORIZ):
+			putchar('-');
+			continue;
+		case (TBL_DATA_DHORIZ):
+			/* FALLTHROUGH */
+		case (TBL_DATA_NDHORIZ):
+			putchar('=');
+			continue;
+		default:
+			break;
+		}
+		printf("[\"%s\"", dp->string ? dp->string : "");
+		if (dp->spans)
+			printf("(%d)", dp->spans);
+		if (NULL == dp->layout)
+			putchar('*');
+		putchar(']');
+		putchar(' ');
+	}
+
+	printf("(tbl) %d:1\n", sp->line);
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/vol.c	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,39 @@
+/*	$Id: vol.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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);
+}
--- /dev/null	Fri Jul 18 08:34:36 2014
+++ new/usr/src/cmd/mandoc/vol.in	Fri Jul 18 08:34:36 2014
@@ -0,0 +1,35 @@
+/*	$Id: vol.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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")
--- old/usr/src/man/Makefile	Fri Jul 18 08:34:36 2014
+++ new/usr/src/man/Makefile	Fri Jul 18 08:34:36 2014
@@ -11,7 +11,7 @@
 
 #
 # Copyright 2011, Richard Lowe
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 #
 
 SUBDIRS=	man1		\
@@ -102,8 +102,9 @@
 clean		:= TARGET = clean
 clobber		:= TARGET = clobber
 install		:= TARGET = install
+check		:= TARGET = check
 
-all clean clobber install: $(SUBDIRS)
+all check clean clobber install: $(SUBDIRS)
 
 $(SUBDIRS):	FRC
 	@cd $@; pwd; $(MAKE) $(TARGET)
--- old/usr/src/man/Makefile.man	Fri Jul 18 08:34:37 2014
+++ new/usr/src/man/Makefile.man	Fri Jul 18 08:34:37 2014
@@ -11,12 +11,12 @@
 
 #
 # Copyright 2011, Richard Lowe
-# Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 #
 
+MANDOC=		$(ONBLD_TOOLS)/bin/${MACH}/mandoc
 ROOTMAN=	$(ROOT)/usr/share/man
 ROOTHASMAN=	$(ROOT)/usr/has/man
-
 FILEMODE=	0444
 
 # The manual section being built, client Makefiles must set this to, for e.g.
@@ -24,6 +24,7 @@
 #
 # MANSECT=
 
+MANCHECKS=	$(MANFILES:%=%.check)
 ROOTMANFILES= 	$(MANFILES:%=$(ROOTMAN)/man$(MANSECT)/%)
 ROOTMANLINKS=	$(MANLINKS:%=$(ROOTMAN)/man$(MANSECT)/%)
 
@@ -30,6 +31,10 @@
 $(ROOTMAN)/man$(MANSECT)/% $(ROOTHASMAN)/man$(MANSECT)/%: %
 		$(INS.file)
 
+$(MANCHECKS):
+		@$(ECHO) "checking $(@:%.check=%)"; \
+		$(MANDOC) -Tlint $(@:%.check=%)
+
 $(MANLINKS):
 		$(RM) $@; $(SYMLINK) $(LINKSRC) $@
 
@@ -38,6 +43,8 @@
 
 all:
 
+check:		$(MANCHECKS)
+
 clean:
 
 clobber:
--- old/usr/src/man/man1/Makefile	Fri Jul 18 08:34:37 2014
+++ new/usr/src/man/man1/Makefile	Fri Jul 18 08:34:37 2014
@@ -12,6 +12,7 @@
 #
 # Copyright 2011, Richard Lowe
 # Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
 
 include		$(SRC)/Makefile.master
@@ -228,6 +229,7 @@
 	 	mailx.1				\
 	 	makekey.1			\
 	 	man.1				\
+	 	mandoc.1			\
 	 	mconnect.1			\
 	 	mcs.1				\
 	 	mdb.1				\
@@ -410,7 +412,6 @@
 	 	w.1				\
 	 	wait.1				\
 	 	wc.1				\
-		whatis.1			\
 	 	which.1				\
 	 	who.1				\
 	 	whocalls.1			\
@@ -555,6 +556,7 @@
 		valyorn.1	\
 		vax.1		\
 		vedit.1		\
+		whatis.1	\
 		whence.1	\
 		while.1		\
 		zcat.1
@@ -561,6 +563,8 @@
 
 intro.1		:= LINKSRC = Intro.1
 
+whatis.1	:= LINKSRC = apropos.1
+
 unalias.1	:= LINKSRC = alias.1
 
 batch.1		:= LINKSRC = at.1
--- old/usr/src/man/man1/apropos.1	Fri Jul 18 08:34:37 2014
+++ new/usr/src/man/man1/apropos.1	Fri Jul 18 08:34:37 2014
@@ -1,142 +1,78 @@
-'\" te
-.\"  Copyright (c) 1996, Sun Microsystems, Inc.  All Rights Reserved
-.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
-.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
-.\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH APROPOS 1 "Dec 20, 1996"
-.SH NAME
-apropos \- locate commands by keyword lookup
-.SH SYNOPSIS
-.LP
-.nf
-\fBapropos\fR \fIkeyword\fR...
-.fi
-
-.SH DESCRIPTION
-.sp
-.LP
-The \fBapropos\fR utility displays the man page name, section number, and a
-short description for each man page whose \fBNAME\fR line contains
-\fIkeyword\fR. This information is contained in the \fB/usr/share/man/windex\fR
-database created by \fBcatman\fR(1M). If \fBcatman\fR(1M) was not run, or was
-run with the \fB-n\fR option, \fBapropos\fR fails. Each word is considered
-separately and the case of letters is ignored. Words which are part of other
-words are considered; for example, when looking for `compile', \fBapropos\fR
-finds all instances of `compiler' also.
-.sp
-.LP
-\fBapropos\fR is actually just the \fB-k\fR option to the \fBman\fR(1) command.
-.SH EXAMPLES
-.LP
-\fBExample 1 \fRTo find a man page whose NAME line contains a keyword
-.sp
-.LP
-Try
-
-.sp
-.in +2
-.nf
-example% \fBapropos password\fR
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-and
-
-.sp
-.in +2
-.nf
-example% \fBapropos editor\fR
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-If the line starts `\fIfilename\fR(\fIsection\fR) .\|.\|.' you can run
-
-.sp
-.in +2
-.nf
-man -s \fIsection filename\fR
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-to display the man page for \fIfilename\fR.
-
-.LP
-\fBExample 2 \fRTo find the man page for the subroutine \fBprintf()\fR
-.sp
-.LP
-Try
-
-.sp
-.in +2
-.nf
-example% \fBapropos format\fR
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-and then
-
-.sp
-.in +2
-.nf
-example% \fBman -s 3s printf\fR
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-to get the manual page on the subroutine \fBprintf()\fR.
-
-.SH FILES
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/windex\fR \fR
-.ad
-.RS 26n
-table of contents and keyword database
-.RE
-
-.SH ATTRIBUTES
-.sp
-.LP
-See \fBattributes\fR(5) for descriptions of the following attributes:
-.sp
-
-.sp
-.TS
-box;
-c | c
-l | l .
-ATTRIBUTE TYPE	ATTRIBUTE VALUE
-_
-CSI	Enabled
-.TE
-
-.SH SEE ALSO
-.sp
-.LP
-\fBman\fR(1), \fBwhatis\fR(1), \fBcatman\fR(1M), \fBattributes\fR(5)
-.SH DIAGNOSTICS
-.sp
-.ne 2
-.na
-\fB\f(CW/usr/share/man/windex: No such file or directory\fR\fR
-.ad
-.sp .6
-.RS 4n
-This database does not exist. \fBcatman\fR(1M) must be run to create it.
-.RE
-
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\" Copyright 2014 Garrett D'Amore <garrett@damore.org>
+.\"
+.Dd Jul 13, 2014
+.Dt APROPOS 1
+.Os
+.Sh NAME
+.Nm apropos ,
+.Nm whatis
+.Nd keyword search in
+.Nm whatis
+database files
+.Sh SYNOPSIS
+.Nm
+.Op Fl M Ar path
+.Op Fl s Ar section
+.Ar keyword ...
+.Nm whatis
+.Op Fl M Ar path
+.Op Fl s Ar section
+.Ar keyword ...
+.Sh DESCRIPTION
+The
+.Nm
+utility searches a set of
+.Nm whatis
+database files matching each
+.Ar keyword .
+The
+.Nm whatis
+utility does the same search but only on complete words. The
+.Nm whatis
+database files are created using
+.Xr man 1
+command.
+.Bl -tag -width ".Fl d"
+.It Fl M Ar path
+Force a specific colon separated manual path instead of the default search path.
+Overrides the
+.Ev MANPATH
+environment variable.
+.It Fl s Ar section
+Restrict search to specified
+.Ar section .
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev MANPATH , PATH"
+.It Ev MANPATH , PATH
+Used to find the location of the
+.Nm whatis
+database files.
+.El
+.Sh DIAGNOSTICS
+The
+.Nm
+utility exits 0 if a keyword matched and 1 if no keywords are matched or no
+.Nm whatis
+databases are found.
+.Sh INTERFACE STABILITY
+.Nm Committed .
+.Sh CODE SET INDEPENDENCE
+Enabled.
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr mandoc 1
--- old/usr/src/man/man1/head.1	Fri Jul 18 08:34:37 2014
+++ new/usr/src/man/man1/head.1	Fri Jul 18 08:34:37 2014
@@ -11,7 +11,7 @@
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
 .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
 .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH HEAD 1 "Mar	4, 2013"
+.TH HEAD 1 "Mar 4, 2013"
 .SH NAME
 head \- display first few lines of files
 .SH SYNOPSIS
--- old/usr/src/man/man1/ld.1	Fri Jul 18 08:34:37 2014
+++ new/usr/src/man/man1/ld.1	Fri Jul 18 08:34:37 2014
@@ -1036,7 +1036,7 @@
 Suggests that \fB-z direct\fR or \fB-B direct\fR be present prior to any
 specified dependency.  This allows predictable symbol binding at runtime.
 
-Can be disabled with \fB-z guidance=nodirect\fR 
+Can be disabled with \fB-z guidance=nodirect\fR
 .RE
 
 .sp
@@ -1105,7 +1105,7 @@
 
 Can be disabled with \fB-z guidance=nounused\fR.
 .RE
-.RE 
+.RE
 
 .sp
 .ne 2
--- old/usr/src/man/man1/man.1	Fri Jul 18 08:34:37 2014
+++ new/usr/src/man/man1/man.1	Fri Jul 18 08:34:37 2014
@@ -1,664 +1,137 @@
-'\" te
-.\" Copyright (c) 2008, Sun Microsystems, Inc. All Rights Reserved.
-.\" Copyright (c) 1980 Regents of the University of California. The Berkeley software License Agreement specifies the terms and conditions for redistribution.
-.TH MAN 1 "May 8, 2008"
-.SH NAME
-man \- find and display reference manual pages
-.SH SYNOPSIS
-.LP
-.nf
-\fBman\fR [\fB-\fR] [\fB-adFlrt\fR] [\fB-M\fR \fIpath\fR] [\fB-T\fR \fImacro-package\fR] [\fB-s\fR \fIsection\fR] \fIname\fR...
-.fi
-
-.LP
-.nf
-\fBman\fR [\fB-M\fR \fIpath\fR] \fB-k\fR \fIkeyword\fR...
-.fi
-
-.LP
-.nf
-\fBman\fR [\fB-M\fR \fIpath\fR] \fB-f\fR \fIfile\fR...
-.fi
-
-.SH DESCRIPTION
-.sp
-.LP
-The \fBman\fR command displays information from the reference manuals. It
-displays complete manual pages that you select by \fIname\fR, or one-line
-summaries selected either by \fIkeyword\fR (\fB-k\fR), or by the name of an
-associated file (\fB-f\fR). If no manual page is located, \fBman\fR prints an
-error message.
-.SS "Source Format"
-.sp
-.LP
-Reference Manual pages are marked up with either \fBnroff\fR (see
-\fBnroff\fR(1)) or \fBSGML\fR (Standard Generalized Markup Language) tags (see
-\fBsgml\fR(5)). The \fBman\fR command recognizes the type of markup and
-processes the file accordingly. The various source files are kept in separate
-directories depending on the type of markup.
-.SS "Location of Manual Pages"
-.sp
-.LP
-The online Reference Manual page directories are conventionally located in
-\fB/usr/share/man\fR. The nroff sources are located in the
-\fB/usr/share/man/man\fR* directories. The \fBSGML\fR sources are located in
-the \fB/usr/share/man/sman\fR* directories. Each directory corresponds to a
-section of the manual. Since these directories are optionally installed, they
-might not reside on your host. You might have to mount \fB/usr/share/man\fR
-from a host on which they do reside.
-.sp
-.LP
-If there are preformatted, up-to-date versions in the corresponding \fBcat\fR*
-or \fBfmt\fR* directories, \fBman\fR simply displays or prints those versions.
-If the preformatted version of interest is out of date or missing, \fBman\fR
-reformats it prior to display and stores the preformatted version if \fBcat\fR*
-or \fBfmt\fR* is writable. The \fBwindex\fR database is not updated. See
-\fBcatman\fR(1M). If directories for the preformatted versions are not
-provided, \fBman\fR reformats a page whenever it is requested. \fBman\fR uses a
-temporary file to store the formatted text during display.
-.sp
-.LP
-If the standard output is not a terminal, or if the `\fB-\fR' flag is given,
-\fBman\fR pipes its output through \fBcat\fR(1). Otherwise, \fBman\fR pipes its
-output through \fBmore\fR(1) to handle paging and underlining on the screen.
-.SH OPTIONS
-.sp
-.LP
-The following options are supported:
-.sp
-.ne 2
-.na
-\fB\fB-a\fR\fR
-.ad
-.RS 20n
-Shows all manual pages matching \fIname\fR within the \fBMANPATH\fR search
-path. Manual pages are displayed in the order found.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-d\fR\fR
-.ad
-.RS 20n
-Debugs. Displays what a section-specifier evaluates to, method used for
-searching, and paths searched by \fBman\fR.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-f\fR \fIfile ...\fR\fR
-.ad
-.RS 20n
-\fBman\fR attempts to locate manual pages related to any of the given
-\fIfile\fRs. It strips the leading path name components from each \fIfile\fR,
-and then prints one-line summaries containing the resulting basename or names.
-This option also uses the \fBwindex\fR database.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-F\fR\fR
-.ad
-.RS 20n
-Forces \fBman\fR to search all directories specified by \fBMANPATH\fR or the
-\fBman.cf\fR file, rather than using the \fBwindex\fR lookup database. This
-option is useful if the database is not up to date and it has been made the
-default behavior of the \fBman\fR command. The option therefore does not have
-to be invoked and is documented here for reference only.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-k\fR \fIkeyword ...\fR\fR
-.ad
-.RS 20n
-Prints out one-line summaries from the \fBwindex\fR database (table of
-contents) that contain any of the given \fIkeyword\fRs. The \fBwindex\fR
-database is created using \fBcatman\fR(1M).
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-l\fR\fR
-.ad
-.RS 20n
-Lists all manual pages found matching \fIname\fR within the search path.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-M\fR \fIpath\fR\fR
-.ad
-.RS 20n
-Specifies an alternate search path for manual pages. \fIpath\fR is a
-colon-separated list of directories that contain manual page directory
-subtrees. For example, if \fIpath\fR is \fB/usr/share/man:/usr/local/man\fR,
-\fBman\fR searches for \fIname\fR in the standard location, and then
-\fB/usr/local/man\fR. When used with the \fB-k\fR or \fB-f\fR options, the
-\fB-M\fR option must appear first. Each directory in the \fIpath\fR is assumed
-to contain subdirectories of the form \fBman\fR* or \fBsman\fR* , one for each
-section. This option overrides the \fBMANPATH\fR environment variable.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-r\fR\fR
-.ad
-.RS 20n
-Reformats the manual page, but does not display it. This replaces the \fBman\fR
-\fB-\fR \fB-t\fR \fIname\fR combination.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-s\fR \fIsection ...\fR\fR
-.ad
-.RS 20n
-Specifies sections of the manual for \fBman\fR to search. The directories
-searched for \fIname\fR are limited to those specified by \fIsection\fR.
-\fIsection\fR can be a numerical digit, perhaps followed by one or more letters
-to match the desired section of the manual, for example, "\fB3libucb\fR". Also,
-\fIsection\fR can be a word, for example, \fBlocal\fR, \fBnew\fR, \fBold\fR,
-\fBpublic\fR. \fIsection\fR can also be a letter. To specify multiple sections,
-separate each section with a comma. This option overrides the \fBMANPATH\fR
-environment variable and the \fBman.cf\fR file. See \fBSearch\fR \fBPath\fR
-below for an explanation of how \fBman\fR conducts its search.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-t\fR\fR
-.ad
-.RS 20n
-\fBman\fR arranges for the specified manual pages to be \fBtroff\fRed to a
-suitable raster output device (see \fBtroff\fR(1)). If both the \fB-\fR and
-\fB-t\fR flags are given, \fBman\fR updates the \fBtroff\fRed versions of each
-named \fIname\fR (if necessary), but does not display them.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-T\fR \fImacro-package\fR\fR
-.ad
-.RS 20n
-Formats manual pages using \fImacro-package\fR rather than the standard
-\fB-man\fR macros defined in \fB/usr/share/lib/tmac/an\fR. See \fBSearch
-Path\fR under USAGE for a complete explanation of the default search path
-order.
-.RE
-
-.SH OPERANDS
-.sp
-.LP
-The following operand is supported:
-.sp
-.ne 2
-.na
-\fB\fIname\fR\fR
-.ad
-.RS 8n
-The name of a standard utility or a keyword.
-.RE
-
-.SH USAGE
-.sp
-.LP
-The usage of \fBman\fR is described below:
-.SS "Manual Page Sections"
-.sp
-.LP
-Entries in the reference manuals are organized into \fIsection\fRs. A section
-name consists of a major section name, typically a single digit, optionally
-followed by a subsection name, typically one or more letters. An unadorned
-major section name, for example, "\fB9\fR", does not act as an abbreviation for
-the subsections of that name, such as "\fB9e\fR", "\fB9f\fR", or "\fB9s\fR".
-That is, each subsection must be searched separately by \fBman\fR \fB-s\fR.
-Each section contains descriptions apropos to a particular reference category,
-with subsections refining these distinctions. See the \fBintro\fR manual pages
-for an explanation of the classification used in this release.
-.SS "Search Path"
-.sp
-.LP
-Before searching for a given \fIname\fR, \fBman\fR constructs a list of
-candidate directories and sections. \fBman\fR searches for \fIname\fR in the
-directories specified by the \fBMANPATH\fR environment variable.
-.sp
-.LP
-In the absence of \fBMANPATH\fR, \fBman\fR constructs its search path based
-upon the \fBPATH\fR environment variable, primarily by substituting \fBman\fR
-for the last component of the \fBPATH\fR element. Special provisions are added
-to account for unique characteristics of directories such as \fB/sbin\fR,
-\fB/usr/ucb\fR, \fB/usr/xpg4/bin\fR, and others. If the file argument contains
-a \fB/\fR character, the \fIdirname\fR portion of the argument is used in place
-of \fBPATH\fR elements to construct the search path.
-.sp
-.LP
-Within the manual page directories, \fBman\fR confines its search to the
-sections specified in the following order:
-.RS +4
-.TP
-.ie t \(bu
-.el o
-\fIsection\fRs specified on the command line with the \fB-s\fR option
-.RE
-.RS +4
-.TP
-.ie t \(bu
-.el o
-\fIsection\fRs embedded in the \fBMANPATH\fR environment variable
-.RE
-.RS +4
-.TP
-.ie t \(bu
-.el o
-\fIsection\fRs specified in the \fBman.cf\fR file for each directory specified
-in the \fBMANPATH\fR environment variable
-.RE
-.sp
-.LP
-If none of the above exist, \fBman\fR searches each directory in the manual
-page path, and displays the first matching manual page found.
-.sp
-.LP
-The \fBman.cf\fR file has the following format:
-.sp
-.in +2
-.nf
-MANSECTS=\fIsection\fR[,\fIsection\fR]...
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-Lines beginning with `\fB#\fR' and blank lines are considered comments, and are
-ignored. Each directory specified in \fBMANPATH\fR can contain a manual page
-configuration file, specifying the default search order for that directory.
-.SH FORMATTING MANUAL PAGES
-.sp
-.LP
-Manual pages are marked up in \fBnroff\fR(1) or \fBsgml\fR(5). Nroff manual
-pages are processed by \fBnroff\fR(1) or \fBtroff\fR(1) with the \fB-man\fR
-macro package. Please refer to \fBman\fR(5) for information on macro usage.
-\fBSGML\fR\(emtagged manual pages are processed by an \fBSGML\fR parser and
-passed to the formatter.
-.SS "Preprocessing Nroff Manual Pages"
-.sp
-.LP
-When formatting an nroff manual page, \fBman\fR examines the first line to
-determine whether it requires special processing. If the first line is a string
-of the form:
-.sp
-.in +2
-.nf
-\&'\e" \fIX\fR
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-where \fIX\fR is separated from the `\fB"\fR' by a single SPACE and consists of
-any combination of characters in the following list, \fBman\fR pipes its input
-to \fBtroff\fR(1) or \fBnroff\fR(1) through the corresponding preprocessors.
-.sp
-.ne 2
-.na
-\fB\fBe\fR\fR
-.ad
-.RS 5n
-\fBeqn\fR(1), or \fBneqn\fR for \fBnroff\fR
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBr\fR\fR
-.ad
-.RS 5n
-\fBrefer\fR(1)
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBt\fR\fR
-.ad
-.RS 5n
-\fBtbl\fR(1)
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBv\fR\fR
-.ad
-.RS 5n
-\fBvgrind\fR(1)
-.RE
-
-.sp
-.LP
-If \fBeqn\fR or \fBneqn\fR is invoked, it automatically reads the file
-\fB/usr/pub/eqnchar\fR (see \fBeqnchar\fR(5)). If \fBnroff\fR(1) is invoked,
-\fBcol\fR(1) is automatically used.
-.SS "Referring to Other nroff Manual Pages"
-.sp
-.LP
-If the first line of the nroff manual page is a reference to another manual
-page entry fitting the pattern:
-.sp
-.in +2
-.nf
-\&.so man*/\fIsourcefile\fR
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-\fBman\fR processes the indicated file in place of the current one. The
-reference must be expressed as a path name relative to the root of the manual
-page directory subtree.
-.sp
-.LP
-When the second or any subsequent line starts with \fB\&.so\fR, \fBman\fR
-ignores it; \fBtroff\fR(1) or \fBnroff\fR(1) processes the request in the usual
-manner.
-.SS "Processing SGML Manual Pages"
-.sp
-.LP
-Manual pages are identified as being marked up in SGML by the presence of the
-string \fB<!DOCTYPE\fR\&. If the file also contains the string
-\fBSHADOW_PAGE\fR, the file refers to another manual page for the content. The
-reference is made with a file entity reference to the manual page that contains
-the text. This is similar to the \fB\&.so\fR mechanism used in the nroff
-formatted man pages.
-.SH ENVIRONMENT VARIABLES
-.sp
-.LP
-See \fBenviron\fR(5) for descriptions of the following environment variables
-that affect the execution of \fBman\fR: \fBLANG\fR, \fBLC_ALL\fR,
-\fBLC_CTYPE\fR, \fBLC_MESSAGES\fR, and \fBNLSPATH\fR.
-.sp
-.ne 2
-.na
-\fB\fBMANPATH\fR\fR
-.ad
-.RS 11n
-A colon-separated list of directories; each directory can be followed by a
-comma-separated list of sections. If set, its value overrides
-\fB/usr/share/man\fR as the default directory search path, and the \fBman.cf\fR
-file as the default section search path. The \fB-M\fR and \fB-s\fR flags, in
-turn, override these values.)
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBPAGER\fR\fR
-.ad
-.RS 11n
-A program to use for interactively delivering \fBman\fR's output to the screen.
-If not set, `\fBmore\fR \fB-s\fR' is used. See \fBmore\fR(1).
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTCAT\fR\fR
-.ad
-.RS 11n
-The name of the program to use to display \fBtroff\fRed manual pages.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBTROFF\fR\fR
-.ad
-.RS 11n
-The name of the formatter to use when the \fB-t\fR flag is given. If not set,
-\fBtroff\fR(1) is used.
-.RE
-
-.SH EXAMPLES
-.LP
-\fBExample 1 \fRCreating a PostScript Version of a man page
-.sp
-.LP
-The following example creates the \fBpipe\fR(2) man page in postscript for csh,
-tcsh, ksh and sh users:
-
-.sp
-.in +2
-.nf
-	% env TCAT=/usr/lib/lp/postscript/dpost man -t -s 2 pipe > pipe.ps
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-This is an alternative to using \fBman\fR \fB-t\fR, which sends the man page to
-the default printer, if the user wants a postscript file version of the man
-page.
-
-.LP
-\fBExample 2 \fRCreating a Text Version of a man page
-.sp
-.LP
-The following example creates the \fBpipe\fR(2) man page in ascii text:
-
-.sp
-.in +2
-.nf
-man pipe.2 | col -x -b > pipe.text
-.fi
-.in -2
-.sp
-
-.sp
-.LP
-This is an alternative to using \fBman\fR \fB-t\fR, which sends the man page to
-the default printer, if the user wants a text file version of the man page.
-
-.SH EXIT STATUS
-.sp
-.LP
-The following exit values are returned:
-.sp
-.ne 2
-.na
-\fB\fB0\fR\fR
-.ad
-.RS 6n
-Successful completion.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB>0\fR\fR
-.ad
-.RS 6n
-An error occurred.
-.RE
-
-.SH FILES
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man\fR\fR
-.ad
-.sp .6
-.RS 4n
-Root of the standard manual page directory subtree
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/man?/*\fR\fR
-.ad
-.sp .6
-.RS 4n
-Unformatted nroff manual entries
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/sman?/*\fR\fR
-.ad
-.sp .6
-.RS 4n
-Unformatted \fBSGML\fR manual entries
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/cat?/*\fR\fR
-.ad
-.sp .6
-.RS 4n
-\fBnroff\fRed manual entries
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/fmt?/*\fR\fR
-.ad
-.sp .6
-.RS 4n
-\fBtroff\fRed manual entries
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/windex\fR\fR
-.ad
-.sp .6
-.RS 4n
-Table of contents and keyword database
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/lib/tmac/an\fR\fR
-.ad
-.sp .6
-.RS 4n
-Standard \fB-man\fR macro package
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/lib/sgml/locale/C/dtd/*\fR\fR
-.ad
-.sp .6
-.RS 4n
-\fBSGML\fR document type definition files
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/lib/sgml/locale/C/solbook/*\fR\fR
-.ad
-.sp .6
-.RS 4n
-\fBSGML\fR style sheet and entity definitions directories
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/lib/pub/eqnchar\fR\fR
-.ad
-.sp .6
-.RS 4n
-Standard definitions for \fBeqn\fR and \fBneqn\fR
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBman.cf\fR\fR
-.ad
-.sp .6
-.RS 4n
-Default search order by section
-.RE
-
-.SH ATTRIBUTES
-.sp
-.LP
-See \fBattributes\fR(5) for descriptions of the following attributes:
-.sp
-
-.sp
-.TS
-box;
-c | c
-l | l .
-ATTRIBUTE TYPE	ATTRIBUTE VALUE
-_
-CSI	Enabled, see \fBNOTES\fR.
-_
-Interface Stability	Committed
-_
-Standard	See \fBstandards\fR(5).
-.TE
-
-.SH SEE ALSO
-.sp
-.LP
-\fBapropos\fR(1), \fBcat\fR(1), \fBcol\fR(1), \fBdpost\fR(1), \fBeqn\fR(1),
-\fBmore\fR(1), \fBnroff\fR(1), \fBrefer\fR(1), \fBtbl\fR(1), \fBtroff\fR(1),
-\fBvgrind\fR(1), \fBwhatis\fR(1), \fBcatman\fR(1M), \fBattributes\fR(5),
-\fBenviron\fR(5), \fBeqnchar\fR(5), \fBman\fR(5), \fBsgml\fR(5),
-\fBstandards\fR(5)
-.SH NOTES
-.sp
-.LP
-The \fB-f\fR and \fB-k\fR options use the \fBwindex\fR database, which is
-created by \fBcatman\fR(1M).
-.sp
-.LP
-The \fBman\fR command is CSI-capable. However, some utilities invoked by the
-\fBman\fR command, namely, \fBtroff\fR, \fBeqn\fR, \fBneqn\fR, \fBrefer\fR,
-\fBtbl\fR, and \fBvgrind\fR, are not verified to be CSI-capable. Because of
-this, the man command with the \fB-t\fR option can not handle non-EUC data.
-Also, using the \fBman\fR command to display man pages that require special
-processing through \fBeqn\fR, \fBneqn\fR, \fBrefer\fR, \fBtbl\fR, or
-\fBvgrind\fR can not be CSI-capable.
-.SH BUGS
-.sp
-.LP
-The manual is supposed to be reproducible either on a phototypesetter or on an
-\fBASCII\fR terminal. However, on a terminal some information (indicated by
-font changes, for instance) is lost.
-.sp
-.LP
-Some dumb terminals cannot process the vertical motions produced by the \fBe\fR
-(see \fBeqn\fR(1)) preprocessing flag. To prevent garbled output on these
-terminals, when you use \fBe\fR, also use \fBt\fR, to invoke \fBcol\fR(1)
-implicitly. This workaround has the disadvantage of eliminating superscripts
-and subscripts, even on those terminals that can display them. Control-q clears
-a terminal that gets confused by \fBeqn\fR(1) output.
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\" Copyright 2014 Garrett D'Amore <garrett@damore.org>
+.\"
+.Dd October 18, 2012
+.Dt MAN 1
+.Os
+.Sh NAME
+.Nm man
+.Nd find and display reference manual pages
+.Sh SYNOPSIS
+.Nm
+.Op Fl alptw
+.Op Fl M Ar manpath
+.Op Fl s Ar mansect
+.Ar page ...
+.Nm
+.Op Fl s Ar mansect
+.Fl f
+.Ar keyword ...
+.Nm
+.Op Fl s Ar mansect
+.Fl k
+.Ar keyword ...
+.Sh DESCRIPTION
+The
+.Nm
+utility finds and displays reference manual pages.
+.Pp
+Options that
+.Nm
+understands:
+.Bl -tag -width indent
+.It Fl M Ar manpath
+Forces a specific colon separated manual path instead of the default
+search path.
+Overrides the
+.Ev MANPATH
+environment variable.
+.It Fl a
+Display all manual pages instead of just the first found for each
+.Ar page
+argument.
+.It Fl f
+Emulate
+.Xr whatis 1 .
+.It Fl k
+Emulate
+.Xr apropos 1 .
+.It Fl l
+Display the location of the manual page instead of the contents of
+the manual page.
+.It Fl p
+Output current path used for searching.
+.It Fl s Ar mansect
+Restrict manual sections searched to the specified colon delimited list.
+.It Fl t
+Send the content formatted as PostScript to the default printer.
+.It Fl w
+Create
+.Nm whatis
+databases used by
+.Xr apropos 1
+and
+.Xr whatis 1 .
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev MANPATH"
+.It Ev LC_ALL , LC_CTYPE , LANG
+Used to find locale-specific manual pages.
+.It Ev MANPATH
+Used to find the location of the manual files.
+Corresponds to the
+.Fl M
+option.
+.It Ev MANWIDTH
+Defines the width of output. If set to
+.Dq Li tty ,
+and output is to a terminal, full width of terminal is used.
+.It Ev PAGER
+Program used to display files. If unset,
+.Dq Li "less -ins"
+is used.
+.It Ev PATH
+Used to find location of manual files if
+.Ev MANPATH
+and
+.Fl M
+are not specified.
+.El
+.Sh FILES
+.Bl -tag -width indent -compact
+.It Pa man.cf
+Per-manpath configuration settings.  The file is formatted as follows:
+.Bd -literal -offset indent
+MANSECT=\fIsection\fR[,\fIsection\fR]...
+.Ed
+.Pp
+Each section consists of a section in the reference manual.  The file
+may also contain comment blank lines or lines consisting of comments, where
+the first character in the line is `#'.  Both blank lines and comment lines are
+ignored.
+.El
+.Sh CODE SET INDEPENDENCE
+Enabled.
+.Sh INTERFACE STABILITY
+The
+.Nm
+utility is
+.Nm Standard ,
+as is the
+.Fl k
+option. The other options are
+.Nm Committed .
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr intro 1 ,
+.Xr mandoc 1 ,
+.Xr whatis 1 ,
+.Xr man 5 ,
+.Xr mdoc 5 ,
+.Xr standards 5
+.Sh NOTES
+Some pages may contain information which cannot be properly displayed on
+all terminals.  In such cases, some information may be lost.
--- /dev/null	Fri Jul 18 08:34:37 2014
+++ new/usr/src/man/man1/mandoc.1	Fri Jul 18 08:34:37 2014
@@ -0,0 +1,678 @@
+.\"
+.\" 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.
+.\"
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\" Copyright 2014 Garrett D'Amore <garrett@damore.org>
+.\"
+.Dd Jul 16, 2014
+.Dt MANDOC 1
+.Os
+.Sh NAME
+.Nm mandoc
+.Nd format and display UNIX manuals
+.Sh SYNOPSIS
+.Nm mandoc
+.Op Fl V
+.Op Fl m Ns Ar format
+.Op Fl O Ns Ar option
+.Op Fl T Ns Ar output
+.Op Fl W Ns Ar level
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility formats
+.Ux
+manual pages for display.
+.Pp
+By default,
+.Nm
+reads
+.Xr mdoc 5
+or
+.Xr man 5
+text from stdin, implying
+.Fl m Ns Cm andoc ,
+and produces
+.Fl T Ns Cm ascii
+output.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl m Ns Ar format
+Input format.
+See
+.Sx Input Formats
+for available formats.
+Defaults to
+.Fl m Ns Cm andoc .
+.It Fl O Ns Ar option
+Comma-separated output options.
+.It Fl T Ns Ar output
+Output format.
+See
+.Sx Output Formats
+for available formats.
+Defaults to
+.Fl T Ns Cm ascii .
+.It Fl V
+Print version and exit.
+.It Fl W Ns Ar level
+Specify the minimum message
+.Ar level
+to be reported on the standard error output and to affect the exit status.
+The
+.Ar level
+can be
+.Cm warning ,
+.Cm error ,
+or
+.Cm fatal .
+The default is
+.Fl W Ns Cm fatal ;
+.Fl W Ns Cm all
+is an alias for
+.Fl W Ns Cm warning .
+See
+.Sx EXIT STATUS
+and
+.Sx DIAGNOSTICS
+for details.
+.Pp
+The special option
+.Fl W Ns Cm stop
+tells
+.Nm
+to exit after parsing a file that causes warnings or errors of at least
+the requested level.
+No formatted output will be produced from that file.
+If both a
+.Ar level
+and
+.Cm stop
+are requested, they can be joined with a comma, for example
+.Fl W Ns Cm error , Ns Cm stop .
+.It Ar file
+Read input from zero or more files.
+If unspecified, reads from stdin.
+If multiple files are specified,
+.Nm
+will halt with the first failed parse.
+.El
+.Ss Input Formats
+The
+.Nm
+utility accepts
+.Xr mdoc 5
+and
+.Xr man 5
+input with
+.Fl m Ns Cm doc
+and
+.Fl m Ns Cm an ,
+respectively.
+The
+.Xr mdoc 5
+format is
+.Em strongly
+recommended;
+.Xr man 5
+should only be used for legacy manuals.
+.Pp
+A third option,
+.Fl m Ns Cm andoc ,
+which is also the default, determines encoding on-the-fly: if the first
+non-comment macro is
+.Sq \&Dd
+or
+.Sq \&Dt ,
+the
+.Xr mdoc 5
+parser is used; otherwise, the
+.Xr man 5
+parser is used.
+.Pp
+If multiple
+files are specified with
+.Fl m Ns Cm andoc ,
+each has its file-type determined this way.
+If multiple files are
+specified and
+.Fl m Ns Cm doc
+or
+.Fl m Ns Cm an
+is specified, then this format is used exclusively.
+.Ss Output Formats
+The
+.Nm
+utility accepts the following
+.Fl T
+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.
+See
+.Sx HTML Output .
+.It Fl T Ns Cm lint
+Parse only: produce no output.
+Implies
+.Fl W Ns Cm warning .
+.It Fl T Ns Cm locale
+Encode output using the current locale.
+See
+.Sx Locale Output .
+.It Fl T Ns Cm man
+Produce
+.Xr man 5
+format output.
+See
+.Sx Man Output .
+.It Fl T Ns Cm pdf
+Produce PDF output.
+See
+.Sx PDF Output .
+.It Fl T Ns Cm ps
+Produce PostScript output.
+See
+.Sx PostScript Output .
+.It Fl T Ns Cm tree
+Produce an indented parse tree.
+.It Fl T Ns Cm utf8
+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 .
+.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
+.Xr ascii 5 .
+.Pp
+Font styles are applied by using back-spaced encoding such that an
+underlined character
+.Sq c
+is rendered as
+.Sq _ Ns \e[bs] Ns c ,
+where
+.Sq \e[bs]
+is the back-space character number 8.
+Emboldened characters are rendered as
+.Sq c Ns \e[bs] Ns c .
+.Pp
+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.
+.Pp
+The following
+.Fl O
+arguments are accepted:
+.Bl -tag -width Ds
+.It Cm indent Ns = Ns Ar indent
+The left margin for normal text is set to
+.Ar indent
+blank characters instead of the default of five for
+.Xr mdoc 5
+and seven for
+.Xr man 5 .
+Increasing this is not recommended; it may result in degraded formatting,
+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.
+.El
+.Ss HTML Output
+Output produced by
+.Fl T Ns Cm html
+conforms to HTML-4.01 strict.
+.Pp
+The
+.Pa example.style.css
+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
+browser.
+.Pp
+Special characters are rendered in decimal-encoded UTF\-8.
+.Pp
+The following
+.Fl O
+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.
+The
+.Cm style
+argument will be ignored.
+This is useful when embedding manual content within existing documents.
+.It Cm includes Ns = Ns Ar fmt
+The string
+.Ar fmt ,
+for example,
+.Ar ../src/%I.html ,
+is used as a template for linked header files (usually via the
+.Sq \&In
+macro).
+Instances of
+.Sq \&%I
+are replaced with the include filename.
+The default is not to present a
+hyperlink.
+.It Cm man Ns = Ns Ar fmt
+The string
+.Ar fmt ,
+for example,
+.Ar ../html%S/%N.%S.html ,
+is used as a template for linked manuals (usually via the
+.Sq \&Xr
+macro).
+Instances of
+.Sq \&%N
+and
+.Sq %S
+are replaced with the linked manual's name and section, respectively.
+If no section is included, section 1 is assumed.
+The default is not to
+present a hyperlink.
+.It Cm style Ns = Ns Ar style.css
+The file
+.Ar style.css
+is used for an external style-sheet.
+This must be a valid absolute or
+relative URI.
+.El
+.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.
+.Ss Man Output
+Translate input format into
+.Xr man 5
+output format.
+This is useful for distributing manual sources to legacy systems
+lacking
+.Xr mdoc 5
+formatters.
+.Pp
+If
+.Xr mdoc 5
+is passed as input, it is translated into
+.Xr man 5 .
+If the input format is
+.Xr man 5 ,
+the input is copied to the output, expanding any
+.Xr mandoc_roff 5
+.Sq so
+requests.
+The parser is also run, and as usual, the
+.Fl W
+level controls which
+.Sx DIAGNOSTICS
+are displayed before copying the input to the output.
+.Ss PDF Output
+PDF-1.1 output may be generated by
+.Fl T Ns Cm pdf .
+See
+.Sx PostScript Output
+for
+.Fl O
+arguments and defaults.
+.Ss PostScript Output
+PostScript
+.Qq Adobe-3.0
+Level-2 pages may be generated by
+.Fl T Ns Cm ps .
+Output pages default to letter sized and are rendered in the Times font
+family, 11-point.
+Margins are calculated as 1/9 the page length and width.
+Line-height is 1.4m.
+.Pp
+Special characters are rendered as in
+.Sx ASCII Output .
+.Pp
+The following
+.Fl O
+arguments are accepted:
+.Bl -tag -width Ds
+.It Cm paper Ns = Ns Ar name
+The paper size
+.Ar name
+may be one of
+.Ar a3 ,
+.Ar a4 ,
+.Ar a5 ,
+.Ar legal ,
+or
+.Ar letter .
+You may also manually specify dimensions as
+.Ar NNxNN ,
+width by height in millimetres.
+If an unknown value is encountered,
+.Ar letter
+is used.
+.El
+.Ss UTF\-8 Output
+Use
+.Fl T Ns Cm utf8
+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
+utility exits with one of the following values, controlled by the message
+.Ar level
+associated with the
+.Fl W
+option:
+.Pp
+.Bl -tag -width Ds -compact
+.It 0
+No warnings or errors occurred, or those that did were ignored because
+they were lower than the requested
+.Ar level .
+.It 2
+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
+.Fl W Ns Cm error
+or
+.Fl W Ns Cm warning
+was specified.
+.It 4
+A fatal parsing error occurred.
+.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.
+Such errors cause
+.Nm
+to exit at once, possibly in the middle of parsing or formatting a file.
+.El
+.Pp
+Note that selecting
+.Fl T Ns Cm lint
+output mode implies
+.Fl W Ns Cm warning .
+.Sh EXAMPLES
+To page manuals to the terminal:
+.Pp
+.Dl $ mandoc \-Wall,stop mandoc.1 2\*(Gt&1 | less
+.Dl $ mandoc mandoc.1 mdoc.5 | less
+.Pp
+To produce HTML manuals with
+.Ar style.css
+as the style-sheet:
+.Pp
+.Dl $ mandoc \-Thtml -Ostyle=style.css mdoc.5 \*(Gt mdoc.5.html
+.Pp
+To check over a large set of manuals:
+.Pp
+.Dl $ mandoc \-Tlint `find /usr/src -name \e*\e.[1-9]`
+.Pp
+To produce a series of PostScript manuals for A4 paper:
+.Pp
+.Dl $ mandoc \-Tps \-Opaper=a4 mdoc.5 man.5 \*(Gt manuals.ps
+.Pp
+Convert a modern
+.Xr mdoc 5
+manual to the older
+.Xr man 5
+format, for use on systems lacking an
+.Xr mdoc 5
+parser:
+.Pp
+.Dl $ mandoc \-Tman foo.mdoc \*(Gt foo.man
+.Sh DIAGNOSTICS
+Standard error messages reporting parsing errors are prefixed by
+.Pp
+.Sm off
+.D1 Ar file : line : column : \ level :
+.Sm on
+.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
+.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
+.Nm
+does not implement it yet.
+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.
+.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
+rendering can be produced.
+Documents causing warnings may render poorly when using other
+formatting tools instead of
+.Nm .
+.El
+.Pp
+Messages of the
+.Cm warning
+and
+.Cm error
+levels 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
+The
+.Sq \&Bd \-literal
+and
+.Sq \&Bd \-unfilled
+macros of
+.Xr mdoc 5
+in
+.Fl T Ns Cm ascii
+are synonyms, as are \-filled and \-ragged.
+.It
+In historic GNU troff, the
+.Sq \&Pa
+.Xr mdoc 5
+macro does not underline when scoped under an
+.Sq \&It
+in the FILES section.
+This behaves correctly in
+.Nm .
+.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 .
+.It
+The
+.Sq \&na
+.Xr man 5
+macro in
+.Fl T Ns Cm ascii
+has no effect.
+.It
+Words aren't hyphenated.
+.El
+.Ss HTML/XHTML Compatibility
+.Bl -bullet -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.
+.It
+The
+.Xr mdoc 5
+.Sq \&Bl \-hang
+and
+.Sq \&Bl \-tag
+list types render similarly (no break following overreached left-hand
+side) due to the expressive constraints of HTML.
+.It
+The
+.Xr man 5
+.Sq IP
+and
+.Sq TP
+lists render similarly.
+.El
+.Sh INTERFACE STABILITY
+The
+.Nm
+utility is
+.Nm Committed ,
+but the details of specific output formats other than ASCII are
+.Nm Uncommitted .
+.Sh SEE ALSO
+.Xr eqn 5 ,
+.Xr man 5 ,
+.Xr mandoc_char 5 ,
+.Xr mdoc 5 ,
+.Xr mandoc_roff 5 ,
+.Xr tbl 5
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Kristaps Dzonsons ,
+.Mt kristaps@bsd.lv .
+.Sh CAVEATS
+In
+.Fl T Ns Cm html
+and
+.Fl T Ns Cm xhtml ,
+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.
--- old/usr/src/man/man1/printf.1	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1/printf.1	Fri Jul 18 08:34:38 2014
@@ -128,7 +128,7 @@
 The special character \fB*\fR may be used instead of a string of decimal digits
 to indicate a minimum field width or a precision.  In this case the next
 available argument is used (or the \fIn\fRth if the form \fIn\fR\fB$\fR is
-used), treating its value as a decimal string. 
+used), treating its value as a decimal string.
 .RE
 .RS +4
 .TP
--- old/usr/src/man/man1/tar.1	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1/tar.1	Fri Jul 18 08:34:38 2014
@@ -1182,7 +1182,7 @@
 .sp
 .LP
 \fBar\fR(1), \fBbasename\fR(1), \fBbzip2\fR(1), \fBcd\fR(1), \fBchown\fR(1),
-\fBcompress\fR)(1), \fBcpio\fR(1), \fBcsh\fR(1), \fBdirname\fR(1), 
+\fBcompress\fR)(1), \fBcpio\fR(1), \fBcsh\fR(1), \fBdirname\fR(1),
 \fBfind\fR(1), \fBgzip\fR(1), \fBls\fR(1), \fBmt\fR(1), \fBpax\fR(1),
 \fBsetfacl\fR(1), \fBumask\fR(1), \fBxz\fR(1), \fBmknod\fR(1M),
 \fBarchives.h\fR(3HEAD), \fBattributes\fR(5), \fBenviron\fR(5),
--- old/usr/src/man/man1/whatis.1	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1/whatis.1	Fri Jul 18 08:34:38 2014
@@ -1,61 +1,78 @@
-'\" te
-.\" Copyright (c) 1992, Sun Microsystems, Inc.
-.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
-.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
-.\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH WHATIS 1 "Sep 14, 1992"
-.SH NAME
-whatis \- display a one-line summary about a keyword
-.SH SYNOPSIS
-.LP
-.nf
-\fBwhatis\fR \fIcommand\fR...
-.fi
-
-.SH DESCRIPTION
-.sp
-.LP
-\fBwhatis\fR looks up a given \fIcommand\fR and displays the header line from
-the manual section. You can then run the \fBman\fR(1) command to get more
-information. If the line starts \fBname(\fIsection\fR)\fR.\|.\|. you can do
-\fBman\fR \fB-s\fR\fB\fIsection\fR name\fR to get the documentation for it.
-Try \fBwhatis ed\fR and then you should do \fBman\fR \fB-s\fR \fB1 ed\fR to get
-the manual page for \fBed\fR(1).
-.sp
-.LP
-\fBwhatis\fR is actually just the \fB-f\fR option to the \fBman\fR(1) command.
-.sp
-.LP
-\fBwhatis\fR uses the \fB/usr/share/man/windex\fR database.  This database is
-created by \fBcatman\fR(1M). If this database does not exist, \fBwhatis\fR will
-fail.
-.SH FILES
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/windex\fR\fR
-.ad
-.RS 25n
-Table of contents and keyword database
-.RE
-
-.SH ATTRIBUTES
-.sp
-.LP
-See \fBattributes\fR(5) for descriptions of the following attributes:
-.sp
-
-.sp
-.TS
-box;
-c | c
-l | l .
-ATTRIBUTE TYPE	ATTRIBUTE VALUE
-_
-CSI	Enabled
-.TE
-
-.SH SEE ALSO
-.sp
-.LP
-\fBapropos\fR(1), \fBman\fR(1), \fBcatman\fR(1M), \fBattributes\fR(5)
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\" Copyright 2014 Garrett D'Amore <garrett@damore.org>
+.\"
+.Dd Jul 13, 2014
+.Dt APROPOS 1
+.Os
+.Sh NAME
+.Nm apropos ,
+.Nm whatis
+.Nd keyword search in
+.Nm whatis
+database files
+.Sh SYNOPSIS
+.Nm
+.Op Fl M Ar path
+.Op Fl s Ar section
+.Ar keyword ...
+.Nm whatis
+.Op Fl M Ar path
+.Op Fl s Ar section
+.Ar keyword ...
+.Sh DESCRIPTION
+The
+.Nm
+utility searches a set of
+.Nm whatis
+database files matching each
+.Ar keyword .
+The
+.Nm whatis
+utility does the same search but only on complete words. The
+.Nm whatis
+database files are created using
+.Xr man 1
+command.
+.Bl -tag -width ".Fl d"
+.It Fl M Ar path
+Force a specific colon separated manual path instead of the default search path.
+Overrides the
+.Ev MANPATH
+environment variable.
+.It Fl s Ar section
+Restrict search to specified
+.Ar section .
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev MANPATH , PATH"
+.It Ev MANPATH , PATH
+Used to find the location of the
+.Nm whatis
+database files.
+.El
+.Sh DIAGNOSTICS
+The
+.Nm
+utility exits 0 if a keyword matched and 1 if no keywords are matched or no
+.Nm whatis
+databases are found.
+.Sh INTERFACE STABILITY
+.Nm Committed .
+.Sh CODE SET INDEPENDENCE
+Enabled.
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr mandoc 1
--- old/usr/src/man/man1m/arcstat.1m	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1m/arcstat.1m	Fri Jul 18 08:34:38 2014
@@ -460,7 +460,7 @@
 Specify the sampling interval in seconds.
 .RE
 
-.SH AUTHORS 
+.SH AUTHORS
 .sp
 .LP
 arcstat was originally written by Neelakanth Nadgir and supported only ZFS ARC statistics.
--- old/usr/src/man/man1m/catman.1m	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1m/catman.1m	Fri Jul 18 08:34:38 2014
@@ -1,373 +1,70 @@
-'\" te
-.\"  Copyright (c) 1998 Sun Microsystems, Inc.  All Rights Reserved.
-.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
-.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
-.\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH CATMAN 1M "Feb 27, 1998"
-.SH NAME
-catman \- create the formatted files for the reference manual
-.SH SYNOPSIS
-.LP
-.nf
-\fB/usr/bin/catman\fR [\fB-c\fR] [\fB-n\fR] [\fB-p\fR] [\fB-t\fR] [\fB-w\fR] [\fB-M\fR \fIdirectory\fR]
-     [\fB-T\fR \fImacro-package\fR] [\fIsections\fR]
-.fi
-
-.SH DESCRIPTION
-.sp
-.LP
-The \fBcatman\fR utility creates the preformatted versions of the on-line
-manual from the \fBnroff\fR(1) or \fBsgml\fR(5) input files. This feature
-allows easy distribution of the preformatted manual pages among a group of
-associated machines (for example, with \fBrdist\fR(1)), since it makes the
-directories of preformatted manual pages self-contained and independent of the
-unformatted entries.
-.sp
-.LP
-\fBcatman\fR also creates the  \fBwindex\fR database file in the directories
-specified by the  \fBMANPATH\fR or the  \fB-M\fR option. The \fBwindex\fR
-database file is a three column list consisting of a keyword, the reference
-page that the keyword points to, and a line of text that describes the purpose
-of the utility or interface  documented on the reference page. Each keyword is
-taken from the comma separated list of words on the \fBNAME\fR line before the
-`\(mi' (dash). The reference page that the keyword points to is the first word
-on the  \fBNAME\fR line. The text after the \(mi on the \fBNAME\fR line is the
-descriptive text in the third column. The \fBNAME\fR line must be immediately
-preceded by the page heading line created by the \fB\&.TH\fR macro (see
-\fBNOTES\fR for required format).
-.sp
-.LP
-Each manual page is examined and those whose preformatted versions are missing
-or out of date are recreated. If any changes are made, \fBcatman\fR recreates
-the \fBwindex\fR database.
-.sp
-.LP
-If a manual page is a \fIshadow\fR page, that is, it sources another manual
-page for its contents, a symbolic link is made in the \fBcat\fR\fIx\fR or
-\fBfmt\fR\fIx\fR directory to the appropriate preformatted manual page.
-.sp
-.LP
-Shadow files in an unformatted nroff source file are identified by the first
-line being of the form \fB\&.so man\fR\fIx\fR\fB/yyy.\fR\fIx\fR\fB\&.\fR
-.sp
-.LP
-Shadow files in the \fBSGML\fR sources are identified by the string
-\fBSHADOW_PAGE\fR. The file entity declared in the shadow file identifies the
-file to be sourced.
-.SH OPTIONS
-.sp
-.LP
-The following options are supported:
-.sp
-.ne 2
-.na
-\fB\fB-c\fR\fR
-.ad
-.RS 20n
-Create unformatted nroff source files in the appropriate \fBman\fR
-subdirectories from the  \fBSGML\fR sources. This option will overwrite any
-existing file in the \fBman\fR directory of the same name as the  \fBSGML\fR
-file.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-n\fR\fR
-.ad
-.RS 20n
-Do not create (or recreate) the \fBwindex\fR database. If the \fB-n\fR option
-is specified, the \fBwindex\fR database is not created and the \fBapropos\fR,
-\fBwhatis\fR, \fBman\fR \fB-f\fR, and \fBman\fR \fB-k\fR commands will fail.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-p\fR\fR
-.ad
-.RS 20n
-Print what would be done instead of doing it.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-t\fR\fR
-.ad
-.RS 20n
-Create \fBtroff\fRed entries in the appropriate \fBfmt\fR subdirectories
-instead of \fBnroff\fRing into the \fBcat\fR subdirectories.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-w\fR\fR
-.ad
-.RS 20n
-Only create the \fBwindex\fR database that is used by \fBwhatis\fR(1) and the
-\fBman\fR(1) \fB-f\fR and \fB-k\fR options.  No manual reformatting is done.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-M\fR\fI directory\fR\fR
-.ad
-.RS 20n
-Update manual pages located in the specified \fIdirectory\fR,
-(\fB/usr/share/man\fR by default). If the  \fB-M\fR option is specified, the
-directory argument must not contain a `,' (comma), since a comma is used to
-delineate section numbers. See \fBman\fR(1).
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB-T\fR\fI macro-package\fR\fR
-.ad
-.RS 20n
-Use \fImacro-package\fR in place of the standard manual page macros, (
-\fBman\fR(5) by default).
-.RE
-
-.SH OPERANDS
-.sp
-.LP
-The following operand is supported:
-.sp
-.ne 2
-.na
-\fB\fIsections\fR\fR
-.ad
-.RS 12n
-If there is one parameter not starting with a `\fB\(mi\fR\&', it is taken to be
-a space separated list of manual sections to be processed by \fBcatman\fR. If
-this operand is specified, only the manual sections in the list will be
-processed.  For example,
-.sp
-.in +2
-.nf
-\fBcatman 1 2 3\fR
-.fi
-.in -2
-.sp
-
-only updates manual sections \fB1\fR, \fB2\fR, and \fB3\fR. If specific
-sections are not listed, all sections in the  \fBman\fR directory specified by
-the environment variable \fBMANPATH\fR are processed.
-.RE
-
-.SH ENVIRONMENT VARIABLES
-.sp
-.ne 2
-.na
-\fB\fBTROFF\fR\fR
-.ad
-.RS 11n
-The name of the formatter to use when the \fB-t\fR flag is given. If not set,
-\fBtroff\fR(1) is used.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBMANPATH\fR\fR
-.ad
-.RS 11n
-A colon-separated list of directories that are processed by \fBcatman\fR and
-\fBman\fR(1). Each directory can be followed by a comma-separated list of
-sections. If set, its value overrides \fB/usr/share/man\fR as the default
-directory search path, and the \fBman.cf\fR file as the default section search
-path. The \fB-M\fR and \fB-s\fR flags, in turn, override these values.
-.RE
-
-.SH FILES
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man\fR\fR
-.ad
-.RS 28n
-default manual directory location
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/man*/*.*\fR\fR
-.ad
-.RS 28n
-raw nroff input files
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/sman*/*.*\fR\fR
-.ad
-.RS 28n
-raw  \fBSGML\fR input files
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/cat*/*.*\fR\fR
-.ad
-.RS 28n
-preformatted \fBnroff\fRed manual pages
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/fmt*/*.*\fR\fR
-.ad
-.RS 28n
-preformatted \fBtroff\fRed manual pages
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/windex\fR\fR
-.ad
-.RS 28n
-table of contents and keyword database
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/lib/makewhatis\fR\fR
-.ad
-.RS 28n
-command script to make  \fBwindex\fR database
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/lib/tmac/an\fR\fR
-.ad
-.RS 28n
-default macro package
-.RE
-
-.SH ATTRIBUTES
-.sp
-.LP
-See \fBattributes\fR(5) for descriptions of the following attributes:
-.sp
-
-.sp
-.TS
-box;
-c | c
-l | l .
-ATTRIBUTE TYPE	ATTRIBUTE VALUE
-_
-CSI	Enabled
-.TE
-
-.SH SEE ALSO
-.sp
-.LP
-\fBapropos\fR(1), \fBman\fR(1), \fBnroff\fR(1), \fBrdist\fR(1), \fBrm\fR(1),
-\fBtroff\fR(1), \fBwhatis\fR(1), \fBattributes\fR(5), \fBman\fR(5),
-\fBsgml\fR(5)
-.SH DIAGNOSTICS
-.sp
-.ne 2
-.na
-\fB\fBman?/xxx.? (.so'ed from man?/yyy.?): No such file or directory\fR\fR
-.ad
-.sp .6
-.RS 4n
-The file outside the parentheses is missing, and is referred to by the file
-inside them.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBtarget of .so in man?/xxx.? must be relative to /usr/man\fR\fR
-.ad
-.sp .6
-.RS 4n
-\fBcatman\fR only allows references to filenames that are relative to the
-directory \fB/usr/man\fR.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fBopendir:man?:\fR \fBNo\fR \fBsuch\fR \fBfile\fR \fBor\fR
-\fBdirectory\fR\fR
-.ad
-.sp .6
-.RS 4n
-A harmless warning message indicating that one of the directories \fBcatman\fR
-normally looks for is missing.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB*.*:\fR \fBNo\fR \fBsuch\fR \fBfile\fR \fBor\fR \fBdirectory\fR\fR
-.ad
-.sp .6
-.RS 4n
-A harmless warning message indicating \fBcatman\fR came across an empty
-directory.
-.RE
-
-.SH WARNINGS
-.sp
-.LP
-If a user, who has previously run \fBcatman\fR to install the \fBcat*\fR
-directories, upgrades the operating system, the entire \fBcat*\fR directory
-structure should be removed prior to running \fBcatman\fR. See \fBrm\fR(1).
-.sp
-.LP
-Do not re-run \fBcatman\fR to re-build the \fBwhatis\fR database unless the
-complete set of \fBman*\fR directories is present. \fBcatman\fR builds this
-\fBwindex\fR file based on the  \fBman*\fR directories.
-.SH NOTES
-.sp
-.LP
-To generate a valid windex index file, \fBcatman\fR has certain requirements.
-Within the individual man page file, \fBcatman\fR requires two macro lines to
-have a specific format. These are the \fB\&.TH \fRpage heading line and the
-\fB\&.SH NAME \fRline.
-.sp
-.LP
-The \fB\&.TH \fRmacro requires at least the first three arguments, that is, the
-filename, section number, and the date.  The \fB\&.TH \fRline starts off with
-the \fB\&.TH \fRmacro, followed by a space, the man page filename, a single
-space, the section number, another single space, and the date. The date should
-appear in double quotes and is specified as "day month year," with the month
-always abbreviated to the first three letters (Jan, Feb, Mar, and so forth).
-.sp
-.LP
-The \fB\&.SH NAME \fRmacro, also known as the \fBNAME \fRline, must immediately
-follow the \fB\&.TH \fRline, with nothing in between those lines. No font
-changes are permitted in the \fBNAME \fRline. The \fBNAME \fRline is
-immediately followed by a line containing the man page filename; then shadow
-page names, if applicable, separated by commas; a dash; and a brief summary
-statement. These elements should all be on one line; no carriage returns are
-permitted.
-.sp
-.LP
-An example of proper coding of these lines is:
-.sp
-.in +2
-.nf
-\&.TH NISMATCH 1M "Apr "10, 1998""
-\&.SH NAME
-nismatch, nisgrep \e- utilities for searching NIS+ tables
-.fi
-.in -2
-
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2014 Garrett D'Amore <garrett@damore.org>
+.\"
+.Dd Jul 16, 2014
+.Dt CATAMN 1M
+.Os
+.Sh NAME
+.Nm catman
+.Nd generate
+.Nm whatis
+database files
+.Sh SYNOPSIS
+.Nm
+.Op Fl M Ar path
+.Sh DESCRIPTION
+The
+.Nm
+utility generates a set of
+.Nm whatis
+database files suitable for use with
+.Xr apropos 1
+and
+.Xr whatis 1 .
+It is supplied for compatibility reasons.  The same databases can
+be generated using the
+.Fl w
+option with
+.Xr man 1 ,
+and that command should be used instead.
+.Bl -tag -width ".Fl d"
+.It Fl M Ar path
+Generate the
+.Nm whatis
+database files within the specified colon separated manual paths.
+Overrides the
+.Ev MANPATH
+environment variable.
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev MANPATH"
+.It Ev MANPATH
+Used to specify a colon seperated list of manual paths within
+which to generate
+.Nm whatis
+database files.
+.El
+.Sh DIAGNOSTICS
+The
+.Nm
+utility exits 0 on success, and non-zero otherwise.
+.Sh INTERFACE STABILITY
+.Nm "Obsolete Committed" .
+.Sh CODE SET INDEPENDENCE
+Enabled.
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr man 1 ,
+.Xr whatis 1
--- old/usr/src/man/man1m/dd.1m	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1m/dd.1m	Fri Jul 18 08:34:38 2014
@@ -410,8 +410,6 @@
 \fBfcntl.h\fR(3HEAD).
 .RE
 
-.RE
-
 .sp
 .LP
 If operands other than \fBconv=\fR and \fBoflag=\fR are specified more than once,
--- old/usr/src/man/man1m/ipadm.1m	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1m/ipadm.1m	Fri Jul 18 08:34:38 2014
@@ -419,7 +419,6 @@
 .sp
 
 .RE
-.RE
 
 .sp
 .ne 2
--- old/usr/src/man/man1m/lofiadm.1m	Fri Jul 18 08:34:38 2014
+++ new/usr/src/man/man1m/lofiadm.1m	Fri Jul 18 08:34:38 2014
@@ -81,7 +81,6 @@
 In the global zone, \fBlofiadm\fR can be used on both the global
 zone devices and all devices owned by other non-global zones on the system.
 .sp
-.LP
 .SH OPTIONS
 .sp
 .LP
--- old/usr/src/man/man1m/root_archive.1m	Fri Jul 18 08:34:39 2014
+++ new/usr/src/man/man1m/root_archive.1m	Fri Jul 18 08:34:39 2014
@@ -21,7 +21,7 @@
 .SH DESCRIPTION
 .sp
 .LP
-The \fBroot_archive\fR utility is used to manipulate boot archives. 
+The \fBroot_archive\fR utility is used to manipulate boot archives.
 The utility can pack and unpack boot archives and image miniroots. Both \fBufs\fR and \fBhsfs\fR
 (\fBiso9660\fR) format archives can be unpacked, although only \fBufs\fR format
 is generated when packing.
--- old/usr/src/man/man1m/zonecfg.1m	Fri Jul 18 08:34:39 2014
+++ new/usr/src/man/man1m/zonecfg.1m	Fri Jul 18 08:34:39 2014
@@ -864,7 +864,6 @@
 
 .sp
 .ne 2
-.mk
 .na
 \fBglobal: \fBfs-allowed\fR\fR
 .ad
--- old/usr/src/man/man2/pipe.2	Fri Jul 18 08:34:39 2014
+++ new/usr/src/man/man2/pipe.2	Fri Jul 18 08:34:39 2014
@@ -41,7 +41,6 @@
 behave identically to \fBpipe()\fR.  Values for \fIflags\fR are constructed
 by a bitwise-inclusive-OR of flags from the following list, defined in
 <\fBfcntl.h\fR>.
-.RE
 
 .sp
 .ne 2
--- old/usr/src/man/man3c/btowc.3c	Fri Jul 18 08:34:39 2014
+++ new/usr/src/man/man3c/btowc.3c	Fri Jul 18 08:34:39 2014
@@ -35,7 +35,7 @@
 .LP
 The behavior of \fBbtowc()\fR is affected by the \fBLC_CTYPE\fR category of the
 current locale.  See \fBenviron\fR(5). The function \fBbtowc_l()\fR does
-not use the current locale, and instead operates on the locale 
+not use the current locale, and instead operates on the locale
 specified by \fIloc\fR.
 .SH RETURN VALUES
 .LP
--- old/usr/src/man/man3c/fgetwc.3c	Fri Jul 18 08:34:39 2014
+++ new/usr/src/man/man3c/fgetwc.3c	Fri Jul 18 08:34:39 2014
@@ -36,7 +36,7 @@
 convert that to the corresponding wide-character code and advance the
 associated file position indicator for the stream (if defined).
 Whereas \fBfgetwc()\fR uses the current locale, \fBfgetwc_l()\fR uses the
-locale specified by \fIloc\R.
+locale specified by \fIloc\fR.
 .LP
 If an error occurs, the resulting value of the file position indicator for the
 stream is indeterminate.
--- old/usr/src/man/man3c/fopen.3c	Fri Jul 18 08:34:39 2014
+++ new/usr/src/man/man3c/fopen.3c	Fri Jul 18 08:34:39 2014
@@ -34,7 +34,7 @@
 .sp
 .ne 2
 .na
-\fB\fBr\fR\fR 
+\fB\fBr\fR\fR
 .ad
 .RS 20n
 Open file for reading.
--- old/usr/src/man/man3c/iswctype.3c	Fri Jul 18 08:34:39 2014
+++ new/usr/src/man/man3c/iswctype.3c	Fri Jul 18 08:34:39 2014
@@ -42,7 +42,7 @@
 and wide-character codes corresponding to the valid character encodings in the
 current locale (or
 .I loc
-for 
+for
 .BR iswctype_l() .)
 If the
 .I wc
--- old/usr/src/man/man3c/mbrtowc.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/mbrtowc.3c	Fri Jul 18 08:34:40 2014
@@ -81,7 +81,7 @@
 internal
 .B mbstate_t
 object, which is initialized at program startup to the
-initial conversion state. Otherwise, the 
+initial conversion state. Otherwise, the
 .B mbstate_t
 object pointed to by
 .I ps
--- old/usr/src/man/man3c/mbsrtowcs.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/mbsrtowcs.3c	Fri Jul 18 08:34:40 2014
@@ -135,7 +135,7 @@
 .B mbsnrtowcs()
 and
 .B mbsrtowcs()
-functions are Standard.  The 
+functions are Standard.  The
 .B mbsnrtowcs_l()
 and
 .B mbsrtowcs_l()
--- old/usr/src/man/man3c/newlocale.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/newlocale.3c	Fri Jul 18 08:34:40 2014
@@ -76,7 +76,7 @@
 Indicates that the locale should be processed based in the values in the
 environment. See
 .BR setlocale (3C)
-and 
+and
 .BR environ (5)
 for more information.
 .LP
--- old/usr/src/man/man3c/strcoll.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/strcoll.3c	Fri Jul 18 08:34:40 2014
@@ -69,8 +69,6 @@
 .SH FILES
 .IP \fB/usr/lib/locale/\fR\fIlocale\fR\fB/LC_COLLATE/*\fR
 collation database for \fIlocale\fR
-.RE
-
 .SH ATTRIBUTES
 .LP
 See \fBattributes\fR(5) for descriptions of the following attributes:
--- old/usr/src/man/man3c/string.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/string.3c	Fri Jul 18 08:34:40 2014
@@ -174,7 +174,7 @@
 The \fBstrcasecmp()\fR and \fBstrncasecmp()\fR functions compare two strings
 byte-by-byte, after
 converting each upper-case character to lower-case (as determined by the
-\fBLC_CTYPE\fR category of the current locale).  Note that neither the contents 
+\fBLC_CTYPE\fR category of the current locale).  Note that neither the contents
 pointed to by \fIs1\fR nor \fIs2\fR are modified.
 .LP
 The functions return an integer
--- old/usr/src/man/man3c/towlower.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/towlower.3c	Fri Jul 18 08:34:40 2014
@@ -26,7 +26,7 @@
 .LP
 .nf
 \fBwint_t\fR \fBtowlower_l\fR(\fBwint_t\fR \fIwc\fR, \fBlocale_t\fR \fIloc\fR);
-.nf
+.fi
 .SH DESCRIPTION
 The function
 .BR towlower()
--- old/usr/src/man/man3c/uselocale.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/uselocale.3c	Fri Jul 18 08:34:40 2014
@@ -34,7 +34,7 @@
 .LP
 If
 .BR (locale_t) 0
-is supplied for 
+is supplied for
 .IR loc ,
 then no change is made to the thread's locale setting.  This can be used
 to query the thread's locale without making any change.
--- old/usr/src/man/man3c/wcscoll.3c	Fri Jul 18 08:34:40 2014
+++ new/usr/src/man/man3c/wcscoll.3c	Fri Jul 18 08:34:40 2014
@@ -50,7 +50,7 @@
 character string pointed to by \fIws1\fR is greater than, equal to, or less
 than the wide character string pointed to by \fIws2\fR, when both are
 interpreted as appropriate to the the current locale, or the locale
-specified by \fIloc\fR. On error, 
+specified by \fIloc\fR. On error,
 they set \fBerrno\fR, but no return value is reserved to indicate an error.
 .SH ERRORS
 .LP
--- old/usr/src/man/man3c/wcsrtombs.3c	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man3c/wcsrtombs.3c	Fri Jul 18 08:34:41 2014
@@ -24,6 +24,7 @@
 .nf
 \fBsize_t\fR \fBwcsnrtombs\fR(\fBchar *restrict\fR \fIdst\fR, \fBconst wchar_t **restrict\fR \fIsrc\fR,
      \fBsize_t\fR \fInwc\fR, \fBsize_t\fR \fIlen\fR, \fBmbstate_t *restrict\fR \fIps\fR);
+.fi
 .LP
 .nf
 #include <wchar.h>
--- old/usr/src/man/man3lib/libc.3lib	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man3lib/libc.3lib	Fri Jul 18 08:34:41 2014
@@ -434,7 +434,7 @@
 \fBmutex_destroy\fR	\fBmutex_init\fR
 \fBmutex_lock\fR	\fBmutex_trylock\fR
 \fBmutex_unlock\fR	\fBnanosleep\fR
-\fBnextafter\fR		\fBnewlocale\fR
+\fBnextafter\fR	\fBnewlocale\fR
 \fBnfs_getfh\fR	\fBnftw\fR
 \fBngettext\fR	\fBnice\fR
 \fBnl_langinfo\fR	\fBnl_langinfo_l\fR
--- old/usr/src/man/man4/init.4	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man4/init.4	Fri Jul 18 08:34:41 2014
@@ -5,7 +5,7 @@
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
 .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
 .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH init 4 "Mar 15, 2014"
+.TH INIT 4 "Mar 15, 2014"
 .SH NAME
 init \- set default system time zone and locale
 .SH SYNOPSIS
@@ -25,7 +25,7 @@
 .LP
 \fB/etc/TIMEZONE\fR is a symbolic link to \fB/etc/default/init\fR.  This
 link exists for compatibility with legacy software, is obsolete, and may
-be removed in a future release. 
+be removed in a future release.
 .sp
 .LP
 The number of environment variables that can be set from
--- old/usr/src/man/man5/Makefile	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man5/Makefile	Fri Jul 18 08:34:41 2014
@@ -13,6 +13,7 @@
 # Copyright 2011, Richard Lowe
 # Copyright (c) 2012 by Delphix. All rights reserved.
 # Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
 
 include		$(SRC)/Makefile.master
@@ -41,6 +42,7 @@
 		dhcp.5			\
 		dhcp_modules.5		\
 		environ.5		\
+		eqn.5			\
 		eqnchar.5		\
 		extendedFILE.5		\
 		filesystem.5		\
@@ -64,7 +66,9 @@
 		lfcompile64.5		\
 		locale.5		\
 		man.5			\
-		mansun.5		\
+		mandoc_char.5		\
+		mandoc_roff.5		\
+		mdoc.5			\
 		me.5			\
 		mech_spnego.5		\
 		mm.5			\
@@ -110,6 +114,7 @@
 		smf_template.5		\
 		standards.5		\
 		sticky.5		\
+		tbl.5			\
 		tecla.5			\
 		term.5			\
 		threads.5		\
--- /dev/null	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man5/eqn.5	Fri Jul 18 08:34:41 2014
@@ -0,0 +1,281 @@
+.\"
+.\" 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.
+.\"
+.\"
+.\" Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\"
+.Dd Sep 25, 2011
+.Dt EQN 5
+.Os
+.Sh NAME
+.Nm eqn
+.Nd eqn language reference for mandoc
+.Sh DESCRIPTION
+The
+.Nm eqn
+language is an equation-formatting language.
+It is used within
+.Xr mdoc 5
+and
+.Xr man 5
+.Ux
+manual pages.
+It describes the
+.Em structure
+of an equation, not its mathematical meaning.
+This manual describes the
+.Nm
+language accepted by the
+.Xr mandoc 1
+utility, which corresponds to the Second Edition eqn specification (see
+.Sx SEE ALSO
+for references).
+.Pp
+Equations within
+.Xr mdoc 5
+or
+.Xr man 5
+documents are enclosed by the standalone
+.Sq \&.EQ
+and
+.Sq \&.EN
+tags.
+Equations are multi-line blocks consisting of formulas and control
+statements.
+.Sh EQUATION STRUCTURE
+Each equation is bracketed by
+.Sq \&.EQ
+and
+.Sq \&.EN
+strings.
+.Em Note :
+these are not the same as
+.Xr roff 5
+macros, and may only be invoked as
+.Sq \&.EQ .
+.Pp
+The equation grammar is as follows, where quoted strings are
+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
+        | box pos box
+        | box mark
+        | \*qmatrix\*q \*q{\*q [col \*q{\*q list \*q}\*q ]*
+        | pile \*q{\*q list \*q}\*q
+        | 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
+list    : eqn
+        | list \*qabove\*q eqn
+space   : [\e^~ \et]
+.Ed
+.Pp
+White-space consists of the space, tab, circumflex, and tilde
+characters.
+If within a quoted string, these space characters are retained.
+Quoted strings are also not scanned for replacement definitions.
+.Pp
+The following text terms are translated into a rendered glyph, if
+available: alpha, beta, chi, delta, epsilon, eta, gamma, iota, kappa,
+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
+space), approx (approximately equals), prime (prime), half (one-half),
+partial (partial differential), inf (infinity), >> (much greater), <<
+(much less), \-> (left arrow), <\- (right arrow), += (plus-minus), !=
+(not equal), == (equivalence), <= (less-than-equal), and >=
+(more-than-equal).
+.Pp
+The following control statements are available:
+.Bl -tag -width Ds
+.It Cm define
+Replace all occurrences of a key with a value.
+Its syntax is as follows:
+.Pp
+.D1 define Ar key cvalc
+.Pp
+The first character of the value string,
+.Ar c ,
+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
+.Pp
+It is an error to have an empty
+.Ar key
+or
+.Ar val .
+Note that a quoted
+.Ar key
+causes errors in some
+.Nm
+implementations and should not be considered portable.
+It is not expanded for replacements.
+Definitions may refer to other definitions; these are evaluated
+recursively when text replacement occurs and not when the definition is
+created.
+.Pp
+Definitions can create arbitrary strings, for example, the following is
+a legal construction.
+.Bd -literal -offset indent
+define foo 'define'
+foo bar 'baz'
+.Ed
+.Pp
+Self-referencing definitions will raise an error.
+The
+.Cm ndefine
+statement is a synonym for
+.Cm define ,
+while
+.Cm tdefine
+is discarded.
+.It Cm gfont
+Set the default font of subsequent output.
+Its syntax is as follows:
+.Pp
+.D1 gfont Ar font
+.Pp
+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
+.Pp
+The
+.Ar size
+value should be an integer.
+.It Cm set
+Set an equation mode.
+In mandoc, both arguments are thrown away.
+Its syntax is as follows:
+.Pp
+.D1 set Ar key val
+.Pp
+The
+.Ar key
+and
+.Ar val
+are not expanded for replacements.
+This statement is a GNU extension.
+.It Cm undef
+Unset a previously-defined key.
+Its syntax is as follows:
+.Pp
+.D1 define Ar key
+.Pp
+Once invoked, the definition for
+.Ar key
+is discarded.
+The
+.Ar key
+is not expanded for replacements.
+This statement is a GNU extension.
+.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 troff.
+In mandoc, this is interpreted as a comment.
+.It
+In troff, The circumflex and tilde white-space symbols map to
+fixed-width spaces.
+In mandoc, these characters are synonyms for the space character.
+.It
+The troff implementation of
+.Nm
+allows for equation alignment with the
+.Cm mark
+and
+.Cm lineup
+tokens.
+mandoc discards these tokens.
+The
+.Cm back Ar n ,
+.Cm fwd Ar n ,
+.Cm up Ar n ,
+and
+.Cm down Ar n
+commands are also ignored.
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr man 7 ,
+.Xr mandoc_char 5 ,
+.Xr mdoc 5 ,
+.Xr roff 5
+.Rs
+.%A Brian W. Kernighan
+.%A Lorinda L. Cherry
+.%T System for Typesetting Mathematics
+.%J Communications of the ACM
+.%V 18
+.%P 151\(en157
+.%D March, 1975
+.Re
+.Rs
+.%A Brian W. Kernighan
+.%A Lorinda L. Cherry
+.%T Typesetting Mathematics, User's Guide
+.%D 1976
+.Re
+.Rs
+.%A Brian W. Kernighan
+.%A Lorinda L. Cherry
+.%T Typesetting Mathematics, User's Guide (Second Edition)
+.%D 1978
+.Re
+.Sh HISTORY
+The eqn utility, a preprocessor for troff, was originally written by
+Brian W. Kernighan and Lorinda L. Cherry in 1975.
+The GNU reimplementation of eqn, part of the GNU troff package, was
+released in 1989 by James Clark.
+The eqn component of
+.Xr mandoc 1
+was added in 2011.
+.Sh AUTHORS
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons ,
+.Mt kristaps@bsd.lv .
--- old/usr/src/man/man5/man.5	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man5/man.5	Fri Jul 18 08:34:41 2014
@@ -1,342 +1,913 @@
-'\" te
-.\" Copyright (c) 1995, Sun Microsystems, Inc.
-.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
-.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
-.\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH MAN 5 "Jan 30, 1995"
-.SH NAME
-man \- macros to format Reference Manual pages
-.SH SYNOPSIS
-.LP
-.nf
-\fBnroff\fR \fB-man\fR \fIfilename\fR...
-.fi
-
-.LP
-.nf
-\fBtroff\fR  \fB-man\fR \fIfilename\fR...
-.fi
-
-.SH DESCRIPTION
-.sp
-.LP
-These macros are used to lay out the reference pages in this manual. Note: if
-\fIfilename\fR contains format input for a preprocessor, the commands shown
-above must be piped through the appropriate preprocessor. This is handled
-automatically by the \fBman\fR(1) command. See the ``Conventions'' section.
-.sp
-.LP
-Any text argument \fIt\fR may be zero to six words. Quotes may be used to
-include SPACE characters in a "word".  If \fItext\fR is empty, the special
-treatment is applied to the next input line with text to be printed. In this
-way \fB\&.I\fR may be used to italicize a whole line, or \fB\&.SB\fR may be
-used to make small bold letters.
-.sp
-.LP
-A prevailing indent distance is remembered between successive indented
-paragraphs, and is reset to default value upon reaching a non-indented
-paragraph.  Default units for indents \fIi\fR are ens.
-.sp
-.LP
-Type font and size are reset to default values before each paragraph, and after
-processing font and size setting macros.
-.sp
-.LP
-These strings are predefined by \fB-man\fR:
-.sp
-.ne 2
-.na
-\fB\fB\e*R\fR\fR
-.ad
-.RS 8n
-`\(rg', `(Reg)' in \fBnroff\fR.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB\e*S\fR\fR
-.ad
-.RS 8n
-Change to default type size.
-.RE
-
-.SS "Requests"
-.sp
-.LP
-* n.t.l. = next text line; p.i. = prevailing indent
-.sp
-
-.sp
-.TS
-c c c c
-c c c c .
-\fIRequest\fR	\fICause\fR	\fIIf no\fR	\fIExplanation\fR
-	\fIBreak\fR	\fIArgument\fR	
-\fB\&.B \fR\fIt\fR	no	\fIt\fR=n.t.l.*	Text is in bold font.
-\fB\&.BI \fR\fIt\fR	no	\fIt\fR=n.t.l.	Join words, alternating bold and italic.
-\fB\&.BR \fR\fIt\fR	no	\fIt\fR=n.t.l.	Join words, alternating bold and roman.
-\fB\&.DT\fR	no	\&.5i 1i...	Restore default tabs.
-\fB\&.HP \fR\fIi\fR	yes	\fIi\fR=p.i.*	T{
-Begin paragraph with hanging indent. Set prevailing indent to \fIi\fR.
-T}
-\fB\&.I \fR\fIt\fR	no	\fIt\fR=n.t.l.	Text is italic.
-\fB\&.IB \fR\fIt\fR	no	\fIt\fR=n.t.l.	Join words, alternating italic and bold.
-\fB\&.IP \fR\fIx i\fR	yes	\fIx\fR=""	Same as \fB\&.TP\fR with tag \fIx\fR.
-\fB\&.IR \fR\fIt\fR	no	\fIt\fR=n.t.l.	T{
-Join words, alternating italic and roman.
-T}
-\fB\&.IX \fR\fIt\fR	no	-	Index macro, for SunSoft internal use.
-\fB\&.LP\fR	yes	-	T{
-Begin left-aligned paragraph. Set prevailing indent to .5i.
-T}
-\fB\&.P\fR	yes	-	Same as \fB\&.LP\fR.
-\fB\&.PD \fR\fId\fR	no	\fId\fR=.4v	T{
-Set vertical distance between paragraphs.
-T}
-\fB\&.PP\fR	yes	-	Same as \fB\&.LP\fR.
-\fB\&.RE\fR	yes	-	T{
-End of relative indent. Restores prevailing indent.
-T}
-\fB\&.RB \fR\fIt\fR	no	\fIt\fR=n.t.l.	Join words, alternating roman and bold.
-\fB\&.RI \fR\fIt\fR	no	\fIt\fR=n.t.l.	T{
-Join words, alternating roman and italic.
-T}
-\fB\&.RS \fR\fIi\fR	yes	\fIi\fR=p.i.	T{
-Start relative indent, increase indent by \fIi\fR. Sets prevailing indent to .5i for nested indents.
-T}
-\fB\&.SB \fR\fIt\fR	no	-	T{
-Reduce size of text by 1 point, make text bold.
-T}
-\fB\&.SH \fR\fIt\fR	yes	-	Section Heading.
-\fB\&.SM \fR\fIt\fR	no	\fIt\fR=n.t.l.	Reduce size of text by 1 point.
-\fB\&.SS \fR\fIt\fR	yes	\fIt\fR=n.t.l.	Section Subheading.
-\fB\&.TH \FR\FIN S "f d, m\fR"
-\fB\&.TH \fR\fIn s d f m\fR	yes	-	T{
-Begin reference page \fIn\fR, of of section \fIs\fR; \fId\fR is the date of the most recent change.  If present, \fIf\fR is the left page footer; \fIm\fR is the main page (center) header.  Sets prevailing indent and tabs to .5i.
-T}
-\fB\&.TP \fR\fIi\fR	yes	\fIi\fR=p.i.	T{
-Begin indented paragraph, with the tag given on the next text line. Set prevailing indent to \fIi\fR.
-T}
-\fB\&.TX \fR\fIt \fR\fIp\fR	no	-	T{
-Resolve the title abbreviation \fIt\fR; join to punctuation mark (or text) \fIp\fR.
-T}
-.TE
-
-.SS "Conventions"
-.sp
-.LP
-When formatting a manual page, \fBman\fR examines the first line to determine
-whether it requires special processing. For example a first line consisting of:
-.sp
-.LP
-\fB\&'\e" t\fR
-.sp
-.LP
-indicates that the manual page must be run through the \fBtbl\fR(1)
-preprocessor.
-.sp
-.LP
-A typical manual page for a command or function is laid out as follows:
-.sp
-.ne 2
-.na
-\fB\&.TH\fI TITLE \FR[1-9]\FR " , "
-.ad
-.RS 23n
-The name of the command or function, which serves as the title of the manual
-page. This is followed by the number of the section in which it appears.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH NAME\fR
-.ad
-.RS 23n
-The name, or list of names, by which the command is called, followed by a dash
-and then a one-line summary of the action performed. All in roman font, this
-section contains no \fBtroff\fR(1) commands or escapes, and no macro requests.
-It is used to generate the \fBwindex\fR database, which is used by the
-\fBwhatis\fR(1) command.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH SYNOPSIS\fR
-.ad
-.RS 23n
-.sp
-.ne 2
-.na
-\fBCommands:\fR
-.ad
-.RS 13n
-The syntax of the command and its arguments, as typed on the command line.
-When in boldface, a word must be typed exactly as printed.  When in italics, a
-word can be replaced with an argument that you supply. References to bold or
-italicized items are not capitalized in other sections, even when they begin a
-sentence.
-.sp
-Syntactic symbols appear in roman face:
-.sp
-.ne 2
-.na
-\fB[ ]\fR
-.ad
-.RS 13n
-An argument, when surrounded by brackets is optional.
-.RE
-
-.sp
-.ne 2
-.na
-\fB|\fR
-.ad
-.RS 13n
-Arguments separated by a vertical bar are exclusive. You can supply only one
-item from such a list.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.\|.\|.\fR
-.ad
-.RS 13n
-Arguments followed by an ellipsis can be repeated. When an ellipsis follows a
-bracketed set, the expression within the brackets can be repeated.
-.RE
-
-.RE
-
-.sp
-.ne 2
-.na
-\fBFunctions:\fR
-.ad
-.RS 14n
-If required, the data declaration, or \fB#include\fR directive, is shown first,
-followed by the  function declaration. Otherwise, the function declaration is
-shown.
-.RE
-
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH DESCRIPTION\fR
-.ad
-.RS 23n
-A narrative overview of the command or function's external behavior. This
-includes how it interacts with files or data, and how it handles the standard
-input, standard output and standard error. Internals and implementation details
-are normally omitted. This section attempts to provide a succinct overview in
-answer to the question, "what does it do?"
-.sp
-Literal text from the synopsis appears in constant width, as do literal
-filenames and references to items that appear elsewhere in the  reference
-manuals. Arguments are italicized.
-.sp
-If a command interprets either subcommands or an input grammar, its command
-interface or input grammar is normally described in a \fBUSAGE\fR section,
-which follows the \fBOPTIONS\fR section.  The  \fBDESCRIPTION\fR section only
-describes the behavior of the command itself, not that of subcommands.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH OPTIONS\fR
-.ad
-.RS 23n
-The list of options along with a description of how each affects the command's
-operation.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH RETURN VALUES\fR
-.ad
-.RS 23n
-A list of the values the library routine will return to the calling  program
-and the conditions that cause these values to be returned.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH EXIT STATUS\fR
-.ad
-.RS 23n
-A list of the values the utility will return to the calling  program or shell,
-and the conditions that cause these values to be  returned.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH FILES\fR
-.ad
-.RS 23n
-A list of files associated with the command or function.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH SEE ALSO\fR
-.ad
-.RS 23n
-A comma-separated list of related manual pages, followed by references to other
-published materials.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH DIAGNOSTICS\fR
-.ad
-.RS 23n
-A list of diagnostic messages and an explanation of each.
-.RE
-
-.sp
-.ne 2
-.na
-\fB\&.SH BUGS\fR
-.ad
-.RS 23n
-A description of limitations, known defects, and possible problems associated
-with the command or function.
-.RE
-
-.SH FILES
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/lib/tmac/an\fR \fR
-.ad
-.RS 27n
-
-.RE
-
-.sp
-.ne 2
-.na
-\fB\fB/usr/share/man/windex\fR\fR
-.ad
-.RS 27n
-
-.RE
-
-.SH SEE ALSO
-.sp
-.LP
-\fBman\fR(1), \fBnroff\fR(1), \fBtroff\fR(1), \fBwhatis\fR(1)
-.sp
-.LP
-Dale Dougherty and   Tim O'Reilly, \fIUnix\fR \fIText\fR \fIProcessing\fR
+.\"
+.\" 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.
+.\"
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\"
+.Dd Jan 3, 2012
+.Dt MAN 5
+.Os
+.Sh NAME
+.Nm man
+.Nd legacy formatting language for manual pages
+.Sh DESCRIPTION
+Traditionally, the
+.Nm man
+language has been used to write
+.Ux
+manuals for the
+.Xr man 1
+utility.
+It supports limited control of presentational details like fonts,
+indentation and spacing.
+This reference document describes the structure of manual pages
+and the syntax and usage of the man language.
+.Pp
+.Bf -emphasis
+Do not use
+.Nm
+to write your manuals:
+.Ef
+It lacks support for semantic markup.
+Use the
+.Xr mdoc 5
+language, instead.
+.Pp
+In a
+.Nm
+document, lines beginning with the control character
+.Sq \&.
+are called
+.Dq macro lines .
+The first word is the macro name.
+It usually consists of two capital letters.
+For a list of available macros, see
+.Sx MACRO OVERVIEW .
+The words following the macro name are arguments to the macro.
+.Pp
+Lines not beginning with the control character are called
+.Dq text lines .
+They provide free-form text to be printed; the formatting of the text
+depends on the respective processing context:
+.Bd -literal -offset indent
+\&.SH Macro lines change control state.
+Text lines are interpreted within the current state.
+.Ed
+.Pp
+Many aspects of the basic syntax of the
+.Nm
+language are based on the
+.Xr roff 5
+language; see the
+.Em LANGUAGE SYNTAX
+and
+.Em MACRO SYNTAX
+sections in the
+.Xr roff 5
+manual for details, in particular regarding
+comments, escape sequences, whitespace, and quoting.
+.Sh MANUAL STRUCTURE
+Each
+.Nm
+document must contain the
+.Sx \&TH
+macro describing the document's section and title.
+It may occur anywhere in the document, although conventionally it
+appears as the first macro.
+.Pp
+Beyond
+.Sx \&TH ,
+at least one macro or text line must appear in the document.
+.Pp
+The following is a well-formed skeleton
+.Nm
+file for a utility
+.Qq progname :
+.Bd -literal -offset indent
+\&.TH PROGNAME 1 "Oct 10, 2009"
+\&.SH NAME
+\efBprogname\efR \e(en a description goes here
+\&.\e\(dq .SH LIBRARY
+\&.\e\(dq For sections 2 & 3 only.
+\&.SH SYNOPSIS
+\efBprogname\efR [\efB\e-options\efR] arguments...
+\&.SH DESCRIPTION
+The \efBfoo\efR utility processes files...
+\&.\e\(dq .SH IMPLEMENTATION NOTES
+\&.\e\(dq .SH RETURN VALUES
+\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq .SH ENVIRONMENT
+\&.\e\(dq For sections 1, 1M, 5, & 6 only.
+\&.\e\(dq .SH FILES
+\&.\e\(dq .SH EXIT STATUS
+\&.\e\(dq For sections 1, 1M, & 6 only.
+\&.\e\(dq .SH EXAMPLES
+\&.\e\(dq .SH DIAGNOSTICS
+\&.\e\(dq For sections 1, 1M, 5, 6, & 7 only.
+\&.\e\(dq .SH ERRORS
+\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq .SH SEE ALSO
+\&.\e\(dq .BR foo ( 1 )
+\&.\e\(dq .SH STANDARDS
+\&.\e\(dq .SH HISTORY
+\&.\e\(dq .SH AUTHORS
+\&.\e\(dq .SH CAVEATS
+\&.\e\(dq .SH BUGS
+\&.\e\(dq .SH SECURITY CONSIDERATIONS
+.Ed
+.Pp
+The sections in a
+.Nm
+document are conventionally ordered as they appear above.
+Sections should be composed as follows:
+.Bl -ohang -offset indent
+.It Em NAME
+The name(s) and a short description of the documented material.
+The syntax for this is generally as follows:
+.Pp
+.D1 \efBname\efR \e(en description
+.It Em LIBRARY
+The name of the library containing the documented material, which is
+assumed to be a function in a section 2 or 3 manual.
+For functions in the C library, this may be as follows:
+.Pp
+.D1 Standard C Library (libc, -lc)
+.It Em SYNOPSIS
+Documents the utility invocation syntax, function call syntax, or device
+configuration.
+.Pp
+For the first, utilities (sections 1, 1M, and 6), this is
+generally structured as follows:
+.Pp
+.D1 \efBname\efR [-\efBab\efR] [-\efBc\efR\efIarg\efR] \efBpath\efR...
+.Pp
+For the second, function calls (sections 2, 3, 9):
+.Pp
+.D1 \&.B char *name(char *\efIarg\efR);
+.Pp
+And for the third, configurations (section 7):
+.Pp
+.D1 \&.B name* at cardbus ? function ?
+.Pp
+Manuals not in these sections generally don't need a
+.Em SYNOPSIS .
+.It Em DESCRIPTION
+This expands upon the brief, one-line description in
+.Em NAME .
+It usually contains a break-down of the options (if documenting a
+command).
+.It Em IMPLEMENTATION NOTES
+Implementation-specific notes should be kept here.
+This is useful when implementing standard functions that may have side
+effects or notable algorithmic implications.
+.It Em RETURN VALUES
+This section documents the return values of functions in sections 2, 3, and 9.
+.It Em ENVIRONMENT
+Documents any usages of environment variables, e.g.,
+.Xr environ 5 .
+.It Em FILES
+Documents files used.
+It's helpful to document both the file name and a short description of how
+the file is used (created, modified, etc.).
+.It Em EXIT STATUS
+This section documents the command exit status for
+section 1, 6, and 8 utilities.
+Historically, this information was described in
+.Em DIAGNOSTICS ,
+a practise that is now discouraged.
+.It Em EXAMPLES
+Example usages.
+This often contains snippets of well-formed,
+well-tested invocations.
+Make sure that examples work properly!
+.It Em DIAGNOSTICS
+Documents error conditions.
+This is most useful in section 4 manuals.
+Historically, this section was used in place of
+.Em EXIT STATUS
+for manuals in sections 1, 6, and 8; however, this practise is
+discouraged.
+.It Em ERRORS
+Documents error handling in sections 2, 3, and 9.
+.It Em SEE ALSO
+References other manuals with related topics.
+This section should exist for most manuals.
+.Pp
+.D1 \&.BR bar \&( 1 \&),
+.Pp
+Cross-references should conventionally be ordered
+first by section, then alphabetically.
+.It Em STANDARDS
+References any standards implemented or used, such as
+.Pp
+.D1 IEEE Std 1003.2 (\e(lqPOSIX.2\e(rq)
+.Pp
+If not adhering to any standards, the
+.Em HISTORY
+section should be used.
+.It Em HISTORY
+A brief history of the subject, including where support first appeared.
+.It Em AUTHORS
+Credits to the person or persons who wrote the code and/or documentation.
+Authors should generally be noted by both name and email address.
+.It Em CAVEATS
+Common misuses and misunderstandings should be explained
+in this section.
+.It Em BUGS
+Known bugs, limitations, and work-arounds should be described
+in this section.
+.It Em SECURITY CONSIDERATIONS
+Documents any security precautions that operators should consider.
+.El
+.Sh MACRO OVERVIEW
+This overview is sorted such that macros of similar purpose are listed
+together, to help find the best macro for any given purpose.
+Deprecated macros are not included in the overview, but can be found
+in the alphabetical reference below.
+.Ss Page header and footer meta-data
+.Bl -column "PP, LP, P" description
+.It Sx TH Ta set the title: Ar title section date Op Ar source Op Ar volume
+.It Sx AT Ta display AT&T UNIX version in the page footer (<= 1 argument)
+.It Sx UC Ta display BSD version in the page footer (<= 1 argument)
+.El
+.Ss Sections and paragraphs
+.Bl -column "PP, LP, P" description
+.It Sx SH Ta section header (one line)
+.It Sx SS Ta subsection header (one line)
+.It Sx PP , LP , P Ta start an undecorated paragraph (no arguments)
+.It Sx RS , RE Ta reset the left margin: Op Ar width
+.It Sx IP Ta indented paragraph: Op Ar head Op Ar width
+.It Sx TP Ta tagged paragraph: Op Ar width
+.It Sx HP Ta hanged paragraph: Op Ar width
+.It Sx \&br Ta force output line break in text mode (no arguments)
+.It Sx \&sp Ta force vertical space: Op Ar height
+.It Sx fi , nf Ta fill mode and no-fill mode (no arguments)
+.It Sx in Ta additional indent: Op Ar width
+.El
+.Ss Physical markup
+.Bl -column "PP, LP, P" description
+.It Sx B Ta boldface font
+.It Sx I Ta italic font
+.It Sx R Ta roman (default) font
+.It Sx SB Ta small boldface font
+.It Sx SM Ta small roman font
+.It Sx BI Ta alternate between boldface and italic fonts
+.It Sx BR Ta alternate between boldface and roman fonts
+.It Sx IB Ta alternate between italic and boldface fonts
+.It Sx IR Ta alternate between italic and roman fonts
+.It Sx RB Ta alternate between roman and boldface fonts
+.It Sx RI Ta alternate between roman and italic fonts
+.El
+.Ss Semantic markup
+.Bl -column "PP, LP, P" description
+.It Sx OP Ta optional arguments
+.El
+.Sh MACRO REFERENCE
+This section is a canonical reference to all macros, arranged
+alphabetically.
+For the scoping of individual macros, see
+.Sx MACRO SYNTAX .
+.Ss \&AT
+Sets the volume for the footer for compatibility with man pages from
+.Tn AT&T UNIX
+releases.
+The optional arguments specify which release it is from.
+.Ss \&B
+Text is rendered in bold face.
+.Pp
+See also
+.Sx \&I
+and
+.Sx \&R .
+.Ss \&BI
+Text is rendered alternately in bold face and italic.
+Thus,
+.Sq .BI this word and that
+causes
+.Sq this
+and
+.Sq and
+to render in bold face, while
+.Sq word
+and
+.Sq that
+render in italics.
+Whitespace between arguments is omitted in output.
+.Pp
+Examples:
+.Pp
+.Dl \&.BI bold italic bold italic
+.Pp
+The output of this example will be emboldened
+.Dq bold
+and italicised
+.Dq italic ,
+with spaces stripped between arguments.
+.Pp
+See also
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RB ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&BR
+Text is rendered alternately in bold face and roman (the default font).
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&RB ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&DT
+Has no effect.
+Included for compatibility.
+.Ss \&HP
+Begin a paragraph whose initial output line is left-justified, but
+subsequent output lines are indented, with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&HP
+.Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument must conform to
+.Sx Scaling Widths .
+If specified, it's saved for later paragraph left-margins; if unspecified, the
+saved or default width is used.
+.Pp
+See also
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&I
+Text is rendered in italics.
+.Pp
+See also
+.Sx \&B
+and
+.Sx \&R .
+.Ss \&IB
+Text is rendered alternately in italics and bold face.
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&BR ,
+.Sx \&RB ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&IP
+Begin an indented paragraph with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&IP
+.Op Cm head Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument defines the width of the left margin and is defined by
+.Sx Scaling Widths .
+It's saved for later paragraph left-margins; if unspecified, the saved or
+default width is used.
+.Pp
+The
+.Cm head
+argument is used as a leading term, flushed to the left margin.
+This is useful for bulleted paragraphs and so on.
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&LP ,
+.Sx \&P ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&IR
+Text is rendered alternately in italics and roman (the default font).
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RB ,
+and
+.Sx \&RI .
+.Ss \&LP
+Begin an undecorated paragraph.
+The scope of a paragraph is closed by a subsequent paragraph,
+sub-section, section, or end of file.
+The saved paragraph left-margin width is reset to the default.
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&P ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&OP
+Optional command-line argument.
+This has the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&OP
+.Cm key Op Cm value
+.Ed
+.Pp
+The
+.Cm key
+is usually a command-line flag and
+.Cm value
+its argument.
+.Ss \&P
+Synonym for
+.Sx \&LP .
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&PP ,
+and
+.Sx \&TP .
+.Ss \&PP
+Synonym for
+.Sx \&LP .
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+and
+.Sx \&TP .
+.Ss \&R
+Text is rendered in roman (the default font).
+.Pp
+See also
+.Sx \&I
+and
+.Sx \&B .
+.Ss \&RB
+Text is rendered alternately in roman (the default font) and bold face.
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RI ,
+and
+.Sx \&IR .
+.Ss \&RE
+Explicitly close out the scope of a prior
+.Sx \&RS .
+The default left margin is restored to the state of the original
+.Sx \&RS
+invocation.
+.Ss \&RI
+Text is rendered alternately in roman (the default font) and italics.
+Whitespace between arguments is omitted in output.
+.Pp
+See
+.Sx \&BI
+for an equivalent example.
+.Pp
+See also
+.Sx \&BI ,
+.Sx \&IB ,
+.Sx \&BR ,
+.Sx \&RB ,
+and
+.Sx \&IR .
+.Ss \&RS
+Temporarily reset the default left margin.
+This has the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&RS
+.Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument must conform to
+.Sx Scaling Widths .
+If not specified, the saved or default width is used.
+.Pp
+See also
+.Sx \&RE .
+.Ss \&SB
+Text is rendered in small size (one point smaller than the default font)
+bold face.
+.Ss \&SH
+Begin a section.
+The scope of a section is only closed by another section or the end of
+file.
+The paragraph left-margin width is reset to the default.
+.Ss \&SM
+Text is rendered in small size (one point smaller than the default
+font).
+.Ss \&SS
+Begin a sub-section.
+The scope of a sub-section is closed by a subsequent sub-section,
+section, or end of file.
+The paragraph left-margin width is reset to the default.
+.Ss \&TH
+Sets the title of the manual page with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&TH
+.Ar title section date
+.Op Ar source Op Ar volume
+.Ed
+.Pp
+Conventionally, the document
+.Ar title
+is given in all caps.
+The recommended
+.Ar date
+format is
+.Sy YYYY-MM-DD
+as specified in the ISO-8601 standard;
+if the argument does not conform, it is printed verbatim.
+If the
+.Ar date
+is empty or not specified, the current date is used.
+The optional
+.Ar source
+string specifies the organisation providing the utility.
+The
+.Ar volume
+string replaces the default rendered volume, which is dictated by the
+manual section.
+.Pp
+Examples:
+.Pp
+.Dl \&.TH CVS 5 "1992-02-12" GNU
+.Ss \&TP
+Begin a paragraph where the head, if exceeding the indentation width, is
+followed by a newline; if not, the body follows on the same line after a
+buffer to the indentation width.
+Subsequent output lines are indented.
+The syntax is as follows:
+.Bd -filled -offset indent
+.Pf \. Sx \&TP
+.Op Cm width
+.Ed
+.Pp
+The
+.Cm width
+argument must conform to
+.Sx Scaling Widths .
+If specified, it's saved for later paragraph left-margins; if
+unspecified, the saved or default width is used.
+.Pp
+See also
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+and
+.Sx \&PP .
+.Ss \&UC
+Sets the volume for the footer for compatibility with man pages from
+BSD releases.
+The optional first argument specifies which release it is from.
+.Ss \&br
+Breaks the current line.
+Consecutive invocations have no further effect.
+.Pp
+See also
+.Sx \&sp .
+.Ss \&fi
+End literal mode begun by
+.Sx \&nf .
+.Ss \&ft
+Change the current font mode.
+See
+.Sx Text Decoration
+for a listing of available font modes.
+.Ss \&in
+Indent relative to the current indentation:
+.Pp
+.D1 Pf \. Sx \&in Op Cm width
+.Pp
+If
+.Cm width
+is signed, the new offset is relative.
+Otherwise, it is absolute.
+This value is reset upon the next paragraph, section, or sub-section.
+.Ss \&na
+Don't align to the right margin.
+.Ss \&nf
+Begin literal mode: all subsequent free-form lines have their end of
+line boundaries preserved.
+May be ended by
+.Sx \&fi .
+Literal mode is implicitly ended by
+.Sx \&SH
+or
+.Sx \&SS .
+.Ss \&sp
+Insert vertical spaces into output with the following syntax:
+.Bd -filled -offset indent
+.Pf \. Sx \&sp
+.Op Cm height
+.Ed
+.Pp
+Insert
+.Cm height
+spaces, which must conform to
+.Sx Scaling Widths .
+If 0, this is equivalent to the
+.Sx \&br
+macro.
+Defaults to 1, if unspecified.
+.Pp
+See also
+.Sx \&br .
+.Sh MACRO SYNTAX
+The
+.Nm
+macros are classified by scope: line scope or block scope.
+Line macros are only scoped to the current line (and, in some
+situations, the subsequent line).
+Block macros are scoped to the current line and subsequent lines until
+closed by another block macro.
+.Ss Line Macros
+Line macros are generally scoped to the current line, with the body
+consisting of zero or more arguments.
+If a macro is scoped to the next line and the line arguments are empty,
+the next line, which must be text, is used instead.
+Thus:
+.Bd -literal -offset indent
+\&.I
+foo
+.Ed
+.Pp
+is equivalent to
+.Sq \&.I foo .
+If next-line macros are invoked consecutively, only the last is used.
+If a next-line macro is followed by a non-next-line macro, an error is
+raised, except for
+.Sx \&br ,
+.Sx \&sp ,
+and
+.Sx \&na .
+.Pp
+The syntax is as follows:
+.Bd -literal -offset indent
+\&.YO \(lBbody...\(rB
+\(lBbody...\(rB
+.Ed
+.Bl -column "MacroX" "ArgumentsX" "ScopeXXXXX" "CompatX" -offset indent
+.It Em Macro Ta Em Arguments Ta Em Scope     Ta Em Notes
+.It Sx \&AT  Ta    <=1       Ta    current   Ta    \&
+.It Sx \&B   Ta    n         Ta    next-line Ta    \&
+.It Sx \&BI  Ta    n         Ta    current   Ta    \&
+.It Sx \&BR  Ta    n         Ta    current   Ta    \&
+.It Sx \&DT  Ta    0         Ta    current   Ta    \&
+.It Sx \&I   Ta    n         Ta    next-line Ta    \&
+.It Sx \&IB  Ta    n         Ta    current   Ta    \&
+.It Sx \&IR  Ta    n         Ta    current   Ta    \&
+.It Sx \&OP  Ta    0, 1      Ta    current   Ta    compat
+.It Sx \&R   Ta    n         Ta    next-line Ta    \&
+.It Sx \&RB  Ta    n         Ta    current   Ta    \&
+.It Sx \&RI  Ta    n         Ta    current   Ta    \&
+.It Sx \&SB  Ta    n         Ta    next-line Ta    \&
+.It Sx \&SM  Ta    n         Ta    next-line Ta    \&
+.It Sx \&TH  Ta    >1, <6    Ta    current   Ta    \&
+.It Sx \&UC  Ta    <=1       Ta    current   Ta    \&
+.It Sx \&br  Ta    0         Ta    current   Ta    compat
+.It Sx \&fi  Ta    0         Ta    current   Ta    compat
+.It Sx \&ft  Ta    1         Ta    current   Ta    compat
+.It Sx \&in  Ta    1         Ta    current   Ta    compat
+.It Sx \&na  Ta    0         Ta    current   Ta    compat
+.It Sx \&nf  Ta    0         Ta    current   Ta    compat
+.It Sx \&sp  Ta    1         Ta    current   Ta    compat
+.El
+.Pp
+Macros marked as
+.Qq compat
+are included for compatibility with the significant corpus of existing
+manuals that mix dialects of roff.
+These macros should not be used for portable
+.Nm
+manuals.
+.Ss Block Macros
+Block macros comprise a head and body.
+As with in-line macros, the head is scoped to the current line and, in
+one circumstance, the next line (the next-line stipulations as in
+.Sx Line Macros
+apply here as well).
+.Pp
+The syntax is as follows:
+.Bd -literal -offset indent
+\&.YO \(lBhead...\(rB
+\(lBhead...\(rB
+\(lBbody...\(rB
+.Ed
+.Pp
+The closure of body scope may be to the section, where a macro is closed
+by
+.Sx \&SH ;
+sub-section, closed by a section or
+.Sx \&SS ;
+part, closed by a section, sub-section, or
+.Sx \&RE ;
+or paragraph, closed by a section, sub-section, part,
+.Sx \&HP ,
+.Sx \&IP ,
+.Sx \&LP ,
+.Sx \&P ,
+.Sx \&PP ,
+or
+.Sx \&TP .
+No closure refers to an explicit block closing macro.
+.Pp
+As a rule, block macros may not be nested; thus, calling a block macro
+while another block macro scope is open, and the open scope is not
+implicitly closed, is syntactically incorrect.
+.Bl -column "MacroX" "ArgumentsX" "Head ScopeX" "sub-sectionX" "compatX" -offset indent
+.It Em Macro Ta Em Arguments Ta Em Head Scope Ta Em Body Scope  Ta Em Notes
+.It Sx \&HP  Ta    <2        Ta    current    Ta    paragraph   Ta    \&
+.It Sx \&IP  Ta    <3        Ta    current    Ta    paragraph   Ta    \&
+.It Sx \&LP  Ta    0         Ta    current    Ta    paragraph   Ta    \&
+.It Sx \&P   Ta    0         Ta    current    Ta    paragraph   Ta    \&
+.It Sx \&PP  Ta    0         Ta    current    Ta    paragraph   Ta    \&
+.It Sx \&RE  Ta    0         Ta    current    Ta    none        Ta    compat
+.It Sx \&RS  Ta    1         Ta    current    Ta    part        Ta    compat
+.It Sx \&SH  Ta    >0        Ta    next-line  Ta    section     Ta    \&
+.It Sx \&SS  Ta    >0        Ta    next-line  Ta    sub-section Ta    \&
+.It Sx \&TP  Ta    n         Ta    next-line  Ta    paragraph   Ta    \&
+.El
+.Pp
+Macros marked
+.Qq compat
+are as mentioned in
+.Sx Line Macros .
+.Pp
+If a block macro is next-line scoped, it may only be followed by in-line
+macros for decorating text.
+.Ss Font handling
+In
+.Nm
+documents, both
+.Sx Physical markup
+macros and
+.Xr roff 5
+.Ql \ef
+font escape sequences can be used to choose fonts.
+In text lines, the effect of manual font selection by escape sequences
+only lasts until the next macro invocation; in macro lines, it only lasts
+until the end of the macro scope.
+Note that macros like
+.Sx \&BR
+open and close a font scope for each argument.
+.Sh COMPATIBILITY
+This section documents areas of questionable portability between
+implementations of the
+.Nm
+language.
+.Pp
+.Bl -dash -compact
+.It
+Do not depend on
+.Sx \&SH
+or
+.Sx \&SS
+to close out a literal context opened with
+.Sx \&nf .
+This behaviour may not be portable.
+.It
+In quoted literals, GNU troff allowed pair-wise double-quotes to produce
+a standalone double-quote in formatted output.
+It is not known whether this behaviour is exhibited by other formatters.
+.It
+troff suppresses a newline before
+.Sq \(aq
+macro output; in mandoc, it is an alias for the standard
+.Sq \&.
+control character.
+.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.
+.It
+The
+.Sq \ef
+scaling unit is accepted by mandoc, but rendered as the default unit.
+.It
+The
+.Sx \&sp
+macro does not accept negative values in mandoc.
+In GNU troff, this would result in strange behaviour.
+.It
+In page header lines, GNU troff versions up to and including 1.21
+only print
+.Ar volume
+names explicitly specified in the
+.Sx \&TH
+macro; mandoc and newer groff print the default volume name
+corresponding to the
+.Ar section
+number when no
+.Ar volume
+is given, like in
+.Xr mdoc 5 .
+.El
+.Pp
+The
+.Sx OP
+macro is part of the extended
+.Nm
+macro set, and may not be portable to non-GNU troff implementations.
+.Sh INTERFACE STABILITY
+.Nm "Obsolete Committed" .
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr mandoc 1 ,
+.Xr eqn 5 ,
+.Xr mandoc_char 5 ,
+.Xr mdoc 5 ,
+.Xr roff 5 ,
+.Xr tbl 5
+.Sh HISTORY
+The
+.Nm
+language first appeared as a macro package for the roff typesetting
+system in
+.At v7 .
+It was later rewritten by James Clark as a macro package for groff.
+Eric S. Raymond wrote the extended
+.Nm
+macros for groff in 2007.
+The stand-alone implementation that is part of the
+.Xr mandoc 1
+utility written by Kristaps Dzonsons appeared in
+.Ox 4.6 .
+.Sh AUTHORS
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons ,
+.Mt kristaps@bsd.lv .
+.Sh CAVEATS
+Do not use this language.
+Use
+.Xr mdoc 5 ,
+instead.
--- /dev/null	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man5/mandoc_char.5	Fri Jul 18 08:34:41 2014
@@ -0,0 +1,744 @@
+.\"
+.\" 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.
+.\"
+.\"
+.\" Copyright (c) 2003 Jason McIntyre <jmc@openbsd.org>
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\"
+.Dd Nov 23, 2011
+.Dt MANDOC_CHAR 5
+.Os
+.Sh NAME
+.Nm mandoc_char
+.Nd mandoc special characters
+.Sh DESCRIPTION
+This page documents the
+.Xr roff 5
+escape sequences accepted by
+.Xr mandoc 1
+to represent special characters in
+.Xr mdoc 5
+and
+.Xr man 5
+documents.
+.Pp
+The rendering depends on the
+.Xr mandoc 1
+output mode; in ASCII output, most characters are completely
+unintelligible.
+For that reason, using any of the special characters documented here,
+except those discussed in the
+.Sx DESCRIPTION ,
+is strongly discouraged; they are supported merely for backwards
+compatibility with existing documents.
+.Pp
+In particular, in English manual pages, do not use special-character
+escape sequences to represent national language characters in author
+names; instead, provide ASCII transcriptions of the names.
+.Ss Dashes and Hyphens
+In typography there are different types of dashes of various width:
+the hyphen (-),
+the minus sign (\-),
+the en-dash (\(en),
+and the em-dash (\(em).
+.Pp
+Hyphens are used for adjectives;
+to separate the two parts of a compound word;
+or to separate a word across two successive lines of text.
+The hyphen does not need to be escaped:
+.Bd -unfilled -offset indent
+blue-eyed
+lorry-driver
+.Ed
+.Pp
+The mathematical minus sign is used for negative numbers or subtraction.
+It should be written as
+.Sq \e- :
+.Bd -unfilled -offset indent
+a = 3 \e- 1;
+b = \e-2;
+.Ed
+.Pp
+The en-dash is used to separate the two elements of a range,
+or can be used the same way as an em-dash.
+It should be written as
+.Sq \e(en :
+.Bd -unfilled -offset indent
+pp. 95\e(en97.
+Go away \e(en or else!
+.Ed
+.Pp
+The em-dash can be used to show an interruption
+or can be used the same way as colons, semi-colons, or parentheses.
+It should be written as
+.Sq \e(em :
+.Bd -unfilled -offset indent
+Three things \e(em apples, oranges, and bananas.
+This is not that \e(em rather, this is that.
+.Ed
+.Pp
+Note:
+hyphens, minus signs, and en-dashes look identical under normal ASCII output.
+Other formats, such as PostScript, render them correctly,
+with differing widths.
+.Ss Spaces
+To separate words in normal text, for indenting and alignment
+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
+characters.
+To prevent a line break between two particular words,
+use the non-breaking space escape sequence
+.Pq Sq \e~
+instead of the normal space character.
+For example, the input string
+.Dq number\e~1
+will be kept together as
+.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
+.Pq Sq \e\ \&
+space escape sequence may be preferable.
+To escape macro names and to protect whitespace at the end
+of input lines, the zero-width space
+.Pq Sq \e&
+is often useful.
+For example, in
+.Xr mdoc 5 ,
+a normal space character can be displayed in single quotes in either
+of the following ways:
+.Pp
+.Dl .Sq \(dq \(dq
+.Dl .Sq \e \e&
+.Ss Quotes
+On request and macro lines, the double-quote character
+.Pq Sq \(dq
+is handled specially to allow quoting.
+One way to prevent this special handling is by using the
+.Sq \e(dq
+escape sequence.
+.Pp
+Note that on text lines, literal double-quote characters can be used
+verbatim.
+All other quote-like characters can be used verbatim as well,
+even on request and macro lines.
+.Ss Periods
+The period
+.Pq Sq \&.
+is handled specially at the beginning of an input line,
+where it introduces a
+.Xr 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
+.Pq Sq \e&.
+to make it behave like normal text.
+.Pp
+Do not use the
+.Sq \e.
+escape sequence.
+It does not prevent special handling of the period.
+.Ss Backslashes
+To include a literal backslash
+.Pq Sq \e
+into the output, use the
+.Pq Sq \ee
+escape sequence.
+.Pp
+Note that doubling it
+.Pq Sq \e\e
+is not the right way to output a backslash.
+Because
+.Xr mandoc 1
+does not implement full
+.Xr roff 5
+functionality, it may work with
+.Xr mandoc 1 ,
+but it may have weird effects on complete
+.Xr roff 5
+implementations.
+.Sh SPECIAL CHARACTERS
+Special characters are encoded as
+.Sq \eX
+.Pq for a one-character escape ,
+.Sq \e(XX
+.Pq two-character ,
+and
+.Sq \e[N]
+.Pq N-character .
+For details, see the
+.Em Special Characters
+subsection of the
+.Xr 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 \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)
+.El
+.Pp
+Lines:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.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(bb    Ta \(bb        Ta broken bar
+.It \e(sl    Ta \(sl        Ta forward slash
+.It \e(rs    Ta \(rs        Ta backward slash
+.El
+.Pp
+Text markers:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(ci    Ta \(ci        Ta circle
+.It \e(bu    Ta \(bu        Ta bullet
+.It \e(dd    Ta \(dd        Ta double dagger
+.It \e(dg    Ta \(dg        Ta dagger
+.It \e(lz    Ta \(lz        Ta lozenge
+.It \e(sq    Ta \(sq        Ta white square
+.It \e(ps    Ta \(ps        Ta paragraph
+.It \e(sc    Ta \(sc        Ta section
+.It \e(lh    Ta \(lh        Ta left hand
+.It \e(rh    Ta \(rh        Ta right hand
+.It \e(at    Ta \(at        Ta at
+.It \e(sh    Ta \(sh        Ta hash (pound)
+.It \e(CR    Ta \(CR        Ta carriage return
+.It \e(OK    Ta \(OK        Ta check mark
+.El
+.Pp
+Legal symbols:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(co    Ta \(co        Ta copyright
+.It \e(rg    Ta \(rg        Ta registered
+.It \e(tm    Ta \(tm        Ta trademarked
+.El
+.Pp
+Punctuation:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(em    Ta \(em        Ta em-dash
+.It \e(en    Ta \(en        Ta en-dash
+.It \e(hy    Ta \(hy        Ta hyphen
+.It \ee      Ta \e          Ta back-slash
+.It \e.      Ta \.          Ta period
+.It \e(r!    Ta \(r!        Ta upside-down exclamation
+.It \e(r?    Ta \(r?        Ta upside-down question
+.El
+.Pp
+Quotes:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(Bq    Ta \(Bq        Ta right low double-quote
+.It \e(bq    Ta \(bq        Ta right low single-quote
+.It \e(lq    Ta \(lq        Ta left double-quote
+.It \e(rq    Ta \(rq        Ta right double-quote
+.It \e(oq    Ta \(oq        Ta left single-quote
+.It \e(cq    Ta \(cq        Ta right single-quote
+.It \e(aq    Ta \(aq        Ta apostrophe quote (text)
+.It \e(dq    Ta \(dq        Ta double quote (text)
+.It \e(Fo    Ta \(Fo        Ta left guillemet
+.It \e(Fc    Ta \(Fc        Ta right guillemet
+.It \e(fo    Ta \(fo        Ta left single guillemet
+.It \e(fc    Ta \(fc        Ta right single guillemet
+.El
+.Pp
+Brackets:
+.Bl -column "xxbracketrightbpx" 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
+.It \e(lC    Ta \(lC        Ta left brace
+.It \e(rC    Ta \(rC        Ta right brace
+.It \e(la    Ta \(la        Ta left angle
+.It \e(ra    Ta \(ra        Ta right angle
+.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[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[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[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[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[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[parenrightex] Ta \[parenrightex] Ta right hooked parenthesis extension
+.El
+.Pp
+Arrows:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(<-    Ta \(<-        Ta left arrow
+.It \e(->    Ta \(->        Ta right arrow
+.It \e(<>    Ta \(<>        Ta left-right arrow
+.It \e(da    Ta \(da        Ta down arrow
+.It \e(ua    Ta \(ua        Ta up arrow
+.It \e(va    Ta \(va        Ta up-down arrow
+.It \e(lA    Ta \(lA        Ta left double-arrow
+.It \e(rA    Ta \(rA        Ta right double-arrow
+.It \e(hA    Ta \(hA        Ta left-right double-arrow
+.It \e(uA    Ta \(uA        Ta up double-arrow
+.It \e(dA    Ta \(dA        Ta down double-arrow
+.It \e(vA    Ta \(vA        Ta up-down double-arrow
+.El
+.Pp
+Logical:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(AN    Ta \(AN        Ta logical and
+.It \e(OR    Ta \(OR        Ta logical or
+.It \e(no    Ta \(no        Ta logical not
+.It \e[tno]  Ta \[tno]      Ta logical not (text)
+.It \e(te    Ta \(te        Ta existential quantifier
+.It \e(fa    Ta \(fa        Ta universal quantifier
+.It \e(st    Ta \(st        Ta such that
+.It \e(tf    Ta \(tf        Ta therefore
+.It \e(3d    Ta \(3d        Ta therefore
+.It \e(or    Ta \(or        Ta bitwise or
+.El
+.Pp
+Mathematical:
+.Bl -column "xxcoproductxx" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(pl    Ta \(pl        Ta plus
+.It \e(mi    Ta \(mi        Ta minus
+.It \e-      Ta \-          Ta minus (text)
+.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(mu    Ta \(mu        Ta multiply
+.It \e[tmu]  Ta \[tmu]      Ta multiply (text)
+.It \e(c*    Ta \(c*        Ta circle-multiply
+.It \e(c+    Ta \(c+        Ta circle-plus
+.It \e(di    Ta \(di        Ta divide
+.It \e[tdi]  Ta \[tdi]      Ta divide (text)
+.It \e(f/    Ta \(f/        Ta fraction
+.It \e(**    Ta \(**        Ta asterisk
+.It \e(<=    Ta \(<=        Ta less-than-equal
+.It \e(>=    Ta \(>=        Ta greater-than-equal
+.It \e(<<    Ta \(<<        Ta much less
+.It \e(>>    Ta \(>>        Ta much greater
+.It \e(eq    Ta \(eq        Ta equal
+.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(pt    Ta \(pt        Ta proportionate
+.It \e(es    Ta \(es        Ta empty set
+.It \e(mo    Ta \(mo        Ta element
+.It \e(nm    Ta \(nm        Ta not element
+.It \e(sb    Ta \(sb        Ta proper subset
+.It \e(nb    Ta \(nb        Ta not subset
+.It \e(sp    Ta \(sp        Ta proper superset
+.It \e(nc    Ta \(nc        Ta not superset
+.It \e(ib    Ta \(ib        Ta reflexive subset
+.It \e(ip    Ta \(ip        Ta reflexive superset
+.It \e(ca    Ta \(ca        Ta intersection
+.It \e(cu    Ta \(cu        Ta union
+.It \e(/_    Ta \(/_        Ta angle
+.It \e(pp    Ta \(pp        Ta perpendicular
+.It \e(is    Ta \(is        Ta integral
+.It \e[integral] Ta \[integral] Ta integral
+.It \e[sum]    Ta \[sum]   Ta summation
+.It \e[product] Ta \[product] Ta product
+.It \e[coproduct] Ta \[coproduct] Ta coproduct
+.It \e(gr    Ta \(gr        Ta gradient
+.It \e(sr    Ta \(sr        Ta square root
+.It \e[sqrt] Ta \[sqrt]     Ta square root
+.It \e(lc    Ta \(lc        Ta left-ceiling
+.It \e(rc    Ta \(rc        Ta right-ceiling
+.It \e(lf    Ta \(lf        Ta left-floor
+.It \e(rf    Ta \(rf        Ta right-floor
+.It \e(if    Ta \(if        Ta infinity
+.It \e(Ah    Ta \(Ah        Ta aleph
+.It \e(Im    Ta \(Im        Ta imaginary
+.It \e(Re    Ta \(Re        Ta real
+.It \e(pd    Ta \(pd        Ta partial differential
+.It \e(-h    Ta \(-h        Ta Planck constant over 2\(*p
+.It \e[12]   Ta \[12]       Ta one-half
+.It \e[14]   Ta \[14]       Ta one-fourth
+.It \e[34]   Ta \[34]       Ta three-fourths
+.El
+.Pp
+Ligatures:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(ff    Ta \(ff        Ta ff ligature
+.It \e(fi    Ta \(fi        Ta fi ligature
+.It \e(fl    Ta \(fl        Ta fl ligature
+.It \e(Fi    Ta \(Fi        Ta ffi ligature
+.It \e(Fl    Ta \(Fl        Ta ffl ligature
+.It \e(AE    Ta \(AE        Ta AE
+.It \e(ae    Ta \(ae        Ta ae
+.It \e(OE    Ta \(OE        Ta OE
+.It \e(oe    Ta \(oe        Ta oe
+.It \e(ss    Ta \(ss        Ta German eszett
+.It \e(IJ    Ta \(IJ        Ta IJ ligature
+.It \e(ij    Ta \(ij        Ta ij ligature
+.El
+.Pp
+Accents:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(a"    Ta \(a"        Ta Hungarian umlaut
+.It \e(a-    Ta \(a-        Ta macron
+.It \e(a.    Ta \(a.        Ta dotted
+.It \e(a^    Ta \(a^        Ta circumflex
+.It \e(aa    Ta \(aa        Ta acute
+.It \e'      Ta \'          Ta acute
+.It \e(ga    Ta \(ga        Ta grave
+.It \e`      Ta \`          Ta grave
+.It \e(ab    Ta \(ab        Ta breve
+.It \e(ac    Ta \(ac        Ta cedilla
+.It \e(ad    Ta \(ad        Ta dieresis
+.It \e(ah    Ta \(ah        Ta caron
+.It \e(ao    Ta \(ao        Ta ring
+.It \e(a~    Ta \(a~        Ta tilde
+.It \e(ho    Ta \(ho        Ta ogonek
+.It \e(ha    Ta \(ha        Ta hat (text)
+.It \e(ti    Ta \(ti        Ta tilde (text)
+.El
+.Pp
+Accented letters:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e('A    Ta \('A        Ta acute A
+.It \e('E    Ta \('E        Ta acute E
+.It \e('I    Ta \('I        Ta acute I
+.It \e('O    Ta \('O        Ta acute O
+.It \e('U    Ta \('U        Ta acute U
+.It \e('a    Ta \('a        Ta acute a
+.It \e('e    Ta \('e        Ta acute e
+.It \e('i    Ta \('i        Ta acute i
+.It \e('o    Ta \('o        Ta acute o
+.It \e('u    Ta \('u        Ta acute u
+.It \e(`A    Ta \(`A        Ta grave A
+.It \e(`E    Ta \(`E        Ta grave E
+.It \e(`I    Ta \(`I        Ta grave I
+.It \e(`O    Ta \(`O        Ta grave O
+.It \e(`U    Ta \(`U        Ta grave U
+.It \e(`a    Ta \(`a        Ta grave a
+.It \e(`e    Ta \(`e        Ta grave e
+.It \e(`i    Ta \(`i        Ta grave i
+.It \e(`o    Ta \(`i        Ta grave o
+.It \e(`u    Ta \(`u        Ta grave u
+.It \e(~A    Ta \(~A        Ta tilde A
+.It \e(~N    Ta \(~N        Ta tilde N
+.It \e(~O    Ta \(~O        Ta tilde O
+.It \e(~a    Ta \(~a        Ta tilde a
+.It \e(~n    Ta \(~n        Ta tilde n
+.It \e(~o    Ta \(~o        Ta tilde o
+.It \e(:A    Ta \(:A        Ta dieresis A
+.It \e(:E    Ta \(:E        Ta dieresis E
+.It \e(:I    Ta \(:I        Ta dieresis I
+.It \e(:O    Ta \(:O        Ta dieresis O
+.It \e(:U    Ta \(:U        Ta dieresis U
+.It \e(:a    Ta \(:a        Ta dieresis a
+.It \e(:e    Ta \(:e        Ta dieresis e
+.It \e(:i    Ta \(:i        Ta dieresis i
+.It \e(:o    Ta \(:o        Ta dieresis o
+.It \e(:u    Ta \(:u        Ta dieresis u
+.It \e(:y    Ta \(:y        Ta dieresis y
+.It \e(^A    Ta \(^A        Ta circumflex A
+.It \e(^E    Ta \(^E        Ta circumflex E
+.It \e(^I    Ta \(^I        Ta circumflex I
+.It \e(^O    Ta \(^O        Ta circumflex O
+.It \e(^U    Ta \(^U        Ta circumflex U
+.It \e(^a    Ta \(^a        Ta circumflex a
+.It \e(^e    Ta \(^e        Ta circumflex e
+.It \e(^i    Ta \(^i        Ta circumflex i
+.It \e(^o    Ta \(^o        Ta circumflex o
+.It \e(^u    Ta \(^u        Ta circumflex u
+.It \e(,C    Ta \(,C        Ta cedilla C
+.It \e(,c    Ta \(,c        Ta cedilla c
+.It \e(/L    Ta \(/L        Ta stroke L
+.It \e(/l    Ta \(/l        Ta stroke l
+.It \e(/O    Ta \(/O        Ta stroke O
+.It \e(/o    Ta \(/o        Ta stroke o
+.It \e(oA    Ta \(oA        Ta ring A
+.It \e(oa    Ta \(oa        Ta ring a
+.El
+.Pp
+Special letters:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(-D    Ta \(-D        Ta Eth
+.It \e(Sd    Ta \(Sd        Ta eth
+.It \e(TP    Ta \(TP        Ta Thorn
+.It \e(Tp    Ta \(Tp        Ta thorn
+.It \e(.i    Ta \(.i        Ta dotless i
+.It \e(.j    Ta \(.j        Ta dotless j
+.El
+.Pp
+Currency:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(Do    Ta \(Do        Ta dollar
+.It \e(ct    Ta \(ct        Ta cent
+.It \e(Eu    Ta \(Eu        Ta Euro symbol
+.It \e(eu    Ta \(eu        Ta Euro symbol
+.It \e(Ye    Ta \(Ye        Ta yen
+.It \e(Po    Ta \(Po        Ta pound
+.It \e(Cs    Ta \(Cs        Ta Scandinavian
+.It \e(Fn    Ta \(Fn        Ta florin
+.El
+.Pp
+Units:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(de    Ta \(de        Ta degree
+.It \e(%0    Ta \(%0        Ta per-thousand
+.It \e(fm    Ta \(fm        Ta minute
+.It \e(sd    Ta \(sd        Ta second
+.It \e(mc    Ta \(mc        Ta micro
+.El
+.Pp
+Greek letters:
+.Bl -column "Input" "Rendered" "Description" -offset indent -compact
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e(*A    Ta \(*A        Ta Alpha
+.It \e(*B    Ta \(*B        Ta Beta
+.It \e(*G    Ta \(*G        Ta Gamma
+.It \e(*D    Ta \(*D        Ta Delta
+.It \e(*E    Ta \(*E        Ta Epsilon
+.It \e(*Z    Ta \(*Z        Ta Zeta
+.It \e(*Y    Ta \(*Y        Ta Eta
+.It \e(*H    Ta \(*H        Ta Theta
+.It \e(*I    Ta \(*I        Ta Iota
+.It \e(*K    Ta \(*K        Ta Kappa
+.It \e(*L    Ta \(*L        Ta Lambda
+.It \e(*M    Ta \(*M        Ta Mu
+.It \e(*N    Ta \(*N        Ta Nu
+.It \e(*C    Ta \(*C        Ta Xi
+.It \e(*O    Ta \(*O        Ta Omicron
+.It \e(*P    Ta \(*P        Ta Pi
+.It \e(*R    Ta \(*R        Ta Rho
+.It \e(*S    Ta \(*S        Ta Sigma
+.It \e(*T    Ta \(*T        Ta Tau
+.It \e(*U    Ta \(*U        Ta Upsilon
+.It \e(*F    Ta \(*F        Ta Phi
+.It \e(*X    Ta \(*X        Ta Chi
+.It \e(*Q    Ta \(*Q        Ta Psi
+.It \e(*W    Ta \(*W        Ta Omega
+.It \e(*a    Ta \(*a        Ta alpha
+.It \e(*b    Ta \(*b        Ta beta
+.It \e(*g    Ta \(*g        Ta gamma
+.It \e(*d    Ta \(*d        Ta delta
+.It \e(*e    Ta \(*e        Ta epsilon
+.It \e(*z    Ta \(*z        Ta zeta
+.It \e(*y    Ta \(*y        Ta eta
+.It \e(*h    Ta \(*h        Ta theta
+.It \e(*i    Ta \(*i        Ta iota
+.It \e(*k    Ta \(*k        Ta kappa
+.It \e(*l    Ta \(*l        Ta lambda
+.It \e(*m    Ta \(*m        Ta mu
+.It \e(*n    Ta \(*n        Ta nu
+.It \e(*c    Ta \(*c        Ta xi
+.It \e(*o    Ta \(*o        Ta omicron
+.It \e(*p    Ta \(*p        Ta pi
+.It \e(*r    Ta \(*r        Ta rho
+.It \e(*s    Ta \(*s        Ta sigma
+.It \e(*t    Ta \(*t        Ta tau
+.It \e(*u    Ta \(*u        Ta upsilon
+.It \e(*f    Ta \(*f        Ta phi
+.It \e(*x    Ta \(*x        Ta chi
+.It \e(*q    Ta \(*q        Ta psi
+.It \e(*w    Ta \(*w        Ta omega
+.It \e(+h    Ta \(+h        Ta theta variant
+.It \e(+f    Ta \(+f        Ta phi variant
+.It \e(+p    Ta \(+p        Ta pi variant
+.It \e(+e    Ta \(+e        Ta epsilon variant
+.It \e(ts    Ta \(ts        Ta sigma terminal
+.El
+.Sh PREDEFINED STRINGS
+Predefined strings are inherited from the macro packages of historical
+troff implementations.
+They are
+.Em not recommended
+for use, as they differ across implementations.
+Manuals using these predefined strings are almost certainly not
+portable.
+.Pp
+Their syntax is similar to special characters, using
+.Sq \e*X
+.Pq for a one-character escape ,
+.Sq \e*(XX
+.Pq two-character ,
+and
+.Sq \e*[N]
+.Pq N-character .
+For details, see the
+.Em Predefined Strings
+subsection of the
+.Xr roff 5
+manual.
+.Bl -column "Input" "Rendered" "Description" -offset indent
+.It Em Input Ta Em Rendered Ta Em Description
+.It \e*(Ba   Ta \*(Ba       Ta vertical bar
+.It \e*(Ne   Ta \*(Ne       Ta not equal
+.It \e*(Ge   Ta \*(Ge       Ta greater-than-equal
+.It \e*(Le   Ta \*(Le       Ta less-than-equal
+.It \e*(Gt   Ta \*(Gt       Ta greater-than
+.It \e*(Lt   Ta \*(Lt       Ta less-than
+.It \e*(Pm   Ta \*(Pm       Ta plus-minus
+.It \e*(If   Ta \*(If       Ta infinity
+.It \e*(Pi   Ta \*(Pi       Ta pi
+.It \e*(Na   Ta \*(Na       Ta NaN
+.It \e*(Am   Ta \*(Am       Ta ampersand
+.It \e*R     Ta \*R         Ta restricted mark
+.It \e*(Tm   Ta \*(Tm       Ta trade mark
+.It \e*q     Ta \*q         Ta double-quote
+.It \e*(Rq   Ta \*(Rq       Ta right-double-quote
+.It \e*(Lq   Ta \*(Lq       Ta left-double-quote
+.It \e*(lp   Ta \*(lp       Ta right-parenthesis
+.It \e*(rp   Ta \*(rp       Ta left-parenthesis
+.It \e*(lq   Ta \*(lq       Ta left double-quote
+.It \e*(rq   Ta \*(rq       Ta right double-quote
+.It \e*(ua   Ta \*(ua       Ta up arrow
+.It \e*(va   Ta \*(va       Ta up-down arrow
+.It \e*(<=   Ta \*(<=       Ta less-than-equal
+.It \e*(>=   Ta \*(>=       Ta greater-than-equal
+.It \e*(aa   Ta \*(aa       Ta acute
+.It \e*(ga   Ta \*(ga       Ta grave
+.It \e*(Px   Ta \*(Px       Ta POSIX standard name
+.It \e*(Ai   Ta \*(Ai       Ta ANSI standard name
+.El
+.Sh UNICODE CHARACTERS
+The escape sequence
+.Pp
+.Dl \e[uXXXX]
+.Pp
+is interpreted as a Unicode codepoint.
+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
+greater than four characters, no zero padding is allowed.
+Unicode surrogates are not allowed.
+.\" .Pp
+.\" Unicode glyphs attenuate to the
+.\" .Sq \&?
+.\" character if invalid or not rendered by current output media.
+.Sh NUMBERED CHARACTERS
+For backward compatibility with existing manuals,
+.Xr mandoc 1
+also supports the
+.Pp
+.Dl \eN\(aq Ns Ar number Ns \(aq
+.Pp
+escape sequence, inserting the character
+.Ar number
+from the current character set into the output.
+Of course, this is inherently non-portable and is already marked
+as deprecated in the Heirloom roff manual.
+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
+troff implementations, at this time limited to GNU troff
+.Pq Qq groff .
+.Pp
+.Bl -dash -compact
+.It
+The \eN\(aq\(aq escape sequence is limited to printable characters; in
+groff, it accepts arbitrary character numbers.
+.It
+In
+.Fl T Ns Cm ascii ,
+the
+\e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product],
+\e[coproduct], \e(gr, \e(\-h, and \e(a. special characters render
+differently between mandoc and groff.
+.It
+In
+.Fl T Ns Cm html
+and
+.Fl T Ns Cm xhtml ,
+the \e(~=, \e(nb, and \e(nc special characters render differently
+between mandoc and groff.
+.It
+The
+.Fl T Ns Cm ps
+and
+.Fl T Ns Cm pdf
+modes format like
+.Fl T Ns Cm ascii
+instead of rendering glyphs as in groff.
+.It
+The \e[radicalex], \e[sqrtex], and \e(ru special characters have been omitted
+from mandoc either because they are poorly documented or they have no
+known representation.
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr man 5 ,
+.Xr mdoc 5 ,
+.Xr roff 5
+.Sh AUTHORS
+The
+.Nm
+manual page was written by
+.An Kristaps Dzonsons ,
+.Mt kristaps@bsd.lv .
+.Sh CAVEATS
+The
+.Sq \e*(Ba
+escape mimics the behaviour of the
+.Sq \&|
+character in
+.Xr mdoc 5 ;
+thus, if you wish to render a vertical bar with no side effects, use
+the
+.Sq \e(ba
+escape.
--- /dev/null	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man5/mandoc_roff.5	Fri Jul 18 08:34:41 2014
@@ -0,0 +1,991 @@
+.\"
+.\" 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.
+.\"
+.\"
+.\" Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\" Copyright 2014 Garrett D'Amore <garrett@damore.org>
+.\"
+.Dd Jul 13, 2014
+.Dt MANDOC_ROFF 5
+.Os
+.Sh NAME
+.Nm mandoc_roff
+.Nd roff language reference for mandoc
+.Sh DESCRIPTION
+The
+.Nm roff
+language is a general purpose text formatting language.
+Since traditional implementations of the
+.Xr mdoc 5
+and
+.Xr man 5
+manual formatting languages are based on it,
+many real-world manuals use small numbers of
+.Nm
+requests intermixed with their
+.Xr mdoc 5
+or
+.Xr man 5
+code.
+To properly format such manuals, the
+.Xr mandoc 1
+utility supports a tiny subset of
+.Nm
+requests.
+Only these requests supported by
+.Xr mandoc 1
+are documented in the present manual,
+together with the basic language syntax shared by
+.Nm ,
+.Xr mdoc 5 ,
+and
+.Xr man 5 .
+For complete
+.Nm
+manuals, consult the
+.Sx SEE ALSO
+section.
+.Pp
+Input lines beginning with the control character
+.Sq \&.
+are parsed for requests and macros.
+Such lines are called
+.Dq request lines
+or
+.Dq macro lines ,
+respectively.
+Requests change the processing state and manipulate the formatting;
+some macros also define the document structure and produce formatted
+output.
+The single quote
+.Pq Qq \(aq
+is accepted as an alternative control character,
+treated by
+.Xr mandoc 1
+just like
+.Ql \&.
+.Pp
+Lines not beginning with control characters are called
+.Dq text lines .
+They provide free-form text to be printed; the formatting of the text
+depends on the respective processing context.
+.Sh LANGUAGE SYNTAX
+.Nm
+documents may contain only graphable 7-bit ASCII characters, the space
+character, and, in certain circumstances, the tab character.
+The back-space character
+.Sq \e
+indicates the start of an escape sequence for
+.Sx Comments ,
+.Sx Special Characters ,
+.Sx Predefined Strings ,
+and
+user-defined strings defined using the
+.Sx ds
+request.
+.Ss Comments
+Text following an escaped double-quote
+.Sq \e\(dq ,
+whether in a request, macro, or text line, is ignored to the end of the line.
+A request line beginning with a control character and comment escape
+.Sq \&.\e\(dq
+is also ignored.
+Furthermore, request lines with only a control character and optional
+trailing whitespace are stripped from input.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.\e\(dq This is a comment line.
+\&.\e\(dq The next line is ignored:
+\&.
+\&.Sh EXAMPLES \e\(dq This is a comment, too.
+\&example text \e\(dq And so is this.
+.Ed
+.Ss Special Characters
+Special characters are used to encode special glyphs and are rendered
+differently across output media.
+They may occur in request, macro, and text lines.
+Sequences begin with the escape character
+.Sq \e
+followed by either an open-parenthesis
+.Sq \&(
+for two-character sequences; an open-bracket
+.Sq \&[
+for n-character sequences (terminated at a close-bracket
+.Sq \&] ) ;
+or a single one character sequence.
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li \e(em
+Two-letter em dash escape.
+.It Li \ee
+One-letter backslash escape.
+.El
+.Pp
+See
+.Xr mandoc_char 5
+for a complete list.
+.Ss Text Decoration
+Terms may be text-decorated using the
+.Sq \ef
+escape followed by an indicator: B (bold), I (italic), R (regular), or P
+(revert to previous mode).
+A numerical representation 3, 2, or 1 (bold, italic, and regular,
+respectively) may be used instead.
+The indicator or numerical representative may be preceded by C
+(constant-width), which is ignored.
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li \efBbold\efR
+Write in bold, then switch to regular font mode.
+.It Li \efIitalic\efP
+Write in italic, then return to previous font mode.
+.El
+.Pp
+Text decoration is
+.Em not
+recommended for
+.Xr mdoc 5 ,
+which encourages semantic annotation.
+.Ss Predefined Strings
+Predefined strings, like
+.Sx Special Characters ,
+mark special output glyphs.
+Predefined strings are escaped with the slash-asterisk,
+.Sq \e* :
+single-character
+.Sq \e*X ,
+two-character
+.Sq \e*(XX ,
+and N-character
+.Sq \e*[N] .
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li \e*(Am
+Two-letter ampersand predefined string.
+.It Li \e*q
+One-letter double-quote predefined string.
+.El
+.Pp
+Predefined strings are not recommended for use,
+as they differ across implementations.
+Those supported by
+.Xr mandoc 1
+are listed in
+.Xr mandoc_char 5 .
+Manuals using these predefined strings are almost certainly not portable.
+.Ss Whitespace
+Whitespace consists of the space character.
+In text lines, whitespace is preserved within a line.
+In request and macro lines, whitespace delimits arguments and is discarded.
+.Pp
+Unescaped trailing spaces are stripped from text line input unless in a
+literal context.
+In general, trailing whitespace on any input line is discouraged for
+reasons of portability.
+In the rare case that a blank character is needed at the end of an
+input line, it may be forced by
+.Sq \e\ \e& .
+.Pp
+Literal space characters can be produced in the output
+using escape sequences.
+In macro lines, they can also be included in arguments using quotation; see
+.Sx MACRO SYNTAX
+for details.
+.Pp
+Blank text lines, which may include whitespace, are only permitted
+within literal contexts.
+If the first character of a text line is a space, that line is printed
+with a leading newline.
+.Ss Scaling Widths
+Many requests and macros support scaled widths for their arguments.
+The syntax for a scaled width is
+.Sq Li [+-]?[0-9]*.[0-9]*[:unit:] ,
+where a decimal must be preceded or followed by at least one digit.
+Negative numbers, while accepted, are truncated to zero.
+.Pp
+The following scaling units are accepted:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It c
+centimetre
+.It i
+inch
+.It P
+pica (~1/6 inch)
+.It p
+point (~1/72 inch)
+.It f
+synonym for
+.Sq u
+.It v
+default vertical span
+.It m
+width of rendered
+.Sq m
+.Pq em
+character
+.It n
+width of rendered
+.Sq n
+.Pq en
+character
+.It u
+default horizontal span
+.It M
+mini-em (~1/100 em)
+.El
+.Pp
+Using anything other than
+.Sq m ,
+.Sq n ,
+.Sq u ,
+or
+.Sq v
+is necessarily non-portable across output media.
+See
+.Sx COMPATIBILITY .
+.Pp
+If a scaling unit is not provided, the numerical value is interpreted
+under the default rules of
+.Sq v
+for vertical spaces and
+.Sq u
+for horizontal ones.
+.Pp
+Examples:
+.Bl -tag -width ".Bl -tag -width 2i" -offset indent -compact
+.It Li \&.Bl -tag -width 2i
+two-inch tagged list indentation in
+.Xr mdoc 5
+.It Li \&.HP 2i
+two-inch tagged list indentation in
+.Xr man 5
+.It Li \&.sp 2v
+two vertical spaces
+.El
+.Ss Sentence Spacing
+Each sentence should terminate at the end of an input line.
+By doing this, a formatter will be able to apply the proper amount of
+spacing after the end of sentence (unescaped) period, exclamation mark,
+or question mark followed by zero or more non-sentence closing
+delimiters
+.Po
+.Sq \&) ,
+.Sq \&] ,
+.Sq \&' ,
+.Sq \&"
+.Pc .
+.Pp
+The proper spacing is also intelligently preserved if a sentence ends at
+the boundary of a macro line.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+Do not end sentences mid-line like this.  Instead,
+end a sentence like this.
+A macro would end like this:
+\&.Xr mandoc 1 \&.
+.Ed
+.Sh REQUEST SYNTAX
+A request or macro line consists of:
+.Pp
+.Bl -enum -compact
+.It
+the control character
+.Sq \&.
+or
+.Sq \(aq
+at the beginning of the line,
+.It
+optionally an arbitrary amount of whitespace,
+.It
+the name of the request or the macro, which is one word of arbitrary
+length, terminated by whitespace,
+.It
+and zero or more arguments delimited by whitespace.
+.El
+.Pp
+Thus, the following request lines are all equivalent:
+.Bd -literal -offset indent
+\&.ig end
+\&.ig    end
+\&.   ig end
+.Ed
+.Sh MACRO SYNTAX
+Macros are provided by the
+.Xr mdoc 5
+and
+.Xr man 5
+languages and can be defined by the
+.Sx \&de
+request.
+When called, they follow the same syntax as requests, except that
+macro arguments may optionally be quoted by enclosing them
+in double quote characters
+.Pq Sq \(dq .
+Quoted text, even if it contains whitespace or would cause
+a macro invocation when unquoted, is always considered literal text.
+Inside quoted text, pairs of double quote characters
+.Pq Sq Qq
+resolve to single double quote characters.
+.Pp
+To be recognised as the beginning of a quoted argument, the opening
+quote character must be preceded by a space character.
+A quoted argument extends to the next double quote character that is not
+part of a pair, or to the end of the input line, whichever comes earlier.
+Leaving out the terminating double quote character at the end of the line
+is discouraged.
+For clarity, if more arguments follow on the same input line,
+it is recommended to follow the terminating double quote character
+by a space character; in case the next character after the terminating
+double quote character is anything else, it is regarded as the beginning
+of the next, unquoted argument.
+.Pp
+Both in quoted and unquoted arguments, pairs of backslashes
+.Pq Sq \e\e
+resolve to single backslashes.
+In unquoted arguments, space characters can alternatively be included
+by preceding them with a backslash
+.Pq Sq \e\~ ,
+but quoting is usually better for clarity.
+.Pp
+Examples:
+.Bl -tag -width Ds -offset indent -compact
+.It Li .Fn strlen \(dqconst char *s\(dq
+Group arguments
+.Qq const char *s
+into one function argument.
+If unspecified,
+.Qq const ,
+.Qq char ,
+and
+.Qq *s
+would be considered separate arguments.
+.It Li .Op \(dqFl a\(dq
+Consider
+.Qq \&Fl a
+as literal text instead of a flag macro.
+.El
+.Sh REQUEST REFERENCE
+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 .
+.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.
+.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.
+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.
+.Ss \&de
+Define a
+.Nm
+macro.
+Its syntax can be either
+.Bd -literal -offset indent
+.Pf . Cm \&de Ar name
+.Ar macro definition
+\&..
+.Ed
+.Pp
+or
+.Bd -literal -offset indent
+.Pf . Cm \&de Ar name Ar end
+.Ar macro definition
+.Pf . Ar end
+.Ed
+.Pp
+Both forms define or redefine the macro
+.Ar name
+to represent the
+.Ar macro definition ,
+which may consist of one or more input lines, including the newline
+characters terminating each line, optionally containing calls to
+.Nm
+requests,
+.Nm
+macros or high-level macros like
+.Xr man 5
+or
+.Xr mdoc 5
+macros, whichever applies to the document in question.
+.Pp
+Specifying a custom
+.Ar end
+macro works in the same way as for
+.Sx \&ig ;
+namely, the call to
+.Sq Pf . Ar end
+first ends the
+.Ar macro definition ,
+and after that, it is also evaluated as a
+.Nm
+request or
+.Nm
+macro, but not as a high-level macro.
+.Pp
+The macro can be invoked later using the syntax
+.Pp
+.D1 Pf . Ar name Op Ar argument Op Ar argument ...
+.Pp
+Regarding argument parsing, see
+.Sx MACRO SYNTAX
+above.
+.Pp
+The line invoking the macro will be replaced
+in the input stream by the
+.Ar macro definition ,
+replacing all occurrences of
+.No \e\e$ Ns Ar N ,
+where
+.Ar N
+is a digit, by the
+.Ar N Ns th Ar argument .
+For example,
+.Bd -literal -offset indent
+\&.de ZN
+\efI\e^\e\e$1\e^\efP\e\e$2
+\&..
+\&.ZN XtFree .
+.Ed
+.Pp
+produces
+.Pp
+.D1 \efI\e^XtFree\e^\efP.
+.Pp
+in the input stream, and thus in the output: \fI\^XtFree\^\fP.
+.Pp
+Since macros and user-defined strings share a common string table,
+defining a macro
+.Ar name
+clobbers the user-defined string
+.Ar name ,
+and the
+.Ar macro definition
+can also be printed using the
+.Sq \e*
+string interpolation syntax described below
+.Sx ds ,
+but this is rarely useful because every macro definition contains at least
+one explicit newline character.
+.Pp
+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.
+.Ss \&dei
+Define a
+.Nm
+macro, specifying the macro name indirectly.
+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
+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.
+Since
+.Xr mandoc 1
+does not implement
+.Nm
+compatibility mode at all, it handles this request as an alias for
+.Sx \&de .
+.Ss \&ds
+Define a user-defined string.
+Its syntax is as follows:
+.Pp
+.D1 Pf . Cm \&ds Ar name Oo \(dq Oc Ns Ar string
+.Pp
+The
+.Ar name
+and
+.Ar string
+arguments are space-separated.
+If the
+.Ar string
+begins with a double-quote character, that character will not be part
+of the string.
+All remaining characters on the input line form the
+.Ar string ,
+including whitespace and double-quote characters, even trailing ones.
+.Pp
+The
+.Ar string
+can be interpolated into subsequent text by using
+.No \e* Ns Bq Ar name
+for a
+.Ar name
+of arbitrary length, or \e*(NN or \e*N if the length of
+.Ar name
+is two or one characters, respectively.
+Interpolation can be prevented by escaping the leading backslash;
+that is, an asterisk preceded by an even number of backslashes
+does not trigger string interpolation.
+.Pp
+Since user-defined strings and macros share a common string table,
+defining a string
+.Ar name
+clobbers the macro
+.Ar name ,
+and the
+.Ar name
+used for defining a string can also be invoked as a macro,
+in which case the following input line will be appended to the
+.Ar string ,
+forming a new input line passed to the
+.Nm
+parser.
+For example,
+.Bd -literal -offset indent
+\&.ds badidea .S
+\&.badidea
+H SYNOPSIS
+.Ed
+.Pp
+invokes the
+.Cm SH
+macro when used in a
+.Xr man 5
+document.
+Such abuse is of course strongly discouraged.
+.Ss \&el
+The
+.Qq else
+half of an if/else conditional.
+Pops a result off the stack of conditional evaluations pushed by
+.Sx \&ie
+and uses it as its conditional.
+If no stack entries are present (e.g., due to no prior
+.Sx \&ie
+calls)
+then false is assumed.
+The syntax of this request is similar to
+.Sx \&if
+except that the conditional is missing.
+.Ss \&EN
+End an equation block.
+See
+.Sx \&EQ .
+.Ss \&EQ
+Begin an equation block.
+See
+.Xr eqn 5
+for a description of the equation language.
+.Ss \&hy
+Set automatic hyphenation mode.
+This line-scoped request is currently ignored.
+.Ss \&ie
+The
+.Qq if
+half of an if/else conditional.
+The result of the conditional is pushed into a stack used by subsequent
+invocations of
+.Sx \&el ,
+which may be separated by any intervening input (or not exist at all).
+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.
+If a conditional is false, its children are not processed, but are
+syntactically interpreted to preserve the integrity of the input
+document.
+Thus,
+.Pp
+.D1 \&.if t .ig
+.Pp
+will discard the
+.Sq \&.ig ,
+which may lead to interesting results, but
+.Pp
+.D1 \&.if t .if t \e{\e
+.Pp
+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} .
+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
+brace or not, then requests and macros
+.Em must
+begin with a control character.
+It is generally more intuitive, in this case, to write
+.Bd -literal -offset indent
+\&.if COND \e{\e
+\&.foo
+bar
+\&.\e}
+.Ed
+.Pp
+than having the request or macro follow as
+.Pp
+.D1 \&.if COND \e{ .foo
+.Pp
+The scope of a conditional is always parsed, but only executed if the
+conditional evaluates to true.
+.Pp
+Note that the
+.Sq \e}
+is converted into a zero-width escape sequence if not passed as a
+standalone macro
+.Sq \&.\e} .
+For example,
+.Pp
+.D1 \&.Fl a \e} b
+.Pp
+will result in
+.Sq \e}
+being considered an argument of the
+.Sq \&Fl
+macro.
+.Ss \&ig
+Ignore input.
+Its syntax can be either
+.Bd -literal -offset indent
+.Pf . Cm \&ig
+.Ar ignored text
+\&..
+.Ed
+.Pp
+or
+.Bd -literal -offset indent
+.Pf . Cm \&ig Ar end
+.Ar ignored text
+.Pf . Ar end
+.Ed
+.Pp
+In the first case, input is ignored until a
+.Sq \&..
+request is encountered on its own line.
+In the second case, input is ignored until the specified
+.Sq Pf . Ar end
+macro is encountered.
+Do not use the escape character
+.Sq \e
+anywhere in the definition of
+.Ar end ;
+it would cause very strange behaviour.
+.Pp
+When the
+.Ar end
+macro is a roff request or a roff macro, like in
+.Pp
+.D1 \&.ig if
+.Pp
+the subsequent invocation of
+.Sx \&if
+will first terminate the
+.Ar ignored text ,
+then be invoked as usual.
+Otherwise, it only terminates the
+.Ar ignored text ,
+and arguments following it or the
+.Sq \&..
+request are discarded.
+.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.
+.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.
+.Ss \&nr
+Define 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
+.Pp
+The
+.Ar value
+may, at the moment, only be an integer.
+So far, only the following register
+.Ar name
+is recognised:
+.Bl -tag -width Ds
+.It Cm nS
+If set to a positive integer value, certain
+.Xr mdoc 5
+macros will behave in the same way as in the
+.Em SYNOPSIS
+section.
+If set to 0, these macros will behave in the same way as outside the
+.Em SYNOPSIS
+section, even when called within the
+.Em SYNOPSIS
+section itself.
+Note that starting a new
+.Xr mdoc 5
+section with the
+.Cm \&Sh
+macro will reset this register.
+.El
+.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.
+.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.
+.Ss \&so
+Include a source file.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&so Ar file
+.Pp
+The
+.Ar file
+will be read and its contents processed as input in place of the
+.Sq \&.so
+request line.
+To avoid inadvertent inclusion of unrelated files,
+.Xr mandoc 1
+only accepts relative paths not containing the strings
+.Qq ../
+and
+.Qq /.. .
+.Pp
+This request requires
+.Xr man 1
+to change to the right directory before calling
+.Xr mandoc 1 ,
+per convention to the root of the manual tree.
+Typical usage looks like:
+.Pp
+.Dl \&.so man3/Xcursor.3
+.Pp
+As the whole concept is rather fragile, the use of
+.Sx \&so
+is discouraged.
+Use
+.Xr ln 1
+instead.
+.Ss \&ta
+Set tab stops.
+This line-scoped request can take an arbitrary number of arguments.
+Currently, it is ignored including its arguments.
+.Ss \&tr
+Output character translation.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Cm \&tr Ar [ab]+
+.Pp
+Pairs of
+.Ar ab
+characters are replaced
+.Ar ( a
+for
+.Ar b ) .
+Replacement (or origin) characters may also be character escapes; thus,
+.Pp
+.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 \&TS
+Begin a table, which formats input in aligned rows and columns.
+See
+.Xr tbl 5
+for a description of the tbl language.
+.Sh COMPATIBILITY
+This section documents compatibility between mandoc and other other
+.Nm
+implementations, at this time limited to GNU troff
+.Pq Qq groff .
+The term
+.Qq historic groff
+refers to groff version 1.15.
+.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.
+.It
+The
+.Cm nS
+register is only compatible with OpenBSD's groff-1.15.
+.It
+Historic groff did not accept white-space before a custom
+.Ar end
+macro for the
+.Sx \&ig
+request.
+.It
+The
+.Sx \&if
+and family would print funny white-spaces with historic groff when
+using the next-line syntax.
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr eqn 5 ,
+.Xr man 5 ,
+.Xr mandoc_char 5 ,
+.Xr mdoc 5 ,
+.Xr tbl 5
+.Rs
+.%A Joseph F. Ossanna
+.%A Brian W. Kernighan
+.%I AT&T Bell Laboratories
+.%T Troff User's Manual
+.%R Computing Science Technical Report
+.%N 54
+.%C Murray Hill, New Jersey
+.%D 1976 and 1992
+.%U http://www.kohala.com/start/troff/cstr54.ps
+.Re
+.Rs
+.%A Joseph F. Ossanna
+.%A Brian W. Kernighan
+.%A Gunnar Ritter
+.%T Heirloom Documentation Tools Nroff/Troff User's Manual
+.%D September 17, 2007
+.%U http://heirloom.sourceforge.net/doctools/troff.pdf
+.Re
+.Sh HISTORY
+The RUNOFF typesetting system, whose input forms the basis for
+.Nm ,
+was written in MAD and FAP for the CTSS operating system by Jerome E.
+Saltzer in 1964.
+Doug McIlroy rewrote it in BCPL in 1969, renaming it
+.Nm .
+Dennis M. Ritchie rewrote McIlroy's
+.Nm
+in PDP-11 assembly for
+.At v1 ,
+Joseph F. Ossanna improved roff and renamed it nroff
+for
+.At v2 ,
+then ported nroff to C as troff, which Brian W. Kernighan released with
+.At v7 .
+In 1989, James Clarke re-implemented troff in C++, naming it groff.
+.Sh AUTHORS
+.An -nosplit
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons ,
+.Mt kristaps@bsd.lv ;
+and
+.An Ingo Schwarze ,
+.Mt schwarze@openbsd.org .
--- /dev/null	Fri Jul 18 08:34:41 2014
+++ new/usr/src/man/man5/mdoc.5	Fri Jul 18 08:34:41 2014
@@ -0,0 +1,3211 @@
+.\"
+.\" 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.
+.\"
+.\"
+.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\" Copyright 2014 Garrett D'Amore <garrett@dmaore.org>
+.\"
+.Dd Jul 16, 2014
+.Dt MDOC 5
+.Os
+.Sh NAME
+.Nm mdoc
+.Nd semantic markup language for formatting manual pages
+.Sh DESCRIPTION
+The
+.Nm mdoc
+language supports authoring of manual pages for the
+.Xr man 1
+utility by allowing semantic annotations of words, phrases,
+page sections and complete manual pages.
+Such annotations are used by formatting tools to achieve a uniform
+presentation across all manuals written in
+.Nm ,
+and to support hyperlinking if supported by the output medium.
+.Pp
+This reference document describes the structure of manual pages
+and the syntax and usage of the
+.Nm
+language.
+The reference implementation of a parsing and formatting tool is
+.Xr mandoc 1 ;
+the
+.Sx COMPATIBILITY
+section describes compatibility with other implementations.
+.Pp
+In an
+.Nm
+document, lines beginning with the control character
+.Sq \&.
+are called
+.Dq macro lines .
+The first word is the macro name.
+It consists of two or three letters.
+Most macro names begin with a capital letter.
+For a list of available macros, see
+.Sx MACRO OVERVIEW .
+The words following the macro name are arguments to the macro, optionally
+including the names of other, callable macros; see
+.Sx MACRO SYNTAX
+for details.
+.Pp
+Lines not beginning with the control character are called
+.Dq text lines .
+They provide free-form text to be printed; the formatting of the text
+depends on the respective processing context:
+.Bd -literal -offset indent
+\&.Sh Macro lines change control state.
+Text lines are interpreted within the current state.
+.Ed
+.Pp
+Many aspects of the basic syntax of the
+.Nm
+language are based on the
+.Xr roff 5
+language; see the
+.Em LANGUAGE SYNTAX
+and
+.Em MACRO SYNTAX
+sections in the
+.Xr roff 5
+manual for details, in particular regarding
+comments, escape sequences, whitespace, and quoting.
+However, using
+.Xr roff 5
+requests in
+.Nm
+documents is discouraged;
+.Xr mandoc 1
+supports some of them merely for backward compatibility.
+.Sh MANUAL STRUCTURE
+A well-formed
+.Nm
+document consists of a document prologue followed by one or more
+sections.
+.Pp
+The prologue, which consists of the
+.Sx \&Dd ,
+.Sx \&Dt ,
+and
+.Sx \&Os
+macros in that order, is required for every document.
+.Pp
+The first section (sections are denoted by
+.Sx \&Sh )
+must be the NAME section, consisting of at least one
+.Sx \&Nm
+followed by
+.Sx \&Nd .
+.Pp
+Following that, convention dictates specifying at least the
+.Em SYNOPSIS
+and
+.Em DESCRIPTION
+sections, although this varies between manual sections.
+.Pp
+The following is a well-formed skeleton
+.Nm
+file for a utility
+.Qq progname :
+.Bd -literal -offset indent
+\&.Dd Jan 1, 1970
+\&.Dt PROGNAME section
+\&.Os
+\&.Sh NAME
+\&.Nm progname
+\&.Nd one line description
+\&.\e\(dq .Sh LIBRARY
+\&.\e\(dq For sections 2, 3, & 9 only.
+\&.Sh SYNOPSIS
+\&.Nm progname
+\&.Op Fl options
+\&.Ar
+\&.Sh DESCRIPTION
+The
+\&.Nm
+utility processes files ...
+\&.\e\(dq .Sh IMPLEMENTATION NOTES
+\&.\e\(dq .Sh RETURN VALUES
+\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq .Sh ENVIRONMENT
+\&.\e\(dq For sections 1, 1M, 5, & 6 only.
+\&.\e\(dq .Sh FILES
+\&.\e\(dq .Sh EXIT STATUS
+\&.\e\(dq For sections 1, 1M, & 6 only.
+\&.\e\(dq .Sh EXAMPLES
+\&.\e\(dq .Sh DIAGNOSTICS
+\&.\e\(dq For sections 1, 1M, 5, 6, & 7 only.
+\&.\e\(dq .Sh ERRORS
+\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq .Sh ARCHITECTURE
+\&.\e\(dq .Sh CODE SET INDEPENDENCE
+\&.\e\(dq For sections 1, 1M, & 3 only.
+\&.\e\(dq .Sh INTERFACE STABILITY
+\&.\e\(dq .Sh MT-LEVEL
+\&.\e\(dq For sections 2 & 3 only.
+\&.\e\(dq .Sh SEE ALSO
+\&.\e\(dq .Xr foobar 1
+\&.\e\(dq .Sh STANDARDS
+\&.\e\(dq .Sh HISTORY
+\&.\e\(dq .Sh AUTHORS
+\&.\e\(dq .Sh CAVEATS
+\&.\e\(dq .Sh BUGS
+\&.\e\(dq .Sh SECURITY CONSIDERATIONS
+\&.\e\(dq Not used in OpenBSD.
+.Ed
+.Pp
+The sections in an
+.Nm
+document are conventionally ordered as they appear above.
+Sections should be composed as follows:
+.Bl -ohang -offset Ds
+.It Em NAME
+The name(s) and a one line description of the documented material.
+The syntax for this as follows:
+.Bd -literal -offset indent
+\&.Nm name0 ,
+\&.Nm name1 ,
+\&.Nm name2
+\&.Nd a one line description
+.Ed
+.Pp
+Multiple
+.Sq \&Nm
+names should be separated by commas.
+.Pp
+The
+.Sx \&Nm
+macro(s) must precede the
+.Sx \&Nd
+macro.
+.Pp
+See
+.Sx \&Nm
+and
+.Sx \&Nd .
+.It Em LIBRARY
+The name of the library containing the documented material, which is
+assumed to be a function in a section 2, 3, or 9 manual.
+The syntax for this is as follows:
+.Bd -literal -offset indent
+\&.Lb libarm
+.Ed
+.Pp
+See
+.Sx \&Lb .
+.It Em SYNOPSIS
+Documents the utility invocation syntax, function call syntax, or device
+configuration.
+.Pp
+For the first, utilities (sections 1, 1M, and 6), this is
+generally structured as follows:
+.Bd -literal -offset indent
+\&.Nm bar
+\&.Op Fl v
+\&.Op Fl o Ar file
+\&.Op Ar
+\&.Nm foo
+\&.Op Fl v
+\&.Op Fl o Ar file
+\&.Op Ar
+.Ed
+.Pp
+Commands should be ordered alphabetically.
+.Pp
+For the second, function calls (sections 2, 3, 9):
+.Bd -literal -offset indent
+\&.In header.h
+\&.Vt extern const char *global;
+\&.Ft "char *"
+\&.Fn foo "const char *src"
+\&.Ft "char *"
+\&.Fn bar "const char *src"
+.Ed
+.Pp
+Ordering of
+.Sx \&In ,
+.Sx \&Vt ,
+.Sx \&Fn ,
+and
+.Sx \&Fo
+macros should follow C header-file conventions.
+.Pp
+And for the third, configurations (section 7):
+.Bd -literal -offset indent
+\&.Cd \(dqit* at isa? port 0x2e\(dq
+\&.Cd \(dqit* at isa? port 0x4e\(dq
+.Ed
+.Pp
+Manuals not in these sections generally don't need a
+.Em SYNOPSIS .
+.Pp
+Some macros are displayed differently in the
+.Em SYNOPSIS
+section, particularly
+.Sx \&Nm ,
+.Sx \&Cd ,
+.Sx \&Fd ,
+.Sx \&Fn ,
+.Sx \&Fo ,
+.Sx \&In ,
+.Sx \&Vt ,
+and
+.Sx \&Ft .
+All of these macros are output on their own line.
+If two such dissimilar macros are pairwise invoked (except for
+.Sx \&Ft
+before
+.Sx \&Fo
+or
+.Sx \&Fn ) ,
+they are separated by a vertical space, unless in the case of
+.Sx \&Fo ,
+.Sx \&Fn ,
+and
+.Sx \&Ft ,
+which are always separated by vertical space.
+.Pp
+When text and macros following an
+.Sx \&Nm
+macro starting an input line span multiple output lines,
+all output lines but the first will be indented to align
+with the text immediately following the
+.Sx \&Nm
+macro, up to the next
+.Sx \&Nm ,
+.Sx \&Sh ,
+or
+.Sx \&Ss
+macro or the end of an enclosing block, whichever comes first.
+.It Em DESCRIPTION
+This begins with an expansion of the brief, one line description in
+.Em NAME :
+.Bd -literal -offset indent
+The
+\&.Nm
+utility does this, that, and the other.
+.Ed
+.Pp
+It usually follows with a breakdown of the options (if documenting a
+command), such as:
+.Bd -literal -offset indent
+The arguments are as follows:
+\&.Bl \-tag \-width Ds
+\&.It Fl v
+Print verbose information.
+\&.El
+.Ed
+.Pp
+Manuals not documenting a command won't include the above fragment.
+.Pp
+Since the
+.Em DESCRIPTION
+section usually contains most of the text of a manual, longer manuals
+often use the
+.Sx \&Ss
+macro to form subsections.
+In very long manuals, the
+.Em DESCRIPTION
+may be split into multiple sections, each started by an
+.Sx \&Sh
+macro followed by a non-standard section name, and each having
+several subsections, like in the present
+.Nm
+manual.
+.It Em IMPLEMENTATION NOTES
+Implementation-specific notes should be kept here.
+This is useful when implementing standard functions that may have side
+effects or notable algorithmic implications.
+.It Em RETURN VALUES
+This section documents the
+return values of functions in sections 2, 3, and 9.
+.Pp
+See
+.Sx \&Rv .
+.It Em ENVIRONMENT
+Lists the environment variables used by the utility,
+and explains the syntax and semantics of their values.
+The
+.Xr environ 5
+manual provides examples of typical content and formatting.
+.Pp
+See
+.Sx \&Ev .
+.It Em FILES
+Documents files used.
+It's helpful to document both the file name and a short description of how
+the file is used (created, modified, etc.).
+.Pp
+See
+.Sx \&Pa .
+.It Em EXIT STATUS
+This section documents the
+command exit status for section 1, 6, and 8 utilities.
+Historically, this information was described in
+.Em DIAGNOSTICS ,
+a practise that is now discouraged.
+.Pp
+See
+.Sx \&Ex .
+.It Em EXAMPLES
+Example usages.
+This often contains snippets of well-formed, well-tested invocations.
+Make sure that examples work properly!
+.It Em DIAGNOSTICS
+Documents error conditions.
+This is most useful in section 4 manuals.
+Historically, this section was used in place of
+.Em EXIT STATUS
+for manuals in sections 1, 6, and 8; however, this practise is
+discouraged.
+.Pp
+See
+.Sx \&Bl
+.Fl diag .
+.It Em ERRORS
+Documents error handling in sections 2, 3, and 9.
+.Pp
+See
+.Sx \&Er .
+.It Em ARCHITECTURE
+This section is usually absent, but will be present when the
+interface is specific to one or more architectures.
+.It Em CODE SET INDEPENDENCE
+Indicates whether the interface operates correctly with various different
+code sets.  True independent code sets will support not only ASCII and
+Extended UNIX Codesets (EUC), but also other multi-byte encodings such as
+UTF-8 and GB2312.
+.Pp
+Generally there will be some limitations that are fairly standard.  See
+.Xr standards 5 for more information about some of these.  Most interfaces
+should support at least UTF-8 in addition to ASCII.
+.It Em INTERFACE STABILITY
+Indicates the level of commitment to the interface. Interfaces can be described
+with in the following ways:
+.Bl -tag -width Ds
+.It Nm Standard
+Indicates that the interface is defined by one or more standards bodies.
+Generally, changes to the interface will be carefully managed to conform
+to the relevant standards.  These interfaces are generally the most suitable
+for use in portable programs.
+.It Nm Committed
+Indicates that the interface is intended to be preserved for the long-haul, and
+will rarely, if ever change, and never without notification (barring
+extraordinary and extenuating circumstances). These interfaces are
+preferred over other interfaces with the exeception of
+.Nm Standard
+interfaces.
+.It Nm Uncommitted
+Indicates that the interface may change.  Generally, changes to these interfaces
+should be infrequent, and some effort will be made to address compatibility
+considerations when changing or removing such interfaces.  However, there is
+no firm commitment to the preservation of the interface.  Most often this
+is applied to interfaces where operational experience with the interface
+is still limited and some need to change may be anticipated.
+.Pp
+Consumers should expect to revalidate any
+.Nm Uncommitted
+interfaces when crossing release boundaries.  Products intended for
+use on many releases or intended to support compatibility with future
+releases should avoid these interfaces.
+.It Nm Volatile
+The interface can change at any time for any reason. Often this relates to
+interfaces that are part of external software components that are still evolving
+rapidly.  Consumers should not expect that the interface (either binary or
+source level) will be unchanged from one release to the next.
+.It Nm Not-an-Interface
+Describes something that is specifically not intended for programmatic
+consumption.  For example, specific human-readable output, or the layout
+of graphical items on a user interface, may be described this way.  Generally
+programmatic alternatives to these will be available, and should be used
+when programmatic consumption is needed.
+.It Nm Private
+This is an internal interface.  Generally these interfaces should only be
+used within the project, and should not be used by other programs or modules.
+The interface can and will change without notice as the project needs, at
+any time.
+.Pp
+Most often, Private interfaces will lack any documentation whatsoever, and
+generally any undocumented interface can be assumed to be Private.
+.It Nm Obsolete
+The interface is not intended for use in new projects or programs, and may
+be removed at a future date.  The
+.Nm Obsolete
+word is a modifier that can
+be applied to other commitment levels. For example an
+.Nm Obsolete Committed
+interface is unlikely to be removed or changed, but nonetheless new use
+is discouraged (perhaps a better newer alternative is present).
+.El
+.It Em MT-LEVEL
+This section describes considerations for the interface when used within
+programs that use multiple threads.  More discussion of these considerations
+is made in the MT-Level section of
+.Xr attributes 5 .
+The interface can be described in the following ways.
+.Bl -tag -width Ds
+.It Nm Safe
+Indicates the interface is safe for use within multiple threads.  There
+may be additional caveats that apply, in which case those will be
+described.  Note that some interfaces have semantics which may affect
+other threads, but these should be an intrinsic part of the interface
+rather than an unexpected side effect.  For example, closing a file in
+one thread will cause that file to be closed in all threads.
+.It Nm Unsafe
+Indicates the interface is unsuitable for concurrent use within multiple
+threads.  A threaded application may still make use of the interface, but
+will be required to provide external synchronization means to ensure that
+only a single thread calls the interface at a time.
+.It Nm MT-Safe
+Indicates that the interface is not only safe for concurrent use, but is
+designed for such use.  For example, a
+.Nm Safe
+interface may make use of a global lock to provide safety, but at reduced
+internal concurrency, whereas an
+.Nm MT-Safe
+interface will be designed to be efficient even when used concurrently.
+.It Nm Async-Signal-Safe
+Indicates that the library is safe for use within a signal handler.  An
+.Nm MT-Safe
+interface can be made
+.Nm Async-Signal-Safe
+by ensuring that it blocks signals when acquiring locks.
+.It Nm Safe with Exections
+As for
+.Nm Safe
+but with specific exceptions noted.
+.It Nm MT-Safe with Exections
+As for
+.Nm MT-Safe
+but with specific exceptions noted.
+.El
+.It Em SEE ALSO
+References other manuals with related topics.
+This section should exist for most manuals.
+Cross-references should conventionally be ordered first by section, then
+alphabetically.
+.Pp
+References to other documentation concerning the topic of the manual page,
+for example authoritative books or journal articles, may also be
+provided in this section.
+.Pp
+See
+.Sx \&Rs
+and
+.Sx \&Xr .
+.It Em STANDARDS
+References any standards implemented or used.
+If not adhering to any standards, the
+.Em HISTORY
+section should be used instead.
+.Pp
+See
+.Sx \&St .
+.It Em HISTORY
+A brief history of the subject, including where it was first implemented,
+and when it was ported to or reimplemented for the operating system at hand.
+.It Em AUTHORS
+Credits to the person or persons who wrote the code and/or documentation.
+Authors should generally be noted by both name and email address.
+.Pp
+See
+.Sx \&An .
+.It Em CAVEATS
+Common misuses and misunderstandings should be explained
+in this section.
+.It Em BUGS
+Known bugs, limitations, and work-arounds should be described
+in this section.
+.It Em SECURITY CONSIDERATIONS
+Documents any security precautions that operators should consider.
+.El
+.Sh MACRO OVERVIEW
+This overview is sorted such that macros of similar purpose are listed
+together, to help find the best macro for any given purpose.
+Deprecated macros are not included in the overview, but can be found below
+in the alphabetical
+.Sx MACRO REFERENCE .
+.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 \&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)
+.El
+.Ss Sections and cross references
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Sh Ta section header (one line)
+.It Sx \&Ss Ta subsection header (one line)
+.It Sx \&Sx Ta internal cross reference to a section or subsection
+.It Sx \&Xr Ta cross reference to another manual page: Ar name section
+.It Sx \&Pp , \&Lp Ta start a text paragraph (no arguments)
+.El
+.Ss Displays and lists
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Bd , \&Ed Ta display block:
+.Fl Ar type
+.Op Fl offset Ar width
+.Op Fl compact
+.It Sx \&D1 Ta indented display (one line)
+.It Sx \&Dl Ta indented literal display (one line)
+.It Sx \&Bl , \&El Ta list block:
+.Fl Ar type
+.Op Fl width Ar val
+.Op Fl offset Ar val
+.Op Fl compact
+.It Sx \&It Ta list item (syntax depends on Fl Ar type )
+.It Sx \&Ta Ta table cell separator in Sx \&Bl Fl column No lists
+.It Sx \&Rs , \&%* , \&Re Ta bibliographic block (references)
+.El
+.Ss Spacing control
+.Bl -column "Brq, Bro, Brc" description
+.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 \&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
+.El
+.Ss Semantic markup for command line utilities:
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Nm Ta start a SYNOPSIS block with the name of a utility
+.It Sx \&Fl Ta command line options (flags) (>=0 arguments)
+.It Sx \&Cm Ta command modifier (>0 arguments)
+.It Sx \&Ar Ta command arguments (>=0 arguments)
+.It Sx \&Op , \&Oo , \&Oc Ta optional syntax elements (enclosure)
+.It Sx \&Ic Ta internal or interactive command (>0 arguments)
+.It Sx \&Ev Ta environmental variable (>0 arguments)
+.It Sx \&Pa Ta file system path (>=0 arguments)
+.El
+.Ss Semantic markup for function libraries:
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&Lb Ta function library (one argument)
+.It Sx \&In Ta include file (one argument)
+.It Sx \&Ft Ta function type (>0 arguments)
+.It Sx \&Fo , \&Fc Ta function block: Ar funcname
+.It Sx \&Fn Ta function name:
+.Op Ar functype
+.Ar funcname
+.Oo
+.Op Ar argtype
+.Ar argname
+.Oc
+.It Sx \&Fa Ta function argument (>0 arguments)
+.It Sx \&Vt Ta variable type (>0 arguments)
+.It Sx \&Va Ta variable name (>0 arguments)
+.It Sx \&Dv Ta defined variable or preprocessor constant (>0 arguments)
+.It Sx \&Er Ta error constant (>0 arguments)
+.It Sx \&Ev Ta environmental variable (>0 arguments)
+.El
+.Ss Various semantic markup:
+.Bl -column "Brq, Bro, Brc" description
+.It Sx \&An Ta author name (>0 arguments)
+.It Sx \&Lk Ta hyperlink: Ar uri Op Ar name
+.It Sx \&Mt Ta Do mailto Dc hyperlink: Ar address
+.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
+.It Sx \&Em Ta italic font or underline (emphasis) (>0 arguments)
+.It Sx \&Sy Ta boldface font (symbolic) (>0 arguments)
+.It Sx \&Li Ta typewriter font (literal) (>0 arguments)
+.It Sx \&No Ta return to roman font (normal) (no arguments)
+.It Sx \&Bf , \&Ef Ta font block:
+.Op Fl Ar type | Cm \&Em | \&Li | \&Sy
+.El
+.Ss Physical enclosures
+.Bl -column "Brq, Bro, Brc" description
+.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
+.It Sx \&Aq , \&Ao , \&Ac Ta enclose in angle brackets: Aq text
+.It Sx \&Eo , \&Ec Ta generic enclosure
+.El
+.Ss Text production
+.Bl -column "Brq, Bro, Brc" description
+.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
+.It Sx \&Nx Ta Nx
+.It Sx \&Fx Ta Fx
+.It Sx \&Ox Ta Ox
+.It Sx \&Dx Ta Dx
+.El
+.Sh MACRO REFERENCE
+This section is a canonical reference of all macros, arranged
+alphabetically.
+For the scoping of individual macros, see
+.Sx MACRO SYNTAX .
+.Ss \&%A
+Author name of an
+.Sx \&Rs
+block.
+Multiple authors should each be accorded their own
+.Sx \%%A
+line.
+Author names should be ordered with full or abbreviated forename(s)
+first, then full surname.
+.Ss \&%B
+Book title of an
+.Sx \&Rs
+block.
+This macro may also be used in a non-bibliographic context when
+referring to book titles.
+.Ss \&%C
+Publication city or location of an
+.Sx \&Rs
+block.
+.Ss \&%D
+Publication date of an
+.Sx \&Rs
+block.
+Recommended formats of arguments are
+.Ar month day , year
+or just
+.Ar year .
+.Ss \&%I
+Publisher or issuer name of an
+.Sx \&Rs
+block.
+.Ss \&%J
+Journal name of an
+.Sx \&Rs
+block.
+.Ss \&%N
+Issue number (usually for journals) of an
+.Sx \&Rs
+block.
+.Ss \&%O
+Optional information of an
+.Sx \&Rs
+block.
+.Ss \&%P
+Book or journal page number of an
+.Sx \&Rs
+block.
+.Ss \&%Q
+Institutional author (school, government, etc.) of an
+.Sx \&Rs
+block.
+Multiple institutional authors should each be accorded their own
+.Sx \&%Q
+line.
+.Ss \&%R
+Technical report name of an
+.Sx \&Rs
+block.
+.Ss \&%T
+Article title of an
+.Sx \&Rs
+block.
+This macro may also be used in a non-bibliographical context when
+referring to article titles.
+.Ss \&%U
+URI of reference document.
+.Ss \&%V
+Volume number of an
+.Sx \&Rs
+block.
+.Ss \&Ac
+Close an
+.Sx \&Ao
+block.
+Does not have any tail arguments.
+.Ss \&Ad
+Memory address.
+Do not use this for postal addresses.
+.Pp
+Examples:
+.Dl \&.Ad [0,$]
+.Dl \&.Ad 0x00000000
+.Ss \&An
+Author name.
+Can be used both for the authors of the program, function, or driver
+documented in the manual, or for the authors of the manual itself.
+Requires either the name of an author or one of the following arguments:
+.Pp
+.Bl -tag -width "-nosplitX" -offset indent -compact
+.It Fl split
+Start a new output line before each subsequent invocation of
+.Sx \&An .
+.It Fl nosplit
+The opposite of
+.Fl split .
+.El
+.Pp
+The default is
+.Fl nosplit .
+The effect of selecting either of the
+.Fl split
+modes ends at the beginning of the
+.Em AUTHORS
+section.
+In the
+.Em AUTHORS
+section, the default is
+.Fl nosplit
+for the first author listing and
+.Fl split
+for all other author listings.
+.Pp
+Examples:
+.Dl \&.An -nosplit
+.Dl \&.An Kristaps Dzonsons \&Aq kristaps@bsd.lv
+.Ss \&Ao
+Begin a block enclosed by angle brackets.
+Does not have any head arguments.
+.Pp
+Examples:
+.Dl \&.Fl -key= \&Ns \&Ao \&Ar val \&Ac
+.Pp
+See also
+.Sx \&Aq .
+.Ss \&Ap
+Inserts an apostrophe without any surrounding whitespace.
+This is generally used as a grammatical device when referring to the verb
+form of a function.
+.Pp
+Examples:
+.Dl \&.Fn execve \&Ap d
+.Ss \&Aq
+Encloses its arguments in angle brackets.
+.Pp
+Examples:
+.Dl \&.Fl -key= \&Ns \&Aq \&Ar val
+.Pp
+.Em Remarks :
+this macro is often abused for rendering URIs, which should instead use
+.Sx \&Lk
+or
+.Sx \&Mt ,
+or to note pre-processor
+.Dq Li #include
+statements, which should use
+.Sx \&In .
+.Pp
+See also
+.Sx \&Ao .
+.Ss \&Ar
+Command arguments.
+If an argument is not provided, the string
+.Dq file ...\&
+is used as a default.
+.Pp
+Examples:
+.Dl ".Fl o Ar file"
+.Dl ".Ar"
+.Dl ".Ar arg1 , arg2 ."
+.Pp
+The arguments to the
+.Sx \&Ar
+macro are names and placeholders for command arguments;
+for fixed strings to be passed verbatim as arguments, use
+.Sx \&Fl
+or
+.Sx \&Cm .
+.Ss \&At
+Formats an AT&T version.
+Accepts one optional argument:
+.Pp
+.Bl -tag -width "v[1-7] | 32vX" -offset indent -compact
+.It Cm v[1-7] | 32v
+A version of
+.At .
+.It Cm III
+.At III .
+.It Cm V[.[1-4]]?
+A version of
+.At V .
+.El
+.Pp
+Note that these arguments do not begin with a hyphen.
+.Pp
+Examples:
+.Dl \&.At
+.Dl \&.At III
+.Dl \&.At V.1
+.Pp
+See also
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+.Sx \&Ox ,
+and
+.Sx \&Ux .
+.Ss \&Bc
+Close a
+.Sx \&Bo
+block.
+Does not have any tail arguments.
+.Ss \&Bd
+Begin a display block.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Bd
+.Fl Ns Ar type
+.Op Fl offset Ar width
+.Op Fl compact
+.Ed
+.Pp
+Display blocks are used to select a different indentation and
+justification than the one used by the surrounding text.
+They may contain both macro lines and text lines.
+By default, a display block is preceded by a vertical space.
+.Pp
+The
+.Ar type
+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.
+Using this display type is not recommended; many
+.Nm
+implementations render it poorly.
+.It Fl filled
+Change the positions of line breaks to fill each line, and left- and
+right-justify the resulting block.
+.It Fl literal
+Produce one output line from each input line,
+and do not justify the block at all.
+Preserve white space as it appears in the input.
+Always use a constant-width font.
+Use this for displaying source code.
+.It Fl ragged
+Change the positions of line breaks to fill each line, and left-justify
+the resulting block.
+.It Fl unfilled
+The same as
+.Fl literal ,
+but using the same font as for normal text, which is a variable width font
+if supported by the output device.
+.El
+.Pp
+The
+.Ar type
+must be provided first.
+Additional arguments may follow:
+.Bl -tag -width 13n -offset indent
+.It Fl offset Ar width
+Indent the display by the
+.Ar width ,
+which may be one of the following:
+.Bl -item
+.It
+One of the pre-defined strings
+.Cm indent ,
+the width of a standard indentation (six constant width characters);
+.Cm indent-two ,
+twice
+.Cm indent ;
+.Cm left ,
+which has no effect;
+.Cm right ,
+which justifies to the right margin; or
+.Cm center ,
+which aligns around an imagined centre axis.
+.It
+A macro invocation, which selects a predefined width
+associated with that macro.
+The most popular is the imaginary macro
+.Ar \&Ds ,
+which resolves to
+.Sy 6n .
+.It
+A width using the syntax described in
+.Sx Scaling Widths .
+.It
+An arbitrary string, which indents by the length of this string.
+.El
+.Pp
+When the argument is missing,
+.Fl offset
+is ignored.
+.It Fl compact
+Do not assert vertical space before the display.
+.El
+.Pp
+Examples:
+.Bd -literal -offset indent
+\&.Bd \-literal \-offset indent \-compact
+   Hello       world.
+\&.Ed
+.Ed
+.Pp
+See also
+.Sx \&D1
+and
+.Sx \&Dl .
+.Ss \&Bf
+Change the font mode for a scoped block of text.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Bf
+.Oo
+.Fl emphasis | literal | symbolic |
+.Cm \&Em | \&Li | \&Sy
+.Oc
+.Ed
+.Pp
+The
+.Fl emphasis
+and
+.Cm \&Em
+argument are equivalent, as are
+.Fl symbolic
+and
+.Cm \&Sy ,
+and
+.Fl literal
+and
+.Cm \&Li .
+Without an argument, this macro does nothing.
+The font mode continues until broken by a new font mode in a nested
+scope or
+.Sx \&Ef
+is encountered.
+.Pp
+See also
+.Sx \&Li ,
+.Sx \&Ef ,
+.Sx \&Em ,
+and
+.Sx \&Sy .
+.Ss \&Bk
+For each macro, keep its output together on the same output line,
+until the end of the macro or the end of the input line is reached,
+whichever comes first.
+Line breaks in text lines are unaffected.
+The syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Bk Fl words
+.Pp
+The
+.Fl words
+argument is required; additional arguments are ignored.
+.Pp
+The following example will not break within each
+.Sx \&Op
+macro line:
+.Bd -literal -offset indent
+\&.Bk \-words
+\&.Op Fl f Ar flags
+\&.Op Fl o Ar output
+\&.Ek
+.Ed
+.Pp
+Be careful in using over-long lines within a keep block!
+Doing so will clobber the right margin.
+.Ss \&Bl
+Begin a list.
+Lists consist of items specified using the
+.Sx \&It
+macro, containing a head or a body or both.
+The list syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Bl
+.Fl Ns Ar type
+.Op Fl width Ar val
+.Op Fl offset Ar val
+.Op Fl compact
+.Op HEAD ...
+.Ed
+.Pp
+The list
+.Ar type
+is mandatory and must be specified first.
+The
+.Fl width
+and
+.Fl offset
+arguments accept
+.Sx Scaling Widths
+or use the length of the given string.
+The
+.Fl offset
+is a global indentation for the whole list, affecting both item heads
+and bodies.
+For those list types supporting it, the
+.Fl width
+argument requests an additional indentation of item bodies,
+to be added to the
+.Fl offset .
+Unless the
+.Fl compact
+argument is specified, list entries are separated by vertical space.
+.Pp
+A list must specify one of the following list types:
+.Bl -tag -width 12n -offset indent
+.It Fl bullet
+No item heads can be specified, but a bullet will be printed at the head
+of each item.
+Item bodies start on the same output line as the bullet
+and are indented according to the
+.Fl width
+argument.
+.It Fl column
+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.
+If the first line of the body of a
+.Fl column
+list is not an
+.Sx \&It
+macro line,
+.Sx \&It
+contexts spanning one input line each are implied until an
+.Sx \&It
+macro line is encountered, at which point items start being interpreted as
+described in the
+.Sx \&It
+documentation.
+.It Fl dash
+Like
+.Fl bullet ,
+except that dashes are used in place of bullets.
+.It Fl diag
+Like
+.Fl inset ,
+except that item heads are not parsed for macro invocations.
+Most often used in the
+.Em DIAGNOSTICS
+section with error constants in the item heads.
+.It Fl enum
+A numbered list.
+No item heads can be specified.
+Formatted like
+.Fl bullet ,
+except that cardinal numbers are used in place of bullets,
+starting at 1.
+.It Fl hang
+Like
+.Fl tag ,
+except that the first lines of item bodies are not indented, but follow
+the item heads like in
+.Fl inset
+lists.
+.It Fl hyphen
+Synonym for
+.Fl dash .
+.It Fl inset
+Item bodies follow items heads on the same line, using normal inter-word
+spacing.
+Bodies are not indented, and the
+.Fl width
+argument is ignored.
+.It Fl item
+No item heads can be specified, and none are printed.
+Bodies are not indented, and the
+.Fl width
+argument is ignored.
+.It Fl ohang
+Item bodies start on the line following item heads and are not indented.
+The
+.Fl width
+argument is ignored.
+.It Fl tag
+Item bodies are indented according to the
+.Fl width
+argument.
+When an item head fits inside the indentation, the item body follows
+this head on the same output line.
+Otherwise, the body starts on the output line following the head.
+.El
+.Pp
+Lists may be nested within lists and displays.
+Nesting of
+.Fl column
+and
+.Fl enum
+lists may not be portable.
+.Pp
+See also
+.Sx \&El
+and
+.Sx \&It .
+.Ss \&Bo
+Begin a block enclosed by square brackets.
+Does not have any head arguments.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Bo 1 ,
+\&.Dv BUFSIZ \&Bc
+.Ed
+.Pp
+See also
+.Sx \&Bq .
+.Ss \&Bq
+Encloses its arguments in square brackets.
+.Pp
+Examples:
+.Dl \&.Bq 1 , \&Dv BUFSIZ
+.Pp
+.Em Remarks :
+this macro is sometimes abused to emulate optional arguments for
+commands; the correct macros to use for this purpose are
+.Sx \&Op ,
+.Sx \&Oo ,
+and
+.Sx \&Oc .
+.Pp
+See also
+.Sx \&Bo .
+.Ss \&Brc
+Close a
+.Sx \&Bro
+block.
+Does not have any tail arguments.
+.Ss \&Bro
+Begin a block enclosed by curly braces.
+Does not have any head arguments.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Bro 1 , ... ,
+\&.Va n \&Brc
+.Ed
+.Pp
+See also
+.Sx \&Brq .
+.Ss \&Brq
+Encloses its arguments in curly braces.
+.Pp
+Examples:
+.Dl \&.Brq 1 , ... , \&Va n
+.Pp
+See also
+.Sx \&Bro .
+.Ss \&Bsx
+Format the BSD/OS version provided as an argument, or a default value if
+no argument is provided.
+.Pp
+Examples:
+.Dl \&.Bsx 1.0
+.Dl \&.Bsx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+.Sx \&Ox ,
+and
+.Sx \&Ux .
+.Ss \&Bt
+Prints
+.Dq is currently in beta test.
+.Ss \&Bx
+Format the BSD version provided as an argument, or a default value if no
+argument is provided.
+.Pp
+Examples:
+.Dl \&.Bx 4.3 Tahoe
+.Dl \&.Bx 4.4
+.Dl \&.Bx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+.Sx \&Ox ,
+and
+.Sx \&Ux .
+.Ss \&Cd
+Kernel configuration declaration.
+This denotes strings accepted by
+.Xr config 8 .
+It is most often used in section 4 manual pages.
+.Pp
+Examples:
+.Dl \&.Cd device le0 at scode?
+.Pp
+.Em Remarks :
+this macro is commonly abused by using quoted literals to retain
+whitespace and align consecutive
+.Sx \&Cd
+declarations.
+This practise is discouraged.
+.Ss \&Cm
+Command modifiers.
+Typically used for fixed strings passed as arguments, unless
+.Sx \&Fl
+is more appropriate.
+Also useful when specifying configuration options or keys.
+.Pp
+Examples:
+.Dl ".Nm mt Fl f Ar device Cm rewind"
+.Dl ".Nm ps Fl o Cm pid , Ns Cm command"
+.Dl ".Nm dd Cm if= Ns Ar file1 Cm of= Ns Ar file2"
+.Dl ".Cm IdentityFile Pa ~/.ssh/id_rsa"
+.Dl ".Cm LogLevel Dv DEBUG"
+.Ss \&D1
+One-line indented display.
+This is formatted by the default rules and is useful for simple indented
+statements.
+It is followed by a newline.
+.Pp
+Examples:
+.Dl \&.D1 \&Fl abcdefgh
+.Pp
+See also
+.Sx \&Bd
+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 .
+.Ss \&Dc
+Close a
+.Sx \&Do
+block.
+Does not have any tail arguments.
+.Ss \&Dd
+Document date.
+This is the mandatory first macro of any
+.Nm
+manual.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Dd Ar month day , year
+.Pp
+The
+.Ar month
+is the full English month name, the
+.Ar day
+is an optionally zero-padded numeral, and the
+.Ar year
+is the full four-digit year.
+.Pp
+Other arguments are not portable; the
+.Xr mandoc 1
+utility handles them as follows:
+.Bl -dash -offset 3n -compact
+.It
+To have the date automatically filled in by the
+.Ox
+version of
+.Xr cvs 1 ,
+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.
+.It
+If a date string cannot be parsed, it is used verbatim.
+.It
+If no date string is given, the current date is used.
+.El
+.Pp
+Examples:
+.Dl \&.Dd $\&Mdocdate$
+.Dl \&.Dd $\&Mdocdate: July 21 2007$
+.Dl \&.Dd July 21, 2007
+.Pp
+See also
+.Sx \&Dt
+and
+.Sx \&Os .
+.Ss \&Dl
+One-line intended display.
+This is formatted as literal text and is useful for commands and
+invocations.
+It is followed by a newline.
+.Pp
+Examples:
+.Dl \&.Dl % mandoc mdoc.5 \e(ba less
+.Pp
+See also
+.Sx \&Bd
+and
+.Sx \&D1 .
+.Ss \&Do
+Begin a block enclosed by double quotes.
+Does not have any head arguments.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Do
+April is the cruellest month
+\&.Dc
+\e(em T.S. Eliot
+.Ed
+.Pp
+See also
+.Sx \&Dq .
+.Ss \&Dq
+Encloses its arguments in
+.Dq typographic
+double-quotes.
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Dq April is the cruellest month
+\e(em T.S. Eliot
+.Ed
+.Pp
+See also
+.Sx \&Qq ,
+.Sx \&Sq ,
+and
+.Sx \&Do .
+.Ss \&Dt
+Document title.
+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 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
+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
+if unspecified.
+.It Ar volume
+This overrides the volume inferred from
+.Ar section .
+This field is optional.
+.It Ar arch
+This specifies the machine architecture a manual page applies to,
+where relevant.
+.El
+.Ss \&Dv
+Defined variables such as preprocessor constants, constant symbols,
+enumeration values, and so on.
+.Pp
+Examples:
+.Dl \&.Dv NULL
+.Dl \&.Dv BUFSIZ
+.Dl \&.Dv STDOUT_FILENO
+.Pp
+See also
+.Sx \&Er
+and
+.Sx \&Ev
+for special-purpose constants and
+.Sx \&Va
+for variable symbols.
+.Ss \&Dx
+Format the DragonFly BSD version provided as an argument, or a default
+value if no argument is provided.
+.Pp
+Examples:
+.Dl \&.Dx 2.4.1
+.Dl \&.Dx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+.Sx \&Ox ,
+and
+.Sx \&Ux .
+.Ss \&Ec
+Close a scope started by
+.Sx \&Eo .
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ec Op Ar TERM
+.Pp
+The
+.Ar TERM
+argument is used as the enclosure tail, for example, specifying \e(rq
+will emulate
+.Sx \&Dc .
+.Ss \&Ed
+End a display context started by
+.Sx \&Bd .
+.Ss \&Ef
+End a font mode context started by
+.Sx \&Bf .
+.Ss \&Ek
+End a keep context started by
+.Sx \&Bk .
+.Ss \&El
+End a list context started by
+.Sx \&Bl .
+.Pp
+See also
+.Sx \&Bl
+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.
+.Pp
+Examples:
+.Dl \&.Em Warnings!
+.Dl \&.Em Remarks :
+.Pp
+See also
+.Sx \&Bf ,
+.Sx \&Li ,
+.Sx \&No ,
+and
+.Sx \&Sy .
+.Ss \&En
+This macro is obsolete and not implemented in
+.Xr mandoc 1 .
+.Ss \&Eo
+An arbitrary enclosure.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Eo Op Ar TERM
+.Pp
+The
+.Ar TERM
+argument is used as the enclosure head, for example, specifying \e(lq
+will emulate
+.Sx \&Do .
+.Ss \&Er
+Error constants for definitions of the
+.Va errno
+libc global variable.
+This is most often used in section 2 and 3 manual pages.
+.Pp
+Examples:
+.Dl \&.Er EPERM
+.Dl \&.Er ENOENT
+.Pp
+See also
+.Sx \&Dv
+for general constants.
+.Ss \&Es
+This macro is obsolete and not implemented.
+.Ss \&Ev
+Environmental variables such as those specified in
+.Xr environ 5 .
+.Pp
+Examples:
+.Dl \&.Ev DISPLAY
+.Dl \&.Ev PATH
+.Pp
+See also
+.Sx \&Dv
+for general constants.
+.Ss \&Ex
+Insert a standard sentence regarding command exit values of 0 on success
+and >0 on failure.
+This is most often used in section 1, 6, and 8 manual pages.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ex Fl std Op Ar utility ...
+.Pp
+If
+.Ar utility
+is not specified, the document's name set by
+.Sx \&Nm
+is used.
+Multiple
+.Ar utility
+arguments are treated as separate utilities.
+.Pp
+See also
+.Sx \&Rv .
+.Ss \&Fa
+Function argument.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Fa
+.Op Cm argtype
+.Cm argname
+.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.
+Most often, the
+.Sx \&Fa
+macro is used in the
+.Em SYNOPSIS
+within
+.Sx \&Fo
+section 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
+.Sx \&Fa ,
+the last argument will also have a trailing comma.
+.Pp
+Examples:
+.Dl \&.Fa \(dqconst char *p\(dq
+.Dl \&.Fa \(dqint a\(dq \(dqint b\(dq \(dqint c\(dq
+.Dl \&.Fa foo
+.Pp
+See also
+.Sx \&Fo .
+.Ss \&Fc
+End a function context started by
+.Sx \&Fo .
+.Ss \&Fd
+Historically used to document include files.
+This usage has been deprecated in favour of
+.Sx \&In .
+Do not use this macro.
+.Pp
+See also
+.Sx MANUAL STRUCTURE
+and
+.Sx \&In .
+.Ss \&Fl
+Command-line flag or option.
+Used when listing arguments to command-line utilities.
+Prints a fixed-width hyphen
+.Sq \-
+directly followed by each argument.
+If no arguments are provided, a hyphen is printed followed by a space.
+If the argument is a macro, a hyphen is prefixed to the subsequent macro
+output.
+.Pp
+Examples:
+.Dl ".Fl R Op Fl H | L | P"
+.Dl ".Op Fl 1AaCcdFfgHhikLlmnopqRrSsTtux"
+.Dl ".Fl type Cm d Fl name Pa CVS"
+.Dl ".Fl Ar signal_number"
+.Dl ".Fl o Fl"
+.Pp
+See also
+.Sx \&Cm .
+.Ss \&Fn
+A function name.
+Its syntax is as follows:
+.Bd -ragged -offset indent
+.Pf \. Ns Sx \&Fn
+.Op Ar functype
+.Ar funcname
+.Op Oo Ar argtype Oc Ar argname
+.Ed
+.Pp
+Function arguments are surrounded in parenthesis and
+are delimited by commas.
+If no arguments are specified, blank parenthesis are output.
+In the
+.Em SYNOPSIS
+section, this macro starts a new output line,
+and a blank line is automatically inserted between function definitions.
+.Pp
+Examples:
+.Dl \&.Fn \(dqint funcname\(dq \(dqint arg0\(dq \(dqint arg1\(dq
+.Dl \&.Fn funcname \(dqint arg0\(dq
+.Dl \&.Fn funcname arg0
+.Pp
+.Bd -literal -offset indent -compact
+\&.Ft functype
+\&.Fn funcname
+.Ed
+.Pp
+When referring to a function documented in another manual page, use
+.Sx \&Xr
+instead.
+See also
+.Sx MANUAL STRUCTURE ,
+.Sx \&Fo ,
+and
+.Sx \&Ft .
+.Ss \&Fo
+Begin a function block.
+This is a multi-line version of
+.Sx \&Fn .
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Fo Ar funcname
+.Pp
+Invocations usually occur in the following context:
+.Bd -ragged -offset indent
+.Pf \. Sx \&Ft Ar functype
+.br
+.Pf \. Sx \&Fo Ar funcname
+.br
+.Pf \. Sx \&Fa Oo Ar argtype Oc Ar argname
+.br
+\&.\.\.
+.br
+.Pf \. Sx \&Fc
+.Ed
+.Pp
+A
+.Sx \&Fo
+scope is closed by
+.Sx \&Fc .
+.Pp
+See also
+.Sx MANUAL STRUCTURE ,
+.Sx \&Fa ,
+.Sx \&Fc ,
+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:
+.Pp
+.Dl Pf . Sx \&Fr Ar value
+.Ss \&Ft
+A function type.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ft Ar functype
+.Pp
+In the
+.Em SYNOPSIS
+section, a new output line is started after this macro.
+.Pp
+Examples:
+.Dl \&.Ft int
+.Bd -literal -offset indent -compact
+\&.Ft functype
+\&.Fn funcname
+.Ed
+.Pp
+See also
+.Sx MANUAL STRUCTURE ,
+.Sx \&Fn ,
+and
+.Sx \&Fo .
+.Ss \&Fx
+Format the
+.Fx
+version provided as an argument, or a default value
+if no argument is provided.
+.Pp
+Examples:
+.Dl \&.Fx 7.1
+.Dl \&.Fx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Nx ,
+.Sx \&Ox ,
+and
+.Sx \&Ux .
+.Ss \&Hf
+This macro is not implemented in
+.Xr mandoc 1 .
+.Pp
+It was used to include the contents of a (header) file literally.
+The syntax was:
+.Pp
+.Dl Pf . Sx \&Hf Ar filename
+.Ss \&Ic
+Designate an internal or interactive command.
+This is similar to
+.Sx \&Cm
+but used for instructions rather than values.
+.Pp
+Examples:
+.Dl \&.Ic :wq
+.Dl \&.Ic hash
+.Dl \&.Ic alias
+.Pp
+Note that using
+.Sx \&Bd Fl literal
+or
+.Sx \&D1
+is preferred for displaying code; the
+.Sx \&Ic
+macro is used when referring to specific instructions.
+.Ss \&In
+An
+.Dq include
+file.
+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 ,
+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.
+.Pp
+Examples:
+.Dl \&.In sys/types.h
+.Pp
+See also
+.Sx MANUAL STRUCTURE .
+.Ss \&It
+A list item.
+The syntax of this macro depends on the list type.
+.Pp
+Lists
+of type
+.Fl hang ,
+.Fl ohang ,
+.Fl inset ,
+and
+.Fl diag
+have the following syntax:
+.Pp
+.D1 Pf \. Sx \&It Ar args
+.Pp
+Lists of type
+.Fl bullet ,
+.Fl dash ,
+.Fl enum ,
+.Fl hyphen
+and
+.Fl item
+have the following syntax:
+.Pp
+.D1 Pf \. Sx \&It
+.Pp
+with subsequent lines interpreted within the scope of the
+.Sx \&It
+until either a closing
+.Sx \&El
+or another
+.Sx \&It .
+.Pp
+The
+.Fl tag
+list has the following syntax:
+.Pp
+.D1 Pf \. Sx \&It Op Cm args
+.Pp
+Subsequent lines are interpreted as with
+.Fl bullet
+and family.
+The line arguments correspond to the list's left-hand side; body
+arguments correspond to the list's contents.
+.Pp
+The
+.Fl column
+list is the most complicated.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&It Ar cell Op <TAB> Ar cell ...
+.D1 Pf \. Sx \&It Ar cell Op Sx \&Ta Ar cell ...
+.Pp
+The arguments consist of one or more lines of text and macros
+representing a complete table line.
+Cells within the line are delimited by tabs or by the special
+.Sx \&Ta
+block macro.
+The tab cell delimiter may only be used within the
+.Sx \&It
+line itself; on following lines, only the
+.Sx \&Ta
+macro can be used to delimit cells, and
+.Sx \&Ta
+is only recognised as a macro when called by other macros,
+not as the first macro on a line.
+.Pp
+Note that quoted strings may span tab-delimited cells on an
+.Sx \&It
+line.
+For example,
+.Pp
+.Dl .It \(dqcol1 ; <TAB> col2 ;\(dq \&;
+.Pp
+will preserve the semicolon whitespace except for the last.
+.Pp
+See also
+.Sx \&Bl .
+.Ss \&Lb
+Specify a library.
+The syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Lb Ar library
+.Pp
+The
+.Ar library
+parameter may be a system library, such as
+.Cm libz
+or
+.Cm libpam ,
+in which case a small library description is printed next to the linker
+invocation; or a custom library, in which case the library name is
+printed in quotes.
+This is most commonly used in the
+.Em SYNOPSIS
+section as described in
+.Sx MANUAL STRUCTURE .
+.Pp
+Examples:
+.Dl \&.Lb libz
+.Dl \&.Lb mdoc
+.Ss \&Li
+Denotes text that should be in a
+.Li literal
+font mode.
+Note that this is a presentation term and should not be used for
+stylistically decorating technical terms.
+.Pp
+On terminal output devices, this is often indistinguishable from
+normal text.
+.Pp
+See also
+.Sx \&Bf ,
+.Sx \&Em ,
+.Sx \&No ,
+and
+.Sx \&Sy .
+.Ss \&Lk
+Format a hyperlink.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Lk Ar uri Op Ar name
+.Pp
+Examples:
+.Dl \&.Lk http://bsd.lv \(dqThe BSD.lv Project\(dq
+.Dl \&.Lk http://bsd.lv
+.Pp
+See also
+.Sx \&Mt .
+.Ss \&Lp
+Synonym for
+.Sx \&Pp .
+.Ss \&Ms
+Display a mathematical symbol.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Ms Ar symbol
+.Pp
+Examples:
+.Dl \&.Ms sigma
+.Dl \&.Ms aleph
+.Ss \&Mt
+Format a
+.Dq mailto:
+hyperlink.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Mt Ar address
+.Pp
+Examples:
+.Dl \&.Mt discuss@manpages.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.
+.Pp
+Examples:
+.Dl Pf . Sx \&Nd mdoc language reference
+.Dl Pf . Sx \&Nd format and display UNIX manuals
+.Pp
+The
+.Sx \&Nd
+macro technically accepts child macros and terminates with a subsequent
+.Sx \&Sh
+invocation.
+Do not assume this behaviour: some
+.Xr whatis 1
+database generators are not smart enough to parse more than the line
+arguments and will display macros verbatim.
+.Pp
+See also
+.Sx \&Nm .
+.Ss \&Nm
+The name of the manual page, or \(em in particular in section 1, 6,
+and 8 pages \(em of an additional command or feature documented in
+the manual page.
+When first invoked, the
+.Sx \&Nm
+macro expects a single argument, the name of the manual page.
+Usually, the first invocation happens in the
+.Em NAME
+section of the page.
+The specified name will be remembered and used whenever the macro is
+called again without arguments later in the page.
+The
+.Sx \&Nm
+macro uses
+.Sx Block full-implicit
+semantics when invoked as the first macro on an input line in the
+.Em SYNOPSIS
+section; otherwise, it uses ordinary
+.Sx In-line
+semantics.
+.Pp
+Examples:
+.Bd -literal -offset indent
+\&.Sh SYNOPSIS
+\&.Nm cat
+\&.Op Fl benstuv
+\&.Op Ar
+.Ed
+.Pp
+In the
+.Em SYNOPSIS
+of section 2, 3 and 9 manual pages, use the
+.Sx \&Fn
+macro rather than
+.Sx \&Nm
+to mark up the name of the manual page.
+.Ss \&No
+Normal text.
+Closes the scope of any preceding in-line macro.
+When used after physical formatting macros like
+.Sx \&Em
+or
+.Sx \&Sy ,
+switches back to the standard font face and weight.
+Can also be used to embed plain text strings in macro lines
+using semantic annotation macros.
+.Pp
+Examples:
+.Dl ".Em italic , Sy bold , No and roman"
+.Pp
+.Bd -literal -offset indent -compact
+\&.Sm off
+\&.Cm :C No / Ar pattern No / Ar replacement No /
+\&.Sm on
+.Ed
+.Pp
+See also
+.Sx \&Em ,
+.Sx \&Li ,
+and
+.Sx \&Sy .
+.Ss \&Ns
+Suppress a space between the output of the preceding macro
+and the following text or macro.
+Following invocation, input is interpreted as normal text
+just like after an
+.Sx \&No
+macro.
+.Pp
+This has no effect when invoked at the start of a macro line.
+.Pp
+Examples:
+.Dl ".Ar name Ns = Ns Ar value"
+.Dl ".Cm :M Ns Ar pattern"
+.Dl ".Fl o Ns Ar output"
+.Pp
+See also
+.Sx \&No
+and
+.Sx \&Sm .
+.Ss \&Nx
+Format the
+.Nx
+version provided as an argument, or a default value if
+no argument is provided.
+.Pp
+Examples:
+.Dl \&.Nx 5.01
+.Dl \&.Nx
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Ox ,
+and
+.Sx \&Ux .
+.Ss \&Oc
+Close multi-line
+.Sx \&Oo
+context.
+.Ss \&Oo
+Multi-line version of
+.Sx \&Op .
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Oo
+\&.Op Fl flag Ns Ar value
+\&.Oc
+.Ed
+.Ss \&Op
+Optional part of a command line.
+Prints the argument(s) in brackets.
+This is most often used in the
+.Em SYNOPSIS
+section of section 1 and 8 manual pages.
+.Pp
+Examples:
+.Dl \&.Op \&Fl a \&Ar b
+.Dl \&.Op \&Ar a | b
+.Pp
+See also
+.Sx \&Oo .
+.Ss \&Os
+Document operating system version.
+This is the mandatory third macro of
+any
+.Nm
+file.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Os Op Ar system Op Ar version
+.Pp
+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.
+.Pp
+Examples:
+.Dl \&.Os
+.Dl \&.Os KTH/CSC/TCS
+.Dl \&.Os BSD 4.3
+.Pp
+See also
+.Sx \&Dd
+and
+.Sx \&Dt .
+.Ss \&Ot
+This macro is obsolete and not implemented in
+.Xr mandoc 1 .
+.Pp
+Historical
+.Xr mdoc 5
+packages described it as
+.Dq "old function type (FORTRAN)" .
+.Ss \&Ox
+Format the
+.Ox
+version provided as an argument, or a default value
+if no argument is provided.
+.Pp
+Examples:
+.Dl \&.Ox 4.5
+.Dl \&.Ox
+.Pp
+See also
+.Sx \&At ,
+.Sx \&Bsx ,
+.Sx \&Bx ,
+.Sx \&Dx ,
+.Sx \&Fx ,
+.Sx \&Nx ,
+and
+.Sx \&Ux .
+.Ss \&Pa
+An absolute or relative file system path, or a file or directory name.
+If an argument is not provided, the character
+.Sq \(ti
+is used as a default.
+.Pp
+Examples:
+.Dl \&.Pa /usr/bin/mandoc
+.Dl \&.Pa /usr/share/man/man5/mdoc.5
+.Pp
+See also
+.Sx \&Lk .
+.Ss \&Pc
+Close parenthesised context opened by
+.Sx \&Po .
+.Ss \&Pf
+Removes the space between its argument
+.Pq Dq prefix
+and the following macro.
+Its syntax is as follows:
+.Pp
+.D1 .Pf Ar prefix macro arguments ...
+.Pp
+This is equivalent to:
+.Pp
+.D1 .No Ar prefix No \&Ns Ar macro arguments ...
+.Pp
+Examples:
+.Dl ".Pf $ Ar variable_name"
+.Dl ".Pf 0x Ar hex_digits"
+.Pp
+See also
+.Sx \&Ns
+and
+.Sx \&Sm .
+.Ss \&Po
+Multi-line version of
+.Sx \&Pq .
+.Ss \&Pp
+Break a paragraph.
+This will assert vertical space between prior and subsequent macros
+and/or text.
+.Pp
+Paragraph breaks are not needed before or after
+.Sx \&Sh
+or
+.Sx \&Ss
+macros or before displays
+.Pq Sx \&Bd
+or lists
+.Pq Sx \&Bl
+unless the
+.Fl compact
+flag is given.
+.Ss \&Pq
+Parenthesised enclosure.
+.Pp
+See also
+.Sx \&Po .
+.Ss \&Qc
+Close quoted context opened by
+.Sx \&Qo .
+.Ss \&Ql
+Format a single-quoted literal.
+See also
+.Sx \&Qq
+and
+.Sx \&Sq .
+.Ss \&Qo
+Multi-line version of
+.Sx \&Qq .
+.Ss \&Qq
+Encloses its arguments in
+.Qq typewriter
+double-quotes.
+Consider using
+.Sx \&Dq .
+.Pp
+See also
+.Sx \&Dq ,
+.Sx \&Sq ,
+and
+.Sx \&Qo .
+.Ss \&Re
+Close an
+.Sx \&Rs
+block.
+Does not have any tail arguments.
+.Ss \&Rs
+Begin a bibliographic
+.Pq Dq reference
+block.
+Does not have any head arguments.
+The block macro may only contain
+.Sx \&%A ,
+.Sx \&%B ,
+.Sx \&%C ,
+.Sx \&%D ,
+.Sx \&%I ,
+.Sx \&%J ,
+.Sx \&%N ,
+.Sx \&%O ,
+.Sx \&%P ,
+.Sx \&%Q ,
+.Sx \&%R ,
+.Sx \&%T ,
+.Sx \&%U ,
+and
+.Sx \&%V
+child macros (at least one must be specified).
+.Pp
+Examples:
+.Bd -literal -offset indent -compact
+\&.Rs
+\&.%A J. E. Hopcroft
+\&.%A J. D. Ullman
+\&.%B Introduction to Automata Theory, Languages, and Computation
+\&.%I Addison-Wesley
+\&.%C Reading, Massachusettes
+\&.%D 1979
+\&.Re
+.Ed
+.Pp
+If an
+.Sx \&Rs
+block is used within a SEE ALSO section, a vertical space is asserted
+before the rendered output, else the block continues on the current
+line.
+.Ss \&Rv
+Insert a standard sentence regarding a function call's return value of 0
+on success and \-1 on error, with the
+.Va errno
+libc global variable set on error.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Rv Fl std Op Ar function ...
+.Pp
+If
+.Ar function
+is not specified, the document's name set by
+.Sx \&Nm
+is used.
+Multiple
+.Ar function
+arguments are treated as separate functions.
+.Pp
+See also
+.Sx \&Ex .
+.Ss \&Sc
+Close single-quoted context opened by
+.Sx \&So .
+.Ss \&Sh
+Begin a new section.
+For a list of conventional manual sections, see
+.Sx MANUAL STRUCTURE .
+These sections should be used unless it's absolutely necessary that
+custom sections be used.
+.Pp
+Section names should be unique so that they may be keyed by
+.Sx \&Sx .
+Although this macro is parsed, it should not consist of child node or it
+may not be linked with
+.Sx \&Sx .
+.Pp
+See also
+.Sx \&Pp ,
+.Sx \&Ss ,
+and
+.Sx \&Sx .
+.Ss \&Sm
+Switches the spacing mode for output generated from macros.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Sm Cm on | off
+.Pp
+By default, spacing is
+.Cm on .
+When switched
+.Cm off ,
+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.
+.Ss \&So
+Multi-line version of
+.Sx \&Sq .
+.Ss \&Sq
+Encloses its arguments in
+.Sq typewriter
+single-quotes.
+.Pp
+See also
+.Sx \&Dq ,
+.Sx \&Qq ,
+and
+.Sx \&So .
+.Ss \&Ss
+Begin a new subsection.
+Unlike with
+.Sx \&Sh ,
+there is no convention for the naming of subsections.
+Except
+.Em DESCRIPTION ,
+the conventional sections described in
+.Sx MANUAL STRUCTURE
+rarely have subsections.
+.Pp
+Sub-section names should be unique so that they may be keyed by
+.Sx \&Sx .
+Although this macro is parsed, it should not consist of child node or it
+may not be linked with
+.Sx \&Sx .
+.Pp
+See also
+.Sx \&Pp ,
+.Sx \&Sh ,
+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
+.It \-isoC
+.St -isoC
+.It \-isoC-90
+.St -isoC-90
+.It \-isoC-amd1
+.St -isoC-amd1
+.It \-isoC-tcor1
+.St -isoC-tcor1
+.It \-isoC-tcor2
+.St -isoC-tcor2
+.It \-isoC-99
+.St -isoC-99
+.It \-isoC-2011
+.St -isoC-2011
+.It \-iso9945-1-90
+.St -iso9945-1-90
+.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
+.It \-xpg3
+.St -xpg3
+.It \-xpg4
+.St -xpg4
+.It \-xpg4.2
+.St -xpg4.2
+.It \-xpg4.3
+.St -xpg4.3
+.It \-xbd5
+.St -xbd5
+.It \-xcu5
+.St -xcu5
+.It \-xsh5
+.St -xsh5
+.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
+.It \-susv3
+.St -susv3
+.It \-svid4
+.St -svid4
+.El
+.Ss \&Sx
+Reference a section or subsection in the same manual page.
+The referenced section or subsection name must be identical to the
+enclosed argument, including whitespace.
+.Pp
+Examples:
+.Dl \&.Sx MANUAL STRUCTURE
+.Pp
+See also
+.Sx \&Sh
+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.
+.Pp
+See also
+.Sx \&Bf ,
+.Sx \&Em ,
+.Sx \&Li ,
+and
+.Sx \&No .
+.Ss \&Ta
+Table cell separator in
+.Sx \&Bl Fl column
+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
+.Ss \&Ud
+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 .
+.Ss \&Va
+A variable name.
+.Pp
+Examples:
+.Dl \&.Va foo
+.Dl \&.Va const char *bar ;
+.Ss \&Vt
+A variable type.
+This is also used for indicating global variables in the
+.Em SYNOPSIS
+section, in which case a variable name is also specified.
+Note that it accepts
+.Sx Block partial-implicit
+syntax when invoked as the first macro on an input line in the
+.Em SYNOPSIS
+section, else it accepts ordinary
+.Sx In-line
+syntax.
+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
+See also
+.Sx MANUAL STRUCTURE
+and
+.Sx \&Va .
+.Ss \&Xc
+Close a scope opened by
+.Sx \&Xo .
+.Ss \&Xo
+Extend the header of an
+.Sx \&It
+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 .
+.Ss \&Xr
+Link to another manual
+.Pq Qq cross-reference .
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&Xr Ar name section
+.Pp
+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.
+.Pp
+Examples:
+.Dl \&.Xr mandoc 1
+.Dl \&.Xr mandoc 1 \&;
+.Dl \&.Xr mandoc 1 \&Ns s behaviour
+.Ss \&br
+Emits a line-break.
+This macro should not be used; it is implemented for compatibility with
+historical manuals.
+.Pp
+Consider using
+.Sx \&Pp
+in the event of natural paragraph breaks.
+.Ss \&sp
+Emits vertical space.
+This macro should not be used; it is implemented for compatibility with
+historical manuals.
+Its syntax is as follows:
+.Pp
+.D1 Pf \. Sx \&sp Op Ar height
+.Pp
+The
+.Ar height
+argument must be formatted as described in
+.Sx Scaling Widths .
+If unspecified,
+.Sx \&sp
+asserts a single vertical space.
+.Sh MACRO SYNTAX
+The syntax of a macro depends on its classification.
+In this section,
+.Sq \-arg
+refers to macro arguments, which may be followed by zero or more
+.Sq parm
+parameters;
+.Sq \&Yo
+opens the scope of a macro; and if specified,
+.Sq \&Yc
+closes it out.
+.Pp
+The
+.Em Callable
+column indicates that the macro may also be called by passing its name
+as an argument to another macro.
+For example,
+.Sq \&.Op \&Fl O \&Ar file
+produces
+.Sq Op Fl O Ar file .
+To prevent a macro call and render the macro name literally,
+escape it by prepending a zero-width space,
+.Sq \e& .
+For example,
+.Sq \&Op \e&Fl O
+produces
+.Sq Op \&Fl O .
+If a macro is not callable but its name appears as an argument
+to another macro, it is interpreted as opaque text.
+For example,
+.Sq \&.Fl \&Sh
+produces
+.Sq Fl \&Sh .
+.Pp
+The
+.Em Parsed
+column indicates whether the macro may call other macros by receiving
+their names as arguments.
+If a macro is not parsed but the name of another macro appears
+as an argument, it is interpreted as opaque text.
+.Pp
+The
+.Em Scope
+column, if applicable, describes closure rules.
+.Ss Block full-explicit
+Multi-line scope closed by an explicit closing macro.
+All macros contains bodies; only
+.Sx \&Bf
+and
+.Pq optionally
+.Sx \&Bl
+contain a head.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB
+\(lBbody...\(rB
+\&.Yc
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&Bd  Ta    \&No     Ta    \&No     Ta    closed by Sx \&Ed
+.It Sx \&Bf  Ta    \&No     Ta    \&No     Ta    closed by Sx \&Ef
+.It Sx \&Bk  Ta    \&No     Ta    \&No     Ta    closed by Sx \&Ek
+.It Sx \&Bl  Ta    \&No     Ta    \&No     Ta    closed by Sx \&El
+.It Sx \&Ed  Ta    \&No     Ta    \&No     Ta    opened by Sx \&Bd
+.It Sx \&Ef  Ta    \&No     Ta    \&No     Ta    opened by Sx \&Bf
+.It Sx \&Ek  Ta    \&No     Ta    \&No     Ta    opened by Sx \&Bk
+.It Sx \&El  Ta    \&No     Ta    \&No     Ta    opened by Sx \&Bl
+.El
+.Ss Block full-implicit
+Multi-line scope closed by end-of-file or implicitly by another macro.
+All macros have bodies; some
+.Po
+.Sx \&It Fl bullet ,
+.Fl hyphen ,
+.Fl dash ,
+.Fl enum ,
+.Fl item
+.Pc
+don't have heads; only one
+.Po
+.Sx \&It
+in
+.Sx \&Bl Fl column
+.Pc
+has multiple heads.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB
+\(lBbody...\(rB
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXXXXXXXXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&It Ta \&No Ta Yes  Ta closed by Sx \&It , Sx \&El
+.It Sx \&Nd Ta \&No Ta \&No Ta closed by Sx \&Sh
+.It Sx \&Nm Ta \&No Ta Yes  Ta closed by Sx \&Nm , Sx \&Sh , Sx \&Ss
+.It Sx \&Sh Ta \&No Ta Yes  Ta closed by Sx \&Sh
+.It Sx \&Ss Ta \&No Ta Yes  Ta closed by Sx \&Sh , Sx \&Ss
+.El
+.Pp
+Note that the
+.Sx \&Nm
+macro is a
+.Sx Block full-implicit
+macro only when invoked as the first macro
+in a
+.Em SYNOPSIS
+section line, else it is
+.Sx In-line .
+.Ss Block partial-explicit
+Like block full-explicit, but also with single-line scope.
+Each has at least a body and, in limited circumstances, a head
+.Po
+.Sx \&Fo ,
+.Sx \&Eo
+.Pc
+and/or tail
+.Pq Sx \&Ec .
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB
+\(lBbody...\(rB
+\&.Yc \(lBtail...\(rB
+
+\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \
+\(lBbody...\(rB \&Yc \(lBtail...\(rB
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&Ac  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Ao
+.It Sx \&Ao  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Ac
+.It Sx \&Bc  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Bo
+.It Sx \&Bo  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Bc
+.It Sx \&Brc Ta    Yes      Ta    Yes      Ta    opened by Sx \&Bro
+.It Sx \&Bro Ta    Yes      Ta    Yes      Ta    closed by Sx \&Brc
+.It Sx \&Dc  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Do
+.It Sx \&Do  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Dc
+.It Sx \&Ec  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Eo
+.It Sx \&Eo  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Ec
+.It Sx \&Fc  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Fo
+.It Sx \&Fo  Ta    \&No     Ta    \&No     Ta    closed by Sx \&Fc
+.It Sx \&Oc  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Oo
+.It Sx \&Oo  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Oc
+.It Sx \&Pc  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Po
+.It Sx \&Po  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Pc
+.It Sx \&Qc  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Oo
+.It Sx \&Qo  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Oc
+.It Sx \&Re  Ta    \&No     Ta    \&No     Ta    opened by Sx \&Rs
+.It Sx \&Rs  Ta    \&No     Ta    \&No     Ta    closed by Sx \&Re
+.It Sx \&Sc  Ta    Yes      Ta    Yes      Ta    opened by Sx \&So
+.It Sx \&So  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Sc
+.It Sx \&Xc  Ta    Yes      Ta    Yes      Ta    opened by Sx \&Xo
+.It Sx \&Xo  Ta    Yes      Ta    Yes      Ta    closed by Sx \&Xc
+.El
+.Ss Block partial-implicit
+Like block full-implicit, but with single-line scope closed by the
+end of the line.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed
+.It Sx \&Aq  Ta    Yes      Ta    Yes
+.It Sx \&Bq  Ta    Yes      Ta    Yes
+.It Sx \&Brq Ta    Yes      Ta    Yes
+.It Sx \&D1  Ta    \&No     Ta    \&Yes
+.It Sx \&Dl  Ta    \&No     Ta    Yes
+.It Sx \&Dq  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
+.It Sx \&Qq  Ta    Yes      Ta    Yes
+.It Sx \&Sq  Ta    Yes      Ta    Yes
+.It Sx \&Vt  Ta    Yes      Ta    Yes
+.El
+.Pp
+Note that the
+.Sx \&Vt
+macro is a
+.Sx Block partial-implicit
+only when invoked as the first macro
+in a
+.Em SYNOPSIS
+section line, else it is
+.Sx In-line .
+.Ss Special block macro
+The
+.Sx \&Ta
+macro can only be used below
+.Sx \&It
+in
+.Sx \&Bl Fl column
+lists.
+It delimits blocks representing table cells;
+these blocks have bodies, but no heads.
+.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope
+.It Sx \&Ta  Ta    Yes      Ta    Yes    Ta closed by Sx \&Ta , Sx \&It
+.El
+.Ss In-line
+Closed by the end of the line, fixed argument lengths,
+and/or subsequent macros.
+In-line macros have only text children.
+If a number (or inequality) of arguments is
+.Pq n ,
+then the macro accepts an arbitrary number of arguments.
+.Bd -literal -offset indent
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB \(lBres...\(rB
+
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB Yc...
+
+\&.Yo \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN
+.Ed
+.Bl -column "MacroX" "CallableX" "ParsedX" "Arguments" -offset indent
+.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Arguments
+.It Sx \&%A  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%B  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%C  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%D  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%I  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%J  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%N  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%O  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%P  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%Q  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%R  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%T  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%U  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&%V  Ta    \&No     Ta    \&No     Ta    >0
+.It Sx \&Ad  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&An  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Ap  Ta    Yes      Ta    Yes      Ta    0
+.It Sx \&Ar  Ta    Yes      Ta    Yes      Ta    n
+.It Sx \&At  Ta    Yes      Ta    Yes      Ta    1
+.It Sx \&Bsx Ta    Yes      Ta    Yes      Ta    n
+.It Sx \&Bt  Ta    \&No     Ta    \&No     Ta    0
+.It Sx \&Bx  Ta    Yes      Ta    Yes      Ta    n
+.It Sx \&Cd  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Cm  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Db  Ta    \&No     Ta    \&No     Ta    1
+.It Sx \&Dd  Ta    \&No     Ta    \&No     Ta    n
+.It Sx \&Dt  Ta    \&No     Ta    \&No     Ta    n
+.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 \&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 \&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
+.It Sx \&Ic  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&In  Ta    \&No     Ta    \&No     Ta    1
+.It Sx \&Lb  Ta    \&No     Ta    \&No     Ta    1
+.It Sx \&Li  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Lk  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Lp  Ta    \&No     Ta    \&No     Ta    0
+.It Sx \&Ms  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Mt  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Nm  Ta    Yes      Ta    Yes      Ta    n
+.It Sx \&No  Ta    Yes      Ta    Yes      Ta    0
+.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 \&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 \&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
+.It Sx \&Tn  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Ud  Ta    \&No     Ta    \&No     Ta    0
+.It Sx \&Ux  Ta    Yes      Ta    Yes      Ta    n
+.It Sx \&Va  Ta    Yes      Ta    Yes      Ta    n
+.It Sx \&Vt  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Xr  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&br  Ta    \&No     Ta    \&No     Ta    0
+.It Sx \&sp  Ta    \&No     Ta    \&No     Ta    1
+.El
+.Ss Delimiters
+When a macro argument consists of one single input character
+considered as a delimiter, the argument gets special handling.
+This does not apply when delimiters appear in arguments containing
+more than one character.
+Consequently, to prevent special handling and just handle it
+like any other argument, a delimiter can be escaped by prepending
+a zero-width space
+.Pq Sq \e& .
+In text lines, delimiters never need escaping, but may be used
+as normal punctuation.
+.Pp
+For many macros, when the leading arguments are opening delimiters,
+these delimiters are put before the macro scope,
+and when the trailing arguments are closing delimiters,
+these delimiters are put after the macro scope.
+For example,
+.Pp
+.D1 Pf \. \&Aq "( [ word ] ) ."
+.Pp
+renders as:
+.Pp
+.D1 Aq ( [ word ] ) .
+.Pp
+Opening delimiters are:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It \&(
+left parenthesis
+.It \&[
+left bracket
+.El
+.Pp
+Closing delimiters are:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It \&.
+period
+.It \&,
+comma
+.It \&:
+colon
+.It \&;
+semicolon
+.It \&)
+right parenthesis
+.It \&]
+right bracket
+.It \&?
+question mark
+.It \&!
+exclamation mark
+.El
+.Pp
+Note that even a period preceded by a backslash
+.Pq Sq \e.\&
+gets this special handling; use
+.Sq \e&.
+to prevent that.
+.Pp
+Many in-line macros interrupt their scope when they encounter
+delimiters, and resume their scope when more arguments follow that
+are not delimiters.
+For example,
+.Pp
+.D1 Pf \. \&Fl "a ( b | c \e*(Ba d ) e"
+.Pp
+renders as:
+.Pp
+.D1 Fl a ( b | c \*(Ba d ) e
+.Pp
+This applies to both opening and closing delimiters,
+and also to the middle delimiter:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It \&|
+vertical bar
+.El
+.Pp
+As a special case, the predefined string \e*(Ba is handled and rendered
+in the same way as a plain
+.Sq \&|
+character.
+Using this predefined string is not recommended in new manuals.
+.Ss Font handling
+In
+.Nm
+documents, usage of semantic markup is recommended in order to have
+proper fonts automatically selected; only when no fitting semantic markup
+is available, consider falling back to
+.Sx Physical markup
+macros.
+Whenever any
+.Nm
+macro switches the
+.Xr roff 5
+font mode, it will automatically restore the previous font when exiting
+its scope.
+Manually switching the font using the
+.Xr 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
+.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.
+Any other number of arguments is replaced by the current date,
+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
+.Sx \&Pa
+does not format its arguments when used in the FILES section under
+certain list types.
+.It
+.Sx \&Ta
+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]
+.It
+.Sq \ef
+.Pq font face
+and
+.Sq \ef
+.Pq font family face
+.Sx Text Decoration
+escapes behave irregularly when specified within line-macro scopes.
+.It
+Negative scaling units return to prior lines.
+Instead, mandoc truncates them to zero.
+.El
+.Pp
+The following features are unimplemented in mandoc:
+.Pp
+.Bl -dash -compact
+.It
+.Sx \&Bd
+.Fl file Ar file .
+.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.
+.It
+The
+.Sq \ef
+scaling unit is accepted by mandoc, but rendered as the default unit.
+.It
+In quoted literals, groff allows pairwise double-quotes to produce a
+standalone double-quote in formatted output.
+This is not supported by mandoc.
+.El
+.Sh SEE ALSO
+.Xr man 1 ,
+.Xr mandoc 1 ,
+.Xr eqn 5 ,
+.Xr man 5 ,
+.Xr mandoc_char 5 ,
+.Xr roff 5 ,
+.Xr tbl 5
+.Sh HISTORY
+The
+.Nm
+language first appeared as a troff macro package in
+.Bx 4.4 .
+It was later significantly updated by Werner Lemberg and Ruslan Ermilov
+in groff-1.17.
+The standalone implementation that is part of the
+.Xr mandoc 1
+utility written by Kristaps Dzonsons appeared in
+.Ox 4.6 .
+in July, 2014.
+.Sh AUTHORS
+The
+.Nm
+reference was written by
+.An Kristaps Dzonsons ,
+.Mt kristaps@bsd.lv .
--- /dev/null	Fri Jul 18 08:34:42 2014
+++ new/usr/src/man/man5/tbl.5	Fri Jul 18 08:34:42 2014
@@ -0,0 +1,349 @@
+.\"
+.\" 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.
+.\"
+.\"
+.\" Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+.\" Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+.\"
+.Dd Sep 3, 2011
+.Dt TBL 5
+.Os
+.Sh NAME
+.Nm tbl
+.Nd tbl language reference for mandoc
+.Sh DESCRIPTION
+The
+.Nm tbl
+language is a table-formatting language.
+It is used within
+.Xr mdoc 5
+and
+.Xr man 5
+.Ux
+manual pages.
+This manual describes the subset of the
+.Nm
+language accepted by the
+.Xr mandoc 1
+utility.
+.Pp
+Tables within
+.Xr mdoc 5
+or
+.Xr man 5
+are enclosed by the
+.Sq TS
+and
+.Sq TE
+macro tags, whose precise syntax is documented in
+.Xr 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
+the cells.
+.Bd -literal -offset indent
+\&.TS
+tab(:) box;
+c5 c5 c5.
+1:2:3
+4:5:6
+\&.TE
+.Ed
+.Pp
+When formatted, the following output is produced:
+.Bd -filled -offset indent -compact
+.TS
+tab(:) box;
+c5 c5 c5.
+1:2:3
+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
+macros.
+A table consists of an optional single line of table
+.Sx Options
+terminated by a semicolon, followed by one or more lines of
+.Sx Layout
+specifications terminated by a period, then
+.Sx Data .
+All input must be 7-bit ASCII.
+Example:
+.Bd -literal -offset indent
+\&.TS
+box tab(:);
+c | c
+| c | c.
+1:2
+3:4
+\&.TE
+.Ed
+.Pp
+Table data is
+.Em pre-processed ,
+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 mdoc 5 ,
+and
+.Xr man 5
+macros such as
+.Bd -literal -offset indent
+\&.TS
+tab(:);
+c c c.
+1:2:3
+\&.Ao
+3:2:1
+\&.Ac
+\&.TE
+.Ed
+.Pp
+in the case of
+.Xr mdoc 5
+or
+.Bd -literal -offset indent
+\&.TS
+tab(:);
+c c c.
+\&.ds ab 2
+1:\e*(ab:3
+\&.I
+3:2:1
+\&.TE
+.Ed
+.Pp
+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.
+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.
+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 box
+Draw a single-line box around the table.
+This may also be invoked with
+.Cm frame .
+.It Cm doublebox
+Draw a double-line box around the table.
+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 linesize
+Accepts a natural number (all digits).
+This option is not supported by
+.Xr mandoc 1 .
+.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.
+.It Cm nospaces
+This option is not supported by
+.Xr mandoc 1 .
+.El
+.Ss Layout
+The table layout follows
+.Sx Options
+or a
+.Sq \&T&
+macro invocation.
+Layout specifies how data rows are displayed on output.
+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
+.It Cm c
+Centre a literal string within its column.
+.It Cm r
+Right-justify a literal string within its column.
+.It Cm l
+Left-justify a literal string within its column.
+.It Cm n
+Justify a number around its last decimal point.
+If the decimal point is not found on the number, it's assumed to trail
+the number.
+.It Cm s
+Horizontally span columns from the last
+.No non- Ns Cm s
+data cell.
+It is an error if spanning columns follow a
+.Cm \-
+or
+.Cm \(ba
+cell, or come first.
+This option is not supported by
+.Xr mandoc 1 .
+.It Cm a
+Left-justify a literal string and pad with one space.
+.It Cm ^
+Vertically span rows from the last
+.No non- Ns Cm ^
+data cell.
+It is an error to invoke a vertical span on the first layout row.
+Unlike a horizontal spanner, you must specify an empty cell (if it not
+empty, the data is discarded) in the corresponding data cell.
+.It Cm \-
+Replace the data cell (its contents will be lost) with a single
+horizontal line.
+This may also be invoked with
+.Cm _ .
+.It Cm =
+Replace the data cell (its contents will be lost) with a double
+horizontal line.
+.It Cm \(ba
+Emit a vertical bar instead of data.
+.It Cm \(ba\(ba
+Emit a double-vertical bar instead of data.
+.El
+.Pp
+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 .
+.Pp
+For example, the following layout specifies a centre-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:
+.Pp
+.Dl c10 | l10 | n
+.Ss Data
+The data section follows the last layout row.
+By default, cells in a data section are delimited by a tab.
+This behaviour may be changed with the
+.Cm tab
+option.
+If
+.Cm _
+or
+.Cm =
+is specified, a single or double line, respectively, is drawn across the
+data field.
+If
+.Cm \e-
+or
+.Cm \e=
+is specified, a line is drawn within the data field (i.e. terminating
+within the cell and not draw to the border).
+If the last cell of a line is
+.Cm T{ ,
+all subsequent lines are included as part of the cell until
+.Cm T}
+is specified as its own data cell.
+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
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr man 5 ,
+.Xr mandoc_char 5 ,
+.Xr mdoc 5 ,
+.Xr roff 5
+.Rs
+.%A M. E. Lesk
+.%T Tbl\(emA Program to Format Tables
+.%D June 11, 1976
+.Re
+.Sh HISTORY
+The tbl utility, a preprocessor for troff, was originally written by M.
+E. Lesk at Bell Labs in 1975.
+The GNU reimplementation of tbl, part of the groff package, was released
+in 1990 by James Clark.
+A standalone tbl implementation was written by Kristaps Dzonsons in
+2010.
+This formed the basis of the implementation that is part of the
+.Xr mandoc 1
+utility.
+.Sh AUTHORS
+This
+.Nm
+reference was written by
+.An Kristaps Dzonsons ,
+.Mt kristaps@bsd.lv .
--- old/usr/src/man/man7d/cpqary3.7d	Fri Jul 18 08:34:42 2014
+++ new/usr/src/man/man7d/cpqary3.7d	Fri Jul 18 08:34:42 2014
@@ -13,9 +13,7 @@
 .\"
 .TH CPQARY3 7D Aug 26, 2013"
 .SH NAME
-.LP
 cpqary3 - provides disk and SCSI tape support for HP Smart Array controllers
-.LP
 .SH DESCRIPTION
 .LP
 The cpqary3 module provides low-level interface routines between the common
@@ -78,7 +76,6 @@
 .BR driver.conf (4),
 .BR sd (7D),
 .BR st (7D)
-.LP
 .SH NOTES
 .LP
 The Smart Array controllers supported by the current version of the
--- old/usr/src/man/man7d/nv_sata.7d	Fri Jul 18 08:34:42 2014
+++ new/usr/src/man/man7d/nv_sata.7d	Fri Jul 18 08:34:42 2014
@@ -4,7 +4,7 @@
 .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License").  You may not use this file except in compliance with the License.
 .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing.  See the License for the specific language governing permissions and limitations under the License.
 .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE.  If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH nv_sata 7D "25 Sep 2011"
+.TH NV_SATA 7D "Sep 25, 2011"
 .SH NAME
 nv_sata \- NVIDIA CK804/MCP04/MCP51/MCP55/MCP61 SATA controller driver
 .SH SYNOPSIS
@@ -16,7 +16,7 @@
 .SH DESCRIPTION
 .sp
 .LP
-The \fBnv_sata\fR driver is a SATA HBA driver that supports NVIDIA CK804/MCP04 
+The \fBnv_sata\fR driver is a SATA HBA driver that supports NVIDIA CK804/MCP04
 and MCP51/MCP55/MCP61 SATA HBA controllers. Note that while these controllers
 support standard SATA features including SATA-II drives, NCQ, hotplug and ATAPI
 drives, the driver currently does not support NCQ features.
--- old/usr/src/man/man7d/pcn.7d	Fri Jul 18 08:34:42 2014
+++ new/usr/src/man/man7d/pcn.7d	Fri Jul 18 08:34:42 2014
@@ -43,7 +43,7 @@
 .SH "APPLICATION PROGRAMMING INTERFACE"
 The \fBpcn\fR driver can be used as either a "style 1" or a "style 2" Data Link
 Service Provider\. Physical points of attachment (PPAs) are interpreted as the
-instance number of the \fBpcn\fR controller as assigned by the Illumos 
+instance number of the \fBpcn\fR controller as assigned by the
 operating environment\.
 .sp
 .LP
@@ -58,7 +58,7 @@
 .TP
 .ie t \(bu
 .el o
-Minimum SDU is 0\. 
+Minimum SDU is 0\.
 .RE
 .RS +4
 .TP
@@ -76,7 +76,7 @@
 .TP
 .ie t \(bu
 .el o
-SAP length is \-2\. The 6\-byte physical address is immediately followed by a 
+SAP length is \-2\. The 6\-byte physical address is immediately followed by a
 2\-byte SAP\.
 .RE
 .RS +4
@@ -89,7 +89,7 @@
 .TP
 .ie t \(bu
 .el o
-The broadcast address is the 6\-byte Ethernet broadcast address 
+The broadcast address is the 6\-byte Ethernet broadcast address
 (\fBff:ff:ff:ff:ff:ff\fR)\.
 .SH "CONFIGURATION"
 .sp
--- old/usr/src/man/man9f/ddi_dmae.9f	Fri Jul 18 08:34:42 2014
+++ new/usr/src/man/man9f/ddi_dmae.9f	Fri Jul 18 08:34:42 2014
@@ -133,7 +133,7 @@
 \fB\fIcookiep\fR\fR
 .ad
 .RS 12n
-A pointer to a \fBddi_dma_cookie\fR(9S) object, 
+A pointer to a \fBddi_dma_cookie\fR(9S) object,
 which contains the address and count.
 .RE
 
--- old/usr/src/man/man9f/rwlock.9f	Fri Jul 18 08:34:42 2014
+++ new/usr/src/man/man9f/rwlock.9f	Fri Jul 18 08:34:42 2014
@@ -140,7 +140,7 @@
 .sp
 .LP
 The \fBrw_enter()\fR function acquires the lock, and blocks if necessary. If
-\fIenter_type\fR is \fBRW_WRITER\fR, the caller blocks if any thread holds 
+\fIenter_type\fR is \fBRW_WRITER\fR, the caller blocks if any thread holds
 the lock. If \fIenter_type\fR is \fBRW_READER\fR, the caller blocks if there
 is a writer or a thread attempting to enter for writing. If \fIenter_type\fR
 is \fBRW_READER_STARVEWRITER\fR, the caller blocks only if there is a writer;
--- old/usr/src/man/man9f/scsi_hba_attach_setup.9f	Fri Jul 18 08:34:42 2014
+++ new/usr/src/man/man9f/scsi_hba_attach_setup.9f	Fri Jul 18 08:34:42 2014
@@ -259,7 +259,7 @@
 .SS "scsi_hba_detach(\|)"
 .sp
 .LP
-The \fBscsi_hba_detach()\fR function removes the reference to the DMA 
+The \fBscsi_hba_detach()\fR function removes the reference to the DMA
 attributes structure and the transport vector for the given instance of an HBA
 driver.
 .SH RETURN VALUES
--- old/usr/src/pkg/manifests/developer-build-onbld.mf	Fri Jul 18 08:34:42 2014
+++ new/usr/src/pkg/manifests/developer-build-onbld.mf	Fri Jul 18 08:34:42 2014
@@ -23,6 +23,7 @@
 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright 2010, Richard Lowe
 # Copyright 2012, Piotr Jasiukajtis
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
 
 set name=pkg.fmri value=pkg:/developer/build/onbld@$(PKGVERS)
@@ -159,6 +160,8 @@
 file path=opt/onbld/lib/python2.6/onbld/Checks/JStyle.pyc mode=0444
 file path=opt/onbld/lib/python2.6/onbld/Checks/Keywords.py mode=0444
 file path=opt/onbld/lib/python2.6/onbld/Checks/Keywords.pyc mode=0444
+file path=opt/onbld/lib/python2.6/onbld/Checks/ManLint.py mode=0444
+file path=opt/onbld/lib/python2.6/onbld/Checks/ManLint.pyc mode=0444
 file path=opt/onbld/lib/python2.6/onbld/Checks/Mapfile.py mode=0444
 file path=opt/onbld/lib/python2.6/onbld/Checks/Mapfile.pyc mode=0444
 file path=opt/onbld/lib/python2.6/onbld/Checks/ProcessCheck.py mode=0444
--- /dev/null	Fri Jul 18 08:34:42 2014
+++ new/usr/src/pkg/manifests/system-man.mf	Fri Jul 18 08:34:42 2014
@@ -0,0 +1,64 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+set name=pkg.fmri value=pkg:/system/man@$(PKGVERS)
+set name=pkg.description \
+    value="utilities for display and formatting of reference manual pages"
+set name=pkg.summary value="Reference Manual Pages Tools"
+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
+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
+file path=usr/share/man/man1m/catman.1m
+file path=usr/share/man/man5/eqn.5
+file path=usr/share/man/man5/man.5
+file path=usr/share/man/man5/mandoc_char.5
+file path=usr/share/man/man5/mandoc_roff.5
+file path=usr/share/man/man5/mdoc.5
+file path=usr/share/man/man5/tbl.5
+hardlink path=usr/bin/apropos target=../../usr/bin/man
+hardlink path=usr/bin/catman target=../../usr/bin/man
+hardlink path=usr/bin/whatis target=../../usr/bin/man
+hardlink path=usr/lib/makewhatis target=../../usr/bin/man
+license lic_CDDL license=lic_CDDL
+license usr/src/cmd/man/THIRDPARTYLICENSE \
+    license=usr/src/cmd/man/THIRDPARTYLICENSE
+license usr/src/cmd/mandoc/THIRDPARTYLICENSE \
+    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.
+# 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.
+#depend fmri=compress/bzip2 type=require
+#depend fmri=compress/gzip type=require
+# less is the default (per user environment) pager.  We really should just
+# import this into illumos-gate.
+depend fmri=text/less type=require
--- old/usr/src/pkg/manifests/text-doctools.mf	Fri Jul 18 08:34:42 2014
+++ new/usr/src/pkg/manifests/text-doctools.mf	Fri Jul 18 08:34:43 2014
@@ -22,8 +22,10 @@
 #
 # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
 
+# man moved to system/man
 set name=pkg.fmri value=pkg:/text/doctools@$(PKGVERS)
 set name=pkg.description \
     value="utilities and fonts for development, display, and production of documentation such as manual pages (nroff/troff)"
@@ -50,7 +52,6 @@
 dir path=usr/share/man/man1m
 dir path=usr/share/man/man5
 file path=usr/bin/addbib mode=0555
-file path=usr/bin/apropos mode=0555
 file path=usr/bin/checkeq mode=0555
 file path=usr/bin/checknr mode=0555
 file path=usr/bin/deroff mode=0555
@@ -228,8 +229,6 @@
 file path=usr/lib/font/devpost/charlib/~ group=lp mode=0444
 file path=usr/lib/font/devpost/fontmaps/post group=lp mode=0444
 file path=usr/lib/font/makedev group=lp mode=0555
-file path=usr/lib/getNAME mode=0555
-file path=usr/lib/makewhatis mode=0555
 file path=usr/lib/refer/hunt mode=0555
 file path=usr/lib/refer/inv mode=0555
 file path=usr/lib/refer/mkey mode=0555
@@ -294,7 +293,6 @@
 file path=usr/share/lib/tmac/v
 file path=usr/share/lib/tmac/vgrind
 file path=usr/share/man/man1/addbib.1
-file path=usr/share/man/man1/apropos.1
 file path=usr/share/man/man1/checknr.1
 file path=usr/share/man/man1/deroff.1
 file path=usr/share/man/man1/diffmk.1
@@ -301,7 +299,6 @@
 file path=usr/share/man/man1/eqn.1
 file path=usr/share/man/man1/indxbib.1
 file path=usr/share/man/man1/lookbib.1
-file path=usr/share/man/man1/man.1
 file path=usr/share/man/man1/nroff.1
 file path=usr/share/man/man1/refer.1
 file path=usr/share/man/man1/roffbib.1
@@ -311,18 +308,11 @@
 file path=usr/share/man/man1/troff.1
 file path=usr/share/man/man1/ul.1
 file path=usr/share/man/man1/vgrind.1
-file path=usr/share/man/man1/whatis.1
-file path=usr/share/man/man1m/catman.1m
 file path=usr/share/man/man5/eqnchar.5
-file path=usr/share/man/man5/man.5
-file path=usr/share/man/man5/mansun.5
 file path=usr/share/man/man5/me.5
 file path=usr/share/man/man5/mm.5
 file path=usr/share/man/man5/ms.5
 file path=usr/share/man/man5/vgrindefs.5
-hardlink path=usr/bin/catman target=../../usr/bin/apropos
-hardlink path=usr/bin/man target=../../usr/bin/apropos
-hardlink path=usr/bin/whatis target=../../usr/bin/apropos
 hardlink path=usr/lib/font/devpost/charlib/~= target=./~
 hardlink path=usr/share/lib/nterm/tab.300s \
     target=../../../../usr/share/lib/nterm/tab.300S
@@ -340,8 +330,6 @@
     license=usr/src/cmd/checknr/THIRDPARTYLICENSE
 license usr/src/cmd/eqn/THIRDPARTYLICENSE \
     license=usr/src/cmd/eqn/THIRDPARTYLICENSE
-license usr/src/cmd/man/src/THIRDPARTYLICENSE \
-    license=usr/src/cmd/man/src/THIRDPARTYLICENSE
 license usr/src/cmd/refer/THIRDPARTYLICENSE \
     license=usr/src/cmd/refer/THIRDPARTYLICENSE
 license usr/src/cmd/soelim/THIRDPARTYLICENSE \
@@ -353,14 +341,6 @@
 license usr/src/cmd/vgrind/THIRDPARTYLICENSE \
     license=usr/src/cmd/vgrind/THIRDPARTYLICENSE
 link path=usr/lib/tmac target=../share/lib/tmac
-link path=usr/man target=./share/man
 link path=usr/share/man/man1/checkeq.1 target=eqn.1
 link path=usr/share/man/man1/neqn.1 target=eqn.1
-# lp is used by man -t and -r
-depend fmri=print/lp/print-client-commands type=require
-# awk is used by catman (via makewhatis)
-depend fmri=system/extended-system-utilities type=require
-# gpic is used as a preprocessor by the apropos command
-depend fmri=text/groff type=require
-# less is the default (per user environment) pager
-depend fmri=text/less type=require
+depend fmri=system/man type=require
--- old/usr/src/tools/Makefile	Fri Jul 18 08:34:43 2014
+++ new/usr/src/tools/Makefile	Fri Jul 18 08:34:43 2014
@@ -53,7 +53,8 @@
 #  special versions of commands for use only in build
 #
 UNSHIPPED_SUBDIRS = \
-	elfsign
+	elfsign \
+	mandoc
 
 sparc_SUBDIRS= \
 	chk4ubin \
--- /dev/null	Fri Jul 18 08:34:43 2014
+++ new/usr/src/tools/mandoc/Makefile	Fri Jul 18 08:34:43 2014
@@ -0,0 +1,51 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+#
+
+
+PROG=		mandoc
+
+CMDDIR=	$(SRC)/cmd/mandoc
+
+include ../Makefile.tools
+
+include $(SRC)/cmd/mandoc/Makefile.common
+
+SRCS=	$(mandoc_OBJS:%.o=$(CMDDIR)/%.c)
+CLEANFILES=	$(PROG) $(OBJS)
+OBJS= $(mandoc_OBJS)
+PROG= mandoc
+
+.KEEP_STATE:
+
+install: all .WAIT $(ROOTONBLDMACHPROG)
+
+$(PROG):	$(OBJS)
+		$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+		$(POST_PROCESS)
+
+all:		$(PROG)
+
+
+%.o:		$(CMDDIR)/%.c
+		$(COMPILE.c) -o $@ $<
+
+%.o:		$(LIBDIR)/%.c
+		$(COMPILE.c) -o $@ $<
+
+clean:
+		$(RM) $(CLEANFILES)
+
+include ../Makefile.targ
+
--- old/usr/src/tools/onbld/Checks/Makefile	Fri Jul 18 08:34:43 2014
+++ new/usr/src/tools/onbld/Checks/Makefile	Fri Jul 18 08:34:43 2014
@@ -24,6 +24,7 @@
 #
 
 # Copyright 2010, Richard Lowe
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 
 include $(SRC)/Makefile.master
 include ../../Makefile.tools
@@ -38,6 +39,7 @@
 	HdrChk.py	\
 	JStyle.py	\
 	Keywords.py	\
+	ManLint.py	\
 	Mapfile.py	\
 	ProcessCheck.py \
 	__init__.py
--- /dev/null	Fri Jul 18 08:34:43 2014
+++ new/usr/src/tools/onbld/Checks/ManLint.py	Fri Jul 18 08:34:43 2014
@@ -0,0 +1,57 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+#
+# ManLint, wrap the mandoc lint tool in a pythonic API
+#
+
+import sys
+from onbld.Checks.ProcessCheck import processcheck
+
+def manlint(fh, filename=None, output=sys.stderr, **opts):
+	opttrans = { 'picky': None }
+
+	for x in filter(lambda x: x not in opttrans, opts):
+		raise TypeError('mandoc() got an unexpected keyword '
+				'argument %s' % x)
+
+	options = [opttrans[x] for x in opts if opts[x] and opttrans[x]]
+	options.append('-Tlint')
+
+	if not filename:
+		filename = fh.name
+
+	ret, tmpfile = processcheck('mandoc', options, fh, output)
+
+	if tmpfile:
+		for line in tmpfile:
+			line = line.replace('<stdin>', filename)
+			output.write(line)
+
+		tmpfile.close()
+	return ret
--- old/usr/src/tools/onbld/Checks/__init__.py	Fri Jul 18 08:34:43 2014
+++ new/usr/src/tools/onbld/Checks/__init__.py	Fri Jul 18 08:34:43 2014
@@ -26,6 +26,7 @@
 #
 
 # Copyright 2010, Richard Lowe
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 
 #
 # The 'checks' package contains various checks that may be run
@@ -39,4 +40,5 @@
 	'HdrChk',
 	'JStyle',
 	'Keywords',
+	'ManLint',
 	'Mapfile']
--- old/usr/src/tools/onbld/hgext/cdm.py	Fri Jul 18 08:34:43 2014
+++ new/usr/src/tools/onbld/hgext/cdm.py	Fri Jul 18 08:34:43 2014
@@ -16,6 +16,7 @@
 #
 # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright 2008, 2011 Richard Lowe
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
 
 '''OpenSolaris extensions to Mercurial
@@ -104,7 +105,7 @@
 from onbld.Scm.WorkSpace import WorkSpace, WorkList
 from onbld.Scm.Backup import CdmBackup
 from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk
-from onbld.Checks import JStyle, Keywords, Mapfile
+from onbld.Checks import JStyle, Keywords, ManLint, Mapfile
 
 
 def yes_no(ui, msg, default):
@@ -390,6 +391,40 @@
     return ret
 
 
+def cdm_manlintchk(ui, repo, *args, **opts):
+    '''check for mandoc lint
+
+    Check for man page formatting errors.
+
+    Files can be excluded from this check using the manlint.NOT
+    file.  See NOT Files in the extension documentation ('hg help
+    cdm').
+    '''
+
+    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
+    exclude = not_check(repo, 'manlint')
+    ret = 0
+
+    # Man pages are identified as having a suffix starting with a digit.
+    ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
+
+    ui.write('Man format check:\n')
+
+    for f, e in filelist:
+        if e and e.is_removed():
+            continue
+        elif (not ManfileRE.match(f)):
+            continue
+        elif (e or opts.get('honour_nots')) and exclude(f):
+            ui.status('Skipping %s...\n' % f)
+            continue
+
+        fh = open(f, 'r')
+        ret |= ManLint.manlint(fh, output=ui, picky=True)
+        fh.close()
+    return ret
+
+
 def cdm_mapfilechk(ui, repo, *args, **opts):
     '''check for a valid mapfile header block in active files
 
@@ -794,6 +829,7 @@
       - cstyle     (C source style)
       - hdrchk     (C header style)
       - jstyle     (java source style)
+      - manlint    (man page formatting)
       - mapfilechk (link-editor mapfiles)
       - permchk    (file permissions)
       - keywords   (SCCS keywords)
@@ -806,6 +842,7 @@
         cdm_cstyle,
         cdm_hdrchk,
         cdm_jstyle,
+        cmd_manlintchk,
         cdm_mapfilechk,
         cdm_permchk,
         cdm_keywords]
@@ -828,6 +865,7 @@
       - hdrchk     (C header style)
       - jstyle     (java source style)
       - keywords   (SCCS keywords)
+      - manlint    (man page formatting)
       - mapfilechk (link-editor mapfiles)
       - permchk    (file permissions)
       - tagchk     (addition/modification of tags)
@@ -848,6 +886,7 @@
         cdm_cstyle,
         cdm_hdrchk,
         cdm_jstyle,
+        cdm_manlintchk,
         cdm_mapfilechk,
         cdm_permchk,
         cdm_keywords,
@@ -1431,6 +1470,8 @@
                                 ('m', 'modified', None, 'show modified files'),
                                 ('r', 'removed', None, 'show removed files')],
                     'hg list [-amrRu] [-p PARENT]'),
+    'manlint': (cdm_manlintchk, [('p', 'parent', '', 'parent workspace')],
+                'hg manlint [-p PARENT]'),
     'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')],
                 'hg mapfilechk [-p PARENT]'),
     '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')],
--- old/usr/src/tools/scripts/git-pbchk.py	Fri Jul 18 08:34:43 2014
+++ new/usr/src/tools/scripts/git-pbchk.py	Fri Jul 18 08:34:43 2014
@@ -17,6 +17,7 @@
 #
 # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright 2008, 2012 Richard Lowe
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
 
 import getopt
@@ -48,7 +49,7 @@
 sys.path.insert(2, os.path.join(os.path.dirname(__file__), ".."))
 
 from onbld.Checks import Comments, Copyright, CStyle, HdrChk
-from onbld.Checks import JStyle, Keywords, Mapfile
+from onbld.Checks import JStyle, Keywords, ManLint, Mapfile
 
 
 class GitError(Exception):
@@ -282,6 +283,16 @@
     return ret
 
 
+def manlint(root, parent, flist, output):
+    ret = 0
+    output.write("Man page format:\n")
+    ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
+    for f in flist(lambda x: ManfileRE.match(x)):
+        fh = open(f, 'r')
+        ret |= ManLint.manlint(fh, output=output, picky=True)
+	fh.close()
+    return ret
+
 def keywords(root, parent, flist, output):
     ret = 0
     output.write("SCCS Keywords:\n")
@@ -323,6 +334,7 @@
             hdrchk,
             jstyle,
             keywords,
+	    manlint,
             mapfilechk]
     run_checks(root, parent, cmds, paths)
 
@@ -334,6 +346,7 @@
             hdrchk,
             jstyle,
             keywords,
+	    manlint,
             mapfilechk]
     run_checks(root, parent, cmds)
 
